@agentproto/runtime 0.3.0 → 0.4.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
@@ -8,6 +8,7 @@ import { AdapterHandle, AdapterLister } from '@agentproto/adapter-kit';
8
8
  import { WorkspaceFs } from './workspace-fs.js';
9
9
  export { createWorkspaceFs } from './workspace-fs.js';
10
10
  export { ConversationMeta, ConversationStore, ConversationTurn, fileConversationStore } from './conversations.js';
11
+ import { StepCache } from '@agentproto/workflow-runtime';
11
12
  export { PROVIDER_ENV_VARS, ProviderEntry, ProvidersFile, injectProviderKeysIntoEnv, loadProviders, providerEnvVar, providersPath, removeProviderKey, setProviderKey } from './providers-store.js';
12
13
 
13
14
  /**
@@ -15,23 +16,48 @@ export { PROVIDER_ENV_VARS, ProviderEntry, ProvidersFile, injectProviderKeysInto
15
16
  * RuntimeEvents (global daemon bus) — session events are scoped to
16
17
  * individual sessions and fire at turn-level granularity.
17
18
  *
18
- * Consumers: EventRing (poll_events cursor), WebhookNotifier
19
+ * Consumers: EventRing (session_events_poll cursor), WebhookNotifier
19
20
  * (fire-and-forget HTTP), RoutineRunner (state machine fan-in),
20
- * and wait_for_any MCP tool (long-poll multiplexed).
21
+ * and session_monitor MCP tool (long-poll multiplexed).
21
22
  */
22
- type SessionEventType = "session:turn-end" | "session:awaiting-input" | "session:exited" | "session:command-done" | "policy:passed" | "policy:failed" | "policy:commit-ready" | "policy:committed";
23
+ type SessionEventType = "session:turn-end" | "session:awaiting-input" | "session:exited" | "session:command-done" | "policy:passed" | "policy:failed" | "policy:commit-ready" | "policy:committed" | "cron:fired" | "cron:succeeded" | "cron:failed";
24
+ /**
25
+ * Structured detail on why a session is awaiting input, when derivable.
26
+ * `source: "structured"` — a driver-reported ACP-style prompt (e.g. a tool
27
+ * permission request with real options). `source: "heuristic"` — a
28
+ * best-effort guess from the tail of the transcript (trailing "?" plus
29
+ * an optional enumerated option list) for drivers that don't report
30
+ * structured prompts. Absent entirely when neither could be determined —
31
+ * callers still have the plain `awaitingInput` boolean in that case.
32
+ */
33
+ interface SessionAwaitingQuestion {
34
+ text: string;
35
+ options?: string[];
36
+ source: "structured" | "heuristic";
37
+ }
23
38
  interface SessionTurnEndEvent {
24
39
  type: "session:turn-end";
25
40
  sessionId: string;
26
41
  awaitingInput: boolean;
27
42
  label?: string;
28
43
  ts: string;
44
+ question?: SessionAwaitingQuestion;
45
+ /**
46
+ * The adapter's stream `turn-end` reason, when the driver reports one
47
+ * (e.g. `"completed"`, `"cancelled"`, `"max_turns"`, or
48
+ * `"watchdog-timeout"` when the ACP client's turn-idle watchdog fired
49
+ * because the adapter went silent — see
50
+ * `@agentproto/acp/client`'s `AcpClientOptions.turnIdleTimeoutMs`).
51
+ * Absent for drivers that don't report a reason.
52
+ */
53
+ reason?: string;
29
54
  }
30
55
  interface SessionAwaitingInputEvent {
31
56
  type: "session:awaiting-input";
32
57
  sessionId: string;
33
58
  label?: string;
34
59
  ts: string;
60
+ question?: SessionAwaitingQuestion;
35
61
  }
36
62
  interface SessionExitedEvent {
37
63
  type: "session:exited";
@@ -41,8 +67,8 @@ interface SessionExitedEvent {
41
67
  label?: string;
42
68
  ts: string;
43
69
  }
44
- /** Emitted when execute_command finishes. commandId matches the id
45
- * returned by the execute_command MCP tool. */
70
+ /** Emitted when command_execute finishes. commandId matches the id
71
+ * returned by the command_execute MCP tool. */
46
72
  interface SessionCommandDoneEvent {
47
73
  type: "session:command-done";
48
74
  sessionId: string;
@@ -68,7 +94,7 @@ interface PolicyFailedEvent {
68
94
  /**
69
95
  * Emitted by the supervisor (WP5) when a `then:"commit"` policy's gate passes
70
96
  * but `requireHumanAck` is set: the commit is staged-and-ready but NOT yet
71
- * executed. Carries the exact paths + message that `ack_policy(approve:true)`
97
+ * executed. Carries the exact paths + message that `policy_ack(approve:true)`
72
98
  * will commit. The policy sits in `awaiting-ack` until acked.
73
99
  */
74
100
  interface PolicyCommitReadyEvent {
@@ -81,7 +107,7 @@ interface PolicyCommitReadyEvent {
81
107
  }
82
108
  /** Emitted by the supervisor (WP5) when a `then:"commit"` policy has actually
83
109
  * committed — either directly (requireHumanAck:false) or after an approving
84
- * `ack_policy`. Carries the resulting commit sha. */
110
+ * `policy_ack`. Carries the resulting commit sha. */
85
111
  interface PolicyCommittedEvent {
86
112
  type: "policy:committed";
87
113
  policyId: string;
@@ -91,7 +117,30 @@ interface PolicyCommittedEvent {
91
117
  message: string;
92
118
  ts: string;
93
119
  }
94
- type SessionEvent = SessionTurnEndEvent | SessionAwaitingInputEvent | SessionExitedEvent | SessionCommandDoneEvent | PolicyPassedEvent | PolicyFailedEvent | PolicyCommitReadyEvent | PolicyCommittedEvent;
120
+ /** Emitted by CronScheduler when a job fires (before the action runs). */
121
+ interface CronFiredEvent {
122
+ type: "cron:fired";
123
+ jobId: string;
124
+ label?: string;
125
+ ts: string;
126
+ }
127
+ /** Emitted by CronScheduler when a job action completes successfully. */
128
+ interface CronSucceededEvent {
129
+ type: "cron:succeeded";
130
+ jobId: string;
131
+ label?: string;
132
+ summary: string;
133
+ ts: string;
134
+ }
135
+ /** Emitted by CronScheduler when a job action fails. */
136
+ interface CronFailedEvent {
137
+ type: "cron:failed";
138
+ jobId: string;
139
+ label?: string;
140
+ error: string;
141
+ ts: string;
142
+ }
143
+ type SessionEvent = SessionTurnEndEvent | SessionAwaitingInputEvent | SessionExitedEvent | SessionCommandDoneEvent | PolicyPassedEvent | PolicyFailedEvent | PolicyCommitReadyEvent | PolicyCommittedEvent | CronFiredEvent | CronSucceededEvent | CronFailedEvent;
95
144
  interface SessionEventBus {
96
145
  emit(ev: SessionEvent): void;
97
146
  /** Subscribe to a specific event type. Returns an unsubscribe fn. */
@@ -132,6 +181,12 @@ interface SessionEventBus {
132
181
  */
133
182
  interface AgentSessionLike {
134
183
  sessionId: string;
184
+ /** OS-level process id of the spawned child, when the driver owns a
185
+ * real subprocess. Mirrored onto `SessionDescriptor.pid` at
186
+ * `spawnAgent` time so `processAlive` can be computed without
187
+ * adapter-specific forensics. Undefined for drivers that don't
188
+ * expose a process (e.g. a future non-subprocess transport). */
189
+ pid?: number;
135
190
  send(message: unknown): AsyncIterable<AgentStreamEvent>;
136
191
  cancel(): Promise<void>;
137
192
  close(): Promise<void>;
@@ -165,7 +220,15 @@ type PtyFactory = (opts: PtyFactoryOptions) => PtyProcess;
165
220
  interface AgentStreamEvent {
166
221
  kind: string;
167
222
  text?: string;
223
+ /** Correlates a "tool-call" with its later "tool-result" (and an
224
+ * "agent-prompt" with the permission it's asking about) — see
225
+ * @agentproto/acp's `StreamEvent`. */
226
+ toolCallId?: string;
168
227
  toolName?: string;
228
+ /** Tool-call input, e.g. an ACP `tool_call`'s `arguments` — see @agentproto/acp's `StreamEvent`. */
229
+ arguments?: unknown;
230
+ /** Tool-call output, e.g. an ACP `tool_call_update`'s `result` — see @agentproto/acp's `StreamEvent`. */
231
+ result?: unknown;
169
232
  isError?: boolean;
170
233
  reason?: string;
171
234
  error?: {
@@ -173,6 +236,30 @@ interface AgentStreamEvent {
173
236
  code?: number;
174
237
  data?: unknown;
175
238
  };
239
+ /** Structured options offered by an "agent-prompt" event (e.g. an ACP
240
+ * `requestPermission` callback surfaced as a clarifying question rather
241
+ * than auto-answered). Typed `unknown` — like `@agentproto/acp`'s own
242
+ * `StreamEvent["options"]` — so this structural type stays assignable
243
+ * from any driver's runtime session without a hard dependency on its
244
+ * exact option shape (`{optionId,name,kind}` for ACP, or whatever a
245
+ * future driver reports); `normalizeAgentPromptOptions` narrows it
246
+ * defensively at the one place it's consumed. */
247
+ options?: unknown;
248
+ /** "plan" event entries — see @agentproto/acp's `StreamEvent`'s `plan` kind. */
249
+ entries?: Array<{
250
+ content: string;
251
+ priority: string;
252
+ status: string;
253
+ }>;
254
+ /** "usage_update" context-window size (tokens). */
255
+ size?: number;
256
+ /** "usage_update" tokens currently in context. */
257
+ used?: number;
258
+ /** "usage_update" cumulative session cost, when the adapter reports one. */
259
+ cost?: {
260
+ amount: number;
261
+ currency: string;
262
+ };
176
263
  }
177
264
  type SessionKind = "terminal" | "agent-cli" | "command" | "browser";
178
265
  type SessionStatus = "starting" | "running" | "exited" | "killed" | "error";
@@ -191,6 +278,19 @@ interface SessionDescriptor {
191
278
  /** Last time anything was written to stdout/stderr. Lets the UI
192
279
  * spot stuck sessions ("running for 2h, last output 12min ago"). */
193
280
  lastOutputAt?: string;
281
+ /** Last time ANY adapter-process activity was observed — ACP
282
+ * JSON-RPC traffic, protocol-level events, not just ring-buffer
283
+ * output lines. Stays current during long tool-call chains where
284
+ * `lastOutputAt` goes stale. Updated on incoming session/update
285
+ * notifications AND on outbound RPC calls. ISO 8601. */
286
+ lastActivityAt?: string;
287
+ /** Whether the underlying OS process is still alive. Computed via
288
+ * `process.kill(pid, 0)` at read time (list()/get()) — cheap,
289
+ * zero-overhead, standard POSIX check. Absent when `pid` is null
290
+ * (no process to check). Never persisted — recomputed fresh on
291
+ * every read since it's a live OS query, stale the instant it's
292
+ * written to disk. */
293
+ processAlive?: boolean;
194
294
  /** Free-text label the spawner can attach (e.g. conversation id,
195
295
  * operator name) so the UI can group/filter. */
196
296
  label?: string;
@@ -249,17 +349,28 @@ interface SessionDescriptor {
249
349
  resumeMetadata?: Record<string, string>;
250
350
  /** Set by the orchestration layer when the agent emits an
251
351
  * "awaiting-input" turn-end. Cleared on the next turn start.
252
- * Used by `wait_for_any` to fast-return without subscribing. */
352
+ * Used by `session_monitor` to fast-return without subscribing. */
253
353
  awaitingInput?: boolean;
354
+ /**
355
+ * Structured detail on WHY the session is awaiting input, when it can be
356
+ * determined — lets an orchestrator distinguish "blocked on a real
357
+ * question" from "just finished a turn" without re-reading raw output.
358
+ * `source: "structured"` comes from a driver-reported ACP-style prompt
359
+ * (`AgentStreamEvent.options`, e.g. a tool permission request);
360
+ * `source: "heuristic"` is a best-effort guess from the tail of the
361
+ * transcript (trailing "?" + an optional enumerated option list) for
362
+ * drivers that don't report structured prompts. Cleared alongside
363
+ * `awaitingInput` on the next turn start. */
364
+ awaitingQuestion?: SessionAwaitingQuestion;
254
365
  /** Count of turns that have fully completed (turn-end emitted) on this
255
- * session. Lets `wait_for_any` fast-return for a session that already
366
+ * session. Lets `session_monitor` fast-return for a session that already
256
367
  * finished its turn before the wait subscribed — a fast turn that ends
257
368
  * in "completed" (not "awaiting-input") leaves no other persisted
258
369
  * signal (status stays "running", busy clears). 0 = never ran a turn,
259
370
  * so a freshly-spawned idle session is NOT mistaken for done. */
260
371
  turnsCompleted?: number;
261
372
  /** True while a turn is actively running (mirror of the internal
262
- * runtime `busy` flag). Lets `wait_for_any` distinguish "idle after a
373
+ * runtime `busy` flag). Lets `session_monitor` distinguish "idle after a
263
374
  * finished turn" from "mid-turn" so it doesn't fast-return a stale
264
375
  * turn-end while the NEXT turn is still generating. */
265
376
  busy?: boolean;
@@ -312,19 +423,29 @@ interface SessionsRegistry {
312
423
  * `kill()` best-effort. */
313
424
  registerBrowser(input: RegisterBrowserInput): SessionDescriptor;
314
425
  /** Send a follow-up turn to a live agent session. Throws when the
315
- * session is missing, not an agent-cli kind, or busy. The events
316
- * stream into the existing ring buffer + line emitter so /stream
317
- * consumers see them as they arrive. */
426
+ * session is missing, not an agent-cli kind, dead (exited/killed/
427
+ * error and unresumable `SessionNotAliveError`), or busy
428
+ * (mid-turn). The events stream into the existing ring buffer +
429
+ * line emitter so /stream consumers see them as they arrive. */
318
430
  sendPrompt(id: string, message: unknown): Promise<void>;
319
- /** Fire-and-forget variant of `sendPrompt`. Validates the same
320
- * preconditions synchronously throws on missing session / wrong
321
- * kind / busy but returns immediately after the turn is started,
322
- * letting the caller stream output via the SSE endpoint instead of
323
- * blocking on the full turn. Async errors during the turn are
324
- * pushed into the ring buffer as `[error]` lines so `/stream`
325
- * subscribers see them. Used by the web UI's chat input where a
326
- * long turn would otherwise freeze the textbox. */
327
- enqueuePrompt(id: string, message: unknown): void;
431
+ /** Fire-and-forget variant of `sendPrompt` for the TURN ITSELF only.
432
+ * Admission (resume attempt + the missing/wrong-kind/dead/busy
433
+ * checks `sendPrompt` throws) is AWAITED before this resolves, so a
434
+ * dead or busy session rejects instead of silently reporting
435
+ * success only resolves once the turn has actually started.
436
+ * Errors during the turn's own execution (network drop, child died
437
+ * mid-turn) are pushed into the ring buffer as `[error]` lines so
438
+ * `/stream` subscribers see them. Used by the web UI's chat input
439
+ * and MCP `agent_prompt`, where a long turn would otherwise freeze
440
+ * the caller. */
441
+ enqueuePrompt(id: string, message: unknown): Promise<void>;
442
+ /** Stamp `lastActivityAt` on a live agent-cli session's descriptor
443
+ * and schedule a debounced persist. Called from the `onActivity`
444
+ * callback threaded down through the driver → ACP client, which
445
+ * fires on ANY adapter-process traffic (not just ring-buffer
446
+ * output) — see `SessionDescriptor.lastActivityAt`. No-op when the
447
+ * id is unknown (session already forgotten). */
448
+ pulseActivity(id: string): void;
328
449
  list(): SessionDescriptor[];
329
450
  get(id: string): SessionDescriptor | undefined;
330
451
  /** Subscribe to a session's output. Returns an unsubscribe fn.
@@ -499,7 +620,7 @@ interface SpawnPtyInput {
499
620
  * for fs/exec/agent-cli.
500
621
  *
501
622
  * Five tools:
502
- * list_adapter_browsers discover available browser adapter ids + metadata
623
+ * browser_adapter_list discover available browser adapter ids + metadata
503
624
  * start_browser ensure a browser adapter is up, register as a session
504
625
  * stop_browser kill a running browser session
505
626
  * list_browsers browse alive/recent browser sessions
@@ -575,7 +696,7 @@ type BrowserAdapterLister = () => {
575
696
  config?: unknown[];
576
697
  }[];
577
698
  /**
578
- * Family-specific descriptor surfaced in the kit-path `list_adapter_browsers`
699
+ * Family-specific descriptor surfaced in the kit-path `browser_adapter_list`
579
700
  * response. Fields match the legacy lister shape so the tool output is
580
701
  * unchanged regardless of which path is active.
581
702
  */
@@ -782,9 +903,30 @@ declare class TunnelRegistry {
782
903
  private loadFromDisk;
783
904
  }
784
905
 
906
+ /**
907
+ * Fire-and-forget webhook notifier for session lifecycle events.
908
+ *
909
+ * Each session can register its own `notifyUrl` (via `agent_start`).
910
+ * A global URL can be set via `AGENTPROTO_NOTIFY_URL` env var or
911
+ * `~/.agentproto/notify.json` (env wins). Both are POSTed when an event
912
+ * fires — the union of per-session + global URLs, deduplicated.
913
+ *
914
+ * Retry policy: one retry after 2 s on network error. No retry on 4xx/5xx.
915
+ * Timeout: 10 s per attempt. All errors are swallowed — the notifier never
916
+ * throws into the session's hot path.
917
+ */
918
+
919
+ interface WebhookNotifier {
920
+ /** Register a per-session URL (called from agent_start). */
921
+ register(sessionId: string, url: string): void;
922
+ unregister(sessionId: string): void;
923
+ /** Handler to wire into SessionEventBus.onAny. Fire-and-forget. */
924
+ onSessionEvent(ev: SessionEvent): void;
925
+ }
926
+
785
927
  /**
786
928
  * Cursor-based ring buffer for session events. Bridges the in-process
787
- * SessionEventBus (push) to the poll_events MCP tool (pull).
929
+ * SessionEventBus (push) to the session_events_poll MCP tool (pull).
788
930
  *
789
931
  * Capacity default: 1 000 events. Oldest entry dropped on overflow —
790
932
  * callers that lag beyond the cap see a gap in their cursor but never
@@ -845,9 +987,9 @@ interface EventRing {
845
987
  *
846
988
  * Trigger: session:turn-end on the watched session(s).
847
989
  * Gate: optional shell command via the same allowlist + cwd-anchor as
848
- * execute_command. exit 0 = pass.
990
+ * command_execute. exit 0 = pass.
849
991
  * Action (then:"emit"): emits policy:passed / policy:failed on the bus
850
- * (readable via poll_events).
992
+ * (readable via session_events_poll).
851
993
  *
852
994
  * WP5 (then:"commit"): on a GREEN gate, prepare a host-side git commit in the
853
995
  * watched session's cwd — `git add -- <explicit paths>` then `git commit -m
@@ -859,7 +1001,7 @@ interface EventRing {
859
1001
  * "nothing to commit" is treated as SUCCESS (idempotent re-commit after a
860
1002
  * restart). `requireHumanAck` defaults to TRUE: the gate-green transition goes
861
1003
  * to `awaiting-ack` and emits `policy:commit-ready` (paths + message) instead of
862
- * committing; the commit runs only on `ack_policy(approve:true)` →
1004
+ * committing; the commit runs only on `policy_ack(approve:true)` →
863
1005
  * `policy:committed` (+ sha) → done; `approve:false` → cancelled. With
864
1006
  * `requireHumanAck:false` the commit runs directly at the green gate.
865
1007
  * onFail (WP2): when gate fails, re-prompts the session(s) (sendPrompt) up to
@@ -913,7 +1055,10 @@ interface ShellGateSpec {
913
1055
  command: string;
914
1056
  args?: string[];
915
1057
  /** Working directory for the gate command. Defaults to the watched
916
- * session's cwd, anchored to the workspace. */
1058
+ * session's own cwd (trusted as-is it was already an explicit,
1059
+ * vetted choice made at `agent_start` time). When given explicitly,
1060
+ * anchored to that session's cwd (not the daemon's boot workspace)
1061
+ * so a stray/injected value can't escape it. */
917
1062
  cwd?: string;
918
1063
  timeoutMs?: number;
919
1064
  }
@@ -970,7 +1115,7 @@ interface CommitSpec {
970
1115
  /**
971
1116
  * Human-in-the-loop gate before the commit actually runs. Default TRUE:
972
1117
  * the green gate transitions to `awaiting-ack` + emits `policy:commit-ready`
973
- * and waits for `ack_policy`. Set false to commit directly at the green gate.
1118
+ * and waits for `policy_ack`. Set false to commit directly at the green gate.
974
1119
  */
975
1120
  requireHumanAck?: boolean;
976
1121
  }
@@ -1073,6 +1218,15 @@ interface CompletionPolicySupervisor {
1073
1218
  */
1074
1219
  ack(policyId: string, approve: boolean): Promise<PolicyRunState | undefined>;
1075
1220
  list(): PolicyRunState[];
1221
+ /**
1222
+ * Subscribe to policy settlements — fired whenever a policy transitions
1223
+ * into a terminal state (done / blocked / cancelled) OR into awaiting-ack.
1224
+ * Used by the `/policies/:id/wait` long-poll to block until a policy
1225
+ * resolves without reimplementing the state machine's event surface.
1226
+ * Returns an unsubscribe fn. The callback receives the policyId; read
1227
+ * the full state via `getStatus(policyId)`.
1228
+ */
1229
+ onSettle(cb: (policyId: string) => void): () => void;
1076
1230
  /**
1077
1231
  * Sync flush of the policy snapshot to disk. Call from the daemon
1078
1232
  * stop() path — mirrors sessions.shutdown().
@@ -1080,34 +1234,13 @@ interface CompletionPolicySupervisor {
1080
1234
  shutdown(): void;
1081
1235
  }
1082
1236
 
1083
- /**
1084
- * Fire-and-forget webhook notifier for session lifecycle events.
1085
- *
1086
- * Each session can register its own `notifyUrl` (via `start_agent_session`).
1087
- * A global URL can be set via `AGENTPROTO_NOTIFY_URL` env var or
1088
- * `~/.agentproto/notify.json` (env wins). Both are POSTed when an event
1089
- * fires — the union of per-session + global URLs, deduplicated.
1090
- *
1091
- * Retry policy: one retry after 2 s on network error. No retry on 4xx/5xx.
1092
- * Timeout: 10 s per attempt. All errors are swallowed — the notifier never
1093
- * throws into the session's hot path.
1094
- */
1095
-
1096
- interface WebhookNotifier {
1097
- /** Register a per-session URL (called from start_agent_session). */
1098
- register(sessionId: string, url: string): void;
1099
- unregister(sessionId: string): void;
1100
- /** Handler to wire into SessionEventBus.onAny. Fire-and-forget. */
1101
- onSessionEvent(ev: SessionEvent): void;
1102
- }
1103
-
1104
1237
  /**
1105
1238
  * Orchestrator sub-gateway (ADR §4.2 / WP2) — the security primitive
1106
1239
  * that lets a spawned agent become a *scoped* orchestrator without
1107
1240
  * handing it the daemon's full toolset.
1108
1241
  *
1109
- * The plain `/mcp` endpoint registers EVERY tool (execute_command,
1110
- * fs_*, remote_*, import_mcp, terminals, driver CRUD) and bypasses auth
1242
+ * The plain `/mcp` endpoint registers EVERY tool (command_execute,
1243
+ * fs_*, remote_*, mcp_import, terminals, driver CRUD) and bypasses auth
1111
1244
  * on loopback — pointing a child at it would be a privilege handout.
1112
1245
  * This module builds a second, scoped MCP server that registers ONLY a
1113
1246
  * curated orchestration allowlist, gated by an unguessable per-child
@@ -1126,21 +1259,20 @@ interface WebhookNotifier {
1126
1259
  * The curated set of tools a scoped child orchestrator may call.
1127
1260
  *
1128
1261
  * INCLUDED — the orchestration primitives:
1129
- * start_agent_session, prompt_agent_session, get_agent_session_output,
1130
- * wait_for_any, poll_events (spawn + drive + fan-in)
1131
- * list_sessions, list_agent_sessions, kill_agent_session (observe +
1132
- * halt). NB: list/kill are NOT yet scoped to the child's own
1133
- * subtree — that subtree filter is WP4 (parentSessionId/depth). For
1134
- * WP2 they are exposed daemon-wide; documented debt.
1262
+ * agent_start, agent_prompt, agent_output, agent_kill,
1263
+ * session_monitor, session_events_poll (spawn + drive + fan-in)
1264
+ * session_list, session_tree (observe). NB: list is NOT yet scoped
1265
+ * to the child's own subtree that subtree filter is WP4
1266
+ * (parentSessionId/depth). For WP2 it is exposed daemon-wide;
1267
+ * documented debt.
1135
1268
  *
1136
1269
  * EXCLUDED — the danger surface (each a separate trust boundary):
1137
- * execute_command, terminal/PTY tools (raw shell), fs tools
1138
- * (read/write/delete file, create_directory, …), remote_* (publish
1139
- * the daemon to the internet), import_mcp / mcp_imported_* /
1140
- * list_discovered_mcps / remove_imported_mcp (widen the daemon's own
1141
- * MCP surface), and driver CRUD (create_/delete_/update_driver these
1142
- * never register here because the scoped server is built with no
1143
- * doctype specs).
1270
+ * command_execute, terminal/PTY tools (raw shell), file_* /
1271
+ * directory_* tools, remote_* (publish the daemon to the internet),
1272
+ * mcp_import / mcp_discovered_* / mcp_imported_* (widen the daemon's
1273
+ * own MCP surface), and driver CRUD (create_/delete_/update_driver —
1274
+ * these never register here because the scoped server is built with
1275
+ * no doctype specs).
1144
1276
  */
1145
1277
  declare const DEFAULT_ORCHESTRATOR_TOOLS: readonly string[];
1146
1278
  interface OrchestratorScope {
@@ -1228,16 +1360,21 @@ interface OrchestratorGatewayDeps {
1228
1360
  listAgentAdapters?: AgentAdapterLister;
1229
1361
  /** Orchestrator injector (WP3/WP4). When wired, a child orchestrator
1230
1362
  * driving the scoped server can itself spawn sub-orchestrators
1231
- * (`orchestrator: true` on its `start_agent_session`) — the new
1363
+ * (`orchestrator: true` on its `agent_start`) — the new
1232
1364
  * token inherits depth+1 and is bounded by the caller's tools
1233
1365
  * (non-re-grant). Omitted → recursion injection is unavailable on
1234
1366
  * the scoped surface (a child can still spawn plain sub-agents). */
1235
1367
  orchestratorInjector?: OrchestratorInjector;
1236
1368
  /** Optional webhook notifier — same singleton as the root /mcp server.
1237
- * When provided, per-session notifyUrl values from start_agent_session
1369
+ * When provided, per-session notifyUrl values from agent_start
1238
1370
  * are registered on spawn and unregistered on exit, so child
1239
1371
  * orchestrators spawning through this scoped gateway also fire webhooks. */
1240
1372
  webhookNotifier?: WebhookNotifier;
1373
+ /** Forwarded to `registerSessionTools` — the daemon's own plain `/mcp`
1374
+ * gateway URL, defaulted onto `hermes` `agent_start` spawns issued
1375
+ * through this scoped sub-gateway that pass no `mcpServers`. See
1376
+ * `RegisterAgentToolsOptions.daemonMcpUrl`. */
1377
+ daemonMcpUrl?: string;
1241
1378
  }
1242
1379
  type OrchestratorMcpServerFactory = (scope: OrchestratorScope) => Promise<McpServer>;
1243
1380
  /**
@@ -1314,33 +1451,11 @@ type OrchestratorInjector = (opts?: {
1314
1451
  /**
1315
1452
  * Build the orchestrator injector. Closed over the gateway's
1316
1453
  * scope-token registry + session-event bus + HTTP port. Returns a
1317
- * function that the `start_agent_session` handler calls when its
1454
+ * function that the `agent_start` handler calls when its
1318
1455
  * `orchestrator` field is set.
1319
1456
  */
1320
1457
  declare function createOrchestratorInjector(deps: OrchestratorInjectorDeps): OrchestratorInjector;
1321
1458
 
1322
- /**
1323
- * Tiny node:http server that fronts the runtime gateway.
1324
- *
1325
- * Routes:
1326
- * GET /health — { status, workspace, registered, uptime }
1327
- * GET /events — SSE stream of RuntimeEvent
1328
- * POST /mcp — MCP Streamable HTTP transport (POST)
1329
- * GET /mcp — MCP SSE response stream (GET)
1330
- * DELETE /mcp — close MCP session
1331
- * GET /conversations — JSON list of conversation summaries
1332
- * GET /conversations/<id> — markdown body of one conversation
1333
- * POST /heartbeat/tick — force-fire one heartbeat tick
1334
- *
1335
- * No auth in `mode: "none"` (loopback default). `mode: "bearer"`
1336
- * checks `Authorization: Bearer <token>` against the configured
1337
- * token. Health is always public so external monitors can probe.
1338
- *
1339
- * MCP transport: stateless mode for v1 — each request is independent.
1340
- * Stateful session pinning can come later when long-running streaming
1341
- * tool calls become a real use case.
1342
- */
1343
-
1344
1459
  /**
1345
1460
  * Pluggable adapter resolver — keeps the runtime package free of any
1346
1461
  * @agentproto/cli dep. The host (cli `serve`, playground, embedding
@@ -1361,12 +1476,25 @@ type AgentAdapterResolver = (slug: string) => Promise<{
1361
1476
  startSession(opts: {
1362
1477
  cwd: string;
1363
1478
  resumeSessionId?: string;
1364
- /** Model identifier forwarded from `start_agent_session`. For ACP
1479
+ /**
1480
+ * Manifest-declared mode id forwarded from `agent_start` (AIP-45
1481
+ * `AgentCliHandle.modes` — e.g. claude-code's `plan` /
1482
+ * `accept-edits` / `bypass-permissions`, codex's `read-only`,
1483
+ * mastracode/opencode's `plan`). Applied at spawn time via
1484
+ * `composeSpawn`'s mode patch (`bin_args_append` / `env`) — BEFORE
1485
+ * the child process is exec'd, unlike `model`/`effort` below.
1486
+ * Adapters with no declared `modes` (e.g. hermes) ignore it; an
1487
+ * unknown id for an adapter that DOES declare modes throws
1488
+ * `RuntimeConfigError` (composeSpawn validates against the
1489
+ * manifest, so a typo fails the spawn rather than silently no-op).
1490
+ */
1491
+ mode?: string;
1492
+ /** Model identifier forwarded from `agent_start`. For ACP
1365
1493
  * adapters this is applied via session/set_config_option after
1366
1494
  * newSession (the ACP wrapper does not forward CLI args to claude).
1367
1495
  * Adapters that don't support model selection ignore it. */
1368
1496
  model?: string;
1369
- /** Effort level forwarded from `start_agent_session`. Effort is
1497
+ /** Effort level forwarded from `agent_start`. Effort is
1370
1498
  * model-dependent — same label ≠ same budget across models; defaults
1371
1499
  * differ by model. Omit to keep the model's own default. Applied
1372
1500
  * via session/set_config_option on ACP adapters; others ignore it. */
@@ -1377,6 +1505,12 @@ type AgentAdapterResolver = (slug: string) => Promise<{
1377
1505
  * a host-chosen scoped toolset (e.g. the daemon's own orchestration
1378
1506
  * gateway). Adapters that don't model MCP mounting ignore it. */
1379
1507
  mcpServers?: AcpMcpServer[];
1508
+ /** Called on any adapter-process activity (ACP JSON-RPC traffic in
1509
+ * either direction) — forwarded to the driver's
1510
+ * `runtime.start({ onActivity })`. The caller (agent_start's MCP
1511
+ * handler, POST /sessions/agent) wires this to pulse
1512
+ * `SessionDescriptor.lastActivityAt` via `registry.pulseActivity(id)`. */
1513
+ onActivity?: () => void;
1380
1514
  }): Promise<AgentSessionLike>;
1381
1515
  /** Display label for the descriptor's `command` field. */
1382
1516
  commandPreview?: string;
@@ -1530,6 +1664,28 @@ declare function makeBrowserAdapterLister(opts: {
1530
1664
  resolveBrowserAdapter?: BrowserAdapterResolver;
1531
1665
  }): AdapterLister<BrowserAdapterInfo>;
1532
1666
 
1667
+ /**
1668
+ * Turns a raw tool-call name + args (and its eventual result) into a short,
1669
+ * human-readable line for ring-buffer / CLI / transcript rendering. Without
1670
+ * this, every tool call renders as a bare `[tool] view` with no args and no
1671
+ * outcome — this module is the one place that knows how to summarize both.
1672
+ */
1673
+ /** A one-line human summary of a tool call, e.g. `read src/foo.ts` or `⏰ wake in 30s — checking CI`. */
1674
+ declare function formatToolCall(toolName: string, args: unknown): string;
1675
+ /**
1676
+ * A short outcome line for a completed tool call, or `null` when there's
1677
+ * nothing useful to show (e.g. an empty/void result). `toolName` is
1678
+ * accepted for parity with `formatToolCall` and future bespoke result
1679
+ * formatting, but generic text extraction covers today's cases.
1680
+ */
1681
+ declare function formatToolResult(toolName: string | undefined, result: unknown, isError: boolean): string | null;
1682
+
1683
+ /** A journal-file StepCache scoped to one cacheKey. Best-effort: read/parse
1684
+ * failures degrade to "no cache" (a miss), never throw into the run. */
1685
+ declare function createFileStepCache(cacheKey: string, opts?: {
1686
+ dir?: string;
1687
+ }): StepCache;
1688
+
1533
1689
  /**
1534
1690
  * `.agentproto/` config dir — runtime-managed state at the root of
1535
1691
  * every workspace. Mirrors the `.git/` model: user-content stays at
@@ -1648,6 +1804,90 @@ declare function readRuntimeMeta(workspace: string): Promise<{
1648
1804
  */
1649
1805
  declare function sweepStaleRuntimeMetas(workspaces: readonly string[], currentWorkspace: string): Promise<string[]>;
1650
1806
 
1807
+ /**
1808
+ * MCP tools for event-driven orchestration:
1809
+ * - session_events_poll — cheap cursor-based snapshot of session events
1810
+ * - session_monitor — multiplexed long-poll (1 call for N sessions)
1811
+ *
1812
+ * These complement the existing per-session waitForTurnEnd inside
1813
+ * agent_output. The new tools handle multi-session fan-in
1814
+ * and external-client retrigger without burning polling tokens.
1815
+ */
1816
+
1817
+ /**
1818
+ * Event kind a session wait can target. Mirrors the `session_monitor`
1819
+ * `event` parameter. `turn-end` also matches `awaiting-input` (both
1820
+ * signal end-of-turn), matching the MCP tool's semantics.
1821
+ */
1822
+ type SessionWaitEvent = "turn-end" | "awaiting-input" | "exited" | "any";
1823
+ /**
1824
+ * Result of a single-session wait. On a hit, carries the session id, the
1825
+ * matched event (ring-bus form, without the `session:` prefix), the
1826
+ * session's current status, and whether it's awaiting input. On a
1827
+ * timeout, `timedOut: true` and the watched id list.
1828
+ */
1829
+ interface SessionWaitResult {
1830
+ timedOut?: boolean;
1831
+ sessionId?: string;
1832
+ event?: string;
1833
+ source?: "ring" | "state" | "bus";
1834
+ awaitingInput?: boolean;
1835
+ status?: string;
1836
+ turnsCompleted?: number;
1837
+ sessionIds?: string[];
1838
+ since?: number;
1839
+ /** Structured awaiting-input question (harness-parity). Surface on
1840
+ * turn-end / awaiting-input matches so callers (MCP session_monitor +
1841
+ * REST /sessions/:id/wait) can read the question without a separate
1842
+ * transcript fetch. Mirrors desc.awaitingQuestion / ev.question. */
1843
+ question?: SessionAwaitingQuestion;
1844
+ }
1845
+ /**
1846
+ * Block until one of the listed sessions fires a matching lifecycle event
1847
+ * (turn-end / awaiting-input / exited / any), or until `timeoutMs` elapses.
1848
+ *
1849
+ * This is the single-session-capable core that the MCP `session_monitor`
1850
+ * tool and the REST `GET /sessions/:id/wait` route both call. The MCP tool
1851
+ * passes N ids (multiplexed fan-in); the REST route passes exactly one.
1852
+ * The semantics are identical: race-free cursor replay → synchronous
1853
+ * already-in-target-state check → bus long-poll → timeout.
1854
+ *
1855
+ * `since` is an EventRing cursor: when provided, already-emitted matching
1856
+ * events for the watched sessions that occurred after that cursor are
1857
+ * returned immediately (race-free replay) before subscribing to the bus.
1858
+ */
1859
+ declare function monitorSessionWait(opts: {
1860
+ registry: SessionsRegistry;
1861
+ sessionEvents: SessionEventBus;
1862
+ eventRing: EventRing;
1863
+ sessionIds: string[];
1864
+ event?: SessionWaitEvent;
1865
+ timeoutMs?: number;
1866
+ since?: number;
1867
+ }): Promise<SessionWaitResult>;
1868
+ /**
1869
+ * Block until the named policy's status transitions out of the active
1870
+ * `watching` / `queued` / `gating` / `nudging` states — i.e. reaches
1871
+ * `done`, `blocked`, `awaiting-ack`, or `cancelled` — then return the
1872
+ * full PolicyRunState. Returns `{ timedOut: true }` on timeout.
1873
+ *
1874
+ * Hooks into the supervisor's `onSettle` callback (the single reliable
1875
+ * signal — some terminal transitions like `cancel()` / `ack(false)` /
1876
+ * single-session-exit emit no SessionEventBus event). The MCP
1877
+ * `policy_status` tool and the REST `GET /policies/:id/wait` route both
1878
+ * delegate here, so the wait semantics are shared across transports.
1879
+ */
1880
+ declare function monitorPolicyWait(opts: {
1881
+ supervisor: CompletionPolicySupervisor;
1882
+ policyId: string;
1883
+ timeoutMs?: number;
1884
+ }): Promise<{
1885
+ timedOut: true;
1886
+ } | {
1887
+ timedOut: false;
1888
+ state: PolicyRunState;
1889
+ }>;
1890
+
1651
1891
  /**
1652
1892
  * @agentproto/runtime — long-running gateway around an agentproto
1653
1893
  * workspace dir.
@@ -1703,18 +1943,18 @@ interface CreateGatewayOptions {
1703
1943
  * Without this, /sessions still works for raw `argv` spawns. */
1704
1944
  resolveAgentAdapter?: AgentAdapterResolver;
1705
1945
  /** Optional adapter lister — when provided, enables
1706
- * `GET /adapters` HTTP route + `list_adapters` MCP tool so UIs
1946
+ * `GET /adapters` HTTP route + `adapter_list` MCP tool so UIs
1707
1947
  * can discover what's installed on the host. */
1708
1948
  listAgentAdapters?: AgentAdapterLister;
1709
1949
  /** Optional browser adapter resolver — when provided, enables the
1710
1950
  * `start_browser` MCP tool (launches Camofox / Bureau / Chromium). */
1711
1951
  resolveBrowserAdapter?: BrowserAdapterResolver;
1712
1952
  /** Optional browser adapter lister — when provided, enables the
1713
- * `list_adapter_browsers` MCP tool. */
1953
+ * `browser_adapter_list` MCP tool. */
1714
1954
  listBrowserAdapters?: BrowserAdapterLister;
1715
1955
  /** Optional PTY factory (node-pty wrapper, typically from the cli
1716
1956
  * layer's `loadNodePtyFactory()`). When provided, enables
1717
- * `POST /sessions/terminal`, the `start_terminal_session` MCP
1957
+ * `POST /sessions/terminal`, the `terminal_start` MCP
1718
1958
  * tool family, and the `/sessions/:id/pty` WebSocket. Without it,
1719
1959
  * those routes return 501 / the MCP tools aren't registered. */
1720
1960
  spawnPty?: PtyFactory;
@@ -1731,6 +1971,19 @@ interface CreateGatewayOptions {
1731
1971
  /** When true, drop the localhost-wildcard defaults — only the
1732
1972
  * explicit `allowedOrigins` list is honoured. */
1733
1973
  strictOrigins?: boolean;
1974
+ /**
1975
+ * Opt-in deferred/lazy MCP tool loading (harness-parity item 3, see
1976
+ * `deferred-tools.ts`). When set, every root-gateway tool outside
1977
+ * `alwaysOn` registers but starts disabled — excluded from the first
1978
+ * `tools/list` — until the always-on `tool_search` meta-tool pulls it
1979
+ * in by keyword. Omitted (default) → every tool is eagerly enabled,
1980
+ * identical to pre-existing behaviour. Does not affect the scoped
1981
+ * `/mcp/orchestrator` sub-gateway, which already exposes a small
1982
+ * curated allowlist (`DEFAULT_ORCHESTRATOR_TOOLS`).
1983
+ */
1984
+ deferredTools?: {
1985
+ alwaysOn?: readonly string[];
1986
+ };
1734
1987
  }
1735
1988
  interface GatewayHandle {
1736
1989
  url: string;
@@ -1774,4 +2027,4 @@ interface GatewayHandle {
1774
2027
  */
1775
2028
  declare function createGateway(opts: CreateGatewayOptions): Promise<GatewayHandle>;
1776
2029
 
1777
- export { type AdapterListEntry, type AgentAdapterLister, type AgentAdapterResolver, type AgentSessionLike, type AgentStreamEvent, type BrowserAdapterHandle, type BrowserAdapterInfo, type BrowserAdapterLister, type BrowserAdapterResolver, BuildHeartbeatAgent, type CreateGatewayOptions, DEFAULT_ORCHESTRATOR_TOOLS, type GatewayHandle, type InboundWatcher, type OrchestratorGatewayDeps, type OrchestratorInjection, type OrchestratorInjector, type OrchestratorInjectorDeps, type OrchestratorMcpServerFactory, type OrchestratorScope, type RegisterBrowserInput, type RegisterSessionInput, type RuntimeMeta, type ScopeTokenRegistry, type SessionDescriptor, type SessionKind, type SessionStatus, type SessionsRegistry, type SpawnAgentInput, type SpawnSessionInput, type TunnelDescriptor, type TunnelProvider, type TunnelStatus, type WatcherDescriptor, type WatcherStartInput, WorkspaceFs, createGateway, createOrchestratorInjector, createOrchestratorMcpServerFactory, createScopeTokenRegistry, daemonRegistryDir, makeBrowserAdapterLister, narrowOrchestratorTools, readDaemonRegistry, readRuntimeMeta, sweepStaleDaemonRegistry, sweepStaleRuntimeMetas, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, writeDaemonRegistryEntry };
2030
+ export { type AdapterListEntry, type AgentAdapterLister, type AgentAdapterResolver, type AgentSessionLike, type AgentStreamEvent, type AttachPolicyInput, type BrowserAdapterHandle, type BrowserAdapterInfo, type BrowserAdapterLister, type BrowserAdapterResolver, BuildHeartbeatAgent, type CommitSpec, type CompletionPolicySupervisor, type CreateGatewayOptions, DEFAULT_ORCHESTRATOR_TOOLS, type GateSpec, type GatewayHandle, type InboundWatcher, type JudgeGateSpec, type OnFailSpec, type OrchestratorGatewayDeps, type OrchestratorInjection, type OrchestratorInjector, type OrchestratorInjectorDeps, type OrchestratorMcpServerFactory, type OrchestratorScope, type PolicyRunState, type PolicyRunStatus, type RegisterBrowserInput, type RegisterSessionInput, type RuntimeMeta, type ScopeTokenRegistry, type SessionDescriptor, type SessionKind, type SessionStatus, type SessionWaitEvent, type SessionWaitResult, type SessionsRegistry, type ShellGateSpec, type SpawnAgentInput, type SpawnSessionInput, type TunnelDescriptor, type TunnelProvider, type TunnelStatus, type WatcherDescriptor, type WatcherStartInput, WorkspaceFs, createFileStepCache, createGateway, createOrchestratorInjector, createOrchestratorMcpServerFactory, createScopeTokenRegistry, daemonRegistryDir, formatToolCall, formatToolResult, makeBrowserAdapterLister, monitorPolicyWait, monitorSessionWait, narrowOrchestratorTools, readDaemonRegistry, readRuntimeMeta, sweepStaleDaemonRegistry, sweepStaleRuntimeMetas, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, writeDaemonRegistryEntry };