@sema-agent/core 7.7.0 → 7.8.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 +37 -0
- package/dist/agents/agent-transcript-tool.js +2 -2
- package/dist/agents/list-agents-tool.js +2 -3
- package/dist/agents/observer.js +2 -2
- package/dist/agents/send-message-tool.js +2 -1
- package/dist/agents/subagent-steps.js +5 -4
- package/dist/agents/subagent.js +2 -3
- package/dist/core/ask-question.js +2 -2
- package/dist/core/checkpoint-store.d.ts +38 -4
- package/dist/core/context-edit.d.ts +0 -22
- package/dist/core/context-edit.js +2 -11
- package/dist/core/engine-notice.d.ts +6 -0
- package/dist/core/fs-write-gate-policy.d.ts +7 -1
- package/dist/core/fs-write-gate-policy.js +7 -7
- package/dist/core/gate-fold.js +2 -2
- package/dist/core/gate-lanes.js +4 -3
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +6 -0
- package/dist/core/hooks.d.ts +8 -2
- package/dist/core/hooks.js +1 -1
- package/dist/core/lsp.js +2 -2
- package/dist/core/mcp-server-spec.d.ts +17 -0
- package/dist/core/mcp.js +3 -1
- package/dist/core/memory-engine/tools.js +4 -12
- package/dist/core/permission-rules.d.ts +32 -2
- package/dist/core/permission-rules.js +43 -37
- package/dist/core/present-plan-tool.js +3 -4
- package/dist/core/runner/active-skill-scope.js +7 -4
- package/dist/core/runner/compaction-call-options.d.ts +1 -1
- package/dist/core/runner/contracts.d.ts +27 -3
- package/dist/core/runner/prepare-ask-lane.d.ts +1 -1
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +5 -4
- package/dist/core/runner/prepare-caps-and-workflow.js +16 -11
- package/dist/core/runner/prepare-defer-classify.d.ts +5 -26
- package/dist/core/runner/prepare-defer-classify.js +10 -10
- package/dist/core/runner/prepare-delegation-surface.d.ts +5 -4
- package/dist/core/runner/prepare-delegation-surface.js +15 -17
- package/dist/core/runner/prepare-gate-stations.d.ts +4 -1
- package/dist/core/runner/prepare-gate-stations.js +3 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +3 -2
- package/dist/core/runner/prepare-hands-readface.js +3 -2
- package/dist/core/runner/prepare-inherited-gate.d.ts +1 -1
- package/dist/core/runner/prepare-inherited-gate.js +1 -0
- package/dist/core/runner/prepare-lsp.d.ts +4 -3
- package/dist/core/runner/prepare-lsp.js +3 -2
- package/dist/core/runner/prepare-memory-engine-session.d.ts +4 -4
- package/dist/core/runner/prepare-memory-engine-session.js +4 -2
- package/dist/core/runner/prepare-offload-wrappers.js +7 -5
- package/dist/core/runner/prepare-park-ask.d.ts +4 -0
- package/dist/core/runner/prepare-park-ask.js +4 -1
- package/dist/core/runner/prepare-project-context.d.ts +5 -23
- package/dist/core/runner/prepare-project-context.js +7 -5
- package/dist/core/runner/prepare-prompt-assembly.d.ts +1 -1
- package/dist/core/runner/prepare-protocol-tools.d.ts +5 -1
- package/dist/core/runner/prepare-protocol-tools.js +38 -21
- package/dist/core/runner/prepare-question-face.d.ts +4 -3
- package/dist/core/runner/prepare-question-face.js +5 -3
- package/dist/core/runner/prepare-run-refs.d.ts +16 -0
- package/dist/core/runner/prepare-run-refs.js +6 -0
- package/dist/core/runner/prepare-safety-scan.js +42 -2
- package/dist/core/runner/prepare-task.js +37 -19
- package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +5 -2
- package/dist/core/runner/prepare-tool-disclosure-mount.js +3 -1
- package/dist/core/runner/prepare-wiring-manifest.d.ts +4 -0
- package/dist/core/runner/prepare-wiring-manifest.js +3 -8
- package/dist/core/runner/run-harness-handlers.d.ts +46 -0
- package/dist/core/runner/run-harness-handlers.js +345 -0
- package/dist/core/runner/run-turn-boundary.d.ts +69 -0
- package/dist/core/runner/run-turn-boundary.js +694 -0
- package/dist/core/runner/runtask.js +47 -1043
- package/dist/core/runner/session-rule-policy.js +9 -4
- package/dist/core/runner/synthetic-tools.js +5 -8
- package/dist/core/runner/tool-disclosure.d.ts +4 -0
- package/dist/core/runner/tool-disclosure.js +9 -5
- package/dist/core/runner/tool-end-body.js +3 -2
- package/dist/core/runner/tool-face-overlay.d.ts +40 -0
- package/dist/core/runner/tool-face-overlay.js +125 -0
- package/dist/core/runner/tool-output-projection.d.ts +10 -0
- package/dist/core/runner/tool-output-projection.js +33 -10
- package/dist/core/sensitive-path-policy.d.ts +3 -0
- package/dist/core/sensitive-path-policy.js +6 -5
- package/dist/core/shared-memory/tools.js +3 -6
- package/dist/core/store-contracts/checkpoint-store-contract.js +23 -0
- package/dist/core/task-event.d.ts +10 -0
- package/dist/core/task-registry.js +4 -5
- package/dist/core/task-spec.d.ts +8 -0
- package/dist/core/tool-catalog-entries.d.ts +48 -0
- package/dist/core/tool-catalog-entries.js +174 -0
- package/dist/core/tool-conformance.d.ts +22 -0
- package/dist/core/tool-conformance.js +37 -0
- package/dist/core/tool-face.d.ts +171 -0
- package/dist/core/tool-face.js +25 -0
- package/dist/core/tool-policy.d.ts +24 -0
- package/dist/core/tool-policy.js +4 -2
- package/dist/core/tool-registry.d.ts +114 -0
- package/dist/core/tool-registry.js +114 -0
- package/dist/core/tool-result-budget.d.ts +0 -3
- package/dist/core/tool-result-budget.js +4 -3
- package/dist/core/tool-result-store.d.ts +0 -18
- package/dist/core/tool-result-store.js +2 -18
- package/dist/core/tool-roster.d.ts +450 -0
- package/dist/core/tool-roster.js +497 -0
- package/dist/core/tool-spec.d.ts +54 -3
- package/dist/core/tools.js +7 -0
- package/dist/core/wiring-manifest.d.ts +13 -0
- package/dist/core/wiring-manifest.js +2 -1
- package/dist/core/write-protect.d.ts +2 -1
- package/dist/core/write-protect.js +5 -4
- package/dist/engine/compaction/utils.d.ts +6 -0
- package/dist/engine/compaction/utils.js +19 -19
- package/dist/engine/loop/types.d.ts +6 -0
- package/dist/index.d.ts +15 -4
- package/dist/index.js +15 -4
- package/dist/orchestration/goal.js +2 -1
- package/dist/orchestration/run-spec.js +3 -5
- package/dist/orchestration/run-workflow-tool.js +2 -2
- package/dist/prompt-assembly/event-registry.js +3 -3
- package/dist/prompt-assembly/tool-catalog.d.ts +2 -2
- package/dist/tools/fs/fs-bash.d.ts +1 -1
- package/dist/tools/fs/fs-bash.js +7 -11
- package/dist/tools/fs/fs-read.js +2 -2
- package/dist/tools/fs/fs-search-tools.d.ts +0 -7
- package/dist/tools/fs/fs-search-tools.js +3 -16
- package/dist/tools/fs/fs-write.js +4 -6
- package/dist/tools/fs/repo-map.js +2 -2
- package/dist/tools/fs/safety.d.ts +2 -1
- package/dist/tools/fs/safety.js +6 -5
- package/dist/tools/monitor.js +2 -2
- package/dist/tools/scheduler-tools.js +5 -8
- package/dist/tools/task-list.js +5 -8
- package/dist/tools/todo.js +2 -2
- package/dist/tools/web.js +3 -6
- package/dist/tools/worktree.js +3 -4
- package/package.json +4 -1
- package/test/export-surface.snapshot.json +147 -7
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import { getToolContract } from "../prompt-assembly/tool-catalog.js";
|
|
4
|
+
import { protocolOf } from "./protocol-table.js";
|
|
5
|
+
import { RENDER_HINT_ACTIVITY_MAX_CHARS, RENDER_HINT_MAX_CHARS, RENDER_HINT_MAX_LIST, TOOL_APPROVAL_CARDS, TOOL_FAMILIES, TOOL_KEYS_MAX, TOOL_KEY_MAX_CHARS, TOOL_MOUNT_TAGS, } from "./tool-face.js";
|
|
6
|
+
function lit(values) {
|
|
7
|
+
return Type.Union(values.map((v) => Type.Literal(v)));
|
|
8
|
+
}
|
|
9
|
+
const Name = Type.String({ minLength: 1, maxLength: 128 });
|
|
10
|
+
const Short = Type.String({ maxLength: 64 });
|
|
11
|
+
const Key = Type.String({ maxLength: TOOL_KEY_MAX_CHARS });
|
|
12
|
+
const Digest16 = Type.String({ minLength: 16, maxLength: 16 });
|
|
13
|
+
export const ROSTER_SOURCES = ["builtin", "caller", "host", "mcp", "a2a", "external", "synthetic"];
|
|
14
|
+
export const ToolRosterRenderHints = Type.Object({
|
|
15
|
+
userFacingName: Type.Optional(Type.String({ maxLength: RENDER_HINT_MAX_CHARS })),
|
|
16
|
+
activity: Type.Optional(Type.String({ maxLength: RENDER_HINT_ACTIVITY_MAX_CHARS })),
|
|
17
|
+
summaryParams: Type.Optional(Type.Array(Key, { maxItems: RENDER_HINT_MAX_LIST })),
|
|
18
|
+
resultCards: Type.Optional(Type.Array(Key, { maxItems: RENDER_HINT_MAX_LIST })),
|
|
19
|
+
approvalCard: Type.Optional(lit(TOOL_APPROVAL_CARDS)),
|
|
20
|
+
}, { additionalProperties: false });
|
|
21
|
+
export const ToolRosterEntry = Type.Object({
|
|
22
|
+
name: Name,
|
|
23
|
+
aliases: Type.Array(Name, { uniqueItems: true }),
|
|
24
|
+
source: lit(ROSTER_SOURCES),
|
|
25
|
+
origin: Type.Optional(Type.Object({ peer: Name, declaredBy: Type.Optional(lit(["center", "file", "request"])) }, { additionalProperties: false })),
|
|
26
|
+
contract: Type.Object({ contractId: Short, implementationRevision: Short }, { additionalProperties: false }),
|
|
27
|
+
shapeDigest: Digest16,
|
|
28
|
+
wireSchemaDigest: Digest16,
|
|
29
|
+
cardId: Type.String({ maxLength: 160 }),
|
|
30
|
+
capabilityId: Digest16,
|
|
31
|
+
inputKeys: Type.Array(Type.Object({ name: Key, type: lit(["string", "number", "integer", "boolean", "object", "array", "null", "mixed"]) }, { additionalProperties: false }), { maxItems: TOOL_KEYS_MAX }),
|
|
32
|
+
effect: lit(["read", "write", "idempotent"]),
|
|
33
|
+
effectProvenance: lit(["declared", "caller", "synthetic", "default"]),
|
|
34
|
+
offload: Type.Boolean(),
|
|
35
|
+
offloadThresholdChars: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
36
|
+
compactable: Type.Boolean(),
|
|
37
|
+
budgetExempt: Type.Boolean(),
|
|
38
|
+
egress: Type.Boolean(),
|
|
39
|
+
egressProvenance: lit(["declared", "caller", "default"]),
|
|
40
|
+
irreversibility: Type.Optional(lit(["never", "maybe", "always"])),
|
|
41
|
+
reversibilityProbe: Type.Boolean(),
|
|
42
|
+
contentOrigin: lit(["local", "execution", "external"]),
|
|
43
|
+
contentOriginProvenance: lit(["declared", "server", "exempted", "default"]),
|
|
44
|
+
family: lit(TOOL_FAMILIES),
|
|
45
|
+
pathTarget: Type.Optional(Type.Object({ param: Key, aliases: Type.Array(Key), access: lit(["read", "create", "edit"]), skillScopeEligible: Type.Boolean() }, { additionalProperties: false })),
|
|
46
|
+
ruleFace: Type.Object({ primaryParams: Type.Array(Key), params: Type.Array(Key) }, { additionalProperties: false }),
|
|
47
|
+
approvalPreview: Type.Boolean(),
|
|
48
|
+
validateInput: Type.Boolean(),
|
|
49
|
+
deferred: Type.Boolean(),
|
|
50
|
+
alwaysLoad: Type.Boolean(),
|
|
51
|
+
modelGate: Type.Optional(Short),
|
|
52
|
+
renderHints: Type.Optional(ToolRosterRenderHints),
|
|
53
|
+
mountedBy: lit(TOOL_MOUNT_TAGS),
|
|
54
|
+
}, { additionalProperties: false });
|
|
55
|
+
export const ToolRoster = Type.Object({
|
|
56
|
+
schemaVersion: Type.Literal(1),
|
|
57
|
+
count: Type.Integer({ minimum: 0 }),
|
|
58
|
+
entries: Type.Array(ToolRosterEntry, { maxItems: 2048 }),
|
|
59
|
+
digest: Digest16,
|
|
60
|
+
}, { additionalProperties: false });
|
|
61
|
+
export const ToolRosterDelta = Type.Object({
|
|
62
|
+
fromDigest: Digest16,
|
|
63
|
+
roster: ToolRoster,
|
|
64
|
+
summary: Type.Object({ added: Type.Array(Name), removed: Type.Array(Name), changed: Type.Array(Name) }, { additionalProperties: false }),
|
|
65
|
+
}, { additionalProperties: false });
|
|
66
|
+
export function rosterEntryNamed(roster, toolName) {
|
|
67
|
+
return roster.entries.find((r) => r.name === toolName) ?? roster.entries.find((r) => r.aliases.includes(toolName));
|
|
68
|
+
}
|
|
69
|
+
export function toolCallFaceOf(roster, toolName) {
|
|
70
|
+
if (roster === undefined)
|
|
71
|
+
return undefined;
|
|
72
|
+
const e = rosterEntryNamed(roster, toolName);
|
|
73
|
+
if (e === undefined)
|
|
74
|
+
return undefined;
|
|
75
|
+
return {
|
|
76
|
+
family: e.family,
|
|
77
|
+
...(e.pathTarget !== undefined ? { pathTarget: e.pathTarget } : {}),
|
|
78
|
+
capabilityId: e.capabilityId,
|
|
79
|
+
contractId: e.contract.contractId,
|
|
80
|
+
shapeDigest: e.shapeDigest,
|
|
81
|
+
implementationRevision: e.contract.implementationRevision,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export function parkedToolIdentity(roster, toolName) {
|
|
85
|
+
if (roster === undefined)
|
|
86
|
+
return {};
|
|
87
|
+
const e = rosterEntryNamed(roster, toolName);
|
|
88
|
+
if (e === undefined)
|
|
89
|
+
return {};
|
|
90
|
+
return { capabilityId: e.capabilityId, contractId: e.contract.contractId, shapeDigest: e.shapeDigest, implementationRevision: e.contract.implementationRevision, rosterDigest: roster.digest };
|
|
91
|
+
}
|
|
92
|
+
export function parkedToolFace(roster, toolName) {
|
|
93
|
+
if (roster === undefined)
|
|
94
|
+
return {};
|
|
95
|
+
const e = rosterEntryNamed(roster, toolName);
|
|
96
|
+
if (e === undefined)
|
|
97
|
+
return {};
|
|
98
|
+
return { family: e.family, ...(e.pathTarget !== undefined ? { pathTarget: e.pathTarget } : {}) };
|
|
99
|
+
}
|
|
100
|
+
export function parkedCallFaceOf(pending) {
|
|
101
|
+
if (pending.family === undefined || pending.capabilityId === undefined || pending.contractId === undefined || pending.shapeDigest === undefined || pending.implementationRevision === undefined)
|
|
102
|
+
return undefined;
|
|
103
|
+
return {
|
|
104
|
+
family: pending.family,
|
|
105
|
+
...(pending.pathTarget !== undefined ? { pathTarget: pending.pathTarget } : {}),
|
|
106
|
+
capabilityId: pending.capabilityId,
|
|
107
|
+
contractId: pending.contractId,
|
|
108
|
+
shapeDigest: pending.shapeDigest,
|
|
109
|
+
implementationRevision: pending.implementationRevision,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
export function callFaceSeat(face) {
|
|
113
|
+
return face !== undefined ? { face } : {};
|
|
114
|
+
}
|
|
115
|
+
export function toolIdentityOf(entry) {
|
|
116
|
+
return { capabilityId: entry.capabilityId, contractId: entry.contract.contractId, shapeDigest: entry.shapeDigest, implementationRevision: entry.contract.implementationRevision };
|
|
117
|
+
}
|
|
118
|
+
function canonical(node) {
|
|
119
|
+
if (Array.isArray(node))
|
|
120
|
+
return `[${node.map(canonical).join(",")}]`;
|
|
121
|
+
if (node === null || typeof node !== "object")
|
|
122
|
+
return JSON.stringify(node) ?? "null";
|
|
123
|
+
const keys = Object.keys(node).sort();
|
|
124
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonical(node[k])}`).join(",")}}`;
|
|
125
|
+
}
|
|
126
|
+
function sha16(text) {
|
|
127
|
+
return createHash("sha256").update(text).digest("hex").slice(0, 16);
|
|
128
|
+
}
|
|
129
|
+
export function mintCapabilityId(t) {
|
|
130
|
+
return sha16(canonical({ source: t.source, peer: t.peer, name: t.name, contractId: t.contractId }));
|
|
131
|
+
}
|
|
132
|
+
export function rosterDigestOf(entries) {
|
|
133
|
+
return sha16(canonical(entries));
|
|
134
|
+
}
|
|
135
|
+
const SCALAR_TYPES = new Set(["string", "number", "integer", "boolean"]);
|
|
136
|
+
function schemaTypeOf(node) {
|
|
137
|
+
if (node === null || typeof node !== "object")
|
|
138
|
+
return { type: "mixed", scalar: false };
|
|
139
|
+
const s = node;
|
|
140
|
+
const branches = s["anyOf"] ?? s["oneOf"];
|
|
141
|
+
if (Array.isArray(branches) && branches.length > 0) {
|
|
142
|
+
const parts = branches.map(schemaTypeOf);
|
|
143
|
+
const scalar = parts.every((p) => p.scalar);
|
|
144
|
+
const first = parts[0].type;
|
|
145
|
+
return parts.every((p) => p.type === first) ? { type: first, scalar } : { type: "mixed", scalar };
|
|
146
|
+
}
|
|
147
|
+
if (s["const"] !== undefined) {
|
|
148
|
+
const t = typeof s["const"];
|
|
149
|
+
return t === "string" || t === "number" || t === "boolean" ? { type: t, scalar: true } : { type: "mixed", scalar: false };
|
|
150
|
+
}
|
|
151
|
+
const en = s["enum"];
|
|
152
|
+
if (Array.isArray(en) && en.length > 0) {
|
|
153
|
+
const kinds = new Set(en.map((v) => typeof v));
|
|
154
|
+
if (kinds.size === 1) {
|
|
155
|
+
const t = [...kinds][0];
|
|
156
|
+
if (t === "string" || t === "number" || t === "boolean")
|
|
157
|
+
return { type: t, scalar: true };
|
|
158
|
+
}
|
|
159
|
+
return { type: "mixed", scalar: [...kinds].every((k) => k === "string" || k === "number" || k === "boolean") };
|
|
160
|
+
}
|
|
161
|
+
const t = s["type"];
|
|
162
|
+
if (typeof t === "string") {
|
|
163
|
+
if (SCALAR_TYPES.has(t))
|
|
164
|
+
return { type: t, scalar: true };
|
|
165
|
+
if (t === "object" || t === "array" || t === "null")
|
|
166
|
+
return { type: t, scalar: false };
|
|
167
|
+
}
|
|
168
|
+
if (Array.isArray(t))
|
|
169
|
+
return { type: "mixed", scalar: t.every((x) => typeof x === "string" && SCALAR_TYPES.has(x)) };
|
|
170
|
+
if (s["properties"] !== undefined)
|
|
171
|
+
return { type: "object", scalar: false };
|
|
172
|
+
if (s["items"] !== undefined)
|
|
173
|
+
return { type: "array", scalar: false };
|
|
174
|
+
return { type: "mixed", scalar: false };
|
|
175
|
+
}
|
|
176
|
+
export function inputKeysOf(schema) {
|
|
177
|
+
const keys = [];
|
|
178
|
+
const scalar = new Set();
|
|
179
|
+
const props = schema?.properties;
|
|
180
|
+
if (props !== null && typeof props === "object" && !Array.isArray(props)) {
|
|
181
|
+
for (const [name, sub] of Object.entries(props)) {
|
|
182
|
+
const t = schemaTypeOf(sub);
|
|
183
|
+
keys.push({ name, type: t.type });
|
|
184
|
+
if (t.scalar)
|
|
185
|
+
scalar.add(name);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return { keys, scalar };
|
|
189
|
+
}
|
|
190
|
+
export function schemaKeyBoundProblem(schema) {
|
|
191
|
+
const props = schema?.properties;
|
|
192
|
+
if (props === null || typeof props !== "object" || Array.isArray(props))
|
|
193
|
+
return undefined;
|
|
194
|
+
const names = Object.keys(props);
|
|
195
|
+
if (names.length > TOOL_KEYS_MAX)
|
|
196
|
+
return `the schema declares ${names.length} top-level properties; a roster row carries at most ${TOOL_KEYS_MAX}`;
|
|
197
|
+
const long = names.find((n) => n.length > TOOL_KEY_MAX_CHARS);
|
|
198
|
+
if (long !== undefined)
|
|
199
|
+
return `the schema property ${JSON.stringify(long.slice(0, 40) + "…")} is ${long.length} characters; a roster key is at most ${TOOL_KEY_MAX_CHARS}`;
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
export function ruleFaceOfInstance(schema, declared, pathTarget) {
|
|
203
|
+
const { scalar } = inputKeysOf(schema);
|
|
204
|
+
const derived = new Set(scalar);
|
|
205
|
+
if (pathTarget !== undefined) {
|
|
206
|
+
derived.add(pathTarget.param);
|
|
207
|
+
for (const a of pathTarget.aliases ?? [])
|
|
208
|
+
derived.add(a);
|
|
209
|
+
}
|
|
210
|
+
const primary = declared?.primaryParams !== undefined ? [...declared.primaryParams] : pathTarget !== undefined ? [pathTarget.param, ...(pathTarget.aliases ?? [])] : [];
|
|
211
|
+
for (const p of primary)
|
|
212
|
+
derived.add(p);
|
|
213
|
+
const params = declared?.params !== undefined ? declared.params.filter((p) => derived.has(p)) : [...derived];
|
|
214
|
+
const ordered = [...new Set([...params, ...primary.filter((p) => params.includes(p) || declared?.params === undefined)])];
|
|
215
|
+
return { primaryParams: primary, params: ordered };
|
|
216
|
+
}
|
|
217
|
+
function capRenderHints(h) {
|
|
218
|
+
const out = {};
|
|
219
|
+
if (typeof h.userFacingName === "string")
|
|
220
|
+
out.userFacingName = h.userFacingName;
|
|
221
|
+
if (typeof h.activity === "string")
|
|
222
|
+
out.activity = h.activity;
|
|
223
|
+
if (Array.isArray(h.summaryParams))
|
|
224
|
+
out.summaryParams = h.summaryParams.filter((s) => typeof s === "string");
|
|
225
|
+
if (Array.isArray(h.resultCards))
|
|
226
|
+
out.resultCards = h.resultCards.filter((s) => typeof s === "string");
|
|
227
|
+
if (typeof h.approvalCard === "string" && TOOL_APPROVAL_CARDS.includes(h.approvalCard))
|
|
228
|
+
out.approvalCard = h.approvalCard;
|
|
229
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
230
|
+
}
|
|
231
|
+
export const faceOf = (t) => t;
|
|
232
|
+
function configError(code, message) {
|
|
233
|
+
const e = new Error(message);
|
|
234
|
+
e.code = code;
|
|
235
|
+
return e;
|
|
236
|
+
}
|
|
237
|
+
export class RosterBuilder {
|
|
238
|
+
#tools = [];
|
|
239
|
+
#engineLane = [];
|
|
240
|
+
#meta = new WeakMap();
|
|
241
|
+
#effects;
|
|
242
|
+
constructor(effects) {
|
|
243
|
+
this.#effects = effects;
|
|
244
|
+
}
|
|
245
|
+
get tools() {
|
|
246
|
+
return this.#tools;
|
|
247
|
+
}
|
|
248
|
+
get length() {
|
|
249
|
+
return this.#tools.length;
|
|
250
|
+
}
|
|
251
|
+
metaOf(tool) {
|
|
252
|
+
return this.#meta.get(tool);
|
|
253
|
+
}
|
|
254
|
+
mount(tool, meta) {
|
|
255
|
+
this.#admit(tool, meta);
|
|
256
|
+
this.#tools.push(tool);
|
|
257
|
+
}
|
|
258
|
+
mountEngineLane(tool, meta) {
|
|
259
|
+
this.#admit(tool, meta);
|
|
260
|
+
this.#engineLane.push(tool);
|
|
261
|
+
}
|
|
262
|
+
#admit(tool, meta) {
|
|
263
|
+
if ((meta.source === "mcp" || meta.source === "a2a" || meta.source === "external") !== (meta.origin !== undefined)) {
|
|
264
|
+
throw configError("config.tool_roster_origin", `tool ${JSON.stringify(tool.name)}: a ${meta.source} mount ${meta.origin === undefined ? "requires" : "must not carry"} an origin`);
|
|
265
|
+
}
|
|
266
|
+
if (!TOOL_MOUNT_TAGS.includes(meta.mountedBy))
|
|
267
|
+
throw configError("config.tool_roster_mount_tag", `tool ${JSON.stringify(tool.name)}: mount tag ${JSON.stringify(meta.mountedBy)} is outside the closed set`);
|
|
268
|
+
this.#meta.set(tool, meta);
|
|
269
|
+
const eff = faceOf(tool).effect;
|
|
270
|
+
if (eff !== undefined && !(meta.mountedBy === "scenario" && this.#effects.has(tool.name)))
|
|
271
|
+
this.#effects.set(tool.name, eff);
|
|
272
|
+
}
|
|
273
|
+
mountAll(tools, meta) {
|
|
274
|
+
for (const t of tools)
|
|
275
|
+
this.mount(t, meta);
|
|
276
|
+
}
|
|
277
|
+
unmountWhere(pred) {
|
|
278
|
+
const removed = [];
|
|
279
|
+
for (let i = this.#tools.length - 1; i >= 0; i--) {
|
|
280
|
+
const t = this.#tools[i];
|
|
281
|
+
if (pred(t, i)) {
|
|
282
|
+
this.#tools.splice(i, 1);
|
|
283
|
+
removed.unshift(t);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return removed;
|
|
287
|
+
}
|
|
288
|
+
replaceAt(index, tool) {
|
|
289
|
+
const prior = this.#tools[index];
|
|
290
|
+
if (prior === undefined)
|
|
291
|
+
throw new Error(`roster: no tool at index ${index}`);
|
|
292
|
+
const meta = this.#meta.get(prior);
|
|
293
|
+
this.#tools[index] = tool;
|
|
294
|
+
if (meta !== undefined)
|
|
295
|
+
this.#meta.set(tool, meta);
|
|
296
|
+
}
|
|
297
|
+
replaceDomain(prefix, fresh, meta) {
|
|
298
|
+
let anchor = -1;
|
|
299
|
+
for (let i = this.#tools.length - 1; i >= 0; i--) {
|
|
300
|
+
if (this.#tools[i].name.startsWith(prefix)) {
|
|
301
|
+
anchor = i;
|
|
302
|
+
this.#tools.splice(i, 1);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
for (const t of fresh) {
|
|
306
|
+
this.#meta.set(t, meta);
|
|
307
|
+
const eff = faceOf(t).effect;
|
|
308
|
+
if (eff !== undefined)
|
|
309
|
+
this.#effects.set(t.name, eff);
|
|
310
|
+
}
|
|
311
|
+
if (anchor >= 0)
|
|
312
|
+
this.#tools.splice(anchor, 0, ...fresh);
|
|
313
|
+
else
|
|
314
|
+
this.#tools.push(...fresh);
|
|
315
|
+
return { anchor };
|
|
316
|
+
}
|
|
317
|
+
snapshot(facts) {
|
|
318
|
+
const all = [...this.#tools, ...this.#engineLane];
|
|
319
|
+
const winner = new Map();
|
|
320
|
+
for (const t of all)
|
|
321
|
+
winner.set(t.name, t);
|
|
322
|
+
const entries = [];
|
|
323
|
+
const emitted = new Set();
|
|
324
|
+
for (const t of all) {
|
|
325
|
+
if (emitted.has(t.name))
|
|
326
|
+
continue;
|
|
327
|
+
emitted.add(t.name);
|
|
328
|
+
const survivor = winner.get(t.name);
|
|
329
|
+
const meta = this.#meta.get(survivor) ?? { source: "caller", mountedBy: "scenario" };
|
|
330
|
+
entries.push(mintRosterEntry(survivor, meta, facts));
|
|
331
|
+
}
|
|
332
|
+
return { schemaVersion: 1, count: entries.length, entries, digest: rosterDigestOf(entries) };
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
export function callerMountSource(tool) {
|
|
336
|
+
return getToolContract(tool, "caller").origin === "core" ? "builtin" : "caller";
|
|
337
|
+
}
|
|
338
|
+
export function mintRosterEntry(tool, meta, facts) {
|
|
339
|
+
const face = faceOf(tool);
|
|
340
|
+
const fallbackOrigin = meta.source === "builtin" ? "core" : meta.source === "synthetic" ? "synthetic" : meta.source === "mcp" || meta.source === "a2a" ? meta.source : "caller";
|
|
341
|
+
const d = getToolContract(tool, fallbackOrigin);
|
|
342
|
+
const peer = meta.origin?.peer ?? null;
|
|
343
|
+
const protocolTool = peer !== null ? protocolOf(tool.name)?.parse(tool.name)?.tool ?? tool.name : tool.name;
|
|
344
|
+
const cardId = meta.source === "builtin" || meta.source === "synthetic"
|
|
345
|
+
? `builtin:${tool.name}`
|
|
346
|
+
: meta.source === "mcp" || meta.source === "a2a"
|
|
347
|
+
? `${meta.source}:${peer}/${protocolTool}`
|
|
348
|
+
: `${meta.source === "external" ? "external" : "caller"}:${d.contractId}`;
|
|
349
|
+
const { keys: inputKeys } = inputKeysOf(tool.parameters);
|
|
350
|
+
const declaredEffect = face.effect;
|
|
351
|
+
const foldedEffect = facts.effects.get(tool.name);
|
|
352
|
+
const effect = declaredEffect ?? foldedEffect ?? "write";
|
|
353
|
+
const effectProvenance = declaredEffect !== undefined ? (meta.source === "synthetic" ? "synthetic" : "declared") : foldedEffect !== undefined && (meta.source === "mcp" || meta.source === "a2a") ? "caller" : "default";
|
|
354
|
+
const egress = face.egress === true || facts.egress.has(tool.name);
|
|
355
|
+
const egressProvenance = face.egress === true ? "declared" : egress ? "caller" : "default";
|
|
356
|
+
const irreversibility = face.irreversibility ?? facts.irreversibilityTier.get(tool.name);
|
|
357
|
+
const reversibilityProbe = face.reversibilityProbe !== undefined || facts.reversibilityProbes.has(tool.name);
|
|
358
|
+
const contentOrigin = face.contentOrigin ?? (facts.trustedTools.has(tool.name) ? "local" : "external");
|
|
359
|
+
const contentOriginProvenance = face.contentOrigin !== undefined ? (meta.source === "mcp" || meta.source === "a2a" ? "server" : "declared") : facts.trustedTools.has(tool.name) ? "exempted" : "default";
|
|
360
|
+
const pathTarget = face.pathTarget;
|
|
361
|
+
const rf = ruleFaceOfInstance(tool.parameters, face.ruleFace, pathTarget);
|
|
362
|
+
const hints = face.renderHints !== undefined ? capRenderHints(face.renderHints) : undefined;
|
|
363
|
+
return {
|
|
364
|
+
name: tool.name,
|
|
365
|
+
aliases: [...new Set(tool.aliases ?? [])],
|
|
366
|
+
source: meta.source,
|
|
367
|
+
...(meta.origin !== undefined ? { origin: { peer: meta.origin.peer, ...(meta.origin.declaredBy !== undefined ? { declaredBy: meta.origin.declaredBy } : {}) } } : {}),
|
|
368
|
+
contract: { contractId: d.contractId.slice(0, 64), implementationRevision: d.implementationRevision.slice(0, 64) },
|
|
369
|
+
shapeDigest: d.shapeDigest,
|
|
370
|
+
wireSchemaDigest: d.wireSchemaDigest,
|
|
371
|
+
cardId: cardId.slice(0, 160),
|
|
372
|
+
capabilityId: mintCapabilityId({ source: meta.source, peer, name: tool.name, contractId: d.contractId }),
|
|
373
|
+
inputKeys,
|
|
374
|
+
effect,
|
|
375
|
+
effectProvenance,
|
|
376
|
+
offload: face.offload !== false,
|
|
377
|
+
...(typeof face.offloadThresholdChars === "number" && Number.isFinite(face.offloadThresholdChars) && face.offloadThresholdChars >= 0 ? { offloadThresholdChars: Math.floor(face.offloadThresholdChars) } : {}),
|
|
378
|
+
compactable: face.compactable === true,
|
|
379
|
+
budgetExempt: face.budgetExempt === true,
|
|
380
|
+
egress,
|
|
381
|
+
egressProvenance,
|
|
382
|
+
...(irreversibility !== undefined ? { irreversibility } : {}),
|
|
383
|
+
reversibilityProbe,
|
|
384
|
+
contentOrigin,
|
|
385
|
+
contentOriginProvenance,
|
|
386
|
+
family: face.family !== undefined && TOOL_FAMILIES.includes(face.family) ? face.family : "other",
|
|
387
|
+
...(pathTarget !== undefined
|
|
388
|
+
? { pathTarget: { param: pathTarget.param, aliases: [...(pathTarget.aliases ?? [])], access: pathTarget.access, skillScopeEligible: pathTarget.skillScopeEligible === true } }
|
|
389
|
+
: {}),
|
|
390
|
+
ruleFace: rf,
|
|
391
|
+
approvalPreview: typeof tool.approvalPreview === "function",
|
|
392
|
+
validateInput: typeof tool.validateInput === "function",
|
|
393
|
+
deferred: facts.deferred.has(tool.name),
|
|
394
|
+
alwaysLoad: face.alwaysLoad === true || face.mcpAlwaysLoad === true,
|
|
395
|
+
...(typeof face.modelGate === "string" ? { modelGate: face.modelGate.slice(0, 64) } : {}),
|
|
396
|
+
...(hints !== undefined ? { renderHints: hints } : {}),
|
|
397
|
+
mountedBy: meta.mountedBy,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
export class ToolRosterDeltaSeat {
|
|
401
|
+
#current;
|
|
402
|
+
#snapshot;
|
|
403
|
+
#pending = [];
|
|
404
|
+
#sink;
|
|
405
|
+
arm(current, snapshot) {
|
|
406
|
+
this.#current = current;
|
|
407
|
+
this.#snapshot = snapshot;
|
|
408
|
+
}
|
|
409
|
+
get current() {
|
|
410
|
+
return this.#current;
|
|
411
|
+
}
|
|
412
|
+
republish() {
|
|
413
|
+
if (this.#current === undefined || this.#snapshot === undefined)
|
|
414
|
+
return undefined;
|
|
415
|
+
const next = this.#snapshot();
|
|
416
|
+
const delta = diffToolRoster(this.#current, next);
|
|
417
|
+
this.#current = next;
|
|
418
|
+
if (this.#sink !== undefined)
|
|
419
|
+
this.#sink(delta);
|
|
420
|
+
else {
|
|
421
|
+
this.#pending.push(delta);
|
|
422
|
+
if (this.#pending.length > 64)
|
|
423
|
+
this.#pending.shift();
|
|
424
|
+
}
|
|
425
|
+
return delta;
|
|
426
|
+
}
|
|
427
|
+
subscribe(sink) {
|
|
428
|
+
this.#sink = sink;
|
|
429
|
+
const pending = this.#pending;
|
|
430
|
+
this.#pending = [];
|
|
431
|
+
for (const d of pending)
|
|
432
|
+
sink(d);
|
|
433
|
+
}
|
|
434
|
+
drain() {
|
|
435
|
+
const pending = this.#pending;
|
|
436
|
+
this.#pending = [];
|
|
437
|
+
return pending;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
export function ruleFacesFromRoster(roster) {
|
|
441
|
+
const byName = new Map(roster.entries.map((e) => [e.name, e]));
|
|
442
|
+
for (const e of roster.entries)
|
|
443
|
+
for (const a of e.aliases)
|
|
444
|
+
if (!byName.has(a))
|
|
445
|
+
byName.set(a, e);
|
|
446
|
+
return (toolName) => {
|
|
447
|
+
const e = byName.get(toolName);
|
|
448
|
+
if (e === undefined)
|
|
449
|
+
return undefined;
|
|
450
|
+
const params = new Set(e.ruleFace.params);
|
|
451
|
+
return {
|
|
452
|
+
primaryParams: e.ruleFace.primaryParams,
|
|
453
|
+
params: e.ruleFace.params,
|
|
454
|
+
nonScalar: e.inputKeys.filter((k) => !params.has(k.name) && (k.type === "object" || k.type === "array")).map((k) => k.name),
|
|
455
|
+
pathTargeting: e.pathTarget !== undefined,
|
|
456
|
+
};
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
export function diffToolRoster(prev, next) {
|
|
460
|
+
const before = new Map(prev.entries.map((e) => [e.name, canonical(e)]));
|
|
461
|
+
const after = new Map(next.entries.map((e) => [e.name, canonical(e)]));
|
|
462
|
+
const added = [];
|
|
463
|
+
const removed = [];
|
|
464
|
+
const changed = [];
|
|
465
|
+
for (const [name, row] of after) {
|
|
466
|
+
const old = before.get(name);
|
|
467
|
+
if (old === undefined)
|
|
468
|
+
added.push(name);
|
|
469
|
+
else if (old !== row)
|
|
470
|
+
changed.push(name);
|
|
471
|
+
}
|
|
472
|
+
for (const name of before.keys())
|
|
473
|
+
if (!after.has(name))
|
|
474
|
+
removed.push(name);
|
|
475
|
+
return { fromDigest: prev.digest, roster: next, summary: { added, removed, changed } };
|
|
476
|
+
}
|
|
477
|
+
export function judgeParkedToolIdentity(pending, row) {
|
|
478
|
+
const carried = pending.capabilityId !== undefined || pending.contractId !== undefined || pending.shapeDigest !== undefined || pending.implementationRevision !== undefined;
|
|
479
|
+
if (row === undefined)
|
|
480
|
+
return "absent";
|
|
481
|
+
if (!carried)
|
|
482
|
+
return "name_only";
|
|
483
|
+
const id = toolIdentityOf(row);
|
|
484
|
+
return pending.capabilityId === id.capabilityId && pending.contractId === id.contractId && pending.shapeDigest === id.shapeDigest && pending.implementationRevision === id.implementationRevision ? "match" : "mismatch";
|
|
485
|
+
}
|
|
486
|
+
export function offendingResultCard(hints, cardId, engineCards) {
|
|
487
|
+
for (const c of hints?.resultCards ?? []) {
|
|
488
|
+
if (typeof c !== "string")
|
|
489
|
+
continue;
|
|
490
|
+
if (c.startsWith(`${cardId}/`))
|
|
491
|
+
continue;
|
|
492
|
+
if (c.startsWith("builtin:") || engineCards.has(c) || !c.includes("/"))
|
|
493
|
+
return c;
|
|
494
|
+
return c;
|
|
495
|
+
}
|
|
496
|
+
return undefined;
|
|
497
|
+
}
|
package/dist/core/tool-spec.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type { AgentDefinition } from "./agent-definition.js";
|
|
|
14
14
|
import type { TaskEvent } from "./task-event.js";
|
|
15
15
|
import type { TaskSpec } from "./task-spec.js";
|
|
16
16
|
import type { BackgroundChildEvent } from "./delegation-frames.js";
|
|
17
|
+
import type { ToolFamily, ToolPathTarget, ToolRenderHints, ToolRuleFace } from "./tool-face.js";
|
|
17
18
|
/**
|
|
18
19
|
* Side-effect class of a tool. Used by wake/resume reconciliation: when a call was interrupted
|
|
19
20
|
* before its result was recorded, the outcome is unknown, so core must decide whether re-doing it
|
|
@@ -403,9 +404,10 @@ export interface ToolSpec<TParams extends TSchema = TSchema> {
|
|
|
403
404
|
* (`config.tool_model_gate_unknown_class`). Restore channels: explicit composition (no tag),
|
|
404
405
|
* {@link TaskSpec.restoreGatedTools}, env `SEMA_TOOL_MODEL_GATE=off`,
|
|
405
406
|
* `RunnerDeps.toolModelGate: false`; {@link TaskSpec.excludeTools} always wins regardless.
|
|
406
|
-
* The
|
|
407
|
-
*
|
|
408
|
-
*
|
|
407
|
+
* The gate DECISION completes on the ToolSpec face inside `prepareConfigDoors`, before any
|
|
408
|
+
* conversion; `defineTool` then forwards the class onto the materialized face (design/388: the
|
|
409
|
+
* effect roster reports `modelGate` on a row that survived the gate, so an operator can read which
|
|
410
|
+
* class a mounted scaffold belongs to — the forwarded value is a read face, never a second gate).
|
|
409
411
|
*/
|
|
410
412
|
modelGate?: string;
|
|
411
413
|
/**
|
|
@@ -421,6 +423,55 @@ export interface ToolSpec<TParams extends TSchema = TSchema> {
|
|
|
421
423
|
/** Declared behavior revision — bump when execute-visible behavior changes. */
|
|
422
424
|
implementationRevision: string;
|
|
423
425
|
};
|
|
426
|
+
/**
|
|
427
|
+
* design/388 §2.2 ① — the DISPLAY / DISPATCH family of this tool (closed set {@link TOOL_FAMILIES}).
|
|
428
|
+
* Input to grouping, card pre-selection and the rule editor's completion — never an authorization
|
|
429
|
+
* source: no gate reads it. Absent on a caller/MCP tool ⇒ the roster reports `"other"`; every
|
|
430
|
+
* built-in definition in the static catalog declares one (the catalog completeness gate reds a
|
|
431
|
+
* missing declaration rather than defaulting it).
|
|
432
|
+
*/
|
|
433
|
+
family?: ToolFamily;
|
|
434
|
+
/**
|
|
435
|
+
* design/388 §2.2 ② — the parameter that names this tool's filesystem TARGET and how it is touched.
|
|
436
|
+
* Present ⇔ the tool can be confirmed BY PATH: write confinement (`allowDirs`, the fs-write gate,
|
|
437
|
+
* the sensitive-path guard, frozen-path denies), edited-file tracking and the transcript-directory
|
|
438
|
+
* gate all read it off the call's face instead of a name table. ABSENT ⇒ the tool is handled as a
|
|
439
|
+
* write whose target cannot be confirmed (fail-closed under every path-confining face) — exactly how
|
|
440
|
+
* a tool outside the retired name tables was handled. `param` outranks `aliases` (NotebookEdit's
|
|
441
|
+
* `notebook_path` beats a decoy `file_path`). `skillScopeEligible` is a SEPARATE grant: only a tool
|
|
442
|
+
* that sets it enters the active-skill `allowPaths` face (Write/Edit do; NotebookEdit deliberately
|
|
443
|
+
* does not; a caller tool that merely takes a built-in's NAME inherits nothing).
|
|
444
|
+
*/
|
|
445
|
+
pathTarget?: ToolPathTarget;
|
|
446
|
+
/**
|
|
447
|
+
* design/388 §2.2 ③ — the parameter vocabulary a permission rule (`Tool(param:pattern)`) may name.
|
|
448
|
+
* DERIVED from the schema when absent (the scalar top-level parameters plus the path target's
|
|
449
|
+
* aliases; object/array parameters never enter — a rule on them can never match). A declaration can
|
|
450
|
+
* only NARROW the derived set. A tool whose `prepareArguments` renames keys must declare its face
|
|
451
|
+
* explicitly (the schema keys are not what a call carries).
|
|
452
|
+
*/
|
|
453
|
+
ruleFace?: ToolRuleFace;
|
|
454
|
+
/**
|
|
455
|
+
* design/388 §2.2 ④ — declarative render hints for a shell that has no local implementation of
|
|
456
|
+
* this tool (an MCP / caller / external tool). Every string is UNTRUSTED display input: the
|
|
457
|
+
* roster caps lengths, the shell strips control and bidi characters. `approvalCard` is a
|
|
458
|
+
* pre-filter, never a compatibility proof — the shell validates the call's args against its own
|
|
459
|
+
* card schema before mounting a specialised card. `resultCards` may only name this tool's own
|
|
460
|
+
* `cardId` namespace; a declaration naming an engine card (`builtin:*`, `mcp`) is refused at prepare.
|
|
461
|
+
*/
|
|
462
|
+
renderHints?: ToolRenderHints;
|
|
463
|
+
/**
|
|
464
|
+
* design/388 §2.2 ⑤ — this tool's results are CLEARABLE by the context-edit micro-compaction
|
|
465
|
+
* (the CC `microCompact` whitelist, previously the name table `COMPACTABLE_TOOLS`). Default
|
|
466
|
+
* false: an undeclared tool's results survive to the harder defenses.
|
|
467
|
+
*/
|
|
468
|
+
compactable?: true;
|
|
469
|
+
/**
|
|
470
|
+
* design/388 §2.2 ⑤ — this tool's results are EXEMPT from the per-batch aggregate result budget
|
|
471
|
+
* because they are self-bounded (Read pages at its own cap; the offload reader pages small
|
|
472
|
+
* slices). Default false: every undeclared tool counts against the budget.
|
|
473
|
+
*/
|
|
474
|
+
budgetExempt?: true;
|
|
424
475
|
/**
|
|
425
476
|
* Execute the tool. Return a string (becomes text content) or a structured result.
|
|
426
477
|
* Throw to signal failure (the loop encodes it as an error tool result).
|
package/dist/core/tools.js
CHANGED
|
@@ -54,6 +54,13 @@ export function defineTool(spec, options) {
|
|
|
54
54
|
...(spec.defer !== undefined ? { defer: spec.defer } : {}),
|
|
55
55
|
...(spec.alwaysLoad !== undefined ? { alwaysLoad: spec.alwaysLoad } : {}),
|
|
56
56
|
...(spec.prepareArguments ? { prepareArguments: spec.prepareArguments } : {}),
|
|
57
|
+
...(spec.family !== undefined ? { family: spec.family } : {}),
|
|
58
|
+
...(spec.pathTarget !== undefined ? { pathTarget: spec.pathTarget } : {}),
|
|
59
|
+
...(spec.ruleFace !== undefined ? { ruleFace: spec.ruleFace } : {}),
|
|
60
|
+
...(spec.renderHints !== undefined ? { renderHints: spec.renderHints } : {}),
|
|
61
|
+
...(spec.compactable === true ? { compactable: true } : {}),
|
|
62
|
+
...(spec.budgetExempt === true ? { budgetExempt: true } : {}),
|
|
63
|
+
...(spec.modelGate !== undefined ? { modelGate: spec.modelGate } : {}),
|
|
57
64
|
...(spec.approvalPreview ? { approvalPreview: spec.approvalPreview } : {}),
|
|
58
65
|
...(spec.validateInput ? { validateInput: spec.validateInput } : {}),
|
|
59
66
|
execute: async (toolCallId, rawParams, signal) => {
|
|
@@ -262,6 +262,17 @@ export interface WiringManifest {
|
|
|
262
262
|
* assembly, and legs of one assembly must fingerprint alike whether or not a server was up.
|
|
263
263
|
*/
|
|
264
264
|
mcp?: readonly WiringManifestMcpEntry[];
|
|
265
|
+
/**
|
|
266
|
+
* design/388 §2.5 — EFFECTIVE half only, and ALWAYS present on an engine-minted effective manifest:
|
|
267
|
+
* the leg's TOOL ROSTER — one row per mounted instance in wire order, every declared axis, the
|
|
268
|
+
* engine-minted identity (`capabilityId`), the schema-derived `inputKeys` / rule vocabulary and the
|
|
269
|
+
* roster `digest`. The static half has no roster to report (it mounts nothing). NOT part of
|
|
270
|
+
* {@link configFingerprint}: a roster is per-leg by construction (a child leg mounts a different set
|
|
271
|
+
* under the same assembly), so hashing it would break the cross-leg equality the fingerprint
|
|
272
|
+
* advertises — the roster carries its own digest for that. A run-time change to the set arrives as a
|
|
273
|
+
* `tool_roster_delta` event, never as a re-emitted manifest.
|
|
274
|
+
*/
|
|
275
|
+
tools?: import("./tool-roster.js").ToolRoster;
|
|
265
276
|
/** EFFECTIVE half only — a short, non-sensitive fingerprint (sha256 prefix over the canonical
|
|
266
277
|
* JSON of this manifest's own resolved facts; every field here is an enum/boolean/count, no
|
|
267
278
|
* secrets) so an operator can correlate legs that ran under the same resolved assembly. */
|
|
@@ -333,6 +344,8 @@ export interface WiringFacts {
|
|
|
333
344
|
/** Effective half only — see {@link WiringManifest.mcp}; the static half materializes nothing.
|
|
334
345
|
* Copied entry-wise onto the manifest (the caller's array is never aliased). */
|
|
335
346
|
mcp?: readonly WiringManifestMcpEntry[];
|
|
347
|
+
/** Effective half only — the leg's minted tool roster (see {@link WiringManifest.tools}). */
|
|
348
|
+
tools?: import("./tool-roster.js").ToolRoster;
|
|
336
349
|
}
|
|
337
350
|
/** Named view of the deps seats the static half reads (a `Pick` of the real {@link RunnerDeps} —
|
|
338
351
|
* single-source shapes, no parallel hand-copied interface). */
|
|
@@ -127,9 +127,10 @@ export function deriveWiringManifest(facts) {
|
|
|
127
127
|
...(facts.half === "effective" && facts.modelGate !== undefined ? { modelGate: { class: facts.modelGate.class, removed: [...facts.modelGate.removed], restore: facts.modelGate.restore } } : {}),
|
|
128
128
|
...(facts.half === "effective" && facts.autoMode !== undefined ? { autoMode: readAutoModeFact(facts.autoMode) } : {}),
|
|
129
129
|
...(facts.half === "effective" && facts.mcp !== undefined ? { mcp: facts.mcp.map((e) => ({ ...e })) } : {}),
|
|
130
|
+
...(facts.half === "effective" && facts.tools !== undefined ? { tools: facts.tools } : {}),
|
|
130
131
|
};
|
|
131
132
|
if (facts.half === "effective") {
|
|
132
|
-
const { leg: _leg, mcp: _mcp, ...assembly } = manifest;
|
|
133
|
+
const { leg: _leg, mcp: _mcp, tools: _tools, ...assembly } = manifest;
|
|
133
134
|
const { provenance: _askSeat, ...askForHash } = assembly.ask;
|
|
134
135
|
const { provenance: _questionSeat, ...questionForHash } = assembly.question;
|
|
135
136
|
manifest.configFingerprint = createHash("sha256")
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ToolCallLike } from "./tool-registry.js";
|
|
1
2
|
/**
|
|
2
3
|
* How one table row matches the judged path:
|
|
3
4
|
* · `"basename"` — the target's last segment equals the row name (CC `DANGEROUS_FILES` form);
|
|
@@ -81,4 +82,4 @@ export declare function compileWriteProtection(entries?: readonly WriteProtected
|
|
|
81
82
|
*/
|
|
82
83
|
export declare function createWriteProtectionCheck(entries?: readonly WriteProtectedEntry[], opts?: {
|
|
83
84
|
dataRoot?: string;
|
|
84
|
-
}): ((toolName: string, args: unknown) => WriteProtectedHit | null) | undefined;
|
|
85
|
+
}): ((toolName: string, args: unknown, face?: ToolCallLike["face"]) => WriteProtectedHit | null) | undefined;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isWinFormPath, writeTargetPath, isUncPath } from "../tools/fs/safety.js";
|
|
2
|
-
import {
|
|
2
|
+
import { isProtectedWrite } from "./tool-registry.js";
|
|
3
3
|
import { dataRootSpellings } from "./sensitive-path-policy.js";
|
|
4
4
|
const freezeTable = (rows) => Object.freeze(rows.map((r) => Object.freeze(r)));
|
|
5
5
|
export const WRITE_PROTECTED_DEFAULT_TABLE = freezeTable([
|
|
@@ -213,10 +213,11 @@ export function createWriteProtectionCheck(entries, opts) {
|
|
|
213
213
|
const matcher = compileWriteProtection(entries, opts);
|
|
214
214
|
if (matcher === undefined)
|
|
215
215
|
return undefined;
|
|
216
|
-
return (toolName, args) => {
|
|
217
|
-
|
|
216
|
+
return (toolName, args, face) => {
|
|
217
|
+
const call = { toolName, args, ...(face !== undefined ? { face } : {}) };
|
|
218
|
+
if (!isProtectedWrite(call))
|
|
218
219
|
return null;
|
|
219
|
-
const path = writeTargetPath(
|
|
220
|
+
const path = writeTargetPath(call);
|
|
220
221
|
if (typeof path !== "string" || path.length === 0)
|
|
221
222
|
return null;
|
|
222
223
|
return matcher.matchPath(path);
|