@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.
- package/CHANGELOG.md +70 -10
- package/dist/agents/agent-transcript-tool.js +2 -2
- package/dist/agents/list-agents-tool.js +2 -3
- package/dist/agents/observer.js +2 -2
- package/dist/agents/peer-admission.d.ts +1 -1
- package/dist/agents/send-message-tool.js +2 -1
- package/dist/agents/subagent-steps.js +5 -4
- package/dist/agents/subagent.js +2 -3
- package/dist/brain/anthropic.js +8 -2
- package/dist/brain/open-responses.js +5 -3
- package/dist/brain/openai.js +31 -8
- package/dist/brain/reasoning.d.ts +32 -0
- package/dist/brain/reasoning.js +18 -0
- package/dist/core/ask-question.js +2 -2
- package/dist/core/auto-mode-defaults.d.ts +16 -0
- package/dist/core/auto-mode-defaults.js +1 -0
- package/dist/core/auto-mode.d.ts +19 -0
- package/dist/core/auto-mode.js +74 -56
- package/dist/core/checkpoint-execution-record.d.ts +110 -0
- package/dist/core/checkpoint-execution-record.js +49 -0
- package/dist/core/checkpoint-store.d.ts +114 -2
- package/dist/core/checkpoint-store.js +35 -2
- package/dist/core/context-edit.d.ts +0 -22
- package/dist/core/context-edit.js +2 -11
- package/dist/core/engine-notice.d.ts +17 -0
- package/dist/core/fs-write-gate-policy.d.ts +7 -1
- package/dist/core/fs-write-gate-policy.js +7 -7
- package/dist/core/gate-fold.js +2 -2
- package/dist/core/gate-lanes.js +4 -3
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +8 -0
- package/dist/core/hooks.d.ts +8 -2
- package/dist/core/hooks.js +1 -1
- package/dist/core/lsp.js +2 -2
- package/dist/core/mcp-server-spec.d.ts +17 -0
- package/dist/core/mcp.js +3 -1
- package/dist/core/memory-engine/tools.js +4 -12
- package/dist/core/permission-rules.d.ts +32 -2
- package/dist/core/permission-rules.js +43 -37
- package/dist/core/present-plan-tool.js +3 -4
- package/dist/core/runner/active-skill-scope.js +7 -4
- package/dist/core/runner/clock-and-limits.d.ts +117 -0
- package/dist/core/runner/clock-and-limits.js +118 -0
- package/dist/core/runner/compaction-call-options.d.ts +1 -1
- package/dist/core/runner/contracts.d.ts +37 -3
- package/dist/core/runner/decide-continuation.d.ts +98 -0
- package/dist/core/runner/decide-continuation.js +133 -0
- package/dist/core/runner/execution-record.d.ts +26 -0
- package/dist/core/runner/execution-record.js +19 -0
- package/dist/core/runner/git-leg-delivery.d.ts +28 -0
- package/dist/core/runner/git-leg-delivery.js +94 -0
- package/dist/core/runner/initial-run-state.d.ts +14 -0
- package/dist/core/runner/initial-run-state.js +11 -0
- package/dist/core/runner/prepare-ask-lane.d.ts +1 -1
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +5 -4
- package/dist/core/runner/prepare-caps-and-workflow.js +33 -11
- package/dist/core/runner/prepare-defer-classify.d.ts +5 -26
- package/dist/core/runner/prepare-defer-classify.js +10 -10
- package/dist/core/runner/prepare-delegation-surface.d.ts +5 -4
- package/dist/core/runner/prepare-delegation-surface.js +15 -17
- package/dist/core/runner/prepare-gate-stations.d.ts +4 -1
- package/dist/core/runner/prepare-gate-stations.js +3 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +3 -2
- package/dist/core/runner/prepare-hands-readface.js +3 -2
- package/dist/core/runner/prepare-inherited-gate.d.ts +1 -1
- package/dist/core/runner/prepare-inherited-gate.js +1 -0
- package/dist/core/runner/prepare-lsp.d.ts +4 -3
- package/dist/core/runner/prepare-lsp.js +3 -2
- package/dist/core/runner/prepare-memory-engine-session.d.ts +4 -4
- package/dist/core/runner/prepare-memory-engine-session.js +4 -2
- package/dist/core/runner/prepare-offload-wrappers.js +7 -5
- package/dist/core/runner/prepare-park-ask.d.ts +4 -0
- package/dist/core/runner/prepare-park-ask.js +4 -1
- package/dist/core/runner/prepare-project-context.d.ts +5 -23
- package/dist/core/runner/prepare-project-context.js +7 -5
- package/dist/core/runner/prepare-prompt-assembly.d.ts +1 -1
- package/dist/core/runner/prepare-protocol-tools.d.ts +5 -1
- package/dist/core/runner/prepare-protocol-tools.js +38 -21
- package/dist/core/runner/prepare-question-face.d.ts +4 -3
- package/dist/core/runner/prepare-question-face.js +5 -3
- package/dist/core/runner/prepare-safety-scan.js +42 -2
- package/dist/core/runner/prepare-task.js +37 -19
- package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +5 -2
- package/dist/core/runner/prepare-tool-disclosure-mount.js +3 -1
- package/dist/core/runner/prepare-wiring-manifest.d.ts +4 -0
- package/dist/core/runner/prepare-wiring-manifest.js +3 -8
- package/dist/core/runner/run-harness-handlers.d.ts +46 -0
- package/dist/core/runner/run-harness-handlers.js +345 -0
- package/dist/core/runner/run-turn-boundary.d.ts +69 -0
- package/dist/core/runner/run-turn-boundary.js +694 -0
- package/dist/core/runner/runtask.d.ts +0 -68
- package/dist/core/runner/runtask.js +66 -1486
- package/dist/core/runner/session-rule-policy.js +9 -4
- package/dist/core/runner/steer-admission.d.ts +17 -0
- package/dist/core/runner/steer-admission.js +17 -0
- package/dist/core/runner/synthetic-tools.js +5 -8
- package/dist/core/runner/tool-disclosure.d.ts +4 -0
- package/dist/core/runner/tool-disclosure.js +9 -5
- package/dist/core/runner/tool-end-body.d.ts +71 -0
- package/dist/core/runner/tool-end-body.js +75 -0
- package/dist/core/runner/tool-face-overlay.d.ts +40 -0
- package/dist/core/runner/tool-face-overlay.js +125 -0
- package/dist/core/runner/tool-output-projection.d.ts +10 -0
- package/dist/core/runner/tool-output-projection.js +33 -10
- package/dist/core/sensitive-path-policy.d.ts +3 -0
- package/dist/core/sensitive-path-policy.js +6 -5
- package/dist/core/shared-memory/tools.js +3 -6
- package/dist/core/store-contracts/checkpoint-store-contract.d.ts +4 -0
- package/dist/core/store-contracts/checkpoint-store-contract.js +108 -0
- package/dist/core/task-event.d.ts +10 -0
- package/dist/core/task-registry.js +4 -5
- package/dist/core/task-spec.d.ts +8 -0
- package/dist/core/tool-catalog-entries.d.ts +48 -0
- package/dist/core/tool-catalog-entries.js +174 -0
- package/dist/core/tool-conformance.d.ts +22 -0
- package/dist/core/tool-conformance.js +37 -0
- package/dist/core/tool-face.d.ts +171 -0
- package/dist/core/tool-face.js +25 -0
- package/dist/core/tool-policy.d.ts +24 -0
- package/dist/core/tool-policy.js +4 -2
- package/dist/core/tool-registry.d.ts +114 -0
- package/dist/core/tool-registry.js +114 -0
- package/dist/core/tool-result-budget.d.ts +0 -3
- package/dist/core/tool-result-budget.js +4 -3
- package/dist/core/tool-result-store.d.ts +0 -18
- package/dist/core/tool-result-store.js +2 -18
- package/dist/core/tool-roster.d.ts +450 -0
- package/dist/core/tool-roster.js +497 -0
- package/dist/core/tool-spec.d.ts +54 -3
- package/dist/core/tools.js +7 -0
- package/dist/core/trace.d.ts +24 -0
- package/dist/core/wiring-manifest.d.ts +13 -0
- package/dist/core/wiring-manifest.js +2 -1
- package/dist/core/write-protect.d.ts +2 -1
- package/dist/core/write-protect.js +5 -4
- package/dist/engine/compaction/utils.d.ts +6 -0
- package/dist/engine/compaction/utils.js +19 -19
- package/dist/engine/loop/types.d.ts +6 -0
- package/dist/index.d.ts +20 -8
- package/dist/index.js +19 -7
- package/dist/orchestration/goal.js +2 -1
- package/dist/orchestration/run-spec.js +3 -5
- package/dist/orchestration/run-workflow-tool.js +2 -2
- package/dist/prompt-assembly/event-registry.js +3 -3
- package/dist/prompt-assembly/tool-catalog.d.ts +2 -2
- package/dist/stores/file/checkpoint-store.d.ts +7 -0
- package/dist/stores/file/checkpoint-store.js +20 -1
- package/dist/tools/fs/fs-bash.d.ts +1 -1
- package/dist/tools/fs/fs-bash.js +7 -11
- package/dist/tools/fs/fs-read.js +2 -2
- package/dist/tools/fs/fs-search-tools.d.ts +0 -7
- package/dist/tools/fs/fs-search-tools.js +3 -16
- package/dist/tools/fs/fs-write.js +4 -6
- package/dist/tools/fs/repo-map.js +2 -2
- package/dist/tools/fs/safety.d.ts +2 -1
- package/dist/tools/fs/safety.js +6 -5
- package/dist/tools/monitor.js +2 -2
- package/dist/tools/scheduler-tools.js +5 -8
- package/dist/tools/task-list.js +5 -8
- package/dist/tools/todo.js +2 -2
- package/dist/tools/web.js +3 -6
- package/dist/tools/worktree.js +3 -4
- package/package.json +4 -1
- 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,
|
|
11
|
-
import {
|
|
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,
|
|
16
|
-
import { ASK_USER_QUESTION_TOOL_NAME, canonicalizeCapturedPlainData, classifyQuestionOutcome, isLiveQuestionFace
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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
|
|
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 {
|
|
68
|
-
import {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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 } =
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
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 =
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
}
|