@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.
- package/CHANGELOG.md +11 -0
- package/dist/agent-executor/contracts.d.ts +26 -20
- package/dist/agent-executor/contracts.js +0 -9
- package/dist/agent-executor/event-translation.d.ts +2 -2
- package/dist/agent-executor/event-translation.js +1 -2
- package/dist/agent-executor/events.d.ts +9 -4
- package/dist/agent-executor/events.js +29 -7
- package/dist/agent-executor/handoff.d.ts +1 -1
- package/dist/agent-executor/handoff.js +0 -1
- package/dist/agent-executor/launch.d.ts +1 -1
- package/dist/agent-executor/launch.js +24 -1
- package/dist/agent-executor/mcp-bridge.d.ts +2 -2
- package/dist/agent-executor/mcp-bridge.js +0 -2
- package/dist/agent-executor/model-receipts.d.ts +6 -0
- package/dist/agent-executor/model-receipts.js +26 -0
- package/dist/agent-executor.d.ts +8 -1663
- package/dist/agent-executor.js +8 -3005
- package/dist/attachment-content.d.ts +45 -0
- package/dist/attachment-content.js +40 -0
- package/dist/core/read-only-tools.d.ts +130 -0
- package/dist/core/read-only-tools.js +115 -0
- package/dist/core/tool-audit.d.ts +131 -0
- package/dist/core/tool-audit.js +113 -0
- package/dist/http/active-context.d.ts +2 -2
- package/dist/http/agents.d.ts +8 -2
- package/dist/http/agents.js +6 -2
- package/dist/http/attachments.d.ts +16 -7
- package/dist/http/attachments.js +3 -2
- package/dist/http/component-catalog.d.ts +2 -2
- package/dist/http/connectors.d.ts +17 -17
- package/dist/http/daemon-status.d.ts +2 -2
- package/dist/http/db-ops.d.ts +3 -3
- package/dist/http/delegated-tools.d.ts +1 -1
- package/dist/http/delegated-tools.js +1 -1
- package/dist/http/frontend-sessions.d.ts +1 -1
- package/dist/http/health.d.ts +6 -6
- package/dist/http/host-tools.d.ts +2 -2
- package/dist/http/media.d.ts +4 -4
- package/dist/http/memory.d.ts +15 -15
- package/dist/http/remote-run-events.d.ts +2 -2
- package/dist/http/research.d.ts +1 -1
- package/dist/http/routines.d.ts +7 -7
- package/dist/http/run-credentials.d.ts +2 -2
- package/dist/http/run-credentials.js +2 -2
- package/dist/http/runs.d.ts +4 -4
- package/dist/http/terminals.d.ts +6 -6
- package/dist/http/tool-catalog.d.ts +2 -2
- package/dist/http/xai.d.ts +6 -6
- package/dist/index.d.ts +76 -571
- package/dist/index.js +76 -571
- package/dist/read-only-tools.d.ts +1 -129
- package/dist/read-only-tools.js +2 -114
- package/dist/run-credentials.d.ts +47 -23
- package/dist/run-credentials.js +24 -8
- package/dist/run-lifecycle.d.ts +1 -1
- package/dist/run-lifecycle.js +6 -5
- package/dist/scheduler.d.ts +5 -0
- package/dist/scheduler.js +13 -1
- package/dist/session-coordination.d.ts +58 -24
- package/dist/session-coordination.js +40 -12
- package/dist/store/event-log/sqlite.js +3 -3
- package/dist/surface-exchanges.d.ts +207 -61
- package/dist/surface-exchanges.js +167 -20
- package/dist/tool-audit.d.ts +1 -113
- package/dist/tool-audit.js +2 -94
- package/dist/tool-recovery/diagnostics.d.ts +132 -0
- package/dist/tool-recovery/diagnostics.js +110 -0
- package/dist/tool-recovery/federated-refusal.d.ts +92 -0
- package/dist/tool-recovery/federated-refusal.js +152 -0
- package/dist/tool-recovery/index.d.ts +6 -0
- package/dist/tool-recovery/index.js +6 -0
- package/dist/tool-recovery/recovery.d.ts +49 -0
- package/dist/tool-recovery/recovery.js +419 -0
- package/dist/tool-recovery/redaction.d.ts +166 -0
- package/dist/tool-recovery/redaction.js +192 -0
- package/dist/tool-recovery/stack.d.ts +69 -0
- package/dist/tool-recovery/stack.js +62 -0
- package/package.json +35 -12
package/dist/agent-executor.d.ts
CHANGED
|
@@ -1,1665 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
|
|
9
|
-
|
|
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
|