@agentproto/runtime 0.4.0 → 0.6.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/dist/index.d.ts CHANGED
@@ -1,15 +1,136 @@
1
1
  import { DoctypeSpec } from '@agentproto/manifest';
2
2
  import { B as BuildHeartbeatAgent } from './heartbeat-COGpMrJS.js';
3
3
  export { H as HeartbeatAgent, a as HeartbeatRunner, R as RuntimeEvent, b as RuntimeEvents, p as parseDuration } from './heartbeat-COGpMrJS.js';
4
- import { AcpMcpServer } from '@agentproto/acp';
4
+ import { AcpPermissionResolution, AcpMcpServer } from '@agentproto/acp';
5
5
  import { ChildProcess } from 'node:child_process';
6
+ import { R as ResolvedAuthSpec, D as DeclaredAdapterOption, A as AdapterAuthDescriptor } from './spawn-defaults-DAbADRd4.js';
7
+ export { a as AuthEcho, b as AuthResolutionError, C as CredentialSource, c as DefaultsAdapterAuthConfig, d as DefaultsAdapterConfig, e as ResolvedSpawnAuthMaterial, f as ResolvedSpawnDefaults, S as SpawnDefaultsConfig, g as credentialFingerprint, n as normalizeSkillsOption, r as resolveAuthSpec, h as resolveSpawnDefaults } from './spawn-defaults-DAbADRd4.js';
6
8
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
- import { AdapterHandle, AdapterLister } from '@agentproto/adapter-kit';
9
+ import { AdapterHandle, SetupField, AdapterResolver, AdapterLister, AdapterEntry } from '@agentproto/provider-kit';
10
+ import { SandboxProvider } from '@agentproto/sandbox';
11
+ import { FrameSink, E2eFrameSink } from '@agentproto/acp/tunnel';
12
+ import { DaemonIdentity } from '@agentproto/secrets/identity';
8
13
  import { WorkspaceFs } from './workspace-fs.js';
9
14
  export { createWorkspaceFs } from './workspace-fs.js';
10
15
  export { ConversationMeta, ConversationStore, ConversationTurn, fileConversationStore } from './conversations.js';
16
+ import { ProviderPreset } from '@agentproto/provider-presets';
11
17
  import { StepCache } from '@agentproto/workflow-runtime';
12
- export { PROVIDER_ENV_VARS, ProviderEntry, ProvidersFile, injectProviderKeysIntoEnv, loadProviders, providerEnvVar, providersPath, removeProviderKey, setProviderKey } from './providers-store.js';
18
+ export { PROVIDER_ENV_VARS, ProviderEntry, ProvidersFile, getProviderKey, injectProviderKeysIntoEnv, loadProviders, providerEnvVar, providersPath, removeProviderKey, setProviderKey } from '@agentproto/providers-store';
19
+ import '@agentproto/model-catalog';
20
+
21
+ /**
22
+ * Per-session JSONL record of a completed `command_execute` invocation
23
+ * (and cron's `kind: "command"` action jobs, which share the same
24
+ * allowlist + `runCommand` path — see cron-scheduler.ts's "one
25
+ * enforcement path, not two" comment).
26
+ *
27
+ * Each call gets its OWN `kind: "command"` session, minted via
28
+ * `SessionsRegistry.recordCommand` — same as an agent-cli or PTY session,
29
+ * so it shows up in `session_list` / `command_list` for free instead of
30
+ * needing a bespoke query surface. Its result is stored at the exact
31
+ * per-id path an agent-cli session's structured transcript already uses:
32
+ * `~/.agentproto/sessions/<id>/events.jsonl` (see transcript-writer.ts).
33
+ * A command session's file holds exactly one line — there's no seq/turn
34
+ * machinery to reconstruct because the call is already finished by the
35
+ * time this is written.
36
+ *
37
+ * This module only knows how to read/write that one line; minting the
38
+ * session itself (and deciding the file's base directory) is
39
+ * `sessions.ts`'s job via `recordCommand`.
40
+ */
41
+ interface CommandLogEntry {
42
+ ts: string;
43
+ command: string;
44
+ args: string[];
45
+ cwd: string;
46
+ exitCode: number;
47
+ signal: string | null;
48
+ durationMs: number;
49
+ stdout: string;
50
+ stderr: string;
51
+ truncated?: boolean;
52
+ }
53
+
54
+ /**
55
+ * Per-session usage snapshot — the single place that decides a session's
56
+ * `costUsd` and where that number came from.
57
+ *
58
+ * Two cost sources feed a session:
59
+ * - an adapter's own usage reader (`readUsage`, e.g. hermes reads its
60
+ * state.db) or an ACP `usage_update` that carries a `cost` block — these
61
+ * are authoritative, tagged `source: "adapter"`.
62
+ * - raw input/output token counts with NO adapter cost (ACP adapters like
63
+ * claude-code / mastracode that report tokens but not dollars) — here we
64
+ * price them ourselves against agentproto's in-repo LLM pricing catalog,
65
+ * tagged `source: "computed"`.
66
+ *
67
+ * When tokens exist but the model is absent from the catalog we NEVER
68
+ * fabricate a price: `costUsd` is left undefined and the snapshot is tagged
69
+ * `source: "no-pricing"`. A session with neither cost nor tokens is
70
+ * `source: "none"`.
71
+ *
72
+ * This module is pure (the catalog lookup is injected) so the decision tree is
73
+ * unit-testable without building the whole model catalog.
74
+ */
75
+ /** Where a session's `costUsd` came from — see the module doc. */
76
+ type UsageSource = "adapter" | "computed" | "no-pricing" | "none";
77
+ /** Per-token USD prices for a model (per 1M tokens), the subset of the
78
+ * catalog's `LLMPricing` this module needs. */
79
+ interface TokenPricing {
80
+ inputPer1M: number;
81
+ outputPer1M: number;
82
+ }
83
+ /** Pluggable pricing accessor — defaults to the in-repo catalog's
84
+ * `resolvePricing`, overridable in tests. Returns undefined for an
85
+ * unknown model (the caller then tags `no-pricing`). */
86
+ type PricingResolver = (model: string) => TokenPricing | undefined;
87
+ /** Raw signals gathered over a turn, handed to `deriveSessionUsage`. */
88
+ interface UsageComputeInput {
89
+ /** Requested model id — needed to look up per-token prices. */
90
+ model?: string;
91
+ /** Cost the adapter reported directly (readUsage or usage_update.cost).
92
+ * Present → authoritative, wins over token-based computation. */
93
+ adapterCostUsd?: number;
94
+ tokensIn?: number;
95
+ tokensOut?: number;
96
+ contextSize?: number;
97
+ contextUsed?: number;
98
+ }
99
+ /** The resolved usage snapshot — the shape `session_usage` returns and the
100
+ * durable `usage_snapshot` transcript record carries. Cost/token fields are
101
+ * omitted when absent so a missing value never reads as a measured zero. */
102
+ interface SessionUsage {
103
+ model?: string;
104
+ costUsd?: number;
105
+ tokensIn?: number;
106
+ tokensOut?: number;
107
+ contextSize?: number;
108
+ contextUsed?: number;
109
+ source: UsageSource;
110
+ }
111
+ /**
112
+ * Decide a session's usage snapshot + cost source from the raw signals.
113
+ *
114
+ * Priority: an adapter-reported cost wins; else price the tokens against the
115
+ * catalog; else `none`. Never fabricates a price for an unpriced model.
116
+ */
117
+ declare function deriveSessionUsage(input: UsageComputeInput, resolve?: PricingResolver): SessionUsage;
118
+ /** Descriptor-shaped fields `projectSessionUsage` reads. */
119
+ interface UsageDescriptorFields {
120
+ model?: string;
121
+ costUsd?: number;
122
+ tokensIn?: number;
123
+ tokensOut?: number;
124
+ contextSize?: number;
125
+ contextUsed?: number;
126
+ usageSource?: UsageSource;
127
+ }
128
+ /**
129
+ * Project a session descriptor's persisted usage fields into the
130
+ * `session_usage` response shape. Omits absent fields; defaults an
131
+ * unstamped `usageSource` to `"none"`.
132
+ */
133
+ declare function projectSessionUsage(desc: UsageDescriptorFields): SessionUsage;
13
134
 
14
135
  /**
15
136
  * In-process pub/sub bus for session lifecycle events. Separate from
@@ -20,7 +141,7 @@ export { PROVIDER_ENV_VARS, ProviderEntry, ProvidersFile, injectProviderKeysInto
20
141
  * (fire-and-forget HTTP), RoutineRunner (state machine fan-in),
21
142
  * and session_monitor MCP tool (long-poll multiplexed).
22
143
  */
23
- type SessionEventType = "session:turn-end" | "session:awaiting-input" | "session:exited" | "session:command-done" | "policy:passed" | "policy:failed" | "policy:commit-ready" | "policy:committed" | "cron:fired" | "cron:succeeded" | "cron:failed";
144
+ type SessionEventType = "session:turn-end" | "session:awaiting-input" | "session:permission-request" | "session:permission-resolved" | "session:exited" | "session:command-done" | "policy:passed" | "policy:failed" | "policy:commit-ready" | "policy:committed" | "cron:fired" | "cron:succeeded" | "cron:failed";
24
145
  /**
25
146
  * Structured detail on why a session is awaiting input, when derivable.
26
147
  * `source: "structured"` — a driver-reported ACP-style prompt (e.g. a tool
@@ -51,6 +172,16 @@ interface SessionTurnEndEvent {
51
172
  * Absent for drivers that don't report a reason.
52
173
  */
53
174
  reason?: string;
175
+ /**
176
+ * True when the turn completed normally but produced ZERO assistant
177
+ * output AND zero tool calls (and wasn't awaiting input) — a silent
178
+ * no-op. Grok via hermes/OpenRouter, and any invalid/rejected model id,
179
+ * complete a turn this way: `turnsCompleted` bumps, `$0` cost, empty
180
+ * transcript. Surfaced so an orchestrator can flag it instead of
181
+ * treating a green turn-end as real progress. Absent (not `false`) on
182
+ * a normal, productive turn.
183
+ */
184
+ empty?: boolean;
54
185
  }
55
186
  interface SessionAwaitingInputEvent {
56
187
  type: "session:awaiting-input";
@@ -59,6 +190,34 @@ interface SessionAwaitingInputEvent {
59
190
  ts: string;
60
191
  question?: SessionAwaitingQuestion;
61
192
  }
193
+ /**
194
+ * Emitted when a permission-hold session parks a `session/request_permission`
195
+ * (see the pending-permissions inbox in sessions.ts). `permissionId` is the
196
+ * stable id `permissions_respond` / `POST /permissions/:id` resolve it with.
197
+ */
198
+ interface SessionPermissionRequestEvent {
199
+ type: "session:permission-request";
200
+ sessionId: string;
201
+ permissionId: string;
202
+ toolName?: string;
203
+ text: string;
204
+ label?: string;
205
+ ts: string;
206
+ }
207
+ /**
208
+ * Emitted when a parked permission is resolved — by an inbox
209
+ * approve/deny, or auto-`cancelled` when the session is torn down while the
210
+ * request is still pending.
211
+ */
212
+ interface SessionPermissionResolvedEvent {
213
+ type: "session:permission-resolved";
214
+ sessionId: string;
215
+ permissionId: string;
216
+ decision: "approve" | "deny" | "cancelled";
217
+ optionId?: string;
218
+ label?: string;
219
+ ts: string;
220
+ }
62
221
  interface SessionExitedEvent {
63
222
  type: "session:exited";
64
223
  sessionId: string;
@@ -66,6 +225,12 @@ interface SessionExitedEvent {
66
225
  status: "exited" | "killed" | "error";
67
226
  label?: string;
68
227
  ts: string;
228
+ /** Mirrors `SessionDescriptor.endedReason` — set when this exit was the
229
+ * daemon dying underneath the session (crash-discovered-at-boot or a
230
+ * forced shutdown kill), not an operator targeting it. Lets a watcher
231
+ * (completion-policy supervisor, `session_monitor`) tell "my agent died
232
+ * because the daemon did" apart from a deliberate kill. Absent otherwise. */
233
+ reason?: "daemon-restart";
69
234
  }
70
235
  /** Emitted when command_execute finishes. commandId matches the id
71
236
  * returned by the command_execute MCP tool. */
@@ -140,7 +305,7 @@ interface CronFailedEvent {
140
305
  error: string;
141
306
  ts: string;
142
307
  }
143
- type SessionEvent = SessionTurnEndEvent | SessionAwaitingInputEvent | SessionExitedEvent | SessionCommandDoneEvent | PolicyPassedEvent | PolicyFailedEvent | PolicyCommitReadyEvent | PolicyCommittedEvent | CronFiredEvent | CronSucceededEvent | CronFailedEvent;
308
+ type SessionEvent = SessionTurnEndEvent | SessionAwaitingInputEvent | SessionPermissionRequestEvent | SessionPermissionResolvedEvent | SessionExitedEvent | SessionCommandDoneEvent | PolicyPassedEvent | PolicyFailedEvent | PolicyCommitReadyEvent | PolicyCommittedEvent | CronFiredEvent | CronSucceededEvent | CronFailedEvent;
144
309
  interface SessionEventBus {
145
310
  emit(ev: SessionEvent): void;
146
311
  /** Subscribe to a specific event type. Returns an unsubscribe fn. */
@@ -152,26 +317,40 @@ interface SessionEventBus {
152
317
  }
153
318
 
154
319
  /**
155
- * Sessions registrytracks long-lived child processes spawned by
156
- * the agentproto daemon (terminals, agent CLIs, custom commands).
157
- *
158
- * The registry lives in-memory for the daemon's lifetime; a recent
159
- * snapshot also persists to `~/.agentproto/sessions.json` so a fresh
160
- * daemon restart can show "you had these running before" instead of
161
- * losing all visibility on a crash.
320
+ * A per-session observer the pluggable seam behind the runtime's transcript
321
+ * tap. `sessions.ts` drives one observer per turn (prompt in, each stream event,
322
+ * the turn-boundary usage snapshot, close). The transcript writer is the first
323
+ * and, today, only member; additional observers (e.g. a Langfuse session tracer)
324
+ * attach without touching the turn loop.
162
325
  *
163
- * Each session owns:
164
- * - identity: id, kind, workspace slug, command + args, started time
165
- * - lifecycle: status (starting/running/exited/killed/error)
166
- * - output: last N lines (ring buffer) for instant attach + a
167
- * Node EventEmitter for live streaming consumers
326
+ * The method set is exactly the subset of `TranscriptWriter` the session loop
327
+ * calls, so `TranscriptWriter` IS a `SessionObserver` (see transcript-writer.ts).
328
+ */
329
+
330
+ interface SessionObserver {
331
+ /** Record the outgoing message that opens a new turn. */
332
+ recordPrompt(sessionId: string, message: unknown): void;
333
+ /** Record one structured stream event (text-delta, tool-call, usage_update, …). */
334
+ recordEvent(sessionId: string, evt: AgentStreamEvent): void;
335
+ /** Record the durable turn-boundary / exit usage snapshot. */
336
+ recordUsageSnapshot(sessionId: string, usage: SessionUsage): void;
337
+ /** Flush and close this session's stream. Idempotent, fire-and-forget. */
338
+ close(sessionId: string): Promise<void>;
339
+ /** Close every open session stream (synchronous shutdown path). */
340
+ closeAll(): Promise<void>;
341
+ }
342
+ /**
343
+ * Fan a `SessionObserver` out over many. Each `record*` call is forwarded to
344
+ * every member in order; `close`/`closeAll` await all members.
168
345
  *
169
- * Consumers:
170
- * - HTTP routes (GET /sessions, /sessions/:id, /sessions/:id/stream,
171
- * POST /sessions/:id/kill)
172
- * - CLI `agentproto sessions` (TUI navigation + attach)
173
- * - guilde-web Active tab (session cards + terminal viewer)
346
+ * Every individual member call is isolated in try/catch so one throwing observer
347
+ * can neither break its siblings nor propagate into the turn loop — the
348
+ * transcript writer is effectively infallible today, but a future network-backed
349
+ * observer (a Langfuse sink) must never be able to throw a turn. Failures are
350
+ * swallowed here (no logging dependency at this layer); observers that need
351
+ * visibility into their own failures own that internally.
174
352
  */
353
+ declare function composeSessionObservers(observers: readonly SessionObserver[]): SessionObserver;
175
354
 
176
355
  /**
177
356
  * Minimal shape we need from a driver-agent-cli session — kept as a
@@ -189,6 +368,13 @@ interface AgentSessionLike {
189
368
  pid?: number;
190
369
  send(message: unknown): AsyncIterable<AgentStreamEvent>;
191
370
  cancel(): Promise<void>;
371
+ /** Resolve a permission request the driver parked in permission-hold mode
372
+ * (see the pending-permissions inbox below). `requestId` is the driver's
373
+ * own stable id, surfaced on the `agent-prompt` event's `toolCallId`.
374
+ * Returns true when a matching parked request was resolved. Absent for
375
+ * drivers that don't model held permissions (sandbox proxy, future
376
+ * transports). */
377
+ respondPermission?(requestId: string, resolution: AcpPermissionResolution): boolean | Promise<boolean>;
192
378
  close(): Promise<void>;
193
379
  }
194
380
  /**
@@ -227,6 +413,11 @@ interface AgentStreamEvent {
227
413
  toolName?: string;
228
414
  /** Tool-call input, e.g. an ACP `tool_call`'s `arguments` — see @agentproto/acp's `StreamEvent`. */
229
415
  arguments?: unknown;
416
+ /** True when a "tool-call" event ENRICHES one already announced under the
417
+ * same `toolCallId` (the agent knew the input only after announcing the
418
+ * call) rather than announcing a new one — see @agentproto/acp's
419
+ * `StreamEvent`. Consumers merge by toolCallId; it is not a second call. */
420
+ isUpdate?: boolean;
230
421
  /** Tool-call output, e.g. an ACP `tool_call_update`'s `result` — see @agentproto/acp's `StreamEvent`. */
231
422
  result?: unknown;
232
423
  isError?: boolean;
@@ -260,9 +451,28 @@ interface AgentStreamEvent {
260
451
  amount: number;
261
452
  currency: string;
262
453
  };
454
+ /** "usage_update" cumulative input/output token counts, when the adapter
455
+ * reports them (lets the daemon price a session whose adapter gives tokens
456
+ * but no `cost`). */
457
+ tokensIn?: number;
458
+ tokensOut?: number;
263
459
  }
264
460
  type SessionKind = "terminal" | "agent-cli" | "command" | "browser";
265
461
  type SessionStatus = "starting" | "running" | "exited" | "killed" | "error";
462
+ /**
463
+ * The billing-auth resolver's OBSERVABLE echo, recorded on a session
464
+ * descriptor. `mode` + `fingerprint` are always present when recorded (a
465
+ * credential resolved); `provider` / `credentialSource` / `setEnv` are the
466
+ * multi-provider resolver's additions (DECISION 9③/10②) — optional for
467
+ * back-compat with descriptors from before that resolver shipped.
468
+ */
469
+ interface SessionAuthEcho {
470
+ mode: "subscription" | "api-key";
471
+ fingerprint: string;
472
+ provider?: string;
473
+ credentialSource?: "explicit-config" | "providers-store" | "none";
474
+ setEnv?: string;
475
+ }
266
476
  interface SessionDescriptor {
267
477
  id: string;
268
478
  kind: SessionKind;
@@ -275,6 +485,16 @@ interface SessionDescriptor {
275
485
  startedAt: string;
276
486
  endedAt?: string;
277
487
  exitCode?: number;
488
+ /** Set alongside `status: "killed"` when the session ended NOT because an
489
+ * operator targeted it (`kill()`) but because the daemon process it lived
490
+ * in went away out from under it — a hard crash discovered at next boot
491
+ * (`loadHistorySnapshot`'s wasAlive reclassification), or a graceful
492
+ * shutdown/restart that force-kills whatever's still busy
493
+ * (`shutdownImpl`). Absent for every other terminal path (operator kill,
494
+ * natural exit, turn error) — the session's own fault, or at least not
495
+ * the daemon's. Lets the UI show "crashed with the daemon" instead of a
496
+ * bare "killed" that reads as deliberate. */
497
+ endedReason?: "daemon-restart";
278
498
  /** Last time anything was written to stdout/stderr. Lets the UI
279
499
  * spot stuck sessions ("running for 2h, last output 12min ago"). */
280
500
  lastOutputAt?: string;
@@ -315,6 +535,25 @@ interface SessionDescriptor {
315
535
  adapterSlug?: string;
316
536
  /** The model the session was requested to run (echoed back at spawn). */
317
537
  model?: string;
538
+ /**
539
+ * Deterministic billing-auth mode + a non-secret credential fingerprint,
540
+ * recorded at spawn time for adapters that resolved an explicit
541
+ * credential (today: claude-code — see `AgentCliAuth.modes` in
542
+ * `@agentproto/driver-agent-cli`). The "verifiability" answer to "what
543
+ * was used": `mode` is the resolved `"subscription" | "api-key"`;
544
+ * `fingerprint` is `credentialFingerprint(mode, credential)` — e.g.
545
+ * `"subscription · sk-ant-oat…3f9c"` — NEVER the raw credential. Absent
546
+ * when no credential resolved (every adapter besides claude-code, in
547
+ * practice) or for a sandboxed spawn (the box's own daemon resolves its
548
+ * own credential independently). Surfaced in `agentproto sessions
549
+ * --watch`'s DETAIL pane and `agent_sessions_list`.
550
+ *
551
+ * Carries the resolver's OBSERVABLE echo (DECISION 9③/10②): `provider`,
552
+ * `credentialSource`, and the `setEnv` actually set — so a verifier checks
553
+ * the RESOLUTION, not the model's self-report. These are optional for
554
+ * back-compat with descriptors persisted before the multi-provider resolver.
555
+ */
556
+ auth?: SessionAuthEcho;
318
557
  /** Cumulative estimated USD cost of the session — best-effort, refreshed
319
558
  * on each turn-end from the adapter's usage reader (e.g. hermes reads its
320
559
  * state.db). Absent for adapters with no usage source. */
@@ -322,6 +561,16 @@ interface SessionDescriptor {
322
561
  /** Cumulative input / output token counts (same source + cadence as costUsd). */
323
562
  tokensIn?: number;
324
563
  tokensOut?: number;
564
+ /** Context-window size + tokens-in-context from the latest `usage_update`
565
+ * event (ACP adapters that report a context window). Refreshed live as
566
+ * usage_update events arrive, not just at turn-end. */
567
+ contextSize?: number;
568
+ contextUsed?: number;
569
+ /** Where `costUsd` came from — `"adapter"` (adapter's own reader or a
570
+ * usage_update cost block), `"computed"` (tokens × in-repo catalog price),
571
+ * `"no-pricing"` (tokens present but the model isn't in the catalog — cost
572
+ * deliberately left undefined), or `"none"`. Stamped at each turn-end. */
573
+ usageSource?: UsageSource;
325
574
  /** ACP-level session id (the adapter's own handle — claude-code's
326
575
  * conversation id, hermes' chat id, …). Set at spawnAgent time
327
576
  * from `agentSession.sessionId`. Survives across daemon restarts
@@ -362,6 +611,14 @@ interface SessionDescriptor {
362
611
  * drivers that don't report structured prompts. Cleared alongside
363
612
  * `awaitingInput` on the next turn start. */
364
613
  awaitingQuestion?: SessionAwaitingQuestion;
614
+ /** True while this session (spawned in permission-hold mode) has at least
615
+ * one permission request parked in the inbox awaiting a human/orchestrator
616
+ * decision. Distinct from `awaitingInput` (a conversational question) — a
617
+ * held permission is resolved via `permissions_respond` / `agentproto
618
+ * permissions approve|deny`, not a normal prompt. Set when a request is
619
+ * registered, cleared when the last pending one for this session resolves.
620
+ * Absent (never `false`) for sessions with no held permissions. */
621
+ awaitingPermission?: boolean;
365
622
  /** Count of turns that have fully completed (turn-end emitted) on this
366
623
  * session. Lets `session_monitor` fast-return for a session that already
367
624
  * finished its turn before the wait subscribed — a fast turn that ends
@@ -374,6 +631,21 @@ interface SessionDescriptor {
374
631
  * finished turn" from "mid-turn" so it doesn't fast-return a stale
375
632
  * turn-end while the NEXT turn is still generating. */
376
633
  busy?: boolean;
634
+ /** What the in-flight turn is currently blocked on, when classifiable
635
+ * from the pending tool call: a spawned sub-agent (`agent_start`) or a
636
+ * shell/terminal command. Deliberately NO "user" variant — waiting on
637
+ * the user is already covered by `awaitingInput`/`awaitingQuestion`.
638
+ *
639
+ * Set on tool-call; released (see `releaseBlockedOn`) on the MATCHING
640
+ * tool-result, on `error` (a failing tool never emits a result), on the
641
+ * next assistant `text-delta` (the model has the floor, so nothing is
642
+ * pending), at turn start, and in the turn's finally. A claim about the
643
+ * present tense — anything that disproves it must clear it. */
644
+ blockedOn?: "subagent" | "command";
645
+ /** toolCallId of the tool-call that set `blockedOn`. A tool-result only
646
+ * clears `blockedOn` when its toolCallId matches — so a nested or
647
+ * interleaved tool finishing first can't clear the flag early. */
648
+ pendingToolCallId?: string;
377
649
  /** Session id of the orchestrator that spawned this session, set when
378
650
  * the spawn arrived via the scoped orchestrator sub-gateway (the
379
651
  * token carried the spawner's identity). Absent for direct `/mcp`
@@ -387,6 +659,17 @@ interface SessionDescriptor {
387
659
  * Treated as 0 when absent (legacy rows / direct spawns that predate
388
660
  * WP4). */
389
661
  depth?: number;
662
+ /** Id of the most recently-completed `kind: "command"` session spawned
663
+ * with the same `cwd`, found at spawn time — see `recordCommand` and
664
+ * `findPriorCommandSessionId`. Deliberately a REFERENCE, not copied
665
+ * content: a new session's own ring buffer / structured transcript
666
+ * never gets a prior command_execute's stdout/stderr spliced in, it
667
+ * just gets told where to look (`command_log_tail({sessionId})` or
668
+ * `agent_export`/`command_list` resolve the id into the full record).
669
+ * Set on `spawnAgent`/`spawnPty` when the registry already has a
670
+ * matching command session; absent otherwise (including for legacy
671
+ * rows persisted before this field existed). */
672
+ priorCommandSessionId?: string;
390
673
  /** Adapter id that drives this session (e.g. "camofox", "bureau"). */
391
674
  browserAdapterId?: string;
392
675
  /** Port the browser service listens on. */
@@ -395,7 +678,63 @@ interface SessionDescriptor {
395
678
  browserBaseUrl?: string;
396
679
  /** Execution location — "local" (default) or "cloud". */
397
680
  browserLocation?: "local" | "cloud";
681
+ /** True when this agent-cli session is running inside a sandbox (`agent_start.sandbox`)
682
+ * rather than as a local subprocess — there's no local PID to check, so
683
+ * `processAlive` never applies (it's already absent whenever `pid` is null). */
684
+ remote?: boolean;
685
+ /** Provider-assigned sandbox id (`BootedSandbox.sandboxId`), when `remote` is true. */
686
+ sandboxId?: string;
687
+ /** What session close does to the box (PR3 lifecycle) — `"kill"` (the
688
+ * default, ephemeral) or `"pause"` (keeps `sandboxId` reconnectable via
689
+ * `agent_start.sandbox.reuse`). Only set when `remote` is true. */
690
+ sandboxTeardown?: "kill" | "pause";
398
691
  }
692
+ /**
693
+ * A `session/request_permission` request parked by a permission-hold session,
694
+ * awaiting a human/orchestrator decision through the cross-session inbox
695
+ * (`permissions_list` / `permissions_respond`, `GET /permissions` /
696
+ * `POST /permissions/:id`, `agentproto permissions ls|approve|deny`).
697
+ */
698
+ interface PendingPermission {
699
+ /** Stable id — the driver's own request id, unique across sessions. Pass to
700
+ * `respondPermission`. */
701
+ id: string;
702
+ sessionId: string;
703
+ /** ACP tool-call id the request was raised for (== `id` today; kept distinct
704
+ * so a future driver can surface a separate correlation id). */
705
+ toolCallId: string;
706
+ /** Tool title/kind the agent is asking permission for, when known. */
707
+ toolName?: string;
708
+ /** Human-readable "Allow X?" line. */
709
+ text: string;
710
+ /** Options the agent offered (ACP `requestPermission` shape). */
711
+ options: Array<{
712
+ optionId: string;
713
+ name?: string;
714
+ kind?: string;
715
+ }>;
716
+ /** ISO timestamp the request was parked. */
717
+ requestedAt: string;
718
+ }
719
+ /** How a caller resolves a pending permission — an explicit `optionId` wins
720
+ * over the `decision`→option mapping (approve → an allow-flavored option,
721
+ * deny → a reject-flavored option). `scope: "always"` prefers an
722
+ * allow-always option when the request offers one. */
723
+ interface PermissionRespondInput {
724
+ decision: "approve" | "deny";
725
+ optionId?: string;
726
+ scope?: "once" | "always";
727
+ }
728
+ type PermissionRespondResult = {
729
+ ok: true;
730
+ permission: PendingPermission;
731
+ decision: "approve" | "deny";
732
+ optionId?: string;
733
+ } | {
734
+ ok: false;
735
+ error: "not_found" | "session_gone" | "no_matching_option" | "unsupported";
736
+ message: string;
737
+ };
399
738
  interface SessionsRegistry {
400
739
  spawn(input: SpawnSessionInput): SessionDescriptor;
401
740
  /** Adopt a ChildProcess that was spawned outside the registry —
@@ -417,17 +756,47 @@ interface SessionsRegistry {
417
756
  * `attachPty(id, ...)`. Throws when the registry was constructed
418
757
  * without a `spawnPty` factory (node-pty optional dep missing). */
419
758
  spawnPty(input: SpawnPtyInput): SessionDescriptor;
759
+ /** Register a COMPLETED `command_execute` (or cron `kind:"command"`
760
+ * action) invocation as its own `kind: "command"` session. Unlike
761
+ * `spawn`/`spawnAgent`/`spawnPty`, there's no live process to track —
762
+ * the command already ran to completion via `runCommand` before this
763
+ * is called — so the descriptor is minted already "finished"
764
+ * (status exited/error, startedAt backdated by `durationMs`,
765
+ * endedAt now) and its full result is written to that session's own
766
+ * `events.jsonl` (see command-log.ts), the same per-id path an
767
+ * agent-cli session's structured transcript uses. Synchronous: the
768
+ * descriptor (and its id) is available immediately, the JSONL write
769
+ * itself is fire-and-forget internally. */
770
+ recordCommand(input: RecordCommandInput): SessionDescriptor;
771
+ /** Read back a `kind:"command"` session's full logged result (the
772
+ * `CommandLogEntry` `recordCommand` wrote). Resolves to null when the
773
+ * session isn't a command session or has no recorded entry. Routed
774
+ * through the registry (rather than callers importing
775
+ * `readCommandLogEntry` directly) so the read always targets the same
776
+ * base directory `recordCommand` wrote to — that dir is test-overridable
777
+ * (`transcriptDir`) and callers outside sessions.ts have no other way
778
+ * to know it. */
779
+ readCommandLog(sessionId: string): Promise<CommandLogEntry | null>;
420
780
  /** Register an already-running browser service adapter as a tracked
421
781
  * session (kind="browser"). Idempotent by identity — each call
422
782
  * mints a fresh session id. The `stop` callback is invoked by
423
783
  * `kill()` best-effort. */
424
784
  registerBrowser(input: RegisterBrowserInput): SessionDescriptor;
425
- /** Send a follow-up turn to a live agent session. Throws when the
426
- * session is missing, not an agent-cli kind, dead (exited/killed/
427
- * error and unresumable — `SessionNotAliveError`), or busy
785
+ /** Send a follow-up turn to a live agent session and AWAIT it. Throws
786
+ * when the session is missing, not an agent-cli kind, dead (exited/
787
+ * killed/error and unresumable — `SessionNotAliveError`), or busy
428
788
  * (mid-turn). The events stream into the existing ring buffer +
429
- * line emitter so /stream consumers see them as they arrive. */
430
- sendPrompt(id: string, message: unknown): Promise<void>;
789
+ * line emitter so /stream consumers see them as they arrive.
790
+ *
791
+ * `opts.interrupt` behaves exactly as it does on `enqueuePrompt`:
792
+ * a mid-turn session has its in-flight turn cancelled and settled
793
+ * before admission, so this prompt redirects the SAME live session
794
+ * instead of hitting the busy rejection. Ignored (identical to the
795
+ * default) when the session is idle. Omitted or `false` reproduces
796
+ * the previous mid-turn rejection byte-for-byte. */
797
+ sendPrompt(id: string, message: unknown, opts?: {
798
+ interrupt?: boolean;
799
+ }): Promise<void>;
431
800
  /** Fire-and-forget variant of `sendPrompt` for the TURN ITSELF only.
432
801
  * Admission (resume attempt + the missing/wrong-kind/dead/busy
433
802
  * checks `sendPrompt` throws) is AWAITED before this resolves, so a
@@ -437,8 +806,18 @@ interface SessionsRegistry {
437
806
  * mid-turn) are pushed into the ring buffer as `[error]` lines so
438
807
  * `/stream` subscribers see them. Used by the web UI's chat input
439
808
  * and MCP `agent_prompt`, where a long turn would otherwise freeze
440
- * the caller. */
441
- enqueuePrompt(id: string, message: unknown): Promise<void>;
809
+ * the caller.
810
+ *
811
+ * `opts.interrupt` lets a caller redirect a mid-turn session instead
812
+ * of hitting the busy rejection: the in-flight turn is cancelled
813
+ * (`agentSession.cancel()`), admission waits for that turn to
814
+ * actually settle (busy flips false — no fixed sleep), then the new
815
+ * prompt is admitted + fired on the SAME live session. Ignored
816
+ * (identical to the default) when the session is idle. Omitted or
817
+ * `false` reproduces today's mid-turn rejection byte-for-byte. */
818
+ enqueuePrompt(id: string, message: unknown, opts?: {
819
+ interrupt?: boolean;
820
+ }): Promise<void>;
442
821
  /** Stamp `lastActivityAt` on a live agent-cli session's descriptor
443
822
  * and schedule a debounced persist. Called from the `onActivity`
444
823
  * callback threaded down through the driver → ACP client, which
@@ -452,6 +831,14 @@ interface SessionsRegistry {
452
831
  * Initial backfill: synchronously invokes `onLine` once for each
453
832
  * line currently in the ring buffer so attaches show context. */
454
833
  attach(id: string, onLine: (line: string, stream: "stdout" | "stderr") => void): (() => void) | null;
834
+ /** Subscribe to a session's structured events.jsonl records as they're
835
+ * written — the live-push half of `GET /sessions/:id/events/stream`'s
836
+ * replay-then-subscribe handoff. Thin passthrough to the transcript
837
+ * writer's own `subscribe` (see transcript-writer.ts for the exactly-
838
+ * once contract this enables); no backfill, no existence check — a
839
+ * session id with no writer state simply never calls back. Returns an
840
+ * unsubscribe fn. */
841
+ subscribeToRecords(id: string, onRecord: (record: Record<string, unknown>) => void): () => void;
455
842
  /** Subscribe to a PTY session's byte stream. Returns a control
456
843
  * handle (write/resize/detach) and null when the session is
457
844
  * missing or not a PTY kind. Replays the ring buffer
@@ -483,6 +870,17 @@ interface SessionsRegistry {
483
870
  * tail. Returns null when the session is missing or not a PTY. */
484
871
  readTerminalOutput(id: string, lastBytes?: number): Buffer | null;
485
872
  kill(id: string, signal?: NodeJS.Signals): boolean;
873
+ /** List permission requests currently parked in the pending-permissions
874
+ * inbox across all permission-hold sessions, newest last. Optionally
875
+ * filtered to one session. */
876
+ listPendingPermissions(filter?: {
877
+ sessionId?: string;
878
+ }): PendingPermission[];
879
+ /** Resolve a parked permission by id — maps the decision (or explicit
880
+ * optionId) to one of the offered options, resolves the held driver RPC,
881
+ * clears the session's awaiting-permission state, and emits
882
+ * `session:permission-resolved`. */
883
+ respondPermission(id: string, input: PermissionRespondInput): Promise<PermissionRespondResult>;
486
884
  /** Stop tracking a session (after it exited and the user clicked
487
885
  * "clear"). Doesn't kill — use `kill` first. */
488
886
  forget(id: string): boolean;
@@ -555,6 +953,11 @@ interface SpawnAgentInput {
555
953
  depth?: number;
556
954
  /** Requested model id — recorded on the descriptor for display + echo. */
557
955
  model?: string;
956
+ /** Resolved auth echo (mode + fingerprint + provider/source/setEnv) —
957
+ * recorded verbatim onto {@link SessionDescriptor.auth}. See that field's
958
+ * doc for the full contract; the caller (`session-spawn.ts`) computes this
959
+ * via the billing-auth resolver, never passing the raw credential here. */
960
+ auth?: SessionAuthEcho;
558
961
  /** Hard ceiling on cumulative session cost (USD). When set and the
559
962
  * adapter's usage reader reports a higher cost at a turn-end, the session
560
963
  * is stopped (best-effort, turn-granular — caps continuation, can't abort
@@ -569,6 +972,23 @@ interface SpawnAgentInput {
569
972
  tokensIn?: number;
570
973
  tokensOut?: number;
571
974
  } | null>;
975
+ /** Opt this session into Langfuse tracing (prompt/completion + tool spans +
976
+ * tokens/cost). Effective opt-in is `trace ?? opts.langfuseTracingDefault ?? false`. */
977
+ trace?: boolean;
978
+ /** True when `agentSession` is a `SandboxAgentSessionProxy` (`agent_start.sandbox`)
979
+ * rather than a local subprocess-backed session — stamped onto the descriptor
980
+ * as `remote` since there's no local PID to report. */
981
+ remote?: boolean;
982
+ /** Provider-assigned sandbox id, when `remote` is true. */
983
+ sandboxId?: string;
984
+ /** What session close does to the box, when `remote` is true — see
985
+ * `SessionDescriptor.sandboxTeardown`. */
986
+ sandboxTeardown?: "kill" | "pause";
987
+ /** True when the driver session was started in permission-hold mode
988
+ * (`AgentCliStartOptions.permissionHold`) — its `agent-prompt` events carry
989
+ * respondable permission requests. Gates whether the registry registers
990
+ * them in the pending-permissions inbox. Default false. */
991
+ permissionHold?: boolean;
572
992
  }
573
993
  interface SpawnSessionInput {
574
994
  kind: SessionKind;
@@ -612,6 +1032,24 @@ interface SpawnPtyInput {
612
1032
  name?: string;
613
1033
  label?: string;
614
1034
  }
1035
+ interface RecordCommandInput {
1036
+ workspaceSlug: string;
1037
+ /** Working directory the command actually ran in (post cwd-anchoring).
1038
+ * Matched against a fresh session's cwd by `findPriorCommandSessionId`. */
1039
+ cwd: string;
1040
+ command: string;
1041
+ args: string[];
1042
+ /** Fields mirror `command-tools.ts`'s `ExecuteResult` — kept inline here
1043
+ * (rather than importing that type) so sessions.ts has no dependency
1044
+ * on command-tools.ts. */
1045
+ exitCode: number;
1046
+ signal: string | null;
1047
+ durationMs: number;
1048
+ stdout: string;
1049
+ stderr: string;
1050
+ truncated?: boolean;
1051
+ label?: string;
1052
+ }
615
1053
 
616
1054
  /**
617
1055
  * MCP tools that expose browser service adapters (Camofox, Bureau, …) to
@@ -714,7 +1152,7 @@ interface BrowserAdapterInfo {
714
1152
  * `TunnelRegistry` (multi-tunnel general surface) can share the same
715
1153
  * provider contract without creating a circular dep.
716
1154
  *
717
- * The tunnel family is the FIRST consumer of `@agentproto/adapter-kit`:
1155
+ * The tunnel family is the FIRST consumer of `@agentproto/provider-kit`:
718
1156
  * {@link TunnelProviderHandle} extends the kit's generic `AdapterHandle`
719
1157
  * (slug/name/version/description/requiresSetup/check) with the
720
1158
  * tunnel-specific `capabilities` + the existing `start`/`stop` lifecycle.
@@ -896,13 +1334,163 @@ declare class TunnelRegistry {
896
1334
  * third-party secrets) merged with descriptor-carried config (named's
897
1335
  * hostname/tunnelId/credentialsFile), descriptor taking precedence.
898
1336
  */
899
- private credsForDescriptor;
1337
+ protected credsForDescriptor(desc: TunnelDescriptor): Promise<TunnelCreds>;
900
1338
  private findEntryByIdOrName;
901
1339
  private schedulePersist;
902
1340
  private persistNow;
903
1341
  private loadFromDisk;
904
1342
  }
905
1343
 
1344
+ /**
1345
+ * Daemon-side pairing registry (design: DESIGN §3/§4, PLAN deliverable 2).
1346
+ *
1347
+ * Owns everything the daemon needs to be *paired with* over an untrusted
1348
+ * rendezvous:
1349
+ *
1350
+ * - **Offer store** — in-memory, single-use, expiry-checked one-time tokens
1351
+ * minted by `pair offer`. A daemon restart voids outstanding offers
1352
+ * (acceptable + documented). The P1 `verifyOfferToken` predicate plugs in
1353
+ * here and SPENDS the token on the first successful handshake.
1354
+ * - **Pairings store** — `~/.agentproto/pairings.json` (0600, atomic write),
1355
+ * one record per paired client: `{clientPub, name, fingerprint, createdAt,
1356
+ * lastSeen, pairRoot, rendezvousUrl}`. `pairRoot` is the long-term shared
1357
+ * secret (`derivePairRoot`) from which reconnect routing tokens derive.
1358
+ * - **Rendezvous connections** — for a fresh offer, and for every persisted
1359
+ * pairing on boot (autoconnect), the daemon dials the rendezvous *outbound*
1360
+ * (`side=daemon&t=<token>`), runs `daemonHandshakeOverSink` with the P1
1361
+ * crypto, and — on success — serves the spliced, E2E-wrapped channel exactly
1362
+ * like `serve --connect` serves a tunnel host. The serving path itself is
1363
+ * injected (`serve`) so this module stays free of pty/adapter concerns and
1364
+ * the CLI can reuse its `createTunnelServer` config verbatim.
1365
+ * - **Reconnect epochs** — a persisted pairing's standing connection uses the
1366
+ * pairing-derived routing token `t' = HKDF(pairRoot, "rv-route"‖epoch)`,
1367
+ * rotated per UTC day. The daemon parks on BOTH the current and previous
1368
+ * epoch tokens so a client whose clock straddles midnight still finds it;
1369
+ * reconnect-with-backoff keeps the standing connection alive.
1370
+ * - **Revocation** — removing a pairing drops its rendezvous connections and
1371
+ * stops the daemon parking on its tokens, so it can no longer be reached.
1372
+ *
1373
+ * ## Reconnect authentication (why no stable client key)
1374
+ *
1375
+ * P1's handshake carries only a client *ephemeral* key (regenerated per
1376
+ * handshake) — there is no persistent client identity key, and the PLAN forbids
1377
+ * changing the handshake. So a reconnect authenticates the client by
1378
+ * **possession of the epoch routing token**, which derives from `pairRoot` — a
1379
+ * secret only the paired client and daemon share (it fell out of the original
1380
+ * ECDH). The daemon dials a pairing's epoch token, and on that connection the
1381
+ * `verifyOfferToken` predicate accepts a hello whose offer-token field equals
1382
+ * that same epoch token (constant-time). Token possession ⇒ paired party. The
1383
+ * stored `fingerprint` (of the first handshake's ephemeral key) is a stable
1384
+ * display/revocation handle, not an authentication input.
1385
+ */
1386
+
1387
+ declare const PAIRINGS_VERSION: 1;
1388
+ /** One persisted pairing. Written verbatim to `pairings.json`. */
1389
+ interface PairingRecord {
1390
+ /** The client ephemeral public key from the FIRST handshake (a snapshot —
1391
+ * reconnects use fresh ephemerals; this is not an auth input). */
1392
+ clientPub: string;
1393
+ /** Human-facing label the client chose on `pair accept`. */
1394
+ name: string;
1395
+ /** `identityFingerprint(clientPub)` from the first handshake — the stable
1396
+ * display + revocation handle. */
1397
+ fingerprint: string;
1398
+ /** ISO-8601 first-pair timestamp. */
1399
+ createdAt: string;
1400
+ /** ISO-8601 of the most recent successful (re)connect. */
1401
+ lastSeen: string;
1402
+ /** Long-term shared secret (base64), from which epoch routing tokens derive. */
1403
+ pairRoot: string;
1404
+ /** Rendezvous endpoint to reconnect through. */
1405
+ rendezvousUrl: string;
1406
+ }
1407
+ /** Mode of a served channel — first-contact offer vs. an established reconnect. */
1408
+ type PairingChannelMode = "offer" | "reconnect";
1409
+ /** Context handed to the injected `serve` when a channel comes up. */
1410
+ interface PairingChannelContext {
1411
+ mode: PairingChannelMode;
1412
+ /** Stable pairing fingerprint (present for reconnect; for an offer it's the
1413
+ * freshly-derived one). */
1414
+ fingerprint: string;
1415
+ /** Client label. */
1416
+ name: string;
1417
+ }
1418
+ /** Handle to a live served channel — the registry closes it on teardown. */
1419
+ interface PairingChannelHandle {
1420
+ close(): Promise<void>;
1421
+ }
1422
+ interface CreatedOffer {
1423
+ /** The one-time offer token (base64url). */
1424
+ token: string;
1425
+ /** Offer expiry, unix seconds. */
1426
+ exp: number;
1427
+ /** The full `agentproto://pair?…` offer URL. */
1428
+ url: string;
1429
+ /** The daemon's identity fingerprint (shown to the human at accept time). */
1430
+ fingerprint: string;
1431
+ /** The rendezvous the offer routes through. */
1432
+ rendezvousUrl: string;
1433
+ /** True when `rendezvousUrl` is the hosted default — i.e. neither an explicit
1434
+ * `--rendezvous` nor a configured `pairing.rendezvous` applied, so the offer
1435
+ * falls back to `HOSTED_RENDEZVOUS_URL`. Surfaced so `pair offer` can flag,
1436
+ * never silently, that the daemon is relaying through our infrastructure. */
1437
+ rendezvousIsHostedDefault: boolean;
1438
+ }
1439
+ interface CreateOfferInput {
1440
+ /** Time-to-live in ms. Default 10 minutes. */
1441
+ ttlMs?: number;
1442
+ /** Rendezvous URL override; falls back to the configured default. */
1443
+ rendezvousUrl?: string;
1444
+ }
1445
+ interface PairingRegistryDeps {
1446
+ /** Lazily load (create on first use) the daemon identity. Kept lazy so a
1447
+ * daemon that never pairs never writes an identity file. */
1448
+ loadIdentity: () => Promise<DaemonIdentity>;
1449
+ /** Path to `pairings.json`. Defaults to `~/.agentproto/pairings.json`. */
1450
+ pairingsPath?: string;
1451
+ /** Configured rendezvous URL (from `config.pairing.rendezvous`). Three states:
1452
+ * - `undefined` (key absent) → `createOffer` falls back to the hosted default
1453
+ * (`HOSTED_RENDEZVOUS_URL`).
1454
+ * - a non-empty URL → that endpoint is the default for offers.
1455
+ * - `""` (explicitly empty) → an explicit opt-out: no default applies, so an
1456
+ * offer without `--rendezvous` fails closed. Wire it through verbatim (do
1457
+ * not coerce `""` to `undefined`) or the opt-out silently reverts to the
1458
+ * hosted default. */
1459
+ defaultRendezvousUrl?: string;
1460
+ /** Dial a rendezvous WS and adapt it to a FrameSink. Injected by the CLI
1461
+ * (uses `ws` + `wrapWebSocket`). Rejects if the dial fails. The `signal`
1462
+ * aborts on registry shutdown. */
1463
+ dial: (wsUrl: string, signal: AbortSignal) => Promise<FrameSink>;
1464
+ /** Serve the E2E-wrapped channel (the CLI builds `createTunnelServer` over
1465
+ * it with the same config `serve --connect` uses). */
1466
+ serve: (sink: E2eFrameSink, ctx: PairingChannelContext) => PairingChannelHandle;
1467
+ /** Injectable clock (ms). Defaults to Date.now. */
1468
+ now?: () => number;
1469
+ /** Diagnostic log sink. */
1470
+ log?: (line: string) => void;
1471
+ /** Reconnect backoff floor / ceiling (ms). Defaults 1s / 30s. */
1472
+ reconnectMinMs?: number;
1473
+ reconnectMaxMs?: number;
1474
+ /** How long a parked daemon socket waits for a client hello before recycling
1475
+ * the connection. Kept just under the broker's park timeout. Default 110s. */
1476
+ handshakeTimeoutMs?: number;
1477
+ }
1478
+ interface PairingRegistry {
1479
+ /** Mint a one-time offer + start dialing the rendezvous for it. */
1480
+ createOffer(input?: CreateOfferInput): Promise<CreatedOffer>;
1481
+ /** All persisted pairings (copies). Loads `pairings.json` on first call. */
1482
+ list(): Promise<PairingRecord[]>;
1483
+ /** Drop a pairing by fingerprint or name; stops its rendezvous connections.
1484
+ * Returns false when nothing matched. */
1485
+ revoke(idOrName: string): Promise<boolean>;
1486
+ /** Start standing reconnect connections for every persisted pairing. Call
1487
+ * after the gateway is up (so the injected `serve` can reach it). */
1488
+ startAutoconnect(): Promise<void>;
1489
+ /** Tear down every offer + reconnect connection and served channel. */
1490
+ shutdown(): Promise<void>;
1491
+ }
1492
+ declare function createPairingRegistry(deps: PairingRegistryDeps): PairingRegistry;
1493
+
906
1494
  /**
907
1495
  * Fire-and-forget webhook notifier for session lifecycle events.
908
1496
  *
@@ -1301,6 +1889,18 @@ interface OrchestratorScope {
1301
1889
  /** Max concurrently-alive children the owning orchestrator may spawn
1302
1890
  * before further spawns are rejected. */
1303
1891
  maxChildren: number;
1892
+ /**
1893
+ * Name of the resolved role that owns this scope (spawn-role-
1894
+ * profiles / privilege lattice). This is the "parent role" the
1895
+ * `canSpawn` gate checks against when this scope is used to spawn a
1896
+ * further child (`session-spawn.ts`). Defaults to `"supervisor"` at
1897
+ * mint time when the caller doesn't say — every role that can reach
1898
+ * `orchestrator: true` today resolves to supervisor (executor's
1899
+ * `toolPolicy.delegation: "deny"` already excludes it from ever
1900
+ * minting a scope), so this preserves #214 behaviour exactly until a
1901
+ * custom role is actually granted delegation.
1902
+ */
1903
+ role: string;
1304
1904
  }
1305
1905
  /**
1306
1906
  * Narrow a caller-requested tool list to ⊆ a ceiling subset.
@@ -1329,6 +1929,9 @@ interface MintScopeOptions {
1329
1929
  maxDepth?: number;
1330
1930
  /** Per-orchestrator alive-child quota (default DEFAULT_MAX_CHILDREN). */
1331
1931
  maxChildren?: number;
1932
+ /** Name of the resolved role that owns this scope. Default
1933
+ * `"supervisor"` — see `OrchestratorScope.role`. */
1934
+ role?: string;
1332
1935
  }
1333
1936
  interface ScopeTokenRegistry {
1334
1937
  /** Mint a fresh scope-token. See `MintScopeOptions`. */
@@ -1447,6 +2050,11 @@ type OrchestratorInjector = (opts?: {
1447
2050
  /** Override the per-orchestrator child quota for the minted scope
1448
2051
  * (clamped to the caller's). Root-only knob. */
1449
2052
  maxChildren?: number;
2053
+ /** Name of the resolved role for the session THIS scope is being
2054
+ * minted for (i.e. the session becoming an orchestrator) — becomes
2055
+ * `OrchestratorScope.role`, the "parent role" a future spawn
2056
+ * through this scope is gated against. Default `"supervisor"`. */
2057
+ role?: string;
1450
2058
  }) => OrchestratorInjection;
1451
2059
  /**
1452
2060
  * Build the orchestrator injector. Closed over the gateway's
@@ -1456,6 +2064,95 @@ type OrchestratorInjector = (opts?: {
1456
2064
  */
1457
2065
  declare function createOrchestratorInjector(deps: OrchestratorInjectorDeps): OrchestratorInjector;
1458
2066
 
2067
+ /**
2068
+ * Shared types for pluggable sandbox providers.
2069
+ *
2070
+ * The sandbox family is a `@agentproto/provider-kit` consumer, same shape as
2071
+ * the tunnel family (`remote-providers/types.ts`): {@link SandboxProviderHandle}
2072
+ * extends the kit's generic `AdapterHandle` (slug/name/version/description/
2073
+ * requiresSetup/check) with the AIP-36 capability namespace and the concrete
2074
+ * `@agentproto/sandbox` `SandboxProvider` (`boot()`) the handle wraps.
2075
+ */
2076
+
2077
+ /**
2078
+ * Declared capabilities of a sandbox provider — pure metadata, surfaced in
2079
+ * `list_sandbox_providers`. Never carries secrets. Namespace mirrors AIP-36
2080
+ * SANDBOX.md (`network.egress`, `mounts`, `lifecycle.pause_after_idle`,
2081
+ * `read_only`, `limits.timeout_ms`).
2082
+ */
2083
+ interface SandboxProviderCapabilities {
2084
+ /** Sandbox can be given a network egress allowlist (AIP-36 `network.egress`). */
2085
+ networkEgress: boolean;
2086
+ /** Sandbox supports mounting external filesystems (AIP-36 `mounts`). */
2087
+ mounts: boolean;
2088
+ /** Sandbox can be paused (not just killed) between turns (AIP-36 `lifecycle.pause_after_idle`). */
2089
+ lifecyclePause: boolean;
2090
+ /** Sandbox can be started read-only (AIP-36 `read_only`). */
2091
+ readOnly: boolean;
2092
+ /** Hard cap on `limits.timeout_ms`, when the provider enforces one. */
2093
+ maxTimeoutMs?: number;
2094
+ }
2095
+ /**
2096
+ * A sandbox provider as an adapter-kit handle. Rides on the kit's generic
2097
+ * {@link AdapterHandle} and adds the sandbox-specific `capabilities` plus
2098
+ * the `@agentproto/sandbox` `SandboxProvider` the handle wraps — the thing
2099
+ * `createSandboxAgentSessionHost` actually boots.
2100
+ */
2101
+ interface SandboxProviderHandle extends AdapterHandle {
2102
+ readonly provider: SandboxProvider;
2103
+ readonly capabilities: SandboxProviderCapabilities;
2104
+ /**
2105
+ * Credential fields this provider accepts via `setup_sandbox_provider`
2106
+ * (e.g. e2b's `apiKey`). Omit (or empty) when the provider needs no
2107
+ * credentials (e.g. `local`).
2108
+ */
2109
+ readonly setupFields?: readonly SetupField[];
2110
+ }
2111
+
2112
+ /**
2113
+ * Sandbox family on top of `@agentproto/provider-kit` — mirrors
2114
+ * `tunnel-adapters.ts`. This module is the entire bridge between the
2115
+ * generic kit and `@agentproto/sandbox`'s `SandboxProvider` concept: it
2116
+ * contributes nothing the kit already owns (catalog/status/creds/ledger/
2117
+ * list/MCP-tool plumbing); it only supplies the sandbox-family `TInfo`
2118
+ * (`SandboxAdapterInfo`), the static `SANDBOX_CATALOG`, and the resolver
2119
+ * that maps a catalog slug to a concrete {@link SandboxProviderHandle}
2120
+ * (`./sandbox-providers/registry.js`).
2121
+ *
2122
+ * Kit primitives used:
2123
+ * - `makeCredsStore` → per-slug 0600 creds under `~/.agentproto/sandbox-creds/`
2124
+ * - `makeSetupLedger` → `~/.agentproto/setup/<slug>.json`
2125
+ * - `makeAdapterResolver` → wraps the throwing `load` into null-on-miss
2126
+ * - `makeAdapterLister` → catalog → status-classified `AdapterEntry[]`
2127
+ * - `makeListTool` → registers `list_sandbox_providers`
2128
+ * - `makeSetupTool` → registers `setup_sandbox_provider` (multi-field
2129
+ * form: e2b's `apiKey`, sensitive)
2130
+ *
2131
+ * This is pure additive plumbing (introspection + setup) — it does NOT wire
2132
+ * a `sandbox` field into `agent_start`. That lands in a follow-up PR; the
2133
+ * `resolveSandboxProvider`/`listSandboxProviders` overrides below exist now
2134
+ * so that later wiring can inject the same resolver/lister this module
2135
+ * builds by default, mirroring `resolveAgentAdapter`/`listAgentAdapters`.
2136
+ *
2137
+ * Security: `toSandboxInfo` exposes only `capabilities` — never a cred
2138
+ * value (Appendix B). The setup tool's fields are marked SENSITIVE and the
2139
+ * result NEVER echoes any field value back.
2140
+ */
2141
+
2142
+ /**
2143
+ * Family descriptor (`TInfo`). Pure metadata surfaced in
2144
+ * `list_sandbox_providers`. The kit's `AdapterEntry` already carries
2145
+ * slug/name/description/status/version, so the only sandbox-specific field
2146
+ * is the declared capability set. NEVER carries a cred value.
2147
+ */
2148
+ interface SandboxAdapterInfo {
2149
+ capabilities: SandboxProviderCapabilities;
2150
+ }
2151
+ /** Resolve a sandbox provider slug to a handle, or null when unavailable. */
2152
+ type SandboxProviderResolver = AdapterResolver<SandboxProviderHandle>;
2153
+ /** List every sandbox provider with its live status + capabilities. */
2154
+ type SandboxProviderLister = AdapterLister<SandboxAdapterInfo>;
2155
+
1459
2156
  /**
1460
2157
  * Pluggable adapter resolver — keeps the runtime package free of any
1461
2158
  * @agentproto/cli dep. The host (cli `serve`, playground, embedding
@@ -1489,6 +2186,17 @@ type AgentAdapterResolver = (slug: string) => Promise<{
1489
2186
  * manifest, so a typo fails the spawn rather than silently no-op).
1490
2187
  */
1491
2188
  mode?: string;
2189
+ /**
2190
+ * Manifest-declared option id → value map forwarded from
2191
+ * `agent_start` (AIP-45 `AgentCliHandle.options` — e.g. hermes'
2192
+ * `skills`). Applied at spawn time via `composeSpawn`'s option
2193
+ * patches (`bin_args_prepend` / `bin_args_template` /
2194
+ * `bin_args_append_when_true` / `env`), validated against each
2195
+ * option's declared `type`/`enum`/`min`/`max`. An id the adapter
2196
+ * doesn't declare throws `RuntimeConfigError` (composeSpawn
2197
+ * validates against the manifest, same as an unknown `mode`).
2198
+ */
2199
+ options?: Record<string, boolean | number | string>;
1492
2200
  /** Model identifier forwarded from `agent_start`. For ACP
1493
2201
  * adapters this is applied via session/set_config_option after
1494
2202
  * newSession (the ACP wrapper does not forward CLI args to claude).
@@ -1511,6 +2219,17 @@ type AgentAdapterResolver = (slug: string) => Promise<{
1511
2219
  * handler, POST /sessions/agent) wires this to pulse
1512
2220
  * `SessionDescriptor.lastActivityAt` via `registry.pulseActivity(id)`. */
1513
2221
  onActivity?: () => void;
2222
+ /** Start the session in permission-hold mode — forwarded to the driver's
2223
+ * `runtime.start({ permissionHold })` so each ACP permission request is
2224
+ * surfaced + parked in the daemon's inbox instead of auto-answered.
2225
+ * Adapters/arms with no permission surface ignore it. Default false. */
2226
+ permissionHold?: boolean;
2227
+ /** FULLY-RESOLVED billing-auth spec forwarded from `agent_start` to the
2228
+ * driver's `runtime.start({ auth })`, computed by the runtime's
2229
+ * `resolveAuthSpec` (provider, ordered mode, setEnv/scrub, credential
2230
+ * source all pre-decided). The driver applies it mechanically. Absent
2231
+ * when the resolver produced no spec (ambient). */
2232
+ auth?: ResolvedAuthSpec;
1514
2233
  }): Promise<AgentSessionLike>;
1515
2234
  /** Display label for the descriptor's `command` field. */
1516
2235
  commandPreview?: string;
@@ -1520,7 +2239,39 @@ type AgentAdapterResolver = (slug: string) => Promise<{
1520
2239
  tokensIn?: number;
1521
2240
  tokensOut?: number;
1522
2241
  } | null>;
2242
+ /** AIP-45 `options[]` this adapter's manifest declares (id + type only —
2243
+ * no spawn internals). Lets `session-spawn.ts` fold a config-level
2244
+ * `defaults.skills` list into `options.skills` using the shape the
2245
+ * manifest actually declared (e.g. hermes' comma-joined string),
2246
+ * instead of guessing. Omitted/empty ⇒ the skills normalization is a
2247
+ * documented no-op for that adapter (e.g. claude-code, which
2248
+ * auto-discovers skills and declares no such option). */
2249
+ declaredOptions?: readonly DeclaredAdapterOption[];
2250
+ /** Billing-auth descriptor projected from the adapter manifest
2251
+ * (`provider` / `authEnforce` / `authSubscription`) — the sole input the
2252
+ * runtime's `resolveAuthSpec` reads about the adapter's auth capability
2253
+ * (keeping the catalog coupling in the runtime, the driver mechanical).
2254
+ * Omitted ⇒ ambient (no credential injection). */
2255
+ authDescriptor?: AdapterAuthDescriptor;
2256
+ /** Adapter's default model id (`models.default`) — lets the resolver derive
2257
+ * a provider for a by-model spawn that omitted `model`. */
2258
+ defaultModel?: string;
1523
2259
  } | null>;
2260
+ /**
2261
+ * UI-safe projection of an AIP-45 `modes[]` entry as surfaced by
2262
+ * `adapter_list`. Mirrors `@agentproto/cli`'s `AdapterMode` without
2263
+ * importing it (the runtime deliberately carries no cli dep — see the
2264
+ * `AgentAdapterLister` note above). Spawn internals (`bin_args_*`, `env`)
2265
+ * are intentionally omitted; `status` is normalised to `"active"` by the
2266
+ * lister when the manifest omits it, so a declared mode is never
2267
+ * silently statusless.
2268
+ */
2269
+ interface AdapterListMode {
2270
+ id: string;
2271
+ description?: string;
2272
+ status: "active" | "noop" | "planned";
2273
+ status_note?: string;
2274
+ }
1524
2275
  /**
1525
2276
  * Compact adapter metadata for the discovery endpoints. Independent
1526
2277
  * of the resolver function above — hosts that can list installed
@@ -1535,6 +2286,10 @@ interface AdapterListEntry {
1535
2286
  protocol: string;
1536
2287
  streaming: boolean;
1537
2288
  packageName: string;
2289
+ /** Declared operation modes with their honest support status, so a
2290
+ * client can see e.g. hermes' `lean` mode is a measured no-op instead
2291
+ * of being silently accepted. Empty when the adapter declares none. */
2292
+ modes: AdapterListMode[];
1538
2293
  }
1539
2294
  type AgentAdapterLister = () => Promise<AdapterListEntry[]>;
1540
2295
  interface AuthOptions {
@@ -1621,7 +2376,7 @@ interface InboundWatcher {
1621
2376
  }
1622
2377
 
1623
2378
  /**
1624
- * Browser family on top of `@agentproto/adapter-kit` — Phase 3 (lightest
2379
+ * Browser family on top of `@agentproto/provider-kit` — Phase 3 (lightest
1625
2380
  * adoption: no creds, no wizard, no catalog file).
1626
2381
  *
1627
2382
  * Provides the kit-compatible types (`BrowserAdapterHandle`) and the
@@ -1680,6 +2435,137 @@ declare function formatToolCall(toolName: string, args: unknown): string;
1680
2435
  */
1681
2436
  declare function formatToolResult(toolName: string | undefined, result: unknown, isError: boolean): string | null;
1682
2437
 
2438
+ /**
2439
+ * Dependency-injection surface for MCP credential resolution.
2440
+ *
2441
+ * `packages/runtime` intentionally does NOT depend on `@agentproto/auth`;
2442
+ * the daemon bootstrap (e.g. `packages/cli`) can set a broker-backed
2443
+ * resolver here, and `session-spawn.ts` will call it when an `mcpServers`
2444
+ * entry carries `credentialRef`. Errors from the hook are treated as
2445
+ * non-fatal by the caller.
2446
+ */
2447
+ interface McpCredentialDeps {
2448
+ /** Resolve brokered headers for an `mcpServers` entry that names a
2449
+ * `credentialRef`. The returned headers are merged ON TOP of the
2450
+ * entry's static `headers` so brokered auth wins on collision. */
2451
+ resolveMcpCredentialHeaders?: (o: {
2452
+ credentialRef: string;
2453
+ signal?: AbortSignal;
2454
+ }) => Promise<Record<string, string> | undefined>;
2455
+ /** Resolve a single named secret (an env-var-shaped slug — e.g. a sandbox
2456
+ * spec's `env.passthrough` / `env.auth.state.env` entries) to its raw
2457
+ * value. Distinct from `resolveMcpCredentialHeaders` above (which
2458
+ * resolves an MCP `credentialRef` PATH into Authorization-style
2459
+ * headers): this is the `SecretResolver` shape `agent_start`'s sandbox
2460
+ * branch (`session-spawn.ts`) passes to `@agentproto/sandbox`'s
2461
+ * `SandboxSecretsConfig.resolver` — so a booted sandbox's env is filled
2462
+ * from the same host-injected broker as every other secret, never a
2463
+ * bare `process.env` read at the call site. Returns null when the slug
2464
+ * can't be resolved (missing/unconfigured). */
2465
+ resolveSandboxSecret?: (slug: string) => Promise<string | null>;
2466
+ }
2467
+ declare function setMcpCredentialDeps(d: McpCredentialDeps): void;
2468
+ declare function getMcpCredentialDeps(): McpCredentialDeps;
2469
+
2470
+ /**
2471
+ * MCP tools that drive the pairing registry — the daemon's "let a client pair
2472
+ * with me over an untrusted rendezvous" surface (design: DESIGN §6).
2473
+ *
2474
+ * Three tools, mirroring the CLI verbs:
2475
+ *
2476
+ * pair_offer — mint a one-time offer + start dialing the rendezvous; returns
2477
+ * the `agentproto://pair?…` URL, fingerprint, and expiry.
2478
+ * pair_list — list persisted pairings (name, fingerprint, lastSeen).
2479
+ * pair_revoke — drop a pairing by fingerprint or name; stops its rendezvous
2480
+ * connections so it can no longer reconnect.
2481
+ *
2482
+ * Registered beside `remote_*` (remote-tools.ts) — same closure-rebind pattern;
2483
+ * the registry singleton lives on the gateway.
2484
+ */
2485
+
2486
+ interface RegisterPairingToolsOptions {
2487
+ registry: PairingRegistry;
2488
+ }
2489
+ declare function registerPairingTools(server: McpServer, opts: RegisterPairingToolsOptions): void;
2490
+
2491
+ /**
2492
+ * Provider-preset family on top of `@agentproto/provider-kit` — the *thin,
2493
+ * honest* lister (Option B). Unlike tunnels/sandboxes/eval-reporters, presets
2494
+ * are bundled static data with no binary to install, no package to resolve, and
2495
+ * no setup ledger: their only "setup" is an ambient API-key env var. So this
2496
+ * module does NOT use `makeAdapterResolver` / `makeAdapterLister` / the wizard
2497
+ * (all install-oriented, a forced fit). It reuses only `makeListTool` — the one
2498
+ * kit primitive that's genuinely about "JSON a lister's output as an MCP tool."
2499
+ *
2500
+ * Status is derived honestly from the daemon's own environment (the env agents
2501
+ * spawn into), not the CLI caller's:
2502
+ * "ready" — `process.env[preset.keyEnv]` is set (key available to spawn)
2503
+ * "available" — key absent (preset known, usable once the env var is provided)
2504
+ *
2505
+ * There is no "supported" state: presets ship in-repo, so they're always at
2506
+ * least "available." `version` is the literal "built-in".
2507
+ *
2508
+ * Security (Appendix B): `PresetInfo` carries only `keyEnv` (the env-var NAME),
2509
+ * never a key value. The list tool never returns creds — there are none to
2510
+ * return; keys live in the operator's environment, not a creds store.
2511
+ */
2512
+
2513
+ /**
2514
+ * Manifest-declared AIP-45 preset, the minimum the catalog needs to merge
2515
+ * an adapter-contributed preset into the listing. Mirrors
2516
+ * `AgentCliPresetDeclaration`'s fields without importing
2517
+ * `@agentproto/driver-agent-cli` into the runtime package (same structural-
2518
+ * type pattern as `DeclaredAdapterOption` in spawn-defaults.ts).
2519
+ */
2520
+ interface DeclaredAdapterPreset {
2521
+ id: string;
2522
+ label: string;
2523
+ description?: string;
2524
+ schemaFlavor: ProviderPreset["schemaFlavor"];
2525
+ baseUrl: string;
2526
+ keyEnv: string;
2527
+ scrubEnv?: string[];
2528
+ defaultModel?: string;
2529
+ homepage?: string;
2530
+ }
2531
+ /**
2532
+ * Family descriptor surfaced as `AdapterEntry.info`. User-facing preset facts
2533
+ * only — no cred values (there are none in the registry), no adapter projection
2534
+ * detail (`scrubEnv` stays internal to adapter manifests).
2535
+ */
2536
+ interface PresetInfo {
2537
+ /** API schema flavor — which adapter family can consume this preset. */
2538
+ schemaFlavor: ProviderPreset["schemaFlavor"];
2539
+ /** Base URL the client hits. */
2540
+ baseUrl: string;
2541
+ /** Env-var NAME holding the API key (never the value). */
2542
+ keyEnv: string;
2543
+ /** Conventional default model id, if any. */
2544
+ defaultModel?: string;
2545
+ /** Homepage/docs URL. */
2546
+ homepage?: string;
2547
+ }
2548
+ /**
2549
+ * Normalize a manifest-declared preset into the canonical `ProviderPreset`
2550
+ * shape the catalog lists. `scrubEnv` defaults to empty (an adapter that
2551
+ * scrubs in code may omit it); `description` defaults to empty. Pure.
2552
+ */
2553
+ declare function declaredPresetToProviderPreset(decl: DeclaredAdapterPreset): ProviderPreset;
2554
+ /**
2555
+ * Map the static preset registry → status-classified catalog entries, reading
2556
+ * the daemon's environment for key presence. Pure (no I/O) save the `process`
2557
+ * .env read; safe to call on every list invocation.
2558
+ *
2559
+ * `adapterPresets` — manifest-declared presets contributed by loaded adapters
2560
+ * (Stage 3 AIP-45 `presets` field). Merged AFTER the built-in registry and
2561
+ * deduped by id: an adapter-declared id that collides with a built-in is
2562
+ * ignored (the registry is the source of truth for canonical providers), so
2563
+ * an adapter can't silently override moonshot/openrouter facts. The live
2564
+ * wiring that reads each adapter's `.presets` and passes them here is Stage 4;
2565
+ * today this is the tested seam.
2566
+ */
2567
+ declare function listPresets(env?: Record<string, string | undefined>, adapterPresets?: readonly DeclaredAdapterPreset[]): AdapterEntry<PresetInfo>[];
2568
+
1683
2569
  /** A journal-file StepCache scoped to one cacheKey. Best-effort: read/parse
1684
2570
  * failures degrade to "no cache" (a miss), never throw into the run. */
1685
2571
  declare function createFileStepCache(cacheKey: string, opts?: {
@@ -1984,6 +2870,27 @@ interface CreateGatewayOptions {
1984
2870
  deferredTools?: {
1985
2871
  alwaysOn?: readonly string[];
1986
2872
  };
2873
+ /**
2874
+ * Optional sandbox provider resolver — overrides the sandbox family's
2875
+ * default resolver (built-ins + `@agentproto/sandbox-<slug>` dynamic
2876
+ * import) used by `list_sandbox_providers` / `setup_sandbox_provider`
2877
+ * AND `agent_start.sandbox`. Mirrors `resolveAgentAdapter`'s injection
2878
+ * shape.
2879
+ */
2880
+ resolveSandboxProvider?: SandboxProviderResolver;
2881
+ /** Optional sandbox provider lister — mirrors `listAgentAdapters`.
2882
+ * Overrides the default catalog-driven lister behind `list_sandbox_providers`. */
2883
+ listSandboxProviders?: SandboxProviderLister;
2884
+ /**
2885
+ * Optional E2E pairing registry (see `createPairingRegistry`). When wired,
2886
+ * the gateway mounts the `/pairings/*` REST routes and the `pair_*` MCP
2887
+ * tools, and exposes the registry on the handle. The CLI builds it (injecting
2888
+ * the ws `dial` + a `createTunnelServer`-backed `serve`) and passes it here so
2889
+ * the runtime stays free of pty/adapter concerns while the pairing surface is
2890
+ * reachable over MCP + HTTP. Autoconnect is the caller's to start (after this
2891
+ * returns) so the injected `serve` can capture the finished gateway.
2892
+ */
2893
+ pairingRegistry?: PairingRegistry;
1987
2894
  }
1988
2895
  interface GatewayHandle {
1989
2896
  url: string;
@@ -1998,6 +2905,10 @@ interface GatewayHandle {
1998
2905
  * local port. Exposed so embedding hosts can open tunnels
1999
2906
  * programmatically without going through the HTTP/MCP surface. */
2000
2907
  tunnels: TunnelRegistry;
2908
+ /** E2E pairing registry, when one was wired via
2909
+ * `CreateGatewayOptions.pairingRegistry`. Undefined otherwise. Exposed so
2910
+ * the CLI can `startAutoconnect()` after boot and `shutdown()` it. */
2911
+ pairing?: PairingRegistry;
2001
2912
  /** Per-boot bearer token required on mutating /sessions/* routes
2002
2913
  * + WS PTY upgrades. Exposed so an embedding host (e.g. the CLI
2003
2914
  * shell that hosts the gateway in-process) can pass it to child
@@ -2027,4 +2938,4 @@ interface GatewayHandle {
2027
2938
  */
2028
2939
  declare function createGateway(opts: CreateGatewayOptions): Promise<GatewayHandle>;
2029
2940
 
2030
- export { type AdapterListEntry, type AgentAdapterLister, type AgentAdapterResolver, type AgentSessionLike, type AgentStreamEvent, type AttachPolicyInput, type BrowserAdapterHandle, type BrowserAdapterInfo, type BrowserAdapterLister, type BrowserAdapterResolver, BuildHeartbeatAgent, type CommitSpec, type CompletionPolicySupervisor, type CreateGatewayOptions, DEFAULT_ORCHESTRATOR_TOOLS, type GateSpec, type GatewayHandle, type InboundWatcher, type JudgeGateSpec, type OnFailSpec, type OrchestratorGatewayDeps, type OrchestratorInjection, type OrchestratorInjector, type OrchestratorInjectorDeps, type OrchestratorMcpServerFactory, type OrchestratorScope, type PolicyRunState, type PolicyRunStatus, type RegisterBrowserInput, type RegisterSessionInput, type RuntimeMeta, type ScopeTokenRegistry, type SessionDescriptor, type SessionKind, type SessionStatus, type SessionWaitEvent, type SessionWaitResult, type SessionsRegistry, type ShellGateSpec, type SpawnAgentInput, type SpawnSessionInput, type TunnelDescriptor, type TunnelProvider, type TunnelStatus, type WatcherDescriptor, type WatcherStartInput, WorkspaceFs, createFileStepCache, createGateway, createOrchestratorInjector, createOrchestratorMcpServerFactory, createScopeTokenRegistry, daemonRegistryDir, formatToolCall, formatToolResult, makeBrowserAdapterLister, monitorPolicyWait, monitorSessionWait, narrowOrchestratorTools, readDaemonRegistry, readRuntimeMeta, sweepStaleDaemonRegistry, sweepStaleRuntimeMetas, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, writeDaemonRegistryEntry };
2941
+ export { AdapterAuthDescriptor, type AdapterListEntry, type AgentAdapterLister, type AgentAdapterResolver, type AgentSessionLike, type AgentStreamEvent, type AttachPolicyInput, type BrowserAdapterHandle, type BrowserAdapterInfo, type BrowserAdapterLister, type BrowserAdapterResolver, BuildHeartbeatAgent, type CommitSpec, type CompletionPolicySupervisor, type CreateGatewayOptions, type CreateOfferInput, type CreatedOffer, DEFAULT_ORCHESTRATOR_TOOLS, DeclaredAdapterOption, type DeclaredAdapterPreset, type GateSpec, type GatewayHandle, type InboundWatcher, type JudgeGateSpec, type McpCredentialDeps, type OnFailSpec, type OrchestratorGatewayDeps, type OrchestratorInjection, type OrchestratorInjector, type OrchestratorInjectorDeps, type OrchestratorMcpServerFactory, type OrchestratorScope, PAIRINGS_VERSION, type PairingChannelContext, type PairingChannelHandle, type PairingChannelMode, type PairingRecord, type PairingRegistry, type PairingRegistryDeps, type PendingPermission, type PermissionRespondInput, type PermissionRespondResult, type PolicyRunState, type PolicyRunStatus, type PresetInfo, type PricingResolver, type RegisterBrowserInput, type RegisterPairingToolsOptions, type RegisterSessionInput, ResolvedAuthSpec, type RuntimeMeta, type SandboxAdapterInfo, type SandboxProviderCapabilities, type SandboxProviderHandle, type SandboxProviderLister, type SandboxProviderResolver, type ScopeTokenRegistry, type SessionDescriptor, type SessionKind, type SessionObserver, type SessionStatus, type SessionUsage, type SessionWaitEvent, type SessionWaitResult, type SessionsRegistry, type ShellGateSpec, type SpawnAgentInput, type SpawnSessionInput, type TokenPricing, type TunnelDescriptor, type TunnelProvider, type TunnelStatus, type UsageComputeInput, type UsageSource, type WatcherDescriptor, type WatcherStartInput, WorkspaceFs, composeSessionObservers, createFileStepCache, createGateway, createOrchestratorInjector, createOrchestratorMcpServerFactory, createPairingRegistry, createScopeTokenRegistry, daemonRegistryDir, declaredPresetToProviderPreset, deriveSessionUsage, formatToolCall, formatToolResult, getMcpCredentialDeps, listPresets, makeBrowserAdapterLister, monitorPolicyWait, monitorSessionWait, narrowOrchestratorTools, projectSessionUsage, readDaemonRegistry, readRuntimeMeta, registerPairingTools, setMcpCredentialDeps, sweepStaleDaemonRegistry, sweepStaleRuntimeMetas, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, writeDaemonRegistryEntry };