@sema-agent/core 7.7.0 → 7.9.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.
Files changed (186) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/dist/agents/agent-transcript-tool.js +2 -2
  3. package/dist/agents/list-agents-tool.js +2 -3
  4. package/dist/agents/observer.js +2 -2
  5. package/dist/agents/send-message-tool.js +2 -1
  6. package/dist/agents/subagent-steps.js +5 -4
  7. package/dist/agents/subagent.d.ts +1 -1
  8. package/dist/agents/subagent.js +2 -3
  9. package/dist/core/ask-origin.d.ts +12 -1
  10. package/dist/core/ask-origin.js +5 -1
  11. package/dist/core/ask-question.js +2 -2
  12. package/dist/core/checkpoint-store.d.ts +64 -24
  13. package/dist/core/context-edit.d.ts +0 -22
  14. package/dist/core/context-edit.js +2 -11
  15. package/dist/core/engine-notice.d.ts +6 -0
  16. package/dist/core/fs-write-gate-policy.d.ts +7 -1
  17. package/dist/core/fs-write-gate-policy.js +7 -7
  18. package/dist/core/gate-fold.js +2 -2
  19. package/dist/core/gate-lanes.js +53 -26
  20. package/dist/core/gate-outcome.d.ts +8 -4
  21. package/dist/core/gate-outcome.js +2 -1
  22. package/dist/core/governance-codes.d.ts +1 -1
  23. package/dist/core/governance-codes.js +6 -0
  24. package/dist/core/hooks.d.ts +64 -33
  25. package/dist/core/hooks.js +1 -1
  26. package/dist/core/lsp.js +2 -2
  27. package/dist/core/mcp-server-spec.d.ts +17 -0
  28. package/dist/core/mcp.js +3 -1
  29. package/dist/core/memory-engine/tools.js +4 -12
  30. package/dist/core/permission-rule-consent.d.ts +29 -36
  31. package/dist/core/permission-rule-consent.js +110 -60
  32. package/dist/core/permission-rule-model.d.ts +254 -55
  33. package/dist/core/permission-rule-model.js +323 -43
  34. package/dist/core/permission-rule-org.d.ts +9 -8
  35. package/dist/core/permission-rule-org.js +6 -15
  36. package/dist/core/permission-rule-provider.d.ts +10 -6
  37. package/dist/core/permission-rule-provider.js +12 -8
  38. package/dist/core/permission-rule-session.d.ts +7 -6
  39. package/dist/core/permission-rule-session.js +29 -10
  40. package/dist/core/permission-rule-store.d.ts +40 -18
  41. package/dist/core/permission-rule-store.js +68 -42
  42. package/dist/core/permission-rule-sync.d.ts +9 -3
  43. package/dist/core/permission-rule-sync.js +29 -25
  44. package/dist/core/permission-rule-syntax.d.ts +30 -0
  45. package/dist/core/permission-rule-syntax.js +44 -0
  46. package/dist/core/permission-rules.d.ts +85 -33
  47. package/dist/core/permission-rules.js +104 -88
  48. package/dist/core/persisted-rule-arms.d.ts +56 -0
  49. package/dist/core/persisted-rule-arms.js +48 -0
  50. package/dist/core/present-plan-tool.js +3 -4
  51. package/dist/core/runner/active-skill-scope.js +7 -4
  52. package/dist/core/runner/compaction-call-options.d.ts +1 -1
  53. package/dist/core/runner/contracts.d.ts +46 -3
  54. package/dist/core/runner/permission-rule-lanes.d.ts +33 -26
  55. package/dist/core/runner/permission-rule-lanes.js +27 -21
  56. package/dist/core/runner/prepare-ask-lane.d.ts +1 -1
  57. package/dist/core/runner/prepare-caps-and-workflow.d.ts +5 -4
  58. package/dist/core/runner/prepare-caps-and-workflow.js +16 -11
  59. package/dist/core/runner/prepare-defer-classify.d.ts +5 -26
  60. package/dist/core/runner/prepare-defer-classify.js +10 -10
  61. package/dist/core/runner/prepare-delegation-surface.d.ts +5 -4
  62. package/dist/core/runner/prepare-delegation-surface.js +15 -17
  63. package/dist/core/runner/prepare-gate-stations.d.ts +4 -1
  64. package/dist/core/runner/prepare-gate-stations.js +4 -2
  65. package/dist/core/runner/prepare-hands-readface.d.ts +3 -2
  66. package/dist/core/runner/prepare-hands-readface.js +3 -2
  67. package/dist/core/runner/prepare-inherited-gate.d.ts +1 -1
  68. package/dist/core/runner/prepare-inherited-gate.js +1 -0
  69. package/dist/core/runner/prepare-lsp.d.ts +4 -3
  70. package/dist/core/runner/prepare-lsp.js +3 -2
  71. package/dist/core/runner/prepare-memory-engine-session.d.ts +4 -4
  72. package/dist/core/runner/prepare-memory-engine-session.js +4 -2
  73. package/dist/core/runner/prepare-offload-wrappers.js +7 -5
  74. package/dist/core/runner/prepare-park-ask.d.ts +4 -0
  75. package/dist/core/runner/prepare-park-ask.js +4 -1
  76. package/dist/core/runner/prepare-project-context.d.ts +5 -23
  77. package/dist/core/runner/prepare-project-context.js +7 -5
  78. package/dist/core/runner/prepare-prompt-assembly.d.ts +1 -1
  79. package/dist/core/runner/prepare-protocol-tools.d.ts +5 -1
  80. package/dist/core/runner/prepare-protocol-tools.js +38 -21
  81. package/dist/core/runner/prepare-question-face.d.ts +4 -3
  82. package/dist/core/runner/prepare-question-face.js +5 -3
  83. package/dist/core/runner/prepare-run-refs.d.ts +16 -0
  84. package/dist/core/runner/prepare-run-refs.js +6 -0
  85. package/dist/core/runner/prepare-safety-scan.js +48 -2
  86. package/dist/core/runner/prepare-task.js +37 -19
  87. package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +5 -2
  88. package/dist/core/runner/prepare-tool-disclosure-mount.js +3 -1
  89. package/dist/core/runner/prepare-wiring-manifest.d.ts +4 -0
  90. package/dist/core/runner/prepare-wiring-manifest.js +3 -8
  91. package/dist/core/runner/resume-admission.d.ts +53 -0
  92. package/dist/core/runner/resume-admission.js +83 -0
  93. package/dist/core/runner/resume-apply.d.ts +50 -0
  94. package/dist/core/runner/resume-apply.js +184 -0
  95. package/dist/core/runner/resume-checkpoint-screen.d.ts +18 -0
  96. package/dist/core/runner/resume-checkpoint-screen.js +108 -0
  97. package/dist/core/runner/resume-claim.d.ts +32 -0
  98. package/dist/core/runner/resume-claim.js +27 -0
  99. package/dist/core/runner/resume-internals-and-config.d.ts +33 -0
  100. package/dist/core/runner/resume-internals-and-config.js +50 -0
  101. package/dist/core/runner/resume-policy-outcome.d.ts +31 -0
  102. package/dist/core/runner/resume-policy-outcome.js +127 -0
  103. package/dist/core/runner/resume-preflight.d.ts +40 -0
  104. package/dist/core/runner/resume-preflight.js +122 -0
  105. package/dist/core/runner/resume-review-outcome.d.ts +30 -0
  106. package/dist/core/runner/resume-review-outcome.js +88 -0
  107. package/dist/core/runner/run-harness-handlers.d.ts +46 -0
  108. package/dist/core/runner/run-harness-handlers.js +345 -0
  109. package/dist/core/runner/run-turn-boundary.d.ts +69 -0
  110. package/dist/core/runner/run-turn-boundary.js +694 -0
  111. package/dist/core/runner/runtask.d.ts +20 -0
  112. package/dist/core/runner/runtask.js +129 -1774
  113. package/dist/core/runner/session-rule-policy.js +9 -4
  114. package/dist/core/runner/synthetic-tools.js +5 -8
  115. package/dist/core/runner/tool-disclosure.d.ts +4 -0
  116. package/dist/core/runner/tool-disclosure.js +9 -5
  117. package/dist/core/runner/tool-end-body.d.ts +12 -5
  118. package/dist/core/runner/tool-end-body.js +8 -7
  119. package/dist/core/runner/tool-face-overlay.d.ts +40 -0
  120. package/dist/core/runner/tool-face-overlay.js +125 -0
  121. package/dist/core/runner/tool-output-projection.d.ts +14 -1
  122. package/dist/core/runner/tool-output-projection.js +24 -18
  123. package/dist/core/runner/turn-attachments.d.ts +2 -2
  124. package/dist/core/sensitive-path-policy.d.ts +3 -0
  125. package/dist/core/sensitive-path-policy.js +6 -5
  126. package/dist/core/shared-memory/tools.js +3 -6
  127. package/dist/core/store-contracts/checkpoint-store-contract.js +23 -0
  128. package/dist/core/store-contracts/permission-rule-sync-contract.js +29 -10
  129. package/dist/core/task-event.d.ts +10 -0
  130. package/dist/core/task-registry.js +4 -5
  131. package/dist/core/task-spec.d.ts +8 -0
  132. package/dist/core/tool-catalog-entries.d.ts +48 -0
  133. package/dist/core/tool-catalog-entries.js +174 -0
  134. package/dist/core/tool-conformance.d.ts +22 -0
  135. package/dist/core/tool-conformance.js +37 -0
  136. package/dist/core/tool-face.d.ts +171 -0
  137. package/dist/core/tool-face.js +25 -0
  138. package/dist/core/tool-policy.d.ts +24 -0
  139. package/dist/core/tool-policy.js +4 -2
  140. package/dist/core/tool-registry.d.ts +114 -0
  141. package/dist/core/tool-registry.js +114 -0
  142. package/dist/core/tool-result-budget.d.ts +0 -3
  143. package/dist/core/tool-result-budget.js +4 -3
  144. package/dist/core/tool-result-store.d.ts +0 -18
  145. package/dist/core/tool-result-store.js +2 -18
  146. package/dist/core/tool-roster.d.ts +459 -0
  147. package/dist/core/tool-roster.js +507 -0
  148. package/dist/core/tool-spec.d.ts +54 -3
  149. package/dist/core/tools.js +7 -0
  150. package/dist/core/wiring-manifest.d.ts +13 -0
  151. package/dist/core/wiring-manifest.js +2 -1
  152. package/dist/core/write-protect.d.ts +2 -1
  153. package/dist/core/write-protect.js +5 -4
  154. package/dist/engine/compaction/utils.d.ts +6 -0
  155. package/dist/engine/compaction/utils.js +19 -19
  156. package/dist/engine/loop/types.d.ts +6 -0
  157. package/dist/index.d.ts +17 -6
  158. package/dist/index.js +17 -6
  159. package/dist/orchestration/goal.js +2 -1
  160. package/dist/orchestration/run-spec.js +3 -5
  161. package/dist/orchestration/run-workflow-tool.js +2 -2
  162. package/dist/prompt-assembly/event-registry.js +3 -3
  163. package/dist/prompt-assembly/tool-catalog.d.ts +2 -2
  164. package/dist/stores/file/checkpoint-store.js +1 -1
  165. package/dist/stores/file/permission-rule-store.d.ts +28 -23
  166. package/dist/stores/file/permission-rule-store.js +64 -16
  167. package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
  168. package/dist/tools/fs/fs-bash.d.ts +1 -1
  169. package/dist/tools/fs/fs-bash.js +7 -11
  170. package/dist/tools/fs/fs-pdf.d.ts +1 -1
  171. package/dist/tools/fs/fs-read.js +2 -2
  172. package/dist/tools/fs/fs-search-tools.d.ts +0 -7
  173. package/dist/tools/fs/fs-search-tools.js +3 -16
  174. package/dist/tools/fs/fs-write.js +4 -6
  175. package/dist/tools/fs/notebook.d.ts +1 -1
  176. package/dist/tools/fs/repo-map.js +2 -2
  177. package/dist/tools/fs/safety.d.ts +3 -2
  178. package/dist/tools/fs/safety.js +6 -5
  179. package/dist/tools/monitor.js +2 -2
  180. package/dist/tools/scheduler-tools.js +5 -8
  181. package/dist/tools/task-list.js +5 -8
  182. package/dist/tools/todo.js +2 -2
  183. package/dist/tools/web.js +3 -6
  184. package/dist/tools/worktree.js +3 -4
  185. package/package.json +4 -1
  186. package/test/export-surface.snapshot.json +189 -17
@@ -301,6 +301,29 @@ export async function checkpointStoreContract(make, runAssertion) {
301
301
  const stored = (await store.get(bitless.token)).pendingAction;
302
302
  assert.equal("hasBidiControls" in stored, false, "projection backfill must not write back to the row");
303
303
  });
304
+ run("design/388 B18 × B6 identity + face group: a row's seven optional members (capabilityId/contractId/shapeDigest/implementationRevision/rosterDigest + family/pathTarget) survive the round-trip verbatim; an absent group stays absent", async () => {
305
+ const store = make();
306
+ const carried = createCheckpointFixture({ token: mintCheckpointToken(), sessionId: "id-carried" });
307
+ const group = {
308
+ capabilityId: "caller::Write@acme.write@2",
309
+ contractId: "acme.write@2",
310
+ shapeDigest: "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
311
+ implementationRevision: "r7",
312
+ rosterDigest: "sha256:fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210",
313
+ family: "file-write",
314
+ pathTarget: { param: "destination", aliases: ["path"], access: "create", skillScopeEligible: true },
315
+ };
316
+ Object.assign(carried.pendingAction, group);
317
+ const bare = createCheckpointFixture({ token: mintCheckpointToken(), sessionId: "id-bare" });
318
+ for (const cp of [carried, bare])
319
+ await store.put(cp.token, cp);
320
+ const back = (await store.get(carried.token)).pendingAction;
321
+ for (const [k, v] of Object.entries(group))
322
+ assert.deepEqual(back[k], v, `the row's ${k} must survive the round-trip verbatim (a whitelisting serializer that drops it makes every resume-edit recheck judge by the catalog's slot for the NAME)`);
323
+ const bareBack = (await store.get(bare.token)).pendingAction;
324
+ for (const k of Object.keys(group))
325
+ assert.equal(k in bareBack, false, `an absent ${k} must stay OMITTED (a pre-388 row reads name-only, never a partial face)`);
326
+ });
304
327
  run("#457 previewWithheld three-form matrix: a row's spelling survives + projects; absent stays absent; an out-of-contract spelling reads as absent", async () => {
305
328
  const store = make();
306
329
  const oversize = createCheckpointFixture({ token: mintCheckpointToken(), sessionId: "pw-oversize" });
@@ -5,14 +5,15 @@ const GLOBAL = { kind: "global" };
5
5
  function add(actor, counter, origin = "user") {
6
6
  return { dot: { actor, counter }, origin, createdAt: "2026-01-01T00:00:00.000Z" };
7
7
  }
8
- function rule(text, adds, scope = GLOBAL) {
8
+ function rule(text, adds, scope = GLOBAL, behavior = "allow") {
9
9
  const m = /^Bash\((.+?)(:\*)?\)$/.exec(text);
10
10
  if (m === null || m[1] === undefined)
11
11
  throw new Error(`contract fixture rule "${text}" is not a Bash(...) form`);
12
- return { rule: text, tool: "Bash", match: m[2] !== undefined ? "prefix" : "exact", command: m[1], scope, adds };
12
+ return { behavior, rule: text, tool: "Bash", match: m[2] !== undefined ? "prefix" : "exact", command: m[1], scope, adds };
13
13
  }
14
- function tomb(text, removed, deletedBy, scope = GLOBAL) {
14
+ function tomb(text, removed, deletedBy, scope = GLOBAL, behavior = "allow") {
15
15
  return {
16
+ behavior,
16
17
  rule: text,
17
18
  scope,
18
19
  removedDots: removed.map(([actor, counter]) => ({ actor, counter })),
@@ -25,10 +26,10 @@ function state(rules = [], tombstones = []) {
25
26
  function liveDots(s) {
26
27
  const out = new Set();
27
28
  for (const r of s.rules) {
28
- const removed = s.tombstones.filter((t) => t.rule === r.rule && sameScope(t.scope, r.scope)).flatMap((t) => t.removedDots);
29
+ const removed = s.tombstones.filter((t) => t.behavior === r.behavior && t.rule === r.rule && sameScope(t.scope, r.scope)).flatMap((t) => t.removedDots);
29
30
  for (const a of r.adds) {
30
31
  if (!removed.some((d) => d.actor === a.dot.actor && d.counter === a.dot.counter))
31
- out.add(`${r.rule}|${a.dot.actor}#${a.dot.counter}`);
32
+ out.add(`${r.behavior === "allow" ? "" : r.behavior + ":"}${r.rule}|${a.dot.actor}#${a.dot.counter}`);
32
33
  }
33
34
  }
34
35
  return out;
@@ -41,7 +42,7 @@ function sameScope(a, b) {
41
42
  return b.kind === "project" && a.root === b.root;
42
43
  }
43
44
  function allDots(s) {
44
- return new Set(s.rules.flatMap((r) => r.adds.map((a) => `${r.rule}|${a.dot.actor}#${a.dot.counter}`)));
45
+ return new Set(s.rules.flatMap((r) => r.adds.map((a) => `${r.behavior === "allow" ? "" : r.behavior + ":"}${r.rule}|${a.dot.actor}#${a.dot.counter}`)));
45
46
  }
46
47
  export async function permissionRuleSyncContract(hooks = {}) {
47
48
  const join = hooks.join ?? joinRuleStates;
@@ -141,8 +142,24 @@ export async function permissionRuleSyncContract(hooks = {}) {
141
142
  assert.deepStrictEqual(new Set(ab.tombstones[0]?.removedDots.map((d) => `${d.actor}#${d.counter}`)), new Set(["a#1", "a#2"]), "the union — a one-sided pick can drop an observed removal");
142
143
  assert.deepStrictEqual(ab, join(b, a));
143
144
  });
145
+ run("behavior is an identity member: the same text as an allow and as a deny are two rows, each with its own adds and its own tombstone", async () => {
146
+ const both = state([rule("Bash(ls)", [add("a", 1)]), rule("Bash(ls)", [add("a", 2)], GLOBAL, "deny")], [tomb("Bash(ls)", [["a", 1]], ["a", 3])]);
147
+ const merged = join(both, state());
148
+ assert.strictEqual(merged.rules.length, 2, "one text, two behaviors — two identity groups, never folded");
149
+ assert.deepStrictEqual(liveDots(merged), new Set(["deny:Bash(ls)|a#2"]), "the allow tombstone must not reach the deny's add");
150
+ assert.deepStrictEqual(join(state(), both), merged, "commutative across the behavior axis");
151
+ const badWord = state([rule("Bash(pwd)", [add("b", 1)])], [Object.assign(tomb("Bash(pwd)", [["b", 1]], ["b", 2]), { behavior: "maybe" })]);
152
+ const screened = join(badWord, state());
153
+ assert.strictEqual(screened.tombstones.length, 0, "an unreadable behavior word on a tombstone must not land");
154
+ assert.ok(liveDots(screened).has("Bash(pwd)|b#1"), "…and must not cover anything");
155
+ });
156
+ run("closed-set identity: a row carrying a behavior word outside the closed set is refused like invalid text", async () => {
157
+ const forgedWord = Object.assign(rule("Bash(ls)", [add("z", 1)]), { behavior: "sometimes" });
158
+ const merged = join(state([forgedWord]), state());
159
+ assert.strictEqual(allDots(merged).size, 0, "a behavior this engine cannot read must not become an allow (or a deny) by falling through");
160
+ });
144
161
  run("metadata re-projection (r2-F2): a record whose stored fields contradict its rule text is refused, both orders", async () => {
145
- const forged = { rule: "Bash(ls)", tool: "Bash", match: "prefix", command: "rm -rf /", scope: GLOBAL, adds: [add("z", 1)] };
162
+ const forged = { behavior: "allow", rule: "Bash(ls)", tool: "Bash", match: "prefix", command: "rm -rf /", scope: GLOBAL, adds: [add("z", 1)] };
146
163
  const clean = state([rule("Bash(git status)", [add("a", 1)])]);
147
164
  const ab = join(state([forged]), clean);
148
165
  const ba = join(clean, state([forged]));
@@ -150,9 +167,11 @@ export async function permissionRuleSyncContract(hooks = {}) {
150
167
  assert.deepStrictEqual(ab, ba, "refusal must preserve commutativity");
151
168
  assert.ok(allDots(ab).has("Bash(git status)|a#1"), "refusing one record must not disturb the rest");
152
169
  });
153
- run("single-validator door: an illegal rule shape (bare-interpreter prefix) cannot ride a join", async () => {
170
+ run("single-validator door: an illegal ALLOW shape (bare-interpreter prefix) cannot ride a join — while the same text as a DENY is a legitimate wide tightening and lands", async () => {
154
171
  const merged = join(state([rule("Bash(node:*)", [add("z", 1)])]), state());
155
172
  assert.strictEqual(allDots(merged).size, 0, "Bash(node:*) must be refused by the shared validator inside the join");
173
+ const denied = join(state([rule("Bash(node:*)", [add("z", 1)], GLOBAL, "deny")]), state());
174
+ assert.deepStrictEqual(allDots(denied), new Set(["deny:Bash(node:*)|z#1"]), "the tightening floor admits the interpreter-headed prefix");
156
175
  });
157
176
  run("durable two-member face (design/382 §4.3): a session-scope row or tombstone never survives a join, and never poisons the round", async () => {
158
177
  const sessionScope = { kind: "session", sessionId: "sess-1" };
@@ -168,8 +187,8 @@ export async function permissionRuleSyncContract(hooks = {}) {
168
187
  const scopeA = { kind: "project", root: "/x" };
169
188
  const scopeB = { kind: "project", root: "/x a" };
170
189
  const s = state([rule("Bash(ls)", [add("v", 1)], scopeA), rule("Bash(ls)", [add("w", 1)], scopeB)], [
171
- { rule: "Bash(ls)", scope: scopeA, removedDots: [{ actor: "v", counter: 1 }], deletedBy: { actor: "a 1", counter: 2 } },
172
- { rule: "Bash(ls)", scope: scopeB, removedDots: [{ actor: "w", counter: 1 }], deletedBy: { actor: "1", counter: 2 } },
190
+ { behavior: "allow", rule: "Bash(ls)", scope: scopeA, removedDots: [{ actor: "v", counter: 1 }], deletedBy: { actor: "a 1", counter: 2 } },
191
+ { behavior: "allow", rule: "Bash(ls)", scope: scopeB, removedDots: [{ actor: "w", counter: 1 }], deletedBy: { actor: "1", counter: 2 } },
173
192
  ]);
174
193
  const merged = join(s, state());
175
194
  assert.strictEqual(merged.tombstones.length, 2, "two distinct delete intents must both survive — a colliding key folds one away");
@@ -885,6 +885,16 @@ export type TaskEvent = ({
885
885
  */
886
886
  type: "wiring_manifest";
887
887
  manifest: import("./wiring-manifest.js").WiringManifest;
888
+ } & TaskEventIdentity) | ({
889
+ /**
890
+ * design/388 §2.5 — a RUN-TIME change to this leg's mounted tool set (today: the RefreshMcpTools
891
+ * seam re-inserting a server's tools in place). Carries the WHOLE post-change roster (positions
892
+ * come from it; a consumer replaces its copy) plus a summary usable only when the consumer holds
893
+ * `fromDigest` — otherwise it adopts the snapshot and records a skew (never rejects). The prepared
894
+ * roster itself rides `wiring_manifest.manifest.tools`; this frame never precedes that one.
895
+ */
896
+ type: "tool_roster_delta";
897
+ delta: import("./tool-roster.js").ToolRosterDelta;
888
898
  } & TaskEventIdentity) | {
889
899
  type: "done";
890
900
  result: TaskResult;
@@ -9,8 +9,9 @@ import { registerMonitorLane, pollMonitorLane, stopMonitorLane } from "./task-re
9
9
  import { registerWorkflowLane, pollWorkflowLane, stopWorkflowLane } from "./task-registry-workflow.js";
10
10
  import { mintCompletionId, canAccessWorkflowRun, formatWorkflowRun, clipTaskOutput, assertOwnership, sleepPollStep, statusFromBackground, rollSpoolText, accountDroppedBytes, renderSpoolBody, spoolDropNote, droppedGapNote, alreadyTerminalStopNote, terminalTaskSummary, TASK_OUTPUT_MAX_CHARS, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE, canAccess, DURABLE_AGENT_HANDLE_RE, } from "./task-registry-shared.js";
11
11
  export { normalizeAgentName, DURABLE_AGENT_HEARTBEAT_MS, DURABLE_AGENT_HANDLE_RE, BG_AGENT_REAP_STOP_ERROR } from "./task-registry-shared.js";
12
- import { TASK_OUTPUT_TOOL_NAME, TASK_STOP_TOOL_NAME, TASK_OUTPUT_CONTRACT, TASK_STOP_CONTRACT, TASK_OUTPUT_MISSING_ID_MESSAGE, TASK_STOP_MISSING_ID_MESSAGE, TASK_STOP_PARAMS, resolveTaskIdArg, REGISTRY_TASK_TOOL_CAPS, composeTaskOutputDescription, composeTaskOutputParams, composeTaskStopDescription, } from "./task-tool-shape.js";
12
+ import { TASK_OUTPUT_TOOL_NAME, TASK_STOP_TOOL_NAME, TASK_OUTPUT_MISSING_ID_MESSAGE, TASK_STOP_MISSING_ID_MESSAGE, TASK_STOP_PARAMS, resolveTaskIdArg, REGISTRY_TASK_TOOL_CAPS, composeTaskOutputDescription, composeTaskOutputParams, composeTaskStopDescription, } from "./task-tool-shape.js";
13
13
  import { durableAgentArmedLane, durableAgentRowProbeLane, beginDurableClaimLane, endDurableClaimLane, reapDurableAgentsLane, noteBackgroundAgentActivityLane, reapStaleSessionBackgroundAgentsLane, releaseDurableTranscriptAnchorLane, bindBackgroundAgentSessionLane, registerBackgroundAgentLane, recordBackgroundAgentOrgAdmissionLane, parkBackgroundAgentLane, reconcileParkedAgentsLane, claimParkedAgentLane, rollbackParkedClaimLane, consumeParkedFlipLane, finalizeParkedResumeLane, settleBackgroundAgentLane, abortBackgroundAgentsForOwnerLane, serveDurableAgentRowLane, resolveBackgroundAgentByNameLane, backgroundAgentCycleSeqLane, activeDelegationHandlesLane, markRetainedContinuationLane, reviveBackgroundAgentLane, settleRevivedAgentLane, unmarkRetainedContinuationLane, attachAgentNotifyLane, deliverToRunningAgentLane, runningBackgroundAgentLabelsLane, runningAgentFooterLane, notFoundRunningAgentsTail, pollBackgroundAgentLane, stopBackgroundAgentLane, } from "./task-registry-agent.js";
14
+ import { toolFace } from "./tool-catalog-entries.js";
14
15
  export { canAccessWorkflowRun, clipTaskOutput, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE };
15
16
  const BLOCK_DEFAULT_TIMEOUT_MS = 30_000;
16
17
  const BLOCK_MAX_TIMEOUT_MS = 600_000;
@@ -1234,10 +1235,9 @@ export function createTaskOutputTool(opts) {
1234
1235
  const caps = { ...REGISTRY_TASK_TOOL_CAPS, notification: opts.notificationWired !== false };
1235
1236
  return defineTool({
1236
1237
  name: TASK_OUTPUT_TOOL_NAME,
1237
- contract: TASK_OUTPUT_CONTRACT,
1238
+ ...toolFace("task-output"),
1238
1239
  description: composeTaskOutputDescription(caps),
1239
1240
  parameters: composeTaskOutputParams(caps),
1240
- effect: "read",
1241
1241
  execute: async (rawArgs, ctx) => {
1242
1242
  const args = rawArgs;
1243
1243
  const id = resolveTaskIdArg(args.task_id);
@@ -1283,10 +1283,9 @@ export function createTaskOutputTool(opts) {
1283
1283
  export function createTaskStopTool(opts) {
1284
1284
  return defineTool({
1285
1285
  name: TASK_STOP_TOOL_NAME,
1286
- contract: TASK_STOP_CONTRACT,
1286
+ ...toolFace("task-stop"),
1287
1287
  description: composeTaskStopDescription(REGISTRY_TASK_TOOL_CAPS),
1288
1288
  parameters: TASK_STOP_PARAMS,
1289
- effect: "write",
1290
1289
  execute: async (rawArgs, ctx) => {
1291
1290
  const args = rawArgs;
1292
1291
  const id = resolveTaskIdArg(args.task_id, args.shell_id);
@@ -387,6 +387,14 @@ export interface TaskSpec {
387
387
  * owns that risk, same trust level as assembling `spec.tools` itself.
388
388
  */
389
389
  excludeTools?: string[];
390
+ /**
391
+ * design/388 §2.2 ⑥ — unmount the WHOLE tool face: the leg mounts nothing (no hands, no
392
+ * scaffolds, no MCP or skill tools; the effect roster reads `entries: []` and the model request
393
+ * carries `tools: []`). The utility-task shape a shell used to express with a hand-kept exclusion
394
+ * list. Not spelled `excludeTools: ["*"]` on purpose — `"*"` is a legal caller tool name (only `__`
395
+ * is reserved). `excludeTools` beside it is redundant, not an error.
396
+ */
397
+ excludeAllTools?: true;
390
398
  /**
391
399
  * Per-request DEFERRED disclosure for ALREADY-MOUNTED tools (built-ins
392
400
  * included — `ToolSpec.defer` only covers caller specs): wire names listed here ship as
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The STATIC TOOL CATALOG (design/388 §2.1 "静态目录"): one row per built-in / synthetic tool DEFINITION
3
+ * core owns — its wire name, the conditions core (or a host scenario factory) mounts it under, the
4
+ * `details.type` cards it mints, and its declared FACE (family / axes / contract / path target / rule
5
+ * vocabulary / render hints / policy bits).
6
+ *
7
+ * SINGLE SOURCE, TWO READERS. Each definition module spreads its row's `face` into the `ToolSpec` it
8
+ * hands `defineTool` (`...TOOL_FACES.read`), so the face a tool declares at mount time IS the face
9
+ * listed here — there is no second declaration to drift. The effect roster (tool-roster.ts) reads every
10
+ * axis off the MOUNTED INSTANCE; the static catalog (`describeToolCatalog()`) reads this table without
11
+ * an env or a deps object. The conformance gate (test/tool-catalog-conformance.test.ts) proves the two
12
+ * agree for every definition it can instantiate; the catalog completeness gate
13
+ * (scripts/verify-tool-catalog.mjs) proves every `defineTool` site in src/ that mints a core-owned name
14
+ * has a row here that declares a `family` — a missing declaration is a RED gate naming the file, never a
15
+ * silent `"other"`.
16
+ *
17
+ * LEAF MODULE by design: it imports vocabulary types and one leaf constant module only. Every definition
18
+ * module and every policy module that derives a name set from the catalog imports THIS file, so a
19
+ * dependency from here back into a tool module would be a load-order cycle with a `const` table in it.
20
+ *
21
+ * Keyed by DEFINITION identity (`id`), not by name — the full and the read-only shell both answer to
22
+ * `Bash`; a catalog predicate counts a name as a member when ANY row under that name matches.
23
+ */
24
+ import type { ToolCatalogEntry, ToolFaceDeclaration } from "./tool-face.js";
25
+ /** The full shell's contract id — declared here (the leaf) and re-exported by fs-bash.ts. */
26
+ export declare const FULL_SHELL_CONTRACT_ID = "core.bash@1";
27
+ export declare const READONLY_SHELL_CONTRACT_ID = "core.bash_readonly@1";
28
+ /** Wire-name constants for definitions that had none (design/388 A-21: literals become references). */
29
+ export declare const TASK_CREATE_TOOL_NAME = "TaskCreate";
30
+ export declare const TASK_GET_TOOL_NAME = "TaskGet";
31
+ export declare const TASK_UPDATE_TOOL_NAME = "TaskUpdate";
32
+ export declare const TASK_LIST_TOOL_NAME = "TaskList";
33
+ export declare const TODO_WRITE_TOOL_NAME = "TodoWrite";
34
+ export declare const REFRESH_MCP_TOOLS_TOOL_NAME = "RefreshMcpTools";
35
+ /**
36
+ * Every row. Order is documentation order (hands → delegation → scaffolds → memory → scheduler → host
37
+ * scenarios); nothing reads the order.
38
+ */
39
+ export declare const TOOL_CATALOG_ENTRIES: readonly ToolCatalogEntry[];
40
+ /**
41
+ * `details.type` cards the ENGINE mints outside any tool definition — the protocol channels' result
42
+ * cards and the fs boundary refusals (minted by the shared safety module on behalf of every fs tool).
43
+ */
44
+ export declare const ENGINE_CARDS: Readonly<Record<string, string>>;
45
+ /** The declared face of one definition, by definition id — what a definition module spreads into its spec. */
46
+ export declare function toolFace(id: string): ToolFaceDeclaration;
47
+ /** Definition-id → face, for the modules that spread one (`...TOOL_FACES.read`). Frozen: a face is data. */
48
+ export declare const TOOL_FACES: Readonly<Record<string, ToolFaceDeclaration>>;
@@ -0,0 +1,174 @@
1
+ import { TASK_OUTPUT_CONTRACT, TASK_STOP_CONTRACT } from "./task-tool-shape.js";
2
+ export const FULL_SHELL_CONTRACT_ID = "core.bash@1";
3
+ export const READONLY_SHELL_CONTRACT_ID = "core.bash_readonly@1";
4
+ export const TASK_CREATE_TOOL_NAME = "TaskCreate";
5
+ export const TASK_GET_TOOL_NAME = "TaskGet";
6
+ export const TASK_UPDATE_TOOL_NAME = "TaskUpdate";
7
+ export const TASK_LIST_TOOL_NAME = "TaskList";
8
+ export const TODO_WRITE_TOOL_NAME = "TodoWrite";
9
+ export const REFRESH_MCP_TOOLS_TOOL_NAME = "RefreshMcpTools";
10
+ const SHELL_RULE_FACE = { primaryParams: ["command"], params: ["command", "timeout", "description", "run_in_background"] };
11
+ const c = (contractId, implementationRevision = "1") => ({ contractId, implementationRevision });
12
+ export const TOOL_CATALOG_ENTRIES = [
13
+ {
14
+ id: "read", name: "Read", source: "builtin", definedIn: "src/tools/fs/fs-read.ts", mountedBy: ["hands"],
15
+ cards: ["text", "notebook", "file_unchanged", "image", "document"],
16
+ face: {
17
+ family: "file-read", effect: "read", contract: c("core.read@1"),
18
+ pathTarget: { param: "file_path", aliases: ["path"], access: "read" },
19
+ ruleFace: { params: ["file_path", "path", "offset", "limit", "pages"] },
20
+ renderHints: { summaryParams: ["file_path"] },
21
+ offload: false, compactable: true, budgetExempt: true,
22
+ },
23
+ },
24
+ {
25
+ id: "edit", name: "Edit", source: "builtin", definedIn: "src/tools/fs/fs-write.ts", mountedBy: ["hands"],
26
+ cards: ["edit"],
27
+ face: {
28
+ family: "file-write", effect: "write", contract: c("core.edit@1"),
29
+ pathTarget: { param: "file_path", aliases: ["path"], access: "edit", skillScopeEligible: true },
30
+ ruleFace: { params: ["file_path", "path", "old_string", "new_string", "replace_all"] },
31
+ renderHints: { summaryParams: ["file_path"], approvalCard: "file-edit" },
32
+ compactable: true,
33
+ },
34
+ },
35
+ {
36
+ id: "write", name: "Write", source: "builtin", definedIn: "src/tools/fs/fs-write.ts", mountedBy: ["hands"],
37
+ cards: ["create", "update"],
38
+ face: {
39
+ family: "file-write", effect: "write", contract: c("core.write@1"),
40
+ pathTarget: { param: "file_path", aliases: ["path"], access: "create", skillScopeEligible: true },
41
+ ruleFace: { params: ["file_path", "path", "content"] },
42
+ renderHints: { summaryParams: ["file_path"], approvalCard: "file-write" },
43
+ offload: false, compactable: true,
44
+ },
45
+ },
46
+ {
47
+ id: "notebook-edit", name: "NotebookEdit", source: "builtin", definedIn: "src/tools/fs/fs-write.ts", mountedBy: ["hands"],
48
+ cards: ["notebook-edit"],
49
+ face: {
50
+ family: "file-write", effect: "write", contract: c("core.notebook_edit@1"),
51
+ pathTarget: { param: "notebook_path", aliases: ["path"], access: "edit" },
52
+ ruleFace: { params: ["notebook_path", "path", "cell_id", "cell_type", "edit_mode", "new_source"] },
53
+ renderHints: { userFacingName: "Edit Notebook", summaryParams: ["notebook_path"], approvalCard: "notebook-edit" },
54
+ },
55
+ },
56
+ {
57
+ id: "bash", name: "Bash", source: "builtin", definedIn: "src/tools/fs/fs-bash.ts", mountedBy: ["hands"],
58
+ cards: ["bash", "bash_invalid_timeout"],
59
+ face: {
60
+ family: "shell", effect: "write", contentOrigin: "execution", contract: c(FULL_SHELL_CONTRACT_ID),
61
+ ruleFace: SHELL_RULE_FACE,
62
+ renderHints: { summaryParams: ["command"], approvalCard: "shell" },
63
+ offloadThresholdChars: 30_000, compactable: true,
64
+ },
65
+ },
66
+ {
67
+ id: "bash-readonly", name: "Bash", source: "builtin", definedIn: "src/tools/fs/fs-bash.ts", mountedBy: ["hands"],
68
+ cards: ["bash", "readonly_out_of_root", "bash_invalid_timeout"],
69
+ face: {
70
+ family: "shell", effect: "read", contract: c(READONLY_SHELL_CONTRACT_ID),
71
+ ruleFace: SHELL_RULE_FACE,
72
+ renderHints: { summaryParams: ["command"], approvalCard: "shell" },
73
+ offloadThresholdChars: 30_000, compactable: true,
74
+ },
75
+ },
76
+ {
77
+ id: "grep", name: "Grep", source: "builtin", definedIn: "src/tools/fs/fs-search-tools.ts", mountedBy: ["hands"],
78
+ cards: ["grep"],
79
+ face: {
80
+ family: "search", effect: "read", contract: c("core.grep@1"),
81
+ pathTarget: { param: "path", access: "read" },
82
+ ruleFace: { params: ["pattern", "path", "glob", "type", "output_mode", "head_limit", "offset", "multiline", "context", "ignore_case", "-i", "-n", "-o", "-A", "-B", "-C"] },
83
+ renderHints: { userFacingName: "Search", summaryParams: ["pattern", "path"] },
84
+ offloadThresholdChars: 20_000, compactable: true,
85
+ },
86
+ },
87
+ {
88
+ id: "glob", name: "Glob", source: "builtin", definedIn: "src/tools/fs/fs-search-tools.ts", mountedBy: ["hands"],
89
+ cards: ["glob"],
90
+ face: {
91
+ family: "search", effect: "read", contract: c("core.glob@1"),
92
+ pathTarget: { param: "path", access: "read" },
93
+ ruleFace: { params: ["pattern", "path", "max_results"] },
94
+ renderHints: { userFacingName: "Search", summaryParams: ["pattern", "path"] },
95
+ compactable: true,
96
+ },
97
+ },
98
+ { id: "repo-map", name: "RepoMap", source: "builtin", definedIn: "src/tools/fs/repo-map.ts", mountedBy: ["hands"], cards: ["repo-map"], face: { family: "search", effect: "read", contract: c("core.repo_map@1") } },
99
+ { id: "lsp", name: "LSP", source: "builtin", definedIn: "src/core/lsp.ts", mountedBy: ["optional"], cards: [], face: { family: "search", effect: "read", contract: c("core.lsp@1") } },
100
+ { id: "enter-worktree", name: "EnterWorktree", source: "builtin", definedIn: "src/tools/worktree.ts", mountedBy: ["hands"], cards: ["worktree"], face: { family: "other", effect: "write", contract: c("core.enter_worktree@1") } },
101
+ { id: "exit-worktree", name: "ExitWorktree", source: "builtin", definedIn: "src/tools/worktree.ts", mountedBy: ["hands"], cards: ["worktree"], face: { family: "other", effect: "write", contract: c("core.exit_worktree@1") } },
102
+ { id: "task-output-hands", name: "TaskOutput", source: "builtin", definedIn: "src/tools/fs/fs-bash.ts", mountedBy: ["hands"], cards: [], face: { family: "delegate", effect: "read", contract: TASK_OUTPUT_CONTRACT } },
103
+ { id: "task-stop-hands", name: "TaskStop", source: "builtin", definedIn: "src/tools/fs/fs-bash.ts", mountedBy: ["hands"], cards: [], face: { family: "delegate", effect: "write", contract: TASK_STOP_CONTRACT } },
104
+ { id: "task-output", name: "TaskOutput", source: "builtin", definedIn: "src/core/task-registry.ts", mountedBy: ["delegation"], cards: ["task-output"], face: { family: "delegate", effect: "read", contract: TASK_OUTPUT_CONTRACT } },
105
+ { id: "task-stop", name: "TaskStop", source: "builtin", definedIn: "src/core/task-registry.ts", mountedBy: ["delegation"], cards: ["task-stop"], face: { family: "delegate", effect: "write", contract: TASK_STOP_CONTRACT } },
106
+ { id: "monitor", name: "Monitor", source: "builtin", definedIn: "src/tools/monitor.ts", mountedBy: ["backgroundTasks"], cards: ["monitor-start"], face: { family: "other", effect: "write", contract: c("core.monitor@1"), offloadThresholdChars: 10_000 } },
107
+ { id: "send-message", name: "SendMessage", source: "builtin", definedIn: "src/agents/send-message-tool.ts", mountedBy: ["delegation", "hostInternals", "peerLane"], cards: ["send-message"], face: { family: "protocol", effect: "write", contract: c("core.send_message@1") } },
108
+ { id: "agent-transcript", name: "AgentTranscript", source: "builtin", definedIn: "src/agents/agent-transcript-tool.ts", mountedBy: ["delegation"], cards: ["agent-transcript"], face: { family: "delegate", effect: "read", contract: c("core.agent_transcript@1") } },
109
+ { id: "list-agents", name: "ListAgents", source: "builtin", definedIn: "src/agents/list-agents-tool.ts", mountedBy: ["peerLane"], cards: ["list-agents"], face: { family: "protocol", effect: "read", aliases: ["ListPeers"], contract: c("core.list_agents@1") } },
110
+ { id: "workflow", name: "Workflow", source: "builtin", definedIn: "src/orchestration/run-workflow-tool.ts", mountedBy: ["workflow"], cards: ["workflow-run"], face: { family: "delegate", effect: "write", contract: c("core.workflow@1") } },
111
+ { id: "report-blocked", name: "ReportBlocked", source: "builtin", definedIn: "src/core/runner/synthetic-tools.ts", mountedBy: ["always"], cards: [], face: { family: "scaffold", effect: "read", contract: c("core.report_blocked@1") } },
112
+ { id: "report-findings", name: "ReportFindings", source: "builtin", definedIn: "src/core/runner/synthetic-tools.ts", mountedBy: ["always"], cards: ["report-findings"], face: { family: "scaffold", effect: "read", contract: c("core.report_findings@1") } },
113
+ { id: "structured-output", name: "StructuredOutput", source: "synthetic", definedIn: "src/core/runner/synthetic-tools.ts", mountedBy: ["optional"], cards: [], face: { family: "scaffold", effect: "read", contract: c("core.structured_output@1") } },
114
+ { id: "skill", name: "Skill", source: "builtin", definedIn: "src/core/runner/synthetic-tools.ts", mountedBy: ["optional"], cards: [], face: { family: "scaffold", effect: "read", contract: c("core.skill@1") } },
115
+ { id: "read-tool-result", name: "ReadToolResult", source: "builtin", definedIn: "src/core/tool-result-store.ts", mountedBy: ["optional"], cards: [], face: { family: "scaffold", effect: "read", contract: c("core.read_tool_result@1"), budgetExempt: true } },
116
+ { id: "tool-search", name: "ToolSearch", source: "builtin", definedIn: "src/core/runner/tool-disclosure.ts", mountedBy: ["optional"], cards: ["tool-search"], face: { family: "scaffold", effect: "read", contract: c("core.tool_search@1") } },
117
+ { id: "ask-user-question", name: "AskUserQuestion", source: "builtin", definedIn: "src/core/ask-question.ts", mountedBy: ["optional"], cards: ["ask-question"], face: { family: "other", effect: "read", contract: c("core.ask_user_question@1"), renderHints: { approvalCard: "question" } } },
118
+ { id: "exit-plan-mode", name: "ExitPlanMode", source: "builtin", definedIn: "src/core/present-plan-tool.ts", mountedBy: ["optional"], cards: ["exit-plan-mode"], face: { family: "plan", effect: "read", contract: c("core.exit_plan_mode@1"), renderHints: { userFacingName: "", approvalCard: "plan" } } },
119
+ { id: "enter-plan-mode", name: "EnterPlanMode", source: "builtin", definedIn: "src/core/present-plan-tool.ts", mountedBy: ["optional"], cards: ["enter-plan-mode"], face: { family: "plan", effect: "read", contract: c("core.enter_plan_mode@1") } },
120
+ { id: "refresh-mcp-tools", name: REFRESH_MCP_TOOLS_TOOL_NAME, source: "builtin", definedIn: "src/core/runner/prepare-protocol-tools.ts", mountedBy: ["optional"], cards: [], face: { family: "protocol", effect: "read" } },
121
+ { id: "memory-search", name: "memory_search", source: "builtin", definedIn: "src/core/memory-engine/tools.ts", mountedBy: ["memory"], cards: [], face: { family: "memory", effect: "read", contentOrigin: "local", contract: c("core.memory_search@1", "6"), offload: false } },
122
+ { id: "memory-get", name: "memory_get", source: "builtin", definedIn: "src/core/memory-engine/tools.ts", mountedBy: ["memory"], cards: [], face: { family: "memory", effect: "read", contentOrigin: "local", contract: c("core.memory_get@1", "6"), offload: false } },
123
+ { id: "memory-index", name: "memory_index", source: "builtin", definedIn: "src/core/memory-engine/tools.ts", mountedBy: ["memory"], cards: [], face: { family: "memory", effect: "read", contentOrigin: "local", contract: c("core.memory_index@1"), offload: false } },
124
+ { id: "memory-list", name: "memory_list", source: "builtin", definedIn: "src/core/shared-memory/tools.ts", mountedBy: ["memory"], cards: [], face: { family: "memory", effect: "read", contract: c("core.memory_list@1"), offload: false } },
125
+ { id: "memory-read", name: "memory_read", source: "builtin", definedIn: "src/core/shared-memory/tools.ts", mountedBy: ["memory"], cards: [], face: { family: "memory", effect: "read", contract: c("core.memory_read@1"), offload: false } },
126
+ { id: "cron-create", name: "CronCreate", source: "builtin", definedIn: "src/tools/scheduler-tools.ts", mountedBy: ["scheduler"], cards: ["cron-create"], face: { family: "other", effect: "write", contract: c("core.cron_create@1") } },
127
+ { id: "cron-delete", name: "CronDelete", source: "builtin", definedIn: "src/tools/scheduler-tools.ts", mountedBy: ["scheduler"], cards: ["cron-delete"], face: { family: "other", effect: "idempotent", contract: c("core.cron_delete@1") } },
128
+ { id: "cron-list", name: "CronList", source: "builtin", definedIn: "src/tools/scheduler-tools.ts", mountedBy: ["scheduler"], cards: ["cron-list"], face: { family: "other", effect: "read", contract: c("core.cron_list@1") } },
129
+ { id: "schedule-wakeup", name: "ScheduleWakeup", source: "builtin", definedIn: "src/tools/scheduler-tools.ts", mountedBy: ["scheduler"], cards: ["schedule-wakeup"], face: { family: "other", effect: "write", contract: c("core.schedule_wakeup@1") } },
130
+ {
131
+ id: "web-fetch", name: "WebFetch", source: "builtin", definedIn: "src/tools/web.ts", mountedBy: ["scenario"], cards: ["web-fetch"],
132
+ face: { family: "web", effect: "read", contentOrigin: "external", contract: c("core.web_fetch@1"), ruleFace: { primaryParams: ["url"] }, renderHints: { summaryParams: ["url"], approvalCard: "fetch" }, compactable: true },
133
+ },
134
+ {
135
+ id: "web-search", name: "WebSearch", source: "builtin", definedIn: "src/tools/web.ts", mountedBy: ["scenario"], cards: ["web-search"],
136
+ face: { family: "web", effect: "read", contentOrigin: "external", contract: c("core.web_search@1"), ruleFace: { primaryParams: ["query"] }, renderHints: { summaryParams: ["query"] }, compactable: true },
137
+ },
138
+ { id: "todo-write", name: TODO_WRITE_TOOL_NAME, source: "builtin", definedIn: "src/tools/todo.ts", mountedBy: ["scenario"], cards: ["todo"], modelGate: "task-scaffold", face: { family: "plan", effect: "idempotent", contract: c("core.todo_write@1") } },
139
+ { id: "task-create", name: TASK_CREATE_TOOL_NAME, source: "builtin", definedIn: "src/tools/task-list.ts", mountedBy: ["scenario"], cards: ["task"], modelGate: "task-scaffold", face: { family: "plan", effect: "write", contract: c("core.task_create@1") } },
140
+ { id: "task-get", name: TASK_GET_TOOL_NAME, source: "builtin", definedIn: "src/tools/task-list.ts", mountedBy: ["scenario"], cards: ["task"], modelGate: "task-scaffold", face: { family: "plan", effect: "read", contract: c("core.task_get@1") } },
141
+ { id: "task-update", name: TASK_UPDATE_TOOL_NAME, source: "builtin", definedIn: "src/tools/task-list.ts", mountedBy: ["scenario"], cards: ["task"], modelGate: "task-scaffold", face: { family: "plan", effect: "idempotent", contract: c("core.task_update@1") } },
142
+ { id: "task-list", name: TASK_LIST_TOOL_NAME, source: "builtin", definedIn: "src/tools/task-list.ts", mountedBy: ["scenario"], cards: ["task-list"], modelGate: "task-scaffold", face: { family: "plan", effect: "read", contract: c("core.task_list@1") } },
143
+ { id: "agent", name: "Agent", source: "builtin", definedIn: "src/agents/subagent.ts", mountedBy: ["scenario"], cards: ["agent", "fork"], face: { family: "delegate", contentOrigin: "local", contract: c("core.agent@1") } },
144
+ { id: "observer-report", name: "ObserverReport", source: "builtin", definedIn: "src/agents/observer.ts", mountedBy: ["scenario"], cards: [], face: { family: "scaffold", effect: "write", contract: c("core.observer_report@1") } },
145
+ { id: "declare-done", name: "DeclareDone", source: "builtin", definedIn: "src/orchestration/goal.ts", mountedBy: ["scenario"], cards: [], face: { family: "scaffold", effect: "read" } },
146
+ ];
147
+ export const ENGINE_CARDS = Object.freeze({
148
+ mcp: "src/core/mcp.ts",
149
+ a2a: "src/core/a2a.ts",
150
+ path_not_in_root: "src/tools/fs/safety.ts",
151
+ read_path_denied: "src/tools/fs/safety.ts",
152
+ });
153
+ const byId = new Map();
154
+ for (const e of TOOL_CATALOG_ENTRIES) {
155
+ if (byId.has(e.id))
156
+ throw new Error(`tool catalog: duplicate definition id ${JSON.stringify(e.id)}`);
157
+ byId.set(e.id, e);
158
+ }
159
+ export function toolFace(id) {
160
+ const e = byId.get(id);
161
+ if (e === undefined)
162
+ throw new Error(`tool catalog: no definition ${JSON.stringify(id)}`);
163
+ return structuredClone(e.face);
164
+ }
165
+ function deepFreeze(value) {
166
+ if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
167
+ Object.freeze(value);
168
+ for (const k of Object.keys(value))
169
+ deepFreeze(value[k]);
170
+ }
171
+ return value;
172
+ }
173
+ deepFreeze(TOOL_CATALOG_ENTRIES);
174
+ export const TOOL_FACES = Object.freeze(Object.fromEntries(TOOL_CATALOG_ENTRIES.map((e) => [e.id, e.face])));
@@ -0,0 +1,22 @@
1
+ import type { ToolMountTag } from "./tool-face.js";
2
+ export interface ToolConformanceVector {
3
+ id: string;
4
+ /** The mount conditions the vector activates. */
5
+ active: readonly ToolMountTag[];
6
+ /** Predicted names the vector cannot mount, each with the seat it lacks. */
7
+ absent: ReadonlyArray<{
8
+ name: string;
9
+ why: string;
10
+ }>;
11
+ /** Mounted names no tag spells, each with the seat that mounts them. */
12
+ extra: ReadonlyArray<{
13
+ name: string;
14
+ why: string;
15
+ }>;
16
+ /** The vector's expected core-mounted name set: predict(active) − absent + extra. */
17
+ expected: readonly string[];
18
+ }
19
+ /** The matrix. Vector ids are stable (a host's own conformance test keys on them). */
20
+ export declare const TOOL_CONFORMANCE_VECTORS: readonly ToolConformanceVector[];
21
+ /** The matrix as a host reads it (a frozen copy). */
22
+ export declare function describeToolConformanceVectors(): readonly ToolConformanceVector[];
@@ -0,0 +1,37 @@
1
+ import { predictMountedToolNames } from "./tool-registry.js";
2
+ const vector = (id, active, absent = [], extra = []) => {
3
+ const predicted = new Set(predictMountedToolNames(new Set(active)));
4
+ for (const a of absent)
5
+ predicted.delete(a.name);
6
+ for (const e of extra)
7
+ predicted.add(e.name);
8
+ return { id, active, absent, extra, expected: [...predicted].sort() };
9
+ };
10
+ const OFFLOAD_READER = { name: "ReadToolResult", why: "the offload store is wired by default (the deployment threshold), an `optional` seat every vector carries" };
11
+ export const TOOL_CONFORMANCE_VECTORS = [
12
+ vector("bare", ["always"], [], [OFFLOAD_READER]),
13
+ vector("hands-full", ["always", "hands", "backgroundTasks", "delegation"], [
14
+ { name: "SendMessage", why: "needs the runner self seat (a direct prepare has none)" },
15
+ { name: "AgentTranscript", why: "needs the runner self seat (a direct prepare has none)" },
16
+ ], [OFFLOAD_READER]),
17
+ vector("hands-readonly", ["always", "hands"], [
18
+ { name: "Edit", why: "the read-only band mounts the read half only" },
19
+ { name: "Write", why: "the read-only band mounts the read half only" },
20
+ { name: "NotebookEdit", why: "the read-only band mounts the read half only" },
21
+ { name: "TaskOutput", why: "the background-shell pair mounts on the full shell only" },
22
+ { name: "TaskStop", why: "the background-shell pair mounts on the full shell only" },
23
+ { name: "EnterWorktree", why: "the worktree pair needs the tracked cwd the full shell owns" },
24
+ { name: "ExitWorktree", why: "the worktree pair needs the tracked cwd the full shell owns" },
25
+ ], [OFFLOAD_READER]),
26
+ vector("plan-mode", ["always", "optional"], [
27
+ { name: "LSP", why: "needs a wired LSP manager" },
28
+ { name: "Skill", why: "needs a skills listing" },
29
+ { name: "StructuredOutput", why: "needs an output schema" },
30
+ { name: "ToolSearch", why: "needs a deferred tool" },
31
+ { name: "RefreshMcpTools", why: "needs a declared MCP server" },
32
+ { name: "EnterPlanMode", why: "needs a checkpoint store beside plan mode" },
33
+ ]),
34
+ ];
35
+ export function describeToolConformanceVectors() {
36
+ return TOOL_CONFORMANCE_VECTORS;
37
+ }