@sema-agent/core 7.7.0 → 7.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/agents/agent-transcript-tool.js +2 -2
  3. package/dist/agents/list-agents-tool.js +2 -3
  4. package/dist/agents/observer.js +2 -2
  5. package/dist/agents/send-message-tool.js +2 -1
  6. package/dist/agents/subagent-steps.js +5 -4
  7. package/dist/agents/subagent.js +2 -3
  8. package/dist/core/ask-question.js +2 -2
  9. package/dist/core/checkpoint-store.d.ts +38 -4
  10. package/dist/core/context-edit.d.ts +0 -22
  11. package/dist/core/context-edit.js +2 -11
  12. package/dist/core/engine-notice.d.ts +6 -0
  13. package/dist/core/fs-write-gate-policy.d.ts +7 -1
  14. package/dist/core/fs-write-gate-policy.js +7 -7
  15. package/dist/core/gate-fold.js +2 -2
  16. package/dist/core/gate-lanes.js +4 -3
  17. package/dist/core/governance-codes.d.ts +1 -1
  18. package/dist/core/governance-codes.js +6 -0
  19. package/dist/core/hooks.d.ts +8 -2
  20. package/dist/core/hooks.js +1 -1
  21. package/dist/core/lsp.js +2 -2
  22. package/dist/core/mcp-server-spec.d.ts +17 -0
  23. package/dist/core/mcp.js +3 -1
  24. package/dist/core/memory-engine/tools.js +4 -12
  25. package/dist/core/permission-rules.d.ts +32 -2
  26. package/dist/core/permission-rules.js +43 -37
  27. package/dist/core/present-plan-tool.js +3 -4
  28. package/dist/core/runner/active-skill-scope.js +7 -4
  29. package/dist/core/runner/compaction-call-options.d.ts +1 -1
  30. package/dist/core/runner/contracts.d.ts +27 -3
  31. package/dist/core/runner/prepare-ask-lane.d.ts +1 -1
  32. package/dist/core/runner/prepare-caps-and-workflow.d.ts +5 -4
  33. package/dist/core/runner/prepare-caps-and-workflow.js +16 -11
  34. package/dist/core/runner/prepare-defer-classify.d.ts +5 -26
  35. package/dist/core/runner/prepare-defer-classify.js +10 -10
  36. package/dist/core/runner/prepare-delegation-surface.d.ts +5 -4
  37. package/dist/core/runner/prepare-delegation-surface.js +15 -17
  38. package/dist/core/runner/prepare-gate-stations.d.ts +4 -1
  39. package/dist/core/runner/prepare-gate-stations.js +3 -1
  40. package/dist/core/runner/prepare-hands-readface.d.ts +3 -2
  41. package/dist/core/runner/prepare-hands-readface.js +3 -2
  42. package/dist/core/runner/prepare-inherited-gate.d.ts +1 -1
  43. package/dist/core/runner/prepare-inherited-gate.js +1 -0
  44. package/dist/core/runner/prepare-lsp.d.ts +4 -3
  45. package/dist/core/runner/prepare-lsp.js +3 -2
  46. package/dist/core/runner/prepare-memory-engine-session.d.ts +4 -4
  47. package/dist/core/runner/prepare-memory-engine-session.js +4 -2
  48. package/dist/core/runner/prepare-offload-wrappers.js +7 -5
  49. package/dist/core/runner/prepare-park-ask.d.ts +4 -0
  50. package/dist/core/runner/prepare-park-ask.js +4 -1
  51. package/dist/core/runner/prepare-project-context.d.ts +5 -23
  52. package/dist/core/runner/prepare-project-context.js +7 -5
  53. package/dist/core/runner/prepare-prompt-assembly.d.ts +1 -1
  54. package/dist/core/runner/prepare-protocol-tools.d.ts +5 -1
  55. package/dist/core/runner/prepare-protocol-tools.js +38 -21
  56. package/dist/core/runner/prepare-question-face.d.ts +4 -3
  57. package/dist/core/runner/prepare-question-face.js +5 -3
  58. package/dist/core/runner/prepare-run-refs.d.ts +16 -0
  59. package/dist/core/runner/prepare-run-refs.js +6 -0
  60. package/dist/core/runner/prepare-safety-scan.js +42 -2
  61. package/dist/core/runner/prepare-task.js +37 -19
  62. package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +5 -2
  63. package/dist/core/runner/prepare-tool-disclosure-mount.js +3 -1
  64. package/dist/core/runner/prepare-wiring-manifest.d.ts +4 -0
  65. package/dist/core/runner/prepare-wiring-manifest.js +3 -8
  66. package/dist/core/runner/run-harness-handlers.d.ts +46 -0
  67. package/dist/core/runner/run-harness-handlers.js +345 -0
  68. package/dist/core/runner/run-turn-boundary.d.ts +69 -0
  69. package/dist/core/runner/run-turn-boundary.js +694 -0
  70. package/dist/core/runner/runtask.js +47 -1043
  71. package/dist/core/runner/session-rule-policy.js +9 -4
  72. package/dist/core/runner/synthetic-tools.js +5 -8
  73. package/dist/core/runner/tool-disclosure.d.ts +4 -0
  74. package/dist/core/runner/tool-disclosure.js +9 -5
  75. package/dist/core/runner/tool-end-body.js +3 -2
  76. package/dist/core/runner/tool-face-overlay.d.ts +40 -0
  77. package/dist/core/runner/tool-face-overlay.js +125 -0
  78. package/dist/core/runner/tool-output-projection.d.ts +10 -0
  79. package/dist/core/runner/tool-output-projection.js +33 -10
  80. package/dist/core/sensitive-path-policy.d.ts +3 -0
  81. package/dist/core/sensitive-path-policy.js +6 -5
  82. package/dist/core/shared-memory/tools.js +3 -6
  83. package/dist/core/store-contracts/checkpoint-store-contract.js +23 -0
  84. package/dist/core/task-event.d.ts +10 -0
  85. package/dist/core/task-registry.js +4 -5
  86. package/dist/core/task-spec.d.ts +8 -0
  87. package/dist/core/tool-catalog-entries.d.ts +48 -0
  88. package/dist/core/tool-catalog-entries.js +174 -0
  89. package/dist/core/tool-conformance.d.ts +22 -0
  90. package/dist/core/tool-conformance.js +37 -0
  91. package/dist/core/tool-face.d.ts +171 -0
  92. package/dist/core/tool-face.js +25 -0
  93. package/dist/core/tool-policy.d.ts +24 -0
  94. package/dist/core/tool-policy.js +4 -2
  95. package/dist/core/tool-registry.d.ts +114 -0
  96. package/dist/core/tool-registry.js +114 -0
  97. package/dist/core/tool-result-budget.d.ts +0 -3
  98. package/dist/core/tool-result-budget.js +4 -3
  99. package/dist/core/tool-result-store.d.ts +0 -18
  100. package/dist/core/tool-result-store.js +2 -18
  101. package/dist/core/tool-roster.d.ts +450 -0
  102. package/dist/core/tool-roster.js +497 -0
  103. package/dist/core/tool-spec.d.ts +54 -3
  104. package/dist/core/tools.js +7 -0
  105. package/dist/core/wiring-manifest.d.ts +13 -0
  106. package/dist/core/wiring-manifest.js +2 -1
  107. package/dist/core/write-protect.d.ts +2 -1
  108. package/dist/core/write-protect.js +5 -4
  109. package/dist/engine/compaction/utils.d.ts +6 -0
  110. package/dist/engine/compaction/utils.js +19 -19
  111. package/dist/engine/loop/types.d.ts +6 -0
  112. package/dist/index.d.ts +15 -4
  113. package/dist/index.js +15 -4
  114. package/dist/orchestration/goal.js +2 -1
  115. package/dist/orchestration/run-spec.js +3 -5
  116. package/dist/orchestration/run-workflow-tool.js +2 -2
  117. package/dist/prompt-assembly/event-registry.js +3 -3
  118. package/dist/prompt-assembly/tool-catalog.d.ts +2 -2
  119. package/dist/tools/fs/fs-bash.d.ts +1 -1
  120. package/dist/tools/fs/fs-bash.js +7 -11
  121. package/dist/tools/fs/fs-read.js +2 -2
  122. package/dist/tools/fs/fs-search-tools.d.ts +0 -7
  123. package/dist/tools/fs/fs-search-tools.js +3 -16
  124. package/dist/tools/fs/fs-write.js +4 -6
  125. package/dist/tools/fs/repo-map.js +2 -2
  126. package/dist/tools/fs/safety.d.ts +2 -1
  127. package/dist/tools/fs/safety.js +6 -5
  128. package/dist/tools/monitor.js +2 -2
  129. package/dist/tools/scheduler-tools.js +5 -8
  130. package/dist/tools/task-list.js +5 -8
  131. package/dist/tools/todo.js +2 -2
  132. package/dist/tools/web.js +3 -6
  133. package/dist/tools/worktree.js +3 -4
  134. package/package.json +4 -1
  135. package/test/export-surface.snapshot.json +147 -7
@@ -2,8 +2,13 @@ import { Type } from "typebox";
2
2
  import { materializeMcpTools } from "../mcp.js";
3
3
  import { materializeA2aTools } from "../a2a.js";
4
4
  import { deliverEngineNotice } from "../types.js";
5
+ import { RosterBuilder } from "../tool-roster.js";
6
+ import { REFRESH_MCP_TOOLS_TOOL_NAME, toolFace } from "../tool-catalog-entries.js";
7
+ import { MCP_NAMESPACE, A2A_NAMESPACE } from "../protocol-table.js";
8
+ import { mintNamespacePrefix } from "../protocol-naming.js";
9
+ import { applyMcpToolFaces } from "./tool-face-overlay.js";
5
10
  export async function prepareProtocolTools(input) {
6
- const { lockedPreflight, spec, deps, reminderMark, reminderDisclosureCounts, runId, sessionId, onceLedger, tools, remoteToolOffload, toolFaceSnapshot, toolEffects, axisExplicitNegatives, irreversibilityTier, irreversibleTools, egressTools, abortController, rollback } = input;
11
+ const { lockedPreflight, spec, deps, reminderMark, reminderDisclosureCounts, runId, sessionId, onceLedger, roster, rosterSeat, remoteToolOffload, toolFaceSnapshot, toolEffects, axisExplicitNegatives, irreversibilityTier, irreversibleTools, egressTools, abortController, rollback } = input;
7
12
  const mcp = lockedPreflight.mcp?.length
8
13
  ? await materializeMcpTools(lockedPreflight.mcp, spec.principal, deps.onElicit, deps.mcpImageResizer, { reminderMark, counts: reminderDisclosureCounts }, mcpRevocationWiring(deps, runId))
9
14
  : { tools: [], toolAxes: [], warnings: [], serverInstructions: [], instructionsDelta: { pendingAdds: [], pendingRemovals: [] }, droppedTools: [], statuses: [], refresh: async () => [], dispose: async () => { } };
@@ -24,14 +29,35 @@ export async function prepareProtocolTools(input) {
24
29
  throw e;
25
30
  }
26
31
  }
27
- tools.push(...mcp.tools.map((t) => remoteToolOffload(t)));
32
+ const faceAnnounced = new Set();
33
+ const withFaces = (tools, serverName) => {
34
+ const server = (lockedPreflight.mcp ?? []).find((s) => s.name === serverName);
35
+ return server === undefined ? [...tools] : applyMcpToolFaces({ tools, server, onNotice: deps.onNotice, announced: faceAnnounced, sessionId: sessionId ?? "", runId });
36
+ };
37
+ {
38
+ const servers = (lockedPreflight.mcp ?? []).map((s) => ({ name: s.name, prefix: mintNamespacePrefix(MCP_NAMESPACE, s.name) }));
39
+ const faced = new Map();
40
+ for (const server of servers) {
41
+ const own = mcp.tools.filter((x) => x.name.startsWith(server.prefix));
42
+ for (const t of withFaces(own, server.name))
43
+ faced.set(t.name, t);
44
+ }
45
+ for (const t of mcp.tools) {
46
+ const server = servers.find((s) => t.name.startsWith(s.prefix));
47
+ const mounted = server === undefined ? t : faced.get(t.name);
48
+ if (mounted === undefined)
49
+ continue;
50
+ roster.mount(remoteToolOffload(mounted), { source: "mcp", mountedBy: "optional", origin: { peer: server?.name ?? MCP_NAMESPACE.parse(t.name)?.peer ?? t.name } });
51
+ }
52
+ }
28
53
  const rebuildHarnessToolsRef = {};
29
54
  const contentOriginWrapRef = {};
30
55
  const toolCallGateArmedRef = { armed: false };
31
56
  if (lockedPreflight.mcp?.length) {
32
- tools.push({
33
- name: "RefreshMcpTools",
34
- label: "RefreshMcpTools",
57
+ roster.mount({
58
+ ...toolFace("refresh-mcp-tools"),
59
+ name: REFRESH_MCP_TOOLS_TOOL_NAME,
60
+ label: REFRESH_MCP_TOOLS_TOOL_NAME,
35
61
  description: "Refresh the tool list of connected MCP servers. Never dials or re-dials connections — it only re-reads the tool list over the existing connection. The refreshed tools are available immediately; you can call them on your next step. Use when a server's expected tool is missing, or the tool list looks stale after a connection recovered. Omit `server` to refresh every connected server.",
36
62
  parameters: Type.Object({
37
63
  server: Type.Optional(Type.String({ description: "Name of a single MCP server to refresh; omit to refresh all connected servers." })),
@@ -76,19 +102,8 @@ export async function prepareProtocolTools(input) {
76
102
  anyActiveFailure = true;
77
103
  continue;
78
104
  }
79
- let domainAnchor = -1;
80
- for (let i = tools.length - 1; i >= 0; i--) {
81
- const t = tools[i];
82
- if (t.name.startsWith(r.prefix)) {
83
- domainAnchor = i;
84
- tools.splice(i, 1);
85
- }
86
- }
87
- const refreshedMounts = pushable.map((t) => remoteToolOffload(t));
88
- if (domainAnchor >= 0)
89
- tools.splice(domainAnchor, 0, ...refreshedMounts);
90
- else
91
- tools.push(...refreshedMounts);
105
+ const refreshedMounts = withFaces(pushable, r.server).map((t) => remoteToolOffload(t));
106
+ roster.replaceDomain(r.prefix, refreshedMounts, { source: "mcp", mountedBy: "optional", origin: { peer: r.server } });
92
107
  changed = true;
93
108
  const detail = [];
94
109
  const shownAdded = r.added.filter((n) => !excludedSet.has(n));
@@ -104,6 +119,8 @@ export async function prepareProtocolTools(input) {
104
119
  }
105
120
  if (changed)
106
121
  await rebuildHarnessToolsRef.current?.();
122
+ if (changed)
123
+ rosterSeat.republish();
107
124
  if (changed && !toolCallGateArmedRef.armed && (irreversibleTools.size > 0 || egressTools.size > 0)) {
108
125
  lines.push("WARNING: refreshed tools declare irreversible/egress safety hints, but this task started with no approval gate registered (no policy/hooks and no gated tools at start) — these hints cannot arm a gate mid-task (RB-46); the refreshed tools run ungated on this deployment shape.");
109
126
  }
@@ -115,8 +132,7 @@ export async function prepareProtocolTools(input) {
115
132
  ...(anyActiveFailure && !changed ? { isError: true } : {}),
116
133
  };
117
134
  },
118
- });
119
- toolEffects.set("RefreshMcpTools", "read");
135
+ }, { source: "builtin", mountedBy: "optional" });
120
136
  }
121
137
  const foldProtocolAxes = (axes, protocolLabel) => {
122
138
  for (const axis of axes) {
@@ -160,7 +176,8 @@ export async function prepareProtocolTools(input) {
160
176
  throw e;
161
177
  }
162
178
  }
163
- tools.push(...a2a.tools.map((t) => remoteToolOffload(t)));
179
+ for (const t of a2a.tools)
180
+ roster.mount(remoteToolOffload(t), { source: "a2a", mountedBy: "optional", origin: { peer: A2A_NAMESPACE.parse(t.name)?.peer ?? t.name } });
164
181
  foldProtocolAxes(a2a.toolAxes, "A2A");
165
182
  return { mcp, a2a, rebuildHarnessToolsRef, contentOriginWrapRef, toolCallGateArmedRef };
166
183
  }
@@ -22,6 +22,7 @@ import { type CheckpointStore } from "../checkpoint-store.js";
22
22
  import type { RunnerDeps, RuntimeCaps, TaskSpec } from "../types.js";
23
23
  import type { PrepareResume } from "./contracts.js";
24
24
  import { type ContentAskBinding } from "./content-ask-bindings.js";
25
+ import { RosterBuilder } from "../tool-roster.js";
25
26
  export { CONTENT_ASK_BINDING_CAP, type ContentAskBinding } from "./content-ask-bindings.js";
26
27
  export interface PrepareQuestionFaceInput {
27
28
  /** borrowed-readonly — the run's frozen question seat (spec > deps), resolved once at the top of prepare;
@@ -45,10 +46,10 @@ export interface PrepareQuestionFaceInput {
45
46
  resume: Pick<PrepareResume, "redeemedContentAskCallId" | "redeemedContentAskQuestionsHash"> | undefined;
46
47
  /** borrowed-readonly — the acquired session id (the tool's source task id; operator-line context). */
47
48
  sessionId: string;
48
- /** borrowed-mutable — the run's shared roster (identity is the contract). Writer here: ONE push (the question
49
+ /** borrowed-mutable — the run's roster builder (identity is the contract). Writer here: ONE mount (the question
49
50
  * tool) when the mount predicate holds; `contentAskRoutable` reads `includes` per call against the LIVE
50
- * array (the roster is edited in place after the mount). */
51
- tools: AgentTool[];
51
+ * read face (the roster is edited in place after the mount). */
52
+ roster: RosterBuilder;
52
53
  /** borrowed-readonly — LIVE GETTER over the inherited-unavailable marker set (declared by the driver's
53
54
  * gate-fold section after this phase; `has` only, per call at run time). See the module header. */
54
55
  inheritedUnavailableAsks: () => ReadonlySet<string>;
@@ -2,9 +2,11 @@ import { createAskUserQuestionTool, isLiveQuestionFace } from "../ask-question.j
2
2
  import { boundInputHashOf } from "../canonical-json.js";
3
3
  import { resolveCheckpointStore } from "../checkpoint-store.js";
4
4
  import { CONTENT_ASK_BINDING_CAP } from "./content-ask-bindings.js";
5
+ import { RosterBuilder } from "../tool-roster.js";
5
6
  export { CONTENT_ASK_BINDING_CAP } from "./content-ask-bindings.js";
6
7
  export function prepareQuestionFace(input) {
7
- const { frozenOnQuestion, spec, deps, runtimeCaps, resolvedInteractionPosture, resume, sessionId, tools, inheritedUnavailableAsks } = input;
8
+ const { frozenOnQuestion, spec, deps, runtimeCaps, resolvedInteractionPosture, resume, sessionId, roster, inheritedUnavailableAsks } = input;
9
+ const tools = roster.tools;
8
10
  const onQuestion = frozenOnQuestion;
9
11
  const liveQuestionFace = isLiveQuestionFace(onQuestion) ? onQuestion : undefined;
10
12
  const contentAskBindings = new Map();
@@ -60,7 +62,7 @@ export function prepareQuestionFace(input) {
60
62
  const questionToolMounted = spec.interactiveTools === true || (spec.interactiveTools !== false && (onQuestion !== undefined || durableQuestionFace));
61
63
  let mountedQuestionTool;
62
64
  if (questionToolMounted)
63
- tools.push((mountedQuestionTool =
65
+ roster.mount((mountedQuestionTool =
64
66
  createAskUserQuestionTool(mountedQuestionFace, { principal: spec.principal, sourceTaskId: sessionId }, {
65
67
  ...(resume?.redeemedContentAskCallId !== undefined
66
68
  ? {
@@ -79,6 +81,6 @@ export function prepareQuestionFace(input) {
79
81
  : "the wired question channel failed") +
80
82
  `) — the model was instructed to self-answer and the run CONTINUES (warning, not a failure).`), { phase: "degraded", sessionId, classification: "no-human-autoanswered" });
81
83
  },
82
- })));
84
+ })), { source: "builtin", mountedBy: "optional" });
83
85
  return { checkpointStore, liveQuestionFace, contentAskBindings, contentAskRoutable, lateStrandedAnswers, discloseStrandedAnswers, settleContentAskBindings, durableQuestionFace, mountedQuestionFace, questionToolMounted, mountedQuestionTool };
84
86
  }
@@ -86,4 +86,20 @@ export interface PrepareRunRefsResult {
86
86
  worktreeIsolation: SubagentWorktreeIsolation | undefined;
87
87
  }
88
88
  /** The M3a phase body — prepareTask's run-refs stretch, verbatim (see the module header). */
89
+ /**
90
+ * A run that opted into `forwardSubagentEvents` may have BACKGROUND children whose frames are forwarded to the
91
+ * deployment's sink from the child's own run. Those forwards are scheduled on later microtasks than the parent's
92
+ * turn, so "the parent stream ended" and "the child's already-emitted frames reached the sink" are two different
93
+ * moments — the gap is one microtask ladder deep and moves whenever the gate's station structure changes (design/393
94
+ * / #594: the three-layer gate added three async frames to a child's gated call, and a consumer that read the parent
95
+ * to `done` then inspected the sink lost the child's `tool_end`). The rule is not a tick count: **before the parent
96
+ * says `done`, every frame a child had already handed to the forward channel is delivered.** One macrotask turn is
97
+ * the smallest boundary that strictly orders after all pending microtasks; it is taken only on the opted-in run,
98
+ * only once, right before `done`.
99
+ */
100
+ export declare function forwardsSubagentEvents(spec: Pick<TaskSpec, "forwardSubagentEvents">): boolean;
101
+ /** The drain itself — one macrotask turn. Callers gate it with {@link forwardsSubagentEvents} SYNCHRONOUSLY so a
102
+ * non-forwarding run's terminal push is not one microtask later than it was (an `await` of an early-returning
103
+ * async function still yields once; the black-box round measured that tick). */
104
+ export declare function drainForwardedFramesBeforeDone(): Promise<void>;
89
105
  export declare function prepareRunRefs(input: PrepareRunRefsInput): PrepareRunRefsResult;
@@ -2,6 +2,12 @@ import { createSubagentWorktreeHelper } from "../../agents/subagent.js";
2
2
  import { SubagentRetainLedger } from "../../agents/retain-ledger.js";
3
3
  import { ActiveSkillScope } from "./active-skill-scope.js";
4
4
  import { createEditedFilesLedger } from "./edited-files-ledger.js";
5
+ export function forwardsSubagentEvents(spec) {
6
+ return spec.forwardSubagentEvents === true;
7
+ }
8
+ export function drainForwardedFramesBeforeDone() {
9
+ return new Promise((resolve) => setImmediate(resolve));
10
+ }
5
11
  export function prepareRunRefs(input) {
6
12
  const { spec, internals, executionEnv, taskRootFinal } = input;
7
13
  const { note: noteFileEdited, snapshot: editedFilesSnapshot } = createEditedFilesLedger();
@@ -1,5 +1,8 @@
1
1
  import { NAMESPACED_NAME_SHAPES } from "../protocol-table.js";
2
- import { HAND_TOOL_EFFECTS } from "../../tools/fs/index.js";
2
+ import { handBandEffects } from "../tool-registry.js";
3
+ import { TOOL_CONTRACT_MAX_CHARS, TOOL_WIRE_NAME_MAX_CHARS } from "../tool-face.js";
4
+ import { schemaKeyBoundProblem } from "../tool-roster.js";
5
+ import { toolFaceProblem } from "./tool-face-overlay.js";
3
6
  import { OFFLOAD_TOOL_NAME } from "../tool-result-store.js";
4
7
  import { PRESENT_PLAN_TOOL_NAME, ENTER_PLAN_MODE_TOOL_NAME } from "../present-plan-tool.js";
5
8
  export function prepareSafetyScan(input) {
@@ -13,6 +16,43 @@ export function prepareSafetyScan(input) {
13
16
  const axisExplicitNegatives = new Map();
14
17
  const reversibilityProbes = new Map();
15
18
  for (const t of spec.tools ?? []) {
19
+ if (t.name.length > TOOL_WIRE_NAME_MAX_CHARS) {
20
+ const e = new Error(`Tool name "${t.name.slice(0, 40)}…" is invalid: ${t.name.length} characters, the roster's bound is ${TOOL_WIRE_NAME_MAX_CHARS}.`);
21
+ e.code = "config.tool_name_too_long";
22
+ throw e;
23
+ }
24
+ for (const alias of t.aliases ?? []) {
25
+ if (alias.length > TOOL_WIRE_NAME_MAX_CHARS) {
26
+ const e = new Error(`Tool alias "${alias.slice(0, 40)}…" (of "${t.name}") is invalid: ${alias.length} characters, the roster's bound is ${TOOL_WIRE_NAME_MAX_CHARS}.`);
27
+ e.code = "config.tool_name_too_long";
28
+ throw e;
29
+ }
30
+ }
31
+ if (t.contract !== undefined && (t.contract.contractId.length > TOOL_CONTRACT_MAX_CHARS || t.contract.implementationRevision.length > TOOL_CONTRACT_MAX_CHARS)) {
32
+ const e = new Error(`Tool "${t.name}" declares a contract member over ${TOOL_CONTRACT_MAX_CHARS} characters (contractId ${t.contract.contractId.length}, implementationRevision ${t.contract.implementationRevision.length}) — the roster's bound.`);
33
+ e.code = "config.tool_contract_too_long";
34
+ throw e;
35
+ }
36
+ {
37
+ const keyProblem = schemaKeyBoundProblem(t.parameters);
38
+ if (keyProblem !== undefined) {
39
+ const e = new Error(`Tool "${t.name}" cannot ride a roster row: ${keyProblem}.`);
40
+ e.code = "config.tool_schema_bound";
41
+ throw e;
42
+ }
43
+ const declared = {
44
+ ...(t.family !== undefined ? { family: t.family } : {}),
45
+ ...(t.pathTarget !== undefined ? { pathTarget: t.pathTarget } : {}),
46
+ ...(t.ruleFace !== undefined ? { ruleFace: t.ruleFace } : {}),
47
+ ...(t.renderHints !== undefined ? { renderHints: t.renderHints } : {}),
48
+ };
49
+ const faceProblem = toolFaceProblem(declared, t.parameters);
50
+ if (faceProblem !== undefined) {
51
+ const e = new Error(`Tool "${t.name}" declares a face its schema cannot carry: ${faceProblem}.`);
52
+ e.code = "config.tool_face_invalid";
53
+ throw e;
54
+ }
55
+ }
16
56
  if (t.name.includes("__")) {
17
57
  const e = new Error(`Tool name "${t.name}" is invalid: "__" is reserved for the engine's protocol tool namespaces (${NAMESPACED_NAME_SHAPES}) and must not appear in a caller tool name.`);
18
58
  e.code = "config.tool_name_invalid";
@@ -65,7 +105,7 @@ export function prepareSafetyScan(input) {
65
105
  }
66
106
  toolEffects.set(OFFLOAD_TOOL_NAME, "read");
67
107
  if (deps.executionEnvFactory || deps.executionEnv) {
68
- for (const [name, effect] of Object.entries(HAND_TOOL_EFFECTS))
108
+ for (const [name, effect] of handBandEffects())
69
109
  toolEffects.set(name, effect);
70
110
  if (spec.handsReadOnly === true)
71
111
  toolEffects.set("Bash", "read");
@@ -71,6 +71,10 @@ import { selfOrchestrationFailClosedReason } from "../../orchestration/workflow-
71
71
  import { remainingBudgetMicroUsd } from "../checkpoint-store.js";
72
72
  import { durableParkGapFor } from "../park-selfcheck.js";
73
73
  import { deliverEngineNotice } from "../types.js";
74
+ import { ToolRosterDeltaSeat } from "../tool-roster.js";
75
+ import { faceOf } from "../tool-roster.js";
76
+ import { catalogEntriesNamed, pathTargetOf } from "../tool-registry.js";
77
+ import { createStructuredProjector } from "./tool-output-projection.js";
74
78
  function warnCompactionWindowHazard(tracer, spec, model, compModel, hostTaskId) {
75
79
  if (compModel === undefined)
76
80
  return;
@@ -363,7 +367,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
363
367
  let readDenyAdditionsNormalized = [];
364
368
  let handsLessResolvedFace;
365
369
  const { liveInheritedGate, seedInheritedGate, inheritedAncestorRules, inheritedShellGate, inheritedParentConstraints, autoModeIntent, inheritedAdmittedOrgScopes, priorOwnOrgVerdict, orgGovernedProvenance, effectiveShellGate, ownSessionRulesRef, memoryAdmittedOrgScopesRef, ownOrgVerdictRef, orgAdmissionCheckpointState, faceCheckpointSection, faceCheckpointState, f012CheckpointState, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, frozenOnQuestion, provenanceForChildrenRef, delegationSettlementRef, inheritedGateForChildren, carrierReadFace, fullShellReachable, gateStopRef, stopForDenialLimit, enrichSpecToolCtx } = prepareInheritedGate({ spec, deps, internals, resume, sessions, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, ownedEnv, lockedPreflight, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, autoModeSeat, handsEnabled, reportUsage, harnessRef, skillScope, forwardEvent, subagentRetain, worktreeIsolation, requestReview, requestStopAfterTurn, enterPlanMode, reminderMark, reminderDisclosureCounts, fileHistoryEnabled, historyScope, historyRoot, historyFs, abortRun: () => { abortController.abort(); void harness.abort(); }, runtimeCaps: () => runtimeCaps, hooks: () => hooks, preToolUseObservational: () => preToolUseObservational, hookTimeoutMs: () => hookTimeoutMs, hookEnvFace: () => hookEnvFace, autoModeDecider: () => autoModeDecider, autoModeDenialTracking: () => autoModeDenialTracking, autoModeArming: () => autoModeArming, agentForkDenial: () => agentForkDenial, observersActive: () => observersActive, resolvedReadFace: () => resolvedReadFace, readDenyAdditionsNormalized: () => readDenyAdditionsNormalized, handsLessResolvedFace: () => handsLessResolvedFace, centerAdoption: () => centerAdoptionRef.current, memoryEngineSession: () => memoryEngineSession });
366
- const { tools, blockedRef, runtimeCaps, runtimeCapsFaulted, complianceDenies, complianceDegraded, agentForkDenial, observersActive, autoModeArmReason, autoModeDecider, autoModeDenialTracking, autoModeArming, selfOrchestrationActive, workflowToolsActive, workflowSizeGuideline } = await prepareCapsAndWorkflow({ spec, deps, internals, session, sessionId, runId, hostTaskId, taskScope, taskRootFinal, model, thinking, lockedPreflight, toolEffects, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, harnessRef, frozenOnAsk, carrierReadFace, forwardEvent, inheritedGateForChildren, enrichSpecToolCtx, maybeOffload, requestReview, enterPlanMode, autoModeIntent, peerLaneActive, peerSendMessageBuiltIn, runnerSelf, memoryEngineSession: () => memoryEngineSession, centerAdoption: () => centerAdoptionRef.current, readDenyAdditionsNormalized: () => readDenyAdditionsNormalized });
370
+ const { roster, blockedRef, runtimeCaps, runtimeCapsFaulted, complianceDenies, complianceDegraded, agentForkDenial, observersActive, autoModeArmReason, autoModeDecider, autoModeDenialTracking, autoModeArming, selfOrchestrationActive, workflowToolsActive, workflowSizeGuideline } = await prepareCapsAndWorkflow({ spec, deps, internals, session, sessionId, runId, hostTaskId, taskScope, taskRootFinal, model, thinking, lockedPreflight, toolEffects, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, harnessRef, frozenOnAsk, carrierReadFace, forwardEvent, inheritedGateForChildren, enrichSpecToolCtx, maybeOffload, requestReview, enterPlanMode, autoModeIntent, peerLaneActive, peerSendMessageBuiltIn, runnerSelf, memoryEngineSession: () => memoryEngineSession, centerAdoption: () => centerAdoptionRef.current, readDenyAdditionsNormalized: () => readDenyAdditionsNormalized });
371
+ const tools = roster.tools;
367
372
  const pausedRef = {};
368
373
  const remoteEnvFailures = [];
369
374
  const suspendLoopRef = { hit: false };
@@ -419,12 +424,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
419
424
  catch {
420
425
  }
421
426
  }
422
- tools.push(createOutputTool(outputRef, spec.outputSchema, compiled.strict ? compiled.modelSchema : undefined));
427
+ roster.mount(createOutputTool(outputRef, spec.outputSchema, compiled.strict ? compiled.modelSchema : undefined), { source: "synthetic", mountedBy: "optional" });
423
428
  }
424
- const { mcp, a2a, rebuildHarnessToolsRef, contentOriginWrapRef, toolCallGateArmedRef } = await prepareProtocolTools({ lockedPreflight, spec, deps, reminderMark, reminderDisclosureCounts, runId, sessionId, onceLedger, tools, remoteToolOffload, toolFaceSnapshot, toolEffects, axisExplicitNegatives, irreversibilityTier, irreversibleTools, egressTools, abortController, rollback });
429
+ const rosterSeat = new ToolRosterDeltaSeat();
430
+ const { mcp, a2a, rebuildHarnessToolsRef, contentOriginWrapRef, toolCallGateArmedRef } = await prepareProtocolTools({ lockedPreflight, spec, deps, reminderMark, reminderDisclosureCounts, runId, sessionId, onceLedger, roster, rosterSeat, remoteToolOffload, toolFaceSnapshot, toolEffects, axisExplicitNegatives, irreversibilityTier, irreversibleTools, egressTools, abortController, rollback });
425
431
  const callIssuedAtRef = {};
426
432
  const memoryWriteGateRef = {};
427
- const handsReadFaceInput = { handsEnabled, executionEnv, taskRootFinal, rebaseRestoredPath, resume, session, sessionId, hostTaskId, taskScope, fullShellReachable, effectiveShellGate, toolFaceSnapshot, model, spec, deps, internals, memoryWriteGateRef, firstPartyOffload, tools, egressTools, irreversibilityTier, irreversibleTools, reversibilityProbes, reminderMark, reminderDisclosureCounts, ...(trackFileEdit !== undefined ? { trackFileEdit } : {}), onFileEdited: noteFileEdited, ...(restoredFilePaths.length > 0 ? { restoredFilePaths } : {}) };
433
+ const handsReadFaceInput = { handsEnabled, executionEnv, taskRootFinal, rebaseRestoredPath, resume, session, sessionId, hostTaskId, taskScope, fullShellReachable, effectiveShellGate, toolFaceSnapshot, model, spec, deps, internals, memoryWriteGateRef, firstPartyOffload, roster, egressTools, irreversibilityTier, irreversibleTools, reversibilityProbes, reminderMark, reminderDisclosureCounts, ...(trackFileEdit !== undefined ? { trackFileEdit } : {}), onFileEdited: noteFileEdited, ...(restoredFilePaths.length > 0 ? { restoredFilePaths } : {}) };
428
434
  const handsReadFace = handsEnabled ? await prepareHandsMount(handsReadFaceInput) : resolveHandsLessReadFace(handsReadFaceInput);
429
435
  const { readFileStateForCheckpoint, seedContextFiles, handsCwdRef, workspaceStateSettle, wsSnapshot, rebaseWsPath, backgroundTaskToolsActive, additionalRootsCanonical, additionalReadRootsCanonical, attachmentRootCanonical, readDenyMatcher, envHandToolNames, sealReadStateSeat } = handsReadFace;
430
436
  resolvedReadFace = handsReadFace.resolvedReadFace;
@@ -432,16 +438,16 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
432
438
  handsLessResolvedFace = handsReadFace.handsLessResolvedFace;
433
439
  shellGatedBash = handsReadFace.shellGatedBash;
434
440
  shellGatedMonitor = handsReadFace.shellGatedMonitor;
435
- const { worktreeSessionRef } = prepareDelegationSurface({ spec, deps, internals, resume, sessionId, hostTaskId, taskScope, taskRootFinal, executionEnv, runnerSelf, backgroundTaskToolsActive, workflowToolsActive, peerLaneActive, peerSendMessageBuiltIn, peerLaneRefs, tools, toolEffects, axisExplicitNegatives, envHandToolNames, toolFaceSnapshot, firstPartyOffload, offloadStore, subagentRetain, enrichSpecToolCtx, handsCwdRef, wsSnapshot, rebaseWsPath, rebaseRestoredPath, workspaceStateSettle });
436
- const { checkpointStore, liveQuestionFace, contentAskBindings, contentAskRoutable, lateStrandedAnswers, discloseStrandedAnswers, settleContentAskBindings, durableQuestionFace, questionToolMounted, mountedQuestionTool } = prepareQuestionFace({ frozenOnQuestion, spec, deps, runtimeCaps, resolvedInteractionPosture, resume, sessionId, tools, inheritedUnavailableAsks: () => inheritedUnavailableAsks });
441
+ const { worktreeSessionRef } = prepareDelegationSurface({ spec, deps, internals, resume, sessionId, hostTaskId, taskScope, taskRootFinal, executionEnv, runnerSelf, backgroundTaskToolsActive, workflowToolsActive, peerLaneActive, peerSendMessageBuiltIn, peerLaneRefs, roster, toolEffects, axisExplicitNegatives, envHandToolNames, toolFaceSnapshot, firstPartyOffload, offloadStore, subagentRetain, enrichSpecToolCtx, handsCwdRef, wsSnapshot, rebaseWsPath, rebaseRestoredPath, workspaceStateSettle });
442
+ const { checkpointStore, liveQuestionFace, contentAskBindings, contentAskRoutable, lateStrandedAnswers, discloseStrandedAnswers, settleContentAskBindings, durableQuestionFace, questionToolMounted, mountedQuestionTool } = prepareQuestionFace({ frozenOnQuestion, spec, deps, runtimeCaps, resolvedInteractionPosture, resume, sessionId, roster, inheritedUnavailableAsks: () => inheritedUnavailableAsks });
437
443
  if (spec.handsReadOnly !== true) {
438
444
  const sessionScope = sessionId ?? spec.principal ?? spec.taskId ?? "default";
439
- tools.push(...createSchedulerTools(executionEnv, {
445
+ roster.mountAll(createSchedulerTools(executionEnv, {
440
446
  scope: sessionScope,
441
447
  ...(spec.principal !== undefined ? { principal: spec.principal } : {}),
442
448
  ...(sessionId !== undefined ? { sessionId } : {}),
443
449
  requestStopAfterTurn,
444
- }).map((t) => (envHandToolNames.add(t.name), t)));
450
+ }).map((t) => (envHandToolNames.add(t.name), t)), { source: "builtin", mountedBy: "scheduler" });
445
451
  }
446
452
  if (internals?.insideFork !== true &&
447
453
  runnerSelf &&
@@ -451,7 +457,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
451
457
  deps.onError?.(new Error(`Fork DENIED for principal "${spec.principal ?? ""}" by runtimeCaps.allowFork=false ` +
452
458
  `(per-principal entitlement governance, not a misconfiguration — Agent(subagent_type:"fork") will refuse honestly)`), { phase: "config", sessionId });
453
459
  }
454
- const { lspDiagnostics, nudgeLspOnEdit, lspRunIdent } = prepareLsp({ spec, deps, executionEnv, taskRootFinal, handsEnabled, sessionId, handsCwdRef, tools, envHandToolNames });
460
+ const { lspDiagnostics, nudgeLspOnEdit, lspRunIdent } = prepareLsp({ spec, deps, executionEnv, taskRootFinal, handsEnabled, sessionId, handsCwdRef, roster, envHandToolNames });
455
461
  const memoryPairNameDomain = new Set();
456
462
  for (const t of tools) {
457
463
  memoryPairNameDomain.add(t.name);
@@ -503,7 +509,10 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
503
509
  const effect = (t.effect ?? toolEffects.get(t.name) ?? "write");
504
510
  if (effect === "read")
505
511
  return false;
506
- if (t.name !== "Write" && t.name !== "Edit" && t.name !== "NotebookEdit" && t.name !== "Bash")
512
+ const face = faceOf(t);
513
+ const pt = face.pathTarget ?? pathTargetOf(t.name);
514
+ const family = face.family ?? catalogEntriesNamed(t.name)[0]?.face.family;
515
+ if (!((pt !== undefined && pt.access !== "read") || family === "shell"))
507
516
  return false;
508
517
  return !(handsEnabled && isRemoteExecutionEnv(executionEnv));
509
518
  }),
@@ -551,13 +560,22 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
551
560
  deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "config", sessionId });
552
561
  }
553
562
  }
554
- const projectContext = await prepareProjectContext({ deps, spec, internals, session, sessionId, hostTaskId, taskRootFinal, handsEnabled, delegation, seedContextFiles, memoryBlockFromEngine, skillScope, tools, toolFaceSnapshot, onceLedger, memoryTools, memoryEngineSession, memorySearchToolsPlanned, memoryPairExcludedName, memoryPairOccupiedName, resume });
563
+ const projectContext = await prepareProjectContext({ deps, spec, internals, session, sessionId, hostTaskId, taskRootFinal, handsEnabled, delegation, seedContextFiles, memoryBlockFromEngine, skillScope, roster, toolFaceSnapshot, onceLedger, memoryTools, memoryEngineSession, memorySearchToolsPlanned, memoryPairExcludedName, memoryPairOccupiedName, resume });
555
564
  const { memoryBlock: memoryBlockWithProject, instructionSources, projectInstructionContent, hasSkillManifest, skillsListing, sharedMemoryPairMounted, memoryEnginePairMounted } = projectContext;
556
565
  const { provider, featureFlags, userSystemPrompt, userAppendSystemPrompt, mcpInstructionsBlock, envFacts, tzValid, userTz, gitStatusRef } = await preparePromptInputs({ spec, deps, failClosedReason: selfOrchestrationFailClosedReason(spec, deps), lockedPreflight, selfOrchestrationActive, executionEnv, reminderMark, resolvedReadFace, toolFaceSnapshot, promptProfile, fableMitigations, internals, thinking, ownedEnv, delegation, resolvedRole, mcp, model, externalContentTargetActive, resume, handsEnabled, taskRootFinal, additionalRootsCanonical, additionalReadRootsCanonical, sessionId });
557
- const { deferred, memoryBlock } = prepareDeferClassify({ spec, deps, tools, toolFaceSnapshot, handsEnabled, fullShellReachable, promptProfile, model, mcp, a2a, sharedMemoryPairMounted, memoryEnginePairMounted, memoryBlock: memoryBlockWithProject, memoryRecallSegment, onceLedger, sessionId });
558
- prepareMemoryEngineSession({ memoryEngineSession, internals, memoryDelegationEvidence, externalContentTargetActive, lockedPreflight, mcp, sessionId, runId, deps, provenanceForChildrenRef, delegationSettlementRef, spec, envHandToolNames, tools, contentOriginWrapRef });
566
+ const { deferred, memoryBlock } = prepareDeferClassify({ spec, deps, roster, toolFaceSnapshot, handsEnabled, fullShellReachable, promptProfile, model, mcp, a2a, sharedMemoryPairMounted, memoryEnginePairMounted, memoryBlock: memoryBlockWithProject, memoryRecallSegment, onceLedger, sessionId });
567
+ prepareMemoryEngineSession({ memoryEngineSession, internals, memoryDelegationEvidence, externalContentTargetActive, lockedPreflight, mcp, sessionId, runId, deps, provenanceForChildrenRef, delegationSettlementRef, spec, envHandToolNames, roster, contentOriginWrapRef });
559
568
  const { systemPromptSeat, renderWithDate, systemBlocks, promptManifest, epochDeclaredSections, epochArtifactDigestForSnapshot, buildAssembleInputs, centerAdoptionRef } = await preparePromptAssembly({ session, deps, internals, resume, provider, userSystemPrompt, userAppendSystemPrompt, featureFlags, mcpInstructionsBlock, memoryBlock, envFacts, promptProfile, fableMitigations, tools, model, sessionId, reminderMark, onceLedger });
560
- const { activeTools, fpRef, turnSnapshotRef, harnessTools, toolsDeltaRef, toolMaterializeStatic, deferDirectCall, staticFaceForRef, listingRideRef } = await prepareToolDisclosureMount({ spec, deps, session, resume, deferred, tools, onceLedger, mcp, promptManifest, offloadReachableToolsRef, contentOriginWrapRef, rebuildHarnessToolsRef, harnessRef });
569
+ const { activeTools, fpRef, turnSnapshotRef, harnessTools, toolsDeltaRef, toolMaterializeStatic, deferDirectCall, staticFaceForRef, listingRideRef, toolSearch } = await prepareToolDisclosureMount({ spec, deps, session, resume, deferred, tools, onceLedger, mcp, promptManifest, offloadReachableToolsRef, contentOriginWrapRef, rebuildHarnessToolsRef, harnessRef });
570
+ if (toolSearch !== undefined)
571
+ roster.mountEngineLane(toolSearch, { source: "builtin", mountedBy: "optional" });
572
+ const rosterFacts = { effects: toolEffects, egress: egressTools, irreversibilityTier, reversibilityProbes, deferred, trustedTools: new Set(spec.memory?.trustedTools ?? []) };
573
+ const toolRoster = roster.snapshot(rosterFacts);
574
+ rosterSeat.arm(toolRoster, () => roster.snapshot(rosterFacts));
575
+ const structuredProjector = createStructuredProjector({
576
+ declaredCards: toolRoster.entries.flatMap((e) => e.renderHints?.resultCards ?? []),
577
+ onUndeclared: (type) => deliverEngineNotice(deps.onNotice, { code: "config.tool_card_undeclared", message: `a tool minted a \`details.type\` card ${JSON.stringify(type)} its definition never declared — dropped from tool_end.structured (declare it on the definition's catalog row or the caller's renderHints.resultCards)`, detail: { type, sessionId, runId } }),
578
+ });
561
579
  const { agentListing, announcedListingsRef, dateChange } = prepareListings({ spec, tools, onceLedger, sessionId, toolFaceSnapshot, activeTools, resume, skillsListing, listingRideRef, renderWithDate, envFacts, tzValid, userTz });
562
580
  const lastBrainContextRef = {};
563
581
  const requestLossyRef = { current: false };
@@ -614,7 +632,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
614
632
  ...(spec.streamingToolExecution === true ? { streamingToolExecution: true } : {}),
615
633
  env: executionEnv,
616
634
  session,
617
- tools: harnessTools,
635
+ tools: [...harnessTools],
618
636
  model,
619
637
  thinkingLevel: thinking ?? "off",
620
638
  systemPrompt: systemPromptSeat.current,
@@ -652,7 +670,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
652
670
  }
653
671
  }
654
672
  const { hooks, hookTimeoutMs, preToolUseObservational, ownGatePreToolUse, basePolicyForResumeEdit, denyNarrowingPolicy, policyLayers, effectivePolicy, inheritedUnavailableAsks, inheritedAskGrants, askSourceIdentity, riskAxesOf, ruleOffersOf, permissionRuleLane, permissionRuleOrgLane, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, foldAskClasses, ancestorSandboxAdmissions } = await preparePolicyChain({ spec, deps, internals, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, lockedPreflight, tools, harnessTools, toolEffects, egressTools, irreversibilityTier, axisExplicitNegatives, ownToolNames, shellGatedBash, handsCwdRef, readDenyMatcher, questionToolMounted, runtimeCaps, inheritedAncestorRules, inheritedParentConstraints, seedInheritedGate, hasSkillManifest, skillScope, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, autoModeDecider, stopForDenialLimit, delegation, abortController, now, ownSessionRulesRef });
655
- const { onAsk, preToolContexts, blockedToolCalls, gateOutcomes, batchHaltRef, blockedTracked, incompleteSuspendAdapter, durableSuspendInfraReady, resourceSuspendEligible, usageGovernance, durableApproval, wiringManifest, parkLaneArmed, gateMachineryActive, hookIdentity, hookEnvFace } = prepareWiringManifest({ spec, deps, internals, resume, sessionId, runId, hostTaskId, taskScope, hooks, preToolUseObservational, policyLayers, effectivePolicy, handsEnabled, mcp, irreversibleTools, egressTools, toolEffects, ownedEnv, executionEnv, offloadStore, onceLedger, usageWindows, envLifetimeSuspendAt, runtimeCaps, resolvedInteractionPosture, durableQuestionFace, liveQuestionFace, checkpointStore, sessions, modelGateManifest, autoModeArmReason, lockedPreflight, peerLaneRefs, peerLaneActive, harness, pausedRef, frozenOnAsk, liveInheritedGate, delegation });
673
+ const { onAsk, preToolContexts, blockedToolCalls, gateOutcomes, batchHaltRef, blockedTracked, incompleteSuspendAdapter, durableSuspendInfraReady, resourceSuspendEligible, usageGovernance, durableApproval, wiringManifest, parkLaneArmed, gateMachineryActive, hookIdentity, hookEnvFace } = prepareWiringManifest({ spec, deps, internals, resume, sessionId, runId, hostTaskId, taskScope, hooks, preToolUseObservational, policyLayers, effectivePolicy, handsEnabled, mcp, irreversibleTools, egressTools, toolEffects, ownedEnv, executionEnv, offloadStore, onceLedger, usageWindows, envLifetimeSuspendAt, runtimeCaps, resolvedInteractionPosture, durableQuestionFace, liveQuestionFace, checkpointStore, sessions, modelGateManifest, autoModeArmReason, lockedPreflight, peerLaneRefs, peerLaneActive, harness, pausedRef, frozenOnAsk, liveInheritedGate, delegation, toolRoster });
656
674
  if (fileHistoryBoundary !== undefined) {
657
675
  harness.on("tool_call", async () => {
658
676
  await fileHistoryBoundary.settle();
@@ -662,13 +680,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
662
680
  const { askLane } = prepareAskLane({ gateMachineryActive, abortController, effectivePolicy, budgetSnapshot, handsCwdRef, tools, inheritedUnavailableAsks, inheritedAskGrants, onAsk, humanReviewRef, now, ruleOffersOf, askSourceIdentity, riskAxesOf, autoModeDenialTracking, spec, deps, sessionId, runId, hooks, hookTimeoutMs, notifyOwnHookCrash });
663
681
  const { saga } = prepareSuspendSaga({ gateMachineryActive, abortController, harness, checkpointStore, deps, sessionId, hostTaskId, taskScope, liveSpendRef, resume, nestedStats, internals, activeTools, outputRef, readFileStateForCheckpoint, faceCheckpointSection, reminderMark, handsCwdRef, worktreeSessionRef, inheritedParentConstraints, seedInheritedGate, inheritedAncestorRules, inheritedShellGate, autoModeIntent, autoModeDecider, inheritedAdmittedOrgScopes, ownOrgVerdictRef, orgGovernedProvenance, announcedListingsRef, gitStatusRef, hookIdentity, placementRootResolved, externalContentTargetActive, remoteEnvFailures, memoryEngineSession, suspendLoopRef, ownedEnv, incompleteSuspendAdapter });
664
682
  const { suspendForResource, suspendForPlatformLimit, suspendForReview } = prepareBoundaryParks({ saga, spec, checkpointStore, abortController, harness, session, sessions, sessionId, deps, priorLedger, maxSlices, maxSuspends, resourceTotal, priorSuspendCount, suspendChainBase, humanReviewRef, liveSpendRef, now, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, pausedRef, remoteEnvFailures, resourceSuspendEligible, durableSuspendInfraReady, incompleteSuspendAdapter });
665
- const { parkAsk } = prepareParkAsk({ askLane, saga, spec, deps, sessionId, checkpointStore, parkLaneArmed, contentAskRoutable, liveQuestionFace, mountedQuestionTool, contentAskBindings, lateStrandedAnswers, discloseStrandedAnswers, onAsk, runtimeCaps, inheritedUnavailableAsks, basePolicyForResumeEdit, budgetSnapshot, handsCwdRef, offloadStore, ownedEnv, incompleteSuspendAdapter, session, sessions, suspendChainBase, maxSuspends, remoteEnvFailures, shellGatedBash, shellGatedMonitor, effectiveShellGate, durableApproval, priorLedger, liveSpendRef, resourceTotal, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, ruleOffersOf, now, humanReviewRef, abortController, harness, pausedRef });
666
- prepareGateStations({ askLane, parkAsk, tools, toolEffects, deps, toolCallGateArmedRef, effectivePolicy, hooks, egressTools, irreversibleTools, spec, complianceDenies, harness, blockedToolCalls, inheritedAskGrants, inheritedUnavailableAsks, foldAskClasses, ancestorSandboxAdmissions, preToolContexts, gateOutcomes, batchHaltRef, blockedTracked, hookIdentity, reminderMark, planModeRef, hostTaskId, sessionId, ownGatePreToolUse, hookTimeoutMs, handsCwdRef, hookEnvFace, irreversibilityTier, reversibilityProbes, abortController, shellGatedBash, shellGatedMonitor, autoModeDecider, autoModeDenialTracking, stopForDenialLimit, permissionRuleLane, permissionRuleOrgLane, questionToolMounted, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, delegation, notifyOwnHookCrash });
683
+ const { parkAsk } = prepareParkAsk({ askLane, saga, spec, deps, sessionId, checkpointStore, toolRosterDeltas: rosterSeat, parkLaneArmed, contentAskRoutable, liveQuestionFace, mountedQuestionTool, contentAskBindings, lateStrandedAnswers, discloseStrandedAnswers, onAsk, runtimeCaps, inheritedUnavailableAsks, basePolicyForResumeEdit, budgetSnapshot, handsCwdRef, offloadStore, ownedEnv, incompleteSuspendAdapter, session, sessions, suspendChainBase, maxSuspends, remoteEnvFailures, shellGatedBash, shellGatedMonitor, effectiveShellGate, durableApproval, priorLedger, liveSpendRef, resourceTotal, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, ruleOffersOf, now, humanReviewRef, abortController, harness, pausedRef });
684
+ prepareGateStations({ askLane, parkAsk, tools, toolRosterDeltas: rosterSeat, toolEffects, deps, toolCallGateArmedRef, effectivePolicy, hooks, egressTools, irreversibleTools, spec, complianceDenies, harness, blockedToolCalls, inheritedAskGrants, inheritedUnavailableAsks, foldAskClasses, ancestorSandboxAdmissions, preToolContexts, gateOutcomes, batchHaltRef, blockedTracked, hookIdentity, reminderMark, planModeRef, hostTaskId, sessionId, ownGatePreToolUse, hookTimeoutMs, handsCwdRef, hookEnvFace, irreversibilityTier, reversibilityProbes, abortController, shellGatedBash, shellGatedMonitor, autoModeDecider, autoModeDenialTracking, stopForDenialLimit, permissionRuleLane, permissionRuleOrgLane, questionToolMounted, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, delegation, notifyOwnHookCrash });
667
685
  const { microCompact, charsPerToken } = prepareContextLane({ model, deps, sessionId, runId, hostTaskId, offloadStore, microCompactKnob, harness, batchHaltRef, gitStatusRef, requestLossyRef, trimPressureRef });
668
686
  const preparedHolder = {};
669
687
  const { cacheBreakDetector, cacheFingerprint, promptOverheadTokens, readTaskFile, normalizeAttachmentPath, isDedupStubResult, recentlyReadFiles, onCompactionApplied, detectExternalChanges, listBackgroundTasks, centerCompactionCandidate, effectiveReadFaceObserved, effectiveReadDenyObserved } = prepareTurnWiring({ deps, systemPromptSeat, harnessTools, fpRef, model, epochArtifactDigestForSnapshot, promptProfile, fableMitigations, systemBlocks, thinking, spec, sessionId, turnSnapshotRef, promptManifest, charsPerToken, handsEnabled, executionEnv, attachmentRootCanonical, additionalRootsCanonical, additionalReadRootsCanonical, readDenyMatcher, resolvedReadFace, handsCwdRef, readFileStateForCheckpoint, denyNarrowingPolicy, abortController, hostTaskId, taskScope, buildAssembleInputs, centerAdoptionRef, harnessRef, epochDeclaredSections, carrierReadFace, readDenyAdditionsNormalized, preparedHolder });
670
688
  await onceLedger.settle(session, announcedListingsRef);
671
- const buildPrepared = () => ({ harness, session, sessionId, runId, reminderMark, reminderDisclosureCounts, editedFilesSnapshot, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp, ...(a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, gateOutcomes, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(fileHistoryBoundary !== undefined ? { fileHistoryBoundary } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), ...(sealReadStateSeat !== undefined ? { sealReadStateSeat } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, pausedRef, suspendProgressRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, gateStopRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, microCompact, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, stopRequestedRef, announcedSnapshotRecovered: onceLedger.recovered, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(projectInstructionContent !== undefined ? { projectInstructionContent } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, gitStatusRef, listBackgroundTasks, hookIdentity, hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
689
+ const buildPrepared = () => ({ harness, session, sessionId, runId, reminderMark, reminderDisclosureCounts, editedFilesSnapshot, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp, ...(a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, gateOutcomes, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(fileHistoryBoundary !== undefined ? { fileHistoryBoundary } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), ...(sealReadStateSeat !== undefined ? { sealReadStateSeat } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, pausedRef, suspendProgressRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, gateStopRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolRoster, toolRosterDeltas: rosterSeat, structuredProjector, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, microCompact, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, stopRequestedRef, announcedSnapshotRecovered: onceLedger.recovered, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(projectInstructionContent !== undefined ? { projectInstructionContent } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, gitStatusRef, listBackgroundTasks, hookIdentity, hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
672
690
  rollback.commit();
673
691
  const prepared = buildPrepared();
674
692
  preparedHolder.current = prepared;
@@ -36,7 +36,7 @@ export interface PrepareToolDisclosureMountInput {
36
36
  deferred: ReadonlySet<string>;
37
37
  /** borrowed-readonly — the run's shared roster (`Prepared.tools`): READ here — the registry build, the reserved-name
38
38
  * check, the live resolution the placeholders and the static face perform at call time. Never written by this phase. */
39
- tools: AgentTool[];
39
+ tools: readonly AgentTool[];
40
40
  /** borrowed-readonly — the once-per-session ledger's failed-server projection (current failures minus the pairs this
41
41
  * session already announced). */
42
42
  onceLedger: {
@@ -87,7 +87,10 @@ export interface PrepareToolDisclosureMountResult {
87
87
  };
88
88
  /** owned — the list the harness is built from: the roster with placeholders in the deferred slots plus ToolSearch (or the
89
89
  * roster itself when nothing is deferred). */
90
- harnessTools: AgentTool[];
90
+ harnessTools: readonly AgentTool[];
91
+ /** owned — the ToolSearch instance the deferred arm appended to the harness list (absent when nothing is deferred): the
92
+ * driver mounts it on the roster's ENGINE LANE so the roster carries every name the model can call. */
93
+ toolSearch?: AgentTool;
91
94
  /** owned — the boundary delta ref (`Prepared.toolsDeltaRef`), minted when anything is deferred OR a server failed. */
92
95
  toolsDeltaRef: Prepared["toolsDeltaRef"];
93
96
  /** owned — the effective strategy (`Prepared.toolMaterializeStatic`). */
@@ -31,6 +31,7 @@ export async function prepareToolDisclosureMount(input) {
31
31
  const fpRef = {};
32
32
  const turnSnapshotRef = {};
33
33
  let harnessTools = tools;
34
+ let toolSearchMounted;
34
35
  const failedMcpServers = onceLedger.mcpFailuresToAnnounce(mcp.statuses);
35
36
  let toolsDeltaRef;
36
37
  let toolMaterializeStatic = false;
@@ -204,6 +205,7 @@ export async function prepareToolDisclosureMount(input) {
204
205
  serializeActivation,
205
206
  });
206
207
  harnessTools = buildToolList(activeTools);
208
+ toolSearchMounted = toolSearch;
207
209
  }
208
210
  else {
209
211
  rebuildHarnessToolsRef.current = async () => {
@@ -215,5 +217,5 @@ export async function prepareToolDisclosureMount(input) {
215
217
  turnSnapshotRef.current?.refreshTools(toolsToFingerprintInputs(list));
216
218
  };
217
219
  }
218
- return { activeTools, fpRef, turnSnapshotRef, harnessTools, toolsDeltaRef, toolMaterializeStatic, deferDirectCall, staticFaceForRef, listingRideRef };
220
+ return { activeTools, fpRef, turnSnapshotRef, harnessTools, toolsDeltaRef, toolMaterializeStatic, deferDirectCall, staticFaceForRef, listingRideRef, ...(toolSearchMounted !== undefined ? { toolSearch: toolSearchMounted } : {}) };
219
221
  }
@@ -29,6 +29,7 @@ import { type OnAsk, type ToolPolicy } from "../tool-policy.js";
29
29
  import type { GateOutcome } from "../gate-outcome.js";
30
30
  import type { RunnerDeps, RuntimeCaps, TaskSpec, ToolEffect, ToolExecuteContext } from "../types.js";
31
31
  import { type UsageWindow } from "../usage-window-store.js";
32
+ import type { ToolRoster } from "../tool-roster.js";
32
33
  import { type AutoModeArmReason, type WiringManifest, type WiringManifestMcpEntry } from "../wiring-manifest.js";
33
34
  import type { AnnounceOnceSink, Prepared, PrepareResume, RunInternals, UsageGovernance } from "./contracts.js";
34
35
  export interface PrepareWiringManifestInput {
@@ -102,6 +103,9 @@ export interface PrepareWiringManifestInput {
102
103
  sessions: SessionStore;
103
104
  /** borrowed-readonly — the model gate's per-leg read face, or undefined. */
104
105
  modelGateManifest: WiringManifest["modelGate"];
106
+ /** borrowed-readonly — the leg's minted tool roster (design/388): rides the manifest's `tools` section and is the
107
+ * source of the hand band's write-capable names for the ungated-write warning (what actually MOUNTED). */
108
+ toolRoster: ToolRoster;
105
109
  /** borrowed-readonly — the arming outcome the caps phase minted (the manifest's auto-mode face). */
106
110
  autoModeArmReason: AutoModeArmReason;
107
111
  /** borrowed-readonly — the frozen preflight; only `mcp` (the declared servers the manifest face joins). */
@@ -1,5 +1,4 @@
1
1
  import { bindPeerLaneDrain } from "../../agents/peer-session-drain.js";
2
- import { HAND_TOOL_EFFECTS } from "../../tools/fs/index.js";
3
2
  import { resolveCheckpointStore } from "../checkpoint-store.js";
4
3
  import { createHookEnvCapabilities, mintHookInvocationIdentity } from "../hooks.js";
5
4
  import { isRemoteExecutionEnv, missingRestoreSurface } from "../remote-env.js";
@@ -109,14 +108,9 @@ function announceDurableGateUnavailable(args) {
109
108
  });
110
109
  }
111
110
  export function prepareWiringManifest(input) {
112
- const { spec, deps, internals, resume, sessionId, runId, hostTaskId, taskScope, hooks, preToolUseObservational, policyLayers, effectivePolicy, handsEnabled, mcp, irreversibleTools, egressTools, toolEffects, ownedEnv, executionEnv, offloadStore, onceLedger, usageWindows, envLifetimeSuspendAt, runtimeCaps, resolvedInteractionPosture, durableQuestionFace, liveQuestionFace, checkpointStore, sessions, modelGateManifest, autoModeArmReason, lockedPreflight, peerLaneRefs, peerLaneActive, harness, pausedRef, frozenOnAsk, liveInheritedGate, delegation } = input;
111
+ const { spec, deps, internals, resume, sessionId, runId, hostTaskId, taskScope, hooks, preToolUseObservational, policyLayers, effectivePolicy, mcp, irreversibleTools, egressTools, toolEffects, ownedEnv, executionEnv, offloadStore, onceLedger, usageWindows, envLifetimeSuspendAt, runtimeCaps, resolvedInteractionPosture, durableQuestionFace, liveQuestionFace, checkpointStore, sessions, modelGateManifest, autoModeArmReason, lockedPreflight, peerLaneRefs, peerLaneActive, harness, pausedRef, frozenOnAsk, liveInheritedGate, delegation, toolRoster } = input;
113
112
  const onAsk = spec.onAsk ?? deps.onAsk;
114
- const handWriteTools = handsEnabled && spec.handsReadOnly !== true
115
- ? Object.keys(HAND_TOOL_EFFECTS).filter((name) => {
116
- const eff = HAND_TOOL_EFFECTS[name];
117
- return eff === "write" || eff === "idempotent";
118
- })
119
- : [];
113
+ const handWriteTools = toolRoster.entries.filter((e) => e.mountedBy === "hands" && (e.effect === "write" || e.effect === "idempotent")).map((e) => e.name);
120
114
  const hasEffectAwareGate = Boolean(policyLayers.length > 0 || (hooks?.preToolUse !== undefined && !preToolUseObservational));
121
115
  const destructiveMcpUngated = mcp.tools.some((t) => !irreversibleTools.has(t.name) && !egressTools.has(t.name) && (toolEffects.get(t.name) ?? "write") !== "read");
122
116
  const firstPartyWriteUngated = (spec.tools ?? [])
@@ -207,6 +201,7 @@ export function prepareWiringManifest(input) {
207
201
  ...(modelGateManifest !== undefined ? { modelGate: modelGateManifest } : {}),
208
202
  autoMode: { armed: autoModeArmReason === "armed", reason: autoModeArmReason },
209
203
  mcp: mcpManifestEntries(lockedPreflight.mcp, mcp.statuses),
204
+ tools: toolRoster,
210
205
  });
211
206
  peerLaneRefs.askEffective = wiringManifest.ask.effective;
212
207
  if (peerLaneActive && internals?.onTaskNotification !== undefined) {
@@ -0,0 +1,46 @@
1
+ import type { AgentHarnessEvent } from "../../internal/harness.js";
2
+ import type { Stats } from "./assemble-result.js";
3
+ import type { HarnessHandlersDeps, Prepared, RunState } from "./contracts.js";
4
+ /**
5
+ * What the driver hands the harness-handlers lane, once per leg — the four positional parameters the
6
+ * factory used to take, as one carrier of REFERENCES (the handlers read the seats live on every event).
7
+ */
8
+ export interface RunHarnessHandlersInput {
9
+ /** borrowed-mutable — the leg's prepared seat. Read throughout; written only into one per-run ref cell
10
+ * (`callIssuedAtRef.current`, cleared when the model's turn ends). */
11
+ prepared: Prepared;
12
+ /** borrowed-mutable — the run's usage counters. This lane is their WRITER: the turn's usage lands on
13
+ * them in onMessageEnd, tool calls and turns are counted in onToolStart / onTurnEnd. */
14
+ stats: Stats;
15
+ /** borrowed-mutable — the run's mutable state; the handlers write the turn / counters / limits /
16
+ * budget groups, per the writer table on the type. */
17
+ rs: RunState;
18
+ /** borrowed-readonly — the driver-declared const seats + the runner's onError, see
19
+ * {@link HarnessHandlersDeps}. */
20
+ deps: HarnessHandlersDeps;
21
+ }
22
+ /** The five harness event handlers + the workspace-move mint, as the driver destructures them. */
23
+ export interface RunHarnessHandlersResult {
24
+ onMessageUpdate: (event: Extract<AgentHarnessEvent, {
25
+ type: "message_update";
26
+ }>) => void;
27
+ onMessageEnd: (event: Extract<AgentHarnessEvent, {
28
+ type: "message_end";
29
+ }>) => void;
30
+ onToolStart: (event: Extract<AgentHarnessEvent, {
31
+ type: "tool_execution_start";
32
+ }>) => void;
33
+ onToolEnd: (event: Extract<AgentHarnessEvent, {
34
+ type: "tool_execution_end";
35
+ }>) => void;
36
+ onTurnEnd: () => void;
37
+ /** RB-475 — the single workspace_changed mint (cwd diff vs the closure's dedupe state); the run body
38
+ * calls it after a resume's approved-call resolution, which bypasses onToolEnd. */
39
+ announceWorkspaceMove: () => void;
40
+ }
41
+ /** design/157 B15 尾件 — R5 抽取:runLocked 的五个 harness 事件处理器(onMessageUpdate/onMessageEnd/
42
+ * onToolStart/onToolEnd/onTurnEnd,~530 行)整块搬迁。正文逐字;唯一字面变化 = this.deps.onError →
43
+ * deps.runnerHooks.onError(1 处)。`lastWorkspaceCwd` 是纯 R5 局部(读写全在 onToolEnd 内,零段外
44
+ * 读者),声明随迁进工厂体——调用点放在原声明位之后,初值 `prepared.cwdRef?.current` 的求值时机不变
45
+ * (中间只有声明无执行)。*/
46
+ export declare function createHarnessHandlers(input: RunHarnessHandlersInput): RunHarnessHandlersResult;