@sema-agent/core 7.53.0 → 7.54.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 +277 -1
- package/LICENSE +3 -1
- package/dist/agents/peer-session-drain.d.ts +0 -1
- package/dist/agents/peer-session-drain.js +0 -2
- package/dist/agents/send-message-tool.js +3 -4
- package/dist/agents/subagent.d.ts +3 -5
- package/dist/agents/subagent.js +18 -11
- package/dist/agents/tool-filter.d.ts +7 -4
- package/dist/agents/tool-filter.js +7 -3
- package/dist/core/a2a.js +4 -2
- package/dist/core/allow-entry.d.ts +1 -0
- package/dist/core/allow-entry.js +1 -0
- package/dist/core/approval-object.d.ts +87 -0
- package/dist/core/approval-object.js +85 -0
- package/dist/core/arg-summary.d.ts +3 -2
- package/dist/core/arg-summary.js +17 -11
- package/dist/core/ask-origin.d.ts +43 -27
- package/dist/core/ask-origin.js +20 -11
- package/dist/core/ask-question.d.ts +0 -1
- package/dist/core/ask-question.js +4 -67
- package/dist/core/ask-unresolvable-notice.d.ts +3 -2
- package/dist/core/auto-compaction.d.ts +49 -50
- package/dist/core/auto-compaction.js +41 -51
- package/dist/core/auto-mode-prompt.d.ts +4 -2
- package/dist/core/auto-mode-prompt.js +4 -3
- package/dist/core/canonical-json.d.ts +69 -0
- package/dist/core/canonical-json.js +130 -8
- package/dist/core/checkpoint-requirements.d.ts +13 -7
- package/dist/core/checkpoint-requirements.js +4 -1
- package/dist/core/checkpoint-store.d.ts +33 -18
- package/dist/core/checkpoint-store.js +5 -4
- package/dist/core/child-clamp-seats.d.ts +45 -6
- package/dist/core/child-clamp-seats.js +56 -4
- package/dist/core/consolidate-scope.d.ts +1 -1
- package/dist/core/cross-boundary-taint.d.ts +213 -0
- package/dist/core/cross-boundary-taint.js +196 -0
- package/dist/core/decision-receipt.d.ts +44 -1
- package/dist/core/decision-receipt.js +16 -0
- package/dist/core/effective-path-target.d.ts +3 -0
- package/dist/core/effective-path-target.js +4 -0
- package/dist/core/engine-notice.d.ts +34 -2
- package/dist/core/engine-notice.js +17 -1
- package/dist/core/engine-storage-backend.d.ts +73 -0
- package/dist/core/engine-storage-backend.js +1 -0
- package/dist/core/file-history-store.d.ts +6 -2
- package/dist/core/file-history-store.js +1 -0
- package/dist/core/gate-fold.js +10 -2
- package/dist/core/gate-lanes.js +30 -21
- package/dist/core/gate-outcome.d.ts +7 -3
- package/dist/core/governance-codes.d.ts +7 -2
- package/dist/core/governance-codes.js +6 -0
- package/dist/core/hooks.d.ts +14 -14
- package/dist/core/hooks.js +1 -2
- package/dist/core/locked-config.d.ts +28 -1
- package/dist/core/locked-config.js +30 -3
- package/dist/core/lsp.d.ts +1 -1
- package/dist/core/mcp-server-spec.d.ts +66 -9
- package/dist/core/mcp-server-spec.js +52 -19
- package/dist/core/mcp.js +5 -4
- package/dist/core/memory-engine/engine.js +2 -1
- package/dist/core/memory-engine/export-bundle.d.ts +1 -1
- package/dist/core/memory-engine/export-bundle.js +1 -1
- package/dist/core/memory-engine/file-backend.d.ts +2 -6
- package/dist/core/memory-engine/file-backend.js +1 -12
- package/dist/core/memory.d.ts +3 -3
- package/dist/core/mode-policy.d.ts +13 -8
- package/dist/core/mode-policy.js +3 -2
- package/dist/core/mods/kernel/receive.d.ts +2 -1
- package/dist/core/mods/kernel/receive.js +7 -2
- package/dist/core/parent-spec-seat.d.ts +10 -13
- package/dist/core/parent-spec-seat.js +14 -6
- package/dist/core/pause-registry.d.ts +35 -1
- package/dist/core/pause-registry.js +10 -0
- package/dist/core/permission-rule-consent.d.ts +18 -5
- package/dist/core/permission-rule-consent.js +13 -8
- package/dist/core/permission-rule-model.d.ts +29 -6
- package/dist/core/permission-rule-model.js +6 -1
- package/dist/core/permission-rule-org.d.ts +37 -0
- package/dist/core/permission-rule-org.js +34 -42
- package/dist/core/permission-rule-session.js +2 -2
- package/dist/core/permission-rule-store.d.ts +8 -0
- package/dist/core/permission-rule-store.js +1 -0
- package/dist/core/permission-rule-sync.js +3 -2
- package/dist/core/read-only-shell.d.ts +1 -1
- package/dist/core/read-only-shell.js +30 -9
- package/dist/core/remote-env.d.ts +41 -1
- package/dist/core/remote-env.js +18 -0
- package/dist/core/resume-startup-cause.d.ts +18 -0
- package/dist/core/resume-startup-cause.js +12 -0
- package/dist/core/runner/approval-feedback.d.ts +46 -0
- package/dist/core/runner/approval-feedback.js +41 -0
- package/dist/core/runner/compaction-call-options.d.ts +3 -3
- package/dist/core/runner/compaction-call-options.js +2 -2
- package/dist/core/runner/contracts.d.ts +33 -41
- package/dist/core/runner/cross-boundary-lane.d.ts +87 -0
- package/dist/core/runner/cross-boundary-lane.js +190 -0
- package/dist/core/runner/decide-continuation.d.ts +4 -4
- package/dist/core/runner/decide-continuation.js +3 -1
- package/dist/core/runner/gate-exit.d.ts +32 -12
- package/dist/core/runner/gate-exit.js +75 -33
- package/dist/core/runner/inherited-ask-grants.js +2 -2
- package/dist/core/runner/person-path-rows.d.ts +2 -2
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +3 -4
- package/dist/core/runner/prepare-acquire-reconcile.js +3 -3
- package/dist/core/runner/prepare-ask-lane.js +3 -3
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +6 -5
- package/dist/core/runner/prepare-caps-and-workflow.js +3 -3
- package/dist/core/runner/prepare-config-doors.d.ts +12 -15
- package/dist/core/runner/prepare-config-doors.js +8 -3
- package/dist/core/runner/prepare-context-lane.js +6 -3
- package/dist/core/runner/prepare-delegation-surface.d.ts +3 -2
- package/dist/core/runner/prepare-delegation-surface.js +2 -3
- package/dist/core/runner/prepare-execution-env.d.ts +1 -2
- package/dist/core/runner/prepare-execution-env.js +2 -1
- package/dist/core/runner/prepare-gate-stations.d.ts +2 -0
- package/dist/core/runner/prepare-gate-stations.js +4 -3
- package/dist/core/runner/prepare-hands-readface.d.ts +2 -2
- package/dist/core/runner/prepare-hands-readface.js +1 -1
- package/dist/core/runner/prepare-inherited-gate.d.ts +2 -0
- package/dist/core/runner/prepare-inherited-gate.js +1 -0
- package/dist/core/runner/prepare-park-ask.d.ts +1 -1
- package/dist/core/runner/prepare-park-ask.js +9 -3
- package/dist/core/runner/prepare-policy-chain.js +2 -7
- package/dist/core/runner/prepare-safety-scan.d.ts +7 -2
- package/dist/core/runner/prepare-safety-scan.js +34 -27
- package/dist/core/runner/prepare-suspend-saga.d.ts +2 -2
- package/dist/core/runner/prepare-suspend-saga.js +6 -2
- package/dist/core/runner/prepare-task.js +13 -10
- package/dist/core/runner/prepare-turn-wiring.d.ts +10 -5
- package/dist/core/runner/prepare-turn-wiring.js +14 -49
- package/dist/core/runner/prepare-wiring-manifest.d.ts +2 -2
- package/dist/core/runner/prepare-wiring-manifest.js +2 -2
- package/dist/core/runner/prepare-workspace-restore.js +2 -2
- package/dist/core/runner/resume-admission.js +9 -9
- package/dist/core/runner/resume-apply.js +4 -4
- package/dist/core/runner/resume-policy-outcome.d.ts +2 -2
- package/dist/core/runner/resume-policy-outcome.js +18 -15
- package/dist/core/runner/resume-preflight.js +2 -2
- package/dist/core/runner/resume-review-outcome.js +3 -3
- package/dist/core/runner/run-leg.js +57 -33
- package/dist/core/runner/run-settle-and-teardown.js +2 -0
- package/dist/core/runner/run-terminal-adoption.js +15 -4
- package/dist/core/runner/session-file-state-replay.d.ts +5 -2
- package/dist/core/runner/session-file-state-replay.js +3 -2
- package/dist/core/runner/stream-steer-verb.js +2 -2
- package/dist/core/runner/tool-face-overlay.d.ts +24 -6
- package/dist/core/runner/tool-face-overlay.js +24 -15
- package/dist/core/runner-deps.d.ts +9 -9
- package/dist/core/session-store.d.ts +35 -16
- package/dist/core/session-store.js +20 -12
- package/dist/core/{shell-lexer.d.ts → shell-run-candidates.d.ts} +36 -12
- package/dist/core/{shell-lexer.js → shell-run-candidates.js} +145 -225
- package/dist/core/shell-wrapper-table.d.ts +5 -3
- package/dist/core/shell-wrapper-table.js +1 -1
- package/dist/core/store-contracts/checkpoint-store-contract.js +2 -2
- package/dist/core/store-contracts/session-store-contract.d.ts +49 -0
- package/dist/core/store-contracts/session-store-contract.js +639 -0
- package/dist/core/store-contracts/session-store-incarnation-contract.d.ts +12 -0
- package/dist/core/store-contracts/session-store-incarnation-contract.js +5 -2
- package/dist/core/task-event.d.ts +6 -4
- package/dist/core/task-registry-agent.js +1 -0
- package/dist/core/task-registry-shared.d.ts +5 -0
- package/dist/core/task-registry.d.ts +7 -0
- package/dist/core/task-registry.js +17 -0
- package/dist/core/task-result.d.ts +1 -1
- package/dist/core/task-spec.d.ts +6 -6
- package/dist/core/tighten-task-spec.d.ts +6 -4
- package/dist/core/tighten-task-spec.js +2 -2
- package/dist/core/tool-catalog-entries.d.ts +1 -1
- package/dist/core/tool-catalog-entries.js +52 -52
- package/dist/core/tool-face-reader.d.ts +49 -0
- package/dist/core/tool-face-reader.js +94 -0
- package/dist/core/tool-face.d.ts +17 -8
- package/dist/core/tool-face.js +1 -0
- package/dist/core/tool-policy.d.ts +64 -23
- package/dist/core/tool-policy.js +62 -56
- package/dist/core/tool-roster.d.ts +46 -28
- package/dist/core/tool-roster.js +91 -14
- package/dist/core/tool-spec-members.js +2 -2
- package/dist/core/tool-spec.d.ts +20 -4
- package/dist/core/tools.js +2 -0
- package/dist/core/trace.d.ts +24 -2
- package/dist/core/types.d.ts +2 -2
- package/dist/core/types.js +1 -1
- package/dist/core/untrusted-text.d.ts +7 -0
- package/dist/core/untrusted-text.js +3 -8
- package/dist/core/user-attachment.d.ts +43 -0
- package/dist/core/user-attachment.js +92 -15
- package/dist/engine/compaction/compaction.d.ts +2 -2
- package/dist/engine/compaction/utils.d.ts +2 -2
- package/dist/engine/harness/types.d.ts +7 -4
- package/dist/engine/llm/types.d.ts +7 -2
- package/dist/engine/llm/types.js +2 -1
- package/dist/engine/session/repo-utils.d.ts +5 -0
- package/dist/engine/session/repo-utils.js +9 -0
- package/dist/engine/session/storage-base.js +2 -2
- package/dist/index.d.ts +23 -9
- package/dist/index.js +19 -8
- package/dist/internal/llm.d.ts +1 -1
- package/dist/internal/llm.js +1 -1
- package/dist/orchestration/run-workflow-tool.d.ts +3 -0
- package/dist/orchestration/run-workflow-tool.js +1 -0
- package/dist/orchestration/workflow-governance.d.ts +1 -1
- package/dist/orchestration/workflow.d.ts +3 -0
- package/dist/orchestration/workflow.js +2 -1
- package/dist/prompt-assembly/catalog/schema.d.ts +3 -0
- package/dist/prompt-assembly/catalog/schema.js +3 -0
- package/dist/prompt-assembly/overlay.js +2 -2
- package/dist/prompt-assembly/select.js +2 -2
- package/dist/prompts/default.d.ts +1 -1
- package/dist/shell/lex.d.ts +87 -8
- package/dist/shell/lex.js +283 -49
- package/dist/stores/file/index.d.ts +8 -6
- package/dist/tools/artifact/artifact-tool.js +10 -0
- package/dist/tools/fs/bash-program-position.d.ts +3 -15
- package/dist/tools/fs/bash-program-position.js +3 -10
- package/dist/tools/fs/bash-readonly-classifier.js +2 -0
- package/dist/tools/fs/fs-bash.d.ts +3 -3
- package/dist/tools/fs/fs-bash.js +72 -101
- package/dist/tools/fs/fs-read.d.ts +12 -0
- package/dist/tools/fs/fs-read.js +10 -9
- package/dist/tools/fs/fs-shared.d.ts +27 -31
- package/dist/tools/fs/fs-shared.js +12 -15
- package/dist/tools/fs/fs-write.js +1 -1
- package/dist/tools/fs/index.js +2 -2
- package/dist/tools/fs/read-deny-home.d.ts +1 -1
- package/dist/tools/fs/read-deny-home.js +5 -4
- package/dist/tools/fs/read-deny.d.ts +16 -3
- package/dist/tools/fs/read-deny.js +12 -9
- package/dist/tools/fs/safety.d.ts +7 -6
- package/dist/tools/fs/shell-read-bindings.d.ts +5 -3
- package/dist/tools/fs/shell-read-bindings.js +15 -16
- package/dist/tools/fs/shell-read-device.js +1 -1
- package/dist/tools/fs/shell-read-plan.js +36 -47
- package/dist/tools/fs/shell-read-word.d.ts +1 -6
- package/dist/tools/fs/shell-read-word.js +2 -20
- package/dist/tools/fs/shell-reentry-text.js +1 -1
- package/dist/tools/web.d.ts +2 -2
- package/package.json +4 -1
- package/test/export-surface.snapshot.json +5654 -5520
- package/wire-changes/core@7.53.0.json +30 -8
- package/wire-changes/core@7.54.0.json +2870 -0
- package/dist/core/shell-scan.d.ts +0 -60
- package/dist/core/shell-scan.js +0 -183
|
@@ -55,6 +55,8 @@ export interface McpServerSpec {
|
|
|
55
55
|
* override is AUTHORITATIVE and may LOWER an effect (vouch a tool is `read`/`idempotent`) as well as raise it
|
|
56
56
|
* (`egress` / irreversible). This is the ONLY trusted way to drop an MCP tool below the fail-closed `write`
|
|
57
57
|
* default (design F). Folds over the server hints in prepare-task (caller > server hint > fail-closed write).
|
|
58
|
+
* `boundaryFlow: "engine"` vouches that a call's effect stays inside the engine: the tool's roster row states no boundary legs
|
|
59
|
+
* (`boundaryLegs: []`) instead of the legs its effect implies — it needs `egress: false` beside it (refused otherwise).
|
|
58
60
|
*
|
|
59
61
|
* A DIFFERENT AXIS from {@link contentOrigin}: this one is about what a call DOES (repeat-safety,
|
|
60
62
|
* blast radius, reversibility — it feeds the approval gate); the content class is about what a call
|
|
@@ -75,16 +77,19 @@ export interface McpServerSpec {
|
|
|
75
77
|
effect?: ToolEffect;
|
|
76
78
|
egress?: boolean;
|
|
77
79
|
irreversibility?: "always" | "never";
|
|
80
|
+
boundaryFlow?: "engine";
|
|
78
81
|
}>;
|
|
79
82
|
/**
|
|
80
83
|
* design/388 §1.1 / §2.7 — the OPTIONAL per-tool FACE overlay for this server's tools (keyed by the server's
|
|
81
84
|
* RAW tool name, like {@link allowTools} / {@link toolAxes}): a deployment lets an MCP tool enter a specialised
|
|
82
85
|
* approval card, be confirmed by path, carry a display name / summary, or narrow its rule vocabulary. TRUSTED
|
|
83
|
-
* (the deployment writes it)
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* `
|
|
86
|
+
* (the deployment writes it). It is read once by `readToolFaces` at the preparation door and at `materializeMcpTools`: a
|
|
87
|
+
* declaration that does not fit on its own (a member the types do not have, a member of the wrong kind, a word outside a
|
|
88
|
+
* closed table, a reserved approval card, an empty tool name) refuses the entry under `config.tool_face_invalid` — the whole
|
|
89
|
+
* prepare at the door. What only the schema can say is judged at every listing (first mount and every refresh): a
|
|
90
|
+
* `pathTarget.param` that is not a top-level property, an `approvalCard` whose keys the schema lacks, a `ruleFace` naming a
|
|
91
|
+
* non-scalar key ⇒ that tool is NOT mounted and the leg announces `config.tool_face_invalid` once; a key naming a tool the
|
|
92
|
+
* listing does not carry announces `config.tool_face_undeclared` once. Applied whole or not at all.
|
|
88
93
|
*/
|
|
89
94
|
toolFaces?: Record<string, {
|
|
90
95
|
family?: ToolFamily;
|
|
@@ -185,6 +190,12 @@ export type ToolAxesRead = {
|
|
|
185
190
|
ok: false;
|
|
186
191
|
problem: string;
|
|
187
192
|
};
|
|
193
|
+
/** A name the declarer wrote, quoted and bounded for a sentence (the sentence names; it never carries a value). The bound holds on the
|
|
194
|
+
* QUOTED form — escaping a quote, a backslash or a control character lengthens a name — so a sentence's budget is spent the same way
|
|
195
|
+
* whatever the name holds. */
|
|
196
|
+
export declare function quotedName(name: string): string;
|
|
197
|
+
/** The kind of a value that is plain data, for a sentence that names what was declared without carrying it. */
|
|
198
|
+
export declare function dataKind(value: unknown): string;
|
|
188
199
|
/**
|
|
189
200
|
* Where a declaration stops being one: the path from the declaration's root — a list index as a number, a member name as a string — and
|
|
190
201
|
* the clause that says what is there ("it contains itself; …"): a kind and a remedy, never a value the declarer wrote.
|
|
@@ -220,6 +231,9 @@ export interface DeclarationPathRule {
|
|
|
220
231
|
* surface's row: it is a member the surface does not read.
|
|
221
232
|
*/
|
|
222
233
|
export declare const DECLARATION_PATH_RULES: Readonly<Record<ToolAxesSurface, readonly DeclarationPathRule[]>>;
|
|
234
|
+
/** The rules of a declaration that fixes no path beyond plain data and lists of names (a `toolAxes` record, a lock declaration): the one
|
|
235
|
+
* empty table, passed on purpose — {@link receiveDeclaration} takes its rules as an argument and refuses an absent one. */
|
|
236
|
+
export declare const NO_DECLARATION_PATH_RULES: readonly DeclarationPathRule[];
|
|
223
237
|
/**
|
|
224
238
|
* The one reading of a declaration a caller hands the engine — an MCP server's or an A2A peer's entry (the snapshot), a `toolAxes`
|
|
225
239
|
* record ({@link readToolAxes}), a lock declaration: the engine's one receipt of a caller's object in its declaration mode, so the
|
|
@@ -227,20 +241,62 @@ export declare const DECLARATION_PATH_RULES: Readonly<Record<ToolAxesSurface, re
|
|
|
227
241
|
* vocabulary over the copy (a list holds names; no member is named `__proto__`; each path `rules` names holds what its rule says — an
|
|
228
242
|
* entry's rules are its surface's {@link DECLARATION_PATH_RULES}). The copy is the answer — the caller's object is never handed on and
|
|
229
243
|
* never read again. A copy that is not a record is answered as it is, for the reader that expected a record to word.
|
|
244
|
+
*
|
|
245
|
+
* `rules` is required: a surface's {@link DECLARATION_PATH_RULES} row, or an empty list ({@link NO_DECLARATION_PATH_RULES} is the engine's
|
|
246
|
+
* own) for a declaration that fixes no path. A value that is not a list (an absent one — what an `at` outside the surfaces indexes to — `null`, a string, an inherited
|
|
247
|
+
* member of the table) is a caller's mistake about which declaration it holds, refused loudly (`config.invalid_argument`) rather than
|
|
248
|
+
* read as "no path rule", which would judge the declaration by less than its surface fixes.
|
|
249
|
+
*/
|
|
250
|
+
export declare function receiveDeclaration(declared: unknown, rules: readonly DeclarationPathRule[]): DeclarationRead;
|
|
251
|
+
/**
|
|
252
|
+
* The per-tool declarations a spec carries as ONE record keyed by the remote tool (or skill) name — what each is called in a
|
|
253
|
+
* sentence about it. Every reader of such a record ({@link readToolAxes}, `readToolFaces`) opens it through
|
|
254
|
+
* {@link receivePerToolDeclaration} and words a refusal through the two sentence functions below, so the one receipt, the one
|
|
255
|
+
* fault vocabulary and the one sentence shape are not written twice.
|
|
256
|
+
*/
|
|
257
|
+
declare const PER_TOOL_DECLARATIONS: {
|
|
258
|
+
readonly toolAxes: {
|
|
259
|
+
readonly plural: "axes";
|
|
260
|
+
readonly singular: "axis";
|
|
261
|
+
};
|
|
262
|
+
readonly toolFaces: {
|
|
263
|
+
readonly plural: "faces";
|
|
264
|
+
readonly singular: "face";
|
|
265
|
+
};
|
|
266
|
+
};
|
|
267
|
+
type PerToolDeclaration = keyof typeof PER_TOOL_DECLARATIONS;
|
|
268
|
+
/** {@link receivePerToolDeclaration}'s answer: the receipt's copy as a record keyed by name, or the one sentence that says why not. */
|
|
269
|
+
type PerToolDeclarationRead = {
|
|
270
|
+
readonly ok: true;
|
|
271
|
+
readonly perTool: Readonly<Record<string, unknown>>;
|
|
272
|
+
} | {
|
|
273
|
+
readonly ok: false;
|
|
274
|
+
readonly problem: string;
|
|
275
|
+
};
|
|
276
|
+
/**
|
|
277
|
+
* A per-tool declaration's receipt: {@link receiveDeclaration} (plain data held to the engine's one depth, every own member read once,
|
|
278
|
+
* lists of names, no `__proto__`), then the copy must be a record of names. A refusal names the tool (or skill) and the member where
|
|
279
|
+
* reading stopped, never a value. `unit` is what the keys are called (`tool` / `skill`).
|
|
230
280
|
*/
|
|
231
|
-
export declare function
|
|
281
|
+
export declare function receivePerToolDeclaration(declared: unknown, field: PerToolDeclaration, unit: string): PerToolDeclarationRead;
|
|
282
|
+
/** The sentence for one tool's (or skill's) declaration that does not fit: `detail` says which member and why. */
|
|
283
|
+
export declare function perToolEntryRefusal(field: PerToolDeclaration, unit: string, key: string, detail: string): string;
|
|
284
|
+
/** The sentence for a per-tool declaration that names a tool (or skill) with an empty name. */
|
|
285
|
+
export declare function perToolEmptyName(field: PerToolDeclaration, unit: string): string;
|
|
232
286
|
/**
|
|
233
287
|
* The one reader of a raw `toolAxes` declaration — {@link McpServerSpec.toolAxes} and {@link A2aServerSpec.toolAxes} are the same
|
|
234
288
|
* declaration, and the engine's preparation door, its materialization of an MCP server and of an A2A peer, and every host that
|
|
235
289
|
* assembles the field from JSON ask this same function. `at` names the surface the declaration sits on, because that decides what its
|
|
236
290
|
* keys are called in the sentence: `"mcp"` — the server's remote tools; `"a2a"` — the peer's remote skills. Absent ⇒ `{ ok: true }`
|
|
237
291
|
* (nothing declared). Anything else must be an object of per-tool objects whose members are exactly `effect` /
|
|
238
|
-
* `egress` / `irreversibility` with legal values; a declaration of any other shape, an empty tool name, an
|
|
292
|
+
* `egress` / `irreversibility` / `boundaryFlow` with legal values; a declaration of any other shape, an empty tool name, an
|
|
239
293
|
* unknown member, a word outside the vocabulary or a non-boolean `egress` on ANY one tool refuses the whole
|
|
240
294
|
* declaration (`ok: false`) — the good tools are not kept and nothing is defaulted, since a caller axis is
|
|
241
295
|
* two-way (it can vouch a tool down as well as mark it up) and a silently lost half is a trust root that is
|
|
242
|
-
* not in force.
|
|
243
|
-
*
|
|
296
|
+
* not in force. `boundaryFlow: "engine"` (the call's effect stays inside the engine — the tool's roster row states no
|
|
297
|
+
* boundary legs) needs `egress: false` in the same entry — `egress: true` contradicts it and an absent one leaves a server hint
|
|
298
|
+
* or the peer default marking it an external write — refused here; whether the other axes contradict each other (`egress`
|
|
299
|
+
* over a non-write effect) is the ingestion's own arithmetic. The declaration is read once, as plain data, by {@link receiveDeclaration} — a
|
|
244
300
|
* Proxy, a map, a class instance, a record built on a prototype or in another realm, a getter that throws, a structure that contains
|
|
245
301
|
* itself, a tool named `__proto__` are refusals that say where they sit (never the thrown text), not a throw — and judged on that copy.
|
|
246
302
|
* `at` is the caller's own word, not the declarer's: one outside {@link ToolAxesSurface} throws `config.invalid_argument` naming the
|
|
@@ -325,6 +381,7 @@ export interface A2aServerSpec {
|
|
|
325
381
|
effect?: ToolEffect;
|
|
326
382
|
egress?: boolean;
|
|
327
383
|
irreversibility?: "always" | "never";
|
|
384
|
+
boundaryFlow?: "engine";
|
|
328
385
|
}>;
|
|
329
386
|
}
|
|
330
387
|
/**
|
|
@@ -18,7 +18,7 @@ function toolAxesSurfaceOf(at) {
|
|
|
18
18
|
throw invalidArgument(`toolAxes is read for a surface, and the surfaces are ${Object.keys(TOOL_AXES_SURFACES).map((s) => JSON.stringify(s)).join(" | ")} ` +
|
|
19
19
|
`(an MCP server's tools, an A2A peer's skills); the surface named is not one of them.`);
|
|
20
20
|
}
|
|
21
|
-
const TOOL_AXIS_MEMBERS = { effect: true, egress: true, irreversibility: true };
|
|
21
|
+
const TOOL_AXIS_MEMBERS = { effect: true, egress: true, irreversibility: true, boundaryFlow: true };
|
|
22
22
|
const TOOL_AXIS_MEMBER_NAMES = Object.keys(TOOL_AXIS_MEMBERS);
|
|
23
23
|
const TOOL_EFFECT_WORDS = SAFETY_AXIS_VOCABULARY.toolEffect;
|
|
24
24
|
const ENGINE_DERIVED_IRREVERSIBILITY = "maybe";
|
|
@@ -27,9 +27,16 @@ const irreversibilityPolesLockstep = true;
|
|
|
27
27
|
void irreversibilityPolesLockstep;
|
|
28
28
|
const toolAxesDeclarationsAreOne = true;
|
|
29
29
|
void toolAxesDeclarationsAreOne;
|
|
30
|
-
function quotedName(name) {
|
|
31
|
-
|
|
30
|
+
export function quotedName(name) {
|
|
31
|
+
const whole = JSON.stringify(name);
|
|
32
|
+
if (name.length <= 80 && whole.length <= QUOTED_NAME_MAX)
|
|
33
|
+
return whole;
|
|
34
|
+
let cut = Math.min(name.length, 80);
|
|
35
|
+
while (cut > 0 && JSON.stringify(`${name.slice(0, cut)}…`).length > QUOTED_NAME_MAX)
|
|
36
|
+
cut--;
|
|
37
|
+
return JSON.stringify(`${name.slice(0, cut)}…`);
|
|
32
38
|
}
|
|
39
|
+
const QUOTED_NAME_MAX = 83;
|
|
33
40
|
function memberNames(path) {
|
|
34
41
|
let rendered = "";
|
|
35
42
|
for (const [index, name] of path.entries()) {
|
|
@@ -39,7 +46,7 @@ function memberNames(path) {
|
|
|
39
46
|
}
|
|
40
47
|
return rendered;
|
|
41
48
|
}
|
|
42
|
-
function dataKind(value) {
|
|
49
|
+
export function dataKind(value) {
|
|
43
50
|
return value === null ? "null" : value === undefined ? "undefined" : Array.isArray(value) ? "an array" : typeof value === "object" ? "a record" : `a ${typeof value}`;
|
|
44
51
|
}
|
|
45
52
|
const RECEIPT_FAULT_PROBLEMS = {
|
|
@@ -63,12 +70,13 @@ export const DECLARATION_PATH_RULES = Object.freeze({
|
|
|
63
70
|
{ path: ["name"], holds: "a string", required: true },
|
|
64
71
|
{ path: ["transport", "env"], holds: "names to strings" },
|
|
65
72
|
{ path: ["transport", "headers"], holds: "names to strings" },
|
|
66
|
-
]),
|
|
73
|
+
].map((rule) => Object.freeze({ ...rule, path: Object.freeze([...rule.path]) }))),
|
|
67
74
|
a2a: Object.freeze([
|
|
68
75
|
{ path: ["name"], holds: "a string", required: true },
|
|
69
76
|
{ path: ["headers"], holds: "names to strings" },
|
|
70
|
-
]),
|
|
77
|
+
].map((rule) => Object.freeze({ ...rule, path: Object.freeze([...rule.path]) }))),
|
|
71
78
|
});
|
|
79
|
+
export const NO_DECLARATION_PATH_RULES = Object.freeze([]);
|
|
72
80
|
const PROTO_MEMBER = `it is a member named "__proto__", which a copy by assignment would turn into a prototype — not a name a declaration can carry`;
|
|
73
81
|
function declarationVocabularyFault(root, rules) {
|
|
74
82
|
const asked = new Set();
|
|
@@ -148,7 +156,11 @@ function declarationVocabularyFault(root, rules) {
|
|
|
148
156
|
}
|
|
149
157
|
return problem === undefined ? undefined : { path, problem };
|
|
150
158
|
}
|
|
151
|
-
export function receiveDeclaration(declared, rules
|
|
159
|
+
export function receiveDeclaration(declared, rules) {
|
|
160
|
+
if (!Array.isArray(rules)) {
|
|
161
|
+
throw invalidArgument(`a declaration is received with the path rules of its surface (${Object.keys(DECLARATION_PATH_RULES).map((s) => `DECLARATION_PATH_RULES[${JSON.stringify(s)}]`).join(" | ")}), ` +
|
|
162
|
+
`or an empty list when it fixes no path; what was passed is ${dataKind(rules)}, not a list of rules.`);
|
|
163
|
+
}
|
|
152
164
|
const received = receive(declared, DECLARATION_RECEIPT_MODE);
|
|
153
165
|
if (!received.ok)
|
|
154
166
|
return { ok: false, fault: declarationFaultOf(received.fault) };
|
|
@@ -175,38 +187,59 @@ function readToolAxisEntry(raw) {
|
|
|
175
187
|
return `member ${quotedName(member)} is a hidden member: its name answers a value the declaration does not list as its own`;
|
|
176
188
|
declared[member] = value;
|
|
177
189
|
}
|
|
178
|
-
const { effect, egress, irreversibility } = declared;
|
|
190
|
+
const { effect, egress, irreversibility, boundaryFlow } = declared;
|
|
179
191
|
if (effect !== undefined && !isToolEffectWord(effect))
|
|
180
192
|
return `member "effect" is not one of ${TOOL_EFFECT_WORDS.join(" | ")}`;
|
|
181
193
|
if (egress !== undefined && typeof egress !== "boolean")
|
|
182
194
|
return `member "egress" is not a boolean`;
|
|
183
195
|
if (irreversibility !== undefined && !isIrreversibilityPoleWord(irreversibility))
|
|
184
196
|
return `member "irreversibility" is not one of ${IRREVERSIBILITY_POLE_WORDS.join(" | ")}`;
|
|
197
|
+
if (boundaryFlow !== undefined && boundaryFlow !== "engine")
|
|
198
|
+
return `member "boundaryFlow" is not "engine"`;
|
|
199
|
+
if (boundaryFlow === "engine" && egress !== false)
|
|
200
|
+
return `member "boundaryFlow": "engine" needs "egress": false in the same entry — add it, or drop "boundaryFlow" (${egress === true ? '"egress": true contradicts it' : 'with no "egress", the server hint or the peer default stays in force'})`;
|
|
185
201
|
return {
|
|
186
202
|
...(effect !== undefined ? { effect } : {}),
|
|
187
203
|
...(egress !== undefined ? { egress } : {}),
|
|
188
204
|
...(irreversibility !== undefined ? { irreversibility } : {}),
|
|
205
|
+
...(boundaryFlow !== undefined ? { boundaryFlow } : {}),
|
|
189
206
|
};
|
|
190
207
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
208
|
+
const PER_TOOL_DECLARATIONS = {
|
|
209
|
+
toolAxes: { plural: "axes", singular: "axis" },
|
|
210
|
+
toolFaces: { plural: "faces", singular: "face" },
|
|
211
|
+
};
|
|
212
|
+
export function receivePerToolDeclaration(declared, field, unit) {
|
|
213
|
+
const received = receiveDeclaration(declared, NO_DECLARATION_PATH_RULES);
|
|
196
214
|
if (!received.ok) {
|
|
197
215
|
const [key, ...member] = received.fault.path;
|
|
198
216
|
if (key === undefined)
|
|
199
|
-
return { ok: false, problem:
|
|
217
|
+
return { ok: false, problem: `${field} cannot be read: ${received.fault.problem}` };
|
|
200
218
|
const where = member.length > 0 ? `member ${memberNames(member)}: ` : "";
|
|
201
|
-
return { ok: false, problem:
|
|
219
|
+
return { ok: false, problem: perToolEntryRefusal(field, unit, String(key), `${where}${received.fault.problem}`) };
|
|
202
220
|
}
|
|
203
221
|
const perTool = received.value;
|
|
204
222
|
if (!isPlainRecord(perTool))
|
|
205
|
-
return { ok: false, problem:
|
|
223
|
+
return { ok: false, problem: `${field} is ${dataKind(perTool)}, not an object of per-${unit} ${PER_TOOL_DECLARATIONS[field].singular} declarations` };
|
|
224
|
+
return { ok: true, perTool };
|
|
225
|
+
}
|
|
226
|
+
export function perToolEntryRefusal(field, unit, key, detail) {
|
|
227
|
+
return `the ${PER_TOOL_DECLARATIONS[field].plural} declared for ${unit} ${quotedName(key)} do not fit: ${detail}`;
|
|
228
|
+
}
|
|
229
|
+
export function perToolEmptyName(field, unit) {
|
|
230
|
+
return `${field} has an empty ${unit} name`;
|
|
231
|
+
}
|
|
232
|
+
export function readToolAxes(declared, at) {
|
|
233
|
+
const unit = toolAxesSurfaceOf(at).key;
|
|
234
|
+
if (declared === undefined)
|
|
235
|
+
return { ok: true };
|
|
236
|
+
const received = receivePerToolDeclaration(declared, "toolAxes", unit);
|
|
237
|
+
if (!received.ok)
|
|
238
|
+
return { ok: false, problem: received.problem };
|
|
206
239
|
const entries = [];
|
|
207
|
-
for (const [key, raw] of Object.entries(perTool)) {
|
|
240
|
+
for (const [key, raw] of Object.entries(received.perTool)) {
|
|
208
241
|
if (key.length === 0)
|
|
209
|
-
return { ok: false, problem:
|
|
242
|
+
return { ok: false, problem: perToolEmptyName("toolAxes", unit) };
|
|
210
243
|
let entry;
|
|
211
244
|
try {
|
|
212
245
|
entry = readToolAxisEntry(raw);
|
|
@@ -215,7 +248,7 @@ export function readToolAxes(declared, at) {
|
|
|
215
248
|
entry = "a member threw while it was read";
|
|
216
249
|
}
|
|
217
250
|
if (typeof entry === "string")
|
|
218
|
-
return { ok: false, problem:
|
|
251
|
+
return { ok: false, problem: perToolEntryRefusal("toolAxes", unit, key, entry) };
|
|
219
252
|
if (Object.keys(entry).length > 0)
|
|
220
253
|
entries.push([key, entry]);
|
|
221
254
|
}
|
package/dist/core/mcp.js
CHANGED
|
@@ -25,9 +25,10 @@ import { MARKED_BLOCK_AFTER_DATA } from "./reminder-mint.js";
|
|
|
25
25
|
import { resolveMcpDeclaredResultSize, resolveMcpRequiresUserInteraction } from "./mcp-tool-meta.js";
|
|
26
26
|
export { resolveMcpDeclaredResultSize } from "./mcp-tool-meta.js";
|
|
27
27
|
import { withContentOrigin } from "./memory-engine/content-origin.js";
|
|
28
|
-
import {
|
|
28
|
+
import { judgeMcpEntryDeclarations, snapshotMcpEntries } from "./locked-config.js";
|
|
29
29
|
import { validateJsonSchemaShape } from "./runner/strict-output-schema.js";
|
|
30
30
|
import { schemaKeyBoundProblem } from "./tool-roster.js";
|
|
31
|
+
import { boundaryFlowFace } from "./cross-boundary-taint.js";
|
|
31
32
|
import { readMember, thrownIs, thrownName, thrownText } from "./thrown-value.js";
|
|
32
33
|
export const MCP_PREFIX = MCP_NAMESPACE.prefix;
|
|
33
34
|
const MCP_OUTPUT_TOKENS_DEFAULT = 25_000;
|
|
@@ -161,7 +162,7 @@ export function mcpStartupTimeoutMs(server) {
|
|
|
161
162
|
return fromEnv;
|
|
162
163
|
if (typeof own === "number" && Number.isSafeInteger(own) && own >= MCP_ENV_MS_MIN && own <= MCP_ENV_MS_MAX)
|
|
163
164
|
return own;
|
|
164
|
-
throw Object.assign(new Error(`MCP server "${inlineUntrusted(server.name, 160)}": startupTimeoutMs must be a whole number of milliseconds from ${MCP_ENV_MS_MIN} to ${MCP_ENV_MS_MAX} (got ${typeof own === "number" ? String(own) : own === null ? "null" : typeof own}) — not dialed`), { code: "config.mcp_startup_timeout_invalid" });
|
|
165
|
+
throw Object.assign(new Error(`MCP server ${typeof server.name === "string" ? `"${inlineUntrusted(server.name, 160)}"` : "(unnamed)"}: startupTimeoutMs must be a whole number of milliseconds from ${MCP_ENV_MS_MIN} to ${MCP_ENV_MS_MAX} (got ${typeof own === "number" ? String(own) : own === null ? "null" : typeof own}) — not dialed`), { code: "config.mcp_startup_timeout_invalid" });
|
|
165
166
|
}
|
|
166
167
|
function markMcpFailure(e, details) {
|
|
167
168
|
e.details = details;
|
|
@@ -666,7 +667,7 @@ export async function materializeMcpTools(specs, principal, onElicit, imageResiz
|
|
|
666
667
|
const snapshot = snapshotMcpEntries(specs);
|
|
667
668
|
const axesRefused = new WeakMap();
|
|
668
669
|
const entries = snapshot.map((spec) => {
|
|
669
|
-
const judged =
|
|
670
|
+
const judged = judgeMcpEntryDeclarations(spec);
|
|
670
671
|
if (judged.ok)
|
|
671
672
|
return judged.entry;
|
|
672
673
|
axesRefused.set(spec, judged.error);
|
|
@@ -1704,7 +1705,7 @@ function intakeListedTools(listed, spec, client, health, imageResizer, reminderD
|
|
|
1704
1705
|
...(mcpMaxResultSizeChars !== undefined ? { mcpMaxResultSizeChars } : {}),
|
|
1705
1706
|
...(mcpAlwaysLoad ? { mcpAlwaysLoad: true } : {}),
|
|
1706
1707
|
...(interaction.declared ? { mcpRequiresUserInteraction: true } : {}),
|
|
1707
|
-
mcpRemoteName: remoteName, ...(t.annotations?.readOnlyHint === true ? { mcpReadOnlyHint: true } : {}), permissionSeat: "passthrough",
|
|
1708
|
+
mcpRemoteName: remoteName, ...boundaryFlowFace(spec.toolAxes?.[remoteName]), ...(t.annotations?.readOnlyHint === true ? { mcpReadOnlyHint: true } : {}), permissionSeat: "passthrough",
|
|
1708
1709
|
execute: async (_toolCallId, params, signal) => {
|
|
1709
1710
|
const what = `The call to tool ${inlineUntrusted(remoteName)}`;
|
|
1710
1711
|
if (isServerRevoked?.(spec.name) === true) {
|
|
@@ -4,6 +4,7 @@ import { dirname, join, relative, sep } from "node:path";
|
|
|
4
4
|
import { uuidv7 } from "../../internal/harness.js";
|
|
5
5
|
import { MAX_MEMORY_BYTES, composeMemoryBlock, firstSentence } from "../memory.js";
|
|
6
6
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
7
|
+
import { canonicalJsonStringify } from "../canonical-json.js";
|
|
7
8
|
import { mintSystemReminder } from "../reminder-mint.js";
|
|
8
9
|
import { formatMemoryAge } from "../memory-recall.js";
|
|
9
10
|
import { committedDistilledOf, committedOriginOf, computeEntryRev, consolidationExposedFrontmatter, distilledEquals, hasDistilledFormExtra, hasOriginFormExtra, isValidEntryId, originEquals, parseEntryFile, serializeEntryFile, stripModelWrittenDistilled, stripModelWrittenOrigin, UNCLOSED_MEMORY_FILE_REASON } from "./frontmatter.js";
|
|
@@ -16,7 +17,7 @@ import { noReplaceRestore, retireStagingToCustody, stagingPathBeside } from "./d
|
|
|
16
17
|
import { sessionWriteHomeOf } from "./session-write-home.js";
|
|
17
18
|
import { landedUndecided, layerDigest, layerObservationLookup, readOnlyLayerCandidates, readOnlyLayerCensus, recordLayerObservations, settleUndecidedLayerObservations, observationBacked, sameObservation, undecidedLayerObservations } from "./layer-observations.js";
|
|
18
19
|
import { markOriginClearanceTombstoned, openOriginClearance, readOriginClearances, settleOriginClearance } from "./origin-clearance.js";
|
|
19
|
-
import { DEFAULT_MAX_ENTRY_DEPTH, MEMORY_INDEX_FILENAME,
|
|
20
|
+
import { DEFAULT_MAX_ENTRY_DEPTH, MEMORY_INDEX_FILENAME, captureErasureInput, erasureRequestInvalid, erasureSelectHash, scanEntryFiles, } from "./file-backend.js";
|
|
20
21
|
import { assembleMemoryExportBundle, computeMemoryBundleHash, memoryBundleInvalid, } from "./export-bundle.js";
|
|
21
22
|
import { QUARANTINE_DIR, SCAN_FUSE_THRESHOLD, quarantineAndTombstone, restoreContainedBaseline, indexLineIsStale, readIndexRevs, writeIndexRevs, bumpScanFuse, scanFuseKey, canonicalize, anchorStoreRoot, landingPathOf, landsOutsideStore, storeActRefusal, storeRootMovedRefusal, adoptRootScopeLayout, clearScanFuse, effectiveControlRootOf, drainMemoryAnnouncements, enqueueMemoryAnnouncement, ensureDirExists, markSessionPolluted, readSessionPollution, fileSessionCaptureRecordStore, recordRetrievedAccount, writeFileNoFollow, withPermissionRemedy, permissionRemedyOf, readRetrievedAccount, registerScope, registeredScopes, scopeDirFor, appendChallengeEvents, appendLineageAudit, rebuildStrictControlPlaneLedger, isStrictControlPlaneLedgerCorrupt, CHALLENGE_LEDGER_MAX_EVENTS, adjudicateLineagePending, challengedEntryIds, clearLineageForEntries, discardLineagePending, lineageAccountOfEntry, lineageContributionsOfSession, lineageLatchedIds, promoteLineagePending, readChallengeEvents, readChallengedHistory, readLineageRecord, recordChallengedHistory, recordLineageCredential, reconcileLineage, readProjectionDebts, resolveChallengeEvent, settleProjectionDebts, stageLineagePending, stageProjectionDebts, } from "./layout.js";
|
|
22
23
|
import { createSessionIncarnationSeat, recalledForAnyIdentity, recalledForIdentity, rememberForIdentity } from "./session-incarnation.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { TransferEvidence } from "./file-backend.js";
|
|
2
2
|
import { type MemoryEntry } from "./types.js";
|
|
3
3
|
/** One exported challenge event (unresolved generations only). Generations are DESTINATION-minted:
|
|
4
4
|
* the bundle deliberately carries no generation number — the importing store re-mints events under
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { canonicalJsonStringify } from "
|
|
2
|
+
import { canonicalJsonStringify } from "../canonical-json.js";
|
|
3
3
|
import { computeEntryRev } from "./frontmatter.js";
|
|
4
4
|
import { MEMORY_ORIGIN_CAUSES } from "./types.js";
|
|
5
5
|
import { ambiguousOriginRepresentation, committedOriginOf } from "./frontmatter.js";
|
|
@@ -126,10 +126,6 @@ export interface MemoryErasureAttestation {
|
|
|
126
126
|
propagation: "local-store-only";
|
|
127
127
|
};
|
|
128
128
|
}
|
|
129
|
-
/** Canonical JSON (recursively sorted keys, no whitespace) — the erasure protocol's ONE comparison
|
|
130
|
-
* and hash basis: `selectHash` complains bind to it, and evidence rows are written/compared in this
|
|
131
|
-
* form so a re-serialized import (key order changed by a middle layer) still compares equal. */
|
|
132
|
-
export declare function canonicalJsonStringify(value: unknown): string;
|
|
133
129
|
/** sha256 hex over the selector's canonical JSON + LF — the replay-identity judge (§4.6) and the
|
|
134
130
|
* anchor validator's recomputation basis (a 64-hex shape check alone would let a forged anchor swap
|
|
135
131
|
* the selector under a kept hash). */
|
|
@@ -373,13 +369,13 @@ export declare class FileMemoryEngineBackend implements MemoryBackend {
|
|
|
373
369
|
/**
|
|
374
370
|
* §3.1 (r3-④) — the migration retry judge: the PERSISTED schema form, set at every real disk read
|
|
375
371
|
* of the ledger and flipped only by the locked migration. Deliberately NOT derived from the
|
|
376
|
-
* in-memory ledger shape: the v1 compat arm upgrades rows in memory (§3.2), so the memory shape
|
|
372
|
+
* in-memory ledger shape: the v1 compat arm (RL-1654-ledger-v1) upgrades rows in memory (§3.2), so the memory shape
|
|
377
373
|
* reads "already v2" after the first stand-down and the retry would die. `"v1"` arms the
|
|
378
374
|
* per-entry opportunistic retry (construction / reads / checkControlPlane / every locked
|
|
379
375
|
* mutation); undefined = the disk form has not been observed yet.
|
|
380
376
|
*/
|
|
381
377
|
private persistedSchemaVersion;
|
|
382
|
-
/** §3.1 — the v1-compat window is loud ONCE per instance (per-row unbound noise stays suppressed;
|
|
378
|
+
/** §3.1 — the v1-compat window (RL-1654-ledger-v1) is loud ONCE per instance (per-row unbound noise stays suppressed;
|
|
383
379
|
* the OVERALL state does not). */
|
|
384
380
|
private announcedV1Compat;
|
|
385
381
|
/** §3.4 — O(1) retry judge for the READ entries' unbound-convergence attempts: recomputed at
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, fstatSync, linkSync, lstatSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, openSync, fsyncSync, closeSync, utimesSync } from "node:fs";
|
|
2
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
3
|
import { dirname, join, relative } from "node:path";
|
|
4
|
+
import { canonicalJsonStringify } from "../canonical-json.js";
|
|
4
5
|
import { jaccardDistance, termSet } from "../memory-vector.js";
|
|
5
6
|
import { MAX_MEMORY_BYTES } from "../memory.js";
|
|
6
7
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
@@ -14,18 +15,6 @@ import { scanMemoryFileName, scanMemoryWrite } from "./scan.js";
|
|
|
14
15
|
import { admittingLandings, cutPerLanding, LandingsNeedAdmission, requireLandingsAdmitted } from "./landing-admission.js";
|
|
15
16
|
import { thrownText } from "../thrown-value.js";
|
|
16
17
|
export { DEFAULT_MAX_ENTRY_DEPTH, MEMORY_INDEX_FILENAME, scanEntryFiles } from "./entry-scan.js";
|
|
17
|
-
export function canonicalJsonStringify(value) {
|
|
18
|
-
if (Array.isArray(value))
|
|
19
|
-
return `[${value.map((v) => (v === undefined || typeof v === "function" || typeof v === "symbol" ? "null" : canonicalJsonStringify(v))).join(",")}]`;
|
|
20
|
-
if (value !== null && typeof value === "object") {
|
|
21
|
-
const rec = value;
|
|
22
|
-
const keys = Object.keys(rec)
|
|
23
|
-
.filter((k) => rec[k] !== undefined)
|
|
24
|
-
.sort();
|
|
25
|
-
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJsonStringify(rec[k])}`).join(",")}}`;
|
|
26
|
-
}
|
|
27
|
-
return JSON.stringify(value);
|
|
28
|
-
}
|
|
29
18
|
export function erasureSelectHash(select) {
|
|
30
19
|
return createHash("sha256").update(`${canonicalJsonStringify(select)}\n`, "utf8").digest("hex");
|
|
31
20
|
}
|
package/dist/core/memory.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export interface ScoredMemory {
|
|
|
16
16
|
/** design/84 Seam B (前置 BLOCKER): true when this note was CREATED by a consolidation ADD (not the
|
|
17
17
|
* `remember` tool / a caller). Periodic/incremental consolidation EXCLUDES these from its candidate set so
|
|
18
18
|
* it never re-consolidates its own output (infinite re-merge). Stores that don't track it leave it absent
|
|
19
|
-
* (treated as not-generated — back-compat: the inline task-end path already excludes this-task notes by id). */
|
|
19
|
+
* (treated as not-generated — back-compat, RL-1654-memorystore: the inline task-end path already excludes this-task notes by id). */
|
|
20
20
|
consolidationGenerated?: boolean;
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
@@ -86,7 +86,7 @@ export interface MemoryStore {
|
|
|
86
86
|
*/
|
|
87
87
|
searchScored?(scope: string, query: string, limit?: number): Promise<ScoredMemory[]> | ScoredMemory[];
|
|
88
88
|
/** The vector-ranking rung this store achieves (design/81 Slice 5). Absent ⇒ treated as `"lexical"`
|
|
89
|
-
* (back-compat). A runtime observable — a deployment reads it to decide index/metrics, NOT to decide
|
|
89
|
+
* (back-compat, RL-1654-memorystore). A runtime observable — a deployment reads it to decide index/metrics, NOT to decide
|
|
90
90
|
* whether to inject an embedder (injection is config-driven; this is the RESULT). See {@link MemoryVectorMode}. */
|
|
91
91
|
readonly vectorMode?: MemoryVectorMode;
|
|
92
92
|
/** Optional: replace a note's text by id (consolidation UPDATE). The id stays stable. Must be safe
|
|
@@ -437,7 +437,7 @@ export declare function expandLexicalTerms(term: string): string[];
|
|
|
437
437
|
* Matching rule: tokenize `text` on word boundaries, then for each whitespace-separated `query` term the
|
|
438
438
|
* text must match AT LEAST ONE of that term's {@link expandLexicalTerms} variants (OR within a term), and
|
|
439
439
|
* EVERY query term must match (AND across terms). The ORIGINAL query term keeps its pre-Slice-2b SUBSTRING
|
|
440
|
-
* semantics (back-compat); the EXPANDED synonym/stem variants are matched on WORD BOUNDARIES (set membership
|
|
440
|
+
* semantics (back-compat, RL-1654-memorystore); the EXPANDED synonym/stem variants are matched on WORD BOUNDARIES (set membership
|
|
441
441
|
* over the text's token set) so a short injected synonym like "key"/"db" cannot substring-match an unrelated
|
|
442
442
|
* word ("keyboard"/"double"). An empty/whitespace-only query never matches.
|
|
443
443
|
*/
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import type { AgentTool, ExecutionEnv } from "../internal/harness-types.js";
|
|
17
17
|
import type { ContainmentFace } from "../tools/fs/containment-face.js";
|
|
18
|
-
import type
|
|
18
|
+
import { type PermissionResult, type ToolCallRequest, type ToolPolicy } from "./tool-policy.js";
|
|
19
19
|
import { type ToolRoster } from "./tool-roster.js";
|
|
20
20
|
import { type PermissionMode } from "./ask-origin.js";
|
|
21
21
|
import type { RuntimeCaps } from "./runtime-caps.js";
|
|
@@ -35,7 +35,7 @@ export type PermissionModeFoldReason = (typeof PERMISSION_MODE_FOLD_REASONS)[num
|
|
|
35
35
|
* `writeFace`: the write face the mode states where no seat declared one (the mode row of {@link import("../tools/fs/containment-face.js").resolveContainmentFace}) —
|
|
36
36
|
* `bypassPermissions` writes outside the write roots (`open`); every other mode states nothing and the default fence stands.
|
|
37
37
|
* `unstated`: the mode's answer for a call to a tool that states no permission of its own (permission seat `passthrough`)
|
|
38
|
-
* when no
|
|
38
|
+
* when no answer covers the fold's allow ({@link asksUnstated}) — `ask` (the arbitration then answers it as any ask: dontAsk refuses it, auto's
|
|
39
39
|
* classifier may answer it), or `none` (the call runs; `bypassPermissions`, which installs no gate and asks nothing it would
|
|
40
40
|
* release). */
|
|
41
41
|
export interface ModePolicy {
|
|
@@ -77,13 +77,18 @@ export interface ModeGateSeats {
|
|
|
77
77
|
/** Does the LAST mounted instance of this name (the one a call dispatches to) declare itself read-only — an MCP server's
|
|
78
78
|
* `readOnlyHint` ({@link AgentTool.mcpReadOnlyHint})? The mode gate's `readOnlyDeclared` seat over the leg's live tools. */
|
|
79
79
|
export declare function mountedToolDeclaresReadOnly(tools: readonly AgentTool[], toolName: string): boolean;
|
|
80
|
-
/** The ONE rule of the unstated question, read at the fold's last station and at the approved-edit round: an allow
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
|
|
80
|
+
/** The ONE rule of the unstated question, read at the fold's last station and at the approved-edit round: an allow on a tool
|
|
81
|
+
* that states no permission of its own, under a mode whose `unstated` answer is `ask`, that no answer covers — no engine
|
|
82
|
+
* record of a settled wait licensing the bytes that run (`runs`; {@link answeredFor}). A layer's allow says nothing here
|
|
83
|
+
* whatever words it carries; who else may clear the question is the allow layer's business (its members, a hook's own `allow`
|
|
84
|
+
* among them), not this rule's. */
|
|
85
|
+
export declare function asksUnstated(d: PermissionResult, call: {
|
|
86
|
+
toolCallId: string;
|
|
87
|
+
toolName: string;
|
|
88
|
+
}, runs: unknown, seat: "passthrough" | undefined, mode: PermissionMode | undefined): boolean;
|
|
84
89
|
/** The unstated question's sentence (the reference implementation's passthrough sentence, without its product name). */
|
|
85
90
|
export declare const unstatedAskMessage: (toolName: string) => string;
|
|
86
|
-
/** One call's permission seat: `passthrough` (an allow
|
|
91
|
+
/** One call's permission seat: `passthrough` (an allow no answer covers meets the mode's unstated answer) and the question
|
|
87
92
|
* put when it does — absent ⇒ {@link unstatedAskMessage}. */
|
|
88
93
|
export interface PermissionSeatReading {
|
|
89
94
|
readonly seat: "passthrough";
|
|
@@ -91,7 +96,7 @@ export interface PermissionSeatReading {
|
|
|
91
96
|
}
|
|
92
97
|
/** The gate's permission-seat inputs (a member group of `ToolGateInput`), read through {@link permissionSeatAt}. */
|
|
93
98
|
export interface PermissionSeatInputs {
|
|
94
|
-
/** The called tool's permission seat ({@link import("./tool-spec.js").ToolSpec.permissionSeat}): `"passthrough"` ⇒ an allow no
|
|
99
|
+
/** The called tool's permission seat ({@link import("./tool-spec.js").ToolSpec.permissionSeat}): `"passthrough"` ⇒ an allow no answer covers
|
|
95
100
|
* meets the mode's answer for an unstated call ({@link ModePolicy.unstated}). Absent ⇒ the tool answers for itself. */
|
|
96
101
|
permissionSeat?: "passthrough";
|
|
97
102
|
/** The seat read off THIS call's arguments, for a tool whose seat depends on them (the Runner wires it for the `Skill`
|
package/dist/core/mode-policy.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createFsWriteGatePolicy } from "./fs-write-gate-policy.js";
|
|
2
2
|
import { isProtectedWrite } from "./tool-registry.js";
|
|
3
|
+
import { answeredFor } from "./tool-policy.js";
|
|
3
4
|
import { rosterEntryNamed } from "./tool-roster.js";
|
|
4
5
|
import { wireOf } from "./tool-name-readings.js";
|
|
5
6
|
import { clampPermissionMode, isPermissionMode, PERMISSION_MODES } from "./ask-origin.js";
|
|
@@ -41,8 +42,8 @@ export function modeAcceptsFileCommand(mode, toolName, args) {
|
|
|
41
42
|
export function mountedToolDeclaresReadOnly(tools, toolName) {
|
|
42
43
|
return tools.findLast((t) => t.name === toolName)?.mcpReadOnlyHint === true;
|
|
43
44
|
}
|
|
44
|
-
export function asksUnstated(d, seat, mode) {
|
|
45
|
-
return d.action === "allow" &&
|
|
45
|
+
export function asksUnstated(d, call, runs, seat, mode) {
|
|
46
|
+
return d.action === "allow" && seat === "passthrough" && mode !== undefined && modePolicyOf(mode).unstated === "ask" && !answeredFor(d, call, runs);
|
|
46
47
|
}
|
|
47
48
|
export const unstatedAskMessage = (toolName) => `The agent requested permission to use ${toolName}, but it has not been granted yet.`;
|
|
48
49
|
export function permissionSeatAt(input, args) {
|
|
@@ -24,7 +24,8 @@ export interface ReceiptMode {
|
|
|
24
24
|
* once on the object (a getter that throws refuses the receipt; one that moves the object's prototype refuses it, but
|
|
25
25
|
* under `others: "copy"`).
|
|
26
26
|
* `refuse`: only own enumerable string-keyed DATA properties are members; an accessor, a non-enumerable property or
|
|
27
|
-
* a symbol-keyed one refuses the receipt (no getter ever runs)
|
|
27
|
+
* a symbol-keyed one refuses the receipt (no getter ever runs) — under `contain`, each is a member left out and reported
|
|
28
|
+
* (a symbol-keyed one at its container's path and its `String(symbol)`), its container received.
|
|
28
29
|
*/
|
|
29
30
|
readonly accessors: "call-once" | "refuse";
|
|
30
31
|
/** How many levels of containers are copied (`Infinity` = every level). */
|
|
@@ -82,8 +82,13 @@ export function receive(value, mode, held) {
|
|
|
82
82
|
const out = list ? [] : {};
|
|
83
83
|
seen.set(v, out);
|
|
84
84
|
open.add(v);
|
|
85
|
-
if (mode.accessors === "refuse"
|
|
86
|
-
|
|
85
|
+
if (mode.accessors === "refuse") {
|
|
86
|
+
const symbols = Object.getOwnPropertySymbols(captured);
|
|
87
|
+
if (symbols.length > 0 && mode.contain !== true)
|
|
88
|
+
refuseContainer(v, "hidden");
|
|
89
|
+
for (const s of symbols)
|
|
90
|
+
faults.push(Object.freeze({ kind: "hidden", path: Object.freeze([...path, String(s)]) }));
|
|
91
|
+
}
|
|
87
92
|
stack.push({ v, out, list, proto, captured, keys: Object.getOwnPropertyNames(captured), level, next: 0, height: 1, key: "", member: undefined });
|
|
88
93
|
return out;
|
|
89
94
|
};
|
|
@@ -35,8 +35,9 @@ export type ParentSpecSeatFields = Pick<TaskSpec, "principal" | "oneShot" | "cli
|
|
|
35
35
|
* · `envFacts` — the sandbox facts (profile / egress / scratchpad).
|
|
36
36
|
* · `memoryPersistenceCapable` — the deployment's DECLARED persistence statement: `false` is a REMOVAL (floor),
|
|
37
37
|
* `true` a default a child's own declaration may narrow.
|
|
38
|
-
* · `
|
|
39
|
-
*
|
|
38
|
+
* · `memory` — the request-level "memory is off" FACT, `{ enabled: false }` and nothing else of the declaration (the clamp
|
|
39
|
+
* table's `memory.enabled` row; present only when the parent stated `enabled: false`): a REMOVAL (floor) that
|
|
40
|
+
* REWRITES the one member on the child's own memory declaration — its scopes and knobs are the child's.
|
|
40
41
|
* · `compaction` — the request's CONTEXT-MANAGEMENT settings (enabled / withinTask / reserve / keepRecent /
|
|
41
42
|
* instructions / attachWorkingFiles / staleToolResultOffload / clampTolerance). One set of settings for the
|
|
42
43
|
* whole delegation tree, measured per leg against the leg's OWN window: the numbers are repaired against the
|
|
@@ -65,7 +66,9 @@ export interface ParentSpecSeat {
|
|
|
65
66
|
readonly additionalReadDirectories?: readonly string[];
|
|
66
67
|
readonly envFacts?: TaskSpec["envFacts"];
|
|
67
68
|
readonly memoryPersistenceCapable?: boolean;
|
|
68
|
-
readonly
|
|
69
|
+
readonly memory?: {
|
|
70
|
+
readonly enabled: false;
|
|
71
|
+
};
|
|
69
72
|
readonly compaction?: TaskSpec["compaction"];
|
|
70
73
|
readonly skillListingBudgetFraction?: number;
|
|
71
74
|
readonly skillListingMaxDescChars?: number;
|
|
@@ -73,13 +76,16 @@ export interface ParentSpecSeat {
|
|
|
73
76
|
readonly promptForm?: "minimal";
|
|
74
77
|
readonly promptBinding?: string;
|
|
75
78
|
}
|
|
79
|
+
/** Refuse (by name, `config.deps_retired_key`) a seat that still carries a retired member. Read at every door a seat enters
|
|
80
|
+
* by hand: the ctx reader, the apply, and the send lane's waker clamps. */
|
|
81
|
+
export declare function assertParentSpecSeatCurrent(seat: object | undefined): void;
|
|
76
82
|
/** Mint the seat off the parent's REBOUND spec and its frozen question face (spec over deps — the caller resolves it once,
|
|
77
83
|
* the same value the parent's own question routing reads). Absent fields stay absent; `oneShot` only when `true`. */
|
|
78
84
|
export declare function parentSpecSeatOf(spec: Omit<ParentSpecSeatFields, "onQuestion">, frozenOnQuestion: OnQuestion | undefined): ParentSpecSeat;
|
|
79
85
|
/**
|
|
80
86
|
* Apply the seat to a child spec — ONE law for every member, both lanes:
|
|
81
87
|
* a value the child spec already carries WINS; the seat FILLS an unset one; the three REMOVAL values
|
|
82
|
-
* (`oneShot: true`, `memoryPersistenceCapable: false`, `
|
|
88
|
+
* (`oneShot: true`, `memoryPersistenceCapable: false`, `memory: { enabled: false }`) land UNCONDITIONALLY.
|
|
83
89
|
* "Already carries" is the child's own authorship — a deployment baseline, a trusted script, an agent definition's
|
|
84
90
|
* fold (the Task lane folds the definition's `memoryPersistenceCapable` before applying; the workflow lane applies at
|
|
85
91
|
* the launch site AFTER the agentType fold) — so a definition may narrow a parent `true` to `false`, a baseline-pinned
|
|
@@ -88,15 +94,6 @@ export declare function parentSpecSeatOf(spec: Omit<ParentSpecSeatFields, "onQue
|
|
|
88
94
|
* No seat (a tool running outside a Runner task) ⇒ the child spec is returned as is.
|
|
89
95
|
*/
|
|
90
96
|
export declare function applyParentSpecSeat<S extends ParentSpecSeatFields>(child: S, seat: ParentSpecSeat | undefined): S;
|
|
91
|
-
/** The ONE way the memory-off floor is written onto a spec — the seat's apply above and the resume lane's
|
|
92
|
-
* clamp fold both call it: keep every field the spec authored (scopes, write scope, content-safety knobs)
|
|
93
|
-
* and state `enabled: false` over them. Returning a value rather than deleting the key IS the mechanism:
|
|
94
|
-
* the next seat mint reads `spec.memory.enabled === false`, so a spec whose `memory` was removed would end
|
|
95
|
-
* the floor one level down. A spec that declared nothing gets the bare `{ enabled: false }`, which the
|
|
96
|
-
* normalizer reads as "no usable scopes" — identical to the absent declaration it replaces. */
|
|
97
|
-
export declare function memoryTurnedOff(memory: TaskSpec["memory"]): NonNullable<TaskSpec["memory"]> & {
|
|
98
|
-
enabled: false;
|
|
99
|
-
};
|
|
100
97
|
/** The ctx keys that restate a seat member as THIS run's own fact (tools read them for scope / receipts / clamps). */
|
|
101
98
|
declare const SEAT_FACTS_ON_CTX: readonly ["principal", "oneShot", "onQuestion", "memoryPersistenceCapable"];
|
|
102
99
|
/**
|