@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
@@ -1,8 +1,9 @@
1
1
  import { forkGovernanceDenial, resolveDelegationEntryCaps } from "../../agents/subagent.js";
2
2
  import { isLiveQuestionFace } from "../ask-question.js";
3
+ import { parentSpecSeatOf } from "../parent-spec-seat.js";
3
4
  import { createPreToolUseConstraintPolicy } from "../hooks.js";
4
5
  import { StubExecutionEnv } from "../stub-env.js";
5
- import { askApproverIdentity, isLiveApproverSeat } from "../tool-policy.js";
6
+ import { askApproverIdentity, durableMandateSourceOf } from "../tool-policy.js";
6
7
  import { attachRebuiltDenialTrackers, createDenialLimitStop } from "./denial-limit-arms.js";
7
8
  export function prepareInheritedGate(input) {
8
9
  const { spec, deps, internals, resume, sessions, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, ownedEnv, lockedPreflight, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, autoModeSeat, handsEnabled, reportUsage, harnessRef, skillScope, forwardEvent, subagentRetain, worktreeIsolation, requestReview, requestStopAfterTurn, enterPlanMode, reminderMark, reminderDisclosureCounts, fileHistoryEnabled, historyScope, historyRoot, historyFs, abortRun } = input;
@@ -93,6 +94,7 @@ export function prepareInheritedGate(input) {
93
94
  }
94
95
  };
95
96
  const frozenOnQuestion = spec.onQuestion ?? deps.onQuestion;
97
+ const parentSpecSeat = parentSpecSeatOf(spec, frozenOnQuestion);
96
98
  const provenanceForChildrenRef = {};
97
99
  const delegationProvenanceForChildren = () => provenanceForChildrenRef.current;
98
100
  const delegationSettlementRef = {};
@@ -106,8 +108,7 @@ export function prepareInheritedGate(input) {
106
108
  ...(ownSessionRulesRef.current !== undefined ? [ownSessionRulesRef.current] : []),
107
109
  ];
108
110
  const ownCallerPolicy = lockedPreflight.toolPolicy;
109
- const durableMandate = input.runtimeCaps()?.forceDurableGate === true ||
110
- (spec.durableApproval !== undefined && !isLiveApproverSeat(frozenOnAsk));
111
+ const durableMandate = durableMandateSourceOf({ forceDurableGate: input.runtimeCaps()?.forceDurableGate === true, durableApprovalDeclared: spec.durableApproval !== undefined, onAsk: frozenOnAsk }) !== undefined;
111
112
  const contentMandate = input.runtimeCaps()?.forceDurableGate === true ||
112
113
  (spec.durableApproval !== undefined && !isLiveQuestionFace(frozenOnQuestion));
113
114
  const ownPreToolUse = input.preToolUseObservational() ? undefined : input.hooks()?.preToolUse;
@@ -186,15 +187,13 @@ export function prepareInheritedGate(input) {
186
187
  ...(readDenyAdditionsNormalized.length > 0 ? { readDenyPatterns: Object.freeze(readDenyAdditionsNormalized.map((e) => ({ ...e }))) } : {}),
187
188
  ...(spec.interactiveTools === false ? { interactiveTools: false } : {}),
188
189
  oneShot: spec.oneShot,
189
- clientContext: spec.clientContext,
190
+ parentSpecSeat,
191
+ ...(internals?.questionFaceStripped === true ? { questionFaceStripped: true } : {}),
190
192
  excludeTools: toolFaceSnapshot.exclude,
191
193
  deferTools: toolFaceSnapshot.defer,
192
194
  alwaysLoadTools: toolFaceSnapshot.alwaysLoad,
193
195
  ...(toolFaceSnapshot.restoreGated !== undefined ? { restoreGatedTools: toolFaceSnapshot.restoreGated } : {}),
194
196
  promptProfile,
195
- ...(spec.additionalDirectories !== undefined ? { additionalDirectories: Object.freeze([...spec.additionalDirectories]) } : {}),
196
- ...(spec.additionalReadDirectories !== undefined ? { additionalReadDirectories: Object.freeze([...spec.additionalReadDirectories]) } : {}),
197
- ...(spec.envFacts !== undefined ? { envFacts: { ...spec.envFacts } } : {}),
198
197
  ...(spec.memoryPersistenceCapable !== undefined ? { memoryPersistenceCapable: spec.memoryPersistenceCapable } : {}),
199
198
  get memoryCaptureOptedOut() {
200
199
  return input.memoryEngineSession()?.captureOptOut?.optedOut() ?? false;
@@ -263,5 +262,5 @@ export function prepareInheritedGate(input) {
263
262
  ...(spec.enablePlanMode === true ? { enterPlanMode } : {}),
264
263
  };
265
264
  };
266
- return { liveInheritedGate, seedInheritedGate, inheritedAncestorRules, inheritedShellGate, inheritedParentConstraints, autoModeIntent, inheritedAdmittedOrgScopes, priorOwnOrgVerdict, orgGovernedProvenance, effectiveShellGate, ownSessionRulesRef, memoryAdmittedOrgScopesRef, ownOrgVerdictRef, orgAdmissionCheckpointState, faceCheckpointSection, faceCheckpointState, f012CheckpointState, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, frozenOnQuestion, provenanceForChildrenRef, delegationSettlementRef, inheritedGateForChildren, carrierReadFace, fullShellReachable, gateStopRef, stopForDenialLimit, enrichSpecToolCtx };
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 };
267
266
  }
@@ -18,12 +18,6 @@ export interface PrepareMemoryInput {
18
18
  /** borrowed-readonly — the resolved task root: the containment boundary every derived memory dir
19
19
  * is checked against, and the repo-root fallback when `deps.rootPath` is unset. */
20
20
  taskRootPath: string;
21
- /** borrowed-mutable — the caller's write-gate cell, and THIS phase is its writer: it installs the
22
- * memory write gate into `.current` (the module header's one out-param effect). Identity is the
23
- * contract — the caller's hands/read-face seat reads this exact cell. */
24
- memoryWriteGateRef: {
25
- current?: BeforeWriteHook;
26
- };
27
21
  /**
28
22
  * borrowed-readonly — #181-F5: whether a tool NAMED `Write` (the tool the CC `# Memory` instruction names) is on the
29
23
  * ASSEMBLED roster this run, not excluded, AND can actually REACH the host-side memory root:
@@ -199,6 +193,10 @@ export interface MemoryInjectionComposition {
199
193
  export interface PrepareMemoryResult {
200
194
  memoryEngineSession: Prepared["memoryEngineSession"];
201
195
  memoryTools?: ToolSpec[];
196
+ /** owned — the memory WRITE gate (Write/Edit/NotebookEdit run their FINAL text through MemoryEngine.gateWrite when
197
+ * the target lands inside the memory writable root; the read-only plane refuses its own domain). Present iff a memory
198
+ * engine mounted; the hands phase composes it into the band's beforeWrite (a memory-less run: every write passes). */
199
+ memoryWriteGate?: BeforeWriteHook;
202
200
  /**
203
201
  * owned — #691: the injection's COMPOSITION step, run by the driver AFTER the hands phase (the mount above runs
204
202
  * BEFORE it). It performs the one destructive read this phase has — `inject()` drains the announcement queue —
@@ -13,7 +13,8 @@ import { FileMemoryEngineBackend } from "../memory-engine/file-backend.js";
13
13
  import { writableRootOf } from "../memory-engine/types.js";
14
14
  let undeclaredCapturePostureAnnounced = false;
15
15
  export async function prepareMemory(input) {
16
- const { spec, deps, sessionId, taskRootPath, memoryWriteGateRef, admissionCtx } = input;
16
+ const { spec, deps, sessionId, taskRootPath, admissionCtx } = input;
17
+ let memoryWriteGate;
17
18
  const memorySpec = normalizeMemorySpec(spec.memory);
18
19
  let admittedOrgScopes = [];
19
20
  let ownOrgVerdict;
@@ -473,7 +474,7 @@ export async function prepareMemory(input) {
473
474
  writeHandle.writeScope !== null &&
474
475
  foreignUnderRoot.length === 0 &&
475
476
  (input.advertisedWritableDirs === undefined || (await input.advertisedWritableDirs.admit({ path: writableRootOf(writeHandle), advertiser: "memory", expectCanonical: writableRootOf(writeHandle) })));
476
- memoryWriteGateRef.current = async (w) => {
477
+ memoryWriteGate = async (w) => {
477
478
  if (readOnlyEngine !== undefined && readOnlyHandle !== undefined) {
478
479
  const ro = readOnlyEngine.gateWrite(readOnlyHandle, w.key, w.content);
479
480
  if (!ro.ok)
@@ -776,6 +777,7 @@ export async function prepareMemory(input) {
776
777
  ownOrgVerdict,
777
778
  effectiveMemoryScopes,
778
779
  ...(memoryTools !== undefined ? { memoryTools } : {}),
780
+ ...(memoryWriteGate !== undefined ? { memoryWriteGate } : {}),
779
781
  composeInjection,
780
782
  };
781
783
  }
@@ -75,9 +75,9 @@ export interface PreparePolicyChainInput {
75
75
  irreversible?: false;
76
76
  egress?: false;
77
77
  }>;
78
- /** borrowed-readonly — the caller tools' names, the first-await snapshot (the sandbox boundary predicate: a caller tool
79
- * is never contained by the hand tools' env). */
80
- ownToolNames: ReadonlySet<string>;
78
+ /** borrowed-readonly — the ENGINE HAND roster: every tool whose effect dispatches through the task's execution env,
79
+ * membership minted at the mount sites (the sandbox boundary predicate's one containment fact). */
80
+ envHandToolNames: ReadonlySet<string>;
81
81
  /** borrowed-readonly — the shell-seat provenance set (the offer factory's `shellGated` mandate term, read per call). */
82
82
  shellGatedTools: ReadonlySet<string>;
83
83
  /** borrowed-readonly — the tracked-cwd seat, read per call by the lanes and the offer factory (never written here). */
@@ -1,11 +1,10 @@
1
- import { SEND_MESSAGE_TOOL_NAME } from "../../agents/send-message-tool.js";
2
1
  import { policyAskClassOf } from "../ask-class.js";
3
2
  import { ASK_USER_QUESTION_TOOL_NAME } from "../ask-question.js";
4
3
  import { resolveCheckpointStore } from "../checkpoint-store.js";
5
4
  import { cloneObserverInput, resolveHookTimeoutMs } from "../hooks.js";
6
5
  import { isNamespacedCoveringRuleName, namespacedRuleNameCovers } from "../permission-rules.js";
7
6
  import { isIsolated } from "../remote-env.js";
8
- import { askApproverIdentity, carryEngineAttestations, carryPrecedingSettlement, checkToolPolicyProjection, combinePolicies, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, isLiveApproverSeat, resolveAsk, toolPolicyNameSets } from "../tool-policy.js";
7
+ import { askApproverIdentity, carryEngineAttestations, carryPrecedingSettlement, checkToolPolicyProjection, combinePolicies, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, durableParkOptInDeclared, isLiveApproverSeat, resolveAsk, toolPolicyNameSets, attestAskUnresolvableCause } from "../tool-policy.js";
9
8
  import { RETIRED_TOOL_NAMES } from "../tool-name-aliases.js";
10
9
  import { NAMESPACED_NAME_SHAPES, protocolOf } from "../protocol-table.js";
11
10
  import { emitTrace } from "../trace.js";
@@ -16,7 +15,7 @@ import { askGrantShapeOf } from "./inherited-ask-grants.js";
16
15
  import { createPermissionRuleLanes, inheritedAskRuleEvidence, createRuleOffersOf } from "./permission-rule-lanes.js";
17
16
  import { createSessionRulePolicy } from "./session-rule-policy.js";
18
17
  export async function preparePolicyChain(input) {
19
- const { spec, deps, internals, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, lockedPreflight, tools, harnessTools, toolEffects, egressTools, irreversibilityTier, axisExplicitNegatives, ownToolNames, shellGatedTools, handsCwdRef, readDenyMatcher, questionToolMounted, runtimeCaps, inheritedAncestorRules, inheritedParentConstraints, seedInheritedGate, hasSkillManifest, skillScope, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, autoModeDecider, stopForDenialLimit, delegation, abortController, now, ownSessionRulesRef } = input;
18
+ const { spec, deps, internals, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, lockedPreflight, tools, harnessTools, toolEffects, egressTools, irreversibilityTier, axisExplicitNegatives, shellGatedTools, envHandToolNames, handsCwdRef, readDenyMatcher, questionToolMounted, runtimeCaps, inheritedAncestorRules, inheritedParentConstraints, seedInheritedGate, hasSkillManifest, skillScope, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, autoModeDecider, stopForDenialLimit, delegation, abortController, now, ownSessionRulesRef } = input;
20
19
  const policy = lockedPreflight.toolPolicy;
21
20
  const auditPolicyNames = (auditee) => {
22
21
  const nameGroups = toolPolicyNameSets(auditee);
@@ -144,6 +143,12 @@ export async function preparePolicyChain(input) {
144
143
  inheritedUnavailableAsks.add(toolCallId);
145
144
  return true;
146
145
  };
146
+ const mandatedCallFloats = (toolCallId) => resolveCheckpointStore(spec, deps) !== undefined && durableParkOptInDeclared({ forceDurableGate: runtimeCaps?.forceDurableGate === true, durableApprovalDeclared: spec.durableApproval !== undefined }) && markInheritedUnavailable(toolCallId);
147
+ const mandateUnreconstructibleDeny = (creq) => attestAskUnresolvableCause({
148
+ action: "deny",
149
+ message: `inherited parent policy requires durable approval for "${creq.toolName}" — the parent's durable ask cannot be reconstructed in a delegated child; denied fail-closed (tighten-only)`,
150
+ }, "mandate_unreconstructible", creq);
151
+ const markerChannelFullDeny = (creq) => attestAskUnresolvableCause({ action: "deny", message: `no approver is reachable for the inherited approval of "${creq.toolName}" and the marker channel is at capacity — denied fail-closed` }, "approver_unavailable", creq);
147
152
  const inheritedAskGrants = new Map();
148
153
  const recordInheritedAskGrant = (toolCallId, approver, presented, waitMs) => {
149
154
  if (typeof approver !== "function")
@@ -190,7 +195,7 @@ export async function preparePolicyChain(input) {
190
195
  list.push({ cls, layer });
191
196
  foldAskClasses.set(toolCallId, list);
192
197
  };
193
- const sandboxBoundaryCapable = (toolName) => egressTools.has(toolName) || toolName.includes("__") || ownToolNames.has(toolName) || toolName === SEND_MESSAGE_TOOL_NAME;
198
+ const sandboxBoundaryCapable = (toolName) => egressTools.has(toolName) || !envHandToolNames.has(toolName);
194
199
  const emitSandboxAdmitted = (info) => {
195
200
  emitTrace(deps.tracer, () => ({
196
201
  kind: "permission.sandbox_admitted",
@@ -358,16 +363,9 @@ export async function preparePolicyChain(input) {
358
363
  };
359
364
  }
360
365
  if (pc.durableMandate === true) {
361
- if (resolveCheckpointStore(spec, deps) !== undefined &&
362
- (spec.durableApproval !== undefined || runtimeCaps?.forceDurableGate === true) &&
363
- markInheritedUnavailable(creq.toolCallId)) {
366
+ if (mandatedCallFloats(creq.toolCallId))
364
367
  return first;
365
- }
366
- return {
367
- action: "deny",
368
- message: `inherited parent policy requires durable approval for "${creq.toolName}" — the parent's durable ` +
369
- `ask cannot be reconstructed in a delegated child; denied fail-closed (tighten-only)`,
370
- };
368
+ return mandateUnreconstructibleDeny(creq);
371
369
  }
372
370
  const judged = await judgeInheritedClassifier({ autoMode: pc.autoMode, ask: first, req: creq, signal: csignal ?? abortController.signal, subject: "this call" });
373
371
  if (judged.kind === "allow")
@@ -398,10 +396,7 @@ export async function preparePolicyChain(input) {
398
396
  if (resolved.action === "deny" && resolved.approverUnavailable === true) {
399
397
  if (markInheritedUnavailable(creq.toolCallId))
400
398
  return inheritedAsk;
401
- return {
402
- action: "deny",
403
- message: `no approver is reachable for the inherited approval of "${creq.toolName}" and the marker channel is at capacity — denied fail-closed`,
404
- };
399
+ return markerChannelFullDeny(creq);
405
400
  }
406
401
  settleDenialLimitFallback({ fallback, mintedHere, tracker: pc.autoMode?.denialTracking, resolved, headless: headlessDenyAtFold, stop: stopForDenialLimit, toolName: creq.toolName, toolCallId: creq.toolCallId });
407
402
  if (resolved.action !== "allow")
@@ -437,16 +432,9 @@ export async function preparePolicyChain(input) {
437
432
  };
438
433
  }
439
434
  if (pc.durableMandate === true) {
440
- if (resolveCheckpointStore(spec, deps) !== undefined &&
441
- (spec.durableApproval !== undefined || runtimeCaps?.forceDurableGate === true) &&
442
- markInheritedUnavailable(creq.toolCallId)) {
435
+ if (mandatedCallFloats(creq.toolCallId))
443
436
  return decision;
444
- }
445
- return {
446
- action: "deny",
447
- message: `inherited parent policy requires durable approval for "${creq.toolName}" — the parent's durable ` +
448
- `ask cannot be reconstructed in a delegated child; denied fail-closed (tighten-only)`,
449
- };
437
+ return mandateUnreconstructibleDeny(creq);
450
438
  }
451
439
  const presentedArgs = decision.updatedInput !== undefined ? decision.updatedInput : creq.args;
452
440
  const judged = await judgeInheritedClassifier({ autoMode: pc.autoMode, ask: decision, req: { ...creq, args: presentedArgs }, signal: csignal ?? abortController.signal, subject: "this call" });
@@ -477,10 +465,7 @@ export async function preparePolicyChain(input) {
477
465
  if (resolved.action === "deny" && resolved.approverUnavailable === true) {
478
466
  if (markInheritedUnavailable(creq.toolCallId))
479
467
  return inheritedAsk;
480
- return {
481
- action: "deny",
482
- message: `no approver is reachable for the inherited approval of "${creq.toolName}" and the marker channel is at capacity — denied fail-closed`,
483
- };
468
+ return markerChannelFullDeny(creq);
484
469
  }
485
470
  settleDenialLimitFallback({ fallback, mintedHere, tracker: pc.autoMode?.denialTracking, resolved, headless: headlessDenyAtFold, stop: stopForDenialLimit, toolName: creq.toolName, toolCallId: creq.toolCallId });
486
471
  if (resolved.action !== "allow")
@@ -95,7 +95,7 @@ export async function preparePromptAssembly(input) {
95
95
  }
96
96
  : undefined;
97
97
  const promptConstitution = assembled.constitution;
98
- const promptBlocks = assembled.legacyBlocks;
98
+ const promptBlocks = assembled.blocks;
99
99
  const collisionAudit = auditToolCollisions(tools);
100
100
  for (const y of collisionAudit.yields) {
101
101
  onceLedger.onError(new Error(`tool mount: caller tool "${y.name}" shadows another tool with the same canonical name (deliberate last-write-wins yield — the later mount serves)`), { phase: "config", sessionId });
@@ -1,5 +1,6 @@
1
1
  import { createAskUserQuestionTool, isLiveQuestionFace } from "../ask-question.js";
2
2
  import { boundInputHashOf } from "../canonical-json.js";
3
+ import { durableParkOptInDeclared } from "../tool-policy.js";
3
4
  import { CONTENT_ASK_BINDING_CAP } from "./content-ask-bindings.js";
4
5
  import { RosterBuilder } from "../tool-roster.js";
5
6
  export { CONTENT_ASK_BINDING_CAP } from "./content-ask-bindings.js";
@@ -41,7 +42,7 @@ export function prepareQuestionFace(input) {
41
42
  byDelivery.set(r.deliveryId, r);
42
43
  return [...byDelivery.values()];
43
44
  };
44
- const durableQuestionFace = questionParkStoreWired && (spec.durableApproval !== undefined || runtimeCaps?.forceDurableGate === true);
45
+ const durableQuestionFace = questionParkStoreWired && durableParkOptInDeclared({ forceDurableGate: runtimeCaps?.forceDurableGate === true, durableApprovalDeclared: spec.durableApproval !== undefined });
45
46
  const mountedQuestionFace = liveQuestionFace !== undefined
46
47
  ? async (req, signal) => {
47
48
  const bound = contentAskBindings.get(req.toolCallId);
@@ -12,7 +12,7 @@ export interface PrepareSafetyScanInput {
12
12
  * The scan's outputs. Ownership annotations (four-class form):
13
13
  *
14
14
  * `ownToolNames` is **borrowed-readonly** — a first-await snapshot with no mutation anywhere after
15
- * the scan; its one later read is the sandbox-boundary predicate's `.has()`.
15
+ * the scan (sandbox containment reads the env-hand roster, not this set).
16
16
  *
17
17
  * The six collections are **borrowed-mutable**: the driver and later phases fold into them IN
18
18
  * PLACE (identity is part of the contract — closures alias these exact objects). Mutation owners
@@ -106,7 +106,6 @@ export function prepareSuspendSaga(input) {
106
106
  activeTools: [...activeTools],
107
107
  outputRef: { value: outputRef.value, set: outputRef.set },
108
108
  nestedStats: { ...nestedStats },
109
- consolidationNotes: undefined,
110
109
  readFileState: readFileStateForCheckpoint ? [...readFileStateForCheckpoint.entries()] : undefined,
111
110
  readFace: faceCheckpointSection(),
112
111
  reminderMark,
@@ -121,7 +120,6 @@ export function prepareSuspendSaga(input) {
121
120
  .map((t) => ({ id: t.task_id, ...(t.description !== undefined ? { description: t.description } : {}) }));
122
121
  return live.length > 0 ? live : undefined;
123
122
  })(),
124
- pendingSteer: undefined,
125
123
  pendingSteerQueue: undefined,
126
124
  inheritedGate: (() => {
127
125
  const requiresParentConstraint = (inheritedParentConstraints?.length ?? 0) > 0 || seedInheritedGate?.requiresParentConstraint === true;
@@ -1,7 +1,7 @@
1
- import { realpathSync } from "node:fs";
2
1
  import { resolve as resolveFsPath } from "node:path";
3
2
  import { AgentHarness, DEFAULT_CLAMP_TOLERANCE, DEFAULT_COMPACTION_SETTINGS, summaryOutputBudgetTokens } from "../../internal/harness.js";
4
3
  import { sanitizeCompactionSettings } from "../auto-compaction.js";
4
+ import { physicalPathOf } from "../physical-path.js";
5
5
  import { projectStaleToolResults, resolveStaleToolResultOffload } from "./compaction-call-options.js";
6
6
  import { uuidv7 } from "../../engine/session/uuid.js";
7
7
  import { brainToRuntime } from "../runtime.js";
@@ -58,6 +58,7 @@ import { derivedRouteFallsBack } from "./derived-route-fallback.js";
58
58
  import { prepareProtocolTools } from "./prepare-protocol-tools.js";
59
59
  import { prepareQuestionFace } from "./prepare-question-face.js";
60
60
  import { prepareLsp } from "./prepare-lsp.js";
61
+ import { prepareArtifact } from "./prepare-artifact.js";
61
62
  import { createRollbackStack } from "./rollback-stack.js";
62
63
  export { __resetMaterializeEnvAnnouncements } from "./prepare-tool-disclosure-mount.js";
63
64
  export { fileHistoryFilesystemIdentity, resolveFileHistoryScope } from "./prepare-file-history.js";
@@ -70,6 +71,7 @@ import { createSchedulerTools } from "../../tools/scheduler-tools.js";
70
71
  import { selfOrchestrationFailClosedReason } from "../../orchestration/workflow-script-runner.js";
71
72
  import { remainingBudgetMicroUsd, resolveCheckpointStore } from "../checkpoint-store.js";
72
73
  import { checkpointSeatOf } from "../checkpoint-seat.js";
74
+ import { handsWriteHalfMounts } from "../../tools/fs/index.js";
73
75
  import { durableParkGapFor } from "../park-selfcheck.js";
74
76
  import { deliverEngineNotice } from "../types.js";
75
77
  import { ToolRosterDeltaSeat } from "../tool-roster.js";
@@ -264,7 +266,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
264
266
  const { toolFaceSnapshot, promptProfile, lockedPreflight, resolvedInteractionPosture, microCompactKnob, resolvedRole, model, thinking, compModel, fableMitigations, memoryDelegationEvidence, memoryProvenance, memoryCapturePolicy, usageWindows, brainCallGuardrailRef, brainCallGuardrailMs } = doors;
265
267
  announceToolModelGate(deps.onNotice, model.id, doors.modelGate);
266
268
  const modelGateManifest = toolModelGateManifest(doors.modelGate);
267
- const { toolEffects, egressTools, irreversibleTools, irreversibilityTier, axisExplicitNegatives, reversibilityProbes, ownToolNames } = prepareSafetyScan({ spec, deps });
269
+ const { toolEffects, egressTools, irreversibleTools, irreversibilityTier, axisExplicitNegatives, reversibilityProbes } = prepareSafetyScan({ spec, deps });
268
270
  let shellGatedTools = new Set();
269
271
  refuseRequireExistingWithoutSession(spec);
270
272
  const { acquired, session, conflictRef, wakeRecovered, resumeAtBeforeParentId } = await prepareAcquireReconcile({ sessions, spec, resume, toolEffects, ...(internals?.sessionPlacement !== undefined ? { placement: internals.sessionPlacement } : {}), ...(() => { const g = durableParkGapFor(deps, spec); return g !== undefined ? { durableParkGap: g } : {}; })() });
@@ -344,7 +346,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
344
346
  else {
345
347
  const canonical = (p, what) => {
346
348
  try {
347
- return realpathSync.native(resolveFsPath(p));
349
+ return physicalPathOf(resolveFsPath(p));
348
350
  }
349
351
  catch (err) {
350
352
  return failIsolation(`cannot canonicalize ${what} (${err instanceof Error ? err.message : String(err)}) — an unverifiable path is treated as not isolated`);
@@ -368,8 +370,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
368
370
  let resolvedReadFace;
369
371
  let readDenyAdditionsNormalized = [];
370
372
  let handsLessResolvedFace;
371
- const { liveInheritedGate, seedInheritedGate, inheritedAncestorRules, inheritedShellGate, inheritedParentConstraints, autoModeIntent, inheritedAdmittedOrgScopes, priorOwnOrgVerdict, orgGovernedProvenance, effectiveShellGate, ownSessionRulesRef, memoryAdmittedOrgScopesRef, ownOrgVerdictRef, orgAdmissionCheckpointState, faceCheckpointSection, faceCheckpointState, f012CheckpointState, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, frozenOnQuestion, provenanceForChildrenRef, delegationSettlementRef, inheritedGateForChildren, carrierReadFace, fullShellReachable, gateStopRef, stopForDenialLimit, enrichSpecToolCtx } = prepareInheritedGate({ spec, deps, internals, resume, sessions, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, ownedEnv, lockedPreflight, checkpointSeat, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, autoModeSeat, handsEnabled, reportUsage, harnessRef, skillScope, forwardEvent, subagentRetain, worktreeIsolation, requestReview, requestStopAfterTurn, enterPlanMode, reminderMark, reminderDisclosureCounts, fileHistoryEnabled, historyScope, historyRoot, historyFs, abortRun: () => { abortController.abort(); void harness.abort(); }, runtimeCaps: () => runtimeCaps, hooks: () => hooks, preToolUseObservational: () => preToolUseObservational, hookTimeoutMs: () => hookTimeoutMs, hookEnvFace: () => hookEnvFace, autoModeDecider: () => autoModeDecider, autoModeDenialTracking: () => autoModeDenialTracking, autoModeArming: () => autoModeArming, agentForkDenial: () => agentForkDenial, observersActive: () => observersActive, resolvedReadFace: () => resolvedReadFace, readDenyAdditionsNormalized: () => readDenyAdditionsNormalized, handsLessResolvedFace: () => handsLessResolvedFace, centerAdoption: () => centerAdoptionRef.current, memoryEngineSession: () => memoryEngineSession });
372
- const { roster, blockedRef, runtimeCaps, runtimeCapsFaulted, complianceDenies, complianceDegraded, agentForkDenial, observersActive, autoModeArmReason, autoModeDecider, autoModeDenialTracking, autoModeArming, selfOrchestrationActive, workflowToolsActive, workflowSizeGuideline } = await prepareCapsAndWorkflow({ spec, deps, internals, session, sessionId, runId, hostTaskId, taskScope, taskRootFinal, model, thinking, lockedPreflight, checkpointSeat, toolEffects, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, harnessRef, frozenOnAsk, carrierReadFace, forwardEvent, inheritedGateForChildren, enrichSpecToolCtx, maybeOffload, requestReview, enterPlanMode, autoModeIntent, peerLaneActive, peerSendMessageBuiltIn, runnerSelf, memoryEngineSession: () => memoryEngineSession, centerAdoption: () => centerAdoptionRef.current, readDenyAdditionsNormalized: () => readDenyAdditionsNormalized });
373
+ const { 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 } = prepareInheritedGate({ spec, deps, internals, resume, sessions, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, ownedEnv, lockedPreflight, checkpointSeat, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, autoModeSeat, handsEnabled, reportUsage, harnessRef, skillScope, forwardEvent, subagentRetain, worktreeIsolation, requestReview, requestStopAfterTurn, enterPlanMode, reminderMark, reminderDisclosureCounts, fileHistoryEnabled, historyScope, historyRoot, historyFs, abortRun: () => { abortController.abort(); void harness.abort(); }, runtimeCaps: () => runtimeCaps, hooks: () => hooks, preToolUseObservational: () => preToolUseObservational, hookTimeoutMs: () => hookTimeoutMs, hookEnvFace: () => hookEnvFace, autoModeDecider: () => autoModeDecider, autoModeDenialTracking: () => autoModeDenialTracking, autoModeArming: () => autoModeArming, agentForkDenial: () => agentForkDenial, observersActive: () => observersActive, resolvedReadFace: () => resolvedReadFace, readDenyAdditionsNormalized: () => readDenyAdditionsNormalized, handsLessResolvedFace: () => handsLessResolvedFace, centerAdoption: () => centerAdoptionRef.current, memoryEngineSession: () => memoryEngineSession });
374
+ const { roster, blockedRef, runtimeCaps, runtimeCapsFaulted, complianceDenies, complianceDegraded, agentForkDenial, observersActive, autoModeArmReason, autoModeDecider, autoModeDenialTracking, autoModeArming, selfOrchestrationActive, workflowToolsActive, workflowSizeGuideline } = await prepareCapsAndWorkflow({ spec, deps, internals, session, sessionId, runId, hostTaskId, taskScope, taskRootFinal, model, thinking, lockedPreflight, checkpointSeat, toolEffects, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, harnessRef, frozenOnAsk, parentSpecSeat, carrierReadFace, forwardEvent, inheritedGateForChildren, enrichSpecToolCtx, maybeOffload, requestReview, enterPlanMode, autoModeIntent, peerLaneActive, peerSendMessageBuiltIn, runnerSelf, memoryEngineSession: () => memoryEngineSession, centerAdoption: () => centerAdoptionRef.current, readDenyAdditionsNormalized: () => readDenyAdditionsNormalized });
373
375
  const tools = roster.tools;
374
376
  const pausedRef = {};
375
377
  const remoteEnvFailures = [];
@@ -432,11 +434,10 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
432
434
  const { mcp, a2a, rebuildHarnessToolsRef, contentOriginWrapRef, toolCallGateArmedRef } = await prepareProtocolTools({ lockedPreflight, spec, deps, reminderMark, reminderDisclosureCounts, runId, sessionId, onceLedger, roster, rosterSeat, remoteToolOffload, toolFaceSnapshot, toolEffects, axisExplicitNegatives, irreversibilityTier, irreversibleTools, egressTools, abortController, rollback });
433
435
  const callIssuedAtRef = {};
434
436
  const workCutRef = { continuation: false, abortedBeforeTurnEnd: false };
435
- const memoryWriteGateRef = {};
436
437
  const advertisedWritableDirs = handsEnabled
437
438
  ? new AdvertisedWritableDirs(executionEnv, (dir, reason) => deps.onError?.(new Error(`${dir.advertiser} advertised writable directory skipped (cannot canonicalize): ${dir.path} — ${reason}`), { phase: "config", sessionId }))
438
439
  : undefined;
439
- const handsWriteHalfPlanned = handsEnabled && spec.handsReadOnly !== true;
440
+ const handsWriteHalfPlanned = handsEnabled && handsWriteHalfMounts({ readOnly: spec.handsReadOnly === true });
440
441
  const memoryPairNameDomain = new Set();
441
442
  for (const t of tools) {
442
443
  memoryPairNameDomain.add(t.name);
@@ -455,12 +456,11 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
455
456
  return { fault: true };
456
457
  }
457
458
  })();
458
- const { memoryEngineSession, memoryTools, composeInjection: composeMemoryInjection, admittedOrgScopes: memoryAdmittedOrgScopes, ownOrgVerdict: memoryOwnOrgVerdict, effectiveMemoryScopes: memoryEffectiveScopes, } = await prepareMemory({
459
+ const { memoryEngineSession, memoryTools, memoryWriteGate, composeInjection: composeMemoryInjection, admittedOrgScopes: memoryAdmittedOrgScopes, ownOrgVerdict: memoryOwnOrgVerdict, effectiveMemoryScopes: memoryEffectiveScopes, } = await prepareMemory({
459
460
  spec,
460
461
  deps,
461
462
  sessionId,
462
463
  taskRootPath: taskRootFinal,
463
- memoryWriteGateRef,
464
464
  reminderMark,
465
465
  writeToolsMounted: (handsWriteHalfPlanned || tools.some((t) => t.name === "Write")) &&
466
466
  !(toolFaceSnapshot.exclude?.includes("Write") ?? false) &&
@@ -505,7 +505,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
505
505
  if (internals?.ownOrgAdmissionRef !== undefined && ownOrgVerdictRef.current !== undefined) {
506
506
  internals.ownOrgAdmissionRef.current = ownOrgVerdictRef.current;
507
507
  }
508
- const handsReadFaceInput = { handsEnabled, executionEnv, taskRootFinal, rebaseRestoredPath, resume, session, sessionId, hostTaskId, taskScope, fullShellReachable, effectiveShellGate, toolFaceSnapshot, model, spec, deps, internals, memoryWriteGateRef, ...(advertisedWritableDirs !== undefined ? { advertisedWritableDirs } : {}), firstPartyOffload, roster, egressTools, irreversibilityTier, irreversibleTools, reversibilityProbes, reminderMark, reminderDisclosureCounts, ...(trackFileEdit !== undefined ? { trackFileEdit } : {}), onFileEdited: noteFileEdited, ...(restoredFilePaths.length > 0 ? { restoredFilePaths } : {}) };
508
+ const handsReadFaceInput = { handsEnabled, executionEnv, taskRootFinal, rebaseRestoredPath, resume, session, sessionId, hostTaskId, taskScope, fullShellReachable, effectiveShellGate, toolFaceSnapshot, model, spec, deps, internals, memoryWriteGate, ...(advertisedWritableDirs !== undefined ? { advertisedWritableDirs } : {}), firstPartyOffload, roster, egressTools, irreversibilityTier, irreversibleTools, reversibilityProbes, reminderMark, reminderDisclosureCounts, ...(trackFileEdit !== undefined ? { trackFileEdit } : {}), onFileEdited: noteFileEdited, ...(restoredFilePaths.length > 0 ? { restoredFilePaths } : {}) };
509
509
  const handsReadFace = handsEnabled ? await prepareHandsMount(handsReadFaceInput) : resolveHandsLessReadFace(handsReadFaceInput);
510
510
  const { readFileStateForCheckpoint, seedContextFiles, handsCwdRef, workspaceStateSettle, wsSnapshot, rebaseWsPath, backgroundTaskToolsActive, additionalRootsCanonical, additionalReadRootsCanonical, attachmentRootCanonical, readDenyMatcher, envHandToolNames, sealReadStateSeat } = handsReadFace;
511
511
  resolvedReadFace = handsReadFace.resolvedReadFace;
@@ -532,6 +532,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
532
532
  `(per-principal entitlement governance, not a misconfiguration — Agent(subagent_type:"fork") will refuse honestly)`), { phase: "config", sessionId });
533
533
  }
534
534
  const { lspDiagnostics, nudgeLspOnEdit, lspRunIdent } = prepareLsp({ spec, deps, executionEnv, taskRootFinal, handsEnabled, sessionId, handsCwdRef, roster, envHandToolNames });
535
+ prepareArtifact({ deps, runId, taskId: hostTaskId, sessionId, agentName: internals?.explicitAgentName, executionEnv, rootCanonical: attachmentRootCanonical, additionalRootsCanonical: [...additionalRootsCanonical, ...additionalReadRootsCanonical], readDenyMatcher, resolvedReadFace, handsCwdRef, roster, firstPartyOffload, envHandToolNames });
535
536
  if (internals?.delegationProvenance !== undefined && resume !== undefined) {
536
537
  const live = internals.delegationProvenance.ref.current;
537
538
  const seed = resume.seed.delegationProvenance;
@@ -681,7 +682,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
681
682
  releaseSignal = () => sig.removeEventListener("abort", onExternalAbort);
682
683
  }
683
684
  }
684
- const { hooks, hookTimeoutMs, preToolUseObservational, ownGatePreToolUse, basePolicyForResumeEdit, denyNarrowingPolicy, policyLayers, effectivePolicy, inheritedUnavailableAsks, inheritedAskGrants, askSourceIdentity, riskAxesOf, ruleOffersOf, permissionRuleLane, permissionRuleOrgLane, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, foldAskClasses, ancestorSandboxAdmissions } = await preparePolicyChain({ spec, deps, internals, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, lockedPreflight, tools, harnessTools, toolEffects, egressTools, irreversibilityTier, axisExplicitNegatives, ownToolNames, shellGatedTools, handsCwdRef, readDenyMatcher, questionToolMounted, runtimeCaps, inheritedAncestorRules, inheritedParentConstraints, seedInheritedGate, hasSkillManifest, skillScope, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, autoModeDecider, stopForDenialLimit, delegation, abortController, now, ownSessionRulesRef });
685
+ const { hooks, hookTimeoutMs, preToolUseObservational, ownGatePreToolUse, basePolicyForResumeEdit, denyNarrowingPolicy, policyLayers, effectivePolicy, inheritedUnavailableAsks, inheritedAskGrants, askSourceIdentity, riskAxesOf, ruleOffersOf, permissionRuleLane, permissionRuleOrgLane, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, foldAskClasses, ancestorSandboxAdmissions } = await preparePolicyChain({ spec, deps, internals, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, lockedPreflight, tools, harnessTools, toolEffects, egressTools, irreversibilityTier, axisExplicitNegatives, shellGatedTools, envHandToolNames, handsCwdRef, readDenyMatcher, questionToolMounted, runtimeCaps, inheritedAncestorRules, inheritedParentConstraints, seedInheritedGate, hasSkillManifest, skillScope, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, autoModeDecider, stopForDenialLimit, delegation, abortController, now, ownSessionRulesRef });
685
686
  const { onAsk, preToolContexts, blockedToolCalls, gateOutcomes, batchHaltRef, blockedTracked, incompleteSuspendAdapter, durableSuspendInfraReady, resourceSuspendEligible, usageGovernance, durableApproval, wiringManifest, parkLaneArmed, gateMachineryActive, hookIdentity, hookEnvFace } = prepareWiringManifest({ spec, deps, internals, resume, sessionId, runId, hostTaskId, taskScope, hooks, preToolUseObservational, policyLayers, effectivePolicy, handsEnabled, mcp, irreversibleTools, egressTools, toolEffects, ownedEnv, executionEnv, offloadStore, onceLedger, usageWindows, envLifetimeSuspendAt, runtimeCaps, resolvedInteractionPosture, durableQuestionFace, liveQuestionFace, checkpointStore, sessions, modelGateManifest, autoModeArmReason, lockedPreflight, peerLaneRefs, peerLaneActive, harness, pausedRef, frozenOnAsk, liveInheritedGate, delegation, toolRoster });
686
687
  if (fileHistoryBoundary !== undefined) {
687
688
  harness.on("tool_call", async () => {
@@ -3,7 +3,7 @@ import { resolveCheckpointStore } from "../checkpoint-store.js";
3
3
  import { createHookEnvCapabilities, mintHookInvocationIdentity } from "../hooks.js";
4
4
  import { isRemoteExecutionEnv, missingRestoreSurface } from "../remote-env.js";
5
5
  import { isVolatileOffloadStore } from "../tool-result-store.js";
6
- import { isLiveApproverSeat } from "../tool-policy.js";
6
+ import { durableMandateSourceOf, isLiveApproverSeat } from "../tool-policy.js";
7
7
  import { deliverEngineNotice } from "../types.js";
8
8
  import { inlineUntrusted } from "../untrusted-text.js";
9
9
  import { GLOBAL_USAGE_KEY, usageRetryAfterMs, windowsGovernCost } from "../usage-window-store.js";
@@ -84,24 +84,26 @@ function buildUsageGovernance(windows, deps, principal, sessionId) {
84
84
  }
85
85
  const ungatedWarnedShapes = new WeakMap();
86
86
  function announceDurableGateUnavailable(args) {
87
- if (!args.forceDurableGate || args.storeWired)
87
+ if (args.mandate === undefined || args.storeWired)
88
88
  return;
89
+ const source = args.mandate;
89
90
  const cause = args.taskStoreDisabled ? "task_store_disabled" : "no_deployment_store";
90
91
  const inheritedContentMandate = (args.parentConstraints ?? []).some((pc) => pc.contentMandate === true);
91
92
  const liveQuestionFace = args.liveQuestionFace && !inheritedContentMandate;
92
93
  deliverEngineNotice(args.onNotice, {
93
94
  code: "config.durable_gate_unavailable",
94
- message: `Durable approval gate mandated but unavailable: the runtime entitlement forceDurableGate is in force for this ` +
95
- `principal, but ${cause === "task_store_disabled" ? 'this task disabled the checkpoint store (checkpointStore: "disabled")' : "this deployment wired no checkpoint store"}, ` +
95
+ message: `Durable approval gate mandated but unavailable: ${source === "force_durable_gate" ? "the runtime entitlement forceDurableGate is in force for this principal" : "this task opted into durable approval (durableApproval) with no live approver seat"}, ` +
96
+ `but ${cause === "task_store_disabled" ? 'this task disabled the checkpoint store (checkpointStore: "disabled")' : "this deployment wired no checkpoint store"}, ` +
96
97
  `so no interactive ask of this task can be parked for a durable answer. Asks resolve on the live chain instead ` +
97
- `(${args.liveApprover ? "a live approver answers permission asks in-stream" : "permission asks are denied fail-closed — no live approver is wired"}; ` +
98
+ `(${args.liveApprover ? "a live approver answers permission asks in-stream" : args.blanketAllow ? 'a blanket "allow" posture answers ordinary permission asks without judgment (a mandated ask is refused)' : "permission asks are denied fail-closed — no live approver is wired"}; ` +
98
99
  `${liveQuestionFace ? "a live question face answers AskUserQuestion in-stream" : args.liveQuestionFace ? "AskUserQuestion is withheld from the live face by an inherited durable mandate and refused" : "AskUserQuestion has no live face and is refused"}) ` +
99
- `and NO durable approval record is written. ${cause === "task_store_disabled" ? "Run this task with its store, or withdraw the entitlement for principals whose runs are machine-started." : "Wire a checkpoint store on this deployment, or withdraw the entitlement for principals served here."}`,
100
+ `and NO durable approval record is written. ${cause === "task_store_disabled" ? "Run this task with its store" : "Wire a checkpoint store on this deployment"}, or ${source === "force_durable_gate" ? "withdraw the entitlement for principals served here" : "give the task a live approver seat (or drop durableApproval)"}.`,
100
101
  detail: {
101
102
  sessionId: args.sessionId,
102
103
  runId: args.runId,
103
104
  ...(args.principal ? { principal: args.principal } : {}),
104
105
  cause,
106
+ source,
105
107
  liveApprover: args.liveApprover,
106
108
  liveQuestionFace,
107
109
  },
@@ -225,7 +227,7 @@ export function prepareWiringManifest(input) {
225
227
  resourceSuspendEligible ||
226
228
  platformSuspendArmed ||
227
229
  spec.enablePlanMode === true;
228
- announceDurableGateUnavailable({ onNotice: deps.onNotice, forceDurableGate: runtimeCaps?.forceDurableGate === true, storeWired: checkpointStore !== undefined, taskStoreDisabled: spec.checkpointStore === "disabled", liveApprover: isLiveApproverSeat(frozenOnAsk), liveQuestionFace: liveQuestionFace !== undefined, parentConstraints: liveInheritedGate?.parentConstraints, sessionId, runId, principal: spec.principal });
230
+ announceDurableGateUnavailable({ onNotice: deps.onNotice, mandate: durableMandateSourceOf({ forceDurableGate: runtimeCaps?.forceDurableGate === true, durableApprovalDeclared: spec.durableApproval !== undefined, onAsk: frozenOnAsk }), storeWired: checkpointStore !== undefined, taskStoreDisabled: spec.checkpointStore === "disabled", liveApprover: isLiveApproverSeat(frozenOnAsk), blanketAllow: frozenOnAsk === "allow", liveQuestionFace: liveQuestionFace !== undefined, parentConstraints: liveInheritedGate?.parentConstraints, sessionId, runId, principal: spec.principal });
229
231
  const hookIdentity = mintHookInvocationIdentity({
230
232
  sessionId,
231
233
  taskId: hostTaskId,
@@ -36,6 +36,7 @@ export async function resumeAdmission(input) {
36
36
  const outcomeGate = outcome.gate;
37
37
  const resumeSignal = config.signal;
38
38
  let suppliedMessage;
39
+ const parkedSteers = readPendingSteerQueue(cp.state);
39
40
  if (outcomeGate === "wake") {
40
41
  suppliedMessage = outcome.message;
41
42
  const gk = cp.gate.kind;
@@ -74,7 +75,7 @@ export async function resumeAdmission(input) {
74
75
  if (suppliedMessage !== undefined) {
75
76
  wakeMessage = validatePendingSteer(suppliedMessage);
76
77
  }
77
- else if (readPendingSteerQueue(cp.state).length === 0) {
78
+ else if (parkedSteers.length === 0) {
78
79
  throw new CheckpointError("wake.nothing_to_deliver", "cannot wake: no message was supplied and the checkpoint holds no parked pendingSteer — an empty " +
79
80
  "wake would burn the checkpoint on a blank continuation; supply `message` or park a steer first");
80
81
  }
@@ -1,9 +1,13 @@
1
1
  import { CheckpointError, readPendingSteerQueue } from "../checkpoint-store.js";
2
2
  import { GLOBAL_USAGE_KEY, resolveUsageWindows, usageRetryAfterMs } from "../usage-window-store.js";
3
3
  import { placementValueOrAbsent } from "./checkpoint-scope.js";
4
+ import { assertNoRetiredRunnerDeps } from "./retired-runner-deps.js";
5
+ import { resolveMicroCompactKnob } from "./prepare-config-doors.js";
4
6
  export function resumeInternalsAndConfig(input) {
5
7
  const { cp, config, internals, outcomeGate, runner } = input;
6
8
  let windowEntrance;
9
+ assertNoRetiredRunnerDeps(runner.deps);
10
+ resolveMicroCompactKnob(runner.deps.microCompact);
7
11
  if (runner.deps.lockedConfig?.keys?.includes("toolPolicy") === true && config.basePolicyForResumeEdit !== undefined) {
8
12
  throw new CheckpointError("checkpoint.invalid_outcome", "TaskSpec.basePolicyForResumeEdit is administratively locked by this deployment (locked key \"toolPolicy\") — a task-supplied " +
9
13
  "resume-edit policy is refused pre-CAS (the checkpoint stays pending); remove the field or change the deployment's lock configuration");
@@ -0,0 +1,3 @@
1
+ import type { RunnerDeps } from "../types.js";
2
+ /** Runs in the pre-first-await config-doors phase, before any resource-bearing preparation, and pre-CAS on a resume. */
3
+ export declare function assertNoRetiredRunnerDeps(deps: RunnerDeps): void;
@@ -0,0 +1,7 @@
1
+ import { assertNoRetiredKeys } from "../retired-keys.js";
2
+ const RETIRED_RUNNER_DEPS_KEYS = {
3
+ legacyRewindBoundaryProbe: "the rewind refusal always names both possibilities (never existed / retired whole-tree era); a deployment that holds that era's table answers the question on its own surface",
4
+ };
5
+ export function assertNoRetiredRunnerDeps(deps) {
6
+ assertNoRetiredKeys(deps, RETIRED_RUNNER_DEPS_KEYS, "RunnerDeps");
7
+ }
@@ -286,9 +286,11 @@ export function createHarnessHandlers(input) {
286
286
  if (prepared.abortController.signal.aborted)
287
287
  prepared.workCutRef.abortedBeforeTurnEnd = true;
288
288
  const stopExtra = rs.turn.turnStopReason !== undefined ? { stopReason: rs.turn.turnStopReason } : {};
289
- queue.push(rs.turn.turnUsage
290
- ? { type: "turn_end", usage: rs.turn.turnUsage, ...(rs.turn.turnUsageMissing ? { usageMissing: true } : {}), ...stopExtra, ...ident() }
291
- : { type: "turn_end", usageMissing: true, ...stopExtra, ...ident() });
289
+ const usage = rs.turn.turnUsage ?? { inputTokens: 0, totalInputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, costMicroUsd: 0 };
290
+ const turnUsageUnknown = rs.turn.turnUsage === undefined || rs.turn.turnUsageMissing;
291
+ if (turnUsageUnknown)
292
+ stats.usageMissing = true;
293
+ queue.push({ type: "turn_end", usage, ...(turnUsageUnknown ? { usageMissing: true } : {}), ...stopExtra, ...ident() });
292
294
  rs.turn.turnUsageMissing = false;
293
295
  rs.turn.turnStopReason = undefined;
294
296
  if (parentToolCallId !== undefined) {
@@ -51,7 +51,7 @@ export interface RunSettleAndTeardownInput {
51
51
  parentToolCallId: string | undefined;
52
52
  /** borrowed-readonly — the sub-agent display name for the terminal tick. */
53
53
  subagentName: string | undefined;
54
- /** borrowed-readonly — the loop-liveness latch: `userHalted` signs the result's halt seat. */
54
+ /** borrowed-readonly — the loop-liveness latch: `userHalted` = the halt verb was ACCEPTED (the unconsumed-halt notice's input; it does not sign the seat). */
55
55
  loopLatch: {
56
56
  readonly userHalted: boolean;
57
57
  };
@@ -11,6 +11,7 @@ import { defaultTaskRegistry } from "../task-registry.js";
11
11
  import { emitTrace } from "../trace.js";
12
12
  import { deliverEngineNotice } from "../types.js";
13
13
  import { assembleResult } from "./assemble-result.js";
14
+ import { discloseUnconsumedHalt } from "./halt-attribution.js";
14
15
  import { TIMER_LATENESS_REPORT_MS } from "./clock-and-limits.js";
15
16
  import { resumeDecisionWasNegative } from "./decide-continuation.js";
16
17
  import { drainForwardedFramesBeforeDone, forwardsSubagentEvents } from "./prepare-run-refs.js";
@@ -102,7 +103,7 @@ export async function runSettleAndTeardown(input) {
102
103
  rewindNotes: prepared.rewindNotes,
103
104
  editedFiles: prepared.editedFilesSnapshot(),
104
105
  haltedOnUserRejection: prepared.batchHaltRef.current !== undefined,
105
- userHalted: loopLatch.userHalted,
106
+ haltConsumption: prepared.harness.haltConsumption(),
106
107
  strandedHumanAnswers,
107
108
  remoteEnvFailures: prepared.remoteEnvFailures,
108
109
  effectiveReadFace: prepared.effectiveReadFace,
@@ -136,6 +137,7 @@ export async function runSettleAndTeardown(input) {
136
137
  },
137
138
  });
138
139
  }
140
+ discloseUnconsumedHalt({ accepted: loopLatch.userHalted, consumption: prepared.harness.haltConsumption(), onNotice: runner.deps.onNotice, sessionId: prepared.sessionId, runId: rs.telemetry.runId, taskId: spec.taskId });
139
141
  if (resume !== undefined &&
140
142
  resume.pendingActionStarted !== true &&
141
143
  result.terminal.kind !== "completed" &&
@@ -1,4 +1,4 @@
1
- import { isAbsolutePathForm } from "../../tools/fs/safety.js";
1
+ import { isAbsoluteForFamily, pathFamilyOf } from "../../tools/fs/safety.js";
2
2
  import { applyRecordedEdit } from "../../tools/fs/fs-shared.js";
3
3
  import { normalizeFileText } from "../../tools/fs/encoding.js";
4
4
  import { isOffloadedDetailReplacement } from "../tool-result-store.js";
@@ -75,7 +75,7 @@ export function wholeFileRecordsFromTranscript(messages) {
75
75
  if (typeof changed !== "string")
76
76
  continue;
77
77
  const replayed = replayedEditContent(rest);
78
- if (replayed !== undefined && isAbsolutePathForm(changed)) {
78
+ if (replayed !== undefined && isAbsoluteForFamily(pathFamilyOf({ root: changed }), changed)) {
79
79
  byPath.set(changed, { path: changed, content: replayed, at: m.timestamp });
80
80
  }
81
81
  else {
@@ -97,7 +97,7 @@ export function wholeFileRecordsFromTranscript(messages) {
97
97
  const nested = rest.file;
98
98
  const holder = isRead ? (typeof nested === "object" && nested !== null ? nested : undefined) : rest;
99
99
  const filePath = holder?.filePath;
100
- if (typeof filePath !== "string" || !isAbsolutePathForm(filePath))
100
+ if (typeof filePath !== "string" || !isAbsoluteForFamily(pathFamilyOf({ root: filePath }), filePath))
101
101
  continue;
102
102
  const content = isRead ? wholeFileFromReadCard(rest) : typeof rest.content === "string" ? rest.content : undefined;
103
103
  if (content === undefined)
@@ -1,5 +1,4 @@
1
- import { canonicalizeTarget, writeTargetPath } from "../../tools/fs/safety.js";
2
- import { isWinFormPath } from "../../tools/fs/safety.js";
1
+ import { canonicalizeTarget, isWinFormPath, pathFamilyOf, writeTargetPath } from "../../tools/fs/safety.js";
3
2
  import { createCoarseCommandNamePolicy, namespacedCoveringEntries, namespacedCoveringHit } from "../tool-policy.js";
4
3
  import { declaredPathTargetOf, isProtectedWrite, pathConfinableWriteToolNames, skillScopeWriteToolNames } from "../tool-registry.js";
5
4
  import { pathTargetBaseOf } from "../effective-path-target.js";
@@ -66,13 +65,13 @@ export function createSessionRulePolicy(rules, opts) {
66
65
  if (typeof path !== "string" || path.length === 0) {
67
66
  return deny(`write tool "${req.toolName}" denied: session rule confines writes to allowDirs but the call has no resolvable path`);
68
67
  }
69
- const canon = await canonicalizeTarget(env, path, signal, pathTargetBaseOf(declared, { root: rootPath, cwd: req.cwd }));
68
+ const canon = await canonicalizeTarget(env, path, signal, pathFamilyOf({ root: rootPath, cwd: req.cwd ?? env.cwd }), pathTargetBaseOf(declared, { root: rootPath, cwd: req.cwd }));
70
69
  if (!canon.ok) {
71
70
  return deny(`write to "${path}" denied: its real target could not be resolved against the session-rule allowDirs`);
72
71
  }
73
72
  const roots = [];
74
73
  for (const raw of allowDirs) {
75
- const r = await canonicalizeTarget(env, raw, signal, rootPath);
74
+ const r = await canonicalizeTarget(env, raw, signal, pathFamilyOf({ root: rootPath ?? env.cwd }), rootPath);
76
75
  if (r.ok)
77
76
  roots.push(r.key);
78
77
  }