@jini-ai/daemon 0.6.6 → 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 +4 -0
- 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,7 @@
|
|
|
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
|
+
|
|
1
5
|
## 0.6.6 — 2026-10-14
|
|
2
6
|
|
|
3
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.
|
|
@@ -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,13 +139,13 @@
|
|
|
139
139
|
"@types/pg": "8.23.1",
|
|
140
140
|
"express": "^4.21.0",
|
|
141
141
|
"@types/express": "^4.17.21",
|
|
142
|
-
"@jini-ai/db": "0.3.4",
|
|
143
|
-
"@jini-ai/http-kit": "0.4.2",
|
|
144
|
-
"@jini-ai/platform": "^0.5.7",
|
|
145
|
-
"@jini-ai/core": "^0.4.6",
|
|
146
142
|
"@jini-ai/agent-runtime": "^0.4.11",
|
|
147
|
-
"@jini-ai/chat": "0.6.
|
|
148
|
-
"@jini-ai/protocol": "^0.4.4"
|
|
143
|
+
"@jini-ai/chat": "0.6.5",
|
|
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
|
+
"@jini-ai/db": "0.3.4"
|
|
149
149
|
},
|
|
150
150
|
"dependencies": {
|
|
151
151
|
"@jini-ai/protocol": "^0.4.4",
|