@sema-agent/core 7.6.3 → 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.
Files changed (164) hide show
  1. package/CHANGELOG.md +70 -10
  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/peer-admission.d.ts +1 -1
  6. package/dist/agents/send-message-tool.js +2 -1
  7. package/dist/agents/subagent-steps.js +5 -4
  8. package/dist/agents/subagent.js +2 -3
  9. package/dist/brain/anthropic.js +8 -2
  10. package/dist/brain/open-responses.js +5 -3
  11. package/dist/brain/openai.js +31 -8
  12. package/dist/brain/reasoning.d.ts +32 -0
  13. package/dist/brain/reasoning.js +18 -0
  14. package/dist/core/ask-question.js +2 -2
  15. package/dist/core/auto-mode-defaults.d.ts +16 -0
  16. package/dist/core/auto-mode-defaults.js +1 -0
  17. package/dist/core/auto-mode.d.ts +19 -0
  18. package/dist/core/auto-mode.js +74 -56
  19. package/dist/core/checkpoint-execution-record.d.ts +110 -0
  20. package/dist/core/checkpoint-execution-record.js +49 -0
  21. package/dist/core/checkpoint-store.d.ts +114 -2
  22. package/dist/core/checkpoint-store.js +35 -2
  23. package/dist/core/context-edit.d.ts +0 -22
  24. package/dist/core/context-edit.js +2 -11
  25. package/dist/core/engine-notice.d.ts +17 -0
  26. package/dist/core/fs-write-gate-policy.d.ts +7 -1
  27. package/dist/core/fs-write-gate-policy.js +7 -7
  28. package/dist/core/gate-fold.js +2 -2
  29. package/dist/core/gate-lanes.js +4 -3
  30. package/dist/core/governance-codes.d.ts +1 -1
  31. package/dist/core/governance-codes.js +8 -0
  32. package/dist/core/hooks.d.ts +8 -2
  33. package/dist/core/hooks.js +1 -1
  34. package/dist/core/lsp.js +2 -2
  35. package/dist/core/mcp-server-spec.d.ts +17 -0
  36. package/dist/core/mcp.js +3 -1
  37. package/dist/core/memory-engine/tools.js +4 -12
  38. package/dist/core/permission-rules.d.ts +32 -2
  39. package/dist/core/permission-rules.js +43 -37
  40. package/dist/core/present-plan-tool.js +3 -4
  41. package/dist/core/runner/active-skill-scope.js +7 -4
  42. package/dist/core/runner/clock-and-limits.d.ts +117 -0
  43. package/dist/core/runner/clock-and-limits.js +118 -0
  44. package/dist/core/runner/compaction-call-options.d.ts +1 -1
  45. package/dist/core/runner/contracts.d.ts +37 -3
  46. package/dist/core/runner/decide-continuation.d.ts +98 -0
  47. package/dist/core/runner/decide-continuation.js +133 -0
  48. package/dist/core/runner/execution-record.d.ts +26 -0
  49. package/dist/core/runner/execution-record.js +19 -0
  50. package/dist/core/runner/git-leg-delivery.d.ts +28 -0
  51. package/dist/core/runner/git-leg-delivery.js +94 -0
  52. package/dist/core/runner/initial-run-state.d.ts +14 -0
  53. package/dist/core/runner/initial-run-state.js +11 -0
  54. package/dist/core/runner/prepare-ask-lane.d.ts +1 -1
  55. package/dist/core/runner/prepare-caps-and-workflow.d.ts +5 -4
  56. package/dist/core/runner/prepare-caps-and-workflow.js +33 -11
  57. package/dist/core/runner/prepare-defer-classify.d.ts +5 -26
  58. package/dist/core/runner/prepare-defer-classify.js +10 -10
  59. package/dist/core/runner/prepare-delegation-surface.d.ts +5 -4
  60. package/dist/core/runner/prepare-delegation-surface.js +15 -17
  61. package/dist/core/runner/prepare-gate-stations.d.ts +4 -1
  62. package/dist/core/runner/prepare-gate-stations.js +3 -1
  63. package/dist/core/runner/prepare-hands-readface.d.ts +3 -2
  64. package/dist/core/runner/prepare-hands-readface.js +3 -2
  65. package/dist/core/runner/prepare-inherited-gate.d.ts +1 -1
  66. package/dist/core/runner/prepare-inherited-gate.js +1 -0
  67. package/dist/core/runner/prepare-lsp.d.ts +4 -3
  68. package/dist/core/runner/prepare-lsp.js +3 -2
  69. package/dist/core/runner/prepare-memory-engine-session.d.ts +4 -4
  70. package/dist/core/runner/prepare-memory-engine-session.js +4 -2
  71. package/dist/core/runner/prepare-offload-wrappers.js +7 -5
  72. package/dist/core/runner/prepare-park-ask.d.ts +4 -0
  73. package/dist/core/runner/prepare-park-ask.js +4 -1
  74. package/dist/core/runner/prepare-project-context.d.ts +5 -23
  75. package/dist/core/runner/prepare-project-context.js +7 -5
  76. package/dist/core/runner/prepare-prompt-assembly.d.ts +1 -1
  77. package/dist/core/runner/prepare-protocol-tools.d.ts +5 -1
  78. package/dist/core/runner/prepare-protocol-tools.js +38 -21
  79. package/dist/core/runner/prepare-question-face.d.ts +4 -3
  80. package/dist/core/runner/prepare-question-face.js +5 -3
  81. package/dist/core/runner/prepare-safety-scan.js +42 -2
  82. package/dist/core/runner/prepare-task.js +37 -19
  83. package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +5 -2
  84. package/dist/core/runner/prepare-tool-disclosure-mount.js +3 -1
  85. package/dist/core/runner/prepare-wiring-manifest.d.ts +4 -0
  86. package/dist/core/runner/prepare-wiring-manifest.js +3 -8
  87. package/dist/core/runner/run-harness-handlers.d.ts +46 -0
  88. package/dist/core/runner/run-harness-handlers.js +345 -0
  89. package/dist/core/runner/run-turn-boundary.d.ts +69 -0
  90. package/dist/core/runner/run-turn-boundary.js +694 -0
  91. package/dist/core/runner/runtask.d.ts +0 -68
  92. package/dist/core/runner/runtask.js +66 -1486
  93. package/dist/core/runner/session-rule-policy.js +9 -4
  94. package/dist/core/runner/steer-admission.d.ts +17 -0
  95. package/dist/core/runner/steer-admission.js +17 -0
  96. package/dist/core/runner/synthetic-tools.js +5 -8
  97. package/dist/core/runner/tool-disclosure.d.ts +4 -0
  98. package/dist/core/runner/tool-disclosure.js +9 -5
  99. package/dist/core/runner/tool-end-body.d.ts +71 -0
  100. package/dist/core/runner/tool-end-body.js +75 -0
  101. package/dist/core/runner/tool-face-overlay.d.ts +40 -0
  102. package/dist/core/runner/tool-face-overlay.js +125 -0
  103. package/dist/core/runner/tool-output-projection.d.ts +10 -0
  104. package/dist/core/runner/tool-output-projection.js +33 -10
  105. package/dist/core/sensitive-path-policy.d.ts +3 -0
  106. package/dist/core/sensitive-path-policy.js +6 -5
  107. package/dist/core/shared-memory/tools.js +3 -6
  108. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +4 -0
  109. package/dist/core/store-contracts/checkpoint-store-contract.js +108 -0
  110. package/dist/core/task-event.d.ts +10 -0
  111. package/dist/core/task-registry.js +4 -5
  112. package/dist/core/task-spec.d.ts +8 -0
  113. package/dist/core/tool-catalog-entries.d.ts +48 -0
  114. package/dist/core/tool-catalog-entries.js +174 -0
  115. package/dist/core/tool-conformance.d.ts +22 -0
  116. package/dist/core/tool-conformance.js +37 -0
  117. package/dist/core/tool-face.d.ts +171 -0
  118. package/dist/core/tool-face.js +25 -0
  119. package/dist/core/tool-policy.d.ts +24 -0
  120. package/dist/core/tool-policy.js +4 -2
  121. package/dist/core/tool-registry.d.ts +114 -0
  122. package/dist/core/tool-registry.js +114 -0
  123. package/dist/core/tool-result-budget.d.ts +0 -3
  124. package/dist/core/tool-result-budget.js +4 -3
  125. package/dist/core/tool-result-store.d.ts +0 -18
  126. package/dist/core/tool-result-store.js +2 -18
  127. package/dist/core/tool-roster.d.ts +450 -0
  128. package/dist/core/tool-roster.js +497 -0
  129. package/dist/core/tool-spec.d.ts +54 -3
  130. package/dist/core/tools.js +7 -0
  131. package/dist/core/trace.d.ts +24 -0
  132. package/dist/core/wiring-manifest.d.ts +13 -0
  133. package/dist/core/wiring-manifest.js +2 -1
  134. package/dist/core/write-protect.d.ts +2 -1
  135. package/dist/core/write-protect.js +5 -4
  136. package/dist/engine/compaction/utils.d.ts +6 -0
  137. package/dist/engine/compaction/utils.js +19 -19
  138. package/dist/engine/loop/types.d.ts +6 -0
  139. package/dist/index.d.ts +20 -8
  140. package/dist/index.js +19 -7
  141. package/dist/orchestration/goal.js +2 -1
  142. package/dist/orchestration/run-spec.js +3 -5
  143. package/dist/orchestration/run-workflow-tool.js +2 -2
  144. package/dist/prompt-assembly/event-registry.js +3 -3
  145. package/dist/prompt-assembly/tool-catalog.d.ts +2 -2
  146. package/dist/stores/file/checkpoint-store.d.ts +7 -0
  147. package/dist/stores/file/checkpoint-store.js +20 -1
  148. package/dist/tools/fs/fs-bash.d.ts +1 -1
  149. package/dist/tools/fs/fs-bash.js +7 -11
  150. package/dist/tools/fs/fs-read.js +2 -2
  151. package/dist/tools/fs/fs-search-tools.d.ts +0 -7
  152. package/dist/tools/fs/fs-search-tools.js +3 -16
  153. package/dist/tools/fs/fs-write.js +4 -6
  154. package/dist/tools/fs/repo-map.js +2 -2
  155. package/dist/tools/fs/safety.d.ts +2 -1
  156. package/dist/tools/fs/safety.js +6 -5
  157. package/dist/tools/monitor.js +2 -2
  158. package/dist/tools/scheduler-tools.js +5 -8
  159. package/dist/tools/task-list.js +5 -8
  160. package/dist/tools/todo.js +2 -2
  161. package/dist/tools/web.js +3 -6
  162. package/dist/tools/worktree.js +3 -4
  163. package/package.json +4 -1
  164. package/test/export-surface.snapshot.json +183 -7
@@ -2,6 +2,7 @@ import { Type } from "typebox";
2
2
  import { errorResult } from "../tools.js";
3
3
  import { CONNECTING_MESSAGE, GENERIC_FAILURE_MESSAGE, NO_STORES_MESSAGE, SHARED_STORE_FRAME, UNAVAILABLE_MESSAGE, datePrefixOf, echoUntrusted, foldReaderError, listPrefixArgument, normalizeEntries, normalizeMemoryPath, normalizeReadValue, normalizeSnapshot, pageDocuments, relativePathRefusal, sanitizeSharedContent, validateDocumentPath, validatePathPrefix, } from "./normalize.js";
4
4
  import { SHARED_MEMORY_LIST_PAGE_SIZE, SHARED_MEMORY_LIST_TOOL_NAME, SHARED_MEMORY_READ_CAP_BYTES, SHARED_MEMORY_READ_TOOL_NAME, } from "./types.js";
5
+ import { toolFace } from "../tool-catalog-entries.js";
5
6
  const LIST_HINT = "List shared memory stores and their documents — check here before telling the user you do not have something.";
6
7
  const LIST_DESCRIPTION = [
7
8
  LIST_HINT,
@@ -125,11 +126,9 @@ export function createSharedMemoryTools(opts) {
125
126
  };
126
127
  const listTool = {
127
128
  name: SHARED_MEMORY_LIST_TOOL_NAME,
129
+ ...toolFace("memory-list"),
128
130
  description: LIST_DESCRIPTION,
129
- effect: "read",
130
131
  defer: true,
131
- offload: false,
132
- contract: { contractId: "core.memory_list@1", implementationRevision: "1" },
133
132
  parameters: Type.Object({
134
133
  store: Type.Optional(Type.String({
135
134
  description: "Id of the connected memory store to list. Omit to list the memory stores connected to this session (id, description, and writable or read-only).",
@@ -258,11 +257,9 @@ export function createSharedMemoryTools(opts) {
258
257
  };
259
258
  const readTool = {
260
259
  name: SHARED_MEMORY_READ_TOOL_NAME,
260
+ ...toolFace("memory-read"),
261
261
  description: READ_DESCRIPTION,
262
- effect: "read",
263
262
  defer: true,
264
- offload: false,
265
- contract: { contractId: "core.memory_read@1", implementationRevision: "1" },
266
263
  parameters: Type.Object({
267
264
  store: Type.String({
268
265
  description: `Id of the connected memory store to read from (call ${SHARED_MEMORY_LIST_TOOL_NAME} with no arguments to see the connected stores).`,
@@ -1,4 +1,5 @@
1
1
  import { type Checkpoint, type CheckpointStore, type ResumeOutcome } from "../checkpoint-store.js";
2
+ import type { GateOutcome } from "../gate-outcome.js";
2
3
  import { type CheckpointFields } from "../pause-registry.js";
3
4
  import { type ContractAssertionRunner } from "./contract-harness.js";
4
5
  /**
@@ -23,6 +24,9 @@ export declare function createCheckpointFixture(over?: Partial<CheckpointFields>
23
24
  export declare const ALLOW: Extract<ResumeOutcome, {
24
25
  gate: "policy_ask";
25
26
  }>;
27
+ export declare const EXECUTED_ALLOW: GateOutcome;
28
+ export declare const EXECUTED_ALLOW_REORDERED: GateOutcome;
29
+ export declare const VETOED_BY_POLICY: GateOutcome;
26
30
  /**
27
31
  * The full cross-backend contract for {@link CheckpointStore} — the SAME assertions for every
28
32
  * backend. Entries are a 1:1 port of the vitest originals (assertion semantics unchanged).
@@ -1,4 +1,5 @@
1
1
  import { strict as assert } from "node:assert";
2
+ import { executionVerdict } from "../checkpoint-execution-record.js";
2
3
  import { MAX_PENDING_STEER_ENTRIES, mintCheckpointToken, readPendingSteerQueue, } from "../checkpoint-store.js";
3
4
  import { checkpointFrom } from "../pause-registry.js";
4
5
  import { beginContract } from "./contract-harness.js";
@@ -36,6 +37,10 @@ export const ALLOW = {
36
37
  boundInputHash: "h0",
37
38
  hostDecision: { decidedBy: "person" },
38
39
  };
40
+ const SETTLED = { kind: "human_allowed", who: { party: "person" }, when: 1_700_000_000_500 };
41
+ export const EXECUTED_ALLOW = Object.freeze({ disposition: Object.freeze({ kind: "allowed" }), settlement: Object.freeze({ ...SETTLED, who: Object.freeze({ ...SETTLED.who }) }), origin: "policy" });
42
+ export const EXECUTED_ALLOW_REORDERED = Object.freeze({ origin: "policy", settlement: Object.freeze({ when: 1_700_000_000_500, who: Object.freeze({ party: "person" }), kind: "human_allowed" }), disposition: Object.freeze({ kind: "allowed" }) });
43
+ export const VETOED_BY_POLICY = Object.freeze({ disposition: Object.freeze({ kind: "denied", deniedBy: "policy" }), settlement: Object.freeze({ ...SETTLED, who: Object.freeze({ ...SETTLED.who }) }), origin: "policy" });
39
44
  export async function checkpointStoreContract(make, runAssertion) {
40
45
  const { run, settle } = beginContract(runAssertion);
41
46
  run("kit prerequisites: reopen/setPendingSteer/listByScope are implemented (REQUIRED by this kit)", async () => {
@@ -43,6 +48,86 @@ export async function checkpointStoreContract(make, runAssertion) {
43
48
  const missing = ["reopen", "setPendingSteer", "listByScope"].filter((m) => typeof probe[m] !== "function");
44
49
  assert.equal(missing.length, 0, `backend does not implement ${missing.join(", ")} — required for the CheckpointStore contract kit`);
45
50
  });
51
+ run("execution declaration: the backend declares execution.outcome === true (a REQUIRED member of the contract)", async () => {
52
+ const probe = make();
53
+ assert.equal(probe.execution?.outcome, true, "backend does not declare `execution = { outcome: true }` — the run refuses the store at its first read");
54
+ assert.equal(typeof probe.recordExecutionOutcome, "function", "backend does not implement recordExecutionOutcome — required by the CheckpointStore contract");
55
+ });
56
+ run("recordExecutionOutcome on a resolved row → `recorded`; the record and its stamp ride the row, the decision winner is untouched, executionVerdict reads executed", async () => {
57
+ const store = make();
58
+ const cp = createCheckpointFixture();
59
+ await store.put(cp.token, cp);
60
+ assert.equal(executionVerdict((await store.get(cp.token))).kind, "unknown");
61
+ await store.resolve(cp.token, cp.scope, ALLOW);
62
+ const before = (await store.get(cp.token));
63
+ assert.equal(executionVerdict(before).kind, "unknown", "a resolved row with no record reads UNKNOWN — never allowed by default");
64
+ assert.equal(await store.recordExecutionOutcome(cp.token, cp.scope, VETOED_BY_POLICY), "recorded");
65
+ const after = (await store.get(cp.token));
66
+ assert.deepEqual(after.executionOutcome, VETOED_BY_POLICY);
67
+ assert.equal(typeof after.executionAtMs, "number");
68
+ assert.equal(Number.isFinite(after.executionAtMs), true);
69
+ assert.deepEqual(executionVerdict(after), { kind: "executed", gate: VETOED_BY_POLICY });
70
+ assert.deepEqual(after.resolvedOutcome, before.resolvedOutcome);
71
+ assert.equal(after.status, "resolved");
72
+ assert.equal(after.rev, before.rev);
73
+ });
74
+ run("recordExecutionOutcome is idempotent on the SAME record (structural, key-order-blind) → `already_recorded`; a DIFFERENT record throws checkpoint.execution_outcome_conflict and the row is byte-identical", async () => {
75
+ const store = make();
76
+ const cp = createCheckpointFixture();
77
+ await store.put(cp.token, cp);
78
+ await store.resolve(cp.token, cp.scope, ALLOW);
79
+ assert.equal(await store.recordExecutionOutcome(cp.token, cp.scope, EXECUTED_ALLOW), "recorded");
80
+ const filed = (await store.get(cp.token));
81
+ assert.equal(await store.recordExecutionOutcome(cp.token, cp.scope, EXECUTED_ALLOW), "already_recorded");
82
+ assert.equal(await store.recordExecutionOutcome(cp.token, cp.scope, EXECUTED_ALLOW_REORDERED), "already_recorded", "equality is over the record's JSON — key order and freezing are not part of the value");
83
+ assert.equal(await store.recordExecutionOutcome(cp.token, cp.scope, structuredClone(EXECUTED_ALLOW)), "already_recorded", "an unfrozen structural twin is the same record");
84
+ await assert.rejects(store.recordExecutionOutcome(cp.token, cp.scope, VETOED_BY_POLICY), (e) => e.code === "checkpoint.execution_outcome_conflict");
85
+ const still = (await store.get(cp.token));
86
+ assert.deepEqual(still.executionOutcome, filed.executionOutcome);
87
+ assert.equal(still.executionAtMs, filed.executionAtMs, "a conflict writes nothing — not even a fresh stamp");
88
+ });
89
+ run("recordExecutionOutcome answers `not_resolved` for a pending and for an expired row, `absent` for a missing row and for a wrong-scope row; none of them write", async () => {
90
+ const store = make();
91
+ const pending = createCheckpointFixture();
92
+ await store.put(pending.token, pending);
93
+ assert.equal(await store.recordExecutionOutcome(pending.token, pending.scope, EXECUTED_ALLOW), "not_resolved");
94
+ assert.equal((await store.get(pending.token)).executionOutcome, undefined);
95
+ assert.equal(await store.recordExecutionOutcome(pending.token, "tenant-b", EXECUTED_ALLOW), "absent", "a wrong-scope row reads exactly like a missing one (the isolation rule)");
96
+ assert.equal(await store.recordExecutionOutcome(mintCheckpointToken(), pending.scope, EXECUTED_ALLOW), "absent");
97
+ const expired = createCheckpointFixture();
98
+ await store.put(expired.token, expired);
99
+ assert.equal(await store.expire(expired.token, expired.scope), true);
100
+ assert.equal(await store.recordExecutionOutcome(expired.token, expired.scope, EXECUTED_ALLOW), "not_resolved");
101
+ assert.equal((await store.get(expired.token)).executionOutcome, undefined);
102
+ });
103
+ run("reopen is refused on a row that carries an execution record (a settled action is not re-openable); the same row reopens before the record lands", async () => {
104
+ const store = make();
105
+ const cp = createCheckpointFixture();
106
+ await store.put(cp.token, cp);
107
+ await store.resolve(cp.token, cp.scope, ALLOW);
108
+ assert.equal(await store.reopen(cp.token, cp.scope, "env_failed"), true);
109
+ assert.equal(await store.resolve(cp.token, cp.scope, ALLOW, { rev: 2 }), true);
110
+ assert.equal(await store.recordExecutionOutcome(cp.token, cp.scope, EXECUTED_ALLOW), "recorded");
111
+ assert.equal(await store.reopen(cp.token, cp.scope, "env_failed"), false);
112
+ assert.equal(await store.reopen(cp.token, cp.scope, "tool_unavailable"), false, "no reason reopens a recorded row");
113
+ const got = (await store.get(cp.token));
114
+ assert.equal(got.status, "resolved");
115
+ assert.equal(got.rev, 3, "a refused reopen bumps nothing");
116
+ assert.equal(got.reopenReason, undefined);
117
+ assert.deepEqual(got.executionOutcome, EXECUTED_ALLOW);
118
+ });
119
+ run("a row filed WITH an execution record (a restored resolved row) keeps it authoritative: get() returns it, reopen is refused, a different record conflicts, the same one is already_recorded", async () => {
120
+ const store = make();
121
+ const cp = createCheckpointFixture({ status: "resolved", rev: 1, executionOutcome: EXECUTED_ALLOW, executionAtMs: 1_700_000_001_000 });
122
+ await store.put(cp.token, cp);
123
+ const back = (await store.get(cp.token));
124
+ assert.deepEqual(back.executionOutcome, EXECUTED_ALLOW);
125
+ assert.equal(back.executionAtMs, 1_700_000_001_000);
126
+ assert.equal(await store.reopen(cp.token, cp.scope, "env_failed"), false, "a restored recorded row is as settled as a live one");
127
+ await assert.rejects(store.recordExecutionOutcome(cp.token, cp.scope, VETOED_BY_POLICY), (e) => e.code === "checkpoint.execution_outcome_conflict");
128
+ assert.equal(await store.recordExecutionOutcome(cp.token, cp.scope, EXECUTED_ALLOW), "already_recorded");
129
+ assert.equal((await store.get(cp.token)).status, "resolved");
130
+ });
46
131
  run("put create-once → already_exists", async () => {
47
132
  const store = make();
48
133
  const cp = createCheckpointFixture();
@@ -216,6 +301,29 @@ export async function checkpointStoreContract(make, runAssertion) {
216
301
  const stored = (await store.get(bitless.token)).pendingAction;
217
302
  assert.equal("hasBidiControls" in stored, false, "projection backfill must not write back to the row");
218
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
+ });
219
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 () => {
220
328
  const store = make();
221
329
  const oversize = createCheckpointFixture({ token: mintCheckpointToken(), sessionId: "pw-oversize" });
@@ -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
+ }