@sema-agent/core 7.11.1 → 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 (78) hide show
  1. package/CHANGELOG.md +41 -0
  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/compaction-knobs.d.ts +45 -0
  15. package/dist/core/runner/compaction-knobs.js +3 -0
  16. package/dist/core/runner/contracts.d.ts +49 -10
  17. package/dist/core/runner/denial-limit-arms.d.ts +10 -13
  18. package/dist/core/runner/denial-limit-arms.js +9 -7
  19. package/dist/core/runner/gate-exit.js +9 -1
  20. package/dist/core/runner/prepare-caps-and-workflow.d.ts +1 -1
  21. package/dist/core/runner/prepare-caps-and-workflow.js +0 -5
  22. package/dist/core/runner/prepare-suspend-saga.d.ts +0 -2
  23. package/dist/core/runner/prepare-suspend-saga.js +2 -10
  24. package/dist/core/runner/prepare-task.js +1 -1
  25. package/dist/core/runner/prepare-wiring-manifest.d.ts +1 -1
  26. package/dist/core/runner/prepare-wiring-manifest.js +1 -8
  27. package/dist/core/runner/run-attachment-seats.d.ts +22 -0
  28. package/dist/core/runner/run-attachment-seats.js +187 -0
  29. package/dist/core/runner/run-brain-sinks.d.ts +29 -0
  30. package/dist/core/runner/run-brain-sinks.js +61 -0
  31. package/dist/core/runner/run-clock-and-content.d.ts +52 -0
  32. package/dist/core/runner/run-clock-and-content.js +26 -0
  33. package/dist/core/runner/run-compaction-machinery.d.ts +35 -0
  34. package/dist/core/runner/run-compaction-machinery.js +98 -0
  35. package/dist/core/runner/run-git-lane.d.ts +64 -0
  36. package/dist/core/runner/run-git-lane.js +102 -0
  37. package/dist/core/runner/run-identity-wiring.d.ts +77 -0
  38. package/dist/core/runner/run-identity-wiring.js +85 -0
  39. package/dist/core/runner/run-leg.d.ts +106 -0
  40. package/dist/core/runner/run-leg.js +462 -0
  41. package/dist/core/runner/run-notification-lane.d.ts +55 -0
  42. package/dist/core/runner/run-notification-lane.js +128 -0
  43. package/dist/core/runner/run-reasoning-seat.d.ts +27 -0
  44. package/dist/core/runner/run-reasoning-seat.js +48 -0
  45. package/dist/core/runner/run-recovery-lanes.d.ts +54 -0
  46. package/dist/core/runner/run-recovery-lanes.js +180 -0
  47. package/dist/core/runner/run-settle-and-teardown.d.ts +109 -0
  48. package/dist/core/runner/run-settle-and-teardown.js +324 -0
  49. package/dist/core/runner/run-stop-and-final-verify.d.ts +32 -0
  50. package/dist/core/runner/run-stop-and-final-verify.js +159 -0
  51. package/dist/core/runner/run-telemetry-and-budget-seats.d.ts +38 -0
  52. package/dist/core/runner/run-telemetry-and-budget-seats.js +159 -0
  53. package/dist/core/runner/run-terminal-adoption.d.ts +99 -0
  54. package/dist/core/runner/run-terminal-adoption.js +120 -0
  55. package/dist/core/runner/run-tool-mount-facts.d.ts +26 -0
  56. package/dist/core/runner/run-tool-mount-facts.js +58 -0
  57. package/dist/core/runner/run-turn-boundary.d.ts +0 -35
  58. package/dist/core/runner/run-turn-boundary.js +1 -3
  59. package/dist/core/runner/runtask.d.ts +14 -3
  60. package/dist/core/runner/runtask.js +125 -2102
  61. package/dist/core/runner-deps.d.ts +4 -14
  62. package/dist/core/store-contracts/workflow-journal-store-contract.d.ts +7 -0
  63. package/dist/core/store-contracts/workflow-journal-store-contract.js +85 -0
  64. package/dist/core/tool-policy.d.ts +37 -93
  65. package/dist/core/tool-policy.js +1 -11
  66. package/dist/core/trace.d.ts +6 -7
  67. package/dist/core/wiring-manifest.d.ts +5 -22
  68. package/dist/core/wiring-manifest.js +3 -11
  69. package/dist/core/workflow-journal-store.d.ts +35 -4
  70. package/dist/core/workflow-journal-store.js +19 -2
  71. package/dist/index.d.ts +3 -2
  72. package/dist/index.js +3 -2
  73. package/dist/orchestration/workflow.js +2 -0
  74. package/dist/stores/file/workflow-journal-store.d.ts +7 -10
  75. package/dist/stores/file/workflow-journal-store.js +2 -4
  76. package/dist/tools/fs/fs-bash.js +3 -2
  77. package/package.json +1 -1
  78. package/test/export-surface.snapshot.json +10 -10
@@ -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
+ }
@@ -0,0 +1,55 @@
1
+ import { type PeerInboundChainRef } from "../../agents/peer-admission.js";
2
+ import type { PushQueue } from "../push-queue.js";
3
+ import type { SessionStore } from "../session.js";
4
+ import { type PendingSessionNotifications, type SystemInjectionPriority, type TaskNotificationPayload } from "../task-notification.js";
5
+ import type { RunnerDeps, TaskEvent, TaskSpec } from "../types.js";
6
+ import type { SessionReadFileStates } from "./prepare-hands-readface.js";
7
+ import type { NotificationLaneBindings, Prepared, PrepareResume, ResumeRun, RunInternals, RunnerDepsSeat, RunnerSelfSeat, TaskIdRef } from "./contracts.js";
8
+ export interface RunNotificationLaneInput<R> {
9
+ /** borrowed-readonly — the task spec: prepare's input, and the principal the peer scope is minted from. */
10
+ spec: TaskSpec;
11
+ /** borrowed-mutable — the run's event queue: the routed `task_notification` frames are pushed here. */
12
+ queue: PushQueue<TaskEvent>;
13
+ /** borrowed-readonly — the resume plan when this leg resumes a checkpoint (prepare's resume seed is derived from it). */
14
+ resume: ResumeRun | undefined;
15
+ /** borrowed-readonly — the trusted run-scoped channel: the detach hub, the uplink, the root / parent anchors, the peer refs, the upstream notification observer. */
16
+ internals: RunInternals | undefined;
17
+ /** borrowed-mutable — the stream layer's backstop carrier, handed to prepare so a prepare THROW still names its run. */
18
+ taskIdRef: TaskIdRef | undefined;
19
+ /** borrowed-readonly — the Runner's deployment deps, read LIVE (`onError` in the park closure — after the run, on a swapped
20
+ * catalog it is still the CURRENT sink; `models` and prepare's `deps` at the natural-snapshot pin); the one home of why it
21
+ * is a seat and not a captured object is {@link RunnerDepsSeat}. */
22
+ runner: RunnerDepsSeat;
23
+ /** borrowed-readonly — the TRUSTED Runner self-reference prepare mounts the `run_workflow` tool over (never a TaskSpec field). */
24
+ self: RunnerSelfSeat;
25
+ /** borrowed-readonly — the session store prepare acquires / creates the leg's session through. */
26
+ sessions: SessionStore;
27
+ /** borrowed-mutable — the Runner's own per-session read-state seat, ALWAYS this Runner's (a caller-supplied one is overridden). */
28
+ sessionReadStates: SessionReadFileStates;
29
+ /** borrowed-mutable — the Runner's per-session parked-notification store: the torn-down lane and the escalation pend here. */
30
+ pendingSessionNotifications: PendingSessionNotifications;
31
+ /** borrowed-readonly — the orchestrator's entry (a lane may not name the orchestrator; the driver hands it in). */
32
+ prepareTask: (spec: TaskSpec, deps: RunnerDeps, sessions: SessionStore, resume: PrepareResume | undefined, internals: RunInternals, runnerSelf: RunnerSelfSeat, taskIdRef: TaskIdRef | undefined) => Promise<Prepared>;
33
+ /** borrowed-readonly — the lanes after this one, entered in this lane's last continuation with what it prepared and bound. */
34
+ next: (lane: RunNotificationLaneResult) => Promise<R>;
35
+ }
36
+ /** What the lane prepared and bound — the seats every lane after it reads. */
37
+ export interface RunNotificationLaneResult {
38
+ /** The leg's prepared seat. */
39
+ prepared: Prepared;
40
+ /** The ONE injection entry every notification producer rides (the identity lane binds the notify() bridge to it). */
41
+ injectTaskNotification: (notification: TaskNotificationPayload, opts?: {
42
+ priority?: SystemInjectionPriority;
43
+ }) => Promise<"queued" | "parked" | "dropped_duplicate">;
44
+ /** The turn-open dedup set (the cross-layer double-park dedup): the identity lane registers a redelivered frame's key here. */
45
+ deliveredAtTurnOpen: Set<string>;
46
+ /** design/176: the peer inbound chain ref this run carries (the spawn chain's, or a fresh one). */
47
+ peerInboundChainRef: PeerInboundChainRef;
48
+ /** The model catalog pinned BEFORE prepare (the natural-snapshot closure for swapDeps): the budget seats resolve against it. */
49
+ modelCatalog: RunnerDeps["models"];
50
+ /** The lane's bindings as one seat over its own variables — see {@link NotificationLaneBindings}. */
51
+ notificationLane: NotificationLaneBindings;
52
+ /** The queue subscription's release; the leg lane's finally calls it right after flipping `live`. */
53
+ unsubscribeTaskNotifications: () => void;
54
+ }
55
+ export declare function runNotificationLane<R>(input: RunNotificationLaneInput<R>): Promise<R>;
@@ -0,0 +1,128 @@
1
+ import { createPeerInboundChainRef, createPeerSelfRef } from "../../agents/peer-admission.js";
2
+ import { boundInputHashOf } from "../canonical-json.js";
3
+ import { isDelegatedAgentTerminal, isTerminalTaskNotification, renderTaskNotificationXml, SystemInjectionQueue, taskNotificationDedupKey } from "../task-notification.js";
4
+ import { ToolDetachHub } from "../tool-detach.js";
5
+ import { pendingContentAskCallId } from "./decide-continuation.js";
6
+ export async function runNotificationLane(input) {
7
+ const { spec, queue, resume, internals, taskIdRef, runner, self, sessions, sessionReadStates, pendingSessionNotifications, prepareTask, next } = input;
8
+ const prepareResume = resume
9
+ ? {
10
+ leafId: resume.cp.leafId,
11
+ suspendedBatch: new Set(resume.cp.pendingAction.kind === "tool_approval" ? resume.cp.pendingAction.batchToolCallIds : []),
12
+ seed: resume.cp.state,
13
+ priorSuspendCount: resume.cp.suspendCount,
14
+ priorLedger: resume.cp.resourceLedger,
15
+ priorHumanReview: resume.cp.humanReview,
16
+ workspaceHandle: resume.cp.state.workspaceHandle,
17
+ executesApprovedAction: resume.cp.pendingAction.kind === "tool_approval" &&
18
+ resume.outcome.gate !== "wake" &&
19
+ resume.outcome.decision === "allow",
20
+ ...(resume.outcome.gate !== "wake" && resume.outcome.decision === "allow" && pendingContentAskCallId(resume.cp) !== undefined
21
+ ? {
22
+ redeemedContentAskCallId: pendingContentAskCallId(resume.cp),
23
+ redeemedContentAskQuestionsHash: boundInputHashOf(resume.cp.pendingAction.args?.questions),
24
+ }
25
+ : {}),
26
+ }
27
+ : undefined;
28
+ const taskNotificationQueue = new SystemInjectionQueue();
29
+ const detachHub = internals?.detachHub ?? new ToolDetachHub();
30
+ let notificationHarness;
31
+ let notificationIdent = () => ({});
32
+ let notificationLaneLive = true;
33
+ let notificationSessionId;
34
+ let descendantAnchorDisclosed = false;
35
+ const parkTaskNotification = (payload, priority) => {
36
+ if (notificationSessionId !== undefined)
37
+ pendingSessionNotifications.pend(notificationSessionId, payload, priority);
38
+ if (!isDelegatedAgentTerminal(payload))
39
+ return;
40
+ const uplink = internals?.parentNotify;
41
+ if (uplink !== undefined) {
42
+ try {
43
+ uplink(payload, { priority });
44
+ }
45
+ catch (e) {
46
+ runner.deps.onError?.(e, { phase: "degraded", sessionId: notificationSessionId ?? "", classification: "descendant-terminal-uplink" });
47
+ }
48
+ return;
49
+ }
50
+ const rootAnchor = internals?.rootSessionId;
51
+ if (rootAnchor !== undefined && rootAnchor !== notificationSessionId) {
52
+ pendingSessionNotifications.pend(rootAnchor, payload, priority);
53
+ return;
54
+ }
55
+ if (internals?.parentSessionId !== undefined && !descendantAnchorDisclosed) {
56
+ descendantAnchorDisclosed = true;
57
+ runner.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" });
58
+ }
59
+ };
60
+ const unsubscribeTaskNotifications = taskNotificationQueue.subscribe((item) => {
61
+ queue.push({ type: "task_notification", notification: item.payload, priority: item.priority, ...notificationIdent() });
62
+ if (notificationHarness && prepared.batchHaltRef.current === undefined) {
63
+ const xml = renderTaskNotificationXml(item.payload);
64
+ const noteOptions = {
65
+ provenance: "engine-note",
66
+ enginePayload: item.payload,
67
+ ...(item.payload.task_type !== "external" && isTerminalTaskNotification(item.payload) ? { capPreferred: true } : {}),
68
+ };
69
+ const deliver = item.priority === "later"
70
+ ? notificationHarness.followUp(xml, noteOptions)
71
+ : notificationHarness.steer(xml, { ...noteOptions, ...(item.priority === "now" ? { immediate: true } : {}) });
72
+ void deliver.then(() => item.onDisposition?.("queued"), () => {
73
+ parkTaskNotification(item.payload, item.priority);
74
+ item.onDisposition?.("parked");
75
+ });
76
+ }
77
+ else {
78
+ parkTaskNotification(item.payload, item.priority);
79
+ item.onDisposition?.("parked");
80
+ }
81
+ });
82
+ const upstreamTaskNotification = internals?.onTaskNotification;
83
+ const deliveredAtTurnOpen = new Set();
84
+ const injectTaskNotification = (notification, opts) => {
85
+ if (deliveredAtTurnOpen.has(taskNotificationDedupKey(notification)))
86
+ return Promise.resolve("dropped_duplicate");
87
+ if (!notificationLaneLive) {
88
+ parkTaskNotification(notification, opts?.priority ?? "later");
89
+ return Promise.resolve("parked");
90
+ }
91
+ return new Promise((resolve) => {
92
+ const accepted = taskNotificationQueue.enqueue({
93
+ kind: "task_notification",
94
+ priority: opts?.priority ?? "later",
95
+ dedupKey: taskNotificationDedupKey(notification),
96
+ payload: notification,
97
+ onDisposition: resolve,
98
+ });
99
+ if (!accepted)
100
+ resolve("queued");
101
+ });
102
+ };
103
+ const peerSelfRef = internals?.peerSelfRef ?? createPeerSelfRef(internals?.registryScope ?? spec.principal ?? "default");
104
+ const peerInboundChainRef = internals?.peerInboundChainRef ?? createPeerInboundChainRef();
105
+ const modelCatalog = runner.deps.models;
106
+ const prepared = await prepareTask(spec, runner.deps, sessions, prepareResume, {
107
+ ...(internals ?? {}),
108
+ peerSelfRef,
109
+ peerInboundChainRef,
110
+ detachHub,
111
+ sessionReadStates,
112
+ onTaskNotification: (notification, opts) => {
113
+ try {
114
+ upstreamTaskNotification?.(notification, opts);
115
+ }
116
+ catch {
117
+ }
118
+ injectTaskNotification(notification, opts);
119
+ },
120
+ }, self, taskIdRef);
121
+ const notificationLane = {
122
+ get harness() { return notificationHarness; }, set harness(h) { notificationHarness = h; },
123
+ get sessionId() { return notificationSessionId; }, set sessionId(s) { notificationSessionId = s; },
124
+ get ident() { return notificationIdent; }, set ident(f) { notificationIdent = f; },
125
+ get live() { return notificationLaneLive; }, set live(v) { notificationLaneLive = v; },
126
+ };
127
+ return await next({ prepared, injectTaskNotification, deliveredAtTurnOpen, peerInboundChainRef, modelCatalog, notificationLane, unsubscribeTaskNotifications });
128
+ }