@sema-agent/core 7.16.0 → 7.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/CHANGELOG.md +173 -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-recall.d.ts +0 -8
  49. package/dist/core/memory.d.ts +1 -1
  50. package/dist/core/oracle-isolation.js +2 -2
  51. package/dist/core/parent-spec-seat.d.ts +66 -0
  52. package/dist/core/parent-spec-seat.js +47 -0
  53. package/dist/core/permission-rule-model.d.ts +11 -6
  54. package/dist/core/permission-rule-model.js +53 -37
  55. package/dist/core/permission-rules.js +8 -3
  56. package/dist/core/protocol-naming.d.ts +2 -2
  57. package/dist/core/remote-env.d.ts +1 -1
  58. package/dist/core/retired-keys.d.ts +19 -0
  59. package/dist/core/retired-keys.js +15 -0
  60. package/dist/core/runner/active-skill-scope.js +3 -3
  61. package/dist/core/runner/assemble-result.d.ts +15 -13
  62. package/dist/core/runner/assemble-result.js +5 -4
  63. package/dist/core/runner/compaction-call-options.d.ts +25 -89
  64. package/dist/core/runner/contracts.d.ts +10 -9
  65. package/dist/core/runner/gate-exit.js +15 -1
  66. package/dist/core/runner/halt-attribution.d.ts +35 -0
  67. package/dist/core/runner/halt-attribution.js +16 -0
  68. package/dist/core/runner/memory-consolidation.d.ts +0 -2
  69. package/dist/core/runner/memory-consolidation.js +0 -1
  70. package/dist/core/runner/prepare-artifact.d.ts +53 -0
  71. package/dist/core/runner/prepare-artifact.js +77 -0
  72. package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -2
  73. package/dist/core/runner/prepare-caps-and-workflow.js +2 -3
  74. package/dist/core/runner/prepare-config-doors.d.ts +3 -3
  75. package/dist/core/runner/prepare-config-doors.js +5 -2
  76. package/dist/core/runner/prepare-context-lane.d.ts +2 -2
  77. package/dist/core/runner/prepare-context-lane.js +2 -13
  78. package/dist/core/runner/prepare-file-history.d.ts +1 -1
  79. package/dist/core/runner/prepare-file-history.js +3 -52
  80. package/dist/core/runner/prepare-hands-readface.d.ts +4 -7
  81. package/dist/core/runner/prepare-hands-readface.js +3 -2
  82. package/dist/core/runner/prepare-inherited-gate.d.ts +5 -2
  83. package/dist/core/runner/prepare-inherited-gate.js +7 -8
  84. package/dist/core/runner/prepare-memory.d.ts +4 -6
  85. package/dist/core/runner/prepare-memory.js +4 -2
  86. package/dist/core/runner/prepare-policy-chain.d.ts +3 -3
  87. package/dist/core/runner/prepare-policy-chain.js +15 -30
  88. package/dist/core/runner/prepare-prompt-assembly.js +1 -1
  89. package/dist/core/runner/prepare-safety-scan.d.ts +1 -1
  90. package/dist/core/runner/prepare-suspend-saga.js +0 -2
  91. package/dist/core/runner/prepare-task.js +10 -9
  92. package/dist/core/runner/prepare-wiring-manifest.js +9 -7
  93. package/dist/core/runner/resume-admission.js +2 -1
  94. package/dist/core/runner/resume-internals-and-config.js +4 -0
  95. package/dist/core/runner/retired-runner-deps.d.ts +3 -0
  96. package/dist/core/runner/retired-runner-deps.js +7 -0
  97. package/dist/core/runner/run-harness-handlers.js +2 -3
  98. package/dist/core/runner/run-settle-and-teardown.d.ts +1 -1
  99. package/dist/core/runner/run-settle-and-teardown.js +3 -1
  100. package/dist/core/runner/session-file-state-replay.js +3 -3
  101. package/dist/core/runner/session-rule-policy.js +3 -4
  102. package/dist/core/runner/stream-halt-verbs.js +1 -1
  103. package/dist/core/runner/stream-steer-verb.js +1 -4
  104. package/dist/core/runner/workspace-path.js +3 -0
  105. package/dist/core/runner-deps.d.ts +30 -49
  106. package/dist/core/safe-notify.d.ts +1 -1
  107. package/dist/core/sensitive-path-policy.d.ts +1 -1
  108. package/dist/core/sensitive-path-policy.js +4 -4
  109. package/dist/core/session.d.ts +2 -2
  110. package/dist/core/skill-tool-specifier.d.ts +1 -1
  111. package/dist/core/skills-directory.d.ts +1 -1
  112. package/dist/core/store-contracts/checkpoint-store-contract.js +1 -1
  113. package/dist/core/strategy-store.d.ts +8 -11
  114. package/dist/core/task-event.d.ts +20 -17
  115. package/dist/core/task-limits.d.ts +1 -1
  116. package/dist/core/task-notification.d.ts +5 -5
  117. package/dist/core/task-registry-agent.d.ts +5 -5
  118. package/dist/core/task-registry-monitor.d.ts +2 -2
  119. package/dist/core/task-registry-shared.d.ts +27 -27
  120. package/dist/core/task-registry-shared.js +3 -13
  121. package/dist/core/task-registry.d.ts +8 -8
  122. package/dist/core/task-result.d.ts +37 -37
  123. package/dist/core/task-spec.d.ts +5 -5
  124. package/dist/core/task-stream.d.ts +16 -11
  125. package/dist/core/tool-catalog-entries.d.ts +1 -0
  126. package/dist/core/tool-catalog-entries.js +5 -0
  127. package/dist/core/tool-conformance.d.ts +0 -2
  128. package/dist/core/tool-conformance.js +1 -3
  129. package/dist/core/tool-errors.d.ts +5 -0
  130. package/dist/core/tool-errors.js +1 -0
  131. package/dist/core/tool-policy.d.ts +51 -22
  132. package/dist/core/tool-policy.js +28 -5
  133. package/dist/core/tool-result-budget.d.ts +1 -1
  134. package/dist/core/tool-result-store.d.ts +11 -11
  135. package/dist/core/tool-spec.d.ts +34 -38
  136. package/dist/core/tools.d.ts +2 -2
  137. package/dist/core/trace.d.ts +12 -15
  138. package/dist/core/untrusted-text.d.ts +1 -1
  139. package/dist/core/wiring-manifest.d.ts +4 -3
  140. package/dist/core/workflow-journal-store.d.ts +11 -11
  141. package/dist/engine/execution-env/node-execution-env.d.ts +3 -2
  142. package/dist/engine/execution-env/node-execution-env.js +4 -2
  143. package/dist/engine/harness/agent-harness.d.ts +47 -4
  144. package/dist/engine/harness/agent-harness.js +52 -9
  145. package/dist/engine/loop/agent-loop.js +15 -8
  146. package/dist/engine/loop/types.d.ts +5 -0
  147. package/dist/index.d.ts +19 -14
  148. package/dist/index.js +15 -10
  149. package/dist/internal/harness.d.ts +1 -0
  150. package/dist/orchestration/run-spec.d.ts +3 -0
  151. package/dist/orchestration/run-spec.js +3 -3
  152. package/dist/orchestration/run-workflow-tool.d.ts +12 -6
  153. package/dist/orchestration/run-workflow-tool.js +23 -5
  154. package/dist/orchestration/workflow-primitives.d.ts +4 -2
  155. package/dist/orchestration/workflow-primitives.js +1 -4
  156. package/dist/orchestration/workflow-types.d.ts +34 -4
  157. package/dist/orchestration/workflow-types.js +2 -0
  158. package/dist/orchestration/workflow.d.ts +39 -7
  159. package/dist/orchestration/workflow.js +234 -113
  160. package/dist/prompt-assembly/assemble.d.ts +2 -2
  161. package/dist/prompt-assembly/assemble.js +9 -9
  162. package/dist/prompt-assembly/composer.d.ts +6 -7
  163. package/dist/prompt-assembly/composer.js +10 -10
  164. package/dist/prompt-assembly/epoch.js +1 -1
  165. package/dist/prompt-assembly/event-registry.d.ts +0 -21
  166. package/dist/prompt-assembly/packs/sema-default.js +30 -30
  167. package/dist/prompt-assembly/types.d.ts +14 -14
  168. package/dist/prompts/coordinator.d.ts +0 -13
  169. package/dist/prompts/coordinator.js +0 -3
  170. package/dist/stores/file/checkpoint-store.js +1 -5
  171. package/dist/stores/file/file-history-store.js +2 -2
  172. package/dist/stores/file/permission-rule-store.d.ts +4 -35
  173. package/dist/stores/file/permission-rule-store.js +4 -148
  174. package/dist/tools/artifact/artifact-text.d.ts +62 -0
  175. package/dist/tools/artifact/artifact-text.js +68 -0
  176. package/dist/tools/artifact/artifact-tool.d.ts +52 -0
  177. package/dist/tools/artifact/artifact-tool.js +351 -0
  178. package/dist/tools/artifact/local-stub.d.ts +26 -0
  179. package/dist/tools/artifact/local-stub.js +296 -0
  180. package/dist/tools/fs/bash-readonly-classifier.d.ts +74 -8
  181. package/dist/tools/fs/bash-readonly-classifier.js +171 -60
  182. package/dist/tools/fs/fs-bash.d.ts +14 -10
  183. package/dist/tools/fs/fs-bash.js +19 -27
  184. package/dist/tools/fs/fs-search-tools.js +5 -2
  185. package/dist/tools/fs/fs-shared.d.ts +2 -0
  186. package/dist/tools/fs/fs-shared.js +3 -11
  187. package/dist/tools/fs/index.d.ts +4 -0
  188. package/dist/tools/fs/index.js +4 -1
  189. package/dist/tools/fs/safety.d.ts +117 -1
  190. package/dist/tools/fs/safety.js +131 -31
  191. package/dist/tools/fs/search.js +8 -6
  192. package/package.json +2 -1
  193. package/test/export-surface.snapshot.json +115 -39
@@ -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 } : {}; })() });
@@ -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,8 @@ 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
+ queue.push({ type: "turn_end", usage, ...(rs.turn.turnUsage === undefined || rs.turn.turnUsageMissing ? { usageMissing: true } : {}), ...stopExtra, ...ident() });
292
291
  rs.turn.turnUsageMissing = false;
293
292
  rs.turn.turnStopReason = undefined;
294
293
  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
  }
@@ -19,7 +19,7 @@ export function streamHaltVerbs(input) {
19
19
  if (receipt.accepted && !abortOwnedBeforeHalt) {
20
20
  h.loop.userHalted = true;
21
21
  }
22
- else {
22
+ else if (!h.loop.userHalted) {
23
23
  deliverEngineNotice(runner.deps.onNotice, {
24
24
  code: "task.halt_unconsumed",
25
25
  message: "a user halt arrived while the run was already ending for its own reason: nothing was cut or stopped " +
@@ -1,6 +1,6 @@
1
1
  import { uuidv7 } from "../../internal/harness.js";
2
2
  import { snapshotActorAssertion } from "../../internal/llm.js";
3
- import { LEGACY_PENDING_STEER_INPUT_ID, MAX_STEER_INPUT_ID_CHARS } from "../checkpoint-store.js";
3
+ import { MAX_STEER_INPUT_ID_CHARS } from "../checkpoint-store.js";
4
4
  import { formatHookFeedback, hookSeatExpiredError, runHookSeat } from "../hooks.js";
5
5
  import { buildHumanInputEvent, frameMidTurnUserInput, projectHumanInput } from "../human-input-projection.js";
6
6
  import { isSystemInjectionPriority, SYSTEM_INJECTION_PRIORITIES } from "../task-notification.js";
@@ -23,9 +23,6 @@ export function streamSteerVerb(input) {
23
23
  if (inputId === "" || inputId.length > MAX_STEER_INPUT_ID_CHARS) {
24
24
  throw steeringError(`inputId must be a non-empty string of at most ${MAX_STEER_INPUT_ID_CHARS} characters`, "steering.invalid_content");
25
25
  }
26
- if (inputId === LEGACY_PENDING_STEER_INPUT_ID) {
27
- throw steeringError(`inputId "${LEGACY_PENDING_STEER_INPUT_ID}" is reserved for a pre-queue parked steer and cannot be supplied by a caller`, "steering.invalid_content");
28
- }
29
26
  }
30
27
  const priorityIn = options?.priority;
31
28
  if (priorityIn !== undefined && !isSystemInjectionPriority(priorityIn)) {
@@ -1,6 +1,9 @@
1
+ import { isWinFormPath, pathFamilyOf } from "../../tools/fs/safety.js";
1
2
  export function rebaseWorkspacePath(p, fromRaw, toRaw) {
2
3
  if (p.includes("\\") || fromRaw.includes("\\") || toRaw.includes("\\"))
3
4
  return p;
5
+ if (pathFamilyOf({ root: fromRaw }) !== "posix" || pathFamilyOf({ root: toRaw }) !== "posix" || isWinFormPath(p))
6
+ return p;
4
7
  const stripTrail = (s) => (s.length > 1 && s.endsWith("/") ? stripTrail(s.slice(0, -1)) : s);
5
8
  const from = stripTrail(fromRaw);
6
9
  const to = stripTrail(toRaw);
@@ -187,7 +187,7 @@ export interface RunnerDeps {
187
187
  */
188
188
  readDenyPatterns?: readonly import("../tools/fs/read-deny.js").ReadDenyEntry[];
189
189
  /**
190
- * #245 (revises the D-4 zero-shrink ruling) — the deployment's built-in deny-table TIER selection:
190
+ * The deployment's built-in deny-table TIER selection:
191
191
  * EXACTLY the listed tiers of
192
192
  * {@link import("../tools/fs/read-deny.js").READ_FACE_BUILTIN_DENY_TABLE} are active (`[]` = none —
193
193
  * explicit and legal); absent = the default selection
@@ -203,7 +203,7 @@ export interface RunnerDeps {
203
203
  */
204
204
  readDenyBuiltinTiers?: readonly string[];
205
205
  /**
206
- * #245 — per-row removal from the built-in deny table by STABLE NAME (= the row's canonical
206
+ * Per-row removal from the built-in deny table by STABLE NAME (= the row's canonical
207
207
  * pattern text, e.g. `".ssh"`, `".config/gcloud"`; the admin delete channel), applied after
208
208
  * {@link readDenyBuiltinTiers} selection. Naming a row of an inactive tier is a satisfied intent
209
209
  * (no-op); a name matching NO table row refuses loudly at prepare (#123 — never silently
@@ -490,30 +490,6 @@ export interface RunnerDeps {
490
490
  * in its own audit surface. Values outside the two-member set refuse prepare loudly.
491
491
  */
492
492
  onTrackFailure?: "refuse" | "proceed-unprotected";
493
- /**
494
- * design/381 §5.3① / §10 S-1 — the ADDITIVE discriminant for the rewind refusal that this engine
495
- * cannot decide on its own. When a restore finds no boundary for the requested entry, there are
496
- * two very different worlds: the boundary NEVER existed (those turns ran with no history store, or
497
- * it was reaped), or it exists in the RETIRED whole-tree snapshot epoch, which the per-edited-file
498
- * seat deliberately does not restore. The engine holds no whole-tree state, so absent this probe
499
- * the refusal honestly names BOTH possibilities rather than picking one — the default, and the
500
- * only honest wording for a deployment that never had a whole-tree epoch at all.
501
- *
502
- * A deployment that DOES hold the retired epoch (its assembly layer can ask the old table whether
503
- * that entry has a legacy row) supplies the bit here, and the refusal splits into the two precise
504
- * messages. `true` = a whole-tree-era boundary exists for that entry; `false` = it definitively
505
- * does not; `undefined` = the deployment cannot tell, which reads exactly like an absent probe.
506
- *
507
- * Consulted ONLY on the refusal path (never on a successful restore), so a slow probe costs
508
- * nothing in the ordinary case. It is a DIAGNOSTIC input, never a control input: a probe that
509
- * throws, hangs past the caller's own patience, or answers a non-boolean cannot change the outcome
510
- * — the refusal still happens, the wording falls back to the honest both-possibilities form, and
511
- * the bad value is disclosed through `onError` (phase "rewind") rather than silently absorbed.
512
- */
513
- legacyRewindBoundaryProbe?: (query: {
514
- sessionId: string;
515
- entryId: string;
516
- }) => boolean | undefined | Promise<boolean | undefined>;
517
493
  /**
518
494
  * design/99 §E6 — DURABLE per-session permission rules (a deny-narrowing folded into the task-time tool
519
495
  * policy). Omitted ⇒ the feature is OFF (no rules read, zero behavior change). A deployment supplies a
@@ -713,7 +689,7 @@ export interface RunnerDeps {
713
689
  */
714
690
  denialLimit?: import("./auto-mode.js").AutoModeDenialLimitOptions;
715
691
  /**
716
- * #503 — OPT IN to recording this arming's serializable criteria (an
692
+ * OPT IN to recording this arming's serializable criteria (an
717
693
  * {@link import("./auto-mode-arming.js").AutoModeArmingRecipe}) on the constraint-chain entries a
718
694
  * durable park freezes, so a CROSS-PROCESS redemption can rebuild an equivalent classifier over its
719
695
  * own model leg ({@link import("./auto-mode-rebuild.js").rebuildAutoModeDecider}) instead of
@@ -733,7 +709,7 @@ export interface RunnerDeps {
733
709
  */
734
710
  persistArming?: boolean;
735
711
  /**
736
- * #503 — the deployment's auto-mode SETTINGS GENERATION (a config revision, a settings hash, a
712
+ * The deployment's auto-mode SETTINGS GENERATION (a config revision, a settings hash, a
737
713
  * publish id — the string is opaque to the engine). Recorded in the arming recipe and compared at
738
714
  * rebuild time against the REDEEMING deployment's own: it is a declaration axis that can only make
739
715
  * the rebuild stricter (see {@link import("./auto-mode-arming.js").foldAutoModeArming}). Absent =
@@ -947,7 +923,7 @@ export interface RunnerDeps {
947
923
  /** design/72 §2.2 (B): deployment default for the suspend-loop cap (see `TaskSpec.maxSuspends`).
948
924
  * Default 5 (loose — human-approval flows re-suspend rarely). A task's own `maxSuspends` overrides. */
949
925
  maxSuspends?: number;
950
- /** RB-458 (ruled 2026-08-03): deployment default for the outer per-brain-call guardrail — see
926
+ /** Deployment default for the outer per-brain-call guardrail — see
951
927
  * `TaskSpec.limits.brainCallGuardrailMs` for the semantics (default one hour; `false`/`0` = off;
952
928
  * an invalid value is a fail-loud config error). A task's own `limits.brainCallGuardrailMs`
953
929
  * overrides it. Set it here to cover EVERY run of this Runner, delegated children included — a
@@ -1156,6 +1132,18 @@ export interface RunnerDeps {
1156
1132
  * `undefined` for a file whose language has no server, and the tool degrades to "fall back to grep". A
1157
1133
  * task's own `lspManager` overrides this. Unset ⇒ no `lsp` tool. */
1158
1134
  lspManager?: import("./lsp.js").LspServerManager;
1135
+ /**
1136
+ * The Artifacts hosting seat — a storage/service-tier seam of the same family as
1137
+ * {@link checkpointStore} and {@link rosterStore}. Three states, judged once at prepare: ABSENT ⇒ no
1138
+ * `Artifact` tool and no announcement (the default: a library deployment has no hosting); a host
1139
+ * whose `capabilities()` satisfies the declaration invariants ⇒ ONE `Artifact` tool assembled from
1140
+ * that declaration (`plane: "hosted"` mounts the hosted face over the declared action subset,
1141
+ * `plane: "local-stub"` — `localArtifactStub({ dir })` — mounts the publish-only local face with
1142
+ * `eval-stub://` links); a host whose declaration fails an invariant ⇒ no tool and a loud
1143
+ * `config.artifact_host_invalid` notice. Filling the seat IS the authorization to publish: there is
1144
+ * no second switch. `TaskSpec.excludeTools` unmounts the tool as it does any other.
1145
+ */
1146
+ artifactHost?: import("./artifact-host.js").ArtifactHost;
1159
1147
  /**
1160
1148
  * Default in-process hooks for all tasks (design/37) — the FULL lifecycle seam of the `Hooks`
1161
1149
  * interface, not just the tool-call trio: `preToolUse` (rewrite/restrict args + inject context),
@@ -1309,27 +1297,20 @@ export interface RunnerDeps {
1309
1297
  */
1310
1298
  toolResultThresholdChars?: number;
1311
1299
  /**
1312
- * design/374 — microCompact machine-alignment knobs. Since the slice-3 default flip BOTH knobs
1313
- * default ON (`machine: "cc"`, `clearOnRejection: true`); the pre-374 behavior is the explicit
1314
- * opt-out `{ machine: "legacy", clearOnRejection: false }`.
1300
+ * design/374 — the microCompact knobs. Both default ON (`machine: "cc"`, `clearOnRejection: true`).
1315
1301
  *
1316
- * - `machine`: which stale-tool-result clearing machine the request pipeline runs —
1317
- * `"cc"` (default: the CC 2.1.223 rejection-leg form — keep 5, ≥20k minimum-savings gate, one
1318
- * deep clear beyond the keep window, CC marker bytes), `"legacy"` (the historical keep-3 /
1319
- * clear-to-budget machine, kept as the compatibility opt-out; it also keeps the pre-374
1320
- * backstop order guard trim as the ordinary second line), or `"off"` (no proactive frontier
1321
- * clearing at all the D-2 off switch; the unified machine then gets its ONE shot at the
1322
- * blocking point instead, the slice-3 arm A, trace trigger `"blocking"`). The pre-flip P-form
1323
- * warning on {@link import("./context-edit.js").ContextEditMachine} is resolved: the slice-3
1324
- * fallback re-ordering shipped with this default (blocking-point re-run → in-turn forced
1325
- * compaction behind the adopt seam → trim demoted to the disaster-only last resort), so an
1326
- * under-20k refusal no longer falls straight into a message-dropping trim.
1302
+ * - `machine`: `"cc"` (default: the CC 2.1.223 rejection-leg form keep 5, ≥20k minimum-savings
1303
+ * gate, one deep clear beyond the keep window, CC marker bytes) or `"off"` (no proactive
1304
+ * frontier clearing at all — the D-2 off switch; the machine then gets its ONE shot at the
1305
+ * blocking point instead, the slice-3 arm A, trace trigger `"blocking"`). The vocabulary is
1306
+ * {@link import("./context-edit.js").MICRO_COMPACT_MACHINES}; the guard chain behind either
1307
+ * value is the same (blocking-point run in-turn forced compaction behind the adopt seam
1308
+ * trim demoted to the disaster-only last resort), so an under-20k refusal never falls straight
1309
+ * into a message-dropping trim.
1327
1310
  * - `clearOnRejection` (MC-R, slice 2): on a provider input-too-long rejection, run ONE cheap
1328
- * deterministic clear over the rejected projection (same cc machine, savings ≥20k or nothing)
1329
- * and retry inside the turn BEFORE the forced-compaction recovery. Default true (flipped with
1330
- * the machine default in slice 3; X2's dark-landing clause is spent). Independent of
1331
- * `machine` — an enabled MC-R always clears in the cc form (the rejection arm has no
1332
- * budget coordinate for the legacy incremental form to stop at). BUDGET ACCOUNTING (design/374
1311
+ * deterministic clear over the rejected projection (the same machine, savings ≥20k or nothing)
1312
+ * and retry inside the turn BEFORE the forced-compaction recovery. Default true. Independent of
1313
+ * `machine` an enabled MC-R clears even with the frontier pass `"off"`. BUDGET ACCOUNTING (design/374
1333
1314
  * §3.2.1, stated here because it is otherwise invisible to a deployment): a successful MC-R
1334
1315
  * clear-and-retry SPENDS one attempt of the shared prompt-too-long recovery budget (default 2
1335
1316
  * attempts per chain), so a chain that clears and is rejected AGAIN has one forced-compaction
@@ -1344,7 +1325,7 @@ export interface RunnerDeps {
1344
1325
  * effect.
1345
1326
  */
1346
1327
  microCompact?: {
1347
- machine?: "off" | "legacy" | "cc";
1328
+ machine?: import("./context-edit.js").MicroCompactMachine;
1348
1329
  clearOnRejection?: boolean;
1349
1330
  };
1350
1331
  /**
@@ -1,5 +1,5 @@
1
1
  /**
2
- * RB-463 — host-callback isolation, one primitive.
2
+ * Host-callback isolation, one primitive.
3
3
  *
4
4
  * The invariant "a callback the HOST handed us must never take the engine down" was implemented by hand at
5
5
  * ~20 sites across 10 modules (observer taps, cascade/verify observability hooks, roster eviction sinks, the
@@ -60,7 +60,7 @@ export declare function dataRootSpellings(explicit: string | undefined): string[
60
60
  * `env` is the same ExecutionEnv the hands run against (inject the one you pass to the Runner), so
61
61
  * canonicalization sees the task's real filesystem (a remote/E2B task resolves in its container).
62
62
  * `rootPath` is the FALLBACK base a relative path resolves against (omit = paths resolve as given).
63
- * RB-108: when the engine stamps a live cwd onto the request (`ToolCallRequest.cwd` — the tracked
63
+ * When the engine stamps a live cwd onto the request (`ToolCallRequest.cwd` — the tracked
64
64
  * directory the shell's `cd` moves and the fs tools resolve with), THAT wins for the call's target, so
65
65
  * the guard judges the file the tool will actually write. `rootPath` still serves every deployment that
66
66
  * calls this policy outside a Runner.
@@ -1,6 +1,6 @@
1
1
  import { homedir } from "node:os";
2
2
  import { isAbsolute, join, resolve } from "node:path";
3
- import { canonicalizeTarget, expandHomeTilde, isAbsolutePathForm, isWinFormPath, writeTargetPath } from "../tools/fs/safety.js";
3
+ import { canonicalizeTarget, expandHomeTilde, isAbsoluteForFamily, isWinFormPath, pathFamilyOf, writeTargetPath } from "../tools/fs/safety.js";
4
4
  import { compileSegmentPattern, matchSegmentPatterns } from "../tools/fs/read-deny.js";
5
5
  import { isProtectedWrite, protectivePathTargetOf } from "./tool-registry.js";
6
6
  import { pathTargetBaseOf } from "./effective-path-target.js";
@@ -40,7 +40,7 @@ export function dataRootSpellings(explicit) {
40
40
  const engineForm = isAbsolute(raw) ? raw : resolve(raw);
41
41
  const out = [];
42
42
  for (const v of [raw, tilde, engineForm]) {
43
- const anchored = isAbsolutePathForm(v) ? v : resolve(v);
43
+ const anchored = isAbsoluteForFamily(pathFamilyOf({ root: v }), v) ? v : resolve(v);
44
44
  if (!out.includes(anchored))
45
45
  out.push(anchored);
46
46
  }
@@ -82,7 +82,7 @@ export function createSensitivePathPolicy(opts) {
82
82
  const keys = [];
83
83
  for (const spelling of dataRootPaths) {
84
84
  try {
85
- const r = await canonicalizeTarget(opts.env, spelling, signal);
85
+ const r = await canonicalizeTarget(opts.env, spelling, signal, pathFamilyOf({ root: spelling }));
86
86
  if (r.ok && !keys.includes(r.key))
87
87
  keys.push(r.key);
88
88
  }
@@ -102,7 +102,7 @@ export function createSensitivePathPolicy(opts) {
102
102
  const path = writeTargetPath(req, protective);
103
103
  if (typeof path !== "string" || path.length === 0)
104
104
  return { action: "allow" };
105
- const canon = await canonicalizeTarget(opts.env, path, signal, pathTargetBaseOf(protective, { root: opts.rootPath, cwd: req.cwd }));
105
+ const canon = await canonicalizeTarget(opts.env, path, signal, pathFamilyOf({ root: opts.rootPath, cwd: req.cwd ?? opts.env.cwd }), pathTargetBaseOf(protective, { root: opts.rootPath, cwd: req.cwd }));
106
106
  if (!canon.ok) {
107
107
  if (canon.unresolvedSymlink) {
108
108
  return {
@@ -74,7 +74,7 @@ export interface SessionStoreSummary {
74
74
  * Absent until a task has run on the session (recorded via {@link SessionStore.noteTaskRun}). */
75
75
  lastTaskId?: string;
76
76
  /**
77
- * #499 — an ENGINE-minted run identity for this session ({@link import("./types.js").TaskResult.runId}),
77
+ * An ENGINE-minted run identity for this session ({@link import("./types.js").TaskResult.runId}),
78
78
  * recorded by the most recent {@link SessionStore.noteTaskRun} write to LAND.
79
79
  * {@link lastTaskId} above is `spec.taskId ?? sessionId`, so on the ordinary deployment that supplies no
80
80
  * host task id it answers the SESSION's own id: a "last run" handle that names the session it hangs off
@@ -317,7 +317,7 @@ export declare function boundedTail(pathEntries: readonly SessionTreeEntry[]): {
317
317
  * times in one prepare (project-memory phase, design/148 center adoption, the epoch-pin
318
318
  * discriminator), and an entry-COUNT proxy at ANY of them re-creates the same lie: a host may write
319
319
  * session_info / model_change / … rows before the first turn (naming a new chat, recording a model
320
- * pick), and none of that is evidence of a prior session (5.29 merge-rescan: the count proxy
320
+ * pick), and none of that is evidence of a prior session (the count proxy
321
321
  * survived at the adoption arm after the pin arm was fixed, silently costing a pre-named session
322
322
  * its published center prompt for life). Conversation content — and only that — is the evidence.
323
323
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * RB-428-B — pattern specifiers inside a {@link import("./types.js").SkillManifest}'s `allowTools`.
2
+ * Pattern specifiers inside a {@link import("./types.js").SkillManifest}'s `allowTools`.
3
3
  *
4
4
  * An entry may be a bare tool name (`Bash`) or a name carrying a specifier (`Bash(git:*)`). A bare name
5
5
  * claims the whole tool; a specifier claims a NARROWER slice of it. Both directions of that sentence are
@@ -42,7 +42,7 @@ export type SkillsDirectoryWarningCode =
42
42
  | "name_mismatch"
43
43
  /** Required `description` absent. */
44
44
  | "missing_description"
45
- /** RB-428-A: an `allowed-tools` entry carries a pattern specifier (`Bash(git:*)`) — the scope policy
45
+ /** An `allowed-tools` entry carries a pattern specifier (`Bash(git:*)`) — the scope policy
46
46
  * matches names, not patterns, so the entry cannot match and the tool is DISABLED in this skill
47
47
  * frame. Disclosed loudly until pattern enforcement lands (the entry is deliberately NOT widened to
48
48
  * its base name: that would turn "only git" into "all of Bash"). */
@@ -185,7 +185,7 @@ export async function checkpointStoreContract(make, runAssertion) {
185
185
  assert.equal(q[0].text, "go");
186
186
  assert.equal(q[0].trusted, false);
187
187
  assert.equal(q[0].seq, 1);
188
- assert.equal((await store.get(cp.token)).state.pendingSteer, undefined);
188
+ assert.equal("pendingSteer" in (await store.get(cp.token)).state, false);
189
189
  });
190
190
  run("setPendingSteer APPENDS: two steers both survive in issue order, each keeping its frozen trust", async () => {
191
191
  const store = make();