@sema-agent/core 7.17.6 → 7.18.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 +165 -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/route-adjudicator.d.ts +18 -0
  8. package/dist/brain/route-adjudicator.js +16 -0
  9. package/dist/core/agent-definition.d.ts +10 -0
  10. package/dist/core/artifact-host.d.ts +41 -4
  11. package/dist/core/artifact-host.js +18 -2
  12. package/dist/core/auto-mode.d.ts +141 -45
  13. package/dist/core/auto-mode.js +109 -3
  14. package/dist/core/checkpoint-store.d.ts +16 -2
  15. package/dist/core/engine-notice.d.ts +47 -1
  16. package/dist/core/engine-notice.js +7 -0
  17. package/dist/core/gate-lanes.js +2 -1
  18. package/dist/core/gate-outcome.d.ts +17 -2
  19. package/dist/core/gate-outcome.js +16 -0
  20. package/dist/core/governance-codes.d.ts +1 -1
  21. package/dist/core/governance-codes.js +8 -0
  22. package/dist/core/hook-seats.d.ts +66 -0
  23. package/dist/core/hook-seats.js +37 -0
  24. package/dist/core/runner/advertised-writable-dirs.d.ts +8 -3
  25. package/dist/core/runner/assemble-result.d.ts +2 -1
  26. package/dist/core/runner/assemble-result.js +1 -1
  27. package/dist/core/runner/classifier-ladder.d.ts +89 -0
  28. package/dist/core/runner/classifier-ladder.js +65 -0
  29. package/dist/core/runner/contracts.d.ts +69 -13
  30. package/dist/core/runner/decide-continuation.d.ts +1 -3
  31. package/dist/core/runner/decide-continuation.js +1 -7
  32. package/dist/core/runner/deferral-census.d.ts +52 -0
  33. package/dist/core/runner/deferral-census.js +44 -0
  34. package/dist/core/runner/derived-route-fallback.d.ts +8 -4
  35. package/dist/core/runner/derived-route-fallback.js +8 -3
  36. package/dist/core/runner/gate-exit.d.ts +12 -0
  37. package/dist/core/runner/gate-exit.js +6 -3
  38. package/dist/core/runner/prepare-artifact.d.ts +10 -2
  39. package/dist/core/runner/prepare-artifact.js +5 -4
  40. package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -2
  41. package/dist/core/runner/prepare-caps-and-workflow.js +30 -31
  42. package/dist/core/runner/prepare-config-doors.d.ts +1 -1
  43. package/dist/core/runner/prepare-config-doors.js +5 -3
  44. package/dist/core/runner/prepare-defer-classify.d.ts +13 -19
  45. package/dist/core/runner/prepare-defer-classify.js +8 -3
  46. package/dist/core/runner/prepare-gate-stations.d.ts +26 -0
  47. package/dist/core/runner/prepare-gate-stations.js +1 -1
  48. package/dist/core/runner/prepare-hands-readface.d.ts +24 -7
  49. package/dist/core/runner/prepare-hands-readface.js +60 -24
  50. package/dist/core/runner/prepare-inherited-gate.d.ts +17 -4
  51. package/dist/core/runner/prepare-inherited-gate.js +10 -2
  52. package/dist/core/runner/prepare-lsp.d.ts +2 -2
  53. package/dist/core/runner/prepare-lsp.js +1 -1
  54. package/dist/core/runner/prepare-policy-chain.js +2 -0
  55. package/dist/core/runner/prepare-project-context.js +4 -2
  56. package/dist/core/runner/prepare-prompt-inputs.d.ts +4 -2
  57. package/dist/core/runner/prepare-prompt-inputs.js +4 -1
  58. package/dist/core/runner/prepare-run-refs.d.ts +9 -1
  59. package/dist/core/runner/prepare-run-refs.js +3 -2
  60. package/dist/core/runner/prepare-suspend-saga.d.ts +5 -0
  61. package/dist/core/runner/prepare-suspend-saga.js +5 -2
  62. package/dist/core/runner/prepare-task.js +18 -13
  63. package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +5 -0
  64. package/dist/core/runner/prepare-tool-disclosure-mount.js +10 -2
  65. package/dist/core/runner/prepare-turn-wiring.d.ts +8 -4
  66. package/dist/core/runner/prepare-turn-wiring.js +3 -2
  67. package/dist/core/runner/prepare-wiring-manifest.d.ts +5 -5
  68. package/dist/core/runner/prepare-wiring-manifest.js +5 -2
  69. package/dist/core/runner/resume-checkpoint-screen.js +7 -0
  70. package/dist/core/runner/run-harness-handlers.js +4 -1
  71. package/dist/core/runner/run-identity-wiring.js +6 -2
  72. package/dist/core/runner/run-leg.d.ts +2 -0
  73. package/dist/core/runner/run-leg.js +35 -41
  74. package/dist/core/runner/run-notification-lane.js +55 -27
  75. package/dist/core/runner/run-settle-and-teardown.js +1 -0
  76. package/dist/core/runner/run-terminal-adoption.js +16 -13
  77. package/dist/core/runner/run-turn-boundary.js +3 -1
  78. package/dist/core/runner/runtask.d.ts +6 -5
  79. package/dist/core/runner/runtask.js +26 -14
  80. package/dist/core/runner/skill-base-dir.d.ts +54 -0
  81. package/dist/core/runner/skill-base-dir.js +42 -0
  82. package/dist/core/runner/synthetic-tools.d.ts +16 -19
  83. package/dist/core/runner/synthetic-tools.js +18 -10
  84. package/dist/core/runner/tool-defer-gate.d.ts +22 -9
  85. package/dist/core/runner/tool-defer-gate.js +15 -5
  86. package/dist/core/runner/tool-disclosure.d.ts +5 -0
  87. package/dist/core/runner/tool-disclosure.js +2 -0
  88. package/dist/core/runner/tool-progress-lane.d.ts +42 -0
  89. package/dist/core/runner/tool-progress-lane.js +61 -0
  90. package/dist/core/runner-deps.d.ts +48 -7
  91. package/dist/core/swappable-deps.d.ts +9 -7
  92. package/dist/core/swappable-deps.js +7 -3
  93. package/dist/core/task-event.d.ts +96 -29
  94. package/dist/core/task-notification.d.ts +142 -16
  95. package/dist/core/task-notification.js +106 -0
  96. package/dist/core/task-result.d.ts +9 -1
  97. package/dist/core/task-spec.d.ts +20 -3
  98. package/dist/core/tighten-task-spec.js +7 -5
  99. package/dist/core/tool-model-gate.d.ts +1 -1
  100. package/dist/core/tool-search-setting.d.ts +78 -0
  101. package/dist/core/tool-search-setting.js +90 -0
  102. package/dist/core/tool-spec.d.ts +8 -0
  103. package/dist/core/types.d.ts +1 -1
  104. package/dist/core/types.js +1 -1
  105. package/dist/core/wiring-manifest.d.ts +46 -0
  106. package/dist/core/wiring-manifest.js +9 -1
  107. package/dist/engine/execution-env/node-execution-env.js +10 -4
  108. package/dist/engine/harness/agent-harness.d.ts +42 -0
  109. package/dist/engine/harness/agent-harness.js +61 -29
  110. package/dist/engine/loop/agent-loop.d.ts +68 -3
  111. package/dist/engine/loop/agent-loop.js +109 -16
  112. package/dist/index.d.ts +10 -5
  113. package/dist/index.js +9 -5
  114. package/dist/internal/harness.d.ts +1 -1
  115. package/dist/orchestration/run-workflow-tool.d.ts +3 -0
  116. package/dist/orchestration/run-workflow-tool.js +6 -1
  117. package/dist/orchestration/workflow-governance.d.ts +7 -1
  118. package/dist/orchestration/workflow-governance.js +9 -5
  119. package/dist/orchestration/workflow-primitives.d.ts +3 -0
  120. package/dist/orchestration/workflow-primitives.js +6 -1
  121. package/dist/orchestration/workflow-types.d.ts +48 -13
  122. package/dist/orchestration/workflow-types.js +1 -1
  123. package/dist/orchestration/workflow.js +45 -17
  124. package/dist/prompt-assembly/epoch.js +2 -0
  125. package/dist/prompt-assembly/packs/sema-default.js +2 -2
  126. package/dist/prompt-assembly/section-usage.d.ts +26 -0
  127. package/dist/prompt-assembly/section-usage.js +10 -0
  128. package/dist/prompt-assembly/types.d.ts +8 -6
  129. package/dist/prompts/default.d.ts +13 -0
  130. package/dist/prompts/default.js +16 -8
  131. package/dist/tools/artifact/artifact-text.d.ts +10 -1
  132. package/dist/tools/artifact/artifact-text.js +6 -3
  133. package/dist/tools/artifact/artifact-tool.d.ts +12 -2
  134. package/dist/tools/artifact/artifact-tool.js +203 -70
  135. package/dist/tools/artifact/conformance.d.ts +56 -0
  136. package/dist/tools/artifact/conformance.js +349 -0
  137. package/dist/tools/artifact/local-stub.js +1 -1
  138. package/dist/tools/artifact/reject-breaker.d.ts +28 -0
  139. package/dist/tools/artifact/reject-breaker.js +42 -0
  140. package/dist/tools/fs/bash-lexer.d.ts +132 -0
  141. package/dist/tools/fs/bash-lexer.js +563 -0
  142. package/dist/tools/fs/bash-program-position.d.ts +142 -0
  143. package/dist/tools/fs/bash-program-position.js +125 -0
  144. package/dist/tools/fs/bash-readonly-classifier.d.ts +54 -16
  145. package/dist/tools/fs/bash-readonly-classifier.js +375 -530
  146. package/dist/tools/fs/containment-face.d.ts +89 -0
  147. package/dist/tools/fs/containment-face.js +81 -0
  148. package/dist/tools/fs/fs-bash.d.ts +46 -1
  149. package/dist/tools/fs/fs-bash.js +158 -22
  150. package/dist/tools/fs/fs-read.d.ts +1 -1
  151. package/dist/tools/fs/fs-search-tools.d.ts +3 -3
  152. package/dist/tools/fs/fs-write.d.ts +4 -3
  153. package/dist/tools/fs/fs-write.js +9 -9
  154. package/dist/tools/fs/index.d.ts +24 -5
  155. package/dist/tools/fs/index.js +18 -6
  156. package/dist/tools/fs/repo-map.d.ts +2 -2
  157. package/dist/tools/fs/safety.d.ts +3 -3
  158. package/dist/tools/fs/safety.js +4 -4
  159. package/package.json +1 -1
  160. package/test/export-surface.snapshot.json +85 -9
  161. package/wire-changes/core@7.17.6.json +81 -0
  162. package/wire-changes/core@7.17.7.json +49 -0
  163. package/dist/tools/fs/read-face.d.ts +0 -68
  164. package/dist/tools/fs/read-face.js +0 -47
@@ -5,22 +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
14
  import { resolveTaskModel } from "../roles.js";
17
15
  import { brainToRuntime } from "../runtime.js";
18
16
  import { defaultTaskRegistry } from "../task-registry.js";
19
- import { emitTrace } from "../trace.js";
20
17
  import { defineTool, isDefineToolProduct, rebindDefineToolCtx } from "../tools.js";
21
18
  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
19
  import { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource } from "../runtime-caps.js";
23
20
  import { derivedRouteFallsBack } from "./derived-route-fallback.js";
21
+ import { CLASSIFIER_LOW_SEAT, CLASSIFIER_OFF_SEAT, classifierLadderRungs, classifierRoundObserver, classifierRungSeat } from "./classifier-ladder.js";
24
22
  import { REPORT_FINDINGS_TOOL_NAME, createReportBlockedTool, createReportFindingsTool } from "./synthetic-tools.js";
25
23
  function classifierReplyText(reply) {
26
24
  return reply.content
@@ -112,7 +110,7 @@ function autoModeArmReasonOf(intent, facePresent, caps, capsFaulted) {
112
110
  return { armed: true, reason: "armed" };
113
111
  }
114
112
  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;
113
+ 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
114
  const roster = new RosterBuilder(toolEffects);
117
115
  for (const t of spec.tools ?? []) {
118
116
  const mounted = mountCallerTool(t);
@@ -218,45 +216,45 @@ export async function prepareCapsAndWorkflow(input) {
218
216
  catch {
219
217
  classifierModel = model;
220
218
  }
221
- if (await derivedRouteFallsBack({ seat: "auto-mode-classifier", derived: classifierModel, primary: model, brain: deps.brain, getApiKeyAndHeaders: spec.getApiKeyAndHeaders, onNotice: deps.onNotice, sessionId, runId })) {
219
+ 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
220
  classifierModel = model;
223
221
  }
224
222
  const classifierLaneRule = peerLaneActive && peerSendMessageBuiltIn;
225
223
  const classifierSystemPrompt = buildAutoModePrompt(classifierLaneRule ? { ...am, crossSessionMessagesRule: CROSS_SESSION_CLASSIFIER_RULE } : am);
226
224
  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;
225
+ const classifierRungs = classifierLadderRungs(am.classifierFallback, classifierModel, model);
226
+ const roundPrompt = new WeakMap();
230
227
  autoModeDenialTracking = createAutoModeDenialTracker(am.denialLimit);
231
228
  autoModeDecider = createAutoModeDecider({
232
229
  ...(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() }] }, {
230
+ candidates: classifierRungs.map((m) => m.id),
231
+ onClassified: classifierRoundObserver({ tracer: deps.tracer, onNotice: deps.onNotice, taskId: hostTaskId, seatModelId: classifierModel.id, ...(sessionId !== undefined ? { sessionId } : {}), ...(runId !== undefined ? { runId } : {}) }),
232
+ classify: async (input, signal, rung) => {
233
+ const rungModel = rung !== undefined ? classifierRungs[rung.index] : undefined;
234
+ if (rung === undefined || rungModel === undefined) {
235
+ throw new Error(`the auto-mode classifier was asked at ladder position ${JSON.stringify(rung?.index)}, which this arming did not build`);
236
+ }
237
+ let build = rung.index > 0 ? roundPrompt.get(input) : undefined;
238
+ if (build === undefined) {
239
+ build = (async () => {
240
+ const ctx = await session.buildContext();
241
+ const known = ctx.messages.filter((m) => !isSyntheticApiErrorMessage(m) && (m.role === "user" || m.role === "assistant" || m.role === "toolResult"));
242
+ return renderAutoModeWindow(known, am.window) + renderAutoModeAction(input);
243
+ })();
244
+ roundPrompt.set(input, build);
245
+ }
246
+ const userPrompt = await build.catch((e) => { throw autoModeRoundNotAsked(e); });
247
+ const classifierAuth = await spec.getApiKeyAndHeaders?.(rungModel);
248
+ const request = (seat) => classifierRuntime.completeSimple(rungModel, { systemPrompt: classifierSystemPrompt, messages: [{ role: "user", content: userPrompt, timestamp: Date.now() }] }, {
251
249
  signal,
252
250
  ...seat,
253
251
  ...(classifierAuth?.apiKey !== undefined ? { apiKey: classifierAuth.apiKey } : {}),
254
252
  ...(classifierAuth?.headers !== undefined ? { headers: classifierAuth.headers } : {}),
255
253
  });
256
- let response = await request(classifierSeat);
257
- if (classifierSeat === classifierOffSeat && classifierReplyEmptyAtCap(response)) {
258
- response = await request(classifierLowSeat);
259
- }
254
+ const rungSeat = classifierRungSeat(rungModel);
255
+ let response = await request(rungSeat);
256
+ if (rungSeat === CLASSIFIER_OFF_SEAT && classifierReplyEmptyAtCap(response))
257
+ response = await request(CLASSIFIER_LOW_SEAT);
260
258
  return classifierReplyText(response);
261
259
  },
262
260
  });
@@ -325,6 +323,7 @@ export async function prepareCapsAndWorkflow(input) {
325
323
  parentCwd: taskRootFinal,
326
324
  parentThinking: () => childThinkingSeat(undefined, deploymentSubagentThinking(runnerSelf.agentCatalog?.roles), harnessRef.current?.getThinkingLevel() ?? thinking).thinking,
327
325
  parentReadFace: () => carrierReadFace(),
326
+ parentWriteFace: () => carrierWriteFace(),
328
327
  parentReadDenyPatterns: () => {
329
328
  const readDenyAdditionsNormalized = input.readDenyAdditionsNormalized();
330
329
  return readDenyAdditionsNormalized.length > 0 ? readDenyAdditionsNormalized : undefined;
@@ -115,7 +115,7 @@ export interface PrepareConfigDoorsInput {
115
115
  /** borrowed-readonly — the caller's spec. NEVER mutated here: the per-task agents rebind returns a
116
116
  * SHALLOW COPY on {@link PrepareConfigDoorsResult.spec} (only `tools` replaced). */
117
117
  spec: TaskSpec;
118
- /** borrowed-readonly — deployment seats. Read for: readFace value screen, locked-config preflight,
118
+ /** borrowed-readonly — deployment seats. Read for: the two containment-face value screens, locked-config preflight,
119
119
  * retention door, interaction-posture door (incl. `deps.onError` disclosure), usage windows,
120
120
  * brain-call guardrail default, model/role maps. */
121
121
  deps: RunnerDeps;
@@ -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";
@@ -196,8 +196,10 @@ export function prepareConfigDoors(input) {
196
196
  throw e;
197
197
  }
198
198
  }
199
- assertReadFaceValue(spec.readFace, "TaskSpec.readFace");
200
- assertReadFaceValue(deps.readFace, "readFace (deployment seat)");
199
+ assertContainmentFaceValue(spec.readFace, "read", "spec");
200
+ assertContainmentFaceValue(deps.readFace, "read", "deps");
201
+ assertContainmentFaceValue(spec.writeFace, "write", "spec");
202
+ assertContainmentFaceValue(deps.writeFace, "write", "deps");
201
203
  const memoryDelegationEvidenceRaw = deps.memoryDelegationEvidence;
202
204
  if (memoryDelegationEvidenceRaw !== undefined && memoryDelegationEvidenceRaw !== "static-face" && memoryDelegationEvidenceRaw !== "attested-only") {
203
205
  const evidence = memoryDelegationEvidenceRaw;
@@ -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;
@@ -1,11 +1,11 @@
1
- import { type BeforeWriteHook, type CwdRef, type ReadDenyMatcher, type ReadFace } from "../../tools/fs/index.js";
1
+ import { type BeforeWriteHook, type CwdRef, type ReadDenyMatcher, type ContainmentFace } from "../../tools/fs/index.js";
2
2
  import { type ReadFileState } from "../../tools/fs/safety.js";
3
3
  import type { RunnerDeps, ShellGateDoctrine, TaskSpec } from "../types.js";
4
4
  import type { Model } from "../../internal/llm.js";
5
- import type { AgentTool, ExecutionEnv, WorkspaceState } from "../../internal/harness.js";
5
+ import type { AgentHarness, AgentTool, ExecutionEnv, WorkspaceState } from "../../internal/harness.js";
6
6
  import type { AdvertisedWritableDirs } from "./advertised-writable-dirs.js";
7
7
  import type { StoredSession } from "../session.js";
8
- import type { IrreversibilityTier, PrepareResume, ReversibilityProbes, RunInternals, ToolFaceSnapshot } from "./contracts.js";
8
+ import type { IrreversibilityTier, PrepareResume, ReversibilityProbes, RunInternals, ToolFaceSnapshot, ToolProgressLane } from "./contracts.js";
9
9
  import { RosterBuilder } from "../tool-roster.js";
10
10
  /** Test seam (mirrors `__resetMalformedNoticeSeatAnnouncement`): never called by production code.
11
11
  * Deliberately asymmetric — it resets only the console latch: the WeakSet arm needs no seam
@@ -145,16 +145,26 @@ export interface PrepareHandsReadFaceInput {
145
145
  model: Model;
146
146
  /** borrowed-readonly — the task-spec seats this phase reads, as a Pick over the SAME spec object
147
147
  * (R-4). `tools` feeds only the band shadow check; `envFacts` only the advertised scratchpad. */
148
- spec: Pick<TaskSpec, "additionalDirectories" | "additionalReadDirectories" | "envFacts" | "handsReadOnly" | "readDenyPatterns" | "readFace" | "oneShot" | "resumeAt" | "sessionId" | "tools">;
148
+ spec: Pick<TaskSpec, "additionalDirectories" | "additionalReadDirectories" | "envFacts" | "handsReadOnly" | "readDenyPatterns" | "readFace" | "writeFace" | "oneShot" | "resumeAt" | "sessionId" | "tools">;
149
149
  /** borrowed-readonly — the deployment seats this phase reads, as a Pick over the SAME deps
150
150
  * object (R-4: `deps.onError?.()` keeps its receiver; a new deps read must widen this Pick).
151
151
  * `readDenyBuiltinTiers`/`readDenyBuiltinExclude` are SNAPSHOTTED before the first await that
152
152
  * precedes the toolkit compile (the caller retains these arrays by reference). */
153
- deps: Pick<RunnerDeps, "onError" | "onNotice" | "tracer" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude" | "readFace" | "permissionRuleStore" | "hands">;
153
+ deps: Pick<RunnerDeps, "onError" | "onNotice" | "tracer" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude" | "readFace" | "writeFace" | "permissionRuleStore" | "hands">;
154
154
  /** borrowed-readonly — the trusted spawn-side seats the band forwards: the finished-command
155
155
  * notification lane, the mid-flight detach hub, and the Runner's per-session
156
156
  * read-state seats (absent on a standalone prepare ⇒ the transcript-replay rung covers alone). */
157
157
  internals: Pick<RunInternals, "onTaskNotification" | "detachHub" | "sessionReadStates"> | undefined;
158
+ /** borrowed-mutable — the harness seat, read LIVE by the band's queued-human-input predicate (the shell's
159
+ * auto-backgrounding leg asks it mid-command). Late-bound: the band is built here, the harness later. */
160
+ harnessRef: {
161
+ current?: AgentHarness;
162
+ };
163
+ /** borrowed-mutable — the tool-progress lane's cell, read LIVE by the band's progress seat; same
164
+ * late-binding contract as {@link harnessRef}. Unbound ⇒ every tick through it is a silent no-op. */
165
+ toolProgressRef: {
166
+ current?: ToolProgressLane;
167
+ };
158
168
  /** borrowed-readonly — ABSOLUTE paths this prepare's restore wrote, deleted,
159
169
  * or failed to write. Every read-state seed above describes the PRE-restore tree, so these keys are
160
170
  * dropped from the map: the engine replaced those bytes itself, and a surviving entry makes the
@@ -255,7 +265,14 @@ export interface PrepareHandsReadFaceResult {
255
265
  /** write-back (D-3 hold) — the run's RESOLVED read face where hands mount; undefined ⇔ no hands
256
266
  * (the hands-less value lives in `handsLessResolvedFace`). The driver assigns this onto its
257
267
  * outer `let` for the call-time closures (ctx enrichment / checkpoint mint / envFacts). */
258
- resolvedReadFace: ReadFace | undefined;
268
+ resolvedReadFace: ContainmentFace | undefined;
269
+ /** write-back — the run's RESOLVED WRITE face where hands mount; undefined ⇔ no hands (the
270
+ * hands-less value lives in `handsLessResolvedWriteFace`). The read seat's twin, resolved by the
271
+ * SAME order on its OWN seats — the two never read each other. */
272
+ resolvedWriteFace: ContainmentFace | undefined;
273
+ /** write-back — the hands-LESS legs' resolved WRITE face (the posture is a property of the RUN,
274
+ * not the mount): the delegation carriers and the checkpoint mint need it on every leg. */
275
+ handsLessResolvedWriteFace: ContainmentFace | undefined;
259
276
  /** write-back (D-3 hold) — the NORMALIZED deny-set additions in force (deployment ∪ task ∪
260
277
  * resume-seed; zero-builtin compile — the persisted/carrier form, never the judged union's
261
278
  * post-dedup remainder). Driver assigns onto its outer `let`. */
@@ -265,7 +282,7 @@ export interface PrepareHandsReadFaceResult {
265
282
  }>;
266
283
  /** write-back (D-3 hold) — the hands-LESS legs' resolved face (the read posture is a
267
284
  * property of the RUN, not the mount). Driver assigns onto its outer `let`. */
268
- handsLessResolvedFace: ReadFace | undefined;
285
+ handsLessResolvedFace: ContainmentFace | undefined;
269
286
  /** owned — the canonicalized write-capable extra roots (additionalDirectories + the advertised
270
287
  * scratchpad), deduped; the driver's envFacts advertisement and attachment readers share the
271
288
  * ARRAY identity. Settled here (no mutation after return). */
@@ -3,17 +3,31 @@ import { StubExecutionEnv } from "../stub-env.js";
3
3
  import { hasBackgroundShell } from "../background-shell.js";
4
4
  import { boundaryIsTheSandbox } from "../remote-env.js";
5
5
  import { defaultTaskRegistry } from "../task-registry.js";
6
- import { bashReadBoundaryProbe, bashReversibilityProbe, compileReadDeny, createHandsToolkit, deploymentReadFaceClampNotice, pdfModelCapabilitiesOf, resolveReadDenyBuiltins, inheritSessionReadFileState, resolveReadFace, seedReadFileStateFromContext, seedReadFileStateFromTranscript, } from "../../tools/fs/index.js";
6
+ import { bashReadBoundaryProbe, bashReversibilityProbe, compileReadDeny, createHandsToolkit, deploymentFaceClampNotice, pdfModelCapabilitiesOf, resolveReadDenyBuiltins, inheritSessionReadFileState, resolveContainmentFace, seedReadFileStateFromContext, seedReadFileStateFromTranscript, } from "../../tools/fs/index.js";
7
7
  import { resolveKey, sha256 } from "../../tools/fs/safety.js";
8
8
  import { normalizeFileText } from "../../tools/fs/encoding.js";
9
9
  import { wholeFileRecordsFromTranscript } from "./session-file-state-replay.js";
10
10
  import { deliverEngineNotice } from "../types.js";
11
11
  import { rebaseWorkspacePath } from "./workspace-path.js";
12
12
  import { RosterBuilder } from "../tool-roster.js";
13
- const readFaceClampAnnouncedSinks = new WeakSet();
14
- let readFaceClampConsoleAnnounced = false;
13
+ const faceClampAnnouncedSinks = { read: new WeakSet(), write: new WeakSet() };
14
+ const faceClampConsoleAnnounced = { read: false, write: false };
15
+ function announceDeploymentFaceClamp(kind, sink) {
16
+ if (typeof sink === "function") {
17
+ if (faceClampAnnouncedSinks[kind].has(sink))
18
+ return;
19
+ faceClampAnnouncedSinks[kind].add(sink);
20
+ }
21
+ else {
22
+ if (faceClampConsoleAnnounced[kind])
23
+ return;
24
+ faceClampConsoleAnnounced[kind] = true;
25
+ }
26
+ deliverEngineNotice(sink, deploymentFaceClampNotice(kind));
27
+ }
15
28
  export function __resetReadFaceClampAnnouncement() {
16
- readFaceClampConsoleAnnounced = false;
29
+ faceClampConsoleAnnounced.read = false;
30
+ faceClampConsoleAnnounced.write = false;
17
31
  }
18
32
  export class SessionReadFileStates {
19
33
  seats = new Map();
@@ -82,17 +96,29 @@ export function resolveHandsLessReadFace(input) {
82
96
  assertStringArraySeat(deps.readDenyPatterns, "RunnerDeps.readDenyPatterns", "compiler-owned");
83
97
  assertStringArraySeat(spec.readDenyPatterns, "TaskSpec.readDenyPatterns", "compiler-owned");
84
98
  let handsLessResolvedFace;
99
+ let handsLessResolvedWriteFace;
85
100
  let readDenyAdditionsNormalized = [];
86
101
  {
87
- const liveFace = resolveReadFace({
88
- specReadFace: spec.readFace,
89
- depsReadFace: deps.readFace,
102
+ const liveFace = resolveContainmentFace({
103
+ kind: "read",
104
+ specFace: spec.readFace,
105
+ depsFace: deps.readFace,
90
106
  readOnlyMount: spec.handsReadOnly === true,
91
107
  orgGoverned: deps.permissionRuleStore?.partitions.org === true,
92
108
  fullShellReachable,
93
109
  });
94
110
  const seed = resume?.seed.readFace;
95
111
  handsLessResolvedFace = resume !== undefined && (seed === undefined || seed.face === "roots") ? "roots" : liveFace;
112
+ const liveWriteFace = resolveContainmentFace({
113
+ kind: "write",
114
+ specFace: spec.writeFace,
115
+ depsFace: deps.writeFace,
116
+ readOnlyMount: spec.handsReadOnly === true,
117
+ orgGoverned: deps.permissionRuleStore?.partitions.org === true,
118
+ fullShellReachable,
119
+ });
120
+ const seedWrite = resume?.seed.writeFace;
121
+ handsLessResolvedWriteFace = resume !== undefined && (seedWrite === undefined || seedWrite.face === "roots") ? "roots" : liveWriteFace;
96
122
  const denyAdditions = [...(deps.readDenyPatterns ?? []), ...(spec.readDenyPatterns ?? []), ...(seed?.denyEntries ?? [])];
97
123
  resolveReadDenyBuiltins({
98
124
  ...(deps.readDenyBuiltinTiers !== undefined ? { tiers: deps.readDenyBuiltinTiers } : {}),
@@ -111,8 +137,10 @@ export function resolveHandsLessReadFace(input) {
111
137
  rebaseWsPath: (p) => p,
112
138
  backgroundTaskToolsActive: false,
113
139
  resolvedReadFace: undefined,
140
+ resolvedWriteFace: undefined,
114
141
  readDenyAdditionsNormalized,
115
142
  handsLessResolvedFace,
143
+ handsLessResolvedWriteFace,
116
144
  additionalRootsCanonical: [],
117
145
  additionalReadRootsCanonical: [],
118
146
  attachmentRootCanonical: undefined,
@@ -138,8 +166,10 @@ export async function prepareHandsMount(input) {
138
166
  let rebaseWsPath = (p) => p;
139
167
  let backgroundTaskToolsActive = false;
140
168
  let resolvedReadFace;
169
+ let resolvedWriteFace;
141
170
  let readDenyAdditionsNormalized = [];
142
171
  let handsLessResolvedFace;
172
+ const handsLessResolvedWriteFace = undefined;
143
173
  const shellGatedTools = new Set();
144
174
  const additionalRootsCanonical = [];
145
175
  const additionalReadRootsCanonical = [];
@@ -218,30 +248,31 @@ export async function prepareHandsMount(input) {
218
248
  };
219
249
  readDenyMatcher = compileReadDeny(readDenyAdditions, "readDenyPatterns", readDenyBuiltinCfg);
220
250
  readDenyAdditionsNormalized = compileReadDeny(readDenyAdditions, "readDenyPatterns", { tiers: [] }).entries;
221
- let liveReadFace = resolveReadFace({
222
- specReadFace: spec.readFace,
223
- depsReadFace: deps.readFace,
251
+ let liveReadFace = resolveContainmentFace({
252
+ kind: "read",
253
+ specFace: spec.readFace,
254
+ depsFace: deps.readFace,
224
255
  readOnlyMount: handsReadOnly,
225
256
  orgGoverned: deps.permissionRuleStore?.partitions.org === true,
226
257
  fullShellReachable,
227
- onDeploymentClamp: () => {
228
- const sink = deps.onNotice;
229
- if (typeof sink === "function") {
230
- if (readFaceClampAnnouncedSinks.has(sink))
231
- return;
232
- readFaceClampAnnouncedSinks.add(sink);
233
- }
234
- else {
235
- if (readFaceClampConsoleAnnounced)
236
- return;
237
- readFaceClampConsoleAnnounced = true;
238
- }
239
- deliverEngineNotice(sink, deploymentReadFaceClampNotice());
240
- },
258
+ onDeploymentClamp: () => announceDeploymentFaceClamp("read", deps.onNotice),
241
259
  });
242
260
  if (resume !== undefined && (seedReadFaceSection === undefined || seedReadFaceSection.face === "roots"))
243
261
  liveReadFace = "roots";
244
262
  resolvedReadFace = liveReadFace;
263
+ let liveWriteFace = resolveContainmentFace({
264
+ kind: "write",
265
+ specFace: spec.writeFace,
266
+ depsFace: deps.writeFace,
267
+ readOnlyMount: handsReadOnly,
268
+ orgGoverned: deps.permissionRuleStore?.partitions.org === true,
269
+ fullShellReachable,
270
+ onDeploymentClamp: () => announceDeploymentFaceClamp("write", deps.onNotice),
271
+ });
272
+ const seedWriteFaceSection = resume?.seed.writeFace;
273
+ if (resume !== undefined && (seedWriteFaceSection === undefined || seedWriteFaceSection.face === "roots"))
274
+ liveWriteFace = "roots";
275
+ resolvedWriteFace = liveWriteFace;
245
276
  const sessionSeats = input.internals?.sessionReadStates;
246
277
  let seatLeafId;
247
278
  try {
@@ -308,6 +339,7 @@ export async function prepareHandsMount(input) {
308
339
  ...(readDenyBuiltinCfg.tiers !== undefined ? { readDenyBuiltinTiers: readDenyBuiltinCfg.tiers } : {}),
309
340
  ...(readDenyBuiltinCfg.exclude !== undefined ? { readDenyBuiltinExclude: readDenyBuiltinCfg.exclude } : {}),
310
341
  readFace: liveReadFace,
342
+ writeFace: liveWriteFace,
311
343
  reminderMark: input.reminderMark,
312
344
  reminderDisclosureCounts: input.reminderDisclosureCounts,
313
345
  ...(input.trackFileEdit !== undefined ? { trackFileEdit: input.trackFileEdit } : {}),
@@ -323,6 +355,8 @@ export async function prepareHandsMount(input) {
323
355
  ...(sessionId !== undefined ? { sessionId } : {}),
324
356
  ...(internals?.onTaskNotification !== undefined ? { taskNotification: internals.onTaskNotification } : {}),
325
357
  ...(internals?.detachHub !== undefined ? { detachHub: internals.detachHub } : {}),
358
+ onToolProgress: (body) => input.toolProgressRef.current?.emit(body),
359
+ queuedHumanInput: () => input.harnessRef.current?.hasPendingUserInput() === true,
326
360
  ...(deps.onNotice !== undefined ? { onNotice: deps.onNotice } : {}),
327
361
  pdfModelCapabilities: pdfModelCapabilitiesOf(model),
328
362
  ...(deps.hands?.bashReadonlyAllow !== undefined ? { bashReadonlyAllow: deps.hands.bashReadonlyAllow } : {}),
@@ -386,6 +420,8 @@ export async function prepareHandsMount(input) {
386
420
  rebaseWsPath,
387
421
  backgroundTaskToolsActive,
388
422
  resolvedReadFace,
423
+ resolvedWriteFace,
424
+ handsLessResolvedWriteFace,
389
425
  readDenyAdditionsNormalized,
390
426
  handsLessResolvedFace,
391
427
  additionalRootsCanonical,
@@ -32,7 +32,7 @@ import type { SubagentWorktreeIsolation } from "../../agents/subagent.js";
32
32
  import { forkGovernanceDenial } from "../../agents/subagent.js";
33
33
  import type { SubagentRetainLedger } from "../../agents/retain-ledger.js";
34
34
  import type { PromptEpochArtifact } from "../../prompt-assembly/artifact.js";
35
- import type { ReadFace } from "../../tools/fs/index.js";
35
+ import type { ContainmentFace } from "../../tools/fs/index.js";
36
36
  import type { AutoModeArmingRecipe } from "../auto-mode-arming.js";
37
37
  import type { AutoModeDecider, AutoModeDenialTracker } from "../auto-mode.js";
38
38
  import { type OnQuestion } from "../ask-question.js";
@@ -159,14 +159,18 @@ export interface PrepareInheritedGateInput {
159
159
  /** borrowed-readonly — LATE-BOUND: the observers grant (ctx `observersAllowed`, present only when explicitly granted). */
160
160
  observersActive: () => boolean;
161
161
  /** borrowed-readonly — LATE-BOUND: the hands mount's resolved read face (undefined until/unless hands mount). */
162
- resolvedReadFace: () => ReadFace | undefined;
162
+ resolvedReadFace: () => ContainmentFace | undefined;
163
+ /** borrowed-readonly — LATE-BOUND: the hands mount's resolved WRITE face (the read seat's twin). */
164
+ resolvedWriteFace: () => ContainmentFace | undefined;
163
165
  /** borrowed-readonly — LATE-BOUND: the normalized deny additions in force (empty until the hands mount resolves them). */
164
166
  readDenyAdditionsNormalized: () => ReadonlyArray<{
165
167
  pattern: string;
166
168
  caseSensitive: boolean;
167
169
  }>;
168
170
  /** borrowed-readonly — LATE-BOUND: the hands-less legs' resolved face (the carrier's second source). */
169
- handsLessResolvedFace: () => ReadFace | undefined;
171
+ handsLessResolvedFace: () => ContainmentFace | undefined;
172
+ /** borrowed-readonly — LATE-BOUND: the hands-less legs' resolved WRITE face (same second source, write seat). */
173
+ handsLessResolvedWriteFace: () => ContainmentFace | undefined;
170
174
  /** borrowed-readonly — LATE-BOUND: the adopted center artifact (ctx `centerArtifactDigest` / `centerSourceRevision`). */
171
175
  centerAdoption: () => {
172
176
  artifact: PromptEpochArtifact;
@@ -230,6 +234,13 @@ export interface PrepareInheritedGateResult {
230
234
  } | undefined;
231
235
  /** owned — the face stamp predicate (`faceCheckpointSection() !== undefined`). */
232
236
  faceCheckpointState: () => boolean;
237
+ /** owned — the WRITE-face checkpoint section builder: written ONLY for a non-default (open) write
238
+ * posture, so an absent section IS "roots" on every reader, including an engine that predates the
239
+ * seat. No version stamp rides it — dropping the section can only ever make a resume STRICTER
240
+ * (the opposite direction from the read section, whose deny additions a blind reader would lose). */
241
+ writeFaceCheckpointSection: () => {
242
+ face: "open" | "roots";
243
+ } | undefined;
233
244
  /** owned — the F-012 stamp predicate (opaque constraints present ∨ seed frozen chain ∨ provenance-armed). */
234
245
  f012CheckpointState: () => boolean;
235
246
  /** owned — the FROZEN approver seat (spec over deps) every station of this leg and its children's chain resolve at. */
@@ -259,7 +270,9 @@ export interface PrepareInheritedGateResult {
259
270
  /** owned — chain assembly SINGLE SITE: upstream chain + this task's own contribution, called by delegation tools at spawn. */
260
271
  inheritedGateForChildren: () => InheritedGate;
261
272
  /** owned — the ONE carrier expression both delegation mounts consume (resolved face ?? hands-less face). */
262
- carrierReadFace: () => ReadFace | undefined;
273
+ carrierReadFace: () => ContainmentFace | undefined;
274
+ /** owned — the same expression on the WRITE seat. */
275
+ carrierWriteFace: () => ContainmentFace | undefined;
263
276
  /** owned — the full shell's structural reachability from spec-time facts. */
264
277
  fullShellReachable: boolean;
265
278
  /** borrowed-mutable — the gate's typed stop seat (`Prepared.gateStopRef`). Writers: `stopForDenialLimit` only. */