@jini-ai/daemon 0.6.5 → 0.6.7
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 +8 -0
- package/dist/agent-executor/agent-env.d.ts +13 -0
- package/dist/agent-executor/agent-env.js +39 -0
- package/dist/agent-executor/contracts.d.ts +5 -0
- package/dist/agent-executor/events.d.ts +3 -0
- package/dist/agent-executor/events.js +18 -2
- package/dist/agent-executor/index.d.ts +1 -0
- package/dist/agent-executor/index.js +1 -0
- package/dist/agent-executor/launch.d.ts +1 -1
- package/dist/agent-executor/launch.js +9 -39
- package/dist/agent-executor/mcp-bridge-probe.d.ts +25 -0
- package/dist/agent-executor/mcp-bridge-probe.js +77 -0
- package/dist/agent-executor/mcp-bridge.d.ts +2 -1
- package/dist/agent-executor/mcp-bridge.js +7 -2
- package/dist/agent-executor.d.ts +1 -1
- package/dist/agent-executor.js +1 -1
- package/dist/delegated-tool-bridge.d.ts +2 -2
- package/dist/http/delegated-tools.d.ts +4 -4
- package/dist/http/delegated-tools.js +6 -2
- package/dist/tool-executor.d.ts +2 -2
- package/dist/tool-executor.js +5 -3
- package/dist/tool-recovery/federated-refusal.js +2 -2
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## 0.6.7 — 2026-10-15
|
|
2
|
+
|
|
3
|
+
- An unknown tool id is a `400 BAD_REQUEST`, not a redacted `500 INTERNAL_ERROR` (b4a3abad): `ToolExecutor` throws `ToolInputError` naming up to eight registered ids the unknown id prefixes (e.g. `content_read` → `content_read.menu`), and the delegated-tools route returns that message.
|
|
4
|
+
|
|
5
|
+
## 0.6.6 — 2026-10-14
|
|
6
|
+
|
|
7
|
+
- A failed jini MCP bridge now says why (8bc0f07d): the run still stops at once, but a disposable credential-free copy of the bridge is spawned, sent MCP `initialize`, and its exit code + stderr tail (e.g. `ERR_MODULE_NOT_FOUND`) go into the `MCP_BRIDGE_UNAVAILABLE` message and `error.details`, emitted before the run's end event. `probeMcpBridge` is exported so hosts can run the same check at boot; `mcpJsonInjection.probeOnFailure: false` opts out. The baseline agent env moves to `agent-env.ts` so probe and launch share one allowlist.
|
|
8
|
+
|
|
1
9
|
## 0.6.5 — 2026-10-14
|
|
2
10
|
|
|
3
11
|
- Express 5 accepted: the optional `express` peer is now `^4.21.0 || ^5.0.0` (1442d875), so an Express 5 host no longer hits ERESOLVE.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deny-by-default agent subprocess environment: `BASELINE_AGENT_ENV_KEYS` resolved from
|
|
3
|
+
* `hostEnv`, plus this run's explicitly-delegated credential(s) — never a passthrough of
|
|
4
|
+
* `hostEnv` itself. Run launch reaches this when the caller omits `AgentExecutorRunInput.env`;
|
|
5
|
+
* supplying `env` bypasses this function for that launch (see its call site). Diagnostic bridge
|
|
6
|
+
* probes reuse this same baseline without adding delegated credentials.
|
|
7
|
+
*/
|
|
8
|
+
export declare function buildAgentEnv({ hostEnv }: {
|
|
9
|
+
hostEnv: NodeJS.ProcessEnv;
|
|
10
|
+
}, { credentialEnv }?: {
|
|
11
|
+
credentialEnv?: Record<string, string> | undefined;
|
|
12
|
+
}): Record<string, string>;
|
|
13
|
+
//# sourceMappingURL=agent-env.d.ts.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixed baseline of host environment variables every spawned agent subprocess may see by
|
|
3
|
+
* default — resolved from the host's env one name at a time, never derived programmatically
|
|
4
|
+
* from `process.env` as a bag, so this can't silently widen. SEC-001's deny-by-default fix; see
|
|
5
|
+
* `AgentExecutorRunInput.env`'s doc for the full threat model.
|
|
6
|
+
*/
|
|
7
|
+
const BASELINE_AGENT_ENV_KEYS = [
|
|
8
|
+
'PATH', 'HOME', 'USERPROFILE', 'TMPDIR', 'TEMP', 'TMP', 'SHELL',
|
|
9
|
+
'LANG', 'LC_ALL', 'LC_CTYPE',
|
|
10
|
+
// `USER` is required for a spawned `claude` CLI to find its own login/credential state — with
|
|
11
|
+
// it omitted (even though `HOME` is present), `claude` fails fast with "Not logged in · Please
|
|
12
|
+
// run /login" despite real credentials existing on disk/keychain. Confirmed by bisection against
|
|
13
|
+
// a real authenticated `claude` install: `BASELINE_AGENT_ENV_KEYS` alone fails, adding back every
|
|
14
|
+
// `CLAUDE_CODE_*`/`CLAUDECODE` var still fails, `LOGNAME`/`SSH_AUTH_SOCK` alone still fail, but
|
|
15
|
+
// `USER` alone flips it to success. Login lookup requires the user identity
|
|
16
|
+
// as well as the home directory.
|
|
17
|
+
'USER',
|
|
18
|
+
'SystemRoot', 'windir', 'ComSpec', 'PATHEXT', // Windows-only; harmless no-ops elsewhere
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Deny-by-default agent subprocess environment: `BASELINE_AGENT_ENV_KEYS` resolved from
|
|
22
|
+
* `hostEnv`, plus this run's explicitly-delegated credential(s) — never a passthrough of
|
|
23
|
+
* `hostEnv` itself. Run launch reaches this when the caller omits `AgentExecutorRunInput.env`;
|
|
24
|
+
* supplying `env` bypasses this function for that launch (see its call site). Diagnostic bridge
|
|
25
|
+
* probes reuse this same baseline without adding delegated credentials.
|
|
26
|
+
*/
|
|
27
|
+
export function buildAgentEnv({ hostEnv }, { credentialEnv } = {}) {
|
|
28
|
+
const result = {};
|
|
29
|
+
for (const key of BASELINE_AGENT_ENV_KEYS) {
|
|
30
|
+
const value = hostEnv[key];
|
|
31
|
+
if (typeof value === 'string')
|
|
32
|
+
result[key] = value;
|
|
33
|
+
}
|
|
34
|
+
for (const [key, value] of Object.entries(credentialEnv ?? {})) {
|
|
35
|
+
result[key] = value;
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=agent-env.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { MessageAttachmentImage } from '../attachment-content.js';
|
|
2
|
+
import type { probeMcpBridge } from './mcp-bridge-probe.js';
|
|
2
3
|
import type { spawn as nodeSpawn, ChildProcess } from 'node:child_process';
|
|
3
4
|
import type { Principal } from '@jini-ai/core';
|
|
4
5
|
import type { RunAgentPayload, RunErrorPayload } from '@jini-ai/protocol';
|
|
@@ -312,6 +313,8 @@ export interface ContinuationOptions {
|
|
|
312
313
|
* interface that defaults to *nothing* rather than a real implementation).
|
|
313
314
|
*/
|
|
314
315
|
export interface McpJsonInjectionOptions {
|
|
316
|
+
/** Probe a reported bridge failure before emitting its error. @default true when injection is configured. */
|
|
317
|
+
readonly probeOnFailure?: boolean;
|
|
315
318
|
/** 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. */
|
|
316
319
|
readonly command: string;
|
|
317
320
|
/** Extra argv for `command`. @default [] */
|
|
@@ -594,6 +597,8 @@ export interface CreateAgentExecutorOptions {
|
|
|
594
597
|
readonly createCommandInvocation?: typeof createCommandInvocation;
|
|
595
598
|
/** @default `node:child_process`'s `spawn` */
|
|
596
599
|
readonly spawn?: typeof nodeSpawn;
|
|
600
|
+
/** Diagnostic bridge probe; independent of the agent spawn seam. @default the real MCP initialize probe. */
|
|
601
|
+
readonly probeMcpBridge?: typeof probeMcpBridge;
|
|
597
602
|
/** @default the real `@jini-ai/agent-runtime` ACP session transport */
|
|
598
603
|
readonly attachAcpSession?: typeof attachAcpSession;
|
|
599
604
|
/**
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { probeMcpBridge } from './mcp-bridge-probe.js';
|
|
1
2
|
import type { ModelCatalogSnapshot } from '@jini-ai/agent-runtime';
|
|
2
3
|
import type { ChildProcess } from 'node:child_process';
|
|
3
4
|
import { attachAcpSession, attachPiRpcSession, type AcpPermissionHandler, type RuntimeAgentDef } from '@jini-ai/agent-runtime';
|
|
@@ -56,6 +57,8 @@ interface WireChildLifecycleContext extends TerminateChildTreeDeps {
|
|
|
56
57
|
* {@link MCP_BRIDGE_UNAVAILABLE} instead of letting it continue without the host's tools.
|
|
57
58
|
*/
|
|
58
59
|
readonly expectsJiniBridge: boolean;
|
|
60
|
+
readonly bridgeProbeEntry: Parameters<typeof probeMcpBridge>[0]['entry'] | undefined;
|
|
61
|
+
readonly probeMcpBridge: typeof probeMcpBridge;
|
|
59
62
|
}
|
|
60
63
|
/**
|
|
61
64
|
* Wires one spawned child's full observable lifecycle: raw stdout/stderr
|
|
@@ -561,9 +561,25 @@ export function wireChildLifecycle(ctx) {
|
|
|
561
561
|
*/
|
|
562
562
|
function stopForUnavailableBridge(initStatus, bridgeStatus) {
|
|
563
563
|
bridgeUnavailable = true;
|
|
564
|
-
const message = bridgeUnavailableMessage(bridgeStatus);
|
|
565
564
|
enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'agent', data: initStatus } }));
|
|
566
|
-
enqueueEmit(() =>
|
|
565
|
+
enqueueEmit(async () => {
|
|
566
|
+
let probe;
|
|
567
|
+
if (ctx.bridgeProbeEntry) {
|
|
568
|
+
try {
|
|
569
|
+
probe = await ctx.probeMcpBridge({ entry: ctx.bridgeProbeEntry });
|
|
570
|
+
}
|
|
571
|
+
catch {
|
|
572
|
+
// A diagnostic failure must not suppress the original bridge error or the terminal event.
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
const message = bridgeUnavailableMessage(bridgeStatus, probe);
|
|
576
|
+
const details = probe ? {
|
|
577
|
+
exitCode: probe.exitCode, signal: probe.signal, stderrTail: probe.stderrTail, elapsedMs: probe.elapsedMs,
|
|
578
|
+
} : undefined;
|
|
579
|
+
await lifecycle.emit({ runId, input: { event: 'error', data: {
|
|
580
|
+
message, error: { code: MCP_BRIDGE_UNAVAILABLE, message, ...(details ? { details } : {}) },
|
|
581
|
+
} } });
|
|
582
|
+
});
|
|
567
583
|
void terminateChildTreeBestEffort(ctx, child, runId, 'cancel', ctx.onCleanupFailure);
|
|
568
584
|
}
|
|
569
585
|
/**
|
|
@@ -93,6 +93,7 @@ export { type SupportedStreamFormat, type AgentExecutorCompatibility, type Agent
|
|
|
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
95
|
export { MCP_BRIDGE_UNAVAILABLE, unavailableJiniBridgeStatus, buildMcpJsonServerEntry, mergeMcpJsonContent, buildAcpMcpBridgeServers, mergeEnvContentMcpConfig, mergeEnvContentInstructions, buildCodexMcpServerToml, buildCodexHomeConfigToml, resolveSourceCodexHomeDir, buildMcpBridgeDelivery, resolveSourceClaudeConfigDir, resolveMcpBridgeForRun, writeMcpJsonIfNeeded, prepareCodexHomeIfNeeded, prepareClaudeConfigDirIfNeeded, } from './mcp-bridge.js';
|
|
96
|
+
export { probeMcpBridge, type McpBridgeProbeResult } from './mcp-bridge-probe.js';
|
|
96
97
|
export { DEFAULT_BUFFERED_STDOUT_MAX_BYTES, runAcpDispatch, runPiRpcDispatch, } from './events.js';
|
|
97
98
|
export { armHandoffWatcher, } from './handoff.js';
|
|
98
99
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -93,6 +93,7 @@ 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
95
|
export { MCP_BRIDGE_UNAVAILABLE, unavailableJiniBridgeStatus, buildMcpJsonServerEntry, mergeMcpJsonContent, buildAcpMcpBridgeServers, mergeEnvContentMcpConfig, mergeEnvContentInstructions, buildCodexMcpServerToml, buildCodexHomeConfigToml, resolveSourceCodexHomeDir, buildMcpBridgeDelivery, resolveSourceClaudeConfigDir, resolveMcpBridgeForRun, writeMcpJsonIfNeeded, prepareCodexHomeIfNeeded, prepareClaudeConfigDirIfNeeded, } from './mcp-bridge.js';
|
|
96
|
+
export { probeMcpBridge } from './mcp-bridge-probe.js';
|
|
96
97
|
export { DEFAULT_BUFFERED_STDOUT_MAX_BYTES, runAcpDispatch, runPiRpcDispatch, } from './events.js';
|
|
97
98
|
export { armHandoffWatcher, } from './handoff.js';
|
|
98
99
|
//# sourceMappingURL=index.js.map
|
|
@@ -379,6 +379,6 @@ export declare function confirmChildSpawned({ input, deps }: {
|
|
|
379
379
|
* @complexity `run()`'s own setup is O(1); steady-state cost is the chosen stream parser's.
|
|
380
380
|
* @overallScore 100/100
|
|
381
381
|
*/
|
|
382
|
-
export declare function createAgentExecutor(requiredArgs: Pick<CreateAgentExecutorOptions, "lifecycle">, optionalArgs?: Pick<CreateAgentExecutorOptions, "runtimeState" | "codexDisablePlugins" | "piGracefulShutdownMs" | "hostEnv" | "getAgentDef" | "resolveAgentLaunch" | "resolveModelForLaunch" | "ensureAgentCapabilities" | "applyAgentLaunchEnv" | "createCommandInvocation" | "spawn" | "attachAcpSession" | "acpPermissionHandler" | "attachPiRpcSession" | "preparePromptFileForAgent" | "prepareAgentLogFile" | "listProcessSnapshots" | "collectProcessTreePids" | "stopProcesses" | "onCleanupFailure" | "journal" | "continuation" | "classifyFailure" | "mcpJsonInjection" | "claudeConfigDirIsolation" | "claudeConfigDirIsolationEnabled" | "bufferedStdoutMaxBytes" | "promptAugmenter">): AgentExecutor;
|
|
382
|
+
export declare function createAgentExecutor(requiredArgs: Pick<CreateAgentExecutorOptions, "lifecycle">, optionalArgs?: Pick<CreateAgentExecutorOptions, "runtimeState" | "codexDisablePlugins" | "piGracefulShutdownMs" | "hostEnv" | "getAgentDef" | "resolveAgentLaunch" | "resolveModelForLaunch" | "ensureAgentCapabilities" | "applyAgentLaunchEnv" | "createCommandInvocation" | "spawn" | "probeMcpBridge" | "attachAcpSession" | "acpPermissionHandler" | "attachPiRpcSession" | "preparePromptFileForAgent" | "prepareAgentLogFile" | "listProcessSnapshots" | "collectProcessTreePids" | "stopProcesses" | "onCleanupFailure" | "journal" | "continuation" | "classifyFailure" | "mcpJsonInjection" | "claudeConfigDirIsolation" | "claudeConfigDirIsolationEnabled" | "bufferedStdoutMaxBytes" | "promptAugmenter">): AgentExecutor;
|
|
383
383
|
export {};
|
|
384
384
|
//# sourceMappingURL=launch.d.ts.map
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { buildAgentEnv } from './agent-env.js';
|
|
2
|
+
import { probeMcpBridge } from './mcp-bridge-probe.js';
|
|
1
3
|
import { spawn as nodeSpawn, } from 'node:child_process';
|
|
2
4
|
import { promises as fsPromises, } from 'node:fs';
|
|
3
5
|
import { tmpdir, } from 'node:os';
|
|
@@ -95,47 +97,10 @@ function toStringEnvRecord(env) {
|
|
|
95
97
|
}
|
|
96
98
|
return result;
|
|
97
99
|
}
|
|
98
|
-
/**
|
|
99
|
-
* Fixed baseline of host environment variables every spawned agent subprocess may see by
|
|
100
|
-
* default — resolved from the host's env one name at a time, never derived programmatically
|
|
101
|
-
* from `process.env` as a bag, so this can't silently widen. SEC-001's deny-by-default fix; see
|
|
102
|
-
* `AgentExecutorRunInput.env`'s doc for the full threat model.
|
|
103
|
-
*/
|
|
104
|
-
const BASELINE_AGENT_ENV_KEYS = [
|
|
105
|
-
'PATH', 'HOME', 'USERPROFILE', 'TMPDIR', 'TEMP', 'TMP', 'SHELL',
|
|
106
|
-
'LANG', 'LC_ALL', 'LC_CTYPE',
|
|
107
|
-
// `USER` is required for a spawned `claude` CLI to find its own login/credential state — with
|
|
108
|
-
// it omitted (even though `HOME` is present), `claude` fails fast with "Not logged in · Please
|
|
109
|
-
// run /login" despite real credentials existing on disk/keychain. Confirmed by bisection against
|
|
110
|
-
// a real authenticated `claude` install: `BASELINE_AGENT_ENV_KEYS` alone fails, adding back every
|
|
111
|
-
// `CLAUDE_CODE_*`/`CLAUDECODE` var still fails, `LOGNAME`/`SSH_AUTH_SOCK` alone still fail, but
|
|
112
|
-
// `USER` alone flips it to success. Login lookup requires the user identity
|
|
113
|
-
// as well as the home directory.
|
|
114
|
-
'USER',
|
|
115
|
-
'SystemRoot', 'windir', 'ComSpec', 'PATHEXT', // Windows-only; harmless no-ops elsewhere
|
|
116
|
-
];
|
|
117
|
-
/**
|
|
118
|
-
* Deny-by-default agent subprocess environment: `BASELINE_AGENT_ENV_KEYS` resolved from
|
|
119
|
-
* `hostEnv`, plus this run's explicitly-delegated credential(s) — never a passthrough of
|
|
120
|
-
* `hostEnv` itself. Only reached when the caller omits `AgentExecutorRunInput.env`; supplying
|
|
121
|
-
* `env` bypasses this function entirely (see its call site).
|
|
122
|
-
*/
|
|
123
|
-
function buildAgentEnv(hostEnv, credentialEnv) {
|
|
124
|
-
const result = {};
|
|
125
|
-
for (const key of BASELINE_AGENT_ENV_KEYS) {
|
|
126
|
-
const value = hostEnv[key];
|
|
127
|
-
if (typeof value === 'string')
|
|
128
|
-
result[key] = value;
|
|
129
|
-
}
|
|
130
|
-
for (const [key, value] of Object.entries(credentialEnv ?? {})) {
|
|
131
|
-
result[key] = value;
|
|
132
|
-
}
|
|
133
|
-
return result;
|
|
134
|
-
}
|
|
135
100
|
/** Snapshot only host configuration used by spawn and config staging; credentials stay per-run.
|
|
136
101
|
* Returns an independent allowlisted record. @complexity O(1), fixed key list. */
|
|
137
102
|
export function selectAgentExecutorHostEnv({ env }, { runtimeState } = {}) {
|
|
138
|
-
const selected = buildAgentEnv(env
|
|
103
|
+
const selected = buildAgentEnv({ hostEnv: env });
|
|
139
104
|
for (const key of ['CODEX_HOME', 'CLAUDE_CONFIG_DIR', 'VELA_OPENCODE_BIN', 'AGENT_RUNTIME_HOME', 'AGENT_RUNTIME_RESOURCE_ROOT', 'VP_HOME', 'NPM_CONFIG_PREFIX', 'npm_config_prefix', 'APPDATA', 'LOCALAPPDATA', 'MISE_DATA_DIR', 'FNM_DIR']) {
|
|
140
105
|
if (typeof env[key] === 'string')
|
|
141
106
|
selected[key] = env[key];
|
|
@@ -239,7 +204,7 @@ export async function resolveImageDeliveryAndArgvBudget({ input, deps }) {
|
|
|
239
204
|
* Pure.
|
|
240
205
|
*/
|
|
241
206
|
export function resolveRunEnv({ input, hostEnv }) {
|
|
242
|
-
return input.env !== undefined ? toStringEnvRecord(input.env) : buildAgentEnv(hostEnv, input.credentialEnv);
|
|
207
|
+
return input.env !== undefined ? toStringEnvRecord(input.env) : buildAgentEnv({ hostEnv }, { credentialEnv: input.credentialEnv });
|
|
243
208
|
}
|
|
244
209
|
/** Phase 3b: launch-path resolution + binary-not-resolved guard. */
|
|
245
210
|
export async function resolveLaunch({ input, deps }) {
|
|
@@ -979,6 +944,11 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
979
944
|
classifyFailure,
|
|
980
945
|
bufferedStdoutMaxBytes,
|
|
981
946
|
expectsJiniBridge: writtenMcpJsonPath !== undefined,
|
|
947
|
+
// probeMcpBridge itself strips the run credential and swaps in a diagnostic run id.
|
|
948
|
+
bridgeProbeEntry: mcpBridge?.kind === 'claude-mcp-json' && mcpJsonInjection?.probeOnFailure !== false
|
|
949
|
+
? mcpBridge.serverEntry
|
|
950
|
+
: undefined,
|
|
951
|
+
probeMcpBridge: options.probeMcpBridge ?? probeMcpBridge,
|
|
982
952
|
})
|
|
983
953
|
: null;
|
|
984
954
|
// Registered with the lifecycle wiring above, before the spawn-confirmation await, so a child
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { spawn as nodeSpawn } from 'node:child_process';
|
|
2
|
+
export interface McpBridgeProbeResult {
|
|
3
|
+
readonly ok: boolean;
|
|
4
|
+
readonly exitCode: number | null;
|
|
5
|
+
readonly signal: string | null;
|
|
6
|
+
readonly stderrTail: string;
|
|
7
|
+
readonly elapsedMs: number;
|
|
8
|
+
}
|
|
9
|
+
/** Initialize a disposable bridge without a run credential; always reap it after the bounded probe.
|
|
10
|
+
* Spawn/errors/exits/timeouts return diagnostics rather than rejecting. Stderr retains 2000 bytes.
|
|
11
|
+
* @complexity O(n) in received bytes; stderr storage is bounded independently of child output.
|
|
12
|
+
*/
|
|
13
|
+
export declare function probeMcpBridge({ entry }: {
|
|
14
|
+
readonly entry: {
|
|
15
|
+
readonly command: string;
|
|
16
|
+
readonly args: readonly string[];
|
|
17
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
18
|
+
};
|
|
19
|
+
}, { spawn, baseEnv, timeoutMs, now }?: {
|
|
20
|
+
readonly spawn?: typeof nodeSpawn;
|
|
21
|
+
readonly baseEnv?: Readonly<Record<string, string>>;
|
|
22
|
+
readonly timeoutMs?: number;
|
|
23
|
+
readonly now?: () => number;
|
|
24
|
+
}): Promise<McpBridgeProbeResult>;
|
|
25
|
+
//# sourceMappingURL=mcp-bridge-probe.d.ts.map
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { spawn as nodeSpawn } from 'node:child_process';
|
|
2
|
+
import { createInterface } from 'node:readline';
|
|
3
|
+
import { redactSecrets } from '@jini-ai/core';
|
|
4
|
+
import { buildAgentEnv } from './agent-env.js';
|
|
5
|
+
import { errorMessage, isRecord } from './values.js';
|
|
6
|
+
/** Initialize a disposable bridge without a run credential; always reap it after the bounded probe.
|
|
7
|
+
* Spawn/errors/exits/timeouts return diagnostics rather than rejecting. Stderr retains 2000 bytes.
|
|
8
|
+
* @complexity O(n) in received bytes; stderr storage is bounded independently of child output.
|
|
9
|
+
*/
|
|
10
|
+
export async function probeMcpBridge({ entry }, { spawn = nodeSpawn, baseEnv = buildAgentEnv({ hostEnv: process.env }), timeoutMs = 15_000, now = Date.now } = {}) {
|
|
11
|
+
const startedAt = now();
|
|
12
|
+
const env = { ...baseEnv, ...entry.env, JINI_RUN_ID: 'bridge-probe' };
|
|
13
|
+
// Initialize needs no callback: never delegate a real run's authority to this diagnostic child.
|
|
14
|
+
delete env.JINI_DAEMON_TOKEN;
|
|
15
|
+
let child;
|
|
16
|
+
let lines;
|
|
17
|
+
let timer;
|
|
18
|
+
let stderr = Buffer.alloc(0);
|
|
19
|
+
const appendStderr = (chunk) => {
|
|
20
|
+
stderr = Buffer.concat([stderr, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)]).subarray(-2000);
|
|
21
|
+
};
|
|
22
|
+
const result = (ok, exitCode = null, signal = null) => ({
|
|
23
|
+
ok, exitCode, signal,
|
|
24
|
+
stderrTail: redactSecrets({ input: stderr.toString('utf8') }, {
|
|
25
|
+
extraPatterns: [{ name: 'daemon_token', regex: /JINI_DAEMON_TOKEN\s*=\s*\S+/gi }],
|
|
26
|
+
}).trim(),
|
|
27
|
+
elapsedMs: now() - startedAt,
|
|
28
|
+
});
|
|
29
|
+
try {
|
|
30
|
+
child = spawn(entry.command, [...entry.args], { env, stdio: 'pipe' });
|
|
31
|
+
const bridge = child;
|
|
32
|
+
lines = bridge.stdout ? createInterface({ input: bridge.stdout, crlfDelay: Infinity }) : undefined;
|
|
33
|
+
return await new Promise((resolve) => {
|
|
34
|
+
const fail = (error) => {
|
|
35
|
+
appendStderr(`\n${errorMessage(error)}`);
|
|
36
|
+
resolve(result(false));
|
|
37
|
+
};
|
|
38
|
+
timer = setTimeout(() => resolve(result(false)), timeoutMs);
|
|
39
|
+
bridge.once('error', fail);
|
|
40
|
+
bridge.once('exit', (code, signal) => resolve(result(false, code, signal)));
|
|
41
|
+
bridge.once('close', (code, signal) => resolve(result(false, code, signal)));
|
|
42
|
+
bridge.stderr?.on('data', appendStderr);
|
|
43
|
+
bridge.stdin?.once('error', fail);
|
|
44
|
+
if (lines) {
|
|
45
|
+
lines.once('error', fail);
|
|
46
|
+
lines.on('line', (line) => {
|
|
47
|
+
try {
|
|
48
|
+
const reply = JSON.parse(line);
|
|
49
|
+
if (isRecord(reply) && reply.id === 1 && Object.hasOwn(reply, 'result'))
|
|
50
|
+
resolve(result(true));
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// Startup chatter is not an initialize response; keep waiting for the matching reply.
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
bridge.stdin?.end(`${JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'initialize', params: {
|
|
58
|
+
protocolVersion: '2025-06-18', capabilities: {}, clientInfo: { name: 'jini-bridge-probe', version: '0' },
|
|
59
|
+
} })}\n`);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
appendStderr(`\n${errorMessage(error)}`);
|
|
64
|
+
return result(false);
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
clearTimeout(timer);
|
|
68
|
+
lines?.close();
|
|
69
|
+
try {
|
|
70
|
+
child?.kill('SIGKILL');
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// A process already gone or refusing cleanup must not discard the original probe diagnostic.
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=mcp-bridge-probe.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { McpBridgeProbeResult } from './mcp-bridge-probe.js';
|
|
1
2
|
import type { AcpMcpServerInput, RuntimeAgentDef } from '@jini-ai/agent-runtime';
|
|
2
3
|
import type { McpJsonInjectionOptions, McpJsonServerEntry, McpBridgeDelivery, PreparedCodexHome, PreparedClaudeConfigDir, ClaudeConfigDirIsolationOptions, FailBeforeSpawn } from './contracts.js';
|
|
3
4
|
/** The run-error code a run gets when its CLI reports the injected `jini` bridge as anything but connected. */
|
|
@@ -16,7 +17,7 @@ export declare function unavailableJiniBridgeStatus({ rawEvent }: {
|
|
|
16
17
|
readonly rawEvent: unknown;
|
|
17
18
|
}): string | undefined;
|
|
18
19
|
/** The user-readable reason a run stopped because its bridge did not connect. */
|
|
19
|
-
export declare function bridgeUnavailableMessage(status: string): string;
|
|
20
|
+
export declare function bridgeUnavailableMessage(status: string, probe?: McpBridgeProbeResult): string;
|
|
20
21
|
/**
|
|
21
22
|
* Builds this run's `mcpServers.jini` entry — pure and synchronous, so every field mapping is
|
|
22
23
|
* directly assertable without touching the filesystem. The credential arrives already resolved:
|
|
@@ -25,8 +25,13 @@ export function unavailableJiniBridgeStatus({ rawEvent }) {
|
|
|
25
25
|
return status === 'connected' ? undefined : status;
|
|
26
26
|
}
|
|
27
27
|
/** The user-readable reason a run stopped because its bridge did not connect. */
|
|
28
|
-
export function bridgeUnavailableMessage(status) {
|
|
29
|
-
|
|
28
|
+
export function bridgeUnavailableMessage(status, probe) {
|
|
29
|
+
const message = `The assistant's tools did not load (MCP bridge "${JINI_MCP_SERVER_KEY}" status: ${status}). The run was stopped instead of continuing without them.`;
|
|
30
|
+
if (!probe || probe.ok)
|
|
31
|
+
return message;
|
|
32
|
+
const lines = probe.stderrTail.trim().split(/\r?\n/);
|
|
33
|
+
const reason = lines.find((line) => line.includes('Error')) ?? lines.at(-1);
|
|
34
|
+
return `${message} Bridge exited with code ${probe.exitCode}: ${reason}`;
|
|
30
35
|
}
|
|
31
36
|
/**
|
|
32
37
|
* Builds this run's `mcpServers.jini` entry — pure and synchronous, so every field mapping is
|
package/dist/agent-executor.d.ts
CHANGED
|
@@ -6,5 +6,5 @@
|
|
|
6
6
|
* wrappers or a second implementation would let tested and shipped behavior drift.
|
|
7
7
|
* See agent-executor/index.ts for the driver contracts and invariants.
|
|
8
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 UserMessageDelivery, 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, selectAgentExecutorHostEnv, 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';
|
|
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 UserMessageDelivery, type CollectProcessTreePidsPort, type StopProcessesPort, type AgentCleanupFailurePhase, type AgentCleanupFailureContext, type ContinuationOptions, type McpJsonInjectionOptions, MCP_BRIDGE_UNAVAILABLE, probeMcpBridge, type McpBridgeProbeResult, 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, selectAgentExecutorHostEnv, 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';
|
|
10
10
|
//# sourceMappingURL=agent-executor.d.ts.map
|
package/dist/agent-executor.js
CHANGED
|
@@ -6,5 +6,5 @@
|
|
|
6
6
|
* wrappers or a second implementation would let tested and shipped behavior drift.
|
|
7
7
|
* See agent-executor/index.ts for the driver contracts and invariants.
|
|
8
8
|
*/
|
|
9
|
-
export { isSupportedStreamFormat, assessAgentExecutorCompatibility, isAgentExecutorSupported, extractUsageTokens, translateStatusEvent, translateToolResultEvent, translateErrorEvent, translateTurnEndEvent, translateAgentRuntimeEvent, AgentExecutorError, MCP_BRIDGE_UNAVAILABLE, unavailableJiniBridgeStatus, buildMcpJsonServerEntry, mergeMcpJsonContent, buildAcpMcpBridgeServers, mergeEnvContentMcpConfig, mergeEnvContentInstructions, buildCodexMcpServerToml, buildCodexHomeConfigToml, resolveSourceCodexHomeDir, buildMcpBridgeDelivery, resolveSourceClaudeConfigDir, DEFAULT_BUFFERED_STDOUT_MAX_BYTES, applyAgentTranslationSideEffects, resolveDefAndStreamFormat, resolveImageDeliveryAndArgvBudget, resolveRunEnv, selectAgentExecutorHostEnv, resolveLaunch, stagePromptFile, stageLogFile, resolveMcpBridgeForRun, computeChildEnv, computeRuntimeContext, buildAgentBuildArgsOptions, resolveSystemPromptOverlayDelivery, buildRunArgs, writeMcpJsonIfNeeded, prepareSystemPromptOverlayFileIfNeeded, prepareCodexHomeIfNeeded, prepareClaudeConfigDirIfNeeded, guardWindowsCommandLineBudget, spawnAgentChildProcess, isStdinDrivenFormat, confirmChildSpawned, armHandoffWatcher, runAcpDispatch, runPiRpcDispatch, createAgentExecutor, } from './agent-executor/index.js';
|
|
9
|
+
export { isSupportedStreamFormat, assessAgentExecutorCompatibility, isAgentExecutorSupported, extractUsageTokens, translateStatusEvent, translateToolResultEvent, translateErrorEvent, translateTurnEndEvent, translateAgentRuntimeEvent, AgentExecutorError, MCP_BRIDGE_UNAVAILABLE, probeMcpBridge, unavailableJiniBridgeStatus, buildMcpJsonServerEntry, mergeMcpJsonContent, buildAcpMcpBridgeServers, mergeEnvContentMcpConfig, mergeEnvContentInstructions, buildCodexMcpServerToml, buildCodexHomeConfigToml, resolveSourceCodexHomeDir, buildMcpBridgeDelivery, resolveSourceClaudeConfigDir, DEFAULT_BUFFERED_STDOUT_MAX_BYTES, applyAgentTranslationSideEffects, resolveDefAndStreamFormat, resolveImageDeliveryAndArgvBudget, resolveRunEnv, selectAgentExecutorHostEnv, resolveLaunch, stagePromptFile, stageLogFile, resolveMcpBridgeForRun, computeChildEnv, computeRuntimeContext, buildAgentBuildArgsOptions, resolveSystemPromptOverlayDelivery, buildRunArgs, writeMcpJsonIfNeeded, prepareSystemPromptOverlayFileIfNeeded, prepareCodexHomeIfNeeded, prepareClaudeConfigDirIfNeeded, guardWindowsCommandLineBudget, spawnAgentChildProcess, isStdinDrivenFormat, confirmChildSpawned, armHandoffWatcher, runAcpDispatch, runPiRpcDispatch, createAgentExecutor, } from './agent-executor/index.js';
|
|
10
10
|
//# sourceMappingURL=agent-executor.js.map
|
|
@@ -33,8 +33,8 @@ export interface DelegatedToolInvocation {
|
|
|
33
33
|
export interface DelegatedToolBridge {
|
|
34
34
|
/**
|
|
35
35
|
* Emits a canonical `tool_use`, executes through `ToolExecutor`, then emits
|
|
36
|
-
* exactly one matching `tool_result`. Unknown tool ids
|
|
37
|
-
*
|
|
36
|
+
* exactly one matching `tool_result`. Unknown tool ids are input errors
|
|
37
|
+
* from `ToolExecutor` and are rethrown after their error
|
|
38
38
|
* result is recorded.
|
|
39
39
|
*/
|
|
40
40
|
execute(requiredArgs: Pick<DelegatedToolInvocation, "runId" | "toolUseId" | "toolId" | "principal" | "input">, optionalArgs?: Pick<DelegatedToolInvocation, "signal">): Promise<ToolExecutionResult>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Express } from 'express';
|
|
2
|
-
import type
|
|
2
|
+
import { type Principal, type ToolRegistry } from '@jini-ai/core';
|
|
3
3
|
import { type RunLifecycle, type ToolExecutionResult, type ToolExecutor } from '../index.js';
|
|
4
4
|
import { type AdapterContext } from '@jini-ai/http-kit';
|
|
5
5
|
export interface DelegatedToolExecuteRequest {
|
|
@@ -81,7 +81,7 @@ export interface DelegatedToolsHttpDeps {
|
|
|
81
81
|
/**
|
|
82
82
|
* Host opt-in: the model-safe text to send in place of `'an internal error occurred'` for every
|
|
83
83
|
* failure this route would otherwise answer with the SEC-005-redacted `500 INTERNAL_ERROR` — a
|
|
84
|
-
* thrown executor
|
|
84
|
+
* thrown executor infrastructure failure, a throwing `resolvePrincipal`, `timed-out`,
|
|
85
85
|
* `cancelled`, and a `failed` result `isModelSafeToolFailure` did not vouch for. Without it, those
|
|
86
86
|
* reach the model as a bare "INTERNAL_ERROR" it can neither act on nor report.
|
|
87
87
|
*
|
|
@@ -98,8 +98,8 @@ export interface DelegatedToolsHttpDeps {
|
|
|
98
98
|
* `POST /api/delegated-tool-calls` — executes one delegated tool call against an already-started
|
|
99
99
|
* run. Maps `ToolExecutionResult.status` to the HTTP response via `toolExecutionResultToApiResult`
|
|
100
100
|
* above — mirroring `db-ops.ts`'s precedent for the identical status union rather than treating
|
|
101
|
-
* every business outcome as a `200`.
|
|
102
|
-
*
|
|
101
|
+
* every business outcome as a `200`. An unregistered `toolId` throws `ToolInputError` and answers
|
|
102
|
+
* `400 BAD_REQUEST`. A genuinely unexpected throw (such as a race where the run
|
|
103
103
|
* became terminal between this route's existence check and the bridge's first emitted event)
|
|
104
104
|
* reaches the same SEC-005 redaction path.
|
|
105
105
|
*/
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
*/
|
|
28
28
|
import { randomUUID } from 'node:crypto';
|
|
29
29
|
import { createApiError } from '@jini-ai/protocol';
|
|
30
|
+
import { ToolInputError } from '@jini-ai/core';
|
|
30
31
|
import { createDelegatedToolBridge } from '../index.js';
|
|
31
32
|
import { defineJsonRoute, mountJsonRoute } from '@jini-ai/http-kit';
|
|
32
33
|
import { validationError } from '@jini-ai/http-kit';
|
|
@@ -159,8 +160,8 @@ function toolExecutionResultToApiResult(deps, runId, toolId, result) {
|
|
|
159
160
|
* `POST /api/delegated-tool-calls` — executes one delegated tool call against an already-started
|
|
160
161
|
* run. Maps `ToolExecutionResult.status` to the HTTP response via `toolExecutionResultToApiResult`
|
|
161
162
|
* above — mirroring `db-ops.ts`'s precedent for the identical status union rather than treating
|
|
162
|
-
* every business outcome as a `200`.
|
|
163
|
-
*
|
|
163
|
+
* every business outcome as a `200`. An unregistered `toolId` throws `ToolInputError` and answers
|
|
164
|
+
* `400 BAD_REQUEST`. A genuinely unexpected throw (such as a race where the run
|
|
164
165
|
* became terminal between this route's existence check and the bridge's first emitted event)
|
|
165
166
|
* reaches the same SEC-005 redaction path.
|
|
166
167
|
*/
|
|
@@ -219,6 +220,9 @@ export const delegatedToolExecuteRoute = defineJsonRoute({
|
|
|
219
220
|
return toolExecutionResultToApiResult(deps, input.runId, input.toolId, result);
|
|
220
221
|
}
|
|
221
222
|
catch (error) {
|
|
223
|
+
if (error instanceof ToolInputError) {
|
|
224
|
+
return err({ error: createApiError({ code: 'BAD_REQUEST', message: error.message }) });
|
|
225
|
+
}
|
|
222
226
|
return err({ error: reportInternalError(deps, 'delegated-tool-execute', error, input.runId, input.toolId) });
|
|
223
227
|
}
|
|
224
228
|
}
|
package/dist/tool-executor.d.ts
CHANGED
|
@@ -83,7 +83,7 @@ export interface ToolExecutor {
|
|
|
83
83
|
* @param emitSurface - Transport-supplied seam letting the handler push a human-only surface into
|
|
84
84
|
* the run's event stream *before* this call settles. Omit for a caller with no run event stream;
|
|
85
85
|
* see `@jini-ai/core`'s `SurfaceEmitter` for why a handler that waits on a human needs it.
|
|
86
|
-
* @throws If `toolId` isn't registered
|
|
86
|
+
* @throws {ToolInputError} If `toolId` isn't registered, with up to eight registered prefix matches.
|
|
87
87
|
*/
|
|
88
88
|
execute(args: {
|
|
89
89
|
readonly principal: Principal;
|
|
@@ -125,7 +125,7 @@ export interface CreateToolExecutorOptions {
|
|
|
125
125
|
* — the one and only caller of that internal export.
|
|
126
126
|
* @param options.delegate - Transport-supplied authorize/confirm UI seam;
|
|
127
127
|
* omit for a headless caller whose tools never need interactive gating.
|
|
128
|
-
* @complexity `execute` is O(1) plus the handler's own cost; `resumeConfirmation`/`cancel`/`getAuditRecord` are O(1) map lookups.
|
|
128
|
+
* @complexity Known-id `execute` is O(1) plus the handler's own cost; unknown ids scan O(n) descriptors for hints. `resumeConfirmation`/`cancel`/`getAuditRecord` are O(1) map lookups.
|
|
129
129
|
* @overallScore 100/100
|
|
130
130
|
*/
|
|
131
131
|
export declare function createToolExecutor(requiredArgs: Pick<CreateToolExecutorOptions, "registry">, optionalArgs?: Pick<CreateToolExecutorOptions, "delegate" | "now">): ToolExecutor;
|
package/dist/tool-executor.js
CHANGED
|
@@ -81,7 +81,7 @@ function truncateOutput(output, maxOutputBytes) {
|
|
|
81
81
|
* — the one and only caller of that internal export.
|
|
82
82
|
* @param options.delegate - Transport-supplied authorize/confirm UI seam;
|
|
83
83
|
* omit for a headless caller whose tools never need interactive gating.
|
|
84
|
-
* @complexity `execute` is O(1) plus the handler's own cost; `resumeConfirmation`/`cancel`/`getAuditRecord` are O(1) map lookups.
|
|
84
|
+
* @complexity Known-id `execute` is O(1) plus the handler's own cost; unknown ids scan O(n) descriptors for hints. `resumeConfirmation`/`cancel`/`getAuditRecord` are O(1) map lookups.
|
|
85
85
|
* @overallScore 100/100
|
|
86
86
|
*/
|
|
87
87
|
export function createToolExecutor(requiredArgs, optionalArgs = {}) {
|
|
@@ -182,7 +182,9 @@ export function createToolExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
182
182
|
// existence, never returns a handler, so this doesn't reopen the leak authorizeToolInvocation
|
|
183
183
|
// closed — that gate still runs, still unconditionally, inside the call below.
|
|
184
184
|
if (!registry.has({ toolId })) {
|
|
185
|
-
|
|
185
|
+
const candidates = registry.list({}).map((tool) => tool.id).filter((id) => id.startsWith(toolId)).slice(0, 8);
|
|
186
|
+
const hint = candidates.length > 0 ? `; did you mean ${candidates.join(', ')}?` : '';
|
|
187
|
+
throw new ToolInputError({ message: `ToolExecutor: unknown tool "${toolId}"${hint}` });
|
|
186
188
|
}
|
|
187
189
|
const executionId = openAudit(principal, run, toolId);
|
|
188
190
|
// Observes `signal` for this execution's ENTIRE remaining lifetime, not just the handler-run
|
|
@@ -216,7 +218,7 @@ export function createToolExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
216
218
|
// Recorded terminal and returned as `failed` with a fixed message. `failed` is the right state:
|
|
217
219
|
// this execution ended, and it ended for an operational reason rather than a decision. The real
|
|
218
220
|
// exception stays server-side. Deliberately narrower than the `unknown tool` throw above, which
|
|
219
|
-
// stays a throw because
|
|
221
|
+
// stays a typed input-error throw because no tool execution exists to settle.
|
|
220
222
|
let resolved;
|
|
221
223
|
try {
|
|
222
224
|
resolved = await authorize(principal, run, toolId, input);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Local federation forks moved to @jini-ai/mcp/federation (+ /stdio, /approvals); see development/DELETED-CODE.md.
|
|
2
2
|
/**
|
|
3
3
|
* `ToolExecutor.execute`'s own contract for an unregistered id (`@jini-ai/daemon`'s
|
|
4
|
-
* `tool-executor.ts`): a thrown `
|
|
5
|
-
*
|
|
4
|
+
* `tool-executor.ts`): a thrown `ToolInputError` (from `@jini-ai/core`) reading `unknown tool "<id>"`.
|
|
5
|
+
* This still matches on the message text, not the class. Duplicated from `tool-executor-audit.ts`'s identical `isUnknownToolError` rather
|
|
6
6
|
* than imported: that file is a sibling one layer up with no dependency on `mcp-federation`, and
|
|
7
7
|
* importing across that line for an 18-character regex is not worth the coupling. Both copies carry
|
|
8
8
|
* the same MEDIUM message-text-coupling note that file's own doc records — if `@jini-ai/daemon`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jini-ai/daemon",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.7",
|
|
4
4
|
"description": "The stateful agent daemon runtime: run lifecycle, durable event-log port, the tool-execution boundary, agent executor, terminal sessions, and routines.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -139,17 +139,17 @@
|
|
|
139
139
|
"@types/pg": "8.23.1",
|
|
140
140
|
"express": "^4.21.0",
|
|
141
141
|
"@types/express": "^4.17.21",
|
|
142
|
-
"@jini-ai/http-kit": "0.4.2",
|
|
143
|
-
"@jini-ai/chat": "0.6.2",
|
|
144
|
-
"@jini-ai/core": "^0.4.6",
|
|
145
142
|
"@jini-ai/agent-runtime": "^0.4.11",
|
|
146
|
-
"@jini-ai/
|
|
143
|
+
"@jini-ai/chat": "0.6.5",
|
|
147
144
|
"@jini-ai/protocol": "^0.4.4",
|
|
145
|
+
"@jini-ai/core": "^0.4.6",
|
|
146
|
+
"@jini-ai/platform": "^0.5.8",
|
|
147
|
+
"@jini-ai/http-kit": "0.4.2",
|
|
148
148
|
"@jini-ai/db": "0.3.4"
|
|
149
149
|
},
|
|
150
150
|
"dependencies": {
|
|
151
|
-
"@jini-ai/
|
|
152
|
-
"@jini-ai/
|
|
151
|
+
"@jini-ai/protocol": "^0.4.4",
|
|
152
|
+
"@jini-ai/core": "^0.4.6"
|
|
153
153
|
},
|
|
154
154
|
"peerDependencies": {
|
|
155
155
|
"node-pty": "^1.1.0",
|