opentui(phase3): launcher integration — HERMES_TUI_ENGINE dual-engine

hermes --tui launches the native OpenTUI engine (Bun) when
HERMES_TUI_ENGINE=opentui (env) or display.tui_engine=opentui (config);
Ink stays the default and the shipping path is untouched.

- _resolve_tui_engine() (env > config > ink); refuses opentui on
  Windows/Termux (no Bun) -> falls back to ink with a notice.
- _make_opentui_argv() -> [bun, src/entry.real.tsx] (no build step).
- _bun_bin() with HERMES_BUN override.
- Branch at top of _make_tui_argv BEFORE _ensure_tui_node (Bun-only host
  must not bootstrap Node).
- Gate _launch_tui NODE_OPTIONS/--max-old-space-size on engine==ink (Bun
  is JSC; the V8 flag errors/ignores).

Verified end-to-end via tmux: real hermes --tui -> Bun -> OpenTUI ->
real Python gateway streamed a real reply. No-flag default still ink.
This commit is contained in:
alt-glitch 2026-06-08 11:11:54 +00:00
parent 24f74eb888
commit 2bd9c9b881
741 changed files with 17733 additions and 79889 deletions

View file

@ -20,9 +20,6 @@ import { cn, themedBody } from "@/lib/utils";
interface Props {
/** The toolset whose backends are being configured. */
toolset: ToolsetInfo;
/** Optional profile to scope config reads/writes to (Skills page profile
* selector). Omitted = the dashboard process's own profile. */
profile?: string;
onClose: () => void;
/** Called after a toggle/provider/key change so the parent grid refreshes. */
onChanged: () => void;
@ -34,7 +31,7 @@ interface Props {
* the toolset on/off, pick a provider, enter API keys, and run a provider's
* post-setup install hook (npm/pip/binary) with a live log tail.
*/
export function ToolsetConfigDrawer({ toolset, profile, onClose, onChanged }: Props) {
export function ToolsetConfigDrawer({ toolset, onClose, onChanged }: Props) {
const { toast, showToast } = useToast();
const [config, setConfig] = useState<ToolsetConfig | null>(null);
const [loading, setLoading] = useState(true);
@ -63,7 +60,7 @@ export function ToolsetConfigDrawer({ toolset, profile, onClose, onChanged }: Pr
// react-hooks/set-state-in-effect — setState only fires inside the
// async .then/.catch/.finally callbacks.
return api
.getToolsetConfig(toolset.name, profile)
.getToolsetConfig(toolset.name)
.then((cfg) => {
setConfig(cfg);
setActiveProvider(cfg.active_provider);
@ -75,7 +72,7 @@ export function ToolsetConfigDrawer({ toolset, profile, onClose, onChanged }: Pr
})
.catch(() => showToast("Failed to load toolset config", "error"))
.finally(() => setLoading(false));
}, [toolset.name, profile, showToast]);
}, [toolset.name, showToast]);
useEffect(() => {
void loadConfig();
@ -124,7 +121,7 @@ export function ToolsetConfigDrawer({ toolset, profile, onClose, onChanged }: Pr
const handleToggle = async (next: boolean) => {
setToggling(true);
try {
await api.toggleToolset(toolset.name, next, profile);
await api.toggleToolset(toolset.name, next);
setEnabled(next);
showToast(
`${toolset.label || toolset.name} ${next ? "enabled" : "disabled"}`,
@ -141,7 +138,7 @@ export function ToolsetConfigDrawer({ toolset, profile, onClose, onChanged }: Pr
const handleSelectProvider = async (provider: ToolsetProvider) => {
setSelecting(provider.name);
try {
await api.selectToolsetProvider(toolset.name, provider.name, profile);
await api.selectToolsetProvider(toolset.name, provider.name);
setActiveProvider(provider.name);
showToast(`Provider set to ${provider.name}`, "success");
onChanged();
@ -167,7 +164,7 @@ export function ToolsetConfigDrawer({ toolset, profile, onClose, onChanged }: Pr
}
setSavingProvider(provider.name);
try {
const res = await api.saveToolsetEnv(toolset.name, env, profile);
const res = await api.saveToolsetEnv(toolset.name, env);
setIsSet((prev) => ({ ...prev, ...res.is_set }));
// Clear saved drafts so the inputs reset to the "saved" placeholder.
setDrafts((prev) => {
@ -198,7 +195,7 @@ export function ToolsetConfigDrawer({ toolset, profile, onClose, onChanged }: Pr
setPostSetupLog([]);
setPostSetupKey(provider.post_setup);
try {
await api.runToolsetPostSetup(toolset.name, provider.post_setup, profile);
await api.runToolsetPostSetup(toolset.name, provider.post_setup);
// Bump the trigger so the poll effect (re)starts tailing the log.
setPostSetupTrigger((n) => n + 1);
} catch (e) {