@sema-agent/core 7.6.3 → 7.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/CHANGELOG.md +70 -10
  2. package/dist/agents/agent-transcript-tool.js +2 -2
  3. package/dist/agents/list-agents-tool.js +2 -3
  4. package/dist/agents/observer.js +2 -2
  5. package/dist/agents/peer-admission.d.ts +1 -1
  6. package/dist/agents/send-message-tool.js +2 -1
  7. package/dist/agents/subagent-steps.js +5 -4
  8. package/dist/agents/subagent.js +2 -3
  9. package/dist/brain/anthropic.js +8 -2
  10. package/dist/brain/open-responses.js +5 -3
  11. package/dist/brain/openai.js +31 -8
  12. package/dist/brain/reasoning.d.ts +32 -0
  13. package/dist/brain/reasoning.js +18 -0
  14. package/dist/core/ask-question.js +2 -2
  15. package/dist/core/auto-mode-defaults.d.ts +16 -0
  16. package/dist/core/auto-mode-defaults.js +1 -0
  17. package/dist/core/auto-mode.d.ts +19 -0
  18. package/dist/core/auto-mode.js +74 -56
  19. package/dist/core/checkpoint-execution-record.d.ts +110 -0
  20. package/dist/core/checkpoint-execution-record.js +49 -0
  21. package/dist/core/checkpoint-store.d.ts +114 -2
  22. package/dist/core/checkpoint-store.js +35 -2
  23. package/dist/core/context-edit.d.ts +0 -22
  24. package/dist/core/context-edit.js +2 -11
  25. package/dist/core/engine-notice.d.ts +17 -0
  26. package/dist/core/fs-write-gate-policy.d.ts +7 -1
  27. package/dist/core/fs-write-gate-policy.js +7 -7
  28. package/dist/core/gate-fold.js +2 -2
  29. package/dist/core/gate-lanes.js +4 -3
  30. package/dist/core/governance-codes.d.ts +1 -1
  31. package/dist/core/governance-codes.js +8 -0
  32. package/dist/core/hooks.d.ts +8 -2
  33. package/dist/core/hooks.js +1 -1
  34. package/dist/core/lsp.js +2 -2
  35. package/dist/core/mcp-server-spec.d.ts +17 -0
  36. package/dist/core/mcp.js +3 -1
  37. package/dist/core/memory-engine/tools.js +4 -12
  38. package/dist/core/permission-rules.d.ts +32 -2
  39. package/dist/core/permission-rules.js +43 -37
  40. package/dist/core/present-plan-tool.js +3 -4
  41. package/dist/core/runner/active-skill-scope.js +7 -4
  42. package/dist/core/runner/clock-and-limits.d.ts +117 -0
  43. package/dist/core/runner/clock-and-limits.js +118 -0
  44. package/dist/core/runner/compaction-call-options.d.ts +1 -1
  45. package/dist/core/runner/contracts.d.ts +37 -3
  46. package/dist/core/runner/decide-continuation.d.ts +98 -0
  47. package/dist/core/runner/decide-continuation.js +133 -0
  48. package/dist/core/runner/execution-record.d.ts +26 -0
  49. package/dist/core/runner/execution-record.js +19 -0
  50. package/dist/core/runner/git-leg-delivery.d.ts +28 -0
  51. package/dist/core/runner/git-leg-delivery.js +94 -0
  52. package/dist/core/runner/initial-run-state.d.ts +14 -0
  53. package/dist/core/runner/initial-run-state.js +11 -0
  54. package/dist/core/runner/prepare-ask-lane.d.ts +1 -1
  55. package/dist/core/runner/prepare-caps-and-workflow.d.ts +5 -4
  56. package/dist/core/runner/prepare-caps-and-workflow.js +33 -11
  57. package/dist/core/runner/prepare-defer-classify.d.ts +5 -26
  58. package/dist/core/runner/prepare-defer-classify.js +10 -10
  59. package/dist/core/runner/prepare-delegation-surface.d.ts +5 -4
  60. package/dist/core/runner/prepare-delegation-surface.js +15 -17
  61. package/dist/core/runner/prepare-gate-stations.d.ts +4 -1
  62. package/dist/core/runner/prepare-gate-stations.js +3 -1
  63. package/dist/core/runner/prepare-hands-readface.d.ts +3 -2
  64. package/dist/core/runner/prepare-hands-readface.js +3 -2
  65. package/dist/core/runner/prepare-inherited-gate.d.ts +1 -1
  66. package/dist/core/runner/prepare-inherited-gate.js +1 -0
  67. package/dist/core/runner/prepare-lsp.d.ts +4 -3
  68. package/dist/core/runner/prepare-lsp.js +3 -2
  69. package/dist/core/runner/prepare-memory-engine-session.d.ts +4 -4
  70. package/dist/core/runner/prepare-memory-engine-session.js +4 -2
  71. package/dist/core/runner/prepare-offload-wrappers.js +7 -5
  72. package/dist/core/runner/prepare-park-ask.d.ts +4 -0
  73. package/dist/core/runner/prepare-park-ask.js +4 -1
  74. package/dist/core/runner/prepare-project-context.d.ts +5 -23
  75. package/dist/core/runner/prepare-project-context.js +7 -5
  76. package/dist/core/runner/prepare-prompt-assembly.d.ts +1 -1
  77. package/dist/core/runner/prepare-protocol-tools.d.ts +5 -1
  78. package/dist/core/runner/prepare-protocol-tools.js +38 -21
  79. package/dist/core/runner/prepare-question-face.d.ts +4 -3
  80. package/dist/core/runner/prepare-question-face.js +5 -3
  81. package/dist/core/runner/prepare-safety-scan.js +42 -2
  82. package/dist/core/runner/prepare-task.js +37 -19
  83. package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +5 -2
  84. package/dist/core/runner/prepare-tool-disclosure-mount.js +3 -1
  85. package/dist/core/runner/prepare-wiring-manifest.d.ts +4 -0
  86. package/dist/core/runner/prepare-wiring-manifest.js +3 -8
  87. package/dist/core/runner/run-harness-handlers.d.ts +46 -0
  88. package/dist/core/runner/run-harness-handlers.js +345 -0
  89. package/dist/core/runner/run-turn-boundary.d.ts +69 -0
  90. package/dist/core/runner/run-turn-boundary.js +694 -0
  91. package/dist/core/runner/runtask.d.ts +0 -68
  92. package/dist/core/runner/runtask.js +66 -1486
  93. package/dist/core/runner/session-rule-policy.js +9 -4
  94. package/dist/core/runner/steer-admission.d.ts +17 -0
  95. package/dist/core/runner/steer-admission.js +17 -0
  96. package/dist/core/runner/synthetic-tools.js +5 -8
  97. package/dist/core/runner/tool-disclosure.d.ts +4 -0
  98. package/dist/core/runner/tool-disclosure.js +9 -5
  99. package/dist/core/runner/tool-end-body.d.ts +71 -0
  100. package/dist/core/runner/tool-end-body.js +75 -0
  101. package/dist/core/runner/tool-face-overlay.d.ts +40 -0
  102. package/dist/core/runner/tool-face-overlay.js +125 -0
  103. package/dist/core/runner/tool-output-projection.d.ts +10 -0
  104. package/dist/core/runner/tool-output-projection.js +33 -10
  105. package/dist/core/sensitive-path-policy.d.ts +3 -0
  106. package/dist/core/sensitive-path-policy.js +6 -5
  107. package/dist/core/shared-memory/tools.js +3 -6
  108. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +4 -0
  109. package/dist/core/store-contracts/checkpoint-store-contract.js +108 -0
  110. package/dist/core/task-event.d.ts +10 -0
  111. package/dist/core/task-registry.js +4 -5
  112. package/dist/core/task-spec.d.ts +8 -0
  113. package/dist/core/tool-catalog-entries.d.ts +48 -0
  114. package/dist/core/tool-catalog-entries.js +174 -0
  115. package/dist/core/tool-conformance.d.ts +22 -0
  116. package/dist/core/tool-conformance.js +37 -0
  117. package/dist/core/tool-face.d.ts +171 -0
  118. package/dist/core/tool-face.js +25 -0
  119. package/dist/core/tool-policy.d.ts +24 -0
  120. package/dist/core/tool-policy.js +4 -2
  121. package/dist/core/tool-registry.d.ts +114 -0
  122. package/dist/core/tool-registry.js +114 -0
  123. package/dist/core/tool-result-budget.d.ts +0 -3
  124. package/dist/core/tool-result-budget.js +4 -3
  125. package/dist/core/tool-result-store.d.ts +0 -18
  126. package/dist/core/tool-result-store.js +2 -18
  127. package/dist/core/tool-roster.d.ts +450 -0
  128. package/dist/core/tool-roster.js +497 -0
  129. package/dist/core/tool-spec.d.ts +54 -3
  130. package/dist/core/tools.js +7 -0
  131. package/dist/core/trace.d.ts +24 -0
  132. package/dist/core/wiring-manifest.d.ts +13 -0
  133. package/dist/core/wiring-manifest.js +2 -1
  134. package/dist/core/write-protect.d.ts +2 -1
  135. package/dist/core/write-protect.js +5 -4
  136. package/dist/engine/compaction/utils.d.ts +6 -0
  137. package/dist/engine/compaction/utils.js +19 -19
  138. package/dist/engine/loop/types.d.ts +6 -0
  139. package/dist/index.d.ts +20 -8
  140. package/dist/index.js +19 -7
  141. package/dist/orchestration/goal.js +2 -1
  142. package/dist/orchestration/run-spec.js +3 -5
  143. package/dist/orchestration/run-workflow-tool.js +2 -2
  144. package/dist/prompt-assembly/event-registry.js +3 -3
  145. package/dist/prompt-assembly/tool-catalog.d.ts +2 -2
  146. package/dist/stores/file/checkpoint-store.d.ts +7 -0
  147. package/dist/stores/file/checkpoint-store.js +20 -1
  148. package/dist/tools/fs/fs-bash.d.ts +1 -1
  149. package/dist/tools/fs/fs-bash.js +7 -11
  150. package/dist/tools/fs/fs-read.js +2 -2
  151. package/dist/tools/fs/fs-search-tools.d.ts +0 -7
  152. package/dist/tools/fs/fs-search-tools.js +3 -16
  153. package/dist/tools/fs/fs-write.js +4 -6
  154. package/dist/tools/fs/repo-map.js +2 -2
  155. package/dist/tools/fs/safety.d.ts +2 -1
  156. package/dist/tools/fs/safety.js +6 -5
  157. package/dist/tools/monitor.js +2 -2
  158. package/dist/tools/scheduler-tools.js +5 -8
  159. package/dist/tools/task-list.js +5 -8
  160. package/dist/tools/todo.js +2 -2
  161. package/dist/tools/web.js +3 -6
  162. package/dist/tools/worktree.js +3 -4
  163. package/package.json +4 -1
  164. package/test/export-surface.snapshot.json +183 -7
@@ -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
@@ -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
  }
@@ -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");