/** * HermesHarness — CodingHarness implementation for Hermes Agent (hermes). * * Hermes is a Python-based coding agent (Nous Research) whose TUI is a Node * front-end over a Python backend. Its native session store is SQLite — * there is no live transcript to parse — so integration rides an env-gated * Python PLUGIN installed permanently into the user's real `~/.hermes` * (shell hooks do not fire in TUI mode; spike-verified 2026-07-16 against * upstream 2ea39dae — see docs/harness-specific-notes/hermes-agent.md). * The plugin writes two per-agent files: * - a JSONL session MIRROR (our own format) = `env.sessionFilePath`, * parsed by `analyzeHermesSessionFile()`; * - a line-structured lifecycle-end SENTINEL whose entries carry * `{completed, interrupted}`, truncated (re-armed) on `pre_api_request` * so a mid-turn steer interrupt never reads as terminal. * * Key characteristics: * - Pi-default prompt delivery (the TUI's bracketed-paste chip expands on * submit — spike-verified with 7.6KB prompts); no `combinesTextAndEnter`. * - NO `configProfile` (decided): a per-spawn HERMES_HOME synth would strip * the user's memories/skills/SOUL.md identity. A config-dir configured for * a hermes role gets the standard warn-and-ignore. * - Readiness: no disk artifact exists at input-ready time and a premature * Enter is silently swallowed, so `waitForReady()` polls capture-pane for * the TUI's literal status-bar marker `─ ready │` (~4.4s after launch). * - Exit codes are always 0 — launch failure surfaces as a ready timeout. * - No `providesToolStatusStream`: `currentTool` derives from the mirror's * parsed `toolCalls` via the normal poll loop. */ import type { CodingHarness, HarnessEnvironment, HarnessSpawnParams, SessionAnalysis } from "#src/harness/interface"; import type { TmuxManager } from "#src/tmux-manager"; import type { SubagentRecord } from "#src/types"; /** * Resolve hermes' TUI `dist/entry.js` path (install tree, not HERMES_HOME * config home). Order: `HERMES_TUI_DIR`, `hermes --version` Install directory, * then the common git-install layout under `~/.hermes/hermes-agent`. */ export declare function resolveHermesTuiEntryJs(home?: string): string | undefined; /** True when entry.js exists, is large enough, and passes `node --check`. */ export declare function isHermesTuiEntryHealthy(entryPath: string): boolean; /** * Under a cross-process flock: if the TUI bundle is unhealthy, trigger one * hermes TUI startup (its install path) and re-check. Warm path is lock + * health check only so parallel spawns stay concurrent after ensure. * * Exported for unit tests (injectable deps). */ export declare function ensureHermesTuiReady(home?: string, opts?: { entryPath?: string; /** Skip flock (tests / platforms without flock). */ skipFlock?: boolean; /** Override repair command runner. */ repair?: () => void; }): void; export declare class HermesHarness implements CodingHarness { readonly id = "hermes"; readonly roleDefinitionDelivery: "prompt-context"; /** Self-described widget identity (fallback row retained in harness-colors). */ readonly widgetIdentity: { colorHex: string; sigil: string; }; readonly completionSentinelKind = "plugin end sentinel"; /** * Default tmux stop combo (fallback row retained in guard-config). * Spike-verified: `C-c` interrupts a running turn and fires the interrupted * sentinel; `Escape` does nothing. */ readonly defaultStopKeyCombo = "C-c"; /** * The mirror lives at a deterministic per-agent path: inside the record's * owned runtime directory (new layout) or ambient $TMPDIR (legacy records). */ resolveSessionDir(record: SubagentRecord): string; resolveEndFilePath(agentId: string, runtimeDir?: string): string; resolveSessionFilePath(agentId: string, runtimeDir?: string): string; resolveOwnedTempPaths(agentId: string, runtimeDir?: string): string[]; /** * Prepare the hermes environment before launching: * 1. Lock-scoped TUI bundle ensure (serialize install/repair only — warm * parallel launches stay concurrent after the lock releases). * 2. Idempotently install the tmux-pilot plugin into the user's real * `~/.hermes/plugins/tmux-pilot/` (permanent, env-gated — inert for the * user's own hermes sessions; cleanup never removes it). * 3. Ensure the `plugins.enabled` entry in `~/.hermes/config.yaml` * (comment-preserving YAML round-trip, atomic write). * 4. Compute the per-agent mirror + end-sentinel paths. */ setupEnvironment(params: HarnessSpawnParams): Promise; /** * Build the hermes launch command: * `cd && env PI_TMUX_MIRROR_FILE=… PI_TMUX_END_FILE=… hermes --tui * --yolo [-m ] [--provider ]`. * * - `--yolo`: approval dialogs would wedge an unattended agent. * - model/provider map verbatim to `-m`/`--provider`; unset fields fall * through to the user's hermes config.yaml defaults. * - NO `--max-turns` (removed upstream; guard turns are tmux-pilot's job) * and NO `--worktree` (tmux-pilot owns worktrees). * - The `env` prefix covers the full pipeline; the plugin subprocess * inherits the vars through the TUI → backend chain (spike-verified). */ buildLaunchCommand(params: HarnessSpawnParams, env: HarnessEnvironment): string; /** * Wait for the hermes TUI's status bar to show the literal ready marker. * * No disk artifact exists at input-ready time (`on_session_start` fires * only after the first prompt is submitted — chicken-and-egg) and an Enter * sent before ready is silently swallowed, so the pane content IS the * readiness signal. Polls by stable window ID (name fallback when absent). * On success, `env.sessionFilePath` is set to the mirror path (known a * priori — no discovery step). */ waitForReady(_params: HarnessSpawnParams, env: HarnessEnvironment, agentId: string, tmux: TmuxManager, signal?: AbortSignal): Promise; readTurnCount(env: HarnessEnvironment): Promise; analyzeSession(filePath: string): SessionAnalysis; /** * Authoritative completion: true only when the end sentinel's LAST line * parses with `completed === true`. The plugin appends a * `{completed, interrupted}` line on every `on_session_end` and truncates * the file on `pre_api_request` (re-arm), so a mid-turn steer interrupt * (`completed: false, interrupted: true` as the last line) reads as NOT * complete, and an empty/absent/unset file reads as NOT complete. */ isComplete(env: HarnessEnvironment): boolean; /** * Remove the per-agent mirror + end-sentinel files ONLY. The plugin and * its `plugins.enabled` entry are permanent (env-gated, inert for the * user's own hermes sessions). */ cleanup(env: HarnessEnvironment): Promise; } //# sourceMappingURL=hermes-harness.d.ts.map