@sema-agent/core 7.7.0 → 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 (135) hide show
  1. package/CHANGELOG.md +37 -0
  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/send-message-tool.js +2 -1
  6. package/dist/agents/subagent-steps.js +5 -4
  7. package/dist/agents/subagent.js +2 -3
  8. package/dist/core/ask-question.js +2 -2
  9. package/dist/core/checkpoint-store.d.ts +38 -4
  10. package/dist/core/context-edit.d.ts +0 -22
  11. package/dist/core/context-edit.js +2 -11
  12. package/dist/core/engine-notice.d.ts +6 -0
  13. package/dist/core/fs-write-gate-policy.d.ts +7 -1
  14. package/dist/core/fs-write-gate-policy.js +7 -7
  15. package/dist/core/gate-fold.js +2 -2
  16. package/dist/core/gate-lanes.js +4 -3
  17. package/dist/core/governance-codes.d.ts +1 -1
  18. package/dist/core/governance-codes.js +6 -0
  19. package/dist/core/hooks.d.ts +8 -2
  20. package/dist/core/hooks.js +1 -1
  21. package/dist/core/lsp.js +2 -2
  22. package/dist/core/mcp-server-spec.d.ts +17 -0
  23. package/dist/core/mcp.js +3 -1
  24. package/dist/core/memory-engine/tools.js +4 -12
  25. package/dist/core/permission-rules.d.ts +32 -2
  26. package/dist/core/permission-rules.js +43 -37
  27. package/dist/core/present-plan-tool.js +3 -4
  28. package/dist/core/runner/active-skill-scope.js +7 -4
  29. package/dist/core/runner/compaction-call-options.d.ts +1 -1
  30. package/dist/core/runner/contracts.d.ts +27 -3
  31. package/dist/core/runner/prepare-ask-lane.d.ts +1 -1
  32. package/dist/core/runner/prepare-caps-and-workflow.d.ts +5 -4
  33. package/dist/core/runner/prepare-caps-and-workflow.js +16 -11
  34. package/dist/core/runner/prepare-defer-classify.d.ts +5 -26
  35. package/dist/core/runner/prepare-defer-classify.js +10 -10
  36. package/dist/core/runner/prepare-delegation-surface.d.ts +5 -4
  37. package/dist/core/runner/prepare-delegation-surface.js +15 -17
  38. package/dist/core/runner/prepare-gate-stations.d.ts +4 -1
  39. package/dist/core/runner/prepare-gate-stations.js +3 -1
  40. package/dist/core/runner/prepare-hands-readface.d.ts +3 -2
  41. package/dist/core/runner/prepare-hands-readface.js +3 -2
  42. package/dist/core/runner/prepare-inherited-gate.d.ts +1 -1
  43. package/dist/core/runner/prepare-inherited-gate.js +1 -0
  44. package/dist/core/runner/prepare-lsp.d.ts +4 -3
  45. package/dist/core/runner/prepare-lsp.js +3 -2
  46. package/dist/core/runner/prepare-memory-engine-session.d.ts +4 -4
  47. package/dist/core/runner/prepare-memory-engine-session.js +4 -2
  48. package/dist/core/runner/prepare-offload-wrappers.js +7 -5
  49. package/dist/core/runner/prepare-park-ask.d.ts +4 -0
  50. package/dist/core/runner/prepare-park-ask.js +4 -1
  51. package/dist/core/runner/prepare-project-context.d.ts +5 -23
  52. package/dist/core/runner/prepare-project-context.js +7 -5
  53. package/dist/core/runner/prepare-prompt-assembly.d.ts +1 -1
  54. package/dist/core/runner/prepare-protocol-tools.d.ts +5 -1
  55. package/dist/core/runner/prepare-protocol-tools.js +38 -21
  56. package/dist/core/runner/prepare-question-face.d.ts +4 -3
  57. package/dist/core/runner/prepare-question-face.js +5 -3
  58. package/dist/core/runner/prepare-run-refs.d.ts +16 -0
  59. package/dist/core/runner/prepare-run-refs.js +6 -0
  60. package/dist/core/runner/prepare-safety-scan.js +42 -2
  61. package/dist/core/runner/prepare-task.js +37 -19
  62. package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +5 -2
  63. package/dist/core/runner/prepare-tool-disclosure-mount.js +3 -1
  64. package/dist/core/runner/prepare-wiring-manifest.d.ts +4 -0
  65. package/dist/core/runner/prepare-wiring-manifest.js +3 -8
  66. package/dist/core/runner/run-harness-handlers.d.ts +46 -0
  67. package/dist/core/runner/run-harness-handlers.js +345 -0
  68. package/dist/core/runner/run-turn-boundary.d.ts +69 -0
  69. package/dist/core/runner/run-turn-boundary.js +694 -0
  70. package/dist/core/runner/runtask.js +47 -1043
  71. package/dist/core/runner/session-rule-policy.js +9 -4
  72. package/dist/core/runner/synthetic-tools.js +5 -8
  73. package/dist/core/runner/tool-disclosure.d.ts +4 -0
  74. package/dist/core/runner/tool-disclosure.js +9 -5
  75. package/dist/core/runner/tool-end-body.js +3 -2
  76. package/dist/core/runner/tool-face-overlay.d.ts +40 -0
  77. package/dist/core/runner/tool-face-overlay.js +125 -0
  78. package/dist/core/runner/tool-output-projection.d.ts +10 -0
  79. package/dist/core/runner/tool-output-projection.js +33 -10
  80. package/dist/core/sensitive-path-policy.d.ts +3 -0
  81. package/dist/core/sensitive-path-policy.js +6 -5
  82. package/dist/core/shared-memory/tools.js +3 -6
  83. package/dist/core/store-contracts/checkpoint-store-contract.js +23 -0
  84. package/dist/core/task-event.d.ts +10 -0
  85. package/dist/core/task-registry.js +4 -5
  86. package/dist/core/task-spec.d.ts +8 -0
  87. package/dist/core/tool-catalog-entries.d.ts +48 -0
  88. package/dist/core/tool-catalog-entries.js +174 -0
  89. package/dist/core/tool-conformance.d.ts +22 -0
  90. package/dist/core/tool-conformance.js +37 -0
  91. package/dist/core/tool-face.d.ts +171 -0
  92. package/dist/core/tool-face.js +25 -0
  93. package/dist/core/tool-policy.d.ts +24 -0
  94. package/dist/core/tool-policy.js +4 -2
  95. package/dist/core/tool-registry.d.ts +114 -0
  96. package/dist/core/tool-registry.js +114 -0
  97. package/dist/core/tool-result-budget.d.ts +0 -3
  98. package/dist/core/tool-result-budget.js +4 -3
  99. package/dist/core/tool-result-store.d.ts +0 -18
  100. package/dist/core/tool-result-store.js +2 -18
  101. package/dist/core/tool-roster.d.ts +450 -0
  102. package/dist/core/tool-roster.js +497 -0
  103. package/dist/core/tool-spec.d.ts +54 -3
  104. package/dist/core/tools.js +7 -0
  105. package/dist/core/wiring-manifest.d.ts +13 -0
  106. package/dist/core/wiring-manifest.js +2 -1
  107. package/dist/core/write-protect.d.ts +2 -1
  108. package/dist/core/write-protect.js +5 -4
  109. package/dist/engine/compaction/utils.d.ts +6 -0
  110. package/dist/engine/compaction/utils.js +19 -19
  111. package/dist/engine/loop/types.d.ts +6 -0
  112. package/dist/index.d.ts +15 -4
  113. package/dist/index.js +15 -4
  114. package/dist/orchestration/goal.js +2 -1
  115. package/dist/orchestration/run-spec.js +3 -5
  116. package/dist/orchestration/run-workflow-tool.js +2 -2
  117. package/dist/prompt-assembly/event-registry.js +3 -3
  118. package/dist/prompt-assembly/tool-catalog.d.ts +2 -2
  119. package/dist/tools/fs/fs-bash.d.ts +1 -1
  120. package/dist/tools/fs/fs-bash.js +7 -11
  121. package/dist/tools/fs/fs-read.js +2 -2
  122. package/dist/tools/fs/fs-search-tools.d.ts +0 -7
  123. package/dist/tools/fs/fs-search-tools.js +3 -16
  124. package/dist/tools/fs/fs-write.js +4 -6
  125. package/dist/tools/fs/repo-map.js +2 -2
  126. package/dist/tools/fs/safety.d.ts +2 -1
  127. package/dist/tools/fs/safety.js +6 -5
  128. package/dist/tools/monitor.js +2 -2
  129. package/dist/tools/scheduler-tools.js +5 -8
  130. package/dist/tools/task-list.js +5 -8
  131. package/dist/tools/todo.js +2 -2
  132. package/dist/tools/web.js +3 -6
  133. package/dist/tools/worktree.js +3 -4
  134. package/package.json +4 -1
  135. package/test/export-surface.snapshot.json +147 -7
@@ -12,7 +12,7 @@ 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";
15
+ import { DEFAULT_COMPACTION_INSTRUCTIONS, createRapidRefillState, maybeCompact, sanitizeCompactionSettings } from "../auto-compaction.js";
16
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";
@@ -20,11 +20,9 @@ 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,27 +30,25 @@ 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
- import { gatedCallIdOf } from "./park-commit.js";
40
+ import { drainForwardedFramesBeforeDone, forwardsSubagentEvents } from "./prepare-run-refs.js";
41
+ import { callFaceSeat, judgeParkedToolIdentity, parkedCallFaceOf, rosterEntryNamed, toolCallFaceOf } from "../tool-roster.js";
43
42
  import { placementValueOrAbsent } from "./checkpoint-scope.js";
44
43
  import { SessionReadFileStates } from "./prepare-hands-readface.js";
45
44
  import { settleTeardownLeg } from "./teardown-bounded.js";
46
- import { hasVerifiableStructureSignal } from "./grounding-signal.js";
47
45
  import { hasDestroy, isIsolated } from "../remote-env.js";
48
46
  import { hasBackgroundShell, sweepBackgroundShells } from "../background-shell.js";
49
47
  import { cloneObserverInput, formatHookFeedback, hookSeatExpiredError, MAX_HOOK_TIMEOUT_MS, mintHookInvocationIdentity, resolveHookTimeoutMs, runHookSeat } from "../hooks.js";
50
48
  import { buildHumanInputEvent, frameMidTurnUserInput, projectHumanInput } from "../human-input-projection.js";
51
- import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY, sanitizeUntrustedText, SHELLED_BODY_ENVELOPE_TAGS } from "../untrusted-text.js";
52
- import { droppedToolDigest, withAnnounceDigests } from "./announce-once-ledger.js";
49
+ import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY, sanitizeUntrustedText } from "../untrusted-text.js";
53
50
  import { appendInterruptionMarker, reconcileInterruptedSession } from "../session-reconcile.js";
54
51
  import { RunnerSharedToolResultStore } from "../tool-result-store.js";
55
- import { formatDiagnosticsBlock } from "../lsp-diagnostics.js";
56
52
  import { isAskOrigin } from "../ask-origin.js";
57
53
  import { isGateKind, PAUSE_REGISTRY, resumeGateMatches } from "../pause-registry.js";
58
54
  import { checkToolPolicyProjection, constraintChainDigest, constraintChainEntryOfSuppliedLayer, refuseOutOfContractDecision, screenApproverAttribution, toolPolicyNameSets } from "../tool-policy.js";
@@ -61,1032 +57,21 @@ import { defaultTaskRegistry } from "../task-registry.js";
61
57
  import { discloseDroppedPending, isDelegatedAgentTerminal, isSystemInjectionPriority, isTerminalTaskNotification, PendingSessionNotifications, renderTaskNotificationXml, SYSTEM_INJECTION_PRIORITIES, SystemInjectionQueue, taskNotificationDedupKey } from "../task-notification.js";
62
58
  import { ToolDetachHub } from "../tool-detach.js";
63
59
  import { createPeerInboundChainRef, createPeerSelfRef } from "../../agents/peer-admission.js";
64
- import { workflowSizeGuidelineChangeNotice } from "../../orchestration/workflow-size-guideline.js";
65
60
  import { createRunState } from "./initial-run-state.js";
66
61
  import { nextHumanInputSeq, sameAcceptedSteerInput } from "./steer-admission.js";
67
- import { batchResponseDigest, reconciledToolEndBody, toolEndBodyFrom, toolResultMsg, writeFamilyOfCanonical } from "./tool-end-body.js";
62
+ import { reconciledToolEndBody, toolEndBodyFrom, toolResultMsg, writeFamilyOfCanonical } from "./tool-end-body.js";
68
63
  import { answerFaceForRedeemedCall, assertOutcomeText, deepJsonEqual, DEFERRED_REISSUE, describeSuppliedValue, pendingContentAskCallId, resumeContinuation, resumeDecisionWasNegative, sameWinner } from "./decide-continuation.js";
69
- import { awaitChargeWithSlowDisclosure, DEFAULT_PRECALL_OUTPUT_TOKENS, discloseUnevaluableWindow, ENV_DUE_GOVERNANCE_READ_BUDGET_MS, GOVERNANCE_READ_STALLED, LIMIT_APPROACH_DEFAULT_THRESHOLDS, limitApproachFrames, platformLimitTerminal, raceUntilDeadline, resolveMaxTurns, startTimeout, TIMER_LATENESS_REPORT_MS } from "./clock-and-limits.js";
64
+ import { awaitChargeWithSlowDisclosure, DEFAULT_PRECALL_OUTPUT_TOKENS, discloseUnevaluableWindow, platformLimitTerminal, resolveMaxTurns, startTimeout, TIMER_LATENESS_REPORT_MS } from "./clock-and-limits.js";
70
65
  import { gitRestateOption, resolveGitLegDelivery, wrapGitFrame } from "./git-leg-delivery.js";
71
- const MAX_CONSECUTIVE_COMPACTION_FAILURES = 3;
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";
72
68
  const RESUME_PREFLIGHT_DEFAULT_TIMEOUT_MS = 10_000;
73
69
  const STOP_HOOK_BLOCK_CAP = 8;
74
- const COMPACTION_REGROWTH_FACTOR = 1.5;
75
- const COMPACTION_FREED_EPSILON = 256;
76
70
  const ORG_DISCLOSURE_MAX_CHARS = 600;
77
- const BATCH_CONTEXT_MIN_KEEP_BYTES = 160;
78
- const BATCH_TRUNCATION_MARKER = "\n…[truncated]";
79
71
  const SUGGESTIONS_DEFAULT_COUNT = 3;
80
72
  const SUGGESTIONS_MAX_COUNT = 8;
81
73
  const SUGGESTIONS_TIMEOUT_MS = 30_000;
82
74
  const SUGGESTIONS_TRANSCRIPT_MESSAGES = 12;
83
- function makeTurnBoundary(prepared, stats, rs, deps) {
84
- const { spec, queue, manualCompactRef, todoToolMounted, taskToolsMounted, walltimeMonotonicDeadline, timeout, ident, postToolBatchHook, batchArgs, compactionBrain, withinTaskCompaction, compactionBreaker, windowSafetyOptions, rapidRefill, drainManualCompact, runnerHooks } = deps;
85
- let finalVerifySeenAtLastBoundary = 0;
86
- const onTurnBoundary = async (event) => {
87
- let boundarySteered = false;
88
- const noNextRequest = () => prepared.stopRequestedRef.current || deps.loopLatch.userHalted;
89
- const walltimeHit = walltimeMonotonicDeadline !== undefined && performance.now() >= walltimeMonotonicDeadline;
90
- const preemptHit = spec.preemptSignal?.aborted === true;
91
- if (preemptHit && !prepared.suspendForResource && !rs.counters.preemptIgnoredReported) {
92
- rs.counters.preemptIgnoredReported = true;
93
- emitTrace(rs.telemetry.tracer, () => ({ kind: "preempt.ignored", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
94
- }
95
- let platformCause;
96
- let platformRetryAfterMs;
97
- const preemptWillSuspend = preemptHit && prepared.suspendForResource !== undefined;
98
- if (!preemptWillSuspend && prepared.pausedRef.current === undefined && !prepared.abortController.signal.aborted) {
99
- const ledgerReadDeadline = prepared.envLifetimeSuspendAt !== undefined ? prepared.envLifetimeSuspendAt + ENV_DUE_GOVERNANCE_READ_BUDGET_MS : undefined;
100
- if (prepared.usageGovernance !== undefined) {
101
- const governance = prepared.usageGovernance;
102
- try {
103
- const at = Date.now();
104
- 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 }));
105
- const windowRead = governance.check(at);
106
- let answer;
107
- if (ledgerReadDeadline !== undefined) {
108
- answer = await raceUntilDeadline(windowRead, ledgerReadDeadline);
109
- if (answer === GOVERNANCE_READ_STALLED) {
110
- void windowRead.catch((lateErr) => {
111
- try {
112
- runnerHooks.onError?.(lateErr instanceof Error ? lateErr : new Error(String(lateErr)), { phase: "config", sessionId: prepared.sessionId });
113
- }
114
- catch {
115
- }
116
- });
117
- try {
118
- 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 });
119
- }
120
- catch {
121
- }
122
- }
123
- }
124
- else {
125
- answer = await windowRead;
126
- }
127
- if (answer !== GOVERNANCE_READ_STALLED) {
128
- platformRetryAfterMs = answer;
129
- if (platformRetryAfterMs !== undefined)
130
- platformCause = "usage_window";
131
- }
132
- }
133
- catch (govErr) {
134
- rs.limits.platformTerminal = govErr instanceof Error ? govErr : new Error(String(govErr));
135
- void prepared.harness.abort();
136
- prepared.abortController.abort();
137
- return undefined;
138
- }
139
- }
140
- if (platformCause === undefined && prepared.envLifetimeSuspendAt !== undefined && Date.now() >= prepared.envLifetimeSuspendAt) {
141
- platformCause = "env_lifetime";
142
- }
143
- }
144
- if (platformCause !== undefined) {
145
- const platformSpend = { costMicroUsd: stats.costMicroUsd, tokens: stats.tokens, turns: stats.turns, walltimeMs: Math.round(performance.now() - rs.telemetry.taskStartMonotonic) };
146
- const committed = prepared.suspendForPlatformLimit !== undefined &&
147
- (await prepared.suspendForPlatformLimit(platformCause, platformSpend, platformRetryAfterMs !== undefined ? { resumeAfterMs: platformRetryAfterMs } : undefined));
148
- if (committed) {
149
- rs.limits.turnsExceeded = false;
150
- rs.limits.budgetHit = undefined;
151
- return undefined;
152
- }
153
- const terminal = platformLimitTerminal(platformCause, platformRetryAfterMs);
154
- rs.limits.platformTerminal = terminal;
155
- try {
156
- runnerHooks.onError?.(terminal, { phase: "config", sessionId: prepared.sessionId });
157
- }
158
- catch {
159
- }
160
- void prepared.harness.abort();
161
- prepared.abortController.abort();
162
- return undefined;
163
- }
164
- if (prepared.suspendForResource &&
165
- (rs.limits.turnsExceeded || rs.limits.budgetHit !== undefined || walltimeHit || preemptHit) &&
166
- prepared.pausedRef.current === undefined) {
167
- const reason = preemptHit
168
- ? "preempt"
169
- : rs.limits.budgetHit !== undefined
170
- ? rs.limits.budgetAxis === "tokens"
171
- ? "budget_tokens"
172
- : "budget_cost"
173
- : rs.limits.turnsExceeded
174
- ? "turns"
175
- : "walltime";
176
- const sliceSpend = { costMicroUsd: stats.costMicroUsd, tokens: stats.tokens, turns: stats.turns, walltimeMs: Math.round(performance.now() - rs.telemetry.taskStartMonotonic) };
177
- if (await prepared.suspendForResource(reason, sliceSpend)) {
178
- rs.limits.turnsExceeded = false;
179
- rs.limits.budgetHit = undefined;
180
- }
181
- else {
182
- void prepared.harness.abort();
183
- prepared.abortController.abort();
184
- }
185
- return undefined;
186
- }
187
- if (walltimeMonotonicDeadline !== undefined &&
188
- prepared.suspendForResource === undefined &&
189
- performance.now() >= walltimeMonotonicDeadline) {
190
- if (!timeout.fired) {
191
- timeout.fired = true;
192
- timeout.clear();
193
- timeout.latenessMs = Math.max(timeout.latenessMs ?? 0, performance.now() - walltimeMonotonicDeadline);
194
- rs.counters.walltimeSyncBackstopFired = true;
195
- void prepared.harness.abort();
196
- prepared.abortController.abort();
197
- }
198
- return undefined;
199
- }
200
- const approachCfg = spec.limits?.approachNotice;
201
- if (approachCfg !== false &&
202
- rs.counters.approachNoticesSent < LIMIT_APPROACH_DEFAULT_THRESHOLDS.length &&
203
- prepared.suspendForResource === undefined &&
204
- rs.turn.lastTurnHadToolCalls &&
205
- prepared.batchHaltRef.current === undefined &&
206
- !boundarySteered &&
207
- !prepared.abortController.signal.aborted) {
208
- const thresholds = approachCfg?.at ?? LIMIT_APPROACH_DEFAULT_THRESHOLDS;
209
- const ratios = [];
210
- if (rs.budget.maxTokensWindow !== undefined && rs.budget.maxTokensWindow > 0) {
211
- ratios.push({ axis: "token budget", ratio: stats.tokens / rs.budget.maxTokensWindow });
212
- }
213
- if (rs.budget.maxCostMicroUsd !== undefined && rs.budget.maxCostMicroUsd > 0) {
214
- ratios.push({ axis: "cost budget", ratio: stats.costMicroUsd / rs.budget.maxCostMicroUsd });
215
- }
216
- const declaredMaxTurns = spec.limits?.maxTurns;
217
- if (declaredMaxTurns !== undefined && declaredMaxTurns > 0) {
218
- ratios.push({ axis: "turn budget", ratio: (stats.turns + 1) / declaredMaxTurns });
219
- }
220
- if (walltimeMonotonicDeadline !== undefined) {
221
- const walltimeWindowMs = walltimeMonotonicDeadline - rs.telemetry.taskStartMonotonic;
222
- if (walltimeWindowMs > 0) {
223
- ratios.push({ axis: "walltime budget", ratio: (performance.now() - rs.telemetry.taskStartMonotonic) / walltimeWindowMs });
224
- }
225
- }
226
- let tightest;
227
- for (const r of ratios)
228
- if (tightest === undefined || r.ratio > tightest.ratio)
229
- tightest = r;
230
- const index = rs.counters.approachNoticesSent;
231
- if (tightest !== undefined && tightest.ratio >= thresholds[index]) {
232
- rs.counters.approachNoticesSent += 1;
233
- const percent = Math.min(100, Math.round(tightest.ratio * 100));
234
- const body = limitApproachFrames(index === 0 ? 0 : 1, tightest.axis, percent, prepared.reminderMark);
235
- void prepared.harness.steer(body, { engineMinted: true }).catch(() => undefined);
236
- queue.push({ type: "steering_injected", source: "limit_approach", preview: body.slice(0, 220), ...ident() });
237
- boundarySteered = true;
238
- }
239
- }
240
- if (prepared.reviewRequestRef.pending !== undefined) {
241
- const { reason } = prepared.reviewRequestRef.pending;
242
- prepared.reviewRequestRef.pending = undefined;
243
- const honorable = prepared.suspendForReview !== undefined &&
244
- !prepared.abortController.signal.aborted &&
245
- prepared.pausedRef.current === undefined;
246
- if (honorable && prepared.suspendForReview) {
247
- await prepared.suspendForReview(reason);
248
- return undefined;
249
- }
250
- emitTrace(rs.telemetry.tracer, () => ({ kind: "review.dropped", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
251
- }
252
- if (prepared.lspDiagnostics &&
253
- !prepared.lspDiagnostics.registry.isEmpty() &&
254
- !prepared.abortController.signal.aborted &&
255
- prepared.batchHaltRef.current === undefined) {
256
- const files = prepared.lspDiagnostics.registry.drain(prepared.lspDiagnostics.runIdent);
257
- if (files.length > 0) {
258
- queue.push({ type: "diagnostics", files, isNew: true, ...ident() });
259
- const block = mintSystemReminder(formatDiagnosticsBlock(files), prepared.reminderMark);
260
- void prepared.harness.steer(block, { provenance: "engine-note" }).catch(() => undefined);
261
- boundarySteered = true;
262
- }
263
- }
264
- let boundaryAttachmentBytes = 0;
265
- let attachmentsPayload;
266
- if ((rs.attach.dateState !== undefined ||
267
- rs.attach.instrState !== undefined ||
268
- rs.attach.sizeGuidelineState !== undefined ||
269
- rs.attach.attachState !== undefined) &&
270
- !boundarySteered &&
271
- prepared.batchHaltRef.current === undefined &&
272
- !noNextRequest() &&
273
- rs.counters.finalVerifyInjections === 0 &&
274
- !prepared.abortController.signal.aborted) {
275
- const due = [];
276
- if (rs.attach.attachState !== undefined) {
277
- const changedFilesOn = Boolean(rs.attach.attachmentsCfg?.changedFiles);
278
- let changed;
279
- if (changedFilesOn && prepared.detectExternalChanges !== undefined) {
280
- const maxFiles = typeof rs.attach.attachmentsCfg?.changedFiles === "object"
281
- ? (rs.attach.attachmentsCfg.changedFiles.maxFiles ?? CHANGED_FILES_MAX)
282
- : CHANGED_FILES_MAX;
283
- const SCAN_CEILING_MS = 2_000;
284
- const scan = await Promise.race([
285
- prepared.detectExternalChanges(maxFiles),
286
- new Promise((resolve) => {
287
- setTimeout(() => resolve({ changed: [], evicted: [] }), SCAN_CEILING_MS);
288
- }),
289
- ]);
290
- for (const p of scan.evicted)
291
- rs.attach.attachState.surfacedMtime.delete(p);
292
- changed = scan.changed;
293
- }
294
- const backgroundTasksOn = (rs.attach.attachmentsCfg?.backgroundTasks ?? eventDefaultOn("background_tasks")) === true;
295
- const bgTasks = backgroundTasksOn && rs.attach.attachState.postCompactPending ? prepared.listBackgroundTasks() : undefined;
296
- const toolsDeltaOn = rs.attach.attachmentsCfg?.toolsDelta === true;
297
- let announcedOnceDirty = false;
298
- const tdRef = toolsDeltaOn ? prepared.toolsDeltaRef : undefined;
299
- const pendingTools = tdRef !== undefined && tdRef.pending.length > 0 ? [...tdRef.pending] : undefined;
300
- const pendingRemovedTools = tdRef !== undefined && tdRef.pendingRemoved.length > 0 ? [...tdRef.pendingRemoved] : undefined;
301
- const pendingReaddedTools = tdRef !== undefined && tdRef.pendingReadded.length > 0 ? [...tdRef.pendingReadded] : undefined;
302
- const pendingFailedMcp = tdRef !== undefined && tdRef.pendingFailed.length > 0 ? tdRef.pendingFailed.map((f) => ({ ...f })) : undefined;
303
- const mcpToolsDelta = pendingRemovedTools !== undefined || pendingReaddedTools !== undefined || pendingFailedMcp !== undefined
304
- ? {
305
- ...(pendingRemovedTools !== undefined ? { removed: pendingRemovedTools } : {}),
306
- ...(pendingReaddedTools !== undefined ? { readded: pendingReaddedTools } : {}),
307
- ...(pendingFailedMcp !== undefined ? { failedServers: pendingFailedMcp } : {}),
308
- }
309
- : undefined;
310
- const mcpInstructionsOn = rs.attach.attachmentsCfg?.mcpInstructions === true;
311
- const mcpDelta = prepared.mcp.instructionsDelta;
312
- const mcpAdds = mcpInstructionsOn && mcpDelta.pendingAdds.length > 0 ? [...mcpDelta.pendingAdds] : undefined;
313
- const mcpRemovals = mcpInstructionsOn && mcpDelta.pendingRemovals.length > 0 ? [...mcpDelta.pendingRemovals] : undefined;
314
- const mcpDropped = mcpInstructionsOn && prepared.mcp.droppedTools.length > 0 ? selectMcpDroppedBatch(prepared.mcp.droppedTools) : undefined;
315
- const budgetUsdOn = rs.attach.attachmentsCfg?.budgetUsd === true && rs.budget.maxCostMicroUsd !== undefined && rs.turn.lastTurnHadToolCalls;
316
- const totalTokensDeclared = rs.attach.attachmentsCfg?.totalTokensReminder;
317
- const totalTokensMode = rs.attach.attachmentsCfg?.totalTokensReminderMode ?? TOTAL_TOKENS_REMINDER_DEFAULT_MODE;
318
- const totalTokensArmedByDefault = totalTokensDeclared === undefined &&
319
- eventDefaultOn("total_tokens_reminder") &&
320
- totalTokensMode !== "fixed" &&
321
- totalTokensMode !== "infinite";
322
- const totalTokensOn = (totalTokensDeclared === true || totalTokensArmedByDefault) && rs.turn.lastTurnHadToolCalls;
323
- const toolSearchReminderOn = rs.attach.attachmentsCfg?.toolSearchReminder === true && prepared.deferredToolNames !== undefined;
324
- const undiscoveredTools = toolSearchReminderOn
325
- ? [...prepared.deferredToolNames].filter((n) => !prepared.activeTools.has(n)).sort()
326
- : undefined;
327
- due.push(...collectDueAttachments(rs.attach.attachState, {
328
- turn: stats.turns,
329
- cadenceTurn: rs.counters.cadenceTurns,
330
- todoMounted: todoToolMounted,
331
- taskListMounted: taskToolsMounted,
332
- planActive: prepared.planModeRef.active,
333
- config: {
334
- todoReminder: rs.attach.attachmentsCfg?.todoReminder === true,
335
- ...(rs.attach.attachmentsCfg?.todoReminderMode !== undefined ? { todoReminderMode: rs.attach.attachmentsCfg.todoReminderMode } : {}),
336
- toolSearchReminder: toolSearchReminderOn,
337
- changedFiles: changedFilesOn,
338
- planModeReminder: rs.attach.attachmentsCfg?.planModeReminder === true,
339
- budgetUsd: budgetUsdOn,
340
- totalTokensReminder: totalTokensOn,
341
- ...(rs.attach.attachmentsCfg?.totalTokensReminderMode !== undefined
342
- ? { totalTokensReminderMode: rs.attach.attachmentsCfg.totalTokensReminderMode }
343
- : {}),
344
- backgroundTasks: backgroundTasksOn,
345
- toolsDelta: toolsDeltaOn,
346
- agentListing: rs.attach.agentListingOn,
347
- skillsListing: rs.attach.skillsListingOn,
348
- mcpInstructions: mcpInstructionsOn,
349
- },
350
- ...(undiscoveredTools !== undefined && undiscoveredTools.length > 0
351
- ? { undiscoveredTools, deferDirectCall: prepared.deferDirectCall }
352
- : {}),
353
- ...(changed !== undefined ? { changedFiles: changed } : {}),
354
- ...(budgetUsdOn && rs.budget.maxCostMicroUsd !== undefined
355
- ? { budgetUsd: { used: stats.costMicroUsd / 1e6, total: rs.budget.maxCostMicroUsd / 1e6 } }
356
- : {}),
357
- ...(totalTokensOn && rs.budget.maxTokensWindow !== undefined
358
- ? { totalTokens: { used: stats.tokens, total: rs.budget.maxTokensWindow } }
359
- : {}),
360
- ...(bgTasks !== undefined ? { backgroundTasks: bgTasks } : {}),
361
- ...(pendingTools !== undefined ? { newTools: pendingTools } : {}),
362
- ...(prepared.toolMaterializeStatic ? { newToolsStaticFace: true } : {}),
363
- ...(prepared.toolMaterializeStatic && pendingTools !== undefined
364
- ? { newToolsSwappedUnderStatic: pendingTools.filter((n) => prepared.staticFaceFor?.(n) !== true) }
365
- : {}),
366
- ...(prepared.toolMaterializeStatic && pendingReaddedTools !== undefined
367
- ? { readdedToolsSwappedUnderStatic: pendingReaddedTools.filter((n) => prepared.staticFaceFor?.(n) !== true) }
368
- : {}),
369
- ...(mcpToolsDelta !== undefined ? { mcpToolsDelta } : {}),
370
- ...(rs.attach.agentListingOn && prepared.agentListing !== undefined
371
- ? {
372
- agentListing: prepared.agentListing.entries,
373
- agentToolName: prepared.agentListing.toolName,
374
- ...(prepared.agentListing.models !== undefined ? { agentModels: prepared.agentListing.models } : {}),
375
- }
376
- : {}),
377
- ...(rs.attach.skillsListingOn && prepared.skillsListing !== undefined ? { skillsListing: prepared.skillsListing.entries } : {}),
378
- ...(mcpAdds !== undefined || mcpRemovals !== undefined
379
- ? { mcpInstructions: { added: mcpAdds ?? [], removed: mcpRemovals ?? [] } }
380
- : {}),
381
- ...(mcpDropped !== undefined ? { mcpDroppedTools: mcpDropped } : {}),
382
- }));
383
- if (pendingTools !== undefined || mcpToolsDelta !== undefined) {
384
- const exact = renderToolsDelta({
385
- ...(pendingTools !== undefined ? { added: pendingTools } : {}),
386
- ...(prepared.toolMaterializeStatic ? { staticFace: true } : {}),
387
- ...(prepared.toolMaterializeStatic && pendingTools !== undefined
388
- ? { swappedUnderStatic: pendingTools.filter((n) => prepared.staticFaceFor?.(n) !== true) }
389
- : {}),
390
- ...(prepared.toolMaterializeStatic && pendingReaddedTools !== undefined
391
- ? { readdedSwappedUnderStatic: pendingReaddedTools.filter((n) => prepared.staticFaceFor?.(n) !== true) }
392
- : {}),
393
- ...(mcpToolsDelta ?? {}),
394
- });
395
- if (exact !== undefined && due.some((a) => a.source === "tools_delta" && a.body === exact)) {
396
- const ref = prepared.toolsDeltaRef;
397
- if (pendingTools !== undefined)
398
- ref.pending.splice(0, pendingTools.length);
399
- if (pendingRemovedTools !== undefined)
400
- ref.pendingRemoved.splice(0, pendingRemovedTools.length);
401
- if (pendingReaddedTools !== undefined)
402
- ref.pendingReadded.splice(0, pendingReaddedTools.length);
403
- if (pendingFailedMcp !== undefined) {
404
- ref.pendingFailed.splice(0, pendingFailedMcp.length);
405
- if (!noNextRequest()) {
406
- prepared.announcedListingsRef.mcpFailed = [...(prepared.announcedListingsRef.mcpFailed ?? []), ...pendingFailedMcp];
407
- announcedOnceDirty = true;
408
- }
409
- }
410
- }
411
- }
412
- if ((mcpAdds !== undefined || mcpRemovals !== undefined) &&
413
- due.some((a) => a.source === "mcp_instructions" && a.body === renderMcpInstructionsDelta(mcpAdds ?? [], mcpRemovals ?? []))) {
414
- if (mcpAdds !== undefined)
415
- mcpDelta.pendingAdds.splice(0, mcpAdds.length);
416
- if (mcpRemovals !== undefined)
417
- mcpDelta.pendingRemovals.splice(0, mcpRemovals.length);
418
- }
419
- if (mcpDropped !== undefined &&
420
- due.some((a) => a.source === "mcp_dropped_tools" && a.body === renderMcpDroppedTools(mcpDropped))) {
421
- prepared.mcp.droppedTools.splice(0, mcpDropped.length);
422
- if (!noNextRequest()) {
423
- prepared.announcedListingsRef.advisories = withAnnounceDigests(prepared.announcedListingsRef.advisories, mcpDropped.map(droppedToolDigest));
424
- announcedOnceDirty = true;
425
- }
426
- }
427
- if (rs.attach.agentListingOn && prepared.agentListing !== undefined && due.some((a) => a.source === "agent_listing")) {
428
- const listing = prepared.agentListing;
429
- const intactBody = renderAgentListingDelta(rs.attach.attachState, listing.entries, listing.toolName, listing.models);
430
- if (intactBody !== undefined && due.some((a) => a.source === "agent_listing" && a.body === intactBody)) {
431
- commitAgentListing(rs.attach.attachState, listing.entries, listing.models);
432
- prepared.announcedListingsRef.agents = listing.entries.map((e) => e.name);
433
- if (listing.models !== undefined)
434
- prepared.announcedListingsRef.models = [...listing.models];
435
- if (prepared.announcedSnapshotRecovered)
436
- await prepared.session.appendAnnouncedListing(prepared.announcedListingsRef).catch(() => undefined);
437
- }
438
- }
439
- if (rs.attach.skillsListingOn && prepared.skillsListing !== undefined && due.some((a) => a.source === "skills_listing")) {
440
- const listing = prepared.skillsListing;
441
- const intactBody = renderSkillsListingDelta(rs.attach.attachState, listing.entries);
442
- if (intactBody !== undefined && due.some((a) => a.source === "skills_listing" && a.body === intactBody)) {
443
- commitSkillsListing(rs.attach.attachState, listing.entries);
444
- prepared.announcedListingsRef.skills = listing.entries.map((e) => e.name);
445
- if (prepared.announcedSnapshotRecovered)
446
- await prepared.session.appendAnnouncedListing(prepared.announcedListingsRef).catch(() => undefined);
447
- }
448
- }
449
- if (announcedOnceDirty && prepared.announcedSnapshotRecovered)
450
- await prepared.session.appendAnnouncedListing(prepared.announcedListingsRef).catch(() => undefined);
451
- }
452
- if (rs.attach.dateState !== undefined && rs.turn.lastTurnHadToolCalls) {
453
- const today = prepared.dateChange.today();
454
- const cand = collectDateChange(rs.attach.dateState, today);
455
- if (cand !== undefined) {
456
- due.unshift(cand);
457
- rs.attach.dateState.announcedDate = today;
458
- }
459
- }
460
- if (rs.attach.sizeGuidelineState !== undefined && rs.turn.lastTurnHadToolCalls) {
461
- const guideline = rs.attach.sizeGuidelineState.current();
462
- if (guideline !== rs.attach.sizeGuidelineState.announcedGuideline) {
463
- due.splice(due.length > 0 && due[0].source === "date_change" ? 1 : 0, 0, {
464
- source: "workflow_size_guideline_change",
465
- body: workflowSizeGuidelineChangeNotice(guideline),
466
- });
467
- rs.attach.sizeGuidelineState.announcedGuideline = guideline;
468
- }
469
- }
470
- if (rs.attach.instrState !== undefined && rs.attach.instrProbe !== undefined && rs.turn.lastTurnHadToolCalls) {
471
- let probed = null;
472
- try {
473
- const PROBE_CEILING_MS = 2_000;
474
- probed = await Promise.race([
475
- rs.attach.instrProbe(prepared.instructionSources),
476
- new Promise((resolve) => {
477
- setTimeout(() => resolve(null), PROBE_CEILING_MS);
478
- }),
479
- ]);
480
- }
481
- catch {
482
- probed = null;
483
- }
484
- const cand = collectInstructionsChange(rs.attach.instrState, probed);
485
- if (cand !== undefined) {
486
- due.splice(due.length > 0 && due[0].source === "date_change" ? 1 : 0, 0, cand.attachment);
487
- commitInstructionsChange(rs.attach.instrState, cand.announced);
488
- }
489
- }
490
- if (due.length > 0) {
491
- attachmentsPayload = due.map((a) => mintSystemReminder(sanitizeUntrustedText(a.body, attachmentEnvelopeTags(a.source)), prepared.reminderMark)).join("\n");
492
- for (const a of due)
493
- boundaryAttachmentBytes += Buffer.byteLength(a.body, "utf8");
494
- rs.attach.attachmentsInjected += due.length;
495
- for (const a of due) {
496
- queue.push({ type: "steering_injected", source: a.source, preview: redactThenCut(a.body, 220), ...ident() });
497
- }
498
- }
499
- }
500
- let batchContextBlock;
501
- const finalVerifyInjectedThisTurn = rs.counters.finalVerifyInjections !== finalVerifySeenAtLastBoundary;
502
- finalVerifySeenAtLastBoundary = rs.counters.finalVerifyInjections;
503
- if (postToolBatchHook !== undefined && rs.turn.toolBatch.length > 0) {
504
- const batch = rs.turn.toolBatch;
505
- rs.turn.toolBatch = [];
506
- batchArgs?.clear();
507
- const injectedThisTurn = finalVerifyInjectedThisTurn ? "final_verification" : undefined;
508
- if (!boundarySteered && !prepared.abortController.signal.aborted) {
509
- try {
510
- const batchSeat = await runHookSeat("postToolBatch", { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal }, (sig) => postToolBatchHook(batch, injectedThisTurn !== undefined ? { injectedThisTurn } : undefined, { identity: prepared.hookIdentity, signal: sig }));
511
- if (batchSeat.expired) {
512
- 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 });
513
- }
514
- const r = batchSeat.expired ? undefined : batchSeat.value;
515
- if (r?.additionalContext && injectedThisTurn === undefined) {
516
- const body = sanitizeUntrustedText(r.additionalContext, SHELLED_BODY_ENVELOPE_TAGS);
517
- const budget = ATTACHMENT_BYTE_CAP - boundaryAttachmentBytes;
518
- if (budget >= BATCH_CONTEXT_MIN_KEEP_BYTES) {
519
- const clipped = clipToBytes(body, budget) === body
520
- ? body
521
- : `${clipToBytes(body, budget - Buffer.byteLength(BATCH_TRUNCATION_MARKER, "utf8"))}${BATCH_TRUNCATION_MARKER}`;
522
- batchContextBlock = mintSystemReminder(clipped, prepared.reminderMark);
523
- }
524
- }
525
- }
526
- catch (err) {
527
- try {
528
- runnerHooks.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "hook", sessionId: prepared.sessionId });
529
- }
530
- catch {
531
- }
532
- }
533
- }
534
- }
535
- if ((attachmentsPayload !== undefined || batchContextBlock !== undefined) && prepared.batchHaltRef.current === undefined) {
536
- const payload = attachmentsPayload !== undefined && batchContextBlock !== undefined ? `${attachmentsPayload}\n${batchContextBlock}` : (attachmentsPayload ?? batchContextBlock);
537
- void prepared.harness.steer(payload, { engineMinted: true }).catch(() => { });
538
- }
539
- const forceManual = manualCompactRef.requested;
540
- if ((!withinTaskCompaction && !forceManual) ||
541
- prepared.abortController.signal.aborted ||
542
- prepared.pausedRef.current !== undefined) {
543
- if (forceManual) {
544
- emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.mooted", version: 1, taskId: rs.telemetry.taskId, reason: "task_ending", ts: Date.now() }));
545
- queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: "manual", reason: "task_ending", ...ident() });
546
- drainManualCompact("mooted");
547
- }
548
- return undefined;
549
- }
550
- if (compactionBreaker.failures >= MAX_CONSECUTIVE_COMPACTION_FAILURES) {
551
- if (forceManual) {
552
- emitTrace(rs.telemetry.tracer, () => ({
553
- kind: "compaction.failed",
554
- version: 1,
555
- taskId: rs.telemetry.taskId,
556
- trigger: "manual",
557
- reason: "compaction circuit breaker open (3 consecutive failures this task) — manual request drained without an attempt",
558
- ts: Date.now(),
559
- }));
560
- 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() });
561
- drainManualCompact("failed");
562
- }
563
- return undefined;
564
- }
565
- const claimedManual = forceManual
566
- ? (() => {
567
- const waiters = manualCompactRef.waiters.splice(0);
568
- let instructions;
569
- for (let i = waiters.length - 1; i >= 0; i--) {
570
- if (waiters[i].instructions !== undefined) {
571
- instructions = waiters[i].instructions;
572
- break;
573
- }
574
- }
575
- return { instructions, waiters };
576
- })()
577
- : undefined;
578
- if (forceManual) {
579
- manualCompactRef.requested = false;
580
- }
581
- const resolveClaimedWaiters = (outcome) => {
582
- if (claimedManual !== undefined)
583
- for (const w of claimedManual.waiters.splice(0))
584
- w.resolve(outcome);
585
- };
586
- const claimedCancelSignals = claimedManual?.waiters.flatMap((w) => (w.signal !== undefined ? [w.signal] : [])) ?? [];
587
- const manualCancelSignal = claimedCancelSignals.length === 0
588
- ? undefined
589
- : claimedCancelSignals.length === 1
590
- ? claimedCancelSignals[0]
591
- : AbortSignal.any(claimedCancelSignals);
592
- const compactionPassSignal = claimedCancelSignals.length > 0
593
- ? AbortSignal.any([prepared.abortController.signal, ...claimedCancelSignals])
594
- : prepared.abortController.signal;
595
- const trimPressureArmed = prepared.trimPressureRef.droppedMessages;
596
- if (trimPressureArmed)
597
- prepared.trimPressureRef.droppedMessages = false;
598
- const trimPressure = trimPressureArmed && !rs.counters.trimForceBackoff;
599
- const passTrigger = trimPressure ? "forced" : forceManual ? "manual" : "auto";
600
- let manualOutcome = "failed";
601
- const manualInstructions = claimedManual?.instructions;
602
- try {
603
- const comp = await maybeCompact({
604
- session: prepared.session,
605
- epochDeclaredSections: prepared.epochDeclaredSections,
606
- ...centerAdoptionOption(prepared),
607
- model: event.model,
608
- compactionModel: prepared.compModel,
609
- ...forkContextOption(prepared, false),
610
- brain: compactionBrain,
611
- getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
612
- thinking: prepared.thinking,
613
- settings: { ...DEFAULT_COMPACTION_SETTINGS, ...spec.compaction },
614
- customInstructions: manualInstructions ?? spec.compaction?.instructions ?? DEFAULT_COMPACTION_INSTRUCTIONS,
615
- signal: compactionPassSignal,
616
- ...(manualCancelSignal !== undefined ? { manualCancelSignal } : {}),
617
- minTokens: forceManual ? 0 : rs.counters.compactionFloor,
618
- force: forceManual,
619
- onInputTruncated: emitInputTruncated(rs.telemetry.tracer, rs.telemetry.taskId),
620
- forceUnderThreshold: trimPressure,
621
- overheadTokens: prepared.promptOverheadTokens,
622
- ...(prepared.activeTools.size > 0 ? { activeTools: [...prepared.activeTools] } : {}),
623
- workingFileAttachments: buildWorkingFileAttachments(spec, prepared),
624
- ...contextInstructionFilesOption(prepared),
625
- ...(prepared.onCompactionApplied ? { onApplied: prepared.onCompactionApplied } : {}),
626
- ...runnerHooks.seamCCompactionOptions(prepared),
627
- ...gitRestateOption(prepared),
628
- ...windowSafetyOptions(event.model),
629
- ...runnerHooks.compactionHookOptions(spec, prepared.sessionId, passTrigger),
630
- });
631
- if (comp.blocked) {
632
- emitTrace(rs.telemetry.tracer, () => ({
633
- kind: "compaction.blocked",
634
- version: 1,
635
- taskId: rs.telemetry.taskId,
636
- trigger: forceManual ? "manual" : "auto",
637
- ts: Date.now(),
638
- }));
639
- }
640
- else {
641
- compactionBreaker.failures = 0;
642
- }
643
- rs.counters.trimForceBackoff = nextTrimForceBackoff(rs.counters.trimForceBackoff, comp, trimPressure);
644
- if (comp.contextUsage) {
645
- queue.push({ type: "context_usage", ...comp.contextUsage, ...ident() });
646
- }
647
- if (comp.unevaluableWindow) {
648
- discloseUnevaluableWindow(comp.unevaluableWindow, prepared, rs.telemetry.tracer, rs.telemetry.taskId, runnerHooks.onError);
649
- }
650
- if (comp.suppressedByFloor) {
651
- const s = comp.suppressedByFloor;
652
- emitTrace(rs.telemetry.tracer, () => ({
653
- kind: "compaction.suppressed",
654
- version: 1,
655
- taskId: rs.telemetry.taskId,
656
- estTokens: s.estTokens,
657
- floor: s.floor,
658
- ts: Date.now(),
659
- }));
660
- }
661
- manualOutcome = comp.compacted ? "compacted" : comp.blocked ? "blocked" : comp.disabled ? "disabled" : "noop";
662
- if (forceManual && !comp.compacted && !comp.blocked) {
663
- if (comp.disabled) {
664
- emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.disabled", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
665
- }
666
- else {
667
- emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.noop", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
668
- }
669
- }
670
- if (!comp.compacted) {
671
- const outcome = comp.blocked
672
- ? "blocked"
673
- : comp.disabled
674
- ? (forceManual || trimPressure ? "disabled" : undefined)
675
- : comp.suppressedByFloor
676
- ? "suppressed"
677
- : comp.noop || forceManual
678
- ? "noop"
679
- : undefined;
680
- if (outcome !== undefined) {
681
- queue.push({ type: "compaction_outcome", outcome, trigger: passTrigger, ...ident() });
682
- }
683
- }
684
- runnerHooks.recordCompactionReuse(prepared, comp);
685
- if (comp.compacted) {
686
- queue.push({
687
- type: "compacted",
688
- trigger: passTrigger,
689
- tokensBefore: comp.tokensBefore ?? 0,
690
- ...(comp.postTriggerTokens !== undefined ? { tokensAfter: comp.postTriggerTokens } : {}),
691
- ...(comp.triggerTokens !== undefined ? { triggerTokensBefore: comp.triggerTokens } : {}),
692
- ...(comp.durationMs !== undefined ? { durationMs: comp.durationMs } : {}),
693
- ...(comp.phaseDurations !== undefined ? { phaseDurations: comp.phaseDurations } : {}),
694
- ...(comp.firstKeptEntryId !== undefined ? { preserved_segment: { firstKeptEntryId: comp.firstKeptEntryId } } : {}),
695
- ...(comp.attachedFiles !== undefined ? { attachedFiles: comp.attachedFiles } : {}),
696
- ...(comp.modelFallback ? { modelFallback: true } : {}),
697
- ...(comp.fallbackReason !== undefined ? { fallbackReason: comp.fallbackReason } : {}),
698
- ...(comp.clampedRatio !== undefined ? { clampedRatio: comp.clampedRatio } : {}),
699
- ...(comp.clampReason !== undefined ? { clampReason: comp.clampReason } : {}),
700
- ...ident(),
701
- });
702
- if (!forceManual && recordCompactionAndCheckRapidRefill(rapidRefill, stats.turns)) {
703
- compactionBreaker.failures = MAX_CONSECUTIVE_COMPACTION_FAILURES;
704
- queue.push({ type: "compaction_outcome", outcome: "disabled", trigger: passTrigger, reason: "rapid_refill: compaction disabled for the rest of this task", ...ident() });
705
- 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 });
706
- }
707
- if (comp.phaseDurations !== undefined && comp.durationMs !== undefined) {
708
- const pd = comp.phaseDurations;
709
- const pdDur = comp.durationMs;
710
- emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.phase_timings", version: 1, taskId: rs.telemetry.taskId, ...pd, durationMs: pdDur, ts: Date.now() }));
711
- }
712
- prepared.cacheBreakDetector?.notifyCompaction();
713
- if (rs.attach.attachState !== undefined) {
714
- rs.attach.attachState.postCompactPending = true;
715
- rebaseCadenceWindows(rs.attach.attachState, rs.counters.cadenceTurns);
716
- }
717
- if ((comp.freedTokens ?? 0) >= COMPACTION_FREED_EPSILON) {
718
- const postSize = comp.postTriggerTokens ?? Math.max(0, (comp.triggerTokens ?? comp.tokensBefore ?? 0) - (comp.freedTokens ?? 0));
719
- rs.counters.compactionFloor = Math.ceil(postSize * COMPACTION_REGROWTH_FACTOR);
720
- }
721
- }
722
- }
723
- catch (err) {
724
- const manualCancelAbort = !prepared.abortController.signal.aborted && isCompactionManualCancel(err);
725
- if (manualCancelAbort) {
726
- emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.mooted", version: 1, taskId: rs.telemetry.taskId, reason: "cancelled", ts: Date.now() }));
727
- queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: passTrigger, reason: "cancelled", ...ident() });
728
- manualOutcome = "mooted";
729
- }
730
- else if (!prepared.abortController.signal.aborted) {
731
- compactionBreaker.failures += 1;
732
- runnerHooks.onError?.(err, { phase: "compaction", sessionId: prepared.sessionId });
733
- const msg = String(err instanceof Error ? err.message : err);
734
- const failReason = msg.length > 512 ? `${msg.slice(0, 512)}…` : msg;
735
- emitTrace(rs.telemetry.tracer, () => ({
736
- kind: "compaction.failed",
737
- version: 1,
738
- taskId: rs.telemetry.taskId,
739
- trigger: passTrigger,
740
- reason: failReason,
741
- ts: Date.now(),
742
- }));
743
- queue.push({ type: "compaction_outcome", outcome: "failed", trigger: passTrigger, reason: failReason, ...ident() });
744
- manualOutcome = "failed";
745
- }
746
- else {
747
- manualOutcome = "mooted";
748
- }
749
- }
750
- if (forceManual)
751
- resolveClaimedWaiters(manualOutcome);
752
- return undefined;
753
- };
754
- return onTurnBoundary;
755
- }
756
- function makeHarnessHandlers(prepared, stats, rs, deps) {
757
- const { spec, queue, internals, ident, parentToolCallId, subagentName, pushContent, emitCommitted, startedToolCallIds, toolStartAt, writeFamilyOf, toolLabels, postToolBatchHook, batchArgs } = deps;
758
- const internalsNotifier = createSafeNotifier({
759
- onError: (f) => console.warn(`[sema-core] ${f.site}: run-internals activity sink threw (contained; further failures counted, not re-disclosed): ${f.error.message}`),
760
- });
761
- let lastWorkspaceCwd = prepared.cwdRef?.current;
762
- const announceWorkspaceMove = () => {
763
- const cwdNow = prepared.cwdRef?.current;
764
- if (cwdNow !== undefined && cwdNow !== lastWorkspaceCwd) {
765
- lastWorkspaceCwd = cwdNow;
766
- queue.push({ type: "workspace_changed", cwd: cwdNow, ...ident() });
767
- }
768
- };
769
- const onMessageUpdate = (event) => {
770
- const ev = event.assistantMessageEvent;
771
- if (rs.turn.callStartAt === undefined)
772
- rs.turn.callStartAt = Date.now();
773
- if (ev.type === "text_delta" && ev.delta) {
774
- if (rs.turn.firstTokenAt === undefined)
775
- rs.turn.firstTokenAt = Date.now();
776
- pushContent({ type: "text_delta", delta: ev.delta, ...ident() });
777
- }
778
- else if (ev.type === "text_end" && typeof ev.content === "string" && ev.content.length !== 0) {
779
- pushContent({ type: "text_end", content: ev.content, ...ident() });
780
- }
781
- else if (ev.type === "thinking_delta" && ev.delta) {
782
- pushContent({ type: "reasoning_delta", delta: ev.delta, ...ident() });
783
- }
784
- if (rs.budget.streamCancel && !rs.limits.budgetHit && (ev.type === "text_delta" || ev.type === "thinking_delta") && ev.delta) {
785
- rs.budget.callOutputChars += ev.delta.length;
786
- if (rs.budget.callOutputChars - rs.budget.lastStreamBudgetCheck >= 256) {
787
- rs.budget.lastStreamBudgetCheck = rs.budget.callOutputChars;
788
- const projectedAxis = rs.budget.projectedOverBudget();
789
- if (projectedAxis !== undefined) {
790
- rs.limits.budgetHit = "exceeded";
791
- rs.limits.budgetAxis = projectedAxis;
792
- void prepared.harness.abort();
793
- prepared.abortController.abort();
794
- }
795
- }
796
- }
797
- };
798
- const onMessageEnd = (event) => {
799
- const m = event.message;
800
- if (event.entryId !== undefined && (m.role === "user" || m.role === "assistant" || m.role === "toolResult")) {
801
- emitCommitted(event.entryId, m.role, m.role === "toolResult" ? m.toolCallId : undefined);
802
- }
803
- if (event.entryId !== undefined && m.role === "user")
804
- prepared.fileHistoryBoundary?.begin(event.entryId);
805
- if (event.entryId !== undefined && m.role === "user" && prepared.gitStatusRef.pendingReceipt !== undefined) {
806
- const pr = prepared.gitStatusRef.pendingReceipt;
807
- const c = m.content;
808
- const text = typeof c === "string"
809
- ? c
810
- : Array.isArray(c) && c.length >= 1 && c[0].type === "text"
811
- ? c[0].text
812
- : undefined;
813
- if (text === pr.text) {
814
- delete prepared.gitStatusRef.pendingReceipt;
815
- pr.commit(event.entryId);
816
- }
817
- }
818
- if (rs.degrade.degraded === undefined && m.role === "assistant") {
819
- const deg = readDegradation(m);
820
- if (deg)
821
- rs.degrade.recordDegraded({ from: deg.from, to: deg.to, reason: deg.reason, atTurn: stats.turns + 1, ...(deg.chain && { chain: deg.chain }) });
822
- }
823
- if (m.role === "assistant" && "usage" in m && m.usage) {
824
- if (m.usageMissing)
825
- rs.turn.turnUsageMissing = true;
826
- const u = m.usage;
827
- stats.tokens += u.totalTokens || 0;
828
- const { totalInputTokens: turnTotal, uncachedInputTokens: turnUncached, costMicroUsd: turnCostMicroUsd, } = usageCostMicroUsd(rs.telemetry.cacheFamily, u, rs.telemetry.pricing);
829
- if (!rs.telemetry.pricingConfigured)
830
- rs.telemetry.unpricedSpend = true;
831
- stats.promptTokens += turnUncached;
832
- stats.totalInputTokens += turnTotal;
833
- stats.cachedTokens += u.cacheRead || 0;
834
- stats.cacheWriteTokens += u.cacheWrite || 0;
835
- stats.outputTokens += u.output || 0;
836
- stats.costMicroUsd += turnCostMicroUsd;
837
- rs.turn.turnUsage ??= { inputTokens: 0, totalInputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, costMicroUsd: 0 };
838
- rs.turn.turnUsage.inputTokens += turnUncached;
839
- rs.turn.turnUsage.totalInputTokens += turnTotal;
840
- rs.turn.turnUsage.outputTokens += u.output || 0;
841
- rs.turn.turnUsage.cacheReadTokens += u.cacheRead || 0;
842
- rs.turn.turnUsage.cacheWriteTokens += u.cacheWrite || 0;
843
- rs.turn.turnUsage.costMicroUsd += turnCostMicroUsd;
844
- const callEndAt = Date.now();
845
- const callIssuedAt = prepared.callIssuedAtRef.current;
846
- prepared.callIssuedAtRef.current = undefined;
847
- emitTrace(rs.telemetry.tracer, () => ({
848
- kind: "brain.call",
849
- version: 1,
850
- taskId: rs.telemetry.taskId,
851
- model: m.model,
852
- provider: m.provider,
853
- turn: stats.turns + 1,
854
- promptTokens: turnUncached,
855
- totalInputTokens: turnTotal,
856
- completionTokens: u.output || 0,
857
- cacheRead: u.cacheRead || 0,
858
- cacheWrite: u.cacheWrite || 0,
859
- latencyMs: rs.turn.callStartAt !== undefined ? callEndAt - rs.turn.callStartAt : 0,
860
- firstTokenMs: rs.turn.callStartAt !== undefined && rs.turn.firstTokenAt !== undefined ? rs.turn.firstTokenAt - rs.turn.callStartAt : undefined,
861
- ...(callIssuedAt !== undefined ? { callStartedAt: callIssuedAt } : {}),
862
- ...(rs.telemetry.pricingConfigured ? { costMicroUsd: turnCostMicroUsd } : {}),
863
- ...(m.role === "assistant" && typeof m.stopReason === "string"
864
- ? { stopReason: m.stopReason }
865
- : {}),
866
- ts: callEndAt,
867
- }));
868
- const cacheRowUnknown = m.usageMissing === true || m.stopReason === "aborted";
869
- if (prepared.cacheBreakDetector && prepared.cacheFingerprint && !cacheRowUnknown) {
870
- const finding = prepared.cacheBreakDetector.observe({
871
- turn: stats.turns + 1,
872
- systemPrompt: prepared.cacheFingerprint.systemPrompt,
873
- tools: prepared.cacheFingerprint.tools,
874
- modelKey: `${m.provider}:${m.model}`,
875
- cacheRead: u.cacheRead || 0,
876
- });
877
- if (finding) {
878
- rs.telemetry.cacheBreakReported = true;
879
- 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 });
880
- }
881
- }
882
- }
883
- if (m.role === "assistant" && typeof m.stopReason === "string") {
884
- rs.turn.turnStopReason = m.stopReason;
885
- }
886
- if (m.role === "assistant") {
887
- const rep = m.repetition;
888
- if (rep !== undefined) {
889
- const observed = [
890
- ...(rep.cut !== undefined ? [{ action: "cut", e: rep.cut }] : []),
891
- ...(rep.spared ?? []).map((e) => ({ action: "spared", e })),
892
- ];
893
- for (const { action, e } of observed) {
894
- if (action === "cut")
895
- rs.counters.repetitionCuts += 1;
896
- else
897
- rs.counters.repetitionSpared += 1;
898
- if (rs.counters.repetitionEvents.length < rs.counters.REPETITION_EVENTS_CAP) {
899
- rs.counters.repetitionEvents.push({ turn: stats.turns + 1, action, rule: e.rule, period: e.period, reps: e.reps, segment: e.segment });
900
- }
901
- emitTrace(rs.telemetry.tracer, () => ({
902
- kind: "repetition.detected",
903
- version: 1,
904
- taskId: rs.telemetry.taskId,
905
- turn: stats.turns + 1,
906
- action,
907
- rule: e.rule,
908
- period: e.period,
909
- reps: e.reps,
910
- segment: e.segment,
911
- ts: Date.now(),
912
- }));
913
- }
914
- }
915
- }
916
- if (m.role === "assistant") {
917
- const content = m.content;
918
- if (rs.attach.attachState !== undefined)
919
- rs.counters.cadenceTurns = advanceCadenceClock(rs.attach.attachState, rs.counters.cadenceTurns, content, writeFamilyOf);
920
- rs.turn.lastTurnHadToolCalls = Array.isArray(content) && content.some((c) => c?.type === "toolCall");
921
- }
922
- rs.turn.callStartAt = undefined;
923
- rs.turn.firstTokenAt = undefined;
924
- rs.budget.callOutputChars = 0;
925
- rs.budget.lastStreamBudgetCheck = 0;
926
- };
927
- const onToolStart = (event) => {
928
- const startAt = Date.now();
929
- toolStartAt.set(event.toolCallId, startAt);
930
- startedToolCallIds.add(event.toolCallId);
931
- batchArgs?.set(event.toolCallId, event.args);
932
- stats.toolCalls += 1;
933
- if (spec.finalVerification === true && !(rs.counters.finalVerifyInjections === 0 ? rs.counters.groundingSignalPreR9 : rs.counters.groundingSignalPostR9) && hasVerifiableStructureSignal(event.args)) {
934
- if (rs.counters.finalVerifyInjections === 0)
935
- rs.counters.groundingSignalPreR9 = true;
936
- else
937
- rs.counters.groundingSignalPostR9 = true;
938
- }
939
- const activityArg = primaryActivityArg(event.args);
940
- 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");
941
- pushContent({
942
- type: "tool_start",
943
- toolCallId: event.toolCallId,
944
- toolName: event.toolName,
945
- ...(toolLabels.get(event.toolName) !== undefined ? { label: toolLabels.get(event.toolName) } : {}),
946
- args: event.args,
947
- ...ident(),
948
- });
949
- };
950
- const onToolEnd = (event) => {
951
- const toolStarted = toolStartAt.get(event.toolCallId);
952
- toolStartAt.delete(event.toolCallId);
953
- const toolNow = Date.now();
954
- if (!rs.counters.wroteThisRun && (prepared.toolEffects.get(event.toolName) ?? "write") !== "read") {
955
- rs.counters.wroteThisRun = true;
956
- }
957
- if (rs.attach.attachState !== undefined && !event.isError) {
958
- const det = event.result?.details;
959
- if (det !== undefined)
960
- reduceToolEnd(rs.attach.attachState, det);
961
- }
962
- if (postToolBatchHook !== undefined) {
963
- if (prepared.blockedToolCalls.delete(event.toolCallId) || event.notExecuted === true) {
964
- batchArgs?.delete(event.toolCallId);
965
- }
966
- else {
967
- const digest = batchResponseDigest(event.result);
968
- rs.turn.toolBatch.push({
969
- toolName: event.toolName,
970
- input: batchArgs?.get(event.toolCallId),
971
- toolCallId: event.toolCallId,
972
- ...(digest !== undefined ? { response: digest } : {}),
973
- isError: event.isError,
974
- });
975
- batchArgs?.delete(event.toolCallId);
976
- }
977
- }
978
- emitTrace(rs.telemetry.tracer, () => ({
979
- kind: "tool.call",
980
- version: 1,
981
- taskId: rs.telemetry.taskId,
982
- name: event.toolName,
983
- toolCallId: event.toolCallId,
984
- turn: stats.turns + 1,
985
- durationMs: toolStarted !== undefined ? toolNow - toolStarted : 0,
986
- ok: !event.isError,
987
- ts: toolNow,
988
- }));
989
- 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");
990
- if (prepared.lspDiagnostics && !event.isError) {
991
- const name = event.toolName;
992
- if (name === "Edit" || name === "Write" || name === "NotebookEdit") {
993
- const d = event.result?.details;
994
- const p = typeof d?.filePath === "string" ? d.filePath : typeof d?.notebookPath === "string" ? d.notebookPath : undefined;
995
- if (p)
996
- prepared.lspDiagnostics.nudge(p);
997
- }
998
- }
999
- const gate = prepared.gateOutcomes.get(event.toolCallId);
1000
- if (gate !== undefined)
1001
- prepared.gateOutcomes.delete(event.toolCallId);
1002
- pushContent({
1003
- type: "tool_end",
1004
- toolCallId: event.toolCallId,
1005
- toolName: event.toolName,
1006
- ...(toolLabels.get(event.toolName) !== undefined ? { label: toolLabels.get(event.toolName) } : {}),
1007
- isError: event.isError,
1008
- ...toolEndBodyFrom(event.result, event.isError, gate, prepared.abortController.signal.aborted ? gatedCallIdOf(prepared) : undefined),
1009
- ...ident(),
1010
- });
1011
- announceWorkspaceMove();
1012
- if (spec.outputSchema && event.toolName === OUTPUT_TOOL_NAME) {
1013
- if (event.isError) {
1014
- rs.degrade.outputErrorStreak += 1;
1015
- if (rs.degrade.outputErrorStreak > rs.limits.outputRetryCap) {
1016
- rs.degrade.outputInvalid = true;
1017
- void prepared.harness.abort();
1018
- prepared.abortController.abort();
1019
- }
1020
- }
1021
- else {
1022
- rs.degrade.outputErrorStreak = 0;
1023
- }
1024
- }
1025
- };
1026
- const onTurnEnd = () => {
1027
- stats.turns += 1;
1028
- const stopExtra = rs.turn.turnStopReason !== undefined ? { stopReason: rs.turn.turnStopReason } : {};
1029
- queue.push(rs.turn.turnUsage
1030
- ? { type: "turn_end", usage: rs.turn.turnUsage, ...(rs.turn.turnUsageMissing ? { usageMissing: true } : {}), ...stopExtra, ...ident() }
1031
- : { type: "turn_end", usageMissing: true, ...stopExtra, ...ident() });
1032
- rs.turn.turnUsageMissing = false;
1033
- rs.turn.turnStopReason = undefined;
1034
- if (parentToolCallId !== undefined) {
1035
- const progress = {
1036
- type: "task_progress",
1037
- taskId: rs.telemetry.taskId,
1038
- ...(internals?.delegationTaskType !== undefined ? { taskType: internals.delegationTaskType } : {}),
1039
- ...(internals?.cycleSeq !== undefined ? { seq: internals.cycleSeq } : {}),
1040
- ...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}),
1041
- ...(subagentName ? { name: subagentName } : {}),
1042
- model: prepared.model.id,
1043
- usage: { totalTokens: stats.tokens, toolUses: stats.toolCalls, durationMs: Date.now() - rs.telemetry.taskStart },
1044
- status: "running",
1045
- ...ident(),
1046
- };
1047
- queue.push(progress);
1048
- try {
1049
- internals?.onForwardEvent?.(progress);
1050
- }
1051
- catch {
1052
- }
1053
- }
1054
- emitTrace(rs.telemetry.tracer, () => ({ kind: "turn.end", version: 1, taskId: rs.telemetry.taskId, turn: stats.turns, ts: Date.now() }));
1055
- rs.turn.turnUsage = undefined;
1056
- rs.turn.callStartAt = undefined;
1057
- rs.turn.firstTokenAt = undefined;
1058
- rs.budget.callOutputChars = 0;
1059
- rs.budget.lastStreamBudgetCheck = 0;
1060
- toolStartAt.clear();
1061
- if (rs.limits.effectiveMaxTurns !== undefined && rs.limits.effectiveMaxTurns > 0 && stats.turns >= rs.limits.effectiveMaxTurns) {
1062
- rs.limits.turnsExceeded = true;
1063
- if (prepared.suspendForResource === undefined) {
1064
- void prepared.harness.abort();
1065
- prepared.abortController.abort();
1066
- }
1067
- }
1068
- else {
1069
- if (rs.degrade.degraded === undefined &&
1070
- spec.limits?.degrade &&
1071
- rs.degrade.degradeToModel &&
1072
- rs.budget.maxCostMicroUsd !== undefined &&
1073
- stats.costMicroUsd >= spec.limits.degrade.atCostFraction * rs.budget.maxCostMicroUsd) {
1074
- void prepared.harness.setModel(rs.degrade.degradeToModel);
1075
- rs.degrade.recordDegraded({ from: prepared.model.id, to: rs.degrade.degradeToModel.id, reason: "budget", atTurn: stats.turns }, rs.degrade.degradeToModel);
1076
- }
1077
- const overAxis = rs.limits.budgetHit ? undefined : rs.budget.overBudget();
1078
- if (overAxis !== undefined) {
1079
- rs.limits.budgetHit = "exceeded";
1080
- rs.limits.budgetAxis = overAxis;
1081
- if (prepared.suspendForResource === undefined) {
1082
- void prepared.harness.abort();
1083
- prepared.abortController.abort();
1084
- }
1085
- }
1086
- }
1087
- };
1088
- return { onMessageUpdate, onMessageEnd, onToolStart, onToolEnd, onTurnEnd, announceWorkspaceMove };
1089
- }
1090
75
  const REFUSED_DECIDE_KEYS = [
1091
76
  ["gateOutcome", "the settlement record is minted by core from the host's decision facts and the row's origin, never supplied"],
1092
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'],
@@ -1348,7 +333,7 @@ export class Runner {
1348
333
  })();
1349
334
  const settled = run.catch(async (err) => {
1350
335
  let code = errorCodeOf(err);
1351
- 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";
1352
337
  const reopenable = resume !== undefined && resume.pendingActionStarted !== true && reopenReason !== undefined && !(resumeDecisionWasNegative(resume) && resume.decisionDelivered === true);
1353
338
  let reopenCommitted = false;
1354
339
  let reopenStateUnknown = false;
@@ -1442,6 +427,8 @@ export class Runner {
1442
427
  onError: (f) => console.warn(`[sema-core] ${f.site}: delegation-lifecycle observer threw (contained; further failures counted, not re-disclosed): ${f.error.message}`),
1443
428
  }), "runtask.onDelegationLifecycle");
1444
429
  }
430
+ if (forwardsSubagentEvents(spec))
431
+ await drainForwardedFramesBeforeDone();
1445
432
  queue.push({ type: "done", result: resultValue });
1446
433
  queue.close();
1447
434
  });
@@ -2559,11 +1546,14 @@ export class Runner {
2559
1546
  const postToolBatchHook = (spec.hooks ?? this.deps.hooks)?.postToolBatch;
2560
1547
  const batchArgs = postToolBatchHook ? new Map() : undefined;
2561
1548
  rs.turn.toolBatch = [];
2562
- const { onMessageUpdate, onMessageEnd, onToolStart, onToolEnd, onTurnEnd, announceWorkspaceMove } = makeHarnessHandlers(prepared, stats, rs, {
2563
- spec, queue, internals, ident, parentToolCallId, subagentName,
2564
- pushContent, emitCommitted, startedToolCallIds, toolStartAt, writeFamilyOf,
2565
- toolLabels, postToolBatchHook, batchArgs,
2566
- 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
+ },
2567
1557
  });
2568
1558
  const recordCompactionUsage = (m, msg) => {
2569
1559
  const u = msg?.usage;
@@ -2977,16 +1967,19 @@ export class Runner {
2977
1967
  for (const w of manualCompactRef.waiters.splice(0))
2978
1968
  w.resolve(outcome);
2979
1969
  };
2980
- const onTurnBoundary = makeTurnBoundary(prepared, stats, rs, {
2981
- spec, queue, loopLatch, manualCompactRef, todoToolMounted, taskToolsMounted,
2982
- walltimeMonotonicDeadline,
2983
- timeout, ident, postToolBatchHook, batchArgs, compactionBrain, withinTaskCompaction,
2984
- compactionBreaker, windowSafetyOptions, rapidRefill, drainManualCompact,
2985
- runnerHooks: {
2986
- onError: this.deps.onError,
2987
- seamCCompactionOptions: (p) => this.seamCCompactionOptions(p),
2988
- compactionHookOptions: (s, sid, trig) => this.compactionHookOptions(s, sid, trig, prepared.hookIdentity, { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal }),
2989
- 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
+ },
2990
1983
  },
2991
1984
  });
2992
1985
  prepared.gitStatusRef.reassert = async () => {
@@ -3496,7 +2489,7 @@ export class Runner {
3496
2489
  }
3497
2490
  catch (err) {
3498
2491
  loopLatch.ended = true;
3499
- if (errorCodeOf(err) === "resume.tool_unavailable") {
2492
+ if (errorCodeOf(err) === "resume.tool_unavailable" || errorCodeOf(err) === "resume.tool_contract_mismatch") {
3500
2493
  await settleTeardownLeg(() => prepared.mcp.dispose(), "mcp.dispose (tool_unavailable leg)", (e) => this.deps.onError?.(e, { phase: "config", sessionId: prepared.sessionId }));
3501
2494
  await settleTeardownLeg(() => prepared.a2a?.dispose(), "a2a.dispose (tool_unavailable leg)", (e) => this.deps.onError?.(e, { phase: "config", sessionId: prepared.sessionId }));
3502
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 }));
@@ -3881,6 +2874,8 @@ export class Runner {
3881
2874
  drainManualCompact("mooted");
3882
2875
  }
3883
2876
  manualCompactRef.emitMooted = undefined;
2877
+ if (forwardsSubagentEvents(spec))
2878
+ await drainForwardedFramesBeforeDone();
3884
2879
  queue.push({ type: "done", result });
3885
2880
  queue.close();
3886
2881
  await prepared.fileHistoryBoundary?.settle();
@@ -4508,6 +3503,7 @@ export class Runner {
4508
3503
  args: editedArgs,
4509
3504
  toolCallId: cp.pendingAction.toolCallId,
4510
3505
  budget: editBudget,
3506
+ ...callFaceSeat(parkedCallFaceOf(cp.pendingAction)),
4511
3507
  ...(cp.state.handsCwd !== undefined ? { cwd: cp.state.handsCwd } : {}),
4512
3508
  };
4513
3509
  const editSignal = config.signal;
@@ -4588,7 +3584,7 @@ export class Runner {
4588
3584
  if (plainPolicyOutcome !== undefined) {
4589
3585
  const parkedOrigin = cp.pendingAction.kind === "tool_approval" ? cp.pendingAction.origin : undefined;
4590
3586
  if (!isAskOrigin(parkedOrigin)) {
4591
- throw new CheckpointError("checkpoint.unsupported_version", "this checkpoint's pending approval carries no origin word (a row minted before the ask's origin was persisted on the park) — its settlement cannot be recorded; the checkpoint stays pending");
3587
+ throw new CheckpointError("checkpoint.unsupported_version", "this checkpoint's pending approval carries no origin word (a row minted before the ask's origin was persisted on the park) — its settlement cannot be recorded; the checkpoint stays pending", { reason: "origin_missing" });
4592
3588
  }
4593
3589
  const facts = plainPolicyOutcome.hostDecision;
4594
3590
  const approverCell = facts.approver !== undefined ? { approver: facts.approver } : {};
@@ -4798,6 +3794,12 @@ export class Runner {
4798
3794
  e.code = "resume.tool_unavailable";
4799
3795
  throw e;
4800
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
+ }
4801
3803
  const completed = new Set(pendingAction.completedCallIds);
4802
3804
  const deferredIds = pendingAction.batchToolCallIds.filter((id) => id !== pendingAction.toolCallId && !completed.has(id));
4803
3805
  const names = new Map();
@@ -4878,6 +3880,7 @@ export class Runner {
4878
3880
  toolCallId: pendingAction.toolCallId,
4879
3881
  budget: beltBudget,
4880
3882
  ...(prepared.cwdRef !== undefined ? { cwd: prepared.cwdRef.current } : {}),
3883
+ ...callFaceSeat(toolCallFaceOf(prepared.toolRosterDeltas.current, pendingAction.toolName)),
4881
3884
  }, prepared.abortController.signal));
4882
3885
  const beltUnsafe = rechecked.action === "deny" ||
4883
3886
  (rechecked.updatedInput !== undefined && !deepJsonEqual(rechecked.updatedInput, resolvedArgs));
@@ -4935,6 +3938,7 @@ export class Runner {
4935
3938
  toolName: pendingAction.toolName,
4936
3939
  args: resolvedArgs,
4937
3940
  toolCallId: pendingAction.toolCallId,
3941
+ ...callFaceSeat(toolCallFaceOf(prepared.toolRosterDeltas.current, pendingAction.toolName)),
4938
3942
  budget: beltBudget,
4939
3943
  ...(prepared.cwdRef !== undefined ? { cwd: prepared.cwdRef.current } : {}),
4940
3944
  }, prepared.abortController.signal));