@agentproto/runtime 3.6.1 → 4.0.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,35 +1,37 @@
1
1
  import { DoctypeSpec } from '@agentproto/manifest';
2
- import { B as BuildHeartbeatAgent } from './heartbeat-COGpMrJS.js';
3
- export { H as HeartbeatAgent, a as HeartbeatRunner, R as RuntimeEvent, b as RuntimeEvents, p as parseDuration } from './heartbeat-COGpMrJS.js';
2
+ import { B as BuildHeartbeatAgent } from './heartbeat-BJtxbDb9.js';
3
+ export { H as HeartbeatAgent, a as HeartbeatRunner, R as RuntimeEvent, b as RuntimeEvents, p as parseDuration } from './heartbeat-BJtxbDb9.js';
4
4
  import { z } from 'zod';
5
5
  import { AcpMcpServer, AcpPermissionResolution } from '@agentproto/acp';
6
6
  import { SessionMode } from '@agentproto/acp/client';
7
7
  export { SessionMode } from '@agentproto/acp/client';
8
8
  import { ChildProcess } from 'node:child_process';
9
- import { R as RouteSpec, S as SessionConfig, P as Posture, C as ContextProfile, E as EffortLevel, A as AuthMethod, a as CanonicalPosture } from './session-config-DbWP9RRj.js';
10
- export { D as DeclaredAdapterMode, c as composeMode, d as decomposeMode } from './session-config-DbWP9RRj.js';
9
+ import { R as RouteSpec, S as SessionConfig, P as Posture, C as ContextProfile, E as EffortLevel, A as AuthMethod, a as CanonicalPosture } from './session-config-ByfVeEQJ.js';
10
+ export { D as DeclaredAdapterMode, c as composeMode, d as decomposeMode } from './session-config-ByfVeEQJ.js';
11
11
  import { CostBudget, AuthProfile, CostBudgetScope } from '@agentproto/auth';
12
12
  export { CostBudget, CostBudgetScope } from '@agentproto/auth';
13
- import { R as ResolvedContextContinuityPolicy } from './context-continuity-ib9_bVYM.js';
13
+ import { S as SpawnBrowserMode, R as ResolveHeadlessBrowser, C as ContextContinuityPolicy, a as ResolvedContextContinuityPolicy } from './context-continuity-Bn99UtVC.js';
14
14
  import { SandboxMode } from '@agentproto/command-sandbox';
15
15
  import { AdapterHandle, SetupField, AdapterResolver, AdapterLister, CredsStore, HarnessCapabilities, AdapterEntry } from '@agentproto/provider-kit';
16
- import { R as ResolvedAuthSpec, D as DeclaredAdapterOption, A as AdapterAuthDescriptor } from './spawn-defaults-CwI9Puwy.js';
17
- export { a as AuthEcho, b as AuthResolutionError, C as CLAUDE_CODE_OAUTH_SOURCE, c as CredentialSource, d as DefaultsAdapterAuthConfig, e as DefaultsAdapterConfig, f as ResolveSubscriptionCredentialInput, g as ResolvedSpawnAuthMaterial, h as ResolvedSpawnDefaults, S as SpawnDefaultsConfig, i as SubscriptionCredentialResolution, j as SubscriptionSourceError, k as credentialFingerprint, n as normalizeSkillsOption, r as resolveAuthSpec, l as resolveSpawnDefaults, m as resolveSubscriptionCredential } from './spawn-defaults-CwI9Puwy.js';
16
+ import { S as SpawnDefaultsConfig, D as DefaultsAdapterAuthConfig, R as ResolvedAuthSpec, a as DeclaredAdapterOption, A as AdapterAuthDescriptor } from './spawn-defaults-CZj39Znq.js';
17
+ export { b as AuthEcho, c as AuthResolutionError, C as CLAUDE_CODE_OAUTH_SOURCE, d as CredentialSource, e as DefaultsAdapterConfig, f as DeferredToolsConfig, g as ResolveSubscriptionCredentialInput, h as ResolvedSpawnAuthMaterial, i as ResolvedSpawnDefaults, j as SubscriptionCredentialResolution, k as SubscriptionSourceError, l as credentialFingerprint, n as normalizeSkillsOption, r as resolveAuthSpec, m as resolveDeferredToolsGatewayOption, o as resolveSpawnDefaults, p as resolveSubscriptionCredential } from './spawn-defaults-CZj39Znq.js';
18
18
  import { CatalogModelsQuery, CatalogModelsResponse } from './catalog-models.js';
19
19
  export { CatalogAdapterInput, CatalogAdapterModelInput, CatalogPricing, CatalogProduct, CatalogRoute, CatalogRouteSummary, CatalogVendor, buildCatalogModels } from './catalog-models.js';
20
20
  import { FooterSession } from './pr-provenance.js';
21
21
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
22
- import { SandboxProvider } from '@agentproto/sandbox';
22
+ import { SandboxProvider, SandboxSpec } from '@agentproto/sandbox';
23
23
  import { BrainManager, ExportedSessionLike } from '@agentproto/workspace-brain';
24
24
  export { BrainManager, BrainStats, IngestReport, IngestResult } from '@agentproto/workspace-brain';
25
25
  import * as _agentproto_tool from '@agentproto/tool';
26
26
  import { ToolContext } from '@agentproto/tool';
27
27
  import { DriverHandle } from '@agentproto/driver';
28
- export { UserPreset, UserPresetsFile, deleteUserPreset, getUserPreset, listUserPresets, loadUserPresets, saveUserPreset, userPresetsPath } from './user-presets.js';
28
+ import { Attestation, LedgerKey, RubricDigest, ReviewPrRef, LaneResult, ReviewLaneExecutor, LaneOutcome } from '@agentproto/review';
29
+ import { UserPreset } from './user-presets.js';
30
+ export { UserPresetsFile, deleteUserPreset, getUserPreset, listUserPresets, loadUserPresets, saveUserPreset, userPresetsPath } from './user-presets.js';
31
+ import { WorktreeIsolationMode, SpawnAttachMode, SpawnDedupeMode } from './config.js';
29
32
  import { ResolvedModel } from '@agentproto/model-catalog';
30
33
  import { FrameSink, E2eFrameSink } from '@agentproto/acp/tunnel';
31
34
  import { DaemonIdentity } from '@agentproto/secrets/identity';
32
- import { WorktreeIsolationMode } from './config.js';
33
35
  import { WorkspaceFs } from './workspace-fs.js';
34
36
  export { createWorkspaceFs } from './workspace-fs.js';
35
37
  export { ConversationMeta, ConversationStore, ConversationTurn, fileConversationStore } from './conversations.js';
@@ -502,6 +504,37 @@ interface UsageDescriptorFields {
502
504
  */
503
505
  declare function projectSessionUsage(desc: UsageDescriptorFields): SessionUsage;
504
506
 
507
+ /**
508
+ * McpAppsHostService — the daemon as the MCP client that fetches MCP App
509
+ * UIs (spec 2026-01-26) on behalf of a session's chat UI.
510
+ *
511
+ * In a session the MCP client is the harness (claude-code, codex, …); it
512
+ * keeps a tool's `_meta.ui.resourceUri` and the `ui://` resource to itself,
513
+ * and the transcript only carries `mcp__<server>__<tool>` + input + result.
514
+ * So the chat asks the daemon, which resolves `<server>` for that session
515
+ * (mcp-app-resolve.ts), connects through a config-keyed pool
516
+ * (mcp-client-pool.ts), and answers three questions:
517
+ *
518
+ * uiIndex which of this server's tools declare a UI (+ app-only tools)
519
+ * readUi the HTML + `_meta.ui` of one of those `ui://` resources
520
+ * callTool an iframe-initiated `tools/call`, allowlisted and recorded
521
+ *
522
+ * None of these throw: every failure is a `status` (or an `isError`
523
+ * result). Failures are cached per (session, alias) for 60s so a
524
+ * transcript full of calls to a dead server costs one attempt a minute.
525
+ */
526
+
527
+ /** Durable record of an iframe-initiated call, appended to the session's
528
+ * events.jsonl as `kind: "mcp_app_tool_call"`. Never carries args or
529
+ * results. */
530
+ interface McpAppToolCallRecord {
531
+ server: string;
532
+ tool: string;
533
+ originToolCallId: string;
534
+ isError: boolean;
535
+ durationMs: number;
536
+ }
537
+
505
538
  /**
506
539
  * ONE normalized shape for a finished tool/command call — the unification
507
540
  * target for the two disjoint logs this repo has today: `recordCommand`'s
@@ -1685,6 +1718,41 @@ declare function createTaskLedger(opts: {
1685
1718
  persist?: boolean;
1686
1719
  }): TaskLedger;
1687
1720
 
1721
+ /**
1722
+ * The single place every stable `SessionDescriptor.endedReason` /
1723
+ * `SessionOutcome.termination.reason` value is declared, so a UI can render
1724
+ * a label for each one without chasing the call site that set it.
1725
+ *
1726
+ * Two families:
1727
+ * - operator-issued — set ONLY by an explicit `POST /sessions/:id/kill`
1728
+ * or the `agent_kill` MCP tool, distinguishing an operator's deliberate
1729
+ * stop from every automatic teardown below;
1730
+ * - internal/automatic — set by the daemon itself, without an operator
1731
+ * in the loop, for a reason the operator couldn't have expressed.
1732
+ *
1733
+ * Backward compatible by construction: this is a value list, not a closed
1734
+ * validator. A descriptor persisted by an older daemon (or a newer one an
1735
+ * older UI doesn't know about yet) carries a `reason` string outside this
1736
+ * list — {@link isKnownSessionEndReason} says so, and callers should fall
1737
+ * back to a generic label rather than throw.
1738
+ */
1739
+ declare const SESSION_END_REASONS: readonly ["daemon-restart", "idle-reaped", "crashed", "operator-completed", "operator-stopped", "cost-cap-exceeded", "policy-cleanup", "parent-exited", "provider-limit", "forgotten"];
1740
+ type SessionEndReason = (typeof SESSION_END_REASONS)[number];
1741
+ /** True when `reason` is one of {@link SESSION_END_REASONS}. A label
1742
+ * lookup, not a wire-format gate — an unrecognized string is still a
1743
+ * valid `endedReason` from a daemon version this build doesn't know
1744
+ * about; callers should render it with a generic fallback, never reject
1745
+ * it. */
1746
+ declare function isKnownSessionEndReason(reason: string | undefined): reason is SessionEndReason;
1747
+ /**
1748
+ * Classify a driver-reported error message as a provider/subscription
1749
+ * usage cap rather than an ordinary tool/turn failure. Used wherever the
1750
+ * daemon would otherwise tag a dead session with the generic `"crashed"` /
1751
+ * `"error"` reason, so an operator sees "wait for your quota to reset"
1752
+ * instead of chasing a phantom bug.
1753
+ */
1754
+ declare function isProviderLimitError(message: string | undefined): boolean;
1755
+
1688
1756
  /**
1689
1757
  * In-process pub/sub bus for session lifecycle events. Separate from
1690
1758
  * RuntimeEvents (global daemon bus) — session events are scoped to
@@ -1695,7 +1763,7 @@ declare function createTaskLedger(opts: {
1695
1763
  * and session_monitor MCP tool (long-poll multiplexed).
1696
1764
  */
1697
1765
 
1698
- type SessionEventType = "session:turn-end" | "session:awaiting-input" | "session:awaiting-input-flagged" | "session:awaiting-question-answered" | "session:permission-request" | "session:permission-resolved" | "session:exited" | "session:reaped" | "session:stalled" | "session:stall-cleared" | "session:watcher-attached" | "session:watcher-detached" | "session:bg-tasks-parked" | "session:bg-tasks-cleared" | "session:resumed" | "session:spawned" | "session:command-done" | "session:model-changed" | "session:config-changed" | "session:renamed" | "session:pinned-changed" | "policy:passed" | "policy:failed" | "policy:commit-ready" | "policy:committed" | "cron:fired" | "cron:succeeded" | "cron:failed" | "activity:changed" | "task:changed" | "workflow:gate-report" | "workflow:suspended" | "workflow:suspend-resumed" | "session:harness-warning";
1766
+ type SessionEventType = "session:turn-end" | "session:awaiting-input" | "session:awaiting-input-flagged" | "session:awaiting-question-answered" | "session:permission-request" | "session:permission-resolved" | "session:exited" | "session:reaped" | "session:stalled" | "session:stall-cleared" | "session:watcher-attached" | "session:watcher-detached" | "session:bg-tasks-parked" | "session:bg-tasks-cleared" | "session:bg-task" | "session:resumed" | "session:spawned" | "session:command-done" | "session:model-changed" | "session:config-changed" | "session:renamed" | "session:pinned-changed" | "session:message" | "policy:passed" | "policy:failed" | "policy:commit-ready" | "policy:committed" | "cron:fired" | "cron:succeeded" | "cron:failed" | "activity:changed" | "task:changed" | "workflow:gate-report" | "workflow:suspended" | "workflow:suspend-resumed" | "session:harness-warning";
1699
1767
  /**
1700
1768
  * Fixed severity vocabulary for a judge-gate finding (WP-D). Deliberately
1701
1769
  * small and fixed so `policy_status` output is comparable across different
@@ -1769,6 +1837,13 @@ interface SessionTurnEndEvent {
1769
1837
  * a normal, productive turn.
1770
1838
  */
1771
1839
  empty?: boolean;
1840
+ /**
1841
+ * True when the turn was not started by a prompt: the agent woke on its
1842
+ * own (Claude Code's task-notification cycle after a background task
1843
+ * settled) and the registry tracked that work as a turn. Absent on an
1844
+ * ordinary prompted turn.
1845
+ */
1846
+ autonomous?: boolean;
1772
1847
  }
1773
1848
  interface SessionAwaitingInputEvent {
1774
1849
  type: "session:awaiting-input";
@@ -1865,15 +1940,14 @@ interface SessionExitedEvent {
1865
1940
  status: "exited" | "killed" | "error";
1866
1941
  label?: string;
1867
1942
  ts: string;
1868
- /** Mirrors `SessionDescriptor.endedReason` — set when this exit was NOT an
1869
- * operator targeting the session: `"daemon-restart"` (the daemon dying
1870
- * underneath it — crash-discovered-at-boot or a forced shutdown kill) or
1871
- * `"idle-reaped"` (the idle-session reaper retiring a long-idle row to free
1872
- * the adapter process, PR-6), or `"crashed"` (the crash-detect sweep found
1873
- * the adapter's OS process gone between turns). Lets a watcher
1874
- * (completion-policy supervisor, `session_monitor`) tell an automatic
1875
- * teardown apart from a deliberate kill. Absent otherwise. */
1876
- reason?: "daemon-restart" | "idle-reaped" | "crashed";
1943
+ /** Mirrors `SessionDescriptor.endedReason` — see {@link SessionEndReason}
1944
+ * for the full, single-source enum (automatic teardowns like
1945
+ * `"daemon-restart"` / `"idle-reaped"` / `"crashed"` / `"cost-cap-exceeded"`
1946
+ * / `"provider-limit"`, and operator-issued `"operator-completed"` /
1947
+ * `"operator-stopped"`). Lets a watcher (completion-policy supervisor,
1948
+ * `session_monitor`) tell WHY this exit happened rather than just that it
1949
+ * did. Absent for a plain natural exit or an ordinary turn error. */
1950
+ reason?: SessionEndReason;
1877
1951
  }
1878
1952
  /**
1879
1953
  * Emitted when the idle-session reaper (PR-6) retires a long-idle agent-cli
@@ -2024,6 +2098,29 @@ interface SessionBgTasksClearedEvent {
2024
2098
  label?: string;
2025
2099
  ts: string;
2026
2100
  }
2101
+ /**
2102
+ * One lifecycle edge of an agent's background task (a backgrounded Bash
2103
+ * command, a monitor, ...) as the agent itself reported it — over ACP, the
2104
+ * AIR `asyncTasks` extension (see @agentproto/acp's `background-task`
2105
+ * StreamEvent). Unlike {@link SessionBgTasksParkedEvent} (a turn-end
2106
+ * heuristic) this is the real lifecycle: `"started"` when the task is
2107
+ * announced, `"settled"` when it reaches a terminal `status`. Mid-life
2108
+ * progress updates only refresh `SessionDescriptor.backgroundTasks` and are
2109
+ * not emitted. Same bus distribution as every other lifecycle event.
2110
+ */
2111
+ interface SessionBgTaskEvent {
2112
+ type: "session:bg-task";
2113
+ sessionId: string;
2114
+ phase: "started" | "settled";
2115
+ taskId: string;
2116
+ taskKind?: string;
2117
+ description?: string;
2118
+ outputFile?: string;
2119
+ status?: string;
2120
+ summary?: string;
2121
+ label?: string;
2122
+ ts: string;
2123
+ }
2027
2124
  /**
2028
2125
  * Emitted when a dead agent-cli row is brought back IN PLACE — same session
2029
2126
  * id, same descriptor — by the lazy resume-on-prompt path (`maybeResumeAgent`)
@@ -2163,6 +2260,27 @@ interface SessionRenamedEvent {
2163
2260
  renamedByUser?: boolean;
2164
2261
  ts: string;
2165
2262
  }
2263
+ /**
2264
+ * A typed inter-session message (`session-message.ts`) was accepted for
2265
+ * `sessionId` (the RECIPIENT) — once at send (no `delivered`) and again when
2266
+ * it's delivered into the recipient's context (`delivered` set). Lets
2267
+ * `session_monitor` / SSE / the VS Code panel react without polling.
2268
+ */
2269
+ interface SessionMessageEvent {
2270
+ type: "session:message";
2271
+ sessionId: string;
2272
+ messageId: string;
2273
+ fromSessionId?: string;
2274
+ relation: "child" | "parent" | "sibling" | "human" | "system";
2275
+ kind: "report" | "question" | "blocker" | "done" | "notice";
2276
+ urgency: "fyi" | "next-turn" | "steer" | "interrupt";
2277
+ delivered?: {
2278
+ via: "wait" | "steer" | "turn" | "interrupt" | "inbox";
2279
+ at: string;
2280
+ turnSeq?: number;
2281
+ };
2282
+ ts: string;
2283
+ }
2166
2284
  /**
2167
2285
  * Emitted when an operator pins or unpins a session for list visibility
2168
2286
  * (`POST /sessions/:id/pin`, the `session_set_pinned` MCP verb). Like
@@ -2356,16 +2474,22 @@ interface WorkflowApprovalResolvedEvent {
2356
2474
  }
2357
2475
  /**
2358
2476
  * Emitted by the workflow runner (`workflow-runner.ts`) when a `kind:
2359
- * "suspend"` step parks its run awaiting an external event — the run's
2360
- * status flips to `awaiting-input` with a durable `awaitingSuspend` record
2361
- * (AIP-15 conformance rule 7), and `workflow_escalation_resolve`'s suspend
2362
- * form resumes it. Same bus distribution as every other lifecycle event.
2477
+ * "suspend"` step parks its run awaiting an external event (`on` set, no
2478
+ * `reason`) — OR when an agent-backed step signals AIP-58 §3(a)
2479
+ * `run.requestInput` (`reason: "input-required"` + `prompt`/`schema?`, no
2480
+ * `on`). Either way the run's status flips to `awaiting-input` with a
2481
+ * durable `awaitingSuspend` record, and `workflow_escalation_resolve`'s
2482
+ * suspend form resumes it. Same bus distribution as every other lifecycle
2483
+ * event.
2363
2484
  */
2364
2485
  interface WorkflowSuspendedEvent {
2365
2486
  type: "workflow:suspended";
2366
2487
  runId: string;
2367
2488
  stepId: string;
2368
- on: readonly string[];
2489
+ on?: readonly string[];
2490
+ reason?: "input-required";
2491
+ prompt?: string;
2492
+ schema?: Record<string, unknown>;
2369
2493
  ts: string;
2370
2494
  }
2371
2495
  /**
@@ -2393,7 +2517,7 @@ interface SessionHarnessWarningEvent {
2393
2517
  label?: string;
2394
2518
  ts: string;
2395
2519
  }
2396
- type SessionEvent = SessionTurnEndEvent | SessionAwaitingInputEvent | SessionAwaitingInputFlaggedEvent | SessionAwaitingQuestionAnsweredEvent | SessionPermissionRequestEvent | SessionPermissionResolvedEvent | SessionExitedEvent | SessionReapedEvent | SessionStalledEvent | SessionStallClearedEvent | SessionWatcherAttachedEvent | SessionWatcherDetachedEvent | SessionBgTasksParkedEvent | SessionBgTasksClearedEvent | SessionResumedEvent | SessionSpawnedEvent | SessionCommandDoneEvent | SessionModelChangedEvent | SessionConfigChangedEvent | SessionRenamedEvent | SessionPinnedEvent | PolicyPassedEvent | PolicyFailedEvent | PolicyCommitReadyEvent | PolicyCommittedEvent | CronFiredEvent | CronSucceededEvent | CronFailedEvent | ActivityChangedEvent | TaskChangedEvent | WorkflowGateReportEvent | WorkflowApprovalRequestedEvent | WorkflowApprovalResolvedEvent | WorkflowSuspendedEvent | WorkflowSuspendResumedEvent | SessionHarnessWarningEvent;
2520
+ type SessionEvent = SessionTurnEndEvent | SessionAwaitingInputEvent | SessionAwaitingInputFlaggedEvent | SessionAwaitingQuestionAnsweredEvent | SessionPermissionRequestEvent | SessionPermissionResolvedEvent | SessionExitedEvent | SessionReapedEvent | SessionStalledEvent | SessionStallClearedEvent | SessionWatcherAttachedEvent | SessionWatcherDetachedEvent | SessionBgTasksParkedEvent | SessionBgTasksClearedEvent | SessionBgTaskEvent | SessionResumedEvent | SessionSpawnedEvent | SessionCommandDoneEvent | SessionModelChangedEvent | SessionConfigChangedEvent | SessionRenamedEvent | SessionPinnedEvent | SessionMessageEvent | PolicyPassedEvent | PolicyFailedEvent | PolicyCommitReadyEvent | PolicyCommittedEvent | CronFiredEvent | CronSucceededEvent | CronFailedEvent | ActivityChangedEvent | TaskChangedEvent | WorkflowGateReportEvent | WorkflowApprovalRequestedEvent | WorkflowApprovalResolvedEvent | WorkflowSuspendedEvent | WorkflowSuspendResumedEvent | SessionHarnessWarningEvent;
2397
2521
  interface SessionEventBus {
2398
2522
  emit(ev: SessionEvent): void;
2399
2523
  /** Subscribe to a specific event type. Returns an unsubscribe fn. */
@@ -2639,50 +2763,192 @@ declare class TunnelRegistry {
2639
2763
  private loadFromDisk;
2640
2764
  }
2641
2765
 
2766
+ /**
2767
+ * RemoteController — owns the gateway's "publish to the internet" state.
2768
+ *
2769
+ * One singleton per gateway. Holds:
2770
+ * - the current tunnel provider's child process (cloudflared, …)
2771
+ * - the bearer token gating /mcp once the tunnel is up
2772
+ * - persisted state in `<workspace>/.agentproto/remote.json`
2773
+ *
2774
+ * The controller is the *only* component that mutates auth at runtime.
2775
+ * The HTTP server reads auth via a getter (see `AuthSource` in
2776
+ * http-server.ts), so flipping `enable() / disable()` here takes effect
2777
+ * on the next request — no restart, no race.
2778
+ *
2779
+ * ## Provider model
2780
+ *
2781
+ * Each provider implements `RemoteProvider` (start / stop / status).
2782
+ * v1 ships only `quick` (Cloudflare Quick Tunnel — no API key, no DNS,
2783
+ * ephemeral `*.trycloudflare.com` URL). Named CF Tunnel and Tailscale
2784
+ * land as additional implementations.
2785
+ *
2786
+ * ## Token model
2787
+ *
2788
+ * On enable we generate a 32-byte random bearer token, return it once
2789
+ * to the caller, and persist only its sha256 hash to disk. Same model
2790
+ * as a Stripe API key — losing the plaintext means rotating, not
2791
+ * recovering. This keeps `.agentproto/remote.json` safe to commit by
2792
+ * accident (it leaks the URL, not the credential).
2793
+ *
2794
+ * ## Security note
2795
+ *
2796
+ * The CF API token (when used by named-tunnel provider) is *never*
2797
+ * accepted as MCP tool input. The controller reads it from
2798
+ * `~/.config/agentproto/cloudflare.json` or `$CLOUDFLARE_API_TOKEN`.
2799
+ * Otherwise a remote agent could exfiltrate it through `remote_enable`.
2800
+ */
2801
+
2802
+ interface EnableInput {
2803
+ provider?: "quick";
2804
+ /**
2805
+ * Local port to expose. Defaults to the gateway's own port — i.e.
2806
+ * "publish this MCP server." Pass any other port to expose a
2807
+ * different local service (Supabase 54371, a Vite dev server, …)
2808
+ * without spinning up a separate tunnel manager.
2809
+ */
2810
+ targetPort?: number;
2811
+ /**
2812
+ * Host the tunnel forwards to. Defaults to `127.0.0.1`. Setting it
2813
+ * to `localhost` resolves through DNS (IPv6 first on most systems);
2814
+ * keep `127.0.0.1` unless the target is explicitly IPv6-bound.
2815
+ */
2816
+ targetHost?: string;
2817
+ }
2818
+ interface EnableResult {
2819
+ publicUrl: string;
2820
+ /**
2821
+ * Bearer token issued by this gateway. Only meaningful when the
2822
+ * tunnel exposes the gateway itself (default). For arbitrary-target
2823
+ * tunnels (`targetPort != gateway.port`) the daemon does not gate
2824
+ * the proxied service, and this is omitted.
2825
+ */
2826
+ bearerToken?: string;
2827
+ /**
2828
+ * `<publicUrl>/mcp`. Only emitted when exposing the gateway.
2829
+ */
2830
+ mcpEndpoint?: string;
2831
+ /**
2832
+ * Paste-ready `.mcp.json` snippet. Only emitted when exposing the
2833
+ * gateway.
2834
+ */
2835
+ mcpConfigSnippet?: string;
2836
+ /**
2837
+ * A single link to open on a phone (PHONE-PLAN.md P1.2) — the token rides
2838
+ * in a URL *fragment* (`#token=`), never a `?` query string, since
2839
+ * fragments never reach a server (no Referer, no access/proxy log, no CDN
2840
+ * cache key). Two shapes, chosen by whether this daemon has the
2841
+ * `@agentik/session-chat` app installed with a UI:
2842
+ * - installed: `<publicUrl>/apps/@agentik/session-chat/ui#token=<t>` —
2843
+ * the phone lands directly in Control Center.
2844
+ * - not installed: `https://cli.agentproto.sh/panel#daemon=<publicUrl>&token=<t>`
2845
+ * — the hosted panel, which speaks the same fragment shape (see
2846
+ * agentproto/cli-site's `readFragmentConnection`).
2847
+ * Only emitted when exposing the gateway (same condition as `bearerToken`)
2848
+ * — a passthrough tunnel has no daemon UI to link to.
2849
+ */
2850
+ phoneUrl?: string;
2851
+ provider: "quick";
2852
+ target: {
2853
+ host: string;
2854
+ port: number;
2855
+ };
2856
+ exposesGateway: boolean;
2857
+ /**
2858
+ * Present ONLY for passthrough tunnels (`exposesGateway === false`):
2859
+ * the daemon does not gate that traffic, so the public URL reaches the
2860
+ * upstream service with no auth from the daemon. A machine-readable flag
2861
+ * so UIs/agents can surface the exposure instead of it being buried in
2862
+ * the tool's prose description.
2863
+ */
2864
+ warning?: string;
2865
+ }
2866
+ interface RemoteStatus {
2867
+ enabled: boolean;
2868
+ provider?: "quick";
2869
+ publicUrl?: string;
2870
+ pid?: number | null;
2871
+ createdAt?: string;
2872
+ target?: {
2873
+ host: string;
2874
+ port: number;
2875
+ };
2876
+ exposesGateway?: boolean;
2877
+ /** Last error from the provider, if any (since last enable). */
2878
+ lastError?: string;
2879
+ }
2880
+
2642
2881
  /**
2643
2882
  * Daemon-side pairing registry (design: DESIGN §3/§4, PLAN deliverable 2).
2644
2883
  *
2645
2884
  * Owns everything the daemon needs to be *paired with* over an untrusted
2646
2885
  * rendezvous:
2647
2886
  *
2648
- * - **Offer store** — in-memory, single-use, expiry-checked one-time tokens
2649
- * minted by `pair offer`. A daemon restart voids outstanding offers
2650
- * (acceptable + documented). The P1 `verifyOfferToken` predicate plugs in
2651
- * here and SPENDS the token on the first successful handshake.
2887
+ * - **Offer store** — in-memory, single-use, expiry-checked one-time offer
2888
+ * secrets minted by `pair offer`. A daemon restart voids outstanding offers
2889
+ * (acceptable + documented). The daemon parks on the offer's ROUTE token and
2890
+ * the handshake's `verifyAuthToken` predicate checks the sealed AUTH token,
2891
+ * SPENDING the offer on the first successful handshake (a wrong token spends
2892
+ * nothing).
2652
2893
  * - **Pairings store** — `~/.agentproto/pairings.json` (0600, atomic write),
2653
2894
  * one record per paired client: `{clientPub, name, fingerprint, createdAt,
2654
2895
  * lastSeen, pairRoot, rendezvousUrl}`. `pairRoot` is the long-term shared
2655
2896
  * secret (`derivePairRoot`) from which reconnect routing tokens derive.
2656
2897
  * - **Rendezvous connections** — for a fresh offer, and for every persisted
2657
2898
  * pairing on boot (autoconnect), the daemon dials the rendezvous *outbound*
2658
- * (`side=daemon&t=<token>`), runs `daemonHandshakeOverSink` with the P1
2899
+ * (`side=daemon&t=<route>`), runs `daemonHandshakeOverSink` with the P1
2659
2900
  * crypto, and — on success — serves the spliced, E2E-wrapped channel exactly
2660
2901
  * like `serve --connect` serves a tunnel host. The serving path itself is
2661
2902
  * injected (`serve`) so this module stays free of pty/adapter concerns and
2662
2903
  * the CLI can reuse its `createTunnelServer` config verbatim.
2663
- * - **Reconnect epochs** — a persisted pairing's standing connection uses the
2664
- * pairing-derived routing token `t' = HKDF(pairRoot, "rv-route"‖epoch)`,
2665
- * rotated per UTC day. The daemon parks on BOTH the current and previous
2904
+ * - **Reconnect epochs** — a persisted pairing's standing connection parks on
2905
+ * the pairing-derived route `HKDF(pairRoot, "rv-route"‖epoch)`, rotated per
2906
+ * UTC day. The daemon parks on BOTH the current and previous
2666
2907
  * epoch tokens so a client whose clock straddles midnight still finds it;
2667
2908
  * reconnect-with-backoff keeps the standing connection alive.
2668
- * - **Revocation** — removing a pairing drops its rendezvous connections and
2669
- * stops the daemon parking on its tokens, so it can no longer be reached.
2909
+ * - **Revocation** — removing a pairing drops its rendezvous connections, so
2910
+ * it can no longer be served. For a grace period (`revokedGraceMs`, default
2911
+ * 14 days) the daemon keeps a *tombstone*: it still parks on the pairing's
2912
+ * epoch ROUTES, completes the (daemon-signed) handshake only for a hello
2913
+ * carrying that epoch's AUTH token, and answers with a single E2E-encrypted
2914
+ * `error{code:"pairing_revoked"}` frame before closing — never serving the
2915
+ * channel. That is what lets a client tell "unpaired" from "daemon
2916
+ * offline" (which both otherwise look like a park timeout at the broker)
2917
+ * and stop retrying. The signal is authenticated like every tunnel frame:
2918
+ * it arrives inside the AEAD channel of a handshake whose transcript the
2919
+ * client verified against the pinned daemon key, so the broker can't forge
2920
+ * it; and the broker, which knows the routes, can't elicit it either (no
2921
+ * auth token). A tombstone keeps only the per-epoch route + auth tokens for
2922
+ * its window — the pair root is dropped at revoke.
2670
2923
  *
2671
2924
  * ## Reconnect authentication (why no stable client key)
2672
2925
  *
2673
- * P1's handshake carries only a client *ephemeral* key (regenerated per
2674
- * handshake) — there is no persistent client identity key, and the PLAN forbids
2675
- * changing the handshake. So a reconnect authenticates the client by
2676
- * **possession of the epoch routing token**, which derives from `pairRoot` — a
2677
- * secret only the paired client and daemon share (it fell out of the original
2678
- * ECDH). The daemon dials a pairing's epoch token, and on that connection the
2679
- * `verifyOfferToken` predicate accepts a hello whose offer-token field equals
2680
- * that same epoch token (constant-time). Token possession ⇒ paired party. The
2681
- * stored `fingerprint` (of the first handshake's ephemeral key) is a stable
2682
- * display/revocation handle, not an authentication input.
2926
+ * The handshake carries only a client *ephemeral* key (regenerated per
2927
+ * handshake) — there is no persistent client identity key. So a reconnect
2928
+ * authenticates the client by **possession of the epoch AUTH token**
2929
+ * `HKDF(pairRoot, "rv-auth"‖epoch)`, which derives from `pairRoot` — a secret
2930
+ * only the paired client and daemon share (it fell out of the original ECDH).
2931
+ * The daemon parks on the pairing's epoch ROUTE, and on that connection the
2932
+ * `verifyAuthToken` predicate accepts a hello whose sealed auth equals the same
2933
+ * epoch's auth token (constant-time). The route and auth are separate one-way
2934
+ * HKDF outputs: the broker sees every route and can't compute an auth from it.
2935
+ * (pair/v1 used ONE value for both, so a broker could replay a logged route as
2936
+ * the proof and be served.) The stored `fingerprint` (of the first handshake's
2937
+ * ephemeral key) is a stable display/revocation handle, not an auth input.
2938
+ *
2939
+ * ## Legacy (pair/v1) pairings
2940
+ *
2941
+ * A v1 `pairings.json` is loaded with every record flagged `legacy`: listed and
2942
+ * revocable, never served. The daemon still parks on a legacy pairing's routes
2943
+ * (the epoch route is unchanged from v1) so that pairing's not-yet-upgraded
2944
+ * client reaches it and is told to re-pair — see `respondToLegacyHandshake` —
2945
+ * rather than timing out. v1 records are not upgraded in place: v1 let the
2946
+ * broker pair as a client, so any v1 record may be the broker's own.
2683
2947
  */
2684
2948
 
2685
- declare const PAIRINGS_VERSION: 1;
2949
+ /** `pairings.json` format. v2 = pairings made under pair/v2 (route/auth
2950
+ * split). A v1 file still loads, every record flagged `legacy`. */
2951
+ declare const PAIRINGS_VERSION: 2;
2686
2952
  /** One persisted pairing. Written verbatim to `pairings.json`. */
2687
2953
  interface PairingRecord {
2688
2954
  /** The client ephemeral public key from the FIRST handshake (a snapshot —
@@ -2701,6 +2967,9 @@ interface PairingRecord {
2701
2967
  pairRoot: string;
2702
2968
  /** Rendezvous endpoint to reconnect through. */
2703
2969
  rendezvousUrl: string;
2970
+ /** Set on a pairing made under the retired pair/v1 protocol: listed and
2971
+ * revocable, never served — its client is told to re-pair. */
2972
+ legacy?: true;
2704
2973
  }
2705
2974
  /** Mode of a served channel — first-contact offer vs. an established reconnect. */
2706
2975
  type PairingChannelMode = "offer" | "reconnect";
@@ -2718,8 +2987,9 @@ interface PairingChannelHandle {
2718
2987
  close(): Promise<void>;
2719
2988
  }
2720
2989
  interface CreatedOffer {
2721
- /** The one-time offer token (base64url). */
2722
- token: string;
2990
+ /** The one-time offer secret (base64url) — the URL's `s`. Never on the wire:
2991
+ * the broker only ever sees its derived route. */
2992
+ secret: string;
2723
2993
  /** Offer expiry, unix seconds. */
2724
2994
  exp: number;
2725
2995
  /** The full `agentproto://pair?…` offer URL. */
@@ -2772,6 +3042,10 @@ interface PairingRegistryDeps {
2772
3042
  /** How long a parked daemon socket waits for a client hello before recycling
2773
3043
  * the connection. Kept just under the broker's park timeout. Default 110s. */
2774
3044
  handshakeTimeoutMs?: number;
3045
+ /** How long a revoked pairing is still answered with `pairing_revoked` (see
3046
+ * "Revocation" above). Default 14 days; `0` disables tombstones, so a
3047
+ * revoked client just stops finding the daemon. */
3048
+ revokedGraceMs?: number;
2775
3049
  }
2776
3050
  interface PairingRegistry {
2777
3051
  /** Mint a one-time offer + start dialing the rendezvous for it. */
@@ -2832,6 +3106,17 @@ interface SandboxProviderHandle extends AdapterHandle {
2832
3106
  * credentials (e.g. `local`).
2833
3107
  */
2834
3108
  readonly setupFields?: readonly SetupField[];
3109
+ /**
3110
+ * The directory INSIDE the box a spawned agent session should default to
3111
+ * when the caller passed no explicit `cwd` — e.g. e2b/Box's login home
3112
+ * `/home/user`. Absent for a same-machine passthrough provider (`local`),
3113
+ * where the HOST's own resolved cwd is already a valid path in the box
3114
+ * (they're the same filesystem) and forwarding it as-is is correct.
3115
+ * Presence of this field is also the signal `bootSandboxAgentSession`
3116
+ * uses to reject an explicit HOST-only path (e.g. `/Volumes/...`) instead
3117
+ * of forwarding it into a box where it can never resolve.
3118
+ */
3119
+ readonly defaultCwd?: string;
2835
3120
  }
2836
3121
 
2837
3122
  /**
@@ -3331,12 +3616,21 @@ interface OrchestratorGatewayDeps {
3331
3616
  * spawn is rejected with `sandbox_provider_not_found`, exactly as the
3332
3617
  * root gateway would be without it. */
3333
3618
  resolveSandboxProvider?: SandboxProviderResolver;
3334
- /** Forwarded to `registerSessionTools` — config.json
3335
- * `defaults.agentPromptInterrupt`, the unset-default for `interrupt` on
3336
- * `agent_prompt` / `message_parent`. Threaded here so a child driving its
3337
- * parent/peers (or reporting up via `message_parent`) through this scoped
3338
- * gateway honours the same daemon default as the root `/mcp` surface. */
3619
+ /** EXPLICIT override for config.json `defaults.agentPromptInterrupt` —
3620
+ * wins over the fresh per-call resolve (`resolveMessagingDefaults`,
3621
+ * see the factory body below). Tests inject this to pin a value without
3622
+ * touching the filesystem; production leaves it unset so every request
3623
+ * reads the live config, hot-applying a `config_set` change with no
3624
+ * restart. Forwarded to `registerSessionTools`. */
3339
3625
  defaultAgentPromptInterrupt?: boolean;
3626
+ /** EXPLICIT override for config.json `defaults.messaging.allowSiblings` —
3627
+ * see `defaultAgentPromptInterrupt` above for the override/resolve
3628
+ * precedence. Forwarded to `registerAgentTools`. */
3629
+ messagingAllowSiblings?: boolean;
3630
+ /** EXPLICIT override for config.json `defaults.messaging.agentInterrupt` —
3631
+ * see `defaultAgentPromptInterrupt` above for the override/resolve
3632
+ * precedence. Forwarded to `registerAgentTools`. */
3633
+ messagingAgentInterrupt?: "allow" | "deny";
3340
3634
  }
3341
3635
  type OrchestratorMcpServerFactory = (scope: OrchestratorScope) => Promise<McpServer>;
3342
3636
  /**
@@ -3391,7 +3685,7 @@ interface OrphanReaperRegistry {
3391
3685
  list(opts?: {
3392
3686
  includeArchived?: boolean;
3393
3687
  }): readonly SessionDescriptor[];
3394
- kill(id: string, signal?: NodeJS.Signals): boolean;
3688
+ kill(id: string, signal?: NodeJS.Signals, reason?: SessionEndReason): boolean;
3395
3689
  }
3396
3690
  /**
3397
3691
  * Reap a dying parent's live descendants (WP-B — orphan reaping). A
@@ -3579,6 +3873,77 @@ declare function createActivityProjector(opts: {
3579
3873
  resolvePrState?: PrStateResolver;
3580
3874
  }): ActivityProjector;
3581
3875
 
3876
+ /**
3877
+ * Spawn-time role profiles — the primitive that decides whether a
3878
+ * spawned agent may itself delegate (spawn further children).
3879
+ *
3880
+ * A role is 3 layers:
3881
+ * - `disposition` — a system-prompt fragment. Soft: sets the mindset.
3882
+ * - `toolPolicy` — whether delegation tools are injected into the
3883
+ * child at all. HARD: enforced by the daemon at the `agent_start`
3884
+ * injection point (`session-spawn.ts`), not by the child's own
3885
+ * behaviour. A child cannot widen its own `toolPolicy` — not via
3886
+ * `promptAppend`, not by requesting `orchestrator` itself.
3887
+ * - `skills[]` — role-specific skill set (built-ins ship empty/
3888
+ * unset; a future pack-carried role can populate this).
3889
+ *
3890
+ * Why the asymmetry matters: a process can bring its own disposition
3891
+ * and skills (those are portable, could live in a skill/pack), but it
3892
+ * can never strip tools the daemon already injected into it. Only the
3893
+ * spawner controls that — which is exactly why `toolPolicy` must be
3894
+ * resolved and enforced here, at spawn time, rather than left to a
3895
+ * prompt instruction the model could rationalize past.
3896
+ */
3897
+
3898
+ type DelegationPolicy = "allow" | "deny";
3899
+ interface RoleToolPolicy {
3900
+ delegation: DelegationPolicy;
3901
+ }
3902
+ interface RoleProfile {
3903
+ name: string;
3904
+ disposition: string;
3905
+ toolPolicy: RoleToolPolicy;
3906
+ skills?: string[];
3907
+ /**
3908
+ * Privilege level in the spawn lattice. Higher = more privileged.
3909
+ * The default spawn-gate rule (`canSpawn`) is non-escalation: a
3910
+ * parent may spawn a child only when `child.level <= parent.level`
3911
+ * — peers and subordinates are fine, spawning something MORE
3912
+ * privileged than yourself is not. (Unbounded same-level recursion —
3913
+ * e.g. supervisor spawning supervisor spawning supervisor — is a
3914
+ * pre-existing, deliberate pattern bounded separately by
3915
+ * `maxDepth`/`maxChildren`, not this lattice; see `spawnableRoles`
3916
+ * below for a role that also needs to cap recursion itself.)
3917
+ * `spawnableRoles` (below), when set, opts a role into a closed
3918
+ * allowlist instead — level is then only used for display/ordering.
3919
+ */
3920
+ level: number;
3921
+ /**
3922
+ * Closed allowlist of role names this role may spawn, by name — the
3923
+ * opt-in "figé pipeline" mode. When set, `canSpawn` checks name
3924
+ * membership instead of the level comparison. Undefined (the
3925
+ * default, open mode) ⇒ non-escalation by `level`.
3926
+ */
3927
+ spawnableRoles?: string[];
3928
+ /**
3929
+ * Role-level default for the daemon self-mount's deferred/lazy tool
3930
+ * loading (harness-parity item 3 — see `deferred-tools.ts`). Applied to
3931
+ * the injected `mcpServers` entry (`session-spawn.ts`'s
3932
+ * `shouldInjectDaemonSelfMount` path) as `?deferred=1|0` when the spawn
3933
+ * itself didn't pass an explicit `deferredTools` override. Undefined ⇒
3934
+ * no role-level opinion, the gateway's own boot-time default
3935
+ * (`defaults.mcp.deferredTools` in config.json) applies unchanged.
3936
+ */
3937
+ deferredTools?: boolean;
3938
+ /**
3939
+ * Role-level default for `agent_start.browser` (see `browser-mount.ts`):
3940
+ * `"headless"` gives every spawn of this role its own isolated headless
3941
+ * Chrome. An explicit spawn `browser` wins; undefined ⇒ no opinion (the
3942
+ * user preset, then `defaults.spawn.browser`, decide).
3943
+ */
3944
+ browser?: SpawnBrowserMode;
3945
+ }
3946
+
3582
3947
  /**
3583
3948
  * Daemon-side AGENTS.md resolution + injection (WP-R2).
3584
3949
  *
@@ -3607,7 +3972,44 @@ declare function createActivityProjector(opts: {
3607
3972
  * absent — plus a standing cd-contract sentence regardless of mode.
3608
3973
  */
3609
3974
  type AgentsMdMode = "inline" | "pointer" | "absent";
3975
+ /** The resolved AGENTS.md's absolute path; undefined when mode is "absent". */
3976
+ interface AgentsMdResolution {
3977
+ mode: AgentsMdMode;
3978
+ /** Absolute path of the resolved AGENTS.md. Undefined when mode is
3979
+ * `"absent"`. */
3980
+ path?: string;
3981
+ /** Full text of the AGENTS.md (inline mode only). */
3982
+ content?: string;
3983
+ /** The prompt block to inject (full inline text or a pointer), or
3984
+ * undefined when absent. Excludes the always-present cd-contract line. */
3985
+ block?: string;
3986
+ /** The standing cd-contract sentence, present in every mode. */
3987
+ contractLine: string;
3988
+ }
3989
+
3990
+ interface WorkspaceRulesResolution {
3991
+ /** Absolute path of the resolved RULES.md. Undefined when absent (or a
3992
+ * read failure degraded to absent). */
3993
+ path?: string;
3994
+ /** Full text of the RULES.md, inlined. Present exactly when `block` is. */
3995
+ content?: string;
3996
+ /** The prompt block to inject, or undefined when absent. */
3997
+ block?: string;
3998
+ }
3610
3999
 
4000
+ /** The `appServe` request carried on `agent_start` — an in-box app dir plus
4001
+ * the port to serve its UI on. */
4002
+ interface SandboxAppServeSpec {
4003
+ /** Absolute path to the app's directory INSIDE the box (e.g.
4004
+ * `/home/user/apps/<slug>`). Must not contain a single quote — the
4005
+ * launcher shell-quotes it. */
4006
+ dir: string;
4007
+ /** Port `agentproto app serve` binds inside the box. Must also be exposed
4008
+ * by the provider (the spawn path appends it to `extraPorts` so a
4009
+ * boot-time exposure exists on providers that pre-resolve). Defaults to
4010
+ * `DEFAULT_APP_SERVE_PORT` when omitted. */
4011
+ port?: number;
4012
+ }
3611
4013
  /** What a served app records on the session descriptor and echoes in the
3612
4014
  * spawn result — non-secret, caller-visible. */
3613
4015
  interface SessionAppServeInfo {
@@ -3833,6 +4235,436 @@ declare function loadWorktreeIsolation(loadCfg?: () => Promise<{
3833
4235
  };
3834
4236
  }>): Promise<WorktreeIsolationMode>;
3835
4237
 
4238
+ /**
4239
+ * Policy layer for `agent_start.attach` — the config-driven decision of WHO a
4240
+ * spawn nests under, kept deliberately separate (and pure) from the spawn
4241
+ * plumbing in `session-spawn.ts`, exactly as `worktree-isolation.ts` is for
4242
+ * `agent_start.worktree`.
4243
+ *
4244
+ * The problem it fixes: on the plain `/mcp` path (no scoped orchestrator
4245
+ * gateway), a spawn was attributed to a parent ONLY when the caller passed an
4246
+ * explicit `parentSessionId` hint — so a supervisor that spawned an executor
4247
+ * without wiring itself as an orchestrator produced a depth-0 ORPHAN. The
4248
+ * daemon already knows the caller's own id on that path (the trusted
4249
+ * `?callerSessionId=` query the self-ref `mcpServers` URL carries — PR 7 /
4250
+ * Gap 7), it just wasn't being used for lineage. This module decides whether
4251
+ * to use it.
4252
+ *
4253
+ * Trust boundary (unchanged from the hint): the scoped `/mcp/orchestrator`
4254
+ * gateway's token is the UNSPOOFABLE source — when a `callerScope` is present
4255
+ * the caller derives its parent from the token and this whole module is
4256
+ * bypassed (see `session-spawn.ts`). Everything here is descriptor-only
4257
+ * lineage on the anonymous root path; it never moves the gating `childDepth`
4258
+ * and so never relaxes a depth-gated worktree/role guard.
4259
+ */
4260
+
4261
+ /**
4262
+ * The `agent_start.attach` field. `true` opts in to attaching under the
4263
+ * derived parent (the caller's own id / an explicit hint) even when the
4264
+ * policy is `on-request`; an object additionally pins an explicit parent id;
4265
+ * `false` forces an INDEPENDENT root (no parent, depth 0) regardless of
4266
+ * policy or derivable identity — the deliberate "launch detached" escape
4267
+ * hatch, mirroring `worktree: false`. Omitted ⇒ the policy mode decides.
4268
+ */
4269
+ type AttachField = boolean | {
4270
+ parent?: string;
4271
+ };
4272
+
4273
+ /**
4274
+ * Shared `agent_start` spawn logic — orchestrator scoped sub-gateway
4275
+ * minting, `mcpServers` merge, hermes default-mcpServers safety net,
4276
+ * depth/quota checks. Extracted from `agent-tools.ts` (the MCP tool)
4277
+ * so the HTTP route can reuse it without re-implementing (and
4278
+ * re-drifting from) the same behaviour.
4279
+ */
4280
+
4281
+ /** `agent_start.sandbox`'s inline-spec form, plus the PR3 reuse field. See
4282
+ * `SpawnAgentSessionInput.sandbox`. */
4283
+ type SandboxSpecInput = SandboxSpec & {
4284
+ reuse?: string;
4285
+ };
4286
+ /** Matches `RegisterAgentToolsOptions.buildOrchestratorMcp` in
4287
+ * agent-tools.ts — kept as its own alias here since that's the shape
4288
+ * actually passed in (opts required), not the more permissive
4289
+ * `OrchestratorInjector` (opts optional) from orchestrator-gateway.ts. */
4290
+ type BuildOrchestratorMcp = (opts: {
4291
+ tools?: readonly string[];
4292
+ caller?: OrchestratorScope;
4293
+ maxDepth?: number;
4294
+ maxChildren?: number;
4295
+ role?: string;
4296
+ }) => {
4297
+ entry: AcpMcpServer;
4298
+ bindLifecycle: (sessionId: string) => () => void;
4299
+ /** The minted scope — only `tools` (the effective, narrowed allowlist)
4300
+ * is read here, to tell whether the child can actually reach
4301
+ * `agent_start` through it (see `delegationReachFor`). Optional so
4302
+ * minimal injectors (tests) needn't return it. */
4303
+ scope?: {
4304
+ tools: ReadonlySet<string>;
4305
+ };
4306
+ };
4307
+ interface SpawnAgentSessionDeps {
4308
+ registry: SessionsRegistry;
4309
+ /** Required — callers must check for its absence themselves (the MCP
4310
+ * tool and HTTP route each have their own "not enabled" response
4311
+ * shape for the missing-resolver case). */
4312
+ resolveAgentAdapter: AgentAdapterResolver;
4313
+ /** Optional orchestrator-injection builder (WP3). See
4314
+ * `RegisterAgentToolsOptions.buildOrchestratorMcp` for the full
4315
+ * contract. Omitted → `orchestrator` is rejected with
4316
+ * `orchestrator_not_enabled`. */
4317
+ buildOrchestratorMcp?: BuildOrchestratorMcp;
4318
+ /** The daemon's own plain `/mcp` gateway URL. See
4319
+ * `RegisterAgentToolsOptions.daemonMcpUrl`. */
4320
+ daemonMcpUrl?: string;
4321
+ /** Calling orchestrator's scope (orchestrator WP4). See
4322
+ * `RegisterAgentToolsOptions.callerScope`. */
4323
+ callerScope?: OrchestratorScope;
4324
+ /** Optional webhook notifier — see `RegisterAgentToolsOptions.webhookNotifier`. */
4325
+ webhookNotifier?: WebhookNotifier;
4326
+ /** Loads config.json's `defaults` block (global + per-adapter skills/
4327
+ * options auto-applied at every spawn — see `resolveSpawnDefaults`).
4328
+ * Defaults to reading the real `~/.agentproto/config.json` via
4329
+ * `loadConfig` when omitted; tests inject a stub to avoid touching
4330
+ * the real file. */
4331
+ loadDefaultsConfig?: () => Promise<SpawnDefaultsConfig | undefined>;
4332
+ /** Builds the per-session headless-browser mount for `browser:
4333
+ * "headless"` (see `browser-mount.ts`). Defaults to the real resolver
4334
+ * (installs chrome-devtools-mcp on first use); tests inject a stub. */
4335
+ resolveHeadlessBrowser?: ResolveHeadlessBrowser;
4336
+ /** Loads the custom (pack-carried) role registry, merged with the
4337
+ * two built-ins by `resolveRole`/`canSpawn` — see `role.ts`'s
4338
+ * `mergeRoleRegistry`. Defaults to `loadDefaultRoleRegistry()`
4339
+ * (`~/.agentproto/roles/` + adapter-carried packs) when omitted;
4340
+ * tests inject a stub registry to avoid touching the real
4341
+ * filesystem. */
4342
+ loadRoleRegistry?: () => Promise<Record<string, RoleProfile>>;
4343
+ /** Resolves an `agent_start.sandbox` slug (or an inline spec's own
4344
+ * `.provider`) to a concrete sandbox provider handle. Required for
4345
+ * `input.sandbox` — omitted ⇒ `sandbox_provider_not_found`. */
4346
+ resolveSandboxProvider?: SandboxProviderResolver;
4347
+ /** Provision a git worktree and return the cwd the spawn should land in —
4348
+ * the injected port behind `agent_start.worktree` (see
4349
+ * `worktree-isolation.ts`). Wired at the composition root by a host that
4350
+ * depends on `@agentproto/worktree` (the CLI). Omitted ⇒ a spawn the
4351
+ * policy says to isolate fails with `worktree_provisioner_not_enabled`
4352
+ * rather than silently spawning unisolated. */
4353
+ provisionWorktree?: WorktreeProvisioner;
4354
+ /** Resolves the effective `worktrees.isolation` policy (env > config >
4355
+ * `on-request`). Defaults to reading the real `~/.agentproto/config.json`
4356
+ * via `loadWorktreeIsolation` when omitted; tests inject a stub to pin the
4357
+ * mode without touching the real file or env. */
4358
+ resolveWorktreeIsolation?: () => Promise<WorktreeIsolationMode>;
4359
+ /** Resolves the effective `spawn.attach` policy (env > config > `always`).
4360
+ * Defaults to `loadSpawnAttach` (reads the real config) when omitted;
4361
+ * tests inject a stub to pin the mode without touching env or the file. */
4362
+ resolveSpawnAttach?: () => Promise<SpawnAttachMode>;
4363
+ /** Resolves the effective `spawn.dedupe` policy (env > config > `always`).
4364
+ * Defaults to `loadSpawnDedupe` (reads the real config) when omitted;
4365
+ * tests inject a stub to pin the mode without touching env or the file.
4366
+ * See `spawn-dedupe.ts`. */
4367
+ resolveSpawnDedupe?: () => Promise<SpawnDedupeMode>;
4368
+ /** Resolves the effective `provenance.wrapGh` opt-in (env > config >
4369
+ * `false`). When it resolves true, a `gh` PATH shim is prepended to the
4370
+ * spawned session's env so any `gh pr create` it (or an adapter
4371
+ * subprocess) runs gets the daemon's provenance footer appended to the
4372
+ * created PR's body — see `gh-provenance-shim.ts`. Defaults to
4373
+ * `loadProvenanceWrapGh` (reads the real config) when omitted; tests
4374
+ * inject a stub to pin it without touching env or the file. */
4375
+ resolveProvenanceWrapGh?: () => Promise<boolean>;
4376
+ /** Resolves + shapes the AGENTS.md for a spawn's `cwd` (see
4377
+ * `agents-md.ts`): inline / pointer / absent, plus the standing cd-contract
4378
+ * line. Defaults to the real resolver over `loadAgentsMdInlineMaxKb`'s
4379
+ * config-derived threshold when omitted; tests inject a stub to avoid
4380
+ * touching the filesystem, git, and the real config on every spawn. */
4381
+ resolveAgentsMd?: (cwd: string, inlineMaxKb?: number) => Promise<AgentsMdResolution>;
4382
+ /** Resolves + shapes the per-workspace RULES.md for a spawn (see
4383
+ * `workspace-rules.ts`): the path of the workspace's `RULES.md`, inlined
4384
+ * in full when present, absent otherwise. Injected into EVERY spawn in
4385
+ * the workspace (root and nested — no depth gate). Defaults to the real
4386
+ * resolver over the workspace state bucket when omitted; tests inject a
4387
+ * stub to avoid touching the real `~/.agentproto` directory. */
4388
+ resolveWorkspaceRules?: (slug: string | undefined) => Promise<WorkspaceRulesResolution>;
4389
+ /** Feeds the adapter-capability spawn guard (`checkModelAdapterEligibility`)
4390
+ * — the SAME `listCatalogModels` the `catalog_models` MCP tool /
4391
+ * `GET /catalog/models` route already use (`RegisterAgentToolsOptions.
4392
+ * listCatalogModels`), reused here rather than re-derived. Omitted ⇒ the
4393
+ * guard is skipped — a spawn that would 404 upstream on an adapter's
4394
+ * manifest not covering the requested model still reaches the driver,
4395
+ * same as before this guard existed. */
4396
+ listCatalogModels?: CatalogModelsLister;
4397
+ }
4398
+ interface SpawnAgentSessionInput {
4399
+ adapter: string;
4400
+ /** Canonical harness slug — recorded on the descriptor; defaults to `adapter`. */
4401
+ harness?: string;
4402
+ cwd?: string;
4403
+ workspaceSlug?: string;
4404
+ /** Trusted-loopback parent-lineage hint (WP-R1). Attributes this spawn to a
4405
+ * logical parent session so it nests under that node in the sessions tree,
4406
+ * filling the gap that otherwise leaves an anonymous `agent_start` (an
4407
+ * agent-to-agent spawn on the root `/mcp`, or a caller that IS a session but
4408
+ * didn't come through the scoped orchestrator gateway) a depth-0 orphan.
4409
+ *
4410
+ * Trust boundary: the scoped `/mcp/orchestrator` gateway derives parent from
4411
+ * its unspoofable scope token, so a hint arriving WITH a `callerScope` is
4412
+ * IGNORED — the scope always wins. The hint is honoured ONLY on the
4413
+ * anonymous root path (no `callerScope`), where there is no scope to
4414
+ * attribute from. `depth` is derived from the resolved parent descriptor
4415
+ * (parent's `depth + 1`, defaulting to 1 when the parent isn't registered)
4416
+ * rather than trusted from the caller. Descriptor-only: a hint never relaxes
4417
+ * the depth-gated worktree/role guards — declaring a logical parent is not
4418
+ * the same as being a nested (scoped) spawn. */
4419
+ parentSessionId?: string;
4420
+ /** Attach policy override for THIS spawn (`agent_start.attach`). `false`
4421
+ * forces an independent root even when a parent is derivable; `true` opts
4422
+ * in to attaching under the derived parent even under an `on-request`
4423
+ * policy; `{ parent }` pins an explicit parent. Omitted ⇒ the daemon's
4424
+ * `spawn.attach` policy decides. Resolved together with `parentSessionId`
4425
+ * and `autoParentSessionId` in `decideSpawnAttach` — see `spawn-attach.ts`.
4426
+ * Ignored WITH a `callerScope` (the scoped orchestrator token always wins). */
4427
+ attach?: AttachField;
4428
+ /** Daemon-DERIVED caller session id — the spawning session's own id, read
4429
+ * from the trusted `?callerSessionId=` query the self-ref `mcpServers` URL
4430
+ * carries (PR 7 / Gap 7), NOT from the caller's arguments. This is the
4431
+ * implicit auto-parent that makes attach-by-default work without the caller
4432
+ * passing its own id. Threaded from `registerAgentTools`' `callerSessionId`
4433
+ * option; lower precedence than an explicit `parentSessionId` hint. */
4434
+ autoParentSessionId?: string;
4435
+ /** Task-board pin for the spawned child, stamped onto its descriptor as
4436
+ * `meta.boardId`. The Task ledger's board resolution prefers this over
4437
+ * the `parentSessionId` lineage walk (see task-ledger.ts's
4438
+ * `resolveBoardId`), so a client spawning several depth-0 roots — a
4439
+ * cowork/operator harness fanning out executors with no shared lineage —
4440
+ * can join them all onto ONE board. Descriptor-only: it never enters the
4441
+ * depth/quota/role gates, and an explicit `boardId` on a task verb still
4442
+ * wins over it. */
4443
+ boardId?: string;
4444
+ /** Source label for this spawn (channel/harness). Descriptor-only. */
4445
+ origin?: string;
4446
+ /** Installed app id this spawn runs on behalf of (`app_run` sets this to
4447
+ * the app's own id). Forwarded to the child process as `APP_ID_ENV` — a
4448
+ * daemon-tool proxy the child builds (mastra-agent's `daemon-mcp-tools.ts`,
4449
+ * P7) auto-injects it into an `app_*` tool call that omits `appId`, since
4450
+ * the model driving the session has no way to know its own appId unless
4451
+ * told. Absent on a spawn made outside `app_run`. */
4452
+ appId?: string;
4453
+ /** Reattach to a pre-existing adapter-native session (claude-code's
4454
+ * conversation id, hermes' chat handle, …) instead of starting
4455
+ * blank. Not exposed on the MCP `agent_start` tool today — only the
4456
+ * HTTP route (`sessions restart`) passes this. */
4457
+ resumeSessionId?: string;
4458
+ prompt?: string;
4459
+ label?: string;
4460
+ /** Explicit session title (SPEC-3 FIX C, `agentproto sessions start
4461
+ * --title`). When set, it wins over the first-sentence derivation from
4462
+ * `prompt` — so a CLI-named session isn't limited to whatever its opening
4463
+ * prompt happened to say. Unlike `label`, it fills the `title` slot, so a
4464
+ * spawner `label` still out-ranks it in the display chain. */
4465
+ title?: string;
4466
+ mode?: string;
4467
+ /** Manifest-declared option id → value map (AIP-45 `options`), applied
4468
+ * at spawn time alongside `mode`. Forwarded verbatim to the driver's
4469
+ * `startSession({ options })` → `composeSpawn`'s option patches. */
4470
+ options?: Record<string, boolean | number | string>;
4471
+ /** Normalized, adapter-agnostic skill ids for this session. Merged with
4472
+ * config.json's `defaults.skills` / `defaults.adapters.<slug>.skills`
4473
+ * (global < per-adapter < this field, which REPLACES the union rather
4474
+ * than merging into it when provided — a deliberate exact set). Folded
4475
+ * into `options.skills` per the resolved adapter's declared `skills`
4476
+ * option shape (e.g. hermes' comma-joined string); adapters with no
4477
+ * such option (e.g. claude-code, which auto-discovers skills) ignore
4478
+ * it. See `resolveSpawnDefaults` / `normalizeSkillsOption`. */
4479
+ skills?: string[];
4480
+ model?: string;
4481
+ effort?: string;
4482
+ /** Decomposed route identity. This is canonical transport; `mode` remains
4483
+ * a legacy adapter projection only. */
4484
+ route?: RouteSpec;
4485
+ /** Named billing credential. Resolved from auth-profiles + keychain at the
4486
+ * final spawn boundary; the secret never crosses HTTP/MCP. */
4487
+ access?: {
4488
+ profileRef?: string;
4489
+ };
4490
+ posture?: Posture;
4491
+ contextProfile?: ContextProfile;
4492
+ /** A preloaded user preset. Callers resolve its id at their boundary so a
4493
+ * preset can also provide the adapter before this core is invoked. */
4494
+ preset?: UserPreset;
4495
+ /**
4496
+ * Deterministic billing-auth mode + EXPLICIT credential for adapters that
4497
+ * declare an env-var vocabulary for it (today: claude-code — see
4498
+ * `AgentCliAuth.modes` in `@agentproto/driver-agent-cli`). `mode` wins over
4499
+ * `~/.agentproto/config.json`'s `defaults.adapters.<slug>.auth.mode`
4500
+ * (default `"subscription"`); the credential (`token` for `"subscription"`,
4501
+ * `apiKey` for `"api-key"`) wins over the config field matching the
4502
+ * RESOLVED mode — see `resolveSpawnDefaults`. Never read from the ambient
4503
+ * shell env; never logged (only a fingerprint is recorded on the session
4504
+ * descriptor — see `credentialFingerprint`). Adapters that don't declare
4505
+ * the vocabulary ignore this field entirely.
4506
+ */
4507
+ auth?: DefaultsAdapterAuthConfig;
4508
+ mcpServers?: AcpMcpServer[];
4509
+ orchestrator?: boolean | {
4510
+ tools?: string[];
4511
+ maxDepth?: number;
4512
+ maxChildren?: number;
4513
+ };
4514
+ notifyUrl?: string;
4515
+ wait?: boolean;
4516
+ maxCostUsd?: number;
4517
+ /** Windowed cost-budget cap (phase 4). DISTINCT from the scalar `maxCostUsd`
4518
+ * session-kill: this is recorded on the descriptor/runtime and drives an
4519
+ * auto-attached governance policy (`policy:failed` on windowed overage) — it
4520
+ * never kills the session. Omit ⇒ no windowed budget. */
4521
+ costBudget?: CostBudget;
4522
+ /** Opt-in auto-restart policy (restart-scheduler, PR-2). When set, an
4523
+ * unexpected death (`crashed` and/or `error`, per `restartPolicy.on`) is
4524
+ * proactively revived in place — see `RestartPolicy`'s doc in
4525
+ * `sessions.ts`. Omitted ⇒ today's lazy-resume-only behaviour. */
4526
+ restartPolicy?: RestartPolicy;
4527
+ /** Context-continuity policy for this session — controls warning,
4528
+ * opportunistic compaction, fresh-continuation, and hard-stop thresholds.
4529
+ * Resolved from global → per-adapter → per-model → explicit override. */
4530
+ contextContinuity?: ContextContinuityPolicy;
4531
+ /** Spawn-time role — `"executor"` | `"supervisor"` | omitted. See
4532
+ * `resolveRole` in `role.ts`. Omitted ⇒ derived from spawn depth
4533
+ * against `defaults.defaultRoleDepthCutoff` (default 1). A resolved
4534
+ * role with `toolPolicy.delegation === "deny"` is a HARD gate:
4535
+ * `orchestrator` is ignored outright and the hermes/default
4536
+ * full-gateway injection excludes `DELEGATION_TOOL_NAMES` — neither
4537
+ * this field's own request nor `promptAppend` can re-open it. */
4538
+ role?: string;
4539
+ /** One-off runtime text layered ON TOP of the resolved role's
4540
+ * disposition (never replacing it) and prepended to `prompt`. See
4541
+ * `composeRoleContext`. Cannot widen `toolPolicy` — see `role` above. */
4542
+ promptAppend?: string;
4543
+ /** Per-spawn override for the daemon self-mount's deferred/lazy
4544
+ * `tools/list` loading (harness-parity item 3 — see `deferred-tools.ts`).
4545
+ * Wins over the resolved role's own default (`RoleProfile.deferredTools`
4546
+ * — on for `executor`), which in turn only applies when this is
4547
+ * omitted. Threaded onto the injected `mcpServers` self-mount ref as
4548
+ * `?deferred=1|0` (`shouldInjectDaemonSelfMount` path below); has no
4549
+ * effect on a caller-supplied `mcpServers` (that URL is the caller's to
4550
+ * compose). Omitted entirely ⇒ no override at all — the gateway's own
4551
+ * boot-time `defaults.mcp.deferredTools` default applies unchanged. */
4552
+ deferredTools?: boolean;
4553
+ /** Give the agent its own isolated headless Chrome: a per-session
4554
+ * chrome-devtools-mcp stdio server appended to `mcpServers` (named
4555
+ * `browser`), plus a short prompt hint naming its tools. Wins over the
4556
+ * resolved role's `browser`, then the user preset's, then config
4557
+ * `defaults.spawn.browser`; all unset ⇒ off. Explicit `true` with a
4558
+ * `sandbox` spawn is rejected (no Chrome in the box image yet). */
4559
+ browser?: SpawnBrowserMode;
4560
+ /** Opt this session into Langfuse tracing (prompt/completion + tool spans +
4561
+ * tokens/cost). Effective opt-in is `trace ?? langfuseTracingDefault ?? false`
4562
+ * — see `SpawnAgentInput.trace` in sessions.ts. */
4563
+ trace?: boolean;
4564
+ /** Run this session inside a sandbox instead of on the host — a provider
4565
+ * slug from `list_sandbox_providers` (e.g. `"local"`, `"e2b"`), or an
4566
+ * inline AIP-36 `SandboxSpec`, optionally carrying `reuse: "<sandboxId>"`
4567
+ * to reconnect to an existing box instead of booting fresh. When set,
4568
+ * this branch resolves the provider, boots (or reconnects) the box,
4569
+ * spawns `adapter` on the BOX's own `agent_start`, and wires a
4570
+ * `SandboxAgentSessionProxy` in place of the local `resolveAgentAdapter`
4571
+ * path — see `sandbox-agent-session-proxy.ts`. */
4572
+ sandbox?: string | SandboxSpecInput;
4573
+ /** WP3 — serve an agentproto app's UI from INSIDE the sandbox box and
4574
+ * return its public URL on the descriptor + result. Requires `sandbox`
4575
+ * (a non-sandbox spawn carrying `appServe` is rejected): the app dir is
4576
+ * resolved INSIDE the box (e.g. `/home/user/apps/<slug>` on the
4577
+ * workstation image), installed via the box daemon's `app_install`, and
4578
+ * `agentproto app serve --host 0.0.0.0 --port <port>` is launched
4579
+ * detached through the box's `command_execute`. The port is appended to
4580
+ * the spec's `extraPorts` so the provider resolves its public URL (also
4581
+ * stamped into `sandboxPorts`), and the serve result lands on the
4582
+ * descriptor's `appServe` field. See `sandbox-app-serve.ts`. */
4583
+ appServe?: SandboxAppServeSpec;
4584
+ /** Isolate this session into its own git worktree instead of spawning in
4585
+ * `cwd` directly. `true` provisions a worktree with an auto-minted branch/
4586
+ * slug; `{ slug?, base? }` pins the slug and/or the base ref it's cut from.
4587
+ * Honoured only at spawn depth 0 (a nested spawn inherits the parent's
4588
+ * ground) and only for a `cwd` inside a git repo (nothing to isolate
4589
+ * otherwise ⇒ spawns plain). The daemon's `worktrees.isolation` policy can
4590
+ * force this on (`always`) or off (`never`) regardless — see
4591
+ * `worktree-isolation.ts`. Ignored entirely for a `sandbox` spawn (the box
4592
+ * already isolates). Cleanup is the human's job (`agentproto worktree
4593
+ * rm|archive|gc`); the tree is never auto-removed on session exit. */
4594
+ worktree?: WorktreeField;
4595
+ /** Acknowledge that spawning IN PLACE into a shared, dirty working tree is
4596
+ * intended, silencing the `sharedDirtyCwd` warning a nested implicit spawn
4597
+ * would otherwise emit (see `decideWorktreeIsolation`). Only meaningful for
4598
+ * a nested (depth > 0) spawn with no `worktree` request and no `sandbox`;
4599
+ * ignored everywhere else. Default false — the warning fires. */
4600
+ allowSharedCwd?: boolean;
4601
+ /** Start this session in permission-hold mode: every ACP permission request
4602
+ * is surfaced + parked in the cross-session inbox (`permissions_list` /
4603
+ * `permissions_respond`) instead of auto-answered. Threaded to the driver's
4604
+ * `startSession({ permissionHold })` and recorded on the descriptor. Ignored
4605
+ * for sandbox spawns (the box's own daemon owns permission handling).
4606
+ * Default false — unchanged auto-answer behaviour. */
4607
+ permissionHold?: boolean;
4608
+ /** Opt this child into the direct in-band crash notification: when it
4609
+ * crashes (`markCrashed`), the supervisor-notify subscriber
4610
+ * (`supervisor-notify.ts`) delivers a `[child-crashed] …` notice into its
4611
+ * `parentSessionId`, if any — enqueued immediately when the parent is
4612
+ * alive and idle, queued for its next turn when busy (never
4613
+ * interrupted). Recorded verbatim onto {@link SessionDescriptor
4614
+ * .notifyParentOnCrash}; see that field's doc. Default false — the free
4615
+ * external webhook path (`notifyUrl`) already fires regardless of this
4616
+ * flag. */
4617
+ notifyParentOnCrash?: boolean;
4618
+ /** Exempt this session from the idle-reaper (`isReapable` in
4619
+ * idle-reaper.ts) regardless of how long it sits idle. Stamped straight
4620
+ * onto the descriptor — see `SessionDescriptor.keepAlive`. Default false
4621
+ * — unchanged reap-eligible behaviour. Toggleable later via the
4622
+ * `session_set_keepalive` MCP verb. */
4623
+ keepAlive?: boolean;
4624
+ /** Caller-declared "this is the same logical spawn" token — a PROMISE, not
4625
+ * a guess. A second `agent_start` with the same `(adapter, cwd,
4626
+ * idempotencyKey)` within `SPAWN_CLAIM_WINDOW_MS` of a SUCCESSFUL spawn
4627
+ * returns that spawn's descriptor instead of forking a second process —
4628
+ * the fix for a retried call otherwise silently duplicating a live
4629
+ * agent. Always wins over the daemon's own derived (implicit) key when
4630
+ * both would apply — see `dedupe` and `spawn-dedupe.ts`. Omitting this
4631
+ * does NOT mean "spawn unconditionally" anymore: when the daemon's
4632
+ * `spawn.dedupe` policy is `"always"` (the default) and this spawn
4633
+ * carries a `label`, the daemon derives an implicit key in its place —
4634
+ * see `dedupe` below to opt out per-call. */
4635
+ idempotencyKey?: string;
4636
+ /** Per-call override for the daemon's `spawn.dedupe` policy (implicit
4637
+ * dedupe when no `idempotencyKey` was supplied — see `spawn-dedupe.ts`).
4638
+ * `false` is the escape hatch: never derive an implicit key for THIS
4639
+ * spawn, regardless of policy — mirrors `attach: false` / `worktree:
4640
+ * false`. `true` forces derivation even under an `"on-request"` policy,
4641
+ * mirroring `attach: true`. Omitted ⇒ the policy mode decides. Has no
4642
+ * effect when an explicit `idempotencyKey` is supplied (it already wins
4643
+ * outright) or when this spawn carries no `label` (nothing to derive
4644
+ * from either way). */
4645
+ dedupe?: boolean;
4646
+ /**
4647
+ * OS-level confinement for the adapter's OWN spawned process
4648
+ * (`@agentproto/command-sandbox` — macOS Seatbelt / Linux bubblewrap),
4649
+ * threaded verbatim to `resolved.startSession({ commandSandbox })` →
4650
+ * `AgentCliRuntime.start`. NOT the AIP-36 `sandbox` field above — that
4651
+ * boots a whole nested daemon on a different machine/box (remote-box
4652
+ * session provider); this wraps THIS host's spawn argv so the adapter's
4653
+ * own process tree can't read/write outside `cwd` (and, `"strict"`,
4654
+ * reach the network), confinement an ACP permission seam can never see
4655
+ * since it only covers tool calls the adapter chooses to report. Exposed
4656
+ * on the `agent_start` MCP tool schema (`agent-tools.ts`) as of PR 6b —
4657
+ * when omitted here, the driver falls back to the workspace's
4658
+ * `.agentproto/command-sandbox.json` `adapterSpawn` key (a key distinct
4659
+ * from the top-level `mode` that same file also carries for
4660
+ * `command_execute` — see `@agentproto/command-sandbox`'s module doc for
4661
+ * why the two axes are never shared), or stays unconfined if that's unset
4662
+ * too. Ignored for a `sandbox` spawn (the box's own daemon would need to
4663
+ * apply this itself).
4664
+ */
4665
+ commandSandbox?: SandboxMode;
4666
+ }
4667
+
3836
4668
  /**
3837
4669
  * Shared kernel for the daemon's worktree-status query surface.
3838
4670
  *
@@ -3853,9 +4685,28 @@ interface WorktreeStatusView {
3853
4685
  branch: string | null;
3854
4686
  class: "reclaim" | "salvage" | "hold";
3855
4687
  reclaimable: boolean;
4688
+ /** Uncommitted work in the tree (modified, staged or untracked paths). */
4689
+ dirty: boolean;
4690
+ /** Per-kind counts, present only when `dirty`. */
4691
+ changes?: {
4692
+ modified: number;
4693
+ staged: number;
4694
+ untracked: number;
4695
+ };
4696
+ /** Ahead/behind vs the base ref the integration check compares against
4697
+ * (the repo's default branch, e.g. `origin/main`). `null` for a detached
4698
+ * tip or when git couldn't resolve the base. */
4699
+ base: null | {
4700
+ ref: string;
4701
+ ahead: number;
4702
+ behind: number;
4703
+ };
4704
+ /** Integration state; `number` whenever the forge matched a PR (open,
4705
+ * merged, partial), `url` when the host knows the forge's web URL. */
3856
4706
  pr: null | {
3857
4707
  state: string;
3858
4708
  number?: number;
4709
+ url?: string;
3859
4710
  };
3860
4711
  sessions: Array<{
3861
4712
  id: string;
@@ -3869,13 +4720,24 @@ interface WorktreeStatusView {
3869
4720
  sessionCount: number;
3870
4721
  };
3871
4722
  }
4723
+ /** Narrows a lister call. `paths` computes only those worktrees (compared
4724
+ * resolved) instead of every worktree of the repo: one forge round-trip for
4725
+ * a per-session lookup, not one per worktree. */
4726
+ interface WorktreeStatusListOptions {
4727
+ paths?: readonly string[];
4728
+ }
3872
4729
  /** Injected port: the runtime asks the host to list worktrees for a repo. */
3873
- type WorktreeStatusLister = (repoRoot: string) => Promise<WorktreeStatusView[]>;
4730
+ type WorktreeStatusLister = (repoRoot: string, options?: WorktreeStatusListOptions) => Promise<WorktreeStatusView[]>;
4731
+ interface ToWorktreeStatusViewOptions {
4732
+ /** Web URL of PR `number` on this repo's forge, when the host can build
4733
+ * one (e.g. from a GitHub `origin` remote). */
4734
+ prUrl?: (number: number) => string | undefined;
4735
+ }
3874
4736
  /**
3875
4737
  * Pure projection from a raw `WorktreeStatusEntry` to the view the daemon
3876
4738
  * surfaces. Kept in one place so the MCP tool and HTTP route cannot drift.
3877
4739
  */
3878
- declare function toWorktreeStatusView(entry: unknown): WorktreeStatusView;
4740
+ declare function toWorktreeStatusView(entry: unknown, options?: ToWorktreeStatusViewOptions): WorktreeStatusView;
3879
4741
 
3880
4742
  /**
3881
4743
  * Shared kernel for the daemon's worktree-`gc` surface — the transport twin of
@@ -3910,7 +4772,7 @@ type WorktreeGcClass = "reclaim" | "salvage" | "hold";
3910
4772
  * registration is still there, but the working directory is gone (see
3911
4773
  * `WorktreeGcPlanEntryView.prunable`).
3912
4774
  */
3913
- type WorktreeGcReclaimReason = "dep-bump" | "orphan" | "prunable";
4775
+ type WorktreeGcReclaimReason = "dep-bump" | "orphan" | "prunable" | "squash-merged" | "patch-merged" | "content-merged";
3914
4776
  /**
3915
4777
  * One entry of the dry-run plan — a runtime-local projection of a
3916
4778
  * `GcPlanEntry`. `tree` / `integration` / `liveness` are flattened to their
@@ -4002,6 +4864,13 @@ interface WorktreeGcRunInput {
4002
4864
  * command) omits it and gets exactly today's behavior.
4003
4865
  */
4004
4866
  protectedPaths?: string[];
4867
+ /**
4868
+ * Worktree-relative paths whose dirt is known noise: a worktree dirty ONLY
4869
+ * on these reads clean (and is removed non-force after they are restored).
4870
+ * Omitted → the engine's default (`.opencode/package-lock.json`); `[]`
4871
+ * disables the allowlist.
4872
+ */
4873
+ noisePaths?: string[];
4005
4874
  }
4006
4875
  /**
4007
4876
  * Injected port: the runtime asks the host to plan (and, when `apply`, execute)
@@ -4011,6 +4880,159 @@ interface WorktreeGcRunInput {
4011
4880
  */
4012
4881
  type WorktreeGcRunner = (input: WorktreeGcRunInput) => Promise<WorktreeGcResult>;
4013
4882
 
4883
+ /**
4884
+ * Shared kernel for the daemon's branch-`gc` surface — the sibling of
4885
+ * `worktree-gc.ts`. The engine (`planBranchGc` / `applyBranchGc` /
4886
+ * `recordBranchVerdict`) lives in `@agentproto/worktree`, a dependency the
4887
+ * runtime deliberately does NOT take, so — exactly as the worktree gc surface
4888
+ * does — the runtime only defines runtime-local structural result types and
4889
+ * the injected ports. The CLI host wires the real runners. Repo-root
4890
+ * resolution is shared with the worktree surfaces via
4891
+ * `resolveWorktreeQueryRoot` in `worktree-status.ts`.
4892
+ */
4893
+ type BranchGcKind = "local" | "remote" | "orphan";
4894
+ type BranchGcClass = "reclaim" | "review" | "hold";
4895
+ type BranchGcStatus = "pr-merged" | "merged" | "squash-merged" | "patch-merged" | "content-merged" | "unmerged";
4896
+ type BranchGcReclaimReason = Exclude<BranchGcStatus, "unmerged"> | "reviewed";
4897
+ type BranchGcHoldReason = "protected" | "worktree" | "open-pr" | "pr-check-unavailable" | "young";
4898
+ type BranchGcTriageVerdict = "obsolete" | "superseded" | "salvage" | "in-progress" | "unclear";
4899
+ interface BranchGcCoverageView {
4900
+ residual: number;
4901
+ covered: number;
4902
+ ignored: number;
4903
+ evolved: number;
4904
+ unique: number;
4905
+ deletes: number;
4906
+ }
4907
+ /** One classified ref — mirrors `BranchGcPlanEntry` structurally. */
4908
+ interface BranchGcPlanEntryView {
4909
+ kind: BranchGcKind;
4910
+ name: string;
4911
+ ref: string;
4912
+ sha: string;
4913
+ date: string;
4914
+ author: string;
4915
+ subject: string;
4916
+ remote?: string;
4917
+ status: BranchGcStatus;
4918
+ history: "current" | "pre-rewrite" | "unrelated";
4919
+ ahead: number | null;
4920
+ behind: number | null;
4921
+ conflicts?: boolean;
4922
+ compareBase?: string;
4923
+ mergeBase?: string;
4924
+ mergedTree?: string | null;
4925
+ coverage?: BranchGcCoverageView;
4926
+ residualFiles?: string[];
4927
+ residualFileCount?: number;
4928
+ ageDays: number;
4929
+ class: BranchGcClass;
4930
+ reclaimReason?: BranchGcReclaimReason;
4931
+ mergedPr?: number;
4932
+ holdReason?: BranchGcHoldReason;
4933
+ holdDetail?: string;
4934
+ pushed?: string;
4935
+ verdict?: {
4936
+ triage: BranchGcTriageVerdict;
4937
+ agree: boolean | null;
4938
+ reviewer: string;
4939
+ };
4940
+ }
4941
+ interface BranchGcPlanView {
4942
+ repoRoot: string;
4943
+ repoName: string;
4944
+ base: string;
4945
+ baseSha: string;
4946
+ baseTree: string;
4947
+ remote: string | null;
4948
+ anchor: string | null;
4949
+ prCheck: {
4950
+ available: boolean;
4951
+ reason?: string;
4952
+ };
4953
+ scopes: BranchGcKind[];
4954
+ minAgeDays: number;
4955
+ includeReviewed: boolean;
4956
+ generatedAt: string;
4957
+ otherRemoteRefs: number;
4958
+ entries: BranchGcPlanEntryView[];
4959
+ }
4960
+ /** kind → class → count, and kind → (status | "protected") → count. */
4961
+ interface BranchGcSummaryView {
4962
+ byClass: Record<BranchGcKind, Record<BranchGcClass, number>>;
4963
+ byStatus: Record<BranchGcKind, Record<string, number>>;
4964
+ }
4965
+ interface BranchGcOutcomeView {
4966
+ kind: BranchGcKind;
4967
+ name: string;
4968
+ sha: string;
4969
+ result: "deleted" | "held" | "skipped-review" | "aborted-moved" | "aborted-vanished" | "aborted-reclassified" | "failed";
4970
+ reclaimReason?: BranchGcReclaimReason;
4971
+ holdReason?: BranchGcHoldReason;
4972
+ currentSha?: string;
4973
+ from?: BranchGcClass;
4974
+ to?: BranchGcClass;
4975
+ message?: string;
4976
+ }
4977
+ type BranchGcResult = {
4978
+ mode: "plan";
4979
+ plan: BranchGcPlanView;
4980
+ summary: BranchGcSummaryView;
4981
+ } | {
4982
+ mode: "apply";
4983
+ plan: BranchGcPlanView;
4984
+ summary: BranchGcSummaryView;
4985
+ outcomes: BranchGcOutcomeView[];
4986
+ /** Restore log written before the first delete; `null` when nothing was deleted. */
4987
+ restoreLog: string | null;
4988
+ };
4989
+ /** Input to the injected runner. `apply` defaults to false at the tool/route
4990
+ * boundary — a bare call is a dry run — and an apply requires `scopes`. */
4991
+ interface BranchGcRunInput {
4992
+ repoRoot: string;
4993
+ apply: boolean;
4994
+ base?: string;
4995
+ scopes?: BranchGcKind[];
4996
+ minAgeDays?: number;
4997
+ includeReviewed: boolean;
4998
+ anchor?: string;
4999
+ }
5000
+ type BranchGcRunner = (input: BranchGcRunInput) => Promise<BranchGcResult>;
5001
+ interface BranchGcVerdictInput {
5002
+ repoRoot: string;
5003
+ /** Validated by the engine (`branchVerdictSchema`) — the runtime only shapes it. */
5004
+ verdict: unknown;
5005
+ }
5006
+ interface BranchGcVerdictRecordView {
5007
+ repo: string;
5008
+ name: string;
5009
+ sha: string;
5010
+ recordedAt: string;
5011
+ triage: {
5012
+ verdict: BranchGcTriageVerdict;
5013
+ confidence: number;
5014
+ reason: string;
5015
+ salvage?: string;
5016
+ };
5017
+ gate?: {
5018
+ agree: boolean;
5019
+ verdict: BranchGcTriageVerdict;
5020
+ reason: string;
5021
+ evidence: string[];
5022
+ };
5023
+ reviewer: string;
5024
+ }
5025
+ /** Injected port: validate + store one reviewer verdict, keyed by repo + tip sha. Throws on an invalid verdict. */
5026
+ type BranchGcVerdictRecorder = (input: BranchGcVerdictInput) => Promise<BranchGcVerdictRecordView>;
5027
+ interface BranchGcVerdictLookupInput {
5028
+ repoRoot: string;
5029
+ /** Full tip sha — the store's key, together with the repo. */
5030
+ sha: string;
5031
+ }
5032
+ /** Injected port: the stored verdict for this exact tip, or `null` — a read-only
5033
+ * lookup, so a caller can check one tip without re-running a whole `branch_gc` plan. */
5034
+ type BranchGcVerdictReader = (input: BranchGcVerdictLookupInput) => Promise<BranchGcVerdictRecordView | null>;
5035
+
4014
5036
  /**
4015
5037
  * Pluggable adapter resolver — keeps the runtime package free of any
4016
5038
  * @agentproto/cli dep. The host (cli `serve`, playground, embedding
@@ -4319,6 +5341,142 @@ interface AuthOptions {
4319
5341
  token?: string;
4320
5342
  }
4321
5343
 
5344
+ /** One background task as mirrored onto `SessionDescriptor.backgroundTasks`. */
5345
+ interface SessionBackgroundTask {
5346
+ taskId: string;
5347
+ /** Friendly task type (`"shell"`, `"monitor"`, ...), when the agent says. */
5348
+ taskKind?: string;
5349
+ description?: string;
5350
+ /** Where the task writes its output, when the agent says. */
5351
+ outputFile?: string;
5352
+ status: "running" | "paused" | "completed" | "failed" | "stopped";
5353
+ summary?: string;
5354
+ toolCallId?: string;
5355
+ /** ISO 8601 — when the daemon first heard of the task. */
5356
+ startedAt: string;
5357
+ }
5358
+
5359
+ /**
5360
+ * Typed inter-session message envelope (AIP-46 §Session messages).
5361
+ *
5362
+ * A PROMPT is an instruction from whoever drives a session; a MESSAGE is a
5363
+ * report between sessions in the same tree whose sender the DAEMON attests.
5364
+ * Everything in `from` is computed here from the caller's verified identity
5365
+ * and the session forest — a send request can never set it.
5366
+ *
5367
+ * Pure module: no registry, no I/O. The registry (sessions.ts) carries a
5368
+ * `SessionMessage` on a queued prompt (`QueuedPrompt.envelope`), renders it
5369
+ * into the model's context with `renderSessionMessages`, and records it as
5370
+ * its own `session-message` transcript record — never a `user-prompt`.
5371
+ */
5372
+ /** Sender's position in the tree, relative to the recipient. */
5373
+ type MessageRelation = "child" | "parent" | "sibling" | "human" | "system";
5374
+ type MessageKind = "report" | "question" | "blocker" | "done" | "notice";
5375
+ type MessageUrgency = "fyi" | "next-turn" | "steer" | "interrupt";
5376
+ type MessageDeliveryVia = "wait" | "steer" | "turn" | "interrupt" | "inbox";
5377
+ declare const MESSAGE_KINDS: readonly MessageKind[];
5378
+ declare const MESSAGE_URGENCIES: readonly MessageUrgency[];
5379
+ interface SessionMessageFrom {
5380
+ /** Absent only for relation `human` | `system`. */
5381
+ sessionId?: string;
5382
+ /** Registry label at send time — display only, never identity. */
5383
+ label?: string;
5384
+ role?: string;
5385
+ adapter?: string;
5386
+ relation: MessageRelation;
5387
+ }
5388
+ interface SessionMessage {
5389
+ /** `msg_<uuid8>`, daemon-minted. */
5390
+ id: string;
5391
+ ts: string;
5392
+ /** Recipient sessionId. */
5393
+ to: string;
5394
+ from: SessionMessageFrom;
5395
+ kind: MessageKind;
5396
+ /** Requested urgency. */
5397
+ urgency: MessageUrgency;
5398
+ /** Stamped on delivery. */
5399
+ delivered?: {
5400
+ via: MessageDeliveryVia;
5401
+ at: string;
5402
+ turnSeq?: number;
5403
+ };
5404
+ /** Thread id; defaults to the first message's id. */
5405
+ correlationId?: string;
5406
+ /** A `SessionMessage.id` this answers. */
5407
+ replyTo?: string;
5408
+ text: string;
5409
+ /** Optional structured payload (≤ `MAX_MESSAGE_DATA_BYTES`). */
5410
+ data?: Record<string, unknown>;
5411
+ ackedAt?: string;
5412
+ }
5413
+ declare const MAX_MESSAGE_DATA_BYTES: number;
5414
+ /** The slice of a session descriptor relation/ACL resolution needs. */
5415
+ interface MessageTreeNode {
5416
+ id: string;
5417
+ parentSessionId?: string;
5418
+ label?: string;
5419
+ role?: string;
5420
+ adapterSlug?: string;
5421
+ }
5422
+ /**
5423
+ * Relation of `sender` to `recipient` in the session forest, or `undefined`
5424
+ * when they're not tree neighbours (cousins, strangers, self). `sender`
5425
+ * absent ⇒ `human` (an HTTP/CLI operator); daemon-originated notices pass
5426
+ * `"system"` explicitly instead of calling this.
5427
+ */
5428
+ declare function resolveRelation(sender: MessageTreeNode | undefined, recipient: MessageTreeNode): Exclude<MessageRelation, "system"> | undefined;
5429
+ /**
5430
+ * ACL: child→parent, parent→direct child, human/system→any. Sibling↔sibling
5431
+ * only when `allowSiblings` (config-gated, default off). Anything else — a
5432
+ * cousin, a stranger, yourself — is refused.
5433
+ */
5434
+ declare function isMessageAllowed(relation: MessageRelation | undefined, opts?: {
5435
+ allowSiblings?: boolean;
5436
+ }): relation is MessageRelation;
5437
+ /** Build a `from` block from the verified sender + the resolved relation. */
5438
+ declare function messageFrom(sender: MessageTreeNode | undefined, relation: MessageRelation): SessionMessageFrom;
5439
+ declare function mintMessageId(): string;
5440
+ declare function createSessionMessage(input: {
5441
+ to: string;
5442
+ from: SessionMessageFrom;
5443
+ text: string;
5444
+ kind?: MessageKind;
5445
+ urgency?: MessageUrgency;
5446
+ replyTo?: string;
5447
+ correlationId?: string;
5448
+ data?: Record<string, unknown>;
5449
+ }): SessionMessage;
5450
+ interface MessageFilter {
5451
+ /** Sender session ids, or `"children"` for any message whose relation is
5452
+ * `child` (i.e. from one of the recipient's own children). */
5453
+ from?: readonly string[] | "children";
5454
+ kind?: readonly MessageKind[];
5455
+ correlationId?: string;
5456
+ }
5457
+ declare function matchesMessageFilter(msg: SessionMessage, filter: MessageFilter): boolean;
5458
+ declare const MESSAGE_TAG = "agentproto-message";
5459
+ /** Child-settable label → attribute-safe: `[A-Za-z0-9._-]`, ≤ 48 chars. */
5460
+ declare function sanitizeLabel(label: string | undefined): string | undefined;
5461
+ /** Escape every envelope/body sentinel inside a message body, so a sender
5462
+ * can't close the tag early and forge a second header. */
5463
+ declare function escapeMessageBody(text: string): string;
5464
+ /** A human prompt is NOT wrapped (humans keep today's UX), but a line that
5465
+ * starts with the envelope sentinel is escaped, so the invariant "text
5466
+ * outside an `<agentproto-message>` tag is the human" holds. Nothing else
5467
+ * in human text changes. */
5468
+ declare function escapeHumanPrompt(text: string): string;
5469
+ declare function renderSessionMessage(msg: SessionMessage): string;
5470
+ /** A coalesced batch → sibling tags, one per message, in order. */
5471
+ declare function renderSessionMessages(msgs: readonly SessionMessage[]): string;
5472
+ /** One-line typed digest of `fyi` messages that never woke the session —
5473
+ * prepended (as a `system-prompt` slice) to the next turn it runs anyway. */
5474
+ declare function renderInboxDigest(msgs: readonly SessionMessage[]): string;
5475
+ /** Taught once, the first time a session RECEIVES a message (recorded as a
5476
+ * `system-prompt`). Children also get the same invariant in their lineage
5477
+ * line at spawn. */
5478
+ declare const MESSAGE_PREAMBLE: string;
5479
+
4322
5480
  /**
4323
5481
  * A per-session observer — the pluggable seam behind the runtime's transcript
4324
5482
  * tap. `sessions.ts` drives one observer per turn (prompt in, each stream event,
@@ -4330,15 +5488,33 @@ interface AuthOptions {
4330
5488
  * calls, so `TranscriptWriter` IS a `SessionObserver` (see transcript-writer.ts).
4331
5489
  */
4332
5490
 
5491
+ /** The sender-side trace of a typed message (`session-message-sent`). */
5492
+ interface SessionMessageSentRecord {
5493
+ messageId: string;
5494
+ to: string;
5495
+ kind: MessageKind;
5496
+ urgency: MessageUrgency;
5497
+ }
4333
5498
  interface SessionObserver {
4334
5499
  /** Record the outgoing message that opens a new turn. `opts.source`, when
4335
5500
  * set, is the prompt's provenance — `agent:<sessionId>` for a prompt
4336
5501
  * injected by another session (`agent_prompt` from a supervisor), absent
4337
5502
  * for a human operator — so a transcript view can attribute the turn to
4338
- * its real author instead of "you". */
5503
+ * its real author instead of "you". `opts.messages`, when set, marks a
5504
+ * typed-message turn: the writer records one `session-message` per
5505
+ * message instead of a `user-prompt`. */
4339
5506
  recordPrompt(sessionId: string, message: unknown, opts?: {
4340
5507
  source?: string;
5508
+ system?: string;
5509
+ messages?: readonly SessionMessage[];
4341
5510
  }): void;
5511
+ /** Record, in the SENDER's transcript, that it sent a typed message.
5512
+ * Optional — observers without a transcript of their own skip it. */
5513
+ recordSessionMessageSent?(sessionId: string, record: SessionMessageSentRecord): void;
5514
+ /** Record a typed message delivered into `sessionId`'s context OUTSIDE a
5515
+ * turn prompt (e.g. as an `inbox_wait` result) — a standalone
5516
+ * `session-message` record. Optional, like `recordSessionMessageSent`. */
5517
+ recordSessionMessage?(sessionId: string, message: SessionMessage): void;
4342
5518
  /** Record one structured stream event (text-delta, tool-call, usage_update, …). */
4343
5519
  recordEvent(sessionId: string, evt: AgentStreamEvent): void;
4344
5520
  /** Record the durable turn-boundary / exit usage snapshot. */
@@ -4361,6 +5537,106 @@ interface SessionObserver {
4361
5537
  */
4362
5538
  declare function composeSessionObservers(observers: readonly SessionObserver[]): SessionObserver;
4363
5539
 
5540
+ /** Where a session's `contextSize` came from — see the module doc. */
5541
+ type ContextSizeSource = "adapter" | "catalog" | "reported";
5542
+
5543
+ /**
5544
+ * Derived session outcome (Level 1) — what an ended agent-cli session
5545
+ * PRODUCED, recorded by the daemon with zero agent cooperation.
5546
+ *
5547
+ * Two axes, never conflated:
5548
+ * - termination — HOW the session stopped (the descriptor's existing
5549
+ * `status` + `endedReason`), copied verbatim into `outcome.termination`;
5550
+ * - outcome — WHAT it produced: its last assistant message, the PRs it
5551
+ * opened, what it cost, and which run / parent it belongs to.
5552
+ *
5553
+ * So a killed or idle-reaped session still shows what it said and did,
5554
+ * instead of a bare "killed".
5555
+ *
5556
+ * Pure by construction (plus one bounded sync tail-read helper for boot
5557
+ * reconcile): no LLM call, no full transcript scan. The registry calls
5558
+ * {@link deriveSessionOutcome} from its single exit funnel (`emitExited`)
5559
+ * and from boot reconcile (`loadHistorySnapshot`).
5560
+ */
5561
+
5562
+ interface SessionOutcomeArtifact {
5563
+ type: "pr" | "commit" | "url";
5564
+ /** The artifact's canonical reference — a PR url, a commit sha, a url. */
5565
+ ref: string;
5566
+ title?: string;
5567
+ }
5568
+ interface SessionOutcomeLink {
5569
+ /** `run` — the workflow run this session was a step of (`ref` = run id,
5570
+ * `title` = `<workflowId>/<stepId>`); `parent` — the orchestrator session
5571
+ * that spawned it; `review` — reserved for the review ledger (Level 2). */
5572
+ rel: "run" | "parent" | "review";
5573
+ ref: string;
5574
+ title?: string;
5575
+ }
5576
+ interface SessionOutcome {
5577
+ /** Level 1 only ever writes `"derived"`; a declared outcome (Level 2) is a
5578
+ * later addition. */
5579
+ source: "derived";
5580
+ /** `produced` — the session said something or left an artifact;
5581
+ * `empty` — no assistant text and no artifacts. */
5582
+ status: "produced" | "empty";
5583
+ /** Last assistant message of the session, trimmed to
5584
+ * {@link OUTCOME_SUMMARY_MAX} chars (the TAIL is kept — the conclusion,
5585
+ * not the preamble). Absent when the session never said anything. */
5586
+ summary?: string;
5587
+ /** Copy of the descriptor's end state at the moment the outcome was
5588
+ * recorded — the termination axis, never mixed into `status`. */
5589
+ termination: {
5590
+ status: string;
5591
+ /** Copy of `SessionDescriptor.endedReason` — see {@link SessionEndReason}
5592
+ * for the full, single-source enum of values (operator kill, cost cap,
5593
+ * provider limit, …). Absent for a plain natural exit or an ordinary
5594
+ * turn error this file doesn't tag. A value outside the known enum is
5595
+ * still valid (a newer/older daemon) — never gate rendering on it being
5596
+ * a member; see `isKnownSessionEndReason`. */
5597
+ reason?: SessionEndReason;
5598
+ /** The reason this row carried BEFORE an operator's "mark as completed"
5599
+ * (`kill(id, signal, "operator-completed")` on an already-terminal
5600
+ * session — the UI's affordance for relabeling a finished-but-not-
5601
+ * operator-tagged row) overwrote `reason` with `"operator-completed"`.
5602
+ * Set ONLY by that relabel, so the true original mechanism (a natural
5603
+ * exit with no reason, `"crashed"`, `"idle-reaped"`, …) is never lost
5604
+ * under the operator's override. Absent otherwise. */
5605
+ previousReason?: SessionEndReason;
5606
+ exitCode?: number;
5607
+ /** True when the session was killed with a turn in flight. */
5608
+ midTurn?: boolean;
5609
+ };
5610
+ cost?: {
5611
+ usd?: number;
5612
+ tokensIn?: number;
5613
+ tokensOut?: number;
5614
+ durationMs?: number;
5615
+ };
5616
+ artifacts?: SessionOutcomeArtifact[];
5617
+ links?: SessionOutcomeLink[];
5618
+ recordedAt: string;
5619
+ }
5620
+ /** The compact list projection of an outcome (`session_list` default). */
5621
+ interface SessionOutcomeCompact {
5622
+ status: SessionOutcome["status"];
5623
+ summary?: string;
5624
+ }
5625
+ interface DeriveSessionOutcomeInput {
5626
+ /** The last assistant message the registry observed (raw, untrimmed). */
5627
+ lastAssistantText?: string;
5628
+ now?: Date;
5629
+ }
5630
+ /**
5631
+ * Build the derived outcome for a session that has just reached a terminal
5632
+ * status. Reads only the descriptor + the caller-supplied last assistant
5633
+ * text; never touches disk.
5634
+ */
5635
+ declare function deriveSessionOutcome(desc: SessionDescriptor, input?: DeriveSessionOutcomeInput): SessionOutcome;
5636
+ /** The compact projection: status + the first
5637
+ * {@link OUTCOME_COMPACT_SUMMARY_MAX} chars of the summary. */
5638
+ declare function compactOutcome(o: SessionOutcome | undefined): SessionOutcomeCompact | undefined;
5639
+
4364
5640
  /**
4365
5641
  * Minimal shape we need from a driver-agent-cli session — kept as a
4366
5642
  * structural type so the runtime package doesn't take a hard
@@ -4384,6 +5660,12 @@ interface AgentSessionLike {
4384
5660
  * drivers that don't model held permissions (sandbox proxy, future
4385
5661
  * transports). */
4386
5662
  respondPermission?(requestId: string, resolution: AcpPermissionResolution): boolean | Promise<boolean>;
5663
+ /** Subscribe to events the agent emits while no turn is in flight — its
5664
+ * own wake-up when a background task settles, and the task lifecycle
5665
+ * itself. Mirrors `@agentproto/driver-agent-cli`'s
5666
+ * `AgentCliRuntimeSession.onOutOfTurnEvent`. Returns an unsubscribe
5667
+ * function. Absent for drivers that can't produce such events. */
5668
+ onOutOfTurnEvent?(listener: (event: AgentStreamEvent) => void): () => void;
4387
5669
  /**
4388
5670
  * Switch the active model on this LIVE session — mirrors
4389
5671
  * `@agentproto/driver-agent-cli`'s `AgentCliRuntimeSession.setModel`
@@ -4420,6 +5702,15 @@ interface AgentSessionLike {
4420
5702
  * routes to restart.
4421
5703
  */
4422
5704
  readonly availableModes?: readonly SessionMode[];
5705
+ /** Whether the agent accepts steering — mirrors
5706
+ * `@agentproto/driver-agent-cli`'s `AgentCliRuntimeSession.steeringSupported`
5707
+ * (ACP `_session/steering`, advertised at initialize). */
5708
+ readonly steeringSupported?: boolean;
5709
+ /** Inject content into the turn in flight — mirrors
5710
+ * `AgentCliRuntimeSession.steer`: `"steered"` (injected), `"promptRequired"`
5711
+ * (no host turn in flight — deliver as a normal prompt), `"unsupported"`.
5712
+ * Never throws. Absent for sessions that can't steer. */
5713
+ steer?(content: unknown): Promise<"steered" | "promptRequired" | "unsupported">;
4423
5714
  close(): Promise<void>;
4424
5715
  }
4425
5716
  /** Result of `SessionsRegistry.setModel` — see that method's doc comment. */
@@ -4586,6 +5877,16 @@ interface AgentStreamEvent {
4586
5877
  * but no `cost`). */
4587
5878
  tokensIn?: number;
4588
5879
  tokensOut?: number;
5880
+ /** "usage_update" model the usage belongs to, when the adapter reports it
5881
+ * (claude-agent-acp's `_meta["_claude/model"]`) — see `context-window.ts`. */
5882
+ model?: string;
5883
+ /** "usage_update" `size` is the adapter's own guess, corrected later by a
5884
+ * cost-bearing frame (claude-agent-acp) — see `context-window.ts`. */
5885
+ sizeInferred?: boolean;
5886
+ /** "usage_update" size as the adapter reported it, recorded only when the
5887
+ * daemon corrected `size` (an inferred window superseded by the catalog or
5888
+ * an earlier authoritative frame) — see `context-window.ts`. */
5889
+ reportedSize?: number;
4589
5890
  /** "permission-resolved" outcome for the "agent-prompt" it answers (same
4590
5891
  * `toolCallId`) — mirrors `session:permission-resolved`'s `decision` so
4591
5892
  * the durable transcript can tell an answered ask from a still-pending
@@ -4612,6 +5913,24 @@ interface AgentStreamEvent {
4612
5913
  qualifiedName?: string;
4613
5914
  };
4614
5915
  }>;
5916
+ /** "background-task" lifecycle edge — see @agentproto/acp's `StreamEvent`'s
5917
+ * `background-task` kind. */
5918
+ phase?: "started" | "updated" | "settled";
5919
+ /** "background-task" task snapshot — see @agentproto/acp's
5920
+ * `BackgroundTaskInfo`. Only `taskId` is guaranteed. */
5921
+ task?: {
5922
+ taskId: string;
5923
+ taskKind?: string;
5924
+ description?: string;
5925
+ outputFile?: string;
5926
+ status?: SessionBackgroundTask["status"];
5927
+ summary?: string;
5928
+ toolCallId?: string;
5929
+ };
5930
+ /** "usage_update" origin of the cycle it closes (claude-agent-acp's
5931
+ * `_meta["_claude/origin"].kind`, e.g. `"task-notification"` for an
5932
+ * autonomous wake) — see @agentproto/acp's `StreamEvent`. */
5933
+ origin?: string;
4615
5934
  }
4616
5935
  /**
4617
5936
  * Env vars the registry injects into every process it spawns on a session's
@@ -4732,6 +6051,13 @@ interface QueuedPrompt {
4732
6051
  * daemon restart. Absent for legacy queued items — `promptOriginLabel`
4733
6052
  * falls back to `source`, then `"user"`. */
4734
6053
  origin?: string;
6054
+ /** Set when this item is a typed inter-session message (`message_parent`,
6055
+ * …) rather than a prompt: the daemon-attested envelope. `message` then
6056
+ * only carries `envelope.text` (for `previewPrompt`); the turn is built
6057
+ * from the envelope — rendered as an `<agentproto-message>` tag, recorded
6058
+ * as a `session-message` transcript record, and coalesced with any
6059
+ * envelope items queued right behind it (never with a plain prompt). */
6060
+ envelope?: SessionMessage;
4735
6061
  }
4736
6062
  /** What `enqueuePrompt` resolved to — lets a caller (e.g. MCP `agent_prompt`)
4737
6063
  * tell the user whether the prompt was delivered/started now or PARKED behind
@@ -4743,6 +6069,22 @@ interface QueuedPrompt {
4743
6069
  interface EnqueuePromptResult {
4744
6070
  queued: boolean;
4745
6071
  }
6072
+ /** What `sendMessage` did with a message — truthful, per AIP-46. */
6073
+ interface SendMessageResult {
6074
+ messageId: string;
6075
+ /** Set when the message already reached the recipient's context (a
6076
+ * waiter, a dispatched turn, an interrupt) or was parked as `fyi`
6077
+ * (`via: "inbox"`); `null` while it's queued behind a busy turn. */
6078
+ delivered: {
6079
+ via: MessageDeliveryVia;
6080
+ } | null;
6081
+ queued: boolean;
6082
+ urgencyApplied: MessageUrgency;
6083
+ }
6084
+ interface WaitForMessagesResult {
6085
+ messages: SessionMessage[];
6086
+ timedOut: boolean;
6087
+ }
4746
6088
  /** One entry in the after-the-fact queue listing (`listQueuedPrompts` /
4747
6089
  * `session_queue_list` / `GET /sessions/:id/queue`). `position` is the
4748
6090
  * array index (0 = next to dispatch). */
@@ -4756,6 +6098,17 @@ interface QueuedPromptView {
4756
6098
  queuedAt: string;
4757
6099
  position: number;
4758
6100
  }
6101
+ /** Provenance stamped onto a `session_continue_fresh` target by
6102
+ * {@link SessionDescriptor.handoff} — which harness the checkpoint moved
6103
+ * from/to and when. `fromHarness === toHarness` for a same-harness
6104
+ * continuation; they differ for a genuine cross-harness handoff
6105
+ * (claude-code -> opencode, etc). */
6106
+ interface SessionHandoff {
6107
+ fromHarness: string;
6108
+ toHarness: string;
6109
+ /** ISO 8601 timestamp the handoff spawn completed. */
6110
+ at: string;
6111
+ }
4759
6112
  interface SessionDescriptor {
4760
6113
  id: string;
4761
6114
  kind: SessionKind;
@@ -4778,30 +6131,36 @@ interface SessionDescriptor {
4778
6131
  startedAt: string;
4779
6132
  endedAt?: string;
4780
6133
  exitCode?: number;
4781
- /** Set alongside `status: "killed"` when the session ended NOT because an
4782
- * operator targeted it (`kill()`) but because of an AUTOMATIC teardown:
4783
- * - `"daemon-restart"` — the daemon process it lived in went away out from
4784
- * under it: a hard crash discovered at next boot
4785
- * (`loadHistorySnapshot`'s wasAlive reclassification), or a graceful
4786
- * shutdown/restart that force-kills whatever's still busy
4787
- * (`shutdownImpl`).
4788
- * - `"idle-reaped"` — the idle-session reaper (`reapIdle`, PR-6) retired a
4789
- * long-idle agent-cli row to free its adapter process. Deliberately kept
4790
- * lazy-resumable (adapterSessionId/cwd intact) so a later prompt revives
4791
- * it, but NEVER eager-resumed on boot — the eager pass (#638) gates on
4792
- * `endedReason === "daemon-restart"`, so an `"idle-reaped"` row is
4793
- * naturally excluded and a resume-storm of dead work is avoided.
4794
- * - `"crashed"` — the crash-detect sweep (`markCrashed`) found the
4795
- * adapter's OS process gone between turns, with no exit event ever
4796
- * emitted for it (agent-cli holds its child alive across turns, so a
4797
- * death outside a turn is otherwise silent until the next RPC throws).
4798
- * `status` is set to `"error"` (not `"killed"`) since nothing targeted
4799
- * it; see `lastError`/`crashedAt` for detail.
4800
- * Absent for every other terminal path (operator kill, natural exit, turn
4801
- * error) — the session's own fault, or at least not an automatic sweep's.
4802
- * Lets the UI show "crashed with the daemon" / "reaped while idle" instead
4803
- * of a bare "killed" that reads as deliberate. */
4804
- endedReason?: "daemon-restart" | "idle-reaped" | "crashed";
6134
+ /** WHY the session ended — every stable value + its meaning is declared
6135
+ * once, in {@link SessionEndReason} (`session-end-reason.ts`), so a UI can
6136
+ * render a label for each without chasing the call site that set it.
6137
+ * Three families:
6138
+ * - automatic daemon-lifecycle teardown (unchanged, pre-existing):
6139
+ * `"daemon-restart"` (the daemon process it lived in went away out from
6140
+ * under it — a hard crash discovered at next boot, or a graceful
6141
+ * shutdown/restart that force-kills whatever's still busy),
6142
+ * `"idle-reaped"` (the idle-session reaper retired a long-idle row —
6143
+ * deliberately kept lazy-resumable but NEVER eager-resumed on boot,
6144
+ * since the eager pass gates on `endedReason === "daemon-restart"`),
6145
+ * `"crashed"` (the crash-detect sweep found the adapter's OS process
6146
+ * gone between turns with no exit event ever emitted for it — `status`
6147
+ * is `"error"`, not `"killed"`, since nothing targeted it);
6148
+ * - operator-issued kill: `"operator-completed"` / `"operator-stopped"`,
6149
+ * set ONLY by an explicit `POST /sessions/:id/kill` or the `agent_kill`
6150
+ * MCP tool (never by an internal automatic teardown), so it reads as
6151
+ * deliberate rather than random;
6152
+ * - other internal/automatic reasons: `"cost-cap-exceeded"` (the
6153
+ * turn-granular `maxCostUsd` cap tripped), `"policy-cleanup"` (a
6154
+ * supervisor gate tore down its own ephemeral judge session),
6155
+ * `"parent-exited"` (an orchestrator's dying subtree reap),
6156
+ * `"provider-limit"` (a driver-reported subscription/usage-cap error,
6157
+ * e.g. Claude Code's "hit your session limit"), `"forgotten"` (a live
6158
+ * session killed as part of an operator `DELETE`).
6159
+ * Absent for every terminal path this file doesn't tag (a plain natural
6160
+ * exit, an ordinary turn error) — the session's own fault, or at least
6161
+ * not something worth a special label. A string outside this list is
6162
+ * still valid (a newer/older daemon) — see `isKnownSessionEndReason`. */
6163
+ endedReason?: SessionEndReason;
4805
6164
  /** Whether a turn was actually in flight the INSTANT `status` flipped to
4806
6165
  * "killed" — captured before anything else runs, because `busy` itself
4807
6166
  * cannot be trusted after the fact: `runAgentTurn`'s `finally` is what
@@ -4819,6 +6178,16 @@ interface SessionDescriptor {
4819
6178
  * `kill()`, `shutdownImpl`'s force-kill, and `loadHistorySnapshot`'s
4820
6179
  * wasAlive reclassification; absent for every other terminal path. */
4821
6180
  killedMidTurn?: boolean;
6181
+ /** What this session PRODUCED — the derived outcome (Level 1) the daemon
6182
+ * records once it reaches a terminal status, alongside (never instead of)
6183
+ * the termination fields above: last assistant message, opened PRs, cost,
6184
+ * run/parent links. agent-cli only. Recorded from the one exit funnel
6185
+ * (`emitExited`) and from boot reconcile for rows that died with the
6186
+ * daemon; first write wins unless a later one is for a new death or is
6187
+ * richer (see `shouldReplaceOutcome`). Dropped when an in-place resume
6188
+ * revives the row. Persisted, so it survives restarts and archiving. See
6189
+ * `session-outcome.ts`. */
6190
+ outcome?: SessionOutcome;
4822
6191
  /** Last time anything was written to stdout/stderr. Lets the UI
4823
6192
  * spot stuck sessions ("running for 2h, last output 12min ago"). */
4824
6193
  lastOutputAt?: string;
@@ -4926,10 +6295,23 @@ interface SessionDescriptor {
4926
6295
  * session sits `busy:false`, `awaitingInput:false`, background tasks
4927
6296
  * pending — a silent dead end. Detection + signal ONLY (same doctrine
4928
6297
  * as `stalledSinceMs`): nothing here re-prompts or wakes the session.
6298
+ * Not stamped for an agent that reports its background-task lifecycle
6299
+ * (see `backgroundTasks`) — that one is tracked and woken for real.
4929
6300
  * Cleared (deleted, never a stale value) on the next turn start and on
4930
6301
  * session exit. Emits `session:bg-tasks-parked` /
4931
6302
  * `session:bg-tasks-cleared`. */
4932
6303
  pendingBgTasks?: number;
6304
+ /** The agent's background tasks that are still running (a backgrounded
6305
+ * Bash command, a monitor, ...), as the agent itself reports them — over
6306
+ * ACP, the AIR `asyncTasks` lifecycle. Unlike `pendingBgTasks` (a
6307
+ * turn-end heuristic) this is the real set: a task is added when it
6308
+ * starts and removed when it settles, so an idle session with a
6309
+ * non-empty list is "waiting on N background tasks", not parked. When
6310
+ * one settles while the session is idle the registry makes sure the
6311
+ * agent is woken (see `SessionsRegistryOptions.backgroundTaskWake`).
6312
+ * Absent when none are running; dropped on exit and on respawn (a new
6313
+ * process owns none of the old one's tasks). */
6314
+ backgroundTasks?: SessionBackgroundTask[];
4933
6315
  /** ISO 8601 timestamp of the last turn that ended because the ADAPTER
4934
6316
  * ITSELF reported a failure — `runAgentTurn` observed a `turn-end` event
4935
6317
  * with `reason:"error"` (session-event-bus.ts's `SessionTurnEndEvent`,
@@ -5083,6 +6465,9 @@ interface SessionDescriptor {
5083
6465
  * field is. Absent (not `false`) for every descriptor from before this
5084
6466
  * field existed — treated the same as `false` everywhere it's read. */
5085
6467
  archived?: boolean;
6468
+ /** ISO-8601 instant `archiveSession` set `archived: true`; cleared by
6469
+ * `unarchiveSession`. Absent on rows archived before this field existed. */
6470
+ archivedAt?: string;
5086
6471
  /** When `true`, the idle-reaper (`isReapable`, `idle-reaper.ts`) never
5087
6472
  * retires this session regardless of how long it's sat idle. For a
5088
6473
  * supervisor that legitimately parks — waiting on a child, waiting on a
@@ -5137,6 +6522,12 @@ interface SessionDescriptor {
5137
6522
  * `worktreePath` without an id identifies a PATH, which a later worktree
5138
6523
  * may reuse; the pair identifies one specific worktree. */
5139
6524
  worktreeId?: string;
6525
+ /** Absolute path of the PRIMARY checkout `worktreePath` was cut from (the
6526
+ * repo root a `worktree_status` query targets), read at spawn from the
6527
+ * worktree admin dir's `commondir` file (`resolveWorktreeIdentity`).
6528
+ * Absent whenever `worktreePath` is, when `commondir` was unreadable, and
6529
+ * for every session persisted before this field existed. */
6530
+ mainRepoPath?: string;
5140
6531
  /** `true` only when `worktreePath` was provisioned by the `worktrees.isolation`
5141
6532
  * policy WITHOUT an explicit `worktree` request from the caller (see
5142
6533
  * `decideWorktreeIsolation`'s `WorktreeDecision.provision.implicit` in
@@ -5331,10 +6722,16 @@ interface SessionDescriptor {
5331
6722
  * usage_update events arrive, not just at turn-end. */
5332
6723
  contextSize?: number;
5333
6724
  contextUsed?: number;
6725
+ /** Where `contextSize` came from — `"adapter"` (a cost-bearing, authoritative
6726
+ * usage_update; sticky), `"catalog"` (model-catalog window for the model),
6727
+ * or `"reported"` (the adapter's own, possibly inferred, frame size). See
6728
+ * `context-window.ts` for the precedence. */
6729
+ contextSizeSource?: ContextSizeSource;
5334
6730
  /** Where `costUsd` came from — `"adapter"` (adapter's own reader or a
5335
6731
  * usage_update cost block), `"computed"` (tokens × in-repo catalog price),
5336
6732
  * `"no-pricing"` (tokens present but the model isn't in the catalog — cost
5337
- * deliberately left undefined), or `"none"`. Stamped at each turn-end. */
6733
+ * deliberately left undefined), or `"none"`. Stamped live on each
6734
+ * cost-bearing usage_update / reader read, and re-derived at turn-end. */
5338
6735
  usageSource?: UsageSource;
5339
6736
  /** Latest known `available_commands_update` payload (see @agentproto/acp's
5340
6737
  * `StreamEvent`'s `available-commands` kind) — the slash-commands/skills
@@ -5456,7 +6853,7 @@ interface SessionDescriptor {
5456
6853
  * next assistant `text-delta` (the model has the floor, so nothing is
5457
6854
  * pending), at turn start, and in the turn's finally. A claim about the
5458
6855
  * present tense — anything that disproves it must clear it. */
5459
- blockedOn?: "subagent" | "command";
6856
+ blockedOn?: "subagent" | "command" | "inbox";
5460
6857
  /** toolCallId of the tool-call that set `blockedOn`. A tool-result only
5461
6858
  * clears `blockedOn` when its toolCallId matches — so a nested or
5462
6859
  * interleaved tool finishing first can't clear the flag early. */
@@ -5472,9 +6869,9 @@ interface SessionDescriptor {
5472
6869
  * (`markCrashed` → `session:exited` with `status:"error"`/
5473
6870
  * `reason:"crashed"`), the supervisor-notify subscriber
5474
6871
  * (`supervisor-notify.ts`) delivers a `[child-crashed]` notice to its
5475
- * `parentSessionId`, if any — directly (enqueued prompt) when the parent
5476
- * is alive and idle, or stamped onto `pendingChildCrashNotices` and
5477
- * flushed at the parent's next turn when it's busy. The free external
6872
+ * `parentSessionId`, if any — dispatched now when the parent is alive
6873
+ * and idle, or parked in its `promptQueue` and drained as its own turn
6874
+ * when the parent's current turn ends. The free external
5478
6875
  * webhook path (`notifyUrl`) already fires regardless of this flag; this
5479
6876
  * only gates the additional IN-BAND signal into the parent's own
5480
6877
  * session, which a caller that isn't a delegating supervisor doesn't
@@ -5483,14 +6880,42 @@ interface SessionDescriptor {
5483
6880
  notifyParentOnCrash?: boolean;
5484
6881
  /** True when the session was spawned in permission-hold mode. */
5485
6882
  permissionHold?: boolean;
5486
- /** Queued `[child-crashed] …` notices from crashed children, stamped by
5487
- * the supervisor-notify subscriber when THIS session was busy at the
5488
- * time a child it should be told about crashed (mid-turn is never
5489
- * interrupted for this — see `notifyParentOnCrash`). Flushed and cleared
5490
- * by prepending the joined notices onto the next dispatched turn's
5491
- * message (`runAgentTurn`) — so delivery survives a busy parent without
5492
- * ever cancelling its in-flight work. Absent when nothing is queued. */
6883
+ /** Effective OS-level confinement of the adapter's own process
6884
+ * (`agent_start.commandSandbox`, else the workspace's
6885
+ * `.agentproto/command-sandbox.json` `adapterSpawn.mode`), resolved at
6886
+ * spawn with the same precedence the driver applies
6887
+ * (`wrapAgentCliSpawn`). Absent when neither engaged the axis (the spawn
6888
+ * ran unconfined without anyone choosing a mode), for a `sandbox` spawn
6889
+ * (confinement is the box's business), and for sessions persisted before
6890
+ * this field existed. Set at spawn time, immutable thereafter. */
6891
+ commandSandbox?: "off" | "workspace" | "strict";
6892
+ /** @deprecated Retired — child reports and `[child-crashed]` notices for a
6893
+ * busy session are now queued as their own `promptQueue` items
6894
+ * (`source:"child:<id>"`) instead of being string-prepended onto the next
6895
+ * prompt. Only read once, at boot, by `migratePendingChildNotices`, which
6896
+ * moves any notices a pre-upgrade daemon persisted into `promptQueue` and
6897
+ * deletes this field. Never written. */
5493
6898
  pendingChildCrashNotices?: string[];
6899
+ /** True once this session has been taught the `<agentproto-message>`
6900
+ * invariant (`MESSAGE_PREAMBLE`) — sent as a `system-prompt` slice ahead
6901
+ * of the FIRST message turn it receives, never again. */
6902
+ messagePreambleSent?: boolean;
6903
+ /** Durable, bounded inbox of typed messages NOT yet consumed (AIP-46
6904
+ * §Session messages): every accepted message lands here until it's
6905
+ * delivered into a turn (auto-acked), returned by `inbox_wait` with
6906
+ * `ack`, or acked via `inbox_ack`. `fyi` messages only ever live here
6907
+ * (surfaced as a digest). Capped at `INBOX_CAP`, oldest dropped first
6908
+ * (`[inbox] dropped`). Full history lives in the transcript, not here.
6909
+ * New array on every mutation, like `promptQueue`. */
6910
+ inbox?: SessionMessage[];
6911
+ /** What the live agent session can do, read from its driver at attach
6912
+ * time (spawn / resume) — shown in `session_list` so a sender can predict
6913
+ * a message's delivery tier. `steering`: the agent accepts ACP steering
6914
+ * (`_session/steering`) — a `steer` message can be injected into its
6915
+ * running turn instead of waiting for it to end. */
6916
+ capabilities?: {
6917
+ steering: boolean;
6918
+ };
5494
6919
  /** FIFO of prompts that arrived while this session was mid-turn and
5495
6920
  * asked to be QUEUED rather than rejected (`enqueuePrompt`'s
5496
6921
  * `opts.queue` arm — see its doc comment). Index 0 is next to
@@ -5505,8 +6930,7 @@ interface SessionDescriptor {
5505
6930
  * `sessionDescriptorsEqual` diff is a shallow `!==` per field, so an
5506
6931
  * in-place `.shift()` here would silently stop propagating queue
5507
6932
  * changes to the transcript panel. `[]` (not absent) once anything
5508
- * has ever been queued, matching `pendingChildCrashNotices`'s
5509
- * convention above. */
6933
+ * has ever been queued. */
5510
6934
  promptQueue?: QueuedPrompt[];
5511
6935
  /** Best-effort context-handoff digest (Fix D), stashed on a descriptor
5512
6936
  * whose resume degraded to a BLANK spawn — the adapter's own
@@ -5514,8 +6938,7 @@ interface SessionDescriptor {
5514
6938
  * false` — built by `buildResumeContextDigest` from the daemon's own
5515
6939
  * `events.jsonl` transcript, which survives both cases. Flushed and
5516
6940
  * cleared by prepending it onto the first dispatched turn's message
5517
- * (`runAgentTurn`), exactly once, same shape as
5518
- * `pendingChildCrashNotices` above. Gated strictly on the blank-fallback
6941
+ * (`runAgentTurn`), exactly once. Gated strictly on the blank-fallback
5519
6942
  * flags at the two sites that set it (`session-restart-core.ts`,
5520
6943
  * `maybeResumeAgent` here) — never set for a resume that actually
5521
6944
  * restored context, so a real continuation is never double-fed its own
@@ -5547,6 +6970,12 @@ interface SessionDescriptor {
5547
6970
  /** When this session was continued fresh into a new session, the target
5548
6971
  * session id. */
5549
6972
  continuedTo?: string;
6973
+ /** Set alongside `continuedFrom` on the NEW session — the harness the
6974
+ * checkpoint moved from/to and when. Present on every `continue_fresh`
6975
+ * spawn, not just a cross-harness one, so a same-harness continuation
6976
+ * and a genuine handoff are both traceable the same way. See
6977
+ * {@link SessionHandoff}. */
6978
+ handoff?: SessionHandoff;
5550
6979
  /** Source label — the channel/harness this session was spawned from
5551
6980
  * ("codex", "cowork", "vscode", "cron", …). Descriptor-only; groups the
5552
6981
  * session under a source node in the tree. */
@@ -5678,6 +7107,9 @@ interface SessionSummary {
5678
7107
  endedAt?: string;
5679
7108
  exitCode?: number;
5680
7109
  killedMidTurn?: boolean;
7110
+ /** Compact derived outcome (status + 120-char summary preview) — see
7111
+ * `SessionDescriptor.outcome`; the full record is on GET /sessions/:id. */
7112
+ outcome?: SessionOutcomeCompact;
5681
7113
  lastOutputAt?: string;
5682
7114
  lastActivityAt?: string;
5683
7115
  currentPhase?: SessionCurrentPhase;
@@ -5697,6 +7129,9 @@ interface SessionSummary {
5697
7129
  renamedByUser?: boolean;
5698
7130
  activitySummary?: SessionActivitySummary;
5699
7131
  archived?: boolean;
7132
+ /** ISO-8601 instant `archiveSession` set `archived: true`; cleared by
7133
+ * `unarchiveSession`. Absent on rows archived before this field existed. */
7134
+ archivedAt?: string;
5700
7135
  keepAlive?: boolean;
5701
7136
  pinned?: boolean;
5702
7137
  pty?: boolean;
@@ -5705,6 +7140,7 @@ interface SessionSummary {
5705
7140
  cwd?: string;
5706
7141
  worktreePath?: string;
5707
7142
  worktreeId?: string;
7143
+ mainRepoPath?: string;
5708
7144
  /** Resolved AGENTS.md path — see `SessionDescriptor.agentsMd`. */
5709
7145
  agentsMd?: string;
5710
7146
  /** Injection mode — see `SessionDescriptor.agentsMdMode`. */
@@ -5719,18 +7155,21 @@ interface SessionSummary {
5719
7155
  tokensOut?: number;
5720
7156
  contextSize?: number;
5721
7157
  contextUsed?: number;
7158
+ contextSizeSource?: ContextSizeSource;
5722
7159
  usageSource?: UsageSource;
5723
7160
  awaitingInput?: boolean;
5724
7161
  awaitingQuestion?: SessionAwaitingQuestion;
5725
7162
  awaitingPermission?: boolean;
5726
7163
  turnsCompleted?: number;
5727
7164
  busy?: boolean;
5728
- blockedOn?: "subagent" | "command";
7165
+ blockedOn?: "subagent" | "command" | "inbox";
5729
7166
  stalledSinceMs?: number;
5730
7167
  /** Parked-with-background-tasks marker — see
5731
7168
  * `SessionDescriptor.pendingBgTasks`. Stamped at turn-end, cleared on the
5732
7169
  * next turn start / exit. */
5733
7170
  pendingBgTasks?: number;
7171
+ /** Running background tasks — see `SessionDescriptor.backgroundTasks`. */
7172
+ backgroundTasks?: SessionBackgroundTask[];
5734
7173
  /** Adapter-reported turn-error marker — see
5735
7174
  * `SessionDescriptor.lastTurnErroredAt`. Stamped at turn-end, cleared on
5736
7175
  * the next turn that completes without one. */
@@ -5762,6 +7201,8 @@ interface SessionSummary {
5762
7201
  sandboxTeardown?: "kill" | "pause";
5763
7202
  sandboxPorts?: Record<number, string>;
5764
7203
  appServe?: SessionAppServeInfo;
7204
+ /** Effective adapter confinement — see `SessionDescriptor.commandSandbox`. */
7205
+ commandSandbox?: "off" | "workspace" | "strict";
5765
7206
  /** Read-time projection of the box's ledger liveness verdict — see
5766
7207
  * `SessionDescriptor.sandboxAlive`. */
5767
7208
  sandboxAlive?: boolean;
@@ -6096,6 +7537,13 @@ interface SessionsRegistry {
6096
7537
  queue?: boolean;
6097
7538
  force?: boolean;
6098
7539
  queueId?: string;
7540
+ /** A typed inter-session message (see `QueuedPrompt.envelope`). When
7541
+ * set, the turn is built from the envelope and `message` is ignored:
7542
+ * rendered as an `<agentproto-message>` tag, recorded as
7543
+ * `session-message` (never `user-prompt`), never matched as a
7544
+ * structured-question answer, and noted in the SENDER's transcript
7545
+ * as `session-message-sent`. */
7546
+ envelope?: SessionMessage;
6099
7547
  }): Promise<EnqueuePromptResult>;
6100
7548
  /** Cancel one not-yet-dispatched item in `SessionDescriptor.promptQueue`
6101
7549
  * by id — the composer's per-item "remove" action. Idempotent: an
@@ -6106,6 +7554,50 @@ interface SessionsRegistry {
6106
7554
  removeQueuedPrompt(id: string, queueId: string): {
6107
7555
  removed: boolean;
6108
7556
  };
7557
+ /** Deliver a daemon-attested typed message (`msg.to` is the recipient;
7558
+ * `from` must already be computed from the verified caller), routed by
7559
+ * `msg.urgency` (AIP-46 §Delivery tiers):
7560
+ * - a matching pending `waitForMessages` wins over every tier (waiter
7561
+ * first — returned as that call's result, never also injected);
7562
+ * - `fyi` — durable inbox only, no wake;
7563
+ * - idle recipient — any other tier starts its own turn now;
7564
+ * - busy + `next-turn` — queued behind the current turn;
7565
+ * - busy + `steer` — injected into the running turn when the daemon's
7566
+ * OWN prompt is in flight (never an autonomous cycle), the agent can
7567
+ * steer, and the per-recipient rate limit allows; else `next-turn`;
7568
+ * - busy + `interrupt` — cancels the turn and delivers — only when
7569
+ * `allowInterrupt` (the caller's grant); otherwise downgraded to
7570
+ * `steer`.
7571
+ * `urgencyApplied` reports the tier actually used. Throws when the
7572
+ * recipient is missing or not alive. */
7573
+ sendMessage(msg: SessionMessage, opts?: {
7574
+ source?: string;
7575
+ origin?: string;
7576
+ allowInterrupt?: boolean;
7577
+ }): Promise<SendMessageResult>;
7578
+ /** Block until a message matching `filter` is in `id`'s inbox (returns at
7579
+ * once when one already is), or `timeoutMs` elapses. Matched messages are
7580
+ * consumed: stamped `delivered.via:"wait"`, removed from the prompt queue
7581
+ * (so they're never also injected as a turn), and — when `ack` (default)
7582
+ * — removed from the inbox. Sets `blockedOn:"inbox"` while parked. */
7583
+ waitForMessages(id: string, filter: MessageFilter, opts: {
7584
+ timeoutMs: number;
7585
+ ack?: boolean;
7586
+ signal?: AbortSignal;
7587
+ }): Promise<WaitForMessagesResult>;
7588
+ /** Read `id`'s inbox (un-consumed messages), newest last. */
7589
+ listInbox(id: string, filter?: MessageFilter & {
7590
+ limit?: number;
7591
+ }): SessionMessage[] | null;
7592
+ /** Find a message `id` RECEIVED (still in its inbox, or already consumed
7593
+ * and only in its transcript) — how `message_reply` routes to the
7594
+ * original sender. `undefined` when `id` never received `messageId`. */
7595
+ findReceivedMessage(id: string, messageId: string): SessionMessage | undefined;
7596
+ /** Ack (remove) messages from `id`'s inbox — and from its prompt queue, so
7597
+ * an acked-but-still-queued message is never delivered afterwards. */
7598
+ ackInbox(id: string, ids: readonly string[] | "all"): {
7599
+ acked: string[];
7600
+ };
6109
7601
  /** Snapshot a session's prompt queue for inspection — the after-the-fact
6110
7602
  * view of what's sitting in `SessionDescriptor.promptQueue` right now.
6111
7603
  * Runs the shared preview + origin-label derivation so every consumer
@@ -6134,7 +7626,8 @@ interface SessionsRegistry {
6134
7626
  *
6135
7627
  * Implemented as promote-to-front + interrupt: the cancelled turn's
6136
7628
  * own `dispatchQueuedPrompt` (in its finally) then drains the promoted
6137
- * item into a fresh turn. On an idle session (nothing to interrupt) the
7629
+ * item — and only that item; the rest wait for a natural turn-end —
7630
+ * into a fresh turn. On an idle session (nothing to interrupt) the
6138
7631
  * promoted item is dispatched directly. Returns:
6139
7632
  * `{ delivered: false, reason }` — `"no-session"` / `"not-in-queue"`;
6140
7633
  * `{ delivered: true, interrupted: boolean }` otherwise. */
@@ -6390,6 +7883,15 @@ interface SessionsRegistry {
6390
7883
  * Initial backfill: synchronously invokes `onLine` once for each
6391
7884
  * line currently in the ring buffer so attaches show context. */
6392
7885
  attach(id: string, onLine: (line: string, stream: "stdout" | "stderr") => void): (() => void) | null;
7886
+ /** Append a `kind: "mcp_app_tool_call"` record (an MCP App iframe's
7887
+ * `tools/call`, proxied by `mcp_app_tool_call`) to the session's
7888
+ * events.jsonl through the same seq-numbered writer the session's own
7889
+ * events use. No-op-safe for an unknown id (the writer creates the dir). */
7890
+ recordMcpAppToolCall(sessionId: string, record: McpAppToolCallRecord): void;
7891
+ /** Tool name the transcript recorded for `toolCallId` (the latest
7892
+ * non-empty `toolName` across its `tool-call` records), or undefined
7893
+ * when the id isn't in the session's events.jsonl. */
7894
+ findToolCallName(sessionId: string, toolCallId: string): Promise<string | undefined>;
6393
7895
  /** Subscribe to a session's structured events.jsonl records as they're
6394
7896
  * written — the live-push half of `GET /sessions/:id/events/stream`'s
6395
7897
  * replay-then-subscribe handoff. Thin passthrough to the transcript
@@ -6433,7 +7935,26 @@ interface SessionsRegistry {
6433
7935
  * bytes at the end; `lastBytes` caps the returned size from the
6434
7936
  * tail. Returns null when the session is missing or not a PTY. */
6435
7937
  readTerminalOutput(id: string, lastBytes?: number): Buffer | null;
6436
- kill(id: string, signal?: NodeJS.Signals): boolean;
7938
+ /** SIGTERM the underlying child + close the agent protocol session.
7939
+ * `reason`, when given, is stamped onto `SessionDescriptor.endedReason`
7940
+ * (see {@link SessionEndReason}) — pass it from every call site that
7941
+ * knows WHY it's killing this session (an operator's explicit stop, a
7942
+ * supervisor tearing down its own judge session, …) rather than leaving
7943
+ * the row's cause of death unlabeled. Omitted preserves today's
7944
+ * behaviour: no `endedReason` is stamped by this call (the row may
7945
+ * already carry one from elsewhere, e.g. a prior `reapIdle`/`markCrashed`
7946
+ * — moot in practice since both already refuse a non-"running" row before
7947
+ * `kill()` would ever run on it).
7948
+ *
7949
+ * A row that's ALREADY terminal (exited/killed/error) normally makes this
7950
+ * a no-op refusal (`false`) — EXCEPT `reason: "operator-completed"`,
7951
+ * which is instead treated as "mark as completed": the persisted
7952
+ * outcome's `termination.reason` is idempotently relabeled to
7953
+ * `"operator-completed"` (the true original mechanism preserved in
7954
+ * `termination.previousReason`) and this returns `true`. Every other
7955
+ * reason (including `"operator-stopped"` or none) on a terminal row
7956
+ * stays the plain no-op. */
7957
+ kill(id: string, signal?: NodeJS.Signals, reason?: SessionEndReason): boolean;
6437
7958
  /** Retire a long-idle agent-cli session to free its adapter process — the
6438
7959
  * primitive the idle-session reaper (`runIdleReapPass`, PR-6) drives on a
6439
7960
  * periodic sweep. Terminates the underlying adapter/child the SAME graceful
@@ -6541,17 +8062,6 @@ interface SessionsRegistry {
6541
8062
  * window keeps aging out naturally rather than being wiped by the
6542
8063
  * give-up itself. Returns false (no-op) for an unknown id. */
6543
8064
  giveUpRestart(id: string, message: string): boolean;
6544
- /** Queue a `[child-crashed] …` notice on a BUSY parent's descriptor
6545
- * (`SessionDescriptor.pendingChildCrashNotices`) for delivery at its next
6546
- * turn (`runAgentTurn`'s flush) — the never-interrupt path
6547
- * `supervisor-notify.ts` takes when the parent can't be prompted
6548
- * directly right now (it's mid-turn). Idempotent: the exact same notice
6549
- * string is never queued twice, so a duplicate event for the same crash
6550
- * can't double-deliver. No-op (returns false) on an unknown id, a
6551
- * non-agent-cli row, or a row that isn't alive (`running`/`starting`) —
6552
- * nothing to flush a notice INTO. Returns true iff the notice was newly
6553
- * queued. */
6554
- stampPendingChildCrashNotice(id: string, notice: string): boolean;
6555
8065
  /** List permission requests currently parked in the pending-permissions
6556
8066
  * inbox across all permission-hold sessions, newest last. Optionally
6557
8067
  * filtered to one session. */
@@ -6563,8 +8073,9 @@ interface SessionsRegistry {
6563
8073
  * clears the session's awaiting-permission state, and emits
6564
8074
  * `session:permission-resolved`. */
6565
8075
  respondPermission(id: string, input: PermissionRespondInput): Promise<PermissionRespondResult>;
6566
- /** Stop tracking a session (after it exited and the user clicked
6567
- * "clear"). Doesn't kill — use `kill` first. */
8076
+ /** Stop tracking a session. A still-live session is torn down through
8077
+ * `kill` first (same teardown as `agent_kill`), so forgetting never
8078
+ * orphans a running process tree. */
6568
8079
  forget(id: string): boolean;
6569
8080
  /** Await every best-effort fire-and-forget per-session write currently in
6570
8081
  * flight — today the `CommandLogEntry` → `ToolCallRecord` chain
@@ -6770,6 +8281,9 @@ interface SpawnAgentInput {
6770
8281
  /** What session close does to the box, when `remote` is true — see
6771
8282
  * `SessionDescriptor.sandboxTeardown`. */
6772
8283
  sandboxTeardown?: "kill" | "pause";
8284
+ /** Effective adapter confinement, resolved by the caller — see
8285
+ * `SessionDescriptor.commandSandbox`. */
8286
+ commandSandbox?: "off" | "workspace" | "strict";
6773
8287
  /** Port-to-URL map from the booted sandbox — see
6774
8288
  * `SessionDescriptor.sandboxPorts`. */
6775
8289
  sandboxPorts?: Record<number, string>;
@@ -7118,7 +8632,7 @@ declare function runEagerResumePass(opts: {
7118
8632
 
7119
8633
  /** A minimal `gh` runner — resolves with the captured exit code + stdout.
7120
8634
  * Injected so tests drive stamping without spawning a real `gh`. */
7121
- type GhRunner = (args: readonly string[], cwd: string) => Promise<{
8635
+ type GhRunner$1 = (args: readonly string[], cwd: string) => Promise<{
7122
8636
  exitCode: number;
7123
8637
  stdout: string;
7124
8638
  }>;
@@ -7206,7 +8720,7 @@ declare function createPrProvenanceReconciler(opts: {
7206
8720
  * Defaults to reading the session's own events.jsonl via
7207
8721
  * {@link readToolCallRecords}; injectable for tests. */
7208
8722
  listToolCalls?: (sessionId: string) => Promise<ToolCallRecord[]>;
7209
- run?: GhRunner;
8723
+ run?: GhRunner$1;
7210
8724
  host?: string;
7211
8725
  }): PrProvenanceReconciler;
7212
8726
 
@@ -7297,6 +8811,79 @@ interface InboundWatcher {
7297
8811
  shutdown(): void;
7298
8812
  }
7299
8813
 
8814
+ /**
8815
+ * Dynamic MCP-app panels for installed `@agentproto/app-kit` apps that ship
8816
+ * a `ui` block (`defineApp({ ui: { html, title?, tools?, csp? } })`,
8817
+ * `app-tools.ts`'s `performInstall`). Bridges `AppRegistry` → `AgnoMcpApp[]`
8818
+ * so `registerMcpApps` (mcp-apps-adapter.ts) can mount them next to the
8819
+ * built-in panels (agentproto_sessions, agents_overview, ...).
8820
+ *
8821
+ * `mcpServerFactory` (index.ts) rebuilds a fresh McpServer per `/mcp`
8822
+ * request, so this module never throws — a bad app record must not take
8823
+ * down every other request. Failures (a collided tool id, an unreadable
8824
+ * `ui.path`) are skipped with a `console.warn`, not surfaced to the caller.
8825
+ */
8826
+
8827
+ /**
8828
+ * The MCP Apps wire (spec `2026-01-26`) over `postMessage` JSON-RPC, exposed
8829
+ * as `window.McpApp.connect() -> Promise<{ callTool, sendMessage,
8830
+ * updateModelContext, openLink, onTeardown }>`. No host in this stack injects
8831
+ * an SDK global into the app iframe, so every UI panel that talks through
8832
+ * `window.McpApp` (all of them — see each app's `ui.ts` under
8833
+ * `packages/apps/src`) would otherwise throw `Cannot read properties of
8834
+ * undefined (reading 'connect')` at boot. Injected here, at serve time, so
8835
+ * every installed app's panel is covered without each app shipping its own
8836
+ * copy of the bridge.
8837
+ *
8838
+ * Bridge surface:
8839
+ * - `callTool(name, args)` — JSON-RPC `tools/call`
8840
+ * - `sendMessage(content)` — JSON-RPC `ui/message`
8841
+ * - `updateModelContext({content?, structuredContent?})` — JSON-RPC
8842
+ * `ui/update-model-context`; each call replaces the previous context.
8843
+ * - `openLink(url)` — JSON-RPC `ui/open-link` {url}
8844
+ * - `onTeardown(cb)` — register a cleanup callback invoked on
8845
+ * `ui/resource-teardown` from the host, at which point the bridge also
8846
+ * replies `{result:{}}` automatically.
8847
+ * - `getHostContext()` / `onHostContext(cb)` — the host context captured
8848
+ * from the `ui/initialize` result and merged on every
8849
+ * `ui/notifications/host-context-changed` (the notification carries only
8850
+ * the changed keys, per the ext-apps spec).
8851
+ * - `getToolInput()` / `onToolInput(cb)` — the arguments the panel's own tool
8852
+ * (`app_ui_<slug>`) was invoked with, from `ui/notifications/tool-input`,
8853
+ * falling back to the tool's own result (`ui/notifications/tool-result`,
8854
+ * which `registerMcpApps` fills with `execute()`'s return value) for a host
8855
+ * that forwards results but not inputs. A late subscriber is replayed the
8856
+ * last value, same as `onHostContext`. Lets an MCP-hosted panel resolve a
8857
+ * deep link (e.g. `{view: "wallets/my-profile"}`) the way the browser-served
8858
+ * panel resolves `location.hash`.
8859
+ * - `displayMode` — `{get, available, request, onChange, mountToggle}`, the
8860
+ * shared controller from `@agentproto/app-client/display-mode` (also
8861
+ * published as `window.McpApp.displayMode` once connected). It is what
8862
+ * mounts the floating ⤢ toggle; an app that wants the control in its own
8863
+ * header opts the floating one out (`<meta name="agentproto-display-toggle"
8864
+ * content="none">`, or `connect({displayToggle: "none"})`) and calls
8865
+ * `displayMode.mountToggle(el)` instead.
8866
+ */
8867
+ declare const MCP_APP_BRIDGE_SCRIPT = "<script>\n(function () {\n window.__AGENTPROTO_UI_TRANSPORT__ = \"mcp\";\n if (window.McpApp) return;\n var nextId = 1, pending = {}, teardownCbs = [];\n var hostContext = null, hostContextCbs = [];\n var toolInput = null, toolInputCbs = [];\n function post(m) { window.parent.postMessage(m, \"*\"); }\n function getHostContext() { return hostContext; }\n function onHostContext(cb) {\n hostContextCbs.push(cb);\n // Replay the last context so a late subscriber isn't stuck blind.\n if (hostContext) { try { cb(hostContext); } catch (_) {} }\n }\n function setHostContext(ctx) {\n if (!ctx || typeof ctx !== \"object\") return;\n // host-context-changed carries only the changed keys \u2014 merge, matching\n // the official ext-apps App behaviour.\n hostContext = Object.assign({}, hostContext || {}, ctx);\n for (var i = 0; i < hostContextCbs.length; i++) {\n try { hostContextCbs[i](hostContext); } catch (_) {}\n }\n }\n function getToolInput() { return toolInput; }\n function onToolInput(cb) {\n toolInputCbs.push(cb);\n // Replay the last known input so a late subscriber isn't stuck blind \u2014\n // same convention as onHostContext.\n if (toolInput) { try { cb(toolInput); } catch (_) {} }\n }\n function setToolInput(input) {\n if (!input || typeof input !== \"object\") return;\n toolInput = input;\n for (var i = 0; i < toolInputCbs.length; i++) {\n try { toolInputCbs[i](toolInput); } catch (_) {}\n }\n }\n // A ui/notifications/tool-result carries the full CallToolResult of the\n // tool call that opened this view (spec 2026-01-26) \u2014 the same envelope\n // registerMcpApps wraps execute()'s return value in (JSON text content, or\n // structuredContent). Read as a fallback source of the tool's arguments\n // for a host that forwards results but not tool-input.\n function extractToolResultInput(result) {\n try {\n if (result && result.structuredContent && typeof result.structuredContent === \"object\") {\n return result.structuredContent;\n }\n var c = result && Array.isArray(result.content) ? result.content[0] : null;\n if (c && c.type === \"text\" && typeof c.text === \"string\") return JSON.parse(c.text);\n } catch (_) {}\n return null;\n }\n window.addEventListener(\"message\", function (e) {\n var m = e.data;\n if (typeof m === \"string\") { try { m = JSON.parse(m); } catch (_) { return; } }\n if (!m || m.jsonrpc !== \"2.0\") return;\n if (m.method === \"ui/resource-teardown\") {\n for (var i = 0; i < teardownCbs.length; i++) teardownCbs[i]();\n post({ jsonrpc: \"2.0\", id: m.id, result: {} });\n return;\n }\n if (m.method === \"ui/notifications/host-context-changed\") { setHostContext(m.params || {}); return; }\n if (m.method === \"ui/notifications/tool-input\") { setToolInput((m.params || {}).arguments); return; }\n if (m.method === \"ui/notifications/tool-result\") { setToolInput(extractToolResultInput(m.params)); return; }\n if (m.id != null && pending[m.id]) { var cb = pending[m.id]; delete pending[m.id]; cb(m.result, m.error); }\n });\n function request(method, params) {\n return new Promise(function (resolve, reject) {\n var id = nextId++;\n pending[id] = function (r, err) { if (err) reject(new Error((err && err.message) || String(err))); else resolve(r); };\n post({ jsonrpc: \"2.0\", id: id, method: method, params: params });\n });\n }\n function requestDisplayMode(mode) { return request(\"ui/request-display-mode\", { mode: mode }); }\n // The shared toggle (@agentproto/app-client/display-mode), injected right\n // after this script. Absent only when the app ships its own\n // window.AgentprotoUI namespace, which suppresses that injection \u2014 the\n // fallback keeps window.McpApp.displayMode callable either way.\n function installDisplayMode(opts) {\n var api = { getHostContext: getHostContext, onHostContext: onHostContext, requestDisplayMode: requestDisplayMode };\n if (window.AgentprotoUI && window.AgentprotoUI.installDisplayMode) {\n return window.AgentprotoUI.installDisplayMode(api, opts);\n }\n return {\n get: function () { return (hostContext && hostContext.displayMode) || \"inline\"; },\n available: function () { return (hostContext && hostContext.availableDisplayModes) || []; },\n request: requestDisplayMode,\n onChange: function (cb) { onHostContext(function (c) { cb((c && c.displayMode) || \"inline\", c); }); return function () {}; },\n mountToggle: function () { return null; }\n };\n }\n window.McpApp = {\n connect: function (opts) {\n if (window.parent === window) return Promise.reject(new Error(\"no host (standalone)\"));\n return request(\"ui/initialize\", {\n appInfo: { name: \"agentproto-app\", version: \"1.0\" },\n appCapabilities: { availableDisplayModes: [\"inline\", \"fullscreen\"] },\n protocolVersion: \"2026-01-26\"\n }).then(function (result) {\n // The initialize result carries the initial hostContext (displayMode,\n // availableDisplayModes, theme, safeAreaInsets) \u2014 capture it before\n // notifying the host, so the toggle renders in the right place on\n // its first paint rather than after a host-context-changed.\n if (result && result.hostContext) setHostContext(result.hostContext);\n post({ jsonrpc: \"2.0\", method: \"ui/notifications/initialized\", params: {} });\n var displayMode = installDisplayMode((opts && opts.displayToggle) ? { toggle: opts.displayToggle } : null);\n window.McpApp.displayMode = displayMode;\n return {\n callTool: function (name, args) { return request(\"tools/call\", { name: name, arguments: args || {} }); },\n sendMessage: function (p) {\n var params = typeof p === \"string\" ? { content: p } : (p || {});\n var content = typeof params.content === \"string\" ? [{ type: \"text\", text: params.content }] : (params.content || []);\n return request(\"ui/message\", { role: \"user\", content: content });\n },\n updateModelContext: function (p) { return request(\"ui/update-model-context\", p || {}); },\n openLink: function (url) { return request(\"ui/open-link\", { url: url }); },\n onTeardown: function (cb) { teardownCbs.push(cb); },\n getHostContext: getHostContext,\n onHostContext: onHostContext,\n getToolInput: getToolInput,\n onToolInput: onToolInput,\n displayMode: displayMode\n };\n });\n }\n };\n})();\n</script>\n";
8868
+ /** Inject `MCP_APP_BRIDGE_SCRIPT` followed by the two
8869
+ * `window.AgentprotoUI` scripts (`DISPLAY_MODE_SCRIPT`, then
8870
+ * `RUNNER_SELECT_SCRIPT`) — every installed app UI gets the shared
8871
+ * display-mode toggle and `mountRunnerSelect` for free, same as it gets
8872
+ * `window.McpApp` (see `injectAfterStructuralTag` for the placement rule).
8873
+ * The halves are independently idempotent, so a single
8874
+ * `injectAfterStructuralTag` call inserts only whichever ones the document
8875
+ * doesn't already define — inserting them one call each would place the
8876
+ * second BEFORE the first (all target "right after the same structural
8877
+ * tag"), which is why they're concatenated first instead. `window.
8878
+ * AgentprotoUI` gates BOTH of its scripts as one unit: a document that
8879
+ * already defines that namespace brings its own, and the bridge's
8880
+ * `installDisplayMode` falls back to a controller with no button rather
8881
+ * than fighting it. The bridge guard matches an assignment only — every
8882
+ * app panel CONSUMES `window.McpApp.connect()`, so matching any mention
8883
+ * would skip injection for exactly the documents that need it, leaving
8884
+ * `window.McpApp` undefined in the host iframe. */
8885
+ declare function injectMcpAppBridge(html: string): string;
8886
+
7300
8887
  /**
7301
8888
  * MCP tools that expose the per-workspace brain to agents.
7302
8889
  *
@@ -7420,6 +9007,326 @@ interface WorkspaceBrainSubscriber {
7420
9007
  }
7421
9008
  declare function createWorkspaceBrainSubscriber(opts: WorkspaceBrainSubscriberOptions): WorkspaceBrainSubscriber;
7422
9009
 
9010
+ /**
9011
+ * Review ledger — the daemon-side record of every review attestation.
9012
+ *
9013
+ * Lives under the daemon state dir, NEVER in the reviewed repo's working
9014
+ * tree: `~/.agentproto/reviews/<repo-slug>/<manifestSha>/<binding>/<rangeSha>.json`
9015
+ * — the path IS the ledger key `(repoRemote, manifestSha, binding, rangeSha)`
9016
+ * (the repo slug is a filesystem-safe form of the normalized remote). One
9017
+ * file per key; a re-run of the same key overwrites it with the newer
9018
+ * attestation.
9019
+ *
9020
+ * Every attestation is recorded — including `incomplete` and dirty-tree ones,
9021
+ * so `review_ledger` shows the history honestly — but only a clean `pass` /
9022
+ * `block` is ever served back as a cache hit (see `lookupCached`).
9023
+ *
9024
+ * Each file is an {@link LedgerEntry}: the self-contained attestation (what
9025
+ * `review_export` writes out) plus host-local metadata that is NOT part of
9026
+ * what gets attested (the checkout path, the manifest path, the manifest's
9027
+ * `verdict.exportDir`).
9028
+ *
9029
+ * Beside each entry sits an optional `<rangeSha>.annotations.json`
9030
+ * ({@link LedgerAnnotations}): the MUTABLE follow-up layer — a PR link found
9031
+ * after the fact, and `review_pr` status snapshots appended over time. It is
9032
+ * never hashed into (or exported with) the attestation, so annotating an
9033
+ * entry can't disturb a signature over it.
9034
+ *
9035
+ * Same persistence primitives as the sibling JSON stores (`node:fs/promises`,
9036
+ * write-tmp + rename). The root is injectable so tests never touch
9037
+ * `~/.agentproto`.
9038
+ */
9039
+
9040
+ /** Host-local metadata stored beside an attestation (never exported). */
9041
+ interface LedgerHostMeta {
9042
+ /** Absolute path of the checkout the review ran in. */
9043
+ repoRoot: string;
9044
+ /** Absolute path of the REVIEW.md that was used. */
9045
+ manifestPath: string;
9046
+ /** The manifest's `verdict.exportDir`, resolved against `repoRoot`. */
9047
+ exportDir?: string;
9048
+ }
9049
+ interface LedgerEntry {
9050
+ attestation: Attestation;
9051
+ host: LedgerHostMeta;
9052
+ }
9053
+ /** One `review_pr` status fetch. */
9054
+ interface PrStatusSnapshot {
9055
+ /** ISO timestamp of the fetch. */
9056
+ fetchedAt: string;
9057
+ state: "open" | "merged" | "closed";
9058
+ reviews: Array<{
9059
+ login: string;
9060
+ state: string;
9061
+ submittedAt: string;
9062
+ }>;
9063
+ checks?: Array<{
9064
+ name: string;
9065
+ conclusion: string | null;
9066
+ }>;
9067
+ }
9068
+ /** The mutable annotations sidecar of a ledger entry. */
9069
+ interface LedgerAnnotations {
9070
+ pr?: ReviewPrRef;
9071
+ /** Oldest first — each `review_pr` call appends one. */
9072
+ prStatus?: PrStatusSnapshot[];
9073
+ }
9074
+ interface ReviewLedgerFilter {
9075
+ repoRemote?: string;
9076
+ rangeSha?: string;
9077
+ binding?: string;
9078
+ manifestSha?: string;
9079
+ }
9080
+ interface ReviewLedger {
9081
+ /** Absolute root directory of the ledger. */
9082
+ readonly root: string;
9083
+ put(entry: LedgerEntry): Promise<string>;
9084
+ get(key: LedgerKey): Promise<LedgerEntry | undefined>;
9085
+ /** A reusable verdict for `key`: a clean (not dirty) `pass`/`block` whose
9086
+ * rubric digests still match. `incomplete` is never reused. */
9087
+ lookupCached(key: LedgerKey, rubrics: readonly RubricDigest[]): Promise<LedgerEntry | undefined>;
9088
+ findByRunId(runId: string): Promise<LedgerEntry | undefined>;
9089
+ /** Entries matching `filter`, newest first. */
9090
+ list(filter?: ReviewLedgerFilter): Promise<LedgerEntry[]>;
9091
+ /** The annotations sidecar of `key` (`{}` when there is none). */
9092
+ getAnnotations(key: LedgerKey): Promise<LedgerAnnotations>;
9093
+ /** Read-modify-write the annotations sidecar of `key`; returns the result. */
9094
+ updateAnnotations(key: LedgerKey, update: (current: LedgerAnnotations) => LedgerAnnotations): Promise<LedgerAnnotations>;
9095
+ }
9096
+ /** Record `pr` as the entry's PR link (replacing any previous link). */
9097
+ declare const withPr: (pr: ReviewPrRef) => (a: LedgerAnnotations) => LedgerAnnotations;
9098
+ /** Append one status snapshot. */
9099
+ declare const withPrStatus: (snapshot: PrStatusSnapshot) => (a: LedgerAnnotations) => LedgerAnnotations;
9100
+ declare const defaultReviewLedgerRoot: () => string;
9101
+ /** Filesystem-safe slug of a normalized repo remote
9102
+ * (`github.com/agentproto/ts` → `github.com_agentproto_ts`). */
9103
+ declare function repoSlug(repoRemote: string): string;
9104
+ declare function createReviewLedger(opts?: {
9105
+ root?: string;
9106
+ }): ReviewLedger;
9107
+
9108
+ /**
9109
+ * Review runner — the daemon host for `@agentproto/review`.
9110
+ *
9111
+ * The pure package parses REVIEW.md and compiles a binding into an ordinary
9112
+ * AIP-15 workflow; this module supplies everything that touches the world:
9113
+ *
9114
+ * 1. resolve the repo + range — `merge-base(<target.base>, HEAD)..HEAD`
9115
+ * unless the caller pins `base`/`head`;
9116
+ * 2. ledger pre-check — a clean `pass`/`block` already recorded for the
9117
+ * same `(repoRemote, manifestSha, binding, rangeSha)` (and the same
9118
+ * rubric digests) is returned with `cached: true`, nothing runs;
9119
+ * 3. execute the compiled workflow through `@agentproto/workflow-runtime`'s
9120
+ * `compileWorkflow` + `runWorkflow` — prepare steps (the engine's own
9121
+ * gate runner), then the FREEZE (head resolved after prepare), then the
9122
+ * lanes in parallel through {@link createReviewLaneExecutor}, then the
9123
+ * fan-in verdict;
9124
+ * 4. build the attestation and write it to the ledger. A CANCELLED run
9125
+ * (`cancel()`, or superseded by a newer head — see `supersede`) writes
9126
+ * nothing: nobody asked for its verdict any more.
9127
+ *
9128
+ * Lanes: command lanes run `sh -c <run>` in their own process group with a
9129
+ * hard timeout; agent lanes spawn a CHILD REVIEWER SESSION through
9130
+ * {@link ReviewerSessionHost} — in the daemon, {@link createDaemonReviewerHost},
9131
+ * which uses the same `spawnAgentSession` core `agent_start` uses and kills
9132
+ * the session through the registry on timeout/cancel (a child session, never
9133
+ * an orphan pid). The reviewer gets a pointer-style prompt (range + rubric
9134
+ * path) and writes a structured verdict file under the ledger's run dir —
9135
+ * outside the reviewed tree.
9136
+ *
9137
+ * Async pattern mirrors `WorkflowRunner`: `start()` returns a run record
9138
+ * immediately and executes in the background; `status()` polls; `wait()`
9139
+ * blocks. Runs live in memory for the daemon's lifetime; the ATTESTATION is
9140
+ * what persists (the ledger), and `status()` falls back to it for a run that
9141
+ * finished before a daemon restart.
9142
+ */
9143
+
9144
+ /**
9145
+ * Normalize a git remote URL so SSH and HTTPS clones of the same repo agree:
9146
+ * `git@github.com:agentproto/ts.git` and `https://user@github.com/agentproto/ts`
9147
+ * both become `github.com/agentproto/ts`. Credentials are always stripped.
9148
+ */
9149
+ declare function normalizeRemote(url: string): string;
9150
+ /** Run one command lane: `sh -c <command>` in its own process group, output
9151
+ * captured (tail-bounded), hard-killed on timeout or cancel. */
9152
+ declare function runShellLane(input: {
9153
+ command: string;
9154
+ cwd: string;
9155
+ timeoutMs: number;
9156
+ signal?: AbortSignal;
9157
+ }): Promise<LaneOutcome>;
9158
+ /** Outcome of one reviewer session's run. */
9159
+ type ReviewerRunResult = {
9160
+ status: "ended";
9161
+ sessionId: string;
9162
+ preset: string;
9163
+ model?: string;
9164
+ } | {
9165
+ status: "timeout";
9166
+ sessionId: string;
9167
+ preset: string;
9168
+ model?: string;
9169
+ } | {
9170
+ status: "failed";
9171
+ error: string;
9172
+ sessionId?: string;
9173
+ preset?: string;
9174
+ model?: string;
9175
+ };
9176
+ /**
9177
+ * The agent-lane executor seam: spawn a reviewer session under a harness
9178
+ * preset, run ONE turn with `prompt`, and stop it — killing it through the
9179
+ * session lifecycle on timeout or cancel. The daemon wires
9180
+ * {@link createDaemonReviewerHost}; tests wire a fake.
9181
+ */
9182
+ interface ReviewerSessionHost {
9183
+ run(input: {
9184
+ preset: string;
9185
+ cwd: string;
9186
+ prompt: string;
9187
+ label: string;
9188
+ timeoutMs: number;
9189
+ parentSessionId?: string;
9190
+ signal?: AbortSignal;
9191
+ }): Promise<ReviewerRunResult>;
9192
+ }
9193
+ interface ReviewLaneExecutorContext {
9194
+ runId: string;
9195
+ reviewId: string;
9196
+ repoRoot: string;
9197
+ manifestPath: string;
9198
+ /** Directory the agent lanes' verdict files are written under. */
9199
+ runDir: string;
9200
+ reviewers?: ReviewerSessionHost;
9201
+ parentSessionId?: string;
9202
+ }
9203
+ /** The daemon's {@link ReviewLaneExecutor}: subprocess command lanes, child
9204
+ * reviewer sessions for agent lanes. */
9205
+ declare function createReviewLaneExecutor(ctx: ReviewLaneExecutorContext): ReviewLaneExecutor;
9206
+ interface ReviewRunInput {
9207
+ /** Any directory inside the repo to review. */
9208
+ cwd: string;
9209
+ /** REVIEW.md path — absolute, or relative to `cwd`. Default: `<repo root>/REVIEW.md`. */
9210
+ manifestPath?: string;
9211
+ /** Binding name. Default: the sole binding, or `default`. */
9212
+ binding?: string;
9213
+ /** Range base (ref or sha). Default: `merge-base(<target.base>, HEAD)`. */
9214
+ base?: string;
9215
+ /** Range head (ref or sha). Default: `HEAD`, resolved AFTER prepare. */
9216
+ head?: string;
9217
+ /** Ignore a cached ledger verdict and re-run. */
9218
+ nocache?: boolean;
9219
+ /** Session the agent lanes' reviewer sessions nest under. */
9220
+ parentSessionId?: string;
9221
+ /** Session that requested the review — recorded as
9222
+ * `attestation.requester.sessionId`. Default: `parentSessionId`. */
9223
+ requesterSessionId?: string;
9224
+ /** The PR the range is being reviewed for, when the caller knows it —
9225
+ * recorded as `attestation.pr` and as the entry's annotation link. */
9226
+ pr?: ReviewPrRef;
9227
+ /** Before running, cancel in-flight runs for the same repo + binding + base
9228
+ * whose head DIFFERS (an older push nobody will read the verdict of) — as
9229
+ * long as that run is the same line of work: the same checkout, or a head
9230
+ * that is an ancestor of this one. A sibling branch reviewed from another
9231
+ * worktree off the same base is never cancelled. Gates should pass
9232
+ * `true`; the default is `false`. */
9233
+ supersede?: boolean;
9234
+ }
9235
+ type ReviewRunStatus = "running" | "done" | "failed" | "cancelled";
9236
+ interface ReviewRun {
9237
+ runId: string;
9238
+ status: ReviewRunStatus;
9239
+ startedAt: string;
9240
+ endedAt?: string;
9241
+ reviewId?: string;
9242
+ binding?: string;
9243
+ repoRemote?: string;
9244
+ /** The resolved range base, once known. */
9245
+ baseSha?: string;
9246
+ /** The range head: the pre-prepare head once resolved, then the frozen head. */
9247
+ headSha?: string;
9248
+ /** The checkout the run reviews, once resolved. */
9249
+ repoRoot?: string;
9250
+ /** Set on a run cancelled because a newer head superseded it. */
9251
+ supersededBy?: string;
9252
+ /** Lanes settled so far (live progress while `running`). */
9253
+ lanes: LaneResult[];
9254
+ /** Set once `done`. */
9255
+ attestation?: Attestation;
9256
+ /** True when `attestation` was served from the ledger — nothing ran. */
9257
+ cached?: boolean;
9258
+ /** Where the attestation was written in the ledger. */
9259
+ ledgerPath?: string;
9260
+ error?: string;
9261
+ }
9262
+ interface ReviewRunner {
9263
+ start(input: ReviewRunInput): ReviewRun;
9264
+ status(runId: string): Promise<ReviewRun | undefined>;
9265
+ wait(runId: string): Promise<ReviewRun | undefined>;
9266
+ cancel(runId: string): boolean;
9267
+ readonly ledger: ReviewLedger;
9268
+ }
9269
+ interface CreateReviewRunnerOptions {
9270
+ ledger: ReviewLedger;
9271
+ /** Agent-lane executor. Omitted ⇒ agent lanes are `skipped` (and the
9272
+ * verdict therefore `incomplete`), never silently passed. */
9273
+ reviewers?: ReviewerSessionHost;
9274
+ /** Attestor identity. Default: `agentproto-runtime@<hostname>`. */
9275
+ daemonId?: string;
9276
+ }
9277
+ /** Placeholder values the daemon binds for every review. `{changed}` is a
9278
+ * shell-quoted turbo filter for the packages changed since the range base
9279
+ * (compose dependents with `...{changed}`). */
9280
+ declare function hostPlaceholders(baseSha: string): Record<string, string>;
9281
+ declare function createReviewRunner(opts: CreateReviewRunnerOptions): ReviewRunner;
9282
+
9283
+ /**
9284
+ * PR follow-up for review attestations — the host side of `review_pr`.
9285
+ *
9286
+ * A review attests a git range; whether that range became a PR, and what
9287
+ * happened to the PR afterwards, is only knowable later. This module asks
9288
+ * GitHub through the daemon host's authed `gh` CLI (shelled out — no HTTP
9289
+ * client, no token handling of our own):
9290
+ *
9291
+ * - {@link findPrForCommit}: `headSha → PR` via
9292
+ * `gh api repos/{owner}/{repo}/commits/{sha}/pulls`;
9293
+ * - {@link fetchPrStatus}: the PR's state + reviews (+ check runs on the
9294
+ * PR head, best-effort) as one {@link PrStatusSnapshot}.
9295
+ *
9296
+ * Every failure is a {@link PrLookupError} with a stable `code` — `gh`
9297
+ * missing, `gh` failing (auth, network, 404), a non-GitHub remote, no PR for
9298
+ * the commit — so the tool can return it as structured data instead of
9299
+ * throwing.
9300
+ */
9301
+
9302
+ type PrLookupErrorCode = "gh_unavailable" | "gh_failed" | "unsupported_remote" | "no_pr";
9303
+ declare class PrLookupError extends Error {
9304
+ readonly code: PrLookupErrorCode;
9305
+ constructor(code: PrLookupErrorCode, message: string);
9306
+ }
9307
+ /** Runs `gh <args>` and resolves its stdout. */
9308
+ type GhRunner = (args: readonly string[]) => Promise<string>;
9309
+ /** The real runner: `gh` from the daemon's PATH. A missing binary is
9310
+ * `gh_unavailable`; any non-zero exit is `gh_failed` with gh's stderr. */
9311
+ declare const execGh: GhRunner;
9312
+ /** `github.com/owner/name` → `owner/name`; `undefined` for any other host.
9313
+ * Also accepts the multi-account SSH host-alias convention
9314
+ * (`git@github.com-work:owner/name` normalizes to `github.com-work/owner/name`). */
9315
+ declare function githubRepoOf(repoRemote: string): string | undefined;
9316
+ /** Parse `https://github.com/owner/name/pull/42` (trailing path/query ok). */
9317
+ declare function parsePrUrl(url: string): {
9318
+ repo: string;
9319
+ number: number;
9320
+ } | undefined;
9321
+ /** The PR `headSha` belongs to, or `undefined` when GitHub knows none.
9322
+ * Prefers a PR whose head IS `headSha`, then an open one, then the first. */
9323
+ declare function findPrForCommit(gh: GhRunner, repo: string, headSha: string): Promise<ReviewPrRef | undefined>;
9324
+ /** The PR's head sha (for matching a `prUrl` to ledger entries). */
9325
+ declare function prHeadSha(gh: GhRunner, repo: string, number: number): Promise<string | undefined>;
9326
+ /** One status snapshot of `pr`: state, reviews, and (best-effort — omitted
9327
+ * when that call fails) the check runs on the PR's current head. */
9328
+ declare function fetchPrStatus(gh: GhRunner, pr: ReviewPrRef, now?: () => Date): Promise<PrStatusSnapshot>;
9329
+
7423
9330
  /**
7424
9331
  * `~/.agentproto/harness-presets.json` — the persisted harness→profile binding
7425
9332
  * (mode 0600). A {@link HarnessPreset} names, per adapter harness, WHICH auth
@@ -7527,6 +9434,77 @@ declare function removeHarnessPreset(id: string): Promise<boolean>;
7527
9434
  */
7528
9435
  declare function setDefaultPreset(harnessSlug: string, presetId: string): Promise<HarnessPreset>;
7529
9436
 
9437
+ /**
9438
+ * The daemon's {@link ReviewerSessionHost}: agent review lanes as CHILD
9439
+ * SESSIONS, spawned through the same `spawnAgentSession` core `agent_start`
9440
+ * uses, waited on with the same `monitorSessionWait` core `session_monitor` /
9441
+ * `sessions wait` use, and stopped through `registry.kill` — the normal
9442
+ * session lifecycle, so a timed-out or cancelled reviewer is a killed session
9443
+ * in `session_list`, never an orphan pid.
9444
+ *
9445
+ * A lane's `preset` names the daemon's existing preset surface — no parallel
9446
+ * model/auth config: first a harness preset (`harness-presets.json`: adapter
9447
+ * harness + auth profile + default model), then a user preset
9448
+ * (`presets.json`, handed to `spawnAgentSession` as-is so it expands the
9449
+ * preset exactly like `agent_start.presetId` does).
9450
+ *
9451
+ * Reviewers spawn with role `executor` (no re-delegation), `worktree: false`
9452
+ * (they read the reviewed checkout's history; they never edit), and
9453
+ * `dedupe: false` (two lanes/runs with the same label are distinct
9454
+ * reviewers, never merged).
9455
+ */
9456
+
9457
+ interface DaemonReviewerHostDeps {
9458
+ registry: SessionsRegistry;
9459
+ sessionEvents: SessionEventBus;
9460
+ eventRing: EventRing;
9461
+ resolveAgentAdapter: AgentAdapterResolver;
9462
+ /** Extra spawn deps forwarded to `spawnAgentSession` (sandbox resolver,
9463
+ * catalog models, …). `registry`/`resolveAgentAdapter` always win. */
9464
+ spawnDeps?: Partial<SpawnAgentSessionDeps>;
9465
+ /** Preset lookups — injectable for tests; default to the real stores. */
9466
+ getHarnessPreset?: (id: string) => Promise<HarnessPreset | undefined>;
9467
+ getUserPreset?: (id: string) => Promise<UserPreset | undefined>;
9468
+ }
9469
+ /** Resolve a lane's `preset` to spawn fields — harness preset first, then a
9470
+ * user preset. `undefined` when neither store knows the id. */
9471
+ declare function resolveReviewerPreset(preset: string, lookups?: Pick<DaemonReviewerHostDeps, "getHarnessPreset" | "getUserPreset">): Promise<Pick<SpawnAgentSessionInput, "adapter" | "model" | "access" | "preset"> | undefined>;
9472
+ declare function createDaemonReviewerHost(deps: DaemonReviewerHostDeps): ReviewerSessionHost;
9473
+
9474
+ /**
9475
+ * MCP tools for the review primitive (`@agentproto/review`):
9476
+ *
9477
+ * review_run run a REVIEW.md binding over a git range → attestation
9478
+ * (ledger hit ⇒ the cached verdict, `cached: true`)
9479
+ * review_status poll a run started with `wait: false`
9480
+ * review_cancel cancel a running review (lane sessions are killed)
9481
+ * review_ledger list recorded attestations
9482
+ * review_export write one attestation out as a standalone JSON file
9483
+ * review_pr follow a recorded review to its GitHub PR (via `gh`):
9484
+ * link it, snapshot the PR's state/reviews/checks into the
9485
+ * entry's annotations, return the combined view
9486
+ *
9487
+ * Async pattern mirrors `workflow_start`/`workflow_status`: `review_run`
9488
+ * blocks by default (`wait: true`) — pass `wait: false` for long reviews and
9489
+ * poll `review_status` with the returned `runId`.
9490
+ *
9491
+ * Trust note: `review_run` executes the command checks the repo's own
9492
+ * REVIEW.md declares (`sh -c`), the same trust `workflow_run_file` extends to
9493
+ * a WORKFLOW.md's gate steps — not the `command_execute` allowlist, which
9494
+ * governs ad-hoc commands a remote caller composes.
9495
+ */
9496
+
9497
+ interface RegisterReviewToolsOptions {
9498
+ runner: ReviewRunner;
9499
+ /** The calling session (from `?callerSessionId=`) — agent-lane reviewer
9500
+ * sessions nest under it, and it is the default requester. */
9501
+ callerSessionId?: string;
9502
+ /** `gh` runner for `review_pr` — injectable for tests. Default: the real
9503
+ * `gh` on the daemon's PATH. */
9504
+ gh?: GhRunner;
9505
+ }
9506
+ declare function registerReviewTools(server: McpServer, opts: RegisterReviewToolsOptions): void;
9507
+
7530
9508
  /**
7531
9509
  * Built-in custom-route registration (PR-5).
7532
9510
  *
@@ -7987,6 +9965,76 @@ declare function reapGcEntry(entry: SandboxLedgerEntry, opts: {
7987
9965
  /** States that still name a box the gc might act on. */
7988
9966
  declare const GC_REAPABLE_STATES: readonly SandboxLedgerState[];
7989
9967
 
9968
+ /**
9969
+ * Sandbox ledger reconcile — closes the gap between what
9970
+ * `~/.agentproto/sandboxes.json` believes is still booted/paused/connected
9971
+ * and what the PROVIDER actually still has running. A failed teardown, a
9972
+ * daemon that crashed mid-close, or a provider-side idle-reap the daemon
9973
+ * never heard about all leave a ledger row claiming a box is alive long
9974
+ * after the provider reaped it — real-world evidence: one daemon's ledger
9975
+ * carried ~50 rows in "paused" while the provider had far fewer boxes left.
9976
+ *
9977
+ * This is read-only against the PROVIDER (`SandboxProvider.probe`, the same
9978
+ * primitive `GET /sandboxes/:id/alive` and `agentproto sandbox list` already
9979
+ * use per-row) and never tears a box down — it only ever flips a ledger
9980
+ * row's `state` to `"gone"` via `recordSandboxLiveness` when the provider
9981
+ * confirms the box no longer exists. A box a caller still wants stays
9982
+ * completely untouched; this module has no `stop()`/`pause()`/`connect()`
9983
+ * call anywhere in it.
9984
+ */
9985
+
9986
+ /** The slice of `SandboxProviderHandle` reconcile needs — just the liveness
9987
+ * probe, so tests (and the `local` provider, which has none) can hand back
9988
+ * a handle without a `probe` at all. */
9989
+ interface SandboxReconcileProviderHandle {
9990
+ provider: {
9991
+ probe?(sandboxId: string): Promise<{
9992
+ alive: boolean;
9993
+ state?: string;
9994
+ }>;
9995
+ };
9996
+ }
9997
+ interface SandboxReconcileDeps {
9998
+ /** Resolve a ledger row's provider slug to a handle. Mirrors
9999
+ * `SandboxGcReapDeps.resolveProvider` (`sandbox-gc.ts`) — same DI shape,
10000
+ * narrower need (probe only, never connect/boot). */
10001
+ resolveProvider: (slug: string) => Promise<SandboxReconcileProviderHandle | null>;
10002
+ /** Defaults to `readSandboxLedger` — injectable so callers that already
10003
+ * hold a mocked/scoped ledger reader (tests, a scoped CLI mock) can reuse
10004
+ * it instead of this module reaching for the real `~/.agentproto/
10005
+ * sandboxes.json` on disk. */
10006
+ readLedger?: (path?: string) => SandboxLedgerEntry[];
10007
+ /** Defaults to `recordSandboxLiveness` — same injectability reasoning. */
10008
+ recordLiveness?: (sandboxId: string, alive: boolean, path?: string) => void;
10009
+ ledgerPath?: string;
10010
+ }
10011
+ /** Verdict for one reconciled row. `"skipped"` means the provider has no
10012
+ * `probe()` (e.g. Box today) — never treated as evidence of death. */
10013
+ type SandboxReconcileVerdict = "alive" | "gone" | "unknown" | "skipped";
10014
+ interface SandboxReconcileRow {
10015
+ sandboxId: string;
10016
+ provider: string;
10017
+ verdict: SandboxReconcileVerdict;
10018
+ /** Present when `verdict` is "unknown" — the resolve/probe failure message. */
10019
+ error?: string;
10020
+ }
10021
+ interface SandboxReconcileResult {
10022
+ checked: number;
10023
+ alive: number;
10024
+ gone: number;
10025
+ unknown: number;
10026
+ skipped: number;
10027
+ rows: SandboxReconcileRow[];
10028
+ }
10029
+ /**
10030
+ * Probe every ledger row still claiming to be alive against its provider,
10031
+ * and stamp the verdict back into the ledger (`recordSandboxLiveness` flips
10032
+ * a dead row to `"gone"`; a live or unprobeable row is left as-is). One
10033
+ * broken row's provider resolution or probe call never aborts the pass for
10034
+ * the rest — each row's outcome is independent and best-effort.
10035
+ */
10036
+ declare function reconcileSandboxLedger(deps: SandboxReconcileDeps): Promise<SandboxReconcileResult>;
10037
+
7990
10038
  /**
7991
10039
  * Idle agent-session reaper (PR-6).
7992
10040
  *
@@ -9558,6 +11606,16 @@ interface CreateGatewayOptions {
9558
11606
  * `worktree_gc` returns a clear "not enabled" error.
9559
11607
  */
9560
11608
  runWorktreeGc?: WorktreeGcRunner;
11609
+ /**
11610
+ * Optional branch-`gc` runner powering `branch_gc` (+ `POST /branches/gc`).
11611
+ * Injected for the same reason as `runWorktreeGc`. Omitted → `branch_gc`
11612
+ * returns a clear "not enabled" error.
11613
+ */
11614
+ runBranchGc?: BranchGcRunner;
11615
+ /** Optional verdict recorder powering `branch_gc_verdict` (+ `POST /branches/gc/verdict`). */
11616
+ recordBranchGcVerdict?: BranchGcVerdictRecorder;
11617
+ /** Optional verdict reader powering `branch_gc_verdict_get`. */
11618
+ readBranchGcVerdict?: BranchGcVerdictReader;
9561
11619
  /**
9562
11620
  * Optional best-effort exit-time reclaim of ONE policy-provisioned
9563
11621
  * (implicit) session's own worktree — powers `SessionDescriptor.
@@ -9607,6 +11665,24 @@ interface CreateGatewayOptions {
9607
11665
  * registered and the `llm_endpoint_*` MCP tools are not exposed. */
9608
11666
  llmEndpoint?: boolean;
9609
11667
  }
11668
+ /**
11669
+ * Default always-on set when `deferredTools` is enabled without an
11670
+ * explicit `alwaysOn` override: the core spawn/drive/observe/report loop
11671
+ * most sessions need immediately — including an EXECUTOR (the role
11672
+ * `deferredTools` defaults on for, see `role.ts`), which cannot spawn
11673
+ * (`agent_start`/`agent_prompt` are stripped for it anyway by
11674
+ * `toolPolicy.delegation: "deny"`, so those two names are simply absent
11675
+ * from a deny-role's registry and cost nothing) but still needs to report
11676
+ * back to its parent (`message_parent`), coordinate over the task ledger
11677
+ * (`task_*`), and observe its own session/turn state
11678
+ * (`session_context_status` alongside the pre-existing `session_list` /
11679
+ * `session_monitor` / `session_events_poll`). Everything else (fs_*,
11680
+ * directory_*, command_*, remote_*, browser_*, terminal_*, mcp_*,
11681
+ * routine_*, workflow_*, policy_*, inbound_watcher_*, session_tree,
11682
+ * agent_export, adapter_list, every `app_ui_*` panel tool, ...) starts
11683
+ * deferred — reachable via `tool_search`, never removed from `tools/call`.
11684
+ */
11685
+ declare const DEFAULT_ALWAYS_ON_TOOLS: readonly string[];
9610
11686
  interface GatewayHandle {
9611
11687
  url: string;
9612
11688
  workspace: string;
@@ -9664,4 +11740,4 @@ interface GatewayHandle {
9664
11740
  */
9665
11741
  declare function createGateway(opts: CreateGatewayOptions): Promise<GatewayHandle>;
9666
11742
 
9667
- export { type ActivityKind, type ActivityListFilter, type ActivityPolicyLister, type ActivityProjector, type ActivityProjectorRegistry, type ActivityProjectorSession, type ActivityRecord, type ActivitySource, type ActivityState, type ActivityWaitingOn, type ActivityWorkflowLister, AdapterAuthDescriptor, type AdapterCapabilitiesLister, type AdapterInstallResult, type AdapterListEntry, type AgentAdapterInstaller, type AgentAdapterLister, type AgentAdapterResolver, type AgentSessionLike, type AgentStreamEvent, type AnthropicReaderConfig, AnthropicRemainingQuotaReader, type AttachPolicyInput, type AttachSandboxOpts, type AttachSandboxResult, AuthMethod, BUCKETS_ROOT, type BrowserAdapterHandle, type BrowserAdapterInfo, type BrowserAdapterLister, type BrowserAdapterResolver, BuildHeartbeatAgent, CanonicalPosture, type CatalogModelsLister, CatalogModelsQuery, CatalogModelsResponse, type CatalogProviderModel, type CatalogProviderModelsQuery, type CatalogProviderModelsResponse, type CatalogProviderPricing, type ClaudeNativeRef, type CommitSpec, type CompletionPolicySupervisor, ContextProfile, type ConversationIndexRecord, type ConversationNativeRef, type CostBudgetDecision, type CrashDetectSummary, type CrashReaperRegistry, type CreateGatewayOptions, type CreateOfferInput, type CreatedOffer, DEAD_SESSION_STATUSES, DEFAULT_BUCKET, DEFAULT_ORCHESTRATOR_TOOLS, DEFAULT_WORKTREE_ISOLATION, DeclaredAdapterOption, type DeclaredAdapterPreset, type EagerResumeFailReason, type EagerResumeOutcome, type EagerResumeSkipReason, type EagerResumeSummary, EffortLevel, GC_REAPABLE_STATES, type GateSpec, type GatewayHandle, type HarnessPreset, type HarnessPresetValidationDeps, HarnessPresetValidationError, type HarnessPresetsFile, type HermesNativeRef, INBOUND_PROVIDERS, type IdleReapSummary, type IdleReaperRegistry, type InboundEndpoint, type InboundEndpointStore, type InboundEndpointStoreOptions, type InboundEnqueuePrompt, type InboundIsSessionAlive, type InboundMessage, type InboundProvider, type InboundRestartSession, type InboundRouteMode, type InboundRouterDeps, type InboundRouterLog, type InboundSpawnForContact, type InboundWatcher, type JudgeGateSpec, LEGACY_SESSIONS_FILE, type LocatedConversation, type LocatedConversationByPath, type McpCredentialDeps, type MigrationMarker, type NormalizeInboundResult, type OnFailSpec, type OpenPrResolver, type OrchestratorGatewayDeps, type OrchestratorInjection, type OrchestratorInjector, type OrchestratorInjectorDeps, type OrchestratorMcpServerFactory, type OrchestratorScope, type OrphanReaperRegistry, PAIRINGS_VERSION, POSTURE_NATIVE_ALIASES, POSTURE_PREAMBLES, type PairingChannelContext, type PairingChannelHandle, type PairingChannelMode, type PairingRecord, type PairingRegistry, type PairingRegistryDeps, type PendingPermission, type PermissionRespondInput, type PermissionRespondResult, type PolicyRunState, type PolicyRunStatus, Posture, type PostureResolution, type PrResolvedState, type PrStateResolver, type PresetInfo, type PricingResolver, type QuotaReadableProfile, type QuotaStoreFile, type QuotaStoreOptions, type ReconnectLogGate, type RegisterBrainToolsOptions, type RegisterBrowserInput, type RegisterPairingToolsOptions, type RegisterSessionInput, type RegisterWebSearchToolsOptions, type RemainingQuota, type RemainingQuotaReader, type ResolveNativeLinkInput, ResolvedAuthSpec, ResumeDisabledError, type ReuseResolution, type RouteAwareLaunchConfig, type RouteAwareLaunchConfigInput, RouteSpec, type RuntimeMeta, SESSION_ID_ENV, type SandboxAdapterInfo, type SandboxConnectionDescriptor, type SandboxGcCandidate, type SandboxGcProviderHandle, type SandboxGcReapDeps, type SandboxGcReapResult, type SandboxLedgerEntry, type SandboxLedgerState, type SandboxProviderCapabilities, type SandboxProviderHandle, type SandboxProviderLister, type SandboxProviderResolver, type ScopeTokenRegistry, SessionConfig, type SessionCurrentPhase, type SessionDescriptor, type SessionKind, type SessionObserver, type SessionSnapshots, type SessionStatus, type SessionUsage, type SessionWaitEvent, type SessionWaitResult, type SessionsRegistry, type ShellGateSpec, type SpawnAgentInput, type SpawnSessionInput, type StallWatchdogRegistry, type StallWatchdogSummary, type StoredProfileQuota, TASK_STATUSES, type TaskCaller, type TaskChangedEvent, type TaskClaimInput, type TaskCreateInput, type TaskGateOutcome, type TaskGateRunner, type TaskLedger, type TaskLedgerRegistry, type TaskLedgerSessionSlice, type TaskListFilter, type TaskRecord, type TaskStatus, type TaskUpdateInput, type TaskVerification, type TaskVerifySupervisor, type TaskWriteResult, type TokenPricing, type TransmitterBinding, type TransmitterBindingStore, type TunnelDescriptor, type TunnelProvider, type TunnelStatus, type UsageBucket, type UsageComputeInput, type UsageRollup, type UsageSnapshotRecord, type UsageSource, WORKSPACE_SLUG_ENV, WORKTREE_ISOLATION_ENV, type WatcherDescriptor, type WatcherStartInput, type WebSearchInput, type WebSearchOutput, type WebSearchResult, type WorkspaceBrainSubscriber, type WorkspaceBrainSubscriberOptions, type WorkspaceBrains, WorkspaceFs, type WorktreeAutoReclaimer, type WorktreeDecision, type WorktreeField, type WorktreeGcClass, type WorktreeGcOutcomeView, type WorktreeGcPlanEntryView, type WorktreeGcReclaimReason, type WorktreeGcResult, type WorktreeGcRunInput, type WorktreeGcRunner, WorktreeIsolationMode, type WorktreeProvisionOutcome, type WorktreeProvisionRequest, type WorktreeProvisioner, type WorktreeRequest, type WorktreeStatusLister, type WorktreeStatusView, activityCounts, addHarnessPreset, appendConversationRecord, attachSandbox, bucketDir, bucketSessionsFile, bucketTranscriptDir, buildCatalogProviderModels, buildMcpConfigSnippet, buildRouteAwareLaunchConfig, canonicalForModeId, claudeProjectSlug, collectGcCandidates, composeSessionObservers, conversationIndexPath, createActivityProjector, createFileStepCache, createGateway, createInboundEndpointStore, createOrchestratorInjector, createOrchestratorMcpServerFactory, createPairingRegistry, createPrProvenanceReconciler, createReconnectLogGate, createScopeTokenRegistry, createSupervisorTaskGateRunner, createTaskLedger, createWorkspaceBrainSubscriber, createWorkspaceBrains, daemonRegistryDir, decideWorktreeIsolation, declaredPresetToProviderPreset, defineBraveSearchHttpDriver, defineSerperHttpDriver, deriveSessionUsage, enrichRollupWithProviderQuota, enrichWithRemainingQuota, evaluateCostBudget, filterActivities, findConversationRecord, findNativeMode, formatToolCall, formatToolResult, getDefaultHarnessPreset, getHarnessPreset, getMcpCredentialDeps, harnessPresetsPath, isAgentCliAuthConfigured, isClosedTaskStatus, isSafeBucketSlug, isTerminalActivityState, listBuckets, listHarnessPresets, listPresets, loadHarnessPresets, loadQuotaStore, loadWorktreeIsolation, locateConversationByNativePath, locateConversationBySessionId, makeBrowserAdapterLister, makeSandboxCredsStore, makeSandboxResolver, migrateLegacySessionsFile, migrationMarkerPath, monitorPolicyWait, monitorSessionWait, narrowOrchestratorTools, normalizeInbound, normalizeModeId, normalizeWorktreeField, parseAnthropicRateLimitHeaders, parseTaskStatus, parseWindow, parseWorktreeIsolationMode, policyToActivities, policyWatchesSession, prToActivities, projectSessionUsage, readConversationIndex, readDaemonRegistry, readProfileQuota, readRegisteredSlugs, readRuntimeMeta, readSandboxLedger, readSessionForBrain, readUsageSnapshots, reapGcEntry, reapOrphanedDescendants, recordProfileQuota, recordSandboxBoot, recordSandboxLiveness, recordSandboxOrigin, recordSandboxState, registerBrainTools, registerBuiltinRoutes, registerPairingTools, registerSandboxAttachTool, registerWebSearchTools, removeHarnessPreset, removeSandboxLedgerEntry, resolveBucketSlug, resolveNativeLink, resolvePosture, resolveReuseFromLedger, rollupUsage, runCrashDetectPass, runEagerResumePass, runIdleReapPass, runStallWatchdogPass, sandboxLedgerPath, setDefaultPreset, setMcpCredentialDeps, sweepStaleDaemonRegistry, sweepStaleRuntimeMetas, toWorktreeStatusView, turnToActivities, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, updateHarnessPreset, upsertSandboxLedger, verifyInboundSignature, webSearchTool, workflowToActivities, writeDaemonRegistryEntry };
11743
+ export { type ActivityKind, type ActivityListFilter, type ActivityPolicyLister, type ActivityProjector, type ActivityProjectorRegistry, type ActivityProjectorSession, type ActivityRecord, type ActivitySource, type ActivityState, type ActivityWaitingOn, type ActivityWorkflowLister, AdapterAuthDescriptor, type AdapterCapabilitiesLister, type AdapterInstallResult, type AdapterListEntry, type AgentAdapterInstaller, type AgentAdapterLister, type AgentAdapterResolver, type AgentSessionLike, type AgentStreamEvent, type AnthropicReaderConfig, AnthropicRemainingQuotaReader, type AttachPolicyInput, type AttachSandboxOpts, type AttachSandboxResult, AuthMethod, BUCKETS_ROOT, type BranchGcClass, type BranchGcCoverageView, type BranchGcHoldReason, type BranchGcKind, type BranchGcOutcomeView, type BranchGcPlanEntryView, type BranchGcPlanView, type BranchGcReclaimReason, type BranchGcResult, type BranchGcRunInput, type BranchGcRunner, type BranchGcStatus, type BranchGcSummaryView, type BranchGcTriageVerdict, type BranchGcVerdictInput, type BranchGcVerdictLookupInput, type BranchGcVerdictReader, type BranchGcVerdictRecordView, type BranchGcVerdictRecorder, type BrowserAdapterHandle, type BrowserAdapterInfo, type BrowserAdapterLister, type BrowserAdapterResolver, BuildHeartbeatAgent, CanonicalPosture, type CatalogModelsLister, CatalogModelsQuery, CatalogModelsResponse, type CatalogProviderModel, type CatalogProviderModelsQuery, type CatalogProviderModelsResponse, type CatalogProviderPricing, type ClaudeNativeRef, type CommitSpec, type CompletionPolicySupervisor, ContextProfile, type ConversationIndexRecord, type ConversationNativeRef, type CostBudgetDecision, type CrashDetectSummary, type CrashReaperRegistry, type CreateGatewayOptions, type CreateOfferInput, type CreateReviewRunnerOptions, type CreatedOffer, DEAD_SESSION_STATUSES, DEFAULT_ALWAYS_ON_TOOLS, DEFAULT_BUCKET, DEFAULT_ORCHESTRATOR_TOOLS, DEFAULT_WORKTREE_ISOLATION, type DaemonReviewerHostDeps, DeclaredAdapterOption, type DeclaredAdapterPreset, DefaultsAdapterAuthConfig, type EagerResumeFailReason, type EagerResumeOutcome, type EagerResumeSkipReason, type EagerResumeSummary, EffortLevel, type EnableInput, type EnableResult, GC_REAPABLE_STATES, type GateSpec, type GatewayHandle, type GhRunner, type HarnessPreset, type HarnessPresetValidationDeps, HarnessPresetValidationError, type HarnessPresetsFile, type HermesNativeRef, INBOUND_PROVIDERS, type IdleReapSummary, type IdleReaperRegistry, type InboundEndpoint, type InboundEndpointStore, type InboundEndpointStoreOptions, type InboundEnqueuePrompt, type InboundIsSessionAlive, type InboundMessage, type InboundProvider, type InboundRestartSession, type InboundRouteMode, type InboundRouterDeps, type InboundRouterLog, type InboundSpawnForContact, type InboundWatcher, type JudgeGateSpec, LEGACY_SESSIONS_FILE, type LedgerAnnotations, type LedgerEntry, type LedgerHostMeta, type LocatedConversation, type LocatedConversationByPath, MAX_MESSAGE_DATA_BYTES, MCP_APP_BRIDGE_SCRIPT, MESSAGE_KINDS, MESSAGE_PREAMBLE, MESSAGE_TAG, MESSAGE_URGENCIES, type McpCredentialDeps, type MessageDeliveryVia, type MessageFilter, type MessageKind, type MessageRelation, type MessageTreeNode, type MessageUrgency, type MigrationMarker, type NormalizeInboundResult, type OnFailSpec, type OpenPrResolver, type OrchestratorGatewayDeps, type OrchestratorInjection, type OrchestratorInjector, type OrchestratorInjectorDeps, type OrchestratorMcpServerFactory, type OrchestratorScope, type OrphanReaperRegistry, PAIRINGS_VERSION, POSTURE_NATIVE_ALIASES, POSTURE_PREAMBLES, type PairingChannelContext, type PairingChannelHandle, type PairingChannelMode, type PairingRecord, type PairingRegistry, type PairingRegistryDeps, type PendingPermission, type PermissionRespondInput, type PermissionRespondResult, type PolicyRunState, type PolicyRunStatus, Posture, type PostureResolution, PrLookupError, type PrLookupErrorCode, type PrResolvedState, type PrStateResolver, type PrStatusSnapshot, type PresetInfo, type PricingResolver, type QuotaReadableProfile, type QuotaStoreFile, type QuotaStoreOptions, type ReconnectLogGate, type RegisterBrainToolsOptions, type RegisterBrowserInput, type RegisterPairingToolsOptions, type RegisterReviewToolsOptions, type RegisterSessionInput, type RegisterWebSearchToolsOptions, type RemainingQuota, type RemainingQuotaReader, type RemoteStatus, type ResolveNativeLinkInput, ResolvedAuthSpec, ResumeDisabledError, type ReuseResolution, type ReviewLedger, type ReviewLedgerFilter, type ReviewRun, type ReviewRunInput, type ReviewRunStatus, type ReviewRunner, type ReviewerRunResult, type ReviewerSessionHost, type RouteAwareLaunchConfig, type RouteAwareLaunchConfigInput, RouteSpec, type RuntimeMeta, SESSION_END_REASONS, SESSION_ID_ENV, type SandboxAdapterInfo, type SandboxConnectionDescriptor, type SandboxGcCandidate, type SandboxGcProviderHandle, type SandboxGcReapDeps, type SandboxGcReapResult, type SandboxLedgerEntry, type SandboxLedgerState, type SandboxProviderCapabilities, type SandboxProviderHandle, type SandboxProviderLister, type SandboxProviderResolver, type SandboxReconcileDeps, type SandboxReconcileProviderHandle, type SandboxReconcileResult, type SandboxReconcileRow, type SandboxReconcileVerdict, type ScopeTokenRegistry, SessionConfig, type SessionCurrentPhase, type SessionDescriptor, type SessionEndReason, type SessionKind, type SessionMessage, type SessionMessageFrom, type SessionMessageSentRecord, type SessionObserver, type SessionOutcome, type SessionOutcomeArtifact, type SessionOutcomeCompact, type SessionOutcomeLink, type SessionSnapshots, type SessionStatus, type SessionUsage, type SessionWaitEvent, type SessionWaitResult, type SessionsRegistry, type ShellGateSpec, type SpawnAgentInput, SpawnDefaultsConfig, type SpawnSessionInput, type StallWatchdogRegistry, type StallWatchdogSummary, type StoredProfileQuota, TASK_STATUSES, type TaskCaller, type TaskChangedEvent, type TaskClaimInput, type TaskCreateInput, type TaskGateOutcome, type TaskGateRunner, type TaskLedger, type TaskLedgerRegistry, type TaskLedgerSessionSlice, type TaskListFilter, type TaskRecord, type TaskStatus, type TaskUpdateInput, type TaskVerification, type TaskVerifySupervisor, type TaskWriteResult, type TokenPricing, type TransmitterBinding, type TransmitterBindingStore, type TunnelDescriptor, type TunnelProvider, type TunnelStatus, type UsageBucket, type UsageComputeInput, type UsageRollup, type UsageSnapshotRecord, type UsageSource, UserPreset, WORKSPACE_SLUG_ENV, WORKTREE_ISOLATION_ENV, type WatcherDescriptor, type WatcherStartInput, type WebSearchInput, type WebSearchOutput, type WebSearchResult, type WorkspaceBrainSubscriber, type WorkspaceBrainSubscriberOptions, type WorkspaceBrains, WorkspaceFs, type WorktreeAutoReclaimer, type WorktreeDecision, type WorktreeField, type WorktreeGcClass, type WorktreeGcOutcomeView, type WorktreeGcPlanEntryView, type WorktreeGcReclaimReason, type WorktreeGcResult, type WorktreeGcRunInput, type WorktreeGcRunner, WorktreeIsolationMode, type WorktreeProvisionOutcome, type WorktreeProvisionRequest, type WorktreeProvisioner, type WorktreeRequest, type WorktreeStatusLister, type WorktreeStatusView, activityCounts, addHarnessPreset, appendConversationRecord, attachSandbox, bucketDir, bucketSessionsFile, bucketTranscriptDir, buildCatalogProviderModels, buildMcpConfigSnippet, buildRouteAwareLaunchConfig, canonicalForModeId, claudeProjectSlug, collectGcCandidates, compactOutcome, composeSessionObservers, conversationIndexPath, createActivityProjector, createDaemonReviewerHost, createFileStepCache, createGateway, createInboundEndpointStore, createOrchestratorInjector, createOrchestratorMcpServerFactory, createPairingRegistry, createPrProvenanceReconciler, createReconnectLogGate, createReviewLaneExecutor, createReviewLedger, createReviewRunner, createScopeTokenRegistry, createSessionMessage, createSupervisorTaskGateRunner, createTaskLedger, createWorkspaceBrainSubscriber, createWorkspaceBrains, daemonRegistryDir, decideWorktreeIsolation, declaredPresetToProviderPreset, defaultReviewLedgerRoot, defineBraveSearchHttpDriver, defineSerperHttpDriver, deriveSessionOutcome, deriveSessionUsage, enrichRollupWithProviderQuota, enrichWithRemainingQuota, escapeHumanPrompt, escapeMessageBody, evaluateCostBudget, execGh, fetchPrStatus, filterActivities, findConversationRecord, findNativeMode, findPrForCommit, formatToolCall, formatToolResult, getDefaultHarnessPreset, getHarnessPreset, getMcpCredentialDeps, githubRepoOf, harnessPresetsPath, hostPlaceholders, injectMcpAppBridge, isAgentCliAuthConfigured, isClosedTaskStatus, isKnownSessionEndReason, isMessageAllowed, isProviderLimitError, isSafeBucketSlug, isTerminalActivityState, listBuckets, listHarnessPresets, listPresets, loadHarnessPresets, loadQuotaStore, loadWorktreeIsolation, locateConversationByNativePath, locateConversationBySessionId, makeBrowserAdapterLister, makeSandboxCredsStore, makeSandboxResolver, matchesMessageFilter, messageFrom, migrateLegacySessionsFile, migrationMarkerPath, mintMessageId, monitorPolicyWait, monitorSessionWait, narrowOrchestratorTools, normalizeInbound, normalizeModeId, normalizeRemote, normalizeWorktreeField, parseAnthropicRateLimitHeaders, parsePrUrl, parseTaskStatus, parseWindow, parseWorktreeIsolationMode, policyToActivities, policyWatchesSession, prHeadSha, prToActivities, projectSessionUsage, readConversationIndex, readDaemonRegistry, readProfileQuota, readRegisteredSlugs, readRuntimeMeta, readSandboxLedger, readSessionForBrain, readUsageSnapshots, reapGcEntry, reapOrphanedDescendants, reconcileSandboxLedger, recordProfileQuota, recordSandboxBoot, recordSandboxLiveness, recordSandboxOrigin, recordSandboxState, registerBrainTools, registerBuiltinRoutes, registerPairingTools, registerReviewTools, registerSandboxAttachTool, registerWebSearchTools, removeHarnessPreset, removeSandboxLedgerEntry, renderInboxDigest, renderSessionMessage, renderSessionMessages, repoSlug, resolveBucketSlug, resolveNativeLink, resolvePosture, resolveRelation, resolveReuseFromLedger, resolveReviewerPreset, rollupUsage, runCrashDetectPass, runEagerResumePass, runIdleReapPass, runShellLane, runStallWatchdogPass, sandboxLedgerPath, sanitizeLabel, setDefaultPreset, setMcpCredentialDeps, sweepStaleDaemonRegistry, sweepStaleRuntimeMetas, toWorktreeStatusView, turnToActivities, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, updateHarnessPreset, upsertSandboxLedger, verifyInboundSignature, webSearchTool, withPr, withPrStatus, workflowToActivities, writeDaemonRegistryEntry };