@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
@@ -6,6 +6,7 @@ import type { CwdRef } from "../../tools/fs/index.js";
6
6
  import { type ToolResultStore } from "../tool-result-store.js";
7
7
  import type { RunnerDeps, TaskSpec, ToolEffect, ToolExecuteContext } from "../types.js";
8
8
  import type { PrepareResume, RunInternals, RunnerSelfSeat, ToolFaceSnapshot } from "./contracts.js";
9
+ import { RosterBuilder } from "../tool-roster.js";
9
10
  export interface PrepareDelegationSurfaceInput {
10
11
  /** borrowed-readonly — the REBOUND spec. Read: `tools` (the shadow checks and the revival spawner's delegation-tool
11
12
  * lookup), `oneShot` (the read/kill faces' completion promises), `retainBackgroundProcesses` (the Monitor receipt),
@@ -44,10 +45,10 @@ export interface PrepareDelegationSurfaceInput {
44
45
  /** borrowed-mutable — the lane's late-bound facts; the SendMessage mount hands the SAME object to its peer seats (identity is
45
46
  * the contract: the wiring station fills `askEffective` later). Not written here. */
46
47
  peerLaneRefs: PeerLaneRefs;
47
- /** borrowed-mutable — the run's shared roster (`Prepared.tools`, whose JSDoc carries the writer table). Writers here: the
48
- * eight mounts (TaskOutput, TaskStop, SendMessage, AgentTranscript, ListAgents, Monitor, the worktree pair, the offload
49
- * reader), every one a push. */
50
- tools: AgentTool[];
48
+ /** borrowed-mutable — the run's roster builder (`Prepared.tools` is its read face; the writer table is on that field).
49
+ * Writers here: the eight mounts (TaskOutput, TaskStop, SendMessage, AgentTranscript, ListAgents, Monitor, the worktree
50
+ * pair, the offload reader), every one a `mount`. */
51
+ roster: RosterBuilder;
51
52
  /** borrowed-mutable — the effect collection (`Prepared.toolEffects`). Writers here: TaskOutput → read, TaskStop / SendMessage
52
53
  * (built-in mount only) / Monitor / EnterWorktree / ExitWorktree → write. */
53
54
  toolEffects: Map<string, ToolEffect>;
@@ -8,6 +8,7 @@ import { createTaskOutputTool, createTaskStopTool, defaultTaskRegistry } from ".
8
8
  import { createReadToolResultTool } from "../tool-result-store.js";
9
9
  import { emitTrace } from "../trace.js";
10
10
  import { deliverEngineNotice } from "../types.js";
11
+ import { RosterBuilder } from "../tool-roster.js";
11
12
  function buildTier3ReviveSpawn(delegationForRevive, enrichSpecToolCtx) {
12
13
  if (delegationForRevive === undefined)
13
14
  return undefined;
@@ -29,21 +30,21 @@ function buildTier3ReviveSpawn(delegationForRevive, enrichSpecToolCtx) {
29
30
  };
30
31
  }
31
32
  export function prepareDelegationSurface(input) {
32
- const { 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 } = input;
33
+ const { 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 } = input;
33
34
  let worktreeSessionRef;
34
35
  const delegationSurfaceActive = backgroundTaskToolsActive || workflowToolsActive;
35
36
  if (delegationSurfaceActive || internals?.parentNotify !== undefined || peerLaneActive) {
36
37
  if (delegationSurfaceActive) {
37
- toolEffects.set("TaskOutput", "read");
38
- toolEffects.set("TaskStop", "write");
39
- tools.push(firstPartyOffload(createTaskOutputTool({ registry: defaultTaskRegistry, owner: hostTaskId, scope: taskScope, sessionId, workflowStore: deps.workflowRunStore, agentStore: deps.backgroundAgentStore, notificationWired: internals?.onTaskNotification !== undefined, oneShot: spec.oneShot, toolResultStore: offloadStore })), firstPartyOffload(createTaskStopTool({ registry: defaultTaskRegistry, owner: hostTaskId, scope: taskScope, sessionId, workflowStore: deps.workflowRunStore, agentStore: deps.backgroundAgentStore })));
38
+ roster.mountAll([
39
+ firstPartyOffload(createTaskOutputTool({ registry: defaultTaskRegistry, owner: hostTaskId, scope: taskScope, sessionId, workflowStore: deps.workflowRunStore, agentStore: deps.backgroundAgentStore, notificationWired: internals?.onTaskNotification !== undefined, oneShot: spec.oneShot, toolResultStore: offloadStore })),
40
+ firstPartyOffload(createTaskStopTool({ registry: defaultTaskRegistry, owner: hostTaskId, scope: taskScope, sessionId, workflowStore: deps.workflowRunStore, agentStore: deps.backgroundAgentStore })),
41
+ ], { source: "builtin", mountedBy: "delegation" });
40
42
  }
41
43
  if (runnerSelf && !(spec.tools ?? []).some((t) => t.name === SEND_MESSAGE_TOOL_NAME)) {
42
- toolEffects.set(SEND_MESSAGE_TOOL_NAME, "write");
43
44
  axisExplicitNegatives.set(SEND_MESSAGE_TOOL_NAME, { ...axisExplicitNegatives.get(SEND_MESSAGE_TOOL_NAME), egress: false });
44
45
  const delegationForRevive = (spec.tools ?? []).find((t) => t.agentListing !== undefined);
45
46
  const reviveSpawn = deps.backgroundAgentStore !== undefined && deps.mailboxStore !== undefined ? buildTier3ReviveSpawn(delegationForRevive, enrichSpecToolCtx) : undefined;
46
- tools.push(firstPartyOffload(createSendMessageTool({
47
+ roster.mount(firstPartyOffload(createSendMessageTool({
47
48
  runner: runnerSelf,
48
49
  registry: defaultTaskRegistry,
49
50
  ...(subagentRetain ? { retain: subagentRetain } : {}),
@@ -76,9 +77,9 @@ export function prepareDelegationSurface(input) {
76
77
  message: `delegation transcript integrity: agent ${handle}'s durable row binds a transcript session the session store attests is gone — the declared transcript durability is being contradicted (check the session store wiring/retention)`,
77
78
  detail: { handle, ...(scope !== undefined ? { scope } : {}) },
78
79
  }),
79
- })));
80
+ })), { source: "builtin", mountedBy: delegationSurfaceActive ? "delegation" : internals?.parentNotify !== undefined ? "hostInternals" : "peerLane" });
80
81
  if (delegationSurfaceActive && !(spec.tools ?? []).some((t) => t.name === AGENT_TRANSCRIPT_TOOL_NAME)) {
81
- tools.push(firstPartyOffload(createAgentTranscriptTool({
82
+ roster.mount(firstPartyOffload(createAgentTranscriptTool({
82
83
  runner: runnerSelf,
83
84
  registry: defaultTaskRegistry,
84
85
  agentStore: deps.backgroundAgentStore,
@@ -91,17 +92,16 @@ export function prepareDelegationSurface(input) {
91
92
  message: `delegation transcript integrity: agent ${handle}'s durable row binds a transcript session the session store attests is gone — the declared transcript durability is being contradicted (check the session store wiring/retention)`,
92
93
  detail: { handle, ...(scope !== undefined ? { scope } : {}) },
93
94
  }),
94
- })));
95
+ })), { source: "builtin", mountedBy: "delegation" });
95
96
  }
96
97
  }
97
98
  }
98
99
  const listAgents = peerLaneActive && peerSendMessageBuiltIn && listAgentsMountable({ exclude: toolFaceSnapshot.exclude, specTools: spec.tools ?? [] }) ? mountListAgents({ peerDirectory: deps.peerDirectory, sessionId, scope: taskScope, hostTaskId, ...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}), ...(internals?.parentSessionId !== undefined ? { parentSessionId: internals.parentSessionId } : {}), registry: defaultTaskRegistry, ...(deps.rosterStore !== undefined ? { roster: deps.rosterStore } : {}), specTools: spec.tools ?? [], toolEffects }) : undefined;
99
100
  if (listAgents !== undefined)
100
- tools.push(firstPartyOffload(listAgents));
101
+ roster.mount(firstPartyOffload(listAgents), { source: "builtin", mountedBy: "peerLane" });
101
102
  if (backgroundTaskToolsActive) {
102
- toolEffects.set("Monitor", "write");
103
103
  envHandToolNames.add("Monitor");
104
- tools.push(firstPartyOffload(createMonitorTool(executionEnv, {
104
+ roster.mount(firstPartyOffload(createMonitorTool(executionEnv, {
105
105
  registry: defaultTaskRegistry,
106
106
  owner: hostTaskId,
107
107
  scope: taskScope,
@@ -110,11 +110,9 @@ export function prepareDelegationSurface(input) {
110
110
  ...(handsCwdRef !== undefined ? { cwdRef: handsCwdRef } : {}),
111
111
  ...(offloadStore !== undefined ? { toolResultStore: offloadStore } : {}),
112
112
  ...(spec.retainBackgroundProcesses === true ? { retainBackgroundProcesses: true } : {}),
113
- })));
113
+ })), { source: "builtin", mountedBy: "backgroundTasks" });
114
114
  }
115
115
  if (handsCwdRef !== undefined) {
116
- toolEffects.set("EnterWorktree", "write");
117
- toolEffects.set("ExitWorktree", "write");
118
116
  worktreeSessionRef = {
119
117
  ...(resume?.seed.activeWorktree
120
118
  ? {
@@ -136,9 +134,9 @@ export function prepareDelegationSurface(input) {
136
134
  };
137
135
  if (workspaceStateSettle !== undefined)
138
136
  workspaceStateSettle.restoredWorktreeDir = worktreeSessionRef.current?.worktreeDir;
139
- tools.push(...createWorktreeTools(executionEnv, { repoRoot: taskRootFinal, cwdRef: handsCwdRef, session: worktreeSessionRef }).map((t) => (envHandToolNames.add(t.name), firstPartyOffload(t))));
137
+ roster.mountAll(createWorktreeTools(executionEnv, { repoRoot: taskRootFinal, cwdRef: handsCwdRef, session: worktreeSessionRef }).map((t) => (envHandToolNames.add(t.name), firstPartyOffload(t))), { source: "builtin", mountedBy: "hands" });
140
138
  }
141
139
  if (offloadStore)
142
- tools.push(createReadToolResultTool(offloadStore));
140
+ roster.mount(createReadToolResultTool(offloadStore), { source: "builtin", mountedBy: "optional" });
143
141
  return { delegationSurfaceActive, worktreeSessionRef };
144
142
  }
@@ -33,6 +33,7 @@ import type { CwdRef } from "../../tools/fs/fs-shared.js";
33
33
  import type { AskLane, IrreversibilityTier, ParkAsk, Prepared, ReversibilityProbes } from "./contracts.js";
34
34
  import type { StopForDenialLimit } from "./denial-limit-arms.js";
35
35
  import type { PermissionRuleLanes } from "./permission-rule-lanes.js";
36
+ import { type ToolRosterDeltaSeat } from "../tool-roster.js";
36
37
  export interface PrepareGateStationsInput {
37
38
  /** borrowed-readonly — the in-stream ask lane, or undefined when the gate machinery is inactive (then no `tool_call`
38
39
  * station is built). Read: `adjudicate`, `resolveAskBound`, `notifyPermissionDenied`, `notifyHookError`. */
@@ -41,7 +42,9 @@ export interface PrepareGateStationsInput {
41
42
  parkAsk: ParkAsk | undefined;
42
43
  /** borrowed-readonly — the FINAL mount roster: iterated once for the effect sync and the write-protection arming. Never
43
44
  * mutated here. */
44
- tools: AgentTool[];
45
+ tools: readonly AgentTool[];
46
+ /** borrowed-readonly — the leg's delta seat (design/388): its current roster is the face source for every gated call. */
47
+ toolRosterDeltas: ToolRosterDeltaSeat;
45
48
  /** borrowed-mutable — the effect collection. Writer here: the final-roster sync fills MISSING entries from each
46
49
  * first-party tool's declared effect (never overrides a scanned one); the plan-mode deny reads it per call. */
47
50
  toolEffects: Map<string, ToolEffect>;
@@ -7,6 +7,7 @@ import { directDeny } from "./gate-exit.js";
7
7
  import { emitTrace } from "../trace.js";
8
8
  import { createWriteProtectionCheck } from "../write-protect.js";
9
9
  import { PATH_CONFINABLE_WRITE_TOOLS } from "./session-rule-policy.js";
10
+ import { toolCallFaceOf } from "../tool-roster.js";
10
11
  function screenGateResult(result) {
11
12
  if (result.suspend !== undefined)
12
13
  return { defects: [] };
@@ -39,7 +40,7 @@ function maybeHumanRejectionHalt(input) {
39
40
  };
40
41
  }
41
42
  export function prepareGateStations(input) {
42
- const { 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 } = input;
43
+ const { askLane, parkAsk, tools, toolRosterDeltas, 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 } = input;
43
44
  if (askLane !== undefined && parkAsk !== undefined) {
44
45
  const { adjudicate, resolveAskBound, notifyPermissionDenied, notifyHookError } = askLane;
45
46
  const { resolveContentAsk, suspendAsk } = parkAsk;
@@ -103,6 +104,7 @@ export function prepareGateStations(input) {
103
104
  hookTimeoutMs,
104
105
  ...(handsCwdRef !== undefined ? { trackedCwd: () => handsCwdRef.current } : {}),
105
106
  ...(hookEnvFace !== undefined ? { hookEnv: hookEnvFace } : {}),
107
+ toolFaceOf: (name) => toolCallFaceOf(toolRosterDeltas.current, name),
106
108
  adjudicate,
107
109
  resolveAsk: resolveAskBound,
108
110
  suspendAsk,
@@ -5,6 +5,7 @@ import type { Model } from "../../internal/llm.js";
5
5
  import type { AgentTool, ExecutionEnv, WorkspaceState } from "../../internal/harness.js";
6
6
  import type { StoredSession } from "../session.js";
7
7
  import type { IrreversibilityTier, PrepareResume, ReversibilityProbes, RunInternals, ToolFaceSnapshot } from "./contracts.js";
8
+ import { RosterBuilder } from "../tool-roster.js";
8
9
  /** Test seam (mirrors `__resetMalformedNoticeSeatAnnouncement`): never called by production code.
9
10
  * Deliberately asymmetric — it resets only the console latch: the WeakSet arm needs no seam
10
11
  * because a test resets it by minting a fresh sink function (identity IS the ledger key), while
@@ -174,8 +175,8 @@ export interface PrepareHandsReadFaceInput {
174
175
  * contract, never map/copy). This phase pushes the hands band IN PLACE (after caller tools /
175
176
  * built-ins / MCP / A2A, before the background-task mounts — T22 order). Mutation owners after
176
177
  * this phase: P11 background/Monitor/worktree pushes, the driver's excludeTools splice, the
177
- * refresh seam's runtime splice/push. */
178
- tools: AgentTool[];
178
+ * refresh seam's runtime splice/push. Since design/388 the array is builder-private: this phase `mountAll`s the band. */
179
+ roster: RosterBuilder;
179
180
  /** borrowed-readonly HERE (census divergence #5 vs the design row): this phase only reads
180
181
  * `.has("Bash")`/`.has("Monitor")` for the provenance bits — its mutation owners are B-2/P8/P9
181
182
  * (+refresh seam)/P11/P22, per the B-2 Result's mutation-owner table. */
@@ -8,6 +8,7 @@ import { normalizeFileText } from "../../tools/fs/encoding.js";
8
8
  import { wholeFileRecordsFromTranscript } from "./session-file-state-replay.js";
9
9
  import { deliverEngineNotice } from "../types.js";
10
10
  import { rebaseWorkspacePath } from "./workspace-path.js";
11
+ import { RosterBuilder } from "../tool-roster.js";
11
12
  const readFaceClampAnnouncedSinks = new WeakSet();
12
13
  let readFaceClampConsoleAnnounced = false;
13
14
  export function __resetReadFaceClampAnnouncement() {
@@ -123,7 +124,7 @@ export function resolveHandsLessReadFace(input) {
123
124
  }
124
125
  export async function prepareHandsMount(input) {
125
126
  const envHandToolNames = new Set();
126
- const { handsEnabled, executionEnv, taskRootFinal, rebaseRestoredPath, resume, session, sessionId, hostTaskId, taskScope, fullShellReachable, effectiveShellGate, toolFaceSnapshot, model, spec, deps, internals, memoryWriteGateRef, firstPartyOffload, tools, egressTools, irreversibilityTier, irreversibleTools, reversibilityProbes } = input;
127
+ const { handsEnabled, executionEnv, taskRootFinal, rebaseRestoredPath, resume, session, sessionId, hostTaskId, taskScope, fullShellReachable, effectiveShellGate, toolFaceSnapshot, model, spec, deps, internals, memoryWriteGateRef, firstPartyOffload, roster, egressTools, irreversibilityTier, irreversibleTools, reversibilityProbes } = input;
127
128
  assertStringArraySeat(spec.additionalDirectories, "TaskSpec.additionalDirectories", "strings");
128
129
  assertStringArraySeat(spec.additionalReadDirectories, "TaskSpec.additionalReadDirectories", "strings");
129
130
  assertStringArraySeat(deps.readDenyPatterns, "RunnerDeps.readDenyPatterns", "compiler-owned");
@@ -351,7 +352,7 @@ export async function prepareHandsMount(input) {
351
352
  deps.onError?.(new Error(`spec.tools ${shadowed.length > 1 ? "entries" : "entry"} ${names} collide${shadowed.length > 1 ? "" : "s"} with the built-in hands tool band — the built-in wins (registered later; the harness tool Map is last-write-wins) and the caller tool never dispatches this task. Rename the caller tool, or don't inject executionEnv if you intend to replace the band.`), { phase: "config", sessionId });
352
353
  }
353
354
  }
354
- tools.push(...band.map((t) => (envHandToolNames.add(t.name), firstPartyOffload(t))));
355
+ roster.mountAll(band.map((t) => (envHandToolNames.add(t.name), firstPartyOffload(t))), { source: "builtin", mountedBy: "hands" });
355
356
  const shellGate = effectiveShellGate;
356
357
  if (shellGate === "off" && !(executionEnv instanceof StubExecutionEnv) && spec.handsReadOnly !== true) {
357
358
  deps.onError?.(new Error(`shell gate doctrine is "off" while a real writable shell (Bash) is mounted — no shell safety-axis ` +
@@ -53,7 +53,7 @@ export interface PrepareInheritedGateInput {
53
53
  * / `oneShot` / `clientContext` / `principal` / `additionalDirectories` / `additionalReadDirectories` / `envFacts` /
54
54
  * `memoryPersistenceCapable` / `getApiKeyAndHeaders` / `checkpointStore` / `backgroundScope` / `enablePlanMode` (the ctx
55
55
  * enrichment's trusted seats). Never mutated. */
56
- spec: Pick<TaskSpec, "shellGate" | "durableApproval" | "onAsk" | "onQuestion" | "handsReadOnly" | "interactiveTools" | "oneShot" | "clientContext" | "principal" | "additionalDirectories" | "additionalReadDirectories" | "envFacts" | "memoryPersistenceCapable" | "getApiKeyAndHeaders" | "checkpointStore" | "backgroundScope" | "enablePlanMode">;
56
+ spec: Pick<TaskSpec, "shellGate" | "durableApproval" | "onAsk" | "onQuestion" | "handsReadOnly" | "interactiveTools" | "oneShot" | "clientContext" | "principal" | "additionalDirectories" | "additionalReadDirectories" | "envFacts" | "memoryPersistenceCapable" | "getApiKeyAndHeaders" | "checkpointStore" | "backgroundScope" | "enablePlanMode" | "excludeAllTools">;
57
57
  /** borrowed-readonly — the deployment seats: `autoMode.denialLimit` (the rebuilt trackers' bounds), `memoryScopeAdmission` /
58
58
  * `deploymentMemoryScopes` / `compliancePostureResolver` (the governed-provenance bit), `onError` (the screening-face crash
59
59
  * lane), `onNotice` (the stop seat's notice), `executionEnv` (the hook-env identity predicate), `delegationEntryCaps` (resolved
@@ -162,6 +162,7 @@ export function prepareInheritedGate(input) {
162
162
  const fullShellReachable = handsEnabled &&
163
163
  !(executionEnv instanceof StubExecutionEnv) &&
164
164
  spec.handsReadOnly !== true &&
165
+ spec.excludeAllTools !== true &&
165
166
  !(toolFaceSnapshot.exclude?.includes("Bash") ?? false);
166
167
  const { gateStopRef, stopForDenialLimit } = createDenialLimitStop({ sessionId, runId, onNotice: deps.onNotice, abort: abortRun });
167
168
  const delegationEntryCapsResolved = resolveDelegationEntryCaps(deps.delegationEntryCaps);
@@ -11,10 +11,11 @@
11
11
  * with no await is extracted as a sync function — an `async` wrapper would open a microtask window).
12
12
  * Read-stability of the host handles for the call: {@link RunInternals} (`@contract prepare.deps-read-stable`).
13
13
  */
14
- import type { AgentTool, ExecutionEnv } from "../../internal/harness.js";
14
+ import type { ExecutionEnv } from "../../internal/harness.js";
15
15
  import type { LspDiagnosticsRegistry } from "../lsp-diagnostics.js";
16
16
  import type { RunnerDeps, TaskSpec } from "../types.js";
17
17
  import type { Prepared } from "./contracts.js";
18
+ import { RosterBuilder } from "../tool-roster.js";
18
19
  export interface PrepareLspInput {
19
20
  /** borrowed-readonly — the REBOUND spec. Read: `lspManager` (the per-task manager, outranks the deployment's),
20
21
  * `lspDiagnostics` (the lane's opt-out), `handsReadOnly` (a read-only run has no lane). Never mutated. */
@@ -34,9 +35,9 @@ export interface PrepareLspInput {
34
35
  /** borrowed-readonly — the tracked-cwd cell the hands band writes; the nudge READS `current` per call as its
35
36
  * resolution base (identity is the contract — the band's `cd` handling writes this exact cell). */
36
37
  handsCwdRef: Prepared["cwdRef"];
37
- /** borrowed-mutable — the run's shared roster (identity is the contract). Writer here: ONE push (the LSP tool)
38
+ /** borrowed-mutable — the run's roster builder (identity is the contract). Writer here: ONE mount (the LSP tool)
38
39
  * when a manager exists. */
39
- tools: AgentTool[];
40
+ roster: RosterBuilder;
40
41
  /** borrowed-mutable — the env-hand membership set the read-posture observer projects; writer here: `add("LSP")`
41
42
  * on the mount. */
42
43
  envHandToolNames: Set<string>;
@@ -1,11 +1,12 @@
1
1
  import { createLspTool, gitCheckIgnoreFilter, resolveLspPath } from "../lsp.js";
2
2
  import { pathToUri } from "../lsp-protocol.js";
3
+ import { RosterBuilder } from "../tool-roster.js";
3
4
  export function prepareLsp(input) {
4
- const { spec, deps, executionEnv, taskRootFinal, handsEnabled, sessionId, handsCwdRef, tools, envHandToolNames } = input;
5
+ const { spec, deps, executionEnv, taskRootFinal, handsEnabled, sessionId, handsCwdRef, roster, envHandToolNames } = input;
5
6
  const lspManager = spec.lspManager ?? deps.lspManager;
6
7
  if (lspManager) {
7
8
  const lspRoot = taskRootFinal;
8
- tools.push(createLspTool(lspManager, { isPathIgnored: gitCheckIgnoreFilter(executionEnv, lspRoot), env: executionEnv }));
9
+ roster.mount(createLspTool(lspManager, { isPathIgnored: gitCheckIgnoreFilter(executionEnv, lspRoot), env: executionEnv }), { source: "builtin", mountedBy: "optional" });
9
10
  envHandToolNames.add("LSP");
10
11
  }
11
12
  const lspDiagnostics = spec.lspDiagnostics !== false && lspManager?.diagnostics !== undefined && handsEnabled && spec.handsReadOnly !== true
@@ -16,12 +16,12 @@
16
16
  * One read changed spelling and nothing else: the declared-class announcement read `mcp?.statuses` while `mcp` had
17
17
  * long been the protocol-tools phase's non-null handle; the optional chain was dead and is now the plain member read.
18
18
  */
19
- import type { AgentTool } from "../../internal/harness.js";
20
19
  import type { LockedPreflight } from "../locked-config.js";
21
20
  import type { MaterializedMcp } from "../mcp.js";
22
21
  import { type DelegationContentSafety } from "../memory-engine/delegation-provenance.js";
23
22
  import type { RunnerDeps, TaskSpec } from "../types.js";
24
23
  import type { Prepared, RunInternals } from "./contracts.js";
24
+ import { RosterBuilder } from "../tool-roster.js";
25
25
  export interface PrepareMemoryEngineSessionInput {
26
26
  /** borrowed-readonly — the mounted engine session, or undefined (the ARMING fact's first half). Read: `pollution`
27
27
  * (the mark face), `contentSafety` (the own half of the narrowing), `settlement` (the seat + the receipt ledger),
@@ -64,10 +64,10 @@ export interface PrepareMemoryEngineSessionInput {
64
64
  spec: Pick<TaskSpec, "tools">;
65
65
  /** borrowed-readonly — the env-hand roster (membership minted at the mount sites); read by the classification. */
66
66
  envHandToolNames: ReadonlySet<string>;
67
- /** borrowed-mutable — the run's ONE tool roster. Writer here: the wrap pass REPLACES entries in place
68
- * (`tools[i] = wrapped`, identity-tracked so a re-run never stacks wrappers); see the roster's writer table on
67
+ /** borrowed-mutable — the run's roster builder. Writer here: the wrap pass REPLACES entries in place
68
+ * (`roster.replaceAt(i, wrapped)`, identity-tracked so a re-run never stacks wrappers); see the roster's writer table on
69
69
  * {@link Prepared.tools}. */
70
- tools: AgentTool[];
70
+ roster: RosterBuilder;
71
71
  /** borrowed-mutable — the re-runnable wrap pass's seat (the protocol-tools phase mints it; the rebuild seam re-runs it
72
72
  * after a refresh re-splice). Writer: THIS phase installs the pass and runs it once. */
73
73
  contentOriginWrapRef: {
@@ -5,8 +5,10 @@ import { protocolOf } from "../protocol-table.js";
5
5
  import { deliverEngineNotice } from "../types.js";
6
6
  import { inlineUntrusted } from "../untrusted-text.js";
7
7
  import { fileArgPath } from "../../tools/fs/safety.js";
8
+ import { RosterBuilder } from "../tool-roster.js";
8
9
  export function prepareMemoryEngineSession(input) {
9
- const { memoryEngineSession, internals, memoryDelegationEvidence, externalContentTargetActive, lockedPreflight, mcp, sessionId, runId, deps, provenanceForChildrenRef, delegationSettlementRef, spec, envHandToolNames, tools, contentOriginWrapRef } = input;
10
+ const { memoryEngineSession, internals, memoryDelegationEvidence, externalContentTargetActive, lockedPreflight, mcp, sessionId, runId, deps, provenanceForChildrenRef, delegationSettlementRef, spec, envHandToolNames, roster, contentOriginWrapRef } = input;
11
+ const tools = roster.tools;
10
12
  const delegationProvenanceChannel = internals?.delegationProvenance;
11
13
  if (memoryEngineSession !== undefined || delegationProvenanceChannel !== undefined) {
12
14
  const pollution = memoryEngineSession?.pollution;
@@ -196,7 +198,7 @@ export function prepareMemoryEngineSession(input) {
196
198
  },
197
199
  };
198
200
  contentOriginWrapped.add(wrapped);
199
- tools[i] = wrapped;
201
+ roster.replaceAt(i, wrapped);
200
202
  }
201
203
  };
202
204
  contentOriginWrapRef.current();
@@ -1,4 +1,6 @@
1
- import { DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, firstPartyOffloadPolicy, InMemoryToolResultStore, RunnerSharedToolResultStore, ScopedToolResultStore, withToolResultOffload, } from "../tool-result-store.js";
1
+ import { DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, InMemoryToolResultStore, RunnerSharedToolResultStore, ScopedToolResultStore, withToolResultOffload, } from "../tool-result-store.js";
2
+ import { faceOf } from "../tool-roster.js";
3
+ const FIRST_PARTY_OFFLOAD_THRESHOLD_CHARS = 50_000;
2
4
  export function prepareOffloadWrappers(input) {
3
5
  const { spec, deps, resume, offloadScope, sessionId } = input;
4
6
  const nestedStats = { tokens: 0, turns: 0, tasks: 0, costMicroUsd: 0, anyUnpriced: false };
@@ -31,10 +33,10 @@ export function prepareOffloadWrappers(input) {
31
33
  return withToolResultOffload(tool, offloadStore, perTool?.offloadThresholdChars ?? offloadThreshold, sessionId, () => offloadReachableToolsRef.current?.());
32
34
  };
33
35
  const firstPartyOffload = (tool) => {
34
- const policy = firstPartyOffloadPolicy(tool.name);
35
- if (policy.offload === false)
36
- return maybeOffload(tool, policy);
37
- return maybeOffload(tool, explicitGlobalThreshold === undefined ? policy : undefined);
36
+ const face = faceOf(tool);
37
+ if (face.offload === false)
38
+ return maybeOffload(tool, { offload: false });
39
+ return maybeOffload(tool, explicitGlobalThreshold === undefined ? { offloadThresholdChars: face.offloadThresholdChars ?? FIRST_PARTY_OFFLOAD_THRESHOLD_CHARS } : undefined);
38
40
  };
39
41
  const remoteToolOffload = (tool) => {
40
42
  if (explicitGlobalThreshold !== undefined)
@@ -30,6 +30,7 @@ import type { CwdRef } from "../../tools/fs/fs-shared.js";
30
30
  import { type ContentAskBinding } from "./content-ask-bindings.js";
31
31
  import type { AskLane, ParkAsk, Prepared, SuspendSaga } from "./contracts.js";
32
32
  import type { createRuleOffersOf } from "./permission-rule-lanes.js";
33
+ import { type ToolRosterDeltaSeat } from "../tool-roster.js";
33
34
  export interface PrepareParkAskInput {
34
35
  /** borrowed-readonly — the in-stream ask lane, or undefined when the gate machinery is inactive (then both closures are
35
36
  * absent). Read: `composedCallSignal` (the fence and the delivery race), `approvalPreviewOf` (the row's preview). */
@@ -49,6 +50,9 @@ export interface PrepareParkAskInput {
49
50
  /** borrowed-readonly — the leg's checkpoint store, or undefined (then `suspendAsk` is absent — the park predicate's store
50
51
  * term). Read: the declared fidelity; the saga writes to it. */
51
52
  checkpointStore: CheckpointStore | undefined;
53
+ /** borrowed-readonly — the leg's delta seat (design/388 B18): the park reads the gated tool's identity off its
54
+ * CURRENT roster snapshot so the row carries who the action was minted against. */
55
+ toolRosterDeltas: ToolRosterDeltaSeat;
52
56
  /** borrowed-readonly — the wiring-manifest phase's park predicate (`parkLane.effective`): arms `suspendAsk`. */
53
57
  parkLaneArmed: boolean;
54
58
  /** borrowed-readonly — the question-face phase's routability predicate (the mounted question tool is the engine's own,
@@ -14,6 +14,7 @@ import { checkpointScopeOf } from "./checkpoint-scope.js";
14
14
  import { CONTENT_ASK_BINDING_CAP } from "./content-ask-bindings.js";
15
15
  import { DEFAULT_UNATTENDED_APPROVAL_TTL_MS, publishCommittedSuspend, sanitizedTtlMs } from "./park-commit.js";
16
16
  import { remoteEnvFailureNote } from "./remote-env-retry.js";
17
+ import { callFaceSeat, parkedToolIdentity, parkedToolFace } from "../tool-roster.js";
17
18
  class ParkRefusal extends Error {
18
19
  constructor(message, options) {
19
20
  super(message, options);
@@ -21,7 +22,7 @@ class ParkRefusal extends Error {
21
22
  }
22
23
  }
23
24
  export function prepareParkAsk(input) {
24
- const { 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 } = input;
25
+ const { askLane, saga, spec, deps, sessionId, checkpointStore, toolRosterDeltas, 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 } = input;
25
26
  if (askLane === undefined || saga === undefined)
26
27
  return { parkAsk: undefined };
27
28
  const { composedCallSignal, approvalPreviewOf } = askLane;
@@ -157,6 +158,7 @@ export function prepareParkAsk(input) {
157
158
  args: presented.value,
158
159
  toolCallId: req.toolCallId,
159
160
  budget: budgetSnapshot,
161
+ ...callFaceSeat(req.face),
160
162
  ...(handsCwdRef !== undefined ? { cwd: handsCwdRef.current } : {}),
161
163
  }, cutSignal)), cutSignal);
162
164
  if (reprojectedRaced.tag === "aborted")
@@ -277,6 +279,7 @@ export function prepareParkAsk(input) {
277
279
  kind: "tool_approval",
278
280
  toolCallId: req.toolCallId,
279
281
  toolName: req.toolName,
282
+ ...parkedToolIdentity(toolRosterDeltas.current, req.toolName), ...parkedToolFace(toolRosterDeltas.current, req.toolName),
280
283
  args: parkedArgs,
281
284
  ...(() => {
282
285
  const p = approvalPreviewOf(req.toolName, parkedArgs);
@@ -1,27 +1,9 @@
1
- /**
2
- * design/390 M9 — prepareTask's PROJECT-CONTEXT phase: the deployment's project memory snapshot (the
3
- * `loadProjectMemory` seam: phase derivation, instruction sources, the framed + fenced project layer
4
- * appended to the memory block), the skills roster (declaration rank, normalization, the 1MB load gate,
5
- * the manifest-scope door, the `skill` tool mount), the two built-in memory pairs' mount-or-yield
6
- * (shared-memory pair, engine-memory pair), and the skills-listing projection the run loop announces.
7
- * The body is the driver's stretch moved verbatim; the interface below is the dependency list it had
8
- * implicitly (design/238 R-1: a new read is an Input field, i.e. a review event).
9
- *
10
- * ASYNC BY CONTENT: the stretch awaits the session branch read, the seam itself and the read-state
11
- * seeding (all inside the `loadProjectMemory` arm). It already sits past prepareTask's first await, so
12
- * the one extra microtask the driver's `await` adds on the seam-less path opens no window any caller
13
- * could reach (design/390 §1.5 S1 synchronicity rule: only pre-first-await stretches must stay sync).
14
- *
15
- * Throws pass through unchanged: `config.reserved_tool_name` (a caller tool named like the skill tool
16
- * while skills are present) is the only refusal here; the seam's own throw and the seeding's are
17
- * caught and forwarded to `deps.onError` (best-effort, never block the task).
18
- */
19
- import type { AgentTool } from "../../internal/harness.js";
20
1
  import { type StoredSession } from "../session.js";
21
2
  import type { RunnerDeps, TaskSpec, ToolSpec } from "../types.js";
22
3
  import type { ActiveSkillScope } from "./active-skill-scope.js";
23
4
  import type { AnnounceOnceSink, Prepared, PrepareResume, RunInternals, ToolFaceSnapshot } from "./contracts.js";
24
5
  import { normalizeSkills } from "./synthetic-tools.js";
6
+ import { RosterBuilder } from "../tool-roster.js";
25
7
  export interface PrepareProjectContextInput {
26
8
  /** borrowed-readonly — the deployment seams this phase reads: `loadProjectMemory` (the snapshot),
27
9
  * `onError` (the operator lane for a throwing seam / seeding / oversize skill), `sharedMemoryStores`
@@ -64,10 +46,10 @@ export interface PrepareProjectContextInput {
64
46
  * and the `skill` tool alias this exact object). Mutated here: `push` of every inherited parent
65
47
  * frame; after this phase only the `skill` tool's execute pushes/pops. */
66
48
  skillScope: ActiveSkillScope;
67
- /** borrowed-mutable — the shared mount array (later refs alias it; in-place contract). Mutated
68
- * here: `push` of the `skill` tool, the shared-memory pair and the engine-memory pair. Occupancy
69
- * is judged on this ALREADY-MOUNTED roster (names + aliases). */
70
- tools: AgentTool[];
49
+ /** borrowed-mutable — the run's roster builder (later refs alias its read face; in-place contract). Mutated
50
+ * here: `mount` of the `skill` tool, the shared-memory pair and the engine-memory pair. Occupancy
51
+ * is judged on the ALREADY-MOUNTED read face (names + aliases). */
52
+ roster: RosterBuilder;
71
53
  /** borrowed-readonly — the frozen task-start tool face; only `exclude` is read (pair yield ①). */
72
54
  toolFaceSnapshot: Pick<ToolFaceSnapshot, "exclude">;
73
55
  /** borrowed-readonly — the once-per-session announcement ledger; only its deduplicated `onError`
@@ -6,8 +6,10 @@ import { createSharedMemoryTools } from "../shared-memory/tools.js";
6
6
  import { SHARED_MEMORY_TOOL_NAMES } from "../shared-memory/types.js";
7
7
  import { defineTool } from "../tools.js";
8
8
  import { SKILL_CONTENT_MAX_CHARS, SKILL_TOOL_NAME, createSkillTool, normalizeSkills } from "./synthetic-tools.js";
9
+ import { RosterBuilder } from "../tool-roster.js";
9
10
  export async function prepareProjectContext(input) {
10
- const { deps, spec, internals, session, sessionId, hostTaskId, taskRootFinal, handsEnabled, delegation, seedContextFiles, memoryBlockFromEngine, skillScope, tools, toolFaceSnapshot, onceLedger, memoryTools, memoryEngineSession, memorySearchToolsPlanned, memoryPairExcludedName, memoryPairOccupiedName, resume } = input;
11
+ const { deps, spec, internals, session, sessionId, hostTaskId, taskRootFinal, handsEnabled, delegation, seedContextFiles, memoryBlockFromEngine, skillScope, roster, toolFaceSnapshot, onceLedger, memoryTools, memoryEngineSession, memorySearchToolsPlanned, memoryPairExcludedName, memoryPairOccupiedName, resume } = input;
12
+ const tools = roster.tools;
11
13
  let memoryBlock = memoryBlockFromEngine;
12
14
  let instructionSources;
13
15
  let projectInstructionContent;
@@ -88,7 +90,7 @@ export async function prepareProjectContext(input) {
88
90
  e.code = "config.reserved_tool_name";
89
91
  throw e;
90
92
  }
91
- tools.push(createSkillTool(skillSpecs, skillScope));
93
+ roster.mount(createSkillTool(skillSpecs, skillScope), { source: "builtin", mountedBy: "optional" });
92
94
  }
93
95
  const sharedMemoryProvider = deps.sharedMemoryStores;
94
96
  let sharedMemoryPairMounted = false;
@@ -110,16 +112,16 @@ export async function prepareProjectContext(input) {
110
112
  " — the pair mounts together or not at all."), { phase: "config", sessionId, classification: "shared-memory-not-mounted" });
111
113
  }
112
114
  else {
113
- tools.push(...createSharedMemoryTools({
115
+ roster.mountAll(createSharedMemoryTools({
114
116
  provider: sharedMemoryProvider,
115
117
  context: { sessionId, taskId: hostTaskId, ...(spec.principal !== undefined ? { principal: spec.principal } : {}) },
116
- }).map((s) => defineTool(s)));
118
+ }).map((s) => defineTool(s)), { source: "builtin", mountedBy: "memory" });
117
119
  sharedMemoryPairMounted = true;
118
120
  }
119
121
  }
120
122
  let memoryEnginePairMounted = false;
121
123
  if (memoryTools !== undefined && memoryTools.length > 0) {
122
- tools.push(...memoryTools.map((s) => defineTool(s)));
124
+ roster.mountAll(memoryTools.map((s) => defineTool(s)), { source: "builtin", mountedBy: "memory" });
123
125
  memoryEnginePairMounted = true;
124
126
  }
125
127
  else if (memoryEngineSession !== undefined && !memorySearchToolsPlanned) {
@@ -48,7 +48,7 @@ export interface PreparePromptAssemblyInput {
48
48
  fableMitigations: boolean;
49
49
  /** borrowed-readonly — the run's ONE tool roster, read only: the assembly receives DETACHED copies, the audit and the
50
50
  * manifest read it in place. Never mutated here. */
51
- tools: AgentTool[];
51
+ tools: readonly AgentTool[];
52
52
  /** borrowed-readonly — the task model; only `promptGuidance` (the model-guidance section). */
53
53
  model: Pick<Model, "promptGuidance">;
54
54
  /** borrowed-readonly — the acquired session id (every operator line's attribution). */
@@ -4,6 +4,7 @@ import { type MaterializedA2a } from "../a2a.js";
4
4
  import type { LockedPreflight } from "../locked-config.js";
5
5
  import type { RunnerDeps, TaskSpec, ToolEffect } from "../types.js";
6
6
  import type { AnnounceOnceSink, IrreversibilityTier, Prepared, RollbackStack, ToolFaceSnapshot } from "./contracts.js";
7
+ import { RosterBuilder, type ToolRosterDeltaSeat } from "../tool-roster.js";
7
8
  export interface PrepareProtocolToolsInput {
8
9
  /** borrowed-readonly — the frozen preflight; only `mcp` (the declared server entries) is read: the
9
10
  * materialize input, the RefreshMcpTools mount predicate, and whether a real client was acquired. */
@@ -34,7 +35,10 @@ export interface PrepareProtocolToolsInput {
34
35
  * Writers here: three pushes (the MCP tools through the wrapper, RefreshMcpTools, the A2A tools through the
35
36
  * wrapper) and, at RUN time, the refresh closure's splice within a server's name-prefix domain. Later
36
37
  * phases push their own mounts; the roster's full writer table is the delegation-surface phase's. */
37
- tools: AgentTool[];
38
+ roster: RosterBuilder;
39
+ /** borrowed-mutable — the run-time delta seat (design/388 §2.5): the refresh closure re-mints the roster through it after
40
+ * every re-splice so the stream learns of the change. Armed by the driver after the roster is minted. */
41
+ rosterSeat: ToolRosterDeltaSeat;
38
42
  /** borrowed-readonly — the large-result wrapper for protocol tools (per-tool `_meta` bar, 50K default). */
39
43
  remoteToolOffload: (tool: AgentTool) => AgentTool;
40
44
  /** borrowed-readonly — the frozen tool face; `exclude` is read at REFRESH time so a deployment-excluded