@sema-agent/core 7.53.0 → 7.54.1

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 (249) hide show
  1. package/CHANGELOG.md +291 -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 +7 -9
  104. package/dist/core/runner/prepare-acquire-reconcile.js +8 -6
  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 +683 -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/parent-chain-guard.d.ts +10 -0
  195. package/dist/engine/session/parent-chain-guard.js +10 -0
  196. package/dist/engine/session/repo-utils.d.ts +5 -0
  197. package/dist/engine/session/repo-utils.js +9 -0
  198. package/dist/engine/session/storage-base.js +5 -2
  199. package/dist/index.d.ts +23 -9
  200. package/dist/index.js +19 -8
  201. package/dist/internal/harness.d.ts +1 -0
  202. package/dist/internal/harness.js +1 -0
  203. package/dist/internal/llm.d.ts +1 -1
  204. package/dist/internal/llm.js +1 -1
  205. package/dist/orchestration/run-workflow-tool.d.ts +3 -0
  206. package/dist/orchestration/run-workflow-tool.js +1 -0
  207. package/dist/orchestration/workflow-governance.d.ts +1 -1
  208. package/dist/orchestration/workflow.d.ts +3 -0
  209. package/dist/orchestration/workflow.js +2 -1
  210. package/dist/prompt-assembly/catalog/schema.d.ts +3 -0
  211. package/dist/prompt-assembly/catalog/schema.js +3 -0
  212. package/dist/prompt-assembly/overlay.js +2 -2
  213. package/dist/prompt-assembly/select.js +2 -2
  214. package/dist/prompts/default.d.ts +1 -1
  215. package/dist/shell/lex.d.ts +87 -8
  216. package/dist/shell/lex.js +283 -49
  217. package/dist/stores/file/index.d.ts +8 -6
  218. package/dist/tools/artifact/artifact-tool.js +10 -0
  219. package/dist/tools/fs/bash-program-position.d.ts +3 -15
  220. package/dist/tools/fs/bash-program-position.js +3 -10
  221. package/dist/tools/fs/bash-readonly-classifier.js +2 -0
  222. package/dist/tools/fs/fs-bash.d.ts +3 -3
  223. package/dist/tools/fs/fs-bash.js +72 -101
  224. package/dist/tools/fs/fs-read.d.ts +12 -0
  225. package/dist/tools/fs/fs-read.js +10 -9
  226. package/dist/tools/fs/fs-shared.d.ts +27 -31
  227. package/dist/tools/fs/fs-shared.js +12 -15
  228. package/dist/tools/fs/fs-write.js +1 -1
  229. package/dist/tools/fs/index.js +2 -2
  230. package/dist/tools/fs/read-deny-home.d.ts +1 -1
  231. package/dist/tools/fs/read-deny-home.js +5 -4
  232. package/dist/tools/fs/read-deny.d.ts +16 -3
  233. package/dist/tools/fs/read-deny.js +12 -9
  234. package/dist/tools/fs/safety.d.ts +7 -6
  235. package/dist/tools/fs/shell-read-bindings.d.ts +5 -3
  236. package/dist/tools/fs/shell-read-bindings.js +15 -16
  237. package/dist/tools/fs/shell-read-device.js +1 -1
  238. package/dist/tools/fs/shell-read-plan.js +36 -47
  239. package/dist/tools/fs/shell-read-word.d.ts +1 -6
  240. package/dist/tools/fs/shell-read-word.js +2 -20
  241. package/dist/tools/fs/shell-reentry-text.js +1 -1
  242. package/dist/tools/web.d.ts +2 -2
  243. package/package.json +4 -1
  244. package/test/export-surface.snapshot.json +5654 -5520
  245. package/wire-changes/core@7.53.0.json +30 -8
  246. package/wire-changes/core@7.54.0.json +2870 -0
  247. package/wire-changes/core@7.54.1.json +37 -0
  248. package/dist/core/shell-scan.d.ts +0 -60
  249. package/dist/core/shell-scan.js +0 -183
@@ -2,6 +2,7 @@ import { Type, type Static, type TSchema } from "typebox";
2
2
  import type { AgentTool } from "../internal/harness-types.js";
3
3
  import { type ReservedApprovalCard, type ToolApprovalCard, type ToolFamily, type ToolRosterPathTarget, type ToolMountTag, type ToolPathTarget, type ToolRenderHints, type ToolRuleFace } from "./tool-face.js";
4
4
  import type { ToolContentOrigin, ToolEffect } from "./tool-spec.js";
5
+ import type { ToolMarkSource } from "./ask-origin.js";
5
6
  export declare const ROSTER_SOURCES: readonly ["builtin", "caller", "host", "mcp", "a2a", "external", "synthetic"];
6
7
  export type RosterSource = (typeof ROSTER_SOURCES)[number];
7
8
  export declare const ToolRosterRenderHints: Type.TObject<{
@@ -50,6 +51,9 @@ export declare const ToolRosterEntry: Type.TObject<{
50
51
  contentOrigin: Type.TUnion<[Type.TLiteral<"local">, Type.TLiteral<"execution">, Type.TLiteral<"external">]>;
51
52
  contentOriginProvenance: Type.TUnion<[Type.TLiteral<"declared">, Type.TLiteral<"server">, Type.TLiteral<"exempted">, Type.TLiteral<"default">]>;
52
53
  family: Type.TUnion<[Type.TLiteral<"shell">, Type.TLiteral<"file-read">, Type.TLiteral<"file-write">, Type.TLiteral<"search">, Type.TLiteral<"web">, Type.TLiteral<"delegate">, Type.TLiteral<"plan">, Type.TLiteral<"scaffold">, Type.TLiteral<"memory">, Type.TLiteral<"protocol">, Type.TLiteral<"other">]>;
54
+ /** The legs a call of this tool travels (`BOUNDARY_LEGS`), or `"per_call"` when its dispatch gives them per call: the engine's own
55
+ * definitions and the protocol tools state them, every other row follows its `effect` (`rosterBoundaryLegs`). */
56
+ boundaryLegs: Type.TUnion<[Type.TLiteral<"per_call">, Type.TArray<Type.TUnion<[Type.TLiteral<"env">, Type.TLiteral<"outside_read">, Type.TLiteral<"outside_write">]>>]>;
53
57
  pathTarget: Type.TOptional<Type.TObject<{
54
58
  param: Type.TString;
55
59
  aliases: Type.TArray<Type.TString>;
@@ -124,6 +128,9 @@ export declare const ToolRoster: Type.TObject<{
124
128
  contentOrigin: Type.TUnion<[Type.TLiteral<"local">, Type.TLiteral<"execution">, Type.TLiteral<"external">]>;
125
129
  contentOriginProvenance: Type.TUnion<[Type.TLiteral<"declared">, Type.TLiteral<"server">, Type.TLiteral<"exempted">, Type.TLiteral<"default">]>;
126
130
  family: Type.TUnion<[Type.TLiteral<"shell">, Type.TLiteral<"file-read">, Type.TLiteral<"file-write">, Type.TLiteral<"search">, Type.TLiteral<"web">, Type.TLiteral<"delegate">, Type.TLiteral<"plan">, Type.TLiteral<"scaffold">, Type.TLiteral<"memory">, Type.TLiteral<"protocol">, Type.TLiteral<"other">]>;
131
+ /** The legs a call of this tool travels (`BOUNDARY_LEGS`), or `"per_call"` when its dispatch gives them per call: the engine's own
132
+ * definitions and the protocol tools state them, every other row follows its `effect` (`rosterBoundaryLegs`). */
133
+ boundaryLegs: Type.TUnion<[Type.TLiteral<"per_call">, Type.TArray<Type.TUnion<[Type.TLiteral<"env">, Type.TLiteral<"outside_read">, Type.TLiteral<"outside_write">]>>]>;
127
134
  pathTarget: Type.TOptional<Type.TObject<{
128
135
  param: Type.TString;
129
136
  aliases: Type.TArray<Type.TString>;
@@ -206,6 +213,9 @@ export declare const ToolRosterDelta: Type.TObject<{
206
213
  contentOrigin: Type.TUnion<[Type.TLiteral<"local">, Type.TLiteral<"execution">, Type.TLiteral<"external">]>;
207
214
  contentOriginProvenance: Type.TUnion<[Type.TLiteral<"declared">, Type.TLiteral<"server">, Type.TLiteral<"exempted">, Type.TLiteral<"default">]>;
208
215
  family: Type.TUnion<[Type.TLiteral<"shell">, Type.TLiteral<"file-read">, Type.TLiteral<"file-write">, Type.TLiteral<"search">, Type.TLiteral<"web">, Type.TLiteral<"delegate">, Type.TLiteral<"plan">, Type.TLiteral<"scaffold">, Type.TLiteral<"memory">, Type.TLiteral<"protocol">, Type.TLiteral<"other">]>;
216
+ /** The legs a call of this tool travels (`BOUNDARY_LEGS`), or `"per_call"` when its dispatch gives them per call: the engine's own
217
+ * definitions and the protocol tools state them, every other row follows its `effect` (`rosterBoundaryLegs`). */
218
+ boundaryLegs: Type.TUnion<[Type.TLiteral<"per_call">, Type.TArray<Type.TUnion<[Type.TLiteral<"env">, Type.TLiteral<"outside_read">, Type.TLiteral<"outside_write">]>>]>;
209
219
  pathTarget: Type.TOptional<Type.TObject<{
210
220
  param: Type.TString;
211
221
  aliases: Type.TArray<Type.TString>;
@@ -281,15 +291,10 @@ export declare function approvalCardMemberOf(call: {
281
291
  * other aliases — the ONE order of a call's names: a reader that must pick one of them by position (a `member` seat)
282
292
  * picks the spelling first, as the same call was judged before name resolution. */
283
293
  export declare function toolCallFaceOf(roster: ToolRoster | undefined, toolName: string): import("./tool-policy.js").ToolCallFace | undefined;
284
- /** design/388 B18 — the identity members a park row carries, read off a roster snapshot by name; an empty object when
285
- * the name is not on the snapshot (the row then reads pre-identity and the resume takes the name-only path). */
286
- export declare function parkedToolIdentity(roster: ToolRoster | undefined, toolName: string): {
287
- capabilityId?: string;
288
- contractId?: string;
289
- shapeDigest?: string;
290
- implementationRevision?: string;
291
- rosterDigest?: string;
292
- };
294
+ /** design/388 B18 — the identity members a park row carries (the tool member of its approval object,
295
+ * `approval-object.ts`), read off a roster snapshot by name; an empty object when the name is not on the snapshot
296
+ * (the row then reads pre-identity and the resume binds the name alone). */
297
+ export declare function parkedToolIdentity(roster: ToolRoster | undefined, toolName: string): Partial<ToolIdentity>;
293
298
  /** design/388 B6 × B18 — the FACE members a park row carries beside its identity: the display family, the declared
294
299
  * path target and the names the parked call was judged under. `names` is copied from the face the gate stamped on the
295
300
  * request (the model's spelling first — the row carries the judged fact, it is not re-derived); a request with no face
@@ -375,7 +380,7 @@ export type RosterBoundCode = "config.tool_name_too_long" | "config.tool_contrac
375
380
  * undefined when every member fits. Read by the declaration door (a `TaskSpec.tools` entry, refused before session
376
381
  * acquire) and by the row mint (every source — an MCP/A2A peer, a definition that reached the mint by a route the
377
382
  * door does not cover — refused before the row exists). A member over its bound is NEVER truncated onto the row: a
378
- * truncated identity is a different identity (`judgeParkedToolIdentity` compares it across a park; the card
383
+ * truncated identity is a different identity (`judgeApprovalObject` compares it across a park; the card
379
384
  * namespace door reads it), and the schema's own bounds would then describe a row the mint never made.
380
385
  */
381
386
  export declare function rosterMemberBoundProblem(m: RosterBoundMembers): {
@@ -427,6 +432,7 @@ export interface MountedToolFace {
427
432
  compactable?: true;
428
433
  budgetExempt?: true;
429
434
  modelGate?: string;
435
+ boundaryLegs?: unknown;
430
436
  }
431
437
  export declare const faceOf: (t: AgentTool) => MountedToolFace;
432
438
  /** The leg facts the snapshot reads beside the instances — the safety scan's folds and the per-task knobs. */
@@ -441,20 +447,32 @@ export interface RosterSnapshotFacts {
441
447
  /** `TaskSpec.memory.trustedTools` — the exemption channel for an undeclared content class. */
442
448
  trustedTools: ReadonlySet<string>;
443
449
  }
450
+ /** The leg's per-name safety-axis tables the builder keeps aligned with its mounts beside the effect map (the safety scan mints
451
+ * them; the protocol folds and the shell seat also write them). */
452
+ export interface RosterAxisTables {
453
+ irreversibilityTier: Map<string, "never" | "maybe" | "always">;
454
+ irreversibleTools: Set<string>;
455
+ reversibilityProbes: Map<string, unknown>;
456
+ egressTools: Set<string>;
457
+ toolMarkSources: Map<string, Set<ToolMarkSource>>;
458
+ }
444
459
  export declare class RosterBuilder {
445
460
  #private;
446
- constructor(effects: Map<string, ToolEffect>);
461
+ /** `axes` — the leg's safety-axis tables beside the effect map (a builder for a single probe tool may go without). */
462
+ constructor(effects: Map<string, ToolEffect>, axes?: RosterAxisTables);
447
463
  /** The mount array as a READ face. The order is the wire order. */
448
464
  get tools(): readonly AgentTool[];
449
465
  get length(): number;
450
466
  metaOf(tool: AgentTool): RosterMountMeta | undefined;
451
467
  /** Mount one instance at the tail. An engine-lane instance that DECLARES an effect writes it into the leg's effect
452
468
  * map — the later mount under a name is the one the harness dispatches to (`snapshot`'s yield rule), so its
453
- * declaration is the map's truth for that name; a pre-mount registration (the hand band, the plan pair, an
454
- * MCP axis) stands only for names no mounted instance declares. The `scenario` lane never writes the map: its
469
+ * declaration is the map's truth for that name — an engine tool's effect reaches the map from its own declaration, here at its
470
+ * mount (nothing writes it by name beforehand; a protocol axis fills a name no mounted instance declares, and the gate's
471
+ * post-assembly sync a kept instance the map still lacks). The `scenario` lane never writes the map: its
455
472
  * declarations were latched by the safety scan BEFORE any mount (on the kept instance of each name, on the
456
473
  * synchronous doors+scan stretch — a microtask cannot flip them), so a scenario mount, kept or dropped, leaves
457
- * the map as the scan made it; an engine lane's mount under that name still displaces it. */
474
+ * the map as the scan made it; an engine lane's mount under that name still displaces it, and the aliases the displaced
475
+ * instance answered to move to the member they now resolve to (`#resettleAliases`). */
458
476
  mount(tool: AgentTool, meta: RosterMountMeta): void;
459
477
  /** An ENGINE-INJECTED instance the harness list carries outside the mount array (the ToolSearch lane): a roster
460
478
  * row — the model can call it — never a mount-array element (the disclosure lane owns that list and appends the
@@ -497,6 +515,20 @@ export declare function rosterCardId(source: RosterSource, toolName: string, pee
497
515
  /** The card namespace a `TaskSpec.tools` entry will own once mounted — the same mint the row makes, so the prepare
498
516
  * door and the roster row cannot disagree about which namespace a caller card must live under. */
499
517
  export declare function callerCardIdOf(tool: Pick<AgentTool, "name" | "parameters">): string;
518
+ /** The protocol segment of a mounted protocol tool's name (`mcp__srv__write_file` ⇒ `write_file`): the name itself when no protocol owns it.
519
+ * ONE reading, asked by the row's mint below and by the judge that holds an MCP face's cards to the namespace that row will own. */
520
+ export declare function protocolToolOf(toolName: string): string;
521
+ /** The refusal of a `boundaryLegs` declaration that is not one — the ONE sentence the roster's declaration door and the row mint both
522
+ * throw (`config.tool_face_invalid`, the code a caller tool's bad face has). */
523
+ export declare function boundaryLegsRefusal(toolName: unknown): Error & {
524
+ code: string;
525
+ };
526
+ /** The judge of a caller roster's `boundaryLegs` where the roster enters, every entry the caller handed, mounted or not: the config-door
527
+ * phase over the snapshot's faces and a resume's screen before its claim over the entries it is handed (one function, so the row stays
528
+ * pending). A declaration that is not one is refused. Not judged here — the snapshot reads it, and only by running the caller's code:
529
+ * an entry or a list of another prototype (its prototype chain answers), a Proxy, an accessor (or a non-enumerable member the receipt
530
+ * does not take) as the name, the declaration or a slot of the list. The row mint judges every other source. */
531
+ export declare function assertToolBoundaryLegs(tools: unknown): void;
500
532
  /** One row (design/388 §2.5). Every axis is read off the instance or the leg facts; nothing off a name table. */
501
533
  export declare function mintRosterEntry(tool: AgentTool, meta: RosterMountMeta, facts: RosterSnapshotFacts): ToolRosterEntry;
502
534
  /**
@@ -533,20 +565,6 @@ export declare function ruleFacesFromRoster(roster: ToolRoster): (toolName: stri
533
565
  } | undefined;
534
566
  /** The delta between two snapshots (design/388 §2.5): the whole `next` roster plus the name summary. */
535
567
  export declare function diffToolRoster(prev: ToolRoster, next: ToolRoster): ToolRosterDelta;
536
- /**
537
- * The resume-side identity judgement (design/388 §2.5 boundary ②, B18/B19): a parked action carrying the
538
- * four identity members is redeemed only against a same-name row whose four members ALL match; a row
539
- * whose contract / shape / revision / source-peer moved is a replacement wearing the name, and the
540
- * resume refuses it (`resume.tool_contract_mismatch`, same family and disposition as
541
- * `resume.tool_unavailable`). A parked action minted BEFORE the identity members existed (all four absent)
542
- * follows the name-only path and is never widened by their absence.
543
- */
544
- export declare function judgeParkedToolIdentity(pending: {
545
- capabilityId?: string;
546
- contractId?: string;
547
- shapeDigest?: string;
548
- implementationRevision?: string;
549
- }, row: ToolRosterEntry | undefined): "match" | "mismatch" | "name_only" | "absent";
550
568
  /** The card-namespace check a CALLER declaration must pass at prepare (design/388 §2.2 ④): a `resultCards`
551
569
  * entry must live under the tool's own `cardId`; naming an engine card (`builtin:*`, or a bare engine card
552
570
  * type) is refused. Returns the offending entry, or undefined. */
@@ -1,5 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
+ import { types } from "node:util";
2
3
  import { Type } from "typebox";
4
+ import { findToolByName } from "../internal/llm.js";
3
5
  import { getToolContract } from "../prompt-assembly/tool-catalog.js";
4
6
  import { protocolOf } from "./protocol-table.js";
5
7
  import { describeToolCatalog } from "./tool-registry.js";
@@ -7,6 +9,8 @@ import { RENDER_HINT_ACTIVITY_MAX_CHARS, RENDER_HINT_MAX_CHARS, RENDER_HINT_MAX_
7
9
  import { ASK_USER_QUESTION_TOOL_NAME } from "./ask-question.js";
8
10
  import { PRESENT_PLAN_TOOL_NAME } from "./present-plan-tool.js";
9
11
  import { wireOf } from "./tool-name-readings.js";
12
+ import { BOUNDARY_LEGS, rosterBoundaryLegs } from "./cross-boundary-taint.js";
13
+ import { isPlainRecord, receive } from "./mods/kernel/receive.js";
10
14
  function lit(values) {
11
15
  return Type.Union(values.map((v) => Type.Literal(v)));
12
16
  }
@@ -46,6 +50,7 @@ export const ToolRosterEntry = Type.Object({
46
50
  contentOrigin: lit(["local", "execution", "external"]),
47
51
  contentOriginProvenance: lit(["declared", "server", "exempted", "default"]),
48
52
  family: lit(TOOL_FAMILIES),
53
+ boundaryLegs: Type.Union([Type.Literal("per_call"), Type.Array(lit(BOUNDARY_LEGS), { uniqueItems: true })]),
49
54
  pathTarget: Type.Optional(Type.Object({
50
55
  param: Key,
51
56
  aliases: Type.Array(Key),
@@ -122,9 +127,7 @@ export function parkedToolIdentity(roster, toolName) {
122
127
  if (roster === undefined)
123
128
  return {};
124
129
  const e = rosterEntryNamed(roster, toolName);
125
- if (e === undefined)
126
- return {};
127
- return { capabilityId: e.capabilityId, contractId: e.contract.contractId, shapeDigest: e.shapeDigest, implementationRevision: e.contract.implementationRevision, rosterDigest: roster.digest };
130
+ return e === undefined ? {} : toolIdentityOf(e);
128
131
  }
129
132
  export function parkedToolFace(roster, req) {
130
133
  if (roster === undefined)
@@ -301,6 +304,18 @@ export function survivorsByName(mounted) {
301
304
  return winner;
302
305
  }
303
306
  export const faceOf = (t) => t;
307
+ const setOrClear = (m, key, value) => {
308
+ if (value === undefined)
309
+ m.delete(key);
310
+ else
311
+ m.set(key, value);
312
+ };
313
+ const memberIf = (s, key, member) => {
314
+ if (member)
315
+ s.add(key);
316
+ else
317
+ s.delete(key);
318
+ };
304
319
  function configError(code, message) {
305
320
  const e = new Error(message);
306
321
  e.code = code;
@@ -311,8 +326,10 @@ export class RosterBuilder {
311
326
  #engineLane = [];
312
327
  #meta = new WeakMap();
313
328
  #effects;
314
- constructor(effects) {
329
+ #axes;
330
+ constructor(effects, axes) {
315
331
  this.#effects = effects;
332
+ this.#axes = axes;
316
333
  }
317
334
  get tools() {
318
335
  return this.#tools;
@@ -324,12 +341,18 @@ export class RosterBuilder {
324
341
  return this.#meta.get(tool);
325
342
  }
326
343
  mount(tool, meta) {
344
+ const displaced = this.#survivorNamed(tool.name);
327
345
  this.#admit(tool, meta);
328
346
  this.#tools.push(tool);
347
+ if (meta.mountedBy !== "scenario")
348
+ this.#resettleAliases([...(displaced?.aliases ?? []), ...(tool.aliases ?? [])]);
329
349
  }
330
350
  mountEngineLane(tool, meta) {
351
+ const displaced = this.#survivorNamed(tool.name);
331
352
  this.#admit(tool, meta);
332
353
  this.#engineLane.push(tool);
354
+ if (meta.mountedBy !== "scenario")
355
+ this.#resettleAliases([...(displaced?.aliases ?? []), ...(tool.aliases ?? [])]);
333
356
  }
334
357
  #admit(tool, meta) {
335
358
  if ((meta.source === "mcp" || meta.source === "a2a" || meta.source === "external") !== (meta.origin !== undefined)) {
@@ -342,6 +365,32 @@ export class RosterBuilder {
342
365
  if (eff !== undefined && meta.mountedBy !== "scenario")
343
366
  this.#effects.set(tool.name, eff);
344
367
  }
368
+ #survivorNamed(name) {
369
+ return survivorsByName([...this.#tools, ...this.#engineLane]).get(name);
370
+ }
371
+ #resettleAliases(aliases) {
372
+ if (aliases.length === 0)
373
+ return;
374
+ const live = [...survivorsByName([...this.#tools, ...this.#engineLane]).values()];
375
+ for (const a of new Set(aliases)) {
376
+ const owner = findToolByName(live, a);
377
+ if (owner?.name === a)
378
+ continue;
379
+ this.#repoint(a, owner?.name);
380
+ }
381
+ }
382
+ #repoint(key, from) {
383
+ const pick = (m) => (from === undefined ? undefined : m.get(from));
384
+ const marks = this.#axes === undefined ? undefined : pick(this.#axes.toolMarkSources);
385
+ setOrClear(this.#effects, key, pick(this.#effects));
386
+ if (this.#axes === undefined)
387
+ return;
388
+ setOrClear(this.#axes.irreversibilityTier, key, pick(this.#axes.irreversibilityTier));
389
+ setOrClear(this.#axes.reversibilityProbes, key, pick(this.#axes.reversibilityProbes));
390
+ setOrClear(this.#axes.toolMarkSources, key, marks === undefined ? undefined : new Set(marks));
391
+ memberIf(this.#axes.irreversibleTools, key, from !== undefined && this.#axes.irreversibleTools.has(from));
392
+ memberIf(this.#axes.egressTools, key, from !== undefined && this.#axes.egressTools.has(from));
393
+ }
345
394
  mountAll(tools, meta) {
346
395
  for (const t of tools)
347
396
  this.mount(t, meta);
@@ -355,6 +404,10 @@ export class RosterBuilder {
355
404
  removed.unshift(t);
356
405
  }
357
406
  }
407
+ for (const t of removed)
408
+ if (this.#survivorNamed(t.name) === undefined)
409
+ this.#effects.delete(t.name);
410
+ this.#resettleAliases(removed.flatMap((t) => t.aliases ?? []));
358
411
  return removed;
359
412
  }
360
413
  replaceAt(index, tool) {
@@ -449,18 +502,50 @@ export function callerCardIdOf(tool) {
449
502
  const t = tool;
450
503
  return rosterCardId(callerMountSource(t), t.name, null, t.name, getToolContract(t, "caller").contractId);
451
504
  }
505
+ export function protocolToolOf(toolName) {
506
+ return protocolOf(toolName)?.parse(toolName)?.tool ?? toolName;
507
+ }
508
+ export function boundaryLegsRefusal(toolName) {
509
+ const named = typeof toolName === "string" ? JSON.stringify(toolName.slice(0, 128)) : `(a ${typeof toolName} name)`;
510
+ return configError("config.tool_face_invalid", `tool ${named}: its boundaryLegs declaration is neither "per_call" nor a list of ${BOUNDARY_LEGS.join(" | ")}`);
511
+ }
512
+ const ROSTER_LEGS_RECEIPT = Object.freeze({ accessors: "refuse", depth: 3, others: "as-is", contain: true, isProxy: (v) => types.isProxy(v) });
513
+ const SLOT = /^(?:0|[1-9]\d*)$/;
514
+ export function assertToolBoundaryLegs(tools) {
515
+ const got = receive(tools, ROSTER_LEGS_RECEIPT);
516
+ if (!got.ok || !Array.isArray(got.value))
517
+ return;
518
+ const unread = new Set();
519
+ for (const f of got.faults ?? []) {
520
+ const [at, member, slot] = f.path;
521
+ if (at !== undefined && (member === "name" || (member === "boundaryLegs" && slot !== undefined && SLOT.test(slot))))
522
+ unread.add(at);
523
+ }
524
+ got.value.forEach((t, i) => {
525
+ if (!isPlainRecord(t) || unread.has(String(i)))
526
+ return;
527
+ const legs = t["boundaryLegs"];
528
+ if (Array.isArray(legs) && Object.getPrototypeOf(legs) !== Array.prototype)
529
+ return;
530
+ if (rosterBoundaryLegs("caller", legs, "write") === undefined)
531
+ throw boundaryLegsRefusal(t["name"]);
532
+ });
533
+ }
452
534
  export function mintRosterEntry(tool, meta, facts) {
453
535
  const face = faceOf(tool);
454
536
  const fallbackOrigin = meta.source === "builtin" ? "core" : meta.source === "synthetic" ? "synthetic" : meta.source === "mcp" || meta.source === "a2a" ? meta.source : "caller";
455
537
  const d = getToolContract(tool, fallbackOrigin);
456
538
  const peer = meta.origin?.peer ?? null;
457
- const protocolTool = peer !== null ? protocolOf(tool.name)?.parse(tool.name)?.tool ?? tool.name : tool.name;
539
+ const protocolTool = peer !== null ? protocolToolOf(tool.name) : tool.name;
458
540
  const cardId = rosterCardId(meta.source, tool.name, peer, protocolTool, d.contractId);
459
541
  const { keys: inputKeys } = inputKeysOf(tool.parameters);
460
542
  const declaredEffect = face.effect;
461
543
  const foldedEffect = facts.effects.get(tool.name);
462
544
  const effect = declaredEffect ?? foldedEffect ?? "write";
463
545
  const effectProvenance = declaredEffect !== undefined ? (meta.source === "synthetic" ? "synthetic" : "declared") : foldedEffect !== undefined && (meta.source === "mcp" || meta.source === "a2a") ? "caller" : "default";
546
+ const boundaryLegs = rosterBoundaryLegs(meta.source, face.boundaryLegs, effect);
547
+ if (boundaryLegs === undefined)
548
+ throw boundaryLegsRefusal(tool.name);
464
549
  const egress = face.egress === true || facts.egress.has(tool.name);
465
550
  const egressProvenance = face.egress === true ? "declared" : egress ? "caller" : "default";
466
551
  const irreversibility = face.irreversibility ?? facts.irreversibilityTier.get(tool.name);
@@ -505,6 +590,7 @@ export function mintRosterEntry(tool, meta, facts) {
505
590
  contentOrigin,
506
591
  contentOriginProvenance,
507
592
  family: face.family !== undefined && TOOL_FAMILIES.includes(face.family) ? face.family : "other",
593
+ boundaryLegs: boundaryLegs === "per_call" ? boundaryLegs : [...boundaryLegs],
508
594
  ...(pathTarget !== undefined
509
595
  ? {
510
596
  pathTarget: {
@@ -608,15 +694,6 @@ export function diffToolRoster(prev, next) {
608
694
  removed.push(name);
609
695
  return { fromDigest: prev.digest, roster: next, summary: { added, removed, changed } };
610
696
  }
611
- export function judgeParkedToolIdentity(pending, row) {
612
- const carried = pending.capabilityId !== undefined || pending.contractId !== undefined || pending.shapeDigest !== undefined || pending.implementationRevision !== undefined;
613
- if (row === undefined)
614
- return "absent";
615
- if (!carried)
616
- return "name_only";
617
- const id = toolIdentityOf(row);
618
- return pending.capabilityId === id.capabilityId && pending.contractId === id.contractId && pending.shapeDigest === id.shapeDigest && pending.implementationRevision === id.implementationRevision ? "match" : "mismatch";
619
- }
620
697
  export function offendingResultCard(hints, cardId, engineCards) {
621
698
  for (const c of hints?.resultCards ?? []) {
622
699
  if (typeof c !== "string")
@@ -5,9 +5,9 @@ export const TOOL_SPEC_MEMBERS = Object.keys({
5
5
  contentOrigin: true, offload: true, offloadThresholdChars: true, defer: true, alwaysLoad: true, prepareArguments: true,
6
6
  validateInput: true, approvalPreview: true, family: true, pathTarget: true, ruleFace: true, renderHints: true, compactable: true,
7
7
  budgetExempt: true, modelGate: true, contract: true, agentListing: true, agentModels: true, agentToolFaces: true,
8
- agentToolLevelAllowTools: true, agentToolLevelDenyTools: true, agentToolPool: true, agentToolInheritsMcp: true, withAgents: true,
8
+ agentToolLevelAllowTools: true, agentToolLevelDenyTools: true, agentToolPool: true, agentToolInheritsMcp: true, withAgents: true, boundaryLegs: true, boundaryLegsFor: true,
9
9
  });
10
- const FUNCTION_MEMBERS = new Set(["execute", "prepareArguments", "validateInput", "approvalPreview", "reversibilityProbe", "isConcurrencySafe", "withAgents"]);
10
+ const FUNCTION_MEMBERS = new Set(["execute", "prepareArguments", "validateInput", "approvalPreview", "reversibilityProbe", "isConcurrencySafe", "withAgents", "boundaryLegsFor"]);
11
11
  const NAME_LIST_MEMBERS = new Set(["aliases", "agentModels", "agentToolLevelAllowTools", "agentToolLevelDenyTools"]);
12
12
  const FACE_LIST_KEYS = new Set(["allowTools", "denyTools"]);
13
13
  const FACE_PLACES = { elementKeys: FACE_LIST_KEYS };
@@ -306,10 +306,10 @@ export interface ToolSpec<TParams extends TSchema = TSchema> {
306
306
  egress?: true;
307
307
  /**
308
308
  * The tool's PERMISSION SEAT. `"passthrough"`: the tool states no permission of its own — under an asking permission mode
309
- * (`default` / `acceptEdits` / `plan` / `auto` / `dontAsk`) a call to it runs only when some layer STATES an allow (an allow
310
- * rule or allow-list entry naming it, an approver's yes, a PreToolUse hook's own `allow`); a policy fold in which nobody
311
- * stated it ("no objection") gets the mode's question (class `unstated`, origin `policy`), which dontAsk refuses, auto's
312
- * classifier may answer, and `bypassPermissions` never raises. The MCP and A2A materializers set it on a remote peer's
309
+ * (`default` / `acceptEdits` / `plan` / `auto` / `dontAsk`) a policy fold's allow is "no objection" and gets the mode's
310
+ * question (class `unstated`, origin `policy`) unless an approver's yes was given for the bytes that run; the allow layer
311
+ * answers it (an allow entry in `RunnerDeps.allowedTools` / `TaskSpec.allowedTools`, a stored row, a skill's list, a PreToolUse
312
+ * hook's own `allow`), dontAsk refuses it, auto's classifier may answer it, and `bypassPermissions` never raises it. The MCP and A2A materializers set it on a remote peer's
313
313
  * tool. Absent: the tool answers for itself (the engine's own stations and the deployment's declarations). Tightening only —
314
314
  * it can make a tool ask, never let one run.
315
315
  */
@@ -524,6 +524,18 @@ export interface ToolSpec<TParams extends TSchema = TSchema> {
524
524
  * missing declaration rather than defaulting it).
525
525
  */
526
526
  family?: ToolFamily;
527
+ /**
528
+ * The legs a call of this tool travels across the device boundary — the engine's own definitions declare it on their catalog face
529
+ * ({@link import("./tool-face.js").ToolFaceDeclaration.boundaryLegs}, required there), and this member carries it onto the mounted
530
+ * instance. Read only on a roster row of source `builtin` / `synthetic` (a `TaskSpec.tools` entry is one only when it carries a core
531
+ * contract); any other tool's row states legs from its `effect`. A value that is neither `"per_call"` nor a list of legs is refused
532
+ * (`config.tool_face_invalid`): for a `TaskSpec.tools` entry where the roster enters, whether or not the entry mounts.
533
+ */
534
+ boundaryLegs?: import("./cross-boundary-taint.js").BoundaryLegsDeclaration;
535
+ /** The per-call half of a `"per_call"` {@link boundaryLegs} declaration, written beside the code that dispatches the call: the legs one
536
+ * call travels, given its arguments as the tool reads them ({@link import("./cross-boundary-taint.js").BoundaryLegsFor}). Read only for
537
+ * a row that declares `"per_call"`; such a row with no resolver, or a resolver that throws, reads as every leg off-target. */
538
+ boundaryLegsFor?: import("./cross-boundary-taint.js").BoundaryLegsFor;
527
539
  /**
528
540
  * design/388 §2.2 ② — the parameter that names this tool's filesystem TARGET and how it is touched.
529
541
  * Present ⇔ the tool can be confirmed BY PATH: write confinement (`allowDirs`, the fs-write gate,
@@ -1156,6 +1168,10 @@ export interface ToolExecuteContext {
1156
1168
  * tool runs outside a Runner task.
1157
1169
  */
1158
1170
  inheritedGateForChildren?: () => import("./runner/contracts.js").InheritedGate;
1171
+ /** The run tree's cross-boundary token ({@link import("./runner/contracts.js").RunInternals.crossBoundaryTree}) — Runner-filled, opaque
1172
+ * and frozen; a delegation or workflow tool hands this SAME token into its child's trusted internals (and records it on a background
1173
+ * handle), so the child joins the tree and a bit any leg sets is read by every other. No tool reads or writes the state through it. */
1174
+ crossBoundaryTreeForChildren?: import("./cross-boundary-taint.js").CrossBoundaryTreeToken;
1159
1175
  /**
1160
1176
  * design/180 half A — the delegation runtime-provenance ARMING face. Runner-filled; a delegation
1161
1177
  * tool calls it at spawn time: a non-undefined return means this (parent) run is armed (it mounts
@@ -95,6 +95,8 @@ function buildProduct(spec, owner, options) {
95
95
  ...(spec.alwaysLoad !== undefined ? { alwaysLoad: spec.alwaysLoad } : {}),
96
96
  ...(spec.prepareArguments ? { prepareArguments: spec.prepareArguments } : {}),
97
97
  ...(spec.family !== undefined ? { family: spec.family } : {}),
98
+ ...(spec.boundaryLegs !== undefined ? { boundaryLegs: spec.boundaryLegs } : {}),
99
+ ...(spec.boundaryLegsFor !== undefined ? { boundaryLegsFor: spec.boundaryLegsFor } : {}),
98
100
  ...(spec.pathTarget !== undefined ? { pathTarget: spec.pathTarget } : {}),
99
101
  ...(spec.ruleFace !== undefined ? { ruleFace: spec.ruleFace } : {}),
100
102
  ...(spec.renderHints !== undefined ? { renderHints: spec.renderHints } : {}),
@@ -332,8 +332,11 @@ export type TraceEvent = {
332
332
  * `tool.call.turn`. Lets a bridge put a `chat` span and the `execute_tool` spans of the tools that
333
333
  * call requested under one parent without re-deriving turn boundaries from event order.
334
334
  *
335
- * ABSENT on the compaction summarizer's own call: that call fires from the turn-boundary hook and
336
- * is not a loop turn, so any number here would be a different measurement wearing the same name.
335
+ * ABSENT on every provider call fired beside the loop — the compaction summarizer (it fires from the turn-boundary hook), the
336
+ * classifier gate, a mod's own call: none is a loop turn, so any number here would be a different measurement wearing the same
337
+ * name. So `turn` present ⇔ the call is the main loop's own; absent ⇔ a call outside the loop. Those rows also carry
338
+ * `latencyMs: 0` (no wall time is measured for them), so a latency average taken over the rows that carry a `turn` is not diluted
339
+ * by them.
337
340
  */
338
341
  turn?: number;
339
342
  /** Normalized prompt tokens that MISSED the cache (Anthropic-protocol `input_tokens`
@@ -555,6 +558,25 @@ export type TraceEvent = {
555
558
  * joined — provider failure, refused snapshot, rollback refusal, missing durable persistence. */
556
559
  message: string;
557
560
  ts: number;
561
+ } | {
562
+ /**
563
+ * The device lane's cross-boundary TAINT on a tree that executes on a target device: once when a leg is prepared on an armed
564
+ * tree (`phase: "prepare"`; `"resume"` on a durable resume's leg), and once when this leg first lets an off-target read into the
565
+ * tree (`"observed"`, with the call). A tree that never armed emits none. Metadata only.
566
+ */
567
+ kind: "permission.cross_boundary_taint";
568
+ version: 1;
569
+ taskId: string;
570
+ runId: string;
571
+ phase: "prepare" | "resume" | "observed";
572
+ /** The tree's taint bits after the event. */
573
+ taint: readonly import("./cross-boundary-taint.js").CrossBoundaryTaintBit[];
574
+ /** The tree has run under the `multi_tenant` posture since it armed. */
575
+ governed: boolean;
576
+ /** The call that let the off-target read in (`observed` only). */
577
+ toolName?: string;
578
+ toolCallId?: string;
579
+ ts: number;
558
580
  } | {
559
581
  /**
560
582
  * F-012 L2 — a pending ask was AUTO-ADMITTED by the sandbox-admission leg: the deployment's
@@ -19,7 +19,7 @@ export type { ToolEffect, ToolContentOrigin, ReversibilityVerdict, ReadRootGrant
19
19
  export type { BeforeWriteRequest, BeforeWriteResult, BeforeWriteHook, TrackEditRequest, TrackEditResult, TrackFileEditHook, FileEditedNotice, FileEditedHook, HandsBandOptions } from "./hands-band.js";
20
20
  export type { AgentDefinition, SkillSpec } from "./agent-definition.js";
21
21
  export type { McpServerSpec, A2aServerSpec, McpElicitRequest, McpElicitResponse, OnElicit, McpRevocationLedger, McpMountIdentity, DeclarationFault, DeclarationPathRule, DeclarationRead, ToolAxesRead, ToolAxesSurface } from "./mcp-server-spec.js";
22
- export { DECLARATION_PATH_RULES, declarationFaultOf, declarerWord, readMcpToolAxes, readToolAxes, receiveDeclaration, toolAxesRefusal } from "./mcp-server-spec.js";
22
+ export { DECLARATION_PATH_RULES, NO_DECLARATION_PATH_RULES, dataKind, declarationFaultOf, declarerWord, perToolEmptyName, perToolEntryRefusal, quotedName, readMcpToolAxes, readToolAxes, receiveDeclaration, receivePerToolDeclaration, toolAxesRefusal } from "./mcp-server-spec.js";
23
23
  export type { TaskLimits } from "./task-limits.js";
24
24
  export type { TaskStatus, RemoteEnvFailureNote, EffectiveMemoryScopes, TaskResult, HumanReviewGateRow } from "./task-result.js";
25
25
  export type { TerminalCause, PausedCause } from "./terminal-cause.js";
@@ -30,6 +30,6 @@ export { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource } from "./runtime-caps.js"
30
30
  export type { BackgroundChildEvent, BackgroundChildRow, DelegationLifecycleEvent } from "./delegation-frames.js";
31
31
  export { __resetMalformedDelegationSeatAnnouncement, backgroundChildTerminal, deliverDelegationLifecycle, emitsChildRowFrames, taskStartedFrame } from "./delegation-frames.js";
32
32
  export type { ProjectMemoryLoad, ResumePreflightInfo, ResumePreflightVerdict, RunnerDeps } from "./runner-deps.js";
33
- export type { EngineNotice, OwedUserInputAccount, UserInputUndrainedCause } from "./engine-notice.js";
33
+ export type { ApprovalCarryRefusal, EngineNotice, OwedUserInputAccount, UserInputUndrainedCause } from "./engine-notice.js";
34
34
  export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, owedUserInputCounts, parentLegIds, settleOwedUserInput, undrainedUserInputNotices, USER_INPUT_UNDRAINED_CAUSES, USER_INPUT_UNDRAINED_FAMILIES } from "./engine-notice.js";
35
35
  export type { AgentTool, Model, StreamFn, ThinkingLevel, ToolInputValidationContext, ToolInputVerdict };
@@ -1,5 +1,5 @@
1
1
  export { MODEL_ROLES } from "./model-seat.js";
2
- export { DECLARATION_PATH_RULES, declarationFaultOf, declarerWord, readMcpToolAxes, readToolAxes, receiveDeclaration, toolAxesRefusal } from "./mcp-server-spec.js";
2
+ export { DECLARATION_PATH_RULES, NO_DECLARATION_PATH_RULES, dataKind, declarationFaultOf, declarerWord, perToolEmptyName, perToolEntryRefusal, quotedName, readMcpToolAxes, readToolAxes, receiveDeclaration, receivePerToolDeclaration, toolAxesRefusal } from "./mcp-server-spec.js";
3
3
  export { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource } from "./runtime-caps.js";
4
4
  export { __resetMalformedDelegationSeatAnnouncement, backgroundChildTerminal, deliverDelegationLifecycle, emitsChildRowFrames, taskStartedFrame } from "./delegation-frames.js";
5
5
  export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, owedUserInputCounts, parentLegIds, settleOwedUserInput, undrainedUserInputNotices, USER_INPUT_UNDRAINED_CAUSES, USER_INPUT_UNDRAINED_FAMILIES } from "./engine-notice.js";
@@ -238,6 +238,13 @@ export declare function defuseExactMarkInSegments(segments: readonly string[], m
238
238
  * {@link FENCED_LANE_ENVELOPE_TAGS}) and at each envelope's own mint site (the registry's guard column).
239
239
  */
240
240
  export declare function sanitizeUntrustedText(text: string, extraTags?: string[]): string;
241
+ /**
242
+ * THE sentence every arm speaks when it refuses a person's text because {@link sanitizeUntrustedText} would change it — the engine's own
243
+ * sentinel tag, opening (with or without attributes) or closing, which the judge refuses alike. `subject` names what carried it. Produced
244
+ * once, here beside the judge, so the sentence and the rule cannot drift apart by arm (the decide's deny / reject `reason`, a plan's
245
+ * `editedPlan`, an allow's `feedback`, a trusted live steer).
246
+ */
247
+ export declare function sentinelTagRefusal(subject: string): string;
241
248
  /**
242
249
  * [c209-C] 收口 C1/C2 (metadata-driven, replaces the shape-anchored
243
250
  * `stripLeadingSystemReminders`) — cut the engine-prepended prefix off a user-lane message using the
@@ -66,14 +66,6 @@ export const ENGINE_ENVELOPES = Object.freeze([
66
66
  fenced: true,
67
67
  disclosed: true,
68
68
  },
69
- {
70
- tag: "working-file",
71
- kind: "framing",
72
- mint: "core/auto-compaction.ts (post-compaction working-file snapshot)",
73
- guard: "sanitizeUntrustedText(WORKING_FILE_BREAKOUT_TAGS) + escapeWorkingFilePathAttr",
74
- fenced: false,
75
- disclosed: false,
76
- },
77
69
  {
78
70
  tag: "summary",
79
71
  kind: "framing",
@@ -482,6 +474,9 @@ export function defuseExactMarkInSegments(segments, mark) {
482
474
  export function sanitizeUntrustedText(text, extraTags = []) {
483
475
  return text.replace(breakoutRe(extraTags), (m) => ESCAPED_LT + m.slice(1));
484
476
  }
477
+ export function sentinelTagRefusal(subject) {
478
+ return `${subject} must not contain a <system-reminder> tag (opening or closing)`;
479
+ }
485
480
  export function cutEnginePrefix(text, enginePrefixChars) {
486
481
  if (typeof enginePrefixChars === "number" &&
487
482
  Number.isInteger(enginePrefixChars) &&
@@ -27,6 +27,8 @@ import type { HumanInputAttachment } from "./task-event.js";
27
27
  import { type ModelFileReader } from "../tools/fs/fs-read.js";
28
28
  import type { SyntheticReadJudge } from "./synthetic-read-verdict.js";
29
29
  import { type ReminderDisclosureCounts } from "./reminder-disclosure.js";
30
+ import { type AgentMessage } from "../internal/harness.js";
31
+ import type { ReadEntry } from "../tools/fs/safety.js";
30
32
  import type { ContextWindowOverlay } from "./context-settings.js";
31
33
  /** At most this many attachments on one objective. */
32
34
  export declare const USER_ATTACHMENTS_MAX = 20;
@@ -116,6 +118,13 @@ export declare function userAttachmentDeclarations(screened: readonly ScreenedUs
116
118
  * run on the captured values before any attachment is read or delivered. Throws `config.user_attachment_invalid`.
117
119
  */
118
120
  export declare function admitUserAttachmentImages(screened: readonly ScreenedUserAttachment[]): Promise<ReadonlyMap<number, ImageContent>>;
121
+ /** The transmission an attachment frame is (the read-state entry its delivery records carries it): when the frame leaves
122
+ * the request — a trim drops it — the entry retires with it. One spelling for the mint and every remover. */
123
+ export declare function attachmentTransmissionId(a: Pick<UserMessageAttachment, "inputId" | "ordinal">): string;
124
+ /** The transmissions a request-view drop took out: those of `view`'s messages that no message of `kept` still carries (a
125
+ * reused call id kept elsewhere is not gone). The guard trim retires the read-state entries they delivered (so the next
126
+ * Read sends the contents again); a later read of the same file is a transmission of its own and stays. */
127
+ export declare function droppedTransmissions(view: ReadonlyArray<AgentMessage>, kept: ReadonlyArray<AgentMessage>): string[];
119
128
  /** One minted frame: the trailing message's seed, and the commit its delivery owes (absent ⇒ nothing to commit). */
120
129
  export interface MintedAttachmentFrame {
121
130
  readonly seed: {
@@ -160,6 +169,40 @@ export declare function readObjectiveAttachments(prepared: Pick<import("./runner
160
169
  frames: MintedAttachmentFrame[];
161
170
  chars: number;
162
171
  }>;
172
+ /** What a compaction's restore reads with: the leg's one file reader (the Read tool's) and the run's synthetic-Read judge,
173
+ * and where a file it does not deliver is reported (a restored file's outcome has no wire face of its own). */
174
+ export interface CompactionRestoreSeat {
175
+ readonly reader: ModelFileReader;
176
+ readonly judge: SyntheticReadJudge;
177
+ readonly cwd: () => string | undefined;
178
+ readonly mark: string | undefined;
179
+ readonly counts: ReminderDisclosureCounts | undefined;
180
+ readonly signal: AbortSignal;
181
+ readonly report: (path: string, reason: string) => void;
182
+ }
183
+ /** One restore: the files to re-read (most recent first) and its bounds. */
184
+ export interface CompactionRestoreRequest {
185
+ readonly candidates: readonly string[];
186
+ readonly maxFiles: number;
187
+ /** What one file may deliver, in its own characters (before the Read's line numbers); an image the estimator's fixed
188
+ * weight × `charsPerToken`. A file over it, or whose frame would send more of its contents (line numbers included)
189
+ * than what is left of `totalChars`, is named, not sent. */
190
+ readonly perFileChars: number;
191
+ readonly totalChars: number;
192
+ readonly charsPerToken: number;
193
+ /** The read state of the context the summary leaves (`readFileStateAfterSummary`): what the reads dedup against. */
194
+ readonly dedupAgainst: ReadonlyMap<string, ReadEntry>;
195
+ }
196
+ /** The synthetic-Read judge's probe face for the restore (probe id `compaction-restore-visibility-probe`). */
197
+ export declare const COMPACTION_RESTORE_PROBE_FACE = "compaction-restore";
198
+ /**
199
+ * Re-read the files a compaction restores and mint their frames — each a Read on the model's behalf ({@link readRestored}),
200
+ * deduplicated against the context the summary leaves, so a file the summary takes out of view is never answered
201
+ * "unchanged"; the frame is the attachment seat's own. A file the context still holds is skipped; a file over
202
+ * its share is a frame that names it and says it is too large (no part of its contents); a refused file is a frame with
203
+ * the reason. Side-effect free toward the run: each frame carries the commit its delivery owes.
204
+ */
205
+ export declare function readCompactionRestore(seat: CompactionRestoreSeat, req: CompactionRestoreRequest): Promise<MintedAttachmentFrame[]>;
163
206
  /** The last attachment frame of the group the message `id` opens, or `id` when it has none (a `resumeAt` that keeps the
164
207
  * message keeps its group). The frames of one input are appended one after another under its message (each the child of
165
208
  * the one before), all carrying its input id. */