@sema-agent/core 7.4.0 → 7.5.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 (115) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/dist/agents/cascade.d.ts +2 -1
  3. package/dist/agents/peer-notices.d.ts +11 -1
  4. package/dist/agents/peer-session-drain.js +2 -0
  5. package/dist/agents/verify.d.ts +2 -1
  6. package/dist/core/ask-origin.d.ts +130 -0
  7. package/dist/core/ask-origin.js +35 -0
  8. package/dist/core/auto-mode-arming.d.ts +40 -1
  9. package/dist/core/auto-mode-arming.js +51 -3
  10. package/dist/core/auto-mode.d.ts +87 -10
  11. package/dist/core/auto-mode.js +34 -6
  12. package/dist/core/checkpoint-store.d.ts +41 -6
  13. package/dist/core/checkpoint-store.js +8 -0
  14. package/dist/core/hooks.d.ts +63 -19
  15. package/dist/core/hooks.js +37 -16
  16. package/dist/core/mcp.d.ts +47 -55
  17. package/dist/core/mcp.js +89 -31
  18. package/dist/core/park-selfcheck.js +3 -0
  19. package/dist/core/permission-rule-consent.d.ts +2 -11
  20. package/dist/core/permission-rule-consent.js +13 -62
  21. package/dist/core/permission-rule-org.d.ts +70 -54
  22. package/dist/core/permission-rule-org.js +47 -61
  23. package/dist/core/permission-rule-provider.d.ts +162 -0
  24. package/dist/core/permission-rule-provider.js +148 -0
  25. package/dist/core/permission-rule-session.d.ts +23 -19
  26. package/dist/core/permission-rule-session.js +5 -5
  27. package/dist/core/permission-rule-store.d.ts +46 -21
  28. package/dist/core/permission-rule-store.js +13 -6
  29. package/dist/core/permission-rule-sync.d.ts +2 -1
  30. package/dist/core/permission-rule-sync.js +11 -0
  31. package/dist/core/runner/assemble-result.d.ts +3 -2
  32. package/dist/core/runner/checkpoint-scope.d.ts +32 -0
  33. package/dist/core/runner/checkpoint-scope.js +4 -0
  34. package/dist/core/runner/contracts.d.ts +1878 -0
  35. package/dist/core/runner/contracts.js +1 -0
  36. package/dist/core/runner/denial-limit-arms.d.ts +57 -31
  37. package/dist/core/runner/denial-limit-arms.js +42 -17
  38. package/dist/core/runner/derived-route-fallback.d.ts +34 -0
  39. package/dist/core/runner/derived-route-fallback.js +16 -0
  40. package/dist/core/runner/prepare-acquire-reconcile.d.ts +1 -1
  41. package/dist/core/runner/prepare-announce-once.d.ts +83 -0
  42. package/dist/core/runner/prepare-announce-once.js +105 -0
  43. package/dist/core/runner/prepare-caps-and-workflow.d.ts +170 -0
  44. package/dist/core/runner/prepare-caps-and-workflow.js +255 -0
  45. package/dist/core/runner/prepare-config-doors.d.ts +2 -10
  46. package/dist/core/runner/prepare-defer-classify.d.ts +86 -0
  47. package/dist/core/runner/prepare-defer-classify.js +107 -0
  48. package/dist/core/runner/prepare-delegation-surface.d.ts +104 -0
  49. package/dist/core/runner/prepare-delegation-surface.js +144 -0
  50. package/dist/core/runner/prepare-execution-env.d.ts +54 -0
  51. package/dist/core/runner/prepare-execution-env.js +86 -0
  52. package/dist/core/runner/prepare-file-history.d.ts +95 -0
  53. package/dist/core/runner/prepare-file-history.js +383 -0
  54. package/dist/core/runner/prepare-hands-readface.d.ts +6 -8
  55. package/dist/core/runner/prepare-hands-readface.js +3 -3
  56. package/dist/core/runner/prepare-inherited-gate.d.ts +268 -0
  57. package/dist/core/runner/prepare-inherited-gate.js +266 -0
  58. package/dist/core/runner/prepare-listings.d.ts +77 -0
  59. package/dist/core/runner/prepare-listings.js +76 -0
  60. package/dist/core/runner/prepare-lsp.d.ts +55 -0
  61. package/dist/core/runner/prepare-lsp.js +27 -0
  62. package/dist/core/runner/prepare-memory.d.ts +1 -1
  63. package/dist/core/runner/prepare-offload-wrappers.d.ts +62 -0
  64. package/dist/core/runner/prepare-offload-wrappers.js +45 -0
  65. package/dist/core/runner/prepare-permission-rules.d.ts +132 -0
  66. package/dist/core/runner/prepare-permission-rules.js +140 -0
  67. package/dist/core/runner/prepare-project-context.d.ts +131 -0
  68. package/dist/core/runner/prepare-project-context.js +150 -0
  69. package/dist/core/runner/prepare-prompt-inputs.d.ts +138 -0
  70. package/dist/core/runner/prepare-prompt-inputs.js +141 -0
  71. package/dist/core/runner/prepare-protocol-tools.d.ts +91 -0
  72. package/dist/core/runner/prepare-protocol-tools.js +182 -0
  73. package/dist/core/runner/prepare-question-face.d.ts +119 -0
  74. package/dist/core/runner/prepare-question-face.js +83 -0
  75. package/dist/core/runner/prepare-run-refs.d.ts +89 -0
  76. package/dist/core/runner/prepare-run-refs.js +39 -0
  77. package/dist/core/runner/prepare-safety-scan.d.ts +3 -2
  78. package/dist/core/runner/prepare-task.d.ts +11 -1815
  79. package/dist/core/runner/prepare-task.js +138 -2542
  80. package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +111 -0
  81. package/dist/core/runner/prepare-tool-disclosure-mount.js +219 -0
  82. package/dist/core/runner/prepare-wiring-manifest.d.ts +184 -0
  83. package/dist/core/runner/prepare-wiring-manifest.js +240 -0
  84. package/dist/core/runner/prepare-workspace-restore.d.ts +1 -27
  85. package/dist/core/runner/prepare-workspace-restore.js +1 -22
  86. package/dist/core/runner/rollback-stack.d.ts +32 -0
  87. package/dist/core/runner/rollback-stack.js +30 -0
  88. package/dist/core/runner/runtask.d.ts +11 -2
  89. package/dist/core/runner/runtask.js +27 -9
  90. package/dist/core/runner/workspace-path.d.ts +33 -0
  91. package/dist/core/runner/workspace-path.js +22 -0
  92. package/dist/core/sensitive-path-policy.d.ts +16 -0
  93. package/dist/core/sensitive-path-policy.js +1 -1
  94. package/dist/core/tool-policy.d.ts +57 -9
  95. package/dist/core/tool-policy.js +11 -0
  96. package/dist/core/types.d.ts +63 -51
  97. package/dist/core/wiring-manifest.d.ts +40 -3
  98. package/dist/core/wiring-manifest.js +4 -3
  99. package/dist/core/write-protect.d.ts +13 -2
  100. package/dist/core/write-protect.js +58 -29
  101. package/dist/engine/harness/types.d.ts +38 -16
  102. package/dist/engine/harness/types.js +25 -1
  103. package/dist/engine/session/session.d.ts +3 -11
  104. package/dist/index.d.ts +10 -6
  105. package/dist/index.js +9 -5
  106. package/dist/internal/harness.d.ts +1 -0
  107. package/dist/stores/file/adoption/adopt.d.ts +1 -1
  108. package/dist/stores/file/adoption/marker.d.ts +1 -1
  109. package/dist/stores/file/permission-rule-adopt.js +4 -3
  110. package/dist/stores/file/permission-rule-store.d.ts +65 -25
  111. package/dist/stores/file/permission-rule-store.js +215 -37
  112. package/dist/stores/file/task-list-store.d.ts +1 -1
  113. package/dist/tools/fs/read-face.d.ts +1 -1
  114. package/package.json +8 -2
  115. package/test/export-surface.snapshot.json +76 -28
@@ -1,75 +1,78 @@
1
1
  import { createHash, randomBytes } from "node:crypto";
2
2
  import { realpathSync } from "node:fs";
3
3
  import { resolve as resolveFsPath } from "node:path";
4
- import { AgentHarness, DEFAULT_CHARS_PER_TOKEN, DEFAULT_CLAMP_TOLERANCE, DEFAULT_COMPACTION_SETTINGS, estimateContextTokens, isSyntheticApiErrorMessage, readCompactionActiveTools, summaryOutputBudgetTokens } from "../../internal/harness.js";
4
+ import { AgentHarness, DEFAULT_CHARS_PER_TOKEN, DEFAULT_CLAMP_TOLERANCE, DEFAULT_COMPACTION_SETTINGS, estimateContextTokens, summaryOutputBudgetTokens } from "../../internal/harness.js";
5
5
  const PROMPT_HASH_SALT = randomBytes(16);
6
6
  import { sanitizeCompactionSettings } from "../auto-compaction.js";
7
7
  import { projectStaleToolResults, resolveStaleToolResultOffload } from "./compaction-call-options.js";
8
- import { createAutoModeDecider, createAutoModeDenialTracker } from "../auto-mode.js";
9
- import { attachRebuiltDenialTrackers, createDenialLimitStop, headlessDenyAtFold, headlessDenyAtRecheck, judgeInheritedClassifier, settleDenialLimitFallback } from "./denial-limit-arms.js";
10
- import { autoModeArmingRecipeOf } from "../auto-mode-arming.js";
11
- import { buildAutoModePrompt, renderAutoModeAction, renderAutoModeWindow } from "../auto-mode-prompt.js";
12
- import { resolveTaskModel } from "../roles.js";
8
+ import { gateAskCarry, inheritedAskCarry, headlessDenyAtFold, headlessDenyAtRecheck, judgeInheritedClassifier, settleDenialLimitFallback } from "./denial-limit-arms.js";
13
9
  import { primaryActivityArg } from "../arg-summary.js";
14
- import { materializeMcpTools } from "../mcp.js";
15
- import { materializeA2aTools } from "../a2a.js";
16
- import { Type } from "typebox";
17
10
  import { Value } from "typebox/value";
18
11
  import { uuidv7 } from "../../engine/session/uuid.js";
19
12
  import { brainToRuntime } from "../runtime.js";
20
13
  import { hasSessionFork } from "../session.js";
21
- import { createSubagentWorktreeHelper, forkGovernanceDenial, resolveDelegationEntryCaps } from "../../agents/subagent.js";
22
- import { createAgentTranscriptTool, AGENT_TRANSCRIPT_TOOL_NAME } from "../../agents/agent-transcript-tool.js";
23
- import { createSendMessageTool, SEND_MESSAGE_TOOL_NAME } from "../../agents/send-message-tool.js";
24
- import { SubagentRetainLedger } from "../../agents/retain-ledger.js";
25
- import { askApproverIdentity, carriesBidiControls, checkToolPolicyProjection, combinePolicies, constraintChainDigest, constraintChainEntryOfLayer, isApprovalSettledBy, isAskDenyResolution, screenApproverAttribution, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, describeThrown, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets, tryCloneArgs } from "../tool-policy.js";
26
- const PERSISTED_RULE_TOOL = "Bash";
27
- const DIRECTORY_RULE_TOOL = "Read";
28
- import { directoryRuleAdmits, eligiblePersisted, findAdmittingRule, lexicalNormalAbsolutePathOf, segmentCoverageOf, suggestRulesForCommand } from "../permission-rule-model.js";
29
- import { normalizePersistedRule } from "../permission-rule-store.js";
30
- import { ActiveSkillScope, createActiveSkillScopePolicy } from "./active-skill-scope.js";
31
- import { CHANGED_FILES_MTIME_EPS_MS, fenceMcpServerInstructions, renderAgentListingDelta } from "./turn-attachments.js";
14
+ import { SEND_MESSAGE_TOOL_NAME } from "../../agents/send-message-tool.js";
15
+ import { askApproverIdentity, isLiveApproverSeat, carriesBidiControls, checkToolPolicyProjection, combinePolicies, constraintChainDigest, constraintChainEntryOfLayer, isApprovalSettledBy, isAskDenyResolution, screenApproverAttribution, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, describeThrown, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets, tryCloneArgs } from "../tool-policy.js";
16
+ import { suggestRulesForCommand } from "../permission-rule-model.js";
17
+ import { PERSISTED_RULE_TOOL, inheritedAskRuleEvidence, createPermissionRuleLanes } from "./prepare-permission-rules.js";
18
+ import { createActiveSkillScopePolicy } from "./active-skill-scope.js";
19
+ import { CHANGED_FILES_MTIME_EPS_MS } from "./turn-attachments.js";
32
20
  import { inlineUntrusted } from "../untrusted-text.js";
21
+ import { loadAnnounceOnceLedger, serializeAnnouncedListings } from "./prepare-announce-once.js";
33
22
  import { isValidReminderMark, mintReminderMark, reminderMarkDeclaration } from "../reminder-mint.js";
34
23
  import { policyAskClassOf } from "../ask-class.js";
35
24
  import { emitTrace } from "../trace.js";
36
25
  import { createSessionRulePolicy, PATH_CONFINABLE_WRITE_TOOLS } from "./session-rule-policy.js";
37
- import { cloneObserverInput, createHookEnvCapabilities, createPreToolUseConstraintPolicy, formatHookFeedback, mintHookInvocationIdentity, persistedRuleMandateOf, resolveHookTimeoutMs, runHookSeat, hookSeatExpiredError, runToolGate } from "../hooks.js";
26
+ import { askCarryRowMembers, cloneObserverInput, formatHookFeedback, persistedRuleMandateOf, resolveHookTimeoutMs, runHookSeat, hookSeatExpiredError, runToolGate } from "../hooks.js";
38
27
  import { createWriteProtectionCheck } from "../write-protect.js";
39
- import { orgRuleVerdictFor } from "../permission-rule-org.js";
40
28
  import { CacheBreakDetector, toolsToFingerprintInputs } from "../cache-break-detector.js";
41
29
  import { reservedCollisions, reservedFor } from "../../brain/request-params.js";
42
- import { adjudicateDerivedRoute, fallbackToPrimaryNotice, sameRouteIdentity } from "../../brain/route-adjudicator.js";
43
30
  import { STALL_CONNECT_MS, STALL_FIRST_TOKEN_MS, STALL_IDLE_MS, withBrainCallGuardrail } from "../../brain/timeout.js";
44
- import { defineTool, isDefineToolProduct } from "../tools.js";
45
31
  import { RETIRED_TOOL_NAMES } from "../tool-name-aliases.js";
46
32
  import { protocolOf } from "../protocol-table.js";
47
33
  import { isNamespacedCoveringRuleName, namespacedRuleNameCovers } from "../permission-rules.js";
48
- import { pathToUri } from "../lsp-protocol.js";
49
- import { DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, createOffloadPersist, firstPartyOffloadPolicy, InMemoryToolResultStore, RunnerSharedToolResultStore, ScopedToolResultStore, isVolatileOffloadStore, OFFLOAD_TOOL_NAME, createReadToolResultTool, withToolResultOffload, } from "../tool-result-store.js";
50
- import { OUTPUT_TOOL_NAME, REPORT_FINDINGS_TOOL_NAME, SKILL_CONTENT_MAX_CHARS, SKILL_TOOL_NAME, createOutputTool, createReportBlockedTool, createReportFindingsTool, createSkillTool, normalizeSkills } from "./synthetic-tools.js";
34
+ import { createOffloadPersist, InMemoryToolResultStore, ScopedToolResultStore, isVolatileOffloadStore, OFFLOAD_TOOL_NAME, } from "../tool-result-store.js";
35
+ import { OUTPUT_TOOL_NAME, createOutputTool } from "./synthetic-tools.js";
51
36
  import { compileOutputSchema } from "./strict-output-schema.js";
52
- import { TOOL_SEARCH_NAME, buildDeferredRegistry, classifyDeferred, extractDiscoveredToolNames, createPlaceholderTool, createToolSearchTool, staticSchemaRenderable, } from "./tool-disclosure.js";
53
- import { createSharedMemoryTools } from "../shared-memory/tools.js";
54
- import { SHARED_MEMORY_TOOL_NAMES } from "../shared-memory/types.js";
55
37
  import { MEMORY_ENGINE_TOOL_NAMES } from "../memory-engine/tools.js";
56
38
  import { classifyToolContentOrigin, contentOriginPollutes, delegationCallIsExternal } from "../memory-engine/content-origin.js";
57
39
  import { narrowContentSafety, readCardAttestation } from "../memory-engine/delegation-provenance.js";
58
40
  import { recordSyncUnattestable, sessionSettlements } from "../memory-engine/delegation-settlement.js";
59
- import { composeMemoryBlock } from "../memory.js";
60
- import { COMPLIANCE_CAPABILITIES, WEB_FETCH_TOOL_NAME, complianceCallDenial, resolveComplianceDenies } from "../compliance.js";
41
+ import { complianceCallDenial } from "../compliance.js";
61
42
  import { foldAdmissionFreeze } from "../memory-admission.js";
62
43
  import { prepareMemory } from "./prepare-memory.js";
44
+ import { prepareDeferClassify } from "./prepare-defer-classify.js";
45
+ import { prepareListings } from "./prepare-listings.js";
46
+ import { preparePromptInputs } from "./prepare-prompt-inputs.js";
47
+ import { hasConversationContent, prepareProjectContext } from "./prepare-project-context.js";
63
48
  import { limitConfigError, prepareConfigDoors } from "./prepare-config-doors.js";
64
49
  import { NAMESPACED_NAME_SHAPES, prepareSafetyScan } from "./prepare-safety-scan.js";
65
50
  import { prepareAcquireReconcile } from "./prepare-acquire-reconcile.js";
66
51
  import { prepareHandsMount, resolveHandsLessReadFace } from "./prepare-hands-readface.js";
67
- import { createEditedFilesLedger } from "./edited-files-ledger.js";
68
52
  import { prepareWorkspaceRestore, remoteEnvFailureNote, restoreWorkspaceWithRetry } from "./prepare-workspace-restore.js";
69
- import { defaultPromptProvider, buildEnvironmentContext, formatLocalDate, isValidTimeZone, PROJECT_CONTEXT_FRAMING } from "../../prompts/default.js";
70
- import { applyGitFrameGuard, probeGitStatusLane } from "./git-status-frame.js";
53
+ import { prepareOffloadWrappers } from "./prepare-offload-wrappers.js";
54
+ import { prepareFileHistory } from "./prepare-file-history.js";
55
+ import { prepareExecutionEnv } from "./prepare-execution-env.js";
56
+ import { prepareRunRefs } from "./prepare-run-refs.js";
57
+ import { prepareInheritedGate } from "./prepare-inherited-gate.js";
58
+ import { prepareCapsAndWorkflow } from "./prepare-caps-and-workflow.js";
59
+ import { prepareDelegationSurface } from "./prepare-delegation-surface.js";
60
+ import { prepareToolDisclosureMount } from "./prepare-tool-disclosure-mount.js";
61
+ import { prepareWiringManifest } from "./prepare-wiring-manifest.js";
62
+ import { checkpointScopeOf } from "./checkpoint-scope.js";
63
+ export { DEFAULT_IRREVERSIBLE_SCOPE, checkpointScopeOf } from "./checkpoint-scope.js";
64
+ export { mcpManifestEntries } from "./prepare-wiring-manifest.js";
65
+ import { derivedRouteFallsBack } from "./derived-route-fallback.js";
66
+ import { prepareProtocolTools } from "./prepare-protocol-tools.js";
67
+ import { CONTENT_ASK_BINDING_CAP, prepareQuestionFace } from "./prepare-question-face.js";
68
+ import { prepareLsp } from "./prepare-lsp.js";
69
+ import { createRollbackStack } from "./rollback-stack.js";
70
+ export { __resetMaterializeEnvAnnouncements } from "./prepare-tool-disclosure-mount.js";
71
+ export { fileHistoryFilesystemIdentity, resolveFileHistoryScope } from "./prepare-file-history.js";
72
+ import { defaultPromptProvider, buildEnvironmentContext } from "../../prompts/default.js";
73
+ import { applyGitFrameGuard } from "./git-status-frame.js";
71
74
  import { assemblePrompt } from "../../prompt-assembly/assemble.js";
72
- import { auditToolCollisions, getToolContract, projectToolManifest } from "../../prompt-assembly/tool-catalog.js";
75
+ import { auditToolCollisions, projectToolManifest } from "../../prompt-assembly/tool-catalog.js";
73
76
  import { resolveEpochAgainstBundled } from "../../prompt-assembly/epoch.js";
74
77
  import { artifactDeclarations } from "../../prompt-assembly/artifact.js";
75
78
  import { buildTurnPromptSnapshot } from "../../prompt-assembly/turn-snapshot.js";
@@ -78,56 +81,22 @@ import { clearStaleToolResults, createClearedProjectionLedger, dropEmptyFailureA
78
81
  import { capAggregateToolResults } from "../tool-result-budget.js";
79
82
  import { capAggregateMediaBytes, AGGREGATE_MEDIA_BUDGET_BYTES } from "../media-byte-cap.js";
80
83
  import { dropOrphanToolResults, guardBudget, insertTrimNotice, trimToBudget } from "../context-guard.js";
81
- import { StubExecutionEnv } from "../stub-env.js";
82
- import { hasDestroy, isIsolated, isRemoteExecutionEnv, isSuspendable, missingRestoreSurface, RemoteExecutionError } from "../remote-env.js";
84
+ import { isIsolated, isRemoteExecutionEnv, isSuspendable, RemoteExecutionError } from "../remote-env.js";
83
85
  import { settleTeardownLeg } from "./teardown-bounded.js";
84
86
  import { hasBackgroundShell, sweepBackgroundShells } from "../background-shell.js";
85
- import { createTaskOutputTool, createTaskStopTool, defaultTaskRegistry } from "../task-registry.js";
86
- import { createMonitorTool } from "../../tools/monitor.js";
87
- import { createWorktreeTools } from "../../tools/worktree.js";
88
- import { announcePeerLaneMount, bindPeerLaneDrain, listAgentsMountable, mountListAgents, peerLaneSendMessageSeats } from "../../agents/peer-session-drain.js";
89
- import { CROSS_SESSION_CLASSIFIER_RULE } from "../../agents/cross-session-envelope.js";
90
- import { NodeExecutionEnv } from "../../engine/execution-env/node-execution-env.js";
91
- import { applyCompactionToReadFileState, isReadDedupStubResult, FULL_SHELL_CONTRACT_ID, HAND_TOOL_EFFECTS } from "../../tools/fs/index.js";
87
+ import { defaultTaskRegistry } from "../task-registry.js";
88
+ import { announcePeerLaneMount } from "../../agents/peer-session-drain.js";
89
+ import { applyCompactionToReadFileState, isReadDedupStubResult } from "../../tools/fs/index.js";
92
90
  import { decodeTextBytes } from "../../tools/fs/encoding.js";
93
- import { ASK_USER_QUESTION_TOOL_NAME, createAskUserQuestionTool, classifyQuestionOutcome, isLiveQuestionFace, validateAskQuestions, } from "../ask-question.js";
91
+ import { ASK_USER_QUESTION_TOOL_NAME, classifyQuestionOutcome, validateAskQuestions, } from "../ask-question.js";
94
92
  import { createSchedulerTools } from "../../tools/scheduler-tools.js";
95
- import { createPresentPlanTool, createEnterPlanModeTool, PRESENT_PLAN_TOOL_NAME } from "../present-plan-tool.js";
96
- import { isSelfOrchestrationActive, selfOrchestrationFailClosedReason } from "../../orchestration/workflow-script-runner.js";
97
- import { createRunWorkflowTool, RUN_WORKFLOW_TOOL_NAME } from "../../orchestration/run-workflow-tool.js";
98
- import { resolveWorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
99
- import { createLspTool, gitCheckIgnoreFilter, resolveLspPath } from "../lsp.js";
93
+ import { PRESENT_PLAN_TOOL_NAME } from "../present-plan-tool.js";
94
+ import { selfOrchestrationFailClosedReason } from "../../orchestration/workflow-script-runner.js";
100
95
  import { fileArgPath, resolveKey } from "../../tools/fs/safety.js";
101
96
  import { BINDING_CHECKPOINT_VERSION, mintCheckpointId, mintCheckpointToken, ORG_ADMISSION_CHECKPOINT_VERSION, F012_CHECKPOINT_VERSION, FACE_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, buildRiskDescriptor, debitLedger, encodeAtFidelity, remainingBudgetMicroUsd, resolveCheckpointStore, resolveDeclaredFidelity, samePlainValue, } from "../checkpoint-store.js";
102
97
  import { boundInputHashOf } from "../canonical-json.js";
103
- import { countElicitOptIns, deriveWiringManifest, resolveAskSeamForm, resolveDeclaredDurability, resolveElicitSeam, resolveQuestionSeam, resolveSubagentTranscriptTier } from "../wiring-manifest.js";
104
98
  import { durableParkGapFor } from "../park-selfcheck.js";
105
- import { GLOBAL_USAGE_KEY, usageRetryAfterMs, windowsGovernCost } from "../usage-window-store.js";
106
99
  import { deliverEngineNotice } from "../types.js";
107
- import { resolveTrackKey } from "../file-history-store.js";
108
- let announcedMaterializeEnvBySink = new WeakMap();
109
- const announcedMaterializeEnvConsole = new Set();
110
- function materializeEnvLedger(onNotice) {
111
- if (typeof onNotice !== "function")
112
- return announcedMaterializeEnvConsole;
113
- let lines = announcedMaterializeEnvBySink.get(onNotice);
114
- if (lines === undefined) {
115
- lines = new Set();
116
- announcedMaterializeEnvBySink.set(onNotice, lines);
117
- }
118
- return lines;
119
- }
120
- export function __resetMaterializeEnvAnnouncements() {
121
- announcedMaterializeEnvBySink = new WeakMap();
122
- announcedMaterializeEnvConsole.clear();
123
- }
124
- function emitMaterializeEnvNotice(onNotice, message, detail) {
125
- const ledger = materializeEnvLedger(onNotice);
126
- if (ledger.has(message))
127
- return;
128
- ledger.add(message);
129
- deliverEngineNotice(onNotice, { code: "config.materialize_env_discarded", message, detail });
130
- }
131
100
  function warnCompactionWindowHazard(tracer, spec, model, compModel, hostTaskId) {
132
101
  if (compModel === undefined)
133
102
  return;
@@ -247,65 +216,9 @@ function announceDeclaredMcpContentClasses(args) {
247
216
  }
248
217
  export { __resetReadFaceClampAnnouncement } from "./prepare-hands-readface.js";
249
218
  const DEFAULT_MAX_SUSPENDS = 5;
250
- const ULTRA_REASONING_TIERS = new Set(["xhigh", "max"]);
251
219
  const DEFAULT_RESOURCE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
252
220
  export const ENV_LIFETIME_SUSPEND_MARGIN_MS = 60_000;
253
221
  export const USAGE_WINDOW_REAP_MARGIN_MS = 60 * 60 * 1000;
254
- function buildUsageGovernance(windows, deps, principal, sessionId) {
255
- if (windows === undefined || windows.length === 0)
256
- return undefined;
257
- const store = deps.usageWindowStore;
258
- if (store === undefined) {
259
- deps.onError?.(new Error("RunnerDeps.usageWindows is set but INACTIVE: no `usageWindowStore` is wired, so cross-task usage cannot be counted and no window can be enforced. Wire InMemoryUsageWindowStore (process-local) or FileUsageWindowStore (restart-surviving)."), { phase: "config", sessionId });
260
- return undefined;
261
- }
262
- const key = principal || GLOBAL_USAGE_KEY;
263
- const governsCost = windowsGovernCost(windows);
264
- let chargedTokens = 0;
265
- let chargedCostMicroUsd = 0;
266
- let announcedCostGap = false;
267
- let recordedUnpricedGap = false;
268
- return {
269
- key,
270
- governsCost,
271
- async check(now) {
272
- const readings = await store.read(key, windows, now);
273
- if (!announcedCostGap && readings.some((r) => r.costUnknown === true)) {
274
- announcedCostGap = true;
275
- deps.onError?.(new Error(`the usage window for ledger key ${JSON.stringify(key)} holds spend that nothing could price, so its maxCostUsd ceiling is being evaluated against a LOWER BOUND until that charge ages out of the window. Price every model this deployment can reach (including degrade targets and the compaction model).`), { phase: "config", sessionId });
276
- }
277
- return usageRetryAfterMs(readings, windows);
278
- },
279
- async commit(cumulativeTokens, cumulativeCostMicroUsd, now) {
280
- if (!governsCost) {
281
- const delta = cumulativeTokens - chargedTokens;
282
- if (delta <= 0)
283
- return;
284
- await store.charge(key, delta, now, windows);
285
- chargedTokens = cumulativeTokens;
286
- return;
287
- }
288
- if (cumulativeCostMicroUsd === undefined) {
289
- const tokenDelta = cumulativeTokens - chargedTokens;
290
- if (tokenDelta > 0 || !recordedUnpricedGap) {
291
- await store.charge(key, Math.max(0, tokenDelta), now, windows, null);
292
- chargedTokens = Math.max(chargedTokens, cumulativeTokens);
293
- recordedUnpricedGap = true;
294
- }
295
- const e = new Error("a deployment usage window declares maxCostUsd, but this run's spend has NO cost figure (RB-368 unpriced: a model served without a RunnerDeps.pricing entry or a Model.cost declaration). Refused rather than charged a fabricated 0 — the money ceiling would have silently stopped applying. Price every model this run can reach, or drop maxCostUsd from the window.");
296
- e.code = "config.usage_window_unpriced";
297
- throw e;
298
- }
299
- const tokenDelta = cumulativeTokens - chargedTokens;
300
- const costDelta = cumulativeCostMicroUsd - chargedCostMicroUsd;
301
- if (tokenDelta <= 0 && costDelta <= 0)
302
- return;
303
- await store.charge(key, Math.max(0, tokenDelta), now, windows, Math.max(0, costDelta));
304
- chargedTokens = cumulativeTokens;
305
- chargedCostMicroUsd = cumulativeCostMicroUsd;
306
- },
307
- };
308
- }
309
222
  export function resolveEnvLifetimeExpiry(env, observedAt) {
310
223
  const lifetimeMs = env.lifetimeMs;
311
224
  if (lifetimeMs === undefined)
@@ -322,22 +235,14 @@ export function resolveEnvLifetimeExpiry(env, observedAt) {
322
235
  return { unanchored: true };
323
236
  return { expiresAt: anchor + lifetimeMs };
324
237
  }
325
- function isLiveApproverSeat(onAsk) {
326
- return typeof onAsk === "function";
327
- }
328
238
  const DEFAULT_UNATTENDED_APPROVAL_TTL_MS = 30 * 24 * 60 * 60 * 1000;
329
239
  function sanitizedTtlMs(ttlMs) {
330
240
  if (ttlMs === undefined)
331
241
  return undefined;
332
242
  return Number.isFinite(ttlMs) && ttlMs > 0 ? ttlMs : DEFAULT_RESOURCE_TTL_MS;
333
243
  }
334
- const ungatedWarnedShapes = new WeakMap();
335
244
  const advisedPolicyNames = new WeakMap();
336
245
  const ADVISED_KEYS_CAP = 64;
337
- export const DEFAULT_IRREVERSIBLE_SCOPE = "irreversible";
338
- export function checkpointScopeOf(spec) {
339
- return spec.durableApproval?.scope || spec.principal || DEFAULT_IRREVERSIBLE_SCOPE;
340
- }
341
246
  class ParkRefusal extends Error {
342
247
  constructor(message, options) {
343
248
  super(message, options);
@@ -346,7 +251,7 @@ class ParkRefusal extends Error {
346
251
  }
347
252
  export { resolveCheckpointStore } from "../checkpoint-store.js";
348
253
  export { isFableFamilyModelId, resolveAttachmentsConfig, resolveModelPromptTraits, resolveTaskLimits } from "./prepare-config-doors.js";
349
- export { rebaseWorkspacePath, rebaseWorkspacePathAcross } from "./prepare-workspace-restore.js";
254
+ export { rebaseWorkspacePath, rebaseWorkspacePathAcross } from "./workspace-path.js";
350
255
  function buildMicroCompactState(deps, model, sessionId, runId, offloadStore, knob) {
351
256
  const triggerWindow = resolveTriggerWindow(model);
352
257
  if (triggerWindow.clamped) {
@@ -457,226 +362,6 @@ export async function runGuardChain(args) {
457
362
  const t = applyGuardTrim(working, anchoredWorking);
458
363
  return { working, trimmed: t.trimmed, trimDroppedMessages: t.dropped };
459
364
  }
460
- function restoredAbsPathsOf(restored, root) {
461
- const out = [];
462
- for (const key of [...restored.filesChanged, ...restored.failed.map((f) => f.path)]) {
463
- const abs = resolveTrackKey(root, key);
464
- if (abs.ok)
465
- out.push(abs.abs);
466
- }
467
- return out;
468
- }
469
- function resolveWiredFileHistoryStore(candidate) {
470
- if (candidate === undefined)
471
- return undefined;
472
- const verbs = ["trackEdit", "annulTrack", "snapshot", "restore", "canRestore", "reap", "adoptScope"];
473
- const verbOf = (v) => {
474
- try {
475
- return candidate[v];
476
- }
477
- catch {
478
- return undefined;
479
- }
480
- };
481
- const probeable = (typeof candidate === "object" && candidate !== null) || typeof candidate === "function";
482
- const missing = probeable ? verbs.filter((v) => typeof verbOf(v) !== "function") : [...verbs];
483
- if (missing.length > 0) {
484
- const shape = candidate === null ? "null" : probeable ? "a value that is not a FileHistoryStore" : `a ${typeof candidate}`;
485
- const e = new Error(`RunnerDeps.fileHistoryStore was wired with ${shape} — missing contract verbs: ${missing.join(", ")}. Refusing prepare rather than half-wiring the rewind seat (a present-but-unusable store makes every Edit/Write/NotebookEdit fail its first-touch backup while restore requests report "no store wired"). Omit the key to run without file history.`);
486
- e.code = "config.invalid_file_history_store";
487
- throw e;
488
- }
489
- return candidate;
490
- }
491
- const LEGACY_PROBE_TIMEOUT_MS = 2_000;
492
- const LEGACY_PROBE_TIMED_OUT = Symbol("legacy-rewind-probe-timeout");
493
- async function legacyRewindEpochTail(deps, sessionId, entryId, form) {
494
- const probe = deps.legacyRewindBoundaryProbe;
495
- let verdict;
496
- if (probe !== undefined) {
497
- try {
498
- let timer;
499
- const timeout = new Promise((resolve) => {
500
- timer = setTimeout(() => resolve(LEGACY_PROBE_TIMED_OUT), LEGACY_PROBE_TIMEOUT_MS);
501
- });
502
- try {
503
- verdict = await Promise.race([Promise.resolve(probe({ sessionId, entryId })), timeout]);
504
- }
505
- finally {
506
- clearTimeout(timer);
507
- }
508
- if (verdict === LEGACY_PROBE_TIMED_OUT) {
509
- verdict = undefined;
510
- try {
511
- deps.onError?.(new Error(`RunnerDeps.legacyRewindBoundaryProbe did not answer within ${LEGACY_PROBE_TIMEOUT_MS}ms while explaining a rewind refusal — the refusal stands and names both possibilities`), { phase: "rewind", sessionId });
512
- }
513
- catch {
514
- }
515
- }
516
- }
517
- catch (err) {
518
- verdict = undefined;
519
- try {
520
- deps.onError?.(new Error(`RunnerDeps.legacyRewindBoundaryProbe threw while explaining a rewind refusal (${err.message}) — the refusal stands and names both possibilities`), { phase: "rewind", sessionId });
521
- }
522
- catch {
523
- }
524
- }
525
- if (verdict !== undefined && typeof verdict !== "boolean") {
526
- try {
527
- deps.onError?.(new Error(`RunnerDeps.legacyRewindBoundaryProbe answered ${JSON.stringify(String(verdict))} for entry "${entryId}" — only true/false/undefined are meaningful, so the refusal names both possibilities instead of trusting it`), { phase: "rewind", sessionId });
528
- }
529
- catch {
530
- }
531
- verdict = undefined;
532
- }
533
- }
534
- if (verdict === true) {
535
- return `This deployment's legacy-epoch probe reports that entry "${entryId}" DOES carry a boundary from the retired whole-tree snapshot era, which this engine's per-edited-file seat does not restore — the two epochs are not interchangeable (a whole-tree manifest carries no tracked-set provenance, so replaying it here would converge files this session never edited). Restore it with the whole-tree-era tooling this deployment kept, or re-run the turns under the current engine.`;
536
- }
537
- if (verdict === false) {
538
- return `This deployment's legacy-epoch probe reports no whole-tree-era boundary for entry "${entryId}" either, so the boundary simply does not exist: its turns ran without a RunnerDeps.fileHistoryStore wired, or it was reaped.`;
539
- }
540
- return form === "keyed"
541
- ? `Boundaries are keyed by a turn's INITIAL entry and exist only for turns that ran with a RunnerDeps.fileHistoryStore wired; a boundary from the retired whole-tree snapshot era is not restorable by this seat, and reaped boundaries have none.`
542
- : `Either the boundary does not exist (its turns ran without a RunnerDeps.fileHistoryStore, or it was reaped), or it was produced in the retired whole-tree snapshot era, which this engine's per-edited-file seat does not restore.`;
543
- }
544
- const FILE_HISTORY_BOUNDARY_TIMEOUT_MS = 30_000;
545
- const FILE_HISTORY_CARRY_MEMO = new WeakMap();
546
- async function adoptForkedFileHistory(store, session, sessionId, onError) {
547
- const memo = FILE_HISTORY_CARRY_MEMO.get(store) ?? { settled: new Set(), disclosed: new Set(), inFlight: new Map() };
548
- FILE_HISTORY_CARRY_MEMO.set(store, memo);
549
- const discloseOnce = (err) => {
550
- if (memo.disclosed.has(sessionId))
551
- return;
552
- memo.disclosed.add(sessionId);
553
- onError?.(err, { phase: "rewind", sessionId });
554
- };
555
- try {
556
- if (memo.settled.has(sessionId))
557
- return;
558
- const forkedFrom = (await session.getMetadata()).forkedFrom;
559
- if (forkedFrom === undefined || forkedFrom === sessionId)
560
- return;
561
- const shared = memo.inFlight.get(sessionId);
562
- if (shared !== undefined)
563
- return await shared;
564
- if (memo.settled.has(sessionId))
565
- return;
566
- const attempt = store.adoptScope(forkedFrom, sessionId).then((carried) => {
567
- if (carried.ok || carried.error.code === "conflict") {
568
- memo.settled.add(sessionId);
569
- return;
570
- }
571
- discloseOnce(new Error(`file-history fork carry from session "${forkedFrom}" failed (${carried.error.code}): ${carried.error.message} — this forked session starts with an EMPTY rewind history, so boundaries recorded before the fork are not restorable from it (the run itself proceeds; the carry is retried on this session's next turn while its scope is still empty)`));
572
- }, (err) => discloseOnce(err));
573
- memo.inFlight.set(sessionId, attempt);
574
- try {
575
- await attempt;
576
- }
577
- finally {
578
- memo.inFlight.delete(sessionId);
579
- }
580
- }
581
- catch (err) {
582
- try {
583
- discloseOnce(err);
584
- }
585
- catch {
586
- }
587
- }
588
- }
589
- function createFileHistoryBoundarySeat(opts) {
590
- let inFlight;
591
- let begun = false;
592
- return {
593
- begin(entryId) {
594
- if (begun)
595
- return;
596
- begun = true;
597
- const ac = new AbortController();
598
- let timer;
599
- const timeout = new Promise((resolve) => {
600
- timer = setTimeout(() => {
601
- ac.abort();
602
- resolve({ ok: false });
603
- }, FILE_HISTORY_BOUNDARY_TIMEOUT_MS);
604
- });
605
- let work;
606
- try {
607
- work = opts.store.snapshot(opts.sessionId, entryId, opts.env, opts.root, ac.signal);
608
- }
609
- catch (err) {
610
- work = Promise.resolve({ ok: false, error: { code: "snapshot_failed", message: `history store threw synchronously: ${err.message}` } });
611
- }
612
- work.catch(() => { });
613
- inFlight = Promise.race([
614
- work.then((r) => (r.ok ? undefined : `boundary capture failed (${r.error.code}): ${r.error.message}`)),
615
- timeout.then(() => `boundary capture timed out after ${FILE_HISTORY_BOUNDARY_TIMEOUT_MS / 1000}s (fenced — the attempt can never publish)`),
616
- ])
617
- .then((failure) => {
618
- if (failure !== undefined) {
619
- try {
620
- opts.onError?.(new Error(`file-history ${failure} — this turn has no boundary and cannot be rewound to (the turn itself proceeds)`), { phase: "rewind", sessionId: opts.sessionId });
621
- }
622
- catch {
623
- }
624
- }
625
- })
626
- .catch((err) => {
627
- try {
628
- opts.onError?.(err, { phase: "rewind", sessionId: opts.sessionId });
629
- }
630
- catch {
631
- }
632
- })
633
- .finally(() => clearTimeout(timer));
634
- },
635
- async settle() {
636
- if (inFlight !== undefined)
637
- await inFlight;
638
- },
639
- };
640
- }
641
- async function resolveFileHistoryCoordinates(enabled, env, taskRoot, lineage, sessionId) {
642
- if (!enabled)
643
- return { historyRoot: taskRoot, historyScope: sessionId, historyFs: "" };
644
- const canonRoot = await env.canonicalPath(taskRoot);
645
- const historyRoot = canonRoot.ok ? canonRoot.value : taskRoot;
646
- const historyFs = fileHistoryFilesystemIdentity(env);
647
- return { historyRoot, historyScope: resolveFileHistoryScope(lineage, historyRoot, historyFs, sessionId), historyFs };
648
- }
649
- export function resolveFileHistoryScope(lineage, historyRoot, historyFs, sessionId) {
650
- if (lineage === undefined || lineage.scope === "" || lineage.root !== historyRoot || lineage.fs !== historyFs)
651
- return sessionId;
652
- return lineage.scope;
653
- }
654
- const envFilesystemTokens = new WeakMap();
655
- let envFilesystemSeq = 0;
656
- export function fileHistoryFilesystemIdentity(env) {
657
- if (isRemoteExecutionEnv(env)) {
658
- try {
659
- const h = env.workspaceHandle();
660
- return JSON.stringify(["remote", h.provider, h.sandboxId, h.deviceId !== undefined && h.deviceId !== "" ? h.deviceId : null]);
661
- }
662
- catch {
663
- }
664
- }
665
- else if (env.hostLocalPaths === true || (env.hostLocalPaths === undefined && env instanceof NodeExecutionEnv)) {
666
- return "host";
667
- }
668
- let token = envFilesystemTokens.get(env);
669
- if (token === undefined) {
670
- token = `instance:${++envFilesystemSeq}`;
671
- envFilesystemTokens.set(env, token);
672
- }
673
- return token;
674
- }
675
- function childScopeRewindRefusal(rootScope, target) {
676
- const e = new Error(`rewind-files: this run is a delegated child recording its edits into the ROOT session "${rootScope}"'s file history, so restoring files to entry "${target}" is the root session's action (rewindFilesTo / resumeAt+restoreFiles on that session), not the child's — the files were NOT rewound`);
677
- e.code = "rewind.child_scope_unsupported";
678
- return e;
679
- }
680
365
  export function gatedCallIdOf(p) {
681
366
  if (p.suspendRef.token !== undefined)
682
367
  return p.suspendRef.gatedCallId;
@@ -701,9 +386,6 @@ function publishCommittedSuspend(refs, token, gate, scope, remoteHandle, checkpo
701
386
  ref.restoreMode = remoteHandle.restoreMode === "park_only" ? "park_only" : "snapshot";
702
387
  ref.scope = scope;
703
388
  }
704
- function hasConversationContent(branch) {
705
- return branch.some((e) => e.type === "message" || e.type === "custom_message" || e.type === "compaction");
706
- }
707
389
  export function effectiveDelegationFacts(internals, seedIsDelegatedChild) {
708
390
  const isDelegatedChild = internals?.isDelegatedChild !== undefined ? internals.isDelegatedChild === true : seedIsDelegatedChild === true;
709
391
  return { isDelegatedChild, isNonForkChild: isDelegatedChild && internals?.insideFork !== true };
@@ -890,19 +572,6 @@ function createDenyObserverNotifier(hooks, hookTimeoutMs, signal, report) {
890
572
  }
891
573
  };
892
574
  }
893
- function mcpRevocationWiring(deps, runId) {
894
- if (deps.mcpRevocations === undefined)
895
- return undefined;
896
- const ledger = deps.mcpRevocations;
897
- return {
898
- isRevoked: (name) => ledger.isRevoked(name),
899
- onProbeFailure: (e) => deliverEngineNotice(deps.onNotice, {
900
- code: "mcp.revocation_probe_failed",
901
- message: `the mcpRevocations.isRevoked probe threw — MCP dispatch fails OPEN (no server treated as revoked) until the probe recovers: ${e instanceof Error ? e.message : String(e)}`,
902
- detail: { message: e instanceof Error ? e.message : String(e), runId },
903
- }),
904
- };
905
- }
906
575
  async function forgetQuietly(sessions, sessionId) {
907
576
  try {
908
577
  if (sessions.forget)
@@ -937,12 +606,21 @@ function screenGateSettlement(result, settling) {
937
606
  else
938
607
  resolution = reportedResolution;
939
608
  }
609
+ const reportedAutoDenied = result.autoDenied;
610
+ let autoDenied;
611
+ if (reportedAutoDenied === true) {
612
+ if (!settling)
613
+ defects.push("a tool-gate settlement reported autoDenied on an executing call — only a BLOCKED call can carry the marker; the frame carries none");
614
+ else
615
+ autoDenied = true;
616
+ }
940
617
  const record = {
941
618
  ...(settledBy !== undefined ? { settledBy } : {}),
942
619
  ...(attribution.approver !== undefined ? { approver: attribution.approver } : {}),
943
620
  ...(resolution !== undefined ? { resolution } : {}),
621
+ ...(autoDenied !== undefined ? { autoDenied } : {}),
944
622
  };
945
- return { ...(settledBy !== undefined || attribution.approver !== undefined || resolution !== undefined ? { record } : {}), defects };
623
+ return { ...(settledBy !== undefined || attribution.approver !== undefined || resolution !== undefined || autoDenied !== undefined ? { record } : {}), defects };
946
624
  }
947
625
  function maybeHumanRejectionHalt(input) {
948
626
  if (!input.bare || input.settledBy !== "human" || input.isDelegatedChild)
@@ -995,37 +673,6 @@ function resolveApprovalPreview(tools, toolName, args) {
995
673
  return { withheld: "unavailable" };
996
674
  }
997
675
  }
998
- function inheritedAskRuleEvidence(deps) {
999
- const org = deps.permissionRuleOrg === undefined ? "not_wired" : "not_adjudicated";
1000
- const personal = deps.permissionRuleStore === undefined ? "not_wired" : "not_adjudicated";
1001
- return Object.freeze({ orgRevisionAbsent: org, orgRuleAbsent: org, personalRuleDotsAbsent: personal });
1002
- }
1003
- function orgRevisionEvidenceOf(resolution, onDefect) {
1004
- const reported = resolution.revision;
1005
- if (reported === undefined)
1006
- return {};
1007
- if (typeof reported === "number" && Number.isFinite(reported))
1008
- return { revision: reported };
1009
- onDefect(`the org rule overlay reported revision ${JSON.stringify(reported)} — a snapshot revision is a finite number; ` +
1010
- `the adjudication stands, but the ask carries no revision evidence for this call`);
1011
- return {};
1012
- }
1013
- function persistedRuleHitOf(admitting) {
1014
- return admitting === undefined
1015
- ? undefined
1016
- : { rules: admitting.map((r) => ({ rule: r.rule, dots: r.adds.map((a) => ({ actor: a.dot.actor, counter: a.dot.counter })) })) };
1017
- }
1018
- function directoryRuleLaneAnswer(table, args, ctx) {
1019
- const filePath = args?.file_path;
1020
- if (typeof filePath !== "string" || filePath === "")
1021
- return undefined;
1022
- const spelled = filePath.startsWith("/") ? filePath : `${(ctx.liveCwd ?? ctx.root ?? "").replace(/\/+$/, "")}/${filePath}`;
1023
- const target = lexicalNormalAbsolutePathOf(spelled);
1024
- if (target === undefined)
1025
- return undefined;
1026
- const hit = table.find((r) => eligiblePersisted(r, { tool: DIRECTORY_RULE_TOOL, cwd: ctx.root, sessionId: ctx.sessionId }) && directoryRuleAdmits(r, target));
1027
- return hit !== undefined ? persistedRuleHitOf([hit]) : undefined;
1028
- }
1029
676
  function makeRuleOffersOf(cfg) {
1030
677
  return (toolName, args, ask) => {
1031
678
  if (!cfg.laneArmed || toolName !== PERSISTED_RULE_TOOL)
@@ -1071,38 +718,6 @@ function makeRuleOffersOf(cfg) {
1071
718
  : { ruleOffersAbsence: "lane_cannot_speak" };
1072
719
  };
1073
720
  }
1074
- function makeOrgAdjudicationLane(overlay, questionToolMounted, onRevisionDefect) {
1075
- if (overlay === undefined)
1076
- return undefined;
1077
- return {
1078
- adjudicate: async (req) => {
1079
- if (req.toolName === ASK_USER_QUESTION_TOOL_NAME && questionToolMounted)
1080
- return { status: "available" };
1081
- let resolution;
1082
- try {
1083
- resolution = await overlay.resolve();
1084
- }
1085
- catch (err) {
1086
- return { status: "unavailable", disclosures: [`the org rule overlay threw: ${err instanceof Error ? err.message : String(err)}`] };
1087
- }
1088
- if (resolution.status === "unavailable")
1089
- return { status: "unavailable", disclosures: resolution.disclosures };
1090
- const revisionCell = orgRevisionEvidenceOf(resolution, onRevisionDefect);
1091
- const command = req.args?.command;
1092
- if (req.toolName !== PERSISTED_RULE_TOOL || typeof command !== "string")
1093
- return { status: "available", ...revisionCell };
1094
- const verdict = orgRuleVerdictFor(resolution.rules, { tool: req.toolName, command });
1095
- return verdict === undefined ? { status: "available", ...revisionCell } : { status: "available", verdict, ...revisionCell };
1096
- },
1097
- };
1098
- }
1099
- function cwdConflictsRestoreError(requestedCwd) {
1100
- const e = new Error(`RunInternals.requestedCwd ("${requestedCwd}") cannot be combined with a checkpoint workspace restore — ` +
1101
- `the restored workspace's own mount path is authoritative for the task root, so a requested cwd on this leg ` +
1102
- `would be ignored (or worse, probed against an unrestored environment). Drop requestedCwd on resume legs.`);
1103
- e.code = "config.cwd_conflicts_restore";
1104
- return e;
1105
- }
1106
721
  async function adoptReminderMark(session, sessionId, seedMark, spawnMark, onError) {
1107
722
  const sessionReminderMark = await (async () => {
1108
723
  try {
@@ -1130,28 +745,6 @@ async function adoptReminderMark(session, sessionId, seedMark, spawnMark, onErro
1130
745
  }
1131
746
  return { reminderMark, reminderDisclosureCounts: {} };
1132
747
  }
1133
- async function derivedRouteFallsBack(args) {
1134
- try {
1135
- if (sameRouteIdentity(args.derived, args.primary))
1136
- return false;
1137
- const verdict = await adjudicateDerivedRoute({ brain: args.brain, model: args.derived, getApiKeyAndHeaders: args.getApiKeyAndHeaders });
1138
- if (verdict === undefined || verdict.ok)
1139
- return false;
1140
- deliverEngineNotice(args.onNotice, fallbackToPrimaryNotice({ seat: args.seat, from: args.derived.id, to: args.primary.id, verdict, ...(args.sessionId !== undefined ? { sessionId: args.sessionId } : {}), ...(args.runId !== undefined ? { runId: args.runId } : {}) }));
1141
- return true;
1142
- }
1143
- catch {
1144
- return false;
1145
- }
1146
- }
1147
- function mintPlacementRootSessionId(placementRootResolved) {
1148
- if (placementRootResolved === "") {
1149
- const e = new Error("placement root resolved EMPTY (internals.placementRoot / internals.rootSessionId carries an empty string) — an empty fixed point cannot key a placement lookup; fix the spawning lane instead of defaulting around it.");
1150
- e.code = "config.placement_root_invalid";
1151
- throw e;
1152
- }
1153
- return placementRootResolved;
1154
- }
1155
748
  function restorePlacementRoot(internals, seedPlacementRoot) {
1156
749
  if (internals?.placementRoot !== undefined)
1157
750
  return internals;
@@ -1166,52 +759,6 @@ export function placementValueOrAbsent(value) {
1166
759
  function stampPlacementRootSessionId(placementRootResolved) {
1167
760
  return placementValueOrAbsent(placementRootResolved);
1168
761
  }
1169
- function explicitlyDeferredMemoryTrio(mounted, roster, deferNames) {
1170
- return mounted ? MEMORY_ENGINE_TOOL_NAMES.filter((n) => roster.some((t) => t.name === n) && (deferNames ?? []).includes(n)) : [];
1171
- }
1172
- function memoryGroupRetractionSet(builtinDeferPairNames, engineTrioInPlay) {
1173
- return new Set([...builtinDeferPairNames, ...(engineTrioInPlay ? MEMORY_ENGINE_TOOL_NAMES : [])]);
1174
- }
1175
- function assembleParentCaptureState(o, i, ctl, ancestors) {
1176
- const build = (optedOut, indeterminate) => ({
1177
- optedOut: optedOut === true,
1178
- indeterminate: indeterminate === true,
1179
- ...(ctl !== undefined ? { controlDir: ctl } : {}),
1180
- ancestors,
1181
- });
1182
- return o instanceof Promise || i instanceof Promise ? Promise.all([o, i]).then(([ov, iv]) => build(ov, iv)) : build(o, i);
1183
- }
1184
- async function spliceSessionOverlayRows(overlay, sessionId, persisted, tracer, hostTaskId) {
1185
- if (overlay === undefined)
1186
- return persisted;
1187
- try {
1188
- const served = structuredClone(await overlay.read(sessionId));
1189
- const sessionRows = served.filter((r) => {
1190
- const scope = r?.scope;
1191
- return scope?.kind === "session" && scope.sessionId === sessionId && !("reject" in normalizePersistedRule(r));
1192
- });
1193
- if (sessionRows.length < served.length) {
1194
- emitTrace(tracer, () => ({
1195
- kind: "permission.rule_store_unreadable",
1196
- version: 1,
1197
- taskId: hostTaskId,
1198
- message: `session-rule overlay served ${served.length - sessionRows.length} row(s) that are not canonical session rows of this session — dropped, not adjudicated`,
1199
- ts: Date.now(),
1200
- }));
1201
- }
1202
- return sessionRows.length > 0 ? [...sessionRows, ...persisted] : persisted;
1203
- }
1204
- catch (err) {
1205
- emitTrace(tracer, () => ({
1206
- kind: "permission.rule_store_unreadable",
1207
- version: 1,
1208
- taskId: hostTaskId,
1209
- message: `session-rule overlay: ${err instanceof Error ? err.message : String(err)}`,
1210
- ts: Date.now(),
1211
- }));
1212
- return persisted;
1213
- }
1214
- }
1215
762
  function refuseRequireExistingWithoutSession(spec) {
1216
763
  if (spec.requireExistingSession && !spec.sessionId) {
1217
764
  const e = new Error(`requireExistingSession requires a sessionId — cannot require an existing session without one (design/114 Phase3)`);
@@ -1219,31 +766,6 @@ function refuseRequireExistingWithoutSession(spec) {
1219
766
  throw e;
1220
767
  }
1221
768
  }
1222
- function screenAutoModeCap(caps, onError, sessionId) {
1223
- if (caps === undefined)
1224
- return { caps, faulted: false };
1225
- if (typeof caps !== "object" || caps === null || Array.isArray(caps)) {
1226
- onError?.(new Error(`runtimeCapsResolver returned a non-record value (${JSON.stringify(caps)}) — read as a resolver fault: every per-principal capability is DENIED for this run (auto mode is not armed; asks flow the original chain). Return a RuntimeCaps object or undefined.`), { phase: "config", sessionId });
1227
- return { caps: { allowWorkflows: false, allowFork: false, autoMode: false }, faulted: true };
1228
- }
1229
- const record = caps;
1230
- if (record.autoMode === undefined || typeof record.autoMode === "boolean")
1231
- return { caps: record, faulted: false };
1232
- onError?.(new Error(`runtimeCapsResolver returned a non-boolean autoMode (${JSON.stringify(record.autoMode)}) — read as a resolver fault: auto mode is DENIED for this run (the classifier is not armed; asks flow the original chain). Return true, false, or omit the key.`), { phase: "config", sessionId });
1233
- return { caps: { ...record, autoMode: false }, faulted: true };
1234
- }
1235
- function mintPersistedArming(am, laneRule, classifierSystemPrompt, onError, sessionId) {
1236
- const arming = autoModeArmingRecipeOf(laneRule ? { ...am, crossSessionMessagesRule: true } : am, {
1237
- promptDigest: `apv1:${createHash("sha256").update(classifierSystemPrompt).digest("hex")}`,
1238
- });
1239
- if (arming === undefined) {
1240
- onError?.(new Error("RunnerDeps.autoMode.persistArming is set but the auto-mode face did not canonicalize into an arming recipe " +
1241
- "(a rule list carrying a non-string, or a non-finite/out-of-range timeoutMs / failureThreshold / window bound) — " +
1242
- "NO arming recipe is recorded on this run's parked constraint chain, and a cross-process redemption keeps the " +
1243
- "conservative behavior (the ancestor classifier answers unavailable and the inherited ask flows to a human)"), { phase: "config", sessionId });
1244
- }
1245
- return arming;
1246
- }
1247
769
  function autoModeLatchHealthy(d) {
1248
770
  try {
1249
771
  return d.breakerOpen() !== true && typeof d.consecutiveFailures === "function" && d.consecutiveFailures() === 0;
@@ -1252,15 +774,6 @@ function autoModeLatchHealthy(d) {
1252
774
  return false;
1253
775
  }
1254
776
  }
1255
- function autoModeArmReasonOf(intent, facePresent, capsAutoMode, capsFaulted) {
1256
- if (!intent)
1257
- return "no_intent";
1258
- if (!facePresent)
1259
- return "no_face";
1260
- if (capsAutoMode === false)
1261
- return capsFaulted ? "resolver_fault" : "denied";
1262
- return "armed";
1263
- }
1264
777
  function validateInputFaultNotice(onError, sessionId) {
1265
778
  return ({ toolName, toolCallId, error }) => onError?.(new Error(`tool input pre-validation (validateInput) threw for ${toolName} (call ${toolCallId}) — read as no verdict; the call proceeded to the gate: ${error instanceof Error ? error.message : String(error)}`), { phase: "hook", sessionId });
1266
779
  }
@@ -1272,50 +785,6 @@ function refuseInvalidAutoModeSeat(spec) {
1272
785
  }
1273
786
  return spec.autoModeRequested === true;
1274
787
  }
1275
- function announceDurableGateUnavailable(args) {
1276
- if (!args.forceDurableGate || args.storeWired)
1277
- return;
1278
- const cause = args.taskStoreNull ? "task_store_null" : "no_deployment_store";
1279
- const inheritedContentMandate = (args.parentConstraints ?? []).some((pc) => pc.contentMandate === true);
1280
- const liveQuestionFace = args.liveQuestionFace && !inheritedContentMandate;
1281
- deliverEngineNotice(args.onNotice, {
1282
- code: "config.durable_gate_unavailable",
1283
- message: `Durable approval gate mandated but unavailable: the runtime entitlement forceDurableGate is in force for this ` +
1284
- `principal, but ${cause === "task_store_null" ? "this task disabled the checkpoint store (checkpointStore: null)" : "this deployment wired no checkpoint store"}, ` +
1285
- `so no interactive ask of this task can be parked for a durable answer. Asks resolve on the live chain instead ` +
1286
- `(${args.liveApprover ? "a live approver answers permission asks in-stream" : "permission asks are denied fail-closed — no live approver is wired"}; ` +
1287
- `${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"}) ` +
1288
- `and NO durable approval record is written. ${cause === "task_store_null" ? "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."}`,
1289
- detail: {
1290
- sessionId: args.sessionId,
1291
- runId: args.runId,
1292
- ...(args.principal ? { principal: args.principal } : {}),
1293
- cause,
1294
- liveApprover: args.liveApprover,
1295
- liveQuestionFace,
1296
- },
1297
- });
1298
- }
1299
- function buildTier3ReviveSpawn(delegationForRevive, enrichSpecToolCtx) {
1300
- if (delegationForRevive === undefined)
1301
- return undefined;
1302
- return async (req) => {
1303
- const out = await delegationForRevive.execute({
1304
- description: req.row.description ?? "revived teammate",
1305
- prompt: req.prompt,
1306
- run_in_background: true,
1307
- ...(req.row.agentType !== undefined ? { subagent_type: req.row.agentType } : {}),
1308
- ...(req.row.name !== undefined ? { name: req.row.name } : {}),
1309
- ...(req.row.model !== undefined ? { model: req.row.model } : {}),
1310
- }, enrichSpecToolCtx({ toolCallId: `rv-${randomBytes(8).toString("hex")}`, reviveClaim: { row: req.row, rev: req.rev, ...(req.peerSeed !== undefined ? { peerSeed: req.peerSeed } : {}) } }));
1311
- const o = (typeof out === "string" ? { content: out } : out);
1312
- return {
1313
- content: typeof o.content === "string" ? o.content : JSON.stringify(o.content),
1314
- ...(o.isError === true ? { isError: true } : {}),
1315
- ...(o.details !== undefined ? { details: o.details } : {}),
1316
- };
1317
- };
1318
- }
1319
788
  export async function prepareTask(spec, deps, sessions, resume, internals, runnerSelf, runIdSink) {
1320
789
  const runId = uuidv7();
1321
790
  if (runIdSink !== undefined)
@@ -1332,6 +801,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1332
801
  refuseRequireExistingWithoutSession(spec);
1333
802
  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 } : {}; })() });
1334
803
  const sessionId = acquired.sessionId;
804
+ const onceLedger = await loadAnnounceOnceLedger(session, resume?.seed.announcedListings, deps.onError);
1335
805
  const hostTaskId = spec.taskId ?? sessionId;
1336
806
  const delegation = effectiveDelegationFacts(internals, resume?.seed.isDelegatedChild);
1337
807
  const placementRootResolved = (internals = restorePlacementRoot(internals, resume?.seed.placementRootSessionId))?.placementRoot ?? internals?.rootSessionId ?? sessionId;
@@ -1366,112 +836,16 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1366
836
  e.code = "config.extra_body_output_cap";
1367
837
  throw e;
1368
838
  }
1369
- const nestedStats = { tokens: 0, turns: 0, tasks: 0, costMicroUsd: 0, anyUnpriced: false };
1370
- if (resume) {
1371
- nestedStats.tokens = resume.seed.nestedStats.tokens;
1372
- nestedStats.turns = resume.seed.nestedStats.turns;
1373
- nestedStats.tasks = resume.seed.nestedStats.tasks;
1374
- nestedStats.costMicroUsd = resume.seed.nestedStats.costMicroUsd;
1375
- nestedStats.anyUnpriced = resume.seed.nestedStats.anyUnpriced ?? true;
1376
- }
1377
- const reportUsage = (u) => {
1378
- nestedStats.tokens += u.tokens;
1379
- nestedStats.turns += u.turns;
1380
- nestedStats.tasks += u.tasks;
1381
- nestedStats.costMicroUsd += u.costMicroUsd ?? 0;
1382
- if (u.costMicroUsd === undefined)
1383
- nestedStats.anyUnpriced = true;
1384
- };
1385
- const explicitGlobalThreshold = spec.toolResultThresholdChars ?? deps.toolResultThresholdChars;
1386
- const offloadThreshold = explicitGlobalThreshold ?? DEFAULT_TOOL_RESULT_THRESHOLD_CHARS;
1387
- const offloadEnabled = Number.isFinite(offloadThreshold) && offloadThreshold > 0;
1388
- const rawOffloadStore = offloadEnabled ? (deps.toolResultStore ?? new InMemoryToolResultStore()) : undefined;
1389
- const offloadStore = rawOffloadStore instanceof RunnerSharedToolResultStore
1390
- ? new ScopedToolResultStore(rawOffloadStore, offloadScope)
1391
- : rawOffloadStore;
1392
- const offloadReachableToolsRef = {};
1393
- const maybeOffload = (tool, perTool) => {
1394
- if (!offloadStore || perTool?.offload === false)
1395
- return tool;
1396
- return withToolResultOffload(tool, offloadStore, perTool?.offloadThresholdChars ?? offloadThreshold, sessionId, () => offloadReachableToolsRef.current?.());
1397
- };
1398
- const firstPartyOffload = (tool) => {
1399
- const policy = firstPartyOffloadPolicy(tool.name);
1400
- if (policy.offload === false)
1401
- return maybeOffload(tool, policy);
1402
- return maybeOffload(tool, explicitGlobalThreshold === undefined ? policy : undefined);
1403
- };
1404
- const remoteToolOffload = (tool) => {
1405
- if (explicitGlobalThreshold !== undefined)
1406
- return maybeOffload(tool);
1407
- return maybeOffload(tool, { offloadThresholdChars: tool.mcpMaxResultSizeChars ?? 50_000 });
1408
- };
839
+ const { nestedStats, reportUsage, offloadStore, offloadReachableToolsRef, maybeOffload, firstPartyOffload, remoteToolOffload } = prepareOffloadWrappers({ spec, deps, resume, offloadScope, sessionId });
1409
840
  if (offloadStore && (spec.tools ?? []).some((t) => t.name === OFFLOAD_TOOL_NAME)) {
1410
841
  const e = new Error(`Tool name "${OFFLOAD_TOOL_NAME}" is reserved when large-result offload is enabled.`);
1411
842
  e.code = "config.reserved_tool_name";
1412
843
  await settleTeardownLeg(() => forgetOnThrow(), "forgetOnThrow (reserved-name leg)", (err) => deps.onError?.(err, { phase: "config", sessionId }));
1413
844
  throw e;
1414
845
  }
1415
- let ownedEnv;
1416
- let ownedEnvObservedAt;
1417
- let mcp;
1418
- let a2a;
1419
- if (internals?.requestedCwd !== undefined && deps.executionEnvFactory === undefined) {
1420
- await settleTeardownLeg(() => forgetOnThrow(), "forgetOnThrow (cwd-unsupported leg)", (err) => deps.onError?.(err, { phase: "config", sessionId }));
1421
- const e = new Error(`Agent cwd "${internals.requestedCwd}" cannot take effect: this deployment has no executionEnvFactory (a static execution environment cannot be re-rooted per agent). Drop the cwd parameter or deploy a factory.`);
1422
- e.code = "config.cwd_unsupported";
1423
- throw e;
1424
- }
1425
- if (internals?.requestedCwd !== undefined && resume?.workspaceHandle !== undefined) {
1426
- await settleTeardownLeg(() => forgetOnThrow(), "forgetOnThrow (cwd-conflicts-restore leg)", (err) => deps.onError?.(err, { phase: "config", sessionId }));
1427
- throw cwdConflictsRestoreError(internals.requestedCwd);
1428
- }
1429
- try {
1430
- ownedEnv = deps.executionEnvFactory
1431
- ? await deps.executionEnvFactory({
1432
- sessionId, placementRootSessionId: mintPlacementRootSessionId(placementRootResolved),
1433
- taskId: spec.taskId,
1434
- ...(internals?.isolation ? { isolation: internals.isolation } : {}),
1435
- ...(internals?.parentCwd ? { parentCwd: internals.parentCwd } : {}),
1436
- })
1437
- : undefined;
1438
- if (ownedEnv !== undefined)
1439
- ownedEnvObservedAt = Date.now();
1440
- }
1441
- catch (factoryErr) {
1442
- await settleTeardownLeg(() => forgetOnThrow(), "forgetOnThrow (env-factory-throw leg)", (err) => deps.onError?.(err, { phase: "config", sessionId }));
1443
- throw factoryErr;
1444
- }
1445
- const executionEnv = ownedEnv ?? deps.executionEnv ?? new StubExecutionEnv();
846
+ const rollback = createRollbackStack((e) => deps.onError?.(e, { phase: "config", sessionId }));
847
+ const { executionEnv, ownedEnvSeat, ownedEnvObservedAt, handsEnabled, taskRootInitial } = await prepareExecutionEnv({ deps, spec, internals, resume, placementRootResolved, sessionId, forgetOnThrow, rollback });
1446
848
  const externalContentTargetActive = executionEnv.externalContentTarget === true || resume?.seed.externalContentTarget === true;
1447
- if (internals?.requestedCwd !== undefined) {
1448
- const canon = async (p) => {
1449
- const r = await executionEnv.canonicalPath(p);
1450
- return r.ok ? r.value : p;
1451
- };
1452
- const want = await canon(internals.requestedCwd);
1453
- const rejectCwd = async (message, code) => {
1454
- await settleTeardownLeg(() => forgetOnThrow(), "forgetOnThrow (cwd-reject leg)", (err) => deps.onError?.(err, { phase: "config", sessionId }));
1455
- await settleTeardownLeg(() => (ownedEnv && hasDestroy(ownedEnv) ? ownedEnv.destroy() : undefined), "ownedEnv.destroy (cwd-reject leg)", (err) => deps.onError?.(err, { phase: "config", sessionId }));
1456
- const e = new Error(message);
1457
- e.code = code;
1458
- throw e;
1459
- };
1460
- if (deps.rootPath !== undefined) {
1461
- const root = await canon(deps.rootPath);
1462
- const sep = root.includes("\\") && !root.includes("/") ? "\\" : "/";
1463
- const rootPrefix = root.endsWith(sep) ? root : root + sep;
1464
- if (want !== root && !want.startsWith(rootPrefix)) {
1465
- await rejectCwd(`Agent cwd "${internals.requestedCwd}" is outside this deployment's declared root "${deps.rootPath}" — the cwd must be located within the deployment root.`, "config.cwd_outside_root");
1466
- }
1467
- }
1468
- const got = await canon(executionEnv.cwd);
1469
- if (want !== got) {
1470
- await rejectCwd(`Agent cwd "${internals.requestedCwd}" was not honored by the execution environment (it rooted at "${executionEnv.cwd}") — failing loud instead of running the agent in the wrong tree.`, "config.cwd_not_honored");
1471
- }
1472
- }
1473
- const handsEnabled = ownedEnv !== undefined || deps.executionEnv !== undefined;
1474
- const taskRootInitial = internals?.isolation === "worktree" || internals?.requestedCwd !== undefined || !deps.rootPath ? executionEnv.cwd : deps.rootPath;
1475
849
  const abortController = new AbortController();
1476
850
  if (spec.signal?.aborted)
1477
851
  abortController.abort();
@@ -1487,15 +861,15 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1487
861
  e.code = "isolation.unavailable";
1488
862
  throw e;
1489
863
  };
1490
- if (ownedEnv === undefined) {
864
+ if (ownedEnvSeat.current === undefined) {
1491
865
  failIsolation("no executionEnvFactory is configured (a static/shared execution env cannot mint a per-agent worktree)");
1492
866
  }
1493
- else if (ownedEnv === deps.executionEnv) {
1494
- ownedEnv = undefined;
867
+ else if (ownedEnvSeat.current === deps.executionEnv) {
868
+ ownedEnvSeat.current = undefined;
1495
869
  failIsolation("the executionEnvFactory returned the shared static env instead of a worktree-rooted env");
1496
870
  }
1497
- else if (isRemoteExecutionEnv(ownedEnv)) {
1498
- if (ownedEnv.capabilities.isolation !== true) {
871
+ else if (isRemoteExecutionEnv(ownedEnvSeat.current)) {
872
+ if (ownedEnvSeat.current.capabilities.isolation !== true) {
1499
873
  failIsolation("the executionEnvFactory returned a remote env without capabilities.isolation — a non-isolated remote target is a shared remote checkout, not a per-agent worktree");
1500
874
  }
1501
875
  }
@@ -1509,7 +883,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1509
883
  }
1510
884
  };
1511
885
  if (deps.rootPath !== undefined || internals.parentCwd !== undefined) {
1512
- const envCwd = canonical(ownedEnv.cwd, "the factory env's cwd");
886
+ const envCwd = canonical(ownedEnvSeat.current.cwd, "the factory env's cwd");
1513
887
  if (deps.rootPath !== undefined && envCwd === canonical(deps.rootPath, "the shared base root")) {
1514
888
  failIsolation("the executionEnvFactory rooted the env at the shared base root instead of a worktree");
1515
889
  }
@@ -1519,625 +893,15 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1519
893
  }
1520
894
  }
1521
895
  }
896
+ const ownedEnv = ownedEnvSeat.current;
1522
897
  const { taskRootFinal, rebaseRestoredPath } = await prepareWorkspaceRestore({ ownedEnv, deps, spec, internals, resume, taskRootInitial, abortController, sessionId });
1523
- const rewindNotes = [];
1524
- const onTrackFailure = deps.onTrackFailure === undefined ? "refuse" : deps.onTrackFailure;
1525
- if (onTrackFailure !== "refuse" && onTrackFailure !== "proceed-unprotected") {
1526
- const e = new Error(`RunnerDeps.onTrackFailure must be "refuse" or "proceed-unprotected"; got ${JSON.stringify(deps.onTrackFailure)} — refusing prepare rather than silently defaulting a first-touch-failure policy.`);
1527
- e.code = "config.invalid_on_track_failure";
1528
- throw e;
1529
- }
1530
- const fileHistoryStore = resolveWiredFileHistoryStore(deps.fileHistoryStore);
1531
- const legacyRewindFiles = spec.rewindFiles;
1532
- if (legacyRewindFiles === true) {
1533
- if (spec.resumeAt !== undefined) {
1534
- const e = new Error(`rewind-files: TaskSpec.rewindFiles is retired (design/381 — rewind now converges the per-edited-file history, not a whole-tree snapshot). This request pairs it with resumeAt, i.e. the RESTORE sense: set restoreFiles: true instead. (The capture sense needs no request anymore — first-touch tracking is always on when a RunnerDeps.fileHistoryStore is wired.)`);
1535
- e.code = "rewind.rewind_files_retired";
1536
- throw e;
1537
- }
1538
- try {
1539
- deps.onError?.(new Error(`TaskSpec.rewindFiles (capture sense) is retired and was ignored: per-edited-file first-touch tracking is always on when RunnerDeps.fileHistoryStore is wired, so a per-turn capture request has nothing left to ask for. Drop the field; the restore sense moved to TaskSpec.restoreFiles.`), { phase: "config", sessionId });
1540
- }
1541
- catch {
1542
- }
1543
- }
1544
- if (spec.restoreFiles === true && spec.resumeAt === undefined) {
1545
- const e = new Error(`rewind-files: restoreFiles: true needs a resumeAt anchor — restoring files means converging them to a specific prior entry's boundary, and this spec names none. Use resumeAt + restoreFiles to move conversation and files together, or rewindFilesTo alone for a code-only restore.`);
1546
- e.code = "rewind.invalid_spec";
1547
- throw e;
1548
- }
1549
- if (spec.resumeAt !== undefined && spec.rewindFilesTo !== undefined) {
1550
- const e = new Error(`rewind-files: resumeAt ("${spec.resumeAt}") and rewindFilesTo ("${spec.rewindFilesTo}") were both set — resumeAt already anchors the file restore when restoreFiles is true, so a separate rewindFilesTo target is a conflicting request. Drop one of the two.`);
1551
- e.code = "rewind.conflicting_targets";
1552
- throw e;
1553
- }
1554
- let rewindTarget = spec.resumeAt !== undefined ? (spec.restoreFiles === true ? spec.resumeAt : undefined) : spec.rewindFilesTo;
1555
- const rewindBefore = rewindTarget !== undefined && spec.resumeAt !== undefined && spec.resumeAtMode === "before";
1556
- if (spec.resumeAt !== undefined && spec.restoreFiles !== true) {
1557
- rewindNotes.push({
1558
- code: "conversation_only",
1559
- message: `the conversation was branched at entry "${spec.resumeAt}" but the working tree was NOT rewound — this task set resumeAt without restoreFiles, so files remain at their current state`,
1560
- });
1561
- }
1562
- const fileHistoryEnabled = fileHistoryStore !== undefined && handsEnabled;
1563
- const { historyRoot, historyScope, historyFs } = await resolveFileHistoryCoordinates(fileHistoryEnabled, executionEnv, taskRootFinal, internals?.fileHistoryLineage, sessionId);
1564
- if (fileHistoryStore !== undefined && internals?.fileHistoryLineage === undefined)
1565
- await adoptForkedFileHistory(fileHistoryStore, session, sessionId, deps.onError);
1566
- if (rewindTarget !== undefined && historyScope !== sessionId)
1567
- throw childScopeRewindRefusal(historyScope, rewindTarget);
1568
- if (fileHistoryStore === undefined) {
1569
- if (rewindTarget !== undefined) {
1570
- const e = new Error(`rewind-files: restoring files to entry "${rewindTarget}" requires a history backend, but this deployment wired no RunnerDeps.fileHistoryStore — no file history was ever recorded, so the files were NOT rewound`);
1571
- e.code = "rewind.store_unconfigured";
1572
- throw e;
1573
- }
1574
- }
1575
- else if (!handsEnabled) {
1576
- if (rewindTarget !== undefined) {
1577
- rewindNotes.push({
1578
- code: "files_env_unsupported",
1579
- message: "the file side of rewind was inert: this deployment mounts no filesystem-capable ExecutionEnv, so no file history was recorded or restored",
1580
- });
1581
- }
1582
- }
1583
- const legacyEpochTail = (entryId, form = "at-or-above") => legacyRewindEpochTail(deps, sessionId, entryId, form);
1584
- let restoredFilePaths = [];
1585
- if (rewindTarget !== undefined && fileHistoryStore !== undefined && handsEnabled) {
1586
- const historyStore = fileHistoryStore;
1587
- if (rewindBefore) {
1588
- let anchor;
1589
- if (await historyStore.canRestore(sessionId, rewindTarget)) {
1590
- anchor = rewindTarget;
1591
- }
1592
- else {
1593
- const walked = new Set();
1594
- let cursor = resumeAtBeforeParentId;
1595
- while (cursor !== null && !walked.has(cursor)) {
1596
- walked.add(cursor);
1597
- if (await historyStore.canRestore(sessionId, cursor)) {
1598
- anchor = cursor;
1599
- break;
1600
- }
1601
- cursor = (await session.getEntry(cursor))?.parentId ?? null;
1602
- }
1603
- }
1604
- if (anchor === undefined) {
1605
- const e = new Error(`rewind-files: no file-history boundary exists at or above the "before" branch point on session "${sessionId}" — cannot restore files consistent with the rewound context. ${await legacyEpochTail(rewindTarget)}`);
1606
- e.code = "rewind_snapshot.unresolvable";
1607
- throw e;
1608
- }
1609
- rewindTarget = anchor;
1610
- }
1611
- const restoreRoot = historyRoot;
1612
- const restoreSignal = spec.signal ? AbortSignal.any([abortController.signal, spec.signal]) : abortController.signal;
1613
- const restored = await historyStore.restore(sessionId, rewindTarget, executionEnv, restoreRoot, restoreSignal);
1614
- const ledgerLine = (r) => `applied ${r.filesChanged.length}, identical ${r.identicalSkipped.length}, refused ${r.refused.length}${r.refused.length > 0 ? ` (${r.refused.map((f) => `${f.path}: ${f.reason}`).join("; ")})` : ""}, failed ${r.failed.length}${r.failed.length > 0 ? ` (${r.failed.map((f) => `${f.path}: ${f.reason}`).join("; ")})` : ""}`;
1615
- if (restored.outcome === "failed") {
1616
- if (restored.error?.code === "not_found") {
1617
- const e = new Error(rewindBefore
1618
- ? `rewind-files: the resolved "before" history anchor "${rewindTarget}" disappeared before restore — files were NOT rewound`
1619
- : `rewind-files: no file-history boundary exists for entry "${rewindTarget}" on session "${sessionId}" — the working tree was NOT rewound. ${await legacyEpochTail(rewindTarget, "keyed")}`);
1620
- e.code = "rewind_snapshot.unresolvable";
1621
- throw e;
1622
- }
1623
- const e = new Error(`rewind-files restore failed (${restored.error?.code ?? "restore_failed"}): ${restored.error?.message ?? "unknown"} — ledger: ${ledgerLine(restored)}`);
1624
- e.code = "rewind.restore_failed";
1625
- throw e;
1626
- }
1627
- if (restored.outcome === "partial") {
1628
- if (spec.acceptPartialRestore === true) {
1629
- rewindNotes.push({
1630
- code: "restore_partial",
1631
- message: `file restore to entry "${rewindTarget}" was PARTIAL and was tolerated by acceptPartialRestore — ledger: ${ledgerLine(restored)}. Re-running the same restore converges (per-file idempotent).`,
1632
- });
1633
- }
1634
- else {
1635
- const e = new Error(`rewind-files: restore to entry "${rewindTarget}" was PARTIAL — ledger: ${ledgerLine(restored)}. The task is refused rather than run on a mixed-state tree (set acceptPartialRestore: true to tolerate and disclose instead); re-running the same restore converges (per-file idempotent).`);
1636
- e.code = "rewind.restore_failed";
1637
- throw e;
1638
- }
1639
- }
1640
- restoredFilePaths = restoredAbsPathsOf(restored, restoreRoot);
1641
- }
1642
- const trackFileEdit = fileHistoryEnabled
1643
- ? async (req) => {
1644
- const r = await fileHistoryStore.trackEdit(historyScope, req.key, executionEnv, historyRoot, req.signal);
1645
- if (r.ok) {
1646
- if (!r.minted)
1647
- return { ok: true };
1648
- return {
1649
- ok: true,
1650
- annul: async (proof) => {
1651
- const a = await fileHistoryStore.annulTrack(historyScope, req.key, historyRoot, proof === "verify" ? { env: executionEnv, ...(req.signal !== undefined ? { signal: req.signal } : {}) } : undefined);
1652
- if (a.ok)
1653
- return;
1654
- try {
1655
- deps.onError?.(new Error(`file-history could not retract the first-touch record for "${req.key}" after its edit failed to land (${a.error.code}: ${a.error.message}) — the path stays tracked, so a rewind to a boundary below it may converge bytes this engine never wrote`), { phase: "rewind", sessionId });
1656
- }
1657
- catch {
1658
- }
1659
- },
1660
- };
1661
- }
1662
- if (onTrackFailure === "proceed-unprotected") {
1663
- try {
1664
- deps.onError?.(new Error(`file-history first-touch backup for "${req.key}" failed (${r.error.code}: ${r.error.message}) — proceeding UNPROTECTED per RunnerDeps.onTrackFailure: the pristine promise for this path is void, and only in this process's memory (no durable trace exists, so after a process restart the next edit can still mint already-modified bytes as pristine v1)`), { phase: "rewind", sessionId });
1665
- }
1666
- catch {
1667
- }
1668
- return { ok: true };
1669
- }
1670
- return {
1671
- ok: false,
1672
- refusal: `Error (${req.tool}): the edit was refused because its first-touch file-history backup could not be persisted (${r.error.code}: ${r.error.message}). Proceeding would leave "${req.path}" with no durable pristine record — after the history store recovers, a retried edit would mint already-modified bytes as the pristine baseline. Retry when the file-history store is healthy.`,
1673
- };
1674
- }
1675
- : undefined;
1676
- const { note: noteFileEdited, snapshot: editedFilesSnapshot } = createEditedFilesLedger();
1677
- const fileHistoryBoundary = fileHistoryEnabled && historyScope === sessionId
1678
- ? createFileHistoryBoundarySeat({ store: fileHistoryStore, sessionId, env: executionEnv, root: historyRoot, onError: deps.onError })
1679
- : undefined;
1680
- const harnessRef = {};
1681
- const skillScope = new ActiveSkillScope();
1682
- const forwardSink = internals?.onForwardEvent;
1683
- const forwardEvent = forwardSink
1684
- ? (e) => {
1685
- if (e.type === "task_progress")
1686
- forwardSink(e);
1687
- else if (spec.forwardSubagentEvents === true &&
1688
- (e.type === "text_delta" || e.type === "text_end" || e.type === "reasoning_delta" || e.type === "tool_start" || e.type === "tool_end")) {
1689
- forwardSink(e);
1690
- }
1691
- }
1692
- : undefined;
1693
- const reviewRequestRef = {};
1694
- const requestReview = (opts) => {
1695
- if (reviewRequestRef.pending === undefined) {
1696
- reviewRequestRef.pending = opts?.reason !== undefined ? { reason: opts.reason } : {};
1697
- }
1698
- };
1699
- const requestStopAfterTurn = () => {
1700
- harnessRef.current?.requestStopAfterTurn();
1701
- };
1702
- const planModeRef = { active: false };
1703
- const enterPlanMode = () => {
1704
- planModeRef.active = true;
1705
- };
1706
- const subagentRetain = spec.retainSubagentSessions ? new SubagentRetainLedger(spec.retainSubagentSessions) : undefined;
1707
- const worktreeIsolation = spec.handsReadOnly !== true ? createSubagentWorktreeHelper(executionEnv, taskRootFinal) : undefined;
1708
- const liveInheritedGate = internals?.inheritedGate;
1709
- const seedInheritedGate = resume?.seed.inheritedGate;
1710
- const inheritedAncestorRules = (() => {
1711
- const seedRules = seedInheritedGate?.ancestorRules;
1712
- const liveRules = liveInheritedGate?.ancestorRules;
1713
- if (seedRules === undefined || seedRules.length === 0)
1714
- return liveRules ?? seedRules;
1715
- if (liveRules === undefined || liveRules.length === 0)
1716
- return seedRules;
1717
- const merged = seedRules.map((s) => {
1718
- const live = liveRules.find((l) => l.sessionId === s.sessionId && l.principal === s.principal);
1719
- return live !== undefined && live.rev >= s.rev ? live : s;
1720
- });
1721
- const extras = liveRules.filter((l) => !seedRules.some((s) => s.sessionId === l.sessionId && s.principal === l.principal));
1722
- return [...merged, ...extras];
1723
- })();
1724
- const shellGateRank = { off: 0, classify: 1, always: 2 };
1725
- const liveShellGate = liveInheritedGate?.shellGate;
1726
- const seedShellGate = seedInheritedGate?.shellGate;
1727
- const inheritedShellGate = liveShellGate === undefined
1728
- ? seedShellGate
1729
- : seedShellGate === undefined
1730
- ? liveShellGate
1731
- : shellGateRank[liveShellGate] >= shellGateRank[seedShellGate]
1732
- ? liveShellGate
1733
- : seedShellGate;
1734
- const inheritedParentConstraints = attachRebuiltDenialTrackers(liveInheritedGate?.parentConstraints, deps.autoMode?.denialLimit);
1735
- const autoModeIntent = autoModeSeat || liveInheritedGate?.autoModeRequested === true || seedInheritedGate?.autoModeRequested === true;
1736
- const liveAdmittedOrg = liveInheritedGate?.admittedOrgScopes;
1737
- const seedAdmittedOrg = seedInheritedGate?.admittedOrgScopes;
1738
- const inheritedAdmittedOrgScopes = liveAdmittedOrg === undefined ? seedAdmittedOrg : seedAdmittedOrg === undefined ? liveAdmittedOrg : liveAdmittedOrg.filter((s) => seedAdmittedOrg.includes(s));
1739
- const inheritedOrgGoverned = liveInheritedGate?.orgAdmissionGoverned === true || seedInheritedGate?.orgAdmissionGoverned === true;
1740
- const checkpointOwnOrgVerdict = resume?.seed.inheritedGate?.ownAdmittedOrgScopes !== undefined
1741
- ? { scopes: resume.seed.inheritedGate.ownAdmittedOrgScopes, writeScope: resume.seed.inheritedGate.ownAdmittedOrgWriteScope ?? null }
1742
- : undefined;
1743
- const refOwnOrgVerdict = internals?.ownOrgAdmissionRef?.current;
1744
- const priorOwnOrgVerdict = checkpointOwnOrgVerdict === undefined
1745
- ? refOwnOrgVerdict
1746
- : refOwnOrgVerdict === undefined
1747
- ? checkpointOwnOrgVerdict
1748
- : {
1749
- scopes: checkpointOwnOrgVerdict.scopes.filter((sc) => refOwnOrgVerdict.scopes.includes(sc)),
1750
- writeScope: checkpointOwnOrgVerdict.writeScope !== null && checkpointOwnOrgVerdict.writeScope === refOwnOrgVerdict.writeScope
1751
- ? checkpointOwnOrgVerdict.writeScope
1752
- : null,
1753
- };
1754
- const orgGovernedProvenance = deps.memoryScopeAdmission !== undefined ||
1755
- (deps.deploymentMemoryScopes !== undefined && deps.deploymentMemoryScopes.length > 0) ||
1756
- deps.compliancePostureResolver !== undefined ||
1757
- inheritedOrgGoverned ||
1758
- priorOwnOrgVerdict !== undefined;
1759
- const specShellGate = spec.shellGate ?? "off";
1760
- const effectiveShellGate = inheritedShellGate !== undefined && shellGateRank[inheritedShellGate] > shellGateRank[specShellGate] ? inheritedShellGate : specShellGate;
1761
- const ownSessionRulesRef = {};
1762
- const memoryAdmittedOrgScopesRef = { current: [] };
1763
- const ownOrgVerdictRef = { current: undefined };
1764
- const orgAdmissionCheckpointState = () => inheritedAdmittedOrgScopes !== undefined || ownOrgVerdictRef.current !== undefined || orgGovernedProvenance;
1765
- const faceCheckpointSection = () => {
1766
- if (resolvedReadFace === undefined && resume !== undefined) {
1767
- const seed = resume.seed.readFace;
1768
- return seed !== undefined ? { face: seed.face, ...(seed.denyEntries !== undefined ? { denyEntries: seed.denyEntries.map((e) => ({ ...e })) } : {}) } : undefined;
1769
- }
1770
- const face = resolvedReadFace ?? handsLessResolvedFace;
1771
- if (face === undefined)
1772
- return undefined;
1773
- if (face === "open" || readDenyAdditionsNormalized.length > 0) {
1774
- return { face, ...(readDenyAdditionsNormalized.length > 0 ? { denyEntries: readDenyAdditionsNormalized.map((e) => ({ ...e })) } : {}) };
1775
- }
1776
- return undefined;
1777
- };
1778
- const faceCheckpointState = () => faceCheckpointSection() !== undefined;
1779
- const f012CheckpointState = () => (inheritedParentConstraints?.length ?? 0) > 0 ||
1780
- seedInheritedGate?.constraintChain !== undefined ||
1781
- internals?.delegationProvenance !== undefined;
1782
- const frozenOnAsk = spec.onAsk ?? deps.onAsk;
1783
- const hookEnvSource = (ownedEnv ?? deps.executionEnv) != null ? executionEnv : undefined;
1784
- const notifyOwnHookCrash = (err) => {
1785
- try {
1786
- deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "hook", sessionId });
1787
- }
1788
- catch {
1789
- }
1790
- };
1791
- const frozenOnQuestion = spec.onQuestion ?? deps.onQuestion;
1792
- const provenanceForChildrenRef = {};
1793
- const delegationProvenanceForChildren = () => provenanceForChildrenRef.current;
1794
- const delegationSettlementRef = {};
1795
- const delegationSettlementForChildren = () => delegationSettlementRef.current;
1796
- const inheritedGateForChildren = () => {
1797
- const ancestorRules = [
1798
- ...(inheritedAncestorRules ?? []),
1799
- ...(ownSessionRulesRef.current !== undefined ? [ownSessionRulesRef.current] : []),
1800
- ];
1801
- const ownCallerPolicy = lockedPreflight.toolPolicy;
1802
- const durableMandate = runtimeCaps?.forceDurableGate === true ||
1803
- (spec.durableApproval !== undefined && !isLiveApproverSeat(frozenOnAsk));
1804
- const contentMandate = runtimeCaps?.forceDurableGate === true ||
1805
- (spec.durableApproval !== undefined && !isLiveQuestionFace(frozenOnQuestion));
1806
- const ownPreToolUse = preToolUseObservational ? undefined : hooks?.preToolUse;
1807
- const hookConstraint = ownPreToolUse !== undefined &&
1808
- !(inheritedParentConstraints ?? []).some((pc) => pc.preToolUse === ownPreToolUse &&
1809
- askApproverIdentity(pc.onAsk) === askApproverIdentity(frozenOnAsk) &&
1810
- (pc.durableMandate === true) === durableMandate &&
1811
- (pc.contentMandate === true) === contentMandate &&
1812
- pc.hookEnv === hookEnvSource &&
1813
- pc.autoMode?.decider === autoModeDecider)
1814
- ? [
1815
- {
1816
- policy: createPreToolUseConstraintPolicy(ownPreToolUse, hookEnvFace, notifyOwnHookCrash, hookTimeoutMs),
1817
- preToolUse: ownPreToolUse,
1818
- ...(hookEnvSource !== undefined ? { hookEnv: hookEnvSource } : {}),
1819
- ...(frozenOnAsk !== undefined ? { onAsk: frozenOnAsk } : {}),
1820
- ...(durableMandate ? { durableMandate: true } : {}),
1821
- ...(contentMandate ? { contentMandate: true } : {}),
1822
- ...(autoModeDecider !== undefined
1823
- ? { autoMode: { decider: autoModeDecider, ...(autoModeDenialTracking !== undefined ? { denialTracking: autoModeDenialTracking } : {}), ...(autoModeArming !== undefined ? { arming: autoModeArming } : {}) } }
1824
- : {}),
1825
- },
1826
- ]
1827
- : [];
1828
- const parentConstraints = [
1829
- ...(inheritedParentConstraints ?? []),
1830
- ...hookConstraint,
1831
- ...(ownCallerPolicy !== undefined
1832
- ? [
1833
- {
1834
- policy: ownCallerPolicy,
1835
- ...(frozenOnAsk !== undefined ? { onAsk: frozenOnAsk } : {}),
1836
- ...(durableMandate ? { durableMandate: true } : {}),
1837
- ...(contentMandate ? { contentMandate: true } : {}),
1838
- ...(autoModeDecider !== undefined
1839
- ? { autoMode: { decider: autoModeDecider, ...(autoModeDenialTracking !== undefined ? { denialTracking: autoModeDenialTracking } : {}), ...(autoModeArming !== undefined ? { arming: autoModeArming } : {}) } }
1840
- : {}),
1841
- },
1842
- ]
1843
- : []),
1844
- ];
1845
- return {
1846
- ...(ancestorRules.length > 0 ? { ancestorRules } : {}),
1847
- ...(effectiveShellGate !== "off" ? { shellGate: effectiveShellGate } : {}),
1848
- ...(autoModeIntent ? { autoModeRequested: true } : {}),
1849
- ...(parentConstraints.length > 0 ? { parentConstraints } : {}),
1850
- admittedOrgScopes: memoryAdmittedOrgScopesRef.current,
1851
- ...(orgGovernedProvenance ? { orgAdmissionGoverned: true } : {}),
1852
- };
1853
- };
1854
- let agentForkDenial;
1855
- let observersActive = false;
898
+ const { trackFileEdit, fileHistoryBoundary, restoredFilePaths, rewindNotes, fileHistoryEnabled, historyScope, historyRoot, historyFs } = await prepareFileHistory({ deps, spec, internals, executionEnv, taskRootFinal, handsEnabled, sessionId, session, resumeAtBeforeParentId, abortController });
899
+ const { noteFileEdited, editedFilesSnapshot, harnessRef, skillScope, forwardEvent, reviewRequestRef, requestReview, stopRequestedRef, requestStopAfterTurn, planModeRef, enterPlanMode, subagentRetain, worktreeIsolation } = prepareRunRefs({ spec, internals, executionEnv, taskRootFinal });
1856
900
  let resolvedReadFace;
1857
901
  let readDenyAdditionsNormalized = [];
1858
902
  let handsLessResolvedFace;
1859
- const carrierReadFace = () => resolvedReadFace ?? handsLessResolvedFace;
1860
- const fullShellReachable = handsEnabled &&
1861
- !(executionEnv instanceof StubExecutionEnv) &&
1862
- spec.handsReadOnly !== true &&
1863
- !(toolFaceSnapshot.exclude?.includes("Bash") ?? false);
1864
- let autoModeDecider;
1865
- let autoModeDenialTracking;
1866
- const { gateStopRef, stopForDenialLimit } = createDenialLimitStop({ sessionId, runId, onNotice: deps.onNotice, abort: () => { abortController.abort(); void harness.abort(); } });
1867
- let autoModeArming;
1868
- const delegationEntryCapsResolved = resolveDelegationEntryCaps(deps.delegationEntryCaps);
1869
- const enrichSpecToolCtx = (ctx) => ({
1870
- ...ctx,
1871
- delegationEntryCaps: delegationEntryCapsResolved,
1872
- reportUsage,
1873
- model: harnessRef.current?.getModel(),
1874
- thinkingLevel: harnessRef.current?.getThinkingLevel(),
1875
- principal: spec.principal,
1876
- ...(frozenOnAsk !== undefined ? { onAsk: frozenOnAsk } : {}),
1877
- ...(frozenOnQuestion !== undefined ? { onQuestion: frozenOnQuestion } : {}),
1878
- ...(spec.handsReadOnly === true ? { handsReadOnly: true } : {}),
1879
- ...(carrierReadFace() === "roots" ? { readFace: "roots" } : {}),
1880
- ...(readDenyAdditionsNormalized.length > 0 ? { readDenyPatterns: Object.freeze(readDenyAdditionsNormalized.map((e) => ({ ...e }))) } : {}),
1881
- ...(spec.interactiveTools === false ? { interactiveTools: false } : {}),
1882
- oneShot: spec.oneShot,
1883
- clientContext: spec.clientContext,
1884
- excludeTools: toolFaceSnapshot.exclude,
1885
- deferTools: toolFaceSnapshot.defer,
1886
- alwaysLoadTools: toolFaceSnapshot.alwaysLoad,
1887
- ...(toolFaceSnapshot.restoreGated !== undefined ? { restoreGatedTools: toolFaceSnapshot.restoreGated } : {}),
1888
- promptProfile,
1889
- ...(spec.additionalDirectories !== undefined ? { additionalDirectories: Object.freeze([...spec.additionalDirectories]) } : {}),
1890
- ...(spec.additionalReadDirectories !== undefined ? { additionalReadDirectories: Object.freeze([...spec.additionalReadDirectories]) } : {}),
1891
- ...(spec.envFacts !== undefined ? { envFacts: { ...spec.envFacts } } : {}),
1892
- ...(spec.memoryPersistenceCapable !== undefined ? { memoryPersistenceCapable: spec.memoryPersistenceCapable } : {}),
1893
- get memoryCaptureOptedOut() {
1894
- return memoryEngineSession?.captureOptOut?.optedOut() ?? false;
1895
- },
1896
- get memoryCaptureIndeterminate() {
1897
- return memoryEngineSession?.captureOptOut?.indeterminate() ?? false;
1898
- },
1899
- get memoryCaptureControlDir() {
1900
- return memoryEngineSession?.engine.controlPlaneDir;
1901
- },
1902
- get memoryCaptureAncestors() {
1903
- const ctl = memoryEngineSession?.engine.controlPlaneDir;
1904
- return [...(internals?.memoryCaptureAncestors ?? []), { sessionId, ...(ctl !== undefined ? { controlDir: ctl } : {}) }];
1905
- },
1906
- getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
1907
- parentCwd: taskRootFinal,
1908
- ...(fileHistoryEnabled ? { fileHistoryLineage: { scope: historyScope, root: historyRoot, fs: historyFs } } : {}),
1909
- reminderMark,
1910
- reminderDisclosureCounts,
1911
- ...(centerAdoption !== undefined ? { centerArtifactDigest: centerAdoption.artifact.artifactDigest } : {}),
1912
- ...(centerAdoption?.sourceRevision !== undefined ? { centerSourceRevision: centerAdoption.sourceRevision } : {}),
1913
- activeSkillScope: () => skillScope.active(),
1914
- inheritedGateForChildren,
1915
- delegationProvenanceForChildren,
1916
- delegationSettlement: delegationSettlementForChildren,
1917
- ...(autoModeDecider ? { autoModeReview: { decider: autoModeDecider } } : {}),
1918
- ...(spec.durableApproval !== undefined ? { durableApprovalForChildren: { ...spec.durableApproval } } : {}),
1919
- ...(spec.checkpointStore === null ? { checkpointStoreDisabledForChildren: true } : {}),
1920
- taskId: hostTaskId,
1921
- ...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}),
1922
- ...(internals?.parentSessionId !== undefined ? { parentSessionId: internals.parentSessionId } : {}),
1923
- ...(internals?.rootSessionId !== undefined ? { rootSessionId: internals.rootSessionId } : {}), ...(internals?.placementRoot !== undefined ? { placementRoot: internals.placementRoot } : {}),
1924
- ...(internals?.explicitAgentName !== undefined ? { spawnedAgentName: internals.explicitAgentName } : {}),
1925
- ...(internals?.peerSelfRef !== undefined ? { peerSelfRef: internals.peerSelfRef } : {}),
1926
- ...(internals?.peerInboundChainRef !== undefined ? { peerInboundChainRef: internals.peerInboundChainRef } : {}),
1927
- ...(deps.rosterStore !== undefined ? { roster: deps.rosterStore } : {}),
1928
- ...(sessions.fork !== undefined
1929
- ? {
1930
- hostSessionFork: async () => {
1931
- const forked = (await sessions.fork(sessionId, spec.principal ?? null)) ?? null;
1932
- if (forked === null)
1933
- return null;
1934
- return {
1935
- sessionId: forked,
1936
- release: async () => {
1937
- await sessions.release?.(forked);
1938
- },
1939
- };
1940
- },
1941
- }
1942
- : {}),
1943
- sessionId,
1944
- ...(spec.backgroundScope !== undefined ? { backgroundScope: spec.backgroundScope } : {}),
1945
- ...(resolvedInteractionPosture !== undefined ? { interactionPosture: resolvedInteractionPosture } : {}),
1946
- ...(deps.onBackgroundChildEvent ? { onBackgroundChildEvent: deps.onBackgroundChildEvent } : {}),
1947
- ...(internals?.onTaskNotification ? { onTaskNotification: internals.onTaskNotification } : {}),
1948
- ...(forwardEvent ? { forwardEvent } : {}),
1949
- ...(internals?.onSubagentSpawn ? { onSubagentSpawn: internals.onSubagentSpawn } : {}),
1950
- ...(subagentRetain ? { subagentRetain } : {}),
1951
- ...(worktreeIsolation ? { worktreeIsolation } : {}),
1952
- ...(internals?.insideFork === true ? { insideFork: true } : {}),
1953
- ...(agentForkDenial !== undefined ? { forkAccess: { denied: agentForkDenial } } : {}),
1954
- ...(observersActive ? { observersAllowed: true } : {}),
1955
- requestReview,
1956
- requestStopAfterTurn,
1957
- ...(spec.enablePlanMode === true ? { enterPlanMode } : {}),
1958
- });
1959
- const tools = (spec.tools ?? []).map((t) => {
1960
- if (isDefineToolProduct(t)) {
1961
- return maybeOffload(t, t);
1962
- }
1963
- return maybeOffload(defineTool({
1964
- ...t,
1965
- execute: (args, ctx) => t.execute(args, enrichSpecToolCtx(ctx)),
1966
- }), t);
1967
- });
1968
- const blockedRef = {};
1969
- if (spec.enableBlockedReport !== false) {
1970
- tools.push(createReportBlockedTool(blockedRef));
1971
- }
1972
- if (!(spec.tools ?? []).some((t) => t.name === REPORT_FINDINGS_TOOL_NAME || t.aliases?.includes(REPORT_FINDINGS_TOOL_NAME))) {
1973
- tools.push(createReportFindingsTool());
1974
- }
1975
- if (spec.enablePlanMode === true && spec.interactiveTools !== false) {
1976
- const planReviewFace = resolveCheckpointStore(spec, deps) !== undefined;
1977
- if (spec.interactiveTools === true || planReviewFace) {
1978
- tools.push(defineTool(createPresentPlanTool(requestReview)));
1979
- if (planReviewFace) {
1980
- tools.push(defineTool(createEnterPlanModeTool(enterPlanMode)));
1981
- }
1982
- }
1983
- }
1984
- let runtimeCaps;
1985
- let runtimeCapsFaulted = false;
1986
- if (deps.runtimeCapsResolver) {
1987
- try {
1988
- runtimeCaps = (await deps.runtimeCapsResolver(spec.principal)) ?? undefined;
1989
- }
1990
- catch (err) {
1991
- deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "config", sessionId });
1992
- runtimeCaps = { allowWorkflows: false, allowFork: false, autoMode: false };
1993
- runtimeCapsFaulted = true;
1994
- }
1995
- const screened = screenAutoModeCap(runtimeCaps, deps.onError, sessionId);
1996
- runtimeCaps = screened.caps;
1997
- runtimeCapsFaulted ||= screened.faulted;
1998
- }
1999
- let complianceDenies = new Set();
2000
- let complianceDegraded = false;
2001
- if (deps.compliancePostureResolver) {
2002
- try {
2003
- const posture = (await deps.compliancePostureResolver(spec.principal)) ?? undefined;
2004
- if (posture !== undefined)
2005
- complianceDenies = resolveComplianceDenies(posture);
2006
- }
2007
- catch (err) {
2008
- deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "config", sessionId });
2009
- complianceDenies = new Set(COMPLIANCE_CAPABILITIES);
2010
- complianceDegraded = true;
2011
- }
2012
- const complianceRefuse = (capability, requested) => {
2013
- const e = new Error(`${requested} is denied for this principal by the compliance posture (capability "${capability}"` +
2014
- `${complianceDegraded ? "; the posture resolver is currently failing, so every managed capability is denied fail-closed" : ""}) — ` +
2015
- `the task is refused rather than silently narrowed.`);
2016
- e.code = complianceDegraded ? "config.compliance_required" : "config.compliance_denied";
2017
- throw e;
2018
- };
2019
- if (complianceDenies.has("mcp_servers") && lockedPreflight.mcp?.length) {
2020
- complianceRefuse("mcp_servers", "TaskSpec.mcp (MCP server materialization)");
2021
- }
2022
- if (complianceDenies.has("workflows") && spec.selfOrchestration === true) {
2023
- complianceRefuse("workflows", "TaskSpec.selfOrchestration (workflow self-orchestration)");
2024
- }
2025
- if (complianceDenies.has("web_fetch")) {
2026
- const webTool = (spec.tools ?? []).find((t) => t.name === WEB_FETCH_TOOL_NAME || (t.aliases ?? []).includes(WEB_FETCH_TOOL_NAME));
2027
- if (webTool !== undefined) {
2028
- complianceRefuse("web_fetch", `TaskSpec.tools["${webTool.name}"] (the WebFetch tool face)`);
2029
- }
2030
- }
2031
- }
2032
- agentForkDenial = forkGovernanceDenial(spec.enableFork, runtimeCaps?.allowFork);
2033
- observersActive = runtimeCaps?.allowObservers === true;
2034
- const autoModeArmReason = autoModeArmReasonOf(autoModeIntent, deps.autoMode !== undefined, runtimeCaps?.autoMode, runtimeCapsFaulted);
2035
- if (autoModeIntent && deps.autoMode !== undefined && runtimeCaps?.autoMode !== false) {
2036
- const am = deps.autoMode;
2037
- let classifierModel;
2038
- try {
2039
- classifierModel = resolveTaskModel({ modelRole: "classifier" }, deps).model;
2040
- }
2041
- catch {
2042
- classifierModel = model;
2043
- }
2044
- if (await derivedRouteFallsBack({ seat: "auto-mode-classifier", derived: classifierModel, primary: model, brain: deps.brain, getApiKeyAndHeaders: spec.getApiKeyAndHeaders, onNotice: deps.onNotice, sessionId, runId })) {
2045
- classifierModel = model;
2046
- }
2047
- const classifierLaneRule = peerLaneActive && peerSendMessageBuiltIn;
2048
- const classifierSystemPrompt = buildAutoModePrompt(classifierLaneRule ? { ...am, crossSessionMessagesRule: CROSS_SESSION_CLASSIFIER_RULE } : am);
2049
- const classifierRuntime = brainToRuntime(deps.brain);
2050
- autoModeDenialTracking = createAutoModeDenialTracker(am.denialLimit);
2051
- autoModeDecider = createAutoModeDecider({
2052
- ...(am.timeoutMs !== undefined ? { timeoutMs: am.timeoutMs } : {}),
2053
- ...(am.failureThreshold !== undefined ? { failureThreshold: am.failureThreshold } : {}),
2054
- ...(am.onBreakerOpen !== undefined ? { onBreakerOpen: am.onBreakerOpen } : {}),
2055
- classify: async (input, signal) => {
2056
- const ctx = await session.buildContext();
2057
- const known = ctx.messages.filter((m) => !isSyntheticApiErrorMessage(m) &&
2058
- (m.role === "user" || m.role === "assistant" || m.role === "toolResult"));
2059
- const userPrompt = renderAutoModeWindow(known, am.window) + renderAutoModeAction(input);
2060
- const classifierAuth = await spec.getApiKeyAndHeaders?.(classifierModel);
2061
- const response = await classifierRuntime.completeSimple(classifierModel, { systemPrompt: classifierSystemPrompt, messages: [{ role: "user", content: userPrompt, timestamp: Date.now() }] }, {
2062
- signal,
2063
- ...(classifierAuth?.apiKey !== undefined ? { apiKey: classifierAuth.apiKey } : {}),
2064
- ...(classifierAuth?.headers !== undefined ? { headers: classifierAuth.headers } : {}),
2065
- });
2066
- return response.content
2067
- .filter((c) => c.type === "text")
2068
- .map((c) => c.text)
2069
- .join("");
2070
- },
2071
- });
2072
- if (am.persistArming === true)
2073
- autoModeArming = mintPersistedArming(am, classifierLaneRule, classifierSystemPrompt, deps.onError, sessionId);
2074
- }
2075
- const deploymentWorkflowReady = runnerSelf !== undefined && isSelfOrchestrationActive(spec, deps);
2076
- const selfOrchestrationActive = deploymentWorkflowReady && runtimeCaps?.allowWorkflows !== false;
2077
- if (deploymentWorkflowReady && runtimeCaps?.allowWorkflows === false) {
2078
- deps.onError?.(new Error(`self-orchestration DENIED for principal "${spec.principal ?? ""}" by runtimeCaps.allowWorkflows=false ` +
2079
- `(per-principal entitlement governance, not a misconfiguration — the run_workflow tool is not mounted)`), { phase: "config", sessionId });
2080
- }
2081
- let workflowToolsActive = false;
2082
- let workflowSizeGuideline;
2083
- if (selfOrchestrationActive && runnerSelf && deps.workflowScriptRunner && deps.workflowGovernanceBaseline) {
2084
- workflowToolsActive = true;
2085
- const currentSizeGuideline = () => resolveWorkflowSizeGuideline(deps.workflowLimits?.sizeGuideline).size;
2086
- workflowSizeGuideline = { legGuideline: currentSizeGuideline(), current: currentSizeGuideline };
2087
- toolEffects.set(RUN_WORKFLOW_TOOL_NAME, "write");
2088
- tools.push(await createRunWorkflowTool({
2089
- runner: runnerSelf,
2090
- scriptRunner: deps.workflowScriptRunner,
2091
- governanceBaseline: deps.workflowGovernanceBaseline,
2092
- parentExcludeTools: toolFaceSnapshot.exclude,
2093
- parentDeferTools: toolFaceSnapshot.defer,
2094
- parentAlwaysLoadTools: toolFaceSnapshot.alwaysLoad,
2095
- ...(toolFaceSnapshot.restoreGated !== undefined ? { parentRestoreGatedTools: toolFaceSnapshot.restoreGated } : {}),
2096
- parentPromptProfile: promptProfile,
2097
- models: deps.models,
2098
- agents: deps.agents,
2099
- builtinAgents: deps.builtinAgents,
2100
- store: deps.workflowRunStore,
2101
- journalStore: deps.workflowJournalStore,
2102
- ...(deps.onBackgroundChildEvent ? { onBackgroundChildEvent: deps.onBackgroundChildEvent } : {}),
2103
- scriptStore: deps.workflowScriptStore,
2104
- builtinWorkflows: deps.builtinWorkflows,
2105
- onAgentSpawn: deps.onWorkflowAgentSpawn,
2106
- scope: taskScope,
2107
- notifier: deps.workflowCompletionNotifier,
2108
- originatingSessionId: sessionId,
2109
- rootSessionId: internals?.rootSessionId ?? sessionId, ...(internals?.placementRoot !== undefined ? { placementRoot: internals.placementRoot } : {}),
2110
- taskRegistry: defaultTaskRegistry,
2111
- taskNotification: internals?.onTaskNotification,
2112
- taskOwner: hostTaskId,
2113
- limits: deps.workflowLimits,
2114
- sourceTaskId: hostTaskId,
2115
- parentModel: () => harnessRef.current?.getModel(),
2116
- ...(spec.getApiKeyAndHeaders !== undefined ? { parentGetApiKeyAndHeaders: spec.getApiKeyAndHeaders } : {}),
2117
- ...(frozenOnAsk !== undefined ? { parentOnAsk: frozenOnAsk } : {}),
2118
- principal: spec.principal,
2119
- oneShot: spec.oneShot,
2120
- ...(resolvedInteractionPosture !== undefined ? { parentInteractionPosture: resolvedInteractionPosture } : {}),
2121
- parentMemoryCaptureState: () => {
2122
- const ctl = memoryEngineSession?.engine.controlPlaneDir;
2123
- const co = memoryEngineSession?.captureOptOut;
2124
- return assembleParentCaptureState(co?.optedOut() ?? false, co?.indeterminate() ?? false, ctl, [...(internals?.memoryCaptureAncestors ?? []), { sessionId, ...(ctl !== undefined ? { controlDir: ctl } : {}) }]);
2125
- },
2126
- autoModeReview: () => (autoModeDecider !== undefined ? { decider: autoModeDecider } : undefined),
2127
- workflowDepth: internals?.workflowDepth,
2128
- parentCwd: taskRootFinal,
2129
- parentThinking: () => harnessRef.current?.getThinkingLevel() ?? thinking,
2130
- parentReadFace: () => carrierReadFace(),
2131
- parentReadDenyPatterns: () => (readDenyAdditionsNormalized.length > 0 ? readDenyAdditionsNormalized : undefined),
2132
- ...(spec.handsReadOnly === true ? { parentHandsReadOnly: true } : {}), ...(spec.interactiveTools === false ? { parentInteractiveTools: false } : {}),
2133
- onNotice: deps.onNotice,
2134
- parentCheckpointStoreDisabled: spec.checkpointStore === null,
2135
- parentCenterArtifactDigest: () => centerAdoption?.artifact.artifactDigest,
2136
- parentCenterSourceRevision: () => centerAdoption?.sourceRevision,
2137
- ...(forwardEvent ? { forwardEvent } : {}),
2138
- inheritedGateForChildren,
2139
- }));
2140
- }
903
+ 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, 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: () => centerAdoption, memoryEngineSession: () => memoryEngineSession });
904
+ const { tools, 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, toolEffects, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, harnessRef, frozenOnAsk, carrierReadFace, forwardEvent, inheritedGateForChildren, enrichSpecToolCtx, maybeOffload, requestReview, enterPlanMode, autoModeIntent, peerLaneActive, peerSendMessageBuiltIn, runnerSelf, memoryEngineSession: () => memoryEngineSession, centerAdoption: () => centerAdoption, readDenyAdditionsNormalized: () => readDenyAdditionsNormalized });
2141
905
  const suspendRef = {};
2142
906
  const reviewRef = {};
2143
907
  const remoteEnvFailures = [];
@@ -2170,191 +934,36 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2170
934
  resourceSpentMicroUsd: priorLedger?.spentMicroUsd ?? 0,
2171
935
  suspendCount: priorSuspendCount,
2172
936
  };
2173
- let worktreeSessionRef;
2174
937
  let suspendForResource;
2175
938
  let suspendForPlatformLimit;
2176
939
  let suspendForReview;
2177
- const outputRef = {};
2178
- if (resume?.seed.outputRef) {
2179
- outputRef.value = resume.seed.outputRef.value;
2180
- outputRef.set = resume.seed.outputRef.set;
2181
- }
2182
- if (spec.outputSchema) {
2183
- if ((spec.tools ?? []).some((t) => t.name === OUTPUT_TOOL_NAME)) {
2184
- const e = new Error(`Tool name "${OUTPUT_TOOL_NAME}" is reserved when TaskSpec.outputSchema is set.`);
2185
- e.code = "config.reserved_tool_name";
2186
- throw e;
2187
- }
2188
- const compiled = compileOutputSchema(spec.outputSchema);
2189
- if ("error" in compiled) {
2190
- const e = new Error(`TaskSpec.outputSchema is not a valid JSON Schema: ${compiled.error}`);
2191
- e.code = "config.invalid_output_schema";
2192
- throw e;
2193
- }
2194
- if (!compiled.strict) {
2195
- try {
2196
- deps.onError?.(new Error(`structured-output: strict schema derivation fell back to non-strict (${compiled.fallbackReason}) — the schema is served to the model as-is`), { phase: "config", sessionId });
2197
- }
2198
- catch {
2199
- }
2200
- }
2201
- tools.push(createOutputTool(outputRef, spec.outputSchema, compiled.strict ? compiled.modelSchema : undefined));
2202
- }
2203
- mcp = lockedPreflight.mcp?.length
2204
- ? await materializeMcpTools(lockedPreflight.mcp, spec.principal, deps.onElicit, deps.mcpImageResizer, { reminderMark, counts: reminderDisclosureCounts }, mcpRevocationWiring(deps, runId))
2205
- : { tools: [], toolAxes: [], warnings: [], serverInstructions: [], instructionsDelta: { pendingAdds: [], pendingRemovals: [] }, droppedTools: [], statuses: [], refresh: async () => [], dispose: async () => { } };
2206
- for (const w of mcp.warnings)
2207
- deps.onError?.(w, { phase: "mcp", sessionId });
2208
- for (const d of mcp.droppedTools) {
2209
- deps.onError?.(new Error(`MCP tool dropped at intake — ${d.tool} (server "${d.server}"): ${d.reason}`), { phase: "mcp", sessionId });
2210
- }
2211
- {
2212
- const callerNames = new Set((spec.tools ?? []).flatMap((t) => [t.name, ...(t.aliases ?? [])]));
2213
- const clash = mcp.tools.find((t) => [t.name, ...(t.aliases ?? [])].some((name) => callerNames.has(name)));
2214
- if (clash) {
2215
- await mcp.dispose();
2216
- const e = new Error(`Tool name "${clash.name}" is reserved by an injected MCP tool — a caller tool of the same name would silently shadow it.`);
2217
- e.code = "config.reserved_tool_name";
2218
- throw e;
2219
- }
2220
- }
2221
- tools.push(...mcp.tools.map((t) => remoteToolOffload(t)));
2222
- const rebuildHarnessToolsRef = {};
2223
- const contentOriginWrapRef = {};
2224
- const toolCallGateArmedRef = { armed: false };
2225
- if (lockedPreflight.mcp?.length) {
2226
- tools.push({
2227
- name: "RefreshMcpTools",
2228
- label: "RefreshMcpTools",
2229
- description: "Refresh the tool list of connected MCP servers. Never dials or re-dials connections — it only re-reads the tool list over the existing connection. The refreshed tools are available immediately; you can call them on your next step. Use when a server's expected tool is missing, or the tool list looks stale after a connection recovered. Omit `server` to refresh every connected server.",
2230
- parameters: Type.Object({
2231
- server: Type.Optional(Type.String({ description: "Name of a single MCP server to refresh; omit to refresh all connected servers." })),
2232
- }),
2233
- execute: async (_toolCallId, params) => {
2234
- const serverArg = params?.server;
2235
- const results = await mcp.refresh(typeof serverArg === "string" && serverArg.length > 0 ? serverArg : undefined);
2236
- const lines = [];
2237
- let changed = false;
2238
- let anyActiveFailure = false;
2239
- for (const r of results) {
2240
- if (r.status !== "refreshed" || r.tools === undefined) {
2241
- lines.push(`${r.server}: ${r.status}${r.error !== undefined ? ` (${r.error})` : ""}`);
2242
- if (r.status === "failed")
2243
- anyActiveFailure = true;
2244
- continue;
2245
- }
2246
- const excludedSet = new Set(toolFaceSnapshot.exclude ?? []);
2247
- const pushable = r.tools.filter((t) => !excludedSet.has(t.name));
2248
- const excludedNow = r.tools.filter((t) => excludedSet.has(t.name)).map((t) => t.name);
2249
- const domainSnapshot = (m) => new Map([...m].filter(([name]) => name.startsWith(r.prefix)));
2250
- const restoreDomain = (m, snap) => {
2251
- for (const name of [...m.keys()])
2252
- if (name.startsWith(r.prefix))
2253
- m.delete(name);
2254
- for (const [name, v] of snap)
2255
- m.set(name, v);
2256
- };
2257
- const priorDomainEffects = domainSnapshot(toolEffects);
2258
- const priorDomainNegatives = domainSnapshot(axisExplicitNegatives);
2259
- for (const name of priorDomainEffects.keys())
2260
- toolEffects.delete(name);
2261
- for (const name of priorDomainNegatives.keys())
2262
- axisExplicitNegatives.delete(name);
2263
- try {
2264
- foldProtocolAxes((r.axes ?? []).filter((a) => !excludedSet.has(a.name)), "MCP");
2265
- }
2266
- catch (foldErr) {
2267
- restoreDomain(toolEffects, priorDomainEffects);
2268
- restoreDomain(axisExplicitNegatives, priorDomainNegatives);
2269
- lines.push(`${r.server}: failed (${foldErr instanceof Error ? foldErr.message : String(foldErr)})`);
2270
- anyActiveFailure = true;
2271
- continue;
2272
- }
2273
- let domainAnchor = -1;
2274
- for (let i = tools.length - 1; i >= 0; i--) {
2275
- const t = tools[i];
2276
- if (t.name.startsWith(r.prefix)) {
2277
- domainAnchor = i;
2278
- tools.splice(i, 1);
2279
- }
2280
- }
2281
- const refreshedMounts = pushable.map((t) => remoteToolOffload(t));
2282
- if (domainAnchor >= 0)
2283
- tools.splice(domainAnchor, 0, ...refreshedMounts);
2284
- else
2285
- tools.push(...refreshedMounts);
2286
- changed = true;
2287
- const detail = [];
2288
- const shownAdded = r.added.filter((n) => !excludedSet.has(n));
2289
- if (shownAdded.length > 0)
2290
- detail.push(`added: ${shownAdded.join(", ")}`);
2291
- if (r.removed.length > 0)
2292
- detail.push(`removed: ${r.removed.join(", ")}`);
2293
- if (excludedNow.length > 0)
2294
- detail.push(`excluded by deployment config (not mounted): ${excludedNow.join(", ")}`);
2295
- if ((r.dropped?.length ?? 0) > 0)
2296
- detail.push(`dropped: ${r.dropped.map((d) => `${d.tool} (${d.reason.length > 90 ? `${d.reason.slice(0, 90)}…` : d.reason})`).join("; ")}`);
2297
- lines.push(`${r.server}: refreshed — ${pushable.length} tool${pushable.length === 1 ? "" : "s"}${detail.length > 0 ? ` (${detail.join("; ")})` : ""}`);
2298
- }
2299
- if (changed)
2300
- await rebuildHarnessToolsRef.current?.();
2301
- if (changed && !toolCallGateArmedRef.armed && (irreversibleTools.size > 0 || egressTools.size > 0)) {
2302
- lines.push("WARNING: refreshed tools declare irreversible/egress safety hints, but this task started with no approval gate registered (no policy/hooks and no gated tools at start) — these hints cannot arm a gate mid-task (RB-46); the refreshed tools run ungated on this deployment shape.");
2303
- }
2304
- const text = lines.length === 0 ? "No connected MCP servers to refresh." : lines.join("\n");
2305
- return {
2306
- content: [{ type: "text", text }],
2307
- details: { results },
2308
- terminate: false,
2309
- ...(anyActiveFailure && !changed ? { isError: true } : {}),
2310
- };
2311
- },
2312
- });
2313
- toolEffects.set("RefreshMcpTools", "read");
2314
- }
2315
- const foldProtocolAxes = (axes, protocolLabel) => {
2316
- for (const axis of axes) {
2317
- if (axis.irreversibility === "always") {
2318
- irreversibilityTier.set(axis.name, "always");
2319
- irreversibleTools.add(axis.name);
2320
- }
2321
- if (axis.irreversibility === "never") {
2322
- axisExplicitNegatives.set(axis.name, { ...axisExplicitNegatives.get(axis.name), irreversible: false });
2323
- }
2324
- if (axis.egress === false) {
2325
- axisExplicitNegatives.set(axis.name, { ...axisExplicitNegatives.get(axis.name), egress: false });
2326
- }
2327
- if (axis.egress) {
2328
- if (axis.effect !== undefined && axis.effect !== "write") {
2329
- const e = new Error(`${protocolLabel} tool "${axis.name}" resolves to egress:true with effect:"${axis.effect}" — an egress tool (external write) must have effect:"write". Clear egress (toolAxes egress:false) if it is a pure read, or set effect:"write".`);
2330
- e.code = "config.egress_requires_write_effect";
2331
- throw e;
2332
- }
2333
- egressTools.add(axis.name);
2334
- }
2335
- if (!toolEffects.has(axis.name))
2336
- toolEffects.set(axis.name, axis.effect ?? "write");
2337
- }
2338
- };
2339
- foldProtocolAxes(mcp.toolAxes, "MCP");
2340
- a2a = spec.a2a?.length
2341
- ? await materializeA2aTools(spec.a2a, spec.principal, abortController.signal)
2342
- : { tools: [], toolAxes: [], warnings: [], statuses: [], refresh: async () => [], dispose: async () => { } };
2343
- for (const w of a2a.warnings)
2344
- deps.onError?.(w, { phase: "a2a", sessionId });
2345
- {
2346
- const callerNames = new Set((spec.tools ?? []).flatMap((t) => [t.name, ...(t.aliases ?? [])]));
2347
- const clash = a2a.tools.find((t) => callerNames.has(t.name));
2348
- if (clash) {
2349
- await a2a.dispose();
2350
- await mcp.dispose();
2351
- const e = new Error(`Tool name "${clash.name}" is reserved by an injected A2A tool — a caller tool of the same name would silently shadow it.`);
940
+ const outputRef = {};
941
+ if (resume?.seed.outputRef) {
942
+ outputRef.value = resume.seed.outputRef.value;
943
+ outputRef.set = resume.seed.outputRef.set;
944
+ }
945
+ if (spec.outputSchema) {
946
+ if ((spec.tools ?? []).some((t) => t.name === OUTPUT_TOOL_NAME)) {
947
+ const e = new Error(`Tool name "${OUTPUT_TOOL_NAME}" is reserved when TaskSpec.outputSchema is set.`);
2352
948
  e.code = "config.reserved_tool_name";
2353
949
  throw e;
2354
950
  }
951
+ const compiled = compileOutputSchema(spec.outputSchema);
952
+ if ("error" in compiled) {
953
+ const e = new Error(`TaskSpec.outputSchema is not a valid JSON Schema: ${compiled.error}`);
954
+ e.code = "config.invalid_output_schema";
955
+ throw e;
956
+ }
957
+ if (!compiled.strict) {
958
+ try {
959
+ onceLedger.onError(new Error(`structured-output: strict schema derivation fell back to non-strict (${compiled.fallbackReason}) — the schema is served to the model as-is`), { phase: "config", sessionId });
960
+ }
961
+ catch {
962
+ }
963
+ }
964
+ tools.push(createOutputTool(outputRef, spec.outputSchema, compiled.strict ? compiled.modelSchema : undefined));
2355
965
  }
2356
- tools.push(...a2a.tools.map((t) => remoteToolOffload(t)));
2357
- foldProtocolAxes(a2a.toolAxes, "A2A");
966
+ const { mcp, a2a, rebuildHarnessToolsRef, contentOriginWrapRef, toolCallGateArmedRef } = await prepareProtocolTools({ lockedPreflight, spec, deps, reminderMark, reminderDisclosureCounts, runId, sessionId, onceLedger, tools, remoteToolOffload, toolFaceSnapshot, toolEffects, axisExplicitNegatives, irreversibilityTier, irreversibleTools, egressTools, abortController, rollback });
2358
967
  const callIssuedAtRef = {};
2359
968
  const memoryWriteGateRef = {};
2360
969
  const handsReadFaceInput = { handsEnabled, executionEnv, taskRootFinal, rebaseRestoredPath, resume, session, sessionId, hostTaskId, taskScope, fullShellReachable, effectiveShellGate, toolFaceSnapshot, model, spec, deps, internals, memoryWriteGateRef, firstPartyOffload, tools, egressTools, irreversibilityTier, irreversibleTools, reversibilityProbes, reminderMark, reminderDisclosureCounts, ...(trackFileEdit !== undefined ? { trackFileEdit } : {}), onFileEdited: noteFileEdited, ...(restoredFilePaths.length > 0 ? { restoredFilePaths } : {}) };
@@ -2365,191 +974,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2365
974
  handsLessResolvedFace = handsReadFace.handsLessResolvedFace;
2366
975
  shellGatedBash = handsReadFace.shellGatedBash;
2367
976
  shellGatedMonitor = handsReadFace.shellGatedMonitor;
2368
- const delegationSurfaceActive = backgroundTaskToolsActive || workflowToolsActive;
2369
- if (delegationSurfaceActive || internals?.parentNotify !== undefined || peerLaneActive) {
2370
- if (delegationSurfaceActive) {
2371
- toolEffects.set("TaskOutput", "read");
2372
- toolEffects.set("TaskStop", "write");
2373
- tools.push(firstPartyOffload(createTaskOutputTool({ registry: defaultTaskRegistry, owner: hostTaskId, scope: taskScope, sessionId, workflowStore: deps.workflowRunStore, agentStore: deps.backgroundAgentStore, notificationWired: internals?.onTaskNotification !== undefined, oneShot: spec.oneShot, toolResultStore: offloadStore })), firstPartyOffload(createTaskStopTool({ registry: defaultTaskRegistry, owner: hostTaskId, scope: taskScope, sessionId, workflowStore: deps.workflowRunStore, agentStore: deps.backgroundAgentStore })));
2374
- }
2375
- if (runnerSelf && !(spec.tools ?? []).some((t) => t.name === SEND_MESSAGE_TOOL_NAME)) {
2376
- toolEffects.set(SEND_MESSAGE_TOOL_NAME, "write");
2377
- axisExplicitNegatives.set(SEND_MESSAGE_TOOL_NAME, { ...axisExplicitNegatives.get(SEND_MESSAGE_TOOL_NAME), egress: false });
2378
- const delegationForRevive = (spec.tools ?? []).find((t) => t.agentListing !== undefined);
2379
- const reviveSpawn = deps.backgroundAgentStore !== undefined && deps.mailboxStore !== undefined ? buildTier3ReviveSpawn(delegationForRevive, enrichSpecToolCtx) : undefined;
2380
- tools.push(firstPartyOffload(createSendMessageTool({
2381
- runner: runnerSelf,
2382
- registry: defaultTaskRegistry,
2383
- ...(subagentRetain ? { retain: subagentRetain } : {}),
2384
- owner: hostTaskId,
2385
- scope: taskScope,
2386
- ...(sessionId !== undefined ? { sessionId } : {}),
2387
- ...(internals?.onTaskNotification !== undefined ? { notify: internals.onTaskNotification } : {}), ...(spec.oneShot !== undefined ? { oneShot: spec.oneShot } : {}), retrievalToolMounted: delegationSurfaceActive,
2388
- ...(internals?.onSubagentSpawn !== undefined ? { sink: internals.onSubagentSpawn } : {}),
2389
- ...(internals?.parentNotify !== undefined
2390
- ? { uplink: internals.parentNotify, ...(internals.parentPeerRef !== undefined ? { uplinkRecipient: internals.parentPeerRef } : {}) }
2391
- : {}),
2392
- ...(internals?.explicitAgentName !== undefined ? { senderName: internals.explicitAgentName } : {}),
2393
- ...(internals?.parentRetainLedger !== undefined ? { siblingRetain: internals.parentRetainLedger } : {}),
2394
- ...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}),
2395
- ...(internals?.parentSessionId !== undefined ? { parentSessionId: internals.parentSessionId } : {}),
2396
- enrichCtx: enrichSpecToolCtx,
2397
- ...(deps.rosterStore !== undefined ? { roster: deps.rosterStore } : {}),
2398
- ...(deps.peerAdmission !== undefined ? { admission: deps.peerAdmission } : {}),
2399
- ...(deps.onNotice !== undefined ? { onNotice: deps.onNotice } : {}),
2400
- ...(internals?.peerSelfRef !== undefined ? { peerSelf: internals.peerSelfRef } : {}),
2401
- ...(internals?.peerInboundChainRef !== undefined ? { peerInbound: internals.peerInboundChainRef } : {}),
2402
- ...(deps.onBackgroundChildEvent ? { onBackgroundChildEvent: deps.onBackgroundChildEvent } : {}),
2403
- ...(deps.backgroundAgentStore !== undefined ? { agentStore: deps.backgroundAgentStore } : {}),
2404
- ...(deps.mailboxStore !== undefined ? { mailbox: deps.mailboxStore } : {}),
2405
- ...(reviveSpawn !== undefined ? { reviveSpawn } : {}),
2406
- ...(peerLaneActive ? peerLaneSendMessageSeats({ peerDirectory: deps.peerDirectory, sessionId, scope: taskScope, ...(internals?.explicitAgentName !== undefined ? { name: internals.explicitAgentName } : {}), refs: peerLaneRefs, listingMounted: listAgentsMountable({ exclude: toolFaceSnapshot.exclude, specTools: spec.tools ?? [] }) }) : {}),
2407
- onNotifyError: (f) => emitTrace(deps.tracer, () => ({ kind: "observer.notify_failed", version: 1, taskId: hostTaskId, site: f.site, message: f.error.message, ts: Date.now() })),
2408
- onTranscriptIntegrityGap: (handle, scope) => deliverEngineNotice(deps.onNotice, {
2409
- code: "delegation.transcript_integrity",
2410
- message: `delegation transcript integrity: agent ${handle}'s durable row binds a transcript session the session store attests is gone — the declared transcript durability is being contradicted (check the session store wiring/retention)`,
2411
- detail: { handle, ...(scope !== undefined ? { scope } : {}) },
2412
- }),
2413
- })));
2414
- if (delegationSurfaceActive && !(spec.tools ?? []).some((t) => t.name === AGENT_TRANSCRIPT_TOOL_NAME)) {
2415
- tools.push(firstPartyOffload(createAgentTranscriptTool({
2416
- runner: runnerSelf,
2417
- registry: defaultTaskRegistry,
2418
- agentStore: deps.backgroundAgentStore,
2419
- owner: hostTaskId,
2420
- scope: taskScope,
2421
- ...(sessionId !== undefined ? { sessionId } : {}),
2422
- enrichCtx: enrichSpecToolCtx,
2423
- onTranscriptIntegrityGap: (handle, scope) => deliverEngineNotice(deps.onNotice, {
2424
- code: "delegation.transcript_integrity",
2425
- message: `delegation transcript integrity: agent ${handle}'s durable row binds a transcript session the session store attests is gone — the declared transcript durability is being contradicted (check the session store wiring/retention)`,
2426
- detail: { handle, ...(scope !== undefined ? { scope } : {}) },
2427
- }),
2428
- })));
2429
- }
2430
- }
2431
- }
2432
- const listAgents = peerLaneActive && peerSendMessageBuiltIn && listAgentsMountable({ exclude: toolFaceSnapshot.exclude, specTools: spec.tools ?? [] }) ? mountListAgents({ peerDirectory: deps.peerDirectory, sessionId, scope: taskScope, hostTaskId, ...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}), ...(internals?.parentSessionId !== undefined ? { parentSessionId: internals.parentSessionId } : {}), registry: defaultTaskRegistry, ...(deps.rosterStore !== undefined ? { roster: deps.rosterStore } : {}), specTools: spec.tools ?? [], toolEffects }) : undefined;
2433
- if (listAgents !== undefined)
2434
- tools.push(firstPartyOffload(listAgents));
2435
- if (backgroundTaskToolsActive) {
2436
- toolEffects.set("Monitor", "write");
2437
- envHandToolNames.add("Monitor");
2438
- tools.push(firstPartyOffload(createMonitorTool(executionEnv, {
2439
- registry: defaultTaskRegistry,
2440
- owner: hostTaskId,
2441
- scope: taskScope,
2442
- ...(sessionId !== undefined ? { sessionId } : {}),
2443
- ...(internals?.onTaskNotification !== undefined ? { onTaskNotification: internals.onTaskNotification } : {}),
2444
- ...(handsCwdRef !== undefined ? { cwdRef: handsCwdRef } : {}),
2445
- ...(offloadStore !== undefined ? { toolResultStore: offloadStore } : {}),
2446
- ...(spec.retainBackgroundProcesses === true ? { retainBackgroundProcesses: true } : {}),
2447
- })));
2448
- }
2449
- if (handsCwdRef !== undefined) {
2450
- toolEffects.set("EnterWorktree", "write");
2451
- toolEffects.set("ExitWorktree", "write");
2452
- worktreeSessionRef = {
2453
- ...(resume?.seed.activeWorktree
2454
- ? {
2455
- current: {
2456
- ...resume.seed.activeWorktree,
2457
- worktreeDir: rebaseRestoredPath(resume.seed.activeWorktree.worktreeDir),
2458
- originalCwd: rebaseRestoredPath(resume.seed.activeWorktree.originalCwd),
2459
- },
2460
- }
2461
- : wsSnapshot?.activeWorktree !== undefined
2462
- ? {
2463
- current: {
2464
- ...wsSnapshot.activeWorktree,
2465
- worktreeDir: rebaseWsPath(wsSnapshot.activeWorktree.worktreeDir),
2466
- originalCwd: rebaseWsPath(wsSnapshot.activeWorktree.originalCwd),
2467
- },
2468
- }
2469
- : {}),
2470
- };
2471
- if (workspaceStateSettle !== undefined)
2472
- workspaceStateSettle.restoredWorktreeDir = worktreeSessionRef.current?.worktreeDir;
2473
- tools.push(...createWorktreeTools(executionEnv, { repoRoot: taskRootFinal, cwdRef: handsCwdRef, session: worktreeSessionRef }).map((t) => (envHandToolNames.add(t.name), firstPartyOffload(t))));
2474
- }
2475
- if (offloadStore)
2476
- tools.push(createReadToolResultTool(offloadStore));
2477
- const onQuestion = frozenOnQuestion;
2478
- const liveQuestionFace = isLiveQuestionFace(onQuestion) ? onQuestion : undefined;
2479
- const contentAskBindings = new Map();
2480
- const CONTENT_ASK_BINDING_CAP = 32;
2481
- const checkpointStore = resolveCheckpointStore(spec, deps);
2482
- const questionParkStoreWired = checkpointStore !== undefined;
2483
- const questionDurableMandateBinding = runtimeCaps?.forceDurableGate === true && questionParkStoreWired;
2484
- const contentAskRoutable = (toolCallId) => liveQuestionFace !== undefined &&
2485
- mountedQuestionTool !== undefined &&
2486
- tools.includes(mountedQuestionTool) &&
2487
- !questionDurableMandateBinding &&
2488
- !inheritedUnavailableAsks.has(toolCallId);
2489
- const lateStrandedAnswers = [];
2490
- const discloseStrandedAnswers = (records, why) => {
2491
- if (records.length === 0)
2492
- return;
2493
- try {
2494
- deps.onError?.(new Error(`AskUserQuestion: ${records.length} question(s) were answered by a person but the call never ` +
2495
- `executed to collect the answer (${records.map((r) => `${r.toolCallId} [delivery ${r.deliveryId}]`).join(", ")}) — ${why}. The answer(s) were NOT ` +
2496
- `delivered to the model and are gone with this leg; re-ask if the decision is still needed.`), { phase: "degraded", sessionId, classification: "unconsumed-human-answer" });
2497
- }
2498
- catch {
2499
- }
2500
- };
2501
- const settleContentAskBindings = () => {
2502
- const stranded = [];
2503
- for (const [callId, bound] of contentAskBindings) {
2504
- if (bound.kind === "answered")
2505
- stranded.push({ deliveryId: bound.deliveryId, toolCallId: callId });
2506
- }
2507
- contentAskBindings.clear();
2508
- discloseStrandedAnswers(stranded, "the leg ended first (abort, batch teardown, or a loop failure)");
2509
- const byDelivery = new Map();
2510
- for (const r of [...stranded, ...lateStrandedAnswers])
2511
- byDelivery.set(r.deliveryId, r);
2512
- return [...byDelivery.values()];
2513
- };
2514
- const durableQuestionFace = questionParkStoreWired && (spec.durableApproval !== undefined || runtimeCaps?.forceDurableGate === true);
2515
- const mountedQuestionFace = liveQuestionFace !== undefined
2516
- ? async (req, signal) => {
2517
- const bound = contentAskBindings.get(req.toolCallId);
2518
- if (bound !== undefined) {
2519
- if (bound.questionsHash === boundInputHashOf(req.questions)) {
2520
- contentAskBindings.delete(req.toolCallId);
2521
- if (bound.kind === "answered")
2522
- return bound.answer;
2523
- throw bound.error;
2524
- }
2525
- contentAskBindings.delete(req.toolCallId);
2526
- }
2527
- return liveQuestionFace(req, signal);
2528
- }
2529
- : onQuestion;
2530
- const questionToolMounted = spec.interactiveTools === true || (spec.interactiveTools !== false && (onQuestion !== undefined || durableQuestionFace));
2531
- let mountedQuestionTool;
2532
- if (questionToolMounted)
2533
- tools.push((mountedQuestionTool =
2534
- createAskUserQuestionTool(mountedQuestionFace, { principal: spec.principal, sourceTaskId: sessionId }, {
2535
- ...(resume?.redeemedContentAskCallId !== undefined
2536
- ? {
2537
- redeemedApprovalCallId: resume.redeemedContentAskCallId,
2538
- ...(resume.redeemedContentAskQuestionsHash !== undefined ? { redeemedApprovalQuestionsHash: resume.redeemedContentAskQuestionsHash } : {}),
2539
- }
2540
- : {}),
2541
- ...(resolvedInteractionPosture !== undefined ? { posture: resolvedInteractionPosture } : {}),
2542
- ...(spec.interactiveQuestionFallback === true ? { interactiveFallback: true } : {}),
2543
- onSyntheticContinuation: ({ questionId, reason }) => {
2544
- deps.onError?.(new Error(`AskUserQuestion ${questionId}: no human answer was obtainable (` +
2545
- (reason === "seam_absent"
2546
- ? "no onQuestion seam is wired"
2547
- : reason === "declined_unavailable"
2548
- ? "the wired question channel reported nobody was reachable"
2549
- : "the wired question channel failed") +
2550
- `) — the model was instructed to self-answer and the run CONTINUES (warning, not a failure).`), { phase: "degraded", sessionId, classification: "no-human-autoanswered" });
2551
- },
2552
- })));
977
+ const { worktreeSessionRef } = prepareDelegationSurface({ spec, deps, internals, resume, sessionId, hostTaskId, taskScope, taskRootFinal, executionEnv, runnerSelf, backgroundTaskToolsActive, workflowToolsActive, peerLaneActive, peerSendMessageBuiltIn, peerLaneRefs, tools, toolEffects, axisExplicitNegatives, envHandToolNames, toolFaceSnapshot, firstPartyOffload, offloadStore, subagentRetain, enrichSpecToolCtx, handsCwdRef, wsSnapshot, rebaseWsPath, rebaseRestoredPath, workspaceStateSettle });
978
+ const { checkpointStore, liveQuestionFace, contentAskBindings, contentAskRoutable, lateStrandedAnswers, discloseStrandedAnswers, settleContentAskBindings, durableQuestionFace, questionToolMounted, mountedQuestionTool } = prepareQuestionFace({ frozenOnQuestion, spec, deps, runtimeCaps, resolvedInteractionPosture, resume, sessionId, tools, inheritedUnavailableAsks: () => inheritedUnavailableAsks });
2553
979
  if (spec.handsReadOnly !== true) {
2554
980
  const sessionScope = sessionId ?? spec.principal ?? spec.taskId ?? "default";
2555
981
  tools.push(...createSchedulerTools(executionEnv, {
@@ -2567,27 +993,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2567
993
  deps.onError?.(new Error(`Fork DENIED for principal "${spec.principal ?? ""}" by runtimeCaps.allowFork=false ` +
2568
994
  `(per-principal entitlement governance, not a misconfiguration — Agent(subagent_type:"fork") will refuse honestly)`), { phase: "config", sessionId });
2569
995
  }
2570
- const lspManager = spec.lspManager ?? deps.lspManager;
2571
- if (lspManager) {
2572
- const lspRoot = taskRootFinal;
2573
- tools.push(createLspTool(lspManager, { isPathIgnored: gitCheckIgnoreFilter(executionEnv, lspRoot), env: executionEnv }));
2574
- envHandToolNames.add("LSP");
2575
- }
2576
- const lspDiagnostics = spec.lspDiagnostics !== false && lspManager?.diagnostics !== undefined && handsEnabled && spec.handsReadOnly !== true
2577
- ? lspManager.diagnostics
2578
- : undefined;
2579
- const lspRunIdent = sessionId;
2580
- const nudgeLspOnEdit = lspDiagnostics
2581
- ? (rawPath) => {
2582
- const baseDir = handsCwdRef?.current ?? taskRootFinal;
2583
- const filePath = resolveLspPath(rawPath, baseDir);
2584
- lspDiagnostics.fileEdited(lspRunIdent, pathToUri(filePath));
2585
- void lspManager
2586
- .sessionFor(filePath, undefined, executionEnv)
2587
- .then((session) => session?.notifyFileChanged?.(filePath))
2588
- .catch(() => undefined);
2589
- }
2590
- : undefined;
996
+ const { lspDiagnostics, nudgeLspOnEdit, lspRunIdent } = prepareLsp({ spec, deps, executionEnv, taskRootFinal, handsEnabled, sessionId, handsCwdRef, tools, envHandToolNames });
2591
997
  const memoryPairNameDomain = new Set();
2592
998
  for (const t of tools) {
2593
999
  memoryPairNameDomain.add(t.name);
@@ -2679,7 +1085,6 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2679
1085
  live.incomplete = true;
2680
1086
  }
2681
1087
  }
2682
- let memoryBlock = memoryBlockFromEngine;
2683
1088
  if (memorySeedFiles?.length && seedContextFiles) {
2684
1089
  try {
2685
1090
  await seedContextFiles(memorySeedFiles);
@@ -2688,366 +1093,10 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2688
1093
  deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "config", sessionId });
2689
1094
  }
2690
1095
  }
2691
- let instructionSources;
2692
- let projectInstructionContent;
2693
- if (deps.loadProjectMemory) {
2694
- let projectMemoryPhase = spec.sessionId ? "resume" : "fresh";
2695
- if (spec.sessionId) {
2696
- try {
2697
- const branch = await session.getBranch();
2698
- const hasConversation = hasConversationContent(branch);
2699
- if (!hasConversation)
2700
- projectMemoryPhase = "fresh";
2701
- else {
2702
- for (let i = branch.length - 1; i >= 0; i--) {
2703
- const e = branch[i];
2704
- if (e.type === "compaction") {
2705
- projectMemoryPhase = "post-compact";
2706
- break;
2707
- }
2708
- if (e.type === "message" && e.message.role === "user")
2709
- break;
2710
- }
2711
- }
2712
- }
2713
- catch {
2714
- }
2715
- }
2716
- let loaded = null;
2717
- try {
2718
- loaded = await Promise.resolve(deps.loadProjectMemory({
2719
- cwd: taskRootFinal,
2720
- handsEnabled,
2721
- isSubagent: delegation.isNonForkChild,
2722
- ...(internals?.agentName ? { agentName: internals.agentName } : {}),
2723
- sessionId,
2724
- phase: projectMemoryPhase,
2725
- }));
2726
- }
2727
- catch (err) {
2728
- deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "config", sessionId });
2729
- }
2730
- const projectMem = loaded !== null && typeof loaded === "object" ? loaded.content : loaded;
2731
- const seededFiles = loaded !== null && typeof loaded === "object" ? loaded.seededFiles : undefined;
2732
- const declaredSources = loaded !== null && typeof loaded === "object" ? loaded.instructionSources : undefined;
2733
- if (declaredSources !== undefined && declaredSources.length > 0)
2734
- instructionSources = declaredSources;
2735
- if (seededFiles?.length && seedContextFiles) {
2736
- try {
2737
- await seedContextFiles(seededFiles);
2738
- }
2739
- catch (err) {
2740
- deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "config", sessionId });
2741
- }
2742
- }
2743
- if (projectMem != null && projectMem.trim()) {
2744
- projectInstructionContent = projectMem;
2745
- const projectBlock = `${PROJECT_CONTEXT_FRAMING}\n\n${composeMemoryBlock(projectMem, "project")}`;
2746
- memoryBlock = memoryBlock ? `${memoryBlock}\n\n${projectBlock}` : projectBlock;
2747
- }
2748
- }
2749
- const declaredSkillRank = new Map();
2750
- for (const s of spec.skills ?? [])
2751
- if (!declaredSkillRank.has(s.name))
2752
- declaredSkillRank.set(s.name, declaredSkillRank.size);
2753
- const skillSpecs = normalizeSkills(spec.skills ?? []).filter((s) => {
2754
- if (s.content.length <= SKILL_CONTENT_MAX_CHARS)
2755
- return true;
2756
- deps.onError?.(new Error(`Skill "${s.name}" content is ${s.content.length} chars, over the ${SKILL_CONTENT_MAX_CHARS}-char load limit — skill not loaded (skills are never truncated; shrink the body or move material to attachments/files).`), { phase: "config", sessionId });
2757
- return false;
2758
- });
2759
- const inheritedFrames = internals?.inheritedManifestScope ?? [];
2760
- for (const frame of inheritedFrames) {
2761
- skillScope.push(frame);
2762
- }
2763
- const hasSkillManifest = skillSpecs.some((s) => s.manifest !== undefined) || inheritedFrames.length > 0;
2764
- if (skillSpecs.length > 0) {
2765
- if (tools.some((t) => t.name === SKILL_TOOL_NAME)) {
2766
- const e = new Error(`Tool name "${SKILL_TOOL_NAME}" is reserved when spec.skills is present.`);
2767
- e.code = "config.reserved_tool_name";
2768
- throw e;
2769
- }
2770
- tools.push(createSkillTool(skillSpecs, skillScope));
2771
- }
2772
- const sharedMemoryProvider = deps.sharedMemoryStores;
2773
- let sharedMemoryPairMounted = false;
2774
- if (sharedMemoryProvider !== undefined) {
2775
- const excluded = new Set(toolFaceSnapshot.exclude ?? []);
2776
- const mountedNameDomain = new Set();
2777
- for (const t of tools) {
2778
- mountedNameDomain.add(t.name);
2779
- for (const alias of t.aliases ?? [])
2780
- mountedNameDomain.add(alias);
2781
- }
2782
- const excludedName = SHARED_MEMORY_TOOL_NAMES.find((n) => excluded.has(n));
2783
- const occupiedName = SHARED_MEMORY_TOOL_NAMES.find((n) => mountedNameDomain.has(n));
2784
- if (excludedName !== undefined || occupiedName !== undefined) {
2785
- deps.onError?.(new Error(`Shared memory tools ${SHARED_MEMORY_TOOL_NAMES.join("/")} were NOT mounted: ` +
2786
- (excludedName !== undefined
2787
- ? `excludeTools removes "${excludedName}"`
2788
- : `this task already declares a tool named "${occupiedName}" (the built-in yields to it)`) +
2789
- " — the pair mounts together or not at all."), { phase: "config", sessionId, classification: "shared-memory-not-mounted" });
2790
- }
2791
- else {
2792
- tools.push(...createSharedMemoryTools({
2793
- provider: sharedMemoryProvider,
2794
- context: { sessionId, taskId: hostTaskId, ...(spec.principal !== undefined ? { principal: spec.principal } : {}) },
2795
- }).map((s) => defineTool(s)));
2796
- sharedMemoryPairMounted = true;
2797
- }
2798
- }
2799
- let memoryEnginePairMounted = false;
2800
- if (memoryTools !== undefined && memoryTools.length > 0) {
2801
- tools.push(...memoryTools.map((s) => defineTool(s)));
2802
- memoryEnginePairMounted = true;
2803
- }
2804
- else if (memoryEngineSession !== undefined && !memorySearchToolsPlanned) {
2805
- deps.onError?.(new Error(`Memory tools ${MEMORY_ENGINE_TOOL_NAMES.join("/")} were NOT mounted: ` +
2806
- (memoryPairExcludedName !== undefined
2807
- ? `excludeTools removes "${memoryPairExcludedName}"`
2808
- : `this task already declares a tool named "${memoryPairOccupiedName}" (the built-in yields to it)`) +
2809
- " — these tools mount together or not at all."), { phase: "config", sessionId, classification: "memory-tools-not-mounted" });
2810
- }
2811
- const skillsListing = skillSpecs.length > 0
2812
- ? {
2813
- entries: skillSpecs.map((s) => ({
2814
- name: s.name,
2815
- description: s.description,
2816
- ...(s.files !== undefined ? { files: s.files.map((f) => ({ path: f.path })) } : {}),
2817
- ...(declaredSkillRank.get(s.name) !== undefined ? { declaredRank: declaredSkillRank.get(s.name) } : {}),
2818
- })),
2819
- seedAnnounced: resume !== undefined || spec.sessionId !== undefined,
2820
- }
2821
- :
2822
- (resume?.seed.announcedListings?.skills?.length ?? 0) > 0 || spec.sessionId !== undefined
2823
- ?
2824
- { entries: [], seedAnnounced: true }
2825
- : undefined;
2826
- const provider = spec.promptProvider ?? deps.promptProvider ?? defaultPromptProvider;
2827
- const promptPolicyEnabled = Boolean(lockedPreflight.toolPolicy);
2828
- const promptHooks = spec.hooks ?? deps.hooks;
2829
- const promptHooksEnabled = Boolean(promptHooks?.preToolUse || promptHooks?.postToolUse);
2830
- const failClosedReason = selfOrchestrationFailClosedReason(spec, deps);
2831
- if (failClosedReason) {
2832
- deps.onError?.(new Error(failClosedReason), { phase: "config", sessionId });
2833
- }
2834
- const featureFlags = {
2835
- policyEnabled: promptPolicyEnabled,
2836
- hooksEnabled: promptHooksEnabled,
2837
- isolationEnabled: isIsolated(executionEnv),
2838
- reminderMark,
2839
- readFaceOpen: resolvedReadFace === "open",
2840
- orchestrationEnabled: selfOrchestrationActive,
2841
- orchestrationDeferred: selfOrchestrationActive && (toolFaceSnapshot.defer?.includes("Workflow") ?? false),
2842
- promptProfile,
2843
- fableMitigations,
2844
- goalEnabled: internals?.goalMode === true,
2845
- awarenessEnabled: thinking !== undefined && ULTRA_REASONING_TIERS.has(thinking),
2846
- worktreeIsolated: internals?.isolation === "worktree" && ownedEnv !== undefined,
2847
- withinTaskCompactionEnabled: (spec.compaction?.enabled ?? true) && (spec.compaction?.withinTask ?? true),
2848
- isSubagent: delegation.isNonForkChild,
2849
- };
2850
- const userSystemPrompt = spec.systemPrompt ?? resolvedRole.systemPrompt ?? internals?.defaultSystemPrompt;
2851
- const userAppendSystemPrompt = spec.appendSystemPrompt;
2852
- const mcpInstructionsBlock = mcp.serverInstructions.length
2853
- ? `# MCP Server Instructions\n${mcp.serverInstructions.map((s) => fenceMcpServerInstructions(s.server, s.text)).join("\n\n")}`
2854
- : undefined;
2855
- const userTz = spec.clientContext?.timeZone;
2856
- const tzValid = userTz !== undefined && isValidTimeZone(userTz);
2857
- const envFacts = { date: formatLocalDate(new Date(), tzValid ? userTz : undefined), modelId: model.id };
2858
- if (tzValid)
2859
- envFacts.timeZone = userTz;
2860
- if (spec.clientContext?.userEmail)
2861
- envFacts.userEmail = spec.clientContext.userEmail;
2862
- const declaredCutoff = deps.modelKnowledgeCutoffs?.[model.id];
2863
- if (declaredCutoff)
2864
- envFacts.knowledgeCutoff = declaredCutoff;
2865
- if (spec.envFacts?.profile)
2866
- envFacts.sandboxProfile = spec.envFacts.profile;
2867
- if (spec.envFacts?.capabilities && spec.envFacts.capabilities.length > 0)
2868
- envFacts.sandboxCapabilities = [...spec.envFacts.capabilities];
2869
- if (spec.envFacts?.pkgSource)
2870
- envFacts.sandboxPkgSource = spec.envFacts.pkgSource;
2871
- if (spec.envFacts?.egress === "none" || spec.envFacts?.egress === "allowlist" || spec.envFacts?.egress === "full")
2872
- envFacts.sandboxEgress = spec.envFacts.egress;
2873
- if (spec.envFacts?.scratchpadDir)
2874
- envFacts.scratchpadDir = spec.envFacts.scratchpadDir;
2875
- if (externalContentTargetActive)
2876
- envFacts.externalContentTarget = true;
2877
- if (resume !== undefined && spec.envFacts?.resumeFacts) {
2878
- const rf = spec.envFacts.resumeFacts;
2879
- const copy = {};
2880
- if (rf.processes === "preserved" || rf.processes === "lost")
2881
- copy.processes = rf.processes;
2882
- if (rf.scratch === "preserved" || rf.scratch === "lost")
2883
- copy.scratch = rf.scratch;
2884
- if (typeof rf.note === "string" && rf.note.length > 0)
2885
- copy.note = rf.note;
2886
- if (Object.keys(copy).length > 0)
2887
- envFacts.resumeFacts = copy;
2888
- }
2889
- if (handsEnabled) {
2890
- envFacts.cwd = taskRootFinal;
2891
- if (additionalRootsCanonical.length > 0) {
2892
- envFacts.additionalDirectories = [...additionalRootsCanonical];
2893
- }
2894
- if (resolvedReadFace !== undefined)
2895
- envFacts.readFace = resolvedReadFace;
2896
- if (additionalReadRootsCanonical.length > 0) {
2897
- envFacts.additionalReadDirectories = [...additionalReadRootsCanonical];
2898
- }
2899
- try {
2900
- const probe = await executionEnv.exec('uname -s; uname -r; (git rev-parse --is-inside-work-tree 2>/dev/null || echo false); (git symbolic-ref --short -q HEAD 2>/dev/null || echo "HEAD (detached)"); (git rev-parse --show-toplevel 2>/dev/null || echo); (test -n "$(git status --porcelain 2>/dev/null | head -1)" && echo dirty || echo clean); (s=$(ps -p $$ -o comm= 2>/dev/null); s=${s##*/}; echo "${s#-}"); (test "$(git rev-parse --git-dir 2>/dev/null)" != "$(git rev-parse --git-common-dir 2>/dev/null)" && echo linked || echo main); (pwd -P 2>/dev/null || pwd)', { cwd: envFacts.cwd, timeout: 10 });
2901
- if (probe.ok && probe.value.exitCode === 0) {
2902
- const lines = probe.value.stdout.split("\n");
2903
- if (lines.length >= 6) {
2904
- const [osName, osVer, git, branch, worktreeRoot, dirty] = lines;
2905
- if (osName?.trim())
2906
- envFacts.platform = osName.trim();
2907
- if (osVer?.trim())
2908
- envFacts.osVersion = osVer.trim();
2909
- envFacts.isGitRepo = (git ?? "").trim() === "true";
2910
- if (envFacts.isGitRepo) {
2911
- const b = (branch ?? "").trim();
2912
- if (b)
2913
- envFacts.gitBranch = b;
2914
- const root = (worktreeRoot ?? "").trim();
2915
- if (root)
2916
- envFacts.gitWorktreeRoot = root;
2917
- const d = (dirty ?? "").trim();
2918
- if (d === "dirty" || d === "clean")
2919
- envFacts.gitDirty = d === "dirty";
2920
- }
2921
- const sh = (lines[6] ?? "").trim();
2922
- if (sh)
2923
- envFacts.shell = sh;
2924
- if (envFacts.isGitRepo && (lines[7] ?? "").trim() === "linked")
2925
- envFacts.isLinkedWorktree = true;
2926
- const probedPwd = (lines[8] ?? "").trim();
2927
- if (probedPwd && envFacts.cwd && probedPwd !== envFacts.cwd) {
2928
- const canonOf = async (p) => {
2929
- try {
2930
- const c = await executionEnv.canonicalPath(p);
2931
- return c.ok ? c.value : p;
2932
- }
2933
- catch {
2934
- return p;
2935
- }
2936
- };
2937
- const canonicalProbed = await canonOf(probedPwd);
2938
- if (canonicalProbed !== (await canonOf(envFacts.cwd))) {
2939
- try {
2940
- deps.onError?.(new Error(`execution-env cwd misalignment: task root is "${envFacts.cwd}" but the env's shell reports pwd "${canonicalProbed}" — per-exec cwd injection may be ignored on this lane (execs/artifacts can land outside the workspace root)`), { phase: "config", sessionId });
2941
- }
2942
- catch {
2943
- }
2944
- }
2945
- }
2946
- }
2947
- }
2948
- }
2949
- catch {
2950
- }
2951
- }
2952
- const gitStatusRef = await probeGitStatusLane({
2953
- executionEnv,
2954
- envFacts,
2955
- handsEnabled,
2956
- taskRoot: taskRootFinal,
2957
- onDegrade: (reason) => deps.onError?.(new Error(`env git snapshot degraded — ${reason}`), { phase: "degraded", sessionId, classification: "env-git-snapshot" }),
2958
- });
2959
- if (toolFaceSnapshot.exclude !== undefined && toolFaceSnapshot.exclude.length > 0) {
2960
- const excluded = new Set(toolFaceSnapshot.exclude);
2961
- for (let i = tools.length - 1; i >= 0; i--)
2962
- if (excluded.has(tools[i].name))
2963
- tools.splice(i, 1);
2964
- }
2965
- if (handsEnabled) {
2966
- const fullShellOnRoster = tools.some((t) => t.name === "Bash" && getToolContract(t, "core").contractId === FULL_SHELL_CONTRACT_ID);
2967
- if (fullShellOnRoster !== fullShellReachable) {
2968
- const e = new Error(`internal invariant: fullShellReachable=${String(fullShellReachable)} but the post-exclusion roster ` +
2969
- `${fullShellOnRoster ? "carries" : "does not carry"} the full shell (${FULL_SHELL_CONTRACT_ID}). ` +
2970
- `A new roster-affecting mechanism was added without updating the predicate.`);
2971
- e.code = "internal.full_shell_reachable_mismatch";
2972
- throw e;
2973
- }
2974
- }
2975
- if (promptProfile === "classic") {
2976
- for (let i = 0; i < tools.length; i++) {
2977
- const t = tools[i];
2978
- if (t.descriptionClassic !== undefined)
2979
- tools[i] = { ...t, description: t.descriptionClassic };
2980
- }
2981
- }
2982
- const userToolNames = (spec.tools ?? []).map((t) => t.name);
2983
- const protocolToolNames = [...mcp.tools.map((t) => t.name), ...a2a.tools.map((t) => t.name)];
2984
- const mcpAlwaysLoadNames = mcp.tools
2985
- .filter((t) => t.mcpAlwaysLoad === true && !(toolFaceSnapshot.defer ?? []).includes(t.name))
2986
- .map((t) => t.name);
2987
- const classifyDeferredOverFace = (face, builtinDeferNames) => {
2988
- const deferredSet = classifyDeferred({
2989
- specs: spec.tools ?? [],
2990
- protocolToolNames,
2991
- fullTools: face.filter((t) => userToolNames.includes(t.name) || protocolToolNames.includes(t.name)),
2992
- deferMode: deps.deferMode,
2993
- model,
2994
- deferNames: [...(toolFaceSnapshot.defer ?? []), ...builtinDeferNames].filter((n) => face.some((t) => t.name === n)),
2995
- alwaysLoadNames: [
2996
- ASK_USER_QUESTION_TOOL_NAME,
2997
- ...(toolFaceSnapshot.alwaysLoad ?? []),
2998
- ...mcpAlwaysLoadNames,
2999
- ],
3000
- });
3001
- for (const n of [...deferredSet]) {
3002
- if (!face.some((t) => t.name === n))
3003
- deferredSet.delete(n);
3004
- }
3005
- return deferredSet;
3006
- };
3007
- const sharedMemoryPair = sharedMemoryPairMounted ? SHARED_MEMORY_TOOL_NAMES.filter((n) => tools.some((t) => t.name === n)) : [];
3008
- const memoryEnginePair = explicitlyDeferredMemoryTrio(memoryEnginePairMounted, tools, toolFaceSnapshot.defer);
3009
- const builtinDeferPairNames = [...sharedMemoryPair, ...memoryEnginePair];
3010
- let deferred;
3011
- if (builtinDeferPairNames.length > 0) {
3012
- const withoutPairs = tools.filter((t) => !builtinDeferPairNames.some((n) => n === t.name));
3013
- const d1 = classifyDeferredOverFace(tools, builtinDeferPairNames);
3014
- const d0 = classifyDeferredOverFace(withoutPairs, []);
3015
- const soleCause = d0.size === 0 && d1.size > 0;
3016
- const supportNameTaken = tools.some((t) => t.name === TOOL_SEARCH_NAME || (t.aliases ?? []).includes(TOOL_SEARCH_NAME));
3017
- if (soleCause && supportNameTaken) {
3018
- const retractNames = memoryGroupRetractionSet(builtinDeferPairNames, memoryEnginePair.length > 0);
3019
- for (let i = tools.length - 1; i >= 0; i--) {
3020
- if (retractNames.has(tools[i].name))
3021
- tools.splice(i, 1);
3022
- }
3023
- if (sharedMemoryPair.length > 0) {
3024
- sharedMemoryPairMounted = false;
3025
- deps.onError?.(new Error(`Shared memory tools ${SHARED_MEMORY_TOOL_NAMES.join("/")} were NOT mounted: mounting them would inject ` +
3026
- `the "${TOOL_SEARCH_NAME}" tool, whose name this task already declares — the pair mounts together or not at all.`), { phase: "config", sessionId, classification: "shared-memory-not-mounted" });
3027
- }
3028
- if (memoryEnginePair.length > 0) {
3029
- memoryEnginePairMounted = false;
3030
- if (memoryBlock !== undefined && memoryRecallSegment !== undefined) {
3031
- const segment = memoryRecallSegment;
3032
- if (memoryBlock === segment)
3033
- memoryBlock = undefined;
3034
- else if (memoryBlock.includes(`\n\n${segment}`))
3035
- memoryBlock = memoryBlock.replace(`\n\n${segment}`, "");
3036
- else if (memoryBlock.startsWith(`${segment}\n\n`))
3037
- memoryBlock = memoryBlock.slice(segment.length + 2);
3038
- }
3039
- deps.onError?.(new Error(`Memory tools ${MEMORY_ENGINE_TOOL_NAMES.join("/")} were NOT mounted: mounting them would inject ` +
3040
- `the "${TOOL_SEARCH_NAME}" tool, whose name this task already declares — these tools mount together or not at all.`), { phase: "config", sessionId, classification: "memory-tools-not-mounted" });
3041
- }
3042
- deferred = d0;
3043
- }
3044
- else {
3045
- deferred = d1;
3046
- }
3047
- }
3048
- else {
3049
- deferred = classifyDeferredOverFace(tools, []);
3050
- }
1096
+ const projectContext = await prepareProjectContext({ deps, spec, internals, session, sessionId, hostTaskId, taskRootFinal, handsEnabled, delegation, seedContextFiles, memoryBlockFromEngine, skillScope, tools, toolFaceSnapshot, onceLedger, memoryTools, memoryEngineSession, memorySearchToolsPlanned, memoryPairExcludedName, memoryPairOccupiedName, resume });
1097
+ const { memoryBlock: memoryBlockWithProject, instructionSources, projectInstructionContent, hasSkillManifest, skillsListing, sharedMemoryPairMounted, memoryEnginePairMounted } = projectContext;
1098
+ const { provider, featureFlags, userSystemPrompt, userAppendSystemPrompt, mcpInstructionsBlock, envFacts, tzValid, userTz, gitStatusRef } = await preparePromptInputs({ spec, deps, failClosedReason: selfOrchestrationFailClosedReason(spec, deps), lockedPreflight, selfOrchestrationActive, executionEnv, reminderMark, resolvedReadFace, toolFaceSnapshot, promptProfile, fableMitigations, internals, thinking, ownedEnv, delegation, resolvedRole, mcp, model, externalContentTargetActive, resume, handsEnabled, taskRootFinal, additionalRootsCanonical, additionalReadRootsCanonical, sessionId });
1099
+ const { deferred, memoryBlock } = prepareDeferClassify({ spec, deps, tools, toolFaceSnapshot, handsEnabled, fullShellReachable, promptProfile, model, mcp, a2a, sharedMemoryPairMounted, memoryEnginePairMounted, memoryBlock: memoryBlockWithProject, memoryRecallSegment, onceLedger, sessionId });
3051
1100
  const delegationProvenanceChannel = internals?.delegationProvenance;
3052
1101
  if (memoryEngineSession !== undefined || delegationProvenanceChannel !== undefined) {
3053
1102
  const pollution = memoryEngineSession?.pollution;
@@ -3328,10 +1377,10 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3328
1377
  const promptBlocks = assembled.legacyBlocks;
3329
1378
  const collisionAudit = auditToolCollisions(tools);
3330
1379
  for (const y of collisionAudit.yields) {
3331
- deps.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 });
1380
+ 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 });
3332
1381
  }
3333
1382
  for (const c of collisionAudit.aliasCollisions) {
3334
- deps.onError?.(new Error(`tool mount: alias "${c.alias}" is claimed by both "${c.owners[0]}" and "${c.owners[1]}" (design/141 warn face — name resolution serves the later mount)`), { phase: "config", sessionId });
1383
+ onceLedger.onError(new Error(`tool mount: alias "${c.alias}" is claimed by both "${c.owners[0]}" and "${c.owners[1]}" (design/141 warn face — name resolution serves the later mount)`), { phase: "config", sessionId });
3335
1384
  }
3336
1385
  const saltedHash = (text) => createHash("sha256").update(PROMPT_HASH_SALT).update(text).digest("hex").slice(0, 12);
3337
1386
  const promptManifest = {
@@ -3365,282 +1414,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3365
1414
  });
3366
1415
  }
3367
1416
  }
3368
- const activeTools = new Set();
3369
- const fpRef = {};
3370
- const turnSnapshotRef = {};
3371
- let harnessTools = tools;
3372
- const failedMcpServers = mcp.statuses
3373
- .filter((s) => s.status === "failed")
3374
- .map((s) => ({ name: inlineUntrusted(s.name, 160), ...(s.error !== undefined ? { error: inlineUntrusted(s.error, 240) } : {}) }));
3375
- let toolsDeltaRef;
3376
- let toolMaterializeStatic = false;
3377
- let deferDirectCall = false;
3378
- const staticFaceForRef = {};
3379
- if (deferred.size > 0 || failedMcpServers.length > 0) {
3380
- toolsDeltaRef = { pending: [], pendingRemoved: [], pendingReadded: [], pendingFailed: failedMcpServers };
3381
- }
3382
- const listingRideRef = {};
3383
- {
3384
- const raw = process.env.SEMA_TOOL_MATERIALIZE_STRATEGY;
3385
- if (raw !== undefined && raw !== "swap" && raw !== "static" && deferred.size === 0) {
3386
- const line = `SEMA_TOOL_MATERIALIZE_STRATEGY=${JSON.stringify(raw)} is not "swap" or "static" — inert on this task (no deferred tools), but a deferring task WITHOUT an explicit spec strategy will refuse to prepare under it (an explicit legal spec outranks and discards it, loudly). Fix or unset the flag.`;
3387
- emitMaterializeEnvNotice(deps.onNotice, line, { raw });
3388
- }
3389
- }
3390
- if (deferred.size > 0) {
3391
- if (deferred.has(TOOL_SEARCH_NAME) || tools.some((t) => t.name === TOOL_SEARCH_NAME)) {
3392
- const e = new Error(`Tool name "${TOOL_SEARCH_NAME}" is reserved when deferred tools are present.`);
3393
- e.code = "config.reserved_tool_name";
3394
- throw e;
3395
- }
3396
- const registry = buildDeferredRegistry(deferred, tools);
3397
- const rawEnvStrategy = process.env.SEMA_TOOL_MATERIALIZE_STRATEGY;
3398
- const envStrategyInvalid = rawEnvStrategy !== undefined && rawEnvStrategy !== "swap" && rawEnvStrategy !== "static";
3399
- if (envStrategyInvalid && spec.toolMaterializeStrategy === undefined) {
3400
- const e = new Error(`SEMA_TOOL_MATERIALIZE_STRATEGY must be "swap" or "static" (got ${JSON.stringify(rawEnvStrategy)}).`);
3401
- e.code = "config.tool_materialize_invalid";
3402
- throw e;
3403
- }
3404
- if (envStrategyInvalid) {
3405
- const line = `SEMA_TOOL_MATERIALIZE_STRATEGY=${JSON.stringify(rawEnvStrategy)} was ignored — not "swap" or "static", and the task spec pins toolMaterializeStrategy=${JSON.stringify(spec.toolMaterializeStrategy)} which outranks it. Fix or unset the env flag.`;
3406
- emitMaterializeEnvNotice(deps.onNotice, line, { raw: rawEnvStrategy, specStrategy: spec.toolMaterializeStrategy });
3407
- }
3408
- const envStrategy = envStrategyInvalid ? undefined : rawEnvStrategy;
3409
- const requestedStrategy = spec.toolMaterializeStrategy ?? envStrategy ?? "swap";
3410
- const laneDegrade = requestedStrategy === "static" && spec.deferSelfResolve === false;
3411
- const materializeStatic = requestedStrategy === "static" && !laneDegrade;
3412
- toolMaterializeStatic = materializeStatic;
3413
- deferDirectCall = spec.deferSelfResolve !== false;
3414
- promptManifest.toolDisclosure = {
3415
- deferredTools: deferred.size,
3416
- strategy: materializeStatic ? "static" : "swap",
3417
- source: laneDegrade ? "degraded_no_direct_lane" : spec.toolMaterializeStrategy !== undefined ? "spec" : envStrategy !== undefined ? "env" : "default",
3418
- };
3419
- const everExempt = new Set();
3420
- const staticFaceFor = (name) => {
3421
- if (!materializeStatic)
3422
- return false;
3423
- if (everExempt.has(name))
3424
- return false;
3425
- if (staticSchemaRenderable(tools.find((t) => t.name === name)?.parameters))
3426
- return true;
3427
- everExempt.add(name);
3428
- return false;
3429
- };
3430
- staticFaceForRef.current = staticFaceFor;
3431
- let activationChain = Promise.resolve();
3432
- const serializeActivation = (section) => {
3433
- const p = activationChain.then(section);
3434
- activationChain = p.then(() => undefined, () => undefined);
3435
- return p;
3436
- };
3437
- const directCallFor = (name) => {
3438
- if (spec.deferSelfResolve === false)
3439
- return undefined;
3440
- const executionMode = tools.find((t) => t.name === name)?.executionMode;
3441
- return {
3442
- resolveReal: () => {
3443
- const real = tools.find((t) => t.name === name);
3444
- if (real === undefined)
3445
- return undefined;
3446
- return {
3447
- parameters: real.parameters,
3448
- invoke: (toolCallId, params, signal, onUpdate) => real.execute(toolCallId, params, signal, onUpdate),
3449
- };
3450
- },
3451
- ...(executionMode !== undefined ? { executionMode } : {}),
3452
- staticFace: () => staticFaceFor(name),
3453
- activate: async () => serializeActivation(async () => {
3454
- if (activeTools.has(name))
3455
- return undefined;
3456
- activeTools.add(name);
3457
- try {
3458
- await rematerialize(activeTools);
3459
- }
3460
- catch (e) {
3461
- activeTools.delete(name);
3462
- throw e;
3463
- }
3464
- return listingRideRef.current?.([name]);
3465
- }),
3466
- };
3467
- };
3468
- const placeholders = new Map([...registry.values()].map((i) => [i.name, createPlaceholderTool(i, directCallFor(i.name))]));
3469
- const { messages } = await session.buildContext();
3470
- for (const n of extractDiscoveredToolNames(messages, registry)) {
3471
- if (deferred.has(n))
3472
- activeTools.add(n);
3473
- }
3474
- for (const entry of await session.getBranch()) {
3475
- if (entry.type !== "compaction" || entry.fromHook === true)
3476
- continue;
3477
- for (const n of readCompactionActiveTools(entry.details)) {
3478
- if (deferred.has(n))
3479
- activeTools.add(n);
3480
- }
3481
- }
3482
- if (resume) {
3483
- for (const n of resume.seed.activeTools)
3484
- if (deferred.has(n))
3485
- activeTools.add(n);
3486
- }
3487
- const callableToolNames = () => {
3488
- const s = new Set(tools.map((t) => t.name));
3489
- for (const n of deferred)
3490
- if (!activeTools.has(n))
3491
- s.delete(n);
3492
- s.add(TOOL_SEARCH_NAME);
3493
- return s;
3494
- };
3495
- offloadReachableToolsRef.current = callableToolNames;
3496
- let toolSearch;
3497
- const buildToolList = (active) => {
3498
- const list = tools.map((t) => (deferred.has(t.name) && (staticFaceFor(t.name) || !active.has(t.name)) ? placeholders.get(t.name) : t));
3499
- list.push(toolSearch);
3500
- return list;
3501
- };
3502
- const announcedTools = new Set(activeTools);
3503
- const withdrawnTools = new Set();
3504
- const deltaRef = toolsDeltaRef;
3505
- const rematerialize = async (active) => {
3506
- contentOriginWrapRef.current?.();
3507
- const list = buildToolList(active);
3508
- await harnessRef.current.setTools(list, list.map((t) => t.name));
3509
- if (fpRef.current)
3510
- fpRef.current.tools = toolsToFingerprintInputs(list);
3511
- turnSnapshotRef.current?.refreshTools(toolsToFingerprintInputs(list));
3512
- const live = new Set(list.map((t) => t.name));
3513
- for (const n of [...announcedTools]) {
3514
- if (!live.has(n)) {
3515
- announcedTools.delete(n);
3516
- withdrawnTools.add(n);
3517
- deltaRef.pendingRemoved.push(n);
3518
- }
3519
- }
3520
- for (const n of active) {
3521
- if (!live.has(n))
3522
- continue;
3523
- if (!announcedTools.has(n)) {
3524
- announcedTools.add(n);
3525
- if (withdrawnTools.delete(n))
3526
- deltaRef.pendingReadded.push(n);
3527
- else
3528
- deltaRef.pending.push(n);
3529
- }
3530
- }
3531
- };
3532
- rebuildHarnessToolsRef.current = () => rematerialize(activeTools);
3533
- toolSearch = createToolSearchTool({
3534
- registry,
3535
- active: activeTools,
3536
- rematerialize,
3537
- listingRide: (newly) => listingRideRef.current?.(newly),
3538
- mountedNames: callableToolNames,
3539
- directCallEnabled: spec.deferSelfResolve !== false,
3540
- isMounted: (name) => tools.some((t) => t.name === name),
3541
- ...(materializeStatic
3542
- ? { staticSchemaFor: (name) => (staticFaceFor(name) ? tools.find((t) => t.name === name)?.parameters : undefined) }
3543
- : {}),
3544
- serializeActivation,
3545
- });
3546
- harnessTools = buildToolList(activeTools);
3547
- }
3548
- else {
3549
- rebuildHarnessToolsRef.current = async () => {
3550
- contentOriginWrapRef.current?.();
3551
- const list = [...tools];
3552
- await harnessRef.current.setTools(list, list.map((t) => t.name));
3553
- if (fpRef.current)
3554
- fpRef.current.tools = toolsToFingerprintInputs(list);
3555
- turnSnapshotRef.current?.refreshTools(toolsToFingerprintInputs(list));
3556
- };
3557
- }
3558
- if (spec.agents !== undefined && spec.agents.length > 0) {
3559
- const known = new Set(tools.flatMap((t) => [t.name, ...(t.aliases ?? [])]));
3560
- for (const def of spec.agents) {
3561
- const unknownAllow = (def.allowTools ?? []).filter((n) => n !== "*" && !known.has(n));
3562
- if (unknownAllow.length > 0) {
3563
- try {
3564
- deps.onError?.(new Error(`TaskSpec.agents: agent "${def.name}" allows tool(s) ${unknownAllow.join(", ")} not present in this task's assembled roster — likely a typo (the entry would be item-filtered at spawn; the agent stays usable). Advisory only: the delegation pool can differ from this roster, so a tool mounted only on the delegation tool or produced by per-spawn extraTools makes this spurious.`), { phase: "config", sessionId });
3565
- }
3566
- catch {
3567
- }
3568
- }
3569
- for (const n of def.denyTools ?? []) {
3570
- if (n === "*")
3571
- continue;
3572
- if (!known.has(n)) {
3573
- const e = new Error(`TaskSpec.agents: agent "${def.name}" declares tool "${n}" in its denied tools, but no such tool exists in this deployment — fix the agent's tools list or mount the tool.`);
3574
- e.code = "config.agent.unknown_tool";
3575
- throw e;
3576
- }
3577
- }
3578
- }
3579
- }
3580
- const rosterBearingSpec = (spec.tools ?? []).find((t) => t.agentListing !== undefined && t.agentListing.length > 0);
3581
- const rosterFaceDeferred = rosterBearingSpec !== undefined &&
3582
- !(toolFaceSnapshot.exclude?.includes(rosterBearingSpec.name) ?? false) &&
3583
- (toolFaceSnapshot.defer?.includes(rosterBearingSpec.name) ?? false) &&
3584
- !activeTools.has(rosterBearingSpec.name);
3585
- const agentListingSpec = rosterBearingSpec !== undefined &&
3586
- !rosterFaceDeferred &&
3587
- !(toolFaceSnapshot.exclude?.includes(rosterBearingSpec.name) ?? false)
3588
- ? rosterBearingSpec
3589
- : undefined;
3590
- const agentListing = agentListingSpec
3591
- ? {
3592
- entries: agentListingSpec.agentListing,
3593
- toolName: agentListingSpec.name,
3594
- seedAnnounced: resume !== undefined || spec.sessionId !== undefined,
3595
- ...(agentListingSpec.agentModels !== undefined ? { models: agentListingSpec.agentModels } : {}),
3596
- }
3597
- :
3598
- !rosterFaceDeferred && ((resume?.seed.announcedListings?.agents?.length ?? 0) > 0 || spec.sessionId !== undefined)
3599
- ?
3600
- {
3601
- entries: [],
3602
- toolName: (spec.tools ?? []).find((t) => t.agentListing !== undefined)?.name ?? "Agent",
3603
- seedAnnounced: true,
3604
- }
3605
- : undefined;
3606
- if (spec.attachments?.agentListing === false && agentListing !== undefined && agentListing.entries.length > 0) {
3607
- deps.onError?.(new Error(`attachments.agentListing is explicitly false but the "${agentListing.toolName}" delegation tool mounts a ${agentListing.entries.length}-type roster — the model will never see the agent-type listing its tool description points to.`), { phase: "config", sessionId });
3608
- }
3609
- if (spec.attachments?.skillsListing === false && skillsListing !== undefined && skillsListing.entries.length > 0) {
3610
- deps.onError?.(new Error(`attachments.skillsListing is explicitly false but ${skillsListing.entries.length} skill(s) are mounted — the model will never see the skills listing the ${SKILL_TOOL_NAME} tool description points to.`), { phase: "config", sessionId });
3611
- }
3612
- const announcedListingsRef = {};
3613
- if (rosterFaceDeferred) {
3614
- if (resume?.seed.announcedListings?.agents !== undefined)
3615
- announcedListingsRef.agents = [...resume.seed.announcedListings.agents];
3616
- if (resume?.seed.announcedListings?.models !== undefined)
3617
- announcedListingsRef.models = [...resume.seed.announcedListings.models];
3618
- if (announcedListingsRef.agents === undefined && announcedListingsRef.models === undefined) {
3619
- try {
3620
- const entrySnap = await session.getAnnouncedListing();
3621
- if (entrySnap?.agents !== undefined)
3622
- announcedListingsRef.agents = [...entrySnap.agents];
3623
- if (entrySnap?.models !== undefined)
3624
- announcedListingsRef.models = [...entrySnap.models];
3625
- }
3626
- catch {
3627
- }
3628
- }
3629
- }
3630
- if (rosterFaceDeferred && rosterBearingSpec !== undefined) {
3631
- const rideEntries = rosterBearingSpec.agentListing;
3632
- const rideModels = rosterBearingSpec.agentModels;
3633
- listingRideRef.current = (newly) => {
3634
- if (!newly.includes(rosterBearingSpec.name))
3635
- return undefined;
3636
- if ((announcedListingsRef.agents?.length ?? 0) > 0)
3637
- return undefined;
3638
- return renderAgentListingDelta({ announcedAgentTypes: undefined }, rideEntries, rosterBearingSpec.name, rideModels);
3639
- };
3640
- }
3641
- const dateChange = renderWithDate
3642
- ? { legDate: envFacts.date, today: () => formatLocalDate(new Date(), tzValid ? userTz : undefined) }
3643
- : undefined;
1417
+ const { activeTools, fpRef, turnSnapshotRef, harnessTools, toolsDeltaRef, toolMaterializeStatic, deferDirectCall, staticFaceForRef, listingRideRef } = await prepareToolDisclosureMount({ spec, deps, session, resume, deferred, tools, onceLedger, mcp, promptManifest, offloadReachableToolsRef, contentOriginWrapRef, rebuildHarnessToolsRef, harnessRef });
1418
+ const { agentListing, announcedListingsRef, dateChange } = prepareListings({ spec, tools, onceLedger, sessionId, toolFaceSnapshot, activeTools, resume, skillsListing, listingRideRef, renderWithDate, envFacts, tzValid, userTz });
3644
1419
  const lastBrainContextRef = {};
3645
1420
  const requestLossyRef = { current: false };
3646
1421
  const staleOffloadWrittenRefs = new Set();
@@ -3660,7 +1435,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3660
1435
  };
3661
1436
  const staleOffloadCfg = resolveStaleToolResultOffload(spec.compaction?.staleToolResultOffload);
3662
1437
  if (staleOffloadCfg !== undefined && offloadStore === undefined) {
3663
- deps.onError?.(new Error("compaction.staleToolResultOffload is set but the tool-result offload store is disabled (toolResultThresholdChars ≤ 0/∞) — the knob is inert this run; re-enable offloading or drop the knob"), { phase: "config", sessionId });
1438
+ onceLedger.onError(new Error("compaction.staleToolResultOffload is set but the tool-result offload store is disabled (toolResultThresholdChars ≤ 0/∞) — the knob is inert this run; re-enable offloading or drop the knob"), { phase: "config", sessionId });
3664
1439
  }
3665
1440
  const staleOffload = staleOffloadCfg !== undefined && offloadStore !== undefined ? { cfg: staleOffloadCfg, store: offloadStore } : undefined;
3666
1441
  const guardedBrain = brainCallGuardrailMs === undefined
@@ -3961,61 +1736,19 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3961
1736
  return {};
3962
1737
  return { riskAxes: { ...(irreversible !== undefined ? { irreversible } : {}), ...(egress !== undefined ? { egress } : {}) } };
3963
1738
  };
3964
- const inheritedAskEvidence = inheritedAskRuleEvidence(deps);
3965
- const permissionRuleLane = (() => {
3966
- const provider = deps.permissionRuleStore;
3967
- const localOwnerDeclared = deps.localOwnerRules === true;
3968
- if (localOwnerDeclared) {
3969
- if (provider === undefined) {
3970
- throw new Error("RunnerDeps.localOwnerRules is declared but no permissionRuleStore provider is wired — there is no bucket for the local owner to hold rules in; refusing rather than running as if the declaration were absent");
3971
- }
3972
- if (provider.forLocalOwner === undefined) {
3973
- throw new Error("RunnerDeps.localOwnerRules is declared but the wired permissionRuleStore provider implements no forLocalOwner() face — a provider without a local-owner bucket cannot honor the declaration; refusing rather than silently resolving zero rules");
3974
- }
3975
- }
3976
- if (provider === undefined)
3977
- return undefined;
3978
- const root = taskRootFinal;
3979
- return {
3980
- admits: async (req) => {
3981
- const anonymous = spec.principal === undefined || spec.principal === "";
3982
- if (anonymous && !localOwnerDeclared)
3983
- return undefined;
3984
- if (req.toolName !== PERSISTED_RULE_TOOL && req.toolName !== DIRECTORY_RULE_TOOL)
3985
- return undefined;
3986
- let listed;
3987
- try {
3988
- listed = anonymous
3989
- ?
3990
- await provider.forLocalOwner().list()
3991
- : await provider.forPrincipal(spec.principal).list();
3992
- }
3993
- catch (err) {
3994
- emitTrace(deps.tracer, () => ({
3995
- kind: "permission.rule_store_unreadable",
3996
- version: 1,
3997
- taskId: hostTaskId,
3998
- message: err instanceof Error ? err.message : String(err),
3999
- ts: Date.now(),
4000
- }));
4001
- return { unreadable: true };
4002
- }
4003
- const table = await spliceSessionOverlayRows(deps.sessionPermissionRules, sessionId, listed.rules, deps.tracer, hostTaskId);
4004
- if (req.toolName === DIRECTORY_RULE_TOOL) {
4005
- return directoryRuleLaneAnswer(table, req.args, { root, sessionId, liveCwd: handsCwdRef?.current });
4006
- }
4007
- const command = req.args?.command;
4008
- if (typeof command !== "string")
4009
- return undefined;
4010
- const admitting = findAdmittingRule(table, { tool: req.toolName, command, cwd: root, sessionId, ...(handsCwdRef?.current !== undefined ? { execCwd: handsCwdRef.current } : {}) });
4011
- if (admitting !== undefined)
4012
- return persistedRuleHitOf(admitting);
4013
- const coverage = segmentCoverageOf(command, { persisted: table }, { tool: req.toolName, cwd: root, sessionId, ...(handsCwdRef?.current !== undefined ? { execCwd: handsCwdRef.current } : {}) });
4014
- return coverage !== undefined ? { segmentCoverage: coverage } : undefined;
4015
- },
4016
- };
4017
- })();
4018
- const permissionRuleOrgLane = makeOrgAdjudicationLane(deps.permissionRuleOrg, questionToolMounted, (message) => deps.onError?.(new Error(message), { phase: "config", sessionId }));
1739
+ const inheritedAskEvidence = inheritedAskRuleEvidence(deps.permissionRuleStore);
1740
+ const { personal: permissionRuleLane, org: permissionRuleOrgLane } = createPermissionRuleLanes({
1741
+ provider: deps.permissionRuleStore,
1742
+ localOwnerDeclared: deps.localOwnerRules === true,
1743
+ principal: spec.principal,
1744
+ sessionId,
1745
+ root: taskRootFinal,
1746
+ liveCwd: () => handsCwdRef?.current,
1747
+ questionToolMounted,
1748
+ questionToolName: ASK_USER_QUESTION_TOOL_NAME,
1749
+ onRevisionDefect: (message) => deps.onError?.(new Error(message), { phase: "config", sessionId }),
1750
+ onDisclosure: (message) => emitTrace(deps.tracer, () => ({ kind: "permission.rule_store_unreadable", version: 1, taskId: hostTaskId, message, ts: Date.now() })),
1751
+ });
4019
1752
  const ruleOffersOf = makeRuleOffersOf({
4020
1753
  laneArmed: permissionRuleLane !== undefined,
4021
1754
  principal: spec.principal,
@@ -4058,7 +1791,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4058
1791
  return { action: "allow", updatedInput: editArgs };
4059
1792
  if (judged.kind === "deny")
4060
1793
  return judged.result;
4061
- const { ask: editAsk, fallback: editFallback, mintedHere: editMintedHere } = judged;
1794
+ const { ask: editAsk, fallback: editFallback, mintedHere: editMintedHere, origin: editOrigin } = judged;
4062
1795
  re = editAsk;
4063
1796
  const rr = await resolveAsk({
4064
1797
  toolName: creq.toolName,
@@ -4070,7 +1803,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4070
1803
  ...riskAxesOf(creq.toolName),
4071
1804
  ...(re.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
4072
1805
  ...(re.persistedRuleShadowed !== undefined ? { persistedRuleShadowed: re.persistedRuleShadowed } : {}),
4073
- ...(editFallback !== undefined ? { denialLimitFallback: editFallback } : {}),
1806
+ ...inheritedAskCarry(editOrigin, editFallback, isLiveApproverSeat(onAskOf), ancestorTracker),
4074
1807
  ruleEvidence: inheritedAskEvidence,
4075
1808
  }, onAskOf, csignal ?? abortController.signal, lateAskSettlementObserver({ toolName: creq.toolName, toolCallId: creq.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice, onError: deps.onError }));
4076
1809
  settleDenialLimitFallback({ fallback: editFallback, mintedHere: editMintedHere, tracker: ancestorTracker, resolved: rr, headless: headlessDenyAtRecheck, stop: stopForDenialLimit, toolName: creq.toolName, toolCallId: creq.toolCallId });
@@ -4131,7 +1864,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4131
1864
  return { action: "allow" };
4132
1865
  if (judged.kind === "deny")
4133
1866
  return judged.result;
4134
- const { ask: inheritedAsk, fallback, mintedHere } = judged;
1867
+ const { ask: inheritedAsk, fallback, mintedHere, origin: inheritedOrigin } = judged;
4135
1868
  if (fallback === undefined && sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq.toolName)) {
4136
1869
  recordAncestorSandboxAdmission(creq.toolCallId, creq.toolName);
4137
1870
  return { action: "allow" };
@@ -4148,7 +1881,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4148
1881
  ...riskAxesOf(creq.toolName),
4149
1882
  ...(inheritedAsk.action === "ask" && inheritedAsk.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
4150
1883
  ...(inheritedAsk.action === "ask" && inheritedAsk.persistedRuleShadowed !== undefined ? { persistedRuleShadowed: inheritedAsk.persistedRuleShadowed } : {}),
4151
- ...(fallback !== undefined ? { denialLimitFallback: fallback } : {}),
1884
+ ...inheritedAskCarry(inheritedOrigin, fallback, isLiveApproverSeat(pc.onAsk), pc.autoMode?.denialTracking),
4152
1885
  ruleEvidence: inheritedAskEvidence,
4153
1886
  }, pc.onAsk, csignal ?? abortController.signal, lateAskSettlementObserver({ toolName: creq.toolName, toolCallId: creq.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice, onError: deps.onError }));
4154
1887
  const askWaitMs = Math.max(0, now() - askT0);
@@ -4211,7 +1944,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4211
1944
  return decision.updatedInput !== undefined ? { action: "allow", updatedInput: decision.updatedInput } : { action: "allow" };
4212
1945
  if (judged.kind === "deny")
4213
1946
  return judged.result;
4214
- const { ask: inheritedAsk, fallback, mintedHere } = judged;
1947
+ const { ask: inheritedAsk, fallback, mintedHere, origin: inheritedOrigin } = judged;
4215
1948
  if (fallback === undefined && sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq.toolName)) {
4216
1949
  recordAncestorSandboxAdmission(creq.toolCallId, creq.toolName);
4217
1950
  return decision.updatedInput !== undefined ? { action: "allow", updatedInput: decision.updatedInput } : { action: "allow" };
@@ -4227,7 +1960,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4227
1960
  ...riskAxesOf(creq.toolName),
4228
1961
  ...(inheritedAsk.action === "ask" && inheritedAsk.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
4229
1962
  ...(inheritedAsk.action === "ask" && inheritedAsk.persistedRuleShadowed !== undefined ? { persistedRuleShadowed: inheritedAsk.persistedRuleShadowed } : {}),
4230
- ...(fallback !== undefined ? { denialLimitFallback: fallback } : {}),
1963
+ ...inheritedAskCarry(inheritedOrigin, fallback, isLiveApproverSeat(pc.onAsk), pc.autoMode?.denialTracking),
4231
1964
  ruleEvidence: inheritedAskEvidence,
4232
1965
  }, pc.onAsk, csignal ?? abortController.signal, lateAskSettlementObserver({ toolName: creq.toolName, toolCallId: creq.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice, onError: deps.onError }));
4233
1966
  const askWaitMs = Math.max(0, now() - askT0);
@@ -4337,131 +2070,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4337
2070
  },
4338
2071
  }
4339
2072
  : foldedPolicy;
4340
- const onAsk = spec.onAsk ?? deps.onAsk;
4341
- const handWriteTools = handsEnabled && spec.handsReadOnly !== true
4342
- ? Object.keys(HAND_TOOL_EFFECTS).filter((name) => {
4343
- const eff = HAND_TOOL_EFFECTS[name];
4344
- return eff === "write" || eff === "idempotent";
4345
- })
4346
- : [];
4347
- const hasEffectAwareGate = Boolean(policyLayers.length > 0 || (hooks?.preToolUse !== undefined && !preToolUseObservational));
4348
- const destructiveMcpUngated = mcp.tools.some((t) => !irreversibleTools.has(t.name) && !egressTools.has(t.name) && (toolEffects.get(t.name) ?? "write") !== "read");
4349
- const firstPartyWriteUngated = (spec.tools ?? [])
4350
- .map((t) => t.name)
4351
- .filter((name) => !irreversibleTools.has(name) && !egressTools.has(name) && toolEffects.get(name) === "write");
4352
- if (!hasEffectAwareGate && (handWriteTools.length > 0 || destructiveMcpUngated || firstPartyWriteUngated.length > 0)) {
4353
- const what = [
4354
- handWriteTools.length > 0 ? `hand tools [${handWriteTools.join(", ")}]` : undefined,
4355
- destructiveMcpUngated ? "MCP write tools" : undefined,
4356
- firstPartyWriteUngated.length > 0 ? `write tools [${firstPartyWriteUngated.join(", ")}]` : undefined,
4357
- ]
4358
- .filter(Boolean)
4359
- .join(" + ");
4360
- if (!ungatedWarnedShapes.get(deps)?.has(what)) {
4361
- let shapes = ungatedWarnedShapes.get(deps);
4362
- if (!shapes) {
4363
- shapes = new Set();
4364
- ungatedWarnedShapes.set(deps, shapes);
4365
- }
4366
- shapes.add(what);
4367
- deps.onError?.(new Error(`write-capable ${what} are present but UNGATED — wire an effect-aware tool policy (or a PreToolUse hook). ` +
4368
- `bash/MCP irreversible actions are otherwise unadjudicated; the deployment sandbox/egress boundary is the only protection (design/77 §4, design/53 §2.H). ` +
4369
- `(This warning fires once per deployment per tool-set shape.)`), { phase: "config", sessionId });
4370
- }
4371
- }
4372
- const preToolContexts = new Map();
4373
- const blockedToolCalls = new Set();
4374
- const approvalSettlement = new Map();
4375
- const humanBareRejections = new Set();
4376
- const batchHaltRef = {};
4377
- const blockedTracked = Boolean(hooks?.postToolUse || hooks?.preToolUse || hooks?.postToolUseFailure || hooks?.postToolBatch);
4378
- const restoreSurfaceGap = ownedEnv !== undefined && isRemoteExecutionEnv(ownedEnv) && ownedEnv.capabilities.suspendable ? missingRestoreSurface(ownedEnv) : [];
4379
- const incompleteSuspendAdapter = restoreSurfaceGap.length > 0 ? restoreSurfaceGap : undefined;
4380
- const durableSuspendInfraReady = resolveCheckpointStore(spec, deps) !== undefined &&
4381
- !(offloadStore !== undefined && isVolatileOffloadStore(offloadStore)) &&
4382
- (ownedEnv === undefined || isRemoteExecutionEnv(ownedEnv)) &&
4383
- incompleteSuspendAdapter === undefined;
4384
- const resourceSuspendEligible = spec.resourceSuspend !== undefined && durableSuspendInfraReady;
4385
- if (spec.resourceSuspend !== undefined && !resourceSuspendEligible) {
4386
- const why = resolveCheckpointStore(spec, deps) === undefined
4387
- ? "no CheckpointStore is wired"
4388
- : offloadStore !== undefined && isVolatileOffloadStore(offloadStore)
4389
- ? "tool-result offload uses the in-memory store (a resume needs durable results)"
4390
- : incompleteSuspendAdapter !== undefined
4391
- ? `the per-task execution env declares capabilities.suspendable but its adapter does not implement ${incompleteSuspendAdapter.join(" or ")} (a snapshot nothing can restore is worse than no snapshot)`
4392
- : "the per-task execution env is not a RemoteExecutionEnv (it would be destroyed on suspend)";
4393
- deps.onError?.(new Error(`resourceSuspend is set but INACTIVE: ${why}; resource limits will hard-fail, not suspend`), {
4394
- phase: "config",
4395
- sessionId,
4396
- });
4397
- }
4398
- const usageGovernance = buildUsageGovernance(usageWindows, deps, spec.principal, sessionId);
4399
- const platformSuspendArmed = durableSuspendInfraReady && (envLifetimeSuspendAt !== undefined || usageGovernance !== undefined);
4400
- const durableApproval = spec.durableApproval ??
4401
- (runtimeCaps?.forceDurableGate ? { scope: spec.principal || DEFAULT_IRREVERSIBLE_SCOPE } : undefined);
4402
- const wiringQuestionSeam = resolveQuestionSeam(spec, deps);
4403
- const wiringQuestionStripped = internals?.questionFaceStripped === true;
4404
- const wiringAskSeam = resolveAskSeamForm(spec, deps);
4405
- const wiringManifest = deriveWiringManifest({
4406
- half: "effective",
4407
- leg: resume !== undefined ? "resume" : internals?.isDelegatedChild === true ? "child" : "root",
4408
- askForm: wiringAskSeam.form,
4409
- ...(wiringAskSeam.provenance !== undefined ? { askProvenance: wiringAskSeam.provenance } : {}),
4410
- questionWired: wiringQuestionSeam.wired,
4411
- ...(wiringQuestionSeam.provenance !== undefined ? { questionProvenance: wiringQuestionSeam.provenance } : {}),
4412
- ...(wiringQuestionStripped ? { questionStrippedByEngine: true } : {}),
4413
- ...(resolvedInteractionPosture !== undefined ? { interactionPosture: resolvedInteractionPosture } : {}),
4414
- ...(spec.interactiveTools === true && !wiringQuestionSeam.wired && !durableQuestionFace && !wiringQuestionStripped
4415
- ? { interactiveToolsWithoutDeliveryFace: true }
4416
- : {}),
4417
- elicitSeamWired: resolveElicitSeam(deps),
4418
- elicitServersOptedIn: countElicitOptIns(spec),
4419
- parkCapable: checkpointStore !== undefined,
4420
- parkDurableApprovalOptIn: spec.durableApproval !== undefined,
4421
- parkForceDurableGate: runtimeCaps?.forceDurableGate === true,
4422
- parkSafetyVocabularyArmed: irreversibleTools.size > 0 || egressTools.size > 0,
4423
- ...(checkpointStore !== undefined ? { checkpointDurability: resolveDeclaredDurability(checkpointStore, "checkpointStore") } : {}),
4424
- sessionDurability: resolveDeclaredDurability(sessions, "sessionStore"),
4425
- backgroundAgentStoreWired: deps.backgroundAgentStore !== undefined,
4426
- subagentTranscriptTier: resolveSubagentTranscriptTier(deps.backgroundAgentStore !== undefined, sessions),
4427
- permissionRuleStoreWired: deps.permissionRuleStore !== undefined,
4428
- permissionRuleSyncWired: deps.permissionRuleSyncWired === true,
4429
- permissionRuleOrgGoverned: deps.permissionRuleOrg !== undefined,
4430
- hostChildEventSinkWired: deps.onBackgroundChildEvent !== undefined,
4431
- lockedConfigWired: deps.lockedConfig !== undefined,
4432
- complianceWired: deps.compliancePostureResolver !== undefined,
4433
- memoryAdmissionWired: deps.memoryScopeAdmission !== undefined,
4434
- retentionPolicyWired: deps.retentionPolicy !== undefined,
4435
- ...(modelGateManifest !== undefined ? { modelGate: modelGateManifest } : {}),
4436
- autoMode: { armed: autoModeArmReason === "armed", reason: autoModeArmReason },
4437
- });
4438
- peerLaneRefs.askEffective = wiringManifest.ask.effective;
4439
- if (peerLaneActive && internals?.onTaskNotification !== undefined) {
4440
- bindPeerLaneDrain(harness, {
4441
- deps,
4442
- sessionId,
4443
- runId,
4444
- scope: taskScope,
4445
- inject: internals.onTaskNotification,
4446
- ownTokens: () => internals?.peerSelfRef?.current.ownTokens ?? [],
4447
- refs: peerLaneRefs,
4448
- selfName: () => internals?.explicitAgentName,
4449
- parked: () => suspendRef.token !== undefined || reviewRef.token !== undefined,
4450
- });
4451
- }
4452
- const parkLaneArmed = wiringManifest.parkLane.effective === true;
4453
- announceDurableGateUnavailable({ onNotice: deps.onNotice, forceDurableGate: runtimeCaps?.forceDurableGate === true, storeWired: checkpointStore !== undefined, taskStoreNull: spec.checkpointStore === null, liveApprover: isLiveApproverSeat(frozenOnAsk), liveQuestionFace: liveQuestionFace !== undefined, parentConstraints: liveInheritedGate?.parentConstraints, sessionId, runId, principal: spec.principal });
4454
- const hookIdentity = mintHookInvocationIdentity({
4455
- sessionId,
4456
- taskId: hostTaskId,
4457
- legKind: wiringManifest.leg.kind,
4458
- isDelegatedChild: delegation.isDelegatedChild,
4459
- ...(internals?.insideFork === true ? { insideFork: true } : {}),
4460
- ...(internals?.agentName !== undefined ? { agentName: internals.agentName } : {}),
4461
- ...(internals?.parentToolCallId !== undefined ? { parentToolCallId: internals.parentToolCallId } : {}),
4462
- });
4463
- const hookContextConsumerWired = hooks?.preToolUse !== undefined || hooks?.postToolUse !== undefined || hooks?.postToolUseFailure !== undefined;
4464
- const hookEnvFace = hookContextConsumerWired && (ownedEnv ?? deps.executionEnv) != null ? createHookEnvCapabilities(executionEnv) : undefined;
2073
+ const { onAsk, preToolContexts, blockedToolCalls, approvalSettlement, humanBareRejections, batchHaltRef, blockedTracked, incompleteSuspendAdapter, durableSuspendInfraReady, resourceSuspendEligible, usageGovernance, platformSuspendArmed, durableApproval, wiringManifest, parkLaneArmed, hookIdentity, hookEnvFace } = prepareWiringManifest({ spec, deps, internals, resume, sessionId, runId, hostTaskId, taskScope, hooks, preToolUseObservational, policyLayers, handsEnabled, mcp, irreversibleTools, egressTools, toolEffects, ownedEnv, executionEnv, offloadStore, onceLedger, usageWindows, envLifetimeSuspendAt, runtimeCaps, resolvedInteractionPosture, durableQuestionFace, liveQuestionFace, checkpointStore, sessions, modelGateManifest, autoModeArmReason, lockedPreflight, peerLaneRefs, peerLaneActive, harness, suspendRef, reviewRef, frozenOnAsk, liveInheritedGate, delegation });
4465
2074
  if (fileHistoryBoundary !== undefined) {
4466
2075
  harness.on("tool_call", async () => {
4467
2076
  await fileHistoryBoundary.settle();
@@ -4509,7 +2118,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4509
2118
  ...riskAxesOf(req.toolName),
4510
2119
  ...(decision.action === "ask" && decision.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
4511
2120
  ...(decision.action === "ask" && decision.persistedRuleShadowed !== undefined ? { persistedRuleShadowed: decision.persistedRuleShadowed } : {}),
4512
- ...(decision.action === "ask" && decision.denialLimitFallback !== undefined ? { denialLimitFallback: decision.denialLimitFallback } : {}),
2121
+ ...(decision.action === "ask" ? gateAskCarry(decision, isLiveApproverSeat(onAsk), autoModeDenialTracking) : {}),
4513
2122
  ...(decision.action === "ask" && decision.probeReason !== undefined ? { probeReason: decision.probeReason } : {}),
4514
2123
  ...(decision.action === "ask" && decision.probeCause !== undefined ? { probeCause: decision.probeCause } : {}),
4515
2124
  ...(decision.action === "ask" && decision.ruleEvidence !== undefined ? { ruleEvidence: decision.ruleEvidence } : {}),
@@ -4616,13 +2225,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4616
2225
  }
4617
2226
  : undefined;
4618
2227
  })(),
4619
- announcedListings: announcedListingsRef.agents !== undefined || announcedListingsRef.skills !== undefined || announcedListingsRef.models !== undefined
4620
- ? {
4621
- ...(announcedListingsRef.agents !== undefined ? { agents: [...announcedListingsRef.agents] } : {}),
4622
- ...(announcedListingsRef.skills !== undefined ? { skills: [...announcedListingsRef.skills] } : {}),
4623
- ...(announcedListingsRef.models !== undefined ? { models: [...announcedListingsRef.models] } : {}),
4624
- }
4625
- : undefined,
2228
+ announcedListings: serializeAnnouncedListings(announcedListingsRef),
4626
2229
  gitAnnouncement: gitStatusRef.announced !== undefined ? { ...gitStatusRef.announced } : undefined,
4627
2230
  delegationProvenance: internals?.delegationProvenance !== undefined ? { ...internals.delegationProvenance.ref.current } : undefined,
4628
2231
  isDelegatedChild: hookIdentity.isDelegatedChild ? true : undefined,
@@ -4999,7 +2602,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4999
2602
  }
5000
2603
  };
5001
2604
  const suspendAsk = parkLaneArmed && checkpointStore !== undefined
5002
- ? async (req, postHookArgs, safety, liveFaceUnavailable, realApproval, shadowedRule, askDecisionReason, probeReason, probeCause, segmentCoverage, matchedAskRule, probeMandated, callSignal) => {
2605
+ ? async (req, postHookArgs, safety, liveFaceUnavailable, realApproval, shadowedRule, askDecisionReason, probeReason, probeCause, segmentCoverage, matchedAskRule, probeMandated, carry) => {
5003
2606
  const syncFirstEligible = req.toolName === ASK_USER_QUESTION_TOOL_NAME ? contentAskRoutable(req.toolCallId) : isLiveApproverSeat(onAsk);
5004
2607
  if (syncFirstEligible &&
5005
2608
  runtimeCaps?.forceDurableGate !== true &&
@@ -5007,7 +2610,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
5007
2610
  !inheritedUnavailableAsks.has(req.toolCallId)) {
5008
2611
  return undefined;
5009
2612
  }
5010
- const cutSignal = composedCallSignal(callSignal);
2613
+ const cutSignal = composedCallSignal(carry?.signal);
5011
2614
  let token;
5012
2615
  let gate;
5013
2616
  let cp;
@@ -5179,6 +2782,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
5179
2782
  ...(bidi ? { hasBidiControls: true } : {}),
5180
2783
  };
5181
2784
  })(),
2785
+ ...(realApproval !== undefined ? { requiresRealApproval: true } : {}),
2786
+ ...askCarryRowMembers(carry),
5182
2787
  ...ruleOffersOf(req.toolName, parkedArgs, {
5183
2788
  ...(realApproval !== undefined ? { requiresRealApproval: true } : {}),
5184
2789
  ...(shadowedRule !== undefined ? { persistedRuleShadowed: shadowedRule } : {}),
@@ -5255,7 +2860,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
5255
2860
  if (eff !== undefined && !toolEffects.has(t.name))
5256
2861
  toolEffects.set(t.name, eff);
5257
2862
  }
5258
- const writeProtectionCheck = createWriteProtectionCheck(deps.writeProtectedPaths);
2863
+ const writeProtectionCheck = createWriteProtectionCheck(deps.writeProtectedPaths, deps.memoryEngineDir !== undefined ? { dataRoot: deps.memoryEngineDir } : undefined);
5259
2864
  const writeProtectionArmed = writeProtectionCheck !== undefined && tools.some((t) => PATH_CONFINABLE_WRITE_TOOLS.has(t.name));
5260
2865
  toolCallGateArmedRef.armed =
5261
2866
  effectivePolicy !== undefined ||
@@ -5759,25 +3364,16 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
5759
3364
  overheadState.promptChars = systemPrompt.length;
5760
3365
  const effectiveReadFaceObserved = carrierReadFace();
5761
3366
  const effectiveReadDenyObserved = readDenyAdditionsNormalized.length > 0 ? readDenyAdditionsNormalized.map((e) => ({ ...e })) : undefined;
3367
+ await onceLedger.settle(session, announcedListingsRef);
5762
3368
  const preparedHolder = {};
5763
- const buildPrepared = () => ({ harness, session, sessionId, runId, reminderMark, reminderDisclosureCounts, editedFilesSnapshot, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, approvalSettlement, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(fileHistoryBoundary !== undefined ? { fileHistoryBoundary } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), ...(sealReadStateSeat !== undefined ? { sealReadStateSeat } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, suspendRef, suspendProgressRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, gateStopRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, microCompact, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(projectInstructionContent !== undefined ? { projectInstructionContent } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, gitStatusRef, listBackgroundTasks, hookIdentity, hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
3369
+ const buildPrepared = () => ({ harness, session, sessionId, runId, reminderMark, reminderDisclosureCounts, editedFilesSnapshot, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp, ...(a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, approvalSettlement, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(fileHistoryBoundary !== undefined ? { fileHistoryBoundary } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), ...(sealReadStateSeat !== undefined ? { sealReadStateSeat } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, suspendRef, suspendProgressRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, gateStopRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, microCompact, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, stopRequestedRef, announcedSnapshotRecovered: onceLedger.recovered, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(projectInstructionContent !== undefined ? { projectInstructionContent } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, gitStatusRef, listBackgroundTasks, hookIdentity, hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
3370
+ rollback.commit();
5764
3371
  const prepared = buildPrepared();
5765
3372
  preparedHolder.current = prepared;
5766
3373
  return prepared;
5767
3374
  }
5768
3375
  catch (prepareErr) {
5769
- if (a2a) {
5770
- const a2aHandle = a2a;
5771
- await settleTeardownLeg(() => a2aHandle.dispose(), "a2a.dispose (prepare-throw leg)", (e) => deps.onError?.(e, { phase: "config", sessionId }));
5772
- }
5773
- if (mcp) {
5774
- const mcpHandle = mcp;
5775
- await settleTeardownLeg(() => mcpHandle.dispose(), "mcp.dispose (prepare-throw leg)", (e) => deps.onError?.(e, { phase: "config", sessionId }));
5776
- }
5777
- if (ownedEnv && hasDestroy(ownedEnv)) {
5778
- const env = ownedEnv;
5779
- await settleTeardownLeg(() => env.destroy(), "ownedEnv.destroy (prepare-throw leg)", (e) => deps.onError?.(e, { phase: "config", sessionId }));
5780
- }
3376
+ await rollback.unwindAll();
5781
3377
  await settleTeardownLeg(() => forgetOnThrow(), "forgetOnThrow (prepare-throw leg)", (e) => deps.onError?.(e, { phase: "config", sessionId }));
5782
3378
  throw prepareErr;
5783
3379
  }