@sema-agent/core 7.6.3 → 7.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/CHANGELOG.md +70 -10
  2. package/dist/agents/agent-transcript-tool.js +2 -2
  3. package/dist/agents/list-agents-tool.js +2 -3
  4. package/dist/agents/observer.js +2 -2
  5. package/dist/agents/peer-admission.d.ts +1 -1
  6. package/dist/agents/send-message-tool.js +2 -1
  7. package/dist/agents/subagent-steps.js +5 -4
  8. package/dist/agents/subagent.js +2 -3
  9. package/dist/brain/anthropic.js +8 -2
  10. package/dist/brain/open-responses.js +5 -3
  11. package/dist/brain/openai.js +31 -8
  12. package/dist/brain/reasoning.d.ts +32 -0
  13. package/dist/brain/reasoning.js +18 -0
  14. package/dist/core/ask-question.js +2 -2
  15. package/dist/core/auto-mode-defaults.d.ts +16 -0
  16. package/dist/core/auto-mode-defaults.js +1 -0
  17. package/dist/core/auto-mode.d.ts +19 -0
  18. package/dist/core/auto-mode.js +74 -56
  19. package/dist/core/checkpoint-execution-record.d.ts +110 -0
  20. package/dist/core/checkpoint-execution-record.js +49 -0
  21. package/dist/core/checkpoint-store.d.ts +114 -2
  22. package/dist/core/checkpoint-store.js +35 -2
  23. package/dist/core/context-edit.d.ts +0 -22
  24. package/dist/core/context-edit.js +2 -11
  25. package/dist/core/engine-notice.d.ts +17 -0
  26. package/dist/core/fs-write-gate-policy.d.ts +7 -1
  27. package/dist/core/fs-write-gate-policy.js +7 -7
  28. package/dist/core/gate-fold.js +2 -2
  29. package/dist/core/gate-lanes.js +4 -3
  30. package/dist/core/governance-codes.d.ts +1 -1
  31. package/dist/core/governance-codes.js +8 -0
  32. package/dist/core/hooks.d.ts +8 -2
  33. package/dist/core/hooks.js +1 -1
  34. package/dist/core/lsp.js +2 -2
  35. package/dist/core/mcp-server-spec.d.ts +17 -0
  36. package/dist/core/mcp.js +3 -1
  37. package/dist/core/memory-engine/tools.js +4 -12
  38. package/dist/core/permission-rules.d.ts +32 -2
  39. package/dist/core/permission-rules.js +43 -37
  40. package/dist/core/present-plan-tool.js +3 -4
  41. package/dist/core/runner/active-skill-scope.js +7 -4
  42. package/dist/core/runner/clock-and-limits.d.ts +117 -0
  43. package/dist/core/runner/clock-and-limits.js +118 -0
  44. package/dist/core/runner/compaction-call-options.d.ts +1 -1
  45. package/dist/core/runner/contracts.d.ts +37 -3
  46. package/dist/core/runner/decide-continuation.d.ts +98 -0
  47. package/dist/core/runner/decide-continuation.js +133 -0
  48. package/dist/core/runner/execution-record.d.ts +26 -0
  49. package/dist/core/runner/execution-record.js +19 -0
  50. package/dist/core/runner/git-leg-delivery.d.ts +28 -0
  51. package/dist/core/runner/git-leg-delivery.js +94 -0
  52. package/dist/core/runner/initial-run-state.d.ts +14 -0
  53. package/dist/core/runner/initial-run-state.js +11 -0
  54. package/dist/core/runner/prepare-ask-lane.d.ts +1 -1
  55. package/dist/core/runner/prepare-caps-and-workflow.d.ts +5 -4
  56. package/dist/core/runner/prepare-caps-and-workflow.js +33 -11
  57. package/dist/core/runner/prepare-defer-classify.d.ts +5 -26
  58. package/dist/core/runner/prepare-defer-classify.js +10 -10
  59. package/dist/core/runner/prepare-delegation-surface.d.ts +5 -4
  60. package/dist/core/runner/prepare-delegation-surface.js +15 -17
  61. package/dist/core/runner/prepare-gate-stations.d.ts +4 -1
  62. package/dist/core/runner/prepare-gate-stations.js +3 -1
  63. package/dist/core/runner/prepare-hands-readface.d.ts +3 -2
  64. package/dist/core/runner/prepare-hands-readface.js +3 -2
  65. package/dist/core/runner/prepare-inherited-gate.d.ts +1 -1
  66. package/dist/core/runner/prepare-inherited-gate.js +1 -0
  67. package/dist/core/runner/prepare-lsp.d.ts +4 -3
  68. package/dist/core/runner/prepare-lsp.js +3 -2
  69. package/dist/core/runner/prepare-memory-engine-session.d.ts +4 -4
  70. package/dist/core/runner/prepare-memory-engine-session.js +4 -2
  71. package/dist/core/runner/prepare-offload-wrappers.js +7 -5
  72. package/dist/core/runner/prepare-park-ask.d.ts +4 -0
  73. package/dist/core/runner/prepare-park-ask.js +4 -1
  74. package/dist/core/runner/prepare-project-context.d.ts +5 -23
  75. package/dist/core/runner/prepare-project-context.js +7 -5
  76. package/dist/core/runner/prepare-prompt-assembly.d.ts +1 -1
  77. package/dist/core/runner/prepare-protocol-tools.d.ts +5 -1
  78. package/dist/core/runner/prepare-protocol-tools.js +38 -21
  79. package/dist/core/runner/prepare-question-face.d.ts +4 -3
  80. package/dist/core/runner/prepare-question-face.js +5 -3
  81. package/dist/core/runner/prepare-safety-scan.js +42 -2
  82. package/dist/core/runner/prepare-task.js +37 -19
  83. package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +5 -2
  84. package/dist/core/runner/prepare-tool-disclosure-mount.js +3 -1
  85. package/dist/core/runner/prepare-wiring-manifest.d.ts +4 -0
  86. package/dist/core/runner/prepare-wiring-manifest.js +3 -8
  87. package/dist/core/runner/run-harness-handlers.d.ts +46 -0
  88. package/dist/core/runner/run-harness-handlers.js +345 -0
  89. package/dist/core/runner/run-turn-boundary.d.ts +69 -0
  90. package/dist/core/runner/run-turn-boundary.js +694 -0
  91. package/dist/core/runner/runtask.d.ts +0 -68
  92. package/dist/core/runner/runtask.js +66 -1486
  93. package/dist/core/runner/session-rule-policy.js +9 -4
  94. package/dist/core/runner/steer-admission.d.ts +17 -0
  95. package/dist/core/runner/steer-admission.js +17 -0
  96. package/dist/core/runner/synthetic-tools.js +5 -8
  97. package/dist/core/runner/tool-disclosure.d.ts +4 -0
  98. package/dist/core/runner/tool-disclosure.js +9 -5
  99. package/dist/core/runner/tool-end-body.d.ts +71 -0
  100. package/dist/core/runner/tool-end-body.js +75 -0
  101. package/dist/core/runner/tool-face-overlay.d.ts +40 -0
  102. package/dist/core/runner/tool-face-overlay.js +125 -0
  103. package/dist/core/runner/tool-output-projection.d.ts +10 -0
  104. package/dist/core/runner/tool-output-projection.js +33 -10
  105. package/dist/core/sensitive-path-policy.d.ts +3 -0
  106. package/dist/core/sensitive-path-policy.js +6 -5
  107. package/dist/core/shared-memory/tools.js +3 -6
  108. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +4 -0
  109. package/dist/core/store-contracts/checkpoint-store-contract.js +108 -0
  110. package/dist/core/task-event.d.ts +10 -0
  111. package/dist/core/task-registry.js +4 -5
  112. package/dist/core/task-spec.d.ts +8 -0
  113. package/dist/core/tool-catalog-entries.d.ts +48 -0
  114. package/dist/core/tool-catalog-entries.js +174 -0
  115. package/dist/core/tool-conformance.d.ts +22 -0
  116. package/dist/core/tool-conformance.js +37 -0
  117. package/dist/core/tool-face.d.ts +171 -0
  118. package/dist/core/tool-face.js +25 -0
  119. package/dist/core/tool-policy.d.ts +24 -0
  120. package/dist/core/tool-policy.js +4 -2
  121. package/dist/core/tool-registry.d.ts +114 -0
  122. package/dist/core/tool-registry.js +114 -0
  123. package/dist/core/tool-result-budget.d.ts +0 -3
  124. package/dist/core/tool-result-budget.js +4 -3
  125. package/dist/core/tool-result-store.d.ts +0 -18
  126. package/dist/core/tool-result-store.js +2 -18
  127. package/dist/core/tool-roster.d.ts +450 -0
  128. package/dist/core/tool-roster.js +497 -0
  129. package/dist/core/tool-spec.d.ts +54 -3
  130. package/dist/core/tools.js +7 -0
  131. package/dist/core/trace.d.ts +24 -0
  132. package/dist/core/wiring-manifest.d.ts +13 -0
  133. package/dist/core/wiring-manifest.js +2 -1
  134. package/dist/core/write-protect.d.ts +2 -1
  135. package/dist/core/write-protect.js +5 -4
  136. package/dist/engine/compaction/utils.d.ts +6 -0
  137. package/dist/engine/compaction/utils.js +19 -19
  138. package/dist/engine/loop/types.d.ts +6 -0
  139. package/dist/index.d.ts +20 -8
  140. package/dist/index.js +19 -7
  141. package/dist/orchestration/goal.js +2 -1
  142. package/dist/orchestration/run-spec.js +3 -5
  143. package/dist/orchestration/run-workflow-tool.js +2 -2
  144. package/dist/prompt-assembly/event-registry.js +3 -3
  145. package/dist/prompt-assembly/tool-catalog.d.ts +2 -2
  146. package/dist/stores/file/checkpoint-store.d.ts +7 -0
  147. package/dist/stores/file/checkpoint-store.js +20 -1
  148. package/dist/tools/fs/fs-bash.d.ts +1 -1
  149. package/dist/tools/fs/fs-bash.js +7 -11
  150. package/dist/tools/fs/fs-read.js +2 -2
  151. package/dist/tools/fs/fs-search-tools.d.ts +0 -7
  152. package/dist/tools/fs/fs-search-tools.js +3 -16
  153. package/dist/tools/fs/fs-write.js +4 -6
  154. package/dist/tools/fs/repo-map.js +2 -2
  155. package/dist/tools/fs/safety.d.ts +2 -1
  156. package/dist/tools/fs/safety.js +6 -5
  157. package/dist/tools/monitor.js +2 -2
  158. package/dist/tools/scheduler-tools.js +5 -8
  159. package/dist/tools/task-list.js +5 -8
  160. package/dist/tools/todo.js +2 -2
  161. package/dist/tools/web.js +3 -6
  162. package/dist/tools/worktree.js +3 -4
  163. package/package.json +4 -1
  164. package/test/export-surface.snapshot.json +183 -7
@@ -7,24 +7,22 @@ import { planRejectionClears, resolveTriggerWindow } from "../context-edit.js";
7
7
  import { AgentHarness, DEFAULT_COMPACTION_SETTINGS, isSyntheticApiErrorMessage, uuidv7 } from "../../internal/harness.js";
8
8
  import { snapshotActorAssertion } from "../../internal/llm.js";
9
9
  import { CheckpointError, BINDING_CHECKPOINT_VERSION, checkpointVersionOf, F012_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, FACE_CHECKPOINT_VERSION, remainingBudgetMicroUsd, readPendingSteerQueue, remainingTokens, resolveCheckpointStore, LEGACY_PENDING_STEER_INPUT_ID, MAX_STEER_INPUT_ID_CHARS, validatePendingSteer, winnerFromOutcome, realApprovalOrgFact } from "../checkpoint-store.js";
10
- import { GIT_STATUS_ECHO_PREVIEW, branchCarriesVisiblePositiveGitFrame, newestEngineGitFrame, stripGitStatusUnits } from "./git-status-frame.js";
11
- import { gitFrameContextVisible, normalizeGitAnnouncement } from "../../internal/harness.js";
10
+ import { GIT_STATUS_ECHO_PREVIEW, stripGitStatusUnits } from "./git-status-frame.js";
11
+ import { settleExecutionRecord } from "./execution-record.js";
12
12
  import { engineVersion } from "../version.js";
13
13
  import { CONFIG_CATALOG_VERSION, declarationReasons, resolveEffectiveConfig } from "../../config/catalog.js";
14
14
  import { eventDefaultOn } from "../../prompt-assembly/event-registry.js";
15
- import { DEFAULT_COMPACTION_INSTRUCTIONS, createRapidRefillState, isCompactionManualCancel, maybeCompact, nextTrimForceBackoff, recordCompactionAndCheckRapidRefill, sanitizeCompactionSettings } from "../auto-compaction.js";
16
- import { ASK_USER_QUESTION_TOOL_NAME, canonicalizeCapturedPlainData, classifyQuestionOutcome, isLiveQuestionFace, markBoundOnlyQuestionFace } from "../ask-question.js";
15
+ import { DEFAULT_COMPACTION_INSTRUCTIONS, createRapidRefillState, maybeCompact, sanitizeCompactionSettings } from "../auto-compaction.js";
16
+ import { ASK_USER_QUESTION_TOOL_NAME, canonicalizeCapturedPlainData, classifyQuestionOutcome, isLiveQuestionFace } from "../ask-question.js";
17
17
  import { boundInputHashOf } from "../canonical-json.js";
18
18
  import { computeCostMicroUsd, isModelPriced, malformedPricingField, modelCostToPricing } from "../pricing.js";
19
19
  import { emitTrace } from "../trace.js";
20
20
  import { ORG_ADJUDICATION_TIMEOUT_MS, settleOrgVerdictWithin } from "../permission-rule-org.js";
21
21
  import { emitTaskOutcome } from "../task-outcome.js";
22
22
  import { isDegenerateCutMessage } from "../../brain/terminal-cause.js";
23
- import { primaryActivityArg } from "../arg-summary.js";
24
23
  import { redactThenCut } from "../../agents/subagent-steps.js";
25
24
  import { resolveReasoning } from "../../brain/reasoning.js";
26
25
  import { adjudicateDerivedRoute, authCarrierFingerprint, fallbackToPrimaryNotice, normalizeBaseUrl, sameRouteIdentity } from "../../brain/route-adjudicator.js";
27
- import { readDegradation } from "../../brain/degrading.js";
28
26
  import { runWithBrainTelemetry, runWithReasoningWireFacts, runWithStatusSink } from "../../brain/status-sink.js";
29
27
  import { expandTiers, resolveModel, resolveTaskModel } from "../roles.js";
30
28
  import { runSideQuery } from "../side-query.js";
@@ -32,1457 +30,48 @@ import { generatePromptSuggestions } from "./prompt-suggestions.js";
32
30
  import { PushQueue } from "../push-queue.js";
33
31
  import { TtlSessionStore } from "../session-store.js";
34
32
  import { toImageContent } from "./image.js";
35
- import { OUTPUT_TOOL_NAME, SKILLS_LISTING_PROBE_HEADER, resolveOutputRetries } from "./synthetic-tools.js";
33
+ import { SKILLS_LISTING_PROBE_HEADER, resolveOutputRetries } from "./synthetic-tools.js";
36
34
  import { cacheFamilyOf, usageCostMicroUsd } from "./usage-accounting.js";
37
35
  import { assembleResult, errorCodeOf } from "./assemble-result.js";
38
36
  import { amendTerminal, terminalProjection } from "./terminal-projection.js";
39
- import { ATTACHMENT_BYTE_CAP, CHANGED_FILES_MAX, TOTAL_TOKENS_REMINDER_DEFAULT_MODE, AGENT_LISTING_REMOVED_HEADER, SKILLS_LISTING_DELTA_HEADER, SKILLS_LISTING_REMOVED_HEADER, advanceCadenceClock, agentListingDeltaHeader, attachmentEnvelopeTags, agentListingInitialHeader, replayAnnouncedListing, replayAnnouncedModels, clipToBytes, collectDateChange, collectDueAttachments, collectInstructionsChange, commitAgentListing, commitInstructionsChange, commitSkillsListing, createAttachmentState, rebaseCadenceWindows, reduceToolEnd, renderAgentListingDelta, renderMcpDroppedTools, renderMcpInstructionsDelta, renderOrphanedBackgroundTasks, selectMcpDroppedBatch, renderSkillsListingDelta, renderToolsDelta, stampWriteAnchor } from "./turn-attachments.js";
37
+ import { AGENT_LISTING_REMOVED_HEADER, SKILLS_LISTING_DELTA_HEADER, SKILLS_LISTING_REMOVED_HEADER, agentListingDeltaHeader, attachmentEnvelopeTags, agentListingInitialHeader, replayAnnouncedListing, replayAnnouncedModels, commitAgentListing, commitSkillsListing, createAttachmentState, rebaseCadenceWindows, reduceToolEnd, renderAgentListingDelta, renderOrphanedBackgroundTasks, renderSkillsListingDelta, stampWriteAnchor } from "./turn-attachments.js";
40
38
  import { buildWorkingFileAttachments, centerAdoptionOption, contextInstructionFilesOption, emitInputTruncated, forkContextOption } from "./compaction-call-options.js";
41
39
  import { effectiveDelegationFacts, prepareTask } from "./prepare-task.js";
42
40
  import { drainForwardedFramesBeforeDone, forwardsSubagentEvents } from "./prepare-run-refs.js";
43
- import { gatedCallIdOf } from "./park-commit.js";
41
+ import { callFaceSeat, judgeParkedToolIdentity, parkedCallFaceOf, rosterEntryNamed, toolCallFaceOf } from "../tool-roster.js";
44
42
  import { placementValueOrAbsent } from "./checkpoint-scope.js";
45
43
  import { SessionReadFileStates } from "./prepare-hands-readface.js";
46
44
  import { settleTeardownLeg } from "./teardown-bounded.js";
47
- import { TOOL_SEARCH_NAME } from "./tool-disclosure.js";
48
- import { hasVerifiableStructureSignal } from "./grounding-signal.js";
49
45
  import { hasDestroy, isIsolated } from "../remote-env.js";
50
46
  import { hasBackgroundShell, sweepBackgroundShells } from "../background-shell.js";
51
47
  import { cloneObserverInput, formatHookFeedback, hookSeatExpiredError, MAX_HOOK_TIMEOUT_MS, mintHookInvocationIdentity, resolveHookTimeoutMs, runHookSeat } from "../hooks.js";
52
48
  import { buildHumanInputEvent, frameMidTurnUserInput, projectHumanInput } from "../human-input-projection.js";
53
- import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY, sanitizeUntrustedText, SHELLED_BODY_ENVELOPE_TAGS } from "../untrusted-text.js";
54
- import { droppedToolDigest, withAnnounceDigests } from "./announce-once-ledger.js";
49
+ import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY, sanitizeUntrustedText } from "../untrusted-text.js";
55
50
  import { appendInterruptionMarker, reconcileInterruptedSession } from "../session-reconcile.js";
56
51
  import { RunnerSharedToolResultStore } from "../tool-result-store.js";
57
- import { formatDiagnosticsBlock } from "../lsp-diagnostics.js";
58
52
  import { isAskOrigin } from "../ask-origin.js";
59
53
  import { isGateKind, PAUSE_REGISTRY, resumeGateMatches } from "../pause-registry.js";
60
54
  import { checkToolPolicyProjection, constraintChainDigest, constraintChainEntryOfSuppliedLayer, refuseOutOfContractDecision, screenApproverAttribution, toolPolicyNameSets } from "../tool-policy.js";
61
55
  import { mintGateOutcome } from "./gate-exit.js";
62
- import { MCP_DELIVERY_VERDICTS } from "../mcp-failure.js";
63
56
  import { defaultTaskRegistry } from "../task-registry.js";
64
57
  import { discloseDroppedPending, isDelegatedAgentTerminal, isSystemInjectionPriority, isTerminalTaskNotification, PendingSessionNotifications, renderTaskNotificationXml, SYSTEM_INJECTION_PRIORITIES, SystemInjectionQueue, taskNotificationDedupKey } from "../task-notification.js";
65
58
  import { ToolDetachHub } from "../tool-detach.js";
66
59
  import { createPeerInboundChainRef, createPeerSelfRef } from "../../agents/peer-admission.js";
67
- import { workflowSizeGuidelineChangeNotice } from "../../orchestration/workflow-size-guideline.js";
68
- import { structuredFrom, toolOutputFrom } from "./tool-output-projection.js";
69
- function createRunState() {
70
- return {
71
- telemetry: { cacheFamily: "input-excludes-cached", pricing: { inputPer1M: 0, outputPer1M: 0 }, pricingConfigured: true, unpricedSpend: false, tracer: undefined, taskId: "", runId: "", taskStart: 0, taskStartMonotonic: 0, cacheBreakReported: false },
72
- degrade: { degradeToModel: undefined, degraded: undefined, outputErrorStreak: 0, outputInvalid: false, recordDegraded: () => { } },
73
- limits: { turnsExceeded: false, budgetHit: undefined, budgetAxis: undefined, platformTerminal: undefined, outputRetryCap: 0, effectiveMaxTurns: undefined },
74
- budget: { remainingMicroUsd: undefined, maxCostMicroUsd: undefined, remainingTokens: undefined, maxTokensWindow: undefined, overBudget: () => undefined, streamCancel: false, callOutputChars: 0, lastStreamBudgetCheck: 0, projectedOverBudget: () => undefined },
75
- turn: { callStartAt: undefined, firstTokenAt: undefined, turnUsage: undefined, turnUsageMissing: false, turnStopReason: undefined, lastTurnHadToolCalls: false, toolBatch: [] },
76
- counters: { approachNoticesSent: 0, walltimeSyncBackstopFired: false, compactionFloor: 0, trimForceBackoff: false, repetitionCuts: 0, repetitionSpared: 0, repetitionEvents: [], REPETITION_EVENTS_CAP: 0, preemptIgnoredReported: false, wroteThisRun: false, finalVerifyInjections: 0, groundingSignalPreR9: false, groundingSignalPostR9: false, cadenceTurns: 0 },
77
- attach: { attachmentsCfg: undefined, agentListingOn: false, skillsListingOn: false, attachState: undefined, dateState: undefined, instrProbe: undefined, instrState: undefined, sizeGuidelineState: undefined, attachmentsInjected: 0 },
78
- };
79
- }
80
- const humanInputOrdinals = new WeakMap();
81
- function nextHumanInputSeq(key) {
82
- let box = humanInputOrdinals.get(key);
83
- if (box === undefined) {
84
- box = { n: 0 };
85
- humanInputOrdinals.set(key, box);
86
- }
87
- return ++box.n;
88
- }
89
- function sameAcceptedSteerInput(a, b) {
90
- return (a.payload === b.payload &&
91
- a.trusted === b.trusted &&
92
- a.priority === b.priority &&
93
- a.actor?.id === b.actor?.id &&
94
- a.actor?.hostAsserted === b.actor?.hostAsserted &&
95
- a.actor?.issuer === b.actor?.issuer);
96
- }
97
- const MAX_CONSECUTIVE_COMPACTION_FAILURES = 3;
60
+ import { createRunState } from "./initial-run-state.js";
61
+ import { nextHumanInputSeq, sameAcceptedSteerInput } from "./steer-admission.js";
62
+ import { reconciledToolEndBody, toolEndBodyFrom, toolResultMsg, writeFamilyOfCanonical } from "./tool-end-body.js";
63
+ import { answerFaceForRedeemedCall, assertOutcomeText, deepJsonEqual, DEFERRED_REISSUE, describeSuppliedValue, pendingContentAskCallId, resumeContinuation, resumeDecisionWasNegative, sameWinner } from "./decide-continuation.js";
64
+ import { awaitChargeWithSlowDisclosure, DEFAULT_PRECALL_OUTPUT_TOKENS, discloseUnevaluableWindow, platformLimitTerminal, resolveMaxTurns, startTimeout, TIMER_LATENESS_REPORT_MS } from "./clock-and-limits.js";
65
+ import { gitRestateOption, resolveGitLegDelivery, wrapGitFrame } from "./git-leg-delivery.js";
66
+ import { COMPACTION_FREED_EPSILON, COMPACTION_REGROWTH_FACTOR, MAX_CONSECUTIVE_COMPACTION_FAILURES, createTurnBoundary } from "./run-turn-boundary.js";
67
+ import { createHarnessHandlers } from "./run-harness-handlers.js";
98
68
  const RESUME_PREFLIGHT_DEFAULT_TIMEOUT_MS = 10_000;
99
69
  const STOP_HOOK_BLOCK_CAP = 8;
100
- const COMPACTION_REGROWTH_FACTOR = 1.5;
101
- const COMPACTION_FREED_EPSILON = 256;
102
- const BATCH_RESPONSE_MAX_CHARS = 500;
103
70
  const ORG_DISCLOSURE_MAX_CHARS = 600;
104
- function batchResponseDigest(result) {
105
- const content = result !== null && typeof result === "object" ? result.content : result;
106
- if (content === undefined || content === null)
107
- return undefined;
108
- let text;
109
- if (typeof content === "string") {
110
- text = content;
111
- }
112
- else if (Array.isArray(content)) {
113
- text = content
114
- .map((c) => (c !== null && typeof c === "object" && typeof c.text === "string" ? c.text : ""))
115
- .filter((t) => t.length > 0)
116
- .join("\n");
117
- }
118
- else {
119
- try {
120
- text = JSON.stringify(content) ?? "";
121
- }
122
- catch {
123
- return undefined;
124
- }
125
- }
126
- if (text.length === 0)
127
- return undefined;
128
- return text.length > BATCH_RESPONSE_MAX_CHARS
129
- ? `${text.slice(0, BATCH_RESPONSE_MAX_CHARS)}…[+${text.length - BATCH_RESPONSE_MAX_CHARS} chars truncated]`
130
- : text;
131
- }
132
- const BATCH_CONTEXT_MIN_KEEP_BYTES = 160;
133
- const BATCH_TRUNCATION_MARKER = "\n…[truncated]";
134
71
  const SUGGESTIONS_DEFAULT_COUNT = 3;
135
72
  const SUGGESTIONS_MAX_COUNT = 8;
136
73
  const SUGGESTIONS_TIMEOUT_MS = 30_000;
137
74
  const SUGGESTIONS_TRANSCRIPT_MESSAGES = 12;
138
- const LIMIT_APPROACH_DEFAULT_THRESHOLDS = [0.8, 0.95];
139
- const DEFAULT_PRECALL_OUTPUT_TOKENS = 4096;
140
- function limitApproachFrames(index, axis, percent, mark) {
141
- return index === 0
142
- ? `${openSystemReminder(mark)}[limit approach] This task has used about ${percent}% of its ${axis}. ` +
143
- `Start converging: finish the step you are on, drop work that is not required for the deliverable, and ` +
144
- `avoid starting anything you cannot complete within what is left.</system-reminder>`
145
- : `${openSystemReminder(mark)}[limit approach] About ${percent}% of the ${axis} is gone. Deliver now: ` +
146
- `stop exploring, write up what you have — including what is incomplete and what you would do next — ` +
147
- `and end the task. Work that is not reported before the allowance runs out is lost.</system-reminder>`;
148
- }
149
- function resolveMaxTurns(limits) {
150
- return limits?.maxTurns;
151
- }
152
- function resumeDecisionWasNegative(resume) {
153
- const o = resume.outcome;
154
- return o.decision === "reject" || o.decision === "deny";
155
- }
156
- const DEFERRED_REISSUE = "[DEFERRED] This tool call shared a batch with a call that suspended for durable approval, so it was " +
157
- "NOT executed on resume. If you still need it, issue it again now.";
158
- function toolEndBodyFrom(result, isError, gate, gatedCallIdOfRun) {
159
- const o = toolOutputFrom(result);
160
- const st = structuredFrom(result);
161
- const det = isError ? result?.details : undefined;
162
- const codeRaw = det?.code;
163
- const kindRaw = det?.errorKind;
164
- const code = typeof codeRaw === "string" ? codeRaw : typeof kindRaw === "string" ? kindRaw : undefined;
165
- const deliveredRaw = det?.delivered;
166
- const delivered = typeof deliveredRaw === "string" && MCP_DELIVERY_VERDICTS.includes(deliveredRaw) ? deliveredRaw : undefined;
167
- const gatedCallId = code === "gate.parked" ? gatedCallIdOfRun : undefined;
168
- return {
169
- ...(o !== undefined ? { output: o.output, ...(o.truncated ? { truncated: true } : {}), ...(o.totalChars !== undefined ? { totalChars: o.totalChars } : {}) } : {}),
170
- ...(st !== undefined ? { structured: st } : {}),
171
- ...(typeof code === "string" ? { errorCode: code } : {}),
172
- ...(delivered !== undefined ? { delivered } : {}),
173
- ...(gatedCallId !== undefined ? { gatedCallId } : {}),
174
- ...(gate !== undefined ? { gate } : {}),
175
- };
176
- }
177
- export function reconciledToolEndBody(orphan) {
178
- return toolEndBodyFrom({ content: orphan.text, details: { code: orphan.errorKind } }, true);
179
- }
180
- function deepJsonEqual(a, b) {
181
- if (a === b)
182
- return true;
183
- if (typeof a !== typeof b || a === null || b === null || typeof a !== "object") {
184
- return false;
185
- }
186
- const aArr = Array.isArray(a);
187
- const bArr = Array.isArray(b);
188
- if (aArr !== bArr)
189
- return false;
190
- if (aArr && bArr) {
191
- if (a.length !== b.length)
192
- return false;
193
- for (let i = 0; i < a.length; i++) {
194
- if (!deepJsonEqual(a[i], b[i]))
195
- return false;
196
- }
197
- const aExtra = Object.keys(a).filter((k) => !isCanonicalIndexKey(k, a.length));
198
- const bExtra = Object.keys(b).filter((k) => !isCanonicalIndexKey(k, b.length));
199
- if (aExtra.length !== bExtra.length)
200
- return false;
201
- return aExtra.every((k) => Object.prototype.hasOwnProperty.call(b, k) && deepJsonEqual(Reflect.get(a, k), Reflect.get(b, k)));
202
- }
203
- const ao = a;
204
- const bo = b;
205
- const aKeys = Object.keys(ao);
206
- const bKeys = Object.keys(bo);
207
- if (aKeys.length !== bKeys.length)
208
- return false;
209
- return aKeys.every((k) => Object.prototype.hasOwnProperty.call(bo, k) && deepJsonEqual(ao[k], bo[k]));
210
- }
211
- function isCanonicalIndexKey(key, length) {
212
- return /^(0|[1-9]\d*)$/.test(key) && Number(key) < length;
213
- }
214
- function sameWinner(incoming, persisted) {
215
- return (incoming.boundCallId === persisted.boundCallId &&
216
- incoming.decision === persisted.decision &&
217
- deepJsonEqual(incoming.updatedInput, persisted.updatedInput) &&
218
- deepJsonEqual(incoming.answer, persisted.answer) &&
219
- (persisted.reason === undefined || incoming.reason === persisted.reason));
220
- }
221
- function pendingContentAskCallId(cp) {
222
- return cp.pendingAction.kind === "tool_approval" && cp.pendingAction.toolName === ASK_USER_QUESTION_TOOL_NAME
223
- ? cp.pendingAction.toolCallId
224
- : undefined;
225
- }
226
- function answerFaceForRedeemedCall(answer, redeemedCallId, base, redeemedQuestionsHash) {
227
- let consumed = false;
228
- const face = async (req, signal) => {
229
- if (!consumed && req.toolCallId === redeemedCallId && boundInputHashOf(req.questions) === redeemedQuestionsHash) {
230
- consumed = true;
231
- return answer;
232
- }
233
- if (base !== undefined)
234
- return base(req, signal);
235
- throw new Error("this resumed leg's answer was bound to the decided question only — a new question has no answer on this leg");
236
- };
237
- return isLiveQuestionFace(base) ? face : markBoundOnlyQuestionFace(face);
238
- }
239
- function writeFamilyOfCanonical(name) {
240
- if (name === "TaskCreate" || name === "TaskUpdate")
241
- return "task";
242
- if (name === "TodoWrite")
243
- return "todo";
244
- if (name === TOOL_SEARCH_NAME)
245
- return "tool_search";
246
- return undefined;
247
- }
248
- function toolResultMsg(toolCallId, toolName, text, isError) {
249
- return {
250
- role: "toolResult",
251
- toolCallId,
252
- toolName,
253
- content: [{ type: "text", text }],
254
- isError,
255
- timestamp: Date.now(),
256
- };
257
- }
258
- function describeSuppliedValue(value) {
259
- return typeof value === "string" ? value : value === null ? "null" : typeof value;
260
- }
261
- function assertOutcomeText(value, field) {
262
- if (value !== undefined && typeof value !== "string") {
263
- throw new CheckpointError("checkpoint.invalid_outcome", `resume \`${field}\` is not a plain string (got ${typeof value}) — a decide's text payload is an operator's plain data, not a live object; refusing pre-CAS, the checkpoint stays pending`);
264
- }
265
- }
266
- function resumeContinuation(resume, mark) {
267
- if (resume.outcome.gate === "wake") {
268
- return formatHookFeedback("You were WOKEN from a parked pause by an operator. Before continuing, re-orient from the workspace: " +
269
- "run `git status` and review your recent changes / last commits to confirm what is already done, then " +
270
- "continue the remaining work. Do NOT restart the task or re-run work that is already committed.", mark);
271
- }
272
- if (resume.outcome.gate === "resource_limit") {
273
- return formatHookFeedback("You were resumed after a pause. Before continuing, re-orient from the workspace: run `git status` and " +
274
- "review your recent changes / last commits to confirm what is already done, then continue the " +
275
- "remaining work. Do NOT restart the task or re-run work that is already committed.", mark);
276
- }
277
- if (resume.outcome.gate === "dry_run_review") {
278
- const verdict = resume.outcome.decision === "approve"
279
- ? "Your predicted change was REVIEWED and APPROVED; it has been applied"
280
- : `Your predicted change was REVIEWED and REJECTED${resume.outcome.reason ? `: ${delimitUntrusted("reviewer note", resume.outcome.reason, REVIEWER_NOTE_MAX_BODY)}` : ""}; it was NOT applied`;
281
- return formatHookFeedback(`You were resumed after a dry-run review. ${verdict}. Before continuing, re-orient from the workspace ` +
282
- "(run `git status` and review your recent changes) to confirm the current state, then continue the " +
283
- "remaining work. Do NOT restart the task or re-run work that is already done.", mark);
284
- }
285
- if (resume.outcome.gate === "plan_review") {
286
- if (resume.outcome.decision === "approve") {
287
- return formatHookFeedback("Your proposed PLAN was REVIEWED and APPROVED. Proceed with that plan now — begin executing it. " +
288
- "This is a RESUMED task; do NOT re-plan or restart from scratch, just carry out the approved plan.", mark);
289
- }
290
- if (resume.outcome.decision === "edit") {
291
- if (!resume.outcome.editedPlan) {
292
- return formatHookFeedback("Your proposed PLAN was REVIEWED and EDITED, but no revised plan text was supplied — proceed with your " +
293
- "ORIGINAL plan as-is, begin executing it now. This is a RESUMED task; do NOT re-plan or restart from scratch.", mark);
294
- }
295
- return formatHookFeedback("Your proposed PLAN was REVIEWED and EDITED by a human reviewer. Proceed with the REVISED plan below " +
296
- "(it supersedes your earlier plan); begin executing it now. This is a RESUMED task; do NOT re-plan " +
297
- "or restart from scratch, just carry out the revised plan.\n\nThe REVISED plan to follow is:\n" +
298
- delimitUntrusted("revised plan", resume.outcome.editedPlan), mark);
299
- }
300
- const why = resume.outcome.reason ? ` Reviewer note: ${delimitUntrusted("reviewer note", resume.outcome.reason, REVIEWER_NOTE_MAX_BODY)}` : "";
301
- return formatHookFeedback(`Your proposed PLAN was REVIEWED and REJECTED; it was NOT executed.${why} Produce a NEW plan that ` +
302
- "addresses the concern, then continue. This is a RESUMED task — re-plan from the current state; do " +
303
- "NOT execute the rejected plan.", mark);
304
- }
305
- const { pendingAction } = resume.cp;
306
- if (pendingAction.kind !== "tool_approval") {
307
- return formatHookFeedback("This is a RESUMED task — continue from where you left off without restarting or re-running prior work.", mark);
308
- }
309
- const decided = resume.outcome.decision === "allow"
310
- ? "was APPROVED and has now been executed — its result is in the tool results above"
311
- : `was DENIED${resume.outcome.reason ? `: ${delimitUntrusted("reviewer note", resume.outcome.reason, REVIEWER_NOTE_MAX_BODY)}` : ""}`;
312
- const remaining = pendingAction.batchToolCallIds.filter((id) => id !== pendingAction.toolCallId && !pendingAction.completedCallIds.includes(id)).length;
313
- const batchNote = remaining > 0
314
- ? ` ${remaining} other tool call(s) from that batch were returned as deferred and not run — re-issue any you still need.`
315
- : "";
316
- return formatHookFeedback(`The pending tool call "${pendingAction.toolName}" ${decided}.${batchNote} ` +
317
- `This is a RESUMED task — every step before this point is already done and its results are in the ` +
318
- `conversation above. Do NOT restart the task or re-run any tool you already ran; continue from this ` +
319
- `exact point, building on the existing results, and finish the remaining work.`, mark);
320
- }
321
- const ENV_DUE_GOVERNANCE_READ_BUDGET_MS = 5_000;
322
- const MAX_TIMER_DELAY_MS = 2_147_483_647;
323
- const DEADLINE_TICK = Symbol("deadline-tick");
324
- export const GOVERNANCE_READ_STALLED = Symbol("governance-read-stalled");
325
- const CHARGE_SETTLE_DISCLOSE_MS = 10_000;
326
- export async function awaitChargeWithSlowDisclosure(charge, onSlow, discloseAfterMs = CHARGE_SETTLE_DISCLOSE_MS) {
327
- let timer = setTimeout(() => {
328
- timer = undefined;
329
- try {
330
- onSlow();
331
- }
332
- catch {
333
- }
334
- }, discloseAfterMs);
335
- try {
336
- return await charge;
337
- }
338
- finally {
339
- if (timer !== undefined)
340
- clearTimeout(timer);
341
- }
342
- }
343
- export async function raceUntilDeadline(p, deadline) {
344
- for (let firstPass = true;; firstPass = false) {
345
- const remaining = deadline - Date.now();
346
- if (remaining <= 0 && !firstPass)
347
- return GOVERNANCE_READ_STALLED;
348
- let timer;
349
- const tick = new Promise((res) => {
350
- timer = setTimeout(() => res(DEADLINE_TICK), Math.max(0, Math.min(remaining, MAX_TIMER_DELAY_MS)));
351
- });
352
- const out = await Promise.race([p, tick]).finally(() => clearTimeout(timer));
353
- if (out !== DEADLINE_TICK)
354
- return out;
355
- }
356
- }
357
- function platformLimitTerminal(reason, retryAfterMs, moment = "turn_boundary") {
358
- const message = moment === "entry"
359
- ? `a deployment usage window is exhausted (RunnerDeps.usageWindows), so the task was refused before its first model call — nothing ran and nothing was spent. There is no checkpoint to suspend into at this point, so the caller re-submits after the window frees, in ${String(retryAfterMs ?? 0)}ms.`
360
- : moment === "resume"
361
- ? `a deployment usage window is exhausted (RunnerDeps.usageWindows), so the resumed leg was stopped BEFORE its first model call — nothing was delivered or executed on this leg. The window frees in ${String(retryAfterMs ?? 0)}ms.`
362
- : reason === "env_lifetime"
363
- ? "the execution environment's declared lifetime is expiring and the run could not be suspended durably (not eligible, or the suspend was refused — see remoteEnvFailures / onError). It was stopped at a clean turn boundary instead of being killed mid-turn when the platform reclaims the environment."
364
- : `a deployment usage window is exhausted (RunnerDeps.usageWindows) and the run could not be suspended durably, so it was stopped at a clean turn boundary. The window frees in ${String(retryAfterMs ?? 0)}ms.`;
365
- const e = new Error(message);
366
- e.code = reason === "env_lifetime" ? "env.lifetime_expired" : "usage.window_exhausted";
367
- if (retryAfterMs !== undefined)
368
- e.retryAfterMs = retryAfterMs;
369
- return e;
370
- }
371
- const disclosedUnevaluableWindow = new WeakSet();
372
- function discloseUnevaluableWindow(u, prepared, tracer, taskId, onError) {
373
- if (disclosedUnevaluableWindow.has(prepared))
374
- return;
375
- disclosedUnevaluableWindow.add(prepared);
376
- emitTrace(tracer, () => ({ kind: "compaction.unevaluable", version: 1, taskId, estTokens: u.estTokens, ts: Date.now() }));
377
- const modelClause = u.modelId !== undefined ? ` "${u.modelId}"` : "";
378
- const cause = u.windowValue !== undefined
379
- ? `its \`${u.windowField}\` is ${u.windowValue}, which is not a positive number of tokens — that field takes precedence over the ` +
380
- `others, so fixing or removing it is what restores the threshold`
381
- : `it declares no context window at all (\`autoCompactTokens\` / \`contextTokens\` / \`contextWindow\` are all absent) — declare one to restore the threshold`;
382
- try {
383
- onError?.(new Error(`auto-compaction cannot run for model${modelClause}: ${cause}. Until then there is no threshold to compare against, so the ` +
384
- `context will grow until the provider refuses the request. The conversation is already about ${u.estTokens} estimated tokens.`), { phase: "config", sessionId: prepared.sessionId });
385
- }
386
- catch {
387
- }
388
- }
389
- function makeTurnBoundary(prepared, stats, rs, deps) {
390
- const { spec, queue, manualCompactRef, todoToolMounted, taskToolsMounted, walltimeMonotonicDeadline, timeout, ident, postToolBatchHook, batchArgs, compactionBrain, withinTaskCompaction, compactionBreaker, windowSafetyOptions, rapidRefill, drainManualCompact, runnerHooks } = deps;
391
- let finalVerifySeenAtLastBoundary = 0;
392
- const onTurnBoundary = async (event) => {
393
- let boundarySteered = false;
394
- const noNextRequest = () => prepared.stopRequestedRef.current || deps.loopLatch.userHalted;
395
- const walltimeHit = walltimeMonotonicDeadline !== undefined && performance.now() >= walltimeMonotonicDeadline;
396
- const preemptHit = spec.preemptSignal?.aborted === true;
397
- if (preemptHit && !prepared.suspendForResource && !rs.counters.preemptIgnoredReported) {
398
- rs.counters.preemptIgnoredReported = true;
399
- emitTrace(rs.telemetry.tracer, () => ({ kind: "preempt.ignored", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
400
- }
401
- let platformCause;
402
- let platformRetryAfterMs;
403
- const preemptWillSuspend = preemptHit && prepared.suspendForResource !== undefined;
404
- if (!preemptWillSuspend && prepared.pausedRef.current === undefined && !prepared.abortController.signal.aborted) {
405
- const ledgerReadDeadline = prepared.envLifetimeSuspendAt !== undefined ? prepared.envLifetimeSuspendAt + ENV_DUE_GOVERNANCE_READ_BUDGET_MS : undefined;
406
- if (prepared.usageGovernance !== undefined) {
407
- const governance = prepared.usageGovernance;
408
- try {
409
- const at = Date.now();
410
- await awaitChargeWithSlowDisclosure(governance.commit(stats.tokens, rs.telemetry.unpricedSpend ? undefined : stats.costMicroUsd, at), () => runnerHooks.onError?.(new Error("the usage ledger CHARGE has not settled after 10s — still waiting (a charge is never abandoned: walking away would double-charge on the end-of-task flush). A wedged ledger store wedges this boundary, now visibly."), { phase: "config", sessionId: prepared.sessionId }));
411
- const windowRead = governance.check(at);
412
- let answer;
413
- if (ledgerReadDeadline !== undefined) {
414
- answer = await raceUntilDeadline(windowRead, ledgerReadDeadline);
415
- if (answer === GOVERNANCE_READ_STALLED) {
416
- void windowRead.catch((lateErr) => {
417
- try {
418
- runnerHooks.onError?.(lateErr instanceof Error ? lateErr : new Error(String(lateErr)), { phase: "config", sessionId: prepared.sessionId });
419
- }
420
- catch {
421
- }
422
- });
423
- try {
424
- runnerHooks.onError?.(new Error("the usage ledger did not answer before the execution environment's suspend deadline — stopping for the environment, the cause this run can still prove. The window state for this boundary is UNKNOWN and was not enforced."), { phase: "config", sessionId: prepared.sessionId });
425
- }
426
- catch {
427
- }
428
- }
429
- }
430
- else {
431
- answer = await windowRead;
432
- }
433
- if (answer !== GOVERNANCE_READ_STALLED) {
434
- platformRetryAfterMs = answer;
435
- if (platformRetryAfterMs !== undefined)
436
- platformCause = "usage_window";
437
- }
438
- }
439
- catch (govErr) {
440
- rs.limits.platformTerminal = govErr instanceof Error ? govErr : new Error(String(govErr));
441
- void prepared.harness.abort();
442
- prepared.abortController.abort();
443
- return undefined;
444
- }
445
- }
446
- if (platformCause === undefined && prepared.envLifetimeSuspendAt !== undefined && Date.now() >= prepared.envLifetimeSuspendAt) {
447
- platformCause = "env_lifetime";
448
- }
449
- }
450
- if (platformCause !== undefined) {
451
- const platformSpend = { costMicroUsd: stats.costMicroUsd, tokens: stats.tokens, turns: stats.turns, walltimeMs: Math.round(performance.now() - rs.telemetry.taskStartMonotonic) };
452
- const committed = prepared.suspendForPlatformLimit !== undefined &&
453
- (await prepared.suspendForPlatformLimit(platformCause, platformSpend, platformRetryAfterMs !== undefined ? { resumeAfterMs: platformRetryAfterMs } : undefined));
454
- if (committed) {
455
- rs.limits.turnsExceeded = false;
456
- rs.limits.budgetHit = undefined;
457
- return undefined;
458
- }
459
- const terminal = platformLimitTerminal(platformCause, platformRetryAfterMs);
460
- rs.limits.platformTerminal = terminal;
461
- try {
462
- runnerHooks.onError?.(terminal, { phase: "config", sessionId: prepared.sessionId });
463
- }
464
- catch {
465
- }
466
- void prepared.harness.abort();
467
- prepared.abortController.abort();
468
- return undefined;
469
- }
470
- if (prepared.suspendForResource &&
471
- (rs.limits.turnsExceeded || rs.limits.budgetHit !== undefined || walltimeHit || preemptHit) &&
472
- prepared.pausedRef.current === undefined) {
473
- const reason = preemptHit
474
- ? "preempt"
475
- : rs.limits.budgetHit !== undefined
476
- ? rs.limits.budgetAxis === "tokens"
477
- ? "budget_tokens"
478
- : "budget_cost"
479
- : rs.limits.turnsExceeded
480
- ? "turns"
481
- : "walltime";
482
- const sliceSpend = { costMicroUsd: stats.costMicroUsd, tokens: stats.tokens, turns: stats.turns, walltimeMs: Math.round(performance.now() - rs.telemetry.taskStartMonotonic) };
483
- if (await prepared.suspendForResource(reason, sliceSpend)) {
484
- rs.limits.turnsExceeded = false;
485
- rs.limits.budgetHit = undefined;
486
- }
487
- else {
488
- void prepared.harness.abort();
489
- prepared.abortController.abort();
490
- }
491
- return undefined;
492
- }
493
- if (walltimeMonotonicDeadline !== undefined &&
494
- prepared.suspendForResource === undefined &&
495
- performance.now() >= walltimeMonotonicDeadline) {
496
- if (!timeout.fired) {
497
- timeout.fired = true;
498
- timeout.clear();
499
- timeout.latenessMs = Math.max(timeout.latenessMs ?? 0, performance.now() - walltimeMonotonicDeadline);
500
- rs.counters.walltimeSyncBackstopFired = true;
501
- void prepared.harness.abort();
502
- prepared.abortController.abort();
503
- }
504
- return undefined;
505
- }
506
- const approachCfg = spec.limits?.approachNotice;
507
- if (approachCfg !== false &&
508
- rs.counters.approachNoticesSent < LIMIT_APPROACH_DEFAULT_THRESHOLDS.length &&
509
- prepared.suspendForResource === undefined &&
510
- rs.turn.lastTurnHadToolCalls &&
511
- prepared.batchHaltRef.current === undefined &&
512
- !boundarySteered &&
513
- !prepared.abortController.signal.aborted) {
514
- const thresholds = approachCfg?.at ?? LIMIT_APPROACH_DEFAULT_THRESHOLDS;
515
- const ratios = [];
516
- if (rs.budget.maxTokensWindow !== undefined && rs.budget.maxTokensWindow > 0) {
517
- ratios.push({ axis: "token budget", ratio: stats.tokens / rs.budget.maxTokensWindow });
518
- }
519
- if (rs.budget.maxCostMicroUsd !== undefined && rs.budget.maxCostMicroUsd > 0) {
520
- ratios.push({ axis: "cost budget", ratio: stats.costMicroUsd / rs.budget.maxCostMicroUsd });
521
- }
522
- const declaredMaxTurns = spec.limits?.maxTurns;
523
- if (declaredMaxTurns !== undefined && declaredMaxTurns > 0) {
524
- ratios.push({ axis: "turn budget", ratio: (stats.turns + 1) / declaredMaxTurns });
525
- }
526
- if (walltimeMonotonicDeadline !== undefined) {
527
- const walltimeWindowMs = walltimeMonotonicDeadline - rs.telemetry.taskStartMonotonic;
528
- if (walltimeWindowMs > 0) {
529
- ratios.push({ axis: "walltime budget", ratio: (performance.now() - rs.telemetry.taskStartMonotonic) / walltimeWindowMs });
530
- }
531
- }
532
- let tightest;
533
- for (const r of ratios)
534
- if (tightest === undefined || r.ratio > tightest.ratio)
535
- tightest = r;
536
- const index = rs.counters.approachNoticesSent;
537
- if (tightest !== undefined && tightest.ratio >= thresholds[index]) {
538
- rs.counters.approachNoticesSent += 1;
539
- const percent = Math.min(100, Math.round(tightest.ratio * 100));
540
- const body = limitApproachFrames(index === 0 ? 0 : 1, tightest.axis, percent, prepared.reminderMark);
541
- void prepared.harness.steer(body, { engineMinted: true }).catch(() => undefined);
542
- queue.push({ type: "steering_injected", source: "limit_approach", preview: body.slice(0, 220), ...ident() });
543
- boundarySteered = true;
544
- }
545
- }
546
- if (prepared.reviewRequestRef.pending !== undefined) {
547
- const { reason } = prepared.reviewRequestRef.pending;
548
- prepared.reviewRequestRef.pending = undefined;
549
- const honorable = prepared.suspendForReview !== undefined &&
550
- !prepared.abortController.signal.aborted &&
551
- prepared.pausedRef.current === undefined;
552
- if (honorable && prepared.suspendForReview) {
553
- await prepared.suspendForReview(reason);
554
- return undefined;
555
- }
556
- emitTrace(rs.telemetry.tracer, () => ({ kind: "review.dropped", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
557
- }
558
- if (prepared.lspDiagnostics &&
559
- !prepared.lspDiagnostics.registry.isEmpty() &&
560
- !prepared.abortController.signal.aborted &&
561
- prepared.batchHaltRef.current === undefined) {
562
- const files = prepared.lspDiagnostics.registry.drain(prepared.lspDiagnostics.runIdent);
563
- if (files.length > 0) {
564
- queue.push({ type: "diagnostics", files, isNew: true, ...ident() });
565
- const block = mintSystemReminder(formatDiagnosticsBlock(files), prepared.reminderMark);
566
- void prepared.harness.steer(block, { provenance: "engine-note" }).catch(() => undefined);
567
- boundarySteered = true;
568
- }
569
- }
570
- let boundaryAttachmentBytes = 0;
571
- let attachmentsPayload;
572
- if ((rs.attach.dateState !== undefined ||
573
- rs.attach.instrState !== undefined ||
574
- rs.attach.sizeGuidelineState !== undefined ||
575
- rs.attach.attachState !== undefined) &&
576
- !boundarySteered &&
577
- prepared.batchHaltRef.current === undefined &&
578
- !noNextRequest() &&
579
- rs.counters.finalVerifyInjections === 0 &&
580
- !prepared.abortController.signal.aborted) {
581
- const due = [];
582
- if (rs.attach.attachState !== undefined) {
583
- const changedFilesOn = Boolean(rs.attach.attachmentsCfg?.changedFiles);
584
- let changed;
585
- if (changedFilesOn && prepared.detectExternalChanges !== undefined) {
586
- const maxFiles = typeof rs.attach.attachmentsCfg?.changedFiles === "object"
587
- ? (rs.attach.attachmentsCfg.changedFiles.maxFiles ?? CHANGED_FILES_MAX)
588
- : CHANGED_FILES_MAX;
589
- const SCAN_CEILING_MS = 2_000;
590
- const scan = await Promise.race([
591
- prepared.detectExternalChanges(maxFiles),
592
- new Promise((resolve) => {
593
- setTimeout(() => resolve({ changed: [], evicted: [] }), SCAN_CEILING_MS);
594
- }),
595
- ]);
596
- for (const p of scan.evicted)
597
- rs.attach.attachState.surfacedMtime.delete(p);
598
- changed = scan.changed;
599
- }
600
- const backgroundTasksOn = (rs.attach.attachmentsCfg?.backgroundTasks ?? eventDefaultOn("background_tasks")) === true;
601
- const bgTasks = backgroundTasksOn && rs.attach.attachState.postCompactPending ? prepared.listBackgroundTasks() : undefined;
602
- const toolsDeltaOn = rs.attach.attachmentsCfg?.toolsDelta === true;
603
- let announcedOnceDirty = false;
604
- const tdRef = toolsDeltaOn ? prepared.toolsDeltaRef : undefined;
605
- const pendingTools = tdRef !== undefined && tdRef.pending.length > 0 ? [...tdRef.pending] : undefined;
606
- const pendingRemovedTools = tdRef !== undefined && tdRef.pendingRemoved.length > 0 ? [...tdRef.pendingRemoved] : undefined;
607
- const pendingReaddedTools = tdRef !== undefined && tdRef.pendingReadded.length > 0 ? [...tdRef.pendingReadded] : undefined;
608
- const pendingFailedMcp = tdRef !== undefined && tdRef.pendingFailed.length > 0 ? tdRef.pendingFailed.map((f) => ({ ...f })) : undefined;
609
- const mcpToolsDelta = pendingRemovedTools !== undefined || pendingReaddedTools !== undefined || pendingFailedMcp !== undefined
610
- ? {
611
- ...(pendingRemovedTools !== undefined ? { removed: pendingRemovedTools } : {}),
612
- ...(pendingReaddedTools !== undefined ? { readded: pendingReaddedTools } : {}),
613
- ...(pendingFailedMcp !== undefined ? { failedServers: pendingFailedMcp } : {}),
614
- }
615
- : undefined;
616
- const mcpInstructionsOn = rs.attach.attachmentsCfg?.mcpInstructions === true;
617
- const mcpDelta = prepared.mcp.instructionsDelta;
618
- const mcpAdds = mcpInstructionsOn && mcpDelta.pendingAdds.length > 0 ? [...mcpDelta.pendingAdds] : undefined;
619
- const mcpRemovals = mcpInstructionsOn && mcpDelta.pendingRemovals.length > 0 ? [...mcpDelta.pendingRemovals] : undefined;
620
- const mcpDropped = mcpInstructionsOn && prepared.mcp.droppedTools.length > 0 ? selectMcpDroppedBatch(prepared.mcp.droppedTools) : undefined;
621
- const budgetUsdOn = rs.attach.attachmentsCfg?.budgetUsd === true && rs.budget.maxCostMicroUsd !== undefined && rs.turn.lastTurnHadToolCalls;
622
- const totalTokensDeclared = rs.attach.attachmentsCfg?.totalTokensReminder;
623
- const totalTokensMode = rs.attach.attachmentsCfg?.totalTokensReminderMode ?? TOTAL_TOKENS_REMINDER_DEFAULT_MODE;
624
- const totalTokensArmedByDefault = totalTokensDeclared === undefined &&
625
- eventDefaultOn("total_tokens_reminder") &&
626
- totalTokensMode !== "fixed" &&
627
- totalTokensMode !== "infinite";
628
- const totalTokensOn = (totalTokensDeclared === true || totalTokensArmedByDefault) && rs.turn.lastTurnHadToolCalls;
629
- const toolSearchReminderOn = rs.attach.attachmentsCfg?.toolSearchReminder === true && prepared.deferredToolNames !== undefined;
630
- const undiscoveredTools = toolSearchReminderOn
631
- ? [...prepared.deferredToolNames].filter((n) => !prepared.activeTools.has(n)).sort()
632
- : undefined;
633
- due.push(...collectDueAttachments(rs.attach.attachState, {
634
- turn: stats.turns,
635
- cadenceTurn: rs.counters.cadenceTurns,
636
- todoMounted: todoToolMounted,
637
- taskListMounted: taskToolsMounted,
638
- planActive: prepared.planModeRef.active,
639
- config: {
640
- todoReminder: rs.attach.attachmentsCfg?.todoReminder === true,
641
- ...(rs.attach.attachmentsCfg?.todoReminderMode !== undefined ? { todoReminderMode: rs.attach.attachmentsCfg.todoReminderMode } : {}),
642
- toolSearchReminder: toolSearchReminderOn,
643
- changedFiles: changedFilesOn,
644
- planModeReminder: rs.attach.attachmentsCfg?.planModeReminder === true,
645
- budgetUsd: budgetUsdOn,
646
- totalTokensReminder: totalTokensOn,
647
- ...(rs.attach.attachmentsCfg?.totalTokensReminderMode !== undefined
648
- ? { totalTokensReminderMode: rs.attach.attachmentsCfg.totalTokensReminderMode }
649
- : {}),
650
- backgroundTasks: backgroundTasksOn,
651
- toolsDelta: toolsDeltaOn,
652
- agentListing: rs.attach.agentListingOn,
653
- skillsListing: rs.attach.skillsListingOn,
654
- mcpInstructions: mcpInstructionsOn,
655
- },
656
- ...(undiscoveredTools !== undefined && undiscoveredTools.length > 0
657
- ? { undiscoveredTools, deferDirectCall: prepared.deferDirectCall }
658
- : {}),
659
- ...(changed !== undefined ? { changedFiles: changed } : {}),
660
- ...(budgetUsdOn && rs.budget.maxCostMicroUsd !== undefined
661
- ? { budgetUsd: { used: stats.costMicroUsd / 1e6, total: rs.budget.maxCostMicroUsd / 1e6 } }
662
- : {}),
663
- ...(totalTokensOn && rs.budget.maxTokensWindow !== undefined
664
- ? { totalTokens: { used: stats.tokens, total: rs.budget.maxTokensWindow } }
665
- : {}),
666
- ...(bgTasks !== undefined ? { backgroundTasks: bgTasks } : {}),
667
- ...(pendingTools !== undefined ? { newTools: pendingTools } : {}),
668
- ...(prepared.toolMaterializeStatic ? { newToolsStaticFace: true } : {}),
669
- ...(prepared.toolMaterializeStatic && pendingTools !== undefined
670
- ? { newToolsSwappedUnderStatic: pendingTools.filter((n) => prepared.staticFaceFor?.(n) !== true) }
671
- : {}),
672
- ...(prepared.toolMaterializeStatic && pendingReaddedTools !== undefined
673
- ? { readdedToolsSwappedUnderStatic: pendingReaddedTools.filter((n) => prepared.staticFaceFor?.(n) !== true) }
674
- : {}),
675
- ...(mcpToolsDelta !== undefined ? { mcpToolsDelta } : {}),
676
- ...(rs.attach.agentListingOn && prepared.agentListing !== undefined
677
- ? {
678
- agentListing: prepared.agentListing.entries,
679
- agentToolName: prepared.agentListing.toolName,
680
- ...(prepared.agentListing.models !== undefined ? { agentModels: prepared.agentListing.models } : {}),
681
- }
682
- : {}),
683
- ...(rs.attach.skillsListingOn && prepared.skillsListing !== undefined ? { skillsListing: prepared.skillsListing.entries } : {}),
684
- ...(mcpAdds !== undefined || mcpRemovals !== undefined
685
- ? { mcpInstructions: { added: mcpAdds ?? [], removed: mcpRemovals ?? [] } }
686
- : {}),
687
- ...(mcpDropped !== undefined ? { mcpDroppedTools: mcpDropped } : {}),
688
- }));
689
- if (pendingTools !== undefined || mcpToolsDelta !== undefined) {
690
- const exact = renderToolsDelta({
691
- ...(pendingTools !== undefined ? { added: pendingTools } : {}),
692
- ...(prepared.toolMaterializeStatic ? { staticFace: true } : {}),
693
- ...(prepared.toolMaterializeStatic && pendingTools !== undefined
694
- ? { swappedUnderStatic: pendingTools.filter((n) => prepared.staticFaceFor?.(n) !== true) }
695
- : {}),
696
- ...(prepared.toolMaterializeStatic && pendingReaddedTools !== undefined
697
- ? { readdedSwappedUnderStatic: pendingReaddedTools.filter((n) => prepared.staticFaceFor?.(n) !== true) }
698
- : {}),
699
- ...(mcpToolsDelta ?? {}),
700
- });
701
- if (exact !== undefined && due.some((a) => a.source === "tools_delta" && a.body === exact)) {
702
- const ref = prepared.toolsDeltaRef;
703
- if (pendingTools !== undefined)
704
- ref.pending.splice(0, pendingTools.length);
705
- if (pendingRemovedTools !== undefined)
706
- ref.pendingRemoved.splice(0, pendingRemovedTools.length);
707
- if (pendingReaddedTools !== undefined)
708
- ref.pendingReadded.splice(0, pendingReaddedTools.length);
709
- if (pendingFailedMcp !== undefined) {
710
- ref.pendingFailed.splice(0, pendingFailedMcp.length);
711
- if (!noNextRequest()) {
712
- prepared.announcedListingsRef.mcpFailed = [...(prepared.announcedListingsRef.mcpFailed ?? []), ...pendingFailedMcp];
713
- announcedOnceDirty = true;
714
- }
715
- }
716
- }
717
- }
718
- if ((mcpAdds !== undefined || mcpRemovals !== undefined) &&
719
- due.some((a) => a.source === "mcp_instructions" && a.body === renderMcpInstructionsDelta(mcpAdds ?? [], mcpRemovals ?? []))) {
720
- if (mcpAdds !== undefined)
721
- mcpDelta.pendingAdds.splice(0, mcpAdds.length);
722
- if (mcpRemovals !== undefined)
723
- mcpDelta.pendingRemovals.splice(0, mcpRemovals.length);
724
- }
725
- if (mcpDropped !== undefined &&
726
- due.some((a) => a.source === "mcp_dropped_tools" && a.body === renderMcpDroppedTools(mcpDropped))) {
727
- prepared.mcp.droppedTools.splice(0, mcpDropped.length);
728
- if (!noNextRequest()) {
729
- prepared.announcedListingsRef.advisories = withAnnounceDigests(prepared.announcedListingsRef.advisories, mcpDropped.map(droppedToolDigest));
730
- announcedOnceDirty = true;
731
- }
732
- }
733
- if (rs.attach.agentListingOn && prepared.agentListing !== undefined && due.some((a) => a.source === "agent_listing")) {
734
- const listing = prepared.agentListing;
735
- const intactBody = renderAgentListingDelta(rs.attach.attachState, listing.entries, listing.toolName, listing.models);
736
- if (intactBody !== undefined && due.some((a) => a.source === "agent_listing" && a.body === intactBody)) {
737
- commitAgentListing(rs.attach.attachState, listing.entries, listing.models);
738
- prepared.announcedListingsRef.agents = listing.entries.map((e) => e.name);
739
- if (listing.models !== undefined)
740
- prepared.announcedListingsRef.models = [...listing.models];
741
- if (prepared.announcedSnapshotRecovered)
742
- await prepared.session.appendAnnouncedListing(prepared.announcedListingsRef).catch(() => undefined);
743
- }
744
- }
745
- if (rs.attach.skillsListingOn && prepared.skillsListing !== undefined && due.some((a) => a.source === "skills_listing")) {
746
- const listing = prepared.skillsListing;
747
- const intactBody = renderSkillsListingDelta(rs.attach.attachState, listing.entries);
748
- if (intactBody !== undefined && due.some((a) => a.source === "skills_listing" && a.body === intactBody)) {
749
- commitSkillsListing(rs.attach.attachState, listing.entries);
750
- prepared.announcedListingsRef.skills = listing.entries.map((e) => e.name);
751
- if (prepared.announcedSnapshotRecovered)
752
- await prepared.session.appendAnnouncedListing(prepared.announcedListingsRef).catch(() => undefined);
753
- }
754
- }
755
- if (announcedOnceDirty && prepared.announcedSnapshotRecovered)
756
- await prepared.session.appendAnnouncedListing(prepared.announcedListingsRef).catch(() => undefined);
757
- }
758
- if (rs.attach.dateState !== undefined && rs.turn.lastTurnHadToolCalls) {
759
- const today = prepared.dateChange.today();
760
- const cand = collectDateChange(rs.attach.dateState, today);
761
- if (cand !== undefined) {
762
- due.unshift(cand);
763
- rs.attach.dateState.announcedDate = today;
764
- }
765
- }
766
- if (rs.attach.sizeGuidelineState !== undefined && rs.turn.lastTurnHadToolCalls) {
767
- const guideline = rs.attach.sizeGuidelineState.current();
768
- if (guideline !== rs.attach.sizeGuidelineState.announcedGuideline) {
769
- due.splice(due.length > 0 && due[0].source === "date_change" ? 1 : 0, 0, {
770
- source: "workflow_size_guideline_change",
771
- body: workflowSizeGuidelineChangeNotice(guideline),
772
- });
773
- rs.attach.sizeGuidelineState.announcedGuideline = guideline;
774
- }
775
- }
776
- if (rs.attach.instrState !== undefined && rs.attach.instrProbe !== undefined && rs.turn.lastTurnHadToolCalls) {
777
- let probed = null;
778
- try {
779
- const PROBE_CEILING_MS = 2_000;
780
- probed = await Promise.race([
781
- rs.attach.instrProbe(prepared.instructionSources),
782
- new Promise((resolve) => {
783
- setTimeout(() => resolve(null), PROBE_CEILING_MS);
784
- }),
785
- ]);
786
- }
787
- catch {
788
- probed = null;
789
- }
790
- const cand = collectInstructionsChange(rs.attach.instrState, probed);
791
- if (cand !== undefined) {
792
- due.splice(due.length > 0 && due[0].source === "date_change" ? 1 : 0, 0, cand.attachment);
793
- commitInstructionsChange(rs.attach.instrState, cand.announced);
794
- }
795
- }
796
- if (due.length > 0) {
797
- attachmentsPayload = due.map((a) => mintSystemReminder(sanitizeUntrustedText(a.body, attachmentEnvelopeTags(a.source)), prepared.reminderMark)).join("\n");
798
- for (const a of due)
799
- boundaryAttachmentBytes += Buffer.byteLength(a.body, "utf8");
800
- rs.attach.attachmentsInjected += due.length;
801
- for (const a of due) {
802
- queue.push({ type: "steering_injected", source: a.source, preview: redactThenCut(a.body, 220), ...ident() });
803
- }
804
- }
805
- }
806
- let batchContextBlock;
807
- const finalVerifyInjectedThisTurn = rs.counters.finalVerifyInjections !== finalVerifySeenAtLastBoundary;
808
- finalVerifySeenAtLastBoundary = rs.counters.finalVerifyInjections;
809
- if (postToolBatchHook !== undefined && rs.turn.toolBatch.length > 0) {
810
- const batch = rs.turn.toolBatch;
811
- rs.turn.toolBatch = [];
812
- batchArgs?.clear();
813
- const injectedThisTurn = finalVerifyInjectedThisTurn ? "final_verification" : undefined;
814
- if (!boundarySteered && !prepared.abortController.signal.aborted) {
815
- try {
816
- const batchSeat = await runHookSeat("postToolBatch", { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal }, (sig) => postToolBatchHook(batch, injectedThisTurn !== undefined ? { injectedThisTurn } : undefined, { identity: prepared.hookIdentity, signal: sig }));
817
- if (batchSeat.expired) {
818
- runnerHooks.onError?.(hookSeatExpiredError("postToolBatch", prepared.hookTimeoutMs, batchSeat.cause, "the boundary observation was abandoned and its additionalContext dropped; the turn boundary itself is unchanged"), { phase: "hook", sessionId: prepared.sessionId });
819
- }
820
- const r = batchSeat.expired ? undefined : batchSeat.value;
821
- if (r?.additionalContext && injectedThisTurn === undefined) {
822
- const body = sanitizeUntrustedText(r.additionalContext, SHELLED_BODY_ENVELOPE_TAGS);
823
- const budget = ATTACHMENT_BYTE_CAP - boundaryAttachmentBytes;
824
- if (budget >= BATCH_CONTEXT_MIN_KEEP_BYTES) {
825
- const clipped = clipToBytes(body, budget) === body
826
- ? body
827
- : `${clipToBytes(body, budget - Buffer.byteLength(BATCH_TRUNCATION_MARKER, "utf8"))}${BATCH_TRUNCATION_MARKER}`;
828
- batchContextBlock = mintSystemReminder(clipped, prepared.reminderMark);
829
- }
830
- }
831
- }
832
- catch (err) {
833
- try {
834
- runnerHooks.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "hook", sessionId: prepared.sessionId });
835
- }
836
- catch {
837
- }
838
- }
839
- }
840
- }
841
- if ((attachmentsPayload !== undefined || batchContextBlock !== undefined) && prepared.batchHaltRef.current === undefined) {
842
- const payload = attachmentsPayload !== undefined && batchContextBlock !== undefined ? `${attachmentsPayload}\n${batchContextBlock}` : (attachmentsPayload ?? batchContextBlock);
843
- void prepared.harness.steer(payload, { engineMinted: true }).catch(() => { });
844
- }
845
- const forceManual = manualCompactRef.requested;
846
- if ((!withinTaskCompaction && !forceManual) ||
847
- prepared.abortController.signal.aborted ||
848
- prepared.pausedRef.current !== undefined) {
849
- if (forceManual) {
850
- emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.mooted", version: 1, taskId: rs.telemetry.taskId, reason: "task_ending", ts: Date.now() }));
851
- queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: "manual", reason: "task_ending", ...ident() });
852
- drainManualCompact("mooted");
853
- }
854
- return undefined;
855
- }
856
- if (compactionBreaker.failures >= MAX_CONSECUTIVE_COMPACTION_FAILURES) {
857
- if (forceManual) {
858
- emitTrace(rs.telemetry.tracer, () => ({
859
- kind: "compaction.failed",
860
- version: 1,
861
- taskId: rs.telemetry.taskId,
862
- trigger: "manual",
863
- reason: "compaction circuit breaker open (3 consecutive failures this task) — manual request drained without an attempt",
864
- ts: Date.now(),
865
- }));
866
- queue.push({ type: "compaction_outcome", outcome: "failed", trigger: "manual", reason: "compaction circuit breaker open (3 consecutive failures this task) — manual request drained without an attempt", ...ident() });
867
- drainManualCompact("failed");
868
- }
869
- return undefined;
870
- }
871
- const claimedManual = forceManual
872
- ? (() => {
873
- const waiters = manualCompactRef.waiters.splice(0);
874
- let instructions;
875
- for (let i = waiters.length - 1; i >= 0; i--) {
876
- if (waiters[i].instructions !== undefined) {
877
- instructions = waiters[i].instructions;
878
- break;
879
- }
880
- }
881
- return { instructions, waiters };
882
- })()
883
- : undefined;
884
- if (forceManual) {
885
- manualCompactRef.requested = false;
886
- }
887
- const resolveClaimedWaiters = (outcome) => {
888
- if (claimedManual !== undefined)
889
- for (const w of claimedManual.waiters.splice(0))
890
- w.resolve(outcome);
891
- };
892
- const claimedCancelSignals = claimedManual?.waiters.flatMap((w) => (w.signal !== undefined ? [w.signal] : [])) ?? [];
893
- const manualCancelSignal = claimedCancelSignals.length === 0
894
- ? undefined
895
- : claimedCancelSignals.length === 1
896
- ? claimedCancelSignals[0]
897
- : AbortSignal.any(claimedCancelSignals);
898
- const compactionPassSignal = claimedCancelSignals.length > 0
899
- ? AbortSignal.any([prepared.abortController.signal, ...claimedCancelSignals])
900
- : prepared.abortController.signal;
901
- const trimPressureArmed = prepared.trimPressureRef.droppedMessages;
902
- if (trimPressureArmed)
903
- prepared.trimPressureRef.droppedMessages = false;
904
- const trimPressure = trimPressureArmed && !rs.counters.trimForceBackoff;
905
- const passTrigger = trimPressure ? "forced" : forceManual ? "manual" : "auto";
906
- let manualOutcome = "failed";
907
- const manualInstructions = claimedManual?.instructions;
908
- try {
909
- const comp = await maybeCompact({
910
- session: prepared.session,
911
- epochDeclaredSections: prepared.epochDeclaredSections,
912
- ...centerAdoptionOption(prepared),
913
- model: event.model,
914
- compactionModel: prepared.compModel,
915
- ...forkContextOption(prepared, false),
916
- brain: compactionBrain,
917
- getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
918
- thinking: prepared.thinking,
919
- settings: { ...DEFAULT_COMPACTION_SETTINGS, ...spec.compaction },
920
- customInstructions: manualInstructions ?? spec.compaction?.instructions ?? DEFAULT_COMPACTION_INSTRUCTIONS,
921
- signal: compactionPassSignal,
922
- ...(manualCancelSignal !== undefined ? { manualCancelSignal } : {}),
923
- minTokens: forceManual ? 0 : rs.counters.compactionFloor,
924
- force: forceManual,
925
- onInputTruncated: emitInputTruncated(rs.telemetry.tracer, rs.telemetry.taskId),
926
- forceUnderThreshold: trimPressure,
927
- overheadTokens: prepared.promptOverheadTokens,
928
- ...(prepared.activeTools.size > 0 ? { activeTools: [...prepared.activeTools] } : {}),
929
- workingFileAttachments: buildWorkingFileAttachments(spec, prepared),
930
- ...contextInstructionFilesOption(prepared),
931
- ...(prepared.onCompactionApplied ? { onApplied: prepared.onCompactionApplied } : {}),
932
- ...runnerHooks.seamCCompactionOptions(prepared),
933
- ...gitRestateOption(prepared),
934
- ...windowSafetyOptions(event.model),
935
- ...runnerHooks.compactionHookOptions(spec, prepared.sessionId, passTrigger),
936
- });
937
- if (comp.blocked) {
938
- emitTrace(rs.telemetry.tracer, () => ({
939
- kind: "compaction.blocked",
940
- version: 1,
941
- taskId: rs.telemetry.taskId,
942
- trigger: forceManual ? "manual" : "auto",
943
- ts: Date.now(),
944
- }));
945
- }
946
- else {
947
- compactionBreaker.failures = 0;
948
- }
949
- rs.counters.trimForceBackoff = nextTrimForceBackoff(rs.counters.trimForceBackoff, comp, trimPressure);
950
- if (comp.contextUsage) {
951
- queue.push({ type: "context_usage", ...comp.contextUsage, ...ident() });
952
- }
953
- if (comp.unevaluableWindow) {
954
- discloseUnevaluableWindow(comp.unevaluableWindow, prepared, rs.telemetry.tracer, rs.telemetry.taskId, runnerHooks.onError);
955
- }
956
- if (comp.suppressedByFloor) {
957
- const s = comp.suppressedByFloor;
958
- emitTrace(rs.telemetry.tracer, () => ({
959
- kind: "compaction.suppressed",
960
- version: 1,
961
- taskId: rs.telemetry.taskId,
962
- estTokens: s.estTokens,
963
- floor: s.floor,
964
- ts: Date.now(),
965
- }));
966
- }
967
- manualOutcome = comp.compacted ? "compacted" : comp.blocked ? "blocked" : comp.disabled ? "disabled" : "noop";
968
- if (forceManual && !comp.compacted && !comp.blocked) {
969
- if (comp.disabled) {
970
- emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.disabled", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
971
- }
972
- else {
973
- emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.noop", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
974
- }
975
- }
976
- if (!comp.compacted) {
977
- const outcome = comp.blocked
978
- ? "blocked"
979
- : comp.disabled
980
- ? (forceManual || trimPressure ? "disabled" : undefined)
981
- : comp.suppressedByFloor
982
- ? "suppressed"
983
- : comp.noop || forceManual
984
- ? "noop"
985
- : undefined;
986
- if (outcome !== undefined) {
987
- queue.push({ type: "compaction_outcome", outcome, trigger: passTrigger, ...ident() });
988
- }
989
- }
990
- runnerHooks.recordCompactionReuse(prepared, comp);
991
- if (comp.compacted) {
992
- queue.push({
993
- type: "compacted",
994
- trigger: passTrigger,
995
- tokensBefore: comp.tokensBefore ?? 0,
996
- ...(comp.postTriggerTokens !== undefined ? { tokensAfter: comp.postTriggerTokens } : {}),
997
- ...(comp.triggerTokens !== undefined ? { triggerTokensBefore: comp.triggerTokens } : {}),
998
- ...(comp.durationMs !== undefined ? { durationMs: comp.durationMs } : {}),
999
- ...(comp.phaseDurations !== undefined ? { phaseDurations: comp.phaseDurations } : {}),
1000
- ...(comp.firstKeptEntryId !== undefined ? { preserved_segment: { firstKeptEntryId: comp.firstKeptEntryId } } : {}),
1001
- ...(comp.attachedFiles !== undefined ? { attachedFiles: comp.attachedFiles } : {}),
1002
- ...(comp.modelFallback ? { modelFallback: true } : {}),
1003
- ...(comp.fallbackReason !== undefined ? { fallbackReason: comp.fallbackReason } : {}),
1004
- ...(comp.clampedRatio !== undefined ? { clampedRatio: comp.clampedRatio } : {}),
1005
- ...(comp.clampReason !== undefined ? { clampReason: comp.clampReason } : {}),
1006
- ...ident(),
1007
- });
1008
- if (!forceManual && recordCompactionAndCheckRapidRefill(rapidRefill, stats.turns)) {
1009
- compactionBreaker.failures = MAX_CONSECUTIVE_COMPACTION_FAILURES;
1010
- queue.push({ type: "compaction_outcome", outcome: "disabled", trigger: passTrigger, reason: "rapid_refill: compaction disabled for the rest of this task", ...ident() });
1011
- runnerHooks.onError?.(new Error("compaction.rapid_refill: the context refilled within <3 turns of compaction 3 times in a row — compaction disabled for the rest of this task (thrash spiral; the transcript is dominated by incompressible content)"), { phase: "compaction", sessionId: prepared.sessionId });
1012
- }
1013
- if (comp.phaseDurations !== undefined && comp.durationMs !== undefined) {
1014
- const pd = comp.phaseDurations;
1015
- const pdDur = comp.durationMs;
1016
- emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.phase_timings", version: 1, taskId: rs.telemetry.taskId, ...pd, durationMs: pdDur, ts: Date.now() }));
1017
- }
1018
- prepared.cacheBreakDetector?.notifyCompaction();
1019
- if (rs.attach.attachState !== undefined) {
1020
- rs.attach.attachState.postCompactPending = true;
1021
- rebaseCadenceWindows(rs.attach.attachState, rs.counters.cadenceTurns);
1022
- }
1023
- if ((comp.freedTokens ?? 0) >= COMPACTION_FREED_EPSILON) {
1024
- const postSize = comp.postTriggerTokens ?? Math.max(0, (comp.triggerTokens ?? comp.tokensBefore ?? 0) - (comp.freedTokens ?? 0));
1025
- rs.counters.compactionFloor = Math.ceil(postSize * COMPACTION_REGROWTH_FACTOR);
1026
- }
1027
- }
1028
- }
1029
- catch (err) {
1030
- const manualCancelAbort = !prepared.abortController.signal.aborted && isCompactionManualCancel(err);
1031
- if (manualCancelAbort) {
1032
- emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.mooted", version: 1, taskId: rs.telemetry.taskId, reason: "cancelled", ts: Date.now() }));
1033
- queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: passTrigger, reason: "cancelled", ...ident() });
1034
- manualOutcome = "mooted";
1035
- }
1036
- else if (!prepared.abortController.signal.aborted) {
1037
- compactionBreaker.failures += 1;
1038
- runnerHooks.onError?.(err, { phase: "compaction", sessionId: prepared.sessionId });
1039
- const msg = String(err instanceof Error ? err.message : err);
1040
- const failReason = msg.length > 512 ? `${msg.slice(0, 512)}…` : msg;
1041
- emitTrace(rs.telemetry.tracer, () => ({
1042
- kind: "compaction.failed",
1043
- version: 1,
1044
- taskId: rs.telemetry.taskId,
1045
- trigger: passTrigger,
1046
- reason: failReason,
1047
- ts: Date.now(),
1048
- }));
1049
- queue.push({ type: "compaction_outcome", outcome: "failed", trigger: passTrigger, reason: failReason, ...ident() });
1050
- manualOutcome = "failed";
1051
- }
1052
- else {
1053
- manualOutcome = "mooted";
1054
- }
1055
- }
1056
- if (forceManual)
1057
- resolveClaimedWaiters(manualOutcome);
1058
- return undefined;
1059
- };
1060
- return onTurnBoundary;
1061
- }
1062
- function wrapGitFrame(body, mark) {
1063
- return mintSystemReminder(sanitizeUntrustedText(body, SHELLED_BODY_ENVELOPE_TAGS), mark);
1064
- }
1065
- async function resolveGitLegDelivery(prepared, cpMirror, report) {
1066
- const ref = prepared.gitStatusRef;
1067
- const frame = ref.frame;
1068
- if (frame === undefined)
1069
- return undefined;
1070
- let prior;
1071
- let pending = false;
1072
- try {
1073
- const branchReadout = await prepared.session.getGitAnnouncement?.();
1074
- if (branchReadout !== undefined) {
1075
- if (branchReadout.status === "pending") {
1076
- pending = true;
1077
- prior = { kind: branchReadout.kind, hash: branchReadout.hash };
1078
- }
1079
- else {
1080
- prior = { kind: branchReadout.kind, hash: branchReadout.hash, ...(branchReadout.entryId !== undefined ? { entryId: branchReadout.entryId } : {}) };
1081
- }
1082
- }
1083
- else {
1084
- const shaped = normalizeGitAnnouncement(cpMirror);
1085
- if (shaped !== undefined) {
1086
- if (shaped.pending === true || shaped.entryId === undefined) {
1087
- pending = true;
1088
- prior = { kind: shaped.kind, hash: shaped.hash };
1089
- }
1090
- else if (gitFrameContextVisible(await prepared.session.getBranch(), shaped.entryId)) {
1091
- prior = { kind: shaped.kind, hash: shaped.hash, entryId: shaped.entryId };
1092
- }
1093
- else {
1094
- pending = true;
1095
- prior = { kind: shaped.kind, hash: shaped.hash };
1096
- }
1097
- }
1098
- }
1099
- }
1100
- catch (err) {
1101
- pending = true;
1102
- report(err instanceof Error ? err : new Error(String(err)));
1103
- }
1104
- if (prior !== undefined && !pending && prior.entryId !== undefined) {
1105
- try {
1106
- const newest = newestEngineGitFrame(await prepared.session.getBranch(), prepared.reminderMark);
1107
- if (newest === undefined || newest.entryId !== prior.entryId)
1108
- pending = true;
1109
- }
1110
- catch (err) {
1111
- pending = true;
1112
- report(err instanceof Error ? err : new Error(String(err)));
1113
- }
1114
- }
1115
- if (prior !== undefined)
1116
- ref.announced = pending ? { ...prior, pending: true } : { ...prior };
1117
- const negative = frame.kind === "unavailable" || frame.kind === "non-repo";
1118
- if (negative) {
1119
- if (prior === undefined && !pending) {
1120
- let mustDisown;
1121
- try {
1122
- mustDisown = branchCarriesVisiblePositiveGitFrame(await prepared.session.getBranch(), prepared.reminderMark);
1123
- }
1124
- catch (err) {
1125
- mustDisown = true;
1126
- report(err instanceof Error ? err : new Error(String(err)));
1127
- }
1128
- return mustDisown ? frame.body : undefined;
1129
- }
1130
- if (prior !== undefined && !pending && prior.kind === frame.kind && prior.hash === frame.hash)
1131
- return undefined;
1132
- return frame.body;
1133
- }
1134
- if (!pending && prior !== undefined && prior.kind === frame.kind && prior.hash === frame.hash) {
1135
- ref.protectedText = wrapGitFrame(frame.body, prepared.reminderMark);
1136
- if (frame.kind === "full" && frame.shrunk !== undefined) {
1137
- ref.wrappedShrink = { find: ref.protectedText, replace: wrapGitFrame(frame.shrunk.body, prepared.reminderMark) };
1138
- }
1139
- return undefined;
1140
- }
1141
- return frame.body;
1142
- }
1143
- function gitRestateOption(prepared) {
1144
- const ref = prepared.gitStatusRef;
1145
- if (ref.frame === undefined || ref.announced === undefined || ref.reassert === undefined)
1146
- return {};
1147
- const use = ref.overBudgetShrunk && ref.frame.shrunk !== undefined
1148
- ? { kind: "degraded", hash: ref.frame.shrunk.hash }
1149
- : { kind: ref.frame.kind, hash: ref.frame.hash };
1150
- return { gitRestate: { pending: use, land: ref.reassert } };
1151
- }
1152
- function makeHarnessHandlers(prepared, stats, rs, deps) {
1153
- const { spec, queue, internals, ident, parentToolCallId, subagentName, pushContent, emitCommitted, startedToolCallIds, toolStartAt, writeFamilyOf, toolLabels, postToolBatchHook, batchArgs } = deps;
1154
- const internalsNotifier = createSafeNotifier({
1155
- onError: (f) => console.warn(`[sema-core] ${f.site}: run-internals activity sink threw (contained; further failures counted, not re-disclosed): ${f.error.message}`),
1156
- });
1157
- let lastWorkspaceCwd = prepared.cwdRef?.current;
1158
- const announceWorkspaceMove = () => {
1159
- const cwdNow = prepared.cwdRef?.current;
1160
- if (cwdNow !== undefined && cwdNow !== lastWorkspaceCwd) {
1161
- lastWorkspaceCwd = cwdNow;
1162
- queue.push({ type: "workspace_changed", cwd: cwdNow, ...ident() });
1163
- }
1164
- };
1165
- const onMessageUpdate = (event) => {
1166
- const ev = event.assistantMessageEvent;
1167
- if (rs.turn.callStartAt === undefined)
1168
- rs.turn.callStartAt = Date.now();
1169
- if (ev.type === "text_delta" && ev.delta) {
1170
- if (rs.turn.firstTokenAt === undefined)
1171
- rs.turn.firstTokenAt = Date.now();
1172
- pushContent({ type: "text_delta", delta: ev.delta, ...ident() });
1173
- }
1174
- else if (ev.type === "text_end" && typeof ev.content === "string" && ev.content.length !== 0) {
1175
- pushContent({ type: "text_end", content: ev.content, ...ident() });
1176
- }
1177
- else if (ev.type === "thinking_delta" && ev.delta) {
1178
- pushContent({ type: "reasoning_delta", delta: ev.delta, ...ident() });
1179
- }
1180
- if (rs.budget.streamCancel && !rs.limits.budgetHit && (ev.type === "text_delta" || ev.type === "thinking_delta") && ev.delta) {
1181
- rs.budget.callOutputChars += ev.delta.length;
1182
- if (rs.budget.callOutputChars - rs.budget.lastStreamBudgetCheck >= 256) {
1183
- rs.budget.lastStreamBudgetCheck = rs.budget.callOutputChars;
1184
- const projectedAxis = rs.budget.projectedOverBudget();
1185
- if (projectedAxis !== undefined) {
1186
- rs.limits.budgetHit = "exceeded";
1187
- rs.limits.budgetAxis = projectedAxis;
1188
- void prepared.harness.abort();
1189
- prepared.abortController.abort();
1190
- }
1191
- }
1192
- }
1193
- };
1194
- const onMessageEnd = (event) => {
1195
- const m = event.message;
1196
- if (event.entryId !== undefined && (m.role === "user" || m.role === "assistant" || m.role === "toolResult")) {
1197
- emitCommitted(event.entryId, m.role, m.role === "toolResult" ? m.toolCallId : undefined);
1198
- }
1199
- if (event.entryId !== undefined && m.role === "user")
1200
- prepared.fileHistoryBoundary?.begin(event.entryId);
1201
- if (event.entryId !== undefined && m.role === "user" && prepared.gitStatusRef.pendingReceipt !== undefined) {
1202
- const pr = prepared.gitStatusRef.pendingReceipt;
1203
- const c = m.content;
1204
- const text = typeof c === "string"
1205
- ? c
1206
- : Array.isArray(c) && c.length >= 1 && c[0].type === "text"
1207
- ? c[0].text
1208
- : undefined;
1209
- if (text === pr.text) {
1210
- delete prepared.gitStatusRef.pendingReceipt;
1211
- pr.commit(event.entryId);
1212
- }
1213
- }
1214
- if (rs.degrade.degraded === undefined && m.role === "assistant") {
1215
- const deg = readDegradation(m);
1216
- if (deg)
1217
- rs.degrade.recordDegraded({ from: deg.from, to: deg.to, reason: deg.reason, atTurn: stats.turns + 1, ...(deg.chain && { chain: deg.chain }) });
1218
- }
1219
- if (m.role === "assistant" && "usage" in m && m.usage) {
1220
- if (m.usageMissing)
1221
- rs.turn.turnUsageMissing = true;
1222
- const u = m.usage;
1223
- stats.tokens += u.totalTokens || 0;
1224
- const { totalInputTokens: turnTotal, uncachedInputTokens: turnUncached, costMicroUsd: turnCostMicroUsd, } = usageCostMicroUsd(rs.telemetry.cacheFamily, u, rs.telemetry.pricing);
1225
- if (!rs.telemetry.pricingConfigured)
1226
- rs.telemetry.unpricedSpend = true;
1227
- stats.promptTokens += turnUncached;
1228
- stats.totalInputTokens += turnTotal;
1229
- stats.cachedTokens += u.cacheRead || 0;
1230
- stats.cacheWriteTokens += u.cacheWrite || 0;
1231
- stats.outputTokens += u.output || 0;
1232
- stats.costMicroUsd += turnCostMicroUsd;
1233
- rs.turn.turnUsage ??= { inputTokens: 0, totalInputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, costMicroUsd: 0 };
1234
- rs.turn.turnUsage.inputTokens += turnUncached;
1235
- rs.turn.turnUsage.totalInputTokens += turnTotal;
1236
- rs.turn.turnUsage.outputTokens += u.output || 0;
1237
- rs.turn.turnUsage.cacheReadTokens += u.cacheRead || 0;
1238
- rs.turn.turnUsage.cacheWriteTokens += u.cacheWrite || 0;
1239
- rs.turn.turnUsage.costMicroUsd += turnCostMicroUsd;
1240
- const callEndAt = Date.now();
1241
- const callIssuedAt = prepared.callIssuedAtRef.current;
1242
- prepared.callIssuedAtRef.current = undefined;
1243
- emitTrace(rs.telemetry.tracer, () => ({
1244
- kind: "brain.call",
1245
- version: 1,
1246
- taskId: rs.telemetry.taskId,
1247
- model: m.model,
1248
- provider: m.provider,
1249
- turn: stats.turns + 1,
1250
- promptTokens: turnUncached,
1251
- totalInputTokens: turnTotal,
1252
- completionTokens: u.output || 0,
1253
- cacheRead: u.cacheRead || 0,
1254
- cacheWrite: u.cacheWrite || 0,
1255
- latencyMs: rs.turn.callStartAt !== undefined ? callEndAt - rs.turn.callStartAt : 0,
1256
- firstTokenMs: rs.turn.callStartAt !== undefined && rs.turn.firstTokenAt !== undefined ? rs.turn.firstTokenAt - rs.turn.callStartAt : undefined,
1257
- ...(callIssuedAt !== undefined ? { callStartedAt: callIssuedAt } : {}),
1258
- ...(rs.telemetry.pricingConfigured ? { costMicroUsd: turnCostMicroUsd } : {}),
1259
- ...(m.role === "assistant" && typeof m.stopReason === "string"
1260
- ? { stopReason: m.stopReason }
1261
- : {}),
1262
- ts: callEndAt,
1263
- }));
1264
- const cacheRowUnknown = m.usageMissing === true || m.stopReason === "aborted";
1265
- if (prepared.cacheBreakDetector && prepared.cacheFingerprint && !cacheRowUnknown) {
1266
- const finding = prepared.cacheBreakDetector.observe({
1267
- turn: stats.turns + 1,
1268
- systemPrompt: prepared.cacheFingerprint.systemPrompt,
1269
- tools: prepared.cacheFingerprint.tools,
1270
- modelKey: `${m.provider}:${m.model}`,
1271
- cacheRead: u.cacheRead || 0,
1272
- });
1273
- if (finding) {
1274
- rs.telemetry.cacheBreakReported = true;
1275
- deps.runnerHooks.onError?.(new Error(`prompt-cache: break at turn ${finding.turn} — cache_read ${finding.cacheReadBefore}→${finding.cacheReadAfter}; ${finding.detail}. See design/31.`), { phase: "prompt-cache", sessionId: prepared.sessionId, classification: finding.cause });
1276
- }
1277
- }
1278
- }
1279
- if (m.role === "assistant" && typeof m.stopReason === "string") {
1280
- rs.turn.turnStopReason = m.stopReason;
1281
- }
1282
- if (m.role === "assistant") {
1283
- const rep = m.repetition;
1284
- if (rep !== undefined) {
1285
- const observed = [
1286
- ...(rep.cut !== undefined ? [{ action: "cut", e: rep.cut }] : []),
1287
- ...(rep.spared ?? []).map((e) => ({ action: "spared", e })),
1288
- ];
1289
- for (const { action, e } of observed) {
1290
- if (action === "cut")
1291
- rs.counters.repetitionCuts += 1;
1292
- else
1293
- rs.counters.repetitionSpared += 1;
1294
- if (rs.counters.repetitionEvents.length < rs.counters.REPETITION_EVENTS_CAP) {
1295
- rs.counters.repetitionEvents.push({ turn: stats.turns + 1, action, rule: e.rule, period: e.period, reps: e.reps, segment: e.segment });
1296
- }
1297
- emitTrace(rs.telemetry.tracer, () => ({
1298
- kind: "repetition.detected",
1299
- version: 1,
1300
- taskId: rs.telemetry.taskId,
1301
- turn: stats.turns + 1,
1302
- action,
1303
- rule: e.rule,
1304
- period: e.period,
1305
- reps: e.reps,
1306
- segment: e.segment,
1307
- ts: Date.now(),
1308
- }));
1309
- }
1310
- }
1311
- }
1312
- if (m.role === "assistant") {
1313
- const content = m.content;
1314
- if (rs.attach.attachState !== undefined)
1315
- rs.counters.cadenceTurns = advanceCadenceClock(rs.attach.attachState, rs.counters.cadenceTurns, content, writeFamilyOf);
1316
- rs.turn.lastTurnHadToolCalls = Array.isArray(content) && content.some((c) => c?.type === "toolCall");
1317
- }
1318
- rs.turn.callStartAt = undefined;
1319
- rs.turn.firstTokenAt = undefined;
1320
- rs.budget.callOutputChars = 0;
1321
- rs.budget.lastStreamBudgetCheck = 0;
1322
- };
1323
- const onToolStart = (event) => {
1324
- const startAt = Date.now();
1325
- toolStartAt.set(event.toolCallId, startAt);
1326
- startedToolCallIds.add(event.toolCallId);
1327
- batchArgs?.set(event.toolCallId, event.args);
1328
- stats.toolCalls += 1;
1329
- if (spec.finalVerification === true && !(rs.counters.finalVerifyInjections === 0 ? rs.counters.groundingSignalPreR9 : rs.counters.groundingSignalPostR9) && hasVerifiableStructureSignal(event.args)) {
1330
- if (rs.counters.finalVerifyInjections === 0)
1331
- rs.counters.groundingSignalPreR9 = true;
1332
- else
1333
- rs.counters.groundingSignalPostR9 = true;
1334
- }
1335
- const activityArg = primaryActivityArg(event.args);
1336
- internalsNotifier.notify(() => observeThenableRejection(internals?.onActivity?.({ phase: "start", toolCallId: event.toolCallId, toolName: event.toolName, at: startAt, ...(activityArg !== undefined ? { arg: activityArg } : {}) }), internalsNotifier, "runtask.onActivity.start"), "runtask.onActivity.start");
1337
- pushContent({
1338
- type: "tool_start",
1339
- toolCallId: event.toolCallId,
1340
- toolName: event.toolName,
1341
- ...(toolLabels.get(event.toolName) !== undefined ? { label: toolLabels.get(event.toolName) } : {}),
1342
- args: event.args,
1343
- ...ident(),
1344
- });
1345
- };
1346
- const onToolEnd = (event) => {
1347
- const toolStarted = toolStartAt.get(event.toolCallId);
1348
- toolStartAt.delete(event.toolCallId);
1349
- const toolNow = Date.now();
1350
- if (!rs.counters.wroteThisRun && (prepared.toolEffects.get(event.toolName) ?? "write") !== "read") {
1351
- rs.counters.wroteThisRun = true;
1352
- }
1353
- if (rs.attach.attachState !== undefined && !event.isError) {
1354
- const det = event.result?.details;
1355
- if (det !== undefined)
1356
- reduceToolEnd(rs.attach.attachState, det);
1357
- }
1358
- if (postToolBatchHook !== undefined) {
1359
- if (prepared.blockedToolCalls.delete(event.toolCallId) || event.notExecuted === true) {
1360
- batchArgs?.delete(event.toolCallId);
1361
- }
1362
- else {
1363
- const digest = batchResponseDigest(event.result);
1364
- rs.turn.toolBatch.push({
1365
- toolName: event.toolName,
1366
- input: batchArgs?.get(event.toolCallId),
1367
- toolCallId: event.toolCallId,
1368
- ...(digest !== undefined ? { response: digest } : {}),
1369
- isError: event.isError,
1370
- });
1371
- batchArgs?.delete(event.toolCallId);
1372
- }
1373
- }
1374
- emitTrace(rs.telemetry.tracer, () => ({
1375
- kind: "tool.call",
1376
- version: 1,
1377
- taskId: rs.telemetry.taskId,
1378
- name: event.toolName,
1379
- toolCallId: event.toolCallId,
1380
- turn: stats.turns + 1,
1381
- durationMs: toolStarted !== undefined ? toolNow - toolStarted : 0,
1382
- ok: !event.isError,
1383
- ts: toolNow,
1384
- }));
1385
- internalsNotifier.notify(() => observeThenableRejection(internals?.onActivity?.({ phase: "end", toolCallId: event.toolCallId, toolName: event.toolName, isError: event.isError, at: toolNow }), internalsNotifier, "runtask.onActivity.end"), "runtask.onActivity.end");
1386
- if (prepared.lspDiagnostics && !event.isError) {
1387
- const name = event.toolName;
1388
- if (name === "Edit" || name === "Write" || name === "NotebookEdit") {
1389
- const d = event.result?.details;
1390
- const p = typeof d?.filePath === "string" ? d.filePath : typeof d?.notebookPath === "string" ? d.notebookPath : undefined;
1391
- if (p)
1392
- prepared.lspDiagnostics.nudge(p);
1393
- }
1394
- }
1395
- const gate = prepared.gateOutcomes.get(event.toolCallId);
1396
- if (gate !== undefined)
1397
- prepared.gateOutcomes.delete(event.toolCallId);
1398
- pushContent({
1399
- type: "tool_end",
1400
- toolCallId: event.toolCallId,
1401
- toolName: event.toolName,
1402
- ...(toolLabels.get(event.toolName) !== undefined ? { label: toolLabels.get(event.toolName) } : {}),
1403
- isError: event.isError,
1404
- ...toolEndBodyFrom(event.result, event.isError, gate, prepared.abortController.signal.aborted ? gatedCallIdOf(prepared) : undefined),
1405
- ...ident(),
1406
- });
1407
- announceWorkspaceMove();
1408
- if (spec.outputSchema && event.toolName === OUTPUT_TOOL_NAME) {
1409
- if (event.isError) {
1410
- rs.degrade.outputErrorStreak += 1;
1411
- if (rs.degrade.outputErrorStreak > rs.limits.outputRetryCap) {
1412
- rs.degrade.outputInvalid = true;
1413
- void prepared.harness.abort();
1414
- prepared.abortController.abort();
1415
- }
1416
- }
1417
- else {
1418
- rs.degrade.outputErrorStreak = 0;
1419
- }
1420
- }
1421
- };
1422
- const onTurnEnd = () => {
1423
- stats.turns += 1;
1424
- const stopExtra = rs.turn.turnStopReason !== undefined ? { stopReason: rs.turn.turnStopReason } : {};
1425
- queue.push(rs.turn.turnUsage
1426
- ? { type: "turn_end", usage: rs.turn.turnUsage, ...(rs.turn.turnUsageMissing ? { usageMissing: true } : {}), ...stopExtra, ...ident() }
1427
- : { type: "turn_end", usageMissing: true, ...stopExtra, ...ident() });
1428
- rs.turn.turnUsageMissing = false;
1429
- rs.turn.turnStopReason = undefined;
1430
- if (parentToolCallId !== undefined) {
1431
- const progress = {
1432
- type: "task_progress",
1433
- taskId: rs.telemetry.taskId,
1434
- ...(internals?.delegationTaskType !== undefined ? { taskType: internals.delegationTaskType } : {}),
1435
- ...(internals?.cycleSeq !== undefined ? { seq: internals.cycleSeq } : {}),
1436
- ...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}),
1437
- ...(subagentName ? { name: subagentName } : {}),
1438
- model: prepared.model.id,
1439
- usage: { totalTokens: stats.tokens, toolUses: stats.toolCalls, durationMs: Date.now() - rs.telemetry.taskStart },
1440
- status: "running",
1441
- ...ident(),
1442
- };
1443
- queue.push(progress);
1444
- try {
1445
- internals?.onForwardEvent?.(progress);
1446
- }
1447
- catch {
1448
- }
1449
- }
1450
- emitTrace(rs.telemetry.tracer, () => ({ kind: "turn.end", version: 1, taskId: rs.telemetry.taskId, turn: stats.turns, ts: Date.now() }));
1451
- rs.turn.turnUsage = undefined;
1452
- rs.turn.callStartAt = undefined;
1453
- rs.turn.firstTokenAt = undefined;
1454
- rs.budget.callOutputChars = 0;
1455
- rs.budget.lastStreamBudgetCheck = 0;
1456
- toolStartAt.clear();
1457
- if (rs.limits.effectiveMaxTurns !== undefined && rs.limits.effectiveMaxTurns > 0 && stats.turns >= rs.limits.effectiveMaxTurns) {
1458
- rs.limits.turnsExceeded = true;
1459
- if (prepared.suspendForResource === undefined) {
1460
- void prepared.harness.abort();
1461
- prepared.abortController.abort();
1462
- }
1463
- }
1464
- else {
1465
- if (rs.degrade.degraded === undefined &&
1466
- spec.limits?.degrade &&
1467
- rs.degrade.degradeToModel &&
1468
- rs.budget.maxCostMicroUsd !== undefined &&
1469
- stats.costMicroUsd >= spec.limits.degrade.atCostFraction * rs.budget.maxCostMicroUsd) {
1470
- void prepared.harness.setModel(rs.degrade.degradeToModel);
1471
- rs.degrade.recordDegraded({ from: prepared.model.id, to: rs.degrade.degradeToModel.id, reason: "budget", atTurn: stats.turns }, rs.degrade.degradeToModel);
1472
- }
1473
- const overAxis = rs.limits.budgetHit ? undefined : rs.budget.overBudget();
1474
- if (overAxis !== undefined) {
1475
- rs.limits.budgetHit = "exceeded";
1476
- rs.limits.budgetAxis = overAxis;
1477
- if (prepared.suspendForResource === undefined) {
1478
- void prepared.harness.abort();
1479
- prepared.abortController.abort();
1480
- }
1481
- }
1482
- }
1483
- };
1484
- return { onMessageUpdate, onMessageEnd, onToolStart, onToolEnd, onTurnEnd, announceWorkspaceMove };
1485
- }
1486
75
  const REFUSED_DECIDE_KEYS = [
1487
76
  ["gateOutcome", "the settlement record is minted by core from the host's decision facts and the row's origin, never supplied"],
1488
77
  ["settledBy", 'a retired key — the host supplies decision FACTS as hostDecision{decidedBy: "person" | "sla_timeout", approver?} and core mints the settlement word from them'],
@@ -1744,7 +333,7 @@ export class Runner {
1744
333
  })();
1745
334
  const settled = run.catch(async (err) => {
1746
335
  let code = errorCodeOf(err);
1747
- const reopenReason = code === "resume.tool_unavailable" ? "tool_unavailable" : "env_failed";
336
+ const reopenReason = code === "resume.tool_unavailable" || code === "resume.tool_contract_mismatch" ? "tool_unavailable" : "env_failed";
1748
337
  const reopenable = resume !== undefined && resume.pendingActionStarted !== true && reopenReason !== undefined && !(resumeDecisionWasNegative(resume) && resume.decisionDelivered === true);
1749
338
  let reopenCommitted = false;
1750
339
  let reopenStateUnknown = false;
@@ -2957,11 +1546,14 @@ export class Runner {
2957
1546
  const postToolBatchHook = (spec.hooks ?? this.deps.hooks)?.postToolBatch;
2958
1547
  const batchArgs = postToolBatchHook ? new Map() : undefined;
2959
1548
  rs.turn.toolBatch = [];
2960
- const { onMessageUpdate, onMessageEnd, onToolStart, onToolEnd, onTurnEnd, announceWorkspaceMove } = makeHarnessHandlers(prepared, stats, rs, {
2961
- spec, queue, internals, ident, parentToolCallId, subagentName,
2962
- pushContent, emitCommitted, startedToolCallIds, toolStartAt, writeFamilyOf,
2963
- toolLabels, postToolBatchHook, batchArgs,
2964
- runnerHooks: { onError: (e, info) => this.deps.onError?.(e, info) },
1549
+ const { onMessageUpdate, onMessageEnd, onToolStart, onToolEnd, onTurnEnd, announceWorkspaceMove } = createHarnessHandlers({
1550
+ prepared, stats, rs,
1551
+ deps: {
1552
+ spec, queue, internals, ident, parentToolCallId, subagentName,
1553
+ pushContent, emitCommitted, startedToolCallIds, toolStartAt, writeFamilyOf,
1554
+ toolLabels, postToolBatchHook, batchArgs,
1555
+ runnerHooks: { onError: (e, info) => this.deps.onError?.(e, info) },
1556
+ },
2965
1557
  });
2966
1558
  const recordCompactionUsage = (m, msg) => {
2967
1559
  const u = msg?.usage;
@@ -3375,16 +1967,19 @@ export class Runner {
3375
1967
  for (const w of manualCompactRef.waiters.splice(0))
3376
1968
  w.resolve(outcome);
3377
1969
  };
3378
- const onTurnBoundary = makeTurnBoundary(prepared, stats, rs, {
3379
- spec, queue, loopLatch, manualCompactRef, todoToolMounted, taskToolsMounted,
3380
- walltimeMonotonicDeadline,
3381
- timeout, ident, postToolBatchHook, batchArgs, compactionBrain, withinTaskCompaction,
3382
- compactionBreaker, windowSafetyOptions, rapidRefill, drainManualCompact,
3383
- runnerHooks: {
3384
- onError: this.deps.onError,
3385
- seamCCompactionOptions: (p) => this.seamCCompactionOptions(p),
3386
- compactionHookOptions: (s, sid, trig) => this.compactionHookOptions(s, sid, trig, prepared.hookIdentity, { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal }),
3387
- recordCompactionReuse: (p, c) => this.recordCompactionReuse(p, c),
1970
+ const onTurnBoundary = createTurnBoundary({
1971
+ prepared, stats, rs,
1972
+ deps: {
1973
+ spec, queue, loopLatch, manualCompactRef, todoToolMounted, taskToolsMounted,
1974
+ walltimeMonotonicDeadline,
1975
+ timeout, ident, postToolBatchHook, batchArgs, compactionBrain, withinTaskCompaction,
1976
+ compactionBreaker, windowSafetyOptions, rapidRefill, drainManualCompact,
1977
+ runnerHooks: {
1978
+ onError: this.deps.onError,
1979
+ seamCCompactionOptions: (p) => this.seamCCompactionOptions(p),
1980
+ compactionHookOptions: (s, sid, trig) => this.compactionHookOptions(s, sid, trig, prepared.hookIdentity, { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal }),
1981
+ recordCompactionReuse: (p, c) => this.recordCompactionReuse(p, c),
1982
+ },
3388
1983
  },
3389
1984
  });
3390
1985
  prepared.gitStatusRef.reassert = async () => {
@@ -3894,7 +2489,7 @@ export class Runner {
3894
2489
  }
3895
2490
  catch (err) {
3896
2491
  loopLatch.ended = true;
3897
- if (errorCodeOf(err) === "resume.tool_unavailable") {
2492
+ if (errorCodeOf(err) === "resume.tool_unavailable" || errorCodeOf(err) === "resume.tool_contract_mismatch") {
3898
2493
  await settleTeardownLeg(() => prepared.mcp.dispose(), "mcp.dispose (tool_unavailable leg)", (e) => this.deps.onError?.(e, { phase: "config", sessionId: prepared.sessionId }));
3899
2494
  await settleTeardownLeg(() => prepared.a2a?.dispose(), "a2a.dispose (tool_unavailable leg)", (e) => this.deps.onError?.(e, { phase: "config", sessionId: prepared.sessionId }));
3900
2495
  await settleTeardownLeg(() => (prepared.ownedEnv && hasDestroy(prepared.ownedEnv) ? prepared.ownedEnv.destroy() : undefined), "ownedEnv.destroy (tool_unavailable leg)", (e) => this.deps.onError?.(e, { phase: "config", sessionId: prepared.sessionId }));
@@ -4908,6 +3503,7 @@ export class Runner {
4908
3503
  args: editedArgs,
4909
3504
  toolCallId: cp.pendingAction.toolCallId,
4910
3505
  budget: editBudget,
3506
+ ...callFaceSeat(parkedCallFaceOf(cp.pendingAction)),
4911
3507
  ...(cp.state.handsCwd !== undefined ? { cwd: cp.state.handsCwd } : {}),
4912
3508
  };
4913
3509
  const editSignal = config.signal;
@@ -5152,6 +3748,7 @@ export class Runner {
5152
3748
  ...(answerFace !== undefined ? { onQuestion: answerFace } : {}),
5153
3749
  };
5154
3750
  const reopenFn = store.reopen?.bind(store);
3751
+ const recordExecutionOutcome = (gate) => store.recordExecutionOutcome(token, cp.scope, gate);
5155
3752
  const onEnvRestoreFailed = reopenFn
5156
3753
  ? async (reason) => {
5157
3754
  const reopened = await reopenFn(token, cp.scope, reason);
@@ -5180,7 +3777,7 @@ export class Runner {
5180
3777
  ? new CheckpointError("checkpoint.reopen_failed", "the resume was aborted before the approved action could run AND the store refused to reopen the checkpoint — the approval is terminally consumed and the suspended work was not executed; a retry needs a fresh approval")
5181
3778
  : new CheckpointError("checkpoint.reopen_failed", "the resume was aborted before the approved action could run and the reopen attempt FAILED IN FLIGHT — the checkpoint's state is unprovable from here: it may already be pending again. Re-read it before deciding; do NOT issue a fresh approval on the assumption the old one is dead (the approved action did NOT run either way)");
5182
3779
  }
5183
- return this.runTaskStream(spec, { cp, outcome: outcomeForStore, onEnvRestoreFailed, ...(wakeMessage !== undefined ? { wakeMessage } : {}), ...(wakeHookContext !== undefined ? { wakeMessageHookContext: wakeHookContext } : {}) }, resumeInternals);
3780
+ return this.runTaskStream(spec, { cp, outcome: outcomeForStore, onEnvRestoreFailed, recordExecutionOutcome, ...(wakeMessage !== undefined ? { wakeMessage } : {}), ...(wakeHookContext !== undefined ? { wakeMessageHookContext: wakeHookContext } : {}) }, resumeInternals);
5184
3781
  }
5185
3782
  async applyResumeDecision(prepared, resume, emit, emitCommitted, onResolvedToolSuccess, onExecuteStart) {
5186
3783
  const { pendingAction } = resume.cp;
@@ -5197,6 +3794,12 @@ export class Runner {
5197
3794
  e.code = "resume.tool_unavailable";
5198
3795
  throw e;
5199
3796
  }
3797
+ const liveRoster = prepared.toolRosterDeltas.current;
3798
+ if (pendingAction.kind === "tool_approval" && liveRoster !== undefined && judgeParkedToolIdentity(pendingAction, rosterEntryNamed(liveRoster, pendingAction.toolName)) === "mismatch") {
3799
+ const e = new Error(`the approved tool "${pendingAction.toolName}" on this leg is not the tool the approval was minted against (its contract / shape / revision differ) — refusing to execute a replacement wearing the name; the checkpoint is reopened for a retry with the original tool present`);
3800
+ e.code = "resume.tool_contract_mismatch";
3801
+ throw e;
3802
+ }
5200
3803
  const completed = new Set(pendingAction.completedCallIds);
5201
3804
  const deferredIds = pendingAction.batchToolCallIds.filter((id) => id !== pendingAction.toolCallId && !completed.has(id));
5202
3805
  const names = new Map();
@@ -5236,10 +3839,18 @@ export class Runner {
5236
3839
  const { outcome } = resume;
5237
3840
  if (pendingAction.kind !== "tool_approval" || outcome.gate !== "policy_ask")
5238
3841
  return;
3842
+ const decidedGate = outcome.gateOutcome;
3843
+ if (decidedGate === undefined) {
3844
+ throw new CheckpointError("checkpoint.invalid_outcome", "pending action reached the resolver with no settlement record on its decision (gateOutcome) — refusing to execute or settle a decision that carries no record");
3845
+ }
5239
3846
  const resolvedArgs = pendingAction.toolName === ASK_USER_QUESTION_TOOL_NAME ? pendingAction.args : (outcome.updatedInput !== undefined ? outcome.updatedInput : pendingAction.args);
5240
3847
  const pendingLabel = (() => { const l = prepared.tools.find((t) => t.name === pendingAction.toolName)?.label; return l !== undefined && l !== pendingAction.toolName ? { label: l } : {}; })();
5241
3848
  emit({ type: "tool_start", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, args: resolvedArgs });
5242
- const emitEnd = (isError, result, gate = outcome.gateOutcome) => emit({ type: "tool_end", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, isError, ...toolEndBodyFrom(result, isError, gate) });
3849
+ const settleEnd = async (isError, result, gate = decidedGate) => {
3850
+ emit({ type: "tool_end", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, isError, ...toolEndBodyFrom(result, isError, gate) });
3851
+ const word = await resume.recordExecutionOutcome(gate);
3852
+ settleExecutionRecord(this.deps.onNotice, word, { toolName: pendingAction.toolName, gate, sessionId: prepared.sessionId, runId: prepared.runId, scope: resume.cp.scope, ...(resume.cp.checkpointId !== undefined ? { checkpointId: resume.cp.checkpointId } : {}) });
3853
+ };
5243
3854
  const vetoed = (layer) => mintGateOutcome({
5244
3855
  deniedBy: layer,
5245
3856
  ...(outcome.gateOutcome?.settlement !== undefined && outcome.gateOutcome.origin !== undefined ? { settled: { settlement: outcome.gateOutcome.settlement, origin: outcome.gateOutcome.origin } } : {}),
@@ -5249,7 +3860,7 @@ export class Runner {
5249
3860
  ? `No one answered the approval request for the pending tool call "${pendingAction.toolName}" — the approval window elapsed with no answer, so it was not executed.`
5250
3861
  : `The pending tool call "${pendingAction.toolName}" was denied by an approver.`;
5251
3862
  const reason = outcome.reason ? delimitUntrusted("reviewer note", outcome.reason, REVIEWER_NOTE_MAX_BODY) : defaultDenial;
5252
- emitEnd(true, { content: formatHookFeedback(reason, prepared.reminderMark) });
3863
+ await settleEnd(true, { content: formatHookFeedback(reason, prepared.reminderMark) });
5253
3864
  const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, formatHookFeedback(reason, prepared.reminderMark), true));
5254
3865
  emitCommitted(eid, "toolResult", pendingAction.toolCallId);
5255
3866
  return;
@@ -5269,12 +3880,13 @@ export class Runner {
5269
3880
  toolCallId: pendingAction.toolCallId,
5270
3881
  budget: beltBudget,
5271
3882
  ...(prepared.cwdRef !== undefined ? { cwd: prepared.cwdRef.current } : {}),
3883
+ ...callFaceSeat(toolCallFaceOf(prepared.toolRosterDeltas.current, pendingAction.toolName)),
5272
3884
  }, prepared.abortController.signal));
5273
3885
  const beltUnsafe = rechecked.action === "deny" ||
5274
3886
  (rechecked.updatedInput !== undefined && !deepJsonEqual(rechecked.updatedInput, resolvedArgs));
5275
3887
  if (beltUnsafe) {
5276
3888
  const editedDenial = formatHookFeedback(`The approver EDITED this call's input; the edited call is refused by the deployment's tool policy and was not executed${rechecked.action === "deny" && rechecked.message ? `: ${rechecked.message}` : " (the policy rewrote the edit to a value that was not approved)"}.`, prepared.reminderMark);
5277
- emitEnd(true, { content: editedDenial }, vetoed("policy"));
3889
+ await settleEnd(true, { content: editedDenial }, vetoed("policy"));
5278
3890
  const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, editedDenial, true));
5279
3891
  emitCommitted(eid, "toolResult", pendingAction.toolCallId);
5280
3892
  return;
@@ -5284,7 +3896,7 @@ export class Runner {
5284
3896
  const gateOrgGoverned = realApprovalOrgFact(gateRealApproval) !== undefined;
5285
3897
  if (gateOrgGoverned && prepared.permissionRuleOrg === undefined) {
5286
3898
  const unwiredDenial = formatHookFeedback(`The approved tool call "${pendingAction.toolName}" was not executed: its approval was minted under organization governance (${gateRealApproval.origin}), and this worker has no org adjudication wiring — a governed approval may only be redeemed where governance can be enforced. This approval is spent; re-issue the call on an org-wired worker.`, prepared.reminderMark);
5287
- emitEnd(true, { content: unwiredDenial }, vetoed("org"));
3899
+ await settleEnd(true, { content: unwiredDenial }, vetoed("org"));
5288
3900
  const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, unwiredDenial, true));
5289
3901
  emitCommitted(eid, "toolResult", pendingAction.toolCallId);
5290
3902
  return;
@@ -5315,7 +3927,7 @@ export class Runner {
5315
3927
  ? ` Governance disclosures: ${inlineUntrusted(org.disclosures.join("; "), ORG_DISCLOSURE_MAX_CHARS)}.`
5316
3928
  : "";
5317
3929
  const orgDenial = formatHookFeedback(`The approved tool call "${pendingAction.toolName}" was not executed: ${blocked}.${orgDisclosures} This approval is spent — the call has to be re-issued and approved again once organization policy permits it.`, prepared.reminderMark);
5318
- emitEnd(true, { content: orgDenial }, vetoed("org"));
3930
+ await settleEnd(true, { content: orgDenial }, vetoed("org"));
5319
3931
  const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, orgDenial, true));
5320
3932
  emitCommitted(eid, "toolResult", pendingAction.toolCallId);
5321
3933
  return;
@@ -5326,12 +3938,13 @@ export class Runner {
5326
3938
  toolName: pendingAction.toolName,
5327
3939
  args: resolvedArgs,
5328
3940
  toolCallId: pendingAction.toolCallId,
3941
+ ...callFaceSeat(toolCallFaceOf(prepared.toolRosterDeltas.current, pendingAction.toolName)),
5329
3942
  budget: beltBudget,
5330
3943
  ...(prepared.cwdRef !== undefined ? { cwd: prepared.cwdRef.current } : {}),
5331
3944
  }, prepared.abortController.signal));
5332
3945
  if (narrowed.action === "deny") {
5333
3946
  const narrowedDenial = formatHookFeedback(`The approved tool call "${pendingAction.toolName}" is now denied by a session rule and was not executed${narrowed.message ? `: ${narrowed.message}` : ""}.`, prepared.reminderMark);
5334
- emitEnd(true, { content: narrowedDenial }, vetoed("policy"));
3947
+ await settleEnd(true, { content: narrowedDenial }, vetoed("policy"));
5335
3948
  const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, narrowedDenial, true));
5336
3949
  emitCommitted(eid, "toolResult", pendingAction.toolCallId);
5337
3950
  return;
@@ -5364,13 +3977,13 @@ export class Runner {
5364
3977
  d = { ...(d ?? {}), errorKind: src.errorKind };
5365
3978
  return d;
5366
3979
  })();
5367
- emitEnd(true, { content: execError, ...(marks !== undefined ? { details: marks } : {}) });
3980
+ await settleEnd(true, { content: execError, ...(marks !== undefined ? { details: marks } : {}) });
5368
3981
  const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, execError, true));
5369
3982
  emitCommitted(eid, "toolResult", pendingAction.toolCallId);
5370
3983
  return;
5371
3984
  }
5372
3985
  const executedIsError = res.isError === true;
5373
- emitEnd(executedIsError, res);
3986
+ await settleEnd(executedIsError, res);
5374
3987
  onResolvedToolSuccess?.(pendingAction.toolName, executedIsError ? undefined : res.details);
5375
3988
  const eid = await prepared.session.appendMessage({
5376
3989
  role: "toolResult",
@@ -5552,36 +4165,3 @@ export class Runner {
5552
4165
  export function runTask(spec, deps) {
5553
4166
  return new Runner(deps).runTask(spec);
5554
4167
  }
5555
- const WALLTIME_SUSPEND_GRACE_SEC = 120;
5556
- const TIMER_LATENESS_REPORT_MS = 5_000;
5557
- function startTimeout(harness, abortController, remainingMs, softSuspendable = false) {
5558
- const state = { fired: false, clear: () => { } };
5559
- if (remainingMs !== undefined) {
5560
- const hardMs = Math.max(0, remainingMs + (softSuspendable ? WALLTIME_SUSPEND_GRACE_SEC * 1000 : 0));
5561
- const scheduledAtMs = Date.now() + hardMs;
5562
- let leftMs = hardMs;
5563
- let timer;
5564
- const armChunk = () => {
5565
- const chunk = Math.max(0, Math.min(leftMs, MAX_TIMER_DELAY_MS));
5566
- timer = setTimeout(() => {
5567
- leftMs -= chunk;
5568
- if (leftMs > 0) {
5569
- armChunk();
5570
- return;
5571
- }
5572
- fireHardAbort();
5573
- }, chunk);
5574
- };
5575
- const fireHardAbort = () => {
5576
- if (state.fired)
5577
- return;
5578
- state.fired = true;
5579
- state.latenessMs = Math.max(0, Date.now() - scheduledAtMs);
5580
- abortController.abort();
5581
- void harness.abort();
5582
- };
5583
- armChunk();
5584
- state.clear = () => clearTimeout(timer);
5585
- }
5586
- return state;
5587
- }