@sema-agent/core 7.53.0 → 7.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/CHANGELOG.md +277 -1
  2. package/LICENSE +3 -1
  3. package/dist/agents/peer-session-drain.d.ts +0 -1
  4. package/dist/agents/peer-session-drain.js +0 -2
  5. package/dist/agents/send-message-tool.js +3 -4
  6. package/dist/agents/subagent.d.ts +3 -5
  7. package/dist/agents/subagent.js +18 -11
  8. package/dist/agents/tool-filter.d.ts +7 -4
  9. package/dist/agents/tool-filter.js +7 -3
  10. package/dist/core/a2a.js +4 -2
  11. package/dist/core/allow-entry.d.ts +1 -0
  12. package/dist/core/allow-entry.js +1 -0
  13. package/dist/core/approval-object.d.ts +87 -0
  14. package/dist/core/approval-object.js +85 -0
  15. package/dist/core/arg-summary.d.ts +3 -2
  16. package/dist/core/arg-summary.js +17 -11
  17. package/dist/core/ask-origin.d.ts +43 -27
  18. package/dist/core/ask-origin.js +20 -11
  19. package/dist/core/ask-question.d.ts +0 -1
  20. package/dist/core/ask-question.js +4 -67
  21. package/dist/core/ask-unresolvable-notice.d.ts +3 -2
  22. package/dist/core/auto-compaction.d.ts +49 -50
  23. package/dist/core/auto-compaction.js +41 -51
  24. package/dist/core/auto-mode-prompt.d.ts +4 -2
  25. package/dist/core/auto-mode-prompt.js +4 -3
  26. package/dist/core/canonical-json.d.ts +69 -0
  27. package/dist/core/canonical-json.js +130 -8
  28. package/dist/core/checkpoint-requirements.d.ts +13 -7
  29. package/dist/core/checkpoint-requirements.js +4 -1
  30. package/dist/core/checkpoint-store.d.ts +33 -18
  31. package/dist/core/checkpoint-store.js +5 -4
  32. package/dist/core/child-clamp-seats.d.ts +45 -6
  33. package/dist/core/child-clamp-seats.js +56 -4
  34. package/dist/core/consolidate-scope.d.ts +1 -1
  35. package/dist/core/cross-boundary-taint.d.ts +213 -0
  36. package/dist/core/cross-boundary-taint.js +196 -0
  37. package/dist/core/decision-receipt.d.ts +44 -1
  38. package/dist/core/decision-receipt.js +16 -0
  39. package/dist/core/effective-path-target.d.ts +3 -0
  40. package/dist/core/effective-path-target.js +4 -0
  41. package/dist/core/engine-notice.d.ts +34 -2
  42. package/dist/core/engine-notice.js +17 -1
  43. package/dist/core/engine-storage-backend.d.ts +73 -0
  44. package/dist/core/engine-storage-backend.js +1 -0
  45. package/dist/core/file-history-store.d.ts +6 -2
  46. package/dist/core/file-history-store.js +1 -0
  47. package/dist/core/gate-fold.js +10 -2
  48. package/dist/core/gate-lanes.js +30 -21
  49. package/dist/core/gate-outcome.d.ts +7 -3
  50. package/dist/core/governance-codes.d.ts +7 -2
  51. package/dist/core/governance-codes.js +6 -0
  52. package/dist/core/hooks.d.ts +14 -14
  53. package/dist/core/hooks.js +1 -2
  54. package/dist/core/locked-config.d.ts +28 -1
  55. package/dist/core/locked-config.js +30 -3
  56. package/dist/core/lsp.d.ts +1 -1
  57. package/dist/core/mcp-server-spec.d.ts +66 -9
  58. package/dist/core/mcp-server-spec.js +52 -19
  59. package/dist/core/mcp.js +5 -4
  60. package/dist/core/memory-engine/engine.js +2 -1
  61. package/dist/core/memory-engine/export-bundle.d.ts +1 -1
  62. package/dist/core/memory-engine/export-bundle.js +1 -1
  63. package/dist/core/memory-engine/file-backend.d.ts +2 -6
  64. package/dist/core/memory-engine/file-backend.js +1 -12
  65. package/dist/core/memory.d.ts +3 -3
  66. package/dist/core/mode-policy.d.ts +13 -8
  67. package/dist/core/mode-policy.js +3 -2
  68. package/dist/core/mods/kernel/receive.d.ts +2 -1
  69. package/dist/core/mods/kernel/receive.js +7 -2
  70. package/dist/core/parent-spec-seat.d.ts +10 -13
  71. package/dist/core/parent-spec-seat.js +14 -6
  72. package/dist/core/pause-registry.d.ts +35 -1
  73. package/dist/core/pause-registry.js +10 -0
  74. package/dist/core/permission-rule-consent.d.ts +18 -5
  75. package/dist/core/permission-rule-consent.js +13 -8
  76. package/dist/core/permission-rule-model.d.ts +29 -6
  77. package/dist/core/permission-rule-model.js +6 -1
  78. package/dist/core/permission-rule-org.d.ts +37 -0
  79. package/dist/core/permission-rule-org.js +34 -42
  80. package/dist/core/permission-rule-session.js +2 -2
  81. package/dist/core/permission-rule-store.d.ts +8 -0
  82. package/dist/core/permission-rule-store.js +1 -0
  83. package/dist/core/permission-rule-sync.js +3 -2
  84. package/dist/core/read-only-shell.d.ts +1 -1
  85. package/dist/core/read-only-shell.js +30 -9
  86. package/dist/core/remote-env.d.ts +41 -1
  87. package/dist/core/remote-env.js +18 -0
  88. package/dist/core/resume-startup-cause.d.ts +18 -0
  89. package/dist/core/resume-startup-cause.js +12 -0
  90. package/dist/core/runner/approval-feedback.d.ts +46 -0
  91. package/dist/core/runner/approval-feedback.js +41 -0
  92. package/dist/core/runner/compaction-call-options.d.ts +3 -3
  93. package/dist/core/runner/compaction-call-options.js +2 -2
  94. package/dist/core/runner/contracts.d.ts +33 -41
  95. package/dist/core/runner/cross-boundary-lane.d.ts +87 -0
  96. package/dist/core/runner/cross-boundary-lane.js +190 -0
  97. package/dist/core/runner/decide-continuation.d.ts +4 -4
  98. package/dist/core/runner/decide-continuation.js +3 -1
  99. package/dist/core/runner/gate-exit.d.ts +32 -12
  100. package/dist/core/runner/gate-exit.js +75 -33
  101. package/dist/core/runner/inherited-ask-grants.js +2 -2
  102. package/dist/core/runner/person-path-rows.d.ts +2 -2
  103. package/dist/core/runner/prepare-acquire-reconcile.d.ts +3 -4
  104. package/dist/core/runner/prepare-acquire-reconcile.js +3 -3
  105. package/dist/core/runner/prepare-ask-lane.js +3 -3
  106. package/dist/core/runner/prepare-caps-and-workflow.d.ts +6 -5
  107. package/dist/core/runner/prepare-caps-and-workflow.js +3 -3
  108. package/dist/core/runner/prepare-config-doors.d.ts +12 -15
  109. package/dist/core/runner/prepare-config-doors.js +8 -3
  110. package/dist/core/runner/prepare-context-lane.js +6 -3
  111. package/dist/core/runner/prepare-delegation-surface.d.ts +3 -2
  112. package/dist/core/runner/prepare-delegation-surface.js +2 -3
  113. package/dist/core/runner/prepare-execution-env.d.ts +1 -2
  114. package/dist/core/runner/prepare-execution-env.js +2 -1
  115. package/dist/core/runner/prepare-gate-stations.d.ts +2 -0
  116. package/dist/core/runner/prepare-gate-stations.js +4 -3
  117. package/dist/core/runner/prepare-hands-readface.d.ts +2 -2
  118. package/dist/core/runner/prepare-hands-readface.js +1 -1
  119. package/dist/core/runner/prepare-inherited-gate.d.ts +2 -0
  120. package/dist/core/runner/prepare-inherited-gate.js +1 -0
  121. package/dist/core/runner/prepare-park-ask.d.ts +1 -1
  122. package/dist/core/runner/prepare-park-ask.js +9 -3
  123. package/dist/core/runner/prepare-policy-chain.js +2 -7
  124. package/dist/core/runner/prepare-safety-scan.d.ts +7 -2
  125. package/dist/core/runner/prepare-safety-scan.js +34 -27
  126. package/dist/core/runner/prepare-suspend-saga.d.ts +2 -2
  127. package/dist/core/runner/prepare-suspend-saga.js +6 -2
  128. package/dist/core/runner/prepare-task.js +13 -10
  129. package/dist/core/runner/prepare-turn-wiring.d.ts +10 -5
  130. package/dist/core/runner/prepare-turn-wiring.js +14 -49
  131. package/dist/core/runner/prepare-wiring-manifest.d.ts +2 -2
  132. package/dist/core/runner/prepare-wiring-manifest.js +2 -2
  133. package/dist/core/runner/prepare-workspace-restore.js +2 -2
  134. package/dist/core/runner/resume-admission.js +9 -9
  135. package/dist/core/runner/resume-apply.js +4 -4
  136. package/dist/core/runner/resume-policy-outcome.d.ts +2 -2
  137. package/dist/core/runner/resume-policy-outcome.js +18 -15
  138. package/dist/core/runner/resume-preflight.js +2 -2
  139. package/dist/core/runner/resume-review-outcome.js +3 -3
  140. package/dist/core/runner/run-leg.js +57 -33
  141. package/dist/core/runner/run-settle-and-teardown.js +2 -0
  142. package/dist/core/runner/run-terminal-adoption.js +15 -4
  143. package/dist/core/runner/session-file-state-replay.d.ts +5 -2
  144. package/dist/core/runner/session-file-state-replay.js +3 -2
  145. package/dist/core/runner/stream-steer-verb.js +2 -2
  146. package/dist/core/runner/tool-face-overlay.d.ts +24 -6
  147. package/dist/core/runner/tool-face-overlay.js +24 -15
  148. package/dist/core/runner-deps.d.ts +9 -9
  149. package/dist/core/session-store.d.ts +35 -16
  150. package/dist/core/session-store.js +20 -12
  151. package/dist/core/{shell-lexer.d.ts → shell-run-candidates.d.ts} +36 -12
  152. package/dist/core/{shell-lexer.js → shell-run-candidates.js} +145 -225
  153. package/dist/core/shell-wrapper-table.d.ts +5 -3
  154. package/dist/core/shell-wrapper-table.js +1 -1
  155. package/dist/core/store-contracts/checkpoint-store-contract.js +2 -2
  156. package/dist/core/store-contracts/session-store-contract.d.ts +49 -0
  157. package/dist/core/store-contracts/session-store-contract.js +639 -0
  158. package/dist/core/store-contracts/session-store-incarnation-contract.d.ts +12 -0
  159. package/dist/core/store-contracts/session-store-incarnation-contract.js +5 -2
  160. package/dist/core/task-event.d.ts +6 -4
  161. package/dist/core/task-registry-agent.js +1 -0
  162. package/dist/core/task-registry-shared.d.ts +5 -0
  163. package/dist/core/task-registry.d.ts +7 -0
  164. package/dist/core/task-registry.js +17 -0
  165. package/dist/core/task-result.d.ts +1 -1
  166. package/dist/core/task-spec.d.ts +6 -6
  167. package/dist/core/tighten-task-spec.d.ts +6 -4
  168. package/dist/core/tighten-task-spec.js +2 -2
  169. package/dist/core/tool-catalog-entries.d.ts +1 -1
  170. package/dist/core/tool-catalog-entries.js +52 -52
  171. package/dist/core/tool-face-reader.d.ts +49 -0
  172. package/dist/core/tool-face-reader.js +94 -0
  173. package/dist/core/tool-face.d.ts +17 -8
  174. package/dist/core/tool-face.js +1 -0
  175. package/dist/core/tool-policy.d.ts +64 -23
  176. package/dist/core/tool-policy.js +62 -56
  177. package/dist/core/tool-roster.d.ts +46 -28
  178. package/dist/core/tool-roster.js +91 -14
  179. package/dist/core/tool-spec-members.js +2 -2
  180. package/dist/core/tool-spec.d.ts +20 -4
  181. package/dist/core/tools.js +2 -0
  182. package/dist/core/trace.d.ts +24 -2
  183. package/dist/core/types.d.ts +2 -2
  184. package/dist/core/types.js +1 -1
  185. package/dist/core/untrusted-text.d.ts +7 -0
  186. package/dist/core/untrusted-text.js +3 -8
  187. package/dist/core/user-attachment.d.ts +43 -0
  188. package/dist/core/user-attachment.js +92 -15
  189. package/dist/engine/compaction/compaction.d.ts +2 -2
  190. package/dist/engine/compaction/utils.d.ts +2 -2
  191. package/dist/engine/harness/types.d.ts +7 -4
  192. package/dist/engine/llm/types.d.ts +7 -2
  193. package/dist/engine/llm/types.js +2 -1
  194. package/dist/engine/session/repo-utils.d.ts +5 -0
  195. package/dist/engine/session/repo-utils.js +9 -0
  196. package/dist/engine/session/storage-base.js +2 -2
  197. package/dist/index.d.ts +23 -9
  198. package/dist/index.js +19 -8
  199. package/dist/internal/llm.d.ts +1 -1
  200. package/dist/internal/llm.js +1 -1
  201. package/dist/orchestration/run-workflow-tool.d.ts +3 -0
  202. package/dist/orchestration/run-workflow-tool.js +1 -0
  203. package/dist/orchestration/workflow-governance.d.ts +1 -1
  204. package/dist/orchestration/workflow.d.ts +3 -0
  205. package/dist/orchestration/workflow.js +2 -1
  206. package/dist/prompt-assembly/catalog/schema.d.ts +3 -0
  207. package/dist/prompt-assembly/catalog/schema.js +3 -0
  208. package/dist/prompt-assembly/overlay.js +2 -2
  209. package/dist/prompt-assembly/select.js +2 -2
  210. package/dist/prompts/default.d.ts +1 -1
  211. package/dist/shell/lex.d.ts +87 -8
  212. package/dist/shell/lex.js +283 -49
  213. package/dist/stores/file/index.d.ts +8 -6
  214. package/dist/tools/artifact/artifact-tool.js +10 -0
  215. package/dist/tools/fs/bash-program-position.d.ts +3 -15
  216. package/dist/tools/fs/bash-program-position.js +3 -10
  217. package/dist/tools/fs/bash-readonly-classifier.js +2 -0
  218. package/dist/tools/fs/fs-bash.d.ts +3 -3
  219. package/dist/tools/fs/fs-bash.js +72 -101
  220. package/dist/tools/fs/fs-read.d.ts +12 -0
  221. package/dist/tools/fs/fs-read.js +10 -9
  222. package/dist/tools/fs/fs-shared.d.ts +27 -31
  223. package/dist/tools/fs/fs-shared.js +12 -15
  224. package/dist/tools/fs/fs-write.js +1 -1
  225. package/dist/tools/fs/index.js +2 -2
  226. package/dist/tools/fs/read-deny-home.d.ts +1 -1
  227. package/dist/tools/fs/read-deny-home.js +5 -4
  228. package/dist/tools/fs/read-deny.d.ts +16 -3
  229. package/dist/tools/fs/read-deny.js +12 -9
  230. package/dist/tools/fs/safety.d.ts +7 -6
  231. package/dist/tools/fs/shell-read-bindings.d.ts +5 -3
  232. package/dist/tools/fs/shell-read-bindings.js +15 -16
  233. package/dist/tools/fs/shell-read-device.js +1 -1
  234. package/dist/tools/fs/shell-read-plan.js +36 -47
  235. package/dist/tools/fs/shell-read-word.d.ts +1 -6
  236. package/dist/tools/fs/shell-read-word.js +2 -20
  237. package/dist/tools/fs/shell-reentry-text.js +1 -1
  238. package/dist/tools/web.d.ts +2 -2
  239. package/package.json +4 -1
  240. package/test/export-surface.snapshot.json +5654 -5520
  241. package/wire-changes/core@7.53.0.json +30 -8
  242. package/wire-changes/core@7.54.0.json +2870 -0
  243. package/dist/core/shell-scan.d.ts +0 -60
  244. package/dist/core/shell-scan.js +0 -183
@@ -25,3 +25,15 @@ export interface SessionStoreIncarnationContractHooks {
25
25
  runAssertion?: ContractAssertionRunner;
26
26
  }
27
27
  export declare function sessionStoreIncarnationContract(make: () => SessionStore, hooks: SessionStoreIncarnationContractHooks): Promise<void>;
28
+ /**
29
+ * The incarnation cases themselves, registered through the caller's `run` (which builds and disposes the
30
+ * store each case gets). One text for both kits that carry them: this one, and `sessionStoreContract`'s
31
+ * incarnation group — a case changed here changes in both, so the two can never pin different laws.
32
+ * Module-internal (not a package export).
33
+ */
34
+ export declare function registerSessionIncarnationCases(run: (name: string, fn: (store: SessionStore) => Promise<void>) => void, hooks: {
35
+ deleteSession: SessionStoreIncarnationContractHooks["deleteSession"];
36
+ /** This kit's own hook is synchronous; `sessionStoreContract`'s may also resolve later (a backend whose
37
+ * restart must first release an exclusive lock). Awaiting a plain value changes nothing for this kit. */
38
+ reopen: (store: SessionStore) => SessionStore | Promise<SessionStore>;
39
+ }): void;
@@ -23,6 +23,10 @@ export async function sessionStoreIncarnationContract(make, hooks) {
23
23
  await store.dispose();
24
24
  }
25
25
  });
26
+ registerSessionIncarnationCases(run, hooks);
27
+ await settle();
28
+ }
29
+ export function registerSessionIncarnationCases(run, hooks) {
26
30
  run("a session created through acquire carries a token, and every later read of the row answers the same one", async (store) => {
27
31
  await store.acquire(ID_A);
28
32
  const first = await tokenOf(store, ID_A);
@@ -44,7 +48,7 @@ export async function sessionStoreIncarnationContract(make, hooks) {
44
48
  await store.acquire(ID_A);
45
49
  const first = await tokenOf(store, ID_A);
46
50
  assertToken(first, "first instance");
47
- const second = hooks.reopen(store);
51
+ const second = await hooks.reopen(store);
48
52
  try {
49
53
  assert.equal(await tokenOf(second, ID_A), first, "an instance-local token would name a different occupant after every restart");
50
54
  await second.acquire(ID_A, { requireExisting: true });
@@ -88,5 +92,4 @@ export async function sessionStoreIncarnationContract(make, hooks) {
88
92
  assert.notEqual(forked, source, "a fork copies the history, never the occupant");
89
93
  assert.equal(await tokenOf(store, ID_B), source, "forking does not touch the source's occupant");
90
94
  });
91
- await settle();
92
95
  }
@@ -716,10 +716,12 @@ export type TaskEvent = ({
716
716
  * handed over again by the engine (once; a second loss is disclosed `resupply_exhausted`), so
717
717
  * this counts what the summary swallowed, not what is gone. Present iff at least one. */
718
718
  summarizedCrossSessionMessages?: number;
719
- /** CC post-compact file-restore parity (2026-07-03): the working files the engine
720
- * re-read into the summary (attachWorkingFiles), attachment order. A shell renders CC's
721
- * `Read {path} (…)` cards under the compaction divider from this. Absent when the feature is
722
- * off or nothing was attached. `truncated` = the content was clipped at the per-file cap. */
719
+ /** CC post-compact file-restore parity: the files this compaction's restore delivered (attachWorkingFiles) —
720
+ * projected from the restore frames' delivery records, frame order. A shell renders CC's `Read {path} (…)`
721
+ * cards under the compaction divider from this. `chars` = the characters of the file's data the frame
722
+ * delivered (the line-numbered contents — the share itself counts the file's own text); `truncated` = the frame
723
+ * holds a partial view. A file the restore named but did not send (too large, refused, gone, a PDF reference)
724
+ * is not listed. Absent when the feature is off or nothing was delivered. */
723
725
  attachedFiles?: Array<{
724
726
  path: string;
725
727
  chars: number;
@@ -380,6 +380,7 @@ export function registerBackgroundAgentLane(core, input) {
380
380
  ...(input.parentTaskId !== undefined ? { parentTaskId: input.parentTaskId } : {}),
381
381
  ...(input.parentSessionId !== undefined ? { parentSessionId: input.parentSessionId } : {}),
382
382
  ...(input.rootSessionId !== undefined ? { rootSessionId: input.rootSessionId } : {}),
383
+ ...(input.crossBoundaryTree !== undefined ? { crossBoundaryTree: input.crossBoundaryTree } : {}),
383
384
  ...(input.deliveryChannel !== undefined ? { channelState: input.deliveryChannel } : {}),
384
385
  ...(input.retainedContinuation !== undefined ? { retainedContinuation: input.retainedContinuation } : {}),
385
386
  ...(input.owner !== undefined ? { owner: input.owner } : {}),
@@ -248,6 +248,8 @@ export interface DurableAgentLane {
248
248
  }
249
249
  export interface BackgroundAgentTaskHandle extends SemaTaskHandle {
250
250
  type: "background_agent";
251
+ /** The spawning tree's cross-boundary token ({@link RegisterBackgroundAgentInput.crossBoundaryTree}), compared by identity. */
252
+ crossBoundaryTree?: object;
251
253
  /** design/115 P3: aborting this cancels the child run (TaskStop / parent teardown). */
252
254
  abort: AbortController;
253
255
  /** Lineage axes, declared (registration always spread them into the handle; readers used
@@ -908,6 +910,9 @@ export declare function commitCompletionIdIfEmpty(target: {
908
910
  completionId?: string;
909
911
  }, value: string): string;
910
912
  export interface RegisterBackgroundAgentInput extends TaskAccess {
913
+ /** The spawning tree's cross-boundary token (`RunInternals.crossBoundaryTree`, the tree the child joins): a poll of this handle
914
+ * from a leg of the SAME tree moves nothing across the device boundary — the child's bits are already in that tree. */
915
+ crossBoundaryTree?: object;
911
916
  /** design/153 §7.2d (件3c) — parked-resume registration shape: the handle is born PARKED (the
912
917
  * pre-consume window keeps the live face's checkpoint arbitration; the consume flip
913
918
  * {@link TaskRegistry.adoptParkedResume} is the only path to running). Pass the ticket's token
@@ -1,6 +1,7 @@
1
1
  import type { AgentTool, ExecutionEnv } from "../internal/harness.js";
2
2
  import type { ToolEffect } from "./types.js";
3
3
  import { type BackgroundHardKillOutcome, type BackgroundShellCapability, type BackgroundShellId } from "./background-shell.js";
4
+ import type { BoundaryLegsGiven } from "./cross-boundary-taint.js";
4
5
  import type { WorkflowRunStore } from "./workflow-run-store.js";
5
6
  import { type BackgroundAgentStore } from "./background-agent-store.js";
6
7
  import type { TaskNotificationPayload } from "./task-notification.js";
@@ -638,6 +639,12 @@ export declare class TaskRegistry {
638
639
  * one terminal notification (exit code / timeout / auto-stop / env-side kill). */
639
640
  registerMonitor(input: RegisterMonitorInput): string;
640
641
  registerWorkflow(input: RegisterWorkflowInput): string;
642
+ /** The legs one TaskOutput (`"output"`) or TaskStop (`"stop"`) call travels, read off the handle the call dispatches to by the same
643
+ * resolution the poll and the stop take: a background shell's or a monitor's process lives in its own environment (the `env` leg
644
+ * reaches that one); an agent handle that recorded the calling tree's own token moves nothing across the boundary; any other handle — an
645
+ * agent of another tree, a workflow run, an id no live handle answers (a durable row read back) — is content from beyond the
646
+ * boundary on a poll and nothing on a stop. */
647
+ boundaryLegsOf(id: string, access: TaskAccess, verb: "output" | "stop", tree: object): BoundaryLegsGiven;
641
648
  pollTask(id: string, access: TaskAccess, opts?: TaskPollOptions): Promise<UnifiedTaskResult>;
642
649
  stopTask(id: string, access: TaskAccess, opts?: TaskStopOptions): Promise<UnifiedTaskResult>;
643
650
  /** S2b — bounded delivery-confirmation wait (see deliverToRunningAgent). Test-tunable. */
@@ -863,6 +863,14 @@ export class TaskRegistry {
863
863
  registerWorkflow(input) {
864
864
  return registerWorkflowLane(this.core, input);
865
865
  }
866
+ boundaryLegsOf(id, access, verb, tree) {
867
+ const handle = this.resolveInternal(id.trim(), access);
868
+ if (handle?.type === "background_bash" || handle?.type === "monitor")
869
+ return { legs: ["env"], env: handle.env };
870
+ if (handle?.type === "background_agent" && handle.crossBoundaryTree === tree)
871
+ return { legs: [] };
872
+ return { legs: verb === "output" ? ["outside_read"] : [] };
873
+ }
866
874
  async pollTask(id, access, opts = {}) {
867
875
  const taskId = id.trim();
868
876
  const deadline = opts.block ? blockDeadline(opts.timeoutMs, Date.now()) : undefined;
@@ -1301,6 +1309,10 @@ export function createTaskOutputTool(opts) {
1301
1309
  ...toolFace("task-output"),
1302
1310
  description: composeTaskOutputDescription(caps),
1303
1311
  parameters: composeTaskOutputParams(caps),
1312
+ boundaryLegsFor: (rawArgs, at) => {
1313
+ const id = resolveTaskIdArg(rawArgs?.task_id);
1314
+ return id ? opts.registry.boundaryLegsOf(id, { owner: opts.owner, scope: opts.scope, ...(opts.sessionId !== undefined ? { sessionId: opts.sessionId } : {}) }, "output", at.tree) : { legs: [] };
1315
+ },
1304
1316
  execute: async (rawArgs, ctx) => {
1305
1317
  const args = rawArgs;
1306
1318
  const id = resolveTaskIdArg(args.task_id);
@@ -1350,6 +1362,11 @@ export function createTaskStopTool(opts) {
1350
1362
  ...toolFace("task-stop"),
1351
1363
  description: composeTaskStopDescription(REGISTRY_TASK_TOOL_CAPS),
1352
1364
  parameters: TASK_STOP_PARAMS,
1365
+ boundaryLegsFor: (rawArgs, at) => {
1366
+ const a = rawArgs;
1367
+ const id = resolveTaskIdArg(a?.task_id, a?.shell_id);
1368
+ return id ? opts.registry.boundaryLegsOf(id, { owner: opts.owner, scope: opts.scope, ...(opts.sessionId !== undefined ? { sessionId: opts.sessionId } : {}) }, "stop", at.tree) : { legs: [] };
1369
+ },
1353
1370
  execute: async (rawArgs, ctx) => {
1354
1371
  const args = rawArgs;
1355
1372
  const id = resolveTaskIdArg(args.task_id, args.shell_id);
@@ -762,7 +762,7 @@ export interface TaskResult {
762
762
  finalVerifyInjected?: true;
763
763
  /** oracle-grounding gate (opus LOW 复审, additive): HOW MANY final-verification injections
764
764
  * fired this run — 1 = the R9 one-shot only, 2 = R9 + the grounding re-entry (hard cap).
765
- * Present iff `finalVerifyInjected` is (the boolean stays for compatibility). */
765
+ * Present iff `finalVerifyInjected` is (the boolean stays for compatibility, RL-1336-finalverify-bool). */
766
766
  finalVerifyInjections?: number;
767
767
  /** design/133: number of attachment blocks injected this run — turn-boundary bundles
768
768
  * (todo/task reminders, changed-files notes, plan-mode re-reminders, listing drift frames;
@@ -301,8 +301,8 @@ export interface TaskSpec {
301
301
  systemPrompt?: string;
302
302
  /** The role base `core/role.base` selects: `"general"` (absent), `"code"`, `"coordinator"`, `"cowork"` (rendered from the
303
303
  * mounted tools and the working directory); `systemPrompt` replaces whichever is selected. Not inherited (a child gets
304
- * the sub-agent persona). A value outside the closed set is refused at prepare. */
305
- promptRole?: "general" | "code" | "coordinator" | "cowork";
304
+ * the sub-agent persona). A value outside the closed set (`HOST_ROLES`, tested by `isHostRole`) is refused at prepare. */
305
+ promptRole?: import("../prompt-assembly/catalog/schema.js").HostRole;
306
306
  /** `"minimal"` leaves the core-behavior band out; the locked sections (mode sections in play among them) and
307
307
  * `appendSystemPrompt` stay. Absent ⇒ standard. Rides the parent spec seat to every engine-built child (a clamp-table
308
308
  * row: no child authors the standard form back). A value outside the closed set is refused at prepare. */
@@ -1312,10 +1312,10 @@ export interface TaskSpec {
1312
1312
  * `attachWorkingFiles` (LONGRUN-2; **default ON since 2026-07-03** — CC 198 hard-codes its
1313
1313
  * post-compact file restore, and LONGRUN-2 measured ≈2.3 extra read round-trips per compaction
1314
1314
  * without it): after each compaction, re-read the task's most recently READ files (CC
1315
- * readFileState semantics; falls back to modified-by-recency when read tracking is absent) and
1316
- * append their current contents to the summary (bounded: `maxFiles` default 3,
1317
- * `maxCharsPerFile` default 16k chars ≈ 4k tokens, plus a total cap that self-scales to ~15% of
1318
- * the model window). The `compacted` wire event carries the attached list as `attachedFiles`
1315
+ * readFileState semantics; else modified-by-recency) the summary leaves out of view, with the Read tool's
1316
+ * read, into attachment frames after the summary (`maxFiles` default 3, `maxCharsPerFile` default 16k chars
1317
+ * of the file's own text, a total cap ~15% of the window in the file contents the frames send, line numbers
1318
+ * included; a file over either is named, never sent in part). The `compacted` wire event lists the files whose contents were delivered as `attachedFiles`
1319
1319
  * (shell renders CC's post-compact Read cards). `false` opts out; an object customizes caps;
1320
1320
  * no-op when the task has no execution env (no hands, nothing to re-read).
1321
1321
  */
@@ -12,10 +12,12 @@ export declare class TaskSpecTightenError extends Error {
12
12
  * the override can only ADD denials/asks). Either alone ⇒ that one. This is the central fix: a blind spread
13
13
  * would drop the base policy entirely.
14
14
  * - **every row of the clamp table** (`CLAMP_SET`: `handsReadOnly`, `interactiveTools`, `approvalReach`, the two
15
- * containment faces, `readDenyPatterns`, `oneShot`, `memoryPersistenceCapable`, `fileHistory`, `promptForm`) — a base carrying the
16
- * row's restricting value meeting an override that states a different one ⇒ **throw** (loosening); otherwise the
17
- * restricting value wins if either side carries it, an add-only list unions, and an override writing `undefined`
18
- * erases nothing.
15
+ * containment faces, `readDenyPatterns`, `oneShot`, `memoryPersistenceCapable`, `fileHistory`, `promptForm`, and
16
+ * `memory.enabled`) — a base carrying the row's restricting value meeting an override that states a different one ⇒
17
+ * **throw** (loosening); otherwise the restricting value wins if either side carries it, an add-only list unions, and an
18
+ * override writing `undefined` erases nothing. `memory` is an object seat with ONE clamped member (`enabled: false`):
19
+ * the later declaration stands whole (its own scopes, write scope and knobs — never mixed with the base's) with
20
+ * `enabled: false` stated on it when either side carries that.
19
21
  * - **`shellGate`** — one value (`"always"`, the operator floor). Either side set ⇒ the floor stands; it cannot
20
22
  * be loosened because absence is the only other state and an override cannot unset a base value.
21
23
  * - **`onAsk`** — no safe automatic merge (the approver and the policy must be coordinated). Both set to
@@ -1,6 +1,6 @@
1
1
  import { combinePolicies } from "./tool-policy.js";
2
2
  import { clampPermissionMode } from "./ask-origin.js";
3
- import { CLAMP_SEATS, composeClampSeat, loosensClampSeat } from "./child-clamp-seats.js";
3
+ import { CLAMP_SEATS, clampCellOf, clampCellPath, composeClampSeat, loosensClampSeat } from "./child-clamp-seats.js";
4
4
  export class TaskSpecTightenError extends Error {
5
5
  constructor(message) {
6
6
  super(message);
@@ -40,7 +40,7 @@ export function tightenTaskSpec(base, overrides) {
40
40
  const b = base[seat];
41
41
  const o = overrides[seat];
42
42
  if (loosensClampSeat(seat, b, o)) {
43
- throw new TaskSpecTightenError(`tightenTaskSpec: cannot loosen ${seat} from ${JSON.stringify(b)} to ${JSON.stringify(o)} (override may only tighten)`);
43
+ throw new TaskSpecTightenError(`tightenTaskSpec: cannot loosen ${clampCellPath(seat)} from ${JSON.stringify(clampCellOf(seat, b))} to ${JSON.stringify(clampCellOf(seat, o))} (override may only tighten)`);
44
44
  }
45
45
  if (b !== undefined || o !== undefined)
46
46
  Object.assign(merged, { [seat]: composeClampSeat(seat, b, o) });
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The STATIC TOOL CATALOG (design/388 §2.1 "静态目录"): one row per built-in / synthetic tool DEFINITION
3
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
4
+ * `details.type` cards it mints, and its declared FACE (family / boundary legs / axes / contract / path target / rule
5
5
  * vocabulary / render hints / policy bits).
6
6
  *
7
7
  * SINGLE SOURCE, TWO READERS. Each definition module spreads its row's `face` into the `ToolSpec` it
@@ -16,7 +16,7 @@ export const TOOL_CATALOG_ENTRIES = [
16
16
  id: "read", name: "Read", source: "builtin", definedIn: "src/tools/fs/fs-read.ts", mountedBy: ["hands"],
17
17
  cards: ["text", "notebook", "file_unchanged", "image", "document"],
18
18
  face: {
19
- family: "file-read", effect: "read", contract: c("core.read@1"),
19
+ family: "file-read", boundaryLegs: ["env"], effect: "read", contract: c("core.read@1"),
20
20
  pathTarget: { param: "file_path", aliases: ["path"], access: "read", base: "cwd", absent: "none" },
21
21
  ruleFace: { params: ["file_path", "path", "offset", "limit", "pages"] },
22
22
  renderHints: { summaryParams: ["file_path"] },
@@ -27,7 +27,7 @@ export const TOOL_CATALOG_ENTRIES = [
27
27
  id: "edit", name: "Edit", source: "builtin", definedIn: "src/tools/fs/fs-write.ts", mountedBy: ["hands"],
28
28
  cards: ["edit"],
29
29
  face: {
30
- family: "file-write", effect: "write", contract: c("core.edit@1"),
30
+ family: "file-write", boundaryLegs: ["env"], effect: "write", contract: c("core.edit@1"),
31
31
  pathTarget: { param: "file_path", aliases: ["path"], access: "edit", skillScopeEligible: true, base: "cwd", absent: "none" },
32
32
  ruleFace: { params: ["file_path", "path", "old_string", "new_string", "replace_all"] },
33
33
  renderHints: { summaryParams: ["file_path"], approvalCard: "file-edit" },
@@ -38,7 +38,7 @@ export const TOOL_CATALOG_ENTRIES = [
38
38
  id: "write", name: "Write", source: "builtin", definedIn: "src/tools/fs/fs-write.ts", mountedBy: ["hands"],
39
39
  cards: ["create", "update"],
40
40
  face: {
41
- family: "file-write", effect: "write", contract: c("core.write@1"),
41
+ family: "file-write", boundaryLegs: ["env"], effect: "write", contract: c("core.write@1"),
42
42
  pathTarget: { param: "file_path", aliases: ["path"], access: "create", skillScopeEligible: true, base: "cwd", absent: "none" },
43
43
  ruleFace: { params: ["file_path", "path", "content"] },
44
44
  renderHints: { summaryParams: ["file_path"], approvalCard: "file-write" },
@@ -49,7 +49,7 @@ export const TOOL_CATALOG_ENTRIES = [
49
49
  id: "notebook-edit", name: "NotebookEdit", source: "builtin", definedIn: "src/tools/fs/fs-write.ts", mountedBy: ["hands"],
50
50
  cards: ["notebook-edit"],
51
51
  face: {
52
- family: "file-write", effect: "write", contract: c("core.notebook_edit@1"),
52
+ family: "file-write", boundaryLegs: ["env"], effect: "write", contract: c("core.notebook_edit@1"),
53
53
  pathTarget: { param: "notebook_path", aliases: ["path"], access: "edit", base: "cwd", absent: "none" },
54
54
  ruleFace: { params: ["notebook_path", "path", "cell_id", "cell_type", "edit_mode", "new_source"] },
55
55
  renderHints: { userFacingName: "Edit Notebook", summaryParams: ["notebook_path"], approvalCard: "notebook-edit" },
@@ -59,7 +59,7 @@ export const TOOL_CATALOG_ENTRIES = [
59
59
  id: "bash", name: "Bash", source: "builtin", definedIn: "src/tools/fs/fs-bash.ts", mountedBy: ["hands"],
60
60
  cards: ["bash", "readonly_out_of_root", "read_unestablished", "bash_invalid_timeout"],
61
61
  face: {
62
- family: "shell", effect: "write", contentOrigin: "execution", contract: c(FULL_SHELL_CONTRACT_ID),
62
+ family: "shell", boundaryLegs: ["env"], effect: "write", contentOrigin: "execution", contract: c(FULL_SHELL_CONTRACT_ID),
63
63
  ruleFace: SHELL_RULE_FACE,
64
64
  renderHints: { summaryParams: ["command"], approvalCard: "shell" },
65
65
  offloadThresholdChars: 30_000, compactable: true,
@@ -69,7 +69,7 @@ export const TOOL_CATALOG_ENTRIES = [
69
69
  id: "bash-readonly", name: "Bash", source: "builtin", definedIn: "src/tools/fs/fs-bash.ts", mountedBy: ["hands"],
70
70
  cards: ["bash", "readonly_out_of_root", "read_unestablished", "bash_invalid_timeout"],
71
71
  face: {
72
- family: "shell", effect: "read", contract: c(READONLY_SHELL_CONTRACT_ID),
72
+ family: "shell", boundaryLegs: ["env"], effect: "read", contract: c(READONLY_SHELL_CONTRACT_ID),
73
73
  ruleFace: SHELL_RULE_FACE,
74
74
  renderHints: { summaryParams: ["command"], approvalCard: "shell" },
75
75
  offloadThresholdChars: 30_000, compactable: true,
@@ -79,7 +79,7 @@ export const TOOL_CATALOG_ENTRIES = [
79
79
  id: "grep", name: "Grep", source: "builtin", definedIn: "src/tools/fs/fs-search-tools.ts", mountedBy: ["hands"],
80
80
  cards: ["grep"],
81
81
  face: {
82
- family: "search", effect: "read", contract: c("core.grep@1"),
82
+ family: "search", boundaryLegs: ["env"], effect: "read", contract: c("core.grep@1"),
83
83
  pathTarget: { param: "path", access: "read", base: "root", absent: "base" },
84
84
  ruleFace: { params: ["pattern", "path", "glob", "type", "output_mode", "head_limit", "offset", "multiline", "context", "ignore_case", "-i", "-n", "-o", "-A", "-B", "-C"] },
85
85
  renderHints: { userFacingName: "Search", summaryParams: ["pattern", "path"] },
@@ -90,7 +90,7 @@ export const TOOL_CATALOG_ENTRIES = [
90
90
  id: "glob", name: "Glob", source: "builtin", definedIn: "src/tools/fs/fs-search-tools.ts", mountedBy: ["hands"],
91
91
  cards: ["glob"],
92
92
  face: {
93
- family: "search", effect: "read", contract: c("core.glob@1"),
93
+ family: "search", boundaryLegs: ["env"], effect: "read", contract: c("core.glob@1"),
94
94
  pathTarget: { param: "path", access: "read", base: "root", absent: "base", patternParam: "pattern" },
95
95
  ruleFace: { params: ["pattern", "path", "max_results"] },
96
96
  renderHints: { userFacingName: "Search", summaryParams: ["pattern", "path"] },
@@ -100,7 +100,7 @@ export const TOOL_CATALOG_ENTRIES = [
100
100
  {
101
101
  id: "repo-map", name: "RepoMap", source: "builtin", definedIn: "src/tools/fs/repo-map.ts", mountedBy: ["hands"], cards: ["repo-map"],
102
102
  face: {
103
- family: "search", effect: "read", contract: c("core.repo_map@1"),
103
+ family: "search", boundaryLegs: ["env"], effect: "read", contract: c("core.repo_map@1"),
104
104
  pathTarget: { param: "path", access: "read", base: "root", absent: "base" },
105
105
  ruleFace: { params: ["path", "max_chars", "max_files"] },
106
106
  },
@@ -108,62 +108,62 @@ export const TOOL_CATALOG_ENTRIES = [
108
108
  {
109
109
  id: "lsp", name: "LSP", source: "builtin", definedIn: "src/core/lsp.ts", mountedBy: ["hands"], cards: [],
110
110
  face: {
111
- family: "search", effect: "read", contract: c("core.lsp@1"),
111
+ family: "search", boundaryLegs: ["env"], effect: "read", contract: c("core.lsp@1"),
112
112
  pathTarget: { param: "filePath", access: "read", base: "cwd", absent: "none" },
113
113
  ruleFace: { params: ["operation", "filePath", "line", "character", "query"] },
114
114
  },
115
115
  },
116
116
  {
117
117
  id: "artifact", name: ARTIFACT_TOOL_NAME, source: "builtin", definedIn: "src/tools/artifact/artifact-tool.ts", mountedBy: ["optional"], cards: ["artifact"],
118
- face: { family: "other", effect: "write", contract: c("core.artifact@1"), ruleFace: { primaryParams: ["action"] }, renderHints: { summaryParams: ["action", "file_path", "url"] } },
118
+ face: { family: "other", boundaryLegs: "per_call", effect: "write", contract: c("core.artifact@1"), ruleFace: { primaryParams: ["action"] }, renderHints: { summaryParams: ["action", "file_path", "url"] } },
119
119
  },
120
- { 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") } },
121
- { 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") } },
122
- { 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 } },
123
- { 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 } },
124
- { 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 } },
125
- { 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 } },
126
- { id: "monitor", name: "Monitor", source: "builtin", definedIn: "src/tools/monitor.ts", mountedBy: ["backgroundTasks"], cards: ["monitor-start", "readonly_out_of_root", "read_unestablished"], face: { family: "other", effect: "write", contract: c("core.monitor@1"), ruleFace: shellRuleFace(["command", "description", "timeout_ms", "persistent"]), offloadThresholdChars: 10_000 } },
127
- { id: "send-message", name: "SendMessage", protocol: ["teammate"], 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") } },
128
- { 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") } },
129
- { id: "list-agents", name: "ListAgents", source: "builtin", definedIn: "src/agents/list-agents-tool.ts", mountedBy: ["delegation", "peerLane"], cards: ["list-agents"], face: { family: "protocol", effect: "read", aliases: ["ListPeers"], contract: c("core.list_agents@1") } },
130
- { id: "read-inbox", name: "ReadInbox", source: "builtin", definedIn: "src/agents/read-inbox-tool.ts", mountedBy: ["peerLane"], cards: ["read-inbox"], face: { family: "protocol", effect: "read", contract: c("core.read_inbox@1"), offload: false, budgetExempt: true } },
131
- { 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") } },
132
- { id: "report-blocked", name: "ReportBlocked", protocol: ["delegated"], source: "builtin", definedIn: "src/core/runner/synthetic-tools.ts", mountedBy: ["always"], cards: [], face: { family: "scaffold", effect: "read", contract: c("core.report_blocked@1") } },
133
- { 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") } },
134
- { id: "structured-output", name: "StructuredOutput", protocol: ["structured"], source: "synthetic", definedIn: "src/core/runner/synthetic-tools.ts", mountedBy: ["optional"], cards: [], face: { family: "scaffold", effect: "read", contract: c("core.structured_output@1") } },
135
- { 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") } },
136
- { 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 } },
137
- { 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") } },
138
- { 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" } } },
139
- { 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" } } },
140
- { 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") } },
141
- { 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" } },
142
- { 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", "7"), offload: false } },
143
- { 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", "7"), offload: false } },
144
- { 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", "2"), offload: false } },
145
- { 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 } },
146
- { 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 } },
147
- { id: "cron-create", name: "CronCreate", protocol: ["teammate"], source: "builtin", definedIn: "src/tools/scheduler-tools.ts", mountedBy: ["scheduler"], cards: ["cron-create"], face: { family: "other", effect: "write", contract: c("core.cron_create@1") } },
148
- { id: "cron-delete", name: "CronDelete", protocol: ["teammate"], source: "builtin", definedIn: "src/tools/scheduler-tools.ts", mountedBy: ["scheduler"], cards: ["cron-delete"], face: { family: "other", effect: "idempotent", contract: c("core.cron_delete@1") } },
149
- { id: "cron-list", name: "CronList", protocol: ["teammate"], source: "builtin", definedIn: "src/tools/scheduler-tools.ts", mountedBy: ["scheduler"], cards: ["cron-list"], face: { family: "other", effect: "read", contract: c("core.cron_list@1") } },
150
- { 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") } },
120
+ { id: "enter-worktree", name: "EnterWorktree", source: "builtin", definedIn: "src/tools/worktree.ts", mountedBy: ["hands"], cards: ["worktree"], face: { family: "other", boundaryLegs: ["env"], effect: "write", contract: c("core.enter_worktree@1") } },
121
+ { id: "exit-worktree", name: "ExitWorktree", source: "builtin", definedIn: "src/tools/worktree.ts", mountedBy: ["hands"], cards: ["worktree"], face: { family: "other", boundaryLegs: ["env"], effect: "write", contract: c("core.exit_worktree@1") } },
122
+ { id: "task-output-hands", name: "TaskOutput", source: "builtin", definedIn: "src/tools/fs/fs-bash.ts", mountedBy: ["hands"], cards: [], face: { family: "delegate", boundaryLegs: ["env"], effect: "read", contract: TASK_OUTPUT_CONTRACT } },
123
+ { id: "task-stop-hands", name: "TaskStop", source: "builtin", definedIn: "src/tools/fs/fs-bash.ts", mountedBy: ["hands"], cards: [], face: { family: "delegate", boundaryLegs: ["env"], effect: "write", contract: TASK_STOP_CONTRACT } },
124
+ { id: "task-output", name: "TaskOutput", source: "builtin", definedIn: "src/core/task-registry.ts", mountedBy: ["delegation"], cards: ["task-output"], face: { family: "delegate", boundaryLegs: "per_call", effect: "read", contract: TASK_OUTPUT_CONTRACT } },
125
+ { id: "task-stop", name: "TaskStop", source: "builtin", definedIn: "src/core/task-registry.ts", mountedBy: ["delegation"], cards: ["task-stop"], face: { family: "delegate", boundaryLegs: "per_call", effect: "write", contract: TASK_STOP_CONTRACT } },
126
+ { id: "monitor", name: "Monitor", source: "builtin", definedIn: "src/tools/monitor.ts", mountedBy: ["backgroundTasks"], cards: ["monitor-start", "readonly_out_of_root", "read_unestablished"], face: { family: "other", boundaryLegs: ["env"], effect: "write", contract: c("core.monitor@1"), ruleFace: shellRuleFace(["command", "description", "timeout_ms", "persistent"]), offloadThresholdChars: 10_000 } },
127
+ { id: "send-message", name: "SendMessage", protocol: ["teammate"], source: "builtin", definedIn: "src/agents/send-message-tool.ts", mountedBy: ["delegation", "hostInternals", "peerLane"], cards: ["send-message"], face: { family: "protocol", boundaryLegs: ["outside_write"], effect: "write", contract: c("core.send_message@1") } },
128
+ { id: "agent-transcript", name: "AgentTranscript", source: "builtin", definedIn: "src/agents/agent-transcript-tool.ts", mountedBy: ["delegation"], cards: ["agent-transcript"], face: { family: "delegate", boundaryLegs: [], effect: "read", contract: c("core.agent_transcript@1") } },
129
+ { id: "list-agents", name: "ListAgents", source: "builtin", definedIn: "src/agents/list-agents-tool.ts", mountedBy: ["delegation", "peerLane"], cards: ["list-agents"], face: { family: "protocol", boundaryLegs: [], effect: "read", aliases: ["ListPeers"], contract: c("core.list_agents@1") } },
130
+ { id: "read-inbox", name: "ReadInbox", source: "builtin", definedIn: "src/agents/read-inbox-tool.ts", mountedBy: ["peerLane"], cards: ["read-inbox"], face: { family: "protocol", boundaryLegs: [], effect: "read", contract: c("core.read_inbox@1"), offload: false, budgetExempt: true } },
131
+ { id: "workflow", name: "Workflow", source: "builtin", definedIn: "src/orchestration/run-workflow-tool.ts", mountedBy: ["workflow"], cards: ["workflow-run"], face: { family: "delegate", boundaryLegs: [], effect: "write", contract: c("core.workflow@1") } },
132
+ { id: "report-blocked", name: "ReportBlocked", protocol: ["delegated"], source: "builtin", definedIn: "src/core/runner/synthetic-tools.ts", mountedBy: ["always"], cards: [], face: { family: "scaffold", boundaryLegs: [], effect: "read", contract: c("core.report_blocked@1") } },
133
+ { id: "report-findings", name: "ReportFindings", source: "builtin", definedIn: "src/core/runner/synthetic-tools.ts", mountedBy: ["always"], cards: ["report-findings"], face: { family: "scaffold", boundaryLegs: [], effect: "read", contract: c("core.report_findings@1") } },
134
+ { id: "structured-output", name: "StructuredOutput", protocol: ["structured"], source: "synthetic", definedIn: "src/core/runner/synthetic-tools.ts", mountedBy: ["optional"], cards: [], face: { family: "scaffold", boundaryLegs: [], effect: "read", contract: c("core.structured_output@1") } },
135
+ { id: "skill", name: "Skill", source: "builtin", definedIn: "src/core/runner/synthetic-tools.ts", mountedBy: ["optional"], cards: [], face: { family: "scaffold", boundaryLegs: [], effect: "read", contract: c("core.skill@1") } },
136
+ { id: "read-tool-result", name: "ReadToolResult", source: "builtin", definedIn: "src/core/tool-result-store.ts", mountedBy: ["optional"], cards: [], face: { family: "scaffold", boundaryLegs: [], effect: "read", contract: c("core.read_tool_result@1"), budgetExempt: true } },
137
+ { id: "tool-search", name: "ToolSearch", source: "builtin", definedIn: "src/core/runner/tool-disclosure.ts", mountedBy: ["optional"], cards: ["tool-search"], face: { family: "scaffold", boundaryLegs: [], effect: "read", contract: c("core.tool_search@1") } },
138
+ { id: "ask-user-question", name: "AskUserQuestion", source: "builtin", definedIn: "src/core/ask-question.ts", mountedBy: ["optional"], cards: ["ask-question"], face: { family: "other", boundaryLegs: [], effect: "read", contract: c("core.ask_user_question@1"), renderHints: { approvalCard: "question" } } },
139
+ { id: "exit-plan-mode", name: "ExitPlanMode", source: "builtin", definedIn: "src/core/present-plan-tool.ts", mountedBy: ["optional"], cards: ["exit-plan-mode"], face: { family: "plan", boundaryLegs: [], effect: "read", contract: c("core.exit_plan_mode@1"), renderHints: { userFacingName: "", approvalCard: "plan" } } },
140
+ { id: "enter-plan-mode", name: "EnterPlanMode", source: "builtin", definedIn: "src/core/present-plan-tool.ts", mountedBy: ["optional"], cards: ["enter-plan-mode"], face: { family: "plan", boundaryLegs: [], effect: "read", contract: c("core.enter_plan_mode@1") } },
141
+ { 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", boundaryLegs: ["outside_read"], effect: "read" } },
142
+ { id: "memory-search", name: "memory_search", source: "builtin", definedIn: "src/core/memory-engine/tools.ts", mountedBy: ["memory"], cards: [], face: { family: "memory", boundaryLegs: [], effect: "read", contentOrigin: "local", contract: c("core.memory_search@1", "7"), offload: false } },
143
+ { id: "memory-get", name: "memory_get", source: "builtin", definedIn: "src/core/memory-engine/tools.ts", mountedBy: ["memory"], cards: [], face: { family: "memory", boundaryLegs: [], effect: "read", contentOrigin: "local", contract: c("core.memory_get@1", "7"), offload: false } },
144
+ { id: "memory-index", name: "memory_index", source: "builtin", definedIn: "src/core/memory-engine/tools.ts", mountedBy: ["memory"], cards: [], face: { family: "memory", boundaryLegs: [], effect: "read", contentOrigin: "local", contract: c("core.memory_index@1", "2"), offload: false } },
145
+ { id: "memory-list", name: "memory_list", source: "builtin", definedIn: "src/core/shared-memory/tools.ts", mountedBy: ["memory"], cards: [], face: { family: "memory", boundaryLegs: [], effect: "read", contract: c("core.memory_list@1"), offload: false } },
146
+ { id: "memory-read", name: "memory_read", source: "builtin", definedIn: "src/core/shared-memory/tools.ts", mountedBy: ["memory"], cards: [], face: { family: "memory", boundaryLegs: [], effect: "read", contract: c("core.memory_read@1"), offload: false } },
147
+ { id: "cron-create", name: "CronCreate", protocol: ["teammate"], source: "builtin", definedIn: "src/tools/scheduler-tools.ts", mountedBy: ["scheduler"], cards: ["cron-create"], face: { family: "other", boundaryLegs: ["env"], effect: "write", contract: c("core.cron_create@1") } },
148
+ { id: "cron-delete", name: "CronDelete", protocol: ["teammate"], source: "builtin", definedIn: "src/tools/scheduler-tools.ts", mountedBy: ["scheduler"], cards: ["cron-delete"], face: { family: "other", boundaryLegs: ["env"], effect: "idempotent", contract: c("core.cron_delete@1") } },
149
+ { id: "cron-list", name: "CronList", protocol: ["teammate"], source: "builtin", definedIn: "src/tools/scheduler-tools.ts", mountedBy: ["scheduler"], cards: ["cron-list"], face: { family: "other", boundaryLegs: ["env"], effect: "read", contract: c("core.cron_list@1") } },
150
+ { id: "schedule-wakeup", name: "ScheduleWakeup", source: "builtin", definedIn: "src/tools/scheduler-tools.ts", mountedBy: ["scheduler"], cards: ["schedule-wakeup"], face: { family: "other", boundaryLegs: ["env"], effect: "write", contract: c("core.schedule_wakeup@1") } },
151
151
  {
152
152
  id: "web-fetch", name: "WebFetch", source: "builtin", definedIn: "src/tools/web.ts", mountedBy: ["scenario"], cards: ["web-fetch"],
153
- face: { family: "web", effect: "read", contentOrigin: "external", contract: c("core.web_fetch@1"), ruleFace: { primaryParams: ["url"] }, renderHints: { summaryParams: ["url"], approvalCard: "fetch" }, compactable: true },
153
+ face: { family: "web", boundaryLegs: ["outside_read"], effect: "read", contentOrigin: "external", contract: c("core.web_fetch@1"), ruleFace: { primaryParams: ["url"] }, renderHints: { summaryParams: ["url"], approvalCard: "fetch" }, compactable: true },
154
154
  },
155
155
  {
156
156
  id: "web-search", name: "WebSearch", source: "builtin", definedIn: "src/tools/web.ts", mountedBy: ["scenario"], cards: ["web-search"],
157
- face: { family: "web", effect: "read", contentOrigin: "external", contract: c("core.web_search@1"), ruleFace: { primaryParams: ["query"] }, renderHints: { summaryParams: ["query"] }, compactable: true },
157
+ face: { family: "web", boundaryLegs: ["outside_read"], effect: "read", contentOrigin: "external", contract: c("core.web_search@1"), ruleFace: { primaryParams: ["query"] }, renderHints: { summaryParams: ["query"] }, compactable: true },
158
158
  },
159
- { 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") } },
160
- { id: "task-create", name: TASK_CREATE_TOOL_NAME, protocol: ["teammate"], protocolUnderCeiling: true, 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") } },
161
- { id: "task-get", name: TASK_GET_TOOL_NAME, protocol: ["teammate"], protocolUnderCeiling: true, 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") } },
162
- { id: "task-update", name: TASK_UPDATE_TOOL_NAME, protocol: ["teammate"], protocolUnderCeiling: true, 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") } },
163
- { id: "task-list", name: TASK_LIST_TOOL_NAME, protocol: ["teammate"], protocolUnderCeiling: true, 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") } },
164
- { 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") } },
165
- { id: "observer-report", name: "ObserverReport", protocol: ["observer"], source: "builtin", definedIn: "src/agents/observer.ts", mountedBy: ["scenario"], cards: [], face: { family: "scaffold", effect: "write", contract: c("core.observer_report@1") } },
166
- { id: "declare-done", name: "DeclareDone", protocol: ["goal"], source: "builtin", definedIn: "src/orchestration/goal.ts", mountedBy: ["scenario"], cards: [], face: { family: "scaffold", effect: "read" } },
159
+ { 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", boundaryLegs: [], effect: "idempotent", contract: c("core.todo_write@1") } },
160
+ { id: "task-create", name: TASK_CREATE_TOOL_NAME, protocol: ["teammate"], source: "builtin", definedIn: "src/tools/task-list.ts", mountedBy: ["scenario"], cards: ["task"], modelGate: "task-scaffold", face: { family: "plan", boundaryLegs: [], effect: "write", contract: c("core.task_create@1") } },
161
+ { id: "task-get", name: TASK_GET_TOOL_NAME, protocol: ["teammate"], source: "builtin", definedIn: "src/tools/task-list.ts", mountedBy: ["scenario"], cards: ["task"], modelGate: "task-scaffold", face: { family: "plan", boundaryLegs: [], effect: "read", contract: c("core.task_get@1") } },
162
+ { id: "task-update", name: TASK_UPDATE_TOOL_NAME, protocol: ["teammate"], source: "builtin", definedIn: "src/tools/task-list.ts", mountedBy: ["scenario"], cards: ["task"], modelGate: "task-scaffold", face: { family: "plan", boundaryLegs: [], effect: "idempotent", contract: c("core.task_update@1") } },
163
+ { id: "task-list", name: TASK_LIST_TOOL_NAME, protocol: ["teammate"], source: "builtin", definedIn: "src/tools/task-list.ts", mountedBy: ["scenario"], cards: ["task-list"], modelGate: "task-scaffold", face: { family: "plan", boundaryLegs: [], effect: "read", contract: c("core.task_list@1") } },
164
+ { id: "agent", name: "Agent", source: "builtin", definedIn: "src/agents/subagent.ts", mountedBy: ["scenario"], cards: ["agent", "fork"], face: { family: "delegate", boundaryLegs: [], contentOrigin: "local", contract: c("core.agent@1") } },
165
+ { id: "observer-report", name: "ObserverReport", protocol: ["observer"], source: "builtin", definedIn: "src/agents/observer.ts", mountedBy: ["scenario"], cards: [], face: { family: "scaffold", boundaryLegs: [], effect: "write", contract: c("core.observer_report@1") } },
166
+ { id: "declare-done", name: "DeclareDone", protocol: ["goal"], source: "builtin", definedIn: "src/orchestration/goal.ts", mountedBy: ["scenario"], cards: [], face: { family: "scaffold", boundaryLegs: [], effect: "read" } },
167
167
  ];
168
168
  export const ENGINE_CARDS = Object.freeze({
169
169
  mcp: "src/core/mcp.ts",
@@ -0,0 +1,49 @@
1
+ /**
2
+ * The READER of an MCP server's `toolFaces` declaration ({@link McpServerSpec.toolFaces}, the per-tool FACE overlay): `readToolFaces(declared)`,
3
+ * the face-side sibling of `readToolAxes` (`mcp-server-spec.ts`). The engine's preparation door and its MCP materialization entrance ask it
4
+ * (`locked-config.ts`), and a host that assembles the field from JSON (a console's write door, a deployment intake) asks it at its own door:
5
+ * one reading of the declaration, whoever asks and whenever. The mount ({@link applyMcpToolFaces}) then consumes the reader's copy and never
6
+ * the raw object, and judges each face against the materialized schema with the very judge this reader ends in.
7
+ *
8
+ * It sits beside `mcp-server-spec.ts` rather than in the runner directory the mount lives in: it is the declaration's door, not a station of
9
+ * the prepare path, and the public surface is tiered by where a symbol is declared (`readToolAxes` is `advanced` for the same reason).
10
+ */
11
+ import { type McpServerSpec } from "./mcp-server-spec.js";
12
+ /**
13
+ * {@link readToolFaces}'s answer. `ok: true` hands back a copy of the faces that the reader itself built, plain data and none of the caller's own
14
+ * objects; `faces` is absent when nothing is declared (no key at all, or an empty record). `ok: false` carries one sentence (its detail cut at
15
+ * 240 characters) naming the tool and the member that did not fit. What it may quote of the declaration is its structure and nothing of its content:
16
+ * a tool or member name (quoted, a tool name cut at 80 characters), the word a closed table refuses, a key or a list entry over its bound (its
17
+ * first 40 characters), a key a pattern parameter names, a count and its bound. It never quotes display text (`userFacingName`, `activity`) or
18
+ * the entries of a list that fits. These are the mount's judge's own sentences, the ones it announces at the listing, so the reader and the mount
19
+ * tell one operator one thing; `readToolAxes` refuses in the same sentence shape and names no word because an axis word is never a name the
20
+ * operator needs echoed to find the fault.
21
+ */
22
+ export type ToolFacesRead = {
23
+ ok: true;
24
+ faces?: NonNullable<McpServerSpec["toolFaces"]>;
25
+ } | {
26
+ ok: false;
27
+ problem: string;
28
+ };
29
+ /**
30
+ * The one reader of a raw `toolFaces` declaration — {@link McpServerSpec.toolFaces}, the per-tool FACE overlay — and the face-side sibling of
31
+ * `readToolAxes` (the safety axes `effect` / `egress` / `irreversibility` are that declaration's, read once, there). The preparation door asks it
32
+ * for every MCP server of a task (`config.tool_face_invalid` refuses the whole prepare — the code a caller tool's bad face has at the caller door;
33
+ * a declaration the entry's own plain-data receipt refuses first keeps `config.invalid_argument`), `materializeMcpTools` asks it per server (that
34
+ * server's dial fails, the same code), and a host that assembles the field from JSON asks it at its own door and hands the engine what it answered.
35
+ *
36
+ * It judges the declaration alone, which is everything the declaration can say without the tool's schema: the receipt of a plain-data
37
+ * declaration (`receiveDeclaration`: a Proxy, a map, a class instance, a getter that throws, a cycle, a nesting past the engine's one
38
+ * depth, a tool named `__proto__` are refusals that say where they sit), the shape of each face (the members the types have, each of its
39
+ * kind), then the very judge the mount runs (`toolFaceProblem`) minus the checks that need the schema — the five closed words
40
+ * (`family`, `pathTarget.access` / `base` / `absent`, `renderHints.approvalCard`), the bounds, and the cross-member rules (a write path
41
+ * target's `base: "root"` / `absent: "base"`, an inert `patternParam`). The checks that read the schema (a `param` that is a top-level
42
+ * property, the keys an approval card needs, the names a summary or a rule face lists) and the one that reads the minted tool (a result card
43
+ * lives under the tool's own namespace) stay with the listing, where the schema and the tool are known.
44
+ *
45
+ * Any one face that does not fit refuses the WHOLE declaration (`ok: false`): a face is an overlay applied whole or not at all, and a
46
+ * dropped face is a tool mounted without the protection its operator declared. Absent ⇒ `{ ok: true }`. An empty record is no declaration
47
+ * either; an empty face `{}` under a tool name is kept as written. An empty tool name is refused (no tool answers to it).
48
+ */
49
+ export declare function readToolFaces(declared: unknown): ToolFacesRead;