@sema-agent/core 7.16.0 → 7.17.1

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 (207) hide show
  1. package/CHANGELOG.md +209 -0
  2. package/dist/agents/builtin-agents.d.ts +3 -3
  3. package/dist/agents/cross-session-envelope.d.ts +0 -1
  4. package/dist/agents/cross-session-envelope.js +0 -1
  5. package/dist/agents/peer-notices.d.ts +0 -9
  6. package/dist/agents/peer-notices.js +0 -63
  7. package/dist/agents/subagent.js +7 -15
  8. package/dist/brain/request-params.d.ts +2 -2
  9. package/dist/config/defaults.d.ts +0 -7
  10. package/dist/config/defaults.js +0 -2
  11. package/dist/core/artifact-host.d.ts +218 -0
  12. package/dist/core/artifact-host.js +92 -0
  13. package/dist/core/ask-origin.d.ts +29 -3
  14. package/dist/core/ask-origin.js +17 -9
  15. package/dist/core/ask-question.d.ts +25 -0
  16. package/dist/core/ask-question.js +4 -0
  17. package/dist/core/ask-unresolvable-notice.d.ts +30 -21
  18. package/dist/core/ask-unresolvable-notice.js +16 -8
  19. package/dist/core/auto-compaction.d.ts +10 -10
  20. package/dist/core/auto-mode-arming.d.ts +2 -2
  21. package/dist/core/background-agent-store.d.ts +5 -5
  22. package/dist/core/background-shell.d.ts +1 -1
  23. package/dist/core/checkpoint-execution-record.d.ts +0 -3
  24. package/dist/core/checkpoint-execution-record.js +0 -4
  25. package/dist/core/checkpoint-store.d.ts +71 -99
  26. package/dist/core/checkpoint-store.js +9 -16
  27. package/dist/core/context-edit.d.ts +36 -104
  28. package/dist/core/context-edit.js +37 -60
  29. package/dist/core/effective-path-target.d.ts +68 -8
  30. package/dist/core/effective-path-target.js +42 -30
  31. package/dist/core/engine-notice.d.ts +46 -32
  32. package/dist/core/env-budget.d.ts +31 -0
  33. package/dist/core/env-budget.js +39 -0
  34. package/dist/core/exec-output-tail.d.ts +5 -5
  35. package/dist/core/file-history-store.js +3 -6
  36. package/dist/core/fs-write-gate-policy.d.ts +1 -1
  37. package/dist/core/fs-write-gate-policy.js +3 -3
  38. package/dist/core/gate-lanes.js +9 -15
  39. package/dist/core/governance-codes.d.ts +1 -1
  40. package/dist/core/governance-codes.js +2 -0
  41. package/dist/core/hands-band.d.ts +1 -1
  42. package/dist/core/hooks.d.ts +18 -17
  43. package/dist/core/lsp-session.d.ts +1 -1
  44. package/dist/core/lsp.d.ts +1 -1
  45. package/dist/core/lsp.js +5 -4
  46. package/dist/core/mcp.d.ts +5 -5
  47. package/dist/core/mcp.js +4 -48
  48. package/dist/core/memory-engine/engine.js +2 -1
  49. package/dist/core/memory-engine/layout.d.ts +18 -6
  50. package/dist/core/memory-engine/layout.js +40 -21
  51. package/dist/core/memory-recall.d.ts +0 -8
  52. package/dist/core/memory.d.ts +1 -1
  53. package/dist/core/oracle-isolation.js +2 -2
  54. package/dist/core/parent-spec-seat.d.ts +66 -0
  55. package/dist/core/parent-spec-seat.js +47 -0
  56. package/dist/core/permission-rule-model.d.ts +11 -6
  57. package/dist/core/permission-rule-model.js +53 -37
  58. package/dist/core/permission-rules.js +8 -3
  59. package/dist/core/physical-path.d.ts +37 -0
  60. package/dist/core/physical-path.js +30 -0
  61. package/dist/core/protocol-naming.d.ts +2 -2
  62. package/dist/core/remote-env.d.ts +1 -1
  63. package/dist/core/retired-keys.d.ts +19 -0
  64. package/dist/core/retired-keys.js +15 -0
  65. package/dist/core/runner/active-skill-scope.js +3 -3
  66. package/dist/core/runner/assemble-result.d.ts +15 -13
  67. package/dist/core/runner/assemble-result.js +5 -4
  68. package/dist/core/runner/compaction-call-options.d.ts +25 -89
  69. package/dist/core/runner/contracts.d.ts +11 -10
  70. package/dist/core/runner/gate-exit.js +15 -1
  71. package/dist/core/runner/halt-attribution.d.ts +35 -0
  72. package/dist/core/runner/halt-attribution.js +16 -0
  73. package/dist/core/runner/memory-consolidation.d.ts +0 -2
  74. package/dist/core/runner/memory-consolidation.js +0 -1
  75. package/dist/core/runner/prepare-artifact.d.ts +52 -0
  76. package/dist/core/runner/prepare-artifact.js +63 -0
  77. package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -2
  78. package/dist/core/runner/prepare-caps-and-workflow.js +2 -3
  79. package/dist/core/runner/prepare-config-doors.d.ts +3 -3
  80. package/dist/core/runner/prepare-config-doors.js +5 -2
  81. package/dist/core/runner/prepare-context-lane.d.ts +2 -2
  82. package/dist/core/runner/prepare-context-lane.js +2 -13
  83. package/dist/core/runner/prepare-file-history.d.ts +1 -1
  84. package/dist/core/runner/prepare-file-history.js +3 -52
  85. package/dist/core/runner/prepare-hands-readface.d.ts +4 -7
  86. package/dist/core/runner/prepare-hands-readface.js +3 -2
  87. package/dist/core/runner/prepare-inherited-gate.d.ts +5 -2
  88. package/dist/core/runner/prepare-inherited-gate.js +7 -8
  89. package/dist/core/runner/prepare-memory.d.ts +4 -6
  90. package/dist/core/runner/prepare-memory.js +4 -2
  91. package/dist/core/runner/prepare-policy-chain.d.ts +3 -3
  92. package/dist/core/runner/prepare-policy-chain.js +15 -30
  93. package/dist/core/runner/prepare-prompt-assembly.js +1 -1
  94. package/dist/core/runner/prepare-question-face.js +2 -1
  95. package/dist/core/runner/prepare-safety-scan.d.ts +1 -1
  96. package/dist/core/runner/prepare-suspend-saga.js +0 -2
  97. package/dist/core/runner/prepare-task.js +12 -11
  98. package/dist/core/runner/prepare-wiring-manifest.js +9 -7
  99. package/dist/core/runner/resume-admission.js +2 -1
  100. package/dist/core/runner/resume-internals-and-config.js +4 -0
  101. package/dist/core/runner/retired-runner-deps.d.ts +3 -0
  102. package/dist/core/runner/retired-runner-deps.js +7 -0
  103. package/dist/core/runner/run-harness-handlers.js +5 -3
  104. package/dist/core/runner/run-settle-and-teardown.d.ts +1 -1
  105. package/dist/core/runner/run-settle-and-teardown.js +3 -1
  106. package/dist/core/runner/session-file-state-replay.js +3 -3
  107. package/dist/core/runner/session-rule-policy.js +3 -4
  108. package/dist/core/runner/stream-halt-verbs.js +1 -1
  109. package/dist/core/runner/stream-steer-verb.js +1 -4
  110. package/dist/core/runner/workspace-path.js +3 -0
  111. package/dist/core/runner-deps.d.ts +30 -49
  112. package/dist/core/safe-notify.d.ts +1 -1
  113. package/dist/core/sensitive-path-policy.d.ts +1 -1
  114. package/dist/core/sensitive-path-policy.js +11 -12
  115. package/dist/core/session.d.ts +2 -2
  116. package/dist/core/skill-tool-specifier.d.ts +1 -1
  117. package/dist/core/skills-directory.d.ts +1 -1
  118. package/dist/core/skills-directory.js +4 -3
  119. package/dist/core/spec-contract.js +5 -4
  120. package/dist/core/store-contracts/checkpoint-store-contract.js +1 -1
  121. package/dist/core/strategy-store.d.ts +8 -11
  122. package/dist/core/task-event.d.ts +20 -17
  123. package/dist/core/task-limits.d.ts +1 -1
  124. package/dist/core/task-notification.d.ts +5 -5
  125. package/dist/core/task-registry-agent.d.ts +5 -5
  126. package/dist/core/task-registry-monitor.d.ts +2 -2
  127. package/dist/core/task-registry-shared.d.ts +32 -28
  128. package/dist/core/task-registry-shared.js +4 -13
  129. package/dist/core/task-registry.d.ts +8 -8
  130. package/dist/core/task-result.d.ts +37 -37
  131. package/dist/core/task-spec.d.ts +5 -5
  132. package/dist/core/task-stream.d.ts +16 -11
  133. package/dist/core/tool-catalog-entries.d.ts +1 -0
  134. package/dist/core/tool-catalog-entries.js +6 -1
  135. package/dist/core/tool-conformance.d.ts +0 -2
  136. package/dist/core/tool-conformance.js +1 -3
  137. package/dist/core/tool-errors.d.ts +5 -0
  138. package/dist/core/tool-errors.js +1 -0
  139. package/dist/core/tool-policy.d.ts +66 -22
  140. package/dist/core/tool-policy.js +31 -5
  141. package/dist/core/tool-result-budget.d.ts +1 -1
  142. package/dist/core/tool-result-store.d.ts +11 -11
  143. package/dist/core/tool-spec.d.ts +34 -38
  144. package/dist/core/tools.d.ts +2 -2
  145. package/dist/core/trace.d.ts +12 -15
  146. package/dist/core/untrusted-text.d.ts +1 -1
  147. package/dist/core/wiring-manifest.d.ts +4 -3
  148. package/dist/core/workflow-journal-store.d.ts +11 -11
  149. package/dist/engine/execution-env/node-execution-env.d.ts +3 -2
  150. package/dist/engine/execution-env/node-execution-env.js +8 -5
  151. package/dist/engine/harness/agent-harness.d.ts +47 -4
  152. package/dist/engine/harness/agent-harness.js +52 -9
  153. package/dist/engine/loop/agent-loop.js +15 -8
  154. package/dist/engine/loop/types.d.ts +5 -0
  155. package/dist/index.d.ts +20 -14
  156. package/dist/index.js +16 -10
  157. package/dist/internal/harness.d.ts +1 -0
  158. package/dist/orchestration/run-spec.d.ts +3 -0
  159. package/dist/orchestration/run-spec.js +3 -3
  160. package/dist/orchestration/run-workflow-tool.d.ts +12 -6
  161. package/dist/orchestration/run-workflow-tool.js +23 -5
  162. package/dist/orchestration/workflow-primitives.d.ts +4 -2
  163. package/dist/orchestration/workflow-primitives.js +1 -4
  164. package/dist/orchestration/workflow-script-store.js +9 -25
  165. package/dist/orchestration/workflow-types.d.ts +34 -4
  166. package/dist/orchestration/workflow-types.js +2 -0
  167. package/dist/orchestration/workflow.d.ts +39 -7
  168. package/dist/orchestration/workflow.js +237 -113
  169. package/dist/prompt-assembly/assemble.d.ts +2 -2
  170. package/dist/prompt-assembly/assemble.js +9 -9
  171. package/dist/prompt-assembly/composer.d.ts +6 -7
  172. package/dist/prompt-assembly/composer.js +10 -10
  173. package/dist/prompt-assembly/epoch.js +1 -1
  174. package/dist/prompt-assembly/event-registry.d.ts +0 -21
  175. package/dist/prompt-assembly/packs/sema-default.js +30 -30
  176. package/dist/prompt-assembly/types.d.ts +14 -14
  177. package/dist/prompts/coordinator.d.ts +0 -13
  178. package/dist/prompts/coordinator.js +0 -3
  179. package/dist/stores/cc/task-list-store.js +2 -10
  180. package/dist/stores/file/checkpoint-store.js +1 -5
  181. package/dist/stores/file/file-history-store.js +2 -2
  182. package/dist/stores/file/fs-atomic.d.ts +15 -18
  183. package/dist/stores/file/fs-atomic.js +4 -14
  184. package/dist/stores/file/mailbox-store.d.ts +7 -11
  185. package/dist/stores/file/mailbox-store.js +4 -11
  186. package/dist/stores/file/permission-rule-store.d.ts +4 -35
  187. package/dist/stores/file/permission-rule-store.js +4 -148
  188. package/dist/tools/artifact/artifact-text.d.ts +62 -0
  189. package/dist/tools/artifact/artifact-text.js +68 -0
  190. package/dist/tools/artifact/artifact-tool.d.ts +52 -0
  191. package/dist/tools/artifact/artifact-tool.js +351 -0
  192. package/dist/tools/artifact/local-stub.d.ts +26 -0
  193. package/dist/tools/artifact/local-stub.js +297 -0
  194. package/dist/tools/fs/bash-readonly-classifier.d.ts +90 -8
  195. package/dist/tools/fs/bash-readonly-classifier.js +294 -70
  196. package/dist/tools/fs/fs-bash.d.ts +14 -10
  197. package/dist/tools/fs/fs-bash.js +50 -33
  198. package/dist/tools/fs/fs-search-tools.js +5 -2
  199. package/dist/tools/fs/fs-shared.d.ts +2 -0
  200. package/dist/tools/fs/fs-shared.js +3 -11
  201. package/dist/tools/fs/index.d.ts +4 -0
  202. package/dist/tools/fs/index.js +4 -1
  203. package/dist/tools/fs/safety.d.ts +117 -1
  204. package/dist/tools/fs/safety.js +131 -31
  205. package/dist/tools/fs/search.js +8 -6
  206. package/package.json +2 -1
  207. package/test/export-surface.snapshot.json +119 -39
@@ -28,7 +28,7 @@ import type { AutoModeDecider, AutoModeDenialTracker } from "../auto-mode.js";
28
28
  import type { CacheBreakDetector, ToolFingerprintInput } from "../cache-break-detector.js";
29
29
  import type { ActorAssertion, Checkpoint, CheckpointState, CheckpointStore, CheckpointToken, PendingSteerEntry, PlatformLimitReason, ReopenReason, ResourceLedger, ResourceLimitReason, ResumeOutcome, SerializedCheckpointState } from "../checkpoint-store.js";
30
30
  import type { ExecutionOutcomeRecordWord } from "../checkpoint-execution-record.js";
31
- import type { ClearedProjectionLedger, ContextEditMachine, OccurrenceIndex } from "../context-edit.js";
31
+ import type { ClearedProjectionLedger, MicroCompactMachine, OccurrenceIndex } from "../context-edit.js";
32
32
  import type { HookInvocationIdentity, Hooks, OrgGateVerdict, PermissionDeniedPayload, PostToolBatchCall, PostToolBatchResult, ToolGateInput } from "../hooks.js";
33
33
  import type { MaterializedMcp } from "../mcp.js";
34
34
  import type { MemoryEngine } from "../memory-engine/engine.js";
@@ -1051,16 +1051,14 @@ export interface Prepared {
1051
1051
  * `context-edit.ts`'s ledger note; per-run in-memory, so durable resume / `resumeAt` rebuilds
1052
1052
  * start EMPTY by construction), the last request's projection seat (what the provider actually
1053
1053
  * saw — the MC-R rejection arm computes its candidates and savings on THIS view, never on the
1054
- * raw session rebuild), the MC-R knob, and the slice-3 arm-B seat. The explicit opt-out
1055
- * (`machine: "legacy"` + MC-R off) ⇒ the ledger never gains an entry and every replay is a
1056
- * same-reference no-op (opt-out bytes unchanged). */
1054
+ * raw session rebuild), the MC-R knob, and the slice-3 arm-B seat. */
1057
1055
  microCompact: PreparedMicroCompact;
1058
1056
  }
1059
1057
  /** See {@link Prepared.microCompact}. */
1060
1058
  export interface PreparedMicroCompact {
1061
- /** The frontier-machine selection — `"off"` = no proactive frontier clearing (the unified
1062
- * machine instead gets its one blocking-point shot, slice-3 arm A). */
1063
- machine: "off" | ContextEditMachine;
1059
+ /** The frontier-machine selection — `"off"` = no proactive frontier clearing (the machine
1060
+ * instead gets its one blocking-point shot, slice-3 arm A). */
1061
+ machine: MicroCompactMachine;
1064
1062
  /** MC-R: one-shot clear-and-retry on a provider input-too-long rejection.
1065
1063
  * Default true since the slice-3 flip. */
1066
1064
  clearOnRejection: boolean;
@@ -1105,7 +1103,7 @@ export interface PrepareResume {
1105
1103
  * crash-interrupted; closing them with `[INTERRUPTED]` would DESTROY the suspended batch, §15.2 #7). */
1106
1104
  suspendedBatch: ReadonlySet<string>;
1107
1105
  /** The §4.bis correctness-state snapshot to re-seed (activeTools / outputRef / nestedStats /
1108
- * consolidationNotes / readFileState). */
1106
+ * readFileState). */
1109
1107
  seed: CheckpointState;
1110
1108
  /** How many times this task already suspended (the resumed checkpoint's
1111
1109
  * `suspendCount`). The next suspend mints `priorSuspendCount + 1`; past `maxSuspends` it fails
@@ -1393,7 +1391,10 @@ export interface RunInternals {
1393
1391
  * `"interactive"` tree's engine-stripped legs are exempt). TRUST POSTURE (ruled 2026-08-05):
1394
1392
  * RunInternals is a public trusted-caller parameter, so this flag is an honesty channel, not a
1395
1393
  * security gate — a caller forging it is a deployment lying to itself; resource-face safety
1396
- * stays with its own fail-closed mechanisms.
1394
+ * stays with its own fail-closed mechanisms. INHERITS DOWN THE TREE: a stripped leg's descendants are faceless by
1395
+ * the same engine act (the trusted ctx carries it as `ToolExecuteContext.questionFaceStripped`, the Workflow mount as
1396
+ * `parentQuestionFaceStripped`), so the posture-door exemption travels with the facelessness it explains; a leg
1397
+ * that strips on its own mints the same flag.
1397
1398
  */
1398
1399
  questionFaceStripped?: true;
1399
1400
  /**
@@ -1516,7 +1517,7 @@ export interface RunInternals {
1516
1517
  workflowParkedResume?: ReadonlyArray<{
1517
1518
  runId: string;
1518
1519
  token: CheckpointToken;
1519
- outcome: ResumeOutcome;
1520
+ outcome?: ResumeOutcome;
1520
1521
  inheritedGate?: InheritedGate;
1521
1522
  }>;
1522
1523
  /**
@@ -1,5 +1,5 @@
1
1
  import { screenGateOutcome, SETTLEMENT_IS_REFUSAL } from "../gate-outcome.js";
2
- import { classifierDenyCauseOf, decisionText, engineSettlementOf } from "../tool-policy.js";
2
+ import { askUnresolvableCauseOf, classifierDenyCauseOf, decisionText, engineSettlementOf } from "../tool-policy.js";
3
3
  import { inlineUntrusted } from "../untrusted-text.js";
4
4
  import { formatHookFeedback } from "../reminder-mint.js";
5
5
  export function mintGateOutcome(facts) {
@@ -96,6 +96,15 @@ export function hookDenyExit(pass, r) {
96
96
  preToolContext: pass.preToolContext,
97
97
  };
98
98
  }
99
+ function unresolvableCauseOfSettlement(gate) {
100
+ if (gate.disposition.kind !== "denied" || gate.disposition.deniedBy !== "ask_resolution" || gate.settlement === undefined)
101
+ return undefined;
102
+ if (gate.settlement.kind === "approver_unavailable")
103
+ return "approver_unavailable";
104
+ if (gate.settlement.kind === "no_approver")
105
+ return "no_approver_wired";
106
+ return undefined;
107
+ }
99
108
  export async function exitGate(pass) {
100
109
  const { input, event, toolName, toolCallId, ledger, notifyPermissionDenied } = pass;
101
110
  if (pass.decision.action === "deny") {
@@ -106,6 +115,11 @@ export async function exitGate(pass) {
106
115
  if (pass.deniedBy === undefined)
107
116
  throw new Error(`the tool gate refused "${toolName}" without a refusing layer — every deny site attributes itself`);
108
117
  const gate = mintGateOutcome({ deniedBy: pass.deniedBy, ...(pass.denyCause !== undefined ? { cause: pass.denyCause } : {}), ...(ledger.settled !== undefined ? { settled: ledger.settled } : {}) });
118
+ const unresolvable = askUnresolvableCauseOf(pass.decision, { toolCallId, toolName }) ?? unresolvableCauseOfSettlement(gate);
119
+ if (unresolvable !== undefined) {
120
+ const cause = unresolvable;
121
+ pass.notifier.notify(() => input.onAskUnresolvable?.({ toolName, toolCallId, cause, parkLaneExisted: pass.suspendAsk !== undefined }), "toolGate.onAskUnresolvable");
122
+ }
109
123
  await notifyPermissionDenied({
110
124
  toolName,
111
125
  input: cloneObserverInput(pass.currentInput),
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Halt attribution, the consumer half: the ONE predicate that turns the harness's
3
+ * consumption statement ({@link HaltConsumption}) into the result's `haltedByUser` seat, and the disclosure
4
+ * for the halt that was accepted but never consumed. Both readers of the statement live here so the law
5
+ * has one spelling: signed ⇔ consumed; unconsumed ⇔ absent + `task.halt_unconsumed`.
6
+ */
7
+ import type { HaltConsumption } from "../../internal/harness.js";
8
+ import { type RunnerDeps } from "../types.js";
9
+ /**
10
+ * The attribution law: `haltedByUser` ⇔ the halt was CONSUMED by the machinery — its cut settled a turn (the
11
+ * loop's report, stamped `"halt"` at the seat's abort) OR its stop request was among the sources a boundary
12
+ * consult stopped the loop for. A halt merely ACCEPTED (latched, receipt answered) while the run ended for its
13
+ * own reason is not consumed and never signs (fail-closed: rather absent than guessed).
14
+ */
15
+ export declare function haltConsumed(c: HaltConsumption | undefined): boolean;
16
+ export interface UnconsumedHaltDisclosureInput {
17
+ /** The verb's acceptance fact (`loopLatch.userHalted`): a halt was latched on this run. */
18
+ accepted: boolean;
19
+ /** The harness's statement, read after the prompt settled. */
20
+ consumption: HaltConsumption | undefined;
21
+ onNotice: RunnerDeps["onNotice"];
22
+ sessionId: string;
23
+ runId: string;
24
+ taskId: string | undefined;
25
+ }
26
+ /**
27
+ * The ACCEPTED-but-UNCONSUMED halt: the verb latched the stop (and may have cut a seat), but the run ended for
28
+ * its own reason before the machinery consumed it — no settlement took the halt's cut as its cause and no
29
+ * boundary consult stopped the loop for it (a brain minting its own `aborted` final, a natural completion past
30
+ * the final commit point, an approval park whose commit won the race). The result reads the run's own terminal
31
+ * without `haltedByUser`; this notice is the halt's trace — the same code the verb mints on its own arm (a halt
32
+ * refused by an already-owned ending), one fact with one spelling, the two mint points disjoint: the verb arm mints
33
+ * only while NO halt has been accepted on the run; once one was, this arm alone speaks. One per such run. Returns whether it was minted.
34
+ */
35
+ export declare function discloseUnconsumedHalt(input: UnconsumedHaltDisclosureInput): boolean;
@@ -0,0 +1,16 @@
1
+ import { deliverEngineNotice } from "../types.js";
2
+ export function haltConsumed(c) {
3
+ return c?.turnCut === "halt" || c?.boundaryStop?.sources.includes("halt") === true;
4
+ }
5
+ export function discloseUnconsumedHalt(input) {
6
+ if (!input.accepted || haltConsumed(input.consumption))
7
+ return false;
8
+ deliverEngineNotice(input.onNotice, {
9
+ code: "task.halt_unconsumed",
10
+ message: "a user halt was accepted, but the run ended for its own reason before the halt stopped or cut anything: " +
11
+ "no turn was settled as its cut and no boundary stopped for it — the run's own ending stands, and the result " +
12
+ "will not carry haltedByUser for this halt.",
13
+ detail: { sessionId: input.sessionId, runId: input.runId, ...(input.taskId !== undefined ? { taskId: input.taskId } : {}) },
14
+ });
15
+ return true;
16
+ }
@@ -23,8 +23,6 @@ export declare const DEFAULT_CONSOLIDATION_BAND: {
23
23
  export declare const DEFAULT_CONSOLIDATION_SEARCH_LIMIT = 20;
24
24
  /** Default cap on this-task notes consolidated in one batch (F6 token bound). */
25
25
  export declare const DEFAULT_CONSOLIDATION_MAX_NOTES = 50;
26
- /** Default independent timeout for the whole consolidation pass (seconds). */
27
- export declare const DEFAULT_CONSOLIDATION_TIMEOUT_SEC = 30;
28
26
  /** Normalize a note body for the EXACT-restatement check in the `<lo` near-dup cheap path. Case- + whitespace-
29
27
  * insensitive ONLY (lower-case, collapse whitespace runs, trim); it PRESERVES every symbol/punctuation so it can
30
28
  * NOT false-equate semantically-distinct facts. (The earlier `[^\p{L}\p{N}]`-stripping
@@ -4,7 +4,6 @@ import { cacheFamilyOf, usageCostMicroUsd } from "./usage-accounting.js";
4
4
  export const DEFAULT_CONSOLIDATION_BAND = { lo: 0.05, hi: 0.3 };
5
5
  export const DEFAULT_CONSOLIDATION_SEARCH_LIMIT = 20;
6
6
  export const DEFAULT_CONSOLIDATION_MAX_NOTES = 50;
7
- export const DEFAULT_CONSOLIDATION_TIMEOUT_SEC = 30;
8
7
  export function normalizeForExactMatch(s) {
9
8
  return s.toLowerCase().replace(/\s+/g, " ").trim();
10
9
  }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * PrepareTask's Artifact phase: the `RunnerDeps.artifactHost` seat is read once; an absent seat mounts
3
+ * nothing and announces nothing; a seat whose declaration fails an invariant mounts nothing and announces
4
+ * `config.artifact_host_invalid` (operator audience) so a bad wiring can never read as "no hosting"; a
5
+ * valid seat mounts ONE `Artifact` tool assembled from the declaration, on the same optional band the LSP
6
+ * seat uses. The tool's local-file read goes through the run's fs fence (root containment, read-deny, read
7
+ * face) exactly as `Read` does, then reads the bytes once from the execution env. A local stub's store
8
+ * (`capabilities.storageDir`) must lie OUTSIDE every root the run may write or read — a ledger the model can
9
+ * edit through the fs tools is not a trust boundary — and a store inside one refuses the mount, naming both.
10
+ * Synchronous: the stretch has no await (the file read is a closure the tool calls later).
11
+ */
12
+ import type { ExecutionEnv } from "../../internal/harness.js";
13
+ import { type ArtifactPlane } from "../artifact-host.js";
14
+ import { RosterBuilder } from "../tool-roster.js";
15
+ import type { AgentTool, RunnerDeps } from "../types.js";
16
+ import type { ReadDenyMatcher, ReadFace } from "../../tools/fs/index.js";
17
+ import type { Prepared } from "./contracts.js";
18
+ export interface PrepareArtifactInput {
19
+ /** borrowed-readonly — the deployment's hosting seat and the notice sink the refusal arm announces on. */
20
+ deps: Pick<RunnerDeps, "artifactHost" | "onNotice">;
21
+ /** borrowed-readonly — the engine-minted run id: the host's `onMount` key and every call context's `runId`. */
22
+ runId: string;
23
+ /** borrowed-readonly — the host task id, the call context's audit attribution. */
24
+ taskId: string;
25
+ /** borrowed-readonly — the acquired session id (notice attribution and the call context's `sessionId`). */
26
+ sessionId: string;
27
+ /** borrowed-readonly — the spawned name this run carries, when any (call-context attribution only). */
28
+ agentName: string | undefined;
29
+ /** borrowed-readonly — the run's resolved env: the byte source of a published local file. */
30
+ executionEnv: ExecutionEnv;
31
+ /** borrowed-readonly — the canonical task root the fs fence contains reads to; undefined ⇒ no local-file publish. */
32
+ rootCanonical: string | undefined;
33
+ /** borrowed-readonly — the extra containment roots (write and read-only) the fence admits. */
34
+ additionalRootsCanonical: readonly string[];
35
+ /** borrowed-readonly — the read-deny matcher in force for the run, when any. */
36
+ readDenyMatcher: ReadDenyMatcher | undefined;
37
+ /** borrowed-readonly — the resolved read face (`"open"` lifts root containment for reads). */
38
+ resolvedReadFace: ReadFace | undefined;
39
+ /** borrowed-readonly — the tracked-cwd cell; `current` is read per call as the relative-path base. */
40
+ handsCwdRef: Prepared["cwdRef"];
41
+ /** borrowed-mutable — the run's roster builder; writer here: ONE mount when the seat is valid. */
42
+ roster: RosterBuilder;
43
+ /** borrowed-readonly — the first-party result-offload wrapper every core mount passes through (a large read or a conflict snapshot is offloaded, not inlined). */
44
+ firstPartyOffload: (tool: AgentTool) => AgentTool;
45
+ /** borrowed-mutable — the env-hand membership set the memory content-origin face reads (a tool that reads the execution env's files is a hand); writer here: `add("Artifact")` on the mount. */
46
+ envHandToolNames: Set<string>;
47
+ }
48
+ export interface PrepareArtifactResult {
49
+ /** owned — the mounted face's plane, or undefined when nothing mounted (absent seat, or an invalid one). */
50
+ artifactPlane: ArtifactPlane | undefined;
51
+ }
52
+ export declare function prepareArtifact(input: PrepareArtifactInput): PrepareArtifactResult;
@@ -0,0 +1,63 @@
1
+ import { readArtifactHostCapabilities } from "../artifact-host.js";
2
+ import { ARTIFACT_TOOL_NAME } from "../tool-catalog-entries.js";
3
+ import { deliverEngineNotice } from "../engine-notice.js";
4
+ import { RosterBuilder } from "../tool-roster.js";
5
+ import { defineTool } from "../tools.js";
6
+ import { inlineUntrusted } from "../untrusted-text.js";
7
+ import { physicalPathOfExisting } from "../physical-path.js";
8
+ import { resolveKey, violationDetails, violationText, withinAnyRoot } from "../../tools/fs/safety.js";
9
+ import { createArtifactTool } from "../../tools/artifact/artifact-tool.js";
10
+ export function prepareArtifact(input) {
11
+ const { deps, runId, taskId, sessionId, agentName, executionEnv, rootCanonical, additionalRootsCanonical, readDenyMatcher, resolvedReadFace, handsCwdRef, roster, firstPartyOffload, envHandToolNames } = input;
12
+ const host = deps.artifactHost;
13
+ if (host === undefined)
14
+ return { artifactPlane: undefined };
15
+ const refuse = (problem) => {
16
+ deliverEngineNotice(deps.onNotice, {
17
+ code: "config.artifact_host_invalid",
18
+ message: `RunnerDeps.artifactHost is wired but unusable, so no Artifact tool is mounted for this task: ${problem}`,
19
+ detail: { problem, sessionId, runId },
20
+ });
21
+ return { artifactPlane: undefined };
22
+ };
23
+ const verdict = readArtifactHostCapabilities(host);
24
+ if (!verdict.ok)
25
+ return refuse(verdict.problem);
26
+ const storageDir = verdict.capabilities.storageDir;
27
+ if (storageDir !== undefined && rootCanonical !== undefined) {
28
+ const real = physicalPathOfExisting(storageDir);
29
+ const inside = [rootCanonical, ...additionalRootsCanonical].find((root) => withinAnyRoot([root], real));
30
+ if (inside !== undefined)
31
+ return refuse(`the local artifact store ${storageDir} lies inside a root this run may reach through its file tools (${inside}); put the store outside every task root and additional directory`);
32
+ }
33
+ try {
34
+ host.onMount?.(runId);
35
+ }
36
+ catch (err) {
37
+ return refuse(`artifactHost.onMount threw: ${err instanceof Error ? err.message : String(err)}`);
38
+ }
39
+ const readLocalFile = async (path, maxBytes, signal) => {
40
+ if (rootCanonical === undefined)
41
+ return { ok: false, reason: "refused", text: "Error (Artifact): this task has no filesystem root, so a local file cannot be published." };
42
+ const rk = await resolveKey(executionEnv, rootCanonical, path, signal, handsCwdRef?.current, additionalRootsCanonical, undefined, readDenyMatcher, resolvedReadFace);
43
+ if (!rk.ok)
44
+ return { ok: false, reason: "refused", text: violationText("Artifact", rk.violation), details: violationDetails(rk.violation) };
45
+ const meta = await executionEnv.fileInfo(rk.key, signal);
46
+ if (!meta.ok)
47
+ return { ok: false, reason: "refused", text: `Error (Artifact): cannot stat ${inlineUntrusted(path, 200)} (${inlineUntrusted(meta.error.code, 40)}).` };
48
+ if (meta.value.kind !== "file")
49
+ return { ok: false, reason: "not_a_file", kind: meta.value.kind };
50
+ if (meta.value.size > maxBytes)
51
+ return { ok: false, reason: "too_large", size: meta.value.size, limit: maxBytes, judgedOn: "metadata" };
52
+ const r = await executionEnv.readBinaryFile(rk.key, signal);
53
+ if (!r.ok)
54
+ return { ok: false, reason: "refused", text: `Error (Artifact): cannot read ${inlineUntrusted(path, 200)} (${inlineUntrusted(r.error.code, 40)}).` };
55
+ if (r.value.byteLength > maxBytes)
56
+ return { ok: false, reason: "too_large", size: r.value.byteLength, limit: maxBytes, judgedOn: "bytes" };
57
+ return { ok: true, key: rk.key, bytes: r.value };
58
+ };
59
+ const artifactSpec = createArtifactTool({ host, capabilities: verdict.capabilities, runId, taskId, sessionId, ...(agentName !== undefined ? { agentName } : {}), readLocalFile });
60
+ roster.mount(firstPartyOffload(defineTool(artifactSpec)), { source: "builtin", mountedBy: "optional" });
61
+ envHandToolNames.add(ARTIFACT_TOOL_NAME);
62
+ return { artifactPlane: verdict.capabilities.plane };
63
+ }
@@ -37,10 +37,11 @@ import type { ReadFace } from "../../tools/fs/index.js";
37
37
  import type { InheritedGate, Prepared, RunInternals, RunnerSelfSeat, ToolFaceSnapshot } from "./contracts.js";
38
38
  import { type BlockedRef } from "./synthetic-tools.js";
39
39
  import { RosterBuilder } from "../tool-roster.js";
40
+ import type { ParentSpecSeat } from "../parent-spec-seat.js";
40
41
  export interface PrepareCapsAndWorkflowInput {
41
42
  /** borrowed-readonly — the REBOUND spec, whole: the roster map reads `tools`; the mounts read `enableBlockedReport`,
42
43
  * `enablePlanMode`, `interactiveTools`; the caps/compliance stations read `principal`, `selfOrchestration`,
43
- * `enableFork`, `getApiKeyAndHeaders`; the Workflow mount forwards `oneShot`, `handsReadOnly`, `interactiveTools`. The
44
+ * `enableFork`, `getApiKeyAndHeaders`; the Workflow mount forwards `handsReadOnly`, `interactiveTools`. The
44
45
  * self-orchestration predicate takes the whole spec. Never mutated. */
45
46
  spec: TaskSpec;
46
47
  /** borrowed-readonly — the deployment seats, whole: `runtimeCapsResolver`, `compliancePostureResolver`, `onError`, `onNotice`,
@@ -54,7 +55,7 @@ export interface PrepareCapsAndWorkflowInput {
54
55
  checkpointSeat: CheckpointSeat | undefined;
55
56
  /** borrowed-readonly — the trusted spawn-side channel; the Workflow mount forwards `rootSessionId`, `placementRoot`,
56
57
  * `onTaskNotification`, `workflowDepth`, and the capture-floor getter reads `memoryCaptureAncestors`. */
57
- internals: Pick<RunInternals, "rootSessionId" | "placementRoot" | "onTaskNotification" | "workflowDepth" | "memoryCaptureAncestors" | "workflowParkedResume"> | undefined;
58
+ internals: Pick<RunInternals, "rootSessionId" | "placementRoot" | "onTaskNotification" | "workflowDepth" | "memoryCaptureAncestors" | "workflowParkedResume" | "questionFaceStripped"> | undefined;
58
59
  /** borrowed-readonly — the acquired session; the classifier leg rebuilds its transcript window from `buildContext`. */
59
60
  session: Pick<StoredSession, "buildContext">;
60
61
  /** borrowed-readonly — the acquired session id (operator-line context, the refusal codes' phase record). */
@@ -88,6 +89,7 @@ export interface PrepareCapsAndWorkflowInput {
88
89
  };
89
90
  /** borrowed-readonly — the FROZEN approver seat (the Workflow mount's parentOnAsk). */
90
91
  frozenOnAsk: OnAsk | undefined;
92
+ /** borrowed-readonly — the parent SPEC seat the children inherit (minted once by the inherited-gate phase; the Workflow mount's dep, the same value the delegation ctx carries). */ parentSpecSeat: ParentSpecSeat;
91
93
  /** borrowed-readonly — the ONE read-face carrier expression (the Workflow mount's parentReadFace getter calls it). */
92
94
  carrierReadFace: () => ReadFace | undefined;
93
95
  /** borrowed-readonly — the filtered display-forwarding wrapper (the Workflow mount's forwardEvent; absent ⇒ no key). */
@@ -112,7 +112,7 @@ function autoModeArmReasonOf(intent, facePresent, caps, capsFaulted) {
112
112
  return { armed: true, reason: "armed" };
113
113
  }
114
114
  export async function prepareCapsAndWorkflow(input) {
115
- const { spec, deps, 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;
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;
116
116
  const roster = new RosterBuilder(toolEffects);
117
117
  for (const t of spec.tools ?? []) {
118
118
  const mounted = mountCallerTool(t);
@@ -312,8 +312,7 @@ export async function prepareCapsAndWorkflow(input) {
312
312
  return mine.length > 0 ? mine : undefined;
313
313
  } }
314
314
  : {}),
315
- principal: spec.principal,
316
- oneShot: spec.oneShot,
315
+ parentSpecSeat, ...(internals?.questionFaceStripped === true ? { parentQuestionFaceStripped: true } : {}),
317
316
  ...(resolvedInteractionPosture !== undefined ? { parentInteractionPosture: resolvedInteractionPosture } : {}),
318
317
  parentMemoryCaptureState: () => {
319
318
  const memoryEngineSession = input.memoryEngineSession();
@@ -22,7 +22,7 @@
22
22
  import { type BrainCallGuardrailRef } from "../../brain/timeout.js";
23
23
  import type { Model } from "../../internal/llm.js";
24
24
  import type { ThinkingLevel } from "../../internal/harness.js";
25
- import type { ContextEditMachine } from "../context-edit.js";
25
+ import { type MicroCompactMachine } from "../context-edit.js";
26
26
  import { type LockedPreflight } from "../locked-config.js";
27
27
  import { type ResolvedRole } from "../roles.js";
28
28
  import type { SessionStore } from "../session.js";
@@ -150,7 +150,7 @@ export interface PrepareConfigDoorsResult {
150
150
  * for the door + placement reasoning). The per-run state builder consumes exactly these values —
151
151
  * the deps bag is never re-read after this door. */
152
152
  microCompactKnob: {
153
- machine: "off" | ContextEditMachine;
153
+ machine: MicroCompactMachine;
154
154
  clearOnRejection: boolean;
155
155
  };
156
156
  /** owned — the resolved role; its `systemPrompt` seat is still read at prompt-input time. */
@@ -213,7 +213,7 @@ export interface PrepareConfigDoorsResult {
213
213
  * it must refuse exactly like a malformed field, never fold to the defaults. The RESOLVED values
214
214
  * returned here are immutable and are the state builder's ONLY source — nothing re-reads the bag. */
215
215
  export declare function resolveMicroCompactKnob(bag: RunnerDeps["microCompact"]): {
216
- machine: "off" | ContextEditMachine;
216
+ machine: MicroCompactMachine;
217
217
  clearOnRejection: boolean;
218
218
  };
219
219
  export declare function prepareConfigDoors(input: PrepareConfigDoorsInput): PrepareConfigDoorsResult;
@@ -1,5 +1,7 @@
1
1
  import { resolveBrainCallGuardrailMs } from "../../brain/timeout.js";
2
2
  import { assertReadFaceValue } from "../../tools/fs/index.js";
3
+ import { MICRO_COMPACT_MACHINES } from "../context-edit.js";
4
+ import { assertNoRetiredRunnerDeps } from "./retired-runner-deps.js";
3
5
  import { resolveCheckpointStore } from "../checkpoint-store.js";
4
6
  import { isModelPriced, malformedPricingField, modelCostToPricing } from "../pricing.js";
5
7
  import { preflightLockedConfig } from "../locked-config.js";
@@ -133,8 +135,8 @@ export function resolveMicroCompactKnob(bag) {
133
135
  throw microCompactConfigError(undefined, bag, "an object carrying optional machine/clearOnRejection keys");
134
136
  }
135
137
  const declaredMachine = bag?.machine;
136
- if (declaredMachine !== undefined && declaredMachine !== "off" && declaredMachine !== "legacy" && declaredMachine !== "cc") {
137
- throw microCompactConfigError("machine", declaredMachine, `"off" | "legacy" | "cc"`);
138
+ if (declaredMachine !== undefined && !MICRO_COMPACT_MACHINES.includes(declaredMachine)) {
139
+ throw microCompactConfigError("machine", declaredMachine, MICRO_COMPACT_MACHINES.map((m) => `"${m}"`).join(" | "));
138
140
  }
139
141
  return { machine: declaredMachine ?? "cc", clearOnRejection: resolveClearOnRejection(bag) };
140
142
  }
@@ -148,6 +150,7 @@ function resolveClearOnRejection(bag) {
148
150
  export function prepareConfigDoors(input) {
149
151
  const { deps, sessions, resume, internals } = input;
150
152
  let spec = input.spec;
153
+ assertNoRetiredRunnerDeps(deps);
151
154
  assertRestoreGatedToolsValue(spec.restoreGatedTools);
152
155
  const microCompactKnob = resolveMicroCompactKnob(deps.microCompact);
153
156
  const assertToolNameListValue = (value, seat) => {
@@ -17,7 +17,7 @@
17
17
  */
18
18
  import { type AgentHarness, type AgentMessage } from "../../internal/harness.js";
19
19
  import type { Model } from "../../internal/llm.js";
20
- import { type ContextEditMachine, type OccurrenceIndex } from "../context-edit.js";
20
+ import { type MicroCompactMachine, type OccurrenceIndex } from "../context-edit.js";
21
21
  import { type ToolResultStore } from "../tool-result-store.js";
22
22
  import { type RunnerDeps } from "../types.js";
23
23
  import type { Prepared, PreparedMicroCompact } from "./contracts.js";
@@ -39,7 +39,7 @@ export interface PrepareContextLaneInput {
39
39
  offloadStore: ToolResultStore | undefined;
40
40
  /** borrowed-readonly — the doors' RESOLVED microCompact knob (immutable values, never the deps bag). */
41
41
  microCompactKnob: {
42
- machine: "off" | ContextEditMachine;
42
+ machine: MicroCompactMachine;
43
43
  clearOnRejection: boolean;
44
44
  };
45
45
  /** borrowed-mutable — the built harness. Writer here: `on("context", …)` — the ONE registration of the request-build
@@ -44,14 +44,8 @@ export function prepareContextLane(input) {
44
44
  let ccPass;
45
45
  const edited = microCompact.machine === "off" ? mediaCapped : clearStaleToolResults(mediaCapped, {
46
46
  budgetTokens: editAt,
47
- machine: microCompact.machine,
48
- ...(microCompact.machine === "cc" || microCompact.clearOnRejection ? { recognizeCcMarkers: true } : {}),
49
- ...(microCompact.machine === "cc"
50
- ? {
51
- clearSource: replayed,
52
- onCleared: (pass) => { ccPass = pass; },
53
- }
54
- : {}),
47
+ clearSource: replayed,
48
+ onCleared: (pass) => { ccPass = pass; },
55
49
  anchoredTotalTokens: estimateContextTokens(mediaCapped, charsPerToken).tokens,
56
50
  charsPerToken,
57
51
  ...(offloadStore
@@ -193,15 +187,10 @@ export async function runGuardChain(args) {
193
187
  };
194
188
  let working = edited;
195
189
  let anchoredWorking = estimateContextTokens(working, charsPerToken).tokens;
196
- if (microCompact.machine === "legacy") {
197
- const t = applyGuardTrim(working, anchoredWorking);
198
- return { working, trimmed: t.trimmed, trimDroppedMessages: t.dropped };
199
- }
200
190
  if (microCompact.machine === "off" && anchoredWorking > guardAt) {
201
191
  let blockingPass;
202
192
  const blockingEdited = clearStaleToolResults(working, {
203
193
  budgetTokens: guardAt,
204
- machine: "cc",
205
194
  clearSource: args.replayed,
206
195
  onCleared: (pass) => { blockingPass = pass; },
207
196
  anchoredTotalTokens: anchoredWorking,
@@ -25,7 +25,7 @@ import type { FileHistoryBoundarySeat, RunInternals } from "./contracts.js";
25
25
  export interface PrepareFileHistoryInput {
26
26
  /** borrowed-readonly — the deployment seats this phase reads: the first-touch failure policy and the
27
27
  * history store (both validated HERE, once), the incident sink, and the retired-epoch diagnostic probe. */
28
- deps: Pick<RunnerDeps, "onTrackFailure" | "fileHistoryStore" | "onError" | "legacyRewindBoundaryProbe">;
28
+ deps: Pick<RunnerDeps, "onTrackFailure" | "fileHistoryStore" | "onError">;
29
29
  /** borrowed-readonly — the REBOUND spec's rewind request fields (the retired `rewindFiles` spelling is
30
30
  * read off the same object as an unknown key) and the caller's cancel signal for the restore. */
31
31
  spec: Pick<TaskSpec, "resumeAt" | "resumeAtMode" | "restoreFiles" | "rewindFilesTo" | "acceptPartialRestore" | "signal">;
@@ -63,7 +63,6 @@ export async function prepareFileHistory(input) {
63
63
  });
64
64
  }
65
65
  }
66
- const legacyEpochTail = (entryId, form = "at-or-above") => legacyRewindEpochTail(deps, sessionId, entryId, form);
67
66
  let restoredFilePaths = [];
68
67
  if (rewindTarget !== undefined && fileHistoryStore !== undefined && handsEnabled) {
69
68
  const historyStore = fileHistoryStore;
@@ -85,7 +84,7 @@ export async function prepareFileHistory(input) {
85
84
  }
86
85
  }
87
86
  if (anchor === undefined) {
88
- const e = new Error(`rewind-files: no file-history boundary exists at or above the "before" branch point on session "${sessionId}" — cannot restore files consistent with the rewound context. ${await legacyEpochTail(rewindTarget)}`);
87
+ const e = new Error(`rewind-files: no file-history boundary exists at or above the "before" branch point on session "${sessionId}" — cannot restore files consistent with the rewound context. ${rewindRefusalTail("at-or-above")}`);
89
88
  e.code = "rewind_snapshot.unresolvable";
90
89
  throw e;
91
90
  }
@@ -99,7 +98,7 @@ export async function prepareFileHistory(input) {
99
98
  if (restored.error?.code === "not_found") {
100
99
  const e = new Error(rewindBefore
101
100
  ? `rewind-files: the resolved "before" history anchor "${rewindTarget}" disappeared before restore — files were NOT rewound`
102
- : `rewind-files: no file-history boundary exists for entry "${rewindTarget}" on session "${sessionId}" — the working tree was NOT rewound. ${await legacyEpochTail(rewindTarget, "keyed")}`);
101
+ : `rewind-files: no file-history boundary exists for entry "${rewindTarget}" on session "${sessionId}" — the working tree was NOT rewound. ${rewindRefusalTail("keyed")}`);
103
102
  e.code = "rewind_snapshot.unresolvable";
104
103
  throw e;
105
104
  }
@@ -192,55 +191,7 @@ function resolveWiredFileHistoryStore(candidate) {
192
191
  }
193
192
  return candidate;
194
193
  }
195
- const LEGACY_PROBE_TIMEOUT_MS = 2_000;
196
- const LEGACY_PROBE_TIMED_OUT = Symbol("legacy-rewind-probe-timeout");
197
- async function legacyRewindEpochTail(deps, sessionId, entryId, form) {
198
- const probe = deps.legacyRewindBoundaryProbe;
199
- let verdict;
200
- if (probe !== undefined) {
201
- try {
202
- let timer;
203
- const timeout = new Promise((resolve) => {
204
- timer = setTimeout(() => resolve(LEGACY_PROBE_TIMED_OUT), LEGACY_PROBE_TIMEOUT_MS);
205
- });
206
- try {
207
- verdict = await Promise.race([Promise.resolve(probe({ sessionId, entryId })), timeout]);
208
- }
209
- finally {
210
- clearTimeout(timer);
211
- }
212
- if (verdict === LEGACY_PROBE_TIMED_OUT) {
213
- verdict = undefined;
214
- try {
215
- deps.onError?.(new Error(`RunnerDeps.legacyRewindBoundaryProbe did not answer within ${LEGACY_PROBE_TIMEOUT_MS}ms while explaining a rewind refusal — the refusal stands and names both possibilities`), { phase: "rewind", sessionId });
216
- }
217
- catch {
218
- }
219
- }
220
- }
221
- catch (err) {
222
- verdict = undefined;
223
- try {
224
- deps.onError?.(new Error(`RunnerDeps.legacyRewindBoundaryProbe threw while explaining a rewind refusal (${err.message}) — the refusal stands and names both possibilities`), { phase: "rewind", sessionId });
225
- }
226
- catch {
227
- }
228
- }
229
- if (verdict !== undefined && typeof verdict !== "boolean") {
230
- try {
231
- deps.onError?.(new Error(`RunnerDeps.legacyRewindBoundaryProbe answered ${JSON.stringify(String(verdict))} for entry "${entryId}" — only true/false/undefined are meaningful, so the refusal names both possibilities instead of trusting it`), { phase: "rewind", sessionId });
232
- }
233
- catch {
234
- }
235
- verdict = undefined;
236
- }
237
- }
238
- if (verdict === true) {
239
- return `This deployment's legacy-epoch probe reports that entry "${entryId}" DOES carry a boundary from the retired whole-tree snapshot era, which this engine's per-edited-file seat does not restore — the two epochs are not interchangeable (a whole-tree manifest carries no tracked-set provenance, so replaying it here would converge files this session never edited). Restore it with the whole-tree-era tooling this deployment kept, or re-run the turns under the current engine.`;
240
- }
241
- if (verdict === false) {
242
- return `This deployment's legacy-epoch probe reports no whole-tree-era boundary for entry "${entryId}" either, so the boundary simply does not exist: its turns ran without a RunnerDeps.fileHistoryStore wired, or it was reaped.`;
243
- }
194
+ function rewindRefusalTail(form) {
244
195
  return form === "keyed"
245
196
  ? `Boundaries are keyed by a turn's INITIAL entry and exist only for turns that ran with a RunnerDeps.fileHistoryStore wired; a boundary from the retired whole-tree snapshot era is not restorable by this seat, and reaped boundaries have none.`
246
197
  : `Either the boundary does not exist (its turns ran without a RunnerDeps.fileHistoryStore, or it was reaped), or it was produced in the retired whole-tree snapshot era, which this engine's per-edited-file seat does not restore.`;
@@ -162,13 +162,10 @@ export interface PrepareHandsReadFaceInput {
162
162
  * when no file restore ran. Fail-closed by construction — dropping a read record only ever
163
163
  * produces refusals. */
164
164
  restoredFilePaths?: readonly string[];
165
- /** borrowed-readonly — (ref cell; the REF itself is never replaced) memory write gate:
166
- * the band's beforeWrite closure reads `.current` call-time; prepareMemory (driver, BEFORE this phase
167
- * since the memory root must be known before the toolkit compiles) installs the gate — the one
168
- * writer; this phase only reads. Declared in the driver because the SAME ref feeds prepareMemory. */
169
- memoryWriteGateRef: {
170
- current?: BeforeWriteHook;
171
- };
165
+ /** borrowed-readonly — the memory write gate the memory phase minted (it runs BEFORE this phase: the memory root
166
+ * must be known before the toolkit compiles), composed into the band's beforeWrite below; undefined for a
167
+ * memory-less run (every write passes the engine half). */
168
+ memoryWriteGate: BeforeWriteHook | undefined;
172
169
  /** borrowed-mutable — the advertised-writable-directory seat (see its module header for the one
173
170
  * rule). Present on the hands leg only (a fence exists). The memory phase, which runs BEFORE this one,
174
171
  * has already admitted its write root through it; this phase admits the deployment's scratchpad and
@@ -123,7 +123,7 @@ export function resolveHandsLessReadFace(input) {
123
123
  }
124
124
  export async function prepareHandsMount(input) {
125
125
  const envHandToolNames = new Set();
126
- const { handsEnabled, executionEnv, taskRootFinal, rebaseRestoredPath, resume, session, sessionId, hostTaskId, taskScope, fullShellReachable, effectiveShellGate, toolFaceSnapshot, model, spec, deps, internals, memoryWriteGateRef, advertisedWritableDirs, firstPartyOffload, roster, egressTools, irreversibilityTier, irreversibleTools, reversibilityProbes } = input;
126
+ const { handsEnabled, executionEnv, taskRootFinal, rebaseRestoredPath, resume, session, sessionId, hostTaskId, taskScope, fullShellReachable, effectiveShellGate, toolFaceSnapshot, model, spec, deps, internals, memoryWriteGate, advertisedWritableDirs, firstPartyOffload, roster, egressTools, irreversibilityTier, irreversibleTools, reversibilityProbes } = input;
127
127
  assertStringArraySeat(spec.additionalDirectories, "TaskSpec.additionalDirectories", "strings");
128
128
  assertStringArraySeat(spec.additionalReadDirectories, "TaskSpec.additionalReadDirectories", "strings");
129
129
  assertStringArraySeat(deps.readDenyPatterns, "RunnerDeps.readDenyPatterns", "compiler-owned");
@@ -328,7 +328,7 @@ export async function prepareHandsMount(input) {
328
328
  if (verdict !== undefined && verdict.ok === false)
329
329
  return verdict;
330
330
  }
331
- return memoryWriteGateRef.current?.(w);
331
+ return memoryWriteGate?.(w);
332
332
  },
333
333
  mountBackgroundTaskTools: false,
334
334
  monitorToolActive: backgroundTaskToolsActive && !(toolFaceSnapshot.exclude?.includes("Monitor") ?? false),
@@ -354,6 +354,7 @@ export async function prepareHandsMount(input) {
354
354
  const shellReadBoundary = () => ({
355
355
  roots: [rootCanonical, ...additionalRootsCanonical, ...additionalReadRootsCanonical],
356
356
  ...(handsCwdRef?.current !== undefined ? { cwd: handsCwdRef.current } : {}),
357
+ ...(executionEnv.homeDir !== undefined ? { homeDir: executionEnv.homeDir } : {}),
357
358
  denyMatch: (p) => readDenyMatcher?.matchPath(p)?.pattern ?? null,
358
359
  ...(resolvedReadFace !== undefined ? { face: resolvedReadFace } : {}),
359
360
  });
@@ -5,7 +5,7 @@
5
5
  * freeze with its tighten-only intersection and the monotonic governed bit), the effective shellGate against the
6
6
  * spec, the three cells later phases fill for the children's chain (own session rules, own admitted org scopes,
7
7
  * own org verdict), the checkpoint-section closures the suspend serializer reads (face / org / F-012 stamps), the
8
- * FROZEN ask and question seats (spec over deps), the two delegation refs the memory phase binds late, the ONE
8
+ * FROZEN ask and question seats (spec over deps), the parent SPEC seat the children inherit, the two delegation refs the memory phase binds late, the ONE
9
9
  * child-chain assembly `inheritedGateForChildren`, the caller-tool ctx enrichment `enrichSpecToolCtx` shared by
10
10
  * the roster wrap and the revival spawner, the denial-limit stop seat, and the full-shell reachability predicate.
11
11
  * The interface is the dependency list the segment had implicitly.
@@ -36,6 +36,7 @@ import type { ReadFace } 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";
39
+ import { type ParentSpecSeat } from "../parent-spec-seat.js";
39
40
  import { type HookEnvCapabilities, type Hooks } from "../hooks.js";
40
41
  import type { LockedPreflight } from "../locked-config.js";
41
42
  import type { OwnOrgAdmissionVerdict } from "../memory-admission.js";
@@ -65,7 +66,7 @@ export interface PrepareInheritedGateInput {
65
66
  /** borrowed-readonly — the trusted spawn-side channel: `inheritedGate` (the LIVE half of the chain fold), `ownOrgAdmissionRef`
66
67
  * (the same-session continuation's freeze), `delegationProvenance` (the F-012 stamp), and the identity / peer / notification
67
68
  * seats the ctx enrichment forwards. Never mutated. */
68
- internals: Pick<RunInternals, "inheritedGate" | "ownOrgAdmissionRef" | "delegationProvenance" | "memoryCaptureAncestors" | "parentTaskId" | "parentSessionId" | "rootSessionId" | "placementRoot" | "explicitAgentName" | "peerSelfRef" | "peerInboundChainRef" | "onTaskNotification" | "onSubagentSpawn" | "insideFork"> | undefined;
69
+ internals: Pick<RunInternals, "inheritedGate" | "ownOrgAdmissionRef" | "delegationProvenance" | "memoryCaptureAncestors" | "parentTaskId" | "parentSessionId" | "rootSessionId" | "placementRoot" | "explicitAgentName" | "peerSelfRef" | "peerInboundChainRef" | "onTaskNotification" | "onSubagentSpawn" | "insideFork" | "questionFaceStripped"> | undefined;
69
70
  /** borrowed-readonly — the resume leg, or undefined on a fresh one: `seed.inheritedGate` is the CHECKPOINT half of the chain
70
71
  * fold (the trust floor the live half may only tighten), `seed.readFace` the hands-less re-park's carried section. */
71
72
  resume: Pick<PrepareResume, "seed"> | undefined;
@@ -240,6 +241,8 @@ export interface PrepareInheritedGateResult {
240
241
  notifyOwnHookCrash: (err: unknown) => void;
241
242
  /** owned — the FROZEN question seat (spec over deps), the content-ask twin of `frozenOnAsk`. */
242
243
  frozenOnQuestion: OnQuestion | undefined;
244
+ /** owned — the parent SPEC seat this run hands its children (`parentSpecSeatOf(spec, frozenOnQuestion)`): the ctx seat and the Workflow mount dep are this one value; both lanes apply it with `applyParentSpecSeat` (one law). */
245
+ parentSpecSeat: ParentSpecSeat;
243
246
  /** borrowed-mutable — the delegation runtime-provenance arming face. Writer: the memory-engine-session phase
244
247
  * (prepareMemoryEngineSession), once per leg when it arms. Read lazily by delegation tools through the ctx. */
245
248
  provenanceForChildrenRef: {