@sema-agent/core 5.61.0 → 5.63.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/dist/agents/subagent.d.ts +12 -2
  3. package/dist/agents/subagent.js +3 -2
  4. package/dist/brain/open-responses.js +8 -3
  5. package/dist/brain/openai.js +4 -4
  6. package/dist/brain/stream-engine.d.ts +13 -2
  7. package/dist/brain/stream-engine.js +3 -3
  8. package/dist/core/auto-compaction.d.ts +6 -4
  9. package/dist/core/auto-compaction.js +3 -0
  10. package/dist/core/auto-mode-prompt-assets.js +1 -1
  11. package/dist/core/checkpoint-store.d.ts +36 -4
  12. package/dist/core/checkpoint-store.js +1 -0
  13. package/dist/core/context-edit.d.ts +36 -29
  14. package/dist/core/context-edit.js +3 -3
  15. package/dist/core/governance-codes.d.ts +1 -1
  16. package/dist/core/governance-codes.js +2 -0
  17. package/dist/core/hooks.d.ts +86 -4
  18. package/dist/core/hooks.js +3 -3
  19. package/dist/core/memory-engine/engine.d.ts +11 -0
  20. package/dist/core/memory-engine/engine.js +29 -3
  21. package/dist/core/memory-engine/index.d.ts +1 -1
  22. package/dist/core/memory-engine/origin-clearance.d.ts +28 -0
  23. package/dist/core/park-selfcheck.js +2 -0
  24. package/dist/core/pricing.d.ts +24 -0
  25. package/dist/core/pricing.js +18 -0
  26. package/dist/core/runner/prepare-config-doors.d.ts +36 -2
  27. package/dist/core/runner/prepare-config-doors.js +66 -8
  28. package/dist/core/runner/prepare-task.d.ts +113 -12
  29. package/dist/core/runner/prepare-task.js +239 -131
  30. package/dist/core/runner/runtask.d.ts +7 -0
  31. package/dist/core/runner/runtask.js +325 -95
  32. package/dist/core/runner/turn-attachments.d.ts +137 -5
  33. package/dist/core/runner/turn-attachments.js +25 -2
  34. package/dist/core/store-contracts/checkpoint-store-contract.js +19 -0
  35. package/dist/core/tool-errors.d.ts +2 -1
  36. package/dist/core/tool-policy.d.ts +27 -0
  37. package/dist/core/trace.d.ts +5 -4
  38. package/dist/core/types.d.ts +163 -29
  39. package/dist/core/untrusted-text.d.ts +5 -4
  40. package/dist/core/untrusted-text.js +8 -0
  41. package/dist/core/usage-window-store.d.ts +109 -8
  42. package/dist/core/usage-window-store.js +79 -12
  43. package/dist/engine/harness/agent-harness.js +20 -5
  44. package/dist/engine/harness/types.d.ts +38 -0
  45. package/dist/engine/loop/agent-loop.js +20 -1
  46. package/dist/engine/loop/types.d.ts +41 -1
  47. package/dist/index.d.ts +1 -1
  48. package/dist/orchestration/run-workflow-tool.d.ts +2 -2
  49. package/dist/orchestration/workflow-types.d.ts +48 -1
  50. package/dist/orchestration/workflow-types.js +12 -4
  51. package/dist/orchestration/workflow.d.ts +14 -3
  52. package/dist/orchestration/workflow.js +44 -19
  53. package/dist/prompt-assembly/event-registry.js +2 -0
  54. package/dist/prompts/default.js +1 -1
  55. package/dist/server/http.d.ts +1 -1
  56. package/dist/stores/file/usage-window-store.d.ts +1 -1
  57. package/dist/stores/file/usage-window-store.js +27 -6
  58. package/dist/tools/loop-tick.js +1 -1
  59. package/dist/tools/scheduler-tools.js +9 -1
  60. package/package.json +1 -1
  61. package/test/export-surface.snapshot.json +3 -1
@@ -15,7 +15,7 @@ import { eventDefaultOn } from "../../prompt-assembly/event-registry.js";
15
15
  import { DEFAULT_COMPACTION_INSTRUCTIONS, createRapidRefillState, isCompactionManualCancel, maybeCompact, nextTrimForceBackoff, recordCompactionAndCheckRapidRefill, sanitizeCompactionSettings } from "../auto-compaction.js";
16
16
  import { ASK_USER_QUESTION_TOOL_NAME, canonicalizeCapturedPlainData, classifyQuestionOutcome, isLiveQuestionFace, markBoundOnlyQuestionFace } from "../ask-question.js";
17
17
  import { boundInputHashOf } from "../canonical-json.js";
18
- import { computeCostMicroUsd, modelCostToPricing } from "../pricing.js";
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";
@@ -36,13 +36,13 @@ import { cacheFamilyOf, usageCostMicroUsd } from "./usage-accounting.js";
36
36
  import { assembleResult, errorCodeOf } from "./assemble-result.js";
37
37
  import { ATTACHMENT_BYTE_CAP, CHANGED_FILES_MAX, AGENT_LISTING_REMOVED_HEADER, SKILLS_LISTING_DELTA_HEADER, SKILLS_LISTING_REMOVED_HEADER, advanceCadenceClock, agentListingDeltaHeader, attachmentEnvelopeTags, agentListingInitialHeader, replayAnnouncedListing, replayAnnouncedModels, clipToBytes, collectDateChange, collectDueAttachments, collectInstructionsChange, commitAgentListing, commitInstructionsChange, commitSkillsListing, createAttachmentState, rebaseCadenceWindows, reduceToolEnd, renderAgentListingDelta, renderMcpDroppedTools, renderMcpInstructionsDelta, renderOrphanedBackgroundTasks, selectMcpDroppedBatch, renderSkillsListingDelta, renderToolsDelta, stampWriteAnchor } from "./turn-attachments.js";
38
38
  import { buildWorkingFileAttachments, centerAdoptionOption, emitInputTruncated, forkContextOption } from "./compaction-call-options.js";
39
- import { gatedCallIdOf, prepareTask, resolveCheckpointStore } from "./prepare-task.js";
39
+ import { effectiveDelegationFacts, gatedCallIdOf, prepareTask, resolveCheckpointStore } from "./prepare-task.js";
40
40
  import { settleTeardownLeg } from "./teardown-bounded.js";
41
41
  import { TOOL_SEARCH_NAME } from "./tool-disclosure.js";
42
42
  import { hasVerifiableStructureSignal } from "./grounding-signal.js";
43
43
  import { hasDestroy, isIsolated } from "../remote-env.js";
44
44
  import { hasBackgroundShell, sweepBackgroundShells } from "../background-shell.js";
45
- import { cloneObserverInput, formatHookFeedback, hookSeatExpiredError, resolveHookTimeoutMs, runHookSeat } from "../hooks.js";
45
+ import { cloneObserverInput, formatHookFeedback, hookSeatExpiredError, mintHookInvocationIdentity, resolveHookTimeoutMs, runHookSeat } from "../hooks.js";
46
46
  import { buildHumanInputEvent, frameMidTurnUserInput, projectHumanInput } from "../human-input-projection.js";
47
47
  import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY, sanitizeUntrustedText, SHELLED_BODY_ENVELOPE_TAGS } from "../untrusted-text.js";
48
48
  import { appendInterruptionMarker, reconcileInterruptedSession } from "../session-reconcile.js";
@@ -395,7 +395,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
395
395
  const governance = prepared.usageGovernance;
396
396
  try {
397
397
  const at = Date.now();
398
- await awaitChargeWithSlowDisclosure(governance.commit(stats.tokens, 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 }));
398
+ 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 }));
399
399
  const windowRead = governance.check(at);
400
400
  let answer;
401
401
  if (ledgerReadDeadline !== undefined) {
@@ -606,6 +606,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
606
606
  const mcpRemovals = mcpInstructionsOn && mcpDelta.pendingRemovals.length > 0 ? [...mcpDelta.pendingRemovals] : undefined;
607
607
  const mcpDropped = mcpInstructionsOn && prepared.mcp.droppedTools.length > 0 ? selectMcpDroppedBatch(prepared.mcp.droppedTools) : undefined;
608
608
  const budgetUsdOn = rs.attach.attachmentsCfg?.budgetUsd === true && rs.budget.maxCostMicroUsd !== undefined && rs.turn.lastTurnHadToolCalls;
609
+ const totalTokensOn = rs.attach.attachmentsCfg?.totalTokensReminder === true && rs.turn.lastTurnHadToolCalls;
609
610
  const toolSearchReminderOn = rs.attach.attachmentsCfg?.toolSearchReminder === true && prepared.deferredToolNames !== undefined;
610
611
  const undiscoveredTools = toolSearchReminderOn
611
612
  ? [...prepared.deferredToolNames].filter((n) => !prepared.activeTools.has(n)).sort()
@@ -623,6 +624,10 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
623
624
  changedFiles: changedFilesOn,
624
625
  planModeReminder: rs.attach.attachmentsCfg?.planModeReminder === true,
625
626
  budgetUsd: budgetUsdOn,
627
+ totalTokensReminder: totalTokensOn,
628
+ ...(rs.attach.attachmentsCfg?.totalTokensReminderMode !== undefined
629
+ ? { totalTokensReminderMode: rs.attach.attachmentsCfg.totalTokensReminderMode }
630
+ : {}),
626
631
  backgroundTasks: backgroundTasksOn,
627
632
  toolsDelta: toolsDeltaOn,
628
633
  agentListing: rs.attach.agentListingOn,
@@ -636,6 +641,9 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
636
641
  ...(budgetUsdOn && rs.budget.maxCostMicroUsd !== undefined
637
642
  ? { budgetUsd: { used: stats.costMicroUsd / 1e6, total: rs.budget.maxCostMicroUsd / 1e6 } }
638
643
  : {}),
644
+ ...(totalTokensOn && rs.budget.maxTokensWindow !== undefined
645
+ ? { totalTokens: { used: stats.tokens, total: rs.budget.maxTokensWindow } }
646
+ : {}),
639
647
  ...(bgTasks !== undefined ? { backgroundTasks: bgTasks } : {}),
640
648
  ...(pendingTools !== undefined ? { newTools: pendingTools } : {}),
641
649
  ...(prepared.toolMaterializeStatic ? { newToolsStaticFace: true } : {}),
@@ -1131,6 +1139,9 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
1131
1139
  rs.turn.firstTokenAt = Date.now();
1132
1140
  pushContent({ type: "text_delta", delta: ev.delta, ...ident() });
1133
1141
  }
1142
+ else if (ev.type === "text_end" && typeof ev.content === "string" && ev.content.length !== 0) {
1143
+ pushContent({ type: "text_end", content: ev.content, ...ident() });
1144
+ }
1134
1145
  else if (ev.type === "thinking_delta" && ev.delta) {
1135
1146
  pushContent({ type: "reasoning_delta", delta: ev.delta, ...ident() });
1136
1147
  }
@@ -1892,6 +1903,47 @@ export class Runner {
1892
1903
  return;
1893
1904
  }
1894
1905
  }
1906
+ if (resultValue !== undefined || h.loop.ended)
1907
+ throw steeringError("the task is no longer running");
1908
+ if (mintsAFrame) {
1909
+ const screen = (spec.hooks ?? this.deps.hooks)?.userPromptSubmit;
1910
+ if (screen !== undefined) {
1911
+ let decision;
1912
+ try {
1913
+ const seat = await runHookSeat("userPromptSubmit", { timeoutMs: h.hookTimeoutMs, signal: h.abortController.signal, abortEnds: true }, (sig) => screen(text, { identity: h.hookIdentity, signal: sig, source: "steer", inputId: effectiveInputId, ...(actor !== undefined ? { actor: snapshotActorAssertion(actor) } : {}) }));
1914
+ if (seat.expired) {
1915
+ if (seat.cause === "timeout") {
1916
+ try {
1917
+ this.deps.onError?.(hookSeatExpiredError("userPromptSubmit", h.hookTimeoutMs, seat.cause, "the steering input was NOT accepted (fail-closed) and the caller was refused typed"), { phase: "hook", sessionId: h.sessionId });
1918
+ }
1919
+ catch {
1920
+ }
1921
+ }
1922
+ throw steeringError(seat.cause === "timeout"
1923
+ ? `the deployment's userPromptSubmit hook did not answer within its ${h.hookTimeoutMs}ms bound while screening this steering input; the input was NOT accepted (fail-closed)`
1924
+ : `the task was cancelled while the deployment's userPromptSubmit hook was still screening this steering input; the input was NOT accepted (fail-closed)`, "steering.blocked_by_hook");
1925
+ }
1926
+ decision = seat.value;
1927
+ }
1928
+ catch (hookErr) {
1929
+ if (hookErr instanceof Error && hookErr.code === "steering.blocked_by_hook")
1930
+ throw hookErr;
1931
+ const err = hookErr instanceof Error ? hookErr : new Error(String(hookErr));
1932
+ try {
1933
+ this.deps.onError?.(err, { phase: "hook", sessionId: h.sessionId });
1934
+ }
1935
+ catch {
1936
+ }
1937
+ throw steeringError(`the deployment's userPromptSubmit hook crashed while screening this steering input (${inlineUntrusted(err.message)}); the input was NOT accepted (fail-closed)`, "steering.blocked_by_hook");
1938
+ }
1939
+ if (decision?.block !== undefined && decision.block !== "") {
1940
+ throw steeringError(`the deployment's userPromptSubmit hook blocked this steering input: ${inlineUntrusted(decision.block)}`, "steering.blocked_by_hook");
1941
+ }
1942
+ if (decision?.additionalContext !== undefined && decision.additionalContext !== "") {
1943
+ payload = `${formatHookFeedback(decision.additionalContext, h.reminderMark)}\n\n${payload}`;
1944
+ }
1945
+ }
1946
+ }
1895
1947
  const injectFramed = async () => {
1896
1948
  const noteOptions = { provenance: "engine-note", callerAuthored: true, parkRecord, ...(actor !== undefined ? { actor } : {}) };
1897
1949
  if (priority === "later") {
@@ -2236,7 +2288,7 @@ export class Runner {
2236
2288
  }
2237
2289
  }
2238
2290
  const loopLatch = { ended: false, userInterrupted: false };
2239
- onReady({ harness: prepared.harness, abortController: prepared.abortController, loop: loopLatch, reminderMark: prepared.reminderMark, sessionId: prepared.sessionId });
2291
+ onReady({ harness: prepared.harness, abortController: prepared.abortController, loop: loopLatch, reminderMark: prepared.reminderMark, sessionId: prepared.sessionId, hookTimeoutMs: prepared.hookTimeoutMs, hookIdentity: prepared.hookIdentity });
2240
2292
  const stats = { turns: 0, tokens: 0, toolCalls: 0, promptTokens: 0, totalInputTokens: 0, cachedTokens: 0, cacheWriteTokens: 0, cacheWriteTokensLong: 0, outputTokens: 0, costMicroUsd: 0 };
2241
2293
  prepared.liveSpendRef.get = () => ({ costMicroUsd: stats.costMicroUsd, tokens: stats.tokens, turns: stats.turns, walltimeMs: Math.round(performance.now() - rs.telemetry.taskStartMonotonic) });
2242
2294
  if (resume &&
@@ -2258,7 +2310,7 @@ export class Runner {
2258
2310
  const rs = createRunState();
2259
2311
  rs.telemetry.cacheFamily = cacheFamilyOf(prepared.model);
2260
2312
  rs.telemetry.pricing = this.deps.pricing?.[prepared.model.id] ?? modelCostToPricing(prepared.model.cost);
2261
- rs.telemetry.pricingConfigured = this.deps.pricing?.[prepared.model.id] !== undefined || prepared.model.cost !== undefined;
2313
+ rs.telemetry.pricingConfigured = isModelPriced(prepared.model, this.deps.pricing);
2262
2314
  if (spec.limits?.degrade) {
2263
2315
  try {
2264
2316
  rs.degrade.degradeToModel = resolveModel(spec.limits.degrade.to, modelCatalog);
@@ -2298,6 +2350,12 @@ export class Runner {
2298
2350
  catch (err) {
2299
2351
  discloseNoteTaskRunFailure(err);
2300
2352
  }
2353
+ const noteUnevaluablePriceTable = (p) => {
2354
+ if (prepared.usageGovernance?.governsCost !== true)
2355
+ return;
2356
+ if (malformedPricingField(p) !== undefined)
2357
+ rs.telemetry.unpricedSpend = true;
2358
+ };
2301
2359
  rs.degrade.recordDegraded = (info, toModel) => {
2302
2360
  if (rs.degrade.degraded !== undefined)
2303
2361
  return;
@@ -2312,13 +2370,15 @@ export class Runner {
2312
2370
  }
2313
2371
  if (m) {
2314
2372
  rs.telemetry.pricing = this.deps.pricing?.[m.id] ?? modelCostToPricing(m.cost);
2315
- rs.telemetry.pricingConfigured = this.deps.pricing?.[m.id] !== undefined || m.cost !== undefined;
2373
+ rs.telemetry.pricingConfigured = isModelPriced(m, this.deps.pricing);
2374
+ noteUnevaluablePriceTable(rs.telemetry.pricing);
2316
2375
  rs.telemetry.cacheFamily = cacheFamilyOf(m);
2317
2376
  }
2318
2377
  else {
2319
2378
  if (this.deps.pricing?.[info.to]) {
2320
2379
  rs.telemetry.pricing = this.deps.pricing[info.to];
2321
2380
  rs.telemetry.pricingConfigured = true;
2381
+ noteUnevaluablePriceTable(rs.telemetry.pricing);
2322
2382
  }
2323
2383
  else {
2324
2384
  rs.telemetry.pricingConfigured = false;
@@ -2737,9 +2797,10 @@ export class Runner {
2737
2797
  return;
2738
2798
  const fam = cacheFamilyOf(m);
2739
2799
  const price = this.deps.pricing?.[m.id] ?? modelCostToPricing(m.cost);
2740
- const priced = this.deps.pricing?.[m.id] !== undefined || m.cost !== undefined;
2800
+ const priced = isModelPriced(m, this.deps.pricing);
2741
2801
  if (!priced)
2742
2802
  rs.telemetry.unpricedSpend = true;
2803
+ noteUnevaluablePriceTable(price);
2743
2804
  const { totalInputTokens, uncachedInputTokens, costMicroUsd } = usageCostMicroUsd(fam, u, price);
2744
2805
  stats.tokens += u.totalTokens || 0;
2745
2806
  stats.promptTokens += uncachedInputTokens;
@@ -2969,6 +3030,105 @@ export class Runner {
2969
3030
  return messages;
2970
3031
  });
2971
3032
  }
3033
+ const runForcedCompactionPass = async (lane, turnSignal) => {
3034
+ if (!(spec.compaction?.enabled ?? true))
3035
+ return false;
3036
+ if (compactionBreaker.failures >= MAX_CONSECUTIVE_COMPACTION_FAILURES)
3037
+ return false;
3038
+ const passSignal = turnSignal !== undefined ? AbortSignal.any([prepared.abortController.signal, turnSignal]) : prepared.abortController.signal;
3039
+ try {
3040
+ const comp = await maybeCompact({
3041
+ session: prepared.session,
3042
+ epochDeclaredSections: prepared.epochDeclaredSections,
3043
+ ...centerAdoptionOption(prepared),
3044
+ model: prepared.harness.getModel(),
3045
+ compactionModel: prepared.compModel,
3046
+ ...forkContextOption(prepared, true),
3047
+ brain: compactionBrain,
3048
+ getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
3049
+ thinking: prepared.thinking,
3050
+ settings: { ...DEFAULT_COMPACTION_SETTINGS, ...spec.compaction },
3051
+ customInstructions: spec.compaction?.instructions ?? DEFAULT_COMPACTION_INSTRUCTIONS,
3052
+ signal: passSignal,
3053
+ minTokens: 0,
3054
+ force: true,
3055
+ overheadTokens: prepared.promptOverheadTokens,
3056
+ ...(prepared.activeTools.size > 0 ? { activeTools: [...prepared.activeTools] } : {}),
3057
+ onInputTruncated: emitInputTruncated(rs.telemetry.tracer, rs.telemetry.taskId),
3058
+ workingFileAttachments: buildWorkingFileAttachments(spec, prepared),
3059
+ ...(prepared.onCompactionApplied ? { onApplied: prepared.onCompactionApplied } : {}),
3060
+ ...this.seamCCompactionOptions(prepared),
3061
+ ...gitRestateOption(prepared),
3062
+ ...windowSafetyOptions(prepared.harness.getModel()),
3063
+ ...this.compactionHookOptions(spec, prepared.sessionId, "forced", prepared.hookIdentity, { timeoutMs: prepared.hookTimeoutMs, signal: passSignal }),
3064
+ });
3065
+ if (comp.compacted) {
3066
+ compactionBreaker.failures = 0;
3067
+ this.recordCompactionReuse(prepared, comp);
3068
+ if ((comp.freedTokens ?? 0) >= COMPACTION_FREED_EPSILON) {
3069
+ const postSize = comp.postTriggerTokens ?? Math.max(0, (comp.triggerTokens ?? comp.tokensBefore ?? 0) - (comp.freedTokens ?? 0));
3070
+ rs.counters.compactionFloor = Math.ceil(postSize * COMPACTION_REGROWTH_FACTOR);
3071
+ }
3072
+ queue.push({
3073
+ type: "compacted",
3074
+ trigger: "forced",
3075
+ tokensBefore: comp.tokensBefore ?? 0,
3076
+ ...(comp.postTriggerTokens !== undefined ? { tokensAfter: comp.postTriggerTokens } : {}),
3077
+ ...(comp.triggerTokens !== undefined ? { triggerTokensBefore: comp.triggerTokens } : {}),
3078
+ ...(comp.durationMs !== undefined ? { durationMs: comp.durationMs } : {}),
3079
+ ...(comp.phaseDurations !== undefined ? { phaseDurations: comp.phaseDurations } : {}),
3080
+ ...(comp.firstKeptEntryId !== undefined ? { preserved_segment: { firstKeptEntryId: comp.firstKeptEntryId } } : {}),
3081
+ ...(comp.attachedFiles !== undefined ? { attachedFiles: comp.attachedFiles } : {}),
3082
+ ...(comp.modelFallback ? { modelFallback: true } : {}),
3083
+ ...(comp.fallbackReason !== undefined ? { fallbackReason: comp.fallbackReason } : {}),
3084
+ ...(comp.clampedRatio !== undefined ? { clampedRatio: comp.clampedRatio } : {}),
3085
+ ...(comp.clampReason !== undefined ? { clampReason: comp.clampReason } : {}),
3086
+ ...ident(),
3087
+ });
3088
+ if (comp.phaseDurations !== undefined && comp.durationMs !== undefined) {
3089
+ const pd = comp.phaseDurations;
3090
+ const pdDur = comp.durationMs;
3091
+ emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.phase_timings", version: 1, taskId: rs.telemetry.taskId, ...pd, durationMs: pdDur, ts: Date.now() }));
3092
+ }
3093
+ prepared.cacheBreakDetector?.notifyCompaction();
3094
+ if (rs.attach.attachState !== undefined) {
3095
+ rs.attach.attachState.postCompactPending = true;
3096
+ rebaseCadenceWindows(rs.attach.attachState, rs.counters.cadenceTurns);
3097
+ }
3098
+ }
3099
+ else {
3100
+ if (comp.noop) {
3101
+ compactionBreaker.failures = 0;
3102
+ }
3103
+ else {
3104
+ compactionBreaker.failures += 1;
3105
+ }
3106
+ const declineReason = lane === "rejection" ? "prompt-too-long recovery pass did not land" : "guard-chain forced compaction pass did not land";
3107
+ if (!comp.noop) {
3108
+ this.deps.onError?.(new Error(declineReason), { phase: "compaction", sessionId: prepared.sessionId });
3109
+ }
3110
+ queue.push({
3111
+ type: "compaction_outcome",
3112
+ outcome: comp.noop ? "noop" : "failed",
3113
+ trigger: "forced",
3114
+ reason: declineReason,
3115
+ ...ident(),
3116
+ });
3117
+ }
3118
+ return comp.compacted === true;
3119
+ }
3120
+ catch (err) {
3121
+ if (turnSignal?.aborted === true && !prepared.abortController.signal.aborted) {
3122
+ queue.push({ type: "compaction_outcome", outcome: "failed", trigger: "forced", reason: "guard-chain forced compaction cut short by a turn interrupt", ...ident() });
3123
+ return false;
3124
+ }
3125
+ compactionBreaker.failures += 1;
3126
+ this.deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "compaction", sessionId: prepared.sessionId });
3127
+ const msg = String(err instanceof Error ? err.message : err);
3128
+ queue.push({ type: "compaction_outcome", outcome: "failed", trigger: "forced", reason: msg.length > 512 ? `${msg.slice(0, 512)}…` : msg, ...ident() });
3129
+ return false;
3130
+ }
3131
+ };
2972
3132
  prepared.harness.setLoopRecovery({
2973
3133
  truncatedOutput: {},
2974
3134
  malformedToolUse: {},
@@ -3014,85 +3174,23 @@ export class Runner {
3014
3174
  return true;
3015
3175
  }
3016
3176
  }
3017
- if (!(spec.compaction?.enabled ?? true))
3018
- return false;
3019
- if (compactionBreaker.failures >= MAX_CONSECUTIVE_COMPACTION_FAILURES)
3020
- return false;
3021
- try {
3022
- const comp = await maybeCompact({
3023
- session: prepared.session,
3024
- epochDeclaredSections: prepared.epochDeclaredSections,
3025
- ...centerAdoptionOption(prepared),
3026
- model: prepared.harness.getModel(),
3027
- compactionModel: prepared.compModel,
3028
- ...forkContextOption(prepared, true),
3029
- brain: compactionBrain,
3030
- getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
3031
- thinking: prepared.thinking,
3032
- settings: { ...DEFAULT_COMPACTION_SETTINGS, ...spec.compaction },
3033
- customInstructions: spec.compaction?.instructions ?? DEFAULT_COMPACTION_INSTRUCTIONS,
3034
- signal: prepared.abortController.signal,
3035
- minTokens: 0,
3036
- force: true,
3037
- overheadTokens: prepared.promptOverheadTokens,
3038
- ...(prepared.activeTools.size > 0 ? { activeTools: [...prepared.activeTools] } : {}),
3039
- onInputTruncated: emitInputTruncated(rs.telemetry.tracer, rs.telemetry.taskId),
3040
- workingFileAttachments: buildWorkingFileAttachments(spec, prepared),
3041
- ...(prepared.onCompactionApplied ? { onApplied: prepared.onCompactionApplied } : {}),
3042
- ...this.seamCCompactionOptions(prepared),
3043
- ...gitRestateOption(prepared),
3044
- ...windowSafetyOptions(prepared.harness.getModel()),
3045
- ...this.compactionHookOptions(spec, prepared.sessionId, "forced", prepared.hookIdentity, { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal }),
3046
- });
3047
- if (comp.compacted) {
3048
- compactionBreaker.failures = 0;
3049
- this.recordCompactionReuse(prepared, comp);
3050
- if ((comp.freedTokens ?? 0) >= COMPACTION_FREED_EPSILON) {
3051
- const postSize = comp.postTriggerTokens ?? Math.max(0, (comp.triggerTokens ?? comp.tokensBefore ?? 0) - (comp.freedTokens ?? 0));
3052
- rs.counters.compactionFloor = Math.ceil(postSize * COMPACTION_REGROWTH_FACTOR);
3053
- }
3054
- queue.push({
3055
- type: "compacted",
3056
- trigger: "forced",
3057
- tokensBefore: comp.tokensBefore ?? 0,
3058
- ...(comp.postTriggerTokens !== undefined ? { tokensAfter: comp.postTriggerTokens } : {}),
3059
- ...(comp.triggerTokens !== undefined ? { triggerTokensBefore: comp.triggerTokens } : {}),
3060
- ...(comp.durationMs !== undefined ? { durationMs: comp.durationMs } : {}),
3061
- ...(comp.phaseDurations !== undefined ? { phaseDurations: comp.phaseDurations } : {}),
3062
- ...(comp.firstKeptEntryId !== undefined ? { preserved_segment: { firstKeptEntryId: comp.firstKeptEntryId } } : {}),
3063
- ...(comp.attachedFiles !== undefined ? { attachedFiles: comp.attachedFiles } : {}),
3064
- ...(comp.modelFallback ? { modelFallback: true } : {}),
3065
- ...(comp.fallbackReason !== undefined ? { fallbackReason: comp.fallbackReason } : {}),
3066
- ...(comp.clampedRatio !== undefined ? { clampedRatio: comp.clampedRatio } : {}),
3067
- ...(comp.clampReason !== undefined ? { clampReason: comp.clampReason } : {}),
3068
- ...ident(),
3069
- });
3070
- if (comp.phaseDurations !== undefined && comp.durationMs !== undefined) {
3071
- const pd = comp.phaseDurations;
3072
- const pdDur = comp.durationMs;
3073
- emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.phase_timings", version: 1, taskId: rs.telemetry.taskId, ...pd, durationMs: pdDur, ts: Date.now() }));
3074
- }
3075
- prepared.cacheBreakDetector?.notifyCompaction();
3076
- if (rs.attach.attachState !== undefined) {
3077
- rs.attach.attachState.postCompactPending = true;
3078
- rebaseCadenceWindows(rs.attach.attachState, rs.counters.cadenceTurns);
3079
- }
3080
- }
3081
- else {
3082
- compactionBreaker.failures += 1;
3083
- queue.push({ type: "compaction_outcome", outcome: comp.noop ? "noop" : "failed", trigger: "forced", reason: "prompt-too-long recovery pass did not land", ...ident() });
3084
- }
3085
- return comp.compacted === true;
3086
- }
3087
- catch (err) {
3088
- compactionBreaker.failures += 1;
3089
- const msg = String(err instanceof Error ? err.message : err);
3090
- queue.push({ type: "compaction_outcome", outcome: "failed", trigger: "forced", reason: msg.length > 512 ? `${msg.slice(0, 512)}…` : msg, ...ident() });
3091
- return false;
3092
- }
3177
+ return runForcedCompactionPass("rejection");
3093
3178
  },
3094
3179
  },
3095
3180
  });
3181
+ prepared.microCompact.inTurnCompactionRef.current = async (turnSignal, anchoredEstimate) => {
3182
+ if (prepared.abortController.signal.aborted || prepared.suspendRef.token !== undefined)
3183
+ return false;
3184
+ if (turnSignal?.aborted === true)
3185
+ return false;
3186
+ if (anchoredEstimate !== undefined && rs.counters.compactionFloor > 0 && anchoredEstimate < rs.counters.compactionFloor) {
3187
+ const floor = rs.counters.compactionFloor;
3188
+ emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.suppressed", version: 1, taskId: rs.telemetry.taskId, estTokens: anchoredEstimate, floor, ts: Date.now() }));
3189
+ queue.push({ type: "compaction_outcome", outcome: "suppressed", trigger: "forced", ...ident() });
3190
+ return false;
3191
+ }
3192
+ return runForcedCompactionPass("guard", turnSignal);
3193
+ };
3096
3194
  const rapidRefill = createRapidRefillState();
3097
3195
  const drainManualCompact = (outcome) => {
3098
3196
  manualCompactRef.requested = false;
@@ -3312,6 +3410,72 @@ export class Runner {
3312
3410
  ...(resume.wakeMessage !== undefined ? [{ entry: resume.wakeMessage, source: "wake" }] : []),
3313
3411
  ];
3314
3412
  for (const { entry: steer, source } of resumeFrames) {
3413
+ if (source === "wake" && resume.wakeMessageHookContext !== undefined) {
3414
+ const hookStart = continuation.length;
3415
+ continuation += "\n\n" + formatHookFeedback(resume.wakeMessageHookContext, prepared.reminderMark);
3416
+ engineSegments.push({ start: hookStart, end: continuation.length });
3417
+ }
3418
+ if (source === "steer") {
3419
+ const parkedScreen = (spec.hooks ?? this.deps.hooks)?.userPromptSubmit;
3420
+ if (parkedScreen !== undefined) {
3421
+ let outcome;
3422
+ try {
3423
+ const seat = await runHookSeat("userPromptSubmit", { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal, abortEnds: true }, (sig) => parkedScreen(steer.text, {
3424
+ identity: prepared.hookIdentity,
3425
+ signal: sig,
3426
+ source: "parked_redelivery",
3427
+ ...(steer.inputId !== undefined ? { inputId: steer.inputId } : {}),
3428
+ ...(steer.actor !== undefined ? { actor: snapshotActorAssertion(steer.actor) } : {}),
3429
+ }));
3430
+ outcome = seat.expired
3431
+ ? { blocked: seat.cause === "timeout" ? `the screen did not answer within its ${prepared.hookTimeoutMs}ms bound (fail-closed)` : "the run was cancelled while the screen was still deciding (fail-closed)" }
3432
+ : seat.value?.block !== undefined && seat.value.block !== ""
3433
+ ? { blocked: inlineUntrusted(seat.value.block) }
3434
+ : { ...(seat.value?.additionalContext !== undefined && seat.value.additionalContext !== "" ? { context: seat.value.additionalContext } : {}) };
3435
+ }
3436
+ catch (hookErr) {
3437
+ const err = hookErr instanceof Error ? hookErr : new Error(String(hookErr));
3438
+ try {
3439
+ this.deps.onError?.(err, { phase: "hook", sessionId: prepared.sessionId });
3440
+ }
3441
+ catch {
3442
+ }
3443
+ outcome = { blocked: `the screen crashed (${inlineUntrusted(err.message)}; fail-closed)` };
3444
+ }
3445
+ if ("blocked" in outcome) {
3446
+ deliverEngineNotice(this.deps.onNotice, {
3447
+ code: "steering.parked_input_blocked",
3448
+ message: "a parked steering input was withheld by the deployment's userPromptSubmit screen when this resume redelivered it: " +
3449
+ `${outcome.blocked}. The parked row was consumed with the checkpoint; the instruction never reached the model — ` +
3450
+ "re-issue it if it still applies.",
3451
+ detail: {
3452
+ ...(steer.inputId !== undefined ? { inputId: steer.inputId } : {}),
3453
+ sessionId: prepared.sessionId,
3454
+ ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}),
3455
+ },
3456
+ });
3457
+ queue.push({
3458
+ ...buildHumanInputEvent({
3459
+ carrier: source,
3460
+ source,
3461
+ delivery: "blocked",
3462
+ sessionSeq: nextHumanInputSeq(prepared.harness),
3463
+ ...(steer.inputId !== undefined ? { inputId: steer.inputId } : {}),
3464
+ ...(steer.actor !== undefined ? { actor: steer.actor } : {}),
3465
+ ...(steer.actor?.issuer !== undefined ? { issuer: steer.actor.issuer } : {}),
3466
+ ...(spec.principal !== undefined ? { principal: spec.principal } : {}),
3467
+ }),
3468
+ ...ident(),
3469
+ });
3470
+ continue;
3471
+ }
3472
+ if ("context" in outcome && outcome.context !== undefined) {
3473
+ const hookStart = continuation.length;
3474
+ continuation += "\n\n" + formatHookFeedback(outcome.context, prepared.reminderMark);
3475
+ engineSegments.push({ start: hookStart, end: continuation.length });
3476
+ }
3477
+ }
3478
+ }
3315
3479
  const start = continuation.length;
3316
3480
  const projected = projectHumanInput({ text: steer.text, actor: steer.actor, source });
3317
3481
  continuation +=
@@ -3362,7 +3526,7 @@ export class Runner {
3362
3526
  const userPromptSubmit = (spec.hooks ?? this.deps.hooks)?.userPromptSubmit;
3363
3527
  if (userPromptSubmit) {
3364
3528
  try {
3365
- const promptSeat = await runHookSeat("userPromptSubmit", { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal, abortEnds: true }, (sig) => userPromptSubmit(spec.objective, { identity: prepared.hookIdentity, signal: sig }));
3529
+ const promptSeat = await runHookSeat("userPromptSubmit", { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal, abortEnds: true }, (sig) => userPromptSubmit(spec.objective, { identity: prepared.hookIdentity, signal: sig, source: "objective", ...(objectiveActor !== undefined ? { actor: snapshotActorAssertion(objectiveActor) } : {}) }));
3366
3530
  if (promptSeat.expired) {
3367
3531
  if (promptSeat.cause === "timeout") {
3368
3532
  try {
@@ -3586,7 +3750,7 @@ export class Runner {
3586
3750
  }
3587
3751
  if (prepared.usageGovernance !== undefined) {
3588
3752
  try {
3589
- await awaitChargeWithSlowDisclosure(prepared.usageGovernance.commit(stats.tokens, Date.now()), () => this.deps.onError?.(new Error("the usage ledger's FINAL charge has not settled after 10s — still waiting (a charge is never abandoned: walking away would double-charge on a later flush). A wedged ledger store wedges this teardown, now visibly."), { phase: "config", sessionId: prepared.sessionId }));
3753
+ await awaitChargeWithSlowDisclosure(prepared.usageGovernance.commit(stats.tokens, rs.telemetry.unpricedSpend ? undefined : stats.costMicroUsd, Date.now()), () => this.deps.onError?.(new Error("the usage ledger's FINAL charge has not settled after 10s — still waiting (a charge is never abandoned: walking away would double-charge on a later flush). A wedged ledger store wedges this teardown, now visibly."), { phase: "config", sessionId: prepared.sessionId }));
3590
3754
  }
3591
3755
  catch (flushErr) {
3592
3756
  this.deps.onError?.(flushErr, { phase: "config", sessionId: prepared.sessionId });
@@ -4169,7 +4333,9 @@ export class Runner {
4169
4333
  }
4170
4334
  }
4171
4335
  let wakeMessage;
4336
+ let wakeHookContext;
4172
4337
  const outcomeGate = outcome.gate;
4338
+ const resumeSignal = taskConfig.signal;
4173
4339
  let suppliedMessage;
4174
4340
  if (outcomeGate === "wake") {
4175
4341
  suppliedMessage = outcome.message;
@@ -4454,7 +4620,7 @@ export class Runner {
4454
4620
  "resuming here would run the leg with that ceiling unenforced. Rejected pre-CAS (the checkpoint stays pending): finish it on a worker of the " +
4455
4621
  "previous release, or start a fresh task bounded by resourceSuspend.totalTokens / totalBudgetUsd.");
4456
4622
  }
4457
- if (taskConfig.signal?.aborted === true && cp.status === "pending") {
4623
+ if (resumeSignal?.aborted === true && cp.status === "pending") {
4458
4624
  throw new CheckpointError("checkpoint.resume_aborted", "the resume was handed an ALREADY-ABORTED signal — refusing to consume the approval on a leg that cannot run it (the checkpoint stays pending and is resumable with a live signal)");
4459
4625
  }
4460
4626
  if (cp.state.workspaceHandle !== undefined && this.deps.executionEnvFactory === undefined) {
@@ -4513,6 +4679,7 @@ export class Runner {
4513
4679
  throw new CheckpointError("checkpoint.invalid_outcome", "TaskSpec.basePolicyForResumeEdit is administratively locked by this deployment (locked key \"toolPolicy\") — a task-supplied " +
4514
4680
  "resume-edit policy is refused pre-CAS (the checkpoint stays pending); remove the field or change the deployment's lock configuration");
4515
4681
  }
4682
+ let recheckGovernanceWindow;
4516
4683
  {
4517
4684
  const rowPrincipal = cp.principal || undefined;
4518
4685
  const suppliedPrincipal = taskConfig.principal || undefined;
@@ -4524,16 +4691,79 @@ export class Runner {
4524
4691
  const owesDelivery = outcomeGate !== "resource_limit" ||
4525
4692
  readPendingSteerQueue(cp.state).length > 0 ||
4526
4693
  (cp.state.runningBackgroundTasks?.length ?? 0) > 0;
4527
- if (owesDelivery && this.deps.usageWindowStore !== undefined) {
4694
+ const usageWindowStore = this.deps.usageWindowStore;
4695
+ if (owesDelivery && usageWindowStore !== undefined) {
4528
4696
  const preCasWindows = resolveUsageWindows(this.deps.usageWindows);
4529
4697
  if (preCasWindows !== undefined && preCasWindows.length > 0) {
4530
4698
  const ledgerKey = (suppliedPrincipal ?? rowPrincipal) || GLOBAL_USAGE_KEY;
4531
- const wait = usageRetryAfterMs(await this.deps.usageWindowStore.read(ledgerKey, preCasWindows, Date.now()));
4532
- if (wait !== undefined) {
4533
- throw new CheckpointError("resume.usage_window_exhausted", `a deployment usage window for ledger key ${JSON.stringify(ledgerKey)} is exhausted, and this checkpoint still owes a delivery a re-mint cannot carry — ` +
4534
- `refused pre-CAS (nothing consumed, nothing unpinned): the same token and the same decision are redeemable once the window frees, in ${String(wait)}ms`, { retryAfterMs: wait });
4699
+ const assertWindowOpen = async () => {
4700
+ const wait = usageRetryAfterMs(await usageWindowStore.read(ledgerKey, preCasWindows, Date.now()), preCasWindows);
4701
+ if (wait !== undefined) {
4702
+ throw new CheckpointError("resume.usage_window_exhausted", `a deployment usage window for ledger key ${JSON.stringify(ledgerKey)} is exhausted, and this checkpoint still owes a delivery a re-mint cannot carry — ` +
4703
+ `refused pre-CAS (nothing consumed, nothing unpinned): the same token and the same decision are redeemable once the window frees, in ${String(wait)}ms`, { retryAfterMs: wait });
4704
+ }
4705
+ };
4706
+ recheckGovernanceWindow = assertWindowOpen;
4707
+ await assertWindowOpen();
4708
+ }
4709
+ }
4710
+ }
4711
+ if (outcomeGate === "wake" && wakeMessage !== undefined) {
4712
+ const resumeScreenHooks = taskConfig.hooks ?? this.deps.hooks;
4713
+ const screen = resumeScreenHooks?.userPromptSubmit;
4714
+ if (screen !== undefined && resumeSignal?.aborted !== true) {
4715
+ const screenedMessage = wakeMessage;
4716
+ const identity = mintHookInvocationIdentity({
4717
+ sessionId: cp.sessionId,
4718
+ taskId: taskConfig.taskId ?? cp.sessionId,
4719
+ legKind: "resume",
4720
+ isDelegatedChild: effectiveDelegationFacts(internals, cp.state.isDelegatedChild).isDelegatedChild,
4721
+ ...(internals?.insideFork === true ? { insideFork: true } : {}),
4722
+ ...(internals?.agentName !== undefined ? { agentName: internals.agentName } : {}),
4723
+ ...(internals?.parentToolCallId !== undefined ? { parentToolCallId: internals.parentToolCallId } : {}),
4724
+ });
4725
+ const screenTimeoutMs = resolveHookTimeoutMs(resumeScreenHooks?.timeoutMs, (badErr) => {
4726
+ try {
4727
+ this.deps.onError?.(badErr instanceof Error ? badErr : new Error(String(badErr)), { phase: "hook", sessionId: cp.sessionId });
4535
4728
  }
4729
+ catch {
4730
+ }
4731
+ }, resumeScreenHooks);
4732
+ let decision;
4733
+ try {
4734
+ const seat = await runHookSeat("userPromptSubmit", { timeoutMs: screenTimeoutMs, ...(resumeSignal !== undefined ? { signal: resumeSignal } : {}), abortEnds: true }, (sig) => screen(screenedMessage.text, {
4735
+ identity,
4736
+ signal: sig,
4737
+ source: "resume_message",
4738
+ ...(screenedMessage.inputId !== undefined ? { inputId: screenedMessage.inputId } : {}),
4739
+ ...(screenedMessage.actor !== undefined ? { actor: snapshotActorAssertion(screenedMessage.actor) } : {}),
4740
+ }));
4741
+ if (seat.expired) {
4742
+ throw new CheckpointError("steering.blocked_by_hook", seat.cause === "timeout"
4743
+ ? `the deployment's userPromptSubmit hook did not answer within its ${screenTimeoutMs}ms bound while screening this wake message; the resume was refused (fail-closed) and the checkpoint stays pending`
4744
+ : `the resume was cancelled while the deployment's userPromptSubmit hook was still screening this wake message; the checkpoint stays pending`);
4745
+ }
4746
+ decision = seat.value;
4747
+ }
4748
+ catch (hookErr) {
4749
+ if (hookErr instanceof CheckpointError)
4750
+ throw hookErr;
4751
+ const err = hookErr instanceof Error ? hookErr : new Error(String(hookErr));
4752
+ try {
4753
+ this.deps.onError?.(err, { phase: "hook", sessionId: cp.sessionId });
4754
+ }
4755
+ catch {
4756
+ }
4757
+ throw new CheckpointError("steering.blocked_by_hook", `the deployment's userPromptSubmit hook crashed while screening this wake message (${inlineUntrusted(err.message)}); the resume was refused (fail-closed) and the checkpoint stays pending`);
4758
+ }
4759
+ if (decision?.block !== undefined && decision.block !== "") {
4760
+ throw new CheckpointError("steering.blocked_by_hook", `the deployment's userPromptSubmit hook blocked this wake message: ${inlineUntrusted(decision.block)}`);
4761
+ }
4762
+ if (decision?.additionalContext !== undefined && decision.additionalContext !== "") {
4763
+ wakeHookContext = decision.additionalContext;
4536
4764
  }
4765
+ if (recheckGovernanceWindow !== undefined)
4766
+ await recheckGovernanceWindow();
4537
4767
  }
4538
4768
  }
4539
4769
  if (plainPolicyOutcome !== undefined &&
@@ -4668,10 +4898,10 @@ export class Runner {
4668
4898
  }
4669
4899
  }
4670
4900
  : undefined;
4671
- if (taskConfig.signal?.aborted === true && consumeFlipDone) {
4901
+ if (resumeSignal?.aborted === true && consumeFlipDone) {
4672
4902
  throw new CheckpointError("checkpoint.resume_aborted", "the resume was aborted after its row was already claimed — the checkpoint stays consumed and the caller's rollback settles the outcome-unknown terminal (reopening here would strand a pending approval no redemption path can reach)");
4673
4903
  }
4674
- if (taskConfig.signal?.aborted === true) {
4904
+ if (resumeSignal?.aborted === true) {
4675
4905
  let reopenOutcome = "refused";
4676
4906
  if (onEnvRestoreFailed !== undefined) {
4677
4907
  try {
@@ -4688,7 +4918,7 @@ export class Runner {
4688
4918
  ? 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")
4689
4919
  : 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)");
4690
4920
  }
4691
- return this.runTaskStream(spec, { cp, outcome: outcomeForStore, onEnvRestoreFailed, ...(wakeMessage !== undefined ? { wakeMessage } : {}) }, resumeInternals);
4921
+ return this.runTaskStream(spec, { cp, outcome: outcomeForStore, onEnvRestoreFailed, ...(wakeMessage !== undefined ? { wakeMessage } : {}), ...(wakeHookContext !== undefined ? { wakeMessageHookContext: wakeHookContext } : {}) }, resumeInternals);
4692
4922
  }
4693
4923
  async applyResumeDecision(prepared, resume, emit, emitCommitted, onResolvedToolSuccess, onExecuteStart) {
4694
4924
  const { pendingAction } = resume.cp;