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
+7 -41
View File
@@ -21,7 +21,7 @@ let
# Single npm deps fetch from the workspace root lockfile.
# All workspace packages share this derivation.
npmDepsHash = "sha256-BfTSh6J2VZ/07tq2DYnKgUViZCgRhW1sC2uj18H65SE=";
npmDepsHash = "sha256-cY+gM1FnTBjmld/uqt7RsqRtW9uQGs8LGokCcxu7bjQ=";
npmDeps = pkgs.fetchNpmDeps {
inherit src;
@@ -53,7 +53,7 @@ in
{
folder, # repo-relative folder with package.json, e.g. "ui-tui"
attr, # flake package attr, e.g. "tui"
...
pname, # e.g. "hermes-tui"
}:
let
# No sourceRoot — the workspace root (with the single package-lock.json)
@@ -152,12 +152,14 @@ in
fi
# Check if lockfile changed (either from the npm i above or from an
# external edit). Runs npm ci if so.
# external edit). Runs npm ci + fix-lockfiles if so.
LOCK_STAMP="$STAMP_DIR/root-lockfile"
LOCK_STAMP_VALUE=$(sha256sum "$REPO_ROOT/package-lock.json" 2>/dev/null | awk '{print $1}')
if [ ! -f "$LOCK_STAMP" ] || [ "$(cat "$LOCK_STAMP")" != "$LOCK_STAMP_VALUE" ]; then
echo "npm: package-lock.json changed, running npm ci..."
( cd "$REPO_ROOT" && CI=true ${pkgs.lib.getExe' nodejs "npm"} ci --silent --no-fund --no-audit 2>/dev/null )
echo "npm: updating nix hash..."
${fixLockfilesExe} || echo "npm: warning: fix-lockfiles failed, run it manually" >&2
mkdir -p "$STAMP_DIR"
echo "$LOCK_STAMP_VALUE" > "$LOCK_STAMP"
fi
@@ -231,45 +233,9 @@ in
OLD_HASH=$(grep -oE 'npmDepsHash = "sha256-[^"]+"' "$LIB_FILE" | head -1 \
| sed -E 's/npmDepsHash = "(.*)"/\1/')
# prefetch-npm-deps says the hash already matches but it only hashes the
# lockfile *contents* and can disagree with fetchNpmDeps + npmConfigHook,
# which validate the full source lockfile against the realized deps cache.
# Trusting prefetch alone produced false "ok" results while the actual
# build was broken (e.g. lockfile engines/os/cpu fields the pinned nixpkgs
# strips from the deps cache, tripping npmConfigHook). So when prefetch
# claims the hash is current, confirm with a real consumer build before
# believing it.
if [ "$NEW_HASH" = "$OLD_HASH" ]; then
if VERIFY_OUT=$(nix build ".#${attr}" --no-link --print-build-logs 2>&1); then
echo "ok"
if [ -n "''${GITHUB_OUTPUT:-}" ]; then
{ echo "stale=false"; echo "changed=false"; } >> "$GITHUB_OUTPUT"
fi
exit 0
fi
# Build failed despite a matching hash. A fixed-output 'got:' means
# prefetch genuinely disagreed with fetchNpmDeps adopt the real hash
# and fall through to the stale-handling path below.
CORRECT_HASH=$(echo "$VERIFY_OUT" | awk '/got:/ {print $2; exit}')
if [ -n "$CORRECT_HASH" ]; then
echo "prefetch-npm-deps reported current ($OLD_HASH) but fetchNpmDeps wants $CORRECT_HASH" >&2
NEW_HASH="$CORRECT_HASH"
elif echo "$VERIFY_OUT" | grep -qE "throttled|HTTP error 418|substituter .* is disabled|some outputs of .* are not valid"; then
echo "skipped (transient cache failure see primary nix build for real status)" >&2
echo "$VERIFY_OUT" | tail -8 >&2
exit 0
else
# Not a stale-hash problem surface it honestly instead of "ok".
echo "::error::nix build .#${attr} failed and it is NOT a stale npmDepsHash (no 'got:' hash in output)." >&2
echo "The committed lockfile may be incompatible with the pinned nixpkgs" >&2
echo "(e.g. engines/os/cpu fields that prefetch-npm-deps strips from the" >&2
echo "deps cache, tripping npmConfigHook). fix-lockfiles cannot repair this." >&2
echo "$VERIFY_OUT" | tail -40 >&2
if [ -n "''${GITHUB_OUTPUT:-}" ]; then
{ echo "stale=false"; echo "changed=false"; } >> "$GITHUB_OUTPUT"
fi
exit 1
fi
echo "ok"
exit 0
fi
HASH_LINE=$(grep -n 'npmDepsHash = "sha256-' "$LIB_FILE" | head -1 | cut -d: -f1)
+3 -12
View File
@@ -49,12 +49,6 @@
configMergeScript = pkgs.callPackage ./configMergeScript.nix { };
# config.yaml mode: group-writable (0660) when interactive users share this
# HERMES_HOME via addToSystemPackages, so they can save settings through the
# CLI/TUI without hitting EACCES; otherwise group-read-only (0640). Secrets
# (.env) stay 0640 regardless — see below.
configYamlMode = if cfg.addToSystemPackages then "0660" else "0640";
# Generate .env from non-secret environment attrset
envFileContent = lib.concatStringsSep "\n" (
lib.mapAttrsToList (k: v: "${k}=${v}") cfg.environment
@@ -734,8 +728,7 @@
chmod 0750 ${cfg.stateDir}/home
# Create subdirs, set setgid + group-writable, migrate existing files.
# Nix-managed .env/.managed stay 0640/0644; config.yaml uses
# configYamlMode (0660 under addToSystemPackages, else 0640).
# Nix-managed files (config.yaml, .env, .managed) stay 0640/0644.
find ${cfg.stateDir}/.hermes -maxdepth 1 \
\( -name "*.db" -o -name "*.db-wal" -o -name "*.db-shm" -o -name "SOUL.md" \) \
-exec chmod g+rw {} + 2>/dev/null || true
@@ -750,14 +743,12 @@
# Merge Nix settings into existing config.yaml.
# Preserves user-added keys (skills, streaming, etc.); Nix keys win.
# If configFile is user-provided (not generated), overwrite instead of merge.
# Mode is configYamlMode (0660 under addToSystemPackages so interactive
# hermes-group users can save settings via the CLI/TUI, else 0640).
${if cfg.configFile != null then ''
install -o ${cfg.user} -g ${cfg.group} -m ${configYamlMode} -D ${configFile} ${cfg.stateDir}/.hermes/config.yaml
install -o ${cfg.user} -g ${cfg.group} -m 0640 -D ${configFile} ${cfg.stateDir}/.hermes/config.yaml
'' else ''
${configMergeScript} ${generatedConfigFile} ${cfg.stateDir}/.hermes/config.yaml
chown ${cfg.user}:${cfg.group} ${cfg.stateDir}/.hermes/config.yaml
chmod ${configYamlMode} ${cfg.stateDir}/.hermes/config.yaml
chmod 0640 ${cfg.stateDir}/.hermes/config.yaml
''}
# Managed mode marker (so interactive shells also detect NixOS management)