@jini-ai/daemon 0.5.3 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/agent-executor/contracts.d.ts +45 -14
  3. package/dist/agent-executor/contracts.js +0 -9
  4. package/dist/agent-executor/event-translation.d.ts +2 -2
  5. package/dist/agent-executor/event-translation.js +0 -2
  6. package/dist/agent-executor/events.d.ts +26 -6
  7. package/dist/agent-executor/events.js +198 -11
  8. package/dist/agent-executor/handoff.d.ts +1 -1
  9. package/dist/agent-executor/handoff.js +0 -1
  10. package/dist/agent-executor/index.d.ts +1 -1
  11. package/dist/agent-executor/launch.d.ts +1 -1
  12. package/dist/agent-executor/launch.js +47 -3
  13. package/dist/agent-executor/mcp-bridge.d.ts +2 -2
  14. package/dist/agent-executor/mcp-bridge.js +19 -8
  15. package/dist/agent-executor/model-receipts.d.ts +6 -0
  16. package/dist/agent-executor/model-receipts.js +26 -0
  17. package/dist/agent-executor.d.ts +7 -1665
  18. package/dist/agent-executor.js +7 -3007
  19. package/dist/core/read-only-tools.d.ts +130 -0
  20. package/dist/core/read-only-tools.js +115 -0
  21. package/dist/core/tool-audit.d.ts +131 -0
  22. package/dist/core/tool-audit.js +113 -0
  23. package/dist/http/active-context.d.ts +2 -2
  24. package/dist/http/agents.d.ts +8 -2
  25. package/dist/http/agents.js +6 -2
  26. package/dist/http/component-catalog.d.ts +2 -2
  27. package/dist/http/connectors.d.ts +17 -17
  28. package/dist/http/daemon-status.d.ts +2 -2
  29. package/dist/http/db-ops.d.ts +3 -3
  30. package/dist/http/delegated-tools.d.ts +1 -1
  31. package/dist/http/delegated-tools.js +1 -1
  32. package/dist/http/frontend-sessions.d.ts +1 -1
  33. package/dist/http/health.d.ts +6 -6
  34. package/dist/http/host-tools.d.ts +2 -2
  35. package/dist/http/index.d.ts +2 -2
  36. package/dist/http/index.js +1 -1
  37. package/dist/http/media.d.ts +4 -4
  38. package/dist/http/memory.d.ts +15 -15
  39. package/dist/http/remote-run-events.d.ts +2 -2
  40. package/dist/http/research.d.ts +1 -1
  41. package/dist/http/route-manifest.js +2 -2
  42. package/dist/http/routines.d.ts +7 -7
  43. package/dist/http/run-credentials.d.ts +2 -2
  44. package/dist/http/run-credentials.js +2 -2
  45. package/dist/http/runs.d.ts +31 -6
  46. package/dist/http/runs.js +36 -1
  47. package/dist/http/terminals.d.ts +6 -6
  48. package/dist/http/tool-catalog.d.ts +2 -2
  49. package/dist/http/xai.d.ts +6 -6
  50. package/dist/index.d.ts +75 -571
  51. package/dist/index.js +75 -571
  52. package/dist/read-only-tools.d.ts +1 -129
  53. package/dist/read-only-tools.js +2 -114
  54. package/dist/run-credentials.d.ts +47 -23
  55. package/dist/run-credentials.js +24 -8
  56. package/dist/scheduler.d.ts +5 -0
  57. package/dist/scheduler.js +13 -1
  58. package/dist/session-coordination.d.ts +58 -24
  59. package/dist/session-coordination.js +40 -12
  60. package/dist/store/event-log/sqlite.js +3 -3
  61. package/dist/surface-exchanges.d.ts +207 -61
  62. package/dist/surface-exchanges.js +167 -20
  63. package/dist/tool-audit.d.ts +1 -113
  64. package/dist/tool-audit.js +2 -94
  65. package/dist/tool-recovery/diagnostics.d.ts +132 -0
  66. package/dist/tool-recovery/diagnostics.js +110 -0
  67. package/dist/tool-recovery/federated-refusal.d.ts +92 -0
  68. package/dist/tool-recovery/federated-refusal.js +152 -0
  69. package/dist/tool-recovery/index.d.ts +6 -0
  70. package/dist/tool-recovery/index.js +6 -0
  71. package/dist/tool-recovery/recovery.d.ts +49 -0
  72. package/dist/tool-recovery/recovery.js +419 -0
  73. package/dist/tool-recovery/redaction.d.ts +166 -0
  74. package/dist/tool-recovery/redaction.js +192 -0
  75. package/dist/tool-recovery/stack.d.ts +69 -0
  76. package/dist/tool-recovery/stack.js +62 -0
  77. package/package.json +35 -12
package/dist/index.d.ts CHANGED
@@ -1,577 +1,81 @@
1
1
  /**
2
2
  * @module @jini-ai/daemon
3
3
  *
4
- * `RunLifecycle` + the durable `EventLog` kernel port (extraction-plan §8
5
- * task 5), plus the `ToolExecutor` tool-execution boundary (extraction-plan
6
- * §2.5 / §8 task 6), and `AgentExecutor` — the driver that wires
7
- * `@jini-ai/agent-runtime` into `RunLifecycle` (extraction-plan §2.1 / §3).
8
- * See `archived provenance ledger` for full provenance and scope-decision notes.
9
- *
10
- * The generic `ArtifactStore` kernel port used to live here too — moved to its own
11
- * `@jini-ai/artifacts` package on 2026-07-19 (see `tokens.ts`'s doc comment).
12
- *
13
- * `routines/` (2026-07-21) adds the `RoutineService` scheduler (DST-safe wall-clock schedule
14
- * math, race-safe scheduled-slot persistence) and the `RoutineStore` CRUD + run-history port,
15
- * mirroring `EventLog`'s kernel-owned, storage-injected precedent. See its own module doc and
16
- * `archived provenance ledger`'s dated section for provenance.
17
- *
18
- * `terminal-session.ts` (2026-07-21) adds the interactive-terminal session manager: a
19
- * `node-pty`-backed `PtySpawn` wired into `@jini-ai/platform`'s generic `TerminalService`, plus
20
- * session-ownership gating and the kill/write/resize lock `@jini-ai/daemon/http`'s `terminals.ts` route
21
- * pack calls into. This is this workspace's first native-compiled-addon dependency — see this
22
- * package's `package.json` and `archived provenance ledger`'s dated section.
23
-
24
- * Archived provenance rationale:
25
- * ## Design decisions (judgment calls the task brief explicitly asked for)
26
- *
27
- * **1. `RunLifecycle` does not spawn or signal subprocesses.** OD's `cancel()`
28
- * (`runs.ts:361-401`) does real tiered signal escalation (ACP `.abort()` → RPC
29
- * grace → `SIGTERM` on the process group → grace → `SIGKILL`, all
30
- * env-var-tunable). That is deeply subprocess/agent-CLI-adapter-specific and is
31
- * explicitly `@jini/agent-runtime`'s territory (task 7), not this task's. This
32
- * package's `cancel()` only records intent (`cancelRequested` + notifies
33
- * `onCancelRequested` listeners, an `AbortSignal`-shaped but framework-neutral
34
- * callback registry) and never itself forces a terminal transition — a driver
35
- * observes the signal and calls `finish()` once it knows the real outcome,
36
- * exactly mirroring OD's actual invariant that `finish()`/`design.runs.finish`
37
- * is the only path to a terminal state, called from the child's real `close`
38
- * event, never synchronously from `cancel()` itself.
39
- *
40
- * **2. `resume()` is a generalization, not a literal port.** OD has two
41
- * unrelated "resume" concepts (see the research notes cited in the handoff
42
- * report): (a) resuming the *external agent CLI's own* session via
43
- * `--resume <id>`/ACP `session/load`, persisted in OD's SQLite schema — a
44
- * real subprocess/vendor-CLI concern out of scope here — and (b) a
45
- * `run.resumable` flag surfaced to the client so a *next user turn* can invoke
46
- * (a). Neither is "resume an interrupted daemon-side run object" — that
47
- * concept doesn't exist in OD. `@jini/daemon`'s `resume()` builds that concept
48
- * fresh: a terminal run with `resumable: true` (set by whoever called
49
- * `finish()`) can transition back to `'running'` on the **same `runId`**,
50
- * continuing the **same `EventLog` cursor sequence** unbroken — no new `'start'`
51
- * event, no cursor reset. This was a deliberate choice to make `resume()`
52
- * meaningful and testable at the kernel layer without a real subprocess/session
53
- * concept to hang it on; see `characterization.test.ts` for the two-segment
54
- * proof (start→agent*→end(failed,resumable)→resume→agent*→end(succeeded), ids
55
- * monotonically increasing across the whole thing).
56
- *
57
- * **3. No `RunStore` port was extracted.** Extraction-plan §2.2's illustrative
58
- * composition example lists `RunStore` and `EventLog` as siblings a pack
59
- * depends on. This task's charter (§8 task 5) names only `RunLifecycle` +
60
- * `EventLog` as deliverables. `RunLifecycle`'s own run-status registry
61
- * (`Map<runId, RunRecord>`) is therefore an internal implementation detail of
62
- * this package's `createRunLifecycle`, not a separately swappable port — a
63
- * persistent run registry (surviving a daemon restart, not just an event
64
- * replay) is not implemented. **Flagged as a real follow-up**: a future task
65
- * (likely task 8, alongside the `@jini/sqlite` durable `EventLog` adapter)
66
- * should decide whether `RunLifecycle`'s registry needs its own `RunStore`
67
- * port for restart-durability parity with §2.2's example, or whether
68
- * `RunLifecycle` itself becomes the thing `@jini/sqlite` backs directly.
69
- *
70
- * **4. The `RunState`/`RunEndPayload.status` spelling mismatch is bridged, not
71
- * fixed.** `@jini/protocol`'s `RunState` uses `'cancelled'` (2 L) while
72
- * `RunEndPayload.status` uses `'canceled'` (1 L) — this is extraction-plan §12
73
- * C5's own cited vocabulary-firewall canary, already present in `@jini/protocol`
74
- * before this task started. `packages/protocol` is explicitly out of scope for
75
- * this task, so `run-lifecycle.ts` bridges the two spellings with an explicit
76
- * `TERMINAL_OUTCOME_TO_END_STATUS` lookup table (with a comment citing this)
77
- * rather than silently letting them drift or fixing the upstream package.
78
- * `run-lifecycle.test.ts`'s finish() test asserts the bridge explicitly.
79
- *
80
- * **5. Driver-emittable events are a closed, explicit union
81
- * (`DriverEmittableInput`)**, not `RunProtocolEvent` minus `'start'`/`'end'`
82
- * derived mechanically — `'start'` is only ever produced by `start()`, `'end'`
83
- * only by `finish()`; a driver calling `emit()` can only ever produce
84
- * `'agent'`/`'stdout'`/`'stderr'`/`'error'`. This mirrors OD's actual invariant
85
- * (`'start'` emitted once by the engine right before spawn, `'end'` only from
86
- * `design.runs.finish`) as a compile-time-enforced contract rather than a
87
- * runtime convention.
88
- *
89
- * **6. `emit()` throws (fails fast) on an unknown or already-terminal run**,
90
- * rather than silently no-op'ing. OD's own code pattern is the caller checking
91
- * `run.cancelRequested || design.runs.isTerminal(run.status)` defensively
92
- * *before* emitting (repeated ~10 times in `start-chat-run.ts` per the
93
- * research), i.e. OD treats "don't emit after terminal" as the caller's job,
94
- * not something `emit()` itself silently absorbs. This package makes that the
95
- * same explicit contract, but surfaces a violation as a thrown error (a driver
96
- * bug) instead of a silently-observed defensive check, per this codebase's
97
- * fail-fast-defaults convention.
98
- *
99
- * **7. A cancel listener that subscribes after `cancel()` already fired still
100
- * receives the notification immediately** (`onCancelRequested` replays the
101
- * last request if `cancelRequested` is already true at subscribe time). This
102
- * was found and fixed during this task's own mandatory score-skepticism pass
103
- * (a plain forward-only listener registry has no memory of past firings, so a
104
- * driver attaching late — e.g. after being constructed asynchronously — would
105
- * otherwise silently never learn a cancellation was already requested). No OD
106
- * equivalent exists to compare against; this is new, `AbortSignal`-inspired
107
- * behavior.
108
- *
109
- * ## New home decision
110
- *
111
- * Ranked alongside `RunLifecycle`/`EventLog` in `@jini/daemon` because, like
112
- * those, it is a daemon-startup-lifecycle concern — the origin's own doc
113
- * comment states it runs "at module import time in server.ts, before
114
- * `openDatabase` opens SQLite," i.e. before any daemon service starts, which is
115
- * exactly this package's charter ("stateful" lifecycle, not a pure-interfaces
116
- * package like `@jini/core`).
117
- *
118
- * ### Design decisions
119
- *
120
- * **1. Authorization vs. confirmation are two distinct gates, not one.**
121
- * `ToolPolicy.authorize` (owned by the tool's registration) answers "is this
122
- * principal permitted to use this tool at all" — a rule-based decision.
123
- * `ExecutionDelegate.onConfirm` (owned by the transport, only consulted when
124
- * `ToolDescriptor.requiresConfirmation` is set) answers "does the user want
125
- * to proceed with *this specific* invocation right now" — an interactive,
126
- * per-call gate. `ExecutionDelegate.onAuthorize` exists too, but only as an
127
- * additional veto *after* the policy already allows (e.g. "does this session
128
- * actually hold an active grant") — it can turn an `'allow'` into a `'deny'`,
129
- * never the reverse, and is never consulted when the policy itself denies
130
- * (tested explicitly).
131
- *
132
- * **2. Confirmation resumability is a real parked Promise, not a polling
133
- * flag.** When `ExecutionDelegate.onConfirm` doesn't supply a decision
134
- * synchronously (returns/resolves `undefined`), `execute()`'s returned
135
- * Promise is parked on an internal `Map<executionId, resolve>` and simply
136
- * does not settle — `resumeConfirmation(executionId, decision)` is the only
137
- * thing that can settle it, and it can be called from a completely separate
138
- * tick/request, arbitrarily far in the future, no polling required. This is
139
- * the concrete answer to "the headless kernel can't prompt, so the transport
140
- * injects an `ExecutionDelegate`": the kernel (`ToolExecutor`) never renders
141
- * anything; it hands the transport a notification (the `onConfirm` call) and
142
- * waits indefinitely for `resumeConfirmation`, exactly the shape a real UI
143
- * approval flow needs. One documented sharp edge: an `async` `onConfirm`
144
- * that itself resolves to `undefined` is treated as "the decision is
145
- * `undefined`" (i.e. denied) rather than "pending," since the code can only
146
- * tell "no decision yet" apart from "the Promise's inner value happens to be
147
- * undefined" by checking whether `onConfirm`'s *own return value* is
148
- * `undefined` before awaiting it — documented on the interface, not
149
- * silently surprising.
150
- *
151
- * **3. Output truncation is a text-output concern, not a general
152
- * serialization limit.** `ToolDescriptor.maxOutputBytes` only truncates a
153
- * `string` handler result exceeding that length; non-string (structured)
154
- * output passes through untouched even when the limit is set. A real
155
- * byte-accurate UTF-8 truncation or a generic-JSON-payload cap was judged
156
- * out of scope for this task's gate ("output truncation" — proven, not
157
- * maximally engineered) — a reasonable follow-up if a real tool handler
158
- * starts returning large structured payloads.
159
- *
160
- * **4. `execute()` throws (doesn't return a status) for an unknown tool
161
- * id.** Denial/confirmation-denial/timeout/cancellation/failure are all
162
- * legitimate business-domain outcomes modeled as `ToolExecutionResult`
163
- * variants; calling `execute()` with a `toolId` nothing registered is a
164
- * routing/programming bug, not a business outcome, so it's a thrown `Error`
165
- * instead — the same distinction OD's own `RunLifecycle`-equivalent code in
166
- * this package (`requireRun`) already draws between "unknown id" (throw) and
167
- * "legitimate terminal state" (return value).
168
- *
169
- * **5. No persistence.** Like `EventLog`'s in-memory reference
170
- * implementation, `createToolExecutor`'s audit records and pending-
171
- * confirmation state live only in the process; a real host that needs audit
172
- * records to survive a restart, or confirmation to resume across a daemon
173
- * restart, layers a durable store behind `getAuditRecord`/`resumeConfirmation`
174
- * later. Out of this task's scope (the gate is "one allowed + one denied
175
- * tool call, resumable confirmation, timeout, cancellation, output
176
- * truncation, and an audit record — no HTTP involved," which this satisfies
177
- * in-process).
178
- *
179
- * **6. A `finally` block was deliberately avoided around the handler's
180
- * try/catch.** An early draft cleaned up the timeout handle and the active-
181
- * `AbortController` map entry in a `finally` clause; istanbul/v8 instruments
182
- * try/finally with a synthetic "abrupt completion through finally" branch
183
- * that is unreachable here (nothing in the catch block can itself throw a
184
- * second exception past the catch), which the coverage-driven pass (Phase
185
- * 6.5) couldn't close without a contrived test. Repeating the two-line
186
- * cleanup at each of the try/catch's own return points instead avoided
187
- * introducing that uncoverable branch — a "dead branch, refactor away"
188
- * call, same discipline as the agent-protocol port's (Part 1) precedent.
189
- *
190
- * ### Design decisions
191
- *
192
- * **1. `resumable` is always `false` in v1.** `RunRetryFailureSignal` (the
193
- * richer classification `decideSafeRunRetry`, in `run/core/retry.ts`,
194
- * consumes) has no producer anywhere in this codebase — OD's real
195
- * ~20-vendor-CLI text-matching failure classifier
196
- * (`run-failure-classification.ts`, researched but never ported; see this
197
- * file's earlier "Not ported" section) was deliberately excluded as its own
198
- * substantial, unscoped task. Every `lifecycle.finish()` call
199
- * `AgentExecutor` makes — pre-spawn guard failures, spawn errors, and the
200
- * real `child.on('close', ...)` terminal transition alike — passes
201
- * `resumable: false`. A blanket "not retryable" default is the only honest
202
- * answer without a classifier; building one is a separate, unscoped
203
- * follow-up, not attempted here.
204
- *
205
- * **2. `turn_end` is not forwarded as an `'agent'` event, and v1 has no
206
- * multi-turn stdin-injection loop.** `turn_end` is Claude-stream-specific
207
- * (synthesized from an assistant message's `stop_reason`) and has no
208
- * `RunAgentPayload` variant — `translateAgentRuntimeEvent` routes it to a
209
- * `'turn-end'` translation kind that `run()` reacts to directly (closing
210
- * stdin) rather than durably recording. **v1 unconditionally closes stdin on
211
- * the first `turn_end` regardless of `stopReason`** — including
212
- * `stop_reason: 'tool_use'`, which in a real multi-turn caller would instead
213
- * inject the tool's result back into the same open stdin and keep the turn
214
- * going. Without that injection loop, a `tool_use`-ending turn's stdin still
215
- * closes (rather than hanging forever waiting for a continuation that will
216
- * never come); the agent CLI is responsible for deciding how to behave when
217
- * its stdin closes mid-tool-use, which each of the 9 defs already handles
218
- * via its own inactivity/EOF behavior. Building the real tool-continuation
219
- * loop needs `ToolExecutorToken` wiring (deciding what to send back) and is
220
- * explicitly out of this task's scope — see the plan's "Explicitly out of
221
- * scope" section.
222
- *
223
- * **3. Session-resume ids are an intentional, documented drop.** Some
224
- * parsers attach a resumable session handle to their `status` event
225
- * (OpenCode's `sessionID`, Codex's `thread_id`/`sess-*`, Qoder's
226
- * `session_id`+`qodercliVersion`) — `translateAgentRuntimeEvent`'s `status`
227
- * case only carries `label`/`model`/`ttftMs`/`detail` (the fields
228
- * `RunAgentPayload`'s `status` variant actually declares), so these ids are
229
- * silently dropped at translation time. This is unrelated to
230
- * `RunLifecycle.resume()`, which is run-level (does the daemon-side run
231
- * object transition back to `'running'`), not CLI-conversation-level (does
232
- * the underlying agent CLI continue its own multi-turn session) — persisting
233
- * and replaying these ids into a real `--resume`/`session/load` call on a
234
- * later turn is a downstream chat-composition concern with no current owner
235
- * in this codebase, matching the same "not this package's charter" boundary
236
- * this file's earlier §2's "resume" design decision draws.
237
- *
238
- * **4. `translateAgentRuntimeEvent`'s `usage` payload drops every sub-field
239
- * `RunAgentPayload`'s narrow `usage` variant has no room for.** The 4 parsers
240
- * attach `thought_tokens`, `cached_read_tokens`/`cached_write_tokens`
241
- * (opencode/gemini/codex), `modelUsage`/`stopReason`/`isError` (qoder), and a
242
- * top-level `stopReason` (claude/copilot) that `RunAgentPayload['usage']`
243
- * (only `usage?: {input_tokens?, output_tokens?}`, `costUsd?`, `durationMs?`)
244
- * has no field for — dropped, not silently miscoerced. Widening
245
- * `RunAgentPayload`'s `usage` shape to carry these is a `@jini/protocol`
246
- * change, out of this task's scope (protocol is a separate package with its
247
- * own owner).
248
- *
249
- * **5. `error` events carry a minimal `RunErrorPayload`** — `{message}`,
250
- * plus `error: {code, message}` only when the parser attached a string
251
- * `code` (currently only claude's error event does). The `raw` field some
252
- * parsers attach (opencode/gemini/qoder — a stringified copy of the whole
253
- * original event, for debugging) is not folded into the error payload; kept
254
- * minimal rather than speculatively enriched.
255
- *
256
- * **6. Every `lifecycle.emit()` call is funneled through a per-run FIFO
257
- * queue (`enqueueEmit`, private to `wireChildLifecycle`), not fired
258
- * independently.** A single stdout `data` chunk can synchronously produce
259
- * several parsed events (one JSON line's `feed()` call may invoke a stream
260
- * parser's `onEvent` more than once — e.g. Codex's `item.completed` firing
261
- * both a `tool_use` guard-check and a `tool_result`), and successive `data`
262
- * events must not have their derived `emit()` calls race each other out of
263
- * order. Each queued task is individually try/caught (a single failing
264
- * `emit()` — e.g. a race against an already-terminal run — does not block
265
- * delivery of subsequently queued events), and the `close` handler awaits
266
- * the queue fully drained before computing the terminal outcome, so
267
- * `finish()`'s `'end'` event is always durably last.
268
- *
269
- * **7. Cancellation escalates the child's full descendant process tree, not
270
- * just the direct child.** `@jini/platform`'s `collectProcessTreePids`
271
- * (cross-platform: POSIX `ps` / Windows `Get-CimInstance`) plus
272
- * `stopProcesses` (SIGTERM → poll-wait → SIGKILL escalation) were both
273
- * previously completely unused — zero callers anywhere in this codebase —
274
- * despite being an exact fit; `AgentExecutor` is their first real caller.
275
- * This catches MCP-server/tool-subprocess descendants an OD-style
276
- * POSIX-process-group-only kill would miss.
277
- *
278
- * **8. Native-agent authorization and Jini tool execution are two explicit
279
- * paths.** ACP's `onPermissionRequest` seam receives the tool-call metadata and
280
- * offered option ids before the ACP agent executes its own tool; without an
281
- * injected policy it fails closed, while a runnable host records and chooses an
282
- * offered allow/reject/cancel outcome. The
283
- * autonomous JSON-stream CLIs still report `tool_use` only after their internal
284
- * execution, so their telemetry remains observational. Separately,
285
- * `createDelegatedToolBridge` is the actual Jini-tool path: it emits a matching
286
- * run event pair around `ToolExecutor.execute`, so registry policy,
287
- * confirmation, timeout, cancellation, and the executor audit all apply. It is
288
- * transport-neutral by design; an MCP or other server must decode a concrete
289
- * delegated request before calling it.
290
- *
291
- * ## 2026-07-22 addition — terminal/PTY route pack: `pnpm guard` re-run, and `node-pty`'s missing Linux prebuild — a real deployment decision, not a bare flag
292
- *
293
- * The 2026-07-21 `terminal-session.ts` addition above flagged the `node-pty` Linux-prebuild gap but
294
- * left it as "undiscovered rather than mitigated" pending a real decision. This pass makes that
295
- * decision, backed by evidence gathered in this session, not assumption:
296
- *
297
- * **`pnpm guard`, actually run this session** (it had not been, per this task's own open-items
298
- * list): root `pnpm guard` (scans all of `packages/@jini/**`, including `terminal-session.ts`) — zero
299
- * violations. No R1–R7 boundary/neutrality/sprawl issues in this file.
300
- *
301
- * **The decision.** This repo's own `ADS-memory/reports/jini-port/START-HERE.md` states the architecture plainly:
302
- * *"Jini is a headless daemon"* — a long-running Node server process, the deployment shape that
303
- * overwhelmingly means a Linux host (container or bare server) in practice, not a developer's own
304
- * macOS/Windows machine. `node-pty@1.1.0`'s npm tarball bundles prebuilt native addons for
305
- * `darwin-arm64`/`darwin-x64`/`win32-arm64`/`win32-x64` but ships **no `linux-*` prebuild** — on a
306
- * Linux install, `node-pty`'s own `install` script falls back to `node-gyp rebuild`, which needs a
307
- * C/C++ toolchain (python3/make/a C compiler) present at `pnpm install` time.
308
- *
309
- * **Empirically verified in this exact session**, not inferred from reading `node-pty`'s source: this
310
- * task's own `pnpm install` ran on a real Linux (x64) sandbox with a standard build toolchain present
311
- * and the fallback **worked correctly and automatically** — `node-gyp rebuild` compiled
312
- * `pty.node` from source (`CXX(target) Release/obj.target/pty/src/unix/pty.o` →
313
- * `SOLINK_MODULE(target) Release/obj.target/pty.node`, `gyp info ok`) with zero manual intervention
314
- * beyond the `pnpm.onlyBuiltDependencies` allow-list entry the 2026-07-21 addition already added
315
- * (without that entry, pnpm's supply-chain-safety default silently skips native postinstall scripts
316
- * entirely and this fallback would never run at all — confirmed by that entry already being present
317
- * in this checkout's root `package.json` before this pass touched anything).
318
- *
319
- * **Decision: accept the gap, with a documented, actionable requirement — not a code fallback.**
320
- * Build-from-source *is* the fallback (already the default, already proven working end-to-end this
321
- * session); nothing further needs to be built. What was missing was making the requirement this
322
- * implies for a deployer explicit rather than silent: **a Linux host deploying this daemon must
323
- * have a C/C++ build toolchain (`python3`, `make`, a C compiler — e.g. Debian/Ubuntu's
324
- * `build-essential` package) available at `pnpm install` time.** This is *not* satisfied by a
325
- * minimal/`slim`/`alpine`-family base image out of the box (those deliberately omit build tooling to
326
- * stay small) — a deploying host must either use a base image that already includes one (e.g. the
327
- * non-slim `node:*-bookworm` family) or install the toolchain as an explicit build-stage step before
328
- * `pnpm install`. No CI configuration exists anywhere in this repo to encode this in (confirmed: no
329
- * `.github/workflows` directory) — this paragraph, plus the 2026-07-21 addition's own flag above, is
330
- * this decision's durable record until a real deployment/CI configuration task exists to encode it
331
- * as an executable check instead of documentation. Rejected alternatives: vendoring a prebuilt
332
- * `linux-x64`/`linux-arm64` `.node` binary into this repo (adds binary artifacts + a maintenance
333
- * burden for a native addon this package doesn't own) and pinning an older `node-pty` version that
334
- * might ship a Linux prebuild (checked — no version in this package's supported range ships one;
335
- * Linux users of `node-pty` upstream are documented to be expected to build from source).
336
- *
337
- * ## 2026-07-22 addition — two independent retry classifiers reconciled at merge time (audit fix, AUD-002)
338
- *
339
- * A second cloud session, working in parallel on a branch (`fix/audit-6-fixes-20260722`) that forked
340
- * before the paragraph above landed, independently built its **own** answer to the identical gap-4
341
- * problem: `classifyProcessExitFailure` + `defaultClassifyFailure`, both new exports added directly to
342
- * `agent-executor.ts`, with a **materially different policy** — only `signal === 'SIGPIPE'` was
343
- * classified retryable (`upstream_unavailable`/`network_error`); every other signal, including
344
- * `SIGKILL`/`SIGTERM`, was `process_exit`/`signal_killed` and explicitly **not** retryable. Because
345
- * this branch touched a file `main` had not (a different file than the `run/core/retry.ts` location
346
- * above), a raw `git merge` would have combined both silently with no conflict marker at all — two
347
- * same-shaped, contradictorily-behaving classifiers coexisting in the tree, one of them dead code by
348
- * accident of which line happened to get wired last. A deep-dive audit
349
- * (`ADS-memory/reports/audit-fastify-merge-and-six-gap-fixes-2026-07-22.md`, AUD-002) caught this
350
- * before it landed.
351
- *
352
- * **Decision made at merge time: keep this file's `run/core/retry.ts` version; delete the branch's
353
- * `agent-executor.ts` duplicate.** Reasoning: the branch's SIGPIPE-only policy is more conservative
354
- * but misses the single most common real-world signal-kill scenario this classifier exists for — an
355
- * OS/container OOM killer or infra-level eviction sending `SIGKILL` to a healthy process, which is
356
- * presumptively transient and exactly the case worth retrying. The broader "any signal is
357
- * presumptively transient" policy this file already documents does risk retrying a genuine crash
358
- * signal (e.g. `SIGSEGV`) that a retry won't fix, but the blast radius of that miss is small and
359
- * bounded: `DEFAULT_SAFE_RUN_RETRY_MAX_ATTEMPTS = 1`, so the cost of being too permissive here is at
360
- * most one wasted extra attempt, while the cost of the branch's more conservative policy is silently
361
- * never retrying the dominant legitimate case. `agent-executor.ts`'s own `classifyProcessExitFailure`/
362
- * `defaultClassifyFailure` exports and their dedicated tests were removed as part of this merge;
363
- * `FailureClassificationContext`'s `code`/`signal`-only scope note is unaffected (both
364
- * implementations agreed on that boundary — see this file's now-single classifier for the reasoning).
365
- *
366
- * Also merged in from the same branch, independent of the classifier question: real `sideEffects`
367
- * wiring (`userVisibleOutputSeen`/`toolCallSeen`, derived live from the translated agent-event stream
368
- * in all three `wire*Lifecycle` drivers) so two of `decideSafeRunRetry`'s four side-effect-suppression
369
- * guards are now genuinely exercised, not permanently dead code — see `resumableFromProcessExit`'s own
370
- * doc in `retry.ts` for exactly which two, and why `artifactWriteSeen`/`liveArtifactSeen` still
371
- * cannot be (no `'artifact'`/`'live_artifact'` event kind exists anywhere in `@jini/protocol`'s
372
- * `RunAgentEventPayload` union today — a real protocol gap, not an oversight; see this repo's own
373
- * "A2UI full protocol deferred" scope note). `attemptCount` stays a documented, correct `0`: no
374
- * automatic same-run retry loop exists anywhere in this codebase yet (gap 4's `resumable` flag is
375
- * read-only metadata for a host's own later follow-up run, not an auto-retry trigger — see
376
- * `resumableFromProcessExit`'s doc), so every real call to this classifier genuinely is evaluating a
377
- * first and only attempt; a future auto-retry loop would need to supply its own real count.
378
- *
379
- * **Verified, personally, this session**: `pnpm --dir packages/daemon exec tsc --noEmit` clean;
380
- * `pnpm --dir packages/daemon run test:coverage` — all tests pass, `retry.ts` and `agent-executor.ts`
381
- * both 100/100/100/100 after the branch's duplicate export and its 8 dedicated unit tests were
382
- * removed and the 3 `wire*Lifecycle` drivers' new side-effect tracking got its own coverage.
383
- *
384
- * ### The decision: def-declared fields, not an id branch
385
- *
386
- * `PROP-plain-format-agent-driving-2026-07-21.md`'s open question 6 asked whether antigravity's
387
- * divergence stays "an id-keyed special case inside `@jini-ai/daemon` (mirroring OD's own choice to
388
- * hardcode `def.id === 'antigravity'` in `server.ts`)" or gets a `RuntimeAgentDef`-level field (its
389
- * "Option C"). **Option C, deliberately** — and the evidence is not a style preference:
390
- *
391
- * - `RuntimeAgentDef` already carries **14** optional behavior flags/hooks the executor reads
392
- * generically (`promptViaFile`, `promptViaStdin`, `resumesSessionViaCli`,
393
- * `capturesSessionIdFromStream`, `resumesSessionViaAcpLoad`, `externalMcpInjection`, `authProbe`,
394
- * `acpMcpEnvFormat`, `defaultModelEnvVar`, `inactivityTimeoutMs`, `maxPromptArgBytes`,
395
- * `supportsCustomModel`, `promptInputFormat`, `promptViaStdin`'s `permissionMode`-aware `buildArgs`).
396
- * - There was **zero** precedent anywhere in this package for the executor branching on a literal
397
- * agent id to decide *behavior* — the antigravity rejection was the only id branch at all, and it
398
- * decided *support*, not behavior.
399
- * - OD's `server.ts` hardcodes `def.id === 'antigravity'` twice; §2d of the same proposal already
400
- * records that OD's *prompt-delivery* call sites, by contrast, key only off declared fields with
401
- * zero id branches. The field-driven half is the half that generalized cleanly.
402
- *
403
- * Consequence worth stating plainly: a second CLI with either property (prints a secret on stdout and
404
- * exits 0; or mutates process-global state its own startup reads back) needs **no new executor code**.
405
- *
406
- * ## 2026-07-30 addition — post-merge audit fixes (Codex gpt-5.6-sol findings on `9cb4ffc50…085c4799a`)
407
- *
408
- * Full verification write-up, per finding, with the observed red/green output:
409
- * `ADS-memory/reports/post-merge-audit-2026-07-29/daemon-fix-report.md`. Findings list:
410
- * `daemon-findings.md` in that same directory. All 5 blocking and 2 of 3 non-blocking findings were real
411
- * — no false positives. Every fix landed behind a test confirmed failing first.
412
- *
413
- * **`.mcp.json` is now one file per run.** `mcpServers.jini.env` carries this run's `JINI_RUN_ID` and its
414
- * bearer `JINI_DAEMON_TOKEN`, and a shared `cwd/.mcp.json` cannot hold two runs' identities at once — a
415
- * CLI loads its MCP config when it starts its client, not at spawn, so a second run in the same directory
416
- * overwrote the entry the first run's child had not read yet and that child then called back with the
417
- * *other* run's id and token. `mcpJsonPathForRun` writes `<cwd>/.mcp.jini-<runId>.json`; `cwd/.mcp.json`
418
- * is now read-only (still the merge source, so a project's own servers survive, and nothing needs
419
- * restoring); the run's file is removed through `cleanupStagedFiles` via a new
420
- * `McpJsonInjectionOptions.removeFile` seam, so a live token no longer outlives the run. Refusing the
421
- * second run instead was rejected: concurrent runs in one cwd are a documented design point (that is why
422
- * `credential` is a per-run resolver at all).
423
- *
424
- * **One follow-up this leaves open, in `@jini-ai/agent-runtime` rather than here.** The run-scoped path
425
- * only reaches the child because the def passes it on — `claude.ts` emits
426
- * `--strict-mcp-config --mcp-config <runtimeContext.mcpJsonPath>`. **`codebuddy.ts`, the other
427
- * `externalMcpInjection: 'claude-mcp-json'` def, ignores `mcpJsonPath`** and relies on its CLI
428
- * auto-discovering `cwd/.mcp.json`, so a codebuddy run with `mcpJsonInjection` configured now writes a
429
- * file nothing reads. That def wants the same two flags under the same guard — deliberately not done
430
- * blind, because codebuddy's acceptance of those flags is unverified and an unsupported flag fails the
431
- * run outright, which is worse than losing tools on a path `claude.ts`'s own live 2026-07-30 note says
432
- * never worked headlessly anyway (auto-discovery needs an interactive trust prompt; the connection sat at
433
- * `"pending"` forever). Verify with one live codebuddy run, then add the flags.
434
- *
435
- * **Resolved the same day, below** — the "the other 3 MCP injection mechanisms" entry closes this
436
- * exact follow-up (Gap A) plus the two mechanisms nothing implemented at all (Gaps B/C), all through
437
- * one dispatch point rather than one-off patches.
438
- *
439
- * **The `'until-close'` stdout accumulator is bounded.** `DEFAULT_BUFFERED_STDOUT_MAX_BYTES` (8 MiB,
440
- * overridable via `CreateAgentExecutorOptions.bufferedStdoutMaxBytes`) — an `until-close` child can emit
441
- * indefinitely or never close, and this driver treats agent CLIs as potentially adversarial elsewhere
442
- * (SEC-001), so one run could exhaust the daemon heap and take every unrelated run with it. Whole chunks
443
- * only (no sliced multi-byte characters), everything after the first refusal dropped, and truncation is
444
- * reported in a host-authored note appended *after* the def's `sanitize` runs — a consumer redactor must
445
- * not be able to delete the line saying output is missing, and a truncated run emits even when the
446
- * sanitized text is empty.
447
- *
448
- * **Nothing fallible now sits in front of `finish()`.** `buildArgs` is guarded like every other
449
- * staging→spawn step (it performs real filesystem writes — that is what `runtimeLock` exists for — so
450
- * EACCES/ENOSPC used to escape `run()` as a bare `Error`, stranding the run *and* holding the
451
- * process-global mutex forever). In all three close handlers, staged-file cleanup and the host's
452
- * `classifyFailure` are guarded by `cleanupStagedFilesSafely`/`classifyFailureSafely`: previously either
453
- * rejection took the terminal transition with it, leaving a `'running'` run whose child was already gone
454
- * and surfacing only as an unhandled rejection. The finding cited only the child-driven handler; the ACP
455
- * and pi-rpc handlers had the identical shape and the identical bug. `reportPostCloseFailure` also
456
- * absorbs a throwing `onCleanupFailure` sink. `AgentCleanupFailurePhase` gains `'staged-file-cleanup'`
457
- * and `'failure-classification'`; `AgentCleanupFailureContext.pid` widens to `number | undefined`.
458
- *
459
- * **`RunLifecycle` no longer exposes uncommitted starts.** `get`/`list` await a pending `startPromise` (a
460
- * failed durable start reads as "no such run" rather than as a running one no restart could rehydrate),
461
- * and `waitForTerminal` awaits *and propagates* it before parking a waiter — which is what closes the
462
- * hang: nothing ever calls `finish()` for an unwound record, so such a waiter was previously never
463
- * resolved or rejected.
464
- *
465
- * **Two smaller ones.** `FrontendSessionRegistry` keys run bindings on the attachment object, not the
466
- * session id: a session id is reusable by design (a reconnecting tab re-attaches under the same id), so a
467
- * stale handle's `detach()` used to remove the *replacement* session, its bindings, and the usability of
468
- * its still-current bind token. `createDefaultRunStartHandler` now forwards `imagePaths`,
469
- * `extraAllowedDirs` and `uploadRoot` — the same silent-drop failure mode `permissionMode` was already
470
- * documented for.
471
- *
472
- * **Left as a note, not patched:** `remote-tool-bridge.ts` does not dedup on `toolUseId`, so a retried
473
- * cross-process POST appends duplicate tool events. Real, but every in-reach fix is worse than the bug:
474
- * the recorder holds only a `RunLifecycle` (no read API, so dedup could only be non-durable in-memory
475
- * state that a restart defeats), it would need per-run lifetime machinery this module has no hook for,
476
- * and `delegated-tool-bridge.ts`'s `execute()` does not dedup either — deduping one half would split two
477
- * paths this module exists to keep byte-identical. The honest fix is idempotency at the
478
- * `RunLifecycle.emit` (or transport) boundary.
479
- *
480
- * Tests: **`@jini-ai/daemon` 654 → 682**, 26 files, zero failures — `agent-executor.test.ts` 189 → 210,
481
- * `frontend-session-registry.test.ts` 41 → 44, `run-lifecycle.test.ts` 53 → 56,
482
- * `run-start-handler.test.ts` 7 → 8. No test deleted or weakened; three existing `.mcp.json` tests were
483
- * updated for the genuinely-changed write path (two of them strengthened — see the report). Coverage:
484
- * package 99.85/99.93/99.64/99.85, with `agent-executor.ts`, `frontend-session-registry.ts` and
485
- * `run-start-handler.ts` all fully covered; the only residual gap is the pre-existing, deliberately
486
- * uncovered `handleInactivityTimeout` guard in `run-lifecycle.ts` that function's own doc already explains.
487
- * `pnpm typecheck` (package and `-r` workspace-wide, since two exported types changed): clean.
488
- * `pnpm guard`: clean.
489
- *
490
- * ## 2026-07-30 addition — the other 3 MCP injection mechanisms get wired: `acp-merge`, the two `*-env-content` strategies, and codebuddy's missing argv
491
- *
492
- * The 2026-07-22 gap-3-part-2 entry above wired exactly one of the four declared
493
- * `externalMcpInjection` strategies. Its own text named the other three as future work ("a future
494
- * task wiring those two would extend `wireAcpLifecycle`'s existing `envFormat`/`mcpServers`
495
- * passthrough or `applyAgentLaunchEnv`'s env composition respectively"). A peer review then confirmed
496
- * the practical consequence: of 24 defs, only `claude` actually delivered MCP tools end to end. This
497
- * entry closes all three remaining gaps. **Adding a strategy for a currently-undeclared agent (e.g.
498
- * `codex`) was explicitly out of scope and was not done.**
499
- *
500
- * **The boundary this is organised around** is per-CLI vs per-mechanism. Identity, binary, and
501
- * model/session/permission argv are genuinely bespoke and stay in each def's own `buildArgs`. MCP
502
- * staging, ACP server merging, and env-content serialisation are *mechanisms*, and each now has
503
- * exactly one implementation that every def declaring it flows through.
504
- *
505
- * **Gap A — `codebuddy` (`'claude-mcp-json'`).** The executor already computed and staged the same
506
- * `.mcp.json` for it, but `defs/codebuddy.ts`'s `buildArgs` never read `runtimeContext.mcpJsonPath`,
507
- * so it was still left on the headless auto-discovery path the claude fix exists to avoid. Rather
508
- * than copy claude's branch, the flag-building moved into a shared
509
- * `@jini-ai/agent-runtime` helper — **`buildClaudeMcpConfigArgs` in `src/defs/shared.ts`** — that both
510
- * defs now call. `--strict-mcp-config` was verified as genuinely supported by CodeBuddy before being
511
- * added (its own CLI reference documents `--mcp-config <fileOrString>` and `--strict-mcp-config`,
512
- * "Only use MCP servers in --mcp-config, ignore other MCP configuration"), so codebuddy gets the same
513
- * strict-isolation guarantee as claude rather than a silently weaker one. `shared.test.ts` pins that
514
- * both defs emit byte-identical MCP argv for identical input, so a future copy-paste divergence fails
515
- * there rather than in production.
516
- *
517
- * **Gap B — the 8 `'acp-merge'` defs** (devin, hermes, kilo, kimi, kiro, reasonix, trae-cli, vibe),
518
- * which were getting *zero* MCP tools, not merely a missing flag. `attachAcpSession` has always
519
- * accepted `mcpServers`; `wireAcpLifecycle` simply never passed any. Fixed once, in that shared
520
- * function (`WireAcpLifecycleContext.mcpServers` → spread into the attach call when non-empty) —
521
- * **no per-def file was touched**, because the gap was never in the defs. A 9th def declaring
522
- * `'acp-merge'` is covered automatically. Each entry's `env` is emitted as a plain object so
523
- * `buildAcpSessionNewParams` keeps owning the array-vs-map wire-shape fork (reasonix declares
524
- * `acpMcpEnvFormat: 'map'`).
525
- *
526
- * **Gap C — `'opencode-env-content'` / `'mimo-env-content'`.** Two labels, one serialiser: MiMo's own
527
- * def doc already stated it consumes the same JSON schema as OpenCode's, differing only in env-var
528
- * namespace, and the tests pin that the emitted content is byte-identical modulo the run id. The
529
- * difference is one row in `ENV_CONTENT_VAR_BY_STRATEGY` (`OPENCODE_CONFIG_CONTENT` /
530
- * `MIMOCODE_CONFIG_CONTENT`), not a second code path. `mergeEnvContentMcpConfig` applies the same
531
- * "merge, never clobber" discipline `mergeMcpJsonContent` does, and for a sharper reason: a host may
532
- * already be handing the CLI the *user's* configured MCP servers through that very variable.
533
- *
534
- * **One dispatch point, one credential.** `buildMcpBridgeDelivery` (pure, exported) is the single
535
- * place a declared strategy maps to a mechanism, returning a discriminated `McpBridgeDelivery` so a
536
- * consumer cannot read another mechanism's payload. `run()` resolves the per-run bearer credential
537
- * exactly once, before `buildArgs`, and hands it to whichever serialiser applies; a rejecting resolver
538
- * still fails the run before spawn. Keyed off the *strategy*, never off `def.id` — a registry-level
539
- * test asserts every real def declaring a strategy gets a non-null delivery, so a 25th def with an
540
- * unhandled strategy fails there.
541
- *
542
- * **Security.** The credential reaches every mechanism through an *environment*, never through
543
- * process arguments (`ps` is readable by other local users): `.mcp.json`'s `env`, the ACP server
544
- * descriptor's `env` (which the ACP agent applies to the MCP child it spawns), and the env-content
545
- * config's `environment` key — itself carried in the spawned CLI's own env var, deliberately not a
546
- * `-c key=value`-style CLI argument. Three dedicated tests assert the secret is absent from argv while
547
- * still present where it must be, including one driving a real subprocess.
548
- *
549
- * **Files changed.** `@jini-ai/agent-runtime`: `src/defs/shared.ts` (new
550
- * `buildClaudeMcpConfigArgs`), `src/defs/claude.ts` and `src/defs/codebuddy.ts` (both call it).
551
- * `@jini-ai/daemon`: `src/agent-executor.ts` (new `buildAcpMcpBridgeServers`,
552
- * `ENV_CONTENT_VAR_BY_STRATEGY`, `mergeEnvContentMcpConfig`, `McpBridgeDelivery`,
553
- * `buildMcpBridgeDelivery`; `writeMcpJsonForRun` narrowed to consume an already-built delivery;
554
- * `wireAcpLifecycle` gained `mcpServers`; `run()` composes `childEnv`). `McpJsonInjectionOptions`
555
- * keeps its name for API compatibility with `@jini-ai/server`'s `agentExecutor` passthrough even
556
- * though it is no longer `.mcp.json`-specific — a naming-debt item, deliberately not renamed here.
557
- * Reconciled the same session with the run-scoped-path fix above: `buildMcpBridgeDelivery`'s
558
- * `'claude-mcp-json'` case calls `mcpJsonPathForRun`, not a shared `cwd/.mcp.json`, so the
559
- * cross-run credential leak that fix closed stays closed under the generalized dispatch too;
560
- * `writeMcpJsonForRun` takes `cwd` as an explicit param so it still reads the project's own
561
- * `.mcp.json` as its merge base while writing the run-scoped path.
562
- *
563
- * **Tests / verification, run this session.** `@jini-ai/agent-runtime` **1848 → 1858**
564
- * (`defs/__tests__/shared.test.ts` +5, `defs/__tests__/codebuddy.test.ts` +5). `@jini-ai/daemon`
565
- * **654 → 687** (`__tests__/agent-executor.test.ts` +30 across `buildAcpMcpBridgeServers`,
566
- * `mergeEnvContentMcpConfig`, `buildMcpBridgeDelivery`, the ACP passthrough block and the env-content
567
- * block; `__tests__/agent-executor-acp.integration.test.ts` +3). Nothing existing regressed. The three
568
- * integration additions are the closest available analogue to the live smoke check that verified the
569
- * claude fix: the existing ACP fixture — a **real spawned Node subprocess** speaking real ACP JSON-RPC
570
- * over real stdio, through the real `attachAcpSession` — now echoes back the `mcpServers` its own
571
- * `session/new` received, so delivery is asserted from the agent's point of view rather than from a
572
- * stubbed attach call, in both `array` and `map` env formats. No vendor CLI or credentials were
573
- * needed, and none of the 24 real agent CLIs were spawned. `tsc --noEmit` and `tsc -p tsconfig.json`
574
- * (build): clean for both packages; root `pnpm -r typecheck`: clean.
4
+ * Stateful kernel orchestration: RunLifecycle, the durable EventLog port,
5
+ * ToolExecutor and the AgentExecutor runtime driver. Feature storage is injected;
6
+ * ArtifactStore belongs to @jini-ai/artifacts. Routines own scheduling and their
7
+ * CRUD/run-history port. Terminal sessions own PTY lifecycle, session access and
8
+ * kill/write/resize locking. Startup data migration belongs here because it must
9
+ * precede service startup, rather than run inside the pure core interfaces.
10
+ *
11
+ * Run contracts:
12
+ * - cancel records intent; drivers finish after the real outcome is known.
13
+ * RunLifecycle does not spawn or signal processes because those mechanics
14
+ * belong to the runtime driver. Late cancellation listeners receive the last
15
+ * request immediately so asynchronous driver setup cannot miss cancellation.
16
+ * - resume reuses the same runId and EventLog cursor sequence without a new start
17
+ * event. This is distinct from resuming a vendor CLI session. The lifecycle's
18
+ * run registry is internal, not a separate RunStore token; durable event replay
19
+ * must not be confused with persistence of arbitrary live process state.
20
+ * - start/finish own start/end events; DriverEmittableInput is an explicit union
21
+ * so drivers cannot originate those transitions. Unknown or terminal emits
22
+ * throw because they indicate driver bugs. The cancelled/canceled spelling
23
+ * bridge preserves the protocol's existing state and payload vocabularies.
24
+ * - Pending durable starts are awaited before exposing run records or parking
25
+ * terminal waiters: a failed append must not expose an uncommitted run.
26
+ *
27
+ * Tool security and confirmation:
28
+ * - Tool policy authorization and user confirmation are separate gates.
29
+ * A transport delegate may veto an allowed policy decision, never override a
30
+ * denial. ToolExecutor is the invocation boundary; handlers are not exposed.
31
+ * - A synchronous undefined confirmation parks the promise for
32
+ * resumeConfirmation; an async promise resolving to undefined is a denial.
33
+ * Audit records and pending confirmations are process-local, so cross-restart
34
+ * durability needs a host-owned store. Unknown tool IDs throw as routing bugs;
35
+ * denial, timeout, cancellation and handler failure are result variants.
36
+ * - maxOutputBytes bounds UTF-8 string output; structured output is not a general
37
+ * serialization limit. Handler timeout/controller cleanup occurs at settlement
38
+ * points; it must not leave an active execution behind.
39
+ *
40
+ * Runtime driver invariants:
41
+ * - Parsed event delivery uses a per-run FIFO and drains before finish so end is
42
+ * durably last. A failed emit does not block later queued events. Cancellation
43
+ * stops the full descendant process tree because tools may spawn grandchildren.
44
+ * - Native ACP permission requests fail closed without an injected policy.
45
+ * Autonomous CLI tool-use telemetry is observational; delegated Jini calls go
46
+ * through ToolExecutor with authorization, confirmation and audit intact.
47
+ * - Vendor usage is narrowed to protocol fields, rather than inventing wire
48
+ * fields; error payloads stay minimal. Session handles and turn-end control
49
+ * are runtime/continuation concerns, distinct from RunLifecycle.resume.
50
+ * - Failure classification belongs to run/core/retry. Signal kills may be
51
+ * transient (including OOM/eviction); the conservative side-effect guards and
52
+ * one-retry limit bound duplicate work and wasted attempts. Resumability
53
+ * metadata does not itself start an automatic retry loop.
54
+ * - Runtime behavior follows def-declared fields, not agent-ID branches, so a
55
+ * second CLI with the same prompt/output/lock behavior needs no driver fork.
56
+ * - MCP mechanisms have one strategy dispatcher. Per-CLI identity and argv stay
57
+ * in runtime defs; mechanism staging, server merging and env serialization
58
+ * stay shared. Existing user servers are merged rather than clobbered.
59
+ * - MCP config files are run-scoped because concurrent runs cannot share bearer
60
+ * identity. The project's .mcp.json is only a merge source; staged files are
61
+ * removed at cleanup so live credentials do not outlast the run. Credentials
62
+ * travel in environments, never argv, which other local users can inspect.
63
+ * - until-close stdout is bounded because an adversarial child can exhaust the
64
+ * daemon heap. Whole chunks preserve multibyte characters; a host truncation
65
+ * note follows vendor sanitization so redaction cannot hide missing output.
66
+ * - Fallible buildArgs, staged-file cleanup, classification and reporting must
67
+ * not strand runs or runtime locks; post-close failures cannot prevent finish.
68
+ * - Frontend session bindings use attachment identity because reconnects reuse
69
+ * session IDs; stale detach handles must not remove replacement sessions.
70
+ * Run-start adapters forward imagePaths, extraAllowedDirs and uploadRoot.
71
+ * - Remote/delegated tool events do not establish retry idempotency. A durable
72
+ * solution belongs at the lifecycle or transport boundary, rather than a
73
+ * restart-unsafe recorder cache that makes the two paths disagree.
74
+ *
75
+ * node-pty uses native addons. Linux deployment requires python3, make and a
76
+ * C/C++ compiler during installation; source compilation is the fallback.
77
+ * Vendoring foreign prebuilt binaries would create an unowned maintenance burden.
78
+ * See the owning modules and packages/daemon/README.md for detailed contracts.
575
79
  */
576
80
  export * from './event-log.js';
577
81
  export * from './close-status.js';