mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-14 14:12:44 +00:00
tests: pin ink engine in _make_tui_argv npm-bootstrap tests (post-merge semantic fix)
Main's rewritten test_tui_npm_install.py tests call _make_tui_argv expecting the Ink/npm flow unconditionally; with the dual-engine dispatch merged in, _resolve_tui_engine() auto-selects opentui whenever ui-opentui/dist is built in the repo, routing the call away from the path under test (first subprocess became 'node --version' instead of 'npm run build'). Pin the engine to ink via an autouse fixture, mirroring the existing pinning precedent in test_tui_resume_flow.py.
This commit is contained in:
parent
ab37440ce6
commit
e1067dbbe5
756 changed files with 79874 additions and 19585 deletions
115
web/src/contexts/ProfileProvider.tsx
Normal file
115
web/src/contexts/ProfileProvider.tsx
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import { useLocation, useSearchParams } from "react-router-dom";
|
||||
import { api, setManagementProfile } from "@/lib/api";
|
||||
import { ProfileContext } from "@/contexts/profile-context";
|
||||
|
||||
/**
|
||||
* Machine-level management-profile scope.
|
||||
*
|
||||
* One switcher (rendered in the sidebar) decides which profile every
|
||||
* management page reads/writes. React STATE is the source of truth; the
|
||||
* URL (`?profile=<name>`) is a synchronized projection of it so deep links
|
||||
* land scoped and refresh survives. The selection is mirrored into the api
|
||||
* module so `fetchJSON` transparently appends it to the profile-scoped
|
||||
* endpoint families. "" = the dashboard's own profile.
|
||||
*
|
||||
* Why state-first instead of URL-first: sidebar nav links are bare paths
|
||||
* (`/config`, `/skills`). A URL-derived scope would silently reset to the
|
||||
* dashboard's own profile on every nav click — the switcher would LOOK
|
||||
* global while normal navigation dropped the write target. With state as
|
||||
* truth, the effect below re-asserts `?profile=` onto the new location
|
||||
* after each navigation, so the scope survives nav and stays deep-linkable.
|
||||
*
|
||||
* This exists because "Set as active" on the Profiles page only flips the
|
||||
* sticky active_profile file (future CLI/gateway runs) — it cannot retarget
|
||||
* the running dashboard. The switcher is the dashboard's own, visible,
|
||||
* write-target selector.
|
||||
*/
|
||||
export function ProfileProvider({ children }: { children: ReactNode }) {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const { pathname } = useLocation();
|
||||
const [profiles, setProfiles] = useState<string[]>([]);
|
||||
const [currentProfile, setCurrentProfile] = useState("default");
|
||||
|
||||
// Initial value comes from the URL (deep link / refresh / unified-launch
|
||||
// preselect); afterwards state leads and the URL follows.
|
||||
const [profile, setProfileState] = useState(
|
||||
() => searchParams.get("profile") ?? "",
|
||||
);
|
||||
|
||||
// Mirror into the api module synchronously on every render where it
|
||||
// changed, so fetches fired by child effects in the same commit see it.
|
||||
setManagementProfile(profile);
|
||||
|
||||
// A profile param arriving via in-app navigation (e.g. the Profiles
|
||||
// page's "Manage skills & tools" linking to /skills?profile=X) must win
|
||||
// over current state — it's an explicit scope request.
|
||||
const urlProfile = searchParams.get("profile");
|
||||
useEffect(() => {
|
||||
if (urlProfile !== null && urlProfile !== profile) {
|
||||
setManagementProfile(urlProfile);
|
||||
setProfileState(urlProfile);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [urlProfile]);
|
||||
|
||||
// Re-assert ?profile= after navigations that dropped it (bare nav links).
|
||||
// Runs on every pathname/profile change; no-ops when already in sync.
|
||||
useEffect(() => {
|
||||
const inUrl = searchParams.get("profile") ?? "";
|
||||
if ((profile || "") === inUrl) return;
|
||||
setSearchParams(
|
||||
(prev) => {
|
||||
const next = new URLSearchParams(prev);
|
||||
if (profile) next.set("profile", profile);
|
||||
else next.delete("profile");
|
||||
return next;
|
||||
},
|
||||
{ replace: true },
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [pathname, profile]);
|
||||
|
||||
useEffect(() => {
|
||||
api
|
||||
.getProfiles()
|
||||
.then((res) => setProfiles(res.profiles.map((p) => p.name)))
|
||||
.catch(() => {});
|
||||
api
|
||||
.getActiveProfile()
|
||||
.then((info) => setCurrentProfile(info.current || "default"))
|
||||
.catch(() => {});
|
||||
}, []);
|
||||
|
||||
const setProfile = useCallback(
|
||||
(name: string) => {
|
||||
setManagementProfile(name);
|
||||
setProfileState(name);
|
||||
setSearchParams(
|
||||
(prev) => {
|
||||
const next = new URLSearchParams(prev);
|
||||
if (name) next.set("profile", name);
|
||||
else next.delete("profile");
|
||||
return next;
|
||||
},
|
||||
{ replace: true },
|
||||
);
|
||||
},
|
||||
[setSearchParams],
|
||||
);
|
||||
|
||||
const value = useMemo(
|
||||
() => ({ profile, currentProfile, profiles, setProfile }),
|
||||
[profile, currentProfile, profiles, setProfile],
|
||||
);
|
||||
|
||||
return (
|
||||
<ProfileContext.Provider value={value}>{children}</ProfileContext.Provider>
|
||||
);
|
||||
}
|
||||
19
web/src/contexts/profile-context.ts
Normal file
19
web/src/contexts/profile-context.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { createContext } from "react";
|
||||
|
||||
export interface ProfileContextValue {
|
||||
/** Profile every management surface reads/writes ("" = the dashboard
|
||||
* process's own profile). */
|
||||
profile: string;
|
||||
/** The profile the dashboard process itself runs under. */
|
||||
currentProfile: string;
|
||||
/** Known profile names (includes "default"). */
|
||||
profiles: string[];
|
||||
setProfile: (name: string) => void;
|
||||
}
|
||||
|
||||
export const ProfileContext = createContext<ProfileContextValue>({
|
||||
profile: "",
|
||||
currentProfile: "default",
|
||||
profiles: [],
|
||||
setProfile: () => {},
|
||||
});
|
||||
6
web/src/contexts/useProfileScope.ts
Normal file
6
web/src/contexts/useProfileScope.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { useContext } from "react";
|
||||
import { ProfileContext } from "@/contexts/profile-context";
|
||||
|
||||
export function useProfileScope() {
|
||||
return useContext(ProfileContext);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue