@agentproto/runtime 0.4.0 → 0.5.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
@@ -3,14 +3,131 @@ 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
4
  import { AcpMcpServer } from '@agentproto/acp';
5
5
  import { ChildProcess } from 'node:child_process';
6
+ import { D as DeclaredAdapterOption } from './spawn-defaults-d5gAhNkV.js';
6
7
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
- import { AdapterHandle, AdapterLister } from '@agentproto/adapter-kit';
8
+ import { AdapterHandle, SetupField, AdapterResolver, AdapterLister, AdapterEntry } from '@agentproto/provider-kit';
9
+ import { SandboxProvider } from '@agentproto/sandbox';
8
10
  import { WorkspaceFs } from './workspace-fs.js';
9
11
  export { createWorkspaceFs } from './workspace-fs.js';
10
12
  export { ConversationMeta, ConversationStore, ConversationTurn, fileConversationStore } from './conversations.js';
13
+ import { ProviderPreset } from '@agentproto/provider-presets';
11
14
  import { StepCache } from '@agentproto/workflow-runtime';
12
15
  export { PROVIDER_ENV_VARS, ProviderEntry, ProvidersFile, injectProviderKeysIntoEnv, loadProviders, providerEnvVar, providersPath, removeProviderKey, setProviderKey } from './providers-store.js';
13
16
 
17
+ /**
18
+ * Per-session JSONL record of a completed `command_execute` invocation
19
+ * (and cron's `kind: "command"` action jobs, which share the same
20
+ * allowlist + `runCommand` path — see cron-scheduler.ts's "one
21
+ * enforcement path, not two" comment).
22
+ *
23
+ * Each call gets its OWN `kind: "command"` session, minted via
24
+ * `SessionsRegistry.recordCommand` — same as an agent-cli or PTY session,
25
+ * so it shows up in `session_list` / `command_list` for free instead of
26
+ * needing a bespoke query surface. Its result is stored at the exact
27
+ * per-id path an agent-cli session's structured transcript already uses:
28
+ * `~/.agentproto/sessions/<id>/events.jsonl` (see transcript-writer.ts).
29
+ * A command session's file holds exactly one line — there's no seq/turn
30
+ * machinery to reconstruct because the call is already finished by the
31
+ * time this is written.
32
+ *
33
+ * This module only knows how to read/write that one line; minting the
34
+ * session itself (and deciding the file's base directory) is
35
+ * `sessions.ts`'s job via `recordCommand`.
36
+ */
37
+ interface CommandLogEntry {
38
+ ts: string;
39
+ command: string;
40
+ args: string[];
41
+ cwd: string;
42
+ exitCode: number;
43
+ signal: string | null;
44
+ durationMs: number;
45
+ stdout: string;
46
+ stderr: string;
47
+ truncated?: boolean;
48
+ }
49
+
50
+ /**
51
+ * Per-session usage snapshot — the single place that decides a session's
52
+ * `costUsd` and where that number came from.
53
+ *
54
+ * Two cost sources feed a session:
55
+ * - an adapter's own usage reader (`readUsage`, e.g. hermes reads its
56
+ * state.db) or an ACP `usage_update` that carries a `cost` block — these
57
+ * are authoritative, tagged `source: "adapter"`.
58
+ * - raw input/output token counts with NO adapter cost (ACP adapters like
59
+ * claude-code / mastracode that report tokens but not dollars) — here we
60
+ * price them ourselves against agentproto's in-repo LLM pricing catalog,
61
+ * tagged `source: "computed"`.
62
+ *
63
+ * When tokens exist but the model is absent from the catalog we NEVER
64
+ * fabricate a price: `costUsd` is left undefined and the snapshot is tagged
65
+ * `source: "no-pricing"`. A session with neither cost nor tokens is
66
+ * `source: "none"`.
67
+ *
68
+ * This module is pure (the catalog lookup is injected) so the decision tree is
69
+ * unit-testable without building the whole model catalog.
70
+ */
71
+ /** Where a session's `costUsd` came from — see the module doc. */
72
+ type UsageSource = "adapter" | "computed" | "no-pricing" | "none";
73
+ /** Per-token USD prices for a model (per 1M tokens), the subset of the
74
+ * catalog's `LLMPricing` this module needs. */
75
+ interface TokenPricing {
76
+ inputPer1M: number;
77
+ outputPer1M: number;
78
+ }
79
+ /** Pluggable pricing accessor — defaults to the in-repo catalog's
80
+ * `resolvePricing`, overridable in tests. Returns undefined for an
81
+ * unknown model (the caller then tags `no-pricing`). */
82
+ type PricingResolver = (model: string) => TokenPricing | undefined;
83
+ /** Raw signals gathered over a turn, handed to `deriveSessionUsage`. */
84
+ interface UsageComputeInput {
85
+ /** Requested model id — needed to look up per-token prices. */
86
+ model?: string;
87
+ /** Cost the adapter reported directly (readUsage or usage_update.cost).
88
+ * Present → authoritative, wins over token-based computation. */
89
+ adapterCostUsd?: number;
90
+ tokensIn?: number;
91
+ tokensOut?: number;
92
+ contextSize?: number;
93
+ contextUsed?: number;
94
+ }
95
+ /** The resolved usage snapshot — the shape `session_usage` returns and the
96
+ * durable `usage_snapshot` transcript record carries. Cost/token fields are
97
+ * omitted when absent so a missing value never reads as a measured zero. */
98
+ interface SessionUsage {
99
+ model?: string;
100
+ costUsd?: number;
101
+ tokensIn?: number;
102
+ tokensOut?: number;
103
+ contextSize?: number;
104
+ contextUsed?: number;
105
+ source: UsageSource;
106
+ }
107
+ /**
108
+ * Decide a session's usage snapshot + cost source from the raw signals.
109
+ *
110
+ * Priority: an adapter-reported cost wins; else price the tokens against the
111
+ * catalog; else `none`. Never fabricates a price for an unpriced model.
112
+ */
113
+ declare function deriveSessionUsage(input: UsageComputeInput, resolve?: PricingResolver): SessionUsage;
114
+ /** Descriptor-shaped fields `projectSessionUsage` reads. */
115
+ interface UsageDescriptorFields {
116
+ model?: string;
117
+ costUsd?: number;
118
+ tokensIn?: number;
119
+ tokensOut?: number;
120
+ contextSize?: number;
121
+ contextUsed?: number;
122
+ usageSource?: UsageSource;
123
+ }
124
+ /**
125
+ * Project a session descriptor's persisted usage fields into the
126
+ * `session_usage` response shape. Omits absent fields; defaults an
127
+ * unstamped `usageSource` to `"none"`.
128
+ */
129
+ declare function projectSessionUsage(desc: UsageDescriptorFields): SessionUsage;
130
+
14
131
  /**
15
132
  * In-process pub/sub bus for session lifecycle events. Separate from
16
133
  * RuntimeEvents (global daemon bus) — session events are scoped to
@@ -152,26 +269,40 @@ interface SessionEventBus {
152
269
  }
153
270
 
154
271
  /**
155
- * Sessions registrytracks long-lived child processes spawned by
156
- * the agentproto daemon (terminals, agent CLIs, custom commands).
272
+ * A per-session observer the pluggable seam behind the runtime's transcript
273
+ * tap. `sessions.ts` drives one observer per turn (prompt in, each stream event,
274
+ * the turn-boundary usage snapshot, close). The transcript writer is the first
275
+ * and, today, only member; additional observers (e.g. a Langfuse session tracer)
276
+ * attach without touching the turn loop.
157
277
  *
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.
162
- *
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
278
+ * The method set is exactly the subset of `TranscriptWriter` the session loop
279
+ * calls, so `TranscriptWriter` IS a `SessionObserver` (see transcript-writer.ts).
280
+ */
281
+
282
+ interface SessionObserver {
283
+ /** Record the outgoing message that opens a new turn. */
284
+ recordPrompt(sessionId: string, message: unknown): void;
285
+ /** Record one structured stream event (text-delta, tool-call, usage_update, ). */
286
+ recordEvent(sessionId: string, evt: AgentStreamEvent): void;
287
+ /** Record the durable turn-boundary / exit usage snapshot. */
288
+ recordUsageSnapshot(sessionId: string, usage: SessionUsage): void;
289
+ /** Flush and close this session's stream. Idempotent, fire-and-forget. */
290
+ close(sessionId: string): Promise<void>;
291
+ /** Close every open session stream (synchronous shutdown path). */
292
+ closeAll(): Promise<void>;
293
+ }
294
+ /**
295
+ * Fan a `SessionObserver` out over many. Each `record*` call is forwarded to
296
+ * every member in order; `close`/`closeAll` await all members.
168
297
  *
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)
298
+ * Every individual member call is isolated in try/catch so one throwing observer
299
+ * can neither break its siblings nor propagate into the turn loop — the
300
+ * transcript writer is effectively infallible today, but a future network-backed
301
+ * observer (a Langfuse sink) must never be able to throw a turn. Failures are
302
+ * swallowed here (no logging dependency at this layer); observers that need
303
+ * visibility into their own failures own that internally.
174
304
  */
305
+ declare function composeSessionObservers(observers: readonly SessionObserver[]): SessionObserver;
175
306
 
176
307
  /**
177
308
  * Minimal shape we need from a driver-agent-cli session — kept as a
@@ -260,6 +391,11 @@ interface AgentStreamEvent {
260
391
  amount: number;
261
392
  currency: string;
262
393
  };
394
+ /** "usage_update" cumulative input/output token counts, when the adapter
395
+ * reports them (lets the daemon price a session whose adapter gives tokens
396
+ * but no `cost`). */
397
+ tokensIn?: number;
398
+ tokensOut?: number;
263
399
  }
264
400
  type SessionKind = "terminal" | "agent-cli" | "command" | "browser";
265
401
  type SessionStatus = "starting" | "running" | "exited" | "killed" | "error";
@@ -322,6 +458,16 @@ interface SessionDescriptor {
322
458
  /** Cumulative input / output token counts (same source + cadence as costUsd). */
323
459
  tokensIn?: number;
324
460
  tokensOut?: number;
461
+ /** Context-window size + tokens-in-context from the latest `usage_update`
462
+ * event (ACP adapters that report a context window). Refreshed live as
463
+ * usage_update events arrive, not just at turn-end. */
464
+ contextSize?: number;
465
+ contextUsed?: number;
466
+ /** Where `costUsd` came from — `"adapter"` (adapter's own reader or a
467
+ * usage_update cost block), `"computed"` (tokens × in-repo catalog price),
468
+ * `"no-pricing"` (tokens present but the model isn't in the catalog — cost
469
+ * deliberately left undefined), or `"none"`. Stamped at each turn-end. */
470
+ usageSource?: UsageSource;
325
471
  /** ACP-level session id (the adapter's own handle — claude-code's
326
472
  * conversation id, hermes' chat id, …). Set at spawnAgent time
327
473
  * from `agentSession.sessionId`. Survives across daemon restarts
@@ -374,6 +520,17 @@ interface SessionDescriptor {
374
520
  * finished turn" from "mid-turn" so it doesn't fast-return a stale
375
521
  * turn-end while the NEXT turn is still generating. */
376
522
  busy?: boolean;
523
+ /** What the in-flight turn is currently blocked on, when classifiable
524
+ * from the pending tool call: a spawned sub-agent (`agent_start`) or a
525
+ * shell/terminal command. Deliberately NO "user" variant — waiting on
526
+ * the user is already covered by `awaitingInput`/`awaitingQuestion`.
527
+ * Set on tool-call, cleared on the MATCHING tool-result (guarded by
528
+ * `pendingToolCallId`), at turn start, and in the turn's finally. */
529
+ blockedOn?: "subagent" | "command";
530
+ /** toolCallId of the tool-call that set `blockedOn`. A tool-result only
531
+ * clears `blockedOn` when its toolCallId matches — so a nested or
532
+ * interleaved tool finishing first can't clear the flag early. */
533
+ pendingToolCallId?: string;
377
534
  /** Session id of the orchestrator that spawned this session, set when
378
535
  * the spawn arrived via the scoped orchestrator sub-gateway (the
379
536
  * token carried the spawner's identity). Absent for direct `/mcp`
@@ -387,6 +544,17 @@ interface SessionDescriptor {
387
544
  * Treated as 0 when absent (legacy rows / direct spawns that predate
388
545
  * WP4). */
389
546
  depth?: number;
547
+ /** Id of the most recently-completed `kind: "command"` session spawned
548
+ * with the same `cwd`, found at spawn time — see `recordCommand` and
549
+ * `findPriorCommandSessionId`. Deliberately a REFERENCE, not copied
550
+ * content: a new session's own ring buffer / structured transcript
551
+ * never gets a prior command_execute's stdout/stderr spliced in, it
552
+ * just gets told where to look (`command_log_tail({sessionId})` or
553
+ * `agent_export`/`command_list` resolve the id into the full record).
554
+ * Set on `spawnAgent`/`spawnPty` when the registry already has a
555
+ * matching command session; absent otherwise (including for legacy
556
+ * rows persisted before this field existed). */
557
+ priorCommandSessionId?: string;
390
558
  /** Adapter id that drives this session (e.g. "camofox", "bureau"). */
391
559
  browserAdapterId?: string;
392
560
  /** Port the browser service listens on. */
@@ -395,6 +563,16 @@ interface SessionDescriptor {
395
563
  browserBaseUrl?: string;
396
564
  /** Execution location — "local" (default) or "cloud". */
397
565
  browserLocation?: "local" | "cloud";
566
+ /** True when this agent-cli session is running inside a sandbox (`agent_start.sandbox`)
567
+ * rather than as a local subprocess — there's no local PID to check, so
568
+ * `processAlive` never applies (it's already absent whenever `pid` is null). */
569
+ remote?: boolean;
570
+ /** Provider-assigned sandbox id (`BootedSandbox.sandboxId`), when `remote` is true. */
571
+ sandboxId?: string;
572
+ /** What session close does to the box (PR3 lifecycle) — `"kill"` (the
573
+ * default, ephemeral) or `"pause"` (keeps `sandboxId` reconnectable via
574
+ * `agent_start.sandbox.reuse`). Only set when `remote` is true. */
575
+ sandboxTeardown?: "kill" | "pause";
398
576
  }
399
577
  interface SessionsRegistry {
400
578
  spawn(input: SpawnSessionInput): SessionDescriptor;
@@ -417,6 +595,27 @@ interface SessionsRegistry {
417
595
  * `attachPty(id, ...)`. Throws when the registry was constructed
418
596
  * without a `spawnPty` factory (node-pty optional dep missing). */
419
597
  spawnPty(input: SpawnPtyInput): SessionDescriptor;
598
+ /** Register a COMPLETED `command_execute` (or cron `kind:"command"`
599
+ * action) invocation as its own `kind: "command"` session. Unlike
600
+ * `spawn`/`spawnAgent`/`spawnPty`, there's no live process to track —
601
+ * the command already ran to completion via `runCommand` before this
602
+ * is called — so the descriptor is minted already "finished"
603
+ * (status exited/error, startedAt backdated by `durationMs`,
604
+ * endedAt now) and its full result is written to that session's own
605
+ * `events.jsonl` (see command-log.ts), the same per-id path an
606
+ * agent-cli session's structured transcript uses. Synchronous: the
607
+ * descriptor (and its id) is available immediately, the JSONL write
608
+ * itself is fire-and-forget internally. */
609
+ recordCommand(input: RecordCommandInput): SessionDescriptor;
610
+ /** Read back a `kind:"command"` session's full logged result (the
611
+ * `CommandLogEntry` `recordCommand` wrote). Resolves to null when the
612
+ * session isn't a command session or has no recorded entry. Routed
613
+ * through the registry (rather than callers importing
614
+ * `readCommandLogEntry` directly) so the read always targets the same
615
+ * base directory `recordCommand` wrote to — that dir is test-overridable
616
+ * (`transcriptDir`) and callers outside sessions.ts have no other way
617
+ * to know it. */
618
+ readCommandLog(sessionId: string): Promise<CommandLogEntry | null>;
420
619
  /** Register an already-running browser service adapter as a tracked
421
620
  * session (kind="browser"). Idempotent by identity — each call
422
621
  * mints a fresh session id. The `stop` callback is invoked by
@@ -437,8 +636,18 @@ interface SessionsRegistry {
437
636
  * mid-turn) are pushed into the ring buffer as `[error]` lines so
438
637
  * `/stream` subscribers see them. Used by the web UI's chat input
439
638
  * and MCP `agent_prompt`, where a long turn would otherwise freeze
440
- * the caller. */
441
- enqueuePrompt(id: string, message: unknown): Promise<void>;
639
+ * the caller.
640
+ *
641
+ * `opts.interrupt` lets a caller redirect a mid-turn session instead
642
+ * of hitting the busy rejection: the in-flight turn is cancelled
643
+ * (`agentSession.cancel()`), admission waits for that turn to
644
+ * actually settle (busy flips false — no fixed sleep), then the new
645
+ * prompt is admitted + fired on the SAME live session. Ignored
646
+ * (identical to the default) when the session is idle. Omitted or
647
+ * `false` reproduces today's mid-turn rejection byte-for-byte. */
648
+ enqueuePrompt(id: string, message: unknown, opts?: {
649
+ interrupt?: boolean;
650
+ }): Promise<void>;
442
651
  /** Stamp `lastActivityAt` on a live agent-cli session's descriptor
443
652
  * and schedule a debounced persist. Called from the `onActivity`
444
653
  * callback threaded down through the driver → ACP client, which
@@ -569,6 +778,18 @@ interface SpawnAgentInput {
569
778
  tokensIn?: number;
570
779
  tokensOut?: number;
571
780
  } | null>;
781
+ /** Opt this session into Langfuse tracing (prompt/completion + tool spans +
782
+ * tokens/cost). Effective opt-in is `trace ?? opts.langfuseTracingDefault ?? false`. */
783
+ trace?: boolean;
784
+ /** True when `agentSession` is a `SandboxAgentSessionProxy` (`agent_start.sandbox`)
785
+ * rather than a local subprocess-backed session — stamped onto the descriptor
786
+ * as `remote` since there's no local PID to report. */
787
+ remote?: boolean;
788
+ /** Provider-assigned sandbox id, when `remote` is true. */
789
+ sandboxId?: string;
790
+ /** What session close does to the box, when `remote` is true — see
791
+ * `SessionDescriptor.sandboxTeardown`. */
792
+ sandboxTeardown?: "kill" | "pause";
572
793
  }
573
794
  interface SpawnSessionInput {
574
795
  kind: SessionKind;
@@ -612,6 +833,24 @@ interface SpawnPtyInput {
612
833
  name?: string;
613
834
  label?: string;
614
835
  }
836
+ interface RecordCommandInput {
837
+ workspaceSlug: string;
838
+ /** Working directory the command actually ran in (post cwd-anchoring).
839
+ * Matched against a fresh session's cwd by `findPriorCommandSessionId`. */
840
+ cwd: string;
841
+ command: string;
842
+ args: string[];
843
+ /** Fields mirror `command-tools.ts`'s `ExecuteResult` — kept inline here
844
+ * (rather than importing that type) so sessions.ts has no dependency
845
+ * on command-tools.ts. */
846
+ exitCode: number;
847
+ signal: string | null;
848
+ durationMs: number;
849
+ stdout: string;
850
+ stderr: string;
851
+ truncated?: boolean;
852
+ label?: string;
853
+ }
615
854
 
616
855
  /**
617
856
  * MCP tools that expose browser service adapters (Camofox, Bureau, …) to
@@ -714,7 +953,7 @@ interface BrowserAdapterInfo {
714
953
  * `TunnelRegistry` (multi-tunnel general surface) can share the same
715
954
  * provider contract without creating a circular dep.
716
955
  *
717
- * The tunnel family is the FIRST consumer of `@agentproto/adapter-kit`:
956
+ * The tunnel family is the FIRST consumer of `@agentproto/provider-kit`:
718
957
  * {@link TunnelProviderHandle} extends the kit's generic `AdapterHandle`
719
958
  * (slug/name/version/description/requiresSetup/check) with the
720
959
  * tunnel-specific `capabilities` + the existing `start`/`stop` lifecycle.
@@ -896,7 +1135,7 @@ declare class TunnelRegistry {
896
1135
  * third-party secrets) merged with descriptor-carried config (named's
897
1136
  * hostname/tunnelId/credentialsFile), descriptor taking precedence.
898
1137
  */
899
- private credsForDescriptor;
1138
+ protected credsForDescriptor(desc: TunnelDescriptor): Promise<TunnelCreds>;
900
1139
  private findEntryByIdOrName;
901
1140
  private schedulePersist;
902
1141
  private persistNow;
@@ -1301,6 +1540,18 @@ interface OrchestratorScope {
1301
1540
  /** Max concurrently-alive children the owning orchestrator may spawn
1302
1541
  * before further spawns are rejected. */
1303
1542
  maxChildren: number;
1543
+ /**
1544
+ * Name of the resolved role that owns this scope (spawn-role-
1545
+ * profiles / privilege lattice). This is the "parent role" the
1546
+ * `canSpawn` gate checks against when this scope is used to spawn a
1547
+ * further child (`session-spawn.ts`). Defaults to `"supervisor"` at
1548
+ * mint time when the caller doesn't say — every role that can reach
1549
+ * `orchestrator: true` today resolves to supervisor (executor's
1550
+ * `toolPolicy.delegation: "deny"` already excludes it from ever
1551
+ * minting a scope), so this preserves #214 behaviour exactly until a
1552
+ * custom role is actually granted delegation.
1553
+ */
1554
+ role: string;
1304
1555
  }
1305
1556
  /**
1306
1557
  * Narrow a caller-requested tool list to ⊆ a ceiling subset.
@@ -1329,6 +1580,9 @@ interface MintScopeOptions {
1329
1580
  maxDepth?: number;
1330
1581
  /** Per-orchestrator alive-child quota (default DEFAULT_MAX_CHILDREN). */
1331
1582
  maxChildren?: number;
1583
+ /** Name of the resolved role that owns this scope. Default
1584
+ * `"supervisor"` — see `OrchestratorScope.role`. */
1585
+ role?: string;
1332
1586
  }
1333
1587
  interface ScopeTokenRegistry {
1334
1588
  /** Mint a fresh scope-token. See `MintScopeOptions`. */
@@ -1447,6 +1701,11 @@ type OrchestratorInjector = (opts?: {
1447
1701
  /** Override the per-orchestrator child quota for the minted scope
1448
1702
  * (clamped to the caller's). Root-only knob. */
1449
1703
  maxChildren?: number;
1704
+ /** Name of the resolved role for the session THIS scope is being
1705
+ * minted for (i.e. the session becoming an orchestrator) — becomes
1706
+ * `OrchestratorScope.role`, the "parent role" a future spawn
1707
+ * through this scope is gated against. Default `"supervisor"`. */
1708
+ role?: string;
1450
1709
  }) => OrchestratorInjection;
1451
1710
  /**
1452
1711
  * Build the orchestrator injector. Closed over the gateway's
@@ -1456,6 +1715,95 @@ type OrchestratorInjector = (opts?: {
1456
1715
  */
1457
1716
  declare function createOrchestratorInjector(deps: OrchestratorInjectorDeps): OrchestratorInjector;
1458
1717
 
1718
+ /**
1719
+ * Shared types for pluggable sandbox providers.
1720
+ *
1721
+ * The sandbox family is a `@agentproto/provider-kit` consumer, same shape as
1722
+ * the tunnel family (`remote-providers/types.ts`): {@link SandboxProviderHandle}
1723
+ * extends the kit's generic `AdapterHandle` (slug/name/version/description/
1724
+ * requiresSetup/check) with the AIP-36 capability namespace and the concrete
1725
+ * `@agentproto/sandbox` `SandboxProvider` (`boot()`) the handle wraps.
1726
+ */
1727
+
1728
+ /**
1729
+ * Declared capabilities of a sandbox provider — pure metadata, surfaced in
1730
+ * `list_sandbox_providers`. Never carries secrets. Namespace mirrors AIP-36
1731
+ * SANDBOX.md (`network.egress`, `mounts`, `lifecycle.pause_after_idle`,
1732
+ * `read_only`, `limits.timeout_ms`).
1733
+ */
1734
+ interface SandboxProviderCapabilities {
1735
+ /** Sandbox can be given a network egress allowlist (AIP-36 `network.egress`). */
1736
+ networkEgress: boolean;
1737
+ /** Sandbox supports mounting external filesystems (AIP-36 `mounts`). */
1738
+ mounts: boolean;
1739
+ /** Sandbox can be paused (not just killed) between turns (AIP-36 `lifecycle.pause_after_idle`). */
1740
+ lifecyclePause: boolean;
1741
+ /** Sandbox can be started read-only (AIP-36 `read_only`). */
1742
+ readOnly: boolean;
1743
+ /** Hard cap on `limits.timeout_ms`, when the provider enforces one. */
1744
+ maxTimeoutMs?: number;
1745
+ }
1746
+ /**
1747
+ * A sandbox provider as an adapter-kit handle. Rides on the kit's generic
1748
+ * {@link AdapterHandle} and adds the sandbox-specific `capabilities` plus
1749
+ * the `@agentproto/sandbox` `SandboxProvider` the handle wraps — the thing
1750
+ * `createSandboxAgentSessionHost` actually boots.
1751
+ */
1752
+ interface SandboxProviderHandle extends AdapterHandle {
1753
+ readonly provider: SandboxProvider;
1754
+ readonly capabilities: SandboxProviderCapabilities;
1755
+ /**
1756
+ * Credential fields this provider accepts via `setup_sandbox_provider`
1757
+ * (e.g. e2b's `apiKey`). Omit (or empty) when the provider needs no
1758
+ * credentials (e.g. `local`).
1759
+ */
1760
+ readonly setupFields?: readonly SetupField[];
1761
+ }
1762
+
1763
+ /**
1764
+ * Sandbox family on top of `@agentproto/provider-kit` — mirrors
1765
+ * `tunnel-adapters.ts`. This module is the entire bridge between the
1766
+ * generic kit and `@agentproto/sandbox`'s `SandboxProvider` concept: it
1767
+ * contributes nothing the kit already owns (catalog/status/creds/ledger/
1768
+ * list/MCP-tool plumbing); it only supplies the sandbox-family `TInfo`
1769
+ * (`SandboxAdapterInfo`), the static `SANDBOX_CATALOG`, and the resolver
1770
+ * that maps a catalog slug to a concrete {@link SandboxProviderHandle}
1771
+ * (`./sandbox-providers/registry.js`).
1772
+ *
1773
+ * Kit primitives used:
1774
+ * - `makeCredsStore` → per-slug 0600 creds under `~/.agentproto/sandbox-creds/`
1775
+ * - `makeSetupLedger` → `~/.agentproto/setup/<slug>.json`
1776
+ * - `makeAdapterResolver` → wraps the throwing `load` into null-on-miss
1777
+ * - `makeAdapterLister` → catalog → status-classified `AdapterEntry[]`
1778
+ * - `makeListTool` → registers `list_sandbox_providers`
1779
+ * - `makeSetupTool` → registers `setup_sandbox_provider` (multi-field
1780
+ * form: e2b's `apiKey`, sensitive)
1781
+ *
1782
+ * This is pure additive plumbing (introspection + setup) — it does NOT wire
1783
+ * a `sandbox` field into `agent_start`. That lands in a follow-up PR; the
1784
+ * `resolveSandboxProvider`/`listSandboxProviders` overrides below exist now
1785
+ * so that later wiring can inject the same resolver/lister this module
1786
+ * builds by default, mirroring `resolveAgentAdapter`/`listAgentAdapters`.
1787
+ *
1788
+ * Security: `toSandboxInfo` exposes only `capabilities` — never a cred
1789
+ * value (Appendix B). The setup tool's fields are marked SENSITIVE and the
1790
+ * result NEVER echoes any field value back.
1791
+ */
1792
+
1793
+ /**
1794
+ * Family descriptor (`TInfo`). Pure metadata surfaced in
1795
+ * `list_sandbox_providers`. The kit's `AdapterEntry` already carries
1796
+ * slug/name/description/status/version, so the only sandbox-specific field
1797
+ * is the declared capability set. NEVER carries a cred value.
1798
+ */
1799
+ interface SandboxAdapterInfo {
1800
+ capabilities: SandboxProviderCapabilities;
1801
+ }
1802
+ /** Resolve a sandbox provider slug to a handle, or null when unavailable. */
1803
+ type SandboxProviderResolver = AdapterResolver<SandboxProviderHandle>;
1804
+ /** List every sandbox provider with its live status + capabilities. */
1805
+ type SandboxProviderLister = AdapterLister<SandboxAdapterInfo>;
1806
+
1459
1807
  /**
1460
1808
  * Pluggable adapter resolver — keeps the runtime package free of any
1461
1809
  * @agentproto/cli dep. The host (cli `serve`, playground, embedding
@@ -1489,6 +1837,17 @@ type AgentAdapterResolver = (slug: string) => Promise<{
1489
1837
  * manifest, so a typo fails the spawn rather than silently no-op).
1490
1838
  */
1491
1839
  mode?: string;
1840
+ /**
1841
+ * Manifest-declared option id → value map forwarded from
1842
+ * `agent_start` (AIP-45 `AgentCliHandle.options` — e.g. hermes'
1843
+ * `skills`). Applied at spawn time via `composeSpawn`'s option
1844
+ * patches (`bin_args_prepend` / `bin_args_template` /
1845
+ * `bin_args_append_when_true` / `env`), validated against each
1846
+ * option's declared `type`/`enum`/`min`/`max`. An id the adapter
1847
+ * doesn't declare throws `RuntimeConfigError` (composeSpawn
1848
+ * validates against the manifest, same as an unknown `mode`).
1849
+ */
1850
+ options?: Record<string, boolean | number | string>;
1492
1851
  /** Model identifier forwarded from `agent_start`. For ACP
1493
1852
  * adapters this is applied via session/set_config_option after
1494
1853
  * newSession (the ACP wrapper does not forward CLI args to claude).
@@ -1520,7 +1879,30 @@ type AgentAdapterResolver = (slug: string) => Promise<{
1520
1879
  tokensIn?: number;
1521
1880
  tokensOut?: number;
1522
1881
  } | null>;
1882
+ /** AIP-45 `options[]` this adapter's manifest declares (id + type only —
1883
+ * no spawn internals). Lets `session-spawn.ts` fold a config-level
1884
+ * `defaults.skills` list into `options.skills` using the shape the
1885
+ * manifest actually declared (e.g. hermes' comma-joined string),
1886
+ * instead of guessing. Omitted/empty ⇒ the skills normalization is a
1887
+ * documented no-op for that adapter (e.g. claude-code, which
1888
+ * auto-discovers skills and declares no such option). */
1889
+ declaredOptions?: readonly DeclaredAdapterOption[];
1523
1890
  } | null>;
1891
+ /**
1892
+ * UI-safe projection of an AIP-45 `modes[]` entry as surfaced by
1893
+ * `adapter_list`. Mirrors `@agentproto/cli`'s `AdapterMode` without
1894
+ * importing it (the runtime deliberately carries no cli dep — see the
1895
+ * `AgentAdapterLister` note above). Spawn internals (`bin_args_*`, `env`)
1896
+ * are intentionally omitted; `status` is normalised to `"active"` by the
1897
+ * lister when the manifest omits it, so a declared mode is never
1898
+ * silently statusless.
1899
+ */
1900
+ interface AdapterListMode {
1901
+ id: string;
1902
+ description?: string;
1903
+ status: "active" | "noop" | "planned";
1904
+ status_note?: string;
1905
+ }
1524
1906
  /**
1525
1907
  * Compact adapter metadata for the discovery endpoints. Independent
1526
1908
  * of the resolver function above — hosts that can list installed
@@ -1535,6 +1917,10 @@ interface AdapterListEntry {
1535
1917
  protocol: string;
1536
1918
  streaming: boolean;
1537
1919
  packageName: string;
1920
+ /** Declared operation modes with their honest support status, so a
1921
+ * client can see e.g. hermes' `lean` mode is a measured no-op instead
1922
+ * of being silently accepted. Empty when the adapter declares none. */
1923
+ modes: AdapterListMode[];
1538
1924
  }
1539
1925
  type AgentAdapterLister = () => Promise<AdapterListEntry[]>;
1540
1926
  interface AuthOptions {
@@ -1621,7 +2007,7 @@ interface InboundWatcher {
1621
2007
  }
1622
2008
 
1623
2009
  /**
1624
- * Browser family on top of `@agentproto/adapter-kit` — Phase 3 (lightest
2010
+ * Browser family on top of `@agentproto/provider-kit` — Phase 3 (lightest
1625
2011
  * adoption: no creds, no wizard, no catalog file).
1626
2012
  *
1627
2013
  * Provides the kit-compatible types (`BrowserAdapterHandle`) and the
@@ -1680,6 +2066,116 @@ declare function formatToolCall(toolName: string, args: unknown): string;
1680
2066
  */
1681
2067
  declare function formatToolResult(toolName: string | undefined, result: unknown, isError: boolean): string | null;
1682
2068
 
2069
+ /**
2070
+ * Dependency-injection surface for MCP credential resolution.
2071
+ *
2072
+ * `packages/runtime` intentionally does NOT depend on `@agentproto/auth`;
2073
+ * the daemon bootstrap (e.g. `packages/cli`) can set a broker-backed
2074
+ * resolver here, and `session-spawn.ts` will call it when an `mcpServers`
2075
+ * entry carries `credentialRef`. Errors from the hook are treated as
2076
+ * non-fatal by the caller.
2077
+ */
2078
+ interface McpCredentialDeps {
2079
+ /** Resolve brokered headers for an `mcpServers` entry that names a
2080
+ * `credentialRef`. The returned headers are merged ON TOP of the
2081
+ * entry's static `headers` so brokered auth wins on collision. */
2082
+ resolveMcpCredentialHeaders?: (o: {
2083
+ credentialRef: string;
2084
+ signal?: AbortSignal;
2085
+ }) => Promise<Record<string, string> | undefined>;
2086
+ /** Resolve a single named secret (an env-var-shaped slug — e.g. a sandbox
2087
+ * spec's `env.passthrough` / `env.auth.state.env` entries) to its raw
2088
+ * value. Distinct from `resolveMcpCredentialHeaders` above (which
2089
+ * resolves an MCP `credentialRef` PATH into Authorization-style
2090
+ * headers): this is the `SecretResolver` shape `agent_start`'s sandbox
2091
+ * branch (`session-spawn.ts`) passes to `@agentproto/sandbox`'s
2092
+ * `SandboxSecretsConfig.resolver` — so a booted sandbox's env is filled
2093
+ * from the same host-injected broker as every other secret, never a
2094
+ * bare `process.env` read at the call site. Returns null when the slug
2095
+ * can't be resolved (missing/unconfigured). */
2096
+ resolveSandboxSecret?: (slug: string) => Promise<string | null>;
2097
+ }
2098
+ declare function setMcpCredentialDeps(d: McpCredentialDeps): void;
2099
+ declare function getMcpCredentialDeps(): McpCredentialDeps;
2100
+
2101
+ /**
2102
+ * Provider-preset family on top of `@agentproto/provider-kit` — the *thin,
2103
+ * honest* lister (Option B). Unlike tunnels/sandboxes/eval-reporters, presets
2104
+ * are bundled static data with no binary to install, no package to resolve, and
2105
+ * no setup ledger: their only "setup" is an ambient API-key env var. So this
2106
+ * module does NOT use `makeAdapterResolver` / `makeAdapterLister` / the wizard
2107
+ * (all install-oriented, a forced fit). It reuses only `makeListTool` — the one
2108
+ * kit primitive that's genuinely about "JSON a lister's output as an MCP tool."
2109
+ *
2110
+ * Status is derived honestly from the daemon's own environment (the env agents
2111
+ * spawn into), not the CLI caller's:
2112
+ * "ready" — `process.env[preset.keyEnv]` is set (key available to spawn)
2113
+ * "available" — key absent (preset known, usable once the env var is provided)
2114
+ *
2115
+ * There is no "supported" state: presets ship in-repo, so they're always at
2116
+ * least "available." `version` is the literal "built-in".
2117
+ *
2118
+ * Security (Appendix B): `PresetInfo` carries only `keyEnv` (the env-var NAME),
2119
+ * never a key value. The list tool never returns creds — there are none to
2120
+ * return; keys live in the operator's environment, not a creds store.
2121
+ */
2122
+
2123
+ /**
2124
+ * Manifest-declared AIP-45 preset, the minimum the catalog needs to merge
2125
+ * an adapter-contributed preset into the listing. Mirrors
2126
+ * `AgentCliPresetDeclaration`'s fields without importing
2127
+ * `@agentproto/driver-agent-cli` into the runtime package (same structural-
2128
+ * type pattern as `DeclaredAdapterOption` in spawn-defaults.ts).
2129
+ */
2130
+ interface DeclaredAdapterPreset {
2131
+ id: string;
2132
+ label: string;
2133
+ description?: string;
2134
+ schemaFlavor: ProviderPreset["schemaFlavor"];
2135
+ baseUrl: string;
2136
+ keyEnv: string;
2137
+ scrubEnv?: string[];
2138
+ defaultModel?: string;
2139
+ homepage?: string;
2140
+ }
2141
+ /**
2142
+ * Family descriptor surfaced as `AdapterEntry.info`. User-facing preset facts
2143
+ * only — no cred values (there are none in the registry), no adapter projection
2144
+ * detail (`scrubEnv` stays internal to adapter manifests).
2145
+ */
2146
+ interface PresetInfo {
2147
+ /** API schema flavor — which adapter family can consume this preset. */
2148
+ schemaFlavor: ProviderPreset["schemaFlavor"];
2149
+ /** Base URL the client hits. */
2150
+ baseUrl: string;
2151
+ /** Env-var NAME holding the API key (never the value). */
2152
+ keyEnv: string;
2153
+ /** Conventional default model id, if any. */
2154
+ defaultModel?: string;
2155
+ /** Homepage/docs URL. */
2156
+ homepage?: string;
2157
+ }
2158
+ /**
2159
+ * Normalize a manifest-declared preset into the canonical `ProviderPreset`
2160
+ * shape the catalog lists. `scrubEnv` defaults to empty (an adapter that
2161
+ * scrubs in code may omit it); `description` defaults to empty. Pure.
2162
+ */
2163
+ declare function declaredPresetToProviderPreset(decl: DeclaredAdapterPreset): ProviderPreset;
2164
+ /**
2165
+ * Map the static preset registry → status-classified catalog entries, reading
2166
+ * the daemon's environment for key presence. Pure (no I/O) save the `process`
2167
+ * .env read; safe to call on every list invocation.
2168
+ *
2169
+ * `adapterPresets` — manifest-declared presets contributed by loaded adapters
2170
+ * (Stage 3 AIP-45 `presets` field). Merged AFTER the built-in registry and
2171
+ * deduped by id: an adapter-declared id that collides with a built-in is
2172
+ * ignored (the registry is the source of truth for canonical providers), so
2173
+ * an adapter can't silently override moonshot/openrouter facts. The live
2174
+ * wiring that reads each adapter's `.presets` and passes them here is Stage 4;
2175
+ * today this is the tested seam.
2176
+ */
2177
+ declare function listPresets(env?: Record<string, string | undefined>, adapterPresets?: readonly DeclaredAdapterPreset[]): AdapterEntry<PresetInfo>[];
2178
+
1683
2179
  /** A journal-file StepCache scoped to one cacheKey. Best-effort: read/parse
1684
2180
  * failures degrade to "no cache" (a miss), never throw into the run. */
1685
2181
  declare function createFileStepCache(cacheKey: string, opts?: {
@@ -1984,6 +2480,17 @@ interface CreateGatewayOptions {
1984
2480
  deferredTools?: {
1985
2481
  alwaysOn?: readonly string[];
1986
2482
  };
2483
+ /**
2484
+ * Optional sandbox provider resolver — overrides the sandbox family's
2485
+ * default resolver (built-ins + `@agentproto/sandbox-<slug>` dynamic
2486
+ * import) used by `list_sandbox_providers` / `setup_sandbox_provider`
2487
+ * AND `agent_start.sandbox`. Mirrors `resolveAgentAdapter`'s injection
2488
+ * shape.
2489
+ */
2490
+ resolveSandboxProvider?: SandboxProviderResolver;
2491
+ /** Optional sandbox provider lister — mirrors `listAgentAdapters`.
2492
+ * Overrides the default catalog-driven lister behind `list_sandbox_providers`. */
2493
+ listSandboxProviders?: SandboxProviderLister;
1987
2494
  }
1988
2495
  interface GatewayHandle {
1989
2496
  url: string;
@@ -2027,4 +2534,4 @@ interface GatewayHandle {
2027
2534
  */
2028
2535
  declare function createGateway(opts: CreateGatewayOptions): Promise<GatewayHandle>;
2029
2536
 
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 };
2537
+ 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 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, type PolicyRunState, type PolicyRunStatus, type PresetInfo, type PricingResolver, type RegisterBrowserInput, type RegisterSessionInput, 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, createScopeTokenRegistry, daemonRegistryDir, declaredPresetToProviderPreset, deriveSessionUsage, formatToolCall, formatToolResult, getMcpCredentialDeps, listPresets, makeBrowserAdapterLister, monitorPolicyWait, monitorSessionWait, narrowOrchestratorTools, projectSessionUsage, readDaemonRegistry, readRuntimeMeta, setMcpCredentialDeps, sweepStaleDaemonRegistry, sweepStaleRuntimeMetas, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, writeDaemonRegistryEntry };