@jini-ai/daemon 0.6.11 → 0.6.15-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/dist/agent-executor/codex/home-config.d.ts +50 -0
- package/dist/agent-executor/codex/home-config.js +209 -0
- package/dist/agent-executor/codex/home-staging.d.ts +60 -0
- package/dist/agent-executor/codex/home-staging.js +195 -0
- package/dist/agent-executor/codex/isolation.d.ts +27 -0
- package/dist/agent-executor/codex/isolation.js +67 -0
- package/dist/agent-executor/contracts.d.ts +4 -1
- package/dist/agent-executor/event-translation.d.ts +7 -7
- package/dist/agent-executor/event-translation.js +17 -17
- package/dist/agent-executor/events.js +1 -1
- package/dist/agent-executor/index.d.ts +3 -1
- package/dist/agent-executor/index.js +3 -1
- package/dist/agent-executor/launch.d.ts +3 -2
- package/dist/agent-executor/launch.js +68 -17
- package/dist/agent-executor/mcp-bridge-probe.d.ts +2 -1
- package/dist/agent-executor/mcp-bridge-probe.js +1 -1
- package/dist/agent-executor/mcp-bridge.d.ts +9 -86
- package/dist/agent-executor/mcp-bridge.js +4 -355
- package/dist/ai-trace-lifecycle.d.ts +48 -0
- package/dist/ai-trace-lifecycle.js +116 -0
- package/dist/core/read-only-tools.js +3 -1
- package/dist/delegated-tool-bridge.js +19 -1
- package/dist/http/attachment-lookup-routes.d.ts +44 -0
- package/dist/http/attachment-lookup-routes.js +71 -0
- package/dist/http/attachments.d.ts +112 -7
- package/dist/http/attachments.js +58 -17
- package/dist/http/connectors.js +23 -18
- package/dist/http/index.d.ts +6 -0
- package/dist/http/index.js +3 -0
- package/dist/http/tool-execution-routes.d.ts +69 -0
- package/dist/http/tool-execution-routes.js +181 -0
- package/dist/http/xai.js +19 -9
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/remote-attachment-lookup.d.ts +29 -0
- package/dist/remote-attachment-lookup.js +71 -0
- package/dist/remote-tool-executor.d.ts +85 -0
- package/dist/remote-tool-executor.js +250 -0
- package/dist/routing-tool-executor.d.ts +29 -0
- package/dist/routing-tool-executor.js +16 -0
- package/dist/run-lifecycle.d.ts +4 -1
- package/dist/run-lifecycle.js +35 -5
- package/dist/store/event-log/sqlite-kernel.d.ts +18 -0
- package/dist/store/event-log/sqlite-kernel.js +135 -0
- package/dist/store/event-log/sqlite-policy.d.ts +93 -0
- package/dist/store/event-log/sqlite-policy.js +74 -0
- package/dist/store/event-log/sqlite.d.ts +7 -31
- package/dist/store/event-log/sqlite.js +32 -95
- package/dist/tool-recovery/analytics.d.ts +27 -0
- package/dist/tool-recovery/analytics.js +38 -0
- package/dist/tool-recovery/index.d.ts +2 -0
- package/dist/tool-recovery/index.js +2 -0
- package/dist/tool-recovery/observability.d.ts +82 -0
- package/dist/tool-recovery/observability.js +88 -0
- package/package.json +15 -14
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { parse } from 'smol-toml';
|
|
2
|
+
import { JINI_MCP_SERVER_KEY } from '../mcp-bridge.js';
|
|
3
|
+
import { isRecord } from '../values.js';
|
|
4
|
+
/** A run with personal MCP off that Codex cannot keep to the bridge alone: refused, never run with a leak. */
|
|
5
|
+
export class CodexMcpIsolationError extends Error {
|
|
6
|
+
}
|
|
7
|
+
/** The user-facing refusal for a {@link CodexMcpIsolationError} reason. */
|
|
8
|
+
export function codexIsolationRefusalMessage(reason) {
|
|
9
|
+
return `Codex can't run with 'Your MCP servers' off: ${reason} Turn it on in the assistant settings, or choose Claude.`;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Parses one Codex config layer with a real TOML parser, so no spelling (quoted or escaped keys,
|
|
13
|
+
* dotted keys, inline tables, or a `[header]`-shaped line inside a multi-line string or array)
|
|
14
|
+
* hides a server from the check. A file that does not parse is refused: what it declares can't be
|
|
15
|
+
* known, and Codex could not load it either.
|
|
16
|
+
* @complexity O(n) in the file length.
|
|
17
|
+
*/
|
|
18
|
+
function parseCodexLayer(raw, path) {
|
|
19
|
+
try {
|
|
20
|
+
return parse(raw);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
throw new CodexMcpIsolationError(`${path} isn't valid TOML, so its MCP servers can't be checked.`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Refuses a layer that trusts a project. Codex loads a project's `.codex/config.toml` only when the
|
|
28
|
+
* config merged below it trusts that project (codex-rs `config/src/loader/mod.rs` L:1098-1125), so
|
|
29
|
+
* with no `projects` table anywhere no project layer, worktree root or root-marker range can add a
|
|
30
|
+
* server. Parsed, so a dotted, inline or escaped spelling is caught too.
|
|
31
|
+
*/
|
|
32
|
+
function assertTrustsNoProject(config, path) {
|
|
33
|
+
if ('projects' in config)
|
|
34
|
+
throw new CodexMcpIsolationError(`${path} trusts projects in a way that can't be removed safely.`);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The real home config must parse before its line-based rewrite ({@link buildCodexHomeConfigToml})
|
|
38
|
+
* is trusted: a malformed block the rewrite drops would otherwise hide that the line scan misread
|
|
39
|
+
* the file. A missing file (`undefined`) is fine.
|
|
40
|
+
* @throws {@link CodexMcpIsolationError} when it isn't valid TOML.
|
|
41
|
+
*/
|
|
42
|
+
export function assertCodexHomeConfigParses({ raw, path }) {
|
|
43
|
+
if (raw !== undefined)
|
|
44
|
+
parseCodexLayer(raw, path);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The staged home config, parsed: it must declare no MCP server but the bridge and trust no
|
|
48
|
+
* project. The home rewrite ({@link buildCodexHomeConfigToml}) is line-based so every other byte
|
|
49
|
+
* survives, so a server or trust entry it cannot see (an escaped header, a dotted root key) or a
|
|
50
|
+
* multi-line value a fake header split is refused here instead of staged. O(n) in the config length.
|
|
51
|
+
* @throws {@link CodexMcpIsolationError} when the staged config would not isolate the run.
|
|
52
|
+
*/
|
|
53
|
+
export function assertStagedHomeIsolated({ homeConfig, path }) {
|
|
54
|
+
const refusal = new CodexMcpIsolationError(`${path} declares MCP servers that can't be removed safely.`);
|
|
55
|
+
let config;
|
|
56
|
+
try {
|
|
57
|
+
config = parse(homeConfig);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
throw refusal;
|
|
61
|
+
}
|
|
62
|
+
const ids = isRecord(config.mcp_servers) ? Object.keys(config.mcp_servers) : [];
|
|
63
|
+
if (ids.some(id => id !== JINI_MCP_SERVER_KEY))
|
|
64
|
+
throw refusal;
|
|
65
|
+
assertTrustsNoProject(config, path);
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=isolation.js.map
|
|
@@ -144,6 +144,9 @@ export interface AgentExecutorRunInput {
|
|
|
144
144
|
readonly imageContents?: readonly MessageAttachmentImage[];
|
|
145
145
|
/** Additional host-validated directories the runtime may read. */
|
|
146
146
|
readonly extraAllowedDirs?: readonly string[];
|
|
147
|
+
/** Owner-picked folders outside filesystemRoot: readable where the CLI can enforce read-only, never
|
|
148
|
+
* a writable grant. Refused before spawn when unsafe (see `validateReadOnlyExtraDirs`). */
|
|
149
|
+
readonly readOnlyExtraDirs?: readonly string[];
|
|
147
150
|
/** Trusted root that must contain pi-rpc image paths after realpath resolution. */
|
|
148
151
|
readonly uploadRoot?: string;
|
|
149
152
|
/**
|
|
@@ -207,7 +210,7 @@ export interface AgentExecutor {
|
|
|
207
210
|
* underlying run is always already transitioned to `'failed'` via
|
|
208
211
|
* `lifecycle.finish()` before this rejects (see index.ts module doc's Invariant).
|
|
209
212
|
*/
|
|
210
|
-
run(requiredArgs: Pick<AgentExecutorRunInput, "runId" | "agentId" | "prompt" | "cwd">, optionalArgs?: Pick<AgentExecutorRunInput, "filesystemRoot" | "model" | "reasoning" | "nativeToolAccess" | "nativeCapabilities" | "cliParity" | "permissionMode" | "imagePaths" | "imageContents" | "extraAllowedDirs" | "uploadRoot" | "credentialEnv" | "env" | "resumeSessionId" | "newSessionId" | "disallowedTools" | "allowedTools" | "settingSources" | "settings" | "chromeIntegration">): Promise<void>;
|
|
213
|
+
run(requiredArgs: Pick<AgentExecutorRunInput, "runId" | "agentId" | "prompt" | "cwd">, optionalArgs?: Pick<AgentExecutorRunInput, "filesystemRoot" | "model" | "reasoning" | "nativeToolAccess" | "nativeCapabilities" | "cliParity" | "permissionMode" | "imagePaths" | "imageContents" | "extraAllowedDirs" | "readOnlyExtraDirs" | "uploadRoot" | "credentialEnv" | "env" | "resumeSessionId" | "newSessionId" | "disallowedTools" | "allowedTools" | "settingSources" | "settings" | "chromeIntegration">): Promise<void>;
|
|
211
214
|
/**
|
|
212
215
|
* Delivers a message the human sent while `runId` is going straight into its live agent process
|
|
213
216
|
* (see {@link UserMessageDelivery} for each outcome). A stream-json agent queues the message
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import type { RunAgentPayload, RunErrorPayload } from '@jini-ai/protocol';
|
|
2
2
|
import type { AgentRuntimeEventTranslation } from './contracts.js';
|
|
3
3
|
/**
|
|
4
|
-
* Narrows a parsed `usage` event's `usage` sub-object
|
|
4
|
+
* Narrows a parsed `usage` event's `usage` sub-object — the one
|
|
5
5
|
* piece of {@link translateUsagePayload} with real nested branching (an optional container holding
|
|
6
|
-
*
|
|
6
|
+
* optional numeric fields), extracted so that function reads as a flat field-by-field mapping.
|
|
7
|
+
* Reuses the runtime's alias owner so cache/reasoning counters survive the canonical boundary.
|
|
7
8
|
* @param rawUsage - `rawEvent.usage` once already narrowed to a record, or `undefined` when absent/malformed.
|
|
8
|
-
* @returns `undefined` when
|
|
9
|
+
* @returns `undefined` when no token count is present — matching `translateUsagePayload`'s
|
|
9
10
|
* original "omit the whole `usage` field rather than emit an empty object" behavior.
|
|
10
11
|
* @complexity O(1).
|
|
11
12
|
*/
|
|
12
13
|
export declare function extractUsageTokens({ rawUsage }: {
|
|
13
14
|
readonly rawUsage: Record<string, unknown> | undefined;
|
|
14
|
-
}): {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
} | undefined;
|
|
15
|
+
}, _options?: Record<string, never>): Extract<RunAgentPayload, {
|
|
16
|
+
type: 'usage';
|
|
17
|
+
}>['usage'];
|
|
18
18
|
export declare function translateStatusEvent({ rawEvent }: {
|
|
19
19
|
readonly rawEvent: Record<string, unknown>;
|
|
20
20
|
}): AgentRuntimeEventTranslation;
|
|
@@ -1,47 +1,46 @@
|
|
|
1
|
+
import { normalizeAiUsage } from '@jini-ai/agent-runtime';
|
|
1
2
|
import { isRecord, asString, asOptionalString, } from './values.js';
|
|
2
3
|
function asOptionalNumber(value) {
|
|
3
4
|
return typeof value === 'number' ? value : undefined;
|
|
4
5
|
}
|
|
5
6
|
/**
|
|
6
|
-
* Narrows a parsed `usage` event's `usage` sub-object
|
|
7
|
+
* Narrows a parsed `usage` event's `usage` sub-object — the one
|
|
7
8
|
* piece of {@link translateUsagePayload} with real nested branching (an optional container holding
|
|
8
|
-
*
|
|
9
|
+
* optional numeric fields), extracted so that function reads as a flat field-by-field mapping.
|
|
10
|
+
* Reuses the runtime's alias owner so cache/reasoning counters survive the canonical boundary.
|
|
9
11
|
* @param rawUsage - `rawEvent.usage` once already narrowed to a record, or `undefined` when absent/malformed.
|
|
10
|
-
* @returns `undefined` when
|
|
12
|
+
* @returns `undefined` when no token count is present — matching `translateUsagePayload`'s
|
|
11
13
|
* original "omit the whole `usage` field rather than emit an empty object" behavior.
|
|
12
14
|
* @complexity O(1).
|
|
13
15
|
*/
|
|
14
|
-
export function extractUsageTokens({ rawUsage }) {
|
|
15
|
-
|
|
16
|
+
export function extractUsageTokens({ rawUsage }, _options = {}) {
|
|
17
|
+
const normalized = normalizeAiUsage({ usage: rawUsage });
|
|
18
|
+
if (!normalized)
|
|
16
19
|
return undefined;
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
if (inputTokens === undefined && outputTokens === undefined)
|
|
20
|
-
return undefined;
|
|
21
|
-
return {
|
|
22
|
-
...(inputTokens !== undefined ? { input_tokens: inputTokens } : {}),
|
|
23
|
-
...(outputTokens !== undefined ? { output_tokens: outputTokens } : {}),
|
|
24
|
-
};
|
|
20
|
+
const fields = { input_tokens: normalized.inputTokens, output_tokens: normalized.outputTokens, cache_read_input_tokens: normalized.cacheReadTokens, cache_creation_input_tokens: normalized.cacheWriteTokens, reasoning_tokens: normalized.reasoningTokens };
|
|
21
|
+
return Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== null));
|
|
25
22
|
}
|
|
26
23
|
/**
|
|
27
24
|
* Narrows one parsed `usage` event's loosely-typed fields into
|
|
28
25
|
* `RunAgentPayload`'s `usage` variant. The 4 source parsers attach extra
|
|
29
|
-
* fields
|
|
26
|
+
* fields beyond the basic counts — `thought_tokens`,
|
|
30
27
|
* `cached_read_tokens`/`cached_write_tokens` (opencode/gemini/codex),
|
|
31
28
|
* `modelUsage`/`stopReason`/`isError` (qoder), a top-level `stopReason`
|
|
32
|
-
* (claude/copilot)
|
|
29
|
+
* (claude/copilot). Cache/reasoning counters and stop reasons now survive tracing; vendor-only
|
|
30
|
+
* `modelUsage` and `isError` remain outside this neutral usage contract.
|
|
33
31
|
* @param rawEvent - The raw `{type:'usage', ...}` record from a stream parser.
|
|
34
32
|
* @returns The narrowed `RunAgentPayload` `usage` variant.
|
|
35
33
|
* @complexity O(1).
|
|
36
34
|
* @overallScore 100/100
|
|
37
35
|
*/
|
|
38
|
-
function translateUsagePayload(rawEvent) {
|
|
36
|
+
function translateUsagePayload({ rawEvent }) {
|
|
39
37
|
const rawUsage = isRecord(rawEvent.usage) ? rawEvent.usage : undefined;
|
|
40
38
|
const usage = extractUsageTokens({ rawUsage: rawUsage });
|
|
41
39
|
const costUsd = asOptionalNumber(rawEvent.costUsd);
|
|
42
40
|
const cumulativeCostUsd = asOptionalNumber(rawEvent.cumulativeCostUsd);
|
|
43
41
|
const costSessionId = asOptionalString(rawEvent.costSessionId);
|
|
44
42
|
const durationMs = asOptionalNumber(rawEvent.durationMs);
|
|
43
|
+
const stopReason = asOptionalString(rawEvent.stopReason);
|
|
45
44
|
return {
|
|
46
45
|
type: 'usage',
|
|
47
46
|
...(usage !== undefined ? { usage } : {}),
|
|
@@ -49,6 +48,7 @@ function translateUsagePayload(rawEvent) {
|
|
|
49
48
|
...(cumulativeCostUsd !== undefined ? { cumulativeCostUsd } : {}),
|
|
50
49
|
...(costSessionId !== undefined ? { costSessionId } : {}),
|
|
51
50
|
...(durationMs !== undefined ? { durationMs } : {}),
|
|
51
|
+
...(stopReason !== undefined ? { stopReason } : {}),
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
54
|
export function translateStatusEvent({ rawEvent }) {
|
|
@@ -115,7 +115,7 @@ export function translateToolResultEvent({ rawEvent }) {
|
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
117
|
function translateUsageEvent(rawEvent) {
|
|
118
|
-
return { kind: 'agent', payload: translateUsagePayload(rawEvent) };
|
|
118
|
+
return { kind: 'agent', payload: translateUsagePayload({ rawEvent }) };
|
|
119
119
|
}
|
|
120
120
|
function translateRawEvent(rawEvent) {
|
|
121
121
|
return { kind: 'agent', payload: { type: 'raw', line: asString(rawEvent.line) } };
|
|
@@ -14,7 +14,7 @@ import { resolveContinuationTransport, } from '../continuation/continuation-tran
|
|
|
14
14
|
import { resultContent, } from '../delegated-tool-bridge.js';
|
|
15
15
|
import { projectToolResultEvent, } from '../tool-result-media.js';
|
|
16
16
|
import { translateAgentRuntimeEvent, translateAcpError, applyAgentTranslationSideEffects, } from './event-translation.js';
|
|
17
|
-
import { errorMessage,
|
|
17
|
+
import { errorMessage, } from './values.js';
|
|
18
18
|
import { MCP_BRIDGE_UNAVAILABLE, unavailableJiniBridgeStatus, bridgeUnavailableMessage, } from './mcp-bridge.js';
|
|
19
19
|
/** Carry PID only on structured status so the host can record its OS start identity early.
|
|
20
20
|
* A PID alone is never proof of death; hosts verify PID reuse before native continuation. */
|
|
@@ -92,7 +92,9 @@
|
|
|
92
92
|
export { type SupportedStreamFormat, type AgentExecutorCompatibility, type AgentRuntimeEventTranslation, type AgentExecutorErrorCode, AgentExecutorError, type AgentExecutorRunInput, type AgentExecutor, type UserMessageDelivery, type CollectProcessTreePidsPort, type StopProcessesPort, type AgentCleanupFailurePhase, type AgentCleanupFailureContext, type ContinuationOptions, type McpJsonInjectionOptions, type McpBridgeDelivery, type PreparedCodexHome, type PreparedClaudeConfigDir, type ClaudeConfigDirSeams, type ClaudeConfigDirIsolationOptions, type FailureClassificationContext, type ClassifyFailure, type CreateAgentExecutorOptions, type SystemPromptOverlayDelivery, type PreparedSystemPromptOverlayFile, type SpawnAgentChildProcessResult, } from './contracts.js';
|
|
93
93
|
export { isSupportedStreamFormat, assessAgentExecutorCompatibility, isAgentExecutorSupported, resolveDefAndStreamFormat, resolveImageDeliveryAndArgvBudget, resolveRunEnv, selectAgentExecutorHostEnv, resolveLaunch, stagePromptFile, stageLogFile, computeChildEnv, computeRuntimeContext, buildAgentBuildArgsOptions, resolveSystemPromptOverlayDelivery, buildRunArgs, prepareSystemPromptOverlayFileIfNeeded, guardWindowsCommandLineBudget, spawnAgentChildProcess, isStdinDrivenFormat, confirmChildSpawned, createAgentExecutor, } from './launch.js';
|
|
94
94
|
export { extractUsageTokens, translateStatusEvent, translateToolResultEvent, translateErrorEvent, translateTurnEndEvent, translateAgentRuntimeEvent, applyAgentTranslationSideEffects, } from './event-translation.js';
|
|
95
|
-
export { MCP_BRIDGE_UNAVAILABLE, unavailableJiniBridgeStatus, buildMcpJsonServerEntry, mergeMcpJsonContent, buildAcpMcpBridgeServers, mergeEnvContentMcpConfig, mergeEnvContentInstructions,
|
|
95
|
+
export { MCP_BRIDGE_UNAVAILABLE, unavailableJiniBridgeStatus, buildMcpJsonServerEntry, mergeMcpJsonContent, buildAcpMcpBridgeServers, mergeEnvContentMcpConfig, mergeEnvContentInstructions, buildMcpBridgeDelivery, resolveSourceClaudeConfigDir, resolveMcpBridgeForRun, writeMcpJsonIfNeeded, prepareClaudeConfigDirIfNeeded, } from './mcp-bridge.js';
|
|
96
|
+
export { buildCodexMcpServerToml, buildCodexHomeConfigToml } from './codex/home-config.js';
|
|
97
|
+
export { resolveSourceCodexHomeDir, prepareCodexHomeIfNeeded } from './codex/home-staging.js';
|
|
96
98
|
export { probeMcpBridge, type McpBridgeProbeResult } from './mcp-bridge-probe.js';
|
|
97
99
|
export { DEFAULT_BUFFERED_STDOUT_MAX_BYTES, runAcpDispatch, runPiRpcDispatch, } from './events.js';
|
|
98
100
|
export { armHandoffWatcher, } from './handoff.js';
|
|
@@ -92,7 +92,9 @@
|
|
|
92
92
|
export { AgentExecutorError, } from './contracts.js';
|
|
93
93
|
export { isSupportedStreamFormat, assessAgentExecutorCompatibility, isAgentExecutorSupported, resolveDefAndStreamFormat, resolveImageDeliveryAndArgvBudget, resolveRunEnv, selectAgentExecutorHostEnv, resolveLaunch, stagePromptFile, stageLogFile, computeChildEnv, computeRuntimeContext, buildAgentBuildArgsOptions, resolveSystemPromptOverlayDelivery, buildRunArgs, prepareSystemPromptOverlayFileIfNeeded, guardWindowsCommandLineBudget, spawnAgentChildProcess, isStdinDrivenFormat, confirmChildSpawned, createAgentExecutor, } from './launch.js';
|
|
94
94
|
export { extractUsageTokens, translateStatusEvent, translateToolResultEvent, translateErrorEvent, translateTurnEndEvent, translateAgentRuntimeEvent, applyAgentTranslationSideEffects, } from './event-translation.js';
|
|
95
|
-
export { MCP_BRIDGE_UNAVAILABLE, unavailableJiniBridgeStatus, buildMcpJsonServerEntry, mergeMcpJsonContent, buildAcpMcpBridgeServers, mergeEnvContentMcpConfig, mergeEnvContentInstructions,
|
|
95
|
+
export { MCP_BRIDGE_UNAVAILABLE, unavailableJiniBridgeStatus, buildMcpJsonServerEntry, mergeMcpJsonContent, buildAcpMcpBridgeServers, mergeEnvContentMcpConfig, mergeEnvContentInstructions, buildMcpBridgeDelivery, resolveSourceClaudeConfigDir, resolveMcpBridgeForRun, writeMcpJsonIfNeeded, prepareClaudeConfigDirIfNeeded, } from './mcp-bridge.js';
|
|
96
|
+
export { buildCodexMcpServerToml, buildCodexHomeConfigToml } from './codex/home-config.js';
|
|
97
|
+
export { resolveSourceCodexHomeDir, prepareCodexHomeIfNeeded } from './codex/home-staging.js';
|
|
96
98
|
export { probeMcpBridge } from './mcp-bridge-probe.js';
|
|
97
99
|
export { DEFAULT_BUFFERED_STDOUT_MAX_BYTES, runAcpDispatch, runPiRpcDispatch, } from './events.js';
|
|
98
100
|
export { armHandoffWatcher, } from './handoff.js';
|
|
@@ -190,14 +190,15 @@ type AgentBuildInput = Parameters<typeof buildAgentBuildArgsOptions>[0]['input']
|
|
|
190
190
|
/** Phase 9a: the def's `buildArgs` 4th argument. Historically omitted when every override was absent;
|
|
191
191
|
* that let defs silently enable auto-approval. Always supply the validated restricted default now. Pure. */
|
|
192
192
|
export declare function buildAgentBuildArgsOptions({ input, systemPromptOverlay }: {
|
|
193
|
-
readonly input: Pick<AgentExecutorRunInput, 'filesystemRoot' | 'model' | 'reasoning' | 'permissionMode' | 'nativeToolAccess' | 'nativeCapabilities' | 'cliParity' | 'disallowedTools' | 'allowedTools' | 'settingSources' | 'settings' | 'chromeIntegration'> & {
|
|
193
|
+
readonly input: Pick<AgentExecutorRunInput, 'filesystemRoot' | 'readOnlyExtraDirs' | 'model' | 'reasoning' | 'permissionMode' | 'nativeToolAccess' | 'nativeCapabilities' | 'cliParity' | 'disallowedTools' | 'allowedTools' | 'settingSources' | 'settings' | 'chromeIntegration'> & {
|
|
194
194
|
filesystemRuntimeReadPaths?: readonly string[];
|
|
195
195
|
filesystemTempDir?: string;
|
|
196
196
|
filesystemSharedTempRoots?: readonly string[];
|
|
197
|
+
filesystemRuntimeTempBase?: string;
|
|
197
198
|
filesystemNotice?: string;
|
|
198
199
|
};
|
|
199
200
|
readonly systemPromptOverlay: string | null | undefined;
|
|
200
|
-
}): RuntimeBuildOptions;
|
|
201
|
+
}, _options?: {}): RuntimeBuildOptions;
|
|
201
202
|
/**
|
|
202
203
|
* **The single dispatch point from a computed system-prompt overlay to its delivery mechanism** —
|
|
203
204
|
* see `RuntimeAgentDef.systemPromptDelivery`'s own doc for the declared shape. Pure and
|
|
@@ -6,14 +6,15 @@ import { spawn as nodeSpawn, } from 'node:child_process';
|
|
|
6
6
|
import { promises as fsPromises, } from 'node:fs';
|
|
7
7
|
import { tmpdir, } from 'node:os';
|
|
8
8
|
import { join, } from 'node:path';
|
|
9
|
-
import { agentCapabilities,
|
|
9
|
+
import { agentCapabilities, createClaudeFilesystemSupportProbe, claudeFilesystemRuntimePaths, claudeFilesystemSettings, validateFilesystemLaunch, validateReadOnlyExtraDirs, bindRuntimeAgentDef, defaultRuntimeState, getRuntimeModelDiscoveryDeps, userToolchainBinDirs, applyAgentLaunchEnv, ensureAgentCapabilities, resolveModelForLaunch, getAgentDef, resolveAgentLaunch, attachAcpSession, attachPiRpcSession, checkPromptArgvBudget, checkWindowsCmdShimCommandLineBudget, checkWindowsDirectExeCommandLineBudget, prepareAgentLogFile, preparePromptFileForAgent, } from '@jini-ai/agent-runtime';
|
|
10
10
|
import { createCommandInvocation } from '@jini-ai/platform/command';
|
|
11
11
|
import { collectProcessTreePids, listProcessSnapshots, stopProcesses, } from '@jini-ai/platform/process';
|
|
12
12
|
import { applyImagePromptDelivery, } from '../image-prompt-delivery.js';
|
|
13
13
|
import { SUPPORTED_STREAM_FORMATS, AgentExecutorError, } from './contracts.js';
|
|
14
14
|
import { errorMessage, } from './values.js';
|
|
15
15
|
import { defaultCleanupFailureSink, DEFAULT_BUFFERED_STDOUT_MAX_BYTES, wireChildLifecycle, writePromptToStdin, runAcpDispatch, runPiRpcDispatch, } from './events.js';
|
|
16
|
-
import { mergeEnvContentMcpConfig, mergeEnvContentInstructions, defaultRemoveMcpJsonFile, resolveMcpBridgeForRun, writeMcpJsonIfNeeded,
|
|
16
|
+
import { mergeEnvContentMcpConfig, mergeEnvContentInstructions, defaultRemoveMcpJsonFile, resolveMcpBridgeForRun, writeMcpJsonIfNeeded, prepareClaudeConfigDirIfNeeded, } from './mcp-bridge.js';
|
|
17
|
+
import { prepareCodexHomeIfNeeded, withStagedCodexHome } from './codex/home-staging.js';
|
|
17
18
|
import { acquireRuntimeLockIfConfigured, armHandoffWatcher, } from './handoff.js';
|
|
18
19
|
/**
|
|
19
20
|
* Narrows a `RuntimeAgentDef.streamFormat` string to the supported
|
|
@@ -279,7 +280,7 @@ export function computeChildEnv({ spawnEnv, mcpBridge }, { codexHomeDir, systemP
|
|
|
279
280
|
...envPassthroughApplied,
|
|
280
281
|
[stagedInstructionsFile.varName]: mergeEnvContentInstructions({ existingRaw: envPassthroughApplied[stagedInstructionsFile.varName], instructionsFilePath: stagedInstructionsFile.path }),
|
|
281
282
|
};
|
|
282
|
-
const codexHomeApplied = codexHomeDir === undefined ? instructionsApplied : {
|
|
283
|
+
const codexHomeApplied = codexHomeDir === undefined ? instructionsApplied : withStagedCodexHome({ env: instructionsApplied, codexHomeDir });
|
|
283
284
|
// Finding 1 of SEC-assistant-env-isolation-2026-09-07: same "only set when staged" shape as
|
|
284
285
|
// codexHomeApplied above — mutually exclusive with it in practice (codexHomeDir is only ever set
|
|
285
286
|
// for a 'codex-toml' bridge, claudeConfigDir only ever for a `claude`-id run), so both existing
|
|
@@ -336,13 +337,24 @@ function nativeBuildOptions({ input, permissionMode }, _options = {}) {
|
|
|
336
337
|
...(input.cliParity === true && permissionMode === 'bypass' && input.nativeToolAccess !== false ? { cliParity: true } : {}),
|
|
337
338
|
};
|
|
338
339
|
}
|
|
339
|
-
/**
|
|
340
|
-
|
|
340
|
+
/** Forward only supplied confinement paths; empty restriction arrays remain meaningful.
|
|
341
|
+
* Keeps executor-resolved filesystem facts through the definition's second settings merge. Pure, O(1).
|
|
342
|
+
*/
|
|
343
|
+
function filesystemBuildOptions({ input }, _options = {}) {
|
|
341
344
|
return {
|
|
342
345
|
...(input.filesystemRoot !== undefined ? { filesystemRoot: input.filesystemRoot } : {}),
|
|
343
346
|
...(input.filesystemSharedTempRoots ? { filesystemSharedTempRoots: input.filesystemSharedTempRoots } : {}),
|
|
344
347
|
...(input.filesystemTempDir ? { filesystemTempDir: input.filesystemTempDir } : {}),
|
|
345
348
|
...(input.filesystemRuntimeReadPaths ? { filesystemRuntimeReadPaths: input.filesystemRuntimeReadPaths } : {}),
|
|
349
|
+
...(input.filesystemRuntimeTempBase !== undefined ? { filesystemRuntimeTempBase: input.filesystemRuntimeTempBase } : {}),
|
|
350
|
+
// Only while a folder limit applies: an unconfined CLI gets no extra grant, read-only or otherwise.
|
|
351
|
+
...(input.filesystemRoot !== undefined && input.readOnlyExtraDirs?.length ? { filesystemReadOnlyDirs: input.readOnlyExtraDirs } : {}),
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
/** Preserve explicit empty restrictions and settings: they have meaning to the native CLI. */
|
|
355
|
+
function settingsBuildOptions({ input }, _options = {}) {
|
|
356
|
+
return {
|
|
357
|
+
...filesystemBuildOptions({ input }),
|
|
346
358
|
...(input.disallowedTools !== undefined ? { disallowedTools: input.disallowedTools } : {}),
|
|
347
359
|
...(input.allowedTools !== undefined ? { allowedTools: input.allowedTools } : {}),
|
|
348
360
|
...(input.settingSources !== undefined ? { settingSources: input.settingSources } : {}),
|
|
@@ -350,9 +362,13 @@ function settingsBuildOptions({ input }, _options = {}) {
|
|
|
350
362
|
...(input.chromeIntegration !== undefined ? { chromeIntegration: input.chromeIntegration } : {}),
|
|
351
363
|
};
|
|
352
364
|
}
|
|
365
|
+
/** A relay needs both a delivered bridge and a runtime that understands it. Pure, O(1). */
|
|
366
|
+
function canRegisterRunMessageRelay({ def, mcpBridge }, _options = {}) {
|
|
367
|
+
return mcpBridge !== null && supportsMessageRelay({ def });
|
|
368
|
+
}
|
|
353
369
|
/** Phase 9a: the def's `buildArgs` 4th argument. Historically omitted when every override was absent;
|
|
354
370
|
* that let defs silently enable auto-approval. Always supply the validated restricted default now. Pure. */
|
|
355
|
-
export function buildAgentBuildArgsOptions({ input, systemPromptOverlay }) {
|
|
371
|
+
export function buildAgentBuildArgsOptions({ input, systemPromptOverlay }, _options = {}) {
|
|
356
372
|
const permissionMode = input.permissionMode ?? 'restricted';
|
|
357
373
|
if (permissionMode !== 'restricted' && permissionMode !== 'bypass') {
|
|
358
374
|
throw new AgentExecutorError({ code: 'AGENT_PERMISSION_MODE_INVALID', message: 'AgentExecutor: permissionMode must be restricted or bypass' });
|
|
@@ -622,6 +638,22 @@ export async function confirmChildSpawned({ input, deps }) {
|
|
|
622
638
|
function nativeCapabilityInput({ input }, _options = {}) {
|
|
623
639
|
return { ...(input.nativeCapabilities !== undefined ? { nativeCapabilities: input.nativeCapabilities } : {}) };
|
|
624
640
|
}
|
|
641
|
+
/** Switch names a person sees for each native capability (the host's settings use the same words). */
|
|
642
|
+
const NATIVE_CAPABILITY_LABELS = {
|
|
643
|
+
shell: 'Bash/shell', fileWrite: 'File edit/write', userMcp: 'Your MCP servers', subagents: 'Subagents/Task',
|
|
644
|
+
userSettings: 'Your skills and settings', web: 'Web fetch/search', workflows: 'Workflows', loginEnvironment: 'Your shell environment',
|
|
645
|
+
};
|
|
646
|
+
/** The refusal a person reads when a CLI without a native-tool policy cannot honor this run's switches:
|
|
647
|
+
* it names the CLI and the switch to turn on. Owner 2026-10-27: the old "no native-tool policy and
|
|
648
|
+
* audit adapter" told a Gemini user nothing. Pure, O(c) for c OFF switches.
|
|
649
|
+
* @example nativeToolPolicyRefusal({ cliName: 'Antigravity', offSwitches: ['userMcp'] }, {});
|
|
650
|
+
*/
|
|
651
|
+
function nativeToolPolicyRefusal({ cliName, offSwitches }, _options = {}) {
|
|
652
|
+
const switches = offSwitches.map(key => `'${NATIVE_CAPABILITY_LABELS[key] ?? key}'`);
|
|
653
|
+
const blocked = switches.length > 0 ? `with ${new Intl.ListFormat('en', { type: 'conjunction' }).format(switches)} off` : `with this chat's CLI tool limits`;
|
|
654
|
+
const action = switches.length === 0 ? `Turn on 'Full CLI access'` : `Turn ${switches.length === 1 ? 'it' : 'them'} on`;
|
|
655
|
+
return `${cliName} can't run ${blocked}. ${action} in the assistant settings, or choose Claude or Codex.`;
|
|
656
|
+
}
|
|
625
657
|
/** Correlation projects retained stdout, so exact-byte journals need a separate blob writer. */
|
|
626
658
|
function validateStdoutJournalOptions(options, _options = {}) {
|
|
627
659
|
if (options.delegatedResultCorrelation && options.journal && !options.stdoutJournalBlobs) {
|
|
@@ -685,12 +717,16 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
685
717
|
const claudeConfigDirIsolationEnabled = options.claudeConfigDirIsolationEnabled ?? false;
|
|
686
718
|
const promptAugmenter = options.promptAugmenter;
|
|
687
719
|
const filesystemTemps = new Map();
|
|
688
|
-
|
|
720
|
+
const verifyClaudeFilesystem = options.assertClaudeFilesystemSupport ?? createClaudeFilesystemSupportProbe({});
|
|
721
|
+
/** Remove this run's private temp directories on startup failure, exit, crash or cancellation.
|
|
722
|
+
* O(d) removals for d staged directories; each failure is reported without skipping the others.
|
|
723
|
+
*/
|
|
689
724
|
async function cleanupFilesystemTemp({ runId }, _optional = {}) {
|
|
690
|
-
const
|
|
691
|
-
if (!
|
|
725
|
+
const paths = filesystemTemps.get(runId);
|
|
726
|
+
if (!paths)
|
|
692
727
|
return;
|
|
693
|
-
await fsPromises.rm(path, { recursive: true, force: true })
|
|
728
|
+
await Promise.all(paths.map(path => fsPromises.rm(path, { recursive: true, force: true })
|
|
729
|
+
.catch(() => console.warn('[agent-executor] private temp cleanup failed'))));
|
|
694
730
|
filesystemTemps.delete(runId);
|
|
695
731
|
}
|
|
696
732
|
/**
|
|
@@ -711,7 +747,7 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
711
747
|
// the user got a bare "Run failed". Best-effort: a stream that will not take the event must not
|
|
712
748
|
// stop the run from finishing.
|
|
713
749
|
await lifecycle
|
|
714
|
-
.emit({ runId: runId, input: { event: 'error', data: { message: `The assistant could not start: ${message.replace(/^AgentExecutor:\s*/, '')}` } } })
|
|
750
|
+
.emit({ runId: runId, input: { event: 'error', data: { error: { code, message }, message: `The assistant could not start: ${message.replace(/^AgentExecutor:\s*/, '')}` } } })
|
|
715
751
|
.catch(() => undefined);
|
|
716
752
|
await cleanupFilesystemTemp({ runId }, {});
|
|
717
753
|
await lifecycle.finish({ runId, status: 'failed', code: null, signal: null, resumable: false });
|
|
@@ -745,8 +781,11 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
745
781
|
// Never silently ignore a host's locked native-tool policy on another CLI.
|
|
746
782
|
// Parity cannot override an explicit lock or a restricted run.
|
|
747
783
|
input.cliParity = input.cliParity === true && input.permissionMode === 'bypass' && input.nativeToolAccess !== false;
|
|
748
|
-
if (
|
|
749
|
-
return
|
|
784
|
+
if (def.supportsNativeToolAccess === true)
|
|
785
|
+
return;
|
|
786
|
+
const offSwitches = Object.entries(input.nativeCapabilities ?? {}).filter(([, value]) => value === false).map(([key]) => key);
|
|
787
|
+
if (input.nativeToolAccess !== undefined && !input.cliParity || offSwitches.length > 0) {
|
|
788
|
+
return failBeforeSpawn({ runId: input.runId, code: 'NATIVE_TOOL_POLICY_UNSUPPORTED', message: nativeToolPolicyRefusal({ cliName: def.name, offSwitches }, {}) });
|
|
750
789
|
}
|
|
751
790
|
}
|
|
752
791
|
/** Validate every directory after image augmentation, before model probes or resource staging.
|
|
@@ -760,6 +799,8 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
760
799
|
...(imageDelivery.extraAllowedDirs ? { extraAllowedDirs: imageDelivery.extraAllowedDirs } : {}),
|
|
761
800
|
...(input.imagePaths ? { imagePaths: input.imagePaths } : {}),
|
|
762
801
|
}, {});
|
|
802
|
+
if (input.readOnlyExtraDirs)
|
|
803
|
+
input.readOnlyExtraDirs = validateReadOnlyExtraDirs({ filesystemRoot: input.filesystemRoot, dirs: input.readOnlyExtraDirs }, {});
|
|
763
804
|
input.cwd = input.filesystemRoot;
|
|
764
805
|
}
|
|
765
806
|
catch {
|
|
@@ -774,20 +815,27 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
774
815
|
if (input.filesystemRoot === undefined)
|
|
775
816
|
return;
|
|
776
817
|
try {
|
|
777
|
-
const
|
|
778
|
-
const supported = await verify({ agentId: def.id, launchPath: launch.launchPath, env: spawnEnv }, {});
|
|
818
|
+
const supported = await verifyClaudeFilesystem({ agentId: def.id, launchPath: launch.launchPath, env: spawnEnv }, {});
|
|
779
819
|
const runtimeDir = join(input.filesystemRoot, '.agent-runtime');
|
|
780
820
|
await fsPromises.mkdir(runtimeDir, { recursive: true, mode: 0o700 });
|
|
781
821
|
// Refuse a runtime-folder symlink escaping the site before creating anything beneath it.
|
|
782
822
|
validateFilesystemLaunch({ filesystemRoot: input.filesystemRoot, cwd: input.cwd, extraAllowedDirs: [runtimeDir] }, {});
|
|
783
823
|
input.filesystemTempDir = await fsPromises.realpath(await fsPromises.mkdtemp(join(runtimeDir, 'tmp-')));
|
|
784
|
-
filesystemTemps.set(input.runId, input.filesystemTempDir);
|
|
824
|
+
filesystemTemps.set(input.runId, [input.filesystemTempDir]);
|
|
785
825
|
// Recheck after creation too: swapping the runtime symlink must never grant outside temp access.
|
|
786
826
|
validateFilesystemLaunch({ filesystemRoot: input.filesystemRoot, cwd: input.cwd, extraAllowedDirs: [input.filesystemTempDir] }, {});
|
|
787
827
|
input.filesystemSharedTempRoots = [spawnEnv.TMPDIR, spawnEnv.TMP, spawnEnv.TEMP].filter((path) => Boolean(path));
|
|
788
828
|
spawnEnv.TMPDIR = input.filesystemTempDir;
|
|
789
829
|
spawnEnv.TMP = input.filesystemTempDir;
|
|
790
830
|
spawnEnv.TEMP = input.filesystemTempDir;
|
|
831
|
+
if (def.id === 'claude' && process.platform !== 'win32') {
|
|
832
|
+
// Claude shortens long Bash socket paths to shared scratch. Keep even the /private alias
|
|
833
|
+
// plus /claude-<uid> below 44 bytes, and isolate it with mkdtemp's private 0700 directory.
|
|
834
|
+
const runtimeTemp = await fsPromises.mkdtemp('/tmp/jini-');
|
|
835
|
+
filesystemTemps.get(input.runId).push(runtimeTemp);
|
|
836
|
+
input.filesystemRuntimeTempBase = runtimeTemp;
|
|
837
|
+
spawnEnv.CLAUDE_CODE_TMPDIR = runtimeTemp;
|
|
838
|
+
}
|
|
791
839
|
if (supported === false) {
|
|
792
840
|
input.filesystemNotice = 'files and shell are not folder-limited for this agent';
|
|
793
841
|
console.warn('[agent-executor] files and shell are not folder-limited for this agent');
|
|
@@ -842,6 +890,9 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
842
890
|
}, options.runtimeState ? { state: options.runtimeState } : {});
|
|
843
891
|
return { systemPromptOverlay, overlayDelivery };
|
|
844
892
|
}
|
|
893
|
+
/** Resolve the launch environment before staging the run's private temp overrides.
|
|
894
|
+
* Mutates only this run's input; O(e) for e environment entries plus executable resolution I/O.
|
|
895
|
+
*/
|
|
845
896
|
async function resolveRunLaunch({ input, def }, _options = {}) {
|
|
846
897
|
// The def's own declared env (e.g. claude's `ENABLE_TOOL_SEARCH=false`) is a default the run's
|
|
847
898
|
// env can override. Detection already spawns with it (`detection.ts`); without this the run
|
|
@@ -884,7 +935,7 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
884
935
|
const formatForStdin = { streamFormat };
|
|
885
936
|
const stdinHandle = isStdinDrivenFormat(formatForStdin)
|
|
886
937
|
? wireChildLifecycle({
|
|
887
|
-
...(
|
|
938
|
+
...(canRegisterRunMessageRelay({ def, mcpBridge }) ? { registerMessageRelay: (relay) => liveRunRelays.set(input.runId, relay) } : {}),
|
|
888
939
|
...(input.model ? { startingModel: input.model } : {}),
|
|
889
940
|
...(modelCatalog ? { modelCatalog } : {}),
|
|
890
941
|
runId: input.runId,
|
|
@@ -16,8 +16,9 @@ export declare function probeMcpBridge({ entry }: {
|
|
|
16
16
|
readonly args: readonly string[];
|
|
17
17
|
readonly env?: Readonly<Record<string, string>>;
|
|
18
18
|
};
|
|
19
|
-
}, { spawn, baseEnv, timeoutMs, now }?: {
|
|
19
|
+
}, { spawn, hostEnv, baseEnv, timeoutMs, now }?: {
|
|
20
20
|
readonly spawn?: typeof nodeSpawn;
|
|
21
|
+
readonly hostEnv?: Readonly<Record<string, string | undefined>>;
|
|
21
22
|
readonly baseEnv?: Readonly<Record<string, string>>;
|
|
22
23
|
readonly timeoutMs?: number;
|
|
23
24
|
readonly now?: () => number;
|
|
@@ -44,7 +44,7 @@ function reapProbe({ timer, lines, child }) {
|
|
|
44
44
|
* Spawn/errors/exits/timeouts return diagnostics rather than rejecting. Stderr retains 2000 bytes.
|
|
45
45
|
* @complexity O(n) in received bytes; stderr storage is bounded independently of child output.
|
|
46
46
|
*/
|
|
47
|
-
export async function probeMcpBridge({ entry }, { spawn = nodeSpawn, baseEnv = buildAgentEnv({ hostEnv
|
|
47
|
+
export async function probeMcpBridge({ entry }, { spawn = nodeSpawn, hostEnv = process.env, baseEnv = buildAgentEnv({ hostEnv }), timeoutMs = 15_000, now = Date.now } = {}) {
|
|
48
48
|
const startedAt = now();
|
|
49
49
|
const env = { ...baseEnv, ...entry.env, JINI_RUN_ID: 'bridge-probe' };
|
|
50
50
|
// Initialize needs no callback: never delegate a real run's authority to this diagnostic child.
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { McpBridgeProbeResult } from './mcp-bridge-probe.js';
|
|
2
2
|
import { ensureAntigravityMcpPermission } from './antigravity-permissions.js';
|
|
3
3
|
import type { AcpMcpServerInput, RuntimeAgentDef } from '@jini-ai/agent-runtime';
|
|
4
|
-
import type { McpJsonInjectionOptions, McpJsonServerEntry, McpBridgeDelivery,
|
|
4
|
+
import type { McpJsonInjectionOptions, McpJsonServerEntry, McpBridgeDelivery, PreparedClaudeConfigDir, ClaudeConfigDirIsolationOptions, FailBeforeSpawn } from './contracts.js';
|
|
5
|
+
export declare const JINI_MCP_SERVER_KEY = "jini";
|
|
5
6
|
/** The run-error code a run gets when its CLI reports the injected `jini` bridge as anything but connected. */
|
|
6
7
|
export declare const MCP_BRIDGE_UNAVAILABLE = "MCP_BRIDGE_UNAVAILABLE";
|
|
7
8
|
/**
|
|
@@ -149,66 +150,6 @@ export declare function mergeEnvContentInstructions({ existingRaw, instructionsF
|
|
|
149
150
|
readonly existingRaw: string | undefined;
|
|
150
151
|
readonly instructionsFilePath: string;
|
|
151
152
|
}): string;
|
|
152
|
-
/**
|
|
153
|
-
* Mechanism 5 of 5 — `'codex-toml'`'s serialization step. Builds the `[mcp_servers.jini]` TOML
|
|
154
|
-
* table (plus, when the entry carries any env vars, a separate `[mcp_servers.jini.env]` table)
|
|
155
|
-
* Codex's own config schema expects.
|
|
156
|
-
*
|
|
157
|
-
* Confirmed against a real installed Codex CLI (0.151.0), not assumed from docs: round-tripping
|
|
158
|
-
* `codex mcp add <name> --env K=V -- <cmd> <args>` against a scratch `CODEX_HOME` and reading back
|
|
159
|
-
* `config.toml` produced exactly this shape (`command`/`args` as TOML strings/array in the main
|
|
160
|
-
* table, env vars in a nested `.env` table) — see `archived provenance ledger` for the transcript.
|
|
161
|
-
* @param entry - The shared bridge entry from {@link buildMcpJsonServerEntry}.
|
|
162
|
-
* @returns A TOML fragment with no leading/trailing blank-line padding — {@link buildCodexHomeConfigToml} owns spacing when combining it with existing content.
|
|
163
|
-
* @complexity O(n) in the number of argv/env entries.
|
|
164
|
-
* @overallScore 100/100
|
|
165
|
-
*/
|
|
166
|
-
export declare function buildCodexMcpServerToml({ entry }: {
|
|
167
|
-
readonly entry: McpJsonServerEntry;
|
|
168
|
-
}): string;
|
|
169
|
-
/**
|
|
170
|
-
* Builds the full `config.toml` a run's scratch `CODEX_HOME` gets: the real Codex home's own
|
|
171
|
-
* config, with any pre-existing `[mcp_servers.jini]` table removed (see
|
|
172
|
-
* {@link stripExistingJiniMcpServerTable}), then this run's own table appended.
|
|
173
|
-
*
|
|
174
|
-
* **Append-only by design, not a parse-and-merge, for everything but the jini table itself.**
|
|
175
|
-
* `mergeMcpJsonContent`/`mergeEnvContentMcpConfig` above can safely parse-merge-reserialize because
|
|
176
|
-
* their formats have a JS-native parser (`JSON.parse`); this driver has no general TOML parser in
|
|
177
|
-
* its dependency graph (see `buildCodexMcpServerToml`'s doc), and every other setting a real Codex
|
|
178
|
-
* install carries — model choice, sandbox policy, the trusted-project list, the operator's own
|
|
179
|
-
* other MCP servers — must survive a spawn byte-for-byte. Only the one table this driver itself
|
|
180
|
-
* owns (`jini` is this integration's own reserved server name — see {@link JINI_MCP_SERVER_KEY}) is
|
|
181
|
-
* removed by default, via the narrow line-oriented scan above, never a full TOML parse. Explicit
|
|
182
|
-
* per-run OFF choices additionally omit personal MCP tables or retain only those tables when
|
|
183
|
-
* user settings are off. Inline root MCP declarations fail closed when isolation is requested.
|
|
184
|
-
* @param existingRaw - The real Codex home's `config.toml` content, or `undefined` when it does not
|
|
185
|
-
* exist (a fresh Codex install — degrades to "start from just this run's block", matching
|
|
186
|
-
* {@link mergeMcpJsonContent}'s own "missing file" handling).
|
|
187
|
-
* @param entry - The shared bridge entry.
|
|
188
|
-
* @returns The full text to write to the scratch `CODEX_HOME`'s `config.toml`.
|
|
189
|
-
* @complexity O(n) in the existing config's length.
|
|
190
|
-
*/
|
|
191
|
-
export declare function buildCodexHomeConfigToml({ existingRaw, entry, inheritUserMcp, inheritUserSettings }: {
|
|
192
|
-
readonly existingRaw: string | undefined;
|
|
193
|
-
readonly entry: McpJsonServerEntry;
|
|
194
|
-
readonly inheritUserMcp?: boolean;
|
|
195
|
-
readonly inheritUserSettings?: boolean;
|
|
196
|
-
}, _optional?: Record<string, never>): string;
|
|
197
|
-
/**
|
|
198
|
-
* Where `'codex-toml'` reads the operator's REAL Codex config from, to seed a run's scratch copy —
|
|
199
|
-
* never where it writes. Resolved against the daemon HOST process's own environment (`hostEnv`,
|
|
200
|
-
* `process.env` at the real call site), not a run's sandboxed spawn env: `CODEX_HOME` is not in
|
|
201
|
-
* `BASELINE_AGENT_ENV_KEYS`, so a spawned child never inherits it anyway, and the whole point here
|
|
202
|
-
* is finding wherever the *operator's actual* Codex install lives, which is a host-machine fact.
|
|
203
|
-
* @param hostEnv - The daemon process's own environment.
|
|
204
|
-
* @returns `hostEnv.CODEX_HOME` when set to a non-blank value (matching Codex's own resolution
|
|
205
|
-
* order), else the CLI's documented default, `~/.codex`.
|
|
206
|
-
* @complexity O(1).
|
|
207
|
-
* @overallScore 100/100
|
|
208
|
-
*/
|
|
209
|
-
export declare function resolveSourceCodexHomeDir({ hostEnv }: {
|
|
210
|
-
readonly hostEnv: NodeJS.ProcessEnv;
|
|
211
|
-
}): string;
|
|
212
153
|
/**
|
|
213
154
|
* **The single dispatch point from an `externalMcpInjection` strategy to its delivery mechanism.**
|
|
214
155
|
* Pure and synchronous — the one effectful input (the per-run bearer credential) arrives already
|
|
@@ -238,6 +179,13 @@ export declare function buildMcpBridgeDelivery(input: {
|
|
|
238
179
|
readonly credential: string | undefined;
|
|
239
180
|
readonly messageRelayNonce?: string;
|
|
240
181
|
}): McpBridgeDelivery | null;
|
|
182
|
+
export declare function defaultReadMcpJsonFile({ path }: {
|
|
183
|
+
readonly path: string;
|
|
184
|
+
}): Promise<string>;
|
|
185
|
+
export declare function defaultWriteMcpJsonFile({ path, content }: {
|
|
186
|
+
readonly path: string;
|
|
187
|
+
readonly content: string;
|
|
188
|
+
}): Promise<void>;
|
|
241
189
|
export declare function defaultRemoveMcpJsonFile({ path }: {
|
|
242
190
|
readonly path: string;
|
|
243
191
|
}): Promise<void>;
|
|
@@ -287,31 +235,6 @@ export declare function writeMcpJsonIfNeeded({ input, deps }: {
|
|
|
287
235
|
readonly failBeforeSpawn: FailBeforeSpawn;
|
|
288
236
|
};
|
|
289
237
|
}): Promise<string | undefined>;
|
|
290
|
-
/**
|
|
291
|
-
* Phase 10b: mechanism 5 of 5's one effect — stages this run's scratch `CODEX_HOME` directory,
|
|
292
|
-
* returning the prepared handle `cleanupStagedFiles` should later release (`null` for every other
|
|
293
|
-
* mechanism, or for an unconfigured host — matching {@link writeMcpJsonIfNeeded}'s identical gate).
|
|
294
|
-
* @param input.def - Only used for its `id`, in the failure message.
|
|
295
|
-
* @param input.mcpBridge - This run's resolved bridge delivery — a no-op unless its `kind` is `'codex-toml'`.
|
|
296
|
-
* @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.
|
|
297
|
-
* @complexity O(1) plus {@link prepareCodexHomeForRun}'s own cost.
|
|
298
|
-
* @overallScore 100/100
|
|
299
|
-
*/
|
|
300
|
-
export declare function prepareCodexHomeIfNeeded({ input, deps }: {
|
|
301
|
-
readonly input: {
|
|
302
|
-
readonly runId: string;
|
|
303
|
-
readonly nativeToolAccess?: boolean | undefined;
|
|
304
|
-
readonly nativeCapabilities?: Partial<import('@jini-ai/agent-runtime').NativeCapabilities>;
|
|
305
|
-
readonly def: RuntimeAgentDef;
|
|
306
|
-
readonly mcpBridge: McpBridgeDelivery | null;
|
|
307
|
-
};
|
|
308
|
-
readonly deps: {
|
|
309
|
-
readonly mcpJsonInjection: McpJsonInjectionOptions | undefined;
|
|
310
|
-
readonly hostEnv: NodeJS.ProcessEnv;
|
|
311
|
-
readonly releaseStagedResources: () => Promise<void>;
|
|
312
|
-
readonly failBeforeSpawn: FailBeforeSpawn;
|
|
313
|
-
};
|
|
314
|
-
}): Promise<PreparedCodexHome | null>;
|
|
315
238
|
/**
|
|
316
239
|
* Finding 1 of SEC-assistant-env-isolation-2026-09-07's one effect — stages this run's scratch
|
|
317
240
|
* `CLAUDE_CONFIG_DIR` directory, returning the prepared handle `cleanupStagedFiles` should later
|