@jini-ai/daemon 0.5.2 → 0.5.4

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 (78) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/agent-executor/contracts.d.ts +26 -20
  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 +1 -2
  6. package/dist/agent-executor/events.d.ts +9 -4
  7. package/dist/agent-executor/events.js +29 -7
  8. package/dist/agent-executor/handoff.d.ts +1 -1
  9. package/dist/agent-executor/handoff.js +0 -1
  10. package/dist/agent-executor/launch.d.ts +1 -1
  11. package/dist/agent-executor/launch.js +24 -1
  12. package/dist/agent-executor/mcp-bridge.d.ts +2 -2
  13. package/dist/agent-executor/mcp-bridge.js +0 -2
  14. package/dist/agent-executor/model-receipts.d.ts +6 -0
  15. package/dist/agent-executor/model-receipts.js +26 -0
  16. package/dist/agent-executor.d.ts +8 -1663
  17. package/dist/agent-executor.js +8 -3005
  18. package/dist/attachment-content.d.ts +45 -0
  19. package/dist/attachment-content.js +40 -0
  20. package/dist/core/read-only-tools.d.ts +130 -0
  21. package/dist/core/read-only-tools.js +115 -0
  22. package/dist/core/tool-audit.d.ts +131 -0
  23. package/dist/core/tool-audit.js +113 -0
  24. package/dist/http/active-context.d.ts +2 -2
  25. package/dist/http/agents.d.ts +8 -2
  26. package/dist/http/agents.js +6 -2
  27. package/dist/http/attachments.d.ts +16 -7
  28. package/dist/http/attachments.js +3 -2
  29. package/dist/http/component-catalog.d.ts +2 -2
  30. package/dist/http/connectors.d.ts +17 -17
  31. package/dist/http/daemon-status.d.ts +2 -2
  32. package/dist/http/db-ops.d.ts +3 -3
  33. package/dist/http/delegated-tools.d.ts +1 -1
  34. package/dist/http/delegated-tools.js +1 -1
  35. package/dist/http/frontend-sessions.d.ts +1 -1
  36. package/dist/http/health.d.ts +6 -6
  37. package/dist/http/host-tools.d.ts +2 -2
  38. package/dist/http/media.d.ts +4 -4
  39. package/dist/http/memory.d.ts +15 -15
  40. package/dist/http/remote-run-events.d.ts +2 -2
  41. package/dist/http/research.d.ts +1 -1
  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 +4 -4
  46. package/dist/http/terminals.d.ts +6 -6
  47. package/dist/http/tool-catalog.d.ts +2 -2
  48. package/dist/http/xai.d.ts +6 -6
  49. package/dist/index.d.ts +76 -571
  50. package/dist/index.js +76 -571
  51. package/dist/read-only-tools.d.ts +1 -129
  52. package/dist/read-only-tools.js +2 -114
  53. package/dist/run-credentials.d.ts +47 -23
  54. package/dist/run-credentials.js +24 -8
  55. package/dist/run-lifecycle.d.ts +1 -1
  56. package/dist/run-lifecycle.js +6 -5
  57. package/dist/scheduler.d.ts +5 -0
  58. package/dist/scheduler.js +13 -1
  59. package/dist/session-coordination.d.ts +58 -24
  60. package/dist/session-coordination.js +40 -12
  61. package/dist/store/event-log/sqlite.js +3 -3
  62. package/dist/surface-exchanges.d.ts +207 -61
  63. package/dist/surface-exchanges.js +167 -20
  64. package/dist/tool-audit.d.ts +1 -113
  65. package/dist/tool-audit.js +2 -94
  66. package/dist/tool-recovery/diagnostics.d.ts +132 -0
  67. package/dist/tool-recovery/diagnostics.js +110 -0
  68. package/dist/tool-recovery/federated-refusal.d.ts +92 -0
  69. package/dist/tool-recovery/federated-refusal.js +152 -0
  70. package/dist/tool-recovery/index.d.ts +6 -0
  71. package/dist/tool-recovery/index.js +6 -0
  72. package/dist/tool-recovery/recovery.d.ts +49 -0
  73. package/dist/tool-recovery/recovery.js +419 -0
  74. package/dist/tool-recovery/redaction.d.ts +166 -0
  75. package/dist/tool-recovery/redaction.js +192 -0
  76. package/dist/tool-recovery/stack.d.ts +69 -0
  77. package/dist/tool-recovery/stack.js +62 -0
  78. package/package.json +35 -12
@@ -1,1665 +1,10 @@
1
1
  /**
2
- * `AgentExecutor` — the driver `RunLifecycle`'s own module doc names as the
3
- * missing piece: *"It does not spawn or signal a subprocess... A driver...
4
- * calls `emit()` for agent/stdout/stderr/error events, observes cancellation
5
- * via `onCancelRequested`, and calls `finish()` once it knows the real
6
- * outcome."* This module is that driver — it wires `@jini-ai/agent-runtime`'s
7
- * registry/launch-resolution/stream-parsers (previously a complete but
8
- * disconnected library, zero callers anywhere outside its own package) into
9
- * a real `node:child_process` spawn, feeding both `RunLifecycle.emit()` and
10
- * this package's own `@jini-ai/protocol` event envelope.
11
- *
12
- * ## v1 scope: all 24 registered agent defs
13
- *
14
- * `@jini-ai/agent-runtime`'s registry ships 24 built-in defs across four
15
- * `streamFormat` families. The JSON-stream-parser family — the four
16
- * `createXStreamHandler`-shaped parsers (`claude-stream-json`,
17
- * `json-event-stream`, `copilot-stream-json`, `qoder-stream-json`), covering
18
- * 9 defs (amp, codebuddy, claude, codex, cursor-agent, opencode, mimo,
19
- * copilot, qoder) — plus all 9 `acp-json-rpc` defs, plus the one `pi-rpc`
20
- * def (`pi`), are wired here. ACP and pi-rpc each own their own JSON-RPC
21
- * prompt-delivery protocol, so each takes its own lifecycle branch rather
22
- * than being treated as a stdout-tail parser; pi-rpc's events arrive through
23
- * the exact same `{type, ...}` vocabulary `translateAgentRuntimeEvent`
24
- * already handles for ACP/JSON-stream (confirmed by reading every
25
- * `mapPiRpcEvent` `send()` call site — no new translation code was needed),
26
- * so only the driver wiring (spawn → attach → cancel → finish) was new for it.
27
- *
28
- * All 5 `streamFormat: 'plain'` defs — grok-build, aider, deepseek, qwen,
29
- * antigravity — are also driven, per
30
- * `ADS-memory/reports/proposals/PROP-plain-format-agent-driving-2026-07-21.md`'s
31
- * recommended "Option B": no structured stream parser at all. By default
32
- * every raw `child.stdout` chunk is forwarded verbatim as a `text_delta`
33
- * `'agent'` event, live, as it arrives (see `wireChildLifecycle`'s
34
- * `streamFormat === 'plain'` branch). Prompt delivery across the 5 is not
35
- * uniform: qwen and antigravity already fit the pre-existing stdin-only
36
- * guard; grok-build stages the prompt to a temp file via
37
- * `preparePromptFileForAgent` (its path threaded into `buildArgs` through a
38
- * `RuntimeContext`, cleaned up after the child exits on every path,
39
- * including pre-spawn/spawn-failure ones); aider/deepseek carry the prompt
40
- * on argv and are guarded pre-spawn by `checkPromptArgvBudget` plus the two
41
- * Windows CreateProcess command-line-expansion guards
42
- * (`checkWindowsCmdShimCommandLineBudget`/`checkWindowsDirectExeCommandLineBudget`).
43
- *
44
- * ## Antigravity's two extra needs, met declaratively
45
- *
46
- * Antigravity was the one def this driver rejected outright, for two reasons
47
- * the proposal doc (§2c) scoped out to a follow-up: `agy` can print an OAuth
48
- * sign-in URL to stdout and *still exit 0*, so live streaming leaks it; and
49
- * its model choice is written into one process-global `settings.json` that
50
- * `agy` reads on its own startup, so two concurrent runs race on it.
51
- *
52
- * Both are now met through **declarative `RuntimeAgentDef` fields this driver
53
- * reads generically** — `needsAgentLogFile`, `stdoutPolicy`, `runtimeLock` —
54
- * not a `def.id === 'antigravity'` branch. That mirrors how all 14 of the
55
- * def's other optional behavior flags (`promptViaFile`, `authProbe`,
56
- * `capturesSessionIdFromStream`, …) already work, and it is a deliberate
57
- * divergence from OD's own `server.ts`, which hardcodes `def.id ===
58
- * 'antigravity'` twice. The three fields are no-ops for the other 23 defs,
59
- * none of which declares any of them — so nothing else's behavior changed.
60
- *
61
- * `run()` still rejects cleanly (never a bare throw) with an
62
- * `AgentExecutorError` for any def whose `streamFormat` or prompt-delivery
63
- * shape this driver does not implement — see `isSupportedStreamFormat` and
64
- * `assessAgentExecutorCompatibility`.
65
- *
66
- * ## Invariant
67
- *
68
- * `RunLifecycle.start()` already transitions a run to `'running'` before
69
- * `run()` is ever called. Every *pre-spawn* failure path in `run()` — unknown
70
- * `agentId`, an unsupported `streamFormat`/prompt-delivery shape, an
71
- * unresolvable binary, or a spawn error — calls `lifecycle.finish({status:
72
- * 'failed', resumable: false, code: null, signal: null})` itself before
73
- * rejecting, so a run can never get stuck `'running'` with no watchdog.
74
- * `resumable` is unconditionally `false` on these paths — there is no spawned
75
- * child, hence nothing a classifier could examine (see
76
- * `FailureClassificationContext`'s own doc).
77
- *
78
- * For a run that *did* spawn and then failed, `resumable` is decided by
79
- * `classifyFailure` (gap 4 — see `ClassifyFailure`'s own doc), an injectable
80
- * port with no default of its own in this module (`undefined` stays
81
- * byte-identical to pre-gap-4 behavior — every `'failed'` outcome
82
- * resumable:false). OD's ~20-vendor-CLI text-matching failure classifier was
83
- * deliberately never ported (see `run/core/failure-taxonomy.ts`'s own doc and
84
- * `archived provenance ledger`). The real zero-config classifier lives in `@jini-ai/daemon`'s
85
- * `run/core/retry.ts` (`resumableFromProcessExit`/`classifyProcessExitFailure`)
86
- * and is wired in by `@jini-ai/server`'s `createLocalNodeDaemon` — see that
87
- * package's own archived provenance ledger, and `run/core/retry.ts`'s own doc for the
88
- * classification policy and its 2026-07-22 merge-time reconciliation against
89
- * a second, independently-built (and rejected) classifier that once lived in
90
- * this module.
91
- */
92
- import { spawn as nodeSpawn, type ChildProcess } from 'node:child_process';
93
- import type { Principal } from '@jini-ai/core';
94
- import type { RunAgentPayload, RunErrorPayload } from '@jini-ai/protocol';
95
- import { applyAgentLaunchEnv, ensureAgentCapabilities, getAgentDef, resolveAgentLaunch, attachAcpSession, attachPiRpcSession, prepareAgentLogFile, preparePromptFileForAgent, type AcpMcpServerInput, type AcpPermissionHandler, type AgentLaunchResolution, type PreparedAgentLogFile, type PreparedPromptFile, type PromptAugmenter, type RuntimeAgentDef, type RuntimeBuildOptions, type RuntimeContext, type RuntimeLockHold } from '@jini-ai/agent-runtime';
96
- import { createCommandInvocation, listProcessSnapshots, type ProcessSnapshot, type StopProcessesResult } from '@jini-ai/platform';
97
- import type { RunByteJournal } from './continuation/journal.js';
98
- import { type ImagePromptDelivery } from './image-prompt-delivery.js';
99
- import type { RunRetrySideEffectState } from './run/core/index.js';
100
- import type { ToolExecutor } from './tool-executor.js';
101
- import type { RunLifecycle } from './run-lifecycle.js';
102
- declare const SUPPORTED_STREAM_FORMATS: readonly ["claude-stream-json", "json-event-stream", "copilot-stream-json", "qoder-stream-json", "acp-json-rpc", "pi-rpc", "plain"];
103
- /** The families of the registry's stream-format this driver implements — see module doc. */
104
- export type SupportedStreamFormat = (typeof SUPPORTED_STREAM_FORMATS)[number];
105
- type JsonStreamFormat = Exclude<SupportedStreamFormat, 'acp-json-rpc' | 'pi-rpc' | 'plain'>;
106
- /**
107
- * `streamFormat` values `wireChildLifecycle` drives directly off raw
108
- * `child.stdout` `'data'` events: the 4 JSON-stream-parser formats (fed
109
- * through a real `feed()`/`flush()` state machine via
110
- * {@link createStreamHandlerForDef}) plus `'plain'` (no parser at all —
111
- * each chunk is forwarded verbatim as a `text_delta`, see that function's
112
- * doc). Distinct from `'acp-json-rpc'`/`'pi-rpc'`, which own their own
113
- * JSON-RPC prompt/event protocol and get their own
114
- * `wireAcpLifecycle`/`wirePiRpcLifecycle` wiring instead.
115
- */
116
- type ChildDrivenStreamFormat = JsonStreamFormat | 'plain';
117
- /**
118
- * Narrows a `RuntimeAgentDef.streamFormat` string to the supported
119
- * families.
120
- * @param value - The def's raw `streamFormat` string.
121
- * @returns `true` when `value` is one of the JSON-stream-parser, ACP, pi-rpc, or plain formats this driver wires.
122
- * @complexity O(1) — fixed membership check.
123
- * @overallScore 100/100
124
- */
125
- export declare function isSupportedStreamFormat(args: {
126
- readonly value: string;
127
- }): args is {
128
- readonly value: SupportedStreamFormat;
129
- };
130
- /**
131
- * Whether `run()` can drive a given def, and — when it can — its `streamFormat` already narrowed for
132
- * the dispatch logic that follows. The narrowing rides along deliberately: it is what lets `run()`
133
- * delegate every compatibility guard here without then re-checking the format to satisfy the type
134
- * system, which would leave an unreachable branch behind.
135
- */
136
- export type AgentExecutorCompatibility = {
137
- readonly supported: true;
138
- readonly streamFormat: SupportedStreamFormat;
139
- } | {
140
- readonly supported: false;
141
- readonly reason: string;
142
- };
143
- /**
144
- * The single source of truth for whether this executor can drive a def.
145
- *
146
- * It exists because that knowledge was previously reachable only by *calling* `run()` and inspecting
147
- * the failure. Anything that lists agents for a user to pick from — a discovery route, an agent
148
- * picker, a CLI healthcheck — needs the same answer *before* a run exists, and had no way to ask it.
149
- * The observable symptom was a consumer advertising an agent that its own executor then rejected the
150
- * instant it was selected.
151
- *
152
- * `run()` consumes this rather than re-checking the conditions itself, so the discovery-time answer
153
- * and the run-time guards cannot disagree. A predicate that merely duplicated the guards would be
154
- * the same bug in a second location.
155
- *
156
- * @param def - The def to assess. Must be the **full** `RuntimeAgentDef`, not a projected
157
- * `DetectedAgent`: that type omits `maxPromptArgBytes`, one of the three prompt-delivery signals
158
- * checked here, so the argv-bound defs (`aider`, `deepseek`) would be misjudged as unsupported.
159
- * @returns A discriminated result — see {@link AgentExecutorCompatibility}. The `reason` text is
160
- * operator-facing and is what `run()` reports as its `AGENT_RUNTIME_UNSUPPORTED` message.
161
- * @complexity O(1) — fixed field checks.
162
- * @overallScore 100/100
163
- */
164
- export declare function assessAgentExecutorCompatibility({ def }: {
165
- readonly def: RuntimeAgentDef;
166
- }): AgentExecutorCompatibility;
167
- /**
168
- * Whether `run()` can actually drive this def — the discovery-time counterpart to the guards inside
169
- * `run()`, so a consumer never offers a user an agent that fails the moment it is selected.
170
- *
171
- * @param def - The full `RuntimeAgentDef`; see {@link assessAgentExecutorCompatibility} for why a
172
- * projected `DetectedAgent` is not sufficient.
173
- * @returns `true` when this executor would attempt the run.
174
- * @complexity O(1).
175
- * @overallScore 100/100
176
- */
177
- export declare function isAgentExecutorSupported({ def }: {
178
- readonly def: RuntimeAgentDef;
179
- }): boolean;
180
- /**
181
- * Result of translating one parsed stream event into this engine's
182
- * vocabulary. `'agent'` is the common case (forward as a `RunAgentPayload`
183
- * via the `'agent'` run event); `'error'` and `'turn-end'` are the two
184
- * type values `run()` handles specially rather than passing through (see
185
- * module doc); `'ignored'` covers anything the 4 parsers never actually
186
- * produce plus defensively malformed/non-record input.
187
- *
188
- * `'agent'`'s optional `sessionId` (gap 5, session resume — see
189
- * `RunEndPayload.sessionRef`'s doc in `@jini-ai/protocol`) is a daemon-internal
190
- * side channel, not part of the `RunAgentPayload` wire payload itself:
191
- * OpenCode's `sessionID`/Codex's `thread_id`/Qoder's and Claude's
192
- * `session_id` all arrive on a `'status'` event alongside fields
193
- * `RunAgentPayload`'s `'status'` variant already models (`label`/`model`/
194
- * `ttftMs`/`detail`) but has no room for a session id itself — surfacing it
195
- * here lets a lifecycle-wiring function capture it into a local variable and
196
- * thread it into its own terminal `finish()` call, without widening the
197
- * public wire protocol just to carry a value that only this module reads.
198
- *
199
- * `'turn-end'`'s optional `stopReason` (gap 3, capability-routed
200
- * continuation transport) is the same kind of internal side channel: the
201
- * claude-stream parser deliberately emits `stopReason` *after* every
202
- * `tool_use` block in the same assistant message has already been
203
- * translated (so a caller can decide whether to keep stdin open before
204
- * closing it), but v1 (pre-gap-3) discarded it and closed stdin
205
- * unconditionally on any `turn-end` — see `packages/daemon/archived provenance ledger`'s
206
- * "Design decision 2" note. `wireChildLifecycle` now reads it to decide
207
- * whether `stop_reason: 'tool_use'` means "inject a tool result and keep
208
- * going" (gap 3, gated — see `ContinuationOptions`) or "close stdin as
209
- * before" (the unconditional default when no continuation is configured).
210
- */
211
- export type AgentRuntimeEventTranslation = {
212
- readonly kind: 'agent';
213
- readonly payload: RunAgentPayload;
214
- readonly sessionId?: string;
215
- } | {
216
- readonly kind: 'error';
217
- readonly payload: RunErrorPayload;
218
- } | {
219
- readonly kind: 'turn-end';
220
- readonly stopReason?: string;
221
- } | {
222
- readonly kind: 'ignored';
223
- };
224
- /**
225
- * Narrows a parsed `usage` event's `usage` sub-object (`{input_tokens?, output_tokens?}`) — the one
226
- * piece of {@link translateUsagePayload} with real nested branching (an optional container holding
227
- * two optional numeric fields), extracted so that function reads as a flat field-by-field mapping.
228
- * @param rawUsage - `rawEvent.usage` once already narrowed to a record, or `undefined` when absent/malformed.
229
- * @returns `undefined` when neither token count is present — matching `translateUsagePayload`'s
230
- * original "omit the whole `usage` field rather than emit an empty object" behavior.
231
- * @complexity O(1).
232
- */
233
- export declare function extractUsageTokens({ rawUsage }: {
234
- readonly rawUsage: Record<string, unknown> | undefined;
235
- }): {
236
- input_tokens?: number;
237
- output_tokens?: number;
238
- } | undefined;
239
- export declare function translateStatusEvent({ rawEvent }: {
240
- readonly rawEvent: Record<string, unknown>;
241
- }): AgentRuntimeEventTranslation;
242
- export declare function translateToolResultEvent({ rawEvent }: {
243
- readonly rawEvent: Record<string, unknown>;
244
- }): AgentRuntimeEventTranslation;
245
- export declare function translateErrorEvent({ rawEvent }: {
246
- readonly rawEvent: Record<string, unknown>;
247
- }): AgentRuntimeEventTranslation;
248
- export declare function translateTurnEndEvent({ rawEvent }: {
249
- readonly rawEvent: Record<string, unknown>;
250
- }): AgentRuntimeEventTranslation;
251
- /**
252
- * Narrows one parser-emitted `{type, ...}` record into this engine's
253
- * `RunAgentPayload` union (or the `error`/`turn-end`/`ignored` routing
254
- * `run()` special-cases). Pure — no I/O, no closure state — so every
255
- * variant each of the 4 supported parsers can produce is directly
256
- * assertable in isolation.
257
- *
258
- * Defensive by construction: several real parser emissions carry fields
259
- * looser than `RunAgentPayload`'s types promise (e.g. copilot's
260
- * `tool.execution_start` emits `id: data.toolCallId ?? null` — a literal
261
- * `null`, not the `string` `RunAgentPayload['tool_use']['id']` demands).
262
- * Every field read here is defensively coerced (`asString`/
263
- * `asOptionalString`/`asOptionalNumber`) rather than trusted, so a
264
- * malformed or null field degrades to a safe default instead of
265
- * propagating `null`/`undefined` into a field typed as required, or
266
- * throwing.
267
- *
268
- * @param rawEvent - One event as delivered to a stream parser's `onEvent` callback.
269
- * @returns The routing + payload this event maps to.
270
- * @complexity O(1) — one table lookup, no iteration.
271
- * @overallScore 100/100
272
- */
273
- export declare function translateAgentRuntimeEvent({ rawEvent }: {
274
- readonly rawEvent: unknown;
275
- }): AgentRuntimeEventTranslation;
276
- /** Machine-readable failure reasons `run()` can reject with — every one is preceded by a `lifecycle.finish({status:'failed'})` call (see module doc's Invariant section). */
277
- export type AgentExecutorErrorCode = 'AGENT_PERMISSION_MODE_INVALID' | 'AGENT_NOT_FOUND' | 'AGENT_RUNTIME_UNSUPPORTED' | 'AGENT_BINARY_NOT_RESOLVED' | 'AGENT_SPAWN_FAILED' | 'AGENT_PROMPT_TOO_LARGE';
278
- /** Thrown by `AgentExecutor.run()` on every failure path — never a bare `Error`, so callers can branch on `.code` instead of parsing `.message`. */
279
- export declare class AgentExecutorError extends Error {
280
- readonly code: AgentExecutorErrorCode;
281
- constructor({ code, message }: {
282
- readonly code: AgentExecutorErrorCode;
283
- readonly message: string;
284
- });
285
- }
286
- export interface AgentExecutorRunInput {
287
- readonly runId: string;
288
- readonly agentId: string;
289
- readonly prompt: string;
290
- readonly cwd: string;
291
- /** Optional host-selected model id, forwarded to every runtime transport. */
292
- readonly model?: string;
293
- /** Optional host-selected reasoning effort, forwarded to runtime argv builders. */
294
- readonly reasoning?: string;
295
- /**
296
- * Forwarded verbatim to `RuntimeBuildOptions.permissionMode` (see `@jini-ai/agent-runtime`'s
297
- * `types.ts`). Every def that has an auto-approve flag (`bypassPermissions` / `--yolo` /
298
- * `--dangerously-skip-permissions`) historically enabled it when omitted because a spawned
299
- * subprocess has no TTY for interactive approval. That rationale explains the old behavior;
300
- * the daemon now fails closed with 'restricted'. A host must explicitly select 'bypass' to
301
- * delegate auto-approval, and any unknown mode is rejected before launching a process.
302
- */
303
- readonly permissionMode?: 'bypass' | 'restricted';
304
- /**
305
- * Host-validated image files. How each one actually reaches the model
306
- * depends on `def.imageDelivery` (see `@jini-ai/agent-runtime`'s `types.ts`):
307
- * a `'native'` def forwards these through its own protocol (argv, ACP
308
- * `resource_link` blocks, or pi-rpc's base64 `images` field); a
309
- * `'prompt-path'` def (currently just `claude`) instead gets them named in
310
- * the prompt text and its allowed directories widened, via
311
- * `image-prompt-delivery.ts#applyImagePromptDelivery`, called once near
312
- * the top of `run()` below.
313
- */
314
- readonly imagePaths?: readonly string[];
315
- /** Additional host-validated directories the runtime may read. */
316
- readonly extraAllowedDirs?: readonly string[];
317
- /** Trusted root that must contain pi-rpc image paths after realpath resolution. */
318
- readonly uploadRoot?: string;
319
- /**
320
- * Credential(s) this run's selected agent/provider needs (e.g. `{ ANTHROPIC_API_KEY: '...' }`),
321
- * delegated explicitly by the host and merged into the baseline-allowlisted env below. Never
322
- * read implicitly from `process.env` — see SEC-001.
323
- */
324
- readonly credentialEnv?: Record<string, string>;
325
- /**
326
- * Explicit escape hatch: when supplied, used verbatim as the spawned subprocess's entire
327
- * environment (no allowlist filtering) — for tests and hosts that have already done their own
328
- * scoping. When omitted (the default), the subprocess gets only `BASELINE_AGENT_ENV_KEYS` from
329
- * the host's real env plus `credentialEnv`, never a full `process.env` passthrough. A spawned
330
- * coding-agent CLI is prompt-influenced and must be treated as potentially adversarial; it must
331
- * not inherit secrets the daemon process happens to hold for unrelated reasons. See SEC-001
332
- * (`ADS-memory/reports/proposals/PROP-agent-subprocess-env-allowlist-2026-07-21.md`) and locked
333
- * architecture decision C8 (`ADS-memory/reports/jini-port/extraction-plan.md`).
334
- */
335
- readonly env?: NodeJS.ProcessEnv;
336
- /**
337
- * Stored session id for this (conversation, agent) pair from a prior run's
338
- * `RunEndPayload.sessionRef` (see `@jini-ai/protocol`'s doc on that field) — set when the host
339
- * wants this run to continue that CLI session instead of starting cold. Forwarded verbatim into
340
- * `RuntimeContext.resumeSessionId`; a `resumesSessionViaCli` def (see
341
- * `@jini-ai/agent-runtime`'s `types.ts`) reads it to pass its CLI's own resume flag, in which case
342
- * the host should send only the latest user turn as `prompt`, not the full transcript. `null` and
343
- * omitted are equivalent: no resume target for this run.
344
- */
345
- readonly resumeSessionId?: string | null;
346
- /**
347
- * A fresh id the host mints and persists when starting a session it wants resumable on a later
348
- * turn (i.e. no `resumeSessionId` is available yet). Forwarded verbatim into
349
- * `RuntimeContext.newSessionId`; a `resumesSessionViaCli` def passes it to its CLI's own
350
- * "start with this id" flag so a later turn's `resumeSessionId` can continue the same underlying
351
- * CLI session. Ignored by defs that don't declare `resumesSessionViaCli`.
352
- */
353
- readonly newSessionId?: string;
354
- /**
355
- * Forwarded verbatim to `RuntimeBuildOptions.disallowedTools` (see `@jini-ai/agent-runtime`'s
356
- * `types.ts`) — Finding 2 of SEC-assistant-env-isolation-2026-09-07. A mechanism only: this
357
- * package bakes in no opinion about which tools to name; the host decides. `undefined`/empty
358
- * means no restriction, byte-identical to today's behavior.
359
- */
360
- readonly disallowedTools?: readonly string[];
361
- /** Same mechanism as {@link disallowedTools}, forwarded to `RuntimeBuildOptions.allowedTools`. */
362
- readonly allowedTools?: readonly string[];
363
- /** Forwarded verbatim to `RuntimeBuildOptions.settingSources` (an empty list = load none of the CLI's settings layers). */
364
- readonly settingSources?: readonly string[];
365
- /** Forwarded verbatim to `RuntimeBuildOptions.settings` (extra settings JSON or file path for this run). */
366
- readonly settings?: string;
367
- }
368
- export interface AgentExecutor {
369
- /**
370
- * Spawns `agentId`'s CLI for `runId`, wires its stdout/stderr into
371
- * `lifecycle.emit()`, and resolves once the child process is confirmed
372
- * spawned (fire-and-forget from there — see module doc). Callers await
373
- * `lifecycle.waitForTerminal(runId)` separately for completion.
374
- * @throws {@link AgentExecutorError} on every failure path — the
375
- * underlying run is always already transitioned to `'failed'` via
376
- * `lifecycle.finish()` before this rejects (see module doc's Invariant).
377
- */
378
- 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>;
379
- }
380
- /** Process discovery is a getter; process collection and stopping accept explicit host inputs. */
381
- export type CollectProcessTreePidsPort = (args: {
382
- readonly processes: ProcessSnapshot[];
383
- readonly rootPids: Array<number | null | undefined>;
384
- }) => number[];
385
- export type StopProcessesPort = (args: {
386
- readonly pids: Array<number | null | undefined>;
387
- }) => Promise<StopProcessesResult>;
388
- interface TerminateChildTreeDeps {
389
- readonly listProcessSnapshots: () => Promise<ProcessSnapshot[]>;
390
- readonly collectProcessTreePids: CollectProcessTreePidsPort;
391
- readonly stopProcesses: StopProcessesPort;
392
- }
393
- /**
394
- * Which step reported a contained failure through `onCleanupFailure` (SEC-007).
395
- *
396
- * The first three come from {@link terminateChildTreeBestEffort} (process-tree teardown). The last
397
- * two are the two fallible steps that sit between a child's `'close'` and `finish()` — staged-file
398
- * removal and the host's own `classifyFailure` — neither of which may prevent the terminal
399
- * transition, and neither of which may fail silently either. See each close handler.
400
- */
401
- export type AgentCleanupFailurePhase = 'cancel' | 'acp-attach-failure' | 'pi-rpc-attach-failure' | 'staged-file-cleanup' | 'failure-classification';
402
- export interface AgentCleanupFailureContext {
403
- readonly runId: string;
404
- readonly phase: AgentCleanupFailurePhase;
405
- /**
406
- * The child's pid. `undefined` only for the post-close phases on a child that never had one
407
- * assigned (a spawn that produced no process): those phases are about this run's own bookkeeping
408
- * rather than about signalling a process, so an absent pid is reportable rather than a
409
- * contradiction.
410
- */
411
- readonly pid: number | undefined;
412
- readonly error: unknown;
413
- }
414
- /**
415
- * Gap 3 (capability-routed continuation transport) — host-owned config for the
416
- * `'stdin-injection'` transport (claude/codebuddy only; see
417
- * `resolveContinuationTransport`'s doc). **Absent by default, and absent means
418
- * zero behavior change**: with no `ContinuationOptions`, every `turn_end`
419
- * closes stdin exactly as it always has (v1 behavior, unconditionally).
420
- *
421
- * `autonomousToolNames` is this task's answer to the debate's Unresolved
422
- * Delta (how does the loop distinguish "the agent is continuing
423
- * autonomously" from "the agent is waiting on a human"): rather than
424
- * inferring intent from the stream, the *host* pre-declares which tool
425
- * names are safe to auto-resolve and re-inject without a human in the loop.
426
- * A `tool_use` whose name is not in this set is left exactly as it was
427
- * before gap 3 — stdin closes, the run proceeds to its normal terminal
428
- * state — even though a `stopReason: 'tool_use'` was observed. This sidesteps
429
- * building unproven intent-detection: nothing auto-continues unless a host
430
- * has explicitly vetted that specific tool as autonomous-safe. A
431
- * human-facing "ask the user a question" tool is simply never added to this
432
- * set; `packages/chat-core/src/question-form.ts`'s existing text-tag
433
- * mechanism (a new `Run` per turn, not mid-turn injection) already covers
434
- * that case without needing this transport at all.
435
- */
436
- export interface ContinuationOptions {
437
- /** 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. */
438
- readonly toolExecutor: ToolExecutor;
439
- /** The principal an injected tool call is authorized as. */
440
- readonly principal: Principal;
441
- /** 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. */
442
- readonly autonomousToolNames: ReadonlySet<string>;
443
- }
444
- /**
445
- * Gap 3, part 2 (MCP-callback continuation transport — the spawn-time half the spike's own
446
- * commit message named as undone: "Item 4 ... NOT done yet"). `resolveContinuationTransport`
447
- * already resolves `'mcp-callback'` for every def with `externalMcpInjection !== undefined`, but
448
- * nothing in this file ever *acted* on that resolution — `execute_delegated_tool`
449
- * (`@jini-ai/mcp`'s `../server/tools/delegated-tool.ts`) only does anything useful once the spawned
450
- * CLI's own client actually launches `jini-mcp` as its MCP server subprocess.
451
- *
452
- * **All six declared strategies are wired.** These options describe *one* bridge server
453
- * (`command`/`args`/`daemonUrl`/`credential`); which transport carries it to a given child is that
454
- * def's own `externalMcpInjection` declaration, and each of the six has exactly one
455
- * implementation here — see {@link buildMcpBridgeDelivery}, which is the single dispatch point.
456
- * The interface name predates the other five mechanisms and is kept for API compatibility with
457
- * `@jini-ai/server`'s `agentExecutor` passthrough; it is no longer `.mcp.json`-specific.
458
- *
459
- * **Host-resolved, not this package's to know.** `command`/`daemonUrl` have no default the way
460
- * `journal`/`continuation`/`classifyFailure` don't either — there is no "real" install layout or
461
- * loopback URL this package could assume on a caller's behalf (matching every other seam on this
462
- * interface that defaults to *nothing* rather than a real implementation).
463
- */
464
- export interface McpJsonInjectionOptions {
465
- /** 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. */
466
- readonly command: string;
467
- /** Extra argv for `command`. @default [] */
468
- readonly args?: readonly string[];
469
- /**
470
- * Extra env for the bridge child, for hosts whose `command` is not a plain Node binary. An Electron
471
- * host passes `{ ELECTRON_RUN_AS_NODE: '1' }`: its `process.execPath` is the app itself, which boots
472
- * as a GUI app without that flag and never speaks MCP. `buildAgentEnv`'s allowlist strips the
473
- * host's own copy from the CLI, so it can only reach the bridge through here. `JINI_*` keys are
474
- * ignored: the run id, callback URL and token are this driver's to set. @default {}
475
- */
476
- readonly env?: Readonly<Record<string, string>>;
477
- /** 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`). */
478
- readonly daemonUrl: string;
479
- /**
480
- * Mints the bearer credential this run's `jini-mcp` child presents on its callbacks, delivered to
481
- * the child through `JINI_DAEMON_TOKEN`. Omit it and the child is spawned exactly as before, with
482
- * no token env var at all — so this is additive for every existing host.
483
- *
484
- * **A resolver, not a string, and deliberately so.** A host's `McpJsonInjectionOptions` is built
485
- * once when it composes its executor — before any run exists. A plain string field could therefore
486
- * only ever carry one boot-wide secret shared by every run, which defeats the point: the reason to
487
- * hand the child a credential at all is that it can be scoped to the one run it was spawned for and
488
- * stop working when that run ends. Taking `runId` here is what makes a per-run credential
489
- * expressible.
490
- *
491
- * May be async so a host can mint through a keystore or signing service. Resolution happens in
492
- * `writeMcpJsonForRun`, which is already async and already effectful; `buildMcpJsonServerEntry`
493
- * stays pure and synchronous and receives the resolved value.
494
- *
495
- * Never hand this the host's own inbound API token. The child is the least-trusted participant in
496
- * the run — it is reachable by whatever the spawned CLI does — so its credential should authorize
497
- * its own callback route and nothing else.
498
- *
499
- * @throws Anything the host's own minting throws. `run()` turns a rejection into a pre-spawn
500
- * `AGENT_SPAWN_FAILED` failure rather than spawning a child that cannot authenticate.
501
- */
502
- readonly credential?: ({ runId }: {
503
- readonly runId: string;
504
- }) => string | Promise<string>;
505
- /** 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) */
506
- readonly readFile?: ({ path }: {
507
- readonly path: string;
508
- }) => Promise<string>;
509
- /** 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) */
510
- readonly writeFile?: ({ path, content }: {
511
- readonly path: string;
512
- readonly content: string;
513
- }) => Promise<void>;
514
- /**
515
- * Removes this run's config file once the run is over — it holds a live per-run bearer token, so
516
- * leaving it behind is the same class of confidentiality gap as a leaked prompt file (see
517
- * `WireChildLifecycleContext.cleanupStagedFiles`). Called on the close handler and on every
518
- * pre-spawn/spawn-failure path, and a rejection is reported rather than allowed to strand the run.
519
- * @default `fs.promises.rm(path, { force: true })` — already-gone is success, not an error.
520
- */
521
- readonly removeFile?: ({ path }: {
522
- readonly path: string;
523
- }) => Promise<void>;
524
- /**
525
- * `'codex-toml'` only. Creates a fresh, randomly-named directory `prepareCodexHomeForRun` stages
526
- * as a run's scratch `CODEX_HOME`. **Must be non-deterministic (a real `mkdtemp`, not a
527
- * caller-computed path)** — unlike `mcpJsonPathForRun`'s deterministic path inside the run's own
528
- * `cwd`, this directory holds a copy of the operator's real Codex login credential, and
529
- * `os.tmpdir()` is a shared location on a multi-user host: a guessable name there is a real
530
- * pre-plant/symlink target for another local user. `fs.mkdtemp`'s random suffix plus its `0700`
531
- * directory mode is the actual confidentiality control, matching the same reasoning
532
- * `@jini-ai/agent-runtime`'s `log-file.ts`/`prompt-file.ts` already apply to their own staged temp
533
- * dirs.
534
- * @param prefix - A caller-composed, run-id-derived prefix (already sanitized) for the mkdtemp
535
- * template; the real suffix mkdtemp appends is what makes the path unpredictable.
536
- * @default `fs.mkdtemp(path.join(os.tmpdir(), prefix))`
537
- */
538
- readonly mkdtemp?: ({ prefix }: {
539
- readonly prefix: string;
540
- }) => Promise<string>;
541
- /**
542
- * `'codex-toml'` only. Recursively removes the scratch `CODEX_HOME` directory `mkdtemp` above
543
- * created — the directory-level analogue of `removeFile`, needed because this mechanism stages a
544
- * whole directory (`config.toml` plus a copied `auth.json`), not one file.
545
- * @default `fs.rm(path, { recursive: true, force: true })` — already-gone is success, not an error.
546
- */
547
- readonly removeDir?: ({ path }: {
548
- readonly path: string;
549
- }) => Promise<void>;
550
- }
551
- /** The run-error code a run gets when its CLI reports the injected `jini` bridge as anything but connected. */
552
- export declare const MCP_BRIDGE_UNAVAILABLE = "MCP_BRIDGE_UNAVAILABLE";
553
- /**
554
- * Judges a CLI's startup report of the injected bridge. Returns the bridge's status (`'missing'`
555
- * when the list omits it) when it is not `'connected'`, and `undefined` when it is connected or
556
- * when `rawEvent` is not an init status carrying an MCP server list (nothing to judge).
557
- *
558
- * `'pending'` counts as a failure on purpose: the CLI decides which MCP tools a session has at
559
- * startup, so a bridge that is not connected by then never joins this run.
560
- * @param rawEvent - One parsed stream event, as `@jini-ai/agent-runtime`'s parser emits it.
561
- * @complexity O(n) in the number of reported servers.
562
- */
563
- export declare function unavailableJiniBridgeStatus({ rawEvent }: {
564
- readonly rawEvent: unknown;
565
- }): string | undefined;
566
- /** One `.mcp.json` `mcpServers` entry — the shape Claude Code's own config schema expects. */
567
- interface McpJsonServerEntry {
568
- readonly command: string;
569
- readonly args: string[];
570
- readonly env: Readonly<Record<string, string>> & {
571
- readonly JINI_RUN_ID: string;
572
- readonly JINI_DAEMON_URL: string;
573
- /** Present only when the host supplied a `credential` resolver — see {@link McpJsonInjectionOptions.credential}. */
574
- readonly JINI_DAEMON_TOKEN?: string;
575
- };
576
- }
577
- /**
578
- * Builds this run's `mcpServers.jini` entry — pure and synchronous, so every field mapping is
579
- * directly assertable without touching the filesystem. The credential arrives already resolved:
580
- * `McpJsonInjectionOptions.credential` is a possibly-async per-run resolver, and awaiting it is
581
- * `writeMcpJsonForRun`'s job, which keeps the effect out of this function.
582
- *
583
- * @param runId - The run this entry scopes its child to.
584
- * @param options - `command`/`args`/`daemonUrl`/`env` from the host's injection options.
585
- * @param credential - The already-resolved bearer token, or `undefined` to omit `JINI_DAEMON_TOKEN`
586
- * entirely. Omitting produces byte-identical output to before this parameter existed.
587
- * @complexity O(1).
588
- * @overallScore 100/100
589
- */
590
- export declare function buildMcpJsonServerEntry({ runId, options }: {
591
- readonly runId: string;
592
- readonly options: Pick<McpJsonInjectionOptions, 'command' | 'args' | 'daemonUrl' | 'env'>;
593
- }, { credential }?: {
594
- readonly credential?: string;
595
- }): McpJsonServerEntry;
596
- /**
597
- * Merges {@link JINI_MCP_SERVER_KEY} into an existing `.mcp.json`'s `mcpServers` map, preserving
598
- * every other key and every other registered server untouched. A missing (`existingRaw ===
599
- * undefined`), empty, or unparseable-as-a-JSON-object existing file all degrade to "start from an
600
- * empty document" rather than throwing — an unparseable project `.mcp.json` is a pre-existing
601
- * problem this driver did not create and cannot safely repair, so it is deliberately overwritten
602
- * with a fresh, valid file containing just this run's bridge entry rather than left broken or
603
- * left blocking the run. Pure — no I/O — so every branch is directly assertable.
604
- * @complexity O(1) plus `JSON.parse`/`JSON.stringify`'s own cost on a small config file.
605
- * @overallScore 100/100
606
- */
607
- export declare function mergeMcpJsonContent({ existingRaw, serverEntry }: {
608
- readonly existingRaw: string | undefined;
609
- readonly serverEntry: McpJsonServerEntry;
610
- }): string;
611
- /**
612
- * Mechanism 2 of 5 — `'acp-merge'`. Re-shapes the same bridge entry into the `mcpServers` element
613
- * an ACP `session/new` call carries, for the 9 ACP-native defs declaring this strategy (amr, devin,
614
- * hermes, kilo, kimi, kiro, reasonix, trae-cli, vibe). Pure.
615
- *
616
- * `env` is emitted as a plain object on purpose: `@jini-ai/agent-runtime`'s
617
- * `buildAcpSessionNewParams` already normalises a plain-object `env` into either the
618
- * `[{name, value}]` array form or the `{"KEY": "val"}` map form according to each def's own
619
- * `acpMcpEnvFormat`, so the per-vendor wire-shape difference stays in the one place that already
620
- * owns it rather than being re-decided here.
621
- *
622
- * **The credential travels in `env`, never in `args`.** An ACP agent spawns this server itself and
623
- * applies `env` to that child's environment; a token in `args` would land in the child's process
624
- * arguments, readable by any other local user via `ps`. Same rule as the `.mcp.json` path.
625
- *
626
- * @param entry - The shared bridge entry from {@link buildMcpJsonServerEntry}.
627
- * @returns A single-element list — this driver contributes exactly its own bridge server and never
628
- * removes or rewrites servers a def or host added by other means.
629
- * @complexity O(1).
630
- * @overallScore 100/100
631
- */
632
- export declare function buildAcpMcpBridgeServers({ entry }: {
633
- readonly entry: McpJsonServerEntry;
634
- }): AcpMcpServerInput[];
635
- /**
636
- * Serialises the bridge entry into the OpenCode-schema config JSON that `OPENCODE_CONFIG_CONTENT`
637
- * / `MIMOCODE_CONFIG_CONTENT` carries, merging into whatever the host already put in that variable
638
- * rather than replacing it — the same "merge, never clobber" discipline
639
- * {@link mergeMcpJsonContent} applies to `.mcp.json`, and for the same reason: a host may already
640
- * be handing the CLI the *user's* configured MCP servers through this exact variable, and
641
- * overwriting it would silently delete them.
642
- *
643
- * A missing, empty, or unparseable-as-a-JSON-object existing value degrades to "start from an empty
644
- * document". Overwriting an unparseable value is deliberate and matches `mergeMcpJsonContent`: this
645
- * driver did not create it, cannot safely repair it, and must not block the run on it.
646
- *
647
- * Emitted per server: `{type: 'local', command: [<command>, ...<args>], environment: {...},
648
- * enabled: true}` — the shape `@jini-ai/mcp`'s own `buildOpenCodeMcpConfigContent` emits for a
649
- * stdio server, so both producers stay schema-compatible.
650
- *
651
- * **The credential lands in `environment`, i.e. the MCP child's env — never in `command`.** OpenCode
652
- * spawns the bridge from `command`, so a token placed there would be visible in `ps` output to
653
- * every other local user. This is the same constraint that keeps `JINI_DAEMON_TOKEN` out of argv on
654
- * the `.mcp.json` and ACP paths.
655
- *
656
- * @param existingRaw - Whatever the spawn env already held for this variable, or `undefined`.
657
- * @param entry - The shared bridge entry from {@link buildMcpJsonServerEntry}.
658
- * @returns The full JSON string to set as the env var's value.
659
- * @complexity O(1) plus `JSON.parse`/`JSON.stringify` over a small config document.
660
- * @overallScore 100/100
661
- */
662
- export declare function mergeEnvContentMcpConfig({ existingRaw, entry }: {
663
- readonly existingRaw: string | undefined;
664
- readonly entry: McpJsonServerEntry;
665
- }): string;
666
- /**
667
- * Merges a staged system-prompt overlay file's path into the `instructions` array of the same
668
- * OpenCode-schema config document {@link mergeEnvContentMcpConfig} writes `mcp` into — for a
669
- * `systemPromptDelivery: { strategy: 'config-instructions-file' }` def (`opencode` today).
670
- *
671
- * Confirmed live (2026-09-01, opencode-cli 1.17.10), not inferred from docs alone:
672
- * 1. `instructions` is honored — a run configured with it visibly followed the file's directive
673
- * (a required exact-token prefix), while an identical run without it did not.
674
- * 2. It appends, never replaces: the same run that followed the custom instruction ALSO still
675
- * answered correctly using opencode's own baked-in environment-context system prompt (asked
676
- * for its cwd, with nothing about cwd anywhere in the custom instructions file) — proof
677
- * opencode's own defaults survive alongside a custom `instructions` entry, not just proof the
678
- * file was read at all.
679
- * 3. Adding this key alongside `mcp` in the same `OPENCODE_CONFIG_CONTENT` document disturbs
680
- * neither: in one combined run, the MCP bridge still got its connection attempt (logged
681
- * `key=jini type=local`) AND the custom instruction was still followed — same as running each
682
- * key alone.
683
- * 4. `instructions` is re-read fresh from the env on every spawn, including a `-s <id>`-resumed
684
- * turn (proved by swapping in a second instructions file between two turns of one resumed
685
- * session and seeing the second turn immediately reflect it while still recalling
686
- * conversation memory from turn one) — so this mechanism is safe to redeliver every turn like
687
- * `'append-flag'`/`'env-var'`, exempt from the prompt-prefix fallback's create-only gating
688
- * (see {@link resolveSystemPromptOverlayDelivery}'s doc): nothing here is ever baked into
689
- * opencode's own persisted session state the way re-injecting fallback prompt text would be.
690
- *
691
- * @param existingRaw - Whatever the spawn env already held for this variable (already possibly
692
- * carrying `mcp`, if `mergeEnvContentMcpConfig` ran first on the same value — order between the two
693
- * doesn't matter, each only touches its own top-level key), or `undefined`.
694
- * @param instructionsFilePath - The staged overlay file's absolute path (see
695
- * {@link prepareSystemPromptOverlayFileIfNeeded}).
696
- * @returns The full JSON string to set as the env var's value. Appends to, never clobbers, any
697
- * `instructions` entries already present — the same "merge, never clobber" discipline
698
- * {@link mergeEnvContentMcpConfig} applies to `mcp`, in case a host is already using this same
699
- * config-content variable to carry the operator's own instruction files.
700
- * @complexity O(1) plus `JSON.parse`/`JSON.stringify` over a small config document.
701
- * @overallScore 100/100
702
- */
703
- export declare function mergeEnvContentInstructions({ existingRaw, instructionsFilePath }: {
704
- readonly existingRaw: string | undefined;
705
- readonly instructionsFilePath: string;
706
- }): string;
707
- /**
708
- * Mechanism 5 of 5 — `'codex-toml'`'s serialization step. Builds the `[mcp_servers.jini]` TOML
709
- * table (plus, when the entry carries any env vars, a separate `[mcp_servers.jini.env]` table)
710
- * Codex's own config schema expects.
711
- *
712
- * Confirmed against a real installed Codex CLI (0.151.0), not assumed from docs: round-tripping
713
- * `codex mcp add <name> --env K=V -- <cmd> <args>` against a scratch `CODEX_HOME` and reading back
714
- * `config.toml` produced exactly this shape (`command`/`args` as TOML strings/array in the main
715
- * table, env vars in a nested `.env` table) — see `archived provenance ledger` for the transcript.
716
- * @param entry - The shared bridge entry from {@link buildMcpJsonServerEntry}.
717
- * @returns A TOML fragment with no leading/trailing blank-line padding — {@link buildCodexHomeConfigToml} owns spacing when combining it with existing content.
718
- * @complexity O(n) in the number of argv/env entries.
719
- * @overallScore 100/100
720
- */
721
- export declare function buildCodexMcpServerToml({ entry }: {
722
- readonly entry: McpJsonServerEntry;
723
- }): string;
724
- /**
725
- * Builds the full `config.toml` a run's scratch `CODEX_HOME` gets: the real Codex home's own
726
- * config, with any pre-existing `[mcp_servers.jini]` table removed (see
727
- * {@link stripExistingJiniMcpServerTable}), then this run's own table appended.
728
- *
729
- * **Append-only by design, not a parse-and-merge, for everything but the jini table itself.**
730
- * `mergeMcpJsonContent`/`mergeEnvContentMcpConfig` above can safely parse-merge-reserialize because
731
- * their formats have a JS-native parser (`JSON.parse`); this driver has no general TOML parser in
732
- * its dependency graph (see `buildCodexMcpServerToml`'s doc), and every other setting a real Codex
733
- * install carries — model choice, sandbox policy, the trusted-project list, the operator's own
734
- * other MCP servers — must survive a spawn byte-for-byte. Only the one table this driver itself
735
- * owns (`jini` is this integration's own reserved server name — see {@link JINI_MCP_SERVER_KEY}) is
736
- * ever removed, via the narrow line-oriented scan above, never a full TOML parse.
737
- * @param existingRaw - The real Codex home's `config.toml` content, or `undefined` when it does not
738
- * exist (a fresh Codex install — degrades to "start from just this run's block", matching
739
- * {@link mergeMcpJsonContent}'s own "missing file" handling).
740
- * @param entry - The shared bridge entry.
741
- * @returns The full text to write to the scratch `CODEX_HOME`'s `config.toml`.
742
- * @complexity O(n) in the existing config's length.
743
- */
744
- export declare function buildCodexHomeConfigToml({ existingRaw, entry }: {
745
- readonly existingRaw: string | undefined;
746
- readonly entry: McpJsonServerEntry;
747
- }): string;
748
- /**
749
- * Where `'codex-toml'` reads the operator's REAL Codex config from, to seed a run's scratch copy —
750
- * never where it writes. Resolved against the daemon HOST process's own environment (`hostEnv`,
751
- * `process.env` at the real call site), not a run's sandboxed spawn env: `CODEX_HOME` is not in
752
- * `BASELINE_AGENT_ENV_KEYS`, so a spawned child never inherits it anyway, and the whole point here
753
- * is finding wherever the *operator's actual* Codex install lives, which is a host-machine fact.
754
- * @param hostEnv - The daemon process's own environment.
755
- * @returns `hostEnv.CODEX_HOME` when set to a non-blank value (matching Codex's own resolution
756
- * order), else the CLI's documented default, `~/.codex`.
757
- * @complexity O(1).
758
- * @overallScore 100/100
759
- */
760
- export declare function resolveSourceCodexHomeDir({ hostEnv }: {
761
- readonly hostEnv: NodeJS.ProcessEnv;
762
- }): string;
763
- /**
764
- * What one run's MCP bridge turns into, discriminated by the delivery mechanism its def declared.
765
- * Exactly one variant is produced per run, and each variant carries only what its own consumer
766
- * needs — so a consumer cannot accidentally read another mechanism's payload.
767
- */
768
- export type McpBridgeDelivery =
769
- /** `'claude-mcp-json'` (claude, codebuddy): a `.mcp.json` staged into the run cwd, whose path the def's `buildArgs` passes as `--mcp-config`. */
770
- {
771
- readonly kind: 'claude-mcp-json';
772
- readonly mcpJsonPath: string;
773
- readonly serverEntry: McpJsonServerEntry;
774
- }
775
- /** `'acp-merge'` (the 9 ACP-native defs): `mcpServers` entries for the ACP `session/new` params. */
776
- | {
777
- readonly kind: 'acp-merge';
778
- readonly mcpServers: readonly AcpMcpServerInput[];
779
- }
780
- /** `'opencode-env-content'` / `'mimo-env-content'` (opencode, mimo): one spawn-env variable carrying the serialised config. */
781
- | {
782
- readonly kind: 'env-content';
783
- readonly envVarName: string;
784
- readonly serverEntry: McpJsonServerEntry;
785
- }
786
- /**
787
- * `'codex-toml'` (codex): no path yet — unlike `'claude-mcp-json'`'s `mcpJsonPath`, the scratch
788
- * `CODEX_HOME` directory is created with `fs.mkdtemp` (a real, non-deterministic filesystem
789
- * effect — see `McpJsonInjectionOptions.mkdtemp`'s own doc for why), so it cannot be computed by
790
- * this delivery's pure, synchronous dispatch. `prepareCodexHomeIfNeeded` stages it separately and
791
- * reports the resulting path back into `childEnv.CODEX_HOME` directly, never through this type.
792
- */
793
- | {
794
- readonly kind: 'codex-toml';
795
- readonly serverEntry: McpJsonServerEntry;
796
- }
797
- /**
798
- * `'env-passthrough'` (antigravity): the bridge entry's `env` triple (`JINI_RUN_ID`/
799
- * `JINI_DAEMON_URL`/`JINI_DAEMON_TOKEN`) is set directly on the spawned CLI's own OS
800
- * environment — no config document, no file, no CLI-specific schema. Correct only because this
801
- * strategy's CLI already inherits its own env down to the stdio MCP child it launches for a
802
- * server registered once, globally, out of band (see `types.ts`'s own doc on this strategy).
803
- */
804
- | {
805
- readonly kind: 'env-passthrough';
806
- readonly serverEntry: McpJsonServerEntry;
807
- };
808
- /**
809
- * **The single dispatch point from an `externalMcpInjection` strategy to its delivery mechanism.**
810
- * Pure and synchronous — the one effectful input (the per-run bearer credential) arrives already
811
- * resolved, so every strategy's mapping is directly assertable without touching the filesystem,
812
- * the environment, or a keystore.
813
- *
814
- * Keyed off the declared *strategy*, never off `def.id`: a def gets a working bridge by declaring a
815
- * mechanism, not by being named in this file. That is what makes the 9 `'acp-merge'` defs work
816
- * without any of their own files being touched.
817
- *
818
- * @param input.cwd - The run's working directory; only `'claude-mcp-json'` uses it, to place this
819
- * run's own config file (see {@link mcpJsonPathForRun}) — never `cwd/.mcp.json` itself.
820
- * @param input.runId - Scopes the bridge child to this run.
821
- * @param input.strategy - The def's declared `externalMcpInjection`, or `undefined` for a def with no native MCP transport.
822
- * @param input.options - The host's bridge options, or `undefined` when the host never configured injection.
823
- * @param input.credential - Already-resolved bearer token, or `undefined` to omit `JINI_DAEMON_TOKEN` entirely.
824
- * @returns `null` when this run delivers nothing — an unconfigured host, or a def declaring no
825
- * strategy — which is byte-identical to this feature not existing.
826
- * @complexity O(1).
827
- * @overallScore 100/100
828
- */
829
- export declare function buildMcpBridgeDelivery(input: {
830
- readonly cwd: string;
831
- readonly runId: string;
832
- readonly strategy: RuntimeAgentDef['externalMcpInjection'];
833
- readonly options: McpJsonInjectionOptions | undefined;
834
- readonly credential: string | undefined;
835
- }): McpBridgeDelivery | null;
836
- /** A staged, run-scoped Codex `CODEX_HOME` — the directory-holding analogue of {@link PreparedPromptFile}/{@link PreparedAgentLogFile} from `@jini-ai/agent-runtime`. */
837
- export type PreparedCodexHome = {
838
- /** Absolute path to hand to the spawned child as its `CODEX_HOME` env var. */
839
- readonly path: string;
840
- /** 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. */
841
- readonly cleanup: () => Promise<void>;
842
- };
843
- /**
844
- * Finding 1 of SEC-assistant-env-isolation-2026-09-07 — a staged, run-scoped `claude` CLI config
845
- * directory, the `CLAUDE_CONFIG_DIR`-isolation analogue of {@link PreparedCodexHome} above.
846
- *
847
- * **The bug this closes:** `BASELINE_AGENT_ENV_KEYS` forwards `HOME` verbatim and neither this
848
- * package nor `@jini-ai/agent-runtime` ever set `CLAUDE_CONFIG_DIR`, so a spawned `claude` child
849
- * resolved its config (skills, plugins, agents, memory-path index, settings) from the OPERATOR's
850
- * own real `$HOME/.claude` — a personal grant (including `Task`/`Edit`/`Write`/`Cron*`/worktree
851
- * tools on this host) the host process never intended to hand the model.
852
- */
853
- export type PreparedClaudeConfigDir = {
854
- /** Absolute path to hand to the spawned child as its `CLAUDE_CONFIG_DIR` env var. */
855
- readonly path: string;
856
- /** 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. */
857
- readonly cleanup: () => Promise<void>;
858
- };
859
- /** 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. */
860
- export interface ClaudeConfigDirSeams {
861
- readonly mkdtemp: ({ prefix }: {
862
- readonly prefix: string;
863
- }) => Promise<string>;
864
- readonly readFile: ({ path }: {
865
- readonly path: string;
866
- }) => Promise<string>;
867
- readonly writeFile: ({ path, content }: {
868
- readonly path: string;
869
- readonly content: string;
870
- }) => Promise<void>;
871
- readonly removeDir: ({ path }: {
872
- readonly path: string;
873
- }) => Promise<void>;
874
- }
875
- /**
876
- * `CreateAgentExecutorOptions.claudeConfigDirIsolation` — every field optional and real-filesystem
877
- * by default, matching this file's standing "no real disk I/O by default in tests" convention (see
878
- * `preparePromptFileForAgent`/`prepareAgentLogFile`'s identical shape). Unlike
879
- * {@link McpJsonInjectionOptions}, this bag is never itself a gate: {@link
880
- * prepareClaudeConfigDirIfNeeded} stages a scratch directory for every `claude`-def run regardless
881
- * of whether a host supplies overrides here — this options bag only lets a test observe/replace the
882
- * filesystem calls, the same way `preparePromptFileForAgent`'s own default does.
883
- */
884
- export interface ClaudeConfigDirIsolationOptions {
885
- /** @default the real `fs.promises.mkdtemp(path.join(os.tmpdir(), prefix))` */
886
- readonly mkdtemp?: ({ prefix }: {
887
- readonly prefix: string;
888
- }) => Promise<string>;
889
- /** Reads the operator's real `.credentials.json`, if any — see {@link prepareClaudeConfigDirForRun}'s own doc. @default the real `fs.promises.readFile` (utf8) */
890
- readonly readFile?: ({ path }: {
891
- readonly path: string;
892
- }) => Promise<string>;
893
- /** Writes the copied `.credentials.json` into the scratch directory. @default the real `fs.promises.writeFile` (utf8) */
894
- readonly writeFile?: ({ path, content }: {
895
- readonly path: string;
896
- readonly content: string;
897
- }) => Promise<void>;
898
- /** @default `fs.promises.rm(path, { recursive: true, force: true })` — already-gone is success, not an error. */
899
- readonly removeDir?: ({ path }: {
900
- readonly path: string;
901
- }) => Promise<void>;
902
- }
903
- /**
904
- * Where `claude`'s own config resolution reads the operator's REAL config from, to seed a run's
905
- * scratch copy — never where it writes. Mirrors {@link resolveSourceCodexHomeDir}'s exact reasoning
906
- * and resolution order: `CLAUDE_CONFIG_DIR` is not in `BASELINE_AGENT_ENV_KEYS`, so a spawned child
907
- * never inherits it anyway — the whole point is finding wherever the *operator's actual* Claude Code
908
- * install lives, a host-machine fact resolved against the daemon HOST process's own environment.
909
- * @param hostEnv - The daemon process's own environment.
910
- * @returns `hostEnv.CLAUDE_CONFIG_DIR` when set to a non-blank value (matching Claude Code's own
911
- * resolution order, confirmed against installed Claude Code 2.1.263), else the CLI's documented
912
- * default, `~/.claude`.
913
- * @complexity O(1).
914
- */
915
- export declare function resolveSourceClaudeConfigDir({ hostEnv }: {
916
- readonly hostEnv: NodeJS.ProcessEnv;
917
- }): string;
918
- /**
919
- * Gap 4 of the run/chat orchestration Final Recommendation: what
920
- * `classifyFailure` (see `CreateAgentExecutorOptions.classifyFailure`) is
921
- * given to decide whether a `'failed'` run is `resumable`. `code`/`signal`
922
- * are the only *content-level* signals cheaply available at every one of the
923
- * three lifecycle-wiring close handlers without new stderr/stdout buffering
924
- * machinery (a host wanting output-pattern-based classification, the way
925
- * OD's own ~20-vendor text-matching classifier worked, would need its own
926
- * listener for that — an honest scope limit, not an oversight).
927
- *
928
- * `sideEffects` (2026-07-22) carries the two `RunRetrySideEffectState` fields
929
- * every `wire*Lifecycle` driver already tracks live from the translated
930
- * agent-event stream it's processing anyway — `userVisibleOutputSeen` (a
931
- * non-empty `text_delta`/`thinking_delta`) and `toolCallSeen` (a `tool_use`)
932
- * — so `decideSafeRunRetry`'s matching suppression guards are genuinely
933
- * exercised, not permanently dead code. Two related fields are deliberately
934
- * absent: `cancelRequested` is never included because it's structurally
935
- * always `false` by the time a classifier runs at all (a cancelled run's
936
- * status already routes to `'cancelled'` before `classifyFailure` is ever
937
- * consulted — see each `wire*Lifecycle` close handler); `artifactWriteSeen`/
938
- * `liveArtifactSeen` have no real signal to derive them from at all —
939
- * `@jini-ai/protocol`'s `RunAgentEventPayload` union (`events.ts`) has no
940
- * `'artifact'`/`'live_artifact'` event kind yet (Jini's own generalized
941
- * GenUI/artifact surface isn't built — see this repo's "A2UI full protocol
942
- * deferred" scope note), unlike OD, which `RunRetrySideEffectState`'s shape
943
- * was carried over from.
944
- */
945
- export interface FailureClassificationContext {
946
- readonly runId: string;
947
- readonly agentId: string;
948
- readonly code: number | null;
949
- readonly signal: string | null;
950
- readonly sideEffects: Pick<RunRetrySideEffectState, 'userVisibleOutputSeen' | 'toolCallSeen'>;
951
- }
952
- /**
953
- * Host-owned failure classifier — gap 4. Decides, for one specific
954
- * `'failed'` run, whether `RunLifecycle.finish()`'s `resumable` flag should
955
- * be `true`. Never consulted for `'succeeded'`/`'cancelled'` outcomes, and
956
- * never consulted for a pre-spawn failure (`failBeforeSpawn`'s call sites) —
957
- * those represent failures where no child process ever ran, so there is
958
- * nothing a classifier could meaningfully examine.
959
- */
960
- export type ClassifyFailure = (context: FailureClassificationContext) => boolean | Promise<boolean>;
961
- /**
962
- * Default ceiling on the `'until-close'` stdout accumulator (see `RuntimeStdoutPolicy` in
963
- * `@jini-ai/agent-runtime`), in bytes of received UTF-8.
964
- *
965
- * A buffered def holds its child's entire stdout in one in-memory string until the process closes,
966
- * which is exactly what makes the accumulator a denial-of-service surface: the child is a
967
- * prompt-influenced agent CLI this driver already treats as potentially adversarial (SEC-001), and
968
- * nothing obliges it to ever close or to stop emitting. Without a ceiling one run could exhaust the
969
- * daemon's heap and take every unrelated run in the process down with it.
970
- *
971
- * 8 MiB is chosen to sit far above any real buffered-agent transcript (antigravity's print-mode
972
- * output — the only `'until-close'` def — is a few KiB of auth prompt and result text) while staying
973
- * small enough that a hostile child cannot meaningfully pressure the heap. A host that genuinely
974
- * needs more passes `CreateAgentExecutorOptions.bufferedStdoutMaxBytes`.
975
- */
976
- export declare const DEFAULT_BUFFERED_STDOUT_MAX_BYTES: number;
977
- /** The side-effect signals {@link applyAgentTranslationSideEffects} reports through, one callback per signal so a caller only wires the ones it actually tracks. */
978
- interface AgentTranslationSideEffectSink {
979
- readonly onSessionId: (args: {
980
- readonly sessionId: string;
981
- }) => void;
982
- readonly onToolCall: () => void;
983
- readonly onUserVisibleOutput: () => void;
984
- }
985
- /**
986
- * Applies one already-translated `'agent'`-kind event's side-effect signals — a captured session id
987
- * (gap 5), and the `toolCallSeen`/`userVisibleOutputSeen` pair every `wire*Lifecycle` driver tracks
988
- * for `FailureClassificationContext.sideEffects` — through `sink`. Extracted from `wireAcpLifecycle`'s
989
- * `send()`, where this exact three-level-deep nesting (session-id check, then tool_use/else-if
990
- * delta-length check) was that function's largest single cognitive-complexity contributor. Pure
991
- * except for calling the injected `sink` callbacks.
992
- * @param payload - The translated event's `RunAgentPayload`.
993
- * @param sessionId - The translation's optional captured session id, or `undefined`.
994
- * @param sink - The driver-specific effects to apply.
995
- * @complexity O(1).
996
- */
997
- export declare function applyAgentTranslationSideEffects({ payload, sessionId, sink }: {
998
- readonly payload: RunAgentPayload;
999
- readonly sessionId: string | undefined;
1000
- readonly sink: AgentTranslationSideEffectSink;
1001
- }): void;
1002
- export interface CreateAgentExecutorOptions {
1003
- readonly lifecycle: RunLifecycle;
1004
- /** @default the real `@jini-ai/agent-runtime` registry lookup */
1005
- readonly getAgentDef?: typeof getAgentDef;
1006
- /** @default the real `@jini-ai/agent-runtime` launch resolver */
1007
- readonly resolveAgentLaunch?: typeof resolveAgentLaunch;
1008
- /** @default the real `@jini-ai/agent-runtime` once-per-binary `--help` capability probe */
1009
- readonly ensureAgentCapabilities?: typeof ensureAgentCapabilities;
1010
- /** @default the real `@jini-ai/agent-runtime` PATH-env composer */
1011
- readonly applyAgentLaunchEnv?: typeof applyAgentLaunchEnv;
1012
- /** @default the real `@jini-ai/platform` cross-platform invocation builder */
1013
- readonly createCommandInvocation?: typeof createCommandInvocation;
1014
- /** @default `node:child_process`'s `spawn` */
1015
- readonly spawn?: typeof nodeSpawn;
1016
- /** @default the real `@jini-ai/agent-runtime` ACP session transport */
1017
- readonly attachAcpSession?: typeof attachAcpSession;
1018
- /**
1019
- * Host-owned policy for ACP agents' native tool calls. The ACP agent still
1020
- * executes its own selected option; Jini-registered tool execution belongs
1021
- * to `createDelegatedToolBridge`, not this permission callback.
1022
- */
1023
- readonly acpPermissionHandler?: AcpPermissionHandler;
1024
- /** @default the real `@jini-ai/agent-runtime` pi-rpc session transport */
1025
- readonly attachPiRpcSession?: typeof attachPiRpcSession;
1026
- /**
1027
- * Stages a `promptViaFile` def's (grok-build) composed prompt to a temp
1028
- * file before `buildArgs` runs. Touches the real filesystem by default
1029
- * (`fs.mkdtemp`/`fs.writeFile`/`fs.rm`) — injectable so tests can drive
1030
- * it without real disk I/O, matching this factory's "no real subprocess,
1031
- * filesystem, or PATH lookup by default in tests" convention.
1032
- * @default the real `@jini-ai/agent-runtime` prompt-file stager
1033
- */
1034
- readonly preparePromptFileForAgent?: typeof preparePromptFileForAgent;
1035
- /**
1036
- * Stages a `needsAgentLogFile` def's (antigravity) diagnostic-log path
1037
- * before `buildArgs` runs. Same real-filesystem/injectable-for-tests deal
1038
- * as `preparePromptFileForAgent` above, and a no-op for every def that
1039
- * did not opt in.
1040
- * @default the real `@jini-ai/agent-runtime` log-file stager
1041
- */
1042
- readonly prepareAgentLogFile?: typeof prepareAgentLogFile;
1043
- /** @default the real `@jini-ai/platform` process-snapshot enumerator */
1044
- readonly listProcessSnapshots?: typeof listProcessSnapshots;
1045
- /** @default the real `@jini-ai/platform` descendant-PID collector */
1046
- readonly collectProcessTreePids?: CollectProcessTreePidsPort;
1047
- /** @default the real `@jini-ai/platform` SIGTERM→SIGKILL escalator */
1048
- readonly stopProcesses?: StopProcessesPort;
1049
- /** Host-owned sink for a process-tree cleanup failure (SEC-007) — e.g. EPERM stopping descendants. @default logs a redacted diagnostic via `console.error` */
1050
- readonly onCleanupFailure?: (context: AgentCleanupFailureContext) => void;
1051
- /**
1052
- * Gap 1's byte-journal (`packages/daemon/src/continuation/journal.ts`) — records every byte
1053
- * this driver sends to or receives from a child agent process, independent of and prior to any
1054
- * parsed/translated event. Covers `writePromptToStdin` (sent) and every `child.stdout`/
1055
- * `child.stderr` `'data'` handler this driver owns (received); does not cover ACP/pi-rpc's own
1056
- * prompt delivery, which happens inside their respective attach functions' own transport, out
1057
- * of this driver's direct view — see `WireAcpLifecycleContext.journal`'s doc.
1058
- * @default no journal — recording is entirely opt-in, unlike every other seam on this
1059
- * interface (which default to a real implementation): there is no generic "real" journal
1060
- * storage this package can default to without a caller-supplied `EventLog` instance.
1061
- */
1062
- readonly journal?: RunByteJournal;
1063
- /**
1064
- * Gap 3's stdin-tool-result injection config — see `ContinuationOptions`'s own doc, especially
1065
- * on why `autonomousToolNames` (not stream-inferred intent) is this task's answer to the
1066
- * human-in-the-loop pause question.
1067
- * @default undefined — every `turn_end` closes stdin unconditionally, byte-identical to
1068
- * pre-gap-3 behavior. Opt-in only, like `journal`: there is no safe default allowlist of
1069
- * "tools okay to auto-continue without a human" this package can supply on a caller's behalf.
1070
- */
1071
- readonly continuation?: ContinuationOptions;
1072
- /**
1073
- * Gap 4's failure classifier — see `ClassifyFailure`'s own doc.
1074
- * @default undefined — every `'failed'` run stays `resumable: false`, byte-identical to
1075
- * pre-gap-4 behavior. No default classifier exists: OD's own ~20-vendor-CLI text-matching
1076
- * failure classifier was deliberately never ported (see this module's own doc), so there is no
1077
- * generic "real" classification logic this package could supply on a caller's behalf.
1078
- */
1079
- readonly classifyFailure?: ClassifyFailure;
1080
- /**
1081
- * Gap 3, part 2's spawn-time `.mcp.json` injection — see {@link McpJsonInjectionOptions}'s own
1082
- * doc for the full design (why only `'claude-mcp-json'`-injection defs, why host-resolved).
1083
- * @default undefined — no `.mcp.json` is written and no filesystem access beyond what already
1084
- * happened (prompt-file staging) occurs on this path, byte-identical to pre-this-task behavior.
1085
- * Opt-in only, like `journal`/`continuation`/`classifyFailure`: there is no safe default
1086
- * `command`/`daemonUrl` this package could assume on a caller's behalf.
1087
- */
1088
- readonly mcpJsonInjection?: McpJsonInjectionOptions;
1089
- /**
1090
- * Finding 1 of SEC-assistant-env-isolation-2026-09-07's injectable filesystem seams — see
1091
- * {@link ClaudeConfigDirIsolationOptions}'s own doc. This bag itself is still NOT a gate (supplying
1092
- * it only lets a test observe/replace the real filesystem calls); whether staging happens AT ALL is
1093
- * now {@link claudeConfigDirIsolationEnabled}'s job — see that field's doc for why the two were
1094
- * split apart instead of overloading this one's presence as the switch.
1095
- * @default the real `fs.promises.mkdtemp`/`readFile`/`rm` — no real disk I/O for every def other
1096
- * than `claude`, matching this factory's "no real filesystem by default in tests" convention.
1097
- */
1098
- readonly claudeConfigDirIsolation?: ClaudeConfigDirIsolationOptions;
1099
- /**
1100
- * The actual on/off switch for Finding 1's `CLAUDE_CONFIG_DIR` isolation (see
1101
- * {@link prepareClaudeConfigDirIfNeeded}'s doc for the staging behavior this gates). Split out as
1102
- * its own boolean rather than reusing {@link claudeConfigDirIsolation}'s presence, because that bag
1103
- * is a test-seam-injection convention shared with every other `*IsolationOptions`/`*Seams` field in
1104
- * this file (see `mcpJsonInjection`'s own "NOT a gate" precedent) — overloading it here would mean a
1105
- * host that only wants to override `mkdtemp` for a test silently also flips production behavior.
1106
- *
1107
- * @default `false`. This DEFAULTS OFF, which reopens the leak Finding 1 closed (the spawned
1108
- * `claude` child again reads the operator's real `~/.claude` — skills, plugins, memory index, and
1109
- * whatever tool grant that directory carries) — **not a regression discovered later, a deliberate
1110
- * rollback landed the same day as the isolation fix itself.** Reason: on macOS the Keychain login
1111
- * `claude auth status` reports is keyed to `CLAUDE_CONFIG_DIR` (see {@link
1112
- * prepareClaudeConfigDirForRun}'s doc), and no caller of this factory was passing a credential of
1113
- * its own (`AgentExecutorRunInput.credentialEnv`) when Finding 1 landed unconditionally — so every
1114
- * isolated child ran unauthenticated and the assistant reported "Not logged in" on its default
1115
- * runtime. A host that provisions a real `ANTHROPIC_API_KEY`/`CLAUDE_CODE_OAUTH_TOKEN` via
1116
- * `credentialEnv` (both outrank Keychain login in Claude Code's own auth precedence, so an isolated,
1117
- * still-logged-in child needs no access to the operator's personal Keychain entry at all) should
1118
- * flip this back on — the mechanism itself is unchanged and was already proven live
1119
- * (2026-09-07); only the default changed.
1120
- */
1121
- readonly claudeConfigDirIsolationEnabled?: boolean;
1122
- /**
1123
- * Ceiling on how many bytes of a `'until-close'` def's stdout this driver will hold in memory
1124
- * before it stops accumulating and reports the shortfall — see
1125
- * {@link DEFAULT_BUFFERED_STDOUT_MAX_BYTES} for the threat this closes and why 8 MiB.
1126
- * @default {@link DEFAULT_BUFFERED_STDOUT_MAX_BYTES}
1127
- */
1128
- readonly bufferedStdoutMaxBytes?: number;
1129
- /**
1130
- * Host-owned system-prompt overlay — see `prompt-augmenter.ts`'s own doc for why this seam
1131
- * exists (product-specific discovery/behavior instructions that don't belong in the engine).
1132
- * When present, `systemOverlay()` is called once per `run()` and its result (if non-null) is
1133
- * threaded through to `buildArgs` as `RuntimeBuildOptions.systemPromptOverlay` — a def with no
1134
- * append-system-prompt mechanism ignores it.
1135
- * @default undefined — no overlay is computed and no def sees `systemPromptOverlay`,
1136
- * byte-identical to pre-this-option behavior.
1137
- */
1138
- readonly promptAugmenter?: PromptAugmenter;
1139
- }
1140
- /**
1141
- * Creates the `AgentExecutor` reference implementation: an in-process
1142
- * `RunLifecycle` driver over real (by default) `@jini-ai/agent-runtime`
1143
- * registry lookup, launch resolution, and stream parsing, plus a real
1144
- * `node:child_process.spawn`. Every collaborator is an injectable seam
1145
- * (matching this package's established convention — see
1146
- * `tool-executor.ts`/`run-lifecycle.ts`) so tests can drive a fake child
1147
- * process and a fake registry without touching the filesystem or spawning
1148
- * a real subprocess.
1149
- *
1150
- * @param options.lifecycle - The `RunLifecycle` this executor drives — its `start()` must already have been called for any `runId` passed to `run()`.
1151
- * @returns An `AgentExecutor` whose `run()` never bare-throws (see module doc's Invariant).
1152
- * @complexity `run()`'s own setup is O(1); steady-state cost is the chosen stream parser's.
1153
- * @overallScore 100/100
1154
- */
1155
- /** `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. */
1156
- type FailBeforeSpawn = (args: {
1157
- readonly runId: string;
1158
- readonly code: AgentExecutorErrorCode;
1159
- readonly message: string;
1160
- }) => Promise<never>;
1161
- interface ResolvedDefAndFormat {
1162
- readonly def: RuntimeAgentDef;
1163
- readonly streamFormat: SupportedStreamFormat;
1164
- }
1165
- /** Phase 1: registry lookup + `assessAgentExecutorCompatibility` guard. */
1166
- export declare function resolveDefAndStreamFormat({ input, deps }: {
1167
- readonly input: Pick<AgentExecutorRunInput, 'runId' | 'agentId'>;
1168
- readonly deps: {
1169
- readonly getAgentDef: typeof getAgentDef;
1170
- readonly failBeforeSpawn: FailBeforeSpawn;
1171
- };
1172
- }): Promise<ResolvedDefAndFormat>;
1173
- /** Phase 2: image-prompt-delivery augmentation + argv-budget guard for argv-bound defs. */
1174
- export declare function resolveImageDeliveryAndArgvBudget({ input, deps }: {
1175
- readonly input: {
1176
- readonly runId: string;
1177
- readonly def: RuntimeAgentDef;
1178
- readonly prompt: string;
1179
- readonly imagePaths: readonly string[] | undefined;
1180
- readonly extraAllowedDirs: readonly string[] | undefined;
1181
- };
1182
- readonly deps: {
1183
- readonly failBeforeSpawn: FailBeforeSpawn;
1184
- };
1185
- }): Promise<ImagePromptDelivery>;
1186
- /**
1187
- * Phase 3a: the subprocess environment this run's launch resolution and spawn should use — the
1188
- * caller-supplied escape hatch verbatim, or the deny-by-default `BASELINE_AGENT_ENV_KEYS` allowlist.
1189
- * Pure.
1190
- */
1191
- export declare function resolveRunEnv({ input, hostEnv }: {
1192
- readonly input: Pick<AgentExecutorRunInput, 'env' | 'credentialEnv'>;
1193
- readonly hostEnv: NodeJS.ProcessEnv;
1194
- }): Record<string, string>;
1195
- /** A resolved launch whose `launchPath` is confirmed non-null — see {@link resolveLaunch}. */
1196
- type ConfirmedAgentLaunchResolution = AgentLaunchResolution & {
1197
- readonly launchPath: string;
1198
- };
1199
- /** Phase 3b: launch-path resolution + binary-not-resolved guard. */
1200
- export declare function resolveLaunch({ input, deps }: {
1201
- readonly input: {
1202
- readonly runId: string;
1203
- readonly def: RuntimeAgentDef;
1204
- readonly resolvedEnv: Record<string, string>;
1205
- };
1206
- readonly deps: {
1207
- readonly resolveAgentLaunch: typeof resolveAgentLaunch;
1208
- readonly failBeforeSpawn: FailBeforeSpawn;
1209
- };
1210
- }): Promise<ConfirmedAgentLaunchResolution>;
1211
- /** Phase 4a: stage a `promptViaFile` def's prompt to a temp file (a no-op for every other def). */
1212
- export declare function stagePromptFile({ input, deps }: {
1213
- readonly input: {
1214
- readonly runId: string;
1215
- readonly def: RuntimeAgentDef;
1216
- readonly prompt: string;
1217
- };
1218
- readonly deps: {
1219
- readonly preparePromptFileForAgent: typeof preparePromptFileForAgent;
1220
- readonly failBeforeSpawn: FailBeforeSpawn;
1221
- };
1222
- }): Promise<PreparedPromptFile | null>;
1223
- /** Phase 4b: stage a `needsAgentLogFile` def's diagnostic-log path (a no-op for every other def). */
1224
- export declare function stageLogFile({ input, deps }: {
1225
- readonly input: {
1226
- readonly runId: string;
1227
- readonly def: RuntimeAgentDef;
1228
- readonly preparedPromptFile: PreparedPromptFile | null;
1229
- };
1230
- readonly deps: {
1231
- readonly prepareAgentLogFile: typeof prepareAgentLogFile;
1232
- readonly failBeforeSpawn: FailBeforeSpawn;
1233
- };
1234
- }): Promise<PreparedAgentLogFile | null>;
1235
- /** Phase 5: resolves this run's MCP bridge delivery (credential resolution + {@link buildMcpBridgeDelivery}). */
1236
- export declare function resolveMcpBridgeForRun({ input, deps }: {
1237
- readonly input: {
1238
- readonly runId: string;
1239
- readonly cwd: string;
1240
- readonly def: RuntimeAgentDef;
1241
- };
1242
- readonly deps: {
1243
- readonly mcpJsonInjection: McpJsonInjectionOptions | undefined;
1244
- readonly cleanupStagedFiles: () => Promise<void>;
1245
- readonly failBeforeSpawn: FailBeforeSpawn;
1246
- };
1247
- }): Promise<McpBridgeDelivery | null>;
1248
- /**
1249
- * Phase 6a/10c: the subprocess environment every env-riding mechanism uses — mechanism 3+4
1250
- * (`'opencode-env-content'`/`'mimo-env-content'`, merged into whatever the host already set there,
1251
- * never a CLI argument: the config embeds `JINI_DAEMON_TOKEN`, and process arguments are readable
1252
- * by any other local user through `ps`), mechanism 5 (`'codex-toml'`, `CODEX_HOME` relocation),
1253
- * mechanism 6 (`'env-passthrough'`, the bridge entry's flat env vars set directly with no carrier
1254
- * document — see {@link McpBridgeDelivery}'s own doc), a
1255
- * `systemPromptDelivery: 'env-var'` def's overlay (`reasonix`'s `REASONIX_ACP_SYSTEM_APPEND` today
1256
- * — see `resolveSystemPromptOverlayDelivery`'s own doc), and a `'config-instructions-file'` def's
1257
- * staged overlay file (`opencode` today — see {@link mergeEnvContentInstructions}'s own doc). Pure
1258
- * — `codexHomeDir` and `stagedInstructionsFile` arrive already staged by
1259
- * {@link prepareCodexHomeIfNeeded} and {@link prepareSystemPromptOverlayFileIfNeeded} respectively,
1260
- * the only parts of this mechanism that are NOT pure (real `mkdtemp`/`writeFile` calls).
1261
- * @param spawnEnv - The env every other spawn-time step (launch-path resolution, `applyAgentLaunchEnv`) already computed.
1262
- * @param mcpBridge - This run's resolved bridge delivery, or `null` for an unconfigured host / no-strategy def.
1263
- * @param codexHomeDir - The staged scratch `CODEX_HOME` path for a `'codex-toml'` def, or `undefined` for every other run (including a `'codex-toml'` def when `mcpJsonInjection` was never configured — see `prepareCodexHomeIfNeeded`'s own gate).
1264
- * @param systemPromptEnvOverrides - `resolveSystemPromptOverlayDelivery`'s `envOverrides` — `{}` (default) for every def but an `'env-var'`-strategy one with an overlay present, in which case it carries that one var. Applied after `codexHomeDir`, so it can never be shadowed by it — the two never share a key (`CODEX_HOME` vs. e.g. `REASONIX_ACP_SYSTEM_APPEND`), so the ordering is a documentation choice, not a correctness one.
1265
- * @param stagedInstructionsFile - `varName` (from the def's own `systemPromptDelivery` declaration) and the staged overlay file's `path`, or `undefined` for every def but a `'config-instructions-file'` one with an overlay present. Merged into `varName`'s value AFTER the `mcp` merge above (reading `envContentApplied`, not the original `spawnEnv`, for that same key) so both a `mcp` entry and an `instructions` entry from the two mechanisms survive together in one document — confirmed live this coexistence is safe (see {@link mergeEnvContentInstructions}'s doc).
1266
- * @complexity O(1) plus `mergeEnvContentMcpConfig`'s and `mergeEnvContentInstructions`'s own `JSON.parse`/`JSON.stringify` cost.
1267
- * @overallScore 100/100
1268
- */
1269
- export declare function computeChildEnv({ spawnEnv, mcpBridge }: {
1270
- readonly spawnEnv: NodeJS.ProcessEnv;
1271
- readonly mcpBridge: McpBridgeDelivery | null;
1272
- }, { codexHomeDir, systemPromptEnvOverrides, stagedInstructionsFile, claudeConfigDir }?: {
1273
- readonly codexHomeDir?: string;
1274
- readonly systemPromptEnvOverrides?: Readonly<Record<string, string>>;
1275
- readonly stagedInstructionsFile?: {
1276
- readonly varName: string;
1277
- readonly path: string;
1278
- };
1279
- readonly claudeConfigDir?: string;
1280
- }): NodeJS.ProcessEnv;
1281
- /**
1282
- * Phase 6b: the `RuntimeContext` `buildArgs` receives — `undefined` unless a file, bridge path, or
1283
- * session id was staged. Pure.
1284
- *
1285
- * `resumeSessionId`/`newSessionId` round-trip a prior run's `RunEndPayload.sessionRef` (see
1286
- * `@jini-ai/protocol`'s doc on that field) back into this run's `RuntimeContext`, letting a
1287
- * `resumesSessionViaCli` def (e.g. claude) continue its own CLI session across turns instead of
1288
- * spawning cold every time. Either one alone must still produce a context — a run supplying ONLY a
1289
- * session id, with no prompt/log file staged and no claude-mcp-json bridge, is exactly the common
1290
- * case for a resumed turn.
1291
- */
1292
- export declare function computeRuntimeContext({ preparedPromptFile, preparedLogFile, mcpBridge }: {
1293
- readonly preparedPromptFile: PreparedPromptFile | null;
1294
- readonly preparedLogFile: PreparedAgentLogFile | null;
1295
- readonly mcpBridge: McpBridgeDelivery | null;
1296
- }, { resumeSessionId, newSessionId }?: {
1297
- readonly resumeSessionId?: string | null;
1298
- readonly newSessionId?: string;
1299
- }): RuntimeContext | undefined;
1300
- /** Phase 9a: the def's `buildArgs` 4th argument. Historically omitted when every override was absent;
1301
- * that let defs silently enable auto-approval. Always supply the validated restricted default now. Pure. */
1302
- export declare function buildAgentBuildArgsOptions({ input, systemPromptOverlay }: {
1303
- readonly input: Pick<AgentExecutorRunInput, 'model' | 'reasoning' | 'permissionMode' | 'disallowedTools' | 'allowedTools' | 'settingSources' | 'settings'>;
1304
- readonly systemPromptOverlay: string | null | undefined;
1305
- }): RuntimeBuildOptions | undefined;
1306
- /**
1307
- * {@link resolveSystemPromptOverlayDelivery}'s decision, threaded out of that one resolver to every
1308
- * channel that can actually carry the overlay to the CLI.
1309
- *
1310
- * **Why `promptPrefix` exists alongside `prompt`.** `prompt` is the composed prompt with the
1311
- * fallback prefix already applied, and is what `buildArgs`, the staged prompt file, and a
1312
- * `promptViaStdin` def's stdin all receive. But the two RPC transports (`runAcpDispatch`,
1313
- * `runPiRpcDispatch`) deliberately send `run()`'s *raw* `input.prompt`, not the image-delivery
1314
- * rewrite this resolver was handed — those defs deliver images through their own native protocol
1315
- * and must never also get `'prompt-path'`'s appended paths (see `resolveImageDeliveryAndArgvBudget`'s
1316
- * call site in `run()` for that hazard). Handing them `prompt` would silently couple them to the
1317
- * image rewrite; handing them `promptPrefix + input.prompt` applies exactly this resolver's overlay
1318
- * decision to exactly the prompt text they were already sending.
1319
- *
1320
- * **The no-double-delivery invariant.** `promptPrefix` is a non-empty string on exactly one path —
1321
- * the universal fallback, the only strategy that carries the overlay *in the prompt text*. Every
1322
- * declared strategy (`'append-flag'`, `'env-var'`, `'config-instructions-file'`) and every
1323
- * suppressed case (no overlay, continuing a session) returns `''`, so a def that receives the
1324
- * overlay through argv, an env var, or a staged config file never also receives it inline. A
1325
- * transport therefore does not need to know which strategy applies: applying `promptPrefix`
1326
- * unconditionally is correct precisely because the resolver already zeroed it where it must not
1327
- * apply.
1328
- */
1329
- export interface SystemPromptOverlayDelivery {
1330
- /**
1331
- * The text to prepend to whatever prompt a transport is about to send — `''` for every def whose
1332
- * overlay rides another channel, so it is always safe to apply unconditionally. See this
1333
- * interface's own doc for why callers with their own prompt text use this rather than `prompt`.
1334
- */
1335
- readonly promptPrefix: string;
1336
- /** `promptPrefix` already applied to the prompt this resolver was handed. */
1337
- readonly prompt: string;
1338
- /** Extra argv to append to `buildArgs`' own result — non-empty only for `'append-flag'`. */
1339
- readonly extraArgs: readonly string[];
1340
- /** Env vars to merge into the spawn env — non-empty only for `'env-var'`. */
1341
- readonly envOverrides: Readonly<Record<string, string>>;
1342
- }
1343
- /**
1344
- * **The single dispatch point from a computed system-prompt overlay to its delivery mechanism** —
1345
- * see `RuntimeAgentDef.systemPromptDelivery`'s own doc for the declared shape. Pure and
1346
- * synchronous, mirroring {@link buildMcpBridgeDelivery}'s "keyed off the declared strategy, never
1347
- * off the def's id" contract: a def earns overlay delivery by declaring a strategy, not by being
1348
- * named in this file. That is what makes every def with no declaration work via the fallback
1349
- * without any of their own files being touched.
1350
- *
1351
- * The fallback (no declared strategy — every def but `claude` today) prefixes the overlay directly
1352
- * onto the composed prompt text, clearly delimited from the user's own request. It is gated on
1353
- * session state, not merely on whether an overlay exists: a def that carries its own conversation
1354
- * memory across spawns (`resumesSessionViaCli` / `resumesSessionViaAcpLoad`) persists whatever its
1355
- * session-creating turn sends it — see `RuntimeContext.resumeSessionId`'s own doc: its presence on
1356
- * a run means "continue a prior session", not "start one". Prefixing on every later turn of that
1357
- * same session would therefore bake the overlay into the CLI's own stored history again and again,
1358
- * compounding without bound turn over turn. So the fallback prefixes only when there is no resume
1359
- * target yet (the session's own first turn, or a def with no session memory at all, which never
1360
- * replays anything back at the CLI and so gets it on every turn).
1361
- *
1362
- * `'append-flag'` and `'env-var'` defs are the opposite case: the flag/env var is a fresh,
1363
- * un-stored per-spawn directive — never part of what a resumed session replays — so it is set on
1364
- * every turn unconditionally, exactly `claude`'s pre-existing (now-centralized) behavior before
1365
- * this function existed.
1366
- *
1367
- * @param input.defId - Looks up this def's probed capabilities for an `'append-flag'` strategy's
1368
- * `capabilityKey`. Otherwise unused — the dispatch itself is keyed off `systemPromptDelivery`, per
1369
- * this function's own doc above, never off the id.
1370
- * @param input.systemPromptDelivery - The def's declared strategy, or `undefined` for the fallback.
1371
- * @param input.resumesSessionViaCli - The def's own flag (see `RuntimeAgentDef`'s doc).
1372
- * @param input.resumesSessionViaAcpLoad - The def's own flag (see `RuntimeAgentDef`'s doc).
1373
- * @param input.overlay - The computed `PromptAugmenter.systemOverlay()` result. `null`/`undefined`/
1374
- * empty short-circuits to "no delivery" — byte-identical to no `PromptAugmenter` configured at all.
1375
- * @param input.prompt - The composed prompt `buildArgs` would otherwise receive verbatim.
1376
- * @param input.resumeSessionId - This run's `RuntimeContext.resumeSessionId`; presence means an
1377
- * existing session is being continued, not created.
1378
- * @returns A {@link SystemPromptOverlayDelivery}: the prefix this run's prompt text must carry
1379
- * (`''` unless the fallback applies), that prefix already applied to `input.prompt`, any extra argv
1380
- * to append to whatever `buildArgs` itself returns, and any env var overrides to merge into the
1381
- * spawn env (`{}` for every strategy but `'env-var'`). Every one of `run()`'s four prompt
1382
- * transports consumes this same result — see the interface's own no-double-delivery note.
1383
- * @complexity O(n) in the overlay/prompt lengths — string concatenation only, no I/O.
1384
- * @overallScore 100/100
1385
- */
1386
- export declare function resolveSystemPromptOverlayDelivery(input: {
1387
- readonly defId: string;
1388
- readonly systemPromptDelivery: RuntimeAgentDef['systemPromptDelivery'];
1389
- readonly resumesSessionViaCli: boolean | undefined;
1390
- readonly resumesSessionViaAcpLoad: boolean | undefined;
1391
- readonly overlay: string | null | undefined;
1392
- readonly prompt: string;
1393
- readonly resumeSessionId: string | null | undefined;
1394
- }): SystemPromptOverlayDelivery;
1395
- /**
1396
- * Phase 9b: calls the def's `buildArgs`, releasing staged resources and failing the run on a throw.
1397
- *
1398
- * @param input.overlayDelivery - This run's already-resolved overlay decision, computed once in
1399
- * `run()` rather than here. It is resolved upstream because `buildArgs` is only ONE of the four
1400
- * channels that can carry the prompt: the staged prompt file is written *before* this function
1401
- * runs, and stdin/ACP/pi-rpc send theirs *after* spawn. A decision made inside this function could
1402
- * therefore only ever reach the 7 defs whose `buildArgs` reads its first argument at all — the
1403
- * other 17 declare it `_prompt` and discard it, which is exactly how the overlay used to go missing.
1404
- */
1405
- export declare function buildRunArgs({ input, deps }: {
1406
- readonly input: {
1407
- readonly runId: string;
1408
- readonly def: RuntimeAgentDef;
1409
- readonly imageDelivery: ImagePromptDelivery;
1410
- readonly imagePaths: readonly string[] | undefined;
1411
- readonly runInput: Pick<AgentExecutorRunInput, 'model' | 'reasoning' | 'permissionMode'>;
1412
- readonly systemPromptOverlay: string | null | undefined;
1413
- readonly overlayDelivery: SystemPromptOverlayDelivery;
1414
- readonly runtimeContext: RuntimeContext | undefined;
1415
- };
1416
- readonly deps: {
1417
- readonly releaseStagedResources: () => Promise<void>;
1418
- readonly failBeforeSpawn: FailBeforeSpawn;
1419
- };
1420
- }): Promise<{
1421
- readonly args: string[];
1422
- readonly envOverrides: Readonly<Record<string, string>>;
1423
- }>;
1424
- /** Phase 10: mechanism 1 of 5's one effect — stages this run's own `.mcp.json`, returning the path `cleanupStagedFiles` should later remove (`undefined` for every other mechanism / unconfigured host). */
1425
- export declare function writeMcpJsonIfNeeded({ input, deps }: {
1426
- readonly input: {
1427
- readonly runId: string;
1428
- readonly cwd: string;
1429
- readonly def: RuntimeAgentDef;
1430
- readonly mcpBridge: McpBridgeDelivery | null;
1431
- };
1432
- readonly deps: {
1433
- readonly mcpJsonInjection: McpJsonInjectionOptions | undefined;
1434
- readonly releaseStagedResources: () => Promise<void>;
1435
- readonly failBeforeSpawn: FailBeforeSpawn;
1436
- };
1437
- }): Promise<string | undefined>;
1438
- /** {@link prepareSystemPromptOverlayFileIfNeeded}'s result. */
1439
- export type PreparedSystemPromptOverlayFile = {
1440
- /** Absolute path to the staged file, ready to merge into a `config-instructions-file` def's `instructions` array. */
1441
- readonly path: string;
1442
- /** Recursively removes the staged directory. Safe to call more than once. */
1443
- readonly cleanup: () => Promise<void>;
1444
- };
1445
- /**
1446
- * Phase 10b1: `systemPromptDelivery: { strategy: 'config-instructions-file' }`'s one effect —
1447
- * stages the computed overlay to a fresh, run-scoped temp file, so `computeChildEnv` has a real
1448
- * path to merge into that def's `instructions` config array (see
1449
- * {@link mergeEnvContentInstructions}'s own doc for the live verification this mechanism rests on).
1450
- * `null` for every other strategy, an unset `systemPromptDelivery`, or no overlay present at all —
1451
- * byte-identical to before this mechanism existed, matching {@link writeMcpJsonIfNeeded}'s and
1452
- * {@link prepareCodexHomeIfNeeded}'s identical no-op-when-inapplicable gate.
1453
- *
1454
- * `opencode`'s `instructions` field only accepts a file path or a remote URL — confirmed live
1455
- * (2026-09-01): a literal instruction string in the array is silently ignored (no error, just never
1456
- * honored), so an inline-text shortcut is not available and this staging step is load-bearing, not
1457
- * a defensive extra.
1458
- * @param input.def - Only used for its `id`, in the failure message, and its `systemPromptDelivery` declaration.
1459
- * @param input.overlay - The computed `PromptAugmenter.systemOverlay()` result for this run.
1460
- * @complexity O(1) plus one directory creation and one file write.
1461
- * @overallScore 100/100
1462
- */
1463
- export declare function prepareSystemPromptOverlayFileIfNeeded({ input, deps }: {
1464
- readonly input: {
1465
- readonly runId: string;
1466
- readonly def: RuntimeAgentDef;
1467
- readonly overlay: string | null | undefined;
1468
- };
1469
- readonly deps: {
1470
- readonly releaseStagedResources: () => Promise<void>;
1471
- readonly failBeforeSpawn: FailBeforeSpawn;
1472
- };
1473
- }): Promise<PreparedSystemPromptOverlayFile | null>;
1474
- /**
1475
- * Phase 10b: mechanism 5 of 5's one effect — stages this run's scratch `CODEX_HOME` directory,
1476
- * returning the prepared handle `cleanupStagedFiles` should later release (`null` for every other
1477
- * mechanism, or for an unconfigured host — matching {@link writeMcpJsonIfNeeded}'s identical gate).
1478
- * @param input.def - Only used for its `id`, in the failure message.
1479
- * @param input.mcpBridge - This run's resolved bridge delivery — a no-op unless its `kind` is `'codex-toml'`.
1480
- * @param deps.hostEnv - The daemon's own environment, threaded through to {@link resolveSourceCodexHomeDir} rather than read from a module-level `process.env` so this phase stays testable with an injected env.
1481
- * @complexity O(1) plus {@link prepareCodexHomeForRun}'s own cost.
1482
- * @overallScore 100/100
1483
- */
1484
- export declare function prepareCodexHomeIfNeeded({ input, deps }: {
1485
- readonly input: {
1486
- readonly runId: string;
1487
- readonly def: RuntimeAgentDef;
1488
- readonly mcpBridge: McpBridgeDelivery | null;
1489
- };
1490
- readonly deps: {
1491
- readonly mcpJsonInjection: McpJsonInjectionOptions | undefined;
1492
- readonly hostEnv: NodeJS.ProcessEnv;
1493
- readonly releaseStagedResources: () => Promise<void>;
1494
- readonly failBeforeSpawn: FailBeforeSpawn;
1495
- };
1496
- }): Promise<PreparedCodexHome | null>;
1497
- /**
1498
- * Finding 1 of SEC-assistant-env-isolation-2026-09-07's one effect — stages this run's scratch
1499
- * `CLAUDE_CONFIG_DIR` directory, returning the prepared handle `cleanupStagedFiles` should later
1500
- * release (`null` for every def other than `claude` — see {@link prepareClaudeConfigDirForRun}'s
1501
- * own doc for why this is unconditional for `claude` runs, unlike {@link prepareCodexHomeIfNeeded}'s
1502
- * gate on a host-configured MCP bridge strategy).
1503
- *
1504
- * Gated on `def.id === 'claude'` directly rather than on `externalMcpInjection === 'claude-mcp-json'`
1505
- * (which `codebuddy` also declares): isolating the operator's personal `~/.claude` is specific to
1506
- * the real `claude` CLI's own config resolution, not to every def that happens to share its `.mcp.
1507
- * json` delivery shape. Matches the existing `USER`-for-claude-login special case already singled
1508
- * out by id in `BASELINE_AGENT_ENV_KEYS`'s own doc, a few hundred lines above.
1509
- *
1510
- * ALSO gated on `deps.enabled` (`CreateAgentExecutorOptions.claudeConfigDirIsolationEnabled`, default
1511
- * `false`) since this task's own fix — see that field's doc for why it defaults off (Keychain login
1512
- * is `CLAUDE_CONFIG_DIR`-keyed and no caller was supplying a credential when this was unconditional).
1513
- * `def.id === 'claude'` is still checked first and independently: a host that flips this flag on
1514
- * should not suddenly stage a directory for `codebuddy` or any other def.
1515
- * @param input.def - Used for both the `id` gate and the failure message.
1516
- * @param deps.enabled - The isolation on/off switch — see this function's own doc above.
1517
- * @param deps.hostEnv - The daemon's own environment, threaded through to {@link resolveSourceClaudeConfigDir} rather than read from a module-level `process.env`, matching {@link prepareCodexHomeIfNeeded}'s identical testability reasoning.
1518
- * @complexity O(1) plus {@link prepareClaudeConfigDirForRun}'s own cost.
1519
- */
1520
- export declare function prepareClaudeConfigDirIfNeeded({ input, deps }: {
1521
- readonly input: {
1522
- readonly runId: string;
1523
- readonly def: RuntimeAgentDef;
1524
- };
1525
- readonly deps: {
1526
- readonly enabled: boolean;
1527
- readonly claudeConfigDirIsolation: ClaudeConfigDirIsolationOptions | undefined;
1528
- readonly hostEnv: NodeJS.ProcessEnv;
1529
- readonly releaseStagedResources: () => Promise<void>;
1530
- readonly failBeforeSpawn: FailBeforeSpawn;
1531
- };
1532
- }): Promise<PreparedClaudeConfigDir | null>;
1533
- /** Phase 11: post-`buildArgs` guard for argv-bound defs whose resolved binary is a Windows shim/.exe — a no-op off-Windows and for non-argv-bound defs. */
1534
- export declare function guardWindowsCommandLineBudget({ input, deps }: {
1535
- readonly input: {
1536
- readonly runId: string;
1537
- readonly def: RuntimeAgentDef;
1538
- readonly launchPath: string;
1539
- readonly args: readonly string[];
1540
- };
1541
- readonly deps: {
1542
- readonly releaseStagedResources: () => Promise<void>;
1543
- readonly failBeforeSpawn: FailBeforeSpawn;
1544
- };
1545
- }): Promise<void>;
1546
- /** {@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. */
1547
- export type SpawnAgentChildProcessResult = {
1548
- readonly kind: 'ok';
1549
- readonly child: ChildProcess;
1550
- } | {
1551
- readonly kind: 'error';
1552
- readonly error: unknown;
1553
- };
1554
- /**
1555
- * Phase 12: the real `node:child_process.spawn` call.
1556
- *
1557
- * **Deliberately synchronous, unlike every other phase in this file.** A spawned child can emit
1558
- * `'error'` on the very next microtask tick (Node schedules it eagerly on some failure modes, and a
1559
- * test harness simulating "the child emits 'error' before 'spawn'" does so explicitly via
1560
- * `queueMicrotask`). `run()` must register its `'error'` listeners (`wireChildLifecycle`'s safety net,
1561
- * then `waitForSpawnOrError`'s `child.once('error', reject)`) in the *same synchronous turn* as this
1562
- * spawn call — Node's `EventEmitter` throws synchronously when `'error'` fires with zero listeners
1563
- * attached. Wrapping this call in an `async function` and `await`ing it (as every other phase here
1564
- * does) would insert a microtask tick between spawn and listener registration, occasionally losing
1565
- * that race — confirmed by a real test failure during this refactor (an uncaught `EventEmitter`
1566
- * `'error'` exception) before this function was changed back to a plain, unawaited call returning a
1567
- * result object instead of throwing/rejecting.
1568
- * @returns `{kind:'ok', child}` on success, `{kind:'error', error}` on a synchronous throw from `spawn`
1569
- * — `run()` itself is responsible for cleanup and `failBeforeSpawn` on the error variant, both of
1570
- * which are safe to make asynchronous since no child (and hence no listener race) exists yet.
1571
- * @complexity O(1) plus `spawn`'s own cost.
1572
- */
1573
- export declare function spawnAgentChildProcess({ input, deps }: {
1574
- readonly input: {
1575
- readonly cwd: string;
1576
- readonly childEnv: NodeJS.ProcessEnv;
1577
- readonly invocation: ReturnType<typeof createCommandInvocation>;
1578
- };
1579
- readonly deps: {
1580
- readonly spawn: typeof nodeSpawn;
1581
- };
1582
- }): SpawnAgentChildProcessResult;
1583
- /** Named predicate replacing an inline `streamFormat === 'acp-json-rpc' || streamFormat === 'pi-rpc'` check — the two formats that own their own prompt/event protocol and skip `wireChildLifecycle`. */
1584
- export declare function isStdinDrivenFormat(args: {
1585
- readonly streamFormat: SupportedStreamFormat;
1586
- }): args is {
1587
- readonly streamFormat: ChildDrivenStreamFormat;
1588
- };
1589
- /** Phase 13: awaits spawn confirmation, routing a failure through the same `failBeforeSpawn` shape every earlier guard uses. */
1590
- export declare function confirmChildSpawned({ input, deps }: {
1591
- readonly input: {
1592
- readonly runId: string;
1593
- readonly def: RuntimeAgentDef;
1594
- readonly child: ChildProcess;
1595
- };
1596
- readonly deps: {
1597
- readonly releaseStagedResources: () => Promise<void>;
1598
- readonly failBeforeSpawn: FailBeforeSpawn;
1599
- };
1600
- }): Promise<void>;
1601
- /** Phase 14: starts a `runtimeLock` def's handoff watcher once a live process exists to consume the locked side effect — a no-op when the def declared no `waitForHandoff`. Deliberately not awaited; see `RuntimeLockHold.waitForHandoff`'s own doc. */
1602
- export declare function armHandoffWatcher({ runtimeLockHold, handoffInput, release }: {
1603
- readonly runtimeLockHold: RuntimeLockHold | undefined;
1604
- readonly handoffInput: {
1605
- readonly logFilePath: string | undefined;
1606
- readonly model: string | undefined;
1607
- readonly processExited: AbortSignal;
1608
- };
1609
- readonly release: () => void;
1610
- }): void;
1611
- interface RunAcpDispatchInput {
1612
- readonly runId: string;
1613
- readonly agentId: string;
1614
- readonly child: ChildProcess;
1615
- readonly prompt: string;
1616
- readonly cwd: string;
1617
- readonly model: string | undefined;
1618
- readonly imagePaths: readonly string[];
1619
- readonly envFormat: 'array' | 'map' | undefined;
1620
- readonly mcpBridge: McpBridgeDelivery | null;
1621
- }
1622
- interface RunAcpDispatchDeps extends TerminateChildTreeDeps {
1623
- readonly lifecycle: RunLifecycle;
1624
- readonly attachAcpSession: typeof attachAcpSession;
1625
- readonly onPermissionRequest: AcpPermissionHandler | undefined;
1626
- readonly onCleanupFailure: (context: AgentCleanupFailureContext) => void;
1627
- readonly cleanupStagedFiles: () => Promise<void>;
1628
- readonly journal: RunByteJournal | undefined;
1629
- readonly classifyFailure: ClassifyFailure | undefined;
1630
- readonly releaseStagedResources: () => Promise<void>;
1631
- readonly failBeforeSpawn: FailBeforeSpawn;
1632
- }
1633
- /** Phase 15 (ACP branch): attaches the ACP session, escalating process-tree teardown and failing the run through `failBeforeSpawn` on an attach-time throw. */
1634
- export declare function runAcpDispatch({ input, deps }: {
1635
- readonly input: RunAcpDispatchInput;
1636
- readonly deps: RunAcpDispatchDeps;
1637
- }): Promise<void>;
1638
- interface RunPiRpcDispatchInput {
1639
- readonly runId: string;
1640
- readonly agentId: string;
1641
- readonly child: ChildProcess;
1642
- readonly prompt: string;
1643
- readonly cwd: string;
1644
- readonly model: string | undefined;
1645
- readonly imagePaths: readonly string[];
1646
- readonly uploadRoot: string | undefined;
1647
- }
1648
- interface RunPiRpcDispatchDeps extends TerminateChildTreeDeps {
1649
- readonly lifecycle: RunLifecycle;
1650
- readonly attachPiRpcSession: typeof attachPiRpcSession;
1651
- readonly onCleanupFailure: (context: AgentCleanupFailureContext) => void;
1652
- readonly cleanupStagedFiles: () => Promise<void>;
1653
- readonly journal: RunByteJournal | undefined;
1654
- readonly classifyFailure: ClassifyFailure | undefined;
1655
- readonly releaseStagedResources: () => Promise<void>;
1656
- readonly failBeforeSpawn: FailBeforeSpawn;
1657
- }
1658
- /** Phase 15 (pi-rpc branch): same discipline as {@link runAcpDispatch}, for the one `'pi-rpc'` def. */
1659
- export declare function runPiRpcDispatch({ input, deps }: {
1660
- readonly input: RunPiRpcDispatchInput;
1661
- readonly deps: RunPiRpcDispatchDeps;
1662
- }): Promise<void>;
1663
- export declare function createAgentExecutor(requiredArgs: Pick<CreateAgentExecutorOptions, "lifecycle">, optionalArgs?: Pick<CreateAgentExecutorOptions, "getAgentDef" | "resolveAgentLaunch" | "ensureAgentCapabilities" | "applyAgentLaunchEnv" | "createCommandInvocation" | "spawn" | "attachAcpSession" | "acpPermissionHandler" | "attachPiRpcSession" | "preparePromptFileForAgent" | "prepareAgentLogFile" | "listProcessSnapshots" | "collectProcessTreePids" | "stopProcesses" | "onCleanupFailure" | "journal" | "continuation" | "classifyFailure" | "mcpJsonInjection" | "claudeConfigDirIsolation" | "claudeConfigDirIsolationEnabled" | "bufferedStdoutMaxBytes" | "promptAugmenter">): AgentExecutor;
1664
- export {};
2
+ * Compatibility entry for the agent executor's original module path.
3
+ * The split implementation owns subprocess launch, credential delivery,
4
+ * cancellation, and run events. Named re-exports preserve the existing API
5
+ * and AgentExecutorError identity across this entry and the package barrel;
6
+ * wrappers or a second implementation would let tested and shipped behavior drift.
7
+ * See agent-executor/index.ts for the driver contracts and invariants.
8
+ */
9
+ export { type SupportedStreamFormat, isSupportedStreamFormat, type AgentExecutorCompatibility, assessAgentExecutorCompatibility, isAgentExecutorSupported, type AgentRuntimeEventTranslation, extractUsageTokens, translateStatusEvent, translateToolResultEvent, translateErrorEvent, translateTurnEndEvent, translateAgentRuntimeEvent, type AgentExecutorErrorCode, AgentExecutorError, type AgentExecutorRunInput, type AgentExecutor, type CollectProcessTreePidsPort, type StopProcessesPort, type AgentCleanupFailurePhase, type AgentCleanupFailureContext, type ContinuationOptions, type McpJsonInjectionOptions, MCP_BRIDGE_UNAVAILABLE, unavailableJiniBridgeStatus, buildMcpJsonServerEntry, mergeMcpJsonContent, buildAcpMcpBridgeServers, mergeEnvContentMcpConfig, mergeEnvContentInstructions, buildCodexMcpServerToml, buildCodexHomeConfigToml, resolveSourceCodexHomeDir, type McpBridgeDelivery, buildMcpBridgeDelivery, type PreparedCodexHome, type PreparedClaudeConfigDir, type ClaudeConfigDirSeams, type ClaudeConfigDirIsolationOptions, resolveSourceClaudeConfigDir, type FailureClassificationContext, type ClassifyFailure, DEFAULT_BUFFERED_STDOUT_MAX_BYTES, applyAgentTranslationSideEffects, type CreateAgentExecutorOptions, resolveDefAndStreamFormat, resolveImageDeliveryAndArgvBudget, resolveRunEnv, resolveLaunch, stagePromptFile, stageLogFile, resolveMcpBridgeForRun, computeChildEnv, computeRuntimeContext, buildAgentBuildArgsOptions, type SystemPromptOverlayDelivery, resolveSystemPromptOverlayDelivery, buildRunArgs, writeMcpJsonIfNeeded, type PreparedSystemPromptOverlayFile, prepareSystemPromptOverlayFileIfNeeded, prepareCodexHomeIfNeeded, prepareClaudeConfigDirIfNeeded, guardWindowsCommandLineBudget, type SpawnAgentChildProcessResult, spawnAgentChildProcess, isStdinDrivenFormat, confirmChildSpawned, armHandoffWatcher, runAcpDispatch, runPiRpcDispatch, createAgentExecutor, } from './agent-executor/index.js';
1665
10
  //# sourceMappingURL=agent-executor.d.ts.map