@jini-ai/daemon 0.3.8 → 0.5.1

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 (159) hide show
  1. package/CHANGELOG.md +124 -0
  2. package/README.md +50 -8
  3. package/dist/agent-executor/contracts.d.ts +736 -0
  4. package/dist/agent-executor/contracts.js +28 -0
  5. package/dist/agent-executor/event-translation.d.ts +87 -0
  6. package/dist/agent-executor/event-translation.js +230 -0
  7. package/dist/agent-executor/events.d.ts +190 -0
  8. package/dist/agent-executor/events.js +848 -0
  9. package/dist/agent-executor/handoff.d.ts +18 -0
  10. package/dist/agent-executor/handoff.js +16 -0
  11. package/dist/agent-executor/index.d.ts +98 -0
  12. package/dist/agent-executor/index.js +98 -0
  13. package/dist/agent-executor/launch.d.ts +375 -0
  14. package/dist/agent-executor/launch.js +1001 -0
  15. package/dist/agent-executor/mcp-bridge.d.ts +338 -0
  16. package/dist/agent-executor/mcp-bridge.js +835 -0
  17. package/dist/agent-executor/values.d.ts +5 -0
  18. package/dist/agent-executor/values.js +13 -0
  19. package/dist/agent-executor.d.ts +375 -176
  20. package/dist/agent-executor.js +388 -335
  21. package/dist/close-status.d.ts +11 -8
  22. package/dist/close-status.js +31 -19
  23. package/dist/continuation/continuation-transport.d.ts +3 -1
  24. package/dist/continuation/continuation-transport.js +1 -1
  25. package/dist/continuation/journal.d.ts +12 -5
  26. package/dist/continuation/journal.js +4 -4
  27. package/dist/continuation/run-scoped-context-store.d.ts +13 -4
  28. package/dist/continuation/run-scoped-context-store.js +6 -6
  29. package/dist/continuation/run-start-handler.d.ts +1 -1
  30. package/dist/continuation/run-start-handler.js +3 -16
  31. package/dist/delegated-tool-bridge.d.ts +7 -3
  32. package/dist/delegated-tool-bridge.js +32 -31
  33. package/dist/event-log.d.ts +7 -5
  34. package/dist/event-log.js +12 -11
  35. package/dist/frontend-capability-tools.d.ts +1 -1
  36. package/dist/frontend-capability-tools.js +3 -2
  37. package/dist/frontend-session-registry.d.ts +32 -9
  38. package/dist/frontend-session-registry.js +12 -11
  39. package/dist/http/active-context.d.ts +105 -0
  40. package/dist/http/active-context.js +67 -0
  41. package/dist/http/agents.d.ts +68 -0
  42. package/dist/http/agents.js +12 -0
  43. package/dist/http/attachments.d.ts +618 -0
  44. package/dist/http/attachments.js +1034 -0
  45. package/dist/http/cancel-owned-runs.d.ts +49 -0
  46. package/dist/http/cancel-owned-runs.js +35 -0
  47. package/dist/http/component-catalog.d.ts +84 -0
  48. package/dist/http/component-catalog.js +64 -0
  49. package/dist/http/connectors.d.ts +209 -0
  50. package/dist/http/connectors.js +409 -0
  51. package/dist/http/daemon-status.d.ts +76 -0
  52. package/dist/http/daemon-status.js +55 -0
  53. package/dist/http/db-ops.d.ts +123 -0
  54. package/dist/http/db-ops.js +189 -0
  55. package/dist/http/delegated-tools.d.ts +113 -0
  56. package/dist/http/delegated-tools.js +230 -0
  57. package/dist/http/frontend-control.d.ts +136 -0
  58. package/dist/http/frontend-control.js +79 -0
  59. package/dist/http/frontend-sessions.d.ts +110 -0
  60. package/dist/http/frontend-sessions.js +180 -0
  61. package/dist/http/health.d.ts +95 -0
  62. package/dist/http/health.js +40 -0
  63. package/dist/http/host-tools.d.ts +214 -0
  64. package/dist/http/host-tools.js +329 -0
  65. package/dist/http/index.d.ts +52 -0
  66. package/dist/http/index.js +27 -0
  67. package/dist/http/media.d.ts +146 -0
  68. package/dist/http/media.js +346 -0
  69. package/dist/http/memory.d.ts +265 -0
  70. package/dist/http/memory.js +328 -0
  71. package/dist/http/model-proxy.d.ts +49 -0
  72. package/dist/http/model-proxy.js +642 -0
  73. package/dist/http/remote-run-events.d.ts +70 -0
  74. package/dist/http/remote-run-events.js +224 -0
  75. package/dist/http/research.d.ts +48 -0
  76. package/dist/http/research.js +315 -0
  77. package/dist/http/route-manifest.d.ts +35 -0
  78. package/dist/http/route-manifest.js +103 -0
  79. package/dist/http/routines.d.ts +120 -0
  80. package/dist/http/routines.js +298 -0
  81. package/dist/http/run-credentials.d.ts +73 -0
  82. package/dist/http/run-credentials.js +71 -0
  83. package/dist/http/runs.d.ts +91 -0
  84. package/dist/http/runs.js +221 -0
  85. package/dist/http/terminals.d.ts +138 -0
  86. package/dist/http/terminals.js +325 -0
  87. package/dist/http/tool-catalog.d.ts +78 -0
  88. package/dist/http/tool-catalog.js +66 -0
  89. package/dist/http/workspace-root.d.ts +77 -0
  90. package/dist/http/workspace-root.js +63 -0
  91. package/dist/http/xai.d.ts +90 -0
  92. package/dist/http/xai.js +655 -0
  93. package/dist/image-prompt-delivery.d.ts +13 -3
  94. package/dist/image-prompt-delivery.js +5 -5
  95. package/dist/index.d.ts +556 -4
  96. package/dist/index.js +556 -4
  97. package/dist/legacy-data-migration.d.ts +59 -8
  98. package/dist/legacy-data-migration.js +38 -33
  99. package/dist/read-only-tools.d.ts +130 -0
  100. package/dist/read-only-tools.js +115 -0
  101. package/dist/remote-tool-bridge.d.ts +11 -5
  102. package/dist/remote-tool-bridge.js +15 -15
  103. package/dist/routines/index.d.ts +1 -1
  104. package/dist/routines/index.js +1 -1
  105. package/dist/routines/routine-store.d.ts +34 -12
  106. package/dist/routines/routine-store.js +21 -20
  107. package/dist/routines/schedule.d.ts +48 -10
  108. package/dist/routines/schedule.js +21 -21
  109. package/dist/routines/scheduler.d.ts +32 -11
  110. package/dist/routines/scheduler.js +61 -57
  111. package/dist/routines/types.d.ts +10 -3
  112. package/dist/run/core/result.d.ts +6 -2
  113. package/dist/run/core/result.js +3 -3
  114. package/dist/run/core/retry.d.ts +17 -4
  115. package/dist/run/core/retry.js +7 -11
  116. package/dist/run/diagnostics/diagnostics.d.ts +14 -4
  117. package/dist/run/diagnostics/diagnostics.js +7 -7
  118. package/dist/run/index.d.ts +1 -1
  119. package/dist/run/index.js +1 -1
  120. package/dist/run-credentials.d.ts +138 -0
  121. package/dist/run-credentials.js +152 -0
  122. package/dist/run-lifecycle.d.ts +92 -33
  123. package/dist/run-lifecycle.js +81 -74
  124. package/dist/scheduler.d.ts +8 -0
  125. package/dist/scheduler.js +2 -0
  126. package/dist/session-coordination.d.ts +115 -0
  127. package/dist/session-coordination.js +111 -0
  128. package/dist/store/agent-sessions/errors.d.ts +8 -0
  129. package/dist/store/agent-sessions/errors.js +10 -0
  130. package/dist/store/agent-sessions/index.d.ts +12 -0
  131. package/dist/store/agent-sessions/index.js +19 -0
  132. package/dist/store/agent-sessions/pglite.d.ts +12 -0
  133. package/dist/store/agent-sessions/pglite.js +7 -0
  134. package/dist/store/agent-sessions/ports.d.ts +24 -0
  135. package/dist/store/agent-sessions/ports.js +2 -0
  136. package/dist/store/agent-sessions/postgres.d.ts +12 -0
  137. package/dist/store/agent-sessions/postgres.js +7 -0
  138. package/dist/store/agent-sessions/schema.d.ts +3 -0
  139. package/dist/store/agent-sessions/schema.js +23 -0
  140. package/dist/store/agent-sessions/sql.d.ts +35 -0
  141. package/dist/store/agent-sessions/sql.js +59 -0
  142. package/dist/store/agent-sessions/sqlite.d.ts +117 -0
  143. package/dist/store/agent-sessions/sqlite.js +128 -0
  144. package/dist/store/event-log/sqlite.d.ts +75 -0
  145. package/dist/store/event-log/sqlite.js +230 -0
  146. package/dist/surface-exchanges.d.ts +97 -0
  147. package/dist/surface-exchanges.js +153 -0
  148. package/dist/terminal-session.d.ts +43 -11
  149. package/dist/terminal-session.js +22 -20
  150. package/dist/tokens.js +4 -4
  151. package/dist/tool-audit.d.ts +114 -0
  152. package/dist/tool-audit.js +95 -0
  153. package/dist/tool-executor.d.ts +22 -7
  154. package/dist/tool-executor.js +16 -16
  155. package/dist/tool-result-media.d.ts +3 -1
  156. package/dist/tool-result-media.js +1 -1
  157. package/dist/tool-result-surfaces.d.ts +3 -1
  158. package/dist/tool-result-surfaces.js +1 -1
  159. package/package.json +127 -9
@@ -0,0 +1,736 @@
1
+ import { type spawn as nodeSpawn, type ChildProcess } from 'node:child_process';
2
+ import { type Principal } from '@jini-ai/core';
3
+ import { type RunAgentPayload, type RunErrorPayload } from '@jini-ai/protocol';
4
+ import { type applyAgentLaunchEnv, type ensureAgentCapabilities, type getAgentDef, type resolveAgentLaunch, type attachAcpSession, type attachPiRpcSession, type prepareAgentLogFile, type preparePromptFileForAgent, type AcpMcpServerInput, type AcpPermissionHandler, type PromptAugmenter } from '@jini-ai/agent-runtime';
5
+ import { type createCommandInvocation, type listProcessSnapshots, type ProcessSnapshot, type StopProcessesResult } from '@jini-ai/platform';
6
+ import { type RunByteJournal } from '../continuation/journal.js';
7
+ import { type RunRetrySideEffectState } from '../run/core/index.js';
8
+ import { type ToolExecutor } from '../tool-executor.js';
9
+ import { type RunLifecycle } from '../run-lifecycle.js';
10
+ /**
11
+ * A parsed, loosely-typed event as produced by one of `@jini-ai/agent-runtime`'s
12
+ * four stream-parser factories — each parser's `onEvent` callback receives
13
+ * `Record<string, unknown>` with a `type` discriminant, not a typed union.
14
+ */
15
+ export type StreamHandler = {
16
+ feed(args: {
17
+ chunk: string;
18
+ }): void;
19
+ flush(): void;
20
+ };
21
+ export declare const SUPPORTED_STREAM_FORMATS: readonly ["claude-stream-json", "json-event-stream", "copilot-stream-json", "qoder-stream-json", "acp-json-rpc", "pi-rpc", "plain"];
22
+ /** The families of the registry's stream-format this driver implements — see index.ts module doc. */
23
+ export type SupportedStreamFormat = (typeof SUPPORTED_STREAM_FORMATS)[number];
24
+ export type JsonStreamFormat = Exclude<SupportedStreamFormat, 'acp-json-rpc' | 'pi-rpc' | 'plain'>;
25
+ /**
26
+ * `streamFormat` values `wireChildLifecycle` drives directly off raw
27
+ * `child.stdout` `'data'` events: the 4 JSON-stream-parser formats (fed
28
+ * through a real `feed()`/`flush()` state machine via
29
+ * {@link createStreamHandlerForDef}) plus `'plain'` (no parser at all —
30
+ * each chunk is forwarded verbatim as a `text_delta`, see that function's
31
+ * doc). Distinct from `'acp-json-rpc'`/`'pi-rpc'`, which own their own
32
+ * JSON-RPC prompt/event protocol and get their own
33
+ * `wireAcpLifecycle`/`wirePiRpcLifecycle` wiring instead.
34
+ */
35
+ export type ChildDrivenStreamFormat = JsonStreamFormat | 'plain';
36
+ /**
37
+ * Whether `run()` can drive a given def, and — when it can — its `streamFormat` already narrowed for
38
+ * the dispatch logic that follows. The narrowing rides along deliberately: it is what lets `run()`
39
+ * delegate every compatibility guard here without then re-checking the format to satisfy the type
40
+ * system, which would leave an unreachable branch behind.
41
+ */
42
+ export type AgentExecutorCompatibility = {
43
+ readonly supported: true;
44
+ readonly streamFormat: SupportedStreamFormat;
45
+ } | {
46
+ readonly supported: false;
47
+ readonly reason: string;
48
+ };
49
+ /**
50
+ * Result of translating one parsed stream event into this engine's
51
+ * vocabulary. `'agent'` is the common case (forward as a `RunAgentPayload`
52
+ * via the `'agent'` run event); `'error'` and `'turn-end'` are the two
53
+ * type values `run()` handles specially rather than passing through (see
54
+ * index.ts module doc); `'ignored'` covers anything the 4 parsers never actually
55
+ * produce plus defensively malformed/non-record input.
56
+ *
57
+ * `'agent'`'s optional `sessionId` (gap 5, session resume — see
58
+ * `RunEndPayload.sessionRef`'s doc in `@jini-ai/protocol`) is a daemon-internal
59
+ * side channel, not part of the `RunAgentPayload` wire payload itself:
60
+ * OpenCode's `sessionID`/Codex's `thread_id`/Qoder's and Claude's
61
+ * `session_id` all arrive on a `'status'` event alongside fields
62
+ * `RunAgentPayload`'s `'status'` variant already models (`label`/`model`/
63
+ * `ttftMs`/`detail`) but has no room for a session id itself — surfacing it
64
+ * here lets a lifecycle-wiring function capture it into a local variable and
65
+ * thread it into its own terminal `finish()` call, without widening the
66
+ * public wire protocol just to carry a value that only this module reads.
67
+ *
68
+ * `'turn-end'`'s optional `stopReason` (gap 3, capability-routed
69
+ * continuation transport) is the same kind of internal side channel: the
70
+ * claude-stream parser deliberately emits `stopReason` *after* every
71
+ * `tool_use` block in the same assistant message has already been
72
+ * translated (so a caller can decide whether to keep stdin open before
73
+ * closing it), but v1 (pre-gap-3) discarded it and closed stdin
74
+ * unconditionally on any `turn-end` — see `packages/daemon/archived provenance ledger`'s
75
+ * "Design decision 2" note. `wireChildLifecycle` now reads it to decide
76
+ * whether `stop_reason: 'tool_use'` means "inject a tool result and keep
77
+ * going" (gap 3, gated — see `ContinuationOptions`) or "close stdin as
78
+ * before" (the unconditional default when no continuation is configured).
79
+ */
80
+ export type AgentRuntimeEventTranslation = {
81
+ readonly kind: 'agent';
82
+ readonly payload: RunAgentPayload;
83
+ readonly sessionId?: string;
84
+ } | {
85
+ readonly kind: 'error';
86
+ readonly payload: RunErrorPayload;
87
+ } | {
88
+ readonly kind: 'turn-end';
89
+ readonly stopReason?: string;
90
+ } | {
91
+ readonly kind: 'ignored';
92
+ };
93
+ /** Machine-readable failure reasons `run()` can reject with — every one is preceded by a `lifecycle.finish({status:'failed'})` call (see index.ts module doc's Invariant section). */
94
+ export type AgentExecutorErrorCode = 'AGENT_PERMISSION_MODE_INVALID' | 'AGENT_NOT_FOUND' | 'AGENT_RUNTIME_UNSUPPORTED' | 'AGENT_BINARY_NOT_RESOLVED' | 'AGENT_SPAWN_FAILED' | 'AGENT_PROMPT_TOO_LARGE';
95
+ /** Thrown by `AgentExecutor.run()` on every failure path — never a bare `Error`, so callers can branch on `.code` instead of parsing `.message`. */
96
+ export declare class AgentExecutorError extends Error {
97
+ readonly code: AgentExecutorErrorCode;
98
+ constructor({ code, message }: {
99
+ readonly code: AgentExecutorErrorCode;
100
+ readonly message: string;
101
+ });
102
+ }
103
+ export interface AgentExecutorRunInput {
104
+ readonly runId: string;
105
+ readonly agentId: string;
106
+ readonly prompt: string;
107
+ readonly cwd: string;
108
+ /** Optional host-selected model id, forwarded to every runtime transport. */
109
+ readonly model?: string;
110
+ /** Optional host-selected reasoning effort, forwarded to runtime argv builders. */
111
+ readonly reasoning?: string;
112
+ /**
113
+ * Forwarded verbatim to `RuntimeBuildOptions.permissionMode` (see `@jini-ai/agent-runtime`'s
114
+ * `types.ts`). Every def that has an auto-approve flag (`bypassPermissions` / `--yolo` /
115
+ * `--dangerously-skip-permissions`) historically enabled it when omitted because a spawned
116
+ * subprocess has no TTY for interactive approval. That rationale explains the old behavior;
117
+ * the daemon now fails closed with 'restricted'. A host must explicitly select 'bypass' to
118
+ * delegate auto-approval, and any unknown mode is rejected before launching a process.
119
+ */
120
+ readonly permissionMode?: 'bypass' | 'restricted';
121
+ /**
122
+ * Host-validated image files. How each one actually reaches the model
123
+ * depends on `def.imageDelivery` (see `@jini-ai/agent-runtime`'s `types.ts`):
124
+ * a `'native'` def forwards these through its own protocol (argv, ACP
125
+ * `resource_link` blocks, or pi-rpc's base64 `images` field); a
126
+ * `'prompt-path'` def (currently just `claude`) instead gets them named in
127
+ * the prompt text and its allowed directories widened, via
128
+ * `image-prompt-delivery.ts#applyImagePromptDelivery`, called once near
129
+ * the top of `run()` in `launch.ts`.
130
+ */
131
+ readonly imagePaths?: readonly string[];
132
+ /** Additional host-validated directories the runtime may read. */
133
+ readonly extraAllowedDirs?: readonly string[];
134
+ /** Trusted root that must contain pi-rpc image paths after realpath resolution. */
135
+ readonly uploadRoot?: string;
136
+ /**
137
+ * Credential(s) this run's selected agent/provider needs (e.g. `{ ANTHROPIC_API_KEY: '...' }`),
138
+ * delegated explicitly by the host and merged into the baseline-allowlisted env below. Never
139
+ * read implicitly from `process.env` — see SEC-001.
140
+ */
141
+ readonly credentialEnv?: Record<string, string>;
142
+ /**
143
+ * Explicit escape hatch: when supplied, used verbatim as the spawned subprocess's entire
144
+ * environment (no allowlist filtering) — for tests and hosts that have already done their own
145
+ * scoping. When omitted (the default), the subprocess gets only `BASELINE_AGENT_ENV_KEYS` from
146
+ * the host's real env plus `credentialEnv`, never a full `process.env` passthrough. A spawned
147
+ * coding-agent CLI is prompt-influenced and must be treated as potentially adversarial; it must
148
+ * not inherit secrets the daemon process happens to hold for unrelated reasons. See SEC-001
149
+ * (`ADS-memory/reports/proposals/PROP-agent-subprocess-env-allowlist-2026-07-21.md`) and locked
150
+ * architecture decision C8 (`ADS-memory/reports/jini-port/extraction-plan.md`).
151
+ */
152
+ readonly env?: NodeJS.ProcessEnv;
153
+ /**
154
+ * Stored session id for this (conversation, agent) pair from a prior run's
155
+ * `RunEndPayload.sessionRef` (see `@jini-ai/protocol`'s doc on that field) — set when the host
156
+ * wants this run to continue that CLI session instead of starting cold. Forwarded verbatim into
157
+ * `RuntimeContext.resumeSessionId`; a `resumesSessionViaCli` def (see
158
+ * `@jini-ai/agent-runtime`'s `types.ts`) reads it to pass its CLI's own resume flag, in which case
159
+ * the host should send only the latest user turn as `prompt`, not the full transcript. `null` and
160
+ * omitted are equivalent: no resume target for this run.
161
+ */
162
+ readonly resumeSessionId?: string | null;
163
+ /**
164
+ * A fresh id the host mints and persists when starting a session it wants resumable on a later
165
+ * turn (i.e. no `resumeSessionId` is available yet). Forwarded verbatim into
166
+ * `RuntimeContext.newSessionId`; a `resumesSessionViaCli` def passes it to its CLI's own
167
+ * "start with this id" flag so a later turn's `resumeSessionId` can continue the same underlying
168
+ * CLI session. Ignored by defs that don't declare `resumesSessionViaCli`.
169
+ */
170
+ readonly newSessionId?: string;
171
+ /**
172
+ * Forwarded verbatim to `RuntimeBuildOptions.disallowedTools` (see `@jini-ai/agent-runtime`'s
173
+ * `types.ts`) — Finding 2 of SEC-assistant-env-isolation-2026-09-07. A mechanism only: this
174
+ * package bakes in no opinion about which tools to name; the host decides. `undefined`/empty
175
+ * means no restriction, byte-identical to today's behavior.
176
+ */
177
+ readonly disallowedTools?: readonly string[];
178
+ /** Same mechanism as {@link disallowedTools}, forwarded to `RuntimeBuildOptions.allowedTools`. */
179
+ readonly allowedTools?: readonly string[];
180
+ /** Forwarded verbatim to `RuntimeBuildOptions.settingSources` (an empty list = load none of the CLI's settings layers). */
181
+ readonly settingSources?: readonly string[];
182
+ /** Forwarded verbatim to `RuntimeBuildOptions.settings` (extra settings JSON or file path for this run). */
183
+ readonly settings?: string;
184
+ }
185
+ export interface AgentExecutor {
186
+ /**
187
+ * Spawns `agentId`'s CLI for `runId`, wires its stdout/stderr into
188
+ * `lifecycle.emit()`, and resolves once the child process is confirmed
189
+ * spawned (fire-and-forget from there — see index.ts module doc). Callers await
190
+ * `lifecycle.waitForTerminal(runId)` separately for completion.
191
+ * @throws {@link AgentExecutorError} on every failure path — the
192
+ * underlying run is always already transitioned to `'failed'` via
193
+ * `lifecycle.finish()` before this rejects (see index.ts module doc's Invariant).
194
+ */
195
+ run(requiredArgs: Pick<AgentExecutorRunInput, "runId" | "agentId" | "prompt" | "cwd">, optionalArgs?: Pick<AgentExecutorRunInput, "model" | "reasoning" | "permissionMode" | "imagePaths" | "extraAllowedDirs" | "uploadRoot" | "credentialEnv" | "env" | "resumeSessionId" | "newSessionId" | "disallowedTools" | "allowedTools" | "settingSources" | "settings">): Promise<void>;
196
+ }
197
+ /** Process discovery is a getter; process collection and stopping accept explicit host inputs. */
198
+ export type CollectProcessTreePidsPort = (args: {
199
+ readonly processes: ProcessSnapshot[];
200
+ readonly rootPids: Array<number | null | undefined>;
201
+ }) => number[];
202
+ export type StopProcessesPort = (args: {
203
+ readonly pids: Array<number | null | undefined>;
204
+ }) => Promise<StopProcessesResult>;
205
+ export interface TerminateChildTreeDeps {
206
+ readonly listProcessSnapshots: () => Promise<ProcessSnapshot[]>;
207
+ readonly collectProcessTreePids: CollectProcessTreePidsPort;
208
+ readonly stopProcesses: StopProcessesPort;
209
+ }
210
+ /**
211
+ * Which step reported a contained failure through `onCleanupFailure` (SEC-007).
212
+ *
213
+ * The first three come from {@link terminateChildTreeBestEffort} (process-tree teardown). The last
214
+ * two are the two fallible steps that sit between a child's `'close'` and `finish()` — staged-file
215
+ * removal and the host's own `classifyFailure` — neither of which may prevent the terminal
216
+ * transition, and neither of which may fail silently either. See each close handler.
217
+ */
218
+ export type AgentCleanupFailurePhase = 'cancel' | 'acp-attach-failure' | 'pi-rpc-attach-failure' | 'staged-file-cleanup' | 'failure-classification';
219
+ export interface AgentCleanupFailureContext {
220
+ readonly runId: string;
221
+ readonly phase: AgentCleanupFailurePhase;
222
+ /**
223
+ * The child's pid. `undefined` only for the post-close phases on a child that never had one
224
+ * assigned (a spawn that produced no process): those phases are about this run's own bookkeeping
225
+ * rather than about signalling a process, so an absent pid is reportable rather than a
226
+ * contradiction.
227
+ */
228
+ readonly pid: number | undefined;
229
+ readonly error: unknown;
230
+ }
231
+ /** A small handle `writePromptToStdin` uses to close stdin exactly once, shared with the `turn_end`-triggered close inside {@link wireChildLifecycle}. */
232
+ export interface StdinCloseHandle {
233
+ closeStdinOnce(): void;
234
+ /** Journals a sent-to-stdin byte chunk, queued through the same FIFO {@link wireChildLifecycle} already uses for emitted events. No-op when no journal was configured (see `CreateAgentExecutorOptions.journal`). */
235
+ recordSentBytes(content: string): void;
236
+ }
237
+ /**
238
+ * Gap 3 (capability-routed continuation transport) — host-owned config for the
239
+ * `'stdin-injection'` transport (claude/codebuddy only; see
240
+ * `resolveContinuationTransport`'s doc). **Absent by default, and absent means
241
+ * zero behavior change**: with no `ContinuationOptions`, every `turn_end`
242
+ * closes stdin exactly as it always has (v1 behavior, unconditionally).
243
+ *
244
+ * `autonomousToolNames` is this task's answer to the debate's Unresolved
245
+ * Delta (how does the loop distinguish "the agent is continuing
246
+ * autonomously" from "the agent is waiting on a human"): rather than
247
+ * inferring intent from the stream, the *host* pre-declares which tool
248
+ * names are safe to auto-resolve and re-inject without a human in the loop.
249
+ * A `tool_use` whose name is not in this set is left exactly as it was
250
+ * before gap 3 — stdin closes, the run proceeds to its normal terminal
251
+ * state — even though a `stopReason: 'tool_use'` was observed. This sidesteps
252
+ * building unproven intent-detection: nothing auto-continues unless a host
253
+ * has explicitly vetted that specific tool as autonomous-safe. A
254
+ * human-facing "ask the user a question" tool is simply never added to this
255
+ * set; `packages/chat-core/src/question-form.ts`'s existing text-tag
256
+ * mechanism (a new `Run` per turn, not mid-turn injection) already covers
257
+ * that case without needing this transport at all.
258
+ */
259
+ export interface ContinuationOptions {
260
+ /** Injected tool results are authorized through this — the same deny-by-default gate every other tool execution path in this codebase uses. No parallel authorization path. */
261
+ readonly toolExecutor: ToolExecutor;
262
+ /** The principal an injected tool call is authorized as. */
263
+ readonly principal: Principal;
264
+ /** Tool names this host has pre-classified as safe to auto-resolve without human involvement. See this interface's own doc for why this — not stream-inferred intent — is gap 3's answer to the human-in-the-loop pause question. */
265
+ readonly autonomousToolNames: ReadonlySet<string>;
266
+ }
267
+ /**
268
+ * Gap 3, part 2 (MCP-callback continuation transport — the spawn-time half the spike's own
269
+ * commit message named as undone: "Item 4 ... NOT done yet"). `resolveContinuationTransport`
270
+ * already resolves `'mcp-callback'` for every def with `externalMcpInjection !== undefined`, but
271
+ * the executor did not originally *act* on that resolution — `execute_delegated_tool`
272
+ * (`@jini-ai/mcp`'s `../server/tools/delegated-tool.ts`) only does anything useful once the spawned
273
+ * CLI's own client actually launches `jini-mcp` as its MCP server subprocess.
274
+ *
275
+ * **All six declared strategies are wired.** These options describe *one* bridge server
276
+ * (`command`/`args`/`daemonUrl`/`credential`); which transport carries it to a given child is that
277
+ * def's own `externalMcpInjection` declaration, and each of the six has exactly one
278
+ * implementation in `mcp-bridge.ts` — see {@link buildMcpBridgeDelivery}, which is the single dispatch point.
279
+ * The interface name predates the other five mechanisms and is kept for API compatibility with
280
+ * `@jini-ai/server`'s `agentExecutor` passthrough; it is no longer `.mcp.json`-specific.
281
+ *
282
+ * **Host-resolved, not this package's to know.** `command`/`daemonUrl` have no default the way
283
+ * `journal`/`continuation`/`classifyFailure` don't either — there is no "real" install layout or
284
+ * loopback URL this package could assume on a caller's behalf (matching every other seam on this
285
+ * interface that defaults to *nothing* rather than a real implementation).
286
+ */
287
+ export interface McpJsonInjectionOptions {
288
+ /** Absolute path (or PATH-resolvable name) to the `jini-mcp` bin entry (`packages/mcp/src/bin/serve.ts`) this driver tells the spawned CLI to launch as its own MCP server subprocess. */
289
+ readonly command: string;
290
+ /** Extra argv for `command`. @default [] */
291
+ readonly args?: readonly string[];
292
+ /**
293
+ * Extra env for the bridge child, for hosts whose `command` is not a plain Node binary. An Electron
294
+ * host passes `{ ELECTRON_RUN_AS_NODE: '1' }`: its `process.execPath` is the app itself, which boots
295
+ * as a GUI app without that flag and never speaks MCP. `buildAgentEnv`'s allowlist strips the
296
+ * host's own copy from the CLI, so it can only reach the bridge through here. `JINI_*` keys are
297
+ * ignored: the run id, callback URL and token are this driver's to set. @default {}
298
+ */
299
+ readonly env?: Readonly<Record<string, string>>;
300
+ /** The daemon's own loopback base URL the spawned `jini-mcp` process calls back into via `JINI_DAEMON_URL` (see `packages/mcp/src/bin/serve.ts`'s `DAEMON_URL_ENV_VAR`). */
301
+ readonly daemonUrl: string;
302
+ /**
303
+ * Mints the bearer credential this run's `jini-mcp` child presents on its callbacks, delivered to
304
+ * the child through `JINI_DAEMON_TOKEN`. Omit it and the child is spawned exactly as before, with
305
+ * no token env var at all — so this is additive for every existing host.
306
+ *
307
+ * **A resolver, not a string, and deliberately so.** A host's `McpJsonInjectionOptions` is built
308
+ * once when it composes its executor — before any run exists. A plain string field could therefore
309
+ * only ever carry one boot-wide secret shared by every run, which defeats the point: the reason to
310
+ * hand the child a credential at all is that it can be scoped to the one run it was spawned for and
311
+ * stop working when that run ends. Taking `runId` here is what makes a per-run credential
312
+ * expressible.
313
+ *
314
+ * May be async so a host can mint through a keystore or signing service. Resolution happens in
315
+ * `writeMcpJsonForRun`, which is already async and already effectful; `buildMcpJsonServerEntry`
316
+ * stays pure and synchronous and receives the resolved value.
317
+ *
318
+ * Never hand this the host's own inbound API token. The child is the least-trusted participant in
319
+ * the run — it is reachable by whatever the spawned CLI does — so its credential should authorize
320
+ * its own callback route and nothing else.
321
+ *
322
+ * @throws Anything the host's own minting throws. `run()` turns a rejection into a pre-spawn
323
+ * `AGENT_SPAWN_FAILED` failure rather than spawning a child that cannot authenticate.
324
+ */
325
+ readonly credential?: ({ runId }: {
326
+ readonly runId: string;
327
+ }) => string | Promise<string>;
328
+ /** Reads the project's own `cwd/.mcp.json` so this driver merges its servers in rather than dropping them. Rejecting (ENOENT or otherwise) is treated as "no existing file" — see `writeMcpJsonForRun`. This file is only ever *read*. @default the real `fs.promises.readFile` (utf8) */
329
+ readonly readFile?: ({ path }: {
330
+ readonly path: string;
331
+ }) => Promise<string>;
332
+ /** Writes the merged content to this run's own config path (see {@link mcpJsonPathForRun}), never to the project's `.mcp.json`. @default the real `fs.promises.writeFile` (utf8) */
333
+ readonly writeFile?: ({ path, content }: {
334
+ readonly path: string;
335
+ readonly content: string;
336
+ }) => Promise<void>;
337
+ /**
338
+ * Removes this run's config file once the run is over — it holds a live per-run bearer token, so
339
+ * leaving it behind is the same class of confidentiality gap as a leaked prompt file (see
340
+ * `WireChildLifecycleContext.cleanupStagedFiles`). Called on the close handler and on every
341
+ * pre-spawn/spawn-failure path, and a rejection is reported rather than allowed to strand the run.
342
+ * @default `fs.promises.rm(path, { force: true })` — already-gone is success, not an error.
343
+ */
344
+ readonly removeFile?: ({ path }: {
345
+ readonly path: string;
346
+ }) => Promise<void>;
347
+ /**
348
+ * `'codex-toml'` only. Creates a fresh, randomly-named directory `prepareCodexHomeForRun` stages
349
+ * as a run's scratch `CODEX_HOME`. **Must be non-deterministic (a real `mkdtemp`, not a
350
+ * caller-computed path)** — unlike `mcpJsonPathForRun`'s deterministic path inside the run's own
351
+ * `cwd`, this directory holds a copy of the operator's real Codex login credential, and
352
+ * `os.tmpdir()` is a shared location on a multi-user host: a guessable name there is a real
353
+ * pre-plant/symlink target for another local user. `fs.mkdtemp`'s random suffix plus its `0700`
354
+ * directory mode is the actual confidentiality control, matching the same reasoning
355
+ * `@jini-ai/agent-runtime`'s `log-file.ts`/`prompt-file.ts` already apply to their own staged temp
356
+ * dirs.
357
+ * @param prefix - A caller-composed, run-id-derived prefix (already sanitized) for the mkdtemp
358
+ * template; the real suffix mkdtemp appends is what makes the path unpredictable.
359
+ * @default `fs.mkdtemp(path.join(os.tmpdir(), prefix))`
360
+ */
361
+ readonly mkdtemp?: ({ prefix }: {
362
+ readonly prefix: string;
363
+ }) => Promise<string>;
364
+ /**
365
+ * `'codex-toml'` only. Recursively removes the scratch `CODEX_HOME` directory `mkdtemp` above
366
+ * created — the directory-level analogue of `removeFile`, needed because this mechanism stages a
367
+ * whole directory (`config.toml` plus a copied `auth.json`), not one file.
368
+ * @default `fs.rm(path, { recursive: true, force: true })` — already-gone is success, not an error.
369
+ */
370
+ readonly removeDir?: ({ path }: {
371
+ readonly path: string;
372
+ }) => Promise<void>;
373
+ }
374
+ /** One `.mcp.json` `mcpServers` entry — the shape Claude Code's own config schema expects. */
375
+ export interface McpJsonServerEntry {
376
+ readonly command: string;
377
+ readonly args: string[];
378
+ readonly env: Readonly<Record<string, string>> & {
379
+ readonly JINI_RUN_ID: string;
380
+ readonly JINI_DAEMON_URL: string;
381
+ /** Present only when the host supplied a `credential` resolver — see {@link McpJsonInjectionOptions.credential}. */
382
+ readonly JINI_DAEMON_TOKEN?: string;
383
+ };
384
+ }
385
+ /**
386
+ * What one run's MCP bridge turns into, discriminated by the delivery mechanism its def declared.
387
+ * Exactly one variant is produced per run, and each variant carries only what its own consumer
388
+ * needs — so a consumer cannot accidentally read another mechanism's payload.
389
+ */
390
+ export type McpBridgeDelivery =
391
+ /** `'claude-mcp-json'` (claude, codebuddy): a `.mcp.json` staged into the run cwd, whose path the def's `buildArgs` passes as `--mcp-config`. */
392
+ {
393
+ readonly kind: 'claude-mcp-json';
394
+ readonly mcpJsonPath: string;
395
+ readonly serverEntry: McpJsonServerEntry;
396
+ }
397
+ /** `'acp-merge'` (the 9 ACP-native defs): `mcpServers` entries for the ACP `session/new` params. */
398
+ | {
399
+ readonly kind: 'acp-merge';
400
+ readonly mcpServers: readonly AcpMcpServerInput[];
401
+ }
402
+ /** `'opencode-env-content'` / `'mimo-env-content'` (opencode, mimo): one spawn-env variable carrying the serialised config. */
403
+ | {
404
+ readonly kind: 'env-content';
405
+ readonly envVarName: string;
406
+ readonly serverEntry: McpJsonServerEntry;
407
+ }
408
+ /**
409
+ * `'codex-toml'` (codex): no path yet — unlike `'claude-mcp-json'`'s `mcpJsonPath`, the scratch
410
+ * `CODEX_HOME` directory is created with `fs.mkdtemp` (a real, non-deterministic filesystem
411
+ * effect — see `McpJsonInjectionOptions.mkdtemp`'s own doc for why), so it cannot be computed by
412
+ * this delivery's pure, synchronous dispatch. `prepareCodexHomeIfNeeded` stages it separately and
413
+ * reports the resulting path back into `childEnv.CODEX_HOME` directly, never through this type.
414
+ */
415
+ | {
416
+ readonly kind: 'codex-toml';
417
+ readonly serverEntry: McpJsonServerEntry;
418
+ }
419
+ /**
420
+ * `'env-passthrough'` (antigravity): the bridge entry's `env` triple (`JINI_RUN_ID`/
421
+ * `JINI_DAEMON_URL`/`JINI_DAEMON_TOKEN`) is set directly on the spawned CLI's own OS
422
+ * environment — no config document, no file, no CLI-specific schema. Correct only because this
423
+ * strategy's CLI already inherits its own env down to the stdio MCP child it launches for a
424
+ * server registered once, globally, out of band (see `types.ts`'s own doc on this strategy).
425
+ */
426
+ | {
427
+ readonly kind: 'env-passthrough';
428
+ readonly serverEntry: McpJsonServerEntry;
429
+ };
430
+ /** A staged, run-scoped Codex `CODEX_HOME` — the directory-holding analogue of {@link PreparedPromptFile}/{@link PreparedAgentLogFile} from `@jini-ai/agent-runtime`. */
431
+ export type PreparedCodexHome = {
432
+ /** Absolute path to hand to the spawned child as its `CODEX_HOME` env var. */
433
+ readonly path: string;
434
+ /** Recursively removes the staged directory — the live `auth.json` copy it may hold makes this a confidentiality cleanup, not just tidiness. Safe to call more than once. */
435
+ readonly cleanup: () => Promise<void>;
436
+ };
437
+ /**
438
+ * Finding 1 of SEC-assistant-env-isolation-2026-09-07 — a staged, run-scoped `claude` CLI config
439
+ * directory, the `CLAUDE_CONFIG_DIR`-isolation analogue of {@link PreparedCodexHome} above.
440
+ *
441
+ * **The bug this closes:** `BASELINE_AGENT_ENV_KEYS` forwards `HOME` verbatim and neither this
442
+ * package nor `@jini-ai/agent-runtime` ever set `CLAUDE_CONFIG_DIR`, so a spawned `claude` child
443
+ * resolved its config (skills, plugins, agents, memory-path index, settings) from the OPERATOR's
444
+ * own real `$HOME/.claude` — a personal grant (including `Task`/`Edit`/`Write`/`Cron*`/worktree
445
+ * tools on this host) the host process never intended to hand the model.
446
+ */
447
+ export type PreparedClaudeConfigDir = {
448
+ /** Absolute path to hand to the spawned child as its `CLAUDE_CONFIG_DIR` env var. */
449
+ readonly path: string;
450
+ /** Recursively removes the staged directory — it may hold a copied login credential (see {@link prepareClaudeConfigDirForRun}'s doc), the same confidentiality-cleanup duty as {@link PreparedCodexHome.cleanup}. Safe to call more than once. */
451
+ readonly cleanup: () => Promise<void>;
452
+ };
453
+ /** The Claude-config-dir mechanism's injectable filesystem seams, real by default — the directory-staging analogue of {@link CodexHomeSeams}, kept as its own small options bag (see {@link CreateAgentExecutorOptions.claudeConfigDirIsolation}) rather than folded into {@link McpJsonInjectionOptions}: isolating the operator's personal config is an env-hygiene concern independent of whether this host configured MCP federation at all, and must not be gated on that unrelated flag. */
454
+ export interface ClaudeConfigDirSeams {
455
+ readonly mkdtemp: ({ prefix }: {
456
+ readonly prefix: string;
457
+ }) => Promise<string>;
458
+ readonly readFile: ({ path }: {
459
+ readonly path: string;
460
+ }) => Promise<string>;
461
+ readonly writeFile: ({ path, content }: {
462
+ readonly path: string;
463
+ readonly content: string;
464
+ }) => Promise<void>;
465
+ readonly removeDir: ({ path }: {
466
+ readonly path: string;
467
+ }) => Promise<void>;
468
+ }
469
+ /**
470
+ * `CreateAgentExecutorOptions.claudeConfigDirIsolation` — every field optional and real-filesystem
471
+ * by default, matching the executor's standing "no real disk I/O by default in tests" convention (see
472
+ * `preparePromptFileForAgent`/`prepareAgentLogFile`'s identical shape). Unlike
473
+ * {@link McpJsonInjectionOptions}, this bag is never itself a gate: {@link
474
+ * prepareClaudeConfigDirIfNeeded} stages a scratch directory for every `claude`-def run regardless
475
+ * of whether a host supplies overrides here — this options bag only lets a test observe/replace the
476
+ * filesystem calls, the same way `preparePromptFileForAgent`'s own default does.
477
+ */
478
+ export interface ClaudeConfigDirIsolationOptions {
479
+ /** @default the real `fs.promises.mkdtemp(path.join(os.tmpdir(), prefix))` */
480
+ readonly mkdtemp?: ({ prefix }: {
481
+ readonly prefix: string;
482
+ }) => Promise<string>;
483
+ /** Reads the operator's real `.credentials.json`, if any — see {@link prepareClaudeConfigDirForRun}'s own doc. @default the real `fs.promises.readFile` (utf8) */
484
+ readonly readFile?: ({ path }: {
485
+ readonly path: string;
486
+ }) => Promise<string>;
487
+ /** Writes the copied `.credentials.json` into the scratch directory. @default the real `fs.promises.writeFile` (utf8) */
488
+ readonly writeFile?: ({ path, content }: {
489
+ readonly path: string;
490
+ readonly content: string;
491
+ }) => Promise<void>;
492
+ /** @default `fs.promises.rm(path, { recursive: true, force: true })` — already-gone is success, not an error. */
493
+ readonly removeDir?: ({ path }: {
494
+ readonly path: string;
495
+ }) => Promise<void>;
496
+ }
497
+ /**
498
+ * Gap 4 of the run/chat orchestration Final Recommendation: what
499
+ * `classifyFailure` (see `CreateAgentExecutorOptions.classifyFailure`) is
500
+ * given to decide whether a `'failed'` run is `resumable`. `code`/`signal`
501
+ * are the only *content-level* signals cheaply available at every one of the
502
+ * three lifecycle-wiring close handlers without new stderr/stdout buffering
503
+ * machinery (a host wanting output-pattern-based classification, the way
504
+ * OD's own ~20-vendor text-matching classifier worked, would need its own
505
+ * listener for that — an honest scope limit, not an oversight).
506
+ *
507
+ * `sideEffects` (2026-07-22) carries the two `RunRetrySideEffectState` fields
508
+ * every `wire*Lifecycle` driver already tracks live from the translated
509
+ * agent-event stream it's processing anyway — `userVisibleOutputSeen` (a
510
+ * non-empty `text_delta`/`thinking_delta`) and `toolCallSeen` (a `tool_use`)
511
+ * — so `decideSafeRunRetry`'s matching suppression guards are genuinely
512
+ * exercised, not permanently dead code. Two related fields are deliberately
513
+ * absent: `cancelRequested` is never included because it's structurally
514
+ * always `false` by the time a classifier runs at all (a cancelled run's
515
+ * status already routes to `'cancelled'` before `classifyFailure` is ever
516
+ * consulted — see each `wire*Lifecycle` close handler); `artifactWriteSeen`/
517
+ * `liveArtifactSeen` have no real signal to derive them from at all —
518
+ * `@jini-ai/protocol`'s `RunAgentEventPayload` union (`events.ts`) has no
519
+ * `'artifact'`/`'live_artifact'` event kind yet (Jini's own generalized
520
+ * GenUI/artifact surface isn't built — see this repo's "A2UI full protocol
521
+ * deferred" scope note), unlike OD, which `RunRetrySideEffectState`'s shape
522
+ * was carried over from.
523
+ */
524
+ export interface FailureClassificationContext {
525
+ readonly runId: string;
526
+ readonly agentId: string;
527
+ readonly code: number | null;
528
+ readonly signal: string | null;
529
+ readonly sideEffects: Pick<RunRetrySideEffectState, 'userVisibleOutputSeen' | 'toolCallSeen'>;
530
+ }
531
+ /**
532
+ * Host-owned failure classifier — gap 4. Decides, for one specific
533
+ * `'failed'` run, whether `RunLifecycle.finish()`'s `resumable` flag should
534
+ * be `true`. Never consulted for `'succeeded'`/`'cancelled'` outcomes, and
535
+ * never consulted for a pre-spawn failure (`failBeforeSpawn`'s call sites) —
536
+ * those represent failures where no child process ever ran, so there is
537
+ * nothing a classifier could meaningfully examine.
538
+ */
539
+ export type ClassifyFailure = (context: FailureClassificationContext) => boolean | Promise<boolean>;
540
+ export interface CreateAgentExecutorOptions {
541
+ readonly lifecycle: RunLifecycle;
542
+ /** @default the real `@jini-ai/agent-runtime` registry lookup */
543
+ readonly getAgentDef?: typeof getAgentDef;
544
+ /** @default the real `@jini-ai/agent-runtime` launch resolver */
545
+ readonly resolveAgentLaunch?: typeof resolveAgentLaunch;
546
+ /** @default the real `@jini-ai/agent-runtime` once-per-binary `--help` capability probe */
547
+ readonly ensureAgentCapabilities?: typeof ensureAgentCapabilities;
548
+ /** @default the real `@jini-ai/agent-runtime` PATH-env composer */
549
+ readonly applyAgentLaunchEnv?: typeof applyAgentLaunchEnv;
550
+ /** @default the real `@jini-ai/platform` cross-platform invocation builder */
551
+ readonly createCommandInvocation?: typeof createCommandInvocation;
552
+ /** @default `node:child_process`'s `spawn` */
553
+ readonly spawn?: typeof nodeSpawn;
554
+ /** @default the real `@jini-ai/agent-runtime` ACP session transport */
555
+ readonly attachAcpSession?: typeof attachAcpSession;
556
+ /**
557
+ * Host-owned policy for ACP agents' native tool calls. The ACP agent still
558
+ * executes its own selected option; Jini-registered tool execution belongs
559
+ * to `createDelegatedToolBridge`, not this permission callback.
560
+ */
561
+ readonly acpPermissionHandler?: AcpPermissionHandler;
562
+ /** @default the real `@jini-ai/agent-runtime` pi-rpc session transport */
563
+ readonly attachPiRpcSession?: typeof attachPiRpcSession;
564
+ /**
565
+ * Stages a `promptViaFile` def's (grok-build) composed prompt to a temp
566
+ * file before `buildArgs` runs. Touches the real filesystem by default
567
+ * (`fs.mkdtemp`/`fs.writeFile`/`fs.rm`) — injectable so tests can drive
568
+ * it without real disk I/O, matching this factory's "no real subprocess,
569
+ * filesystem, or PATH lookup by default in tests" convention.
570
+ * @default the real `@jini-ai/agent-runtime` prompt-file stager
571
+ */
572
+ readonly preparePromptFileForAgent?: typeof preparePromptFileForAgent;
573
+ /**
574
+ * Stages a `needsAgentLogFile` def's (antigravity) diagnostic-log path
575
+ * before `buildArgs` runs. Same real-filesystem/injectable-for-tests deal
576
+ * as `preparePromptFileForAgent` above, and a no-op for every def that
577
+ * did not opt in.
578
+ * @default the real `@jini-ai/agent-runtime` log-file stager
579
+ */
580
+ readonly prepareAgentLogFile?: typeof prepareAgentLogFile;
581
+ /** @default the real `@jini-ai/platform` process-snapshot enumerator */
582
+ readonly listProcessSnapshots?: typeof listProcessSnapshots;
583
+ /** @default the real `@jini-ai/platform` descendant-PID collector */
584
+ readonly collectProcessTreePids?: CollectProcessTreePidsPort;
585
+ /** @default the real `@jini-ai/platform` SIGTERM→SIGKILL escalator */
586
+ readonly stopProcesses?: StopProcessesPort;
587
+ /** Host-owned sink for a process-tree cleanup failure (SEC-007) — e.g. EPERM stopping descendants. @default logs a redacted diagnostic via `console.error` */
588
+ readonly onCleanupFailure?: (context: AgentCleanupFailureContext) => void;
589
+ /**
590
+ * Gap 1's byte-journal (`packages/daemon/src/continuation/journal.ts`) — records every byte
591
+ * this driver sends to or receives from a child agent process, independent of and prior to any
592
+ * parsed/translated event. Covers `writePromptToStdin` (sent) and every `child.stdout`/
593
+ * `child.stderr` `'data'` handler this driver owns (received); does not cover ACP/pi-rpc's own
594
+ * prompt delivery, which happens inside their respective attach functions' own transport, out
595
+ * of this driver's direct view — see `WireAcpLifecycleContext.journal`'s doc.
596
+ * @default no journal — recording is entirely opt-in, unlike every other seam on this
597
+ * interface (which default to a real implementation): there is no generic "real" journal
598
+ * storage this package can default to without a caller-supplied `EventLog` instance.
599
+ */
600
+ readonly journal?: RunByteJournal;
601
+ /**
602
+ * Gap 3's stdin-tool-result injection config — see `ContinuationOptions`'s own doc, especially
603
+ * on why `autonomousToolNames` (not stream-inferred intent) is this task's answer to the
604
+ * human-in-the-loop pause question.
605
+ * @default undefined — every `turn_end` closes stdin unconditionally, byte-identical to
606
+ * pre-gap-3 behavior. Opt-in only, like `journal`: there is no safe default allowlist of
607
+ * "tools okay to auto-continue without a human" this package can supply on a caller's behalf.
608
+ */
609
+ readonly continuation?: ContinuationOptions;
610
+ /**
611
+ * Gap 4's failure classifier — see `ClassifyFailure`'s own doc.
612
+ * @default undefined — every `'failed'` run stays `resumable: false`, byte-identical to
613
+ * pre-gap-4 behavior. No default classifier exists: OD's own ~20-vendor-CLI text-matching
614
+ * failure classifier was deliberately never ported (see index.ts module doc), so there is no
615
+ * generic "real" classification logic this package could supply on a caller's behalf.
616
+ */
617
+ readonly classifyFailure?: ClassifyFailure;
618
+ /**
619
+ * Gap 3, part 2's spawn-time `.mcp.json` injection — see {@link McpJsonInjectionOptions}'s own
620
+ * doc for the full design (why only `'claude-mcp-json'`-injection defs, why host-resolved).
621
+ * @default undefined — no `.mcp.json` is written and no filesystem access beyond what already
622
+ * happened (prompt-file staging) occurs on this path, byte-identical to pre-this-task behavior.
623
+ * Opt-in only, like `journal`/`continuation`/`classifyFailure`: there is no safe default
624
+ * `command`/`daemonUrl` this package could assume on a caller's behalf.
625
+ */
626
+ readonly mcpJsonInjection?: McpJsonInjectionOptions;
627
+ /**
628
+ * Finding 1 of SEC-assistant-env-isolation-2026-09-07's injectable filesystem seams — see
629
+ * {@link ClaudeConfigDirIsolationOptions}'s own doc. This bag itself is still NOT a gate (supplying
630
+ * it only lets a test observe/replace the real filesystem calls); whether staging happens AT ALL is
631
+ * now {@link claudeConfigDirIsolationEnabled}'s job — see that field's doc for why the two were
632
+ * split apart instead of overloading this one's presence as the switch.
633
+ * @default the real `fs.promises.mkdtemp`/`readFile`/`rm` — no real disk I/O for every def other
634
+ * than `claude`, matching this factory's "no real filesystem by default in tests" convention.
635
+ */
636
+ readonly claudeConfigDirIsolation?: ClaudeConfigDirIsolationOptions;
637
+ /**
638
+ * The actual on/off switch for Finding 1's `CLAUDE_CONFIG_DIR` isolation (see
639
+ * {@link prepareClaudeConfigDirIfNeeded}'s doc for the staging behavior this gates). Split out as
640
+ * its own boolean rather than reusing {@link claudeConfigDirIsolation}'s presence, because that bag
641
+ * is a test-seam-injection convention shared with every other `*IsolationOptions`/`*Seams` field in
642
+ * this file (see `mcpJsonInjection`'s own "NOT a gate" precedent) — overloading it here would mean a
643
+ * host that only wants to override `mkdtemp` for a test silently also flips production behavior.
644
+ *
645
+ * @default `false`. This DEFAULTS OFF, which reopens the leak Finding 1 closed (the spawned
646
+ * `claude` child again reads the operator's real `~/.claude` — skills, plugins, memory index, and
647
+ * whatever tool grant that directory carries) — **not a regression discovered later, a deliberate
648
+ * rollback landed the same day as the isolation fix itself.** Reason: on macOS the Keychain login
649
+ * `claude auth status` reports is keyed to `CLAUDE_CONFIG_DIR` (see {@link
650
+ * prepareClaudeConfigDirForRun}'s doc), and no caller of this factory was passing a credential of
651
+ * its own (`AgentExecutorRunInput.credentialEnv`) when Finding 1 landed unconditionally — so every
652
+ * isolated child ran unauthenticated and the assistant reported "Not logged in" on its default
653
+ * runtime. A host that provisions a real `ANTHROPIC_API_KEY`/`CLAUDE_CODE_OAUTH_TOKEN` via
654
+ * `credentialEnv` (both outrank Keychain login in Claude Code's own auth precedence, so an isolated,
655
+ * still-logged-in child needs no access to the operator's personal Keychain entry at all) should
656
+ * flip this back on — the mechanism itself is unchanged and was already proven live
657
+ * (2026-09-07); only the default changed.
658
+ */
659
+ readonly claudeConfigDirIsolationEnabled?: boolean;
660
+ /**
661
+ * Ceiling on how many bytes of a `'until-close'` def's stdout this driver will hold in memory
662
+ * before it stops accumulating and reports the shortfall — see
663
+ * {@link DEFAULT_BUFFERED_STDOUT_MAX_BYTES} for the threat this closes and why 8 MiB.
664
+ * @default {@link DEFAULT_BUFFERED_STDOUT_MAX_BYTES}
665
+ */
666
+ readonly bufferedStdoutMaxBytes?: number;
667
+ /**
668
+ * Host-owned system-prompt overlay — see `prompt-augmenter.ts`'s own doc for why this seam
669
+ * exists (product-specific discovery/behavior instructions that don't belong in the engine).
670
+ * When present, `systemOverlay()` is called once per `run()` and its result (if non-null) is
671
+ * threaded through to `buildArgs` as `RuntimeBuildOptions.systemPromptOverlay` — a def with no
672
+ * append-system-prompt mechanism ignores it.
673
+ * @default undefined — no overlay is computed and no def sees `systemPromptOverlay`,
674
+ * byte-identical to pre-this-option behavior.
675
+ */
676
+ readonly promptAugmenter?: PromptAugmenter;
677
+ }
678
+ /** `createAgentExecutor`'s internal `failBeforeSpawn` — a nested closure over `lifecycle`, so every extracted `run()` phase below takes it as an explicit injected collaborator rather than reaching for a module-level one. */
679
+ export type FailBeforeSpawn = (args: {
680
+ readonly runId: string;
681
+ readonly code: AgentExecutorErrorCode;
682
+ readonly message: string;
683
+ }) => Promise<never>;
684
+ /**
685
+ * {@link resolveSystemPromptOverlayDelivery}'s decision, threaded out of that one resolver to every
686
+ * channel that can actually carry the overlay to the CLI.
687
+ *
688
+ * **Why `promptPrefix` exists alongside `prompt`.** `prompt` is the composed prompt with the
689
+ * fallback prefix already applied, and is what `buildArgs`, the staged prompt file, and a
690
+ * `promptViaStdin` def's stdin all receive. But the two RPC transports (`runAcpDispatch`,
691
+ * `runPiRpcDispatch`) deliberately send `run()`'s *raw* `input.prompt`, not the image-delivery
692
+ * rewrite this resolver was handed — those defs deliver images through their own native protocol
693
+ * and must never also get `'prompt-path'`'s appended paths (see `resolveImageDeliveryAndArgvBudget`'s
694
+ * call site in `run()` for that hazard). Handing them `prompt` would silently couple them to the
695
+ * image rewrite; handing them `promptPrefix + input.prompt` applies exactly this resolver's overlay
696
+ * decision to exactly the prompt text they were already sending.
697
+ *
698
+ * **The no-double-delivery invariant.** `promptPrefix` is a non-empty string on exactly one path —
699
+ * the universal fallback, the only strategy that carries the overlay *in the prompt text*. Every
700
+ * declared strategy (`'append-flag'`, `'env-var'`, `'config-instructions-file'`) and every
701
+ * suppressed case (no overlay, continuing a session) returns `''`, so a def that receives the
702
+ * overlay through argv, an env var, or a staged config file never also receives it inline. A
703
+ * transport therefore does not need to know which strategy applies: applying `promptPrefix`
704
+ * unconditionally is correct precisely because the resolver already zeroed it where it must not
705
+ * apply.
706
+ */
707
+ export interface SystemPromptOverlayDelivery {
708
+ /**
709
+ * The text to prepend to whatever prompt a transport is about to send — `''` for every def whose
710
+ * overlay rides another channel, so it is always safe to apply unconditionally. See this
711
+ * interface's own doc for why callers with their own prompt text use this rather than `prompt`.
712
+ */
713
+ readonly promptPrefix: string;
714
+ /** `promptPrefix` already applied to the prompt this resolver was handed. */
715
+ readonly prompt: string;
716
+ /** Extra argv to append to `buildArgs`' own result — non-empty only for `'append-flag'`. */
717
+ readonly extraArgs: readonly string[];
718
+ /** Env vars to merge into the spawn env — non-empty only for `'env-var'`. */
719
+ readonly envOverrides: Readonly<Record<string, string>>;
720
+ }
721
+ /** {@link prepareSystemPromptOverlayFileIfNeeded}'s result. */
722
+ export type PreparedSystemPromptOverlayFile = {
723
+ /** Absolute path to the staged file, ready to merge into a `config-instructions-file` def's `instructions` array. */
724
+ readonly path: string;
725
+ /** Recursively removes the staged directory. Safe to call more than once. */
726
+ readonly cleanup: () => Promise<void>;
727
+ };
728
+ /** {@link spawnAgentChildProcess}'s result — a discriminated union rather than a thrown/rejected outcome so the call can stay synchronous; see that function's doc for why. */
729
+ export type SpawnAgentChildProcessResult = {
730
+ readonly kind: 'ok';
731
+ readonly child: ChildProcess;
732
+ } | {
733
+ readonly kind: 'error';
734
+ readonly error: unknown;
735
+ };
736
+ //# sourceMappingURL=contracts.d.ts.map