@jini-ai/daemon 0.5.3 → 0.6.0
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/agent-executor/contracts.d.ts +45 -14
- package/dist/agent-executor/contracts.js +0 -9
- package/dist/agent-executor/event-translation.d.ts +2 -2
- package/dist/agent-executor/event-translation.js +0 -2
- package/dist/agent-executor/events.d.ts +26 -6
- package/dist/agent-executor/events.js +198 -11
- package/dist/agent-executor/handoff.d.ts +1 -1
- package/dist/agent-executor/handoff.js +0 -1
- package/dist/agent-executor/index.d.ts +1 -1
- package/dist/agent-executor/launch.d.ts +1 -1
- package/dist/agent-executor/launch.js +47 -3
- package/dist/agent-executor/mcp-bridge.d.ts +2 -2
- package/dist/agent-executor/mcp-bridge.js +19 -8
- package/dist/agent-executor/model-receipts.d.ts +6 -0
- package/dist/agent-executor/model-receipts.js +26 -0
- package/dist/agent-executor.d.ts +7 -1665
- package/dist/agent-executor.js +7 -3007
- package/dist/core/read-only-tools.d.ts +130 -0
- package/dist/core/read-only-tools.js +115 -0
- package/dist/core/tool-audit.d.ts +131 -0
- package/dist/core/tool-audit.js +113 -0
- package/dist/http/active-context.d.ts +2 -2
- package/dist/http/agents.d.ts +8 -2
- package/dist/http/agents.js +6 -2
- package/dist/http/component-catalog.d.ts +2 -2
- package/dist/http/connectors.d.ts +17 -17
- package/dist/http/daemon-status.d.ts +2 -2
- package/dist/http/db-ops.d.ts +3 -3
- package/dist/http/delegated-tools.d.ts +1 -1
- package/dist/http/delegated-tools.js +1 -1
- package/dist/http/frontend-sessions.d.ts +1 -1
- package/dist/http/health.d.ts +6 -6
- package/dist/http/host-tools.d.ts +2 -2
- package/dist/http/index.d.ts +2 -2
- package/dist/http/index.js +1 -1
- package/dist/http/media.d.ts +4 -4
- package/dist/http/memory.d.ts +15 -15
- package/dist/http/remote-run-events.d.ts +2 -2
- package/dist/http/research.d.ts +1 -1
- package/dist/http/route-manifest.js +2 -2
- package/dist/http/routines.d.ts +7 -7
- package/dist/http/run-credentials.d.ts +2 -2
- package/dist/http/run-credentials.js +2 -2
- package/dist/http/runs.d.ts +31 -6
- package/dist/http/runs.js +36 -1
- package/dist/http/terminals.d.ts +6 -6
- package/dist/http/tool-catalog.d.ts +2 -2
- package/dist/http/xai.d.ts +6 -6
- package/dist/index.d.ts +75 -571
- package/dist/index.js +75 -571
- package/dist/read-only-tools.d.ts +1 -129
- package/dist/read-only-tools.js +2 -114
- package/dist/run-credentials.d.ts +47 -23
- package/dist/run-credentials.js +24 -8
- package/dist/scheduler.d.ts +5 -0
- package/dist/scheduler.js +13 -1
- package/dist/session-coordination.d.ts +58 -24
- package/dist/session-coordination.js +40 -12
- package/dist/store/event-log/sqlite.js +3 -3
- package/dist/surface-exchanges.d.ts +207 -61
- package/dist/surface-exchanges.js +167 -20
- package/dist/tool-audit.d.ts +1 -113
- package/dist/tool-audit.js +2 -94
- package/dist/tool-recovery/diagnostics.d.ts +132 -0
- package/dist/tool-recovery/diagnostics.js +110 -0
- package/dist/tool-recovery/federated-refusal.d.ts +92 -0
- package/dist/tool-recovery/federated-refusal.js +152 -0
- package/dist/tool-recovery/index.d.ts +6 -0
- package/dist/tool-recovery/index.js +6 -0
- package/dist/tool-recovery/recovery.d.ts +49 -0
- package/dist/tool-recovery/recovery.js +419 -0
- package/dist/tool-recovery/redaction.d.ts +166 -0
- package/dist/tool-recovery/redaction.js +192 -0
- package/dist/tool-recovery/stack.d.ts +69 -0
- package/dist/tool-recovery/stack.js +62 -0
- package/package.json +35 -12
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Blanks secret-shaped values out of a FAILED tool's error text and mints a durable, copyable
|
|
3
|
+
* error ID for the ones that need server-side follow-up — the 2026-09-16 owner decision ("hide
|
|
4
|
+
* secrets only"): a failed tool's error stays visible everywhere it already reaches (the model, the
|
|
5
|
+
* chat transcript, the tool card, admin BYOK, the daemon's delegated-tool route, the MCP-UI
|
|
6
|
+
* redemption endpoint, http-kit's server log), only a secret-shaped VALUE inside it never does.
|
|
7
|
+
*
|
|
8
|
+
* ## Position — see `tool-executor-stack.ts`'s own header
|
|
9
|
+
* Sits between `withReadOnlyToolConstraint` and `withToolAttemptAudit`, and INSIDE
|
|
10
|
+
* `withToolFailureRecovery` (which calls `inner` for the original call, the remedy, and the retry —
|
|
11
|
+
* each one redacted here). A read-only refusal is set by `withToolFailureRecovery` on the OUTER
|
|
12
|
+
* result, after this layer has already run, so it is never touched by it.
|
|
13
|
+
*
|
|
14
|
+
* ## Behavior by result
|
|
15
|
+
* | Result | Action |
|
|
16
|
+
* |---|---|
|
|
17
|
+
* | `failed` with `errorKind` `'internal'` or missing | Redact, mint an ID, prefix `Error <ID>: `, add `errorId`, report one {@link ToolFailureRecord} |
|
|
18
|
+
* | `failed` with `errorKind: 'validation'` | Redact only — no ID, no record. Byte-identical when nothing secret-shaped is present |
|
|
19
|
+
* | any other status | Returned as the SAME object, untouched |
|
|
20
|
+
* | inner throws | Rethrown untouched |
|
|
21
|
+
*
|
|
22
|
+
* A validation failure is the caller's own input problem: its message is already the full, safe
|
|
23
|
+
* reason, and nothing about it needs a server-side lookup — see `contracts/core/secret-redaction.ts`
|
|
24
|
+
* for why only the VALUE is ever blanked, never the surrounding text.
|
|
25
|
+
*
|
|
26
|
+
* Architectural role: `src/assistant` composition-layer adapter, same layer as
|
|
27
|
+
* `tool-executor-audit.ts` and `tool-failure-recovery.ts`. Depends only on the Tovu policy
|
|
28
|
+
* adapter over `@jini-ai/diagnostics/redaction/secrets-only` and `@jini-ai/daemon`'s `ToolExecutor` shape.
|
|
29
|
+
*/
|
|
30
|
+
/** Matches a minted error ID anywhere in a string — exported for tests and for a later chat-side
|
|
31
|
+
* copy button (`ToolCard` matching this against `result.content`, tracked separately). */
|
|
32
|
+
export const TOOL_ERROR_ID_PATTERN = /\bERR-[0-9A-F]{4}(?:-[0-9A-F]{4}){3}\b/;
|
|
33
|
+
/**
|
|
34
|
+
* Mints one error ID: 64 bits of `crypto.randomBytes`, formatted `ERR-XXXX-XXXX-XXXX-XXXX`.
|
|
35
|
+
*
|
|
36
|
+
* @returns A string always matching {@link TOOL_ERROR_ID_PATTERN}.
|
|
37
|
+
* @complexity O(1).
|
|
38
|
+
*/
|
|
39
|
+
export function mintToolErrorId({ randomHex }, _optional = {}) {
|
|
40
|
+
const hex = randomHex({ bytes: 8 }).toUpperCase();
|
|
41
|
+
const groups = hex.match(/.{4}/g) ?? [];
|
|
42
|
+
return `ERR-${groups.join("-")}`;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Reads the error ID this decorator attached, if any.
|
|
46
|
+
*
|
|
47
|
+
* @param result - Any `ToolExecutionResult`, redacted or not.
|
|
48
|
+
* @returns The ID, or `undefined` for a result this decorator never touched (not `failed`,
|
|
49
|
+
* `errorKind: 'validation'`, or produced upstream of this layer).
|
|
50
|
+
* @complexity O(1).
|
|
51
|
+
*/
|
|
52
|
+
export function readToolErrorId({ result }, _optional = {}) {
|
|
53
|
+
const id = result.errorId;
|
|
54
|
+
return typeof id === "string" ? id : undefined;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* `@jini-ai/http-kit`'s `isModelSafeToolFailure` predicate for the delegated-tool route: `true` only
|
|
58
|
+
* for a failure this decorator already redacted and minted an ID for, so the model gets
|
|
59
|
+
* `Error <ID>: <reason>` instead of an opaque `INTERNAL_ERROR`. A failure without the ID never
|
|
60
|
+
* passed through this layer and keeps http-kit's SEC-005 redaction.
|
|
61
|
+
*
|
|
62
|
+
* @param result - The settled result the route is about to map.
|
|
63
|
+
* @returns Whether the route may show `result.error` to the model verbatim.
|
|
64
|
+
* @complexity O(1).
|
|
65
|
+
*/
|
|
66
|
+
export function isRedactedToolFailure({ result }, _optional = {}) {
|
|
67
|
+
return readToolErrorId({ result }, {}) !== undefined;
|
|
68
|
+
}
|
|
69
|
+
/** The unredacted reason for one delegated-route internal error, before {@link redactSecretShapes}. */
|
|
70
|
+
function delegatedInternalErrorReason(context) {
|
|
71
|
+
if (context.status === "timed-out")
|
|
72
|
+
return `tool "${context.toolId}" timed out before it finished`;
|
|
73
|
+
if (context.status === "cancelled") {
|
|
74
|
+
return `tool "${context.toolId}" was cancelled before it finished (the run ended or the call was abandoned)`;
|
|
75
|
+
}
|
|
76
|
+
if (context.error instanceof Error && context.error.message.length > 0)
|
|
77
|
+
return context.error.message;
|
|
78
|
+
if (typeof context.error === "string" && context.error.length > 0)
|
|
79
|
+
return context.error;
|
|
80
|
+
return `tool "${context.toolId}" failed`;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* `@jini-ai/http-kit`'s `describeInternalError` for the agent daemon's delegated-tool route: the
|
|
84
|
+
* text a model gets in place of the bare `INTERNAL_ERROR: an internal error occurred` for every
|
|
85
|
+
* failure that route would otherwise redact whole — an unknown tool id (the executor throws), a
|
|
86
|
+
* throwing `resolvePrincipal`, a `timed-out` or `cancelled` result, and a `failed` result that never
|
|
87
|
+
* passed through {@link withRedactedToolFailures}. Same "hide secrets only" rule and the same
|
|
88
|
+
* `Error <ID>: <reason>` shape as that decorator, with one {@link ToolFailureRecord} per call.
|
|
89
|
+
*
|
|
90
|
+
* There is no execution or resolved principal to record for a throw, so the record carries
|
|
91
|
+
* http-kit's `correlationId` in `executionId` instead — the id that joins it to http-kit's own log
|
|
92
|
+
* line for the same failure.
|
|
93
|
+
*
|
|
94
|
+
* @param context - The failure as http-kit reports it.
|
|
95
|
+
* @param deps - Test seams; production passes nothing.
|
|
96
|
+
* @returns `Error <ID>: <redacted reason>`.
|
|
97
|
+
* @complexity O(reason length) for the redaction pass.
|
|
98
|
+
*/
|
|
99
|
+
export function describeDelegatedInternalError({ context, ...deps }, _optional = {}) {
|
|
100
|
+
const { text, redactions } = deps.redactSecretShapes({ text: delegatedInternalErrorReason(context) });
|
|
101
|
+
const errorId = deps.mintErrorId();
|
|
102
|
+
safeOnFailure(deps.onFailure, {
|
|
103
|
+
errorId,
|
|
104
|
+
toolId: context.toolId,
|
|
105
|
+
runId: context.runId,
|
|
106
|
+
principalId: "(unresolved)",
|
|
107
|
+
executionId: `(none) correlationId=${context.correlationId}`,
|
|
108
|
+
redactions,
|
|
109
|
+
message: text,
|
|
110
|
+
});
|
|
111
|
+
return `Error ${errorId}: ${text}`;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The two error-disclosure options the agent daemon spreads into `registerDelegatedToolRoutes`, as
|
|
115
|
+
* one value so the daemon and its tests wire the identical pair: a failure this layer already
|
|
116
|
+
* ID-tagged answers `422 TOOL_EXECUTION_FAILED`, and every other internal error answers `500` with
|
|
117
|
+
* {@link describeDelegatedInternalError}'s text.
|
|
118
|
+
*
|
|
119
|
+
* @param deps - Test seams forwarded to the describer; production passes nothing.
|
|
120
|
+
* @complexity O(1).
|
|
121
|
+
*/
|
|
122
|
+
export function delegatedToolErrorDisclosure(deps, _optional = {}) {
|
|
123
|
+
return {
|
|
124
|
+
isModelSafeToolFailure: ({ result }) => isRedactedToolFailure({ result }, {}),
|
|
125
|
+
describeInternalError: (context) => describeDelegatedInternalError({ context, ...deps }, {}),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/** Calls `onFailure`, containing a throw so a hostile or buggy sink can never turn a
|
|
129
|
+
* redacted result into a thrown error — audit is observation, never a gate, the same rule
|
|
130
|
+
* `tool-executor-audit.ts`'s `appendSafely` enforces for its own sink. */
|
|
131
|
+
function safeOnFailure(onFailure, record) {
|
|
132
|
+
try {
|
|
133
|
+
onFailure(record);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// A failing observer must neither gate the result nor leak its own exception text.
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Redacts one `failed` result per this file's Behavior table — the only place this file makes a
|
|
141
|
+
* decision; `withRedactedToolFailures` itself is pure plumbing around this.
|
|
142
|
+
*
|
|
143
|
+
* @complexity O(1) plus {@link redactSecretShapes}'s own O(text length) pass.
|
|
144
|
+
*/
|
|
145
|
+
function redactFailedResult(result, context, deps) {
|
|
146
|
+
if (result.errorKind === "validation") {
|
|
147
|
+
if (result.error === undefined)
|
|
148
|
+
return result;
|
|
149
|
+
return { ...result, error: deps.redactSecretShapes({ text: result.error }).text };
|
|
150
|
+
}
|
|
151
|
+
const mintErrorId = deps.mintErrorId;
|
|
152
|
+
const onFailure = deps.onFailure;
|
|
153
|
+
const { text, redactions } = deps.redactSecretShapes({ text: result.error ?? `tool "${context.toolId}" failed` });
|
|
154
|
+
const errorId = mintErrorId();
|
|
155
|
+
safeOnFailure(onFailure, {
|
|
156
|
+
errorId,
|
|
157
|
+
toolId: context.toolId,
|
|
158
|
+
runId: context.runId,
|
|
159
|
+
principalId: context.principalId,
|
|
160
|
+
executionId: result.executionId,
|
|
161
|
+
redactions,
|
|
162
|
+
message: text,
|
|
163
|
+
});
|
|
164
|
+
const redacted = { ...result, error: `Error ${errorId}: ${text}`, errorId };
|
|
165
|
+
return redacted;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Wraps `inner` so every `failed` result it produces is redacted per this file's Behavior table
|
|
169
|
+
* before any caller — recovery, the BYOK map, the delegated-tool bridge, the MCP-UI route, or
|
|
170
|
+
* http-kit's log — ever sees it. Every other status, and every throw, passes through unchanged.
|
|
171
|
+
*
|
|
172
|
+
* @param inner - The executor to wrap. Its own decisions (status, `errorKind`, throwing) are never
|
|
173
|
+
* second-guessed here — this layer only ever transforms an already-`failed` result's `error` text.
|
|
174
|
+
* @param deps - Test seams; production callers pass nothing and get the real minter and logger.
|
|
175
|
+
* @returns A drop-in `ToolExecutor`. `resumeConfirmation`, `cancel`, `getAuditRecord` delegate
|
|
176
|
+
* straight through, matching `tool-failure-recovery.ts`'s and `tool-executor-audit.ts`'s own shape.
|
|
177
|
+
* @complexity O(1) beyond `inner`'s own cost and {@link redactFailedResult}'s.
|
|
178
|
+
*/
|
|
179
|
+
export function withRedactedToolFailures({ inner, ...deps }, _optional = {}) {
|
|
180
|
+
return {
|
|
181
|
+
execute: async ({ principal, run, toolId, input }, { signal, emitSurface } = {}) => {
|
|
182
|
+
const result = await inner.execute({ principal, run, toolId, input }, { ...(signal === undefined ? {} : { signal }), ...(emitSurface === undefined ? {} : { emitSurface }) });
|
|
183
|
+
if (result.status !== "failed")
|
|
184
|
+
return result;
|
|
185
|
+
return redactFailedResult(result, { toolId, runId: run.id, principalId: principal.id }, deps);
|
|
186
|
+
},
|
|
187
|
+
resumeConfirmation: (args) => inner.resumeConfirmation(args),
|
|
188
|
+
cancel: (executionId) => inner.cancel(executionId),
|
|
189
|
+
getAuditRecord: (executionId) => inner.getAuditRecord(executionId),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=redaction.js.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The one place Tovu's `ToolExecutor` decorator stack is composed.
|
|
3
|
+
*
|
|
4
|
+
* The order of these three wrappers is load-bearing in two different directions, and it was
|
|
5
|
+
* previously spelled out only as an expression inside `agent-daemon-server.ts` — a module that boots
|
|
6
|
+
* Express, opens a database, and spawns agents, so nothing could exercise the composition without
|
|
7
|
+
* booting all of it. A security property that no test can reach is a security property that drifts:
|
|
8
|
+
* the read-only gateway's bypass (see `read-only-tool-constraint.ts`) lived precisely in the gap
|
|
9
|
+
* between "the route check is tested" and "the composition is not". This function closes that gap by
|
|
10
|
+
* being the composition, callable on its own.
|
|
11
|
+
*
|
|
12
|
+
* Innermost to outermost:
|
|
13
|
+
*
|
|
14
|
+
* 1. `withReadOnlyToolConstraint` — wraps the BARE executor, so every dispatch by every layer above
|
|
15
|
+
* it, including a tool id no caller ever named, has to pass the read-only gate to reach a handler.
|
|
16
|
+
* Anything further out would be bypassed by whatever sits beneath it.
|
|
17
|
+
* 2. `withRedactedToolFailures` (2026-09-16) — blanks secret-shaped values out of every `failed`
|
|
18
|
+
* result and mints its `ERR-…` id, per the owner's "hide secrets only" ruling (see that file's own
|
|
19
|
+
* header). Sits ABOVE the read-only gate so a read-only refusal (`status: 'denied'`, set by layer
|
|
20
|
+
* 1, never `'failed'`) is never touched by it, and BELOW `withToolAttemptAudit` so the audit sees
|
|
21
|
+
* the finished `errorId` and can store it (`tool-executor-audit.ts`). It sits INSIDE
|
|
22
|
+
* `withToolFailureRecovery` (layer 4) rather than outside it: recovery's own read-only-remedy
|
|
23
|
+
* refusal message is set on the OUTER result, after this layer has already run, so that message is
|
|
24
|
+
* never redacted either — only the original/remedy/retry calls recovery makes THROUGH `inner` pass
|
|
25
|
+
* through this layer, each redacted on its own.
|
|
26
|
+
* 3. `withToolAttemptAudit` — appends `requested` BEFORE delegating, which is the only ordering that
|
|
27
|
+
* records an unknown tool id or a throwing authorization (see that file's own doc). Sitting above
|
|
28
|
+
* the constraint gate means a read-only refusal is itself audited as a denial. Skipped entirely
|
|
29
|
+
* when the caller supplies no `toolAttemptAudit` — see {@link AssistantToolExecutorDeps}'s own doc.
|
|
30
|
+
* 4. `withToolFailureRecovery` — outermost, so the remedy call and the retry it can make each land as
|
|
31
|
+
* their own audited attempt row. The opposite order would collapse all three into the one outer
|
|
32
|
+
* "completed" row the audit records for the call the transport actually made, losing the remedy
|
|
33
|
+
* tool's own attempt from the trail entirely.
|
|
34
|
+
*
|
|
35
|
+
* Composed the SAME way for both of Tovu's tool-dispatch surfaces: the agent daemon's delegated-tool
|
|
36
|
+
* route and BYOK's `byok-tool-surface.ts` (as of 2026-09-06 — previously BYOK hand-assembled its own
|
|
37
|
+
* second copy of this stack that never wrapped `withReadOnlyToolConstraint`, so a decorator known to
|
|
38
|
+
* apply to "every read-only gateway" in fact applied to only one of the two; see that file's own
|
|
39
|
+
* `executor` construction for the call site).
|
|
40
|
+
*
|
|
41
|
+
* Architectural role: `src/assistant` composition-layer adapter. Takes its collaborators as
|
|
42
|
+
* arguments and constructs no policy, sink, or registry of its own.
|
|
43
|
+
*/
|
|
44
|
+
import { type ToolExecutor } from "../tool-executor.js";
|
|
45
|
+
import type { ToolRegistry } from "@jini-ai/core";
|
|
46
|
+
import type { ToolAttemptAuditSink } from "../core/tool-audit.js";
|
|
47
|
+
import type { SurfaceExchangeStore } from "../surface-exchanges.js";
|
|
48
|
+
import { type ToolFailureRecoveryDeps } from "./recovery.js";
|
|
49
|
+
import { type RedactedToolFailuresDeps } from "./redaction.js";
|
|
50
|
+
export interface AssistantToolExecutorDeps extends Omit<ToolFailureRecoveryDeps, "registry"> {
|
|
51
|
+
readonly registry: ToolRegistry;
|
|
52
|
+
readonly surfaceExchanges: SurfaceExchangeStore;
|
|
53
|
+
readonly newExecutionId: () => string;
|
|
54
|
+
/** Optional sink is a real supported configuration; never require a dummy observer. */
|
|
55
|
+
readonly toolAttemptAudit?: {
|
|
56
|
+
readonly sink: ToolAttemptAuditSink;
|
|
57
|
+
readonly workspaceId: string;
|
|
58
|
+
readonly now: () => string;
|
|
59
|
+
readonly newAttemptId: () => string;
|
|
60
|
+
readonly onSinkError?: (error: unknown) => void;
|
|
61
|
+
};
|
|
62
|
+
readonly toolFailures: RedactedToolFailuresDeps;
|
|
63
|
+
}
|
|
64
|
+
/** One registry and innermost read-only gate protect original, remedy and retry alike.
|
|
65
|
+
* Redaction precedes observation; recovery is outermost so each actual attempt is audited.
|
|
66
|
+
* @complexity O(1) composition; each decorator owns its runtime cost.
|
|
67
|
+
*/
|
|
68
|
+
export declare function createAssistantToolExecutor(deps: AssistantToolExecutorDeps, _optional?: {}): ToolExecutor;
|
|
69
|
+
//# sourceMappingURL=stack.d.ts.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The one place Tovu's `ToolExecutor` decorator stack is composed.
|
|
3
|
+
*
|
|
4
|
+
* The order of these three wrappers is load-bearing in two different directions, and it was
|
|
5
|
+
* previously spelled out only as an expression inside `agent-daemon-server.ts` — a module that boots
|
|
6
|
+
* Express, opens a database, and spawns agents, so nothing could exercise the composition without
|
|
7
|
+
* booting all of it. A security property that no test can reach is a security property that drifts:
|
|
8
|
+
* the read-only gateway's bypass (see `read-only-tool-constraint.ts`) lived precisely in the gap
|
|
9
|
+
* between "the route check is tested" and "the composition is not". This function closes that gap by
|
|
10
|
+
* being the composition, callable on its own.
|
|
11
|
+
*
|
|
12
|
+
* Innermost to outermost:
|
|
13
|
+
*
|
|
14
|
+
* 1. `withReadOnlyToolConstraint` — wraps the BARE executor, so every dispatch by every layer above
|
|
15
|
+
* it, including a tool id no caller ever named, has to pass the read-only gate to reach a handler.
|
|
16
|
+
* Anything further out would be bypassed by whatever sits beneath it.
|
|
17
|
+
* 2. `withRedactedToolFailures` (2026-09-16) — blanks secret-shaped values out of every `failed`
|
|
18
|
+
* result and mints its `ERR-…` id, per the owner's "hide secrets only" ruling (see that file's own
|
|
19
|
+
* header). Sits ABOVE the read-only gate so a read-only refusal (`status: 'denied'`, set by layer
|
|
20
|
+
* 1, never `'failed'`) is never touched by it, and BELOW `withToolAttemptAudit` so the audit sees
|
|
21
|
+
* the finished `errorId` and can store it (`tool-executor-audit.ts`). It sits INSIDE
|
|
22
|
+
* `withToolFailureRecovery` (layer 4) rather than outside it: recovery's own read-only-remedy
|
|
23
|
+
* refusal message is set on the OUTER result, after this layer has already run, so that message is
|
|
24
|
+
* never redacted either — only the original/remedy/retry calls recovery makes THROUGH `inner` pass
|
|
25
|
+
* through this layer, each redacted on its own.
|
|
26
|
+
* 3. `withToolAttemptAudit` — appends `requested` BEFORE delegating, which is the only ordering that
|
|
27
|
+
* records an unknown tool id or a throwing authorization (see that file's own doc). Sitting above
|
|
28
|
+
* the constraint gate means a read-only refusal is itself audited as a denial. Skipped entirely
|
|
29
|
+
* when the caller supplies no `toolAttemptAudit` — see {@link AssistantToolExecutorDeps}'s own doc.
|
|
30
|
+
* 4. `withToolFailureRecovery` — outermost, so the remedy call and the retry it can make each land as
|
|
31
|
+
* their own audited attempt row. The opposite order would collapse all three into the one outer
|
|
32
|
+
* "completed" row the audit records for the call the transport actually made, losing the remedy
|
|
33
|
+
* tool's own attempt from the trail entirely.
|
|
34
|
+
*
|
|
35
|
+
* Composed the SAME way for both of Tovu's tool-dispatch surfaces: the agent daemon's delegated-tool
|
|
36
|
+
* route and BYOK's `byok-tool-surface.ts` (as of 2026-09-06 — previously BYOK hand-assembled its own
|
|
37
|
+
* second copy of this stack that never wrapped `withReadOnlyToolConstraint`, so a decorator known to
|
|
38
|
+
* apply to "every read-only gateway" in fact applied to only one of the two; see that file's own
|
|
39
|
+
* `executor` construction for the call site).
|
|
40
|
+
*
|
|
41
|
+
* Architectural role: `src/assistant` composition-layer adapter. Takes its collaborators as
|
|
42
|
+
* arguments and constructs no policy, sink, or registry of its own.
|
|
43
|
+
*/
|
|
44
|
+
import { createToolExecutor } from "../tool-executor.js";
|
|
45
|
+
import { withReadOnlyToolConstraint } from "../core/read-only-tools.js";
|
|
46
|
+
import { withToolAttemptAudit } from "../core/tool-audit.js";
|
|
47
|
+
import { withToolFailureRecovery } from "./recovery.js";
|
|
48
|
+
import { withRedactedToolFailures, readToolErrorId } from "./redaction.js";
|
|
49
|
+
/** One registry and innermost read-only gate protect original, remedy and retry alike.
|
|
50
|
+
* Redaction precedes observation; recovery is outermost so each actual attempt is audited.
|
|
51
|
+
* @complexity O(1) composition; each decorator owns its runtime cost.
|
|
52
|
+
*/
|
|
53
|
+
export function createAssistantToolExecutor(deps, _optional = {}) {
|
|
54
|
+
const guarded = withReadOnlyToolConstraint({ inner: createToolExecutor({ registry: deps.registry }),
|
|
55
|
+
registry: deps.registry, idGenerator: { newId: deps.newExecutionId }, messages: deps.readOnlyMessages }, {});
|
|
56
|
+
const redacted = withRedactedToolFailures({ inner: guarded, ...deps.toolFailures }, {});
|
|
57
|
+
const audited = deps.toolAttemptAudit
|
|
58
|
+
? withToolAttemptAudit({ inner: redacted, ...deps.toolAttemptAudit, readErrorId: readToolErrorId }, deps.toolAttemptAudit.onSinkError ? { onSinkError: deps.toolAttemptAudit.onSinkError } : {})
|
|
59
|
+
: redacted;
|
|
60
|
+
return withToolFailureRecovery({ ...deps, inner: audited }, {});
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=stack.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jini-ai/daemon",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
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": {
|
|
@@ -25,7 +25,15 @@
|
|
|
25
25
|
"./tool-audit": "universal",
|
|
26
26
|
"./surface-exchanges": "universal",
|
|
27
27
|
"./session-coordination": "universal",
|
|
28
|
-
"./run-credentials": "node"
|
|
28
|
+
"./run-credentials": "node",
|
|
29
|
+
"./tool-recovery": "node"
|
|
30
|
+
},
|
|
31
|
+
"isolation": {
|
|
32
|
+
"shared": [
|
|
33
|
+
"src/tool-executor.ts",
|
|
34
|
+
"src/surface-exchanges.ts",
|
|
35
|
+
"src/scheduler.ts"
|
|
36
|
+
]
|
|
29
37
|
}
|
|
30
38
|
},
|
|
31
39
|
"type": "module",
|
|
@@ -97,6 +105,11 @@
|
|
|
97
105
|
"types": "./dist/run-credentials.d.ts",
|
|
98
106
|
"import": "./dist/run-credentials.js",
|
|
99
107
|
"default": "./dist/run-credentials.js"
|
|
108
|
+
},
|
|
109
|
+
"./tool-recovery": {
|
|
110
|
+
"types": "./dist/tool-recovery/index.d.ts",
|
|
111
|
+
"import": "./dist/tool-recovery/index.js",
|
|
112
|
+
"default": "./dist/tool-recovery/index.js"
|
|
100
113
|
}
|
|
101
114
|
},
|
|
102
115
|
"files": [
|
|
@@ -115,12 +128,6 @@
|
|
|
115
128
|
"access": "public",
|
|
116
129
|
"registry": "https://registry.npmjs.org"
|
|
117
130
|
},
|
|
118
|
-
"dependencies": {
|
|
119
|
-
"@jini-ai/protocol": "^0.4.1",
|
|
120
|
-
"@jini-ai/agent-runtime": "^0.4.1",
|
|
121
|
-
"@jini-ai/platform": "^0.5.1",
|
|
122
|
-
"@jini-ai/core": "^0.4.1"
|
|
123
|
-
},
|
|
124
131
|
"devDependencies": {
|
|
125
132
|
"@vitest/coverage-v8": "^2.1.9",
|
|
126
133
|
"node-pty": "^1.1.0",
|
|
@@ -132,9 +139,17 @@
|
|
|
132
139
|
"@types/pg": "8.23.1",
|
|
133
140
|
"express": "^4.21.0",
|
|
134
141
|
"@types/express": "^4.17.21",
|
|
135
|
-
"@jini-ai/
|
|
136
|
-
"@jini-ai/
|
|
137
|
-
"@jini-ai/
|
|
142
|
+
"@jini-ai/db": "0.3.1",
|
|
143
|
+
"@jini-ai/http-kit": "0.4.1",
|
|
144
|
+
"@jini-ai/agent-runtime": "^0.4.4",
|
|
145
|
+
"@jini-ai/chat": "0.5.7",
|
|
146
|
+
"@jini-ai/core": "^0.4.2",
|
|
147
|
+
"@jini-ai/platform": "^0.5.2",
|
|
148
|
+
"@jini-ai/protocol": "^0.4.3"
|
|
149
|
+
},
|
|
150
|
+
"dependencies": {
|
|
151
|
+
"@jini-ai/core": "^0.4.2",
|
|
152
|
+
"@jini-ai/protocol": "^0.4.3"
|
|
138
153
|
},
|
|
139
154
|
"peerDependencies": {
|
|
140
155
|
"node-pty": "^1.1.0",
|
|
@@ -144,7 +159,9 @@
|
|
|
144
159
|
"@electric-sql/pglite": "0.5.8",
|
|
145
160
|
"pg": "^8.23.0",
|
|
146
161
|
"@jini-ai/http-kit": "^0.4.0",
|
|
147
|
-
"express": "^4.21.0"
|
|
162
|
+
"express": "^4.21.0",
|
|
163
|
+
"@jini-ai/agent-runtime": "^0.4.4",
|
|
164
|
+
"@jini-ai/platform": "^0.5.1"
|
|
148
165
|
},
|
|
149
166
|
"peerDependenciesMeta": {
|
|
150
167
|
"node-pty": {
|
|
@@ -170,6 +187,12 @@
|
|
|
170
187
|
},
|
|
171
188
|
"express": {
|
|
172
189
|
"optional": true
|
|
190
|
+
},
|
|
191
|
+
"@jini-ai/agent-runtime": {
|
|
192
|
+
"optional": true
|
|
193
|
+
},
|
|
194
|
+
"@jini-ai/platform": {
|
|
195
|
+
"optional": true
|
|
173
196
|
}
|
|
174
197
|
},
|
|
175
198
|
"scripts": {
|