@sema-agent/core 7.17.7 → 7.19.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 (201) hide show
  1. package/CHANGELOG.md +233 -13
  2. package/dist/agents/send-message-tool.js +1 -0
  3. package/dist/agents/subagent.d.ts +1 -0
  4. package/dist/agents/subagent.js +5 -0
  5. package/dist/agents/teacher.js +1 -0
  6. package/dist/agents/verify.js +3 -0
  7. package/dist/brain/anthropic.js +6 -3
  8. package/dist/brain/open-responses.js +10 -4
  9. package/dist/brain/openai.js +5 -2
  10. package/dist/brain/reasoning.d.ts +13 -13
  11. package/dist/brain/reasoning.js +2 -2
  12. package/dist/brain/route-adjudicator.d.ts +18 -0
  13. package/dist/brain/route-adjudicator.js +16 -0
  14. package/dist/brain/stream-engine.js +2 -1
  15. package/dist/brain/stream-shared.d.ts +3 -0
  16. package/dist/brain/stream-shared.js +1 -0
  17. package/dist/core/agent-definition.d.ts +10 -0
  18. package/dist/core/artifact-host.d.ts +41 -4
  19. package/dist/core/artifact-host.js +18 -2
  20. package/dist/core/ask-origin.d.ts +16 -0
  21. package/dist/core/ask-origin.js +8 -0
  22. package/dist/core/auto-mode-arming.d.ts +6 -0
  23. package/dist/core/auto-mode-arming.js +5 -5
  24. package/dist/core/auto-mode-defaults.d.ts +43 -15
  25. package/dist/core/auto-mode-defaults.js +4 -1
  26. package/dist/core/auto-mode.d.ts +184 -59
  27. package/dist/core/auto-mode.js +188 -13
  28. package/dist/core/checkpoint-store.d.ts +23 -2
  29. package/dist/core/checkpoint-store.js +2 -2
  30. package/dist/core/engine-notice.d.ts +77 -1
  31. package/dist/core/engine-notice.js +7 -0
  32. package/dist/core/gate-fold.js +5 -1
  33. package/dist/core/gate-lanes.js +9 -8
  34. package/dist/core/gate-outcome.d.ts +42 -2
  35. package/dist/core/gate-outcome.js +23 -0
  36. package/dist/core/governance-codes.d.ts +1 -1
  37. package/dist/core/governance-codes.js +14 -0
  38. package/dist/core/hook-seats.d.ts +66 -0
  39. package/dist/core/hook-seats.js +37 -0
  40. package/dist/core/hooks.d.ts +1 -1
  41. package/dist/core/read-root-candidate.d.ts +12 -0
  42. package/dist/core/read-root-candidate.js +24 -0
  43. package/dist/core/roles.d.ts +9 -1
  44. package/dist/core/roles.js +4 -0
  45. package/dist/core/runner/advertised-writable-dirs.d.ts +8 -3
  46. package/dist/core/runner/assemble-result.d.ts +2 -1
  47. package/dist/core/runner/assemble-result.js +1 -1
  48. package/dist/core/runner/classifier-ladder.d.ts +227 -0
  49. package/dist/core/runner/classifier-ladder.js +214 -0
  50. package/dist/core/runner/contracts.d.ts +71 -13
  51. package/dist/core/runner/decide-continuation.d.ts +1 -3
  52. package/dist/core/runner/decide-continuation.js +1 -7
  53. package/dist/core/runner/deferral-census.d.ts +52 -0
  54. package/dist/core/runner/deferral-census.js +44 -0
  55. package/dist/core/runner/denial-limit-arms.js +2 -2
  56. package/dist/core/runner/derived-route-fallback.d.ts +8 -4
  57. package/dist/core/runner/derived-route-fallback.js +8 -3
  58. package/dist/core/runner/gate-exit.d.ts +12 -0
  59. package/dist/core/runner/gate-exit.js +6 -3
  60. package/dist/core/runner/prepare-artifact.d.ts +10 -2
  61. package/dist/core/runner/prepare-artifact.js +5 -4
  62. package/dist/core/runner/prepare-ask-lane.js +1 -0
  63. package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -2
  64. package/dist/core/runner/prepare-caps-and-workflow.js +43 -55
  65. package/dist/core/runner/prepare-config-doors.d.ts +7 -1
  66. package/dist/core/runner/prepare-config-doors.js +13 -13
  67. package/dist/core/runner/prepare-defer-classify.d.ts +13 -19
  68. package/dist/core/runner/prepare-defer-classify.js +8 -3
  69. package/dist/core/runner/prepare-gate-stations.d.ts +26 -0
  70. package/dist/core/runner/prepare-gate-stations.js +1 -1
  71. package/dist/core/runner/prepare-hands-readface.d.ts +24 -7
  72. package/dist/core/runner/prepare-hands-readface.js +60 -24
  73. package/dist/core/runner/prepare-inherited-gate.d.ts +17 -4
  74. package/dist/core/runner/prepare-inherited-gate.js +10 -2
  75. package/dist/core/runner/prepare-lsp.d.ts +2 -2
  76. package/dist/core/runner/prepare-lsp.js +1 -1
  77. package/dist/core/runner/prepare-policy-chain.js +2 -0
  78. package/dist/core/runner/prepare-project-context.d.ts +5 -1
  79. package/dist/core/runner/prepare-project-context.js +7 -4
  80. package/dist/core/runner/prepare-prompt-inputs.d.ts +4 -2
  81. package/dist/core/runner/prepare-prompt-inputs.js +4 -1
  82. package/dist/core/runner/prepare-run-refs.d.ts +9 -1
  83. package/dist/core/runner/prepare-run-refs.js +3 -2
  84. package/dist/core/runner/prepare-suspend-saga.d.ts +5 -0
  85. package/dist/core/runner/prepare-suspend-saga.js +5 -2
  86. package/dist/core/runner/prepare-task.js +19 -14
  87. package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +5 -0
  88. package/dist/core/runner/prepare-tool-disclosure-mount.js +10 -2
  89. package/dist/core/runner/prepare-turn-wiring.d.ts +8 -4
  90. package/dist/core/runner/prepare-turn-wiring.js +3 -2
  91. package/dist/core/runner/prepare-wiring-manifest.d.ts +5 -5
  92. package/dist/core/runner/prepare-wiring-manifest.js +5 -2
  93. package/dist/core/runner/resume-apply.js +3 -3
  94. package/dist/core/runner/resume-checkpoint-screen.js +7 -0
  95. package/dist/core/runner/run-harness-handlers.js +4 -1
  96. package/dist/core/runner/run-identity-wiring.js +6 -2
  97. package/dist/core/runner/run-leg.d.ts +2 -0
  98. package/dist/core/runner/run-leg.js +45 -45
  99. package/dist/core/runner/run-notification-lane.js +55 -27
  100. package/dist/core/runner/run-settle-and-teardown.js +1 -0
  101. package/dist/core/runner/run-terminal-adoption.js +16 -13
  102. package/dist/core/runner/run-turn-boundary.js +15 -5
  103. package/dist/core/runner/runtask.d.ts +6 -5
  104. package/dist/core/runner/runtask.js +26 -14
  105. package/dist/core/runner/skill-base-dir.d.ts +54 -0
  106. package/dist/core/runner/skill-base-dir.js +42 -0
  107. package/dist/core/runner/synthetic-tools.d.ts +83 -48
  108. package/dist/core/runner/synthetic-tools.js +69 -32
  109. package/dist/core/runner/tool-defer-gate.d.ts +22 -9
  110. package/dist/core/runner/tool-defer-gate.js +15 -5
  111. package/dist/core/runner/tool-disclosure.d.ts +5 -0
  112. package/dist/core/runner/tool-disclosure.js +2 -0
  113. package/dist/core/runner/tool-progress-lane.d.ts +42 -0
  114. package/dist/core/runner/tool-progress-lane.js +61 -0
  115. package/dist/core/runner/turn-attachments.d.ts +43 -12
  116. package/dist/core/runner/turn-attachments.js +58 -14
  117. package/dist/core/runner-deps.d.ts +48 -7
  118. package/dist/core/sensitive-path-policy.d.ts +58 -17
  119. package/dist/core/sensitive-path-policy.js +33 -5
  120. package/dist/core/skill-listing-budget.d.ts +112 -0
  121. package/dist/core/skill-listing-budget.js +58 -0
  122. package/dist/core/swappable-deps.d.ts +9 -7
  123. package/dist/core/swappable-deps.js +7 -3
  124. package/dist/core/task-event.d.ts +96 -29
  125. package/dist/core/task-notification.d.ts +142 -16
  126. package/dist/core/task-notification.js +106 -0
  127. package/dist/core/task-result.d.ts +9 -1
  128. package/dist/core/task-spec.d.ts +46 -3
  129. package/dist/core/tighten-task-spec.js +7 -5
  130. package/dist/core/tool-model-gate.d.ts +1 -1
  131. package/dist/core/tool-policy.d.ts +19 -4
  132. package/dist/core/tool-policy.js +6 -7
  133. package/dist/core/tool-search-setting.d.ts +78 -0
  134. package/dist/core/tool-search-setting.js +90 -0
  135. package/dist/core/tool-spec.d.ts +45 -4
  136. package/dist/core/types.d.ts +2 -2
  137. package/dist/core/types.js +1 -1
  138. package/dist/core/wiring-manifest.d.ts +46 -0
  139. package/dist/core/wiring-manifest.js +9 -1
  140. package/dist/core/write-protect.d.ts +8 -3
  141. package/dist/core/write-protect.js +8 -5
  142. package/dist/engine/execution-env/node-execution-env.js +10 -4
  143. package/dist/engine/harness/agent-harness.d.ts +42 -0
  144. package/dist/engine/harness/agent-harness.js +61 -29
  145. package/dist/engine/loop/agent-loop.d.ts +68 -3
  146. package/dist/engine/loop/agent-loop.js +109 -16
  147. package/dist/index.d.ts +13 -8
  148. package/dist/index.js +11 -7
  149. package/dist/internal/harness.d.ts +1 -1
  150. package/dist/orchestration/run-workflow-tool.d.ts +3 -0
  151. package/dist/orchestration/run-workflow-tool.js +6 -1
  152. package/dist/orchestration/workflow-governance.d.ts +7 -1
  153. package/dist/orchestration/workflow-governance.js +9 -5
  154. package/dist/orchestration/workflow-primitives.d.ts +3 -0
  155. package/dist/orchestration/workflow-primitives.js +6 -1
  156. package/dist/orchestration/workflow-types.d.ts +48 -13
  157. package/dist/orchestration/workflow-types.js +1 -1
  158. package/dist/orchestration/workflow.js +45 -17
  159. package/dist/prompt-assembly/epoch.js +2 -0
  160. package/dist/prompt-assembly/packs/sema-default.js +2 -2
  161. package/dist/prompt-assembly/section-usage.d.ts +26 -0
  162. package/dist/prompt-assembly/section-usage.js +10 -0
  163. package/dist/prompt-assembly/types.d.ts +8 -6
  164. package/dist/prompts/default.d.ts +13 -0
  165. package/dist/prompts/default.js +16 -8
  166. package/dist/tools/artifact/artifact-text.d.ts +10 -1
  167. package/dist/tools/artifact/artifact-text.js +6 -3
  168. package/dist/tools/artifact/artifact-tool.d.ts +12 -2
  169. package/dist/tools/artifact/artifact-tool.js +203 -70
  170. package/dist/tools/artifact/conformance.d.ts +56 -0
  171. package/dist/tools/artifact/conformance.js +349 -0
  172. package/dist/tools/artifact/local-stub.js +1 -1
  173. package/dist/tools/artifact/reject-breaker.d.ts +28 -0
  174. package/dist/tools/artifact/reject-breaker.js +42 -0
  175. package/dist/tools/fs/bash-lexer.d.ts +132 -0
  176. package/dist/tools/fs/bash-lexer.js +563 -0
  177. package/dist/tools/fs/bash-program-position.d.ts +142 -0
  178. package/dist/tools/fs/bash-program-position.js +125 -0
  179. package/dist/tools/fs/bash-readonly-classifier.d.ts +81 -16
  180. package/dist/tools/fs/bash-readonly-classifier.js +180 -558
  181. package/dist/tools/fs/containment-face.d.ts +89 -0
  182. package/dist/tools/fs/containment-face.js +81 -0
  183. package/dist/tools/fs/fs-bash.d.ts +46 -1
  184. package/dist/tools/fs/fs-bash.js +165 -25
  185. package/dist/tools/fs/fs-read.d.ts +1 -1
  186. package/dist/tools/fs/fs-search-tools.d.ts +3 -3
  187. package/dist/tools/fs/fs-write.d.ts +4 -3
  188. package/dist/tools/fs/fs-write.js +9 -9
  189. package/dist/tools/fs/index.d.ts +24 -5
  190. package/dist/tools/fs/index.js +18 -6
  191. package/dist/tools/fs/read-deny.d.ts +4 -3
  192. package/dist/tools/fs/repo-map.d.ts +2 -2
  193. package/dist/tools/fs/safety.d.ts +18 -3
  194. package/dist/tools/fs/safety.js +11 -4
  195. package/package.json +1 -1
  196. package/test/export-surface.snapshot.json +99 -13
  197. package/wire-changes/core@7.17.7.json +49 -0
  198. package/wire-changes/core@7.18.0.json +1273 -0
  199. package/wire-changes/core@7.19.0.json +457 -0
  200. package/dist/tools/fs/read-face.d.ts +0 -68
  201. package/dist/tools/fs/read-face.js +0 -47
@@ -22,6 +22,7 @@
22
22
  * contract (`ToolGateInput`, `ToolGateResult`, the observer payload), which stays declared there.
23
23
  */
24
24
  import { type ClassifierDenyCause, type DeniedBy, type GateOutcome, type Settlement } from "../gate-outcome.js";
25
+ import type { AutoModeClassifierRound } from "../auto-mode.js";
25
26
  import type { AskOrigin, AskOriginFacts } from "../ask-origin.js";
26
27
  import { type PermissionResult, type ResolvedAsk, type ToolCallRequest } from "../tool-policy.js";
27
28
  import type { SafeNotifier } from "../safe-notify.js";
@@ -40,6 +41,10 @@ export interface GateExitFacts {
40
41
  deniedBy?: DeniedBy;
41
42
  cause?: ClassifierDenyCause;
42
43
  settled?: SettledAsk;
44
+ /** The classifier CLASSIFICATION this pass ran, when it ran one on a decider that can name its model.
45
+ * Orthogonal to `deniedBy`: one that ALLOWED is attributed too (a fallback model's allow is exactly the case
46
+ * an operator needs to see), which is why it rides the disposition's shared member, not the deny arm. */
47
+ classifier?: AutoModeClassifierRound;
43
48
  }
44
49
  /**
45
50
  * Mint the outcome. A defect here is an ENGINE defect (a refusing site that did not say who refused, a
@@ -220,6 +225,13 @@ export interface GatePass {
220
225
  * re-check when an inherited station's attested deny arrives through the chain; undefined for a ruling
221
226
  * block and every non-classifier deny. The exit mints `GateDisposition.cause` from it. */
222
227
  denyCause: ClassifierDenyCause | undefined;
228
+ /** borrowed-mutable — the classifier CLASSIFICATION this pass ran ({@link AutoModeClassifierRound}).
229
+ * Written ONCE by the gate's classifier step from the verdict the decider handed back, on EVERY arm (allow
230
+ * included — a fallback model's allow is the fact the record exists for); absent when no classifier was
231
+ * consulted or the decider could name no model. Both exits mint `GateDisposition.classifier` from it.
232
+ * OPTIONAL rather than `| undefined` (the `probeUnanswered` form): the dispatch that builds the pass is
233
+ * not a station of this lane and has nothing to declare here. */
234
+ classifierRound?: AutoModeClassifierRound;
223
235
  /** borrowed-mutable — the standing verdict. The no-policy default until the adjudication station writes the
224
236
  * policy's answer; then rewritten by every station that changes the verdict (fold, tightens, org, rule
225
237
  * lane, stamp, classifier, sandbox, content lane, resolution, edit chain). */
@@ -4,8 +4,11 @@ import { inlineUntrusted } from "../untrusted-text.js";
4
4
  import { formatHookFeedback } from "../reminder-mint.js";
5
5
  export function mintGateOutcome(facts) {
6
6
  const settlement = facts.settled !== undefined ? Object.freeze({ ...facts.settled.settlement, who: Object.freeze({ ...facts.settled.settlement.who }) }) : undefined;
7
+ const attribution = facts.classifier !== undefined ? Object.freeze({ ...facts.classifier, ...(facts.classifier.fallback !== undefined ? { fallback: Object.freeze({ ...facts.classifier.fallback }) } : {}) }) : undefined;
7
8
  const outcome = Object.freeze({
8
- disposition: Object.freeze(facts.deniedBy !== undefined ? { kind: "denied", deniedBy: facts.deniedBy, ...(facts.cause !== undefined ? { cause: facts.cause } : {}) } : { kind: "allowed" }),
9
+ disposition: Object.freeze(facts.deniedBy !== undefined
10
+ ? { kind: "denied", deniedBy: facts.deniedBy, ...(facts.cause !== undefined ? { cause: facts.cause } : {}), ...(attribution !== undefined ? { classifier: attribution } : {}) }
11
+ : { kind: "allowed", ...(attribution !== undefined ? { classifier: attribution } : {}) }),
9
12
  ...(facts.settled !== undefined ? { settlement: settlement, origin: facts.settled.origin } : {}),
10
13
  });
11
14
  const defects = screenGateOutcome(outcome);
@@ -114,7 +117,7 @@ export async function exitGate(pass) {
114
117
  }
115
118
  if (pass.deniedBy === undefined)
116
119
  throw new Error(`the tool gate refused "${toolName}" without a refusing layer — every deny site attributes itself`);
117
- const gate = mintGateOutcome({ deniedBy: pass.deniedBy, ...(pass.denyCause !== undefined ? { cause: pass.denyCause } : {}), ...(ledger.settled !== undefined ? { settled: ledger.settled } : {}) });
120
+ const gate = mintGateOutcome({ deniedBy: pass.deniedBy, ...(pass.denyCause !== undefined ? { cause: pass.denyCause } : {}), ...(ledger.settled !== undefined ? { settled: ledger.settled } : {}), ...(pass.classifierRound !== undefined ? { classifier: pass.classifierRound } : {}) });
118
121
  const unresolvable = askUnresolvableCauseOf(pass.decision, { toolCallId, toolName }) ?? unresolvableCauseOfSettlement(gate);
119
122
  if (unresolvable !== undefined) {
120
123
  const cause = unresolvable;
@@ -142,7 +145,7 @@ export async function exitGate(pass) {
142
145
  }
143
146
  return {
144
147
  updatedInput: pass.currentInput === event.input ? undefined : pass.currentInput,
145
- gate: mintGateOutcome(ledger.settled !== undefined ? { settled: ledger.settled } : {}),
148
+ gate: mintGateOutcome({ ...(ledger.settled !== undefined ? { settled: ledger.settled } : {}), ...(pass.classifierRound !== undefined ? { classifier: pass.classifierRound } : {}) }),
146
149
  preToolContext: pass.preToolContext,
147
150
  };
148
151
  }
@@ -7,13 +7,18 @@
7
7
  * face) exactly as `Read` does, then reads the bytes once from the execution env. A local stub's store
8
8
  * (`capabilities.storageDir`) must lie OUTSIDE every root the run may write or read — a ledger the model can
9
9
  * edit through the fs tools is not a trust boundary — and a store inside one refuses the mount, naming both.
10
+ * The store is a path on THIS process's filesystem; a root is the execution env's canonical key. The two are
11
+ * compared as identities under one spelling family only when both spell the same family (`within` reads both
12
+ * under the root's family); a root of the other family names a filesystem the store cannot be on, and no
13
+ * containment claim is made against it.
10
14
  * Synchronous: the stretch has no await (the file read is a closure the tool calls later).
11
15
  */
12
16
  import type { ExecutionEnv } from "../../internal/harness.js";
17
+ import type { OnAsk } from "../tool-policy.js";
13
18
  import { type ArtifactPlane } from "../artifact-host.js";
14
19
  import { RosterBuilder } from "../tool-roster.js";
15
20
  import type { AgentTool, RunnerDeps } from "../types.js";
16
- import type { ReadDenyMatcher, ReadFace } from "../../tools/fs/index.js";
21
+ import type { ReadDenyMatcher, ContainmentFace } from "../../tools/fs/index.js";
17
22
  import type { Prepared } from "./contracts.js";
18
23
  export interface PrepareArtifactInput {
19
24
  /** borrowed-readonly — the deployment's hosting seat and the notice sink the refusal arm announces on. */
@@ -26,6 +31,9 @@ export interface PrepareArtifactInput {
26
31
  sessionId: string;
27
32
  /** borrowed-readonly — the spawned name this run carries, when any (call-context attribution only). */
28
33
  agentName: string | undefined;
34
+ /** borrowed-readonly — the run's effective approver (`spec.onAsk ?? deps.onAsk`, the seat the tool gate resolves at):
35
+ * the tool's shared-read consent gate rides it as a mount dep (a core mount's execute ctx is minimal). */
36
+ onAsk: OnAsk | undefined;
29
37
  /** borrowed-readonly — the run's resolved env: the byte source of a published local file. */
30
38
  executionEnv: ExecutionEnv;
31
39
  /** borrowed-readonly — the canonical task root the fs fence contains reads to; undefined ⇒ no local-file publish. */
@@ -35,7 +43,7 @@ export interface PrepareArtifactInput {
35
43
  /** borrowed-readonly — the read-deny matcher in force for the run, when any. */
36
44
  readDenyMatcher: ReadDenyMatcher | undefined;
37
45
  /** borrowed-readonly — the resolved read face (`"open"` lifts root containment for reads). */
38
- resolvedReadFace: ReadFace | undefined;
46
+ resolvedReadFace: ContainmentFace | undefined;
39
47
  /** borrowed-readonly — the tracked-cwd cell; `current` is read per call as the relative-path base. */
40
48
  handsCwdRef: Prepared["cwdRef"];
41
49
  /** borrowed-mutable — the run's roster builder; writer here: ONE mount when the seat is valid. */
@@ -5,10 +5,10 @@ import { RosterBuilder } from "../tool-roster.js";
5
5
  import { defineTool } from "../tools.js";
6
6
  import { inlineUntrusted } from "../untrusted-text.js";
7
7
  import { physicalPathOfExisting } from "../physical-path.js";
8
- import { resolveKey, violationDetails, violationText, withinAnyRoot } from "../../tools/fs/safety.js";
8
+ import { pathFamilyOf, resolveKey, violationDetails, violationText, withinAnyRoot } from "../../tools/fs/safety.js";
9
9
  import { createArtifactTool } from "../../tools/artifact/artifact-tool.js";
10
10
  export function prepareArtifact(input) {
11
- const { deps, runId, taskId, sessionId, agentName, executionEnv, rootCanonical, additionalRootsCanonical, readDenyMatcher, resolvedReadFace, handsCwdRef, roster, firstPartyOffload, envHandToolNames } = input;
11
+ const { deps, runId, taskId, sessionId, agentName, onAsk, executionEnv, rootCanonical, additionalRootsCanonical, readDenyMatcher, resolvedReadFace, handsCwdRef, roster, firstPartyOffload, envHandToolNames } = input;
12
12
  const host = deps.artifactHost;
13
13
  if (host === undefined)
14
14
  return { artifactPlane: undefined };
@@ -26,7 +26,8 @@ export function prepareArtifact(input) {
26
26
  const storageDir = verdict.capabilities.storageDir;
27
27
  if (storageDir !== undefined && rootCanonical !== undefined) {
28
28
  const real = physicalPathOfExisting(storageDir);
29
- const inside = [rootCanonical, ...additionalRootsCanonical].find((root) => withinAnyRoot([root], real));
29
+ const storeFamily = pathFamilyOf({ root: real });
30
+ const inside = [rootCanonical, ...additionalRootsCanonical].find((root) => pathFamilyOf({ root }) === storeFamily && withinAnyRoot([root], real));
30
31
  if (inside !== undefined)
31
32
  return refuse(`the local artifact store ${storageDir} lies inside a root this run may reach through its file tools (${inside}); put the store outside every task root and additional directory`);
32
33
  }
@@ -56,7 +57,7 @@ export function prepareArtifact(input) {
56
57
  return { ok: false, reason: "too_large", size: r.value.byteLength, limit: maxBytes, judgedOn: "bytes" };
57
58
  return { ok: true, key: rk.key, bytes: r.value };
58
59
  };
59
- const artifactSpec = createArtifactTool({ host, capabilities: verdict.capabilities, runId, taskId, sessionId, ...(agentName !== undefined ? { agentName } : {}), readLocalFile });
60
+ const artifactSpec = createArtifactTool({ host, capabilities: verdict.capabilities, runId, taskId, sessionId, ...(agentName !== undefined ? { agentName } : {}), readLocalFile, ...(onAsk !== undefined ? { onAsk } : {}) });
60
61
  roster.mount(firstPartyOffload(defineTool(artifactSpec)), { source: "builtin", mountedBy: "optional" });
61
62
  envHandToolNames.add(ARTIFACT_TOOL_NAME);
62
63
  return { artifactPlane: verdict.capabilities.plane };
@@ -102,6 +102,7 @@ export function prepareAskLane(input) {
102
102
  ...(decision.action === "ask" ? gateAskCarry(decision, isLiveApproverSeat(onAsk), autoModeDenialTracking) : {}),
103
103
  ...(decision.action === "ask" && decision.probeReason !== undefined ? { probeReason: decision.probeReason } : {}),
104
104
  ...(decision.action === "ask" && decision.probeCause !== undefined ? { probeCause: decision.probeCause } : {}),
105
+ ...(decision.action === "ask" && decision.probeReadRootCandidate !== undefined ? { readRootCandidate: decision.probeReadRootCandidate } : {}),
105
106
  ...(decision.action === "ask" && decision.ruleEvidence !== undefined ? { ruleEvidence: decision.ruleEvidence } : {}),
106
107
  }, onAsk, composedCallSignal(callSignal), lateAskSettlementObserver({ toolName: req.toolName, toolCallId: req.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice, onError: deps.onError }));
107
108
  const waitMs = Math.max(0, now() - t0);
@@ -33,7 +33,7 @@ import type { OnAsk } from "../tool-policy.js";
33
33
  import type { RunnerDeps, RuntimeCaps, TaskEvent, TaskSpec, ToolExecuteContext } from "../types.js";
34
34
  import type { AutoModeArmFact } from "../wiring-manifest.js";
35
35
  import type { AssertAllKeysHandled } from "../ask-origin.js";
36
- import type { ReadFace } from "../../tools/fs/index.js";
36
+ import type { ContainmentFace } from "../../tools/fs/index.js";
37
37
  import type { InheritedGate, Prepared, RunInternals, RunnerSelfSeat, ToolFaceSnapshot } from "./contracts.js";
38
38
  import { type BlockedRef } from "./synthetic-tools.js";
39
39
  import { RosterBuilder } from "../tool-roster.js";
@@ -91,7 +91,9 @@ export interface PrepareCapsAndWorkflowInput {
91
91
  frozenOnAsk: OnAsk | undefined;
92
92
  /** borrowed-readonly — the parent SPEC seat the children inherit (minted once by the inherited-gate phase; the Workflow mount's dep, the same value the delegation ctx carries). */ parentSpecSeat: ParentSpecSeat;
93
93
  /** borrowed-readonly — the ONE read-face carrier expression (the Workflow mount's parentReadFace getter calls it). */
94
- carrierReadFace: () => ReadFace | undefined;
94
+ carrierReadFace: () => ContainmentFace | undefined;
95
+ /** borrowed-readonly — the same carrier expression on the WRITE seat. */
96
+ carrierWriteFace: () => ContainmentFace | undefined;
95
97
  /** borrowed-readonly — the filtered display-forwarding wrapper (the Workflow mount's forwardEvent; absent ⇒ no key). */
96
98
  forwardEvent: ((e: TaskEvent) => void) | undefined;
97
99
  /** borrowed-readonly — the child-chain assembly (the Workflow mount's inheritedGateForChildren). */
@@ -5,34 +5,20 @@ import { CROSS_SESSION_CLASSIFIER_RULE } from "../../agents/cross-session-envelo
5
5
  import { createRunWorkflowTool } from "../../orchestration/run-workflow-tool.js";
6
6
  import { isSelfOrchestrationActive } from "../../orchestration/workflow-script-runner.js";
7
7
  import { resolveWorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
8
- import { thinkingOffExpressible } from "../../brain/reasoning.js";
9
8
  import { autoModeArmingRecipeOf } from "../auto-mode-arming.js";
10
- import { AUTO_MODE_CLASSIFIER_LOW_REASONING_BUDGET_TOKENS, AUTO_MODE_CLASSIFIER_MAX_TOKENS } from "../auto-mode-defaults.js";
11
9
  import { buildAutoModePrompt, renderAutoModeAction, renderAutoModeWindow } from "../auto-mode-prompt.js";
12
- import { createAutoModeDecider, createAutoModeDenialTracker } from "../auto-mode.js";
10
+ import { autoModeClassifierFallbackOf, autoModeRoundNotAsked, createAutoModeDecider, createAutoModeDenialTracker } from "../auto-mode.js";
13
11
  import { checkpointStoreOfSeat } from "../checkpoint-seat.js";
14
12
  import { COMPLIANCE_CAPABILITIES, WEB_FETCH_TOOL_NAME, resolveComplianceDenies } from "../compliance.js";
15
13
  import { createPresentPlanTool, createEnterPlanModeTool } from "../present-plan-tool.js";
16
- import { resolveTaskModel } from "../roles.js";
17
14
  import { brainToRuntime } from "../runtime.js";
18
15
  import { defaultTaskRegistry } from "../task-registry.js";
19
- import { emitTrace } from "../trace.js";
20
16
  import { defineTool, isDefineToolProduct, rebindDefineToolCtx } from "../tools.js";
21
17
  const MOUNT_REMEDY = "author it as a plain-object ToolSpec (own `name` and `execute`), hand the defineTool product itself, or wrap a product through the supported wrapper form (a fresh object re-stamped with `stampDefineToolBrand` whose rebind re-wraps the rebound product)";
22
18
  import { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource } from "../runtime-caps.js";
23
19
  import { derivedRouteFallsBack } from "./derived-route-fallback.js";
20
+ import { CLASSIFIER_ATTEMPT_SUFFIX, CLASSIFIER_THINK_SEAT, announceClassifierRoleDefaulted, classifierLadderRungs, classifierReplyAsksAgain, classifierReplyFailure, classifierReplyVerdictText, classifierRoundObserver, classifierRungSeat, classifierSeatModel } from "./classifier-ladder.js";
24
21
  import { REPORT_FINDINGS_TOOL_NAME, createReportBlockedTool, createReportFindingsTool } from "./synthetic-tools.js";
25
- function classifierReplyText(reply) {
26
- return reply.content
27
- .filter((c) => c.type === "text")
28
- .map((c) => c.text)
29
- .join("");
30
- }
31
- function classifierReplyEmptyAtCap(reply) {
32
- if (classifierReplyText(reply) !== "")
33
- return false;
34
- return reply.stopReason === "length" || (reply.stopReason === "error" && reply.errorKind === "length_empty");
35
- }
36
22
  import { RosterBuilder, callerMountSource } from "../tool-roster.js";
37
23
  function assembleParentCaptureState(o, i, ctl, ancestors) {
38
24
  const build = (optedOut, indeterminate) => ({
@@ -112,7 +98,7 @@ function autoModeArmReasonOf(intent, facePresent, caps, capsFaulted) {
112
98
  return { armed: true, reason: "armed" };
113
99
  }
114
100
  export async function prepareCapsAndWorkflow(input) {
115
- const { spec, deps, parentSpecSeat, checkpointSeat, internals, session, sessionId, runId, hostTaskId, taskScope, taskRootFinal, model, thinking, lockedPreflight, toolEffects, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, harnessRef, frozenOnAsk, carrierReadFace, forwardEvent, inheritedGateForChildren, enrichSpecToolCtx, maybeOffload, requestReview, enterPlanMode, autoModeIntent, peerLaneActive, peerSendMessageBuiltIn, runnerSelf } = input;
101
+ const { spec, deps, parentSpecSeat, checkpointSeat, internals, session, sessionId, runId, hostTaskId, taskScope, taskRootFinal, model, thinking, lockedPreflight, toolEffects, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, harnessRef, frozenOnAsk, carrierReadFace, carrierWriteFace, forwardEvent, inheritedGateForChildren, enrichSpecToolCtx, maybeOffload, requestReview, enterPlanMode, autoModeIntent, peerLaneActive, peerSendMessageBuiltIn, runnerSelf } = input;
116
102
  const roster = new RosterBuilder(toolEffects);
117
103
  for (const t of spec.tools ?? []) {
118
104
  const mounted = mountCallerTool(t);
@@ -211,53 +197,54 @@ export async function prepareCapsAndWorkflow(input) {
211
197
  const autoModeArmReason = autoModeArmReasonOf(autoModeIntent, deps.autoMode !== undefined, runtimeCaps, runtimeCapsFaulted);
212
198
  if (autoModeIntent && deps.autoMode !== undefined && runtimeCaps?.autoMode !== false) {
213
199
  const am = deps.autoMode;
214
- let classifierModel;
215
- try {
216
- classifierModel = resolveTaskModel({ modelRole: "classifier" }, deps).model;
217
- }
218
- catch {
219
- classifierModel = model;
220
- }
221
- if (await derivedRouteFallsBack({ seat: "auto-mode-classifier", derived: classifierModel, primary: model, brain: deps.brain, getApiKeyAndHeaders: spec.getApiKeyAndHeaders, onNotice: deps.onNotice, sessionId, runId })) {
200
+ const classifierSeat = classifierSeatModel({ deps, main: model });
201
+ let classifierModel = classifierSeat.model;
202
+ if (await derivedRouteFallsBack({ seat: "auto-mode-classifier", derived: classifierModel, primary: model, brain: deps.brain, getApiKeyAndHeaders: spec.getApiKeyAndHeaders, onNotice: deps.onNotice, sessionId, runId, onBrokenPairing: autoModeClassifierFallbackOf(am.classifierFallback) === "none" ? "refuse" : "fall_back" })) {
222
203
  classifierModel = model;
223
204
  }
205
+ announceClassifierRoleDefaulted({ onNotice: deps.onNotice, seat: { ...classifierSeat, model: classifierModel }, ...(sessionId !== undefined ? { sessionId } : {}), ...(runId !== undefined ? { runId } : {}) });
224
206
  const classifierLaneRule = peerLaneActive && peerSendMessageBuiltIn;
225
207
  const classifierSystemPrompt = buildAutoModePrompt(classifierLaneRule ? { ...am, crossSessionMessagesRule: CROSS_SESSION_CLASSIFIER_RULE } : am);
226
208
  const classifierRuntime = brainToRuntime(deps.brain);
227
- const classifierOffSeat = { reasoning: "off", maxTokens: AUTO_MODE_CLASSIFIER_MAX_TOKENS };
228
- const classifierLowSeat = { reasoning: "low", maxTokens: AUTO_MODE_CLASSIFIER_MAX_TOKENS + AUTO_MODE_CLASSIFIER_LOW_REASONING_BUDGET_TOKENS };
229
- const classifierSeat = thinkingOffExpressible(classifierModel) ? classifierOffSeat : classifierLowSeat;
209
+ const classifierRungs = classifierLadderRungs(am.classifierFallback, classifierModel, model);
210
+ const roundPrompt = new WeakMap();
230
211
  autoModeDenialTracking = createAutoModeDenialTracker(am.denialLimit);
231
212
  autoModeDecider = createAutoModeDecider({
232
213
  ...(am.timeoutMs !== undefined ? { timeoutMs: am.timeoutMs } : {}),
233
- onClassified: (info) => emitTrace(deps.tracer, () => ({
234
- kind: "auto_mode.classified",
235
- version: 1,
236
- taskId: hostTaskId,
237
- toolCallId: info.toolCallId,
238
- model: classifierModel.id,
239
- ms: info.ms,
240
- verdict: info.verdict,
241
- ...(info.cause !== undefined ? { cause: info.cause } : {}),
242
- ts: Date.now(),
243
- })),
244
- classify: async (input, signal) => {
245
- const ctx = await session.buildContext();
246
- const known = ctx.messages.filter((m) => !isSyntheticApiErrorMessage(m) &&
247
- (m.role === "user" || m.role === "assistant" || m.role === "toolResult"));
248
- const userPrompt = renderAutoModeWindow(known, am.window) + renderAutoModeAction(input);
249
- const classifierAuth = await spec.getApiKeyAndHeaders?.(classifierModel);
250
- const request = (seat) => classifierRuntime.completeSimple(classifierModel, { systemPrompt: classifierSystemPrompt, messages: [{ role: "user", content: userPrompt, timestamp: Date.now() }] }, {
251
- signal,
252
- ...seat,
253
- ...(classifierAuth?.apiKey !== undefined ? { apiKey: classifierAuth.apiKey } : {}),
254
- ...(classifierAuth?.headers !== undefined ? { headers: classifierAuth.headers } : {}),
255
- });
256
- let response = await request(classifierSeat);
257
- if (classifierSeat === classifierOffSeat && classifierReplyEmptyAtCap(response)) {
258
- response = await request(classifierLowSeat);
214
+ candidates: classifierRungs.map((m) => m.id),
215
+ onClassified: classifierRoundObserver({ tracer: deps.tracer, onNotice: deps.onNotice, taskId: hostTaskId, seatModelId: classifierModel.id, ...(sessionId !== undefined ? { sessionId } : {}), ...(runId !== undefined ? { runId } : {}) }),
216
+ classify: async (input, signal, rung) => {
217
+ const rungModel = rung !== undefined ? classifierRungs[rung.index] : undefined;
218
+ if (rung === undefined || rungModel === undefined) {
219
+ throw new Error(`the auto-mode classifier was asked at ladder position ${JSON.stringify(rung?.index)}, which this arming did not build`);
220
+ }
221
+ let build = rung.index > 0 ? roundPrompt.get(input) : undefined;
222
+ if (build === undefined) {
223
+ build = (async () => {
224
+ const ctx = await session.buildContext();
225
+ const known = ctx.messages.filter((m) => !isSyntheticApiErrorMessage(m) && (m.role === "user" || m.role === "assistant" || m.role === "toolResult"));
226
+ return renderAutoModeWindow(known, am.window) + renderAutoModeAction(input);
227
+ })();
228
+ roundPrompt.set(input, build);
259
229
  }
260
- return classifierReplyText(response);
230
+ const userPrompt = await build.catch((e) => { throw autoModeRoundNotAsked(e); });
231
+ const classifierAuth = await spec.getApiKeyAndHeaders?.(rungModel);
232
+ const request = async (seat, suffix) => {
233
+ const response = await classifierRuntime.completeSimple(rungModel, { systemPrompt: classifierSystemPrompt, messages: [{ role: "user", content: userPrompt + suffix, timestamp: Date.now() }] }, {
234
+ signal,
235
+ ...seat,
236
+ ...(classifierAuth?.apiKey !== undefined ? { apiKey: classifierAuth.apiKey } : {}),
237
+ ...(classifierAuth?.headers !== undefined ? { headers: classifierAuth.headers } : {}),
238
+ });
239
+ const failure = classifierReplyFailure(response);
240
+ if (failure !== undefined)
241
+ throw failure;
242
+ return response;
243
+ };
244
+ let response = await request(classifierRungSeat(rungModel), CLASSIFIER_ATTEMPT_SUFFIX.first);
245
+ if (classifierReplyAsksAgain(response) && signal?.aborted !== true)
246
+ response = await request(CLASSIFIER_THINK_SEAT, CLASSIFIER_ATTEMPT_SUFFIX.reask);
247
+ return classifierReplyVerdictText(response);
261
248
  },
262
249
  });
263
250
  if (am.persistArming === true)
@@ -325,6 +312,7 @@ export async function prepareCapsAndWorkflow(input) {
325
312
  parentCwd: taskRootFinal,
326
313
  parentThinking: () => childThinkingSeat(undefined, deploymentSubagentThinking(runnerSelf.agentCatalog?.roles), harnessRef.current?.getThinkingLevel() ?? thinking).thinking,
327
314
  parentReadFace: () => carrierReadFace(),
315
+ parentWriteFace: () => carrierWriteFace(),
328
316
  parentReadDenyPatterns: () => {
329
317
  const readDenyAdditionsNormalized = input.readDenyAdditionsNormalized();
330
318
  return readDenyAdditionsNormalized.length > 0 ? readDenyAdditionsNormalized : undefined;
@@ -25,6 +25,7 @@ import type { ThinkingLevel } from "../../internal/harness.js";
25
25
  import { type MicroCompactMachine } from "../context-edit.js";
26
26
  import { type LockedPreflight } from "../locked-config.js";
27
27
  import { type ResolvedRole } from "../roles.js";
28
+ import { type ResolvedSkillListingBudget } from "../skill-listing-budget.js";
28
29
  import type { SessionStore } from "../session.js";
29
30
  import type { RunnerDeps, TaskLimits, TaskSpec } from "../types.js";
30
31
  import { type UsageWindow } from "../usage-window-store.js";
@@ -115,7 +116,7 @@ export interface PrepareConfigDoorsInput {
115
116
  /** borrowed-readonly — the caller's spec. NEVER mutated here: the per-task agents rebind returns a
116
117
  * SHALLOW COPY on {@link PrepareConfigDoorsResult.spec} (only `tools` replaced). */
117
118
  spec: TaskSpec;
118
- /** borrowed-readonly — deployment seats. Read for: readFace value screen, locked-config preflight,
119
+ /** borrowed-readonly — deployment seats. Read for: the two containment-face value screens, locked-config preflight,
119
120
  * retention door, interaction-posture door (incl. `deps.onError` disclosure), usage windows,
120
121
  * brain-call guardrail default, model/role maps. */
121
122
  deps: RunnerDeps;
@@ -184,6 +185,11 @@ export interface PrepareConfigDoorsResult {
184
185
  * mutated (or getter-backed) after the door must not present an unscreened value to the mark
185
186
  * branch (the read-once posture). */
186
187
  memoryDelegationEvidence: "static-face" | "attested-only";
188
+ /** owned — the lowered skills-listing budget seats (`TaskSpec.skillListingBudgetFraction` /
189
+ * `skillListingMaxDescChars`): the renderer's two numbers, resolved once against the resolved main
190
+ * model's window. Read at the door so a bad value refuses BEFORE session acquisition, and carried so
191
+ * the listing phase and the boundary renderer measure against the same resolution. */
192
+ skillListingBudget: ResolvedSkillListingBudget;
187
193
  /** owned — the NORMALIZED memory-provenance mode: the deps seat read ONCE in
188
194
  * the synchronous pre-await stretch, screened, absent folded to the `"carry"` default.
189
195
  * The memory phase constructs every engine with THIS value — never a live deps re-read. */
@@ -1,5 +1,5 @@
1
1
  import { resolveBrainCallGuardrailMs } from "../../brain/timeout.js";
2
- import { assertReadFaceValue } from "../../tools/fs/index.js";
2
+ import { assertContainmentFaceValue } from "../../tools/fs/index.js";
3
3
  import { MICRO_COMPACT_MACHINES } from "../context-edit.js";
4
4
  import { assertNoRetiredRunnerDeps } from "./retired-runner-deps.js";
5
5
  import { resolveCheckpointStore } from "../checkpoint-store.js";
@@ -7,6 +7,8 @@ import { isModelPriced, malformedPricingField, modelCostToPricing } from "../pri
7
7
  import { preflightLockedConfig } from "../locked-config.js";
8
8
  import { assertRetentionCapability } from "../retention.js";
9
9
  import { resolveModel, resolveTaskModel, roleModelIfSet } from "../roles.js";
10
+ import { resolveSkillListingBudget } from "../skill-listing-budget.js";
11
+ import { describeRejectedValue } from "../tool-search-setting.js";
10
12
  import { applyToolModelGate, assertRestoreGatedToolsValue, modelIdTail } from "../tool-model-gate.js";
11
13
  import { resolveUsageWindows } from "../usage-window-store.js";
12
14
  import { deriveAskEffective, resolveAskSeamForm, resolveQuestionSeam } from "../wiring-manifest.js";
@@ -35,16 +37,6 @@ export function limitConfigError(code, message) {
35
37
  e.code = code;
36
38
  return e;
37
39
  }
38
- function describeRejectedValue(value) {
39
- try {
40
- const quoted = JSON.stringify(value);
41
- if (quoted !== undefined)
42
- return quoted;
43
- }
44
- catch {
45
- }
46
- return `a ${typeof value} value`;
47
- }
48
40
  export function resolveTaskLimits(limits) {
49
41
  if (limits === undefined)
50
42
  return undefined;
@@ -196,8 +188,10 @@ export function prepareConfigDoors(input) {
196
188
  throw e;
197
189
  }
198
190
  }
199
- assertReadFaceValue(spec.readFace, "TaskSpec.readFace");
200
- assertReadFaceValue(deps.readFace, "readFace (deployment seat)");
191
+ assertContainmentFaceValue(spec.readFace, "read", "spec");
192
+ assertContainmentFaceValue(deps.readFace, "read", "deps");
193
+ assertContainmentFaceValue(spec.writeFace, "write", "spec");
194
+ assertContainmentFaceValue(deps.writeFace, "write", "deps");
201
195
  const memoryDelegationEvidenceRaw = deps.memoryDelegationEvidence;
202
196
  if (memoryDelegationEvidenceRaw !== undefined && memoryDelegationEvidenceRaw !== "static-face" && memoryDelegationEvidenceRaw !== "attested-only") {
203
197
  const evidence = memoryDelegationEvidenceRaw;
@@ -414,6 +408,11 @@ export function prepareConfigDoors(input) {
414
408
  if (gateDecision.survivors !== undefined) {
415
409
  spec = { ...spec, tools: gateDecision.survivors };
416
410
  }
411
+ const skillListingBudget = resolveSkillListingBudget({
412
+ fraction: spec.skillListingBudgetFraction,
413
+ maxDescChars: spec.skillListingMaxDescChars,
414
+ model,
415
+ });
417
416
  const modelGate = Object.freeze({
418
417
  removedByClass: gateDecision.removedByClass,
419
418
  unknownClasses: gateDecision.unknownClasses,
@@ -437,6 +436,7 @@ export function prepareConfigDoors(input) {
437
436
  fableMitigations,
438
437
  modelGate,
439
438
  memoryDelegationEvidence,
439
+ skillListingBudget,
440
440
  memoryProvenance,
441
441
  memoryCapturePolicy,
442
442
  usageWindows,
@@ -5,11 +5,10 @@ import type { RunnerDeps, TaskSpec } from "../types.js";
5
5
  import type { AnnounceOnceSink, ToolFaceSnapshot } from "./contracts.js";
6
6
  import { RosterBuilder } from "../tool-roster.js";
7
7
  export interface PrepareDeferClassifyInput {
8
- /** borrowed-readonly — the REBOUND spec (`spec′`); only `tools` is read (the caller roster the
9
- * classifier judges: names for the full-schema filter, specs for the `defer` declarations). */
8
+ /** borrowed-readonly — the REBOUND spec (`spec′`); only `tools` is read (names for the full-schema filter, specs for the `defer` declarations). */
10
9
  spec: Pick<TaskSpec, "tools" | "excludeAllTools">;
11
- /** borrowed-readonly — deployment deps; only `deferMode` is read (the classifier's mode knob). */
12
- deps: Pick<RunnerDeps, "deferMode">;
10
+ /** borrowed-readonly — deps; `deferMode` (candidate widening) and `toolSearch` (rule ②'s gate) are the only seats read. */
11
+ deps: Pick<RunnerDeps, "deferMode" | "toolSearch">;
13
12
  /** borrowed-mutable — the shared mount array (later refs alias it; in-place contract, never
14
13
  * re-created). Mutated here, in order: `splice` of every excluded name; element REPLACEMENT with a
15
14
  * shallow copy for the classic-profile description swap (the caller's ToolSpec object is never
@@ -17,21 +16,17 @@ export interface PrepareDeferClassifyInput {
17
16
  * (element replacement with the wrapped object), the disclosure mount, the MCP refresh re-splice. Since
18
17
  * Every one of those writes is a builder verb (`unmountWhere` / `replaceAt`). */
19
18
  roster: RosterBuilder;
20
- /** borrowed-readonly — the frozen task-start tool face: `exclude` (the valve), `defer` (operator
21
- * defers, exact wire names), `alwaysLoad` (the inline pins). */
19
+ /** borrowed-readonly — the frozen task-start tool face: `exclude` (the valve), `defer` (operator defers, exact wire names), `alwaysLoad` (the pins). */
22
20
  toolFaceSnapshot: Pick<ToolFaceSnapshot, "exclude" | "defer" | "alwaysLoad">;
23
- /** borrowed-readonly — whether a real execution env is mounted (the full-shell invariant runs only
24
- * where a shell could be on the roster). */
21
+ /** borrowed-readonly — whether a real execution env is mounted (the full-shell invariant runs only where a shell could be on the roster). */
25
22
  handsEnabled: boolean;
26
- /** borrowed-readonly — the spec-time precomputation the invariant checks the post-exclusion roster
27
- * against: a mismatch is an engine bug, thrown. */
23
+ /** borrowed-readonly — the spec-time precomputation the invariant checks the post-exclusion roster against: a mismatch is an engine bug, thrown. */
28
24
  fullShellReachable: boolean;
29
25
  /** borrowed-readonly — the resolved prompt profile (`classic` swaps in `descriptionClassic`). */
30
26
  promptProfile: "simple" | "classic";
31
27
  /** borrowed-readonly — the resolved model (the deferral gate's threshold reads its context window). */
32
28
  model: Model;
33
- /** borrowed-readonly — the materialized MCP roster; only `tools` is read (protocol names for the
34
- * remote-roster arm; `mcpAlwaysLoad` self-declarations, losing to an explicit operator defer). */
29
+ /** borrowed-readonly — the materialized MCP roster; only `tools` is read (protocol names; `mcpAlwaysLoad` self-declarations lose to an operator defer). */
35
30
  mcp: Pick<MaterializedMcp, "tools">;
36
31
  /** borrowed-readonly — the materialized A2A roster; only `tools` is read (protocol names). */
37
32
  a2a: Pick<MaterializedA2a, "tools">;
@@ -39,15 +34,12 @@ export interface PrepareDeferClassifyInput {
39
34
  sharedMemoryPairMounted: boolean;
40
35
  /** borrowed-readonly — whether the engine-memory pair mounted (project-context phase). */
41
36
  memoryEnginePairMounted: boolean;
42
- /** borrowed-readonly — the memory block as the project-context phase left it. The engine pair's
43
- * retraction strips the recall paragraph from it and the stripped value returns as
44
- * {@link PrepareDeferClassifyResult.memoryBlock}; the input value itself is never mutated. */
37
+ /** borrowed-readonly — the memory block as the project-context phase left it; the engine pair's retraction strips the recall paragraph and returns the
38
+ * stripped value as {@link PrepareDeferClassifyResult.memoryBlock}. The input is never mutated. */
45
39
  memoryBlock: string | undefined;
46
- /** borrowed-readonly — the exact recall-discipline segment the memory phase composed (the strip
47
- * removes THIS string, never a re-composition). */
40
+ /** borrowed-readonly — the exact recall-discipline segment the memory phase composed (the strip removes THIS string, never a re-composition). */
48
41
  memoryRecallSegment: string | undefined;
49
- /** borrowed-readonly — the once-per-session ledger's deduplicated operator sink (a retraction is a
50
- * session-level fact, announced once). */
42
+ /** borrowed-readonly — the once-per-session ledger's deduplicated operator sink (a retraction is announced once per session). */
51
43
  onceLedger: AnnounceOnceSink;
52
44
  /** borrowed-readonly — the acquired session's id (operator-line context). */
53
45
  sessionId: string;
@@ -60,6 +52,8 @@ export interface PrepareDeferClassifyResult {
60
52
  /** owned — the memory block after the engine pair's retraction strip (unchanged when no retraction
61
53
  * ran). The driver binds it under the name the prompt assembly reads. */
62
54
  memoryBlock: string | undefined;
55
+ /** owned — the gate policy this classification was MEASURED under; the census reports it from here, never a second reading of the seat. */
56
+ toolSearch: import("../tool-search-setting.js").ResolvedToolSearch;
63
57
  }
64
58
  /** The M11 phase body — prepareTask's tool-face fold + deferred classification, verbatim (module header). */
65
59
  export declare function prepareDeferClassify(input: PrepareDeferClassifyInput): PrepareDeferClassifyResult;
@@ -4,8 +4,9 @@ import { ASK_USER_QUESTION_TOOL_NAME } from "../ask-question.js";
4
4
  import { MEMORY_ENGINE_TOOL_NAMES } from "../memory-engine/tools.js";
5
5
  import { SHARED_MEMORY_TOOL_NAMES } from "../shared-memory/types.js";
6
6
  import { protocolOf } from "../protocol-table.js";
7
- import { classifyDeferred } from "./tool-defer-gate.js";
7
+ import { classifyDeferredWithGate } from "./tool-defer-gate.js";
8
8
  import { TOOL_SEARCH_NAME } from "./tool-disclosure.js";
9
+ import { resolveToolSearch, toolSearchSettingOf } from "../tool-search-setting.js";
9
10
  import { RosterBuilder } from "../tool-roster.js";
10
11
  function explicitlyDeferredMemoryTrio(mounted, roster, callerDeferNames) {
11
12
  return mounted ? MEMORY_ENGINE_TOOL_NAMES.filter((n) => roster.some((t) => t.name === n) && (callerDeferNames ?? []).includes(n)) : [];
@@ -45,13 +46,16 @@ export function prepareDeferClassify(input) {
45
46
  const mcpAlwaysLoadNames = mcp.tools
46
47
  .filter((t) => t.mcpAlwaysLoad === true && !(toolFaceSnapshot.defer ?? []).includes(t.name))
47
48
  .map((t) => t.name);
49
+ const resolvedToolSearch = resolveToolSearch(deps.toolSearch);
50
+ const toolSearchSeat = toolSearchSettingOf(resolvedToolSearch);
48
51
  const classifyDeferredOverFace = (face, builtinDeferNames) => {
49
52
  const mounted = new Set(face.map((t) => t.name));
50
- const deferredSet = classifyDeferred({
53
+ const classified = classifyDeferredWithGate({
51
54
  specs: spec.tools ?? [],
52
55
  protocolToolNames,
53
56
  fullTools: face,
54
57
  deferMode: deps.deferMode,
58
+ toolSearch: toolSearchSeat,
55
59
  model,
56
60
  callerDeferNames: (toolFaceSnapshot.defer ?? []).filter((n) => mounted.has(n)),
57
61
  builtinDeferNames: builtinDeferNames.filter((n) => mounted.has(n)),
@@ -61,6 +65,7 @@ export function prepareDeferClassify(input) {
61
65
  ...mcpAlwaysLoadNames,
62
66
  ],
63
67
  });
68
+ const deferredSet = classified.deferred;
64
69
  for (const n of [...deferredSet])
65
70
  if (!mounted.has(n))
66
71
  deferredSet.delete(n);
@@ -105,5 +110,5 @@ export function prepareDeferClassify(input) {
105
110
  else {
106
111
  deferred = classifyDeferredOverFace(tools, []);
107
112
  }
108
- return { deferred, memoryBlock };
113
+ return { deferred, memoryBlock, toolSearch: resolvedToolSearch };
109
114
  }
@@ -28,12 +28,38 @@ import type { AutoModeDecider, AutoModeDenialTracker } from "../auto-mode.js";
28
28
  import { type ComplianceCapability } from "../compliance.js";
29
29
  import { type HookEnvCapabilities, type HookInvocationIdentity, type Hooks } from "../hooks.js";
30
30
  import type { ToolPolicy } from "../tool-policy.js";
31
+ import { type GateOutcome } from "../gate-outcome.js";
31
32
  import type { RunnerDeps, TaskSpec, ToolEffect } from "../types.js";
32
33
  import type { CwdRef } from "../../tools/fs/fs-shared.js";
33
34
  import type { AskLane, IrreversibilityTier, ParkAsk, Prepared, ReversibilityProbes } from "./contracts.js";
34
35
  import type { StopForDenialLimit } from "./denial-limit-arms.js";
35
36
  import type { PermissionRuleLanes } from "./permission-rule-lanes.js";
36
37
  import { type ToolRosterDeltaSeat } from "../tool-roster.js";
38
+ /**
39
+ * The parent-thread human-rejection boundary — judged ONCE here, from the pass's own record (CC parity:
40
+ * the reference CLI's reject path aborts exactly when there is no feedback and the run is not a
41
+ * subagent; a rejection WITH the decider's note, and every delegated child — forks included — keep the
42
+ * continue / adapt-or-report posture): the final block IS a person's refusal (`deniedBy: ask_resolution`
43
+ * and a `human_refused` settlement — not a later fold layer's deny that merely followed a human
44
+ * interaction), the refusal carried NO note (`settlement.note` absent — a fact composed at the refusal,
45
+ * never re-derived from message text), and the run is not a delegated child (the raw trusted delegation
46
+ * facet — the same fact that selects the refusal text's posture, so the boundary and the words the model
47
+ * was told always agree). Policy/hook/classifier denies and window/abort settlements never satisfy it.
48
+ *
49
+ * Returns the loop directive that settles every never-started same-batch sibling: honest
50
+ * known-not-executed wording plus the machine mark (`error`/`code` twin — the wire errorCode lift
51
+ * reads `details.code`; `details.error` is the in-band mark result consumers grep) naming the
52
+ * rejected call. `undefined` = no boundary (the block stands alone, current behavior).
53
+ */
54
+ export declare function maybeHumanRejectionHalt(input: {
55
+ gate: GateOutcome | undefined;
56
+ isDelegatedChild: boolean;
57
+ toolName: string;
58
+ toolCallId: string;
59
+ }): {
60
+ reason: string;
61
+ details: Record<string, unknown>;
62
+ } | undefined;
37
63
  export interface PrepareGateStationsInput {
38
64
  /** borrowed-readonly — the in-stream ask lane, or undefined when the gate machinery is inactive (then no `tool_call`
39
65
  * station is built). Read: `adjudicate`, `resolveAskBound`, `notifyPermissionDenied`, `notifyHookError`. */
@@ -23,7 +23,7 @@ function screenGateResult(result) {
23
23
  }
24
24
  return defects.length > 0 ? { defects } : { gate, defects };
25
25
  }
26
- function maybeHumanRejectionHalt(input) {
26
+ export function maybeHumanRejectionHalt(input) {
27
27
  const d = input.gate?.disposition;
28
28
  const s = input.gate?.settlement;
29
29
  const bare = d?.kind === "denied" && d.deniedBy === "ask_resolution" && s?.kind === "human_refused" && s.note === undefined;