@agentproto/runtime 2.11.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/dist/catalog-models.d.ts +1 -1
- package/dist/catalog-models.mjs.map +1 -1
- package/dist/command-allowlist.d.ts +38 -1
- package/dist/command-allowlist.mjs +25 -1
- package/dist/command-allowlist.mjs.map +1 -1
- package/dist/config.d.ts +14 -1
- package/dist/config.mjs.map +1 -1
- package/dist/index.d.ts +454 -71
- package/dist/index.mjs +10866 -7792
- package/dist/index.mjs.map +1 -1
- package/dist/pr-provenance.d.ts +26 -1
- package/dist/pr-provenance.mjs +20 -3
- package/dist/pr-provenance.mjs.map +1 -1
- package/dist/providers-store.d.ts +1 -0
- package/dist/resume-strategies.mjs +38 -1
- package/dist/resume-strategies.mjs.map +1 -1
- package/dist/session-presence.mjs.map +1 -1
- package/dist/{spawn-defaults-DUnTfwVK.d.ts → spawn-defaults-D4W661ui.d.ts} +8 -0
- package/dist/tool-envelope.d.ts +1 -0
- package/dist/tool-envelope.mjs +3 -0
- package/dist/tool-envelope.mjs.map +1 -0
- package/package.json +42 -35
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config.ts","../src/session-presence.ts"],"names":["fs"],"mappings":"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","file":"session-presence.mjs","sourcesContent":["/**\n * `~/.agentproto/config.json` — single hand-editable JSON for the\n * agentproto control plane's defaults. Sits alongside the existing\n * surface files (workspaces.json, credentials.json, sessions.json):\n *\n * workspaces.json which directories are workspaces + which is active\n * credentials.json tunnel host bearer tokens (mode 0600)\n * sessions.json last-known snapshot of the registry (informational)\n * config.json daemon defaults: port, bind, allowed origins,\n * tunnel host, feature toggles\n *\n * Resolution order for every daemon knob is:\n * 1. CLI flag (e.g. --port)\n * 2. Env var (where one exists, e.g. AGENTPROTO_TOKEN)\n * 3. config.json\n * 4. Hardcoded default\n *\n * This means a user can call `agentproto config set daemon.port 18791`\n * once and never re-pass `--port 18791` to `serve install` etc. CLI\n * flags still win for one-off overrides.\n *\n * Schema is intentionally narrow + extensible — unknown keys are\n * preserved on save (deep-merge), so a newer CLI writing a new\n * field won't drop one an older CLI doesn't know about. No secrets\n * here; credentials stay in credentials.json (mode 0600).\n */\n\nimport { promises as fs } from \"node:fs\"\nimport { homedir } from \"node:os\"\nimport { dirname, join } from \"node:path\"\nimport type { SpawnDefaultsConfig } from \"./spawn-defaults.js\"\n\nexport const CONFIG_VERSION = 1 as const\n\nexport interface DaemonConfig {\n /** Absolute path to the workspace the daemon binds to at boot. */\n workspace?: string\n /** HTTP port. Default 18790. */\n port?: number\n /** Bind addr. Default 127.0.0.1. */\n bind?: string\n /** Trusted browser origins for mutating /sessions/* routes (in\n * addition to the hardcoded localhost defaults). */\n allowedOrigins?: string[]\n /** When true, the daemon does NOT auto-trust localhost-on-any-port.\n * Only origins explicitly listed in `allowedOrigins` are allowed.\n * Pair with a curated list (e.g. `[\"http://localhost:3000\"]`) for\n * hardened setups. Default false. */\n strictOrigins?: boolean\n /** Server label sent in tunnel hello frames. */\n label?: string\n /** Bearer token gating the gateway at boot (`AuthOptions` with\n * `mode: \"bearer\"`). Unlike `remote_enable`'s ephemeral quick-tunnel\n * token, this one lives in config.json and survives daemon restarts.\n * Set via `agentproto config set daemon.authToken <token>` (e.g.\n * `$(openssl rand -hex 32)`). Unset ⇒ the gateway boots with\n * `mode: \"none\"` — fully open on loopback, same as today. */\n authToken?: string\n /** Opt-in eager resume-on-boot (session-survivability plan §5\n * \"Opt-in vs automatic\", PR-4). When true, a daemon restart doesn't just\n * leave agent-cli sessions dead-but-lazy-resumable — the boot pass eagerly\n * re-spawns the eligible ones (those that died with\n * `endedReason: \"daemon-restart\"` and still pass `canResume`) IN PLACE,\n * restoring liveness without waiting for a prompt. Lazy resume-on-prompt is\n * always on regardless of this flag (it costs nothing until someone acts);\n * this flag only controls the *automatic* boot-time pass. Default false: a\n * box-wide restart should not silently relaunch every adapter (cost, spawn\n * storm, credential re-resolution), and default-off also keeps two daemons\n * sharing the workspace buckets from racing to resume the same rows. Set via\n * `agentproto config set daemon.resumeSessionsOnBoot true`. Surfaced in\n * `daemon_health` / `GET /health`. */\n resumeSessionsOnBoot?: boolean\n /** Idle agent-session reaper (PR-6). When set to a positive number of\n * milliseconds, the daemon periodically retires agent-cli sessions that have\n * been idle (not busy, not awaiting input, `status:\"running\"`) longer than\n * this: it SIGTERMs the adapter child to free the process and flips the row\n * to `killed`/`endedReason:\"idle-reaped\"` — dead-but-lazy-resumable (a later\n * prompt revives it) and, critically, excluded from eager resume-on-boot\n * (#638 gates on `endedReason === \"daemon-restart\"`), so enabling this makes\n * eager resume safe: only genuinely-recent work survives a restart. Default\n * OFF (unset / 0 / negative ⇒ the reaper never runs). Resolution order\n * mirrors the module docblock: `AGENTPROTO_IDLE_REAP_AFTER_MS` env > this\n * field > off. Set via `agentproto config set daemon.idleReapAfterMs\n * <ms>`. Surfaced in `daemon_health` / `GET /health`. */\n idleReapAfterMs?: number\n /** Crash-detect sweep interval in ms (crash-detect PR-1). Unlike\n * `idleReapAfterMs`, this is DEFAULT ON (non-destructive observability):\n * the daemon periodically probes every live agent-cli session's OS\n * process (`process.kill(pid, 0)`) and, when it's provably gone with no\n * exit event ever emitted for it, flips the row to\n * `error`/`endedReason:\"crashed\"` with a `lastError` string — surfacing a\n * death that would otherwise sit silently as `status:\"running\"` until the\n * next prompt's RPC throws (or forever, for a parked session with no next\n * prompt). Detects only; never restarts or notifies (later PRs). Set to a\n * non-positive value to disable the sweep entirely. Resolution order\n * mirrors `idleReapAfterMs`: `AGENTPROTO_CRASH_DETECT_INTERVAL_MS` env >\n * this field > the hardcoded default (30s). Set via `agentproto config\n * set daemon.crashDetectIntervalMs <ms>`. Surfaced in `daemon_health` /\n * `GET /health`. */\n crashDetectIntervalMs?: number\n /** Restart-scheduler sweep interval in ms (restart-scheduler PR-2). OFF by\n * default (unset / 0 / negative ⇒ the sweep never runs), unlike\n * `crashDetectIntervalMs` — a positive value here only arms the periodic\n * sweep that EXECUTES an already-scheduled restart; it never opts a\n * session in by itself (that's the per-session `agent_start.restartPolicy`\n * field). Resolution order mirrors `idleReapAfterMs`:\n * `AGENTPROTO_RESTART_SWEEP_INTERVAL_MS` env > this field > off. Set via\n * `agentproto config set daemon.restartSweepIntervalMs <ms>`. Surfaced in\n * `daemon_health` / `GET /health`. */\n restartSweepIntervalMs?: number\n /** Turn-liveness watchdog threshold in ms (turn-liveness-watchdog\n * chantier). Same shape as `crashDetectIntervalMs`: DEFAULT ON\n * (non-destructive observability). The daemon periodically sweeps every\n * BUSY agent-cli session and, for one that is mid-turn, NOT legitimately\n * `blockedOn` a subagent/command, and has had no adapter activity\n * (`lastActivityAt`) for longer than this threshold, stamps\n * `stalledSinceMs` on the descriptor and emits `session:stalled` —\n * surfacing a dead adapter stream (network drop, hung child — zero\n * frames mid-turn) that would otherwise sit indistinguishable from\n * healthy long work: `status:\"running\"`, `lastError:null`, no other\n * signal. Conservative by design: a session legitimately blocked on a\n * long tool call is NEVER a candidate, no matter how long it's silent —\n * see `stall-watchdog.ts`'s docblock for why. Detects only; never kills\n * or restarts (this is a later concern, if ever). Set to a non-positive\n * value to disable the sweep entirely. Resolution order mirrors\n * `idleReapAfterMs`: `AGENTPROTO_TURN_STALL_AFTER_MS` env > this field >\n * the hardcoded default (5 min). Set via `agentproto config set\n * daemon.turnStallAfterMs <ms>`. Surfaced in `daemon_health` /\n * `GET /health`. */\n turnStallAfterMs?: number\n}\n\nexport interface TunnelConfig {\n /** Cloud WS URL. When set + autoconnect=true, `agentproto serve`\n * bootstraps with `--connect <host>`. */\n host?: string\n /** apt_ daemon token to present at the tunnel upgrade. When set,\n * `agentproto serve` uses this BEFORE falling back to\n * credentials.json — handy in profiles where the token-per-host\n * mapping in credentials.json doesn't fit (e.g. host = tunnel URL\n * but credentials were minted against the api URL). */\n token?: string\n /** Whether `agentproto daemon start` connects the tunnel by\n * default. v0 only — implementer can ignore until daemon needs it. */\n autoconnect?: boolean\n /**\n * Opt into end-to-end encryption of the outbound `serve --connect` tunnel\n * (design: tunnel-e2e/v1). When true, the daemon negotiates a\n * token-authenticated ephemeral handshake with the host and wraps the tunnel\n * frames in an AEAD box, so even the trusted host loses plaintext visibility.\n * The handshake authenticates both ends against the shared `tunnel.token`, so\n * `token` MUST also be set. Fully backward-compatible: if the host doesn't\n * advertise e2e (an older host), the daemon falls back to today's plaintext\n * tunnel. Unset/false ⇒ plaintext, byte-identical to today. */\n e2e?: boolean\n}\n\nexport interface FeaturesConfig {\n /** Hint that PTY is desired — informational; the daemon still\n * detects node-pty's presence at runtime. */\n pty?: boolean\n /** Enable the local LLM Endpoint proxy sidecar (route registration,\n * MCP tools, child-process lifecycle). Default false — the endpoint is\n * an opt-in feature; when off, the `llm-endpoint` custom route is not\n * registered and the `llm_endpoint_*` MCP tools are not exposed. */\n llmEndpoint?: boolean\n}\n\n/**\n * Policy for `agentproto worktree new` (PLAN.md §1.4 — config carries\n * policy, never state; git itself is the authority for which worktrees\n * exist). This is the fix for the sprawl the plan measured: 31 linked\n * worktrees across 6 different parent directories, because there was no\n * `worktree new` verb and therefore no convention to converge on.\n */\n/**\n * How the daemon isolates a freshly-spawned agent session into its own git\n * worktree (`agent_start.worktree`):\n * - `\"always\"` — every depth-0 spawn is provisioned into a worktree,\n * whether or not the caller asked. A cwd that is not in\n * a git repo has nothing to isolate, so it spawns plain.\n * - `\"on-request\"` — isolate ONLY when the caller passes `worktree`. This\n * is the default and the back-compatible behaviour:\n * today's callers pass nothing and spawn exactly where\n * they asked.\n * - `\"never\"` — isolation is off; an explicit `worktree` field is\n * REJECTED (loud, not silently ignored) so a caller\n * never believes it got an isolated tree it didn't.\n */\nexport type WorktreeIsolationMode = \"always\" | \"on-request\" | \"never\"\n\nexport interface WorktreesConfig {\n /**\n * Absolute path new worktrees are created under. Layout:\n * `<root>/<repoName>/<slug>`. Resolution order (mirrors every other\n * knob in this file, see the module docblock): `--root` flag >\n * `AGENTPROTO_WORKTREES_ROOT` env > this field > the hardcoded default\n * `~/.agentproto/worktrees`. The default is a real single root, not\n * \"unconfigured\" — `worktree new` converges to one place with zero\n * setup, which is the only way the sprawl actually stops (the 6 roots\n * that exist today are 6 people each inventing a default by hand).\n */\n root?: string\n /**\n * Policy for `agent_start.worktree` isolation. Resolution order mirrors\n * the module docblock (there is no CLI flag — this is a daemon-side\n * policy read at spawn, not a per-invocation flag):\n * `AGENTPROTO_WORKTREES_ISOLATION` env > this field > the hardcoded\n * default `\"on-request\"`. `\"on-request\"` is deliberately the default:\n * any other would break back-compat by isolating callers that never\n * asked (see `worktree-isolation.ts`).\n */\n isolation?: WorktreeIsolationMode\n}\n\n/**\n * Policy for `agent_start.attach` — whether a spawn AUTO-attaches to its\n * calling session as parent lineage. `\"always\"` (the default) nests a\n * spawned child under the session that spawned it whenever that identity is\n * derivable (the trusted `?callerSessionId=` on the daemon self-ref URL, or\n * an explicit `parentSessionId` hint), so a supervisor's executors stop\n * landing as depth-0 orphans. `\"on-request\"` reverts to the pre-attach\n * behaviour: auto-attribution is off, and a child nests only when the caller\n * explicitly opts in (`attach: true` / `attach: { parent }` / an explicit\n * `parentSessionId`). Either way a per-call `attach: false` forces an\n * independent root, exactly as `worktree: false` opts out of isolation. See\n * `spawn-attach.ts`.\n */\nexport type SpawnAttachMode = \"always\" | \"on-request\"\n\n/**\n * Policy for `agent_start`'s IMPLICIT dedupe — what happens when a caller\n * spawns with NO `idempotencyKey` at all (see `spawn-dedupe.ts`):\n * - `\"always\"` — the daemon DERIVES an implicit key from the spawn's\n * `label` (required — see below) plus a hash of the\n * initial `prompt`, and dedupes a same-adapter/cwd\n * repeat against it exactly as an explicit key would.\n * This is the default, mirroring `attach`'s own\n * \"opt-in-only guard is not a guard\" precedent above.\n * Deriving needs a `label` to produce anything at all\n * — an unlabelled spawn is untouched, which is what\n * keeps this safe for the fan-out pattern this repo\n * exercises (several agents into one cwd with no\n * shared label): see `spawn-dedupe.ts`'s docblock for\n * the full false-dedup analysis, and PR #803's own\n * no-opt-in label+cwd warning backstop in\n * `session-spawn.ts`, which independently landed on\n * the same label-is-the-signal boundary.\n * - `\"on-request\"` — no implicit derivation; only an explicit\n * `idempotencyKey` dedupes (today's behaviour,\n * unchanged). A per-call `dedupe: true` still opts in\n * under this policy, mirroring `attach: true`.\n * Either way a per-call `dedupe: false` disables implicit derivation for\n * that one spawn regardless of policy — the escape hatch, mirroring\n * `attach: false` / `worktree: false`. An explicit `idempotencyKey` always\n * wins over a derived one (derivation is only attempted when the caller\n * supplied none).\n */\nexport type SpawnDedupeMode = \"always\" | \"on-request\"\n\nexport interface SpawnConfig {\n /**\n * Attach policy for `agent_start`. Resolution order mirrors the module\n * docblock (no CLI flag — a daemon-side policy read at spawn):\n * `AGENTPROTO_SPAWN_ATTACH` env > this field > the hardcoded default\n * `\"always\"`. Unlike `worktrees.isolation` (whose default preserves\n * back-compat by NOT isolating), attach defaults ON: an orphaned executor\n * is a bug, not a feature, and the auto-parent is descriptor-only lineage\n * that never relaxes a privilege gate.\n */\n attach?: SpawnAttachMode\n /**\n * Implicit-dedupe policy for `agent_start`. Resolution order mirrors\n * `attach` above (no CLI flag — a daemon-side policy read at spawn):\n * `AGENTPROTO_SPAWN_DEDUPE` env > this field > the hardcoded default\n * `\"always\"`. See {@link SpawnDedupeMode} and `spawn-dedupe.ts` for the\n * full reasoning: a retry-safety guard that only works when a caller\n * remembers to ask for it (`idempotencyKey`) is not a guard — the same\n * argument `attach` already settled for parent lineage.\n */\n dedupe?: SpawnDedupeMode\n}\n\n/**\n * Provenance policy — the opt-in `gh` PATH shim (`gh-provenance-shim.ts`).\n * Off by default. When `wrapGh` is on, every agent session the daemon spawns\n * gets a shim directory prepended to its PATH so that any `gh pr create` the\n * session (or an adapter subprocess shelling out — claude-code, codex, …) runs\n * has a deterministic `@agentproto-bot` provenance footer appended to the\n * created PR's BODY, matching the footer the cloud runner stamps. The TOOL\n * stamps, never the model; commit messages are never touched (the repo's\n * hygiene-check forbids attribution there). Resolution order mirrors\n * `spawn.attach`: `AGENTPROTO_PROVENANCE_WRAP_GH` env > this field > default\n * `false`.\n */\nexport interface ProvenanceConfig {\n /** Enable the opt-in `gh` provenance PATH shim for spawned sessions. */\n wrapGh?: boolean\n}\n\n/**\n * Session-presence policy — how the dashboard triages a session into its\n * `running` / `tending` / `attention` / `quiet` presence state (see\n * `session-presence.ts`). Optional block on `AgentprotoConfig.sessions`.\n */\nexport interface SessionsConfig {\n /**\n * How long (seconds) a session stays shown as `running` after its last\n * turn ends, before it settles into `attention`/`quiet`. The grace window\n * is measured from the session's most recent `lastActivityAt` — ANY new\n * event resets it — so a session that just answered does not read as\n * \"quiet\" the instant its turn finishes, but something that went idle long\n * ago, with nothing waiting on it, settles grey. Resolution order mirrors\n * every other knob in this file (module docblock): env var\n * `AGENTPROTO_SESSIONS_ATTENTION_DELAY_SEC` > this field > the hardcoded\n * default `60`. Absent ⇒ 60s.\n */\n attentionDelaySec?: number\n}\n\n/**\n * Policy for the daemon's own AGENTS.md resolution + injection (WP-R2) —\n * how a spawned session's initial prompt carries the repo's `AGENTS.md`\n * (see `agents-md.ts`). The daemon resolves the nearest `AGENTS.md` walking\n * up from the session's `cwd`, bounded by that repo's git toplevel, and\n * injects one AGENTS.md block after the role disposition whenever the spawn\n * carries a prompt.\n */\nexport interface AgentsMdConfig {\n /**\n * Inline-vs-pointer threshold in KiB. A resolved AGENTS.md whose byte size\n * is STRICTLY LESS than `inlineMaxKb` KiB is inlined in full into the\n * spawn's initial prompt, clearly delimited; `>=` the threshold is injected\n * as a short pointer telling the agent to read the file before its first\n * tool call instead. An absent AGENTS.md (nothing found up the walk) injects\n * nothing but is still reported on the descriptor via\n * `SessionDescriptor.agentsMdMode === \"absent\"`. Resolution order mirrors\n * the module docblock (no CLI flag, and no env override — not part of the\n * approved design): this field > the hardcoded default `8` (KiB).\n */\n inlineMaxKb?: number\n}\n\nexport interface PairingConfig {\n /** Rendezvous broker WS URL (ws:// or wss://) used by `pair offer` and by\n * autoconnect on boot. When unset, `pair offer` requires an explicit\n * `--rendezvous`. Mirrors `tunnel.host`. */\n rendezvous?: string\n /** Whether the daemon opens standing rendezvous connections for every\n * persisted pairing on boot (so a paired client can reconnect anytime).\n * Mirrors `tunnel.autoconnect`. Default true when a rendezvous is set. */\n autoconnect?: boolean\n}\n\n/**\n * A user-defined generic ACP agent — the config-file half of\n * `AcpAgentSpec` (the slug is the record key in `acpAgents`, so it's\n * omitted here). Any CLI that already speaks the Agent Client Protocol\n * can be wired with zero code by declaring one of these under\n * `acpAgents.<slug>` in `~/.agentproto/config.json`; the CLI's\n * `acpHandleFromSpec` mints a runnable `AgentCliHandle` from it at\n * resolve time (see `packages/cli/src/registry/acp-generic.ts`). Kept\n * in this package (not the CLI's) so `config.ts` stays the single\n * source of truth for the config surface without a cli→runtime→cli\n * import cycle — the CLI's `AcpAgentSpec` extends this shape.\n */\nexport interface AcpAgentConfigEntry {\n /** Display name. Defaults to the slug when omitted. */\n name?: string\n /** One-line description surfaced in `agentproto acp ls`. */\n description?: string\n /** Executable to spawn, e.g. \"gemini\". */\n bin: string\n /** Extra argv appended after `bin`, e.g. [\"--experimental-acp\"]. */\n bin_args?: string[]\n /** Extra environment variables for the spawned process. */\n env?: Record<string, string>\n /** Flag the CLI uses to receive the working directory, if it needs\n * one passed explicitly (most ACP agents take cwd over the wire). */\n cwd_flag?: string\n /** When true, advertise resumable + native-resume continuation. */\n resumable?: boolean\n /** Known model ids for the agent (informational + validation hints). */\n models?: { default?: string; allowed?: string[] }\n /** Billing endpoint this CLI's own auth bills (e.g. \"mistral\",\n * \"moonshot\", \"google\") — the provider whose wallet/auth-profile the\n * agent consumes. Lets clients link the harness to that provider's\n * wallets even when no model list is declared. Unset when the CLI's\n * billing target isn't a single known endpoint. */\n provider?: string\n /** Shown when `bin` is missing from PATH (how to install the CLI). */\n install_hint?: string\n}\n\n/**\n * Per-environment connection bundle. A profile overrides specific\n * fields of the top-level `daemon` / `tunnel` / `features` blocks\n * when selected via `--profile <name>` (or the top-level\n * `activeProfile` setting). Missing fields fall through to the\n * top-level config, so a profile only needs to declare what's\n * different — typically just `tunnel.host` + `tunnel.token`.\n *\n * Example:\n * {\n * \"daemon\": { \"workspace\": \"/code\", \"port\": 18790 },\n * \"activeProfile\": \"local\",\n * \"profiles\": {\n * \"local\": { \"tunnel\": { \"host\": \"ws://localhost:3200/connect\",\n * \"token\": \"apt_local\", \"autoconnect\": true } },\n * \"prod\": { \"tunnel\": { \"host\": \"wss://tunnel.guilde.work/connect\",\n * \"token\": \"apt_prod\", \"autoconnect\": true },\n * \"daemon\": { \"port\": 18791 } }\n * }\n * }\n *\n * Sandbox daemons generate per-sandbox profile entries at provision\n * time so the daemon inside the sandbox boots with\n * `agentproto serve --profile sandbox-<id>` and no extra plumbing.\n */\nexport interface ProfileConfig {\n daemon?: DaemonConfig\n tunnel?: TunnelConfig\n features?: FeaturesConfig\n}\n\n/**\n * A user-defined named terminal/TUI preset stored in\n * `~/.agentproto/config.json` under `terminalPresets`. Presets keep\n * local launch recipes (argv, env, cwd, name/label) out of shared\n * adapter manifests — e.g. pointing a Claude Code TUI at a local\n * LLM gateway without retyping proxy env vars every spawn.\n */\nexport interface TerminalPreset {\n /** Command + args to spawn. When provided, `sessions terminal` can\n * be used without `-- <argv...>`. */\n argv?: string[]\n /** Extra environment variables layered on top of the daemon's\n * inherited process.env. Values MUST be strings. */\n env?: Record<string, string>\n /** Working directory for the PTY session. Relative paths are\n * resolved against the current working directory at CLI time. */\n cwd?: string\n /** Workspace slug used for cwd fallback when `cwd` is omitted. */\n workspace?: string\n /** Stable session name passed to the registry (`name` field). */\n name?: string\n /** Human-readable label surfaced in session listings. */\n label?: string\n}\n\nexport interface AgentprotoConfig {\n version?: number\n daemon?: DaemonConfig\n tunnel?: TunnelConfig\n features?: FeaturesConfig\n /** E2E daemon-pairing defaults (rendezvous URL + autoconnect). */\n pairing?: PairingConfig\n /** Where `agentproto worktree new` creates worktrees. See\n * {@link WorktreesConfig}. */\n worktrees?: WorktreesConfig\n /** Spawn-time policy (`agent_start`). See {@link SpawnConfig}. */\n spawn?: SpawnConfig\n /** Session-presence policy for the dashboard. See {@link SessionsConfig}. */\n sessions?: SessionsConfig\n /** Provenance policy — the opt-in `gh` PATH shim. See {@link ProvenanceConfig}. */\n provenance?: ProvenanceConfig\n /** Daemon-side AGENTS.md resolution/injection policy. See {@link AgentsMdConfig}. */\n agentsMd?: AgentsMdConfig\n /** Named connection profiles. See `ProfileConfig` for the merge\n * semantics — a profile's fields shallow-override the top-level\n * defaults for the selected run. */\n profiles?: Record<string, ProfileConfig>\n /** Profile name to use when `--profile` isn't passed. When unset,\n * the top-level `daemon` / `tunnel` blocks are used directly. */\n activeProfile?: string\n /** Default `skills` + `options` auto-applied to every `agent_start`\n * spawn — global and per-adapter. See `resolveSpawnDefaults` in\n * `spawn-defaults.ts` for the merge precedence with an explicit call.\n * Absent ⇒ current behaviour exactly (no regression). */\n defaults?: SpawnDefaultsConfig\n /** User-defined generic ACP agents, keyed by adapter slug. Each entry\n * is minted into a runnable handle by the CLI's `acpHandleFromSpec`\n * when `resolveAdapter(slug)` finds no npm adapter package. User\n * entries shadow the curated `ACP_CATALOG` on slug collision. */\n acpAgents?: Record<string, AcpAgentConfigEntry>\n /** User-defined named terminal/TUI presets. Local-only; never\n * packaged in shared adapter manifests or defaults. */\n terminalPresets?: Record<string, TerminalPreset>\n /** Unknown keys preserved across save round-trips. */\n [unknown: string]: unknown\n}\n\nexport const CONFIG_FILE_PATH = (): string =>\n join(homedir(), \".agentproto\", \"config.json\")\n\n/**\n * Drop any `acpAgents` entries that aren't a shape we can turn into a\n * handle. The one hard requirement is a non-empty string `bin` (the\n * executable to spawn); everything else is optional and defaulted\n * downstream. Invalid entries are removed rather than throwing so the\n * daemon still boots — one warning names the offending slug so the\n * user can fix their config. Returns `undefined` when nothing valid\n * remains, keeping the key absent (== \"no generic agents\").\n */\nfunction sanitizeAcpAgents(\n raw: unknown,\n target: string,\n): Record<string, AcpAgentConfigEntry> | undefined {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) {\n console.warn(\n `[runtime/config] ${target}: 'acpAgents' is not an object — ignoring`,\n )\n return undefined\n }\n const out: Record<string, AcpAgentConfigEntry> = {}\n for (const [slug, value] of Object.entries(raw as Record<string, unknown>)) {\n if (\n value &&\n typeof value === \"object\" &&\n !Array.isArray(value) &&\n typeof (value as { bin?: unknown }).bin === \"string\" &&\n (value as { bin: string }).bin.length > 0\n ) {\n out[slug] = value as AcpAgentConfigEntry\n } else {\n console.warn(\n `[runtime/config] ${target}: acpAgents.${slug} is missing a string 'bin' — ignoring`,\n )\n }\n }\n return Object.keys(out).length > 0 ? out : undefined\n}\n\n/**\n * Load config.json. Returns an empty object (NOT null) when the file\n * is missing, malformed, or unreadable — callers can `cfg.daemon?.port`\n * safely without null-guards. Errors during a malformed-read are\n * logged once so the user notices the file is broken without the\n * daemon refusing to boot.\n */\nexport async function loadConfig(path?: string): Promise<AgentprotoConfig> {\n const target = path ?? CONFIG_FILE_PATH()\n try {\n const raw = await fs.readFile(target, \"utf8\")\n const parsed = JSON.parse(raw) as unknown\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n const cfg = parsed as AgentprotoConfig\n // Sanitize `acpAgents` in the same tolerant spirit as the rest of\n // this loader: a malformed entry is dropped (with one warning) so a\n // single bad hand-edit can't make every generic ACP agent\n // unresolvable. Full AIP-45 validation happens later, at\n // `acpHandleFromSpec` time, with precise field-level messages.\n if (cfg.acpAgents !== undefined) {\n cfg.acpAgents = sanitizeAcpAgents(cfg.acpAgents, target)\n }\n return cfg\n }\n console.warn(\n `[runtime/config] ${target}: top-level value is not an object — ignoring`,\n )\n return {}\n } catch (err) {\n // ENOENT is the common case; only warn on other shapes.\n const code = (err as NodeJS.ErrnoException).code\n if (code && code !== \"ENOENT\") {\n console.warn(\n `[runtime/config] failed to read ${target}: ${\n err instanceof Error ? err.message : String(err)\n }`,\n )\n }\n return {}\n }\n}\n\n/**\n * Write config.json atomically (tmp + rename) so a concurrent\n * `agentproto config edit` can't half-truncate the file. Writes\n * `next` AS-IS — callers are expected to pass the full desired\n * state (loaded the existing config, mutated, passed it back).\n *\n * Earlier versions deep-merged with the on-disk file, but that made\n * deletions impossible: `setConfigKey(cfg, \"x\", undefined)` would\n * remove the key from memory, then the deep-merge would silently\n * re-add it from disk. The current design trusts the caller's\n * snapshot and uses atomic rename for crash safety.\n */\nexport async function saveConfig(\n next: AgentprotoConfig,\n path?: string,\n): Promise<void> {\n const target = path ?? CONFIG_FILE_PATH()\n const payload = { ...next, version: CONFIG_VERSION }\n const dir = dirname(target)\n await fs.mkdir(dir, { recursive: true })\n const tmp = `${target}.tmp`\n await fs.writeFile(tmp, JSON.stringify(payload, null, 2) + \"\\n\", \"utf8\")\n await fs.rename(tmp, target)\n}\n\n/**\n * Read a dot-notation key (`daemon.port`) out of a config. Returns\n * `undefined` when any segment is missing.\n */\nexport function getConfigKey(\n cfg: AgentprotoConfig,\n dotted: string,\n): unknown {\n let cur: unknown = cfg\n for (const part of dotted.split(\".\")) {\n if (cur == null || typeof cur !== \"object\") return undefined\n cur = (cur as Record<string, unknown>)[part]\n }\n return cur\n}\n\n/**\n * Set a dot-notation key in a config. Returns a new object — does\n * NOT mutate. Creates intermediate objects as needed. Setting\n * `value: undefined` is treated as a delete.\n */\nexport function setConfigKey(\n cfg: AgentprotoConfig,\n dotted: string,\n value: unknown,\n): AgentprotoConfig {\n const parts = dotted.split(\".\")\n const out: AgentprotoConfig = { ...cfg }\n let cur: Record<string, unknown> = out as Record<string, unknown>\n for (let i = 0; i < parts.length - 1; i++) {\n const k = parts[i]!\n const next = cur[k]\n if (next && typeof next === \"object\" && !Array.isArray(next)) {\n cur[k] = { ...(next as Record<string, unknown>) }\n } else {\n cur[k] = {}\n }\n cur = cur[k] as Record<string, unknown>\n }\n const leaf = parts[parts.length - 1]!\n if (value === undefined) {\n delete cur[leaf]\n } else {\n cur[leaf] = value\n }\n return out\n}\n\n/**\n * Deep merge — objects are recursively combined, everything else\n * (arrays, primitives) is replaced wholesale by `b`. Mirrors what\n * `Object.assign({}, a, b)` does for shallow keys.\n */\nfunction deepMerge<A extends Record<string, unknown>, B extends Record<string, unknown>>(\n a: A,\n b: B,\n): A & B {\n const out: Record<string, unknown> = { ...a }\n for (const [k, v] of Object.entries(b)) {\n const cur = out[k]\n if (\n v &&\n typeof v === \"object\" &&\n !Array.isArray(v) &&\n cur &&\n typeof cur === \"object\" &&\n !Array.isArray(cur)\n ) {\n out[k] = deepMerge(\n cur as Record<string, unknown>,\n v as Record<string, unknown>,\n )\n } else {\n out[k] = v\n }\n }\n return out as A & B\n}\n","/**\n * DASHBOARD-level session-presence triage — \"which of my many sessions in the\n * LIST needs a glance?\" — as one shared, pure classifier consumed identically\n * by the CLI sessions table and the VS Code sessions tree / webview panel.\n *\n * This is deliberately COARSER than `activityFor` (the vscode package's\n * 8-state per-session classifier, `sessionsTree.logic.ts`): that one answers\n * the fine-grained \"what IS this one session\" question for a transcript chip\n * / status bar / detail row, with its own stall detection and `pendingBgTasks`\n * conventions. This answers the list-level presence question with a four-state\n * vocabulary, and resolves the divergence bug where the CLI and VS Code each\n * derived their own (already-inconsistent) reading of the same `SessionDescriptor`.\n *\n * The four states map onto \"does anything want my attention right now, and if\n * so what kind\":\n * running — a turn is actively turning, OR the session just finished one\n * (still inside the grace window). Peers with `tending` at the\n * top of any presence-urgency sort. Do not touch.\n * tending — not running, not in grace, but has active sub-agents\n * (`childrenBusy`) or background tasks still pending\n * (`pendingBgTasks`) — it's busy THROUGH others. Peers with\n * `running`, not a step below it. Half-filled mark. Watch it,\n * don't prompt it.\n * attention — needs the human: awaiting input or a held permission\n * (`awaitingInput`/`awaitingPermission`), or its own last turn\n * failed in-band (`lastTurnErroredAt`), or it ended badly\n * (error / non-zero exit). Amber. Look at me.\n * quiet — none of the above. Grey. Leave alone.\n *\n * The classifier is PURE: it never reads config/fs/timers. The caller resolves\n * `attentionDelaySec` (via `resolveAttentionDelaySec`) and passes it in, and\n * passes a `now` so tests (and a repainting UI) can control time. State is\n * re-derived fresh on every call — the grace window is measured from\n * `lastActivityAt`, so an event arriving resets it automatically; there is no\n * stored timer state to keep in sync.\n */\n\nimport { loadConfig } from \"./config.js\"\n\n/** Env override for the grace window (seconds). Highest-priority source,\n * ahead of the `sessions.attentionDelaySec` config field — see\n * `resolveAttentionDelaySec`. */\nexport const ATTENTION_DELAY_ENV = \"AGENTPROTO_SESSIONS_ATTENTION_DELAY_SEC\"\n\n/** The default grace window (seconds) when nothing is configured. */\nexport const DEFAULT_ATTENTION_DELAY_SEC = 60\n\n/**\n * The four presence states, in display-priority order. `running` and `tending`\n * are PEERS at the top (both are \"in motion, leave alone\"); `attention` is the\n * only one that says \"a human should look\"; `quiet` needs nothing.\n */\nexport type SessionPresence = \"running\" | \"tending\" | \"attention\" | \"quiet\"\n\n/**\n * Structural input — the subset of `SessionDescriptor` (runtime) / the\n * vscode client type that the classifier reads. Kept as a handful of optional\n * fields (not a hard `Pick<SessionDescriptor, …>`) so both packages' slightly\n * different descriptor shapes satisfy it without a cast.\n */\nexport interface SessionPresenceInput {\n status?: string\n /** A turn is actually in flight right now. */\n busy?: boolean\n /** Sub-agents (children/grandchildren) currently mid-turn. */\n childrenBusy?: number\n /** Background tool starts from the last turn still pending. */\n pendingBgTasks?: number\n /** Blocked on the human for a reply. */\n awaitingInput?: boolean\n /** Blocked on the human for a permission decision. */\n awaitingPermission?: boolean\n /** ISO 8601 of the last observed activity — the \"something just happened\"\n * timestamp that anchors the grace window. Falls back to `lastOutputAt`. */\n lastActivityAt?: string\n lastOutputAt?: string\n /** The adapter's own last turn failed in-band — needs a look, not a\n * re-prompt on faith. */\n lastTurnErroredAt?: string\n /** How the session ended, when it has. */\n exitCode?: number\n}\n\nexport interface PresenceOptions {\n /** Epoch ms serving as \"now\". Defaults to `Date.now()`. Pass an explicit\n * value when you need deterministic, time-controlled classification. */\n now?: number\n /** The grace window to use — resolved by the caller via\n * `resolveAttentionDelaySec`. Defaults to {@link DEFAULT_ATTENTION_DELAY_SEC}. */\n attentionDelaySec?: number\n}\n\nconst TERMINAL_STATUSES = new Set([\"exited\", \"killed\", \"error\"])\n\n/** Whether the session's most recent activity sits within the grace window of\n * `now` — i.e. it is still \"just did something\". `lastActivityAt` is the\n * truthiest \"something happened\" timestamp (it resets on ANY event, which is\n * exactly the \"any new event resets the grace timer\" contract); `lastOutputAt`\n * is the fallback for a descriptor predating it. Absent → false. */\nexport function inAttentionGrace(\n s: Pick<SessionPresenceInput, \"lastActivityAt\" | \"lastOutputAt\">,\n opts?: PresenceOptions,\n): boolean {\n const delaySec = opts?.attentionDelaySec ?? DEFAULT_ATTENTION_DELAY_SEC\n if (delaySec <= 0) return false\n const now = opts?.now ?? Date.now()\n const iso = s.lastActivityAt ?? s.lastOutputAt\n if (!iso) return false\n const at = Date.parse(iso)\n if (Number.isNaN(at)) return false\n return now - at < delaySec * 1000\n}\n\n/**\n * Classify a session onto the four-state presence axis. Most-urgent-first\n * precedence: terminal error > terminal done > the human is needed\n * (`awaitingInput`/`awaitingPermission`/`lastTurnErroredAt`) > a turn is in\n * flight > still inside the grace window > tending (children / bg) > quiet.\n *\n * Terminal sessions are folded onto the same four states rather than left\n * unclassified, so every consumer can render a total badge from this one\n * function: one that ended badly (`status:\"error\"` or a non-zero exit) reads\n * `attention` — a crashed/failed row still deserves a glance — while a clean\n * or stopped one reads `quiet`.\n */\nexport function presenceFor(\n s: SessionPresenceInput,\n opts?: PresenceOptions,\n): SessionPresence {\n if (TERMINAL_STATUSES.has(s.status ?? \"\")) {\n return isTerminalError(s) ? \"attention\" : \"quiet\"\n }\n // Blocked on the human is the loudest live signal — it is the whole reason\n // the dashboard has an \"attention\" lane at all. `lastTurnErroredAt` (the\n // adapter's own in-band failure, process alive) folds in here: the last thing\n // it did needs a look, not a re-prompt on faith.\n if (s.awaitingInput === true || s.awaitingPermission === true || s.lastTurnErroredAt) {\n return \"attention\"\n }\n // A turn actually in flight. Also covers `starting` (booting is in motion,\n // and the session isn't quiet simply because no turn has landed yet).\n if (s.busy === true || s.status === \"starting\") return \"running\"\n // Just finished — still inside the grace window, so the list doesn't dump a\n // session into \"quiet\" the instant its turn ends. Re-derived from\n // `lastActivityAt` every call, so a new event resets the timer by moving the\n // anchor forward.\n if (inAttentionGrace(s, opts)) return \"running\"\n // Not busy, not in grace, but busy THROUGH others (active sub-agents or\n // background tasks still pending) — \"tending\", a peer of running, not a\n // step below it.\n if ((s.childrenBusy ?? 0) > 0 || (s.pendingBgTasks ?? 0) > 0) return \"tending\"\n return \"quiet\"\n}\n\n/** Terminal-error detection shared by the classifier — `status:\"error\"` or a\n * non-zero `exitCode` (a non-zero exit on a `killed` row is how SIGTERM reads,\n * so exit code alone is NOT enough; status \"killed\" already maps to quiet). */\nfunction isTerminalError(s: SessionPresenceInput): boolean {\n if (s.status === \"error\") return true\n return s.status === \"exited\" && typeof s.exitCode === \"number\" && s.exitCode > 0\n}\n\n/**\n * Resolve the effective grace window: env > config field > default. Mirrors\n * `loadWorktreeIsolation`'s precedence and \"never throws / unreadable config\n * falls through to the default\" discipline (`worktree-isolation.ts`).\n */\nexport async function resolveAttentionDelaySec(\n loadCfg: () => Promise<{ sessions?: { attentionDelaySec?: number } }> = loadConfig,\n): Promise<number> {\n const fromEnv = Number(process.env[ATTENTION_DELAY_ENV])\n if (Number.isFinite(fromEnv) && fromEnv >= 0) return fromEnv\n try {\n const cfg = await loadCfg()\n const fromCfg = cfg.sessions?.attentionDelaySec\n if (typeof fromCfg === \"number\" && Number.isFinite(fromCfg) && fromCfg >= 0) {\n return fromCfg\n }\n } catch {\n // fall through to default\n }\n return DEFAULT_ATTENTION_DELAY_SEC\n}"]}
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/session-presence.ts"],"names":["fs"],"mappings":"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","file":"session-presence.mjs","sourcesContent":["/**\n * `~/.agentproto/config.json` — single hand-editable JSON for the\n * agentproto control plane's defaults. Sits alongside the existing\n * surface files (workspaces.json, credentials.json, sessions.json):\n *\n * workspaces.json which directories are workspaces + which is active\n * credentials.json tunnel host bearer tokens (mode 0600)\n * sessions.json last-known snapshot of the registry (informational)\n * config.json daemon defaults: port, bind, allowed origins,\n * tunnel host, feature toggles\n *\n * Resolution order for every daemon knob is:\n * 1. CLI flag (e.g. --port)\n * 2. Env var (where one exists, e.g. AGENTPROTO_TOKEN)\n * 3. config.json\n * 4. Hardcoded default\n *\n * This means a user can call `agentproto config set daemon.port 18791`\n * once and never re-pass `--port 18791` to `serve install` etc. CLI\n * flags still win for one-off overrides.\n *\n * Schema is intentionally narrow + extensible — unknown keys are\n * preserved on save (deep-merge), so a newer CLI writing a new\n * field won't drop one an older CLI doesn't know about. No secrets\n * here; credentials stay in credentials.json (mode 0600).\n */\n\nimport { promises as fs } from \"node:fs\"\nimport { homedir } from \"node:os\"\nimport { dirname, join } from \"node:path\"\nimport type { SpawnDefaultsConfig } from \"./spawn-defaults.js\"\n\nexport const CONFIG_VERSION = 1 as const\n\nexport interface DaemonConfig {\n /** Absolute path to the workspace the daemon binds to at boot. */\n workspace?: string\n /** HTTP port. Default 18790. */\n port?: number\n /** Bind addr. Default 127.0.0.1. */\n bind?: string\n /** Trusted browser origins for mutating /sessions/* routes (in\n * addition to the hardcoded localhost defaults). */\n allowedOrigins?: string[]\n /** When true, the daemon does NOT auto-trust localhost-on-any-port.\n * Only origins explicitly listed in `allowedOrigins` are allowed.\n * Pair with a curated list (e.g. `[\"http://localhost:3000\"]`) for\n * hardened setups. Default false. */\n strictOrigins?: boolean\n /** Server label sent in tunnel hello frames. */\n label?: string\n /** Bearer token gating the gateway at boot (`AuthOptions` with\n * `mode: \"bearer\"`). Unlike `remote_enable`'s ephemeral quick-tunnel\n * token, this one lives in config.json and survives daemon restarts.\n * Set via `agentproto config set daemon.authToken <token>` (e.g.\n * `$(openssl rand -hex 32)`). Unset ⇒ the gateway boots with\n * `mode: \"none\"` — fully open on loopback, same as today. */\n authToken?: string\n /** Opt-in eager resume-on-boot (session-survivability plan §5\n * \"Opt-in vs automatic\", PR-4). When true, a daemon restart doesn't just\n * leave agent-cli sessions dead-but-lazy-resumable — the boot pass eagerly\n * re-spawns the eligible ones (those that died with\n * `endedReason: \"daemon-restart\"` and still pass `canResume`) IN PLACE,\n * restoring liveness without waiting for a prompt. Lazy resume-on-prompt is\n * always on regardless of this flag (it costs nothing until someone acts);\n * this flag only controls the *automatic* boot-time pass. Default false: a\n * box-wide restart should not silently relaunch every adapter (cost, spawn\n * storm, credential re-resolution), and default-off also keeps two daemons\n * sharing the workspace buckets from racing to resume the same rows. Set via\n * `agentproto config set daemon.resumeSessionsOnBoot true`. Surfaced in\n * `daemon_health` / `GET /health`. */\n resumeSessionsOnBoot?: boolean\n /** Idle agent-session reaper (PR-6). When set to a positive number of\n * milliseconds, the daemon periodically retires agent-cli sessions that have\n * been idle (not busy, not awaiting input, `status:\"running\"`) longer than\n * this: it SIGTERMs the adapter child to free the process and flips the row\n * to `killed`/`endedReason:\"idle-reaped\"` — dead-but-lazy-resumable (a later\n * prompt revives it) and, critically, excluded from eager resume-on-boot\n * (#638 gates on `endedReason === \"daemon-restart\"`), so enabling this makes\n * eager resume safe: only genuinely-recent work survives a restart. Default\n * OFF (unset / 0 / negative ⇒ the reaper never runs). Resolution order\n * mirrors the module docblock: `AGENTPROTO_IDLE_REAP_AFTER_MS` env > this\n * field > off. Set via `agentproto config set daemon.idleReapAfterMs\n * <ms>`. Surfaced in `daemon_health` / `GET /health`. */\n idleReapAfterMs?: number\n /** Crash-detect sweep interval in ms (crash-detect PR-1). Unlike\n * `idleReapAfterMs`, this is DEFAULT ON (non-destructive observability):\n * the daemon periodically probes every live agent-cli session's OS\n * process (`process.kill(pid, 0)`) and, when it's provably gone with no\n * exit event ever emitted for it, flips the row to\n * `error`/`endedReason:\"crashed\"` with a `lastError` string — surfacing a\n * death that would otherwise sit silently as `status:\"running\"` until the\n * next prompt's RPC throws (or forever, for a parked session with no next\n * prompt). Detects only; never restarts or notifies (later PRs). Set to a\n * non-positive value to disable the sweep entirely. Resolution order\n * mirrors `idleReapAfterMs`: `AGENTPROTO_CRASH_DETECT_INTERVAL_MS` env >\n * this field > the hardcoded default (30s). Set via `agentproto config\n * set daemon.crashDetectIntervalMs <ms>`. Surfaced in `daemon_health` /\n * `GET /health`. */\n crashDetectIntervalMs?: number\n /** Restart-scheduler sweep interval in ms (restart-scheduler PR-2). OFF by\n * default (unset / 0 / negative ⇒ the sweep never runs), unlike\n * `crashDetectIntervalMs` — a positive value here only arms the periodic\n * sweep that EXECUTES an already-scheduled restart; it never opts a\n * session in by itself (that's the per-session `agent_start.restartPolicy`\n * field). Resolution order mirrors `idleReapAfterMs`:\n * `AGENTPROTO_RESTART_SWEEP_INTERVAL_MS` env > this field > off. Set via\n * `agentproto config set daemon.restartSweepIntervalMs <ms>`. Surfaced in\n * `daemon_health` / `GET /health`. */\n restartSweepIntervalMs?: number\n /** Turn-liveness watchdog threshold in ms (turn-liveness-watchdog\n * chantier). Same shape as `crashDetectIntervalMs`: DEFAULT ON\n * (non-destructive observability). The daemon periodically sweeps every\n * BUSY agent-cli session and, for one that is mid-turn, NOT legitimately\n * `blockedOn` a subagent/command, and has had no adapter activity\n * (`lastActivityAt`) for longer than this threshold, stamps\n * `stalledSinceMs` on the descriptor and emits `session:stalled` —\n * surfacing a dead adapter stream (network drop, hung child — zero\n * frames mid-turn) that would otherwise sit indistinguishable from\n * healthy long work: `status:\"running\"`, `lastError:null`, no other\n * signal. Conservative by design: a session legitimately blocked on a\n * long tool call is NEVER a candidate, no matter how long it's silent —\n * see `stall-watchdog.ts`'s docblock for why. Detects only; never kills\n * or restarts (this is a later concern, if ever). Set to a non-positive\n * value to disable the sweep entirely. Resolution order mirrors\n * `idleReapAfterMs`: `AGENTPROTO_TURN_STALL_AFTER_MS` env > this field >\n * the hardcoded default (5 min). Set via `agentproto config set\n * daemon.turnStallAfterMs <ms>`. Surfaced in `daemon_health` /\n * `GET /health`. */\n turnStallAfterMs?: number\n}\n\nexport interface TunnelConfig {\n /** Cloud WS URL. When set + autoconnect=true, `agentproto serve`\n * bootstraps with `--connect <host>`. */\n host?: string\n /** apt_ daemon token to present at the tunnel upgrade. When set,\n * `agentproto serve` uses this BEFORE falling back to\n * credentials.json — handy in profiles where the token-per-host\n * mapping in credentials.json doesn't fit (e.g. host = tunnel URL\n * but credentials were minted against the api URL). */\n token?: string\n /** Whether `agentproto daemon start` connects the tunnel by\n * default. v0 only — implementer can ignore until daemon needs it. */\n autoconnect?: boolean\n /**\n * Opt into end-to-end encryption of the outbound `serve --connect` tunnel\n * (design: tunnel-e2e/v1). When true, the daemon negotiates a\n * token-authenticated ephemeral handshake with the host and wraps the tunnel\n * frames in an AEAD box, so even the trusted host loses plaintext visibility.\n * The handshake authenticates both ends against the shared `tunnel.token`, so\n * `token` MUST also be set. Fully backward-compatible: if the host doesn't\n * advertise e2e (an older host), the daemon falls back to today's plaintext\n * tunnel. Unset/false ⇒ plaintext, byte-identical to today. */\n e2e?: boolean\n}\n\nexport interface FeaturesConfig {\n /** Hint that PTY is desired — informational; the daemon still\n * detects node-pty's presence at runtime. */\n pty?: boolean\n /** Enable the local LLM Endpoint proxy sidecar (route registration,\n * MCP tools, child-process lifecycle). Default false — the endpoint is\n * an opt-in feature; when off, the `llm-endpoint` custom route is not\n * registered and the `llm_endpoint_*` MCP tools are not exposed. */\n llmEndpoint?: boolean\n}\n\n/**\n * Policy for `agentproto worktree new` (PLAN.md §1.4 — config carries\n * policy, never state; git itself is the authority for which worktrees\n * exist). This is the fix for the sprawl the plan measured: 31 linked\n * worktrees across 6 different parent directories, because there was no\n * `worktree new` verb and therefore no convention to converge on.\n */\n/**\n * How the daemon isolates a freshly-spawned agent session into its own git\n * worktree (`agent_start.worktree`):\n * - `\"always\"` — every depth-0 spawn is provisioned into a worktree,\n * whether or not the caller asked. A cwd that is not in\n * a git repo has nothing to isolate, so it spawns plain.\n * - `\"on-request\"` — isolate ONLY when the caller passes `worktree`. This\n * is the default and the back-compatible behaviour:\n * today's callers pass nothing and spawn exactly where\n * they asked.\n * - `\"never\"` — isolation is off; an explicit `worktree` field is\n * REJECTED (loud, not silently ignored) so a caller\n * never believes it got an isolated tree it didn't.\n */\nexport type WorktreeIsolationMode = \"always\" | \"on-request\" | \"never\"\n\nexport interface WorktreesConfig {\n /**\n * Absolute path new worktrees are created under. Layout:\n * `<root>/<repoName>/<slug>`. Resolution order (mirrors every other\n * knob in this file, see the module docblock): `--root` flag >\n * `AGENTPROTO_WORKTREES_ROOT` env > this field > the hardcoded default\n * `~/.agentproto/worktrees`. The default is a real single root, not\n * \"unconfigured\" — `worktree new` converges to one place with zero\n * setup, which is the only way the sprawl actually stops (the 6 roots\n * that exist today are 6 people each inventing a default by hand).\n */\n root?: string\n /**\n * Policy for `agent_start.worktree` isolation. Resolution order mirrors\n * the module docblock (there is no CLI flag — this is a daemon-side\n * policy read at spawn, not a per-invocation flag):\n * `AGENTPROTO_WORKTREES_ISOLATION` env > this field > the hardcoded\n * default `\"on-request\"`. `\"on-request\"` is deliberately the default:\n * any other would break back-compat by isolating callers that never\n * asked (see `worktree-isolation.ts`).\n */\n isolation?: WorktreeIsolationMode\n}\n\n/**\n * Policy for `agent_start.attach` — whether a spawn AUTO-attaches to its\n * calling session as parent lineage. `\"always\"` (the default) nests a\n * spawned child under the session that spawned it whenever that identity is\n * derivable (the trusted `?callerSessionId=` on the daemon self-ref URL, or\n * an explicit `parentSessionId` hint), so a supervisor's executors stop\n * landing as depth-0 orphans. `\"on-request\"` reverts to the pre-attach\n * behaviour: auto-attribution is off, and a child nests only when the caller\n * explicitly opts in (`attach: true` / `attach: { parent }` / an explicit\n * `parentSessionId`). Either way a per-call `attach: false` forces an\n * independent root, exactly as `worktree: false` opts out of isolation. See\n * `spawn-attach.ts`.\n */\nexport type SpawnAttachMode = \"always\" | \"on-request\"\n\n/**\n * Policy for `agent_start`'s IMPLICIT dedupe — what happens when a caller\n * spawns with NO `idempotencyKey` at all (see `spawn-dedupe.ts`):\n * - `\"always\"` — the daemon DERIVES an implicit key from the spawn's\n * `label` (required — see below) plus a hash of the\n * initial `prompt`, and dedupes a same-adapter/cwd\n * repeat against it exactly as an explicit key would.\n * This is the default, mirroring `attach`'s own\n * \"opt-in-only guard is not a guard\" precedent above.\n * Deriving needs a `label` to produce anything at all\n * — an unlabelled spawn is untouched, which is what\n * keeps this safe for the fan-out pattern this repo\n * exercises (several agents into one cwd with no\n * shared label): see `spawn-dedupe.ts`'s docblock for\n * the full false-dedup analysis, and PR #803's own\n * no-opt-in label+cwd warning backstop in\n * `session-spawn.ts`, which independently landed on\n * the same label-is-the-signal boundary.\n * - `\"on-request\"` — no implicit derivation; only an explicit\n * `idempotencyKey` dedupes (today's behaviour,\n * unchanged). A per-call `dedupe: true` still opts in\n * under this policy, mirroring `attach: true`.\n * Either way a per-call `dedupe: false` disables implicit derivation for\n * that one spawn regardless of policy — the escape hatch, mirroring\n * `attach: false` / `worktree: false`. An explicit `idempotencyKey` always\n * wins over a derived one (derivation is only attempted when the caller\n * supplied none).\n */\nexport type SpawnDedupeMode = \"always\" | \"on-request\"\n\nexport interface SpawnConfig {\n /**\n * Attach policy for `agent_start`. Resolution order mirrors the module\n * docblock (no CLI flag — a daemon-side policy read at spawn):\n * `AGENTPROTO_SPAWN_ATTACH` env > this field > the hardcoded default\n * `\"always\"`. Unlike `worktrees.isolation` (whose default preserves\n * back-compat by NOT isolating), attach defaults ON: an orphaned executor\n * is a bug, not a feature, and the auto-parent is descriptor-only lineage\n * that never relaxes a privilege gate.\n */\n attach?: SpawnAttachMode\n /**\n * Implicit-dedupe policy for `agent_start`. Resolution order mirrors\n * `attach` above (no CLI flag — a daemon-side policy read at spawn):\n * `AGENTPROTO_SPAWN_DEDUPE` env > this field > the hardcoded default\n * `\"always\"`. See {@link SpawnDedupeMode} and `spawn-dedupe.ts` for the\n * full reasoning: a retry-safety guard that only works when a caller\n * remembers to ask for it (`idempotencyKey`) is not a guard — the same\n * argument `attach` already settled for parent lineage.\n */\n dedupe?: SpawnDedupeMode\n}\n\n/**\n * Provenance policy — the opt-in `gh` PATH shim (`gh-provenance-shim.ts`).\n * Off by default. When `wrapGh` is on, every agent session the daemon spawns\n * gets a shim directory prepended to its PATH so that any `gh pr create` the\n * session (or an adapter subprocess shelling out — claude-code, codex, …) runs\n * has a deterministic `@agentproto-bot` provenance footer appended to the\n * created PR's BODY, matching the footer the cloud runner stamps. The TOOL\n * stamps, never the model; commit messages are never touched (the repo's\n * hygiene-check forbids attribution there). Resolution order mirrors\n * `spawn.attach`: `AGENTPROTO_PROVENANCE_WRAP_GH` env > this field > default\n * `false`.\n */\nexport interface ProvenanceConfig {\n /** Enable the opt-in `gh` provenance PATH shim for spawned sessions. */\n wrapGh?: boolean\n}\n\n/**\n * Session-presence policy — how the dashboard triages a session into its\n * `running` / `tending` / `attention` / `quiet` presence state (see\n * `session-presence.ts`). Optional block on `AgentprotoConfig.sessions`.\n */\nexport interface SessionsConfig {\n /**\n * How long (seconds) a session stays shown as `running` after its last\n * turn ends, before it settles into `attention`/`quiet`. The grace window\n * is measured from the session's most recent `lastActivityAt` — ANY new\n * event resets it — so a session that just answered does not read as\n * \"quiet\" the instant its turn finishes, but something that went idle long\n * ago, with nothing waiting on it, settles grey. Resolution order mirrors\n * every other knob in this file (module docblock): env var\n * `AGENTPROTO_SESSIONS_ATTENTION_DELAY_SEC` > this field > the hardcoded\n * default `60`. Absent ⇒ 60s.\n */\n attentionDelaySec?: number\n /**\n * Root directory the daemon stores per-session transcripts under — each\n * session's `events.jsonl` lands at `<eventsDir>/<sessionId>/events.jsonl`\n * (and a terminal session's `terminal.jsonl` alongside it). Every\n * reader/writer (transcript writer, `/sessions/:id/events` HTTP routes,\n * exports, tool-call/usage logs) resolves through this single knob.\n * Resolution order mirrors every other knob in this file: relative paths\n * resolve against the home directory. Absent ⇒ the hardcoded default\n * `~/.agentproto/sessions` — zero behaviour change out of the box. A\n * change requires a daemon restart to take effect (and existing sessions'\n * transcripts do NOT move).\n */\n eventsDir?: string\n}\n\n/**\n * Policy for the daemon's own AGENTS.md resolution + injection (WP-R2) —\n * how a spawned session's initial prompt carries the repo's `AGENTS.md`\n * (see `agents-md.ts`). The daemon resolves the nearest `AGENTS.md` walking\n * up from the session's `cwd`, bounded by that repo's git toplevel, and\n * injects one AGENTS.md block after the role disposition whenever the spawn\n * carries a prompt.\n */\nexport interface AgentsMdConfig {\n /**\n * Inline-vs-pointer threshold in KiB. A resolved AGENTS.md whose byte size\n * is STRICTLY LESS than `inlineMaxKb` KiB is inlined in full into the\n * spawn's initial prompt, clearly delimited; `>=` the threshold is injected\n * as a short pointer telling the agent to read the file before its first\n * tool call instead. An absent AGENTS.md (nothing found up the walk) injects\n * nothing but is still reported on the descriptor via\n * `SessionDescriptor.agentsMdMode === \"absent\"`. Resolution order mirrors\n * the module docblock (no CLI flag, and no env override — not part of the\n * approved design): this field > the hardcoded default `8` (KiB).\n */\n inlineMaxKb?: number\n}\n\nexport interface PairingConfig {\n /** Rendezvous broker WS URL (ws:// or wss://) used by `pair offer` and by\n * autoconnect on boot. When unset, `pair offer` requires an explicit\n * `--rendezvous`. Mirrors `tunnel.host`. */\n rendezvous?: string\n /** Whether the daemon opens standing rendezvous connections for every\n * persisted pairing on boot (so a paired client can reconnect anytime).\n * Mirrors `tunnel.autoconnect`. Default true when a rendezvous is set. */\n autoconnect?: boolean\n}\n\n/**\n * A user-defined generic ACP agent — the config-file half of\n * `AcpAgentSpec` (the slug is the record key in `acpAgents`, so it's\n * omitted here). Any CLI that already speaks the Agent Client Protocol\n * can be wired with zero code by declaring one of these under\n * `acpAgents.<slug>` in `~/.agentproto/config.json`; the CLI's\n * `acpHandleFromSpec` mints a runnable `AgentCliHandle` from it at\n * resolve time (see `packages/cli/src/registry/acp-generic.ts`). Kept\n * in this package (not the CLI's) so `config.ts` stays the single\n * source of truth for the config surface without a cli→runtime→cli\n * import cycle — the CLI's `AcpAgentSpec` extends this shape.\n */\nexport interface AcpAgentConfigEntry {\n /** Display name. Defaults to the slug when omitted. */\n name?: string\n /** One-line description surfaced in `agentproto acp ls`. */\n description?: string\n /** Executable to spawn, e.g. \"gemini\". */\n bin: string\n /** Extra argv appended after `bin`, e.g. [\"--experimental-acp\"]. */\n bin_args?: string[]\n /** Extra environment variables for the spawned process. */\n env?: Record<string, string>\n /** Flag the CLI uses to receive the working directory, if it needs\n * one passed explicitly (most ACP agents take cwd over the wire). */\n cwd_flag?: string\n /** When true, advertise resumable + native-resume continuation. */\n resumable?: boolean\n /** Known model ids for the agent (informational + validation hints). */\n models?: { default?: string; allowed?: string[] }\n /** Billing endpoint this CLI's own auth bills (e.g. \"mistral\",\n * \"moonshot\", \"google\") — the provider whose wallet/auth-profile the\n * agent consumes. Lets clients link the harness to that provider's\n * wallets even when no model list is declared. Unset when the CLI's\n * billing target isn't a single known endpoint. */\n provider?: string\n /** Shown when `bin` is missing from PATH (how to install the CLI). */\n install_hint?: string\n}\n\n/**\n * Per-environment connection bundle. A profile overrides specific\n * fields of the top-level `daemon` / `tunnel` / `features` blocks\n * when selected via `--profile <name>` (or the top-level\n * `activeProfile` setting). Missing fields fall through to the\n * top-level config, so a profile only needs to declare what's\n * different — typically just `tunnel.host` + `tunnel.token`.\n *\n * Example:\n * {\n * \"daemon\": { \"workspace\": \"/code\", \"port\": 18790 },\n * \"activeProfile\": \"local\",\n * \"profiles\": {\n * \"local\": { \"tunnel\": { \"host\": \"ws://localhost:3200/connect\",\n * \"token\": \"apt_local\", \"autoconnect\": true } },\n * \"prod\": { \"tunnel\": { \"host\": \"wss://tunnel.guilde.work/connect\",\n * \"token\": \"apt_prod\", \"autoconnect\": true },\n * \"daemon\": { \"port\": 18791 } }\n * }\n * }\n *\n * Sandbox daemons generate per-sandbox profile entries at provision\n * time so the daemon inside the sandbox boots with\n * `agentproto serve --profile sandbox-<id>` and no extra plumbing.\n */\nexport interface ProfileConfig {\n daemon?: DaemonConfig\n tunnel?: TunnelConfig\n features?: FeaturesConfig\n}\n\n/**\n * A user-defined named terminal/TUI preset stored in\n * `~/.agentproto/config.json` under `terminalPresets`. Presets keep\n * local launch recipes (argv, env, cwd, name/label) out of shared\n * adapter manifests — e.g. pointing a Claude Code TUI at a local\n * LLM gateway without retyping proxy env vars every spawn.\n */\nexport interface TerminalPreset {\n /** Command + args to spawn. When provided, `sessions terminal` can\n * be used without `-- <argv...>`. */\n argv?: string[]\n /** Extra environment variables layered on top of the daemon's\n * inherited process.env. Values MUST be strings. */\n env?: Record<string, string>\n /** Working directory for the PTY session. Relative paths are\n * resolved against the current working directory at CLI time. */\n cwd?: string\n /** Workspace slug used for cwd fallback when `cwd` is omitted. */\n workspace?: string\n /** Stable session name passed to the registry (`name` field). */\n name?: string\n /** Human-readable label surfaced in session listings. */\n label?: string\n}\n\nexport interface AgentprotoConfig {\n version?: number\n daemon?: DaemonConfig\n tunnel?: TunnelConfig\n features?: FeaturesConfig\n /** E2E daemon-pairing defaults (rendezvous URL + autoconnect). */\n pairing?: PairingConfig\n /** Where `agentproto worktree new` creates worktrees. See\n * {@link WorktreesConfig}. */\n worktrees?: WorktreesConfig\n /** Spawn-time policy (`agent_start`). See {@link SpawnConfig}. */\n spawn?: SpawnConfig\n /** Session-presence policy for the dashboard. See {@link SessionsConfig}. */\n sessions?: SessionsConfig\n /** Provenance policy — the opt-in `gh` PATH shim. See {@link ProvenanceConfig}. */\n provenance?: ProvenanceConfig\n /** Daemon-side AGENTS.md resolution/injection policy. See {@link AgentsMdConfig}. */\n agentsMd?: AgentsMdConfig\n /** Named connection profiles. See `ProfileConfig` for the merge\n * semantics — a profile's fields shallow-override the top-level\n * defaults for the selected run. */\n profiles?: Record<string, ProfileConfig>\n /** Profile name to use when `--profile` isn't passed. When unset,\n * the top-level `daemon` / `tunnel` blocks are used directly. */\n activeProfile?: string\n /** Default `skills` + `options` auto-applied to every `agent_start`\n * spawn — global and per-adapter. See `resolveSpawnDefaults` in\n * `spawn-defaults.ts` for the merge precedence with an explicit call.\n * Absent ⇒ current behaviour exactly (no regression). */\n defaults?: SpawnDefaultsConfig\n /** User-defined generic ACP agents, keyed by adapter slug. Each entry\n * is minted into a runnable handle by the CLI's `acpHandleFromSpec`\n * when `resolveAdapter(slug)` finds no npm adapter package. User\n * entries shadow the curated `ACP_CATALOG` on slug collision. */\n acpAgents?: Record<string, AcpAgentConfigEntry>\n /** User-defined named terminal/TUI presets. Local-only; never\n * packaged in shared adapter manifests or defaults. */\n terminalPresets?: Record<string, TerminalPreset>\n /** Unknown keys preserved across save round-trips. */\n [unknown: string]: unknown\n}\n\nexport const CONFIG_FILE_PATH = (): string =>\n join(homedir(), \".agentproto\", \"config.json\")\n\n/**\n * Drop any `acpAgents` entries that aren't a shape we can turn into a\n * handle. The one hard requirement is a non-empty string `bin` (the\n * executable to spawn); everything else is optional and defaulted\n * downstream. Invalid entries are removed rather than throwing so the\n * daemon still boots — one warning names the offending slug so the\n * user can fix their config. Returns `undefined` when nothing valid\n * remains, keeping the key absent (== \"no generic agents\").\n */\nfunction sanitizeAcpAgents(\n raw: unknown,\n target: string,\n): Record<string, AcpAgentConfigEntry> | undefined {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) {\n console.warn(\n `[runtime/config] ${target}: 'acpAgents' is not an object — ignoring`,\n )\n return undefined\n }\n const out: Record<string, AcpAgentConfigEntry> = {}\n for (const [slug, value] of Object.entries(raw as Record<string, unknown>)) {\n if (\n value &&\n typeof value === \"object\" &&\n !Array.isArray(value) &&\n typeof (value as { bin?: unknown }).bin === \"string\" &&\n (value as { bin: string }).bin.length > 0\n ) {\n out[slug] = value as AcpAgentConfigEntry\n } else {\n console.warn(\n `[runtime/config] ${target}: acpAgents.${slug} is missing a string 'bin' — ignoring`,\n )\n }\n }\n return Object.keys(out).length > 0 ? out : undefined\n}\n\n/**\n * Load config.json. Returns an empty object (NOT null) when the file\n * is missing, malformed, or unreadable — callers can `cfg.daemon?.port`\n * safely without null-guards. Errors during a malformed-read are\n * logged once so the user notices the file is broken without the\n * daemon refusing to boot.\n */\nexport async function loadConfig(path?: string): Promise<AgentprotoConfig> {\n const target = path ?? CONFIG_FILE_PATH()\n try {\n const raw = await fs.readFile(target, \"utf8\")\n const parsed = JSON.parse(raw) as unknown\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n const cfg = parsed as AgentprotoConfig\n // Sanitize `acpAgents` in the same tolerant spirit as the rest of\n // this loader: a malformed entry is dropped (with one warning) so a\n // single bad hand-edit can't make every generic ACP agent\n // unresolvable. Full AIP-45 validation happens later, at\n // `acpHandleFromSpec` time, with precise field-level messages.\n if (cfg.acpAgents !== undefined) {\n cfg.acpAgents = sanitizeAcpAgents(cfg.acpAgents, target)\n }\n return cfg\n }\n console.warn(\n `[runtime/config] ${target}: top-level value is not an object — ignoring`,\n )\n return {}\n } catch (err) {\n // ENOENT is the common case; only warn on other shapes.\n const code = (err as NodeJS.ErrnoException).code\n if (code && code !== \"ENOENT\") {\n console.warn(\n `[runtime/config] failed to read ${target}: ${\n err instanceof Error ? err.message : String(err)\n }`,\n )\n }\n return {}\n }\n}\n\n/**\n * Write config.json atomically (tmp + rename) so a concurrent\n * `agentproto config edit` can't half-truncate the file. Writes\n * `next` AS-IS — callers are expected to pass the full desired\n * state (loaded the existing config, mutated, passed it back).\n *\n * Earlier versions deep-merged with the on-disk file, but that made\n * deletions impossible: `setConfigKey(cfg, \"x\", undefined)` would\n * remove the key from memory, then the deep-merge would silently\n * re-add it from disk. The current design trusts the caller's\n * snapshot and uses atomic rename for crash safety.\n */\nexport async function saveConfig(\n next: AgentprotoConfig,\n path?: string,\n): Promise<void> {\n const target = path ?? CONFIG_FILE_PATH()\n const payload = { ...next, version: CONFIG_VERSION }\n const dir = dirname(target)\n await fs.mkdir(dir, { recursive: true })\n const tmp = `${target}.tmp`\n await fs.writeFile(tmp, JSON.stringify(payload, null, 2) + \"\\n\", \"utf8\")\n await fs.rename(tmp, target)\n}\n\n/**\n * Read a dot-notation key (`daemon.port`) out of a config. Returns\n * `undefined` when any segment is missing.\n */\nexport function getConfigKey(\n cfg: AgentprotoConfig,\n dotted: string,\n): unknown {\n let cur: unknown = cfg\n for (const part of dotted.split(\".\")) {\n if (cur == null || typeof cur !== \"object\") return undefined\n cur = (cur as Record<string, unknown>)[part]\n }\n return cur\n}\n\n/**\n * Set a dot-notation key in a config. Returns a new object — does\n * NOT mutate. Creates intermediate objects as needed. Setting\n * `value: undefined` is treated as a delete.\n */\nexport function setConfigKey(\n cfg: AgentprotoConfig,\n dotted: string,\n value: unknown,\n): AgentprotoConfig {\n const parts = dotted.split(\".\")\n const out: AgentprotoConfig = { ...cfg }\n let cur: Record<string, unknown> = out as Record<string, unknown>\n for (let i = 0; i < parts.length - 1; i++) {\n const k = parts[i]!\n const next = cur[k]\n if (next && typeof next === \"object\" && !Array.isArray(next)) {\n cur[k] = { ...(next as Record<string, unknown>) }\n } else {\n cur[k] = {}\n }\n cur = cur[k] as Record<string, unknown>\n }\n const leaf = parts[parts.length - 1]!\n if (value === undefined) {\n delete cur[leaf]\n } else {\n cur[leaf] = value\n }\n return out\n}\n\n/**\n * Deep merge — objects are recursively combined, everything else\n * (arrays, primitives) is replaced wholesale by `b`. Mirrors what\n * `Object.assign({}, a, b)` does for shallow keys.\n */\nfunction deepMerge<A extends Record<string, unknown>, B extends Record<string, unknown>>(\n a: A,\n b: B,\n): A & B {\n const out: Record<string, unknown> = { ...a }\n for (const [k, v] of Object.entries(b)) {\n const cur = out[k]\n if (\n v &&\n typeof v === \"object\" &&\n !Array.isArray(v) &&\n cur &&\n typeof cur === \"object\" &&\n !Array.isArray(cur)\n ) {\n out[k] = deepMerge(\n cur as Record<string, unknown>,\n v as Record<string, unknown>,\n )\n } else {\n out[k] = v\n }\n }\n return out as A & B\n}\n","/**\n * DASHBOARD-level session-presence triage — \"which of my many sessions in the\n * LIST needs a glance?\" — as one shared, pure classifier consumed identically\n * by the CLI sessions table and the VS Code sessions tree / webview panel.\n *\n * This is deliberately COARSER than `activityFor` (the vscode package's\n * 8-state per-session classifier, `sessionsTree.logic.ts`): that one answers\n * the fine-grained \"what IS this one session\" question for a transcript chip\n * / status bar / detail row, with its own stall detection and `pendingBgTasks`\n * conventions. This answers the list-level presence question with a four-state\n * vocabulary, and resolves the divergence bug where the CLI and VS Code each\n * derived their own (already-inconsistent) reading of the same `SessionDescriptor`.\n *\n * The four states map onto \"does anything want my attention right now, and if\n * so what kind\":\n * running — a turn is actively turning, OR the session just finished one\n * (still inside the grace window). Peers with `tending` at the\n * top of any presence-urgency sort. Do not touch.\n * tending — not running, not in grace, but has active sub-agents\n * (`childrenBusy`) or background tasks still pending\n * (`pendingBgTasks`) — it's busy THROUGH others. Peers with\n * `running`, not a step below it. Half-filled mark. Watch it,\n * don't prompt it.\n * attention — needs the human: awaiting input or a held permission\n * (`awaitingInput`/`awaitingPermission`), or its own last turn\n * failed in-band (`lastTurnErroredAt`), or it ended badly\n * (error / non-zero exit). Amber. Look at me.\n * quiet — none of the above. Grey. Leave alone.\n *\n * The classifier is PURE: it never reads config/fs/timers. The caller resolves\n * `attentionDelaySec` (via `resolveAttentionDelaySec`) and passes it in, and\n * passes a `now` so tests (and a repainting UI) can control time. State is\n * re-derived fresh on every call — the grace window is measured from\n * `lastActivityAt`, so an event arriving resets it automatically; there is no\n * stored timer state to keep in sync.\n */\n\nimport { loadConfig } from \"./config.js\"\n\n/** Env override for the grace window (seconds). Highest-priority source,\n * ahead of the `sessions.attentionDelaySec` config field — see\n * `resolveAttentionDelaySec`. */\nexport const ATTENTION_DELAY_ENV = \"AGENTPROTO_SESSIONS_ATTENTION_DELAY_SEC\"\n\n/** The default grace window (seconds) when nothing is configured. */\nexport const DEFAULT_ATTENTION_DELAY_SEC = 60\n\n/**\n * The four presence states, in display-priority order. `running` and `tending`\n * are PEERS at the top (both are \"in motion, leave alone\"); `attention` is the\n * only one that says \"a human should look\"; `quiet` needs nothing.\n */\nexport type SessionPresence = \"running\" | \"tending\" | \"attention\" | \"quiet\"\n\n/**\n * Structural input — the subset of `SessionDescriptor` (runtime) / the\n * vscode client type that the classifier reads. Kept as a handful of optional\n * fields (not a hard `Pick<SessionDescriptor, …>`) so both packages' slightly\n * different descriptor shapes satisfy it without a cast.\n */\nexport interface SessionPresenceInput {\n status?: string\n /** A turn is actually in flight right now. */\n busy?: boolean\n /** Sub-agents (children/grandchildren) currently mid-turn. */\n childrenBusy?: number\n /** Background tool starts from the last turn still pending. */\n pendingBgTasks?: number\n /** Blocked on the human for a reply. */\n awaitingInput?: boolean\n /** Blocked on the human for a permission decision. */\n awaitingPermission?: boolean\n /** ISO 8601 of the last observed activity — the \"something just happened\"\n * timestamp that anchors the grace window. Falls back to `lastOutputAt`. */\n lastActivityAt?: string\n lastOutputAt?: string\n /** The adapter's own last turn failed in-band — needs a look, not a\n * re-prompt on faith. */\n lastTurnErroredAt?: string\n /** How the session ended, when it has. */\n exitCode?: number\n}\n\nexport interface PresenceOptions {\n /** Epoch ms serving as \"now\". Defaults to `Date.now()`. Pass an explicit\n * value when you need deterministic, time-controlled classification. */\n now?: number\n /** The grace window to use — resolved by the caller via\n * `resolveAttentionDelaySec`. Defaults to {@link DEFAULT_ATTENTION_DELAY_SEC}. */\n attentionDelaySec?: number\n}\n\nconst TERMINAL_STATUSES = new Set([\"exited\", \"killed\", \"error\"])\n\n/** Whether the session's most recent activity sits within the grace window of\n * `now` — i.e. it is still \"just did something\". `lastActivityAt` is the\n * truthiest \"something happened\" timestamp (it resets on ANY event, which is\n * exactly the \"any new event resets the grace timer\" contract); `lastOutputAt`\n * is the fallback for a descriptor predating it. Absent → false. */\nexport function inAttentionGrace(\n s: Pick<SessionPresenceInput, \"lastActivityAt\" | \"lastOutputAt\">,\n opts?: PresenceOptions,\n): boolean {\n const delaySec = opts?.attentionDelaySec ?? DEFAULT_ATTENTION_DELAY_SEC\n if (delaySec <= 0) return false\n const now = opts?.now ?? Date.now()\n const iso = s.lastActivityAt ?? s.lastOutputAt\n if (!iso) return false\n const at = Date.parse(iso)\n if (Number.isNaN(at)) return false\n return now - at < delaySec * 1000\n}\n\n/**\n * Classify a session onto the four-state presence axis. Most-urgent-first\n * precedence: terminal error > terminal done > the human is needed\n * (`awaitingInput`/`awaitingPermission`/`lastTurnErroredAt`) > a turn is in\n * flight > still inside the grace window > tending (children / bg) > quiet.\n *\n * Terminal sessions are folded onto the same four states rather than left\n * unclassified, so every consumer can render a total badge from this one\n * function: one that ended badly (`status:\"error\"` or a non-zero exit) reads\n * `attention` — a crashed/failed row still deserves a glance — while a clean\n * or stopped one reads `quiet`.\n */\nexport function presenceFor(\n s: SessionPresenceInput,\n opts?: PresenceOptions,\n): SessionPresence {\n if (TERMINAL_STATUSES.has(s.status ?? \"\")) {\n return isTerminalError(s) ? \"attention\" : \"quiet\"\n }\n // Blocked on the human is the loudest live signal — it is the whole reason\n // the dashboard has an \"attention\" lane at all. `lastTurnErroredAt` (the\n // adapter's own in-band failure, process alive) folds in here: the last thing\n // it did needs a look, not a re-prompt on faith.\n if (s.awaitingInput === true || s.awaitingPermission === true || s.lastTurnErroredAt) {\n return \"attention\"\n }\n // A turn actually in flight. Also covers `starting` (booting is in motion,\n // and the session isn't quiet simply because no turn has landed yet).\n if (s.busy === true || s.status === \"starting\") return \"running\"\n // Just finished — still inside the grace window, so the list doesn't dump a\n // session into \"quiet\" the instant its turn ends. Re-derived from\n // `lastActivityAt` every call, so a new event resets the timer by moving the\n // anchor forward.\n if (inAttentionGrace(s, opts)) return \"running\"\n // Not busy, not in grace, but busy THROUGH others (active sub-agents or\n // background tasks still pending) — \"tending\", a peer of running, not a\n // step below it.\n if ((s.childrenBusy ?? 0) > 0 || (s.pendingBgTasks ?? 0) > 0) return \"tending\"\n return \"quiet\"\n}\n\n/** Terminal-error detection shared by the classifier — `status:\"error\"` or a\n * non-zero `exitCode` (a non-zero exit on a `killed` row is how SIGTERM reads,\n * so exit code alone is NOT enough; status \"killed\" already maps to quiet). */\nfunction isTerminalError(s: SessionPresenceInput): boolean {\n if (s.status === \"error\") return true\n return s.status === \"exited\" && typeof s.exitCode === \"number\" && s.exitCode > 0\n}\n\n/**\n * Resolve the effective grace window: env > config field > default. Mirrors\n * `loadWorktreeIsolation`'s precedence and \"never throws / unreadable config\n * falls through to the default\" discipline (`worktree-isolation.ts`).\n */\nexport async function resolveAttentionDelaySec(\n loadCfg: () => Promise<{ sessions?: { attentionDelaySec?: number } }> = loadConfig,\n): Promise<number> {\n const fromEnv = Number(process.env[ATTENTION_DELAY_ENV])\n if (Number.isFinite(fromEnv) && fromEnv >= 0) return fromEnv\n try {\n const cfg = await loadCfg()\n const fromCfg = cfg.sessions?.attentionDelaySec\n if (typeof fromCfg === \"number\" && Number.isFinite(fromCfg) && fromCfg >= 0) {\n return fromCfg\n }\n } catch {\n // fall through to default\n }\n return DEFAULT_ATTENTION_DELAY_SEC\n}"]}
|
|
@@ -136,6 +136,14 @@ interface ResolvedSpawnAuthMaterial {
|
|
|
136
136
|
* key" (fail-fast) from "set nothing" (ambient) — both give no credential.
|
|
137
137
|
* DECISION 5. */
|
|
138
138
|
explicit: boolean;
|
|
139
|
+
/** True when the `auth` block came from CONFIG (`defaults.adapters.<slug>.auth`)
|
|
140
|
+
* rather than the per-spawn `agent_start.auth` request. A sandboxed spawn
|
|
141
|
+
* bills on the BOX daemon, which has neither the host's config nor its
|
|
142
|
+
* keychain — the host uses this to know it must resolve the configured
|
|
143
|
+
* wallet credential itself and forward it (see `session-spawn.ts`'s
|
|
144
|
+
* `resolveHostAuth`). A per-spawn `auth` keeps its own raw-forward path
|
|
145
|
+
* and never forces host adapter resolution. */
|
|
146
|
+
explicitConfig: boolean;
|
|
139
147
|
/** Subscription bearer token (per-spawn > config), if configured. STATIC
|
|
140
148
|
* material only — the self-refreshing {@link subscriptionSource} is resolved
|
|
141
149
|
* separately (and impurely) by the caller. */
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { CursorPayload, Page, PageParams, PaginateOpts, decodeCursor, encodeCursor, pageParamsShape, paginate, toolText } from '@agentproto/tool';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"tool-envelope.mjs","sourcesContent":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentproto/runtime",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "@agentproto/runtime — long-running gateway that turns an agentproto workspace into a live runtime. Composes @agentproto/mcp-server (CRUD verbs) with HTTP transport, HEARTBEAT.md autonomy loop, and append-only conversation persistence. Drop a workspace dir, point your MCP client at it, and the agent ticks on its own.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agentproto",
|
|
@@ -105,6 +105,11 @@
|
|
|
105
105
|
"types": "./dist/user-presets.d.ts",
|
|
106
106
|
"import": "./dist/user-presets.mjs",
|
|
107
107
|
"default": "./dist/user-presets.mjs"
|
|
108
|
+
},
|
|
109
|
+
"./tool-envelope": {
|
|
110
|
+
"types": "./dist/tool-envelope.d.ts",
|
|
111
|
+
"import": "./dist/tool-envelope.mjs",
|
|
112
|
+
"default": "./dist/tool-envelope.mjs"
|
|
108
113
|
}
|
|
109
114
|
},
|
|
110
115
|
"files": [
|
|
@@ -117,50 +122,52 @@
|
|
|
117
122
|
},
|
|
118
123
|
"dependencies": {
|
|
119
124
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
120
|
-
"ai": "^6.0.
|
|
121
|
-
"croner": "^9.
|
|
125
|
+
"ai": "^6.0.277",
|
|
126
|
+
"croner": "^9.1.0",
|
|
122
127
|
"gray-matter": "^4.0.3",
|
|
123
128
|
"ws": "^8.21.3",
|
|
124
129
|
"zod": "^4.5.4",
|
|
125
|
-
"@agentproto/acp": "0.
|
|
126
|
-
"@agentproto/agent": "0.2.
|
|
127
|
-
"@agentproto/app-
|
|
128
|
-
"@agentproto/
|
|
129
|
-
"@agentproto/
|
|
130
|
-
"@agentproto/
|
|
131
|
-
"@agentproto/
|
|
132
|
-
"@agentproto/driver
|
|
133
|
-
"@agentproto/
|
|
134
|
-
"@agentproto/
|
|
135
|
-
"@agentproto/
|
|
136
|
-
"@agentproto/
|
|
137
|
-
"@agentproto/
|
|
138
|
-
"@agentproto/
|
|
139
|
-
"@agentproto/
|
|
140
|
-
"@agentproto/
|
|
141
|
-
"@agentproto/
|
|
142
|
-
"@agentproto/
|
|
143
|
-
"@agentproto/
|
|
144
|
-
"@agentproto/
|
|
145
|
-
"@agentproto/
|
|
146
|
-
"@agentproto/
|
|
147
|
-
"@agentproto/
|
|
148
|
-
"@agentproto/workflow
|
|
149
|
-
"@agentproto/
|
|
130
|
+
"@agentproto/acp": "0.8.1",
|
|
131
|
+
"@agentproto/agent": "0.2.3",
|
|
132
|
+
"@agentproto/app-client": "0.3.2",
|
|
133
|
+
"@agentproto/app-kit": "1.1.1",
|
|
134
|
+
"@agentproto/apps": "0.9.3",
|
|
135
|
+
"@agentproto/auth": "1.0.2",
|
|
136
|
+
"@agentproto/command-sandbox": "0.2.1",
|
|
137
|
+
"@agentproto/driver": "0.2.2",
|
|
138
|
+
"@agentproto/driver-agent-cli": "2.4.2",
|
|
139
|
+
"@agentproto/driver-http": "0.1.6",
|
|
140
|
+
"@agentproto/eval-reporters": "0.2.12",
|
|
141
|
+
"@agentproto/manifest": "0.2.2",
|
|
142
|
+
"@agentproto/mcp-server": "0.3.0",
|
|
143
|
+
"@agentproto/model-catalog": "0.9.4",
|
|
144
|
+
"@agentproto/provider-kit": "0.4.3",
|
|
145
|
+
"@agentproto/provider-presets": "0.6.2",
|
|
146
|
+
"@agentproto/providers-store": "0.3.13",
|
|
147
|
+
"@agentproto/redaction": "0.2.2",
|
|
148
|
+
"@agentproto/routine": "0.2.2",
|
|
149
|
+
"@agentproto/sandbox": "0.4.0",
|
|
150
|
+
"@agentproto/secrets": "0.2.5",
|
|
151
|
+
"@agentproto/telemetry-langfuse": "0.2.10",
|
|
152
|
+
"@agentproto/tool": "0.3.0",
|
|
153
|
+
"@agentproto/workflow": "0.6.0",
|
|
154
|
+
"@agentproto/workflow-loader": "0.2.2",
|
|
155
|
+
"@agentproto/workflow-runtime": "0.11.0",
|
|
156
|
+
"@agentproto/workspace-brain": "0.4.5"
|
|
150
157
|
},
|
|
151
158
|
"optionalDependencies": {
|
|
152
|
-
"@agentproto/sandbox-e2b": "0.
|
|
159
|
+
"@agentproto/sandbox-e2b": "0.4.2"
|
|
153
160
|
},
|
|
154
161
|
"devDependencies": {
|
|
155
|
-
"@types/node": "^25.
|
|
156
|
-
"@types/ws": "^8.
|
|
162
|
+
"@types/node": "^25.9.5",
|
|
163
|
+
"@types/ws": "^8.18.1",
|
|
157
164
|
"tsup": "^8.5.1",
|
|
158
165
|
"typescript": "^5.9.3",
|
|
159
166
|
"vite-node": "^3.2.4",
|
|
160
|
-
"vitest": "^3.2.
|
|
161
|
-
"@agentproto/rendezvous": "0.2.
|
|
162
|
-
"@agentproto/tooling": "0.1.0
|
|
163
|
-
"@agentproto/transcript-fixtures": "0.2.
|
|
167
|
+
"vitest": "^3.2.7",
|
|
168
|
+
"@agentproto/rendezvous": "0.2.3",
|
|
169
|
+
"@agentproto/tooling": "0.1.0",
|
|
170
|
+
"@agentproto/transcript-fixtures": "0.2.1"
|
|
164
171
|
},
|
|
165
172
|
"scripts": {
|
|
166
173
|
"dev": "tsup --watch",
|