@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
@@ -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. */
@@ -70,7 +70,9 @@ export function prepareInheritedGate(input) {
70
70
  const readDenyAdditionsNormalized = input.readDenyAdditionsNormalized();
71
71
  if (resolvedReadFace === undefined && resume !== undefined) {
72
72
  const seed = resume.seed.readFace;
73
- return seed !== undefined ? { face: seed.face, ...(seed.denyEntries !== undefined ? { denyEntries: seed.denyEntries.map((e) => ({ ...e })) } : {}) } : undefined;
73
+ if (seed === undefined)
74
+ return undefined;
75
+ return { face: handsLessResolvedFace ?? seed.face, ...(seed.denyEntries !== undefined ? { denyEntries: seed.denyEntries.map((e) => ({ ...e })) } : {}) };
74
76
  }
75
77
  const face = resolvedReadFace ?? handsLessResolvedFace;
76
78
  if (face === undefined)
@@ -81,6 +83,10 @@ export function prepareInheritedGate(input) {
81
83
  return undefined;
82
84
  };
83
85
  const faceCheckpointState = () => faceCheckpointSection() !== undefined;
86
+ const writeFaceCheckpointSection = () => {
87
+ const face = input.resolvedWriteFace() ?? input.handsLessResolvedWriteFace();
88
+ return face === "open" ? { face } : undefined;
89
+ };
84
90
  const f012CheckpointState = () => (inheritedParentConstraints?.length ?? 0) > 0 ||
85
91
  seedInheritedGate?.constraintChain !== undefined ||
86
92
  internals?.delegationProvenance !== undefined;
@@ -160,6 +166,7 @@ export function prepareInheritedGate(input) {
160
166
  };
161
167
  };
162
168
  const carrierReadFace = () => input.resolvedReadFace() ?? input.handsLessResolvedFace();
169
+ const carrierWriteFace = () => input.resolvedWriteFace() ?? input.handsLessResolvedWriteFace();
163
170
  const fullShellReachable = handsEnabled &&
164
171
  !(executionEnv instanceof StubExecutionEnv) &&
165
172
  spec.handsReadOnly !== true &&
@@ -184,6 +191,7 @@ export function prepareInheritedGate(input) {
184
191
  ...(frozenOnQuestion !== undefined ? { onQuestion: frozenOnQuestion } : {}),
185
192
  ...(spec.handsReadOnly === true ? { handsReadOnly: true } : {}),
186
193
  ...(carrierReadFace() === "roots" ? { readFace: "roots" } : {}),
194
+ ...(carrierWriteFace() === "roots" ? { writeFace: "roots" } : {}),
187
195
  ...(readDenyAdditionsNormalized.length > 0 ? { readDenyPatterns: Object.freeze(readDenyAdditionsNormalized.map((e) => ({ ...e }))) } : {}),
188
196
  ...(spec.interactiveTools === false ? { interactiveTools: false } : {}),
189
197
  oneShot: spec.oneShot,
@@ -262,5 +270,5 @@ export function prepareInheritedGate(input) {
262
270
  ...(spec.enablePlanMode === true ? { enterPlanMode } : {}),
263
271
  };
264
272
  };
265
- return { liveInheritedGate, seedInheritedGate, inheritedAncestorRules, inheritedShellGate, inheritedParentConstraints, autoModeIntent, inheritedAdmittedOrgScopes, priorOwnOrgVerdict, orgGovernedProvenance, effectiveShellGate, ownSessionRulesRef, memoryAdmittedOrgScopesRef, ownOrgVerdictRef, orgAdmissionCheckpointState, faceCheckpointSection, faceCheckpointState, f012CheckpointState, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, frozenOnQuestion, parentSpecSeat, provenanceForChildrenRef, delegationSettlementRef, inheritedGateForChildren, carrierReadFace, fullShellReachable, gateStopRef, stopForDenialLimit, enrichSpecToolCtx };
273
+ return { liveInheritedGate, seedInheritedGate, inheritedAncestorRules, inheritedShellGate, inheritedParentConstraints, autoModeIntent, inheritedAdmittedOrgScopes, priorOwnOrgVerdict, orgGovernedProvenance, effectiveShellGate, ownSessionRulesRef, memoryAdmittedOrgScopesRef, ownOrgVerdictRef, orgAdmissionCheckpointState, faceCheckpointSection, faceCheckpointState, writeFaceCheckpointSection, f012CheckpointState, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, frozenOnQuestion, parentSpecSeat, provenanceForChildrenRef, delegationSettlementRef, inheritedGateForChildren, carrierReadFace, carrierWriteFace, fullShellReachable, gateStopRef, stopForDenialLimit, enrichSpecToolCtx };
266
274
  }
@@ -48,9 +48,9 @@ export interface PrepareLspResult {
48
48
  lspDiagnostics: LspDiagnosticsRegistry | undefined;
49
49
  /** owned — the edit nudge (raw path in), or undefined when the lane is not live. */
50
50
  nudgeLspOnEdit: ((rawPath: string) => void) | undefined;
51
- /** borrowed-readonly — the run's registry key (= the session id): a session hosts one run at a time and the
52
- * run loop releases the key at the terminal. */
51
+ /** borrowed-readonly — the run's registry key (= the session id): one run per session, released at the terminal. */
53
52
  lspRunIdent: string;
53
+ /** Did a MANAGER seat resolve? The manifest's LSP-section presence condition, from the ONE fold that decided the mount. */ managerWired: boolean;
54
54
  }
55
55
  /** The M8 phase body — prepareTask's LSP stretch, verbatim (see the module header). */
56
56
  export declare function prepareLsp(input: PrepareLspInput): PrepareLspResult;
@@ -24,5 +24,5 @@ export function prepareLsp(input) {
24
24
  .catch(() => undefined);
25
25
  }
26
26
  : undefined;
27
- return { lspDiagnostics, nudgeLspOnEdit, lspRunIdent };
27
+ return { lspDiagnostics, nudgeLspOnEdit, lspRunIdent, managerWired: lspManager !== undefined };
28
28
  }
@@ -2,6 +2,7 @@ import { policyAskClassOf } from "../ask-class.js";
2
2
  import { ASK_USER_QUESTION_TOOL_NAME } from "../ask-question.js";
3
3
  import { resolveCheckpointStore } from "../checkpoint-store.js";
4
4
  import { cloneObserverInput, resolveHookTimeoutMs } from "../hooks.js";
5
+ import { assertHookSeatsCallable } from "../hook-seats.js";
5
6
  import { isNamespacedCoveringRuleName, namespacedRuleNameCovers } from "../permission-rules.js";
6
7
  import { isIsolated } from "../remote-env.js";
7
8
  import { askApproverIdentity, carryEngineAttestations, carryPrecedingSettlement, checkToolPolicyProjection, combinePolicies, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, durableParkOptInDeclared, isLiveApproverSeat, resolveAsk, toolPolicyNameSets, attestAskUnresolvableCause } from "../tool-policy.js";
@@ -113,6 +114,7 @@ export async function preparePolicyChain(input) {
113
114
  const denyNarrowingPolicy = narrowingLayers.length === 0 ? undefined : narrowingLayers.length === 1 ? narrowingLayers[0] : combinePolicies(...narrowingLayers);
114
115
  const basePolicyForResumeEdit = lockedPreflight.basePolicyForResumeEdit ?? lockedPreflight.toolPolicy;
115
116
  const hooks = spec.hooks ?? deps.hooks;
117
+ assertHookSeatsCallable(hooks, spec.hooks !== undefined ? "spec" : "deps");
116
118
  const hookTimeoutMs = resolveHookTimeoutMs(hooks?.timeoutMs, notifyOwnHookCrash, hooks);
117
119
  const preToolUseObservational = hooks?.preToolUse !== undefined && hooks.preToolUseObservational === true;
118
120
  const ownGatePreToolUse = !preToolUseObservational
@@ -1,3 +1,4 @@
1
+ import type { ResolvedSkillListingBudget } from "../skill-listing-budget.js";
1
2
  import { type StoredSession } from "../session.js";
2
3
  import type { RunnerDeps, TaskSpec, ToolSpec } from "../types.js";
3
4
  import type { ActiveSkillScope } from "./active-skill-scope.js";
@@ -8,7 +9,7 @@ export interface PrepareProjectContextInput {
8
9
  /** borrowed-readonly — the deployment seams this phase reads: `loadProjectMemory` (the snapshot),
9
10
  * `onError` (the operator lane for a throwing seam / seeding / oversize skill), `sharedMemoryStores`
10
11
  * (the shared-memory pair's switch: wired ⇒ mount). Receiver preserved (`deps.onError?.(…)`). */
11
- deps: Pick<RunnerDeps, "loadProjectMemory" | "onError" | "sharedMemoryStores">;
12
+ deps: Pick<RunnerDeps, "loadProjectMemory" | "onError" | "sharedMemoryStores" | "onNotice">;
12
13
  /** borrowed-readonly — the REBOUND spec (`spec′`). Read: `sessionId` (phase derivation + the
13
14
  * continuation projections), `skills` (the roster), `principal` (the shared-memory tools' trusted
14
15
  * identity). Never mutated. */
@@ -69,6 +70,9 @@ export interface PrepareProjectContextInput {
69
70
  /** borrowed-readonly — the durable-resume carrier; read for `!== undefined` (seedAnnounced) and
70
71
  * `seed.announcedListings.skills` (the removal-to-zero projection's gate). */
71
72
  resume: Pick<PrepareResume, "seed"> | undefined;
73
+ /** borrowed-readonly — the lowered listing-budget seats from the config door. The listing projection
74
+ * carries it to the boundary renderer, and the over-budget disclosure below is priced against it. */
75
+ skillListingBudget: ResolvedSkillListingBudget;
72
76
  }
73
77
  export interface PrepareProjectContextResult {
74
78
  /** owned — the memory block with the project layer appended at the tail (unchanged when the seam is
@@ -6,9 +6,10 @@ import { createSharedMemoryTools } from "../shared-memory/tools.js";
6
6
  import { SHARED_MEMORY_TOOL_NAMES } from "../shared-memory/types.js";
7
7
  import { defineTool } from "../tools.js";
8
8
  import { SKILL_CONTENT_MAX_CHARS, SKILL_TOOL_NAME, createSkillTool, normalizeSkills } from "./synthetic-tools.js";
9
+ import { substitutedSkillLength } from "./skill-base-dir.js";
9
10
  import { RosterBuilder } from "../tool-roster.js";
10
11
  export async function prepareProjectContext(input) {
11
- const { deps, spec, internals, session, sessionId, hostTaskId, taskRootFinal, handsEnabled, delegation, seedContextFiles, memoryBlockFromEngine, skillScope, roster, toolFaceSnapshot, onceLedger, memoryTools, memoryEngineSession, memorySearchToolsPlanned, memoryPairExcludedName, memoryPairOccupiedName, resume } = input;
12
+ const { deps, spec, internals, session, sessionId, hostTaskId, taskRootFinal, handsEnabled, delegation, seedContextFiles, memoryBlockFromEngine, skillScope, roster, toolFaceSnapshot, onceLedger, memoryTools, memoryEngineSession, memorySearchToolsPlanned, memoryPairExcludedName, memoryPairOccupiedName, resume, skillListingBudget } = input;
12
13
  const tools = roster.tools;
13
14
  let memoryBlock = memoryBlockFromEngine;
14
15
  let instructionSources;
@@ -74,9 +75,10 @@ export async function prepareProjectContext(input) {
74
75
  if (!declaredSkillRank.has(s.name))
75
76
  declaredSkillRank.set(s.name, declaredSkillRank.size);
76
77
  const skillSpecs = normalizeSkills(spec.skills ?? []).filter((s) => {
77
- if (s.content.length <= SKILL_CONTENT_MAX_CHARS)
78
+ const delivered = substitutedSkillLength(s.content, s.baseDir);
79
+ if (delivered <= SKILL_CONTENT_MAX_CHARS)
78
80
  return true;
79
- deps.onError?.(new Error(`Skill "${s.name}" content is ${s.content.length} chars, over the ${SKILL_CONTENT_MAX_CHARS}-char load limit — skill not loaded (skills are never truncated; shrink the body or move material to attachments/files).`), { phase: "config", sessionId });
81
+ deps.onError?.(new Error(`Skill "${s.name}" content is ${delivered} chars${delivered === s.content.length ? "" : ` once its base directory resolves (${s.content.length} as written)`}, over the ${SKILL_CONTENT_MAX_CHARS}-char load limit — skill not loaded (skills are never truncated; shrink the body or move material to attachments/files).`), { phase: "config", sessionId });
80
82
  return false;
81
83
  });
82
84
  const inheritedFrames = internals?.inheritedManifestScope ?? [];
@@ -140,11 +142,12 @@ export async function prepareProjectContext(input) {
140
142
  ...(declaredSkillRank.get(s.name) !== undefined ? { declaredRank: declaredSkillRank.get(s.name) } : {}),
141
143
  })),
142
144
  seedAnnounced: resume !== undefined || spec.sessionId !== undefined,
145
+ budget: skillListingBudget,
143
146
  }
144
147
  :
145
148
  (resume?.seed.announcedListings?.skills?.length ?? 0) > 0 || spec.sessionId !== undefined
146
149
  ?
147
- { entries: [], seedAnnounced: true }
150
+ { entries: [], seedAnnounced: true, budget: skillListingBudget }
148
151
  : undefined;
149
152
  return { memoryBlock, instructionSources, projectInstructionContent, skillSpecs, hasSkillManifest, skillsListing, sharedMemoryPairMounted, memoryEnginePairMounted };
150
153
  }
@@ -22,7 +22,7 @@
22
22
  import type { ExecutionEnv, ThinkingLevel } from "../../internal/harness.js";
23
23
  import type { Model } from "../../internal/llm.js";
24
24
  import { type EnvironmentFacts, type PromptProvider } from "../../prompts/default.js";
25
- import type { ReadFace } from "../../tools/fs/index.js";
25
+ import type { ContainmentFace } from "../../tools/fs/index.js";
26
26
  import type { LockedPreflight } from "../locked-config.js";
27
27
  import type { MaterializedMcp } from "../mcp.js";
28
28
  import type { ResolvedRole } from "../roles.js";
@@ -53,7 +53,9 @@ export interface PreparePromptInputsInput {
53
53
  /** borrowed-readonly — the session's reminder mark (rides the prompt facts for the declaration). */
54
54
  reminderMark: string;
55
55
  /** borrowed-readonly — the resolved read face (the open-reads flag + the structured env fact). */
56
- resolvedReadFace: ReadFace | undefined;
56
+ resolvedReadFace: ContainmentFace | undefined;
57
+ /** borrowed-readonly — the resolved write face (the open-writes flag + the structured env fact). */
58
+ resolvedWriteFace: ContainmentFace | undefined;
57
59
  /** borrowed-readonly — the frozen tool face; only `defer` is read (the Workflow short-pointer flag). */
58
60
  toolFaceSnapshot: Pick<ToolFaceSnapshot, "defer">;
59
61
  /** borrowed-readonly — the resolved prompt profile (rides the flags to the legacy ctx). */
@@ -4,7 +4,7 @@ import { probeGitStatusLane } from "./git-status-frame.js";
4
4
  import { fenceMcpServerInstructions } from "./turn-attachments.js";
5
5
  const ULTRA_REASONING_TIERS = new Set(["xhigh", "max"]);
6
6
  export async function preparePromptInputs(input) {
7
- const { spec, deps, failClosedReason, lockedPreflight, selfOrchestrationActive, executionEnv, reminderMark, resolvedReadFace, toolFaceSnapshot, promptProfile, fableMitigations, internals, thinking, ownedEnv, delegation, resolvedRole, mcp, model, externalContentTargetActive, resume, handsEnabled, taskRootFinal, additionalRootsCanonical, additionalReadRootsCanonical, sessionId, advertisedWritableDirs } = input;
7
+ const { spec, deps, failClosedReason, lockedPreflight, selfOrchestrationActive, executionEnv, reminderMark, resolvedReadFace, resolvedWriteFace, toolFaceSnapshot, promptProfile, fableMitigations, internals, thinking, ownedEnv, delegation, resolvedRole, mcp, model, externalContentTargetActive, resume, handsEnabled, taskRootFinal, additionalRootsCanonical, additionalReadRootsCanonical, sessionId, advertisedWritableDirs } = input;
8
8
  const provider = spec.promptProvider ?? deps.promptProvider ?? defaultPromptProvider;
9
9
  const promptPolicyEnabled = Boolean(lockedPreflight.toolPolicy);
10
10
  const promptHooks = spec.hooks ?? deps.hooks;
@@ -18,6 +18,7 @@ export async function preparePromptInputs(input) {
18
18
  isolationEnabled: isIsolated(executionEnv),
19
19
  reminderMark,
20
20
  readFaceOpen: resolvedReadFace === "open",
21
+ writeFaceOpen: resolvedWriteFace === "open",
21
22
  orchestrationEnabled: selfOrchestrationActive,
22
23
  orchestrationDeferred: selfOrchestrationActive && (toolFaceSnapshot.defer?.includes("Workflow") ?? false),
23
24
  promptProfile,
@@ -74,6 +75,8 @@ export async function preparePromptInputs(input) {
74
75
  }
75
76
  if (resolvedReadFace !== undefined)
76
77
  envFacts.readFace = resolvedReadFace;
78
+ if (resolvedWriteFace !== undefined)
79
+ envFacts.writeFace = resolvedWriteFace;
77
80
  if (additionalReadRootsCanonical.length > 0) {
78
81
  envFacts.additionalReadDirectories = [...additionalReadRootsCanonical];
79
82
  }
@@ -20,7 +20,7 @@ import type { SubagentWorktreeIsolation } from "../../agents/subagent.js";
20
20
  import { SubagentRetainLedger } from "../../agents/retain-ledger.js";
21
21
  import type { TaskEvent, TaskSpec } from "../types.js";
22
22
  import { ActiveSkillScope } from "./active-skill-scope.js";
23
- import type { Prepared, RunInternals } from "./contracts.js";
23
+ import type { Prepared, RunInternals, ToolProgressLane } from "./contracts.js";
24
24
  import { type EditedFilesLedger } from "./edited-files-ledger.js";
25
25
  export interface PrepareRunRefsInput {
26
26
  /** borrowed-readonly — the REBOUND spec. Read: `forwardSubagentEvents` (widens the forwarding filter),
@@ -52,6 +52,14 @@ export interface PrepareRunRefsResult {
52
52
  harnessRef: {
53
53
  current?: AgentHarness;
54
54
  };
55
+ /** borrowed-mutable — the tool-progress lane's seat (`Prepared.toolProgressRef`), the same late-bound
56
+ * shape and reason as `harnessRef`: the band is built HERE while the lane needs the run's content pusher
57
+ * and identity mint, which exist only after prepare returns. Writer (R-3): the driver, ONCE, right after
58
+ * `pushContent` exists. Readers: the band's progress seat and the harness-handler lane. Identity is the
59
+ * contract. Unbound is LEGITIMATE — a band outside a Runner has no run to tick on, and emissions no-op. */
60
+ toolProgressRef: {
61
+ current?: ToolProgressLane;
62
+ };
55
63
  /** borrowed-mutable — the per-task LIFO skill scope. Writers: the project-context phase (the inherited-
56
64
  * from-parent seed frame, `push`) and the `skill` tool minted over it (`push` on a manifest load; a frame
57
65
  * closes by `pop`). Readers: the tool ctx's `activeSkillScope()` snapshot and the skill-scope policy layer. */
@@ -12,6 +12,7 @@ export function prepareRunRefs(input) {
12
12
  const { spec, internals, executionEnv, taskRootFinal } = input;
13
13
  const { note: noteFileEdited, snapshot: editedFilesSnapshot } = createEditedFilesLedger();
14
14
  const harnessRef = {};
15
+ const toolProgressRef = {};
15
16
  const skillScope = new ActiveSkillScope();
16
17
  const forwardSink = internals?.onForwardEvent;
17
18
  const forwardEvent = forwardSink
@@ -19,7 +20,7 @@ export function prepareRunRefs(input) {
19
20
  if (e.type === "task_progress")
20
21
  forwardSink(e);
21
22
  else if (spec.forwardSubagentEvents === true &&
22
- (e.type === "text_delta" || e.type === "text_end" || e.type === "reasoning_delta" || e.type === "tool_start" || e.type === "tool_end")) {
23
+ (e.type === "text_delta" || e.type === "text_end" || e.type === "reasoning_delta" || e.type === "tool_start" || e.type === "tool_progress" || e.type === "tool_end")) {
23
24
  forwardSink(e);
24
25
  }
25
26
  }
@@ -41,5 +42,5 @@ export function prepareRunRefs(input) {
41
42
  };
42
43
  const subagentRetain = spec.retainSubagentSessions ? new SubagentRetainLedger(spec.retainSubagentSessions) : undefined;
43
44
  const worktreeIsolation = spec.handsReadOnly !== true ? createSubagentWorktreeHelper(executionEnv, taskRootFinal) : undefined;
44
- return { noteFileEdited, editedFilesSnapshot, harnessRef, skillScope, forwardEvent, reviewRequestRef, requestReview, stopRequestedRef, requestStopAfterTurn, planModeRef, enterPlanMode, subagentRetain, worktreeIsolation };
45
+ return { noteFileEdited, editedFilesSnapshot, harnessRef, toolProgressRef, skillScope, forwardEvent, reviewRequestRef, requestReview, stopRequestedRef, requestStopAfterTurn, planModeRef, enterPlanMode, subagentRetain, worktreeIsolation };
45
46
  }
@@ -109,12 +109,17 @@ export interface PrepareSuspendSagaInput {
109
109
  readFileStateForCheckpoint: ReadFileState | undefined;
110
110
  /** borrowed-readonly — the inherited-gate phase's read-face section builder (the row's `readFace`). */
111
111
  faceCheckpointSection: () => SerializedCheckpointState["readFace"];
112
+ /** borrowed-readonly — the inherited-gate phase's WRITE-face section builder (the row's `writeFace`). */
113
+ writeFaceCheckpointSection: () => SerializedCheckpointState["writeFace"];
112
114
  /** borrowed-readonly — the leg's reminder provenance mark (travels with the row). */
113
115
  reminderMark: string;
114
116
  /** borrowed-readonly — the hand's live tracked cwd, or undefined; `current` is read at mint time. Not written here. */
115
117
  handsCwdRef: CwdRef | undefined;
116
118
  /** borrowed-readonly — the active EnterWorktree session seat, or undefined; `current` is read at mint time. */
117
119
  worktreeSessionRef: Prepared["worktreeSessionRef"];
120
+ /** borrowed-readonly — the resumed leg's not-yet-delivered continuation content; its orphan obituaries fold into this
121
+ * snapshot at mint time (a re-park before delivery must not lose them). */
122
+ undeliveredContinuationRef: Prepared["undeliveredContinuationRef"];
118
123
  /** borrowed-readonly — the LIVE inherited parent constraints, or undefined (the row's `requiresParentConstraint`,
119
124
  * count, frozen chain and the screening-park disclosure's predicate). */
120
125
  inheritedParentConstraints: NonNullable<InheritedGate["parentConstraints"]>[number][] | undefined;
@@ -78,7 +78,7 @@ function stampPlacementRootSessionId(placementRootResolved) {
78
78
  return placementValueOrAbsent(placementRootResolved);
79
79
  }
80
80
  export function prepareSuspendSaga(input) {
81
- const { gateMachineryActive, abortController, harness, checkpointStore, deps, sessionId, hostTaskId, taskScope, liveSpendRef, resume, nestedStats, internals, activeTools, outputRef, readFileStateForCheckpoint, faceCheckpointSection, reminderMark, handsCwdRef, worktreeSessionRef, inheritedParentConstraints, seedInheritedGate, inheritedAncestorRules, inheritedShellGate, autoModeIntent, inheritedAdmittedOrgScopes, ownOrgVerdictRef, orgGovernedProvenance, announcedListingsRef, gitStatusRef, hookIdentity, placementRootResolved, externalContentTargetActive, remoteEnvFailures, memoryEngineSession, suspendLoopRef, ownedEnv, incompleteSuspendAdapter } = input;
81
+ const { gateMachineryActive, abortController, harness, checkpointStore, deps, sessionId, hostTaskId, taskScope, liveSpendRef, resume, nestedStats, internals, activeTools, outputRef, readFileStateForCheckpoint, faceCheckpointSection, writeFaceCheckpointSection, reminderMark, handsCwdRef, worktreeSessionRef, undeliveredContinuationRef, inheritedParentConstraints, seedInheritedGate, inheritedAncestorRules, inheritedShellGate, autoModeIntent, inheritedAdmittedOrgScopes, ownOrgVerdictRef, orgGovernedProvenance, announcedListingsRef, gitStatusRef, hookIdentity, placementRootResolved, externalContentTargetActive, remoteEnvFailures, memoryEngineSession, suspendLoopRef, ownedEnv, incompleteSuspendAdapter } = input;
82
82
  if (!gateMachineryActive)
83
83
  return { saga: undefined };
84
84
  const inFlightSpendMicroUsd = () => {
@@ -108,6 +108,7 @@ export function prepareSuspendSaga(input) {
108
108
  nestedStats: { ...nestedStats },
109
109
  readFileState: readFileStateForCheckpoint ? [...readFileStateForCheckpoint.entries()] : undefined,
110
110
  readFace: faceCheckpointSection(),
111
+ writeFace: writeFaceCheckpointSection(),
111
112
  reminderMark,
112
113
  repairBundle: repairBundleForCheckpoint(parkedSpendMicroUsd),
113
114
  workspaceHandle,
@@ -118,7 +119,9 @@ export function prepareSuspendSaga(input) {
118
119
  .list({ owner: hostTaskId, scope: taskScope, sessionId })
119
120
  .filter((t) => t.status === "pending" || t.status === "running")
120
121
  .map((t) => ({ id: t.task_id, ...(t.description !== undefined ? { description: t.description } : {}) }));
121
- return live.length > 0 ? live : undefined;
122
+ const owed = undeliveredContinuationRef.current.orphanTasks.filter((o) => !live.some((t) => t.id === o.id));
123
+ const all = [...owed, ...live];
124
+ return all.length > 0 ? all : undefined;
122
125
  })(),
123
126
  pendingSteerQueue: undefined,
124
127
  inheritedGate: (() => {