@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
@@ -3,7 +3,7 @@ import { createSafeNotifier } from "../safe-notify.js";
3
3
  import { discloseDroppedPending, renderTaskNotificationXml, taskNotificationDedupKey } from "../task-notification.js";
4
4
  import { deliverDelegationLifecycle, deliverEngineNotice, undrainedUserInputNotices } from "../types.js";
5
5
  export function runIdentityWiring(input) {
6
- const { spec, queue, prepared, internals, taskIdRef, onReady, manualCompactRef, notifyRef, captureOptOutRef, injectTaskNotification, deliveredAtTurnOpen, peerInboundChainRef, notificationLane, undrainedUserAtEnd, pendingSessionNotifications, runner } = input;
6
+ const { spec, queue, prepared, internals, taskIdRef, onReady, manualCompactRef, notifyRef, captureOptOutRef, injectTaskNotification, deliveredAtTurnOpen, peerInboundChainRef, notificationLane, pendingSessionNotifications, runner } = input;
7
7
  notificationLane.harness = prepared.harness;
8
8
  notificationLane.sessionId = prepared.sessionId;
9
9
  if (taskIdRef)
@@ -40,9 +40,10 @@ export function runIdentityWiring(input) {
40
40
  pendingSessionNotifications.pend(notificationLane.sessionId, p);
41
41
  };
42
42
  prepared.harness.engineInjectionsHeld = () => prepared.batchHaltRef.current !== undefined;
43
+ let undrainedUserAtEnd;
43
44
  prepared.harness.onUndrainedUserInputs = (counts) => {
44
45
  if (prepared.pausedRef.current !== undefined) {
45
- undrainedUserAtEnd.current = counts;
46
+ undrainedUserAtEnd = counts;
46
47
  return;
47
48
  }
48
49
  for (const notice of undrainedUserInputNotices(counts, spec.taskId ?? prepared.sessionId, prepared.sessionId, prepared.runId)) {
@@ -80,5 +81,5 @@ export function runIdentityWiring(input) {
80
81
  const loopLatch = { ended: false, userInterrupted: false, userHalted: false };
81
82
  onReady({ harness: prepared.harness, abortController: prepared.abortController, loop: loopLatch, reminderMark: prepared.reminderMark, sessionId: prepared.sessionId, runId: prepared.runId, hookTimeoutMs: prepared.hookTimeoutMs, hookIdentity: prepared.hookIdentity });
82
83
  const stats = { turns: 0, tokens: 0, toolCalls: 0, promptTokens: 0, totalInputTokens: 0, cachedTokens: 0, cacheWriteTokens: 0, cacheWriteTokensLong: 0, outputTokens: 0, costMicroUsd: 0 };
83
- return { runSourceTaskId, parentToolCallId, ident, emitDelegationLifecycle, loopLatch, stats };
84
+ return { runSourceTaskId, parentToolCallId, ident, emitDelegationLifecycle, loopLatch, stats, undrainedUserAtEnd: { get current() { return undrainedUserAtEnd; } } };
84
85
  }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * design/393 S6 — the run body's LEG (R13), verbatim from `Runner.runLocked`: the one `harness.prompt` of the leg and
3
+ * everything that decides whether it is issued. Two legs behind one try / catch / finally: the RESUME leg (the walltime
4
+ * and governance-window entrance claims, the gated decision's application, the exhaustion arms, the continuation prompt's
5
+ * assembly — orphaned background tasks, the git frame, the parked steer queue and the wake message each under its own trust
6
+ * framing and redelivery screen — and the prompt itself) and the OBJECTIVE leg (the UserPromptSubmit screen, the git
7
+ * frame's primary delivery, the first-frame listings, the pre-call budget / token / entry-governance gates, the image
8
+ * conversion, the objective's human-input frame and the prompt itself). The tail captures the abort facts; the catch
9
+ * re-throws the resume-equivalent env failures after a bounded teardown and adopts everything else as the leg's throw;
10
+ * the finally releases the timer, the diagnostics, the signal, the content-ask bindings, flips the notification lane
11
+ * and releases the four subscriptions in their order.
12
+ *
13
+ * Async (twenty first-level awaits, the same awaits at the same positions). It ends by entering the terminal adoption
14
+ * (`next`) in its LAST continuation — the adoption's first read sits on the tick the finally ran on, as it did in the
15
+ * one-function body — and returns what it returns. The two legs stay ONE function on purpose: splitting them into two
16
+ * async functions (the design's "internal split") costs a microtask on the leg → tail edge, and the tail's first reads
17
+ * there — `abortController.signal.aborted`, `loopLatch.userInterrupted` — are host-mutable attribution facts a late read
18
+ * would mis-sign (the adversarial r2 HIGH-1 finding recorded on them); the forwarded-frame depth ratchet measured the
19
+ * extra hop (58 → 59) when the split was tried.
20
+ */
21
+ import type { AssistantMessage } from "../../internal/llm.js";
22
+ import { type ActorAssertion } from "../checkpoint-store.js";
23
+ import type { PushQueue } from "../push-queue.js";
24
+ import { type TaskEvent, type TaskSpec } from "../types.js";
25
+ import { type WriteFamily } from "./turn-attachments.js";
26
+ import type { NotificationLaneBindings, Prepared, ResumeRun, RunnerDepsSeat, RunState } from "./contracts.js";
27
+ export interface RunLegInput<R> {
28
+ /** borrowed-readonly — the task spec: the objective, the images, the hooks slot, the principal, the task id. */
29
+ spec: TaskSpec;
30
+ /** borrowed-mutable — the run's event queue: the human-input frames, the git echo frames, the listing echo frames. */
31
+ queue: PushQueue<TaskEvent>;
32
+ /** borrowed-mutable — the leg's prepared seat: the harness is prompted, the blocked / git / listing refs written, the abort controller aborted and the signal released in the finally. */
33
+ prepared: Prepared;
34
+ /** borrowed-readonly — the resume plan when this leg resumes a checkpoint: chooses the resume leg; its delivery bits are written. */
35
+ resume: ResumeRun | undefined;
36
+ /** borrowed-mutable — the run's mutable state: the limit flags and the platform terminal are written by the entrance gates. */
37
+ rs: RunState;
38
+ /** borrowed-readonly — the API-boundary attribution snapshot of the objective's actor (the objective leg's speaker envelope). */
39
+ entryActor: ActorAssertion | undefined;
40
+ /** borrowed-readonly — the effective walltime window (undefined = unbounded): `<= 0` refuses the first turn. */
41
+ effectiveTimeoutMs: number | undefined;
42
+ /** borrowed-mutable — the hard walltime timer: claimed (`fired`) and cleared by the exhausted arms and the finally. */
43
+ timeout: {
44
+ fired: boolean;
45
+ clear: () => void;
46
+ };
47
+ /** borrowed-readonly — the content push (the resumed batch's frames ride it with the leg's identity). */
48
+ pushContent: (e: TaskEvent) => void;
49
+ /** borrowed-readonly — the run's event-identity mint. */
50
+ ident: () => {
51
+ eventId: string;
52
+ parentToolCallId?: string;
53
+ sourceTaskId?: string;
54
+ };
55
+ /** borrowed-readonly — the `message_committed` mint (the resumed batch's re-applied tool results). */
56
+ emitCommitted: (entryId: string, role: "user" | "assistant" | "toolResult", toolCallId?: string) => void;
57
+ /** borrowed-readonly — the write-family resolver (the resume-executed gated write's attachment anchor). */
58
+ writeFamilyOf: (name: string) => WriteFamily | undefined;
59
+ /** borrowed-mutable — the observed tool-start ids: the resume-executed gated call registers itself here. */
60
+ startedToolCallIds: Set<string>;
61
+ /** borrowed-readonly — the harness-handlers lane's workspace-move announcer (the approved `cd`'s frame). */
62
+ announceWorkspaceMove: () => void;
63
+ /** borrowed-readonly — the brain-sinks composition the leg's prompt runs under. */
64
+ withBrainSinks: <T>(fn: () => Promise<T>) => Promise<T>;
65
+ /** borrowed-readonly — the git lane's parked-mirror flush, settled after the prompt (single-turn legs reach no boundary). */
66
+ flushGitMirror: () => Promise<void>;
67
+ /** borrowed-mutable — the loop-liveness latch: `ended` flips when the prompt settles (or a no-prompt / throw arm is taken); `userInterrupted` is snapshotted. */
68
+ loopLatch: {
69
+ ended: boolean;
70
+ readonly userInterrupted: boolean;
71
+ };
72
+ /** borrowed-mutable — the notification lane's bindings seat: `live` flips false in the finally (every later notification parks). */
73
+ notificationLane: NotificationLaneBindings;
74
+ /** borrowed-readonly — the notification lane's subscription release, called in the finally right after the flip. */
75
+ unsubscribeTaskNotifications: () => void;
76
+ /** borrowed-readonly — the git lane's owed-frame retry release, called in the finally first of the three. */
77
+ unsubGitRetry: () => void;
78
+ /** borrowed-readonly — the git lane's boundary-handler release, called second. */
79
+ unsubBoundary: () => void;
80
+ /** borrowed-readonly — the git lane's harness-event release, called third. */
81
+ unsub: () => void;
82
+ /** borrowed-readonly — the Runner's deployment deps, read LIVE (`onError`, `hooks`, `onNotice`, `allowImageUrl`); the one home of
83
+ * why it is a seat and not a captured object is {@link RunnerDepsSeat}. */
84
+ runner: RunnerDepsSeat;
85
+ /** borrowed-readonly — the Runner's application of the resumed decision (its private method, as a delegate). */
86
+ applyResumeDecision: (prepared: Prepared, resume: ResumeRun, emit: (e: TaskEvent) => void, emitCommitted: (entryId: string, role: "user" | "assistant" | "toolResult", toolCallId?: string) => void, onResolvedToolSuccess?: (toolName: string, details: unknown) => void, onExecuteStart?: (toolCallId: string) => void) => Promise<void>;
87
+ /** borrowed-readonly — the terminal adoption, entered in this lane's last continuation with the leg's facts. */
88
+ next: (leg: RunLegResult) => Promise<R>;
89
+ }
90
+ /** The leg's facts — what the terminal adoption and the settle read. */
91
+ export interface RunLegResult {
92
+ /** The final assistant message; undefined on every no-prompt arm (blocked, refused, exhausted). */
93
+ final: AssistantMessage | undefined;
94
+ /** What the leg threw (undefined on a clean end); the terminal adoption may lift a typed cause over it. */
95
+ threw: unknown;
96
+ /** The live-abort flag, captured before the finally's unconditional release-abort. */
97
+ abortedLive: boolean;
98
+ /** The snapshot of `loopLatch.userInterrupted`, taken with `abortedLive`. */
99
+ userInterruptedLive: boolean;
100
+ /** design/174: the answered-but-never-collected deliveries the finally settled. */
101
+ strandedHumanAnswers: ReadonlyArray<{
102
+ deliveryId: string;
103
+ toolCallId: string;
104
+ }>;
105
+ }
106
+ export declare function runLeg<R>(input: RunLegInput<R>): Promise<R>;
@@ -0,0 +1,462 @@
1
+ import { snapshotActorAssertion } from "../../internal/llm.js";
2
+ import { redactThenCut } from "../../agents/subagent-steps.js";
3
+ import { readPendingSteerQueue } from "../checkpoint-store.js";
4
+ import { formatHookFeedback, hookSeatExpiredError, runHookSeat } from "../hooks.js";
5
+ import { buildHumanInputEvent, projectHumanInput } from "../human-input-projection.js";
6
+ import { computeCostMicroUsd } from "../pricing.js";
7
+ import { mintSystemReminder } from "../reminder-mint.js";
8
+ import { hasDestroy } from "../remote-env.js";
9
+ import { deliverEngineNotice } from "../types.js";
10
+ import { delimitUntrusted, inlineUntrusted, sanitizeUntrustedText } from "../untrusted-text.js";
11
+ import { errorCodeOf } from "./assemble-result.js";
12
+ import { DEFAULT_PRECALL_OUTPUT_TOKENS, platformLimitTerminal } from "./clock-and-limits.js";
13
+ import { resumeContinuation } from "./decide-continuation.js";
14
+ import { resolveGitLegDelivery, wrapGitFrame } from "./git-leg-delivery.js";
15
+ import { GIT_STATUS_ECHO_PREVIEW } from "./git-status-frame.js";
16
+ import { toImageContent } from "./image.js";
17
+ import { nextHumanInputSeq } from "./steer-admission.js";
18
+ import { settleTeardownLeg } from "./teardown-bounded.js";
19
+ import { attachmentEnvelopeTags, commitAgentListing, commitSkillsListing, reduceToolEnd, renderAgentListingDelta, renderOrphanedBackgroundTasks, renderSkillsListingDelta, stampWriteAnchor } from "./turn-attachments.js";
20
+ export async function runLeg(input) {
21
+ const { spec, queue, prepared, resume, rs, entryActor, effectiveTimeoutMs, timeout, pushContent, ident, emitCommitted, writeFamilyOf, startedToolCallIds, announceWorkspaceMove, withBrainSinks, flushGitMirror, loopLatch, notificationLane, unsubscribeTaskNotifications, unsubGitRetry, unsubBoundary, unsub, runner, applyResumeDecision, next } = input;
22
+ let final;
23
+ let threw;
24
+ let abortedLive = false;
25
+ let userInterruptedLive = false;
26
+ let strandedHumanAnswers = [];
27
+ try {
28
+ if (prepared.abortController.signal.aborted) {
29
+ const e = new Error("run aborted");
30
+ e.name = "AbortError";
31
+ throw e;
32
+ }
33
+ if (resume) {
34
+ const walltimeExhaustedResume = effectiveTimeoutMs !== undefined && effectiveTimeoutMs <= 0;
35
+ const resumeWindowRetryAfterMs = !walltimeExhaustedResume && prepared.usageGovernance !== undefined ? await prepared.usageGovernance.check(Date.now()) : undefined;
36
+ if (resumeWindowRetryAfterMs !== undefined) {
37
+ const owesNothing = resume.outcome.gate === "resource_limit" &&
38
+ readPendingSteerQueue(resume.cp.state).length === 0 &&
39
+ (resume.cp.state.runningBackgroundTasks?.length ?? 0) === 0;
40
+ const committed = owesNothing &&
41
+ prepared.suspendForPlatformLimit !== undefined &&
42
+ (await prepared.suspendForPlatformLimit("usage_window", { costMicroUsd: 0, tokens: 0, turns: 0, walltimeMs: Math.round(performance.now() - rs.telemetry.taskStartMonotonic) }, { resumeAfterMs: resumeWindowRetryAfterMs }));
43
+ if (committed) {
44
+ rs.limits.turnsExceeded = false;
45
+ rs.limits.budgetHit = undefined;
46
+ }
47
+ else {
48
+ const terminal = platformLimitTerminal("usage_window", resumeWindowRetryAfterMs, "resume");
49
+ rs.limits.platformTerminal = terminal;
50
+ try {
51
+ runner.deps.onError?.(terminal, { phase: "config", sessionId: prepared.sessionId });
52
+ }
53
+ catch {
54
+ }
55
+ }
56
+ }
57
+ if (resumeWindowRetryAfterMs === undefined && !walltimeExhaustedResume && resume.outcome.gate !== "wake") {
58
+ await applyResumeDecision(prepared, resume, (e) => pushContent({ ...e, ...ident() }), emitCommitted, (toolName, details) => {
59
+ if (rs.attach.attachState === undefined)
60
+ return;
61
+ const family = writeFamilyOf(toolName);
62
+ if (family !== undefined)
63
+ stampWriteAnchor(rs.attach.attachState, family, rs.counters.cadenceTurns);
64
+ if (details !== undefined)
65
+ reduceToolEnd(rs.attach.attachState, details);
66
+ }, (toolCallId) => {
67
+ startedToolCallIds.add(toolCallId);
68
+ if (resume !== undefined && resume.cp.pendingAction.kind === "tool_approval" && toolCallId === resume.cp.pendingAction.toolCallId) {
69
+ resume.pendingActionStarted = true;
70
+ }
71
+ });
72
+ if (resume.outcome.gate === "policy_ask")
73
+ resume.decisionDelivered = true;
74
+ announceWorkspaceMove();
75
+ }
76
+ if (resumeWindowRetryAfterMs !== undefined) {
77
+ }
78
+ else if (walltimeExhaustedResume) {
79
+ timeout.fired = true;
80
+ timeout.clear();
81
+ void prepared.harness.abort();
82
+ prepared.abortController.abort();
83
+ }
84
+ else if (rs.budget.maxTokensWindow !== undefined && rs.budget.maxTokensWindow <= 0) {
85
+ rs.limits.budgetHit = "exceeded";
86
+ rs.limits.budgetAxis = "tokens";
87
+ }
88
+ else if (rs.budget.maxCostMicroUsd !== undefined && rs.budget.maxCostMicroUsd <= 0) {
89
+ rs.limits.budgetHit = "exceeded";
90
+ rs.limits.budgetAxis = "cost";
91
+ }
92
+ else {
93
+ let continuation = resumeContinuation(resume, prepared.reminderMark);
94
+ const bgSnapshot = resume.cp.state.runningBackgroundTasks;
95
+ if (bgSnapshot !== undefined && bgSnapshot.length > 0) {
96
+ const alive = new Set(prepared.listBackgroundTasks().map((t) => t.id));
97
+ const orphans = bgSnapshot.filter((t) => !alive.has(t.id));
98
+ if (orphans.length > 0) {
99
+ continuation += "\n\n" + formatHookFeedback(renderOrphanedBackgroundTasks(orphans), prepared.reminderMark);
100
+ }
101
+ }
102
+ let gitResumeDelivered;
103
+ {
104
+ const gitBody = await resolveGitLegDelivery(prepared, resume.cp.state.gitAnnouncement, (err) => {
105
+ try {
106
+ runner.deps.onError?.(new Error(`git announcement ladder unreadable (conservative re-announce): ${err.message}`), { phase: "degraded", sessionId: prepared.sessionId });
107
+ }
108
+ catch {
109
+ }
110
+ });
111
+ const gitFrame = prepared.gitStatusRef.frame;
112
+ if (gitBody !== undefined && gitFrame !== undefined) {
113
+ const wrappedGit = wrapGitFrame(gitBody, prepared.reminderMark);
114
+ continuation += "\n\n" + wrappedGit;
115
+ gitResumeDelivered = { kind: gitFrame.kind, hash: gitFrame.hash, wrapped: wrappedGit };
116
+ if (gitFrame.kind === "full" && gitFrame.shrunk !== undefined) {
117
+ prepared.gitStatusRef.wrappedShrink = { find: wrappedGit, replace: wrapGitFrame(gitFrame.shrunk.body, prepared.reminderMark) };
118
+ }
119
+ }
120
+ }
121
+ const engineSegments = continuation.length > 0 ? [{ start: 0, end: continuation.length }] : [];
122
+ const resumeFrames = [
123
+ ...readPendingSteerQueue(resume.cp.state).map((entry) => ({ entry, source: "steer" })),
124
+ ...(resume.wakeMessage !== undefined ? [{ entry: resume.wakeMessage, source: "wake" }] : []),
125
+ ];
126
+ for (const { entry: steer, source } of resumeFrames) {
127
+ if (source === "wake" && resume.wakeMessageHookContext !== undefined) {
128
+ const hookStart = continuation.length;
129
+ continuation += "\n\n" + formatHookFeedback(resume.wakeMessageHookContext, prepared.reminderMark);
130
+ engineSegments.push({ start: hookStart, end: continuation.length });
131
+ }
132
+ if (source === "steer") {
133
+ const parkedScreen = (spec.hooks ?? runner.deps.hooks)?.userPromptSubmit;
134
+ if (parkedScreen !== undefined) {
135
+ let outcome;
136
+ try {
137
+ const seat = await runHookSeat("userPromptSubmit", { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal, abortEnds: true }, (sig) => parkedScreen(steer.text, {
138
+ identity: prepared.hookIdentity,
139
+ signal: sig,
140
+ source: "parked_redelivery",
141
+ ...(steer.inputId !== undefined ? { inputId: steer.inputId } : {}),
142
+ ...(steer.actor !== undefined ? { actor: snapshotActorAssertion(steer.actor) } : {}),
143
+ }));
144
+ outcome = seat.expired
145
+ ? { 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)" }
146
+ : seat.value?.block !== undefined && seat.value.block !== ""
147
+ ? { blocked: inlineUntrusted(seat.value.block) }
148
+ : { ...(seat.value?.additionalContext !== undefined && seat.value.additionalContext !== "" ? { context: seat.value.additionalContext } : {}) };
149
+ }
150
+ catch (hookErr) {
151
+ const err = hookErr instanceof Error ? hookErr : new Error(String(hookErr));
152
+ try {
153
+ runner.deps.onError?.(err, { phase: "hook", sessionId: prepared.sessionId });
154
+ }
155
+ catch {
156
+ }
157
+ outcome = { blocked: `the screen crashed (${inlineUntrusted(err.message)}; fail-closed)` };
158
+ }
159
+ if ("blocked" in outcome) {
160
+ deliverEngineNotice(runner.deps.onNotice, {
161
+ code: "steering.parked_input_blocked",
162
+ message: "a parked steering input was withheld by the deployment's userPromptSubmit screen when this resume redelivered it: " +
163
+ `${outcome.blocked}. The parked row was consumed with the checkpoint; the instruction never reached the model — ` +
164
+ "re-issue it if it still applies.",
165
+ detail: {
166
+ ...(steer.inputId !== undefined ? { inputId: steer.inputId } : {}),
167
+ sessionId: prepared.sessionId,
168
+ runId: prepared.runId,
169
+ ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}),
170
+ },
171
+ });
172
+ queue.push({
173
+ ...buildHumanInputEvent({
174
+ carrier: source,
175
+ source,
176
+ delivery: "blocked",
177
+ sessionSeq: nextHumanInputSeq(prepared.harness),
178
+ ...(steer.inputId !== undefined ? { inputId: steer.inputId } : {}),
179
+ ...(steer.actor !== undefined ? { actor: steer.actor } : {}),
180
+ ...(steer.actor?.issuer !== undefined ? { issuer: steer.actor.issuer } : {}),
181
+ ...(spec.principal !== undefined ? { principal: spec.principal } : {}),
182
+ }),
183
+ ...ident(),
184
+ });
185
+ continue;
186
+ }
187
+ if ("context" in outcome && outcome.context !== undefined) {
188
+ const hookStart = continuation.length;
189
+ continuation += "\n\n" + formatHookFeedback(outcome.context, prepared.reminderMark);
190
+ engineSegments.push({ start: hookStart, end: continuation.length });
191
+ }
192
+ }
193
+ }
194
+ const start = continuation.length;
195
+ const projected = projectHumanInput({ text: steer.text, actor: steer.actor, source });
196
+ continuation +=
197
+ "\n\n" +
198
+ (steer.trusted ? formatHookFeedback(projected, prepared.reminderMark) : delimitUntrusted("supervisor steering message", projected));
199
+ if (steer.trusted)
200
+ engineSegments.push({ start, end: continuation.length });
201
+ queue.push({
202
+ ...buildHumanInputEvent({
203
+ carrier: source,
204
+ source,
205
+ delivery: "applied",
206
+ sessionSeq: nextHumanInputSeq(prepared.harness),
207
+ inputId: steer.inputId,
208
+ ...(steer.actor !== undefined ? { actor: steer.actor } : {}),
209
+ ...(steer.actor?.issuer !== undefined ? { issuer: steer.actor.issuer } : {}),
210
+ ...(spec.principal !== undefined ? { principal: spec.principal } : {}),
211
+ }),
212
+ ...ident(),
213
+ });
214
+ }
215
+ if (resume !== undefined)
216
+ resume.decisionDelivered = true;
217
+ if (gitResumeDelivered !== undefined) {
218
+ const delivered = gitResumeDelivered;
219
+ prepared.gitStatusRef.protectedText = delivered.wrapped;
220
+ prepared.gitStatusRef.pendingReceipt = {
221
+ text: continuation,
222
+ commit: (entryId) => {
223
+ prepared.gitStatusRef.announced = { kind: delivered.kind, hash: delivered.hash, entryId };
224
+ queue.push({ type: "steering_injected", source: "git_status", preview: GIT_STATUS_ECHO_PREVIEW[delivered.kind], ...ident() });
225
+ rs.attach.attachmentsInjected += 1;
226
+ prepared.gitStatusRef.mirrorOwed = { kind: delivered.kind, hash: delivered.hash, entryId };
227
+ },
228
+ };
229
+ }
230
+ final = await withBrainSinks(() => prepared.harness.prompt(continuation, { engineSegments }));
231
+ await flushGitMirror();
232
+ if (prepared.gitStatusRef.announced?.pending === true)
233
+ await prepared.gitStatusRef.reassert?.();
234
+ }
235
+ }
236
+ else {
237
+ const objectiveActor = entryActor;
238
+ const projectedObjective = projectHumanInput({ text: spec.objective, actor: objectiveActor, source: "objective" });
239
+ let effectiveObjective = projectedObjective;
240
+ let promptBlocked = false;
241
+ const userPromptSubmit = (spec.hooks ?? runner.deps.hooks)?.userPromptSubmit;
242
+ if (userPromptSubmit) {
243
+ try {
244
+ 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) } : {}) }));
245
+ if (promptSeat.expired) {
246
+ if (promptSeat.cause === "timeout") {
247
+ try {
248
+ runner.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 });
249
+ }
250
+ catch {
251
+ }
252
+ }
253
+ prepared.blockedRef.reason = formatHookFeedback(promptSeat.cause === "timeout"
254
+ ? `the deployment's userPromptSubmit hook did not answer within its ${prepared.hookTimeoutMs}ms bound while screening this prompt; ` +
255
+ `the prompt was NOT submitted (fail-closed)`
256
+ : `the task was cancelled while the deployment's userPromptSubmit hook was still screening this prompt; ` +
257
+ `the prompt was NOT submitted (fail-closed)`, prepared.reminderMark);
258
+ promptBlocked = true;
259
+ }
260
+ const decision = promptSeat.expired ? undefined : promptSeat.value;
261
+ if (decision?.block) {
262
+ prepared.blockedRef.reason = formatHookFeedback(decision.block, prepared.reminderMark);
263
+ promptBlocked = true;
264
+ }
265
+ else if (decision?.additionalContext) {
266
+ effectiveObjective = `${formatHookFeedback(decision.additionalContext, prepared.reminderMark)}\n\n${projectedObjective}`;
267
+ }
268
+ }
269
+ catch (hookErr) {
270
+ const err = hookErr instanceof Error ? hookErr : new Error(String(hookErr));
271
+ try {
272
+ runner.deps.onError?.(err, { phase: "hook", sessionId: prepared.sessionId });
273
+ }
274
+ catch {
275
+ }
276
+ prepared.blockedRef.reason = formatHookFeedback(`the deployment's userPromptSubmit hook crashed while screening this prompt (${err.message}); ` +
277
+ `the prompt was NOT submitted (fail-closed)`, prepared.reminderMark);
278
+ promptBlocked = true;
279
+ }
280
+ }
281
+ let gitLegDelivered;
282
+ if (!promptBlocked) {
283
+ const gitBody = await resolveGitLegDelivery(prepared, undefined, (err) => {
284
+ try {
285
+ runner.deps.onError?.(new Error(`git announcement ladder unreadable (conservative re-announce): ${err.message}`), { phase: "degraded", sessionId: prepared.sessionId });
286
+ }
287
+ catch {
288
+ }
289
+ });
290
+ const gitFrame = prepared.gitStatusRef.frame;
291
+ if (gitBody !== undefined && gitFrame !== undefined) {
292
+ const wrappedGit = wrapGitFrame(gitBody, prepared.reminderMark);
293
+ const standalone = spec.images !== undefined && spec.images.length > 0;
294
+ if (!standalone)
295
+ effectiveObjective = `${wrappedGit}\n${effectiveObjective}`;
296
+ gitLegDelivered = { kind: gitFrame.kind, hash: gitFrame.hash, standalone, wrapped: wrappedGit };
297
+ if (gitFrame.kind === "full" && gitFrame.shrunk !== undefined) {
298
+ prepared.gitStatusRef.wrappedShrink = { find: wrappedGit, replace: wrapGitFrame(gitFrame.shrunk.body, prepared.reminderMark) };
299
+ }
300
+ }
301
+ }
302
+ const firstFrames = [];
303
+ if (rs.attach.attachState !== undefined) {
304
+ const attach = rs.attach.attachState;
305
+ if (rs.attach.agentListingOn && prepared.agentListing !== undefined) {
306
+ const listing = prepared.agentListing;
307
+ const body = renderAgentListingDelta(attach, listing.entries, listing.toolName, listing.models);
308
+ if (body !== undefined) {
309
+ firstFrames.push({
310
+ source: "agent_listing",
311
+ body,
312
+ commit: () => {
313
+ commitAgentListing(attach, listing.entries, listing.models);
314
+ prepared.announcedListingsRef.agents = listing.entries.map((e) => e.name);
315
+ if (listing.models !== undefined)
316
+ prepared.announcedListingsRef.models = [...listing.models];
317
+ },
318
+ });
319
+ }
320
+ }
321
+ if (rs.attach.skillsListingOn && prepared.skillsListing !== undefined) {
322
+ const listing = prepared.skillsListing;
323
+ const body = renderSkillsListingDelta(attach, listing.entries);
324
+ if (body !== undefined) {
325
+ firstFrames.push({
326
+ source: "skills_listing",
327
+ body,
328
+ commit: () => {
329
+ commitSkillsListing(attach, listing.entries);
330
+ prepared.announcedListingsRef.skills = listing.entries.map((e) => e.name);
331
+ },
332
+ });
333
+ }
334
+ }
335
+ if (firstFrames.length > 0) {
336
+ effectiveObjective = `${firstFrames.map((f) => mintSystemReminder(sanitizeUntrustedText(f.body, attachmentEnvelopeTags(f.source)), prepared.reminderMark)).join("\n")}\n${effectiveObjective}`;
337
+ }
338
+ }
339
+ const gitQueuedChars = gitLegDelivered !== undefined && gitLegDelivered.standalone ? gitLegDelivered.wrapped.length : 0;
340
+ const precallMicroUsd = rs.budget.maxCostMicroUsd === undefined
341
+ ? 0
342
+ : computeCostMicroUsd({
343
+ totalInputTokens: Math.ceil((effectiveObjective.length + gitQueuedChars) / 4),
344
+ cacheReadTokens: 0,
345
+ cacheWriteTokens: 0,
346
+ cacheWriteTokensLong: 0,
347
+ outputTokens: prepared.model.maxTokens ?? DEFAULT_PRECALL_OUTPUT_TOKENS,
348
+ }, rs.telemetry.pricing);
349
+ const precallCeilingMicroUsd = prepared.suspendForResource !== undefined ? rs.budget.remainingMicroUsd : rs.budget.maxCostMicroUsd;
350
+ const precallTokens = Math.ceil((effectiveObjective.length + gitQueuedChars) / 4) + (prepared.model.maxTokens ?? DEFAULT_PRECALL_OUTPUT_TOKENS);
351
+ const precallCeilingTokens = prepared.suspendForResource !== undefined ? rs.budget.remainingTokens : rs.budget.maxTokensWindow;
352
+ const entryUsageRetryAfterMs = prepared.usageGovernance !== undefined ? await prepared.usageGovernance.check(Date.now()) : undefined;
353
+ if (promptBlocked) {
354
+ }
355
+ else if (entryUsageRetryAfterMs !== undefined) {
356
+ const terminal = platformLimitTerminal("usage_window", entryUsageRetryAfterMs, "entry");
357
+ rs.limits.platformTerminal = terminal;
358
+ try {
359
+ runner.deps.onError?.(terminal, { phase: "config", sessionId: prepared.sessionId });
360
+ }
361
+ catch {
362
+ }
363
+ }
364
+ else if (precallCeilingTokens !== undefined && precallTokens > precallCeilingTokens) {
365
+ rs.limits.budgetHit = "precall";
366
+ rs.limits.budgetAxis = "tokens";
367
+ }
368
+ else if (precallCeilingMicroUsd !== undefined && precallMicroUsd > precallCeilingMicroUsd) {
369
+ rs.limits.budgetHit = "precall";
370
+ rs.limits.budgetAxis = "cost";
371
+ }
372
+ else if (effectiveTimeoutMs !== undefined && effectiveTimeoutMs <= 0) {
373
+ timeout.fired = true;
374
+ timeout.clear();
375
+ void prepared.harness.abort();
376
+ prepared.abortController.abort();
377
+ }
378
+ else {
379
+ const images = spec.images
380
+ ? await Promise.all(spec.images.map((img) => toImageContent(img, runner.deps.allowImageUrl)))
381
+ : undefined;
382
+ if (gitLegDelivered !== undefined) {
383
+ const delivered = gitLegDelivered;
384
+ if (delivered.standalone)
385
+ await prepared.harness.nextTurn(delivered.wrapped, { engineMinted: true });
386
+ prepared.gitStatusRef.protectedText = delivered.wrapped;
387
+ prepared.gitStatusRef.pendingReceipt = {
388
+ text: delivered.standalone ? delivered.wrapped : effectiveObjective,
389
+ commit: (entryId) => {
390
+ prepared.gitStatusRef.announced = { kind: delivered.kind, hash: delivered.hash, entryId };
391
+ queue.push({ type: "steering_injected", source: "git_status", preview: GIT_STATUS_ECHO_PREVIEW[delivered.kind], ...ident() });
392
+ rs.attach.attachmentsInjected += 1;
393
+ prepared.gitStatusRef.mirrorOwed = { kind: delivered.kind, hash: delivered.hash, entryId };
394
+ },
395
+ };
396
+ }
397
+ for (const f of firstFrames) {
398
+ f.commit();
399
+ queue.push({ type: "steering_injected", source: f.source, preview: redactThenCut(f.body, 220), ...ident() });
400
+ }
401
+ rs.attach.attachmentsInjected += firstFrames.length;
402
+ if (firstFrames.length > 0 && prepared.announcedSnapshotRecovered)
403
+ await prepared.session.appendAnnouncedListing(prepared.announcedListingsRef).catch(() => undefined);
404
+ const enginePrefixChars = effectiveObjective.length - projectedObjective.length;
405
+ queue.push({
406
+ ...buildHumanInputEvent({
407
+ carrier: "objective",
408
+ source: "objective",
409
+ delivery: "applied",
410
+ sessionSeq: nextHumanInputSeq(prepared.harness),
411
+ ...(objectiveActor !== undefined ? { actor: objectiveActor } : {}),
412
+ ...(objectiveActor?.issuer !== undefined ? { issuer: objectiveActor.issuer } : {}),
413
+ ...(spec.principal !== undefined ? { principal: spec.principal } : {}),
414
+ }),
415
+ ...ident(),
416
+ });
417
+ final = await withBrainSinks(() => prepared.harness.prompt(effectiveObjective, {
418
+ ...(images && images.length > 0 ? { images } : {}),
419
+ ...(enginePrefixChars > 0 ? { enginePrefixChars } : {}),
420
+ ...(objectiveActor !== undefined ? { actor: objectiveActor } : {}),
421
+ }));
422
+ await flushGitMirror();
423
+ if (prepared.gitStatusRef.announced?.pending === true)
424
+ await prepared.gitStatusRef.reassert?.();
425
+ }
426
+ }
427
+ loopLatch.ended = true;
428
+ abortedLive = prepared.abortController.signal.aborted;
429
+ userInterruptedLive = loopLatch.userInterrupted;
430
+ }
431
+ catch (err) {
432
+ loopLatch.ended = true;
433
+ if (errorCodeOf(err) === "resume.tool_unavailable" || errorCodeOf(err) === "resume.tool_contract_mismatch") {
434
+ await settleTeardownLeg(() => prepared.mcp.dispose(), "mcp.dispose (tool_unavailable leg)", (e) => runner.deps.onError?.(e, { phase: "config", sessionId: prepared.sessionId }));
435
+ await settleTeardownLeg(() => prepared.a2a?.dispose(), "a2a.dispose (tool_unavailable leg)", (e) => runner.deps.onError?.(e, { phase: "config", sessionId: prepared.sessionId }));
436
+ await settleTeardownLeg(() => (prepared.ownedEnv && hasDestroy(prepared.ownedEnv) ? prepared.ownedEnv.destroy() : undefined), "ownedEnv.destroy (tool_unavailable leg)", (e) => runner.deps.onError?.(e, { phase: "config", sessionId: prepared.sessionId }));
437
+ throw err;
438
+ }
439
+ threw = err;
440
+ abortedLive = prepared.abortController.signal.aborted;
441
+ userInterruptedLive = loopLatch.userInterrupted;
442
+ }
443
+ finally {
444
+ timeout.clear();
445
+ prepared.lspDiagnostics?.registry.releaseRun(prepared.lspDiagnostics.runIdent);
446
+ prepared.abortController.abort();
447
+ prepared.releaseSignal();
448
+ strandedHumanAnswers = prepared.settleContentAskBindings();
449
+ try {
450
+ prepared.harness.recoverUndrainedEngineNotes();
451
+ }
452
+ catch {
453
+ }
454
+ notificationLane.live = false;
455
+ unsubscribeTaskNotifications();
456
+ prepared.toolRosterDeltas.unsubscribe();
457
+ unsubGitRetry();
458
+ unsubBoundary();
459
+ unsub();
460
+ }
461
+ return await next({ final, threw, abortedLive, userInterruptedLive, strandedHumanAnswers });
462
+ }