@sema-agent/core 7.11.2 → 7.12.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 (57) hide show
  1. package/CHANGELOG.md +42 -13
  2. package/dist/core/auto-mode-arming.d.ts +10 -14
  3. package/dist/core/auto-mode-arming.js +3 -9
  4. package/dist/core/auto-mode-defaults.d.ts +0 -2
  5. package/dist/core/auto-mode-defaults.js +0 -1
  6. package/dist/core/auto-mode-rebuild.d.ts +6 -13
  7. package/dist/core/auto-mode-rebuild.js +0 -2
  8. package/dist/core/auto-mode.d.ts +30 -89
  9. package/dist/core/auto-mode.js +12 -59
  10. package/dist/core/checkpoint-store.d.ts +1 -3
  11. package/dist/core/gate-fold.js +1 -9
  12. package/dist/core/gate-lanes.js +15 -9
  13. package/dist/core/hooks.d.ts +6 -0
  14. package/dist/core/runner/contracts.d.ts +41 -9
  15. package/dist/core/runner/denial-limit-arms.d.ts +10 -13
  16. package/dist/core/runner/denial-limit-arms.js +9 -7
  17. package/dist/core/runner/gate-exit.js +9 -1
  18. package/dist/core/runner/prepare-caps-and-workflow.d.ts +1 -1
  19. package/dist/core/runner/prepare-caps-and-workflow.js +0 -5
  20. package/dist/core/runner/prepare-suspend-saga.d.ts +0 -2
  21. package/dist/core/runner/prepare-suspend-saga.js +2 -10
  22. package/dist/core/runner/prepare-task.js +1 -1
  23. package/dist/core/runner/prepare-wiring-manifest.d.ts +1 -1
  24. package/dist/core/runner/prepare-wiring-manifest.js +1 -8
  25. package/dist/core/runner/run-attachment-seats.d.ts +4 -2
  26. package/dist/core/runner/run-attachment-seats.js +2 -2
  27. package/dist/core/runner/run-identity-wiring.d.ts +14 -33
  28. package/dist/core/runner/run-identity-wiring.js +4 -3
  29. package/dist/core/runner/run-leg.d.ts +106 -0
  30. package/dist/core/runner/run-leg.js +462 -0
  31. package/dist/core/runner/run-notification-lane.d.ts +55 -0
  32. package/dist/core/runner/run-notification-lane.js +128 -0
  33. package/dist/core/runner/run-reasoning-seat.d.ts +5 -5
  34. package/dist/core/runner/run-reasoning-seat.js +7 -7
  35. package/dist/core/runner/run-settle-and-teardown.d.ts +109 -0
  36. package/dist/core/runner/run-settle-and-teardown.js +324 -0
  37. package/dist/core/runner/run-terminal-adoption.d.ts +99 -0
  38. package/dist/core/runner/run-terminal-adoption.js +120 -0
  39. package/dist/core/runner/runtask.d.ts +14 -3
  40. package/dist/core/runner/runtask.js +55 -1010
  41. package/dist/core/runner-deps.d.ts +4 -14
  42. package/dist/core/store-contracts/workflow-journal-store-contract.d.ts +7 -0
  43. package/dist/core/store-contracts/workflow-journal-store-contract.js +85 -0
  44. package/dist/core/tool-policy.d.ts +37 -93
  45. package/dist/core/tool-policy.js +1 -11
  46. package/dist/core/trace.d.ts +6 -7
  47. package/dist/core/wiring-manifest.d.ts +5 -22
  48. package/dist/core/wiring-manifest.js +3 -11
  49. package/dist/core/workflow-journal-store.d.ts +35 -4
  50. package/dist/core/workflow-journal-store.js +19 -2
  51. package/dist/index.d.ts +3 -2
  52. package/dist/index.js +3 -2
  53. package/dist/orchestration/workflow.js +2 -0
  54. package/dist/stores/file/workflow-journal-store.d.ts +7 -10
  55. package/dist/stores/file/workflow-journal-store.js +2 -4
  56. package/package.json +1 -1
  57. package/test/export-surface.snapshot.json +10 -10
@@ -1,63 +1,45 @@
1
- import { mintSystemReminder } from "../reminder-mint.js";
2
- import { deliverEngineNotice, undrainedUserInputNotices } from "../types.js";
3
- import { AgentHarness, DEFAULT_COMPACTION_SETTINGS, isSyntheticApiErrorMessage, uuidv7 } from "../../internal/harness.js";
1
+ import { deliverEngineNotice } from "../types.js";
2
+ import { DEFAULT_COMPACTION_SETTINGS, isSyntheticApiErrorMessage, uuidv7 } from "../../internal/harness.js";
4
3
  import { snapshotActorAssertion } from "../../internal/llm.js";
5
- import { CheckpointError, remainingBudgetMicroUsd, readPendingSteerQueue, resolveCheckpointStore, realApprovalOrgFact } from "../checkpoint-store.js";
6
- import { GIT_STATUS_ECHO_PREVIEW } from "./git-status-frame.js";
4
+ import { CheckpointError, remainingBudgetMicroUsd, realApprovalOrgFact } from "../checkpoint-store.js";
7
5
  import { settleExecutionRecord } from "./execution-record.js";
8
- import { eventDefaultOn } from "../../prompt-assembly/event-registry.js";
9
6
  import { DEFAULT_COMPACTION_INSTRUCTIONS, createRapidRefillState, maybeCompact } from "../auto-compaction.js";
10
7
  import { ASK_USER_QUESTION_TOOL_NAME } from "../ask-question.js";
11
8
  import { boundInputHashOf } from "../canonical-json.js";
12
- import { computeCostMicroUsd, modelCostToPricing } from "../pricing.js";
9
+ import { modelCostToPricing } from "../pricing.js";
13
10
  import { emitTrace } from "../trace.js";
14
11
  import { ORG_ADJUDICATION_TIMEOUT_MS, settleOrgVerdictWithin } from "../permission-rule-org.js";
15
12
  import { emitTaskOutcome } from "../task-outcome.js";
16
- import { isDegenerateCutMessage } from "../../brain/terminal-cause.js";
17
- import { redactThenCut } from "../../agents/subagent-steps.js";
18
13
  import { adjudicateDerivedRoute, authCarrierFingerprint, fallbackToPrimaryNotice, normalizeBaseUrl, sameRouteIdentity } from "../../brain/route-adjudicator.js";
19
14
  import { runWithReasoningWireFacts } from "../../brain/status-sink.js";
20
15
  import { expandTiers, resolveTaskModel } from "../roles.js";
21
16
  import { screenSwappableDeps } from "../swappable-deps.js";
22
- import { AutoModeBreakerLedger } from "../auto-mode.js";
23
17
  import { runSideQuery } from "../side-query.js";
24
18
  import { generatePromptSuggestions } from "./prompt-suggestions.js";
25
19
  import { PushQueue } from "../push-queue.js";
26
20
  import { TtlSessionStore } from "../session-store.js";
27
- import { toImageContent } from "./image.js";
28
- import { assembleResult, errorCodeOf } from "./assemble-result.js";
29
- import { amendTerminal, terminalProjection } from "./terminal-projection.js";
30
- import { attachmentEnvelopeTags, commitAgentListing, commitSkillsListing, reduceToolEnd, renderAgentListingDelta, renderOrphanedBackgroundTasks, renderSkillsListingDelta, stampWriteAnchor } from "./turn-attachments.js";
21
+ import { errorCodeOf } from "./assemble-result.js";
31
22
  import { buildWorkingFileAttachments, centerAdoptionOption, contextInstructionFilesOption, emitInputTruncated, forkContextOption } from "./compaction-call-options.js";
32
23
  import { effectiveDelegationFacts, prepareTask } from "./prepare-task.js";
33
- import { drainForwardedFramesBeforeDone, forwardsSubagentEvents } from "./prepare-run-refs.js";
34
24
  import { callFaceSeat, judgeParkedToolIdentity, rosterEntryNamed, toolCallFaceOf } from "../tool-roster.js";
35
25
  import { normalizePersistedRuleHit } from "../gate-lanes.js";
36
26
  import { applyPersistedTightening, disclosedRuleSet } from "../persisted-rule-arms.js";
37
27
  import { SessionReadFileStates } from "./prepare-hands-readface.js";
38
- import { settleTeardownLeg } from "./teardown-bounded.js";
39
- import { hasDestroy, isIsolated } from "../remote-env.js";
40
- import { hasBackgroundShell, sweepBackgroundShells } from "../background-shell.js";
28
+ import { hasDestroy } from "../remote-env.js";
41
29
  import { formatHookFeedback, hookSeatExpiredError, resolveHookTimeoutMs, runHookSeat } from "../hooks.js";
42
- import { buildHumanInputEvent, projectHumanInput } from "../human-input-projection.js";
43
- import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY, sanitizeUntrustedText } from "../untrusted-text.js";
44
- import { appendInterruptionMarker, reconcileInterruptedSession } from "../session-reconcile.js";
30
+ import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY } from "../untrusted-text.js";
45
31
  import { RunnerSharedToolResultStore } from "../tool-result-store.js";
46
32
  import { PAUSE_REGISTRY } from "../pause-registry.js";
47
33
  import { refuseOutOfContractDecision, toolPolicyNameSets } from "../tool-policy.js";
48
34
  import { mintGateOutcome } from "./gate-exit.js";
49
- import { defaultTaskRegistry } from "../task-registry.js";
50
- import { isDelegatedAgentTerminal, isTerminalTaskNotification, PendingSessionNotifications, renderTaskNotificationXml, SystemInjectionQueue, taskNotificationDedupKey } from "../task-notification.js";
35
+ import { PendingSessionNotifications } from "../task-notification.js";
51
36
  import { ToolDetachHub } from "../tool-detach.js";
52
- import { createPeerInboundChainRef, createPeerSelfRef } from "../../agents/peer-admission.js";
53
37
  import { createRunState } from "./initial-run-state.js";
54
- import { nextHumanInputSeq } from "./steer-admission.js";
55
38
  import { reconciledToolEndBody, toolEndBodyFrom, toolResultMsg } from "./tool-end-body.js";
56
- import { answerFaceForRedeemedCall, deepJsonEqual, DEFERRED_REISSUE, pendingContentAskCallId, resumeContinuation, resumeDecisionWasNegative } from "./decide-continuation.js";
57
- import { awaitChargeWithSlowDisclosure, DEFAULT_PRECALL_OUTPUT_TOKENS, discloseUnevaluableWindow, platformLimitTerminal, TIMER_LATENESS_REPORT_MS } from "./clock-and-limits.js";
58
- import { gitRestateOption, resolveGitLegDelivery, wrapGitFrame } from "./git-leg-delivery.js";
39
+ import { answerFaceForRedeemedCall, deepJsonEqual, DEFERRED_REISSUE } from "./decide-continuation.js";
40
+ import { discloseUnevaluableWindow } from "./clock-and-limits.js";
41
+ import { gitRestateOption } from "./git-leg-delivery.js";
59
42
  import { createTurnBoundary } from "./run-turn-boundary.js";
60
- import { MAX_CONSECUTIVE_COMPACTION_FAILURES } from "./compaction-knobs.js";
61
43
  import { createHarnessHandlers } from "./run-harness-handlers.js";
62
44
  import { resumeAdmission } from "./resume-admission.js";
63
45
  import { resumeReviewOutcome } from "./resume-review-outcome.js";
@@ -72,6 +54,10 @@ import { streamReap } from "./stream-reap.js";
72
54
  import { streamSteerVerb } from "./stream-steer-verb.js";
73
55
  import { streamLifecycleVerbs } from "./stream-lifecycle-verbs.js";
74
56
  import { streamHaltVerbs } from "./stream-halt-verbs.js";
57
+ import { runNotificationLane } from "./run-notification-lane.js";
58
+ import { runLeg } from "./run-leg.js";
59
+ import { runTerminalAdoption } from "./run-terminal-adoption.js";
60
+ import { runSettleAndTeardown } from "./run-settle-and-teardown.js";
75
61
  import { runIdentityWiring } from "./run-identity-wiring.js";
76
62
  import { runTelemetryAndBudgetSeats } from "./run-telemetry-and-budget-seats.js";
77
63
  import { runAttachmentSeats } from "./run-attachment-seats.js";
@@ -93,7 +79,6 @@ export class Runner {
93
79
  sessions;
94
80
  sessionLocks = new Map();
95
81
  sessionReadStates = new SessionReadFileStates();
96
- autoModeBreakerLedger = new AutoModeBreakerLedger();
97
82
  pendingSessionNotifications = new PendingSessionNotifications();
98
83
  parentConstraintRegistry = new Map();
99
84
  suspendedEnvReaps = new Map();
@@ -429,140 +414,35 @@ export class Runner {
429
414
  destroy,
430
415
  };
431
416
  }
432
- async runLocked(input) {
433
- const { spec, queue, setResult, onSuggestions, onReady, onSuspend, manualCompactRef, taskIdRef, resume, internals, notifyRef, captureOptOutRef, entryActor, entryTracer } = input;
434
- const prepareResume = resume
435
- ? {
436
- leafId: resume.cp.leafId,
437
- suspendedBatch: new Set(resume.cp.pendingAction.kind === "tool_approval" ? resume.cp.pendingAction.batchToolCallIds : []),
438
- seed: resume.cp.state,
439
- priorSuspendCount: resume.cp.suspendCount,
440
- priorLedger: resume.cp.resourceLedger,
441
- priorHumanReview: resume.cp.humanReview,
442
- workspaceHandle: resume.cp.state.workspaceHandle,
443
- executesApprovedAction: resume.cp.pendingAction.kind === "tool_approval" &&
444
- resume.outcome.gate !== "wake" &&
445
- resume.outcome.decision === "allow",
446
- ...(resume.outcome.gate !== "wake" && resume.outcome.decision === "allow" && pendingContentAskCallId(resume.cp) !== undefined
447
- ? {
448
- redeemedContentAskCallId: pendingContentAskCallId(resume.cp),
449
- redeemedContentAskQuestionsHash: boundInputHashOf(resume.cp.pendingAction.args?.questions),
450
- }
451
- : {}),
452
- }
453
- : undefined;
454
- const taskNotificationQueue = new SystemInjectionQueue();
455
- const detachHub = internals?.detachHub ?? new ToolDetachHub();
456
- let notificationHarness;
457
- let notificationIdent = () => ({});
458
- let notificationLaneLive = true;
459
- let notificationSessionId;
460
- let descendantAnchorDisclosed = false;
461
- const parkTaskNotification = (payload, priority) => {
462
- if (notificationSessionId !== undefined)
463
- this.pendingSessionNotifications.pend(notificationSessionId, payload, priority);
464
- if (!isDelegatedAgentTerminal(payload))
465
- return;
466
- const uplink = internals?.parentNotify;
467
- if (uplink !== undefined) {
468
- try {
469
- uplink(payload, { priority });
470
- }
471
- catch (e) {
472
- this.deps.onError?.(e, { phase: "degraded", sessionId: notificationSessionId ?? "", classification: "descendant-terminal-uplink" });
473
- }
474
- return;
475
- }
476
- const rootAnchor = internals?.rootSessionId;
477
- if (rootAnchor !== undefined && rootAnchor !== notificationSessionId) {
478
- this.pendingSessionNotifications.pend(rootAnchor, payload, priority);
479
- return;
480
- }
481
- if (internals?.parentSessionId !== undefined && !descendantAnchorDisclosed) {
482
- descendantAnchorDisclosed = true;
483
- this.deps.onError?.(new Error(`background-agent terminal ${payload.task_id} parked on this run's own session — the delegation tree's root session is not reachable from this run (no uplink, no root anchor)`), { phase: "degraded", sessionId: notificationSessionId ?? "", classification: "descendant-terminal-unrouted" });
484
- }
485
- };
486
- const unsubscribeTaskNotifications = taskNotificationQueue.subscribe((item) => {
487
- queue.push({ type: "task_notification", notification: item.payload, priority: item.priority, ...notificationIdent() });
488
- if (notificationHarness && prepared.batchHaltRef.current === undefined) {
489
- const xml = renderTaskNotificationXml(item.payload);
490
- const noteOptions = {
491
- provenance: "engine-note",
492
- enginePayload: item.payload,
493
- ...(item.payload.task_type !== "external" && isTerminalTaskNotification(item.payload) ? { capPreferred: true } : {}),
494
- };
495
- const deliver = item.priority === "later"
496
- ? notificationHarness.followUp(xml, noteOptions)
497
- : notificationHarness.steer(xml, { ...noteOptions, ...(item.priority === "now" ? { immediate: true } : {}) });
498
- void deliver.then(() => item.onDisposition?.("queued"), () => {
499
- parkTaskNotification(item.payload, item.priority);
500
- item.onDisposition?.("parked");
501
- });
502
- }
503
- else {
504
- parkTaskNotification(item.payload, item.priority);
505
- item.onDisposition?.("parked");
506
- }
417
+ runLocked(input) {
418
+ const { spec, queue, resume, internals, taskIdRef } = input;
419
+ return runNotificationLane({
420
+ spec, queue, resume, internals, taskIdRef,
421
+ runner: this.depsSeat, self: this, sessions: this.sessions, sessionReadStates: this.sessionReadStates, pendingSessionNotifications: this.pendingSessionNotifications,
422
+ prepareTask,
423
+ next: (lane) => this.runSeatLanes(input, lane),
507
424
  });
508
- const upstreamTaskNotification = internals?.onTaskNotification;
509
- const deliveredAtTurnOpen = new Set();
510
- const injectTaskNotification = (notification, opts) => {
511
- if (deliveredAtTurnOpen.has(taskNotificationDedupKey(notification)))
512
- return Promise.resolve("dropped_duplicate");
513
- if (!notificationLaneLive) {
514
- parkTaskNotification(notification, opts?.priority ?? "later");
515
- return Promise.resolve("parked");
516
- }
517
- return new Promise((resolve) => {
518
- const accepted = taskNotificationQueue.enqueue({
519
- kind: "task_notification",
520
- priority: opts?.priority ?? "later",
521
- dedupKey: taskNotificationDedupKey(notification),
522
- payload: notification,
523
- onDisposition: resolve,
524
- });
525
- if (!accepted)
526
- resolve("queued");
527
- });
528
- };
529
- const peerSelfRef = internals?.peerSelfRef ?? createPeerSelfRef(internals?.registryScope ?? spec.principal ?? "default");
530
- const peerInboundChainRef = internals?.peerInboundChainRef ?? createPeerInboundChainRef();
531
- const modelCatalog = this.deps.models;
532
- const prepared = await prepareTask(spec, this.deps, this.sessions, prepareResume, {
533
- ...(internals ?? {}),
534
- peerSelfRef,
535
- peerInboundChainRef,
536
- detachHub,
537
- sessionReadStates: this.sessionReadStates,
538
- autoModeBreakerLedger: this.autoModeBreakerLedger,
539
- onTaskNotification: (notification, opts) => {
540
- try {
541
- upstreamTaskNotification?.(notification, opts);
542
- }
543
- catch {
544
- }
545
- injectTaskNotification(notification, opts);
546
- },
547
- }, this, taskIdRef);
548
- let undrainedUserAtEnd;
549
- const notificationLane = {
550
- get harness() { return notificationHarness; }, set harness(h) { notificationHarness = h; },
551
- get sessionId() { return notificationSessionId; }, set sessionId(s) { notificationSessionId = s; },
552
- get ident() { return notificationIdent; }, set ident(f) { notificationIdent = f; },
553
- };
554
- const undrainedUserAtEndCell = { get current() { return undrainedUserAtEnd; }, set current(v) { undrainedUserAtEnd = v; } };
555
- const { runSourceTaskId, parentToolCallId, ident, emitDelegationLifecycle, loopLatch, stats } = runIdentityWiring({
425
+ }
426
+ runSeatLanes(input, lane) {
427
+ const { spec, queue, onReady, manualCompactRef, taskIdRef, resume, internals, notifyRef, captureOptOutRef, entryTracer } = input;
428
+ const { prepared, injectTaskNotification, deliveredAtTurnOpen, peerInboundChainRef, modelCatalog, notificationLane } = lane;
429
+ const { runSourceTaskId, parentToolCallId, ident, emitDelegationLifecycle, loopLatch, stats, undrainedUserAtEnd } = runIdentityWiring({
556
430
  spec, queue, prepared, internals, taskIdRef, onReady, manualCompactRef, notifyRef, captureOptOutRef, injectTaskNotification, deliveredAtTurnOpen, peerInboundChainRef,
557
- notificationLane, undrainedUserAtEnd: undrainedUserAtEndCell, pendingSessionNotifications: this.pendingSessionNotifications, runner: this.depsSeat,
431
+ notificationLane, pendingSessionNotifications: this.pendingSessionNotifications, runner: this.depsSeat,
558
432
  });
559
433
  const rs = createRunState();
560
434
  const { noteUnevaluablePriceTable } = runTelemetryAndBudgetSeats({ spec, prepared, resume, stats, taskIdRef, entryTracer, modelCatalog, runSourceTaskId, rs, runner: this.depsSeat, sessions: this.sessions });
561
- await runAttachmentSeats({ spec, prepared, resume, rs, runner: this.depsSeat });
435
+ return runAttachmentSeats({
436
+ spec, prepared, resume, rs, runner: this.depsSeat,
437
+ next: () => this.runAssembliesAndLegs(input, lane, { rs, stats, ident, parentToolCallId, emitDelegationLifecycle, loopLatch, undrainedUserAtEnd, noteUnevaluablePriceTable }),
438
+ });
439
+ }
440
+ runAssembliesAndLegs(input, lane, seats) {
441
+ const { spec, queue, setResult, onSuggestions, onSuspend, manualCompactRef, taskIdRef, resume, internals, entryActor } = input;
442
+ const { prepared, notificationLane, unsubscribeTaskNotifications } = lane;
443
+ const { rs, stats, ident, parentToolCallId, emitDelegationLifecycle, loopLatch, undrainedUserAtEnd, noteUnevaluablePriceTable } = seats;
562
444
  const { todoToolMounted, taskToolsMounted, writeFamilyOf, toolStartAt, startedToolCallIds } = runToolMountFacts({ spec, prepared, rs });
563
- let reasoningResolution;
564
- const reasoningResolutionCell = { get current() { return reasoningResolution; }, set current(v) { reasoningResolution = v; } };
565
- const { observeReasoningWireFacts } = runReasoningSeat({ prepared, rs, taskIdRef, reasoningResolution: reasoningResolutionCell });
445
+ const { observeReasoningWireFacts, reasoningResolution } = runReasoningSeat({ prepared, rs, taskIdRef });
566
446
  const { effectiveTimeoutMs, walltimeMonotonicDeadline, timeout, pushContent, ownCommittedTailRef, emitCommitted } = runClockAndContent({ spec, prepared, queue, internals, rs, ident, parentToolCallId, startedToolCallIds });
567
447
  const { subagentName, withBrainSinks } = runBrainSinks({ queue, internals, rs, ident, parentToolCallId, observeReasoningWireFacts });
568
448
  const toolLabels = new Map(prepared.tools.flatMap((t) => (t.label !== undefined && t.label !== t.name ? [[t.name, t.label]] : [])));
@@ -614,859 +494,24 @@ export class Runner {
614
494
  },
615
495
  });
616
496
  const { flushGitMirror, unsubGitRetry, unsubBoundary, unsub } = runGitLane({ prepared, queue, rs, ident, onMessageUpdate, onMessageEnd, onToolStart, onToolEnd, onTurnEnd, onTurnBoundary, runner: this.depsSeat });
617
- let final;
618
- let threw;
619
- let abortedLive = false;
620
- let userInterruptedLive = false;
621
- let strandedHumanAnswers = [];
622
- try {
623
- if (prepared.abortController.signal.aborted) {
624
- const e = new Error("run aborted");
625
- e.name = "AbortError";
626
- throw e;
627
- }
628
- if (resume) {
629
- const walltimeExhaustedResume = effectiveTimeoutMs !== undefined && effectiveTimeoutMs <= 0;
630
- const resumeWindowRetryAfterMs = !walltimeExhaustedResume && prepared.usageGovernance !== undefined ? await prepared.usageGovernance.check(Date.now()) : undefined;
631
- if (resumeWindowRetryAfterMs !== undefined) {
632
- const owesNothing = resume.outcome.gate === "resource_limit" &&
633
- readPendingSteerQueue(resume.cp.state).length === 0 &&
634
- (resume.cp.state.runningBackgroundTasks?.length ?? 0) === 0;
635
- const committed = owesNothing &&
636
- prepared.suspendForPlatformLimit !== undefined &&
637
- (await prepared.suspendForPlatformLimit("usage_window", { costMicroUsd: 0, tokens: 0, turns: 0, walltimeMs: Math.round(performance.now() - rs.telemetry.taskStartMonotonic) }, { resumeAfterMs: resumeWindowRetryAfterMs }));
638
- if (committed) {
639
- rs.limits.turnsExceeded = false;
640
- rs.limits.budgetHit = undefined;
641
- }
642
- else {
643
- const terminal = platformLimitTerminal("usage_window", resumeWindowRetryAfterMs, "resume");
644
- rs.limits.platformTerminal = terminal;
645
- try {
646
- this.deps.onError?.(terminal, { phase: "config", sessionId: prepared.sessionId });
647
- }
648
- catch {
649
- }
650
- }
651
- }
652
- if (resumeWindowRetryAfterMs === undefined && !walltimeExhaustedResume && resume.outcome.gate !== "wake") {
653
- await this.applyResumeDecision(prepared, resume, (e) => pushContent({ ...e, ...ident() }), emitCommitted, (toolName, details) => {
654
- if (rs.attach.attachState === undefined)
655
- return;
656
- const family = writeFamilyOf(toolName);
657
- if (family !== undefined)
658
- stampWriteAnchor(rs.attach.attachState, family, rs.counters.cadenceTurns);
659
- if (details !== undefined)
660
- reduceToolEnd(rs.attach.attachState, details);
661
- }, (toolCallId) => {
662
- startedToolCallIds.add(toolCallId);
663
- if (resume !== undefined && resume.cp.pendingAction.kind === "tool_approval" && toolCallId === resume.cp.pendingAction.toolCallId) {
664
- resume.pendingActionStarted = true;
665
- }
666
- });
667
- if (resume.outcome.gate === "policy_ask")
668
- resume.decisionDelivered = true;
669
- announceWorkspaceMove();
670
- }
671
- if (resumeWindowRetryAfterMs !== undefined) {
672
- }
673
- else if (walltimeExhaustedResume) {
674
- timeout.fired = true;
675
- timeout.clear();
676
- void prepared.harness.abort();
677
- prepared.abortController.abort();
678
- }
679
- else if (rs.budget.maxTokensWindow !== undefined && rs.budget.maxTokensWindow <= 0) {
680
- rs.limits.budgetHit = "exceeded";
681
- rs.limits.budgetAxis = "tokens";
682
- }
683
- else if (rs.budget.maxCostMicroUsd !== undefined && rs.budget.maxCostMicroUsd <= 0) {
684
- rs.limits.budgetHit = "exceeded";
685
- rs.limits.budgetAxis = "cost";
686
- }
687
- else {
688
- let continuation = resumeContinuation(resume, prepared.reminderMark);
689
- const bgSnapshot = resume.cp.state.runningBackgroundTasks;
690
- if (bgSnapshot !== undefined && bgSnapshot.length > 0) {
691
- const alive = new Set(prepared.listBackgroundTasks().map((t) => t.id));
692
- const orphans = bgSnapshot.filter((t) => !alive.has(t.id));
693
- if (orphans.length > 0) {
694
- continuation += "\n\n" + formatHookFeedback(renderOrphanedBackgroundTasks(orphans), prepared.reminderMark);
695
- }
696
- }
697
- let gitResumeDelivered;
698
- {
699
- const gitBody = await resolveGitLegDelivery(prepared, resume.cp.state.gitAnnouncement, (err) => {
700
- try {
701
- this.deps.onError?.(new Error(`git announcement ladder unreadable (conservative re-announce): ${err.message}`), { phase: "degraded", sessionId: prepared.sessionId });
702
- }
703
- catch {
704
- }
705
- });
706
- const gitFrame = prepared.gitStatusRef.frame;
707
- if (gitBody !== undefined && gitFrame !== undefined) {
708
- const wrappedGit = wrapGitFrame(gitBody, prepared.reminderMark);
709
- continuation += "\n\n" + wrappedGit;
710
- gitResumeDelivered = { kind: gitFrame.kind, hash: gitFrame.hash, wrapped: wrappedGit };
711
- if (gitFrame.kind === "full" && gitFrame.shrunk !== undefined) {
712
- prepared.gitStatusRef.wrappedShrink = { find: wrappedGit, replace: wrapGitFrame(gitFrame.shrunk.body, prepared.reminderMark) };
713
- }
714
- }
715
- }
716
- const engineSegments = continuation.length > 0 ? [{ start: 0, end: continuation.length }] : [];
717
- const resumeFrames = [
718
- ...readPendingSteerQueue(resume.cp.state).map((entry) => ({ entry, source: "steer" })),
719
- ...(resume.wakeMessage !== undefined ? [{ entry: resume.wakeMessage, source: "wake" }] : []),
720
- ];
721
- for (const { entry: steer, source } of resumeFrames) {
722
- if (source === "wake" && resume.wakeMessageHookContext !== undefined) {
723
- const hookStart = continuation.length;
724
- continuation += "\n\n" + formatHookFeedback(resume.wakeMessageHookContext, prepared.reminderMark);
725
- engineSegments.push({ start: hookStart, end: continuation.length });
726
- }
727
- if (source === "steer") {
728
- const parkedScreen = (spec.hooks ?? this.deps.hooks)?.userPromptSubmit;
729
- if (parkedScreen !== undefined) {
730
- let outcome;
731
- try {
732
- const seat = await runHookSeat("userPromptSubmit", { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal, abortEnds: true }, (sig) => parkedScreen(steer.text, {
733
- identity: prepared.hookIdentity,
734
- signal: sig,
735
- source: "parked_redelivery",
736
- ...(steer.inputId !== undefined ? { inputId: steer.inputId } : {}),
737
- ...(steer.actor !== undefined ? { actor: snapshotActorAssertion(steer.actor) } : {}),
738
- }));
739
- outcome = seat.expired
740
- ? { 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)" }
741
- : seat.value?.block !== undefined && seat.value.block !== ""
742
- ? { blocked: inlineUntrusted(seat.value.block) }
743
- : { ...(seat.value?.additionalContext !== undefined && seat.value.additionalContext !== "" ? { context: seat.value.additionalContext } : {}) };
744
- }
745
- catch (hookErr) {
746
- const err = hookErr instanceof Error ? hookErr : new Error(String(hookErr));
747
- try {
748
- this.deps.onError?.(err, { phase: "hook", sessionId: prepared.sessionId });
749
- }
750
- catch {
751
- }
752
- outcome = { blocked: `the screen crashed (${inlineUntrusted(err.message)}; fail-closed)` };
753
- }
754
- if ("blocked" in outcome) {
755
- deliverEngineNotice(this.deps.onNotice, {
756
- code: "steering.parked_input_blocked",
757
- message: "a parked steering input was withheld by the deployment's userPromptSubmit screen when this resume redelivered it: " +
758
- `${outcome.blocked}. The parked row was consumed with the checkpoint; the instruction never reached the model — ` +
759
- "re-issue it if it still applies.",
760
- detail: {
761
- ...(steer.inputId !== undefined ? { inputId: steer.inputId } : {}),
762
- sessionId: prepared.sessionId,
763
- runId: prepared.runId,
764
- ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}),
765
- },
766
- });
767
- queue.push({
768
- ...buildHumanInputEvent({
769
- carrier: source,
770
- source,
771
- delivery: "blocked",
772
- sessionSeq: nextHumanInputSeq(prepared.harness),
773
- ...(steer.inputId !== undefined ? { inputId: steer.inputId } : {}),
774
- ...(steer.actor !== undefined ? { actor: steer.actor } : {}),
775
- ...(steer.actor?.issuer !== undefined ? { issuer: steer.actor.issuer } : {}),
776
- ...(spec.principal !== undefined ? { principal: spec.principal } : {}),
777
- }),
778
- ...ident(),
779
- });
780
- continue;
781
- }
782
- if ("context" in outcome && outcome.context !== undefined) {
783
- const hookStart = continuation.length;
784
- continuation += "\n\n" + formatHookFeedback(outcome.context, prepared.reminderMark);
785
- engineSegments.push({ start: hookStart, end: continuation.length });
786
- }
787
- }
788
- }
789
- const start = continuation.length;
790
- const projected = projectHumanInput({ text: steer.text, actor: steer.actor, source });
791
- continuation +=
792
- "\n\n" +
793
- (steer.trusted ? formatHookFeedback(projected, prepared.reminderMark) : delimitUntrusted("supervisor steering message", projected));
794
- if (steer.trusted)
795
- engineSegments.push({ start, end: continuation.length });
796
- queue.push({
797
- ...buildHumanInputEvent({
798
- carrier: source,
799
- source,
800
- delivery: "applied",
801
- sessionSeq: nextHumanInputSeq(prepared.harness),
802
- inputId: steer.inputId,
803
- ...(steer.actor !== undefined ? { actor: steer.actor } : {}),
804
- ...(steer.actor?.issuer !== undefined ? { issuer: steer.actor.issuer } : {}),
805
- ...(spec.principal !== undefined ? { principal: spec.principal } : {}),
806
- }),
807
- ...ident(),
808
- });
809
- }
810
- if (resume !== undefined)
811
- resume.decisionDelivered = true;
812
- if (gitResumeDelivered !== undefined) {
813
- const delivered = gitResumeDelivered;
814
- prepared.gitStatusRef.protectedText = delivered.wrapped;
815
- prepared.gitStatusRef.pendingReceipt = {
816
- text: continuation,
817
- commit: (entryId) => {
818
- prepared.gitStatusRef.announced = { kind: delivered.kind, hash: delivered.hash, entryId };
819
- queue.push({ type: "steering_injected", source: "git_status", preview: GIT_STATUS_ECHO_PREVIEW[delivered.kind], ...ident() });
820
- rs.attach.attachmentsInjected += 1;
821
- prepared.gitStatusRef.mirrorOwed = { kind: delivered.kind, hash: delivered.hash, entryId };
822
- },
823
- };
824
- }
825
- final = await withBrainSinks(() => prepared.harness.prompt(continuation, { engineSegments }));
826
- await flushGitMirror();
827
- if (prepared.gitStatusRef.announced?.pending === true)
828
- await prepared.gitStatusRef.reassert?.();
829
- }
830
- }
831
- else {
832
- const objectiveActor = entryActor;
833
- const projectedObjective = projectHumanInput({ text: spec.objective, actor: objectiveActor, source: "objective" });
834
- let effectiveObjective = projectedObjective;
835
- let promptBlocked = false;
836
- const userPromptSubmit = (spec.hooks ?? this.deps.hooks)?.userPromptSubmit;
837
- if (userPromptSubmit) {
838
- try {
839
- 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) } : {}) }));
840
- if (promptSeat.expired) {
841
- if (promptSeat.cause === "timeout") {
842
- try {
843
- this.deps.onError?.(hookSeatExpiredError("userPromptSubmit", prepared.hookTimeoutMs, promptSeat.cause, "the prompt was NOT submitted (fail-closed) and the task ends blocked"), { phase: "hook", sessionId: prepared.sessionId });
844
- }
845
- catch {
846
- }
847
- }
848
- prepared.blockedRef.reason = formatHookFeedback(promptSeat.cause === "timeout"
849
- ? `the deployment's userPromptSubmit hook did not answer within its ${prepared.hookTimeoutMs}ms bound while screening this prompt; ` +
850
- `the prompt was NOT submitted (fail-closed)`
851
- : `the task was cancelled while the deployment's userPromptSubmit hook was still screening this prompt; ` +
852
- `the prompt was NOT submitted (fail-closed)`, prepared.reminderMark);
853
- promptBlocked = true;
854
- }
855
- const decision = promptSeat.expired ? undefined : promptSeat.value;
856
- if (decision?.block) {
857
- prepared.blockedRef.reason = formatHookFeedback(decision.block, prepared.reminderMark);
858
- promptBlocked = true;
859
- }
860
- else if (decision?.additionalContext) {
861
- effectiveObjective = `${formatHookFeedback(decision.additionalContext, prepared.reminderMark)}\n\n${projectedObjective}`;
862
- }
863
- }
864
- catch (hookErr) {
865
- const err = hookErr instanceof Error ? hookErr : new Error(String(hookErr));
866
- try {
867
- this.deps.onError?.(err, { phase: "hook", sessionId: prepared.sessionId });
868
- }
869
- catch {
870
- }
871
- prepared.blockedRef.reason = formatHookFeedback(`the deployment's userPromptSubmit hook crashed while screening this prompt (${err.message}); ` +
872
- `the prompt was NOT submitted (fail-closed)`, prepared.reminderMark);
873
- promptBlocked = true;
874
- }
875
- }
876
- let gitLegDelivered;
877
- if (!promptBlocked) {
878
- const gitBody = await resolveGitLegDelivery(prepared, undefined, (err) => {
879
- try {
880
- this.deps.onError?.(new Error(`git announcement ladder unreadable (conservative re-announce): ${err.message}`), { phase: "degraded", sessionId: prepared.sessionId });
881
- }
882
- catch {
883
- }
884
- });
885
- const gitFrame = prepared.gitStatusRef.frame;
886
- if (gitBody !== undefined && gitFrame !== undefined) {
887
- const wrappedGit = wrapGitFrame(gitBody, prepared.reminderMark);
888
- const standalone = spec.images !== undefined && spec.images.length > 0;
889
- if (!standalone)
890
- effectiveObjective = `${wrappedGit}\n${effectiveObjective}`;
891
- gitLegDelivered = { kind: gitFrame.kind, hash: gitFrame.hash, standalone, wrapped: wrappedGit };
892
- if (gitFrame.kind === "full" && gitFrame.shrunk !== undefined) {
893
- prepared.gitStatusRef.wrappedShrink = { find: wrappedGit, replace: wrapGitFrame(gitFrame.shrunk.body, prepared.reminderMark) };
894
- }
895
- }
896
- }
897
- const firstFrames = [];
898
- if (rs.attach.attachState !== undefined) {
899
- const attach = rs.attach.attachState;
900
- if (rs.attach.agentListingOn && prepared.agentListing !== undefined) {
901
- const listing = prepared.agentListing;
902
- const body = renderAgentListingDelta(attach, listing.entries, listing.toolName, listing.models);
903
- if (body !== undefined) {
904
- firstFrames.push({
905
- source: "agent_listing",
906
- body,
907
- commit: () => {
908
- commitAgentListing(attach, listing.entries, listing.models);
909
- prepared.announcedListingsRef.agents = listing.entries.map((e) => e.name);
910
- if (listing.models !== undefined)
911
- prepared.announcedListingsRef.models = [...listing.models];
912
- },
913
- });
914
- }
915
- }
916
- if (rs.attach.skillsListingOn && prepared.skillsListing !== undefined) {
917
- const listing = prepared.skillsListing;
918
- const body = renderSkillsListingDelta(attach, listing.entries);
919
- if (body !== undefined) {
920
- firstFrames.push({
921
- source: "skills_listing",
922
- body,
923
- commit: () => {
924
- commitSkillsListing(attach, listing.entries);
925
- prepared.announcedListingsRef.skills = listing.entries.map((e) => e.name);
926
- },
927
- });
928
- }
929
- }
930
- if (firstFrames.length > 0) {
931
- effectiveObjective = `${firstFrames.map((f) => mintSystemReminder(sanitizeUntrustedText(f.body, attachmentEnvelopeTags(f.source)), prepared.reminderMark)).join("\n")}\n${effectiveObjective}`;
932
- }
933
- }
934
- const gitQueuedChars = gitLegDelivered !== undefined && gitLegDelivered.standalone ? gitLegDelivered.wrapped.length : 0;
935
- const precallMicroUsd = rs.budget.maxCostMicroUsd === undefined
936
- ? 0
937
- : computeCostMicroUsd({
938
- totalInputTokens: Math.ceil((effectiveObjective.length + gitQueuedChars) / 4),
939
- cacheReadTokens: 0,
940
- cacheWriteTokens: 0,
941
- cacheWriteTokensLong: 0,
942
- outputTokens: prepared.model.maxTokens ?? DEFAULT_PRECALL_OUTPUT_TOKENS,
943
- }, rs.telemetry.pricing);
944
- const precallCeilingMicroUsd = prepared.suspendForResource !== undefined ? rs.budget.remainingMicroUsd : rs.budget.maxCostMicroUsd;
945
- const precallTokens = Math.ceil((effectiveObjective.length + gitQueuedChars) / 4) + (prepared.model.maxTokens ?? DEFAULT_PRECALL_OUTPUT_TOKENS);
946
- const precallCeilingTokens = prepared.suspendForResource !== undefined ? rs.budget.remainingTokens : rs.budget.maxTokensWindow;
947
- const entryUsageRetryAfterMs = prepared.usageGovernance !== undefined ? await prepared.usageGovernance.check(Date.now()) : undefined;
948
- if (promptBlocked) {
949
- }
950
- else if (entryUsageRetryAfterMs !== undefined) {
951
- const terminal = platformLimitTerminal("usage_window", entryUsageRetryAfterMs, "entry");
952
- rs.limits.platformTerminal = terminal;
953
- try {
954
- this.deps.onError?.(terminal, { phase: "config", sessionId: prepared.sessionId });
955
- }
956
- catch {
957
- }
958
- }
959
- else if (precallCeilingTokens !== undefined && precallTokens > precallCeilingTokens) {
960
- rs.limits.budgetHit = "precall";
961
- rs.limits.budgetAxis = "tokens";
962
- }
963
- else if (precallCeilingMicroUsd !== undefined && precallMicroUsd > precallCeilingMicroUsd) {
964
- rs.limits.budgetHit = "precall";
965
- rs.limits.budgetAxis = "cost";
966
- }
967
- else if (effectiveTimeoutMs !== undefined && effectiveTimeoutMs <= 0) {
968
- timeout.fired = true;
969
- timeout.clear();
970
- void prepared.harness.abort();
971
- prepared.abortController.abort();
972
- }
973
- else {
974
- const images = spec.images
975
- ? await Promise.all(spec.images.map((img) => toImageContent(img, this.deps.allowImageUrl)))
976
- : undefined;
977
- if (gitLegDelivered !== undefined) {
978
- const delivered = gitLegDelivered;
979
- if (delivered.standalone)
980
- await prepared.harness.nextTurn(delivered.wrapped, { engineMinted: true });
981
- prepared.gitStatusRef.protectedText = delivered.wrapped;
982
- prepared.gitStatusRef.pendingReceipt = {
983
- text: delivered.standalone ? delivered.wrapped : effectiveObjective,
984
- commit: (entryId) => {
985
- prepared.gitStatusRef.announced = { kind: delivered.kind, hash: delivered.hash, entryId };
986
- queue.push({ type: "steering_injected", source: "git_status", preview: GIT_STATUS_ECHO_PREVIEW[delivered.kind], ...ident() });
987
- rs.attach.attachmentsInjected += 1;
988
- prepared.gitStatusRef.mirrorOwed = { kind: delivered.kind, hash: delivered.hash, entryId };
989
- },
990
- };
991
- }
992
- for (const f of firstFrames) {
993
- f.commit();
994
- queue.push({ type: "steering_injected", source: f.source, preview: redactThenCut(f.body, 220), ...ident() });
995
- }
996
- rs.attach.attachmentsInjected += firstFrames.length;
997
- if (firstFrames.length > 0 && prepared.announcedSnapshotRecovered)
998
- await prepared.session.appendAnnouncedListing(prepared.announcedListingsRef).catch(() => undefined);
999
- const enginePrefixChars = effectiveObjective.length - projectedObjective.length;
1000
- queue.push({
1001
- ...buildHumanInputEvent({
1002
- carrier: "objective",
1003
- source: "objective",
1004
- delivery: "applied",
1005
- sessionSeq: nextHumanInputSeq(prepared.harness),
1006
- ...(objectiveActor !== undefined ? { actor: objectiveActor } : {}),
1007
- ...(objectiveActor?.issuer !== undefined ? { issuer: objectiveActor.issuer } : {}),
1008
- ...(spec.principal !== undefined ? { principal: spec.principal } : {}),
1009
- }),
1010
- ...ident(),
1011
- });
1012
- final = await withBrainSinks(() => prepared.harness.prompt(effectiveObjective, {
1013
- ...(images && images.length > 0 ? { images } : {}),
1014
- ...(enginePrefixChars > 0 ? { enginePrefixChars } : {}),
1015
- ...(objectiveActor !== undefined ? { actor: objectiveActor } : {}),
1016
- }));
1017
- await flushGitMirror();
1018
- if (prepared.gitStatusRef.announced?.pending === true)
1019
- await prepared.gitStatusRef.reassert?.();
1020
- }
1021
- }
1022
- loopLatch.ended = true;
1023
- abortedLive = prepared.abortController.signal.aborted;
1024
- userInterruptedLive = loopLatch.userInterrupted;
1025
- }
1026
- catch (err) {
1027
- loopLatch.ended = true;
1028
- if (errorCodeOf(err) === "resume.tool_unavailable" || errorCodeOf(err) === "resume.tool_contract_mismatch") {
1029
- await settleTeardownLeg(() => prepared.mcp.dispose(), "mcp.dispose (tool_unavailable leg)", (e) => this.deps.onError?.(e, { phase: "config", sessionId: prepared.sessionId }));
1030
- await settleTeardownLeg(() => prepared.a2a?.dispose(), "a2a.dispose (tool_unavailable leg)", (e) => this.deps.onError?.(e, { phase: "config", sessionId: prepared.sessionId }));
1031
- 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 }));
1032
- throw err;
1033
- }
1034
- threw = err;
1035
- abortedLive = prepared.abortController.signal.aborted;
1036
- userInterruptedLive = loopLatch.userInterrupted;
1037
- }
1038
- finally {
1039
- timeout.clear();
1040
- prepared.lspDiagnostics?.registry.releaseRun(prepared.lspDiagnostics.runIdent);
1041
- prepared.abortController.abort();
1042
- prepared.releaseSignal();
1043
- strandedHumanAnswers = prepared.settleContentAskBindings();
1044
- try {
1045
- prepared.harness.recoverUndrainedEngineNotes();
1046
- }
1047
- catch {
1048
- }
1049
- notificationLaneLive = false;
1050
- unsubscribeTaskNotifications();
1051
- prepared.toolRosterDeltas.unsubscribe();
1052
- unsubGitRetry();
1053
- unsubBoundary();
1054
- unsub();
1055
- }
1056
- if (threw === undefined && prepared.brainCallGuardrailRef.timedOut !== undefined) {
1057
- threw = prepared.brainCallGuardrailRef.timedOut;
1058
- }
1059
- if (threw === undefined && prepared.gateStopRef.terminal !== undefined) {
1060
- threw = prepared.gateStopRef.terminal;
1061
- }
1062
- if (rs.limits.platformTerminal !== undefined) {
1063
- threw = rs.limits.platformTerminal;
1064
- }
1065
- if (prepared.usageGovernance !== undefined) {
1066
- try {
1067
- 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 }));
1068
- }
1069
- catch (flushErr) {
1070
- this.deps.onError?.(flushErr, { phase: "config", sessionId: prepared.sessionId });
1071
- }
1072
- }
1073
- const committedPause = prepared.pausedRef.current;
1074
- const durablyPaused = committedPause !== undefined;
1075
- const interrupted = !durablyPaused &&
1076
- (threw !== undefined || timeout.fired || rs.limits.turnsExceeded || rs.limits.budgetHit !== undefined || abortedLive || final?.stopReason === "aborted");
1077
- let orphansClosed = 0;
1078
- let reconcileComplete = false;
1079
- if (interrupted) {
1080
- try {
1081
- const report = await reconcileInterruptedSession(prepared.session, prepared.toolEffects, undefined, startedToolCallIds);
1082
- orphansClosed = report.recovered.length;
1083
- for (const orphan of report.recovered) {
1084
- queue.push({ type: "tool_end", toolCallId: orphan.toolCallId, toolName: orphan.toolName, ...(toolLabels.has(orphan.toolName) ? { label: toolLabels.get(orphan.toolName) } : {}), isError: true, ...reconciledToolEndBody(orphan, prepared.structuredProjector), ...ident() });
1085
- emitCommitted(orphan.entryId, "toolResult", orphan.toolCallId);
1086
- }
1087
- reconcileComplete = true;
1088
- }
1089
- catch (reconcileErr) {
1090
- this.deps.onError?.(reconcileErr instanceof Error ? reconcileErr : new Error(String(reconcileErr)), {
1091
- phase: "interrupt-reconcile",
1092
- sessionId: prepared.sessionId,
1093
- });
1094
- }
1095
- try {
1096
- await prepared.harness.flushQueuedSessionWrites();
1097
- }
1098
- catch (flushErr) {
1099
- this.deps.onError?.(flushErr instanceof Error ? flushErr : new Error(String(flushErr)), {
1100
- phase: "interrupt-reconcile",
1101
- sessionId: prepared.sessionId,
1102
- });
1103
- }
1104
- }
1105
- if (interrupted && userInterruptedLive && reconcileComplete) {
1106
- try {
1107
- const entryId = await appendInterruptionMarker(prepared.session, { toolUseInFlight: orphansClosed > 0 });
1108
- emitCommitted(entryId, "user");
1109
- }
1110
- catch (markerErr) {
1111
- this.deps.onError?.(markerErr instanceof Error ? markerErr : new Error(String(markerErr)), {
1112
- phase: "interrupt-reconcile",
1113
- sessionId: prepared.sessionId,
1114
- });
1115
- }
1116
- }
1117
- let migratedParked = { steer: 0, followUp: 0 };
1118
- if (committedPause !== undefined && this.deps.checkpointStore !== undefined) {
1119
- const parkToken = committedPause.cause.token;
1120
- const parkScope = committedPause.scope;
1121
- const store = this.deps.checkpointStore;
1122
- const carried = [];
1123
- for (const record of prepared.harness.readParkableUserInputs()) {
1124
- try {
1125
- if (!(await store.setPendingSteer(parkToken, parkScope, record)))
1126
- break;
1127
- carried.push(record);
1128
- }
1129
- catch (parkErr) {
1130
- this.deps.onError?.(parkErr instanceof Error ? parkErr : new Error(String(parkErr)), {
1131
- phase: "config",
1132
- sessionId: prepared.sessionId,
1133
- });
1134
- const parkCode = errorCodeOf(parkErr);
1135
- if (parkCode === "steering.invalid_content" || parkCode === "steering.queue_full")
1136
- continue;
1137
- break;
1138
- }
1139
- }
1140
- migratedParked = prepared.harness.dropParkedUserInputs(carried);
1141
- }
1142
- if (undrainedUserAtEnd !== undefined) {
1143
- const remaining = {
1144
- steer: Math.max(0, undrainedUserAtEnd.steer - migratedParked.steer),
1145
- followUp: Math.max(0, undrainedUserAtEnd.followUp - migratedParked.followUp),
1146
- };
1147
- for (const notice of undrainedUserInputNotices(remaining, spec.taskId ?? prepared.sessionId, prepared.sessionId, prepared.runId)) {
1148
- deliverEngineNotice(this.deps.onNotice, notice);
1149
- }
1150
- }
1151
- if (rs.attach.attachState?.postCompactPending === true && (rs.attach.attachmentsCfg?.backgroundTasks ?? eventDefaultOn("background_tasks")) === true) {
1152
- emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.announce_dropped", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
1153
- }
1154
- const comp = await this.finish(spec, prepared, {
1155
- telemetry: { tracer: rs.telemetry.tracer, taskId: rs.telemetry.taskId },
1156
- skipCompaction: durablyPaused || compactionBreaker.failures >= MAX_CONSECUTIVE_COMPACTION_FAILURES,
1157
- minTokens: rs.counters.compactionFloor,
1158
- brain: compactionBrain,
1159
- windowSafety: windowSafetyOptions(prepared.model),
1160
- onCompactionFailed: (reason) => queue.push({ type: "compaction_outcome", outcome: "failed", trigger: "auto", reason, ...ident() }),
1161
- cancelled: abortedLive,
497
+ return runLeg({
498
+ spec, queue, prepared, resume, rs, entryActor, effectiveTimeoutMs, timeout, pushContent, ident, emitCommitted, writeFamilyOf, startedToolCallIds,
499
+ announceWorkspaceMove, withBrainSinks, flushGitMirror, loopLatch, notificationLane, unsubscribeTaskNotifications, unsubGitRetry, unsubBoundary, unsub,
500
+ runner: this.depsSeat,
501
+ applyResumeDecision: (p, r, emit, committed, onResolved, onStart) => this.applyResumeDecision(p, r, emit, committed, onResolved, onStart),
502
+ next: ({ final, threw, abortedLive, userInterruptedLive, strandedHumanAnswers }) => runTerminalAdoption({
503
+ spec, queue, prepared, rs, stats, ident, emitCommitted, startedToolCallIds, toolLabels, timeout, compactionBrain, compactionBreaker, windowSafetyOptions,
504
+ undrainedUserAtEnd, final, threw, abortedLive, userInterruptedLive, runner: this.depsSeat,
505
+ finish: (s, p, o) => this.finish(s, p, o),
506
+ next: ({ threw, comp }) => runSettleAndTeardown({
507
+ spec, queue, prepared, resume, internals, rs, stats, ident, parentToolCallId, subagentName, loopLatch, timeout, ownCommittedTailRef,
508
+ emitDelegationLifecycle, taskIdRef, manualCompactRef, drainManualCompact, final, threw, abortedLive, strandedHumanAnswers, reasoningResolution, comp,
509
+ setResult, onSuggestions, onSuspend, runner: this.depsSeat, sessions: this.sessions,
510
+ suggestNextPrompts: (s, p, r) => this.suggestNextPrompts(s, p, r),
511
+ teardownOwnedEnv: (p) => this.teardownOwnedEnv(p),
512
+ }),
513
+ }),
1162
514
  });
1163
- try {
1164
- if (comp?.compacted) {
1165
- queue.push({
1166
- type: "compacted",
1167
- trigger: "auto",
1168
- tokensBefore: comp.tokensBefore ?? 0,
1169
- ...(comp.postTriggerTokens !== undefined ? { tokensAfter: comp.postTriggerTokens } : {}),
1170
- ...(comp.triggerTokens !== undefined ? { triggerTokensBefore: comp.triggerTokens } : {}),
1171
- ...(comp.durationMs !== undefined ? { durationMs: comp.durationMs } : {}),
1172
- ...(comp.phaseDurations !== undefined ? { phaseDurations: comp.phaseDurations } : {}),
1173
- ...(comp.firstKeptEntryId !== undefined ? { preserved_segment: { firstKeptEntryId: comp.firstKeptEntryId } } : {}),
1174
- ...(comp.attachedFiles !== undefined ? { attachedFiles: comp.attachedFiles } : {}),
1175
- ...(comp.modelFallback ? { modelFallback: true } : {}),
1176
- ...(comp.fallbackReason !== undefined ? { fallbackReason: comp.fallbackReason } : {}),
1177
- ...(comp.clampedRatio !== undefined ? { clampedRatio: comp.clampedRatio } : {}),
1178
- ...(comp.clampReason !== undefined ? { clampReason: comp.clampReason } : {}),
1179
- ...ident(),
1180
- });
1181
- if (comp.phaseDurations !== undefined && comp.durationMs !== undefined) {
1182
- const pd = comp.phaseDurations;
1183
- const pdDur = comp.durationMs;
1184
- emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.phase_timings", version: 1, taskId: rs.telemetry.taskId, ...pd, durationMs: pdDur, ts: Date.now() }));
1185
- }
1186
- prepared.cacheBreakDetector?.notifyCompaction();
1187
- }
1188
- if (prepared.nestedStats.tasks > 0) {
1189
- stats.nested = {
1190
- tokens: prepared.nestedStats.tokens,
1191
- turns: prepared.nestedStats.turns,
1192
- tasks: prepared.nestedStats.tasks,
1193
- ...(prepared.nestedStats.anyUnpriced ? {} : { costMicroUsd: prepared.nestedStats.costMicroUsd }),
1194
- };
1195
- }
1196
- if (stats.totalInputTokens > 0) {
1197
- const rawHit = stats.cachedTokens / stats.totalInputTokens;
1198
- if (stats.cachedTokens > stats.totalInputTokens) {
1199
- this.deps.onError?.(new Error(`prompt-cache: usage/api mismatch — cachedTokens (${stats.cachedTokens}) > totalInputTokens (${stats.totalInputTokens}); the cache family is likely wrong. Does model.api match the brain serving it? Override with model.params.promptCacheFamily = "input-includes-cached" | "input-excludes-cached".`), { phase: "prompt-cache", sessionId: prepared.sessionId });
1200
- }
1201
- stats.cacheHitRate = Math.min(1, rawHit);
1202
- if (!rs.telemetry.cacheBreakReported && stats.turns >= 2 && stats.totalInputTokens >= 8000 && stats.cacheHitRate < 0.15) {
1203
- const cacheWritten = stats.cacheWriteTokens + stats.cacheWriteTokensLong;
1204
- const cause = rs.degrade.degraded
1205
- ? `a mid-task model switch (${rs.degrade.degraded.from} → ${rs.degrade.degraded.to}, degradation) reset the prefix cache — this is the likely cause`
1206
- : cacheWritten > 0
1207
- ? `this task reported ${cacheWritten} cache-write tokens across its calls (compaction included) but served almost none back as reads — consistent with a prompt prefix that CHANGES between turns (client-side: volatile content up front, or per-turn tool churn/reorder) and, less often, with server-side eviction. Keep volatile content (memory/timestamps/ids) out of the prefix and the tool list stable in membership AND order`
1208
- : `no call in this task reported any cache-write tokens — and this API family may not report them at all (an openai-shaped usage row carries cached READS only), so the write side is no evidence here; check that the prompt prefix (system prompt + tool list, membership AND order) is byte-stable across turns and that this route caches this model`;
1209
- this.deps.onError?.(new Error(`prompt-cache: low prefix-cache hit rate ${(stats.cacheHitRate * 100).toFixed(0)}% over ${stats.turns} turns (${stats.totalInputTokens} prompt tokens) — ${cause}. See design/09.`), { phase: "prompt-cache", sessionId: prepared.sessionId });
1210
- }
1211
- }
1212
- if (prepared.resourceLedger &&
1213
- (resume?.cp.gate.kind === "resource_limit" || prepared.pausedRef.current?.cause.gate.kind === "resource_limit")) {
1214
- stats.costMicroUsd += prepared.resourceLedger.spentMicroUsd;
1215
- stats.tokens += prepared.resourceLedger.spentTokens;
1216
- stats.turns += prepared.resourceLedger.spentTurns;
1217
- }
1218
- if (prepared.humanReviewRef.count > 0) {
1219
- stats.humanReview = {
1220
- count: prepared.humanReviewRef.count,
1221
- totalWaitMs: prepared.humanReviewRef.totalWaitMs,
1222
- gates: [...prepared.humanReviewRef.gates],
1223
- };
1224
- }
1225
- const reminderDisclosuresActive = Object.keys(prepared.reminderDisclosureCounts).length > 0;
1226
- if (rs.counters.finalVerifyInjections > 0 || rs.attach.attachmentsInjected > 0 || rs.counters.repetitionCuts > 0 || rs.counters.repetitionSpared > 0 || rs.counters.approachNoticesSent > 0 || reminderDisclosuresActive) {
1227
- stats.mechanisms = {
1228
- ...(rs.counters.finalVerifyInjections > 0 ? { finalVerifyInjected: true } : {}),
1229
- ...(rs.counters.finalVerifyInjections > 0 ? { finalVerifyInjections: rs.counters.finalVerifyInjections } : {}),
1230
- ...(rs.attach.attachmentsInjected > 0 ? { attachmentsInjected: rs.attach.attachmentsInjected } : {}),
1231
- ...(rs.counters.approachNoticesSent > 0 ? { approachNoticesSent: rs.counters.approachNoticesSent } : {}),
1232
- ...(rs.counters.repetitionCuts > 0 ? { repetitionCuts: rs.counters.repetitionCuts } : {}),
1233
- ...(rs.counters.repetitionSpared > 0 ? { repetitionSpared: rs.counters.repetitionSpared } : {}),
1234
- ...(rs.counters.repetitionEvents.length > 0 ? { repetitionEvents: rs.counters.repetitionEvents } : {}),
1235
- ...(reminderDisclosuresActive ? { reminderDisclosures: { ...prepared.reminderDisclosureCounts } } : {}),
1236
- };
1237
- }
1238
- const result = assembleResult(spec, prepared.sessionId, final, stats, {
1239
- threw,
1240
- runId: prepared.runId,
1241
- model: prepared.model.id,
1242
- unpricedSpend: rs.telemetry.unpricedSpend,
1243
- rewindNotes: prepared.rewindNotes,
1244
- editedFiles: prepared.editedFilesSnapshot(),
1245
- haltedOnUserRejection: prepared.batchHaltRef.current !== undefined,
1246
- userHalted: loopLatch.userHalted,
1247
- strandedHumanAnswers,
1248
- remoteEnvFailures: prepared.remoteEnvFailures,
1249
- effectiveReadFace: prepared.effectiveReadFace,
1250
- effectiveReadDenyPatterns: prepared.effectiveReadDenyPatterns,
1251
- effectiveMemoryScopes: prepared.effectiveMemoryScopes,
1252
- effectiveReasoning: reasoningResolution,
1253
- retryAfterMs: rs.limits.platformTerminal?.retryAfterMs,
1254
- abortedForTimeout: timeout.fired,
1255
- abortedForTurns: rs.limits.turnsExceeded,
1256
- abortedLive,
1257
- budgetHit: rs.limits.budgetHit,
1258
- budgetAxis: rs.limits.budgetAxis,
1259
- blockedReason: prepared.blockedRef.reason,
1260
- conflict: prepared.conflictRef.hit,
1261
- gitCoreOverBudget: prepared.gitStatusRef.terminalCode === "irreducible_core_over_budget",
1262
- outputInvalid: rs.degrade.outputInvalid,
1263
- suspendLoop: prepared.suspendLoopRef.hit,
1264
- ...(prepared.pausedRef.current !== undefined ? { paused: prepared.pausedRef.current.cause } : {}),
1265
- });
1266
- if (resume !== undefined &&
1267
- resume.pendingActionStarted !== true &&
1268
- result.terminal.kind !== "completed" &&
1269
- result.terminal.kind !== "paused" &&
1270
- !(resumeDecisionWasNegative(resume) && resume.decisionDelivered === true) &&
1271
- resume.onEnvRestoreFailed !== undefined) {
1272
- try {
1273
- await resume.onEnvRestoreFailed("env_failed");
1274
- amendTerminal(result, "resume.reopened_unstarted", (m) => `${m ?? "the resume leg did not complete"} — the approved action never started, so the checkpoint was REOPENED (pending): the same approval can be resumed again, do not ask for a fresh one`);
1275
- }
1276
- catch {
1277
- amendTerminal(result, "resume.reopen_failed_unstarted", (m) => `${m ?? "the resume leg did not complete"} — the approved action never started, and reopening the checkpoint FAILED: its state is unprovable from here. Re-read it before deciding; do NOT issue a fresh approval on the assumption the old one is dead`);
1278
- }
1279
- }
1280
- prepared.sealReadStateSeat?.(ownCommittedTailRef.current);
1281
- {
1282
- const wss = prepared.workspaceStateSettle;
1283
- if (wss !== undefined && result.terminal.kind !== "paused") {
1284
- const curCwd = prepared.cwdRef?.current !== undefined && prepared.cwdRef.current !== wss.rootCanonical ? prepared.cwdRef.current : undefined;
1285
- const curWt = prepared.worktreeSessionRef?.current;
1286
- const changed = curCwd !== wss.restoredHandsCwd || curWt?.worktreeDir !== wss.restoredWorktreeDir;
1287
- if (curCwd !== undefined || curWt !== undefined || changed || wss.baselineUnknown === true) {
1288
- await settleTeardownLeg(() => prepared.session.appendWorkspaceState({
1289
- taskRoot: wss.rootCanonical,
1290
- ...(curCwd !== undefined ? { handsCwd: curCwd } : {}),
1291
- ...(curWt !== undefined ? { activeWorktree: { ...curWt } } : {}),
1292
- }), "session.appendWorkspaceState (settle leg)", (e) => this.deps.onError?.(e, { phase: "config", sessionId: prepared.sessionId }));
1293
- }
1294
- }
1295
- }
1296
- const stopFailureHook = (spec.hooks ?? this.deps.hooks)?.stopFailure;
1297
- if (stopFailureHook !== undefined &&
1298
- result.terminal.kind === "failed" &&
1299
- final?.stopReason === "error" &&
1300
- !isDegenerateCutMessage(final) &&
1301
- threw === undefined &&
1302
- rs.limits.budgetHit === undefined &&
1303
- !rs.degrade.outputInvalid &&
1304
- !prepared.suspendLoopRef.hit &&
1305
- !abortedLive &&
1306
- result.terminal.code !== "conflict") {
1307
- const failedCause = result.terminal;
1308
- try {
1309
- const failureSeat = await runHookSeat("stopFailure", { timeoutMs: prepared.hookTimeoutMs }, (sig) => stopFailureHook({
1310
- identity: prepared.hookIdentity,
1311
- error: failedCause.message ?? "model error",
1312
- ...(failedCause.code !== undefined ? { errorKind: failedCause.code } : {}),
1313
- turns: stats.turns,
1314
- signal: sig,
1315
- }));
1316
- if (failureSeat.expired) {
1317
- try {
1318
- this.deps.onError?.(hookSeatExpiredError("stopFailure", prepared.hookTimeoutMs, failureSeat.cause, "the terminal observation was abandoned; the assembled TaskResult is unchanged"), { phase: "hook", sessionId: prepared.sessionId });
1319
- }
1320
- catch {
1321
- }
1322
- }
1323
- }
1324
- catch (err) {
1325
- try {
1326
- this.deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "hook", sessionId: prepared.sessionId });
1327
- }
1328
- catch {
1329
- }
1330
- }
1331
- }
1332
- const terminalFace = terminalProjection(result.terminal);
1333
- emitDelegationLifecycle({
1334
- phase: "terminal",
1335
- identity: prepared.hookIdentity,
1336
- status: terminalFace.status,
1337
- turns: stats.turns,
1338
- ...(terminalFace.errorCode !== undefined ? { errorCode: terminalFace.errorCode } : {}),
1339
- });
1340
- if (taskIdRef !== undefined)
1341
- taskIdRef.delegationTerminalOwed = undefined;
1342
- if (rs.degrade.degraded)
1343
- result.degraded = rs.degrade.degraded;
1344
- if (prepared.outputRef.set)
1345
- result.structuredOutput = prepared.outputRef.value;
1346
- const committedToken = prepared.pausedRef.current?.cause.token;
1347
- const committedScope = prepared.pausedRef.current?.scope;
1348
- if (committedToken !== undefined) {
1349
- const store = resolveCheckpointStore(spec, this.deps);
1350
- if (store && committedScope !== undefined) {
1351
- onSuspend({
1352
- env: prepared.ownedEnv,
1353
- token: committedToken,
1354
- scope: committedScope,
1355
- store,
1356
- sessionId: prepared.sessionId,
1357
- });
1358
- }
1359
- }
1360
- if (resume && committedToken === undefined) {
1361
- await settleTeardownLeg(() => this.sessions.unpin?.(resume.cp.sessionId), "sessions.unpin (resumed-terminal leg)", (e) => this.deps.onError?.(e, { phase: "config", sessionId: prepared.sessionId }));
1362
- }
1363
- if (prepared.memoryEngineSession) {
1364
- const mes = prepared.memoryEngineSession;
1365
- await settleTeardownLeg(() => mes.harvest(), "memoryEngineSession.harvest (settle leg)", (e) => this.deps.onError?.(e, { phase: "config", sessionId: prepared.sessionId }));
1366
- }
1367
- emitTrace(rs.telemetry.tracer, () => ({
1368
- kind: "task.end",
1369
- version: 1,
1370
- taskId: rs.telemetry.taskId,
1371
- runId: rs.telemetry.runId,
1372
- status: terminalFace.status,
1373
- errorCode: terminalFace.errorCode,
1374
- turns: stats.turns,
1375
- tokens: stats.tokens,
1376
- ...(rs.telemetry.unpricedSpend ? {} : { costMicroUsd: stats.costMicroUsd }),
1377
- durationMs: Date.now() - rs.telemetry.taskStart,
1378
- ...(prepared.outputRef.set ? { hasStructuredOutput: true } : {}),
1379
- ...(stats.mechanisms !== undefined
1380
- ? { mechanisms: (({ repetitionEvents: _events, ...scalars }) => scalars)(stats.mechanisms) }
1381
- : {}),
1382
- ...(timeout.latenessMs !== undefined && timeout.latenessMs > TIMER_LATENESS_REPORT_MS
1383
- ? { timerLatenessMs: timeout.latenessMs }
1384
- : {}),
1385
- ...(rs.counters.walltimeSyncBackstopFired ? { walltimeSyncBackstop: true } : {}),
1386
- ts: Date.now(),
1387
- }));
1388
- setResult(result);
1389
- if (parentToolCallId !== undefined && result.terminal.kind !== "paused") {
1390
- const terminalTick = {
1391
- type: "task_progress",
1392
- taskId: rs.telemetry.taskId,
1393
- ...(internals?.delegationTaskType !== undefined ? { taskType: internals.delegationTaskType } : {}),
1394
- ...(internals?.cycleSeq !== undefined ? { seq: internals.cycleSeq } : {}),
1395
- ...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}),
1396
- ...(subagentName ? { name: subagentName } : {}),
1397
- model: prepared.model.id,
1398
- usage: { totalTokens: stats.tokens, toolUses: stats.toolCalls, durationMs: Date.now() - rs.telemetry.taskStart },
1399
- status: result.terminal.kind === "completed" ? "completed" : "failed",
1400
- ...ident(),
1401
- };
1402
- queue.push(terminalTick);
1403
- try {
1404
- internals?.onForwardEvent?.(terminalTick);
1405
- }
1406
- catch {
1407
- }
1408
- }
1409
- if (manualCompactRef.waiters.length > 0) {
1410
- queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: "manual", reason: "task_ending", ...ident() });
1411
- drainManualCompact("mooted");
1412
- }
1413
- manualCompactRef.emitMooted = undefined;
1414
- if (forwardsSubagentEvents(spec))
1415
- await drainForwardedFramesBeforeDone();
1416
- queue.push({ type: "done", result });
1417
- queue.close();
1418
- await prepared.fileHistoryBoundary?.settle();
1419
- if (spec.suggestNextPrompts && result.terminal.kind === "completed") {
1420
- onSuggestions(this.suggestNextPrompts(spec, prepared, result));
1421
- }
1422
- }
1423
- finally {
1424
- const ownedEnvDying = prepared.ownedEnv !== undefined &&
1425
- hasDestroy(prepared.ownedEnv) &&
1426
- isIsolated(prepared.ownedEnv) &&
1427
- prepared.pausedRef.current === undefined;
1428
- await settleTeardownLeg(() => defaultTaskRegistry.settleKilledForOwner({ owner: spec.taskId ?? prepared.sessionId, scope: spec.principal ?? "default", sessionId: prepared.sessionId }, {
1429
- source: abortedLive &&
1430
- !timeout.fired &&
1431
- !rs.limits.turnsExceeded &&
1432
- rs.limits.budgetHit === undefined &&
1433
- prepared.pausedRef.current === undefined
1434
- ? "user"
1435
- : "parent",
1436
- skipSessionScoped: true,
1437
- envDying: ownedEnvDying,
1438
- retainProcesses: spec.retainBackgroundProcesses === true,
1439
- }), "taskRegistry.settleKilledForOwner", (e) => this.deps.onError?.(e, { phase: "config", sessionId: prepared.sessionId }));
1440
- const bgEnv = prepared.ownedEnv ?? this.deps.executionEnv;
1441
- const retainBgBySpec = spec.retainBackgroundProcesses === true && !ownedEnvDying;
1442
- if (bgEnv && hasBackgroundShell(bgEnv) && !retainBgBySpec) {
1443
- try {
1444
- const keepAlive = ownedEnvDying && bgEnv === prepared.ownedEnv
1445
- ? []
1446
- : [
1447
- ...defaultTaskRegistry.sessionResidentShellIds(prepared.sessionId, bgEnv),
1448
- ...defaultTaskRegistry.timeoutResidentShellIds(bgEnv),
1449
- ...defaultTaskRegistry.retainedShellIds(bgEnv),
1450
- ];
1451
- await settleTeardownLeg(() => sweepBackgroundShells(bgEnv, defaultTaskRegistry, keepAlive.length > 0 ? { except: keepAlive } : undefined), "sweepBackgroundShells", (e) => this.deps.onError?.(e, { phase: "config", sessionId: prepared.sessionId }));
1452
- }
1453
- catch (e) {
1454
- this.deps.onError?.(e, { phase: "config", sessionId: prepared.sessionId });
1455
- }
1456
- }
1457
- defaultTaskRegistry.clearBackgroundForOwner({
1458
- owner: spec.taskId ?? prepared.sessionId,
1459
- scope: spec.principal ?? "default",
1460
- });
1461
- defaultTaskRegistry.abortBackgroundAgentsForOwner({
1462
- owner: spec.taskId ?? prepared.sessionId,
1463
- scope: spec.principal ?? "default",
1464
- }, { skipSessionScoped: true });
1465
- if (prepared.subagentRetain) {
1466
- await settleTeardownLeg(() => prepared.subagentRetain.disposeAll(), "subagentRetain.disposeAll", (e) => this.deps.onError?.(e, { phase: "config", sessionId: prepared.sessionId }));
1467
- }
1468
- await settleTeardownLeg(() => this.teardownOwnedEnv(prepared), "teardownOwnedEnv", (e) => this.deps.onError?.(e, { phase: "config", sessionId: prepared.sessionId }));
1469
- }
1470
515
  }
1471
516
  async suggestNextPrompts(spec, prepared, result) {
1472
517
  const cfg = typeof spec.suggestNextPrompts === "object" ? spec.suggestNextPrompts : {};