@sema-agent/server 1.316.0 → 1.317.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 (50) hide show
  1. package/README.md +9 -0
  2. package/README.zh-CN.md +7 -0
  3. package/dist/config-types.d.ts +1 -1
  4. package/dist/config.d.ts +14 -0
  5. package/dist/config.js +148 -45
  6. package/dist/elicitation.js +2 -2
  7. package/dist/http/route-ctx.d.ts +51 -2
  8. package/dist/http/routes/approvals-assistant.d.ts +11 -0
  9. package/dist/http/routes/approvals-assistant.js +530 -0
  10. package/dist/http/routes/attachments.js +7 -7
  11. package/dist/http/routes/fleet.d.ts +4 -0
  12. package/dist/http/routes/fleet.js +147 -0
  13. package/dist/http/routes/images.js +29 -29
  14. package/dist/http/routes/leader.d.ts +4 -0
  15. package/dist/http/routes/leader.js +48 -0
  16. package/dist/http/routes/memory-policy.js +10 -10
  17. package/dist/http/routes/notify-wake.d.ts +4 -0
  18. package/dist/http/routes/notify-wake.js +133 -0
  19. package/dist/http/routes/observability.js +5 -5
  20. package/dist/http/routes/runs.d.ts +19 -0
  21. package/dist/http/routes/runs.js +967 -0
  22. package/dist/http/routes/session-sync.js +28 -28
  23. package/dist/http/routes/sessions-list.js +8 -8
  24. package/dist/http/routes/sessions.js +47 -47
  25. package/dist/http/routes/side-query.d.ts +4 -0
  26. package/dist/http/routes/side-query.js +88 -0
  27. package/dist/http/routes/tasks.d.ts +4 -0
  28. package/dist/http/routes/tasks.js +632 -0
  29. package/dist/http/routes/trace-usage.d.ts +4 -0
  30. package/dist/http/routes/trace-usage.js +239 -0
  31. package/dist/http/routes/workflows.d.ts +5 -0
  32. package/dist/http/routes/workflows.js +337 -0
  33. package/dist/http/run-meta.d.ts +11 -0
  34. package/dist/http/run-meta.js +16 -0
  35. package/dist/http/send.d.ts +1 -0
  36. package/dist/http/send.js +15 -0
  37. package/dist/http/server.d.ts +6 -5
  38. package/dist/http/server.js +241 -3166
  39. package/dist/http/sse-log.js +2 -2
  40. package/dist/http/wire-types.d.ts +6 -0
  41. package/dist/leader/endpoint.js +4 -4
  42. package/dist/main.js +5 -5
  43. package/dist/plugins/remote-env-host.js +4 -3
  44. package/dist/question.js +2 -2
  45. package/dist/run-local.js +1 -1
  46. package/dist/tool-approval.js +2 -2
  47. package/dist/trace/ledger-sink.js +1 -1
  48. package/dist/trace/project.d.ts +1 -0
  49. package/dist/trace/project.js +3 -0
  50. package/package.json +1 -1
@@ -0,0 +1,632 @@
1
+ import { runWithVerification, runCascade, uuidv7 } from "@sema-agent/core";
2
+ import { markChildrenStoppedByUserOnAbort, resumeAtHttpStatus, stripCheckpointToken, HEARTBEAT_MS } from "../../runs.js";
3
+ import { withPrincipal } from "../../observability/principal-context.js";
4
+ import { fleetRunPublisher, fleetRunLabels, fleetRunResiduals } from "../../fleet/fleet-bus.js";
5
+ import { defaultSubagentTailBus, projectTailFrame } from "../../fleet/subagent-tail-bus.js";
6
+ import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotificationStreamKey, NotifiedKeys } from "../../orchestration/workflow-completion-inbox.js";
7
+ import { createLedgerSink } from "../../trace/ledger-sink.js";
8
+ import { redactSecrets } from "../../trace/redact.js";
9
+ import { turnEndEventData, contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, workspaceChangedEventData, appendModelUsageDelta, attachModelUsage } from "../../trace/project.js";
10
+ import { cascadeConfig, runMeta } from "../run-meta.js";
11
+ import { scopedIdempotencyKey } from "../idempotency.js";
12
+ import { sendJson, sendError, sseHeaders } from "../send.js";
13
+ import { headerStr, gatedPrincipal } from "../principal-gate.js";
14
+ export async function handleTasks(req, res, url, ctx) {
15
+ const miss = { fell: false };
16
+ await handleTasksBody(req, res, url, ctx, miss);
17
+ return !miss.fell;
18
+ }
19
+ async function handleTasksBody(req, res, url, ctx, miss) {
20
+ const { deps } = ctx;
21
+ const { idemCache, inflightRuns, cancelledViaVerb, steerableRuns, counters } = ctx.registry;
22
+ const { rateLimited, quotaExceeded, leaseDenied } = ctx.helpers;
23
+ const { prepareSpec, finalizeTaskResult } = ctx.legs;
24
+ const reqState = ctx.req;
25
+ const source = ctx.req.source;
26
+ if (req.method === "POST" && (url === "/v1/tasks" || url === "/v1/tasks/stream")) {
27
+ const rawIdem = headerStr(req.headers["idempotency-key"]);
28
+ const idemKey = rawIdem ? scopedIdempotencyKey(rawIdem, source, gatedPrincipal(req, deps.config)) : undefined;
29
+ const cached = idemKey ? idemCache.peek(idemKey) : undefined;
30
+ if (cached) {
31
+ if (url === "/v1/tasks/stream") {
32
+ sseHeaders(res);
33
+ const hb = setInterval(() => {
34
+ if (!res.writableEnded)
35
+ res.write(`event: heartbeat\ndata: {}\n\n`);
36
+ }, 15_000);
37
+ try {
38
+ const resp = await cached;
39
+ if (!res.writableEnded)
40
+ res.write(`data: ${JSON.stringify({ type: "done", result: resp.body, replay: true })}\n\n`);
41
+ }
42
+ finally {
43
+ clearInterval(hb);
44
+ }
45
+ res.end();
46
+ }
47
+ else {
48
+ const resp = await cached;
49
+ sendJson(res, resp.status, resp.body);
50
+ }
51
+ return;
52
+ }
53
+ if (rateLimited(req, res) || quotaExceeded(req, res) || (await leaseDenied(req, res)))
54
+ return;
55
+ const prepared = await prepareSpec(req, res);
56
+ if (!prepared)
57
+ return;
58
+ const principal = prepared.auth?.principal;
59
+ if (url === "/v1/tasks/stream") {
60
+ if (prepared.verify || prepared.cascade) {
61
+ sendError(res, 400, "request.field_conflict", "verify / cascade are not supported on /v1/tasks/stream (both are multi-attempt, not a single stream) — use /v1/tasks or /v1/runs");
62
+ return;
63
+ }
64
+ const earlyDurableTid = deps.runStore && prepared.spec.sessionId ? uuidv7() : undefined;
65
+ const detachOnDisconnect = headerStr(req.headers["x-detach-on-disconnect"]) === "true";
66
+ if (detachOnDisconnect && earlyDurableTid === undefined) {
67
+ sendError(res, 400, "request.precondition_unmet", "x-detach-on-disconnect requires a durable run (a run store + sessionId) — without one the detached result would be unqueryable");
68
+ return;
69
+ }
70
+ reqState.streamTaskId = earlyDurableTid;
71
+ let usageKey;
72
+ reqState.streamDetached = detachOnDisconnect;
73
+ sseHeaders(res, earlyDurableTid ? { "x-task-id": earlyDurableTid } : undefined);
74
+ if (earlyDurableTid) {
75
+ res.write(`event: meta\ndata: ${JSON.stringify({ type: "meta", taskId: earlyDurableTid, ...(prepared.spec.sessionId ? { sessionId: prepared.spec.sessionId } : {}) })}\n\n`);
76
+ }
77
+ const ac = new AbortController();
78
+ if (prepared.spec.sessionId)
79
+ markChildrenStoppedByUserOnAbort(ac.signal, prepared.spec.sessionId, prepared.auth?.principal);
80
+ let closed = false;
81
+ let detachLogged = false;
82
+ const onDisconnect = () => {
83
+ if (detachOnDisconnect) {
84
+ if (res.writableEnded)
85
+ return;
86
+ if (!detachLogged) {
87
+ detachLogged = true;
88
+ res.on("error", () => undefined);
89
+ deps.logger?.info("stream_client_detached", { taskId: earlyDurableTid, sessionId: prepared.spec.sessionId, detached: true });
90
+ }
91
+ return;
92
+ }
93
+ closed = true;
94
+ ac.abort();
95
+ };
96
+ req.on("close", onDisconnect);
97
+ res.on("close", () => {
98
+ if (!res.writableEnded)
99
+ onDisconnect();
100
+ });
101
+ const hb = setInterval(() => {
102
+ if (!res.writableEnded && !res.destroyed)
103
+ res.write(`event: heartbeat\ndata: {}\n\n`);
104
+ }, 15_000);
105
+ let ranLive = false;
106
+ let resp;
107
+ const runStreamSubmitLeg = async () => {
108
+ ranLive = true;
109
+ let finalResult;
110
+ let runRowSettled = false;
111
+ let durableTaskId;
112
+ let durableHeartbeat;
113
+ let userMsgEntryId;
114
+ let anchorPut = false;
115
+ const putRewindAnchor = () => {
116
+ if (anchorPut || !userMsgEntryId || !durableTaskId || !prepared.spec.sessionId || !deps.resumeAnchorStore)
117
+ return;
118
+ anchorPut = true;
119
+ void deps.resumeAnchorStore
120
+ .put(prepared.spec.sessionId, durableTaskId, userMsgEntryId, principal ?? null)
121
+ .catch(() => deps.metrics?.inc("resume_anchor_capture_failed"));
122
+ };
123
+ if (deps.runStore && prepared.spec.sessionId) {
124
+ const tid = earlyDurableTid ?? uuidv7();
125
+ const created = await deps.runStore.createRun(tid, prepared.spec.sessionId, principal ?? null, deps.instanceId ?? "default", runMeta(prepared, source));
126
+ if (created.ok)
127
+ deps.sessionTitler?.maybeTitle(prepared.spec.sessionId, prepared.spec.objective);
128
+ if (!created.ok) {
129
+ const conflict = { error: "session already has an active run — POST /v1/runs/{activeTaskId}/cancel stops it (same-instance interactive runs abort immediately)", errorCode: "conflict.session_active_run", activeTaskId: created.activeTaskId };
130
+ res.write(`data: ${JSON.stringify({ type: "done", result: { status: "failed", errorMessage: conflict.error, activeTaskId: conflict.activeTaskId } })}\n\n`);
131
+ return { status: 409, body: conflict };
132
+ }
133
+ durableTaskId = tid;
134
+ }
135
+ let ledgerSink;
136
+ let ledgerTerminal = false;
137
+ if (detachOnDisconnect && durableTaskId && deps.runStore) {
138
+ const rs = deps.runStore;
139
+ const tid = durableTaskId;
140
+ ledgerSink = createLedgerSink({ appendEvent: (seq, type, data) => rs.appendEvent(tid, seq, type, data), persistThinking: deps.config.traceThinking });
141
+ usageKey = prepared.spec.sessionId;
142
+ if (usageKey)
143
+ deps.modelUsage?.register(usageKey);
144
+ }
145
+ if (deps.checkpointStore && prepared.spec.sessionId) {
146
+ await deps.checkpointStore.putCtx(prepared.spec.sessionId, { body: prepared.body, memoryScope: prepared.auth?.memoryScope });
147
+ }
148
+ if (durableTaskId && deps.runStore) {
149
+ const rs = deps.runStore;
150
+ const tid = durableTaskId;
151
+ const owner = principal ?? null;
152
+ const abortFromVerb = () => {
153
+ cancelledViaVerb.add(tid);
154
+ ac.abort();
155
+ };
156
+ durableHeartbeat = setInterval(() => {
157
+ void rs.heartbeat(tid, owner).catch(() => undefined);
158
+ if (!ac.signal.aborted) {
159
+ void Promise.resolve(rs.isCancelRequested?.(tid, owner)).then((c) => { if (c)
160
+ abortFromVerb(); }).catch(() => undefined);
161
+ }
162
+ }, HEARTBEAT_MS);
163
+ durableHeartbeat.unref?.();
164
+ inflightRuns.set(tid, ac);
165
+ void Promise.resolve(rs.isCancelRequested?.(tid, owner)).then((c) => { if (c && !ac.signal.aborted)
166
+ abortFromVerb(); }).catch(() => undefined);
167
+ }
168
+ let fleetPub;
169
+ try {
170
+ fleetPub = durableTaskId
171
+ ? fleetRunPublisher(deps.fleetBus, { runId: durableTaskId, scope: gatedPrincipal(req, deps.config) ?? "default", rootTaskId: prepared.spec.sessionId, ...fleetRunLabels(prepared.spec.objective) })
172
+ : undefined;
173
+ }
174
+ catch (e) {
175
+ if (durableHeartbeat)
176
+ clearInterval(durableHeartbeat);
177
+ if (durableTaskId && inflightRuns.get(durableTaskId) === ac) {
178
+ inflightRuns.delete(durableTaskId);
179
+ cancelledViaVerb.delete(durableTaskId);
180
+ }
181
+ throw e;
182
+ }
183
+ let fleetSettled = false;
184
+ const settleFleet = (status, residuals) => {
185
+ if (fleetSettled)
186
+ return;
187
+ fleetSettled = true;
188
+ fleetPub?.onTerminal(status, residuals);
189
+ };
190
+ let liveStreamRef;
191
+ const subagentHandleEvictions = [];
192
+ let syncLegLive = true;
193
+ const syncNotifiedKeys = new NotifiedKeys();
194
+ try {
195
+ fleetPub?.onStart();
196
+ }
197
+ catch (e) {
198
+ if (durableHeartbeat)
199
+ clearInterval(durableHeartbeat);
200
+ if (durableTaskId && inflightRuns.get(durableTaskId) === ac) {
201
+ inflightRuns.delete(durableTaskId);
202
+ cancelledViaVerb.delete(durableTaskId);
203
+ }
204
+ settleFleet("failed");
205
+ throw e;
206
+ }
207
+ try {
208
+ const streamBody = async () => {
209
+ await withPrincipal(principal, async () => {
210
+ const fwdInternals = {
211
+ onForwardEvent: (e) => {
212
+ fleetPub?.onForwardEvent(e);
213
+ ledgerSink?.onForwardEvent(e);
214
+ {
215
+ const bg = e.bgAgentId;
216
+ if (bg !== undefined && defaultSubagentTailBus.hasSubscribers(bg)) {
217
+ const f = projectTailFrame(e);
218
+ if (f)
219
+ defaultSubagentTailBus.publish(bg, f);
220
+ }
221
+ }
222
+ if (res.writableEnded)
223
+ return;
224
+ const t = e.type;
225
+ if (t === "task_progress") {
226
+ res.write(`data: ${JSON.stringify({ type: "task_progress", ...taskProgressEventData(e) })}\n\n`);
227
+ }
228
+ else if (t === "tool_start") {
229
+ const tsv = e;
230
+ res.write(`data: ${JSON.stringify({ type: "tool_start", ...toolStartEventData(tsv) })}\n\n`);
231
+ }
232
+ else if (t === "tool_end") {
233
+ const te = e;
234
+ res.write(`data: ${JSON.stringify({ type: "tool_end", ...toolEndEventData(te) })}\n\n`);
235
+ }
236
+ else if (t === "text_delta") {
237
+ const td = e;
238
+ res.write(`data: ${JSON.stringify({ type: "text_delta", delta: td.delta, ...(td.eventId ? { eventId: td.eventId } : {}), ...(td.parentToolCallId ? { parentToolCallId: td.parentToolCallId } : {}) })}\n\n`);
239
+ }
240
+ else if (t === "reasoning_delta") {
241
+ const rd = e;
242
+ res.write(`data: ${JSON.stringify({ type: "reasoning_delta", delta: redactSecrets(rd.delta), ...(rd.eventId ? { eventId: rd.eventId } : {}), ...(rd.parentToolCallId ? { parentToolCallId: rd.parentToolCallId } : {}) })}\n\n`);
243
+ }
244
+ },
245
+ onTaskNotification: (n) => {
246
+ if (n.task_type === "workflow")
247
+ return;
248
+ if (defaultSubagentTailBus.hasSubscribers(n.task_id)) {
249
+ defaultSubagentTailBus.publish(n.task_id, { type: "task_settled", taskId: n.task_id, status: n.status, ...(typeof n.seq === "number" ? { seq: n.seq } : {}), ...(n.summary ? { summary: redactSecrets(n.summary) } : {}) });
250
+ }
251
+ const hadRow = fleetPub?.onChildTerminal(n.sessionId ?? n.task_id, n.status, n.task_id, n.toolUseId) ?? false;
252
+ const deliverable = syncLegLive && !res.writableEnded && !res.destroyed;
253
+ const parked = !deliverable && Boolean(deps.workflowCompletionInbox && prepared.spec.sessionId);
254
+ deps.logger?.info?.("task_notification_observed", { route: "sync-stream", taskId: n.task_id, taskType: n.task_type, status: n.status, hadFleetRow: hadRow, legLive: syncLegLive, parkedDurable: parked });
255
+ if (parked) {
256
+ void deps.workflowCompletionInbox.enqueue(taskNotificationInboxEntry(prepared.spec.sessionId, askOwner, n, Date.now(), durableTaskId)).catch((err) => deps.logger?.warn?.("park_enqueue_failed", { route: "sync-stream", taskId: n.task_id, err: err instanceof Error ? err.message : String(err) }));
257
+ }
258
+ else if (deliverable) {
259
+ const key = taskNotificationStreamKey(n);
260
+ if (syncNotifiedKeys.get(key) === undefined) {
261
+ syncNotifiedKeys.set(key, Promise.resolve(true));
262
+ res.write(`data: ${JSON.stringify({ type: "task_notification", ...taskNotificationEventData({ notification: n }) })}\n\n`);
263
+ }
264
+ }
265
+ },
266
+ ...(deps.subagentSteerRegistry && durableTaskId
267
+ ? {
268
+ onSubagentSpawn: (handle) => {
269
+ subagentHandleEvictions.push(deps.subagentSteerRegistry.register(durableTaskId, handle));
270
+ },
271
+ }
272
+ : {}),
273
+ };
274
+ const liveStream = deps.runner.runTaskStream({ ...prepared.spec, signal: ac.signal }, undefined, fwdInternals);
275
+ liveStreamRef = liveStream;
276
+ if (durableTaskId)
277
+ steerableRuns.set(durableTaskId, liveStream);
278
+ for await (const ev of liveStream) {
279
+ if (closed)
280
+ break;
281
+ if (ev.type === "message_committed" && ev.role === "user")
282
+ userMsgEntryId = ev.entryId;
283
+ if (ev.type === "done") {
284
+ finalResult = stripCheckpointToken(ev.result);
285
+ if (durableTaskId && finalResult)
286
+ finalResult = { ...finalResult, taskId: durableTaskId };
287
+ if (durableTaskId && cancelledViaVerb.has(durableTaskId) && finalResult?.status === "failed" && !finalResult.errorCode) {
288
+ finalResult = { ...finalResult, errorCode: "cancelled" };
289
+ }
290
+ finalizeTaskResult(ev.result, principal, prepared.spec.objective, prepared.spec.sessionId);
291
+ putRewindAnchor();
292
+ settleFleet(finalResult?.status ?? "completed", fleetRunResiduals(finalResult));
293
+ if (ledgerSink && finalResult) {
294
+ if (usageKey && deps.modelUsage && deps.runStore && durableTaskId) {
295
+ const rs2 = deps.runStore;
296
+ const tid2 = durableTaskId;
297
+ const fr = finalResult;
298
+ finalResult = await attachModelUsage(fr, { append: ledgerSink.append, getEvents: (_id, after) => rs2.getEvents(tid2, after), modelUsage: deps.modelUsage, taskId: usageKey }).catch(() => fr);
299
+ }
300
+ await ledgerSink.onDone(finalResult);
301
+ ledgerTerminal = true;
302
+ }
303
+ res.write(`data: ${JSON.stringify({ ...ev, result: finalResult })}\n\n`);
304
+ continue;
305
+ }
306
+ if (ledgerSink) {
307
+ await ledgerSink.onEvent(ev);
308
+ if (ev.type === "turn_end" && usageKey)
309
+ await appendModelUsageDelta(ledgerSink.append, deps.modelUsage, usageKey);
310
+ }
311
+ fleetPub?.onEvent(ev);
312
+ if (ev.type === "status")
313
+ deps.metrics?.inc("brain_retry_total", { phase: String(ev.phase) });
314
+ if (ev.type === "status") {
315
+ res.write(`data: ${JSON.stringify({ type: "status", ...brainStatusEventData(ev) })}\n\n`);
316
+ }
317
+ else if (ev.type === "tool_end") {
318
+ const te = ev;
319
+ res.write(`data: ${JSON.stringify({ type: "tool_end", ...toolEndEventData(te) })}\n\n`);
320
+ }
321
+ else if (ev.type === "tool_start") {
322
+ const tsv = ev;
323
+ res.write(`data: ${JSON.stringify({ type: "tool_start", ...toolStartEventData(tsv) })}\n\n`);
324
+ }
325
+ else if (ev.type === "task_notification") {
326
+ const tnv = ev.notification;
327
+ const tnvKey = tnv?.task_id ? taskNotificationStreamKey({ task_type: tnv.task_type, task_id: tnv.task_id, status: String(tnv.status), seq: tnv.seq }) : undefined;
328
+ const tnvPrior = tnvKey !== undefined ? syncNotifiedKeys.get(tnvKey) : undefined;
329
+ if (tnvPrior === undefined || !(await tnvPrior)) {
330
+ if (tnvKey !== undefined)
331
+ syncNotifiedKeys.set(tnvKey, Promise.resolve(true));
332
+ res.write(`data: ${JSON.stringify({ type: "task_notification", ...taskNotificationEventData(ev) })}\n\n`);
333
+ }
334
+ }
335
+ else if (ev.type === "reasoning_delta" && typeof ev.delta === "string") {
336
+ res.write(`data: ${JSON.stringify({ type: "reasoning_delta", delta: redactSecrets(ev.delta), ...(ev.eventId ? { eventId: ev.eventId } : {}), ...(ev.parentToolCallId ? { parentToolCallId: ev.parentToolCallId } : {}) })}\n\n`);
337
+ }
338
+ else if (ev.type === "task_progress") {
339
+ res.write(`data: ${JSON.stringify({ type: "task_progress", ...taskProgressEventData(ev) })}\n\n`);
340
+ }
341
+ else if (ev.type === "compacted") {
342
+ res.write(`data: ${JSON.stringify({ type: "compacted", ...compactedEventData(ev) })}\n\n`);
343
+ }
344
+ else if (ev.type === "diagnostics") {
345
+ res.write(`data: ${JSON.stringify({ type: "diagnostics", ...diagnosticsEventData(ev) })}\n\n`);
346
+ }
347
+ else if (ev.type === "steering_injected") {
348
+ res.write(`data: ${JSON.stringify({ type: "steering_injected", ...steeringInjectedEventData(ev) })}\n\n`);
349
+ }
350
+ else if (ev.type === "workspace_changed") {
351
+ res.write(`data: ${JSON.stringify({ type: "workspace_changed", ...workspaceChangedEventData(ev) })}\n\n`);
352
+ }
353
+ else if (ev.type === "text_delta" || ev.type === "turn_end" || ev.type === "message_committed" || ev.type === "context_usage") {
354
+ const e = ev;
355
+ const ident = {
356
+ ...(e.eventId !== undefined ? { eventId: e.eventId } : {}),
357
+ ...(e.parentToolCallId !== undefined ? { parentToolCallId: e.parentToolCallId } : {}),
358
+ ...(e.sourceTaskId !== undefined ? { sourceTaskId: e.sourceTaskId } : {}),
359
+ ...(e.bgAgentId !== undefined ? { bgAgentId: e.bgAgentId } : {}),
360
+ };
361
+ const arm = ev.type === "text_delta"
362
+ ? { type: "text_delta", delta: e.delta, ...ident }
363
+ : ev.type === "turn_end"
364
+ ? { type: "turn_end", ...(e.usage !== undefined ? { usage: e.usage } : {}), ...(e.usageMissing !== undefined ? { usageMissing: e.usageMissing } : {}), ...(e.stopReason !== undefined ? { stopReason: e.stopReason } : {}), ...ident }
365
+ : ev.type === "message_committed"
366
+ ? { type: "message_committed", entryId: e.entryId, role: e.role, ...(e.toolCallId !== undefined ? { toolCallId: e.toolCallId } : {}), ...ident }
367
+ : { type: "context_usage", ...contextUsageEventData(ev), ...ident };
368
+ res.write(`data: ${JSON.stringify(arm)}\n\n`);
369
+ }
370
+ else {
371
+ res.write(`data: ${JSON.stringify(ev)}\n\n`);
372
+ }
373
+ }
374
+ });
375
+ };
376
+ const askTaskId = durableTaskId ?? uuidv7();
377
+ const askOwner = gatedPrincipal(req, deps.config) ?? null;
378
+ const emitAsk = (frame) => {
379
+ if (!res.writableEnded)
380
+ res.write(`event: ${frame.type}\ndata: ${JSON.stringify(frame)}\n\n`);
381
+ };
382
+ await emitPendingWorkflowCompletions(deps.workflowCompletionInbox, prepared.spec.sessionId, askOwner, (frame) => {
383
+ if (closed || res.writableEnded || res.destroyed)
384
+ throw new Error("stream ended before the completion frame was written");
385
+ const f = frame;
386
+ if (f.type === "task_notification" && f.task_id)
387
+ syncNotifiedKeys.set(taskNotificationStreamKey({ task_type: f.task_type, task_id: f.task_id, status: String(f.status), seq: f.seq }), Promise.resolve(true));
388
+ res.write(`data: ${JSON.stringify(frame)}\n\n`);
389
+ }, { route: "sync-stream-open", connection: "live-sse", log: (m, x) => deps.logger?.info?.(m, x) });
390
+ const withElicit = deps.elicitation
391
+ ? () => deps.elicitation.runWithContext({ taskId: askTaskId, owner: askOwner, emit: emitAsk, abortSignal: ac.signal }, streamBody)
392
+ : streamBody;
393
+ const withQuestion = () => deps.question
394
+ ? deps.question.runWithContext({ taskId: askTaskId, owner: askOwner, emit: emitAsk, abortSignal: ac.signal }, withElicit)
395
+ : withElicit();
396
+ const emitApproval = (frame) => {
397
+ if (res.writableEnded || res.destroyed)
398
+ throw new Error("live stream ended — approval card undeliverable");
399
+ emitAsk(frame);
400
+ };
401
+ const approvalCtx = deps.toolApproval
402
+ ? { taskId: askTaskId, owner: askOwner, emit: emitApproval, abortSignal: ac.signal, ...(prepared.spec.sessionId ? { sessionId: prepared.spec.sessionId } : {}) }
403
+ : undefined;
404
+ if (approvalCtx && deps.toolApproval) {
405
+ prepared.spec.onAsk = deps.toolApproval.boundAsk({
406
+ owner: askOwner,
407
+ taskId: askTaskId,
408
+ ...(prepared.spec.sessionId ? { sessionId: prepared.spec.sessionId } : {}),
409
+ });
410
+ }
411
+ const withApproval = () => approvalCtx && deps.toolApproval ? deps.toolApproval.runWithContext(approvalCtx, withQuestion) : withQuestion();
412
+ await (deps.sendUserFile
413
+ ? deps.sendUserFile.runWithContext({
414
+ taskId: askTaskId,
415
+ emit: async (f) => {
416
+ void (await emitAsk(f));
417
+ if (ledgerSink) {
418
+ const { type, ...rest } = f;
419
+ await ledgerSink.append(type, rest).catch(() => undefined);
420
+ }
421
+ },
422
+ }, withApproval)
423
+ : withApproval());
424
+ if (durableTaskId && deps.runStore) {
425
+ if (finalResult?.status === "suspended" && deps.checkpointStore)
426
+ await deps.runStore.setSuspended(durableTaskId);
427
+ else if (finalResult?.status === "needs_review" && deps.checkpointStore)
428
+ await deps.runStore.setNeedsReview(durableTaskId);
429
+ else if (finalResult)
430
+ await deps.runStore.setTerminal(durableTaskId, finalResult.status, finalResult, finalResult.errorMessage ?? null);
431
+ else {
432
+ putRewindAnchor();
433
+ const pending = await deps.checkpointStore?.findPendingTokenBySession(prepared.spec.sessionId);
434
+ if (pending) {
435
+ if (ledgerSink && !ledgerTerminal) {
436
+ ledgerTerminal = true;
437
+ await ledgerSink.flush().catch(() => undefined);
438
+ await ledgerSink.appendParked("suspended", {}).catch(() => undefined);
439
+ }
440
+ await deps.runStore.setSuspended(durableTaskId);
441
+ settleFleet("suspended");
442
+ }
443
+ else {
444
+ const dcErr = "stream client disconnected before completion (run aborted)";
445
+ const dc = { taskId: durableTaskId, sessionId: prepared.spec.sessionId ?? "", status: "failed", errorCode: "cancelled", errorMessage: dcErr, stats: { turns: 0, tokens: 0 } };
446
+ if (ledgerSink && !ledgerTerminal) {
447
+ ledgerTerminal = true;
448
+ await ledgerSink.flush().catch(() => undefined);
449
+ await ledgerSink.append("failed", { errorMessage: dcErr, errorCode: "cancelled" }).catch(() => undefined);
450
+ }
451
+ await deps.runStore.setTerminal(durableTaskId, "failed", dc, dcErr);
452
+ settleFleet("failed");
453
+ }
454
+ }
455
+ runRowSettled = true;
456
+ }
457
+ }
458
+ finally {
459
+ syncLegLive = false;
460
+ if (durableHeartbeat)
461
+ clearInterval(durableHeartbeat);
462
+ if (durableTaskId && steerableRuns.get(durableTaskId) === liveStreamRef)
463
+ steerableRuns.delete(durableTaskId);
464
+ if (durableTaskId && inflightRuns.get(durableTaskId) === ac) {
465
+ inflightRuns.delete(durableTaskId);
466
+ cancelledViaVerb.delete(durableTaskId);
467
+ }
468
+ for (const evict of subagentHandleEvictions)
469
+ evict();
470
+ if (!runRowSettled && durableTaskId && deps.runStore) {
471
+ try {
472
+ const row = await deps.runStore.getRun(durableTaskId);
473
+ if (row?.status === "running") {
474
+ const pending = prepared.spec.sessionId ? await deps.checkpointStore?.findPendingTokenBySession(prepared.spec.sessionId) : undefined;
475
+ if (pending) {
476
+ if (ledgerSink && !ledgerTerminal) {
477
+ ledgerTerminal = true;
478
+ await ledgerSink.flush().catch(() => undefined);
479
+ await ledgerSink.appendParked("suspended", {}).catch(() => undefined);
480
+ }
481
+ await deps.runStore.setSuspended(durableTaskId);
482
+ settleFleet("suspended");
483
+ }
484
+ else if (finalResult) {
485
+ if (ledgerSink && !ledgerTerminal) {
486
+ ledgerTerminal = true;
487
+ await ledgerSink.onDone(finalResult).catch(() => undefined);
488
+ }
489
+ await deps.runStore.setTerminal(durableTaskId, finalResult.status, finalResult, finalResult.errorMessage ?? null);
490
+ settleFleet(finalResult.status, fleetRunResiduals(finalResult));
491
+ }
492
+ else if (ac.signal.aborted) {
493
+ const err = "stream aborted before completion (run cancelled)";
494
+ const c = { taskId: durableTaskId, sessionId: prepared.spec.sessionId ?? "", status: "failed", errorCode: "cancelled", errorMessage: err, stats: { turns: 0, tokens: 0 } };
495
+ if (ledgerSink && !ledgerTerminal) {
496
+ ledgerTerminal = true;
497
+ await ledgerSink.flush().catch(() => undefined);
498
+ await ledgerSink.append("failed", { errorMessage: err, errorCode: "cancelled" }).catch(() => undefined);
499
+ }
500
+ await deps.runStore.setTerminal(durableTaskId, "failed", c, err);
501
+ }
502
+ else {
503
+ const err = "stream leg threw before a terminal event";
504
+ if (ledgerSink && !ledgerTerminal) {
505
+ ledgerTerminal = true;
506
+ await ledgerSink.flush().catch(() => undefined);
507
+ await ledgerSink.append("failed", { errorMessage: err }).catch(() => undefined);
508
+ }
509
+ await deps.runStore.setTerminal(durableTaskId, "failed", null, err);
510
+ }
511
+ }
512
+ }
513
+ catch {
514
+ }
515
+ }
516
+ settleFleet("failed");
517
+ }
518
+ return { status: 200, body: finalResult };
519
+ };
520
+ counters.uncountedBillableInflight++;
521
+ try {
522
+ resp = await idemCache.run(idemKey, runStreamSubmitLeg, (r) => r.body?.status === "completed");
523
+ }
524
+ catch (e) {
525
+ deps.logger?.error("stream_error", { closed, err: e instanceof Error ? e.message : String(e) });
526
+ if (!closed)
527
+ throw e;
528
+ }
529
+ finally {
530
+ counters.uncountedBillableInflight--;
531
+ clearInterval(hb);
532
+ if (usageKey)
533
+ deps.modelUsage?.clear(usageKey);
534
+ }
535
+ if (!ranLive && resp && !res.writableEnded) {
536
+ res.write(`data: ${JSON.stringify({ type: "done", result: resp.body, replay: true })}\n\n`);
537
+ }
538
+ res.end();
539
+ }
540
+ else {
541
+ const runSyncSubmitLeg = async () => {
542
+ let durableTaskId;
543
+ let durableHeartbeat;
544
+ if (deps.runStore && prepared.spec.sessionId) {
545
+ const tid = uuidv7();
546
+ const created = await deps.runStore.createRun(tid, prepared.spec.sessionId, principal ?? null, deps.instanceId ?? "default", runMeta(prepared, source));
547
+ if (!created.ok)
548
+ return { status: 409, body: { error: "session already has an active run — POST /v1/runs/{activeTaskId}/cancel stops it (same-instance interactive runs abort immediately)", errorCode: "conflict.session_active_run", activeTaskId: created.activeTaskId } };
549
+ deps.sessionTitler?.maybeTitle(prepared.spec.sessionId, prepared.spec.objective);
550
+ durableTaskId = tid;
551
+ }
552
+ if (deps.checkpointStore && prepared.spec.sessionId) {
553
+ await deps.checkpointStore.putCtx(prepared.spec.sessionId, { body: prepared.body, memoryScope: prepared.auth?.memoryScope });
554
+ }
555
+ const syncCancelCtrl = new AbortController();
556
+ if (prepared.spec.sessionId)
557
+ markChildrenStoppedByUserOnAbort(syncCancelCtrl.signal, prepared.spec.sessionId, prepared.auth?.principal);
558
+ if (durableTaskId && deps.runStore) {
559
+ const rs = deps.runStore;
560
+ const tid = durableTaskId;
561
+ const owner = principal ?? null;
562
+ const abortFromVerb = () => {
563
+ cancelledViaVerb.add(tid);
564
+ syncCancelCtrl.abort();
565
+ };
566
+ durableHeartbeat = setInterval(() => {
567
+ void rs.heartbeat(tid, owner).catch(() => undefined);
568
+ if (!syncCancelCtrl.signal.aborted) {
569
+ void Promise.resolve(rs.isCancelRequested?.(tid, owner)).then((c) => { if (c)
570
+ abortFromVerb(); }).catch(() => undefined);
571
+ }
572
+ }, HEARTBEAT_MS);
573
+ durableHeartbeat.unref?.();
574
+ inflightRuns.set(tid, syncCancelCtrl);
575
+ void Promise.resolve(rs.isCancelRequested?.(tid, owner)).then((c) => { if (c && !syncCancelCtrl.signal.aborted)
576
+ abortFromVerb(); }).catch(() => undefined);
577
+ }
578
+ else if (durableTaskId) {
579
+ inflightRuns.set(durableTaskId, syncCancelCtrl);
580
+ }
581
+ const specWithSignal = { ...prepared.spec, signal: syncCancelCtrl.signal };
582
+ let result;
583
+ let cancelVerbLabel = false;
584
+ counters.uncountedBillableInflight++;
585
+ try {
586
+ const verifyLeg = (v) => runWithVerification(deps.runner, specWithSignal, v);
587
+ const cascadeLeg = () => runCascade(deps.runner, specWithSignal, cascadeConfig(deps.config.cascadeLadder, prepared.spec.maxCostUsd));
588
+ const plainLeg = () => deps.runner.runTask(specWithSignal);
589
+ result = await withPrincipal(principal, () => prepared.verify ? verifyLeg(prepared.verify) : prepared.cascade ? cascadeLeg() : plainLeg());
590
+ }
591
+ finally {
592
+ counters.uncountedBillableInflight--;
593
+ if (durableHeartbeat)
594
+ clearInterval(durableHeartbeat);
595
+ if (durableTaskId && inflightRuns.get(durableTaskId) === syncCancelCtrl) {
596
+ inflightRuns.delete(durableTaskId);
597
+ cancelVerbLabel = cancelledViaVerb.has(durableTaskId);
598
+ cancelledViaVerb.delete(durableTaskId);
599
+ }
600
+ }
601
+ if (cancelVerbLabel && result.status === "failed" && !result.errorCode) {
602
+ result = { ...result, errorCode: "cancelled" };
603
+ }
604
+ const resumeAtStatus = resumeAtHttpStatus(result);
605
+ if (resumeAtStatus) {
606
+ if (durableTaskId && deps.runStore)
607
+ await deps.runStore.setTerminal(durableTaskId, "failed", stripCheckpointToken(result), result.errorMessage ?? null);
608
+ return { status: resumeAtStatus, body: { errorCode: result.errorCode, error: result.errorMessage ?? "resume-at failed" } };
609
+ }
610
+ if (result.status === "suspended" && deps.checkpointStore && deps.runStore && prepared.spec.sessionId) {
611
+ if (durableTaskId)
612
+ await deps.runStore.setSuspended(durableTaskId);
613
+ return { status: 200, body: { taskId: durableTaskId, sessionId: prepared.spec.sessionId, status: "suspended" } };
614
+ }
615
+ if (result.status === "needs_review" && deps.checkpointStore && deps.runStore && prepared.spec.sessionId) {
616
+ if (durableTaskId)
617
+ await deps.runStore.setNeedsReview(durableTaskId);
618
+ return { status: 200, body: { taskId: durableTaskId, sessionId: prepared.spec.sessionId, status: "needs_review" } };
619
+ }
620
+ if (durableTaskId && deps.runStore)
621
+ await deps.runStore.setTerminal(durableTaskId, result.status, stripCheckpointToken(result), result.errorMessage ?? null);
622
+ finalizeTaskResult(result, principal, prepared.spec.objective, prepared.spec.sessionId);
623
+ return { status: 200, body: stripCheckpointToken(result) };
624
+ };
625
+ const resp = await idemCache.run(idemKey, runSyncSubmitLeg, (r) => r.status < 400);
626
+ sendJson(res, resp.status, resp.body);
627
+ }
628
+ return;
629
+ }
630
+ miss.fell = true;
631
+ }
632
+ //# sourceMappingURL=tasks.js.map
@@ -0,0 +1,4 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ import type { RouteCtx } from "../route-ctx.js";
3
+ export declare function handleTraceUsage(req: IncomingMessage, res: ServerResponse, url: string, ctx: RouteCtx): Promise<boolean>;
4
+ //# sourceMappingURL=trace-usage.d.ts.map