@agentproto/runtime 5.1.0 → 5.3.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.
@@ -0,0 +1,748 @@
1
+ import { S as SpawnDefaultsConfig } from './spawn-defaults-Cp9SGZex.js';
2
+
3
+ /**
4
+ * Model roles — ONE place to say "which model does role X use".
5
+ *
6
+ * A role is a dotted name for a job a model does inside agentproto
7
+ * (`review.small`, `review.large`, `review.pr`, `judge.session`, …).
8
+ * Consumers (the repo-maintenance workflow, the session-steward, agent
9
+ * manifests via `model: role:<name>`, and later the CI reviewer and the
10
+ * local push gate) ask for the ROLE instead of hard-coding a model id, so
11
+ * "use GLM for review" is one config edit, not five files in three formats.
12
+ *
13
+ * PRECEDENCE — the single definition (highest first):
14
+ * 1. `input` an explicit workflow/run input (`reviewModelSmall`, …)
15
+ * 2. `workspace` the repo's own `agentproto.json` → `models`
16
+ * 3. `daemon` `~/.agentproto/config.json` → `models`
17
+ * 4. `default` {@link DEFAULT_MODEL_ROLES} below — the only built-in table
18
+ *
19
+ * Every resolution reports which layer won (`source`). A layer value that is
20
+ * empty or not a string / `{ model }` object is skipped, never fatal.
21
+ *
22
+ * This file is pure (no I/O) so the config schema can import it without a
23
+ * cycle; the loaders + the `model_roles` MCP tool live in
24
+ * `model-roles-tools.ts`.
25
+ */
26
+ /** Object form of a role value: a model id plus optional serving hints. */
27
+ interface ModelRoleEntry {
28
+ model: string;
29
+ /** Router/gateway the model should be reached through (e.g. `openrouter`). */
30
+ route?: string;
31
+ /** Named auth profile to spawn the role's session with. */
32
+ profile?: string;
33
+ }
34
+ /** A role value as written in a config file: a bare model id, or an entry. */
35
+ type ModelRoleValue = string | ModelRoleEntry;
36
+ /** ROLE → value, the shape of the `models` block in every layer. */
37
+ type ModelRolesConfig = Record<string, ModelRoleValue>;
38
+ type ModelRoleSource = "input" | "workspace" | "daemon" | "default";
39
+ /** The built-in defaults — the ONLY hard-coded model ids for these jobs. */
40
+ declare const DEFAULT_MODEL_ROLES: Readonly<Record<string, string>>;
41
+ /** Prefix an agent manifest's `model:` uses to reference a role. */
42
+ declare const MODEL_ROLE_REF_PREFIX = "role:";
43
+ interface ModelRoleContext {
44
+ /** Explicit per-run values keyed by role (layer 1). */
45
+ input?: Record<string, ModelRoleValue | undefined> | undefined;
46
+ /** The workspace/repo `models` block (layer 2). */
47
+ workspace?: Record<string, unknown> | undefined;
48
+ /** The daemon config `models` block (layer 3). */
49
+ daemon?: Record<string, unknown> | undefined;
50
+ }
51
+ interface ResolvedModelRole {
52
+ role: string;
53
+ model: string;
54
+ route?: string;
55
+ profile?: string;
56
+ source: ModelRoleSource;
57
+ }
58
+ /** `role:review.large` → `review.large`; undefined for a non-role string. */
59
+ declare function parseModelRoleRef(ref: string): string | undefined;
60
+ /**
61
+ * Resolve ONE role through the precedence chain. `undefined` only when no
62
+ * layer — including the built-in table — knows the role.
63
+ */
64
+ declare function resolveModelRole(role: string, ctx?: ModelRoleContext): ResolvedModelRole | undefined;
65
+ /**
66
+ * Every role in play — the built-in ones plus any a layer configures — each
67
+ * resolved with its source, sorted by role name. `roles` narrows the list
68
+ * (an unknown role with no value anywhere is simply omitted).
69
+ */
70
+ declare function listModelRoles(ctx?: ModelRoleContext, roles?: readonly string[]): ResolvedModelRole[];
71
+
72
+ /**
73
+ * `~/.agentproto/config.json` — single hand-editable JSON for the
74
+ * agentproto control plane's defaults. Sits alongside the existing
75
+ * surface files (workspaces.json, credentials.json, sessions.json):
76
+ *
77
+ * workspaces.json which directories are workspaces + which is active
78
+ * credentials.json tunnel host bearer tokens (mode 0600)
79
+ * sessions.json last-known snapshot of the registry (informational)
80
+ * config.json daemon defaults: port, bind, allowed origins,
81
+ * tunnel host, feature toggles
82
+ *
83
+ * Resolution order for every daemon knob is:
84
+ * 1. CLI flag (e.g. --port)
85
+ * 2. Env var (where one exists, e.g. AGENTPROTO_TOKEN)
86
+ * 3. config.json
87
+ * 4. Hardcoded default
88
+ *
89
+ * This means a user can call `agentproto config set daemon.port 18791`
90
+ * once and never re-pass `--port 18791` to `serve install` etc. CLI
91
+ * flags still win for one-off overrides.
92
+ *
93
+ * Schema is intentionally narrow + extensible — unknown keys are
94
+ * preserved on save (deep-merge), so a newer CLI writing a new
95
+ * field won't drop one an older CLI doesn't know about. No secrets
96
+ * here; credentials stay in credentials.json (mode 0600).
97
+ */
98
+
99
+ declare const CONFIG_VERSION: 1;
100
+ interface DaemonConfig {
101
+ /** Absolute path to the workspace the daemon binds to at boot. */
102
+ workspace?: string;
103
+ /** HTTP port. Default 18790. */
104
+ port?: number;
105
+ /** Bind addr. Default 127.0.0.1. */
106
+ bind?: string;
107
+ /** Trusted browser origins for mutating /sessions/* routes (in
108
+ * addition to the hardcoded localhost defaults). */
109
+ allowedOrigins?: string[];
110
+ /** When true, the daemon does NOT auto-trust localhost-on-any-port.
111
+ * Only origins explicitly listed in `allowedOrigins` are allowed.
112
+ * Pair with a curated list (e.g. `["http://localhost:3000"]`) for
113
+ * hardened setups. Default false. */
114
+ strictOrigins?: boolean;
115
+ /** Server label sent in tunnel hello frames. */
116
+ label?: string;
117
+ /** Bearer token gating the gateway at boot (`AuthOptions` with
118
+ * `mode: "bearer"`). Unlike `remote_enable`'s ephemeral quick-tunnel
119
+ * token, this one lives in config.json and survives daemon restarts.
120
+ * Set via `agentproto config set daemon.authToken <token>` (e.g.
121
+ * `$(openssl rand -hex 32)`). Unset ⇒ the gateway boots with
122
+ * `mode: "none"` — fully open on loopback, same as today. */
123
+ authToken?: string;
124
+ /** Opt-in eager resume-on-boot (session-survivability plan §5
125
+ * "Opt-in vs automatic", PR-4). When true, a daemon restart doesn't just
126
+ * leave agent-cli sessions dead-but-lazy-resumable — the boot pass eagerly
127
+ * re-spawns the eligible ones (those that died with
128
+ * `endedReason: "daemon-restart"` and still pass `canResume`) IN PLACE,
129
+ * restoring liveness without waiting for a prompt. Lazy resume-on-prompt is
130
+ * always on regardless of this flag (it costs nothing until someone acts);
131
+ * this flag only controls the *automatic* boot-time pass. Default false: a
132
+ * box-wide restart should not silently relaunch every adapter (cost, spawn
133
+ * storm, credential re-resolution), and default-off also keeps two daemons
134
+ * sharing the workspace buckets from racing to resume the same rows. Set via
135
+ * `agentproto config set daemon.resumeSessionsOnBoot true`. Surfaced in
136
+ * `daemon_health` / `GET /health`. */
137
+ resumeSessionsOnBoot?: boolean;
138
+ /** Opt-in continue-on-boot (continue-interrupted). When true, right after
139
+ * the eager resume pass, the boot sends a "the daemon restarted mid-turn,
140
+ * check the state on disk and continue" prompt to every session the LAST
141
+ * restart interrupted mid-turn (`interrupted: true`) that is resumable,
142
+ * under the resume cap, idle, and whose resume didn't fail. Never loops: at
143
+ * most one auto-continue per restart and `MAX_AUTO_CONTINUE_ATTEMPTS` in a
144
+ * row without a successful turn-end. Default false — the interrupted prompt
145
+ * itself is never auto-retried, and a continue prompt is still a prompt
146
+ * (tokens, side effects), so it's opt-in. The manual
147
+ * `session_continue_interrupted` verb works regardless. Set via
148
+ * `agentproto config set daemon.continueInterruptedOnBoot true`. */
149
+ continueInterruptedOnBoot?: boolean;
150
+ /** Idle agent-session reaper (PR-6). When set to a positive number of
151
+ * milliseconds, the daemon periodically retires agent-cli sessions that have
152
+ * been idle (not busy, not awaiting input, `status:"running"`) longer than
153
+ * this: it SIGTERMs the adapter child to free the process and flips the row
154
+ * to `killed`/`endedReason:"idle-reaped"` — dead-but-lazy-resumable (a later
155
+ * prompt revives it) and, critically, excluded from eager resume-on-boot
156
+ * (#638 gates on `endedReason === "daemon-restart"`), so enabling this makes
157
+ * eager resume safe: only genuinely-recent work survives a restart. Default
158
+ * OFF (unset / 0 / negative ⇒ the reaper never runs). Resolution order
159
+ * mirrors the module docblock: `AGENTPROTO_IDLE_REAP_AFTER_MS` env > this
160
+ * field > off. Set via `agentproto config set daemon.idleReapAfterMs
161
+ * <ms>`. Surfaced in `daemon_health` / `GET /health`. */
162
+ idleReapAfterMs?: number;
163
+ /** Crash-detect sweep interval in ms (crash-detect PR-1). Unlike
164
+ * `idleReapAfterMs`, this is DEFAULT ON (non-destructive observability):
165
+ * the daemon periodically probes every live agent-cli session's OS
166
+ * process (`process.kill(pid, 0)`) and, when it's provably gone with no
167
+ * exit event ever emitted for it, flips the row to
168
+ * `error`/`endedReason:"crashed"` with a `lastError` string — surfacing a
169
+ * death that would otherwise sit silently as `status:"running"` until the
170
+ * next prompt's RPC throws (or forever, for a parked session with no next
171
+ * prompt). Detects only; never restarts or notifies (later PRs). Set to a
172
+ * non-positive value to disable the sweep entirely. Resolution order
173
+ * mirrors `idleReapAfterMs`: `AGENTPROTO_CRASH_DETECT_INTERVAL_MS` env >
174
+ * this field > the hardcoded default (30s). Set via `agentproto config
175
+ * set daemon.crashDetectIntervalMs <ms>`. Surfaced in `daemon_health` /
176
+ * `GET /health`. */
177
+ crashDetectIntervalMs?: number;
178
+ /** Restart-scheduler sweep interval in ms (restart-scheduler PR-2). OFF by
179
+ * default (unset / 0 / negative ⇒ the sweep never runs), unlike
180
+ * `crashDetectIntervalMs` — a positive value here only arms the periodic
181
+ * sweep that EXECUTES an already-scheduled restart; it never opts a
182
+ * session in by itself (that's the per-session `agent_start.restartPolicy`
183
+ * field). Resolution order mirrors `idleReapAfterMs`:
184
+ * `AGENTPROTO_RESTART_SWEEP_INTERVAL_MS` env > this field > off. Set via
185
+ * `agentproto config set daemon.restartSweepIntervalMs <ms>`. Surfaced in
186
+ * `daemon_health` / `GET /health`. */
187
+ restartSweepIntervalMs?: number;
188
+ /** Turn-liveness watchdog threshold in ms (turn-liveness-watchdog
189
+ * chantier). Same shape as `crashDetectIntervalMs`: DEFAULT ON
190
+ * (non-destructive observability). The daemon periodically sweeps every
191
+ * BUSY agent-cli session and, for one that is mid-turn, NOT legitimately
192
+ * `blockedOn` a subagent/command, and has had no adapter activity
193
+ * (`lastActivityAt`) for longer than this threshold, stamps
194
+ * `stalledSinceMs` on the descriptor and emits `session:stalled` —
195
+ * surfacing a dead adapter stream (network drop, hung child — zero
196
+ * frames mid-turn) that would otherwise sit indistinguishable from
197
+ * healthy long work: `status:"running"`, `lastError:null`, no other
198
+ * signal. Conservative by design: a session legitimately blocked on a
199
+ * long tool call is NEVER a candidate, no matter how long it's silent —
200
+ * see `stall-watchdog.ts`'s docblock for why. Detects only; never kills
201
+ * or restarts (this is a later concern, if ever). Set to a non-positive
202
+ * value to disable the sweep entirely. Resolution order mirrors
203
+ * `idleReapAfterMs`: `AGENTPROTO_TURN_STALL_AFTER_MS` env > this field >
204
+ * the hardcoded default (5 min). Set via `agentproto config set
205
+ * daemon.turnStallAfterMs <ms>`. Surfaced in `daemon_health` /
206
+ * `GET /health`. */
207
+ turnStallAfterMs?: number;
208
+ }
209
+ interface TitlerConfig {
210
+ /** Enable the daemon-side session titler. Default false. */
211
+ enabled?: boolean;
212
+ /** OpenRouter model id used to generate titles. Default
213
+ * `z-ai/glm-5.2@openrouter`. Requires `OPENROUTER_API_KEY` in the
214
+ * daemon's environment; without a key the titler falls back to the
215
+ * local (first-prompt-line) title. */
216
+ model?: string;
217
+ }
218
+ interface CatalogSourceConfig {
219
+ /** HTTP(S) URL returning `{ entries: AppCatalogEntry[] }`. */
220
+ url: string;
221
+ }
222
+ interface CatalogConfig {
223
+ /** Remote app-catalog sources merged into `app_catalog`. When set, these
224
+ * win over `sources` in `~/.agentproto/app-catalog.json`. */
225
+ sources?: CatalogSourceConfig[];
226
+ }
227
+ interface TunnelConfig {
228
+ /** Cloud WS URL. When set + autoconnect=true, `agentproto serve`
229
+ * bootstraps with `--connect <host>`. */
230
+ host?: string;
231
+ /** apt_ daemon token to present at the tunnel upgrade. When set,
232
+ * `agentproto serve` uses this BEFORE falling back to
233
+ * credentials.json — handy in profiles where the token-per-host
234
+ * mapping in credentials.json doesn't fit (e.g. host = tunnel URL
235
+ * but credentials were minted against the api URL). */
236
+ token?: string;
237
+ /** Whether `agentproto daemon start` connects the tunnel by
238
+ * default. v0 only — implementer can ignore until daemon needs it. */
239
+ autoconnect?: boolean;
240
+ /**
241
+ * Opt into end-to-end encryption of the outbound `serve --connect` tunnel
242
+ * (design: tunnel-e2e/v1). When true, the daemon negotiates a
243
+ * token-authenticated ephemeral handshake with the host and wraps the tunnel
244
+ * frames in an AEAD box, so even the trusted host loses plaintext visibility.
245
+ * The handshake authenticates both ends against the shared `tunnel.token`, so
246
+ * `token` MUST also be set. Fully backward-compatible: if the host doesn't
247
+ * advertise e2e (an older host), the daemon falls back to today's plaintext
248
+ * tunnel. Unset/false ⇒ plaintext, byte-identical to today. */
249
+ e2e?: boolean;
250
+ }
251
+ interface FeaturesConfig {
252
+ /** Hint that PTY is desired — informational; the daemon still
253
+ * detects node-pty's presence at runtime. */
254
+ pty?: boolean;
255
+ /** Enable the local LLM Endpoint proxy sidecar (route registration,
256
+ * MCP tools, child-process lifecycle). Default false — the endpoint is
257
+ * an opt-in feature; when off, the `llm-endpoint` custom route is not
258
+ * registered and the `llm_endpoint_*` MCP tools are not exposed. */
259
+ llmEndpoint?: boolean;
260
+ /** Expose this daemon's local inference endpoint(s) (the `llmEndpoint`
261
+ * sidecar above) to paired controllers over a HOST-scoped pairing —
262
+ * `GET /device-inference/v1/models` + `POST
263
+ * /device-inference/v1/chat/completions` in http-server.ts. Default
264
+ * false: even with `llmEndpoint` on, a plain remote-control pairing (or
265
+ * `serve --connect`) never gets these routes regardless of this flag —
266
+ * it also requires the pairing itself to be host-scoped (`pair offer
267
+ * --host` + `devices add`). Toggle via `agentproto devices
268
+ * share-inference on|off`. */
269
+ deviceInferenceShare?: boolean;
270
+ /** Opt this daemon in to being usable as an `agent_start({ sandbox:
271
+ * "device:<name>" })` target (DEVICES-PLAN PR-D) — exposes
272
+ * `/device-spawn/*` in http-server.ts, a self-proxy onto this daemon's
273
+ * own `/mcp` + `/sessions/:id/events/stream`. Default false: even with
274
+ * this on, only a HOST-scoped pairing (`pair offer --host` + `devices
275
+ * add`) can reach the routes — a plain remote-control pairing never can,
276
+ * whatever this is set to. Toggle via `agentproto devices allow-spawn
277
+ * on|off`. */
278
+ deviceSpawnAllow?: boolean;
279
+ }
280
+ /**
281
+ * Policy for `agentproto worktree new` (PLAN.md §1.4 — config carries
282
+ * policy, never state; git itself is the authority for which worktrees
283
+ * exist). This is the fix for the sprawl the plan measured: 31 linked
284
+ * worktrees across 6 different parent directories, because there was no
285
+ * `worktree new` verb and therefore no convention to converge on.
286
+ */
287
+ /**
288
+ * How the daemon isolates a freshly-spawned agent session into its own git
289
+ * worktree (`agent_start.worktree`):
290
+ * - `"always"` — every depth-0 spawn is provisioned into a worktree,
291
+ * whether or not the caller asked. A cwd that is not in
292
+ * a git repo has nothing to isolate, so it spawns plain.
293
+ * - `"on-request"` — isolate ONLY when the caller passes `worktree`. This
294
+ * is the default and the back-compatible behaviour:
295
+ * today's callers pass nothing and spawn exactly where
296
+ * they asked.
297
+ * - `"never"` — isolation is off; an explicit `worktree` field is
298
+ * REJECTED (loud, not silently ignored) so a caller
299
+ * never believes it got an isolated tree it didn't.
300
+ */
301
+ type WorktreeIsolationMode = "always" | "on-request" | "never";
302
+ interface WorktreesConfig {
303
+ /**
304
+ * Absolute path new worktrees are created under. Layout:
305
+ * `<root>/<repoName>/<slug>`. Resolution order (mirrors every other
306
+ * knob in this file, see the module docblock): `--root` flag >
307
+ * `AGENTPROTO_WORKTREES_ROOT` env > this field > the hardcoded default
308
+ * `~/.agentproto/worktrees`. The default is a real single root, not
309
+ * "unconfigured" — `worktree new` converges to one place with zero
310
+ * setup, which is the only way the sprawl actually stops (the 6 roots
311
+ * that exist today are 6 people each inventing a default by hand).
312
+ */
313
+ root?: string;
314
+ /**
315
+ * Policy for `agent_start.worktree` isolation. Resolution order mirrors
316
+ * the module docblock (there is no CLI flag — this is a daemon-side
317
+ * policy read at spawn, not a per-invocation flag):
318
+ * `AGENTPROTO_WORKTREES_ISOLATION` env > this field > the hardcoded
319
+ * default `"on-request"`. `"on-request"` is deliberately the default:
320
+ * any other would break back-compat by isolating callers that never
321
+ * asked (see `worktree-isolation.ts`).
322
+ */
323
+ isolation?: WorktreeIsolationMode;
324
+ /**
325
+ * Max worktree provisionings running their HEAVY phases (`cloneGlobs`,
326
+ * `depsCmd`, `copyGlobs`, the `worktree.setup` hooks) at once, daemon-wide.
327
+ * A burst of `agent_start({worktree})` spawns otherwise runs that many
328
+ * concurrent `pnpm install`s against one store on one disk (the 2026-09-28
329
+ * incident: load average 170, installs 3.5x slower). The rest wait in a
330
+ * FIFO queue, fair across spawning callers. `git worktree add` and the
331
+ * cheap prep stay unthrottled. Resolution order:
332
+ * `AGENTPROTO_WORKTREES_PROVISION_CONCURRENCY` env > this field > the
333
+ * default `2`. An integer >= 1; `0` means unlimited (the pre-queue
334
+ * behavior). Invalid values fall back to the default.
335
+ */
336
+ provisionConcurrency?: number;
337
+ /**
338
+ * Per-repo overrides of {@link provisionConcurrency}, additional to (never
339
+ * looser than) the global cap. Keys are an absolute repo-root path (`~/`
340
+ * expands) or a bare repo directory name; values as above. Example:
341
+ * `{ "agentik-studio": 1 }` serializes installs of that repo even when
342
+ * other repos may install two at a time.
343
+ */
344
+ provisionConcurrencyByRepo?: Record<string, number>;
345
+ /**
346
+ * Optional load guard: hold back starting a NEW heavy segment while the
347
+ * 1-minute load average exceeds `cores x provisionLoadFactor`. Unset / `0`
348
+ * = off (the default). Never blocks when nothing is running, so a loaded
349
+ * host cannot deadlock the queue.
350
+ */
351
+ provisionLoadFactor?: number;
352
+ }
353
+ /**
354
+ * Policy for `agent_start.attach` — whether a spawn AUTO-attaches to its
355
+ * calling session as parent lineage. `"always"` (the default) nests a
356
+ * spawned child under the session that spawned it whenever that identity is
357
+ * derivable (the trusted `?callerSessionId=` on the daemon self-ref URL, or
358
+ * an explicit `parentSessionId` hint), so a supervisor's executors stop
359
+ * landing as depth-0 orphans. `"on-request"` reverts to the pre-attach
360
+ * behaviour: auto-attribution is off, and a child nests only when the caller
361
+ * explicitly opts in (`attach: true` / `attach: { parent }` / an explicit
362
+ * `parentSessionId`). Either way a per-call `attach: false` forces an
363
+ * independent root, exactly as `worktree: false` opts out of isolation. See
364
+ * `spawn-attach.ts`.
365
+ */
366
+ type SpawnAttachMode = "always" | "on-request";
367
+ /**
368
+ * Policy for `agent_start`'s IMPLICIT dedupe — what happens when a caller
369
+ * spawns with NO `idempotencyKey` at all (see `spawn-dedupe.ts`):
370
+ * - `"always"` — the daemon DERIVES an implicit key from the spawn's
371
+ * `label` (required — see below) plus a hash of the
372
+ * initial `prompt`, and dedupes a same-adapter/cwd
373
+ * repeat against it exactly as an explicit key would.
374
+ * This is the default, mirroring `attach`'s own
375
+ * "opt-in-only guard is not a guard" precedent above.
376
+ * Deriving needs a `label` to produce anything at all
377
+ * — an unlabelled spawn is untouched, which is what
378
+ * keeps this safe for the fan-out pattern this repo
379
+ * exercises (several agents into one cwd with no
380
+ * shared label): see `spawn-dedupe.ts`'s docblock for
381
+ * the full false-dedup analysis, and PR #803's own
382
+ * no-opt-in label+cwd warning backstop in
383
+ * `session-spawn.ts`, which independently landed on
384
+ * the same label-is-the-signal boundary.
385
+ * - `"on-request"` — no implicit derivation; only an explicit
386
+ * `idempotencyKey` dedupes (today's behaviour,
387
+ * unchanged). A per-call `dedupe: true` still opts in
388
+ * under this policy, mirroring `attach: true`.
389
+ * Either way a per-call `dedupe: false` disables implicit derivation for
390
+ * that one spawn regardless of policy — the escape hatch, mirroring
391
+ * `attach: false` / `worktree: false`. An explicit `idempotencyKey` always
392
+ * wins over a derived one (derivation is only attempted when the caller
393
+ * supplied none).
394
+ */
395
+ type SpawnDedupeMode = "always" | "on-request";
396
+ interface SpawnConfig {
397
+ /**
398
+ * Attach policy for `agent_start`. Resolution order mirrors the module
399
+ * docblock (no CLI flag — a daemon-side policy read at spawn):
400
+ * `AGENTPROTO_SPAWN_ATTACH` env > this field > the hardcoded default
401
+ * `"always"`. Unlike `worktrees.isolation` (whose default preserves
402
+ * back-compat by NOT isolating), attach defaults ON: an orphaned executor
403
+ * is a bug, not a feature, and the auto-parent is descriptor-only lineage
404
+ * that never relaxes a privilege gate.
405
+ */
406
+ attach?: SpawnAttachMode;
407
+ /**
408
+ * Implicit-dedupe policy for `agent_start`. Resolution order mirrors
409
+ * `attach` above (no CLI flag — a daemon-side policy read at spawn):
410
+ * `AGENTPROTO_SPAWN_DEDUPE` env > this field > the hardcoded default
411
+ * `"always"`. See {@link SpawnDedupeMode} and `spawn-dedupe.ts` for the
412
+ * full reasoning: a retry-safety guard that only works when a caller
413
+ * remembers to ask for it (`idempotencyKey`) is not a guard — the same
414
+ * argument `attach` already settled for parent lineage.
415
+ */
416
+ dedupe?: SpawnDedupeMode;
417
+ }
418
+ /**
419
+ * Provenance policy — the opt-in `gh` PATH shim (`gh-provenance-shim.ts`).
420
+ * Off by default. When `wrapGh` is on, every agent session the daemon spawns
421
+ * gets a shim directory prepended to its PATH so that any `gh pr create` the
422
+ * session (or an adapter subprocess shelling out — claude-code, codex, …) runs
423
+ * has a deterministic `@agentproto-bot` provenance footer appended to the
424
+ * created PR's BODY, matching the footer the cloud runner stamps. The TOOL
425
+ * stamps, never the model; commit messages are never touched (the repo's
426
+ * hygiene-check forbids attribution there). Resolution order mirrors
427
+ * `spawn.attach`: `AGENTPROTO_PROVENANCE_WRAP_GH` env > this field > default
428
+ * `false`.
429
+ */
430
+ interface ProvenanceConfig {
431
+ /** Enable the opt-in `gh` provenance PATH shim for spawned sessions. */
432
+ wrapGh?: boolean;
433
+ }
434
+ /**
435
+ * Approvals policy (`packages/runtime/src/approvals/`, E1a) — config for
436
+ * the `web_click` human decision channel. See `AIP-7-AMENDMENT.md` for the
437
+ * full channel design.
438
+ */
439
+ interface ApprovalsConfig {
440
+ /**
441
+ * Origins allowed to decide a pending approval through `POST
442
+ * /approvals/:id/decision` (in addition to the daemon's own per-boot
443
+ * bearer token, which is ALWAYS required on that route regardless of
444
+ * this list). Default empty — `web_click` is OFF until at least one
445
+ * origin is configured. Lockout-shaped like `daemon.allowedOrigins`,
446
+ * but a SEPARATE list: the general browser-origin allowlist is not
447
+ * automatically trusted to decide approvals.
448
+ */
449
+ webOrigins?: string[];
450
+ }
451
+ /**
452
+ * Sentinel auto-link policy (AIP-60 §6, step 4) — whether a PR an executor
453
+ * session opens gets a sentinel created for it automatically.
454
+ */
455
+ interface SentinelConfig {
456
+ /** Auto-watch a PR when a session opens one (`recordOpenedPr`, fed by
457
+ * `command_execute`'s stamper and both `pr-provenance-reconciler.ts`
458
+ * lanes). When unset, the daemon defaults to `true` only when the
459
+ * `local-gh` provider is available (i.e. `gh auth status` succeeds) —
460
+ * otherwise `false`, logged once. Explicitly setting this always wins
461
+ * over that probe. Per-spawn opt-out: `agent_start.sentinel: false`. */
462
+ autoWatchPrs?: boolean;
463
+ }
464
+ /**
465
+ * Review-primitive host policy (`@agentproto/review` + `review-runner.ts`).
466
+ */
467
+ interface ReviewConfig {
468
+ /** The owner identity attestation signatures claim (`attestor.signature.
469
+ * principal`) — see `review-signing.ts`'s `resolvePrincipal`. When unset,
470
+ * the daemon falls back to `git config user.email` of the reviewed repo,
471
+ * then a host-derived value; this never blocks signing, it only lets an
472
+ * operator pin one identity across every repo the daemon reviews. */
473
+ principal?: string;
474
+ }
475
+ /**
476
+ * Session-presence policy — how the dashboard triages a session into its
477
+ * `running` / `tending` / `attention` / `quiet` presence state (see
478
+ * `session-presence.ts`). Optional block on `AgentprotoConfig.sessions`.
479
+ */
480
+ interface SessionsConfig {
481
+ /**
482
+ * How long (seconds) a session stays shown as `running` after its last
483
+ * turn ends, before it settles into `attention`/`quiet`. The grace window
484
+ * is measured from the session's most recent `lastActivityAt` — ANY new
485
+ * event resets it — so a session that just answered does not read as
486
+ * "quiet" the instant its turn finishes, but something that went idle long
487
+ * ago, with nothing waiting on it, settles grey. Resolution order mirrors
488
+ * every other knob in this file (module docblock): env var
489
+ * `AGENTPROTO_SESSIONS_ATTENTION_DELAY_SEC` > this field > the hardcoded
490
+ * default `60`. Absent ⇒ 60s.
491
+ */
492
+ attentionDelaySec?: number;
493
+ /**
494
+ * Root directory the daemon stores per-session transcripts under — each
495
+ * session's `events.jsonl` lands at `<eventsDir>/<sessionId>/events.jsonl`
496
+ * (and a terminal session's `terminal.jsonl` alongside it). Every
497
+ * reader/writer (transcript writer, `/sessions/:id/events` HTTP routes,
498
+ * exports, tool-call/usage logs) resolves through this single knob.
499
+ * Resolution order mirrors every other knob in this file: relative paths
500
+ * resolve against the home directory. Absent ⇒ the hardcoded default
501
+ * `~/.agentproto/sessions` — zero behaviour change out of the box. A
502
+ * change requires a daemon restart to take effect (and existing sessions'
503
+ * transcripts do NOT move).
504
+ */
505
+ eventsDir?: string;
506
+ }
507
+ /**
508
+ * Policy for the daemon's own AGENTS.md resolution + injection (WP-R2) —
509
+ * how a spawned session's initial prompt carries the repo's `AGENTS.md`
510
+ * (see `agents-md.ts`). The daemon resolves the nearest `AGENTS.md` walking
511
+ * up from the session's `cwd`, bounded by that repo's git toplevel, and
512
+ * injects one AGENTS.md block after the role disposition whenever the spawn
513
+ * carries a prompt.
514
+ */
515
+ interface AgentsMdConfig {
516
+ /**
517
+ * Inline-vs-pointer threshold in KiB. A resolved AGENTS.md whose byte size
518
+ * is STRICTLY LESS than `inlineMaxKb` KiB is inlined in full into the
519
+ * spawn's initial prompt, clearly delimited; `>=` the threshold is injected
520
+ * as a short pointer telling the agent to read the file before its first
521
+ * tool call instead. An absent AGENTS.md (nothing found up the walk) injects
522
+ * nothing but is still reported on the descriptor via
523
+ * `SessionDescriptor.agentsMdMode === "absent"`. Resolution order mirrors
524
+ * the module docblock (no CLI flag, and no env override — not part of the
525
+ * approved design): this field > the hardcoded default `8` (KiB).
526
+ */
527
+ inlineMaxKb?: number;
528
+ }
529
+ interface PairingConfig {
530
+ /** Rendezvous broker WS URL (ws:// or wss://) used by `pair offer` and by
531
+ * autoconnect on boot. When unset, `pair offer` requires an explicit
532
+ * `--rendezvous`. Mirrors `tunnel.host`. */
533
+ rendezvous?: string;
534
+ /** Whether the daemon opens standing rendezvous connections for every
535
+ * persisted pairing on boot (so a paired client can reconnect anytime).
536
+ * Mirrors `tunnel.autoconnect`. Default true when a rendezvous is set. */
537
+ autoconnect?: boolean;
538
+ /** Web pair page for `pair offer --qr` (the offer rides in its fragment).
539
+ * A template with `{fp}` in the HOSTNAME, filled with the daemon
540
+ * fingerprint (one origin per daemon), or a plain http(s) URL (one shared
541
+ * origin). Unset → `https://{fp}.agentproto.cloud/pair`. `--pair-page`
542
+ * overrides it. */
543
+ pairPage?: string;
544
+ }
545
+ /**
546
+ * A user-defined generic ACP agent — the config-file half of
547
+ * `AcpAgentSpec` (the slug is the record key in `acpAgents`, so it's
548
+ * omitted here). Any CLI that already speaks the Agent Client Protocol
549
+ * can be wired with zero code by declaring one of these under
550
+ * `acpAgents.<slug>` in `~/.agentproto/config.json`; the CLI's
551
+ * `acpHandleFromSpec` mints a runnable `AgentCliHandle` from it at
552
+ * resolve time (see `packages/cli/src/registry/acp-generic.ts`). Kept
553
+ * in this package (not the CLI's) so `config.ts` stays the single
554
+ * source of truth for the config surface without a cli→runtime→cli
555
+ * import cycle — the CLI's `AcpAgentSpec` extends this shape.
556
+ */
557
+ interface AcpAgentConfigEntry {
558
+ /** Display name. Defaults to the slug when omitted. */
559
+ name?: string;
560
+ /** One-line description surfaced in `agentproto acp ls`. */
561
+ description?: string;
562
+ /** Executable to spawn, e.g. "gemini". */
563
+ bin: string;
564
+ /** Extra argv appended after `bin`, e.g. ["--experimental-acp"]. */
565
+ bin_args?: string[];
566
+ /** Extra environment variables for the spawned process. */
567
+ env?: Record<string, string>;
568
+ /** Flag the CLI uses to receive the working directory, if it needs
569
+ * one passed explicitly (most ACP agents take cwd over the wire). */
570
+ cwd_flag?: string;
571
+ /** When true, advertise resumable + native-resume continuation. */
572
+ resumable?: boolean;
573
+ /** Known model ids for the agent (informational + validation hints). */
574
+ models?: {
575
+ default?: string;
576
+ allowed?: string[];
577
+ };
578
+ /** Billing endpoint this CLI's own auth bills (e.g. "mistral",
579
+ * "moonshot", "google") — the provider whose wallet/auth-profile the
580
+ * agent consumes. Lets clients link the harness to that provider's
581
+ * wallets even when no model list is declared. Unset when the CLI's
582
+ * billing target isn't a single known endpoint. */
583
+ provider?: string;
584
+ /** Shown when `bin` is missing from PATH (how to install the CLI). */
585
+ install_hint?: string;
586
+ }
587
+ /**
588
+ * Per-environment connection bundle. A profile overrides specific
589
+ * fields of the top-level `daemon` / `tunnel` / `features` blocks
590
+ * when selected via `--profile <name>` (or the top-level
591
+ * `activeProfile` setting). Missing fields fall through to the
592
+ * top-level config, so a profile only needs to declare what's
593
+ * different — typically just `tunnel.host` + `tunnel.token`.
594
+ *
595
+ * Example:
596
+ * {
597
+ * "daemon": { "workspace": "/code", "port": 18790 },
598
+ * "activeProfile": "local",
599
+ * "profiles": {
600
+ * "local": { "tunnel": { "host": "ws://localhost:3200/connect",
601
+ * "token": "apt_local", "autoconnect": true } },
602
+ * "prod": { "tunnel": { "host": "wss://tunnel.guilde.work/connect",
603
+ * "token": "apt_prod", "autoconnect": true },
604
+ * "daemon": { "port": 18791 } }
605
+ * }
606
+ * }
607
+ *
608
+ * Sandbox daemons generate per-sandbox profile entries at provision
609
+ * time so the daemon inside the sandbox boots with
610
+ * `agentproto serve --profile sandbox-<id>` and no extra plumbing.
611
+ */
612
+ interface ProfileConfig {
613
+ daemon?: DaemonConfig;
614
+ tunnel?: TunnelConfig;
615
+ features?: FeaturesConfig;
616
+ }
617
+ /**
618
+ * A user-defined named terminal/TUI preset stored in
619
+ * `~/.agentproto/config.json` under `terminalPresets`. Presets keep
620
+ * local launch recipes (argv, env, cwd, name/label) out of shared
621
+ * adapter manifests — e.g. pointing a Claude Code TUI at a local
622
+ * LLM gateway without retyping proxy env vars every spawn.
623
+ */
624
+ interface TerminalPreset {
625
+ /** Command + args to spawn. When provided, `sessions terminal` can
626
+ * be used without `-- <argv...>`. */
627
+ argv?: string[];
628
+ /** Extra environment variables layered on top of the daemon's
629
+ * inherited process.env. Values MUST be strings. */
630
+ env?: Record<string, string>;
631
+ /** Working directory for the PTY session. Relative paths are
632
+ * resolved against the current working directory at CLI time. */
633
+ cwd?: string;
634
+ /** Workspace slug used for cwd fallback when `cwd` is omitted. */
635
+ workspace?: string;
636
+ /** Stable session name passed to the registry (`name` field). */
637
+ name?: string;
638
+ /** Human-readable label surfaced in session listings. */
639
+ label?: string;
640
+ }
641
+ interface AgentprotoConfig {
642
+ version?: number;
643
+ daemon?: DaemonConfig;
644
+ tunnel?: TunnelConfig;
645
+ features?: FeaturesConfig;
646
+ /** E2E daemon-pairing defaults (rendezvous URL + autoconnect). */
647
+ pairing?: PairingConfig;
648
+ /** Where `agentproto worktree new` creates worktrees. See
649
+ * {@link WorktreesConfig}. */
650
+ worktrees?: WorktreesConfig;
651
+ /** Spawn-time policy (`agent_start`). See {@link SpawnConfig}. */
652
+ spawn?: SpawnConfig;
653
+ /** Session-presence policy for the dashboard. See {@link SessionsConfig}. */
654
+ sessions?: SessionsConfig;
655
+ /** Provenance policy — the opt-in `gh` PATH shim. See {@link ProvenanceConfig}. */
656
+ provenance?: ProvenanceConfig;
657
+ /** Approvals policy — the `web_click` origin allowlist. See {@link ApprovalsConfig}. */
658
+ approvals?: ApprovalsConfig;
659
+ /** Sentinel auto-link policy. See {@link SentinelConfig}. */
660
+ sentinel?: SentinelConfig;
661
+ /** Review-primitive host policy. See {@link ReviewConfig}. */
662
+ review?: ReviewConfig;
663
+ /** Daemon-side AGENTS.md resolution/injection policy. See {@link AgentsMdConfig}. */
664
+ agentsMd?: AgentsMdConfig;
665
+ /** Model roles: ROLE → model id (or `{ model, route?, profile? }`), the
666
+ * one place reviewer/judge models are configured. Layer 3 of the
667
+ * precedence documented in `model-roles.ts` (input > workspace
668
+ * `agentproto.json` > this > built-in default). Role names contain dots,
669
+ * so `getConfigKey`/`setConfigKey` treat everything after `models.` as
670
+ * ONE key (`models.review.pr` → `models["review.pr"]`). */
671
+ models?: ModelRolesConfig;
672
+ /** Daemon-side session titler (`session-titler.ts`). DEFAULT OFF — when
673
+ * `enabled` is not explicitly true the titler is a complete no-op and
674
+ * nothing renames anything. When on, at the end of the FIRST completed
675
+ * turn of a `kind:"agent-cli"` session whose label is still a spawn
676
+ * default (`chat-starter`, `chat-starter-autoprompt`, `agentproto`, empty,
677
+ * or equal to the derived title), the daemon generates a short prosaic
678
+ * title (4-8 words) from the first turn's transcript and renames the
679
+ * session via the same write path as `session_rename`. Generation tries
680
+ * OpenRouter (`model`, default `z-ai/glm-5.2@openrouter`, needs
681
+ * `OPENROUTER_API_KEY`) and falls back locally (first line of the first
682
+ * user prompt, 6 whole words) on any failure. A user-created label is
683
+ * NEVER overwritten, and a session is titled at most once. */
684
+ titler?: TitlerConfig;
685
+ /** App catalog settings (`app-catalog.ts`). See {@link CatalogConfig}. */
686
+ catalog?: CatalogConfig;
687
+ /** Named connection profiles. See `ProfileConfig` for the merge
688
+ * semantics — a profile's fields shallow-override the top-level
689
+ * defaults for the selected run. */
690
+ profiles?: Record<string, ProfileConfig>;
691
+ /** Profile name to use when `--profile` isn't passed. When unset,
692
+ * the top-level `daemon` / `tunnel` blocks are used directly. */
693
+ activeProfile?: string;
694
+ /** Default `skills` + `options` auto-applied to every `agent_start`
695
+ * spawn — global and per-adapter. See `resolveSpawnDefaults` in
696
+ * `spawn-defaults.ts` for the merge precedence with an explicit call.
697
+ * Absent ⇒ current behaviour exactly (no regression). */
698
+ defaults?: SpawnDefaultsConfig;
699
+ /** User-defined generic ACP agents, keyed by adapter slug. Each entry
700
+ * is minted into a runnable handle by the CLI's `acpHandleFromSpec`
701
+ * when `resolveAdapter(slug)` finds no npm adapter package. User
702
+ * entries shadow the curated `ACP_CATALOG` on slug collision. */
703
+ acpAgents?: Record<string, AcpAgentConfigEntry>;
704
+ /** User-defined named terminal/TUI presets. Local-only; never
705
+ * packaged in shared adapter manifests or defaults. */
706
+ terminalPresets?: Record<string, TerminalPreset>;
707
+ /** Unknown keys preserved across save round-trips. */
708
+ [unknown: string]: unknown;
709
+ }
710
+ declare const CONFIG_FILE_PATH: () => string;
711
+ /**
712
+ * Load config.json. Returns an empty object (NOT null) when the file
713
+ * is missing, malformed, or unreadable — callers can `cfg.daemon?.port`
714
+ * safely without null-guards. Errors during a malformed-read are
715
+ * logged once so the user notices the file is broken without the
716
+ * daemon refusing to boot.
717
+ */
718
+ declare function loadConfig(path?: string): Promise<AgentprotoConfig>;
719
+ /**
720
+ * Write config.json atomically (tmp + rename) so a concurrent
721
+ * `agentproto config edit` can't half-truncate the file. Writes
722
+ * `next` AS-IS — callers are expected to pass the full desired
723
+ * state (loaded the existing config, mutated, passed it back).
724
+ *
725
+ * Earlier versions deep-merged with the on-disk file, but that made
726
+ * deletions impossible: `setConfigKey(cfg, "x", undefined)` would
727
+ * remove the key from memory, then the deep-merge would silently
728
+ * re-add it from disk. The current design trusts the caller's
729
+ * snapshot and uses atomic rename for crash safety.
730
+ */
731
+ declare function saveConfig(next: AgentprotoConfig, path?: string): Promise<void>;
732
+ /** Split a dotted key into path segments. `models.<role>` is the one
733
+ * exception: role names are themselves dotted (`review.pr`), so everything
734
+ * after `models.` is a single segment. */
735
+ declare function splitConfigPath(dotted: string): string[];
736
+ /**
737
+ * Read a dot-notation key (`daemon.port`) out of a config. Returns
738
+ * `undefined` when any segment is missing.
739
+ */
740
+ declare function getConfigKey(cfg: AgentprotoConfig, dotted: string): unknown;
741
+ /**
742
+ * Set a dot-notation key in a config. Returns a new object — does
743
+ * NOT mutate. Creates intermediate objects as needed. Setting
744
+ * `value: undefined` is treated as a delete.
745
+ */
746
+ declare function setConfigKey(cfg: AgentprotoConfig, dotted: string, value: unknown): AgentprotoConfig;
747
+
748
+ export { type ApprovalsConfig as A, loadConfig as B, type CatalogConfig as C, type DaemonConfig as D, saveConfig as E, type FeaturesConfig as F, setConfigKey as G, splitConfigPath as H, MODEL_ROLE_REF_PREFIX as M, type PairingConfig as P, type ResolvedModelRole as R, type SpawnConfig as S, type TunnelConfig as T, type WorktreesConfig as W, type SessionsConfig as a, type ProvenanceConfig as b, type AgentsMdConfig as c, type TitlerConfig as d, type ProfileConfig as e, type AcpAgentConfigEntry as f, type TerminalPreset as g, type WorktreeIsolationMode as h, type SpawnAttachMode as i, type SpawnDedupeMode as j, DEFAULT_MODEL_ROLES as k, type ModelRoleContext as l, type ModelRoleEntry as m, type ModelRoleSource as n, type ModelRoleValue as o, type ModelRolesConfig as p, listModelRoles as q, parseModelRoleRef as r, resolveModelRole as s, type AgentprotoConfig as t, CONFIG_FILE_PATH as u, CONFIG_VERSION as v, type CatalogSourceConfig as w, type ReviewConfig as x, type SentinelConfig as y, getConfigKey as z };