@sema-agent/server 1.290.0 → 1.291.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.
@@ -25,6 +25,11 @@ export declare function resumeFactsForLane(provider: "e2b" | "k8s" | "ssh" | "ad
25
25
  export declare function scratchpadSessionSegment(sessionId: string): string;
26
26
  export declare function scratchpadDirFor(localDataRoot: string, sessionId: string): string;
27
27
  export declare function ensureScratchpadDir(localDataRoot: string, sessionId: string): Promise<string | undefined>;
28
+ export declare function acceptShellScratchpadDir(raw: unknown, opts: {
29
+ requirePrincipal: boolean;
30
+ hostSemanticsLane: boolean;
31
+ warn: (msg: string, meta?: object) => void;
32
+ }): Promise<string | undefined>;
28
33
  export declare function purgeScratchpadDir(localDataRoot: string, sessionId: string): Promise<void>;
29
34
  export declare function sweepStaleScratchpads(localDataRoot: string, opts: {
30
35
  olderThanMs: number;
package/dist/env-facts.js CHANGED
@@ -1,4 +1,4 @@
1
- import { join } from "node:path";
1
+ import { isAbsolute, join, resolve, sep } from "node:path";
2
2
  import { mkdir, readdir, rm, stat } from "node:fs/promises";
3
3
  import { createHash } from "node:crypto";
4
4
  const MAX_STR = 64;
@@ -59,6 +59,40 @@ export async function ensureScratchpadDir(localDataRoot, sessionId) {
59
59
  return undefined;
60
60
  }
61
61
  }
62
+ export async function acceptShellScratchpadDir(raw, opts) {
63
+ if (typeof raw !== "string" || raw.length === 0)
64
+ return undefined;
65
+ if (opts.requirePrincipal) {
66
+ opts.warn("shell_scratchpad_ignored", { reason: "multi_tenant", note: "tenant-supplied exempt dir on a shared worker disk — server-computed scratchpad used instead" });
67
+ return undefined;
68
+ }
69
+ if (!opts.hostSemanticsLane) {
70
+ opts.warn("shell_scratchpad_ignored", { reason: "remote_lane", note: "worker-local path is not real to remote-sandbox hands — in-sandbox scratchpad used instead" });
71
+ return undefined;
72
+ }
73
+ if (!isAbsolute(raw)) {
74
+ opts.warn("shell_scratchpad_ignored", { reason: "not_absolute" });
75
+ return undefined;
76
+ }
77
+ if (raw.length > MAX_PATH) {
78
+ opts.warn("shell_scratchpad_ignored", { reason: "too_long", length: raw.length, max: MAX_PATH });
79
+ return undefined;
80
+ }
81
+ const canonical = resolve(raw);
82
+ const depth = canonical.split(sep).filter((s) => s.length > 0).length;
83
+ if (depth < 3) {
84
+ opts.warn("shell_scratchpad_ignored", { reason: "too_shallow", canonical });
85
+ return undefined;
86
+ }
87
+ try {
88
+ await mkdir(canonical, { recursive: true });
89
+ return canonical;
90
+ }
91
+ catch (err) {
92
+ opts.warn("shell_scratchpad_ignored", { reason: "mkdir_failed", error: err instanceof Error ? err.message : String(err) });
93
+ return undefined;
94
+ }
95
+ }
62
96
  export async function purgeScratchpadDir(localDataRoot, sessionId) {
63
97
  const dir = scratchpadDirFor(localDataRoot, sessionId);
64
98
  try {
@@ -23,6 +23,20 @@ export interface FleetTaskRow {
23
23
  };
24
24
  toolUses?: number;
25
25
  transcriptId?: string;
26
+ stoppedBy?: string;
27
+ usage?: {
28
+ totalTokens?: number;
29
+ toolUses?: number;
30
+ durationMs?: number;
31
+ tokens?: number;
32
+ turns?: number;
33
+ costMicroUsd?: number;
34
+ };
35
+ resumable?: boolean;
36
+ editedFiles?: Array<{
37
+ path: string;
38
+ edits: number;
39
+ }>;
26
40
  }
27
41
  export interface FleetWorkflowRow {
28
42
  id: string;
@@ -161,9 +175,27 @@ export declare function fleetRunPublisher(bus: FleetEventBus | undefined, run: {
161
175
  status?: string;
162
176
  }) => void;
163
177
  onChildTerminal: (taskId: string, status: string, altId?: string, toolUseId?: string) => boolean;
164
- onTerminal: (status: string) => void;
178
+ onTerminal: (status: string, residuals?: {
179
+ usage?: FleetTaskRow["usage"];
180
+ stoppedBy?: string;
181
+ transcriptId?: string;
182
+ }) => void;
165
183
  };
166
184
  export type FleetRunPublisher = ReturnType<typeof fleetRunPublisher>;
185
+ export declare function fleetRunResiduals(result: {
186
+ sessionId?: string;
187
+ errorCode?: string;
188
+ stats?: {
189
+ turns?: number;
190
+ tokens?: number;
191
+ toolCalls?: number;
192
+ costMicroUsd?: number;
193
+ };
194
+ } | null | undefined): {
195
+ usage?: FleetTaskRow["usage"];
196
+ stoppedBy?: string;
197
+ transcriptId?: string;
198
+ };
167
199
  export declare function fleetBackgroundChildPublisher(bus?: FleetEventBus, log?: (msg: string, fields: Record<string, unknown>) => void): (e: import("@sema-agent/core").BackgroundChildEvent) => void;
168
200
  export declare function fleetRunLabels(objective: string | undefined): {
169
201
  name: string;
@@ -136,21 +136,47 @@ export function fleetRunPublisher(bus, run) {
136
136
  }
137
137
  return hadRow;
138
138
  },
139
- onTerminal(status) {
139
+ onTerminal(status, residuals) {
140
140
  settledLeg = true;
141
141
  const fleetStatus = runStatusToFleet(status);
142
- bus.publishTask({ id: run.runId, status: fleetStatus, tokens, elapsedMs: Date.now() - startedAt });
142
+ const terminal = fleetStatus === "completed" || fleetStatus === "failed" || fleetStatus === "killed";
143
+ const res = terminal && residuals
144
+ ? {
145
+ ...(residuals.usage ? { usage: residuals.usage } : {}),
146
+ ...(residuals.stoppedBy ? { stoppedBy: residuals.stoppedBy } : {}),
147
+ ...(residuals.transcriptId ? { transcriptId: residuals.transcriptId } : {}),
148
+ }
149
+ : {};
150
+ bus.publishTask({ id: run.runId, status: fleetStatus, tokens, elapsedMs: Date.now() - startedAt, ...res });
143
151
  for (const cid of children)
144
152
  bus.removeTask(cid);
145
153
  children.clear();
146
154
  childStartedAt.clear();
147
155
  childByToolCall.clear();
148
- if (fleetStatus === "completed" || fleetStatus === "failed" || fleetStatus === "killed") {
156
+ if (terminal) {
149
157
  bus.removeTask(run.runId);
150
158
  }
151
159
  },
152
160
  };
153
161
  }
162
+ export function fleetRunResiduals(result) {
163
+ if (!result)
164
+ return {};
165
+ const s = result.stats;
166
+ const usage = s
167
+ ? {
168
+ ...(typeof s.tokens === "number" ? { totalTokens: s.tokens } : {}),
169
+ ...(typeof s.turns === "number" ? { turns: s.turns } : {}),
170
+ ...(typeof s.toolCalls === "number" ? { toolUses: s.toolCalls } : {}),
171
+ ...(typeof s.costMicroUsd === "number" ? { costMicroUsd: s.costMicroUsd } : {}),
172
+ }
173
+ : {};
174
+ return {
175
+ ...(Object.keys(usage).length > 0 ? { usage } : {}),
176
+ ...(result.errorCode === "cancelled" ? { stoppedBy: "user" } : {}),
177
+ ...(result.sessionId ? { transcriptId: result.sessionId } : {}),
178
+ };
179
+ }
154
180
  export function fleetBackgroundChildPublisher(bus, log) {
155
181
  if (!bus)
156
182
  return () => undefined;
@@ -325,7 +351,25 @@ export function fleetBackgroundChildPublisher(bus, log) {
325
351
  return;
326
352
  }
327
353
  const status = e.status === "completed" ? "completed" : e.status === "killed" ? "killed" : "failed";
328
- bus.publishTask({ id: e.taskId, ...rowTags(m), status });
354
+ bus.publishTask({
355
+ id: e.taskId, ...rowTags(m), status,
356
+ ...(e.stoppedBy ? { stoppedBy: e.stoppedBy } : {}),
357
+ ...(e.resumable !== undefined ? { resumable: e.resumable } : {}),
358
+ ...(e.transcriptId ? { transcriptId: e.transcriptId } : {}),
359
+ ...(e.editedFiles !== undefined ? { editedFiles: e.editedFiles.slice(0, 100).map((f) => ({ path: redactSecrets(f.path), edits: f.edits })) } : {}),
360
+ ...(e.usage
361
+ ? {
362
+ usage: {
363
+ ...(e.usage.totalTokens !== undefined ? { totalTokens: e.usage.totalTokens } : {}),
364
+ ...(e.usage.toolUses !== undefined ? { toolUses: e.usage.toolUses } : {}),
365
+ ...(e.usage.durationMs !== undefined ? { durationMs: e.usage.durationMs } : {}),
366
+ ...(e.usage.tokens !== undefined ? { tokens: e.usage.tokens } : {}),
367
+ ...(e.usage.turns !== undefined ? { turns: e.usage.turns } : {}),
368
+ ...(e.usage.costMicroUsd !== undefined ? { costMicroUsd: e.usage.costMicroUsd } : {}),
369
+ },
370
+ }
371
+ : {}),
372
+ });
329
373
  bus.removeTask(e.taskId);
330
374
  if (m.aliasUuid) {
331
375
  const tail = ` ${m.aliasUuid}`;
@@ -221,6 +221,7 @@ export interface TaskRequestBody {
221
221
  toolsDelta?: boolean;
222
222
  };
223
223
  forwardSubagentEvents?: boolean;
224
+ scratchpadDir?: string;
224
225
  retainSubagentSessions?: boolean | {
225
226
  ttlMs?: number;
226
227
  max?: number;
@@ -21,7 +21,7 @@ import { runInBackground, evictIfConflict, stripCheckpointToken, resumeAtHttpSta
21
21
  import { looksLikeJwt } from "../auth-bridge.js";
22
22
  import { redactSteerIn, STEER_IN_MAX_CHARS, STEER_IN_MAX_REQUEST_CHARS } from "../orchestration/workflow-agent-steer.js";
23
23
  import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotificationFoldKey, taskNotificationStreamKey, NotifiedKeys } from "../orchestration/workflow-completion-inbox.js";
24
- import { fleetRunPublisher, fleetRunLabels } from "../fleet/fleet-bus.js";
24
+ import { fleetRunPublisher, fleetRunLabels, fleetRunResiduals } from "../fleet/fleet-bus.js";
25
25
  import { defaultSubagentTailBus, projectTailFrame } from "../fleet/subagent-tail-bus.js";
26
26
  import { RateLimiter } from "../observability/rate-limit.js";
27
27
  import { withPrincipal } from "../observability/principal-context.js";
@@ -679,6 +679,7 @@ export function createHttpServer(deps) {
679
679
  toolOutput: true,
680
680
  messageIdentity: true,
681
681
  forwardSubagentEvents: true,
682
+ scratchpadDir: true,
682
683
  subagentSteer: Boolean(deps.subagentSteerRegistry) && Boolean(deps.runStore),
683
684
  subagentResume: Boolean(deps.subagentSteerRegistry) && Boolean(deps.runStore),
684
685
  taskSettings: { permissions: true, permissionMode: true, model: true, outputStyle: true, env: false, hooks: deps.config.requirePrincipal !== true },
@@ -853,8 +854,12 @@ export function createHttpServer(deps) {
853
854
  return;
854
855
  }
855
856
  streamTaskId = earlyDurableTid;
857
+ let usageKey;
856
858
  streamDetached = detachOnDisconnect;
857
859
  sseHeaders(res, earlyDurableTid ? { "x-task-id": earlyDurableTid } : undefined);
860
+ if (earlyDurableTid) {
861
+ res.write(`event: meta\ndata: ${JSON.stringify({ type: "meta", taskId: earlyDurableTid, ...(prepared.spec.sessionId ? { sessionId: prepared.spec.sessionId } : {}) })}\n\n`);
862
+ }
858
863
  const ac = new AbortController();
859
864
  if (prepared.spec.sessionId)
860
865
  markChildrenStoppedByUserOnAbort(ac.signal, prepared.spec.sessionId, prepared.auth?.principal);
@@ -921,6 +926,9 @@ export function createHttpServer(deps) {
921
926
  const rs = deps.runStore;
922
927
  const tid = durableTaskId;
923
928
  ledgerSink = createLedgerSink({ appendEvent: (seq, type, data) => rs.appendEvent(tid, seq, type, data), persistThinking: deps.config.traceThinking });
929
+ usageKey = prepared.spec.sessionId;
930
+ if (usageKey)
931
+ deps.modelUsage?.register(usageKey);
924
932
  }
925
933
  if (deps.checkpointStore && prepared.spec.sessionId) {
926
934
  await deps.checkpointStore.putCtx(prepared.spec.sessionId, { body: prepared.body, memoryScope: prepared.auth?.memoryScope });
@@ -961,11 +969,11 @@ export function createHttpServer(deps) {
961
969
  throw e;
962
970
  }
963
971
  let fleetSettled = false;
964
- const settleFleet = (status) => {
972
+ const settleFleet = (status, residuals) => {
965
973
  if (fleetSettled)
966
974
  return;
967
975
  fleetSettled = true;
968
- fleetPub?.onTerminal(status);
976
+ fleetPub?.onTerminal(status, residuals);
969
977
  };
970
978
  let liveStreamRef;
971
979
  const subagentHandleEvictions = [];
@@ -1069,16 +1077,25 @@ export function createHttpServer(deps) {
1069
1077
  }
1070
1078
  finalizeTaskResult(ev.result, principal, prepared.spec.objective, prepared.spec.sessionId);
1071
1079
  putRewindAnchor();
1072
- settleFleet(finalResult?.status ?? "completed");
1080
+ settleFleet(finalResult?.status ?? "completed", fleetRunResiduals(finalResult));
1073
1081
  if (ledgerSink && finalResult) {
1082
+ if (usageKey && deps.modelUsage && deps.runStore && durableTaskId) {
1083
+ const rs2 = deps.runStore;
1084
+ const tid2 = durableTaskId;
1085
+ const fr = finalResult;
1086
+ finalResult = await attachModelUsage(fr, { append: ledgerSink.append, getEvents: (_id, after) => rs2.getEvents(tid2, after), modelUsage: deps.modelUsage, taskId: usageKey }).catch(() => fr);
1087
+ }
1074
1088
  await ledgerSink.onDone(finalResult);
1075
1089
  ledgerTerminal = true;
1076
1090
  }
1077
1091
  res.write(`data: ${JSON.stringify({ ...ev, result: finalResult })}\n\n`);
1078
1092
  continue;
1079
1093
  }
1080
- if (ledgerSink)
1094
+ if (ledgerSink) {
1081
1095
  await ledgerSink.onEvent(ev);
1096
+ if (ev.type === "turn_end" && usageKey)
1097
+ await appendModelUsageDelta(ledgerSink.append, deps.modelUsage, usageKey);
1098
+ }
1082
1099
  fleetPub?.onEvent(ev);
1083
1100
  if (ev.type === "status")
1084
1101
  deps.metrics?.inc("brain_retry_total", { phase: String(ev.phase) });
@@ -1164,7 +1181,16 @@ export function createHttpServer(deps) {
1164
1181
  }
1165
1182
  const withApproval = () => approvalCtx && deps.toolApproval ? deps.toolApproval.runWithContext(approvalCtx, withQuestion) : withQuestion();
1166
1183
  await (deps.sendUserFile
1167
- ? deps.sendUserFile.runWithContext({ taskId: askTaskId, emit: async (f) => void (await emitAsk(f)) }, withApproval)
1184
+ ? deps.sendUserFile.runWithContext({
1185
+ taskId: askTaskId,
1186
+ emit: async (f) => {
1187
+ void (await emitAsk(f));
1188
+ if (ledgerSink) {
1189
+ const { type, ...rest } = f;
1190
+ await ledgerSink.append(type, rest).catch(() => undefined);
1191
+ }
1192
+ },
1193
+ }, withApproval)
1168
1194
  : withApproval());
1169
1195
  if (durableTaskId && deps.runStore) {
1170
1196
  if (finalResult?.status === "suspended" && deps.checkpointStore)
@@ -1232,7 +1258,7 @@ export function createHttpServer(deps) {
1232
1258
  await ledgerSink.onDone(finalResult).catch(() => undefined);
1233
1259
  }
1234
1260
  await deps.runStore.setTerminal(durableTaskId, finalResult.status, finalResult, finalResult.errorMessage ?? null);
1235
- settleFleet(finalResult.status);
1261
+ settleFleet(finalResult.status, fleetRunResiduals(finalResult));
1236
1262
  }
1237
1263
  else if (ac.signal.aborted) {
1238
1264
  const err = "stream aborted before completion (run cancelled)";
@@ -1271,6 +1297,8 @@ export function createHttpServer(deps) {
1271
1297
  finally {
1272
1298
  uncountedBillableInflight--;
1273
1299
  clearInterval(hb);
1300
+ if (usageKey)
1301
+ deps.modelUsage?.clear(usageKey);
1274
1302
  }
1275
1303
  if (!ranLive && resp && !res.writableEnded) {
1276
1304
  res.write(`data: ${JSON.stringify({ type: "done", result: resp.body, replay: true })}\n\n`);
@@ -2163,7 +2191,17 @@ export function createHttpServer(deps) {
2163
2191
  });
2164
2192
  return;
2165
2193
  }
2166
- sendJson(res, 200, { taskId: runId, target, content: out.content, output: out.details });
2194
+ const g14 = (() => {
2195
+ const d = out.details;
2196
+ if (!d || d.error !== undefined || d.retrieval_status === "not_ready")
2197
+ return {};
2198
+ if (d.type === "background_bash")
2199
+ return { cursorSemantics: d.details && "bytesDroppedBeforeCursor" in d.details ? "cursor" : "full" };
2200
+ if (d.type === "monitor" || d.type === "background_agent")
2201
+ return { cursorSemantics: "full" };
2202
+ return {};
2203
+ })();
2204
+ sendJson(res, 200, { taskId: runId, target, content: out.content, output: out.details, ...g14 });
2167
2205
  return;
2168
2206
  }
2169
2207
  const subVerbMatch = req.method === "POST" ? (RUN_SUBAGENT_STEER_RE.exec(url) ?? RUN_SUBAGENT_RESUME_RE.exec(url)) : null;
@@ -5078,11 +5116,11 @@ export function createHttpServer(deps) {
5078
5116
  ? fleetRunPublisher(deps.fleetBus, { runId: taskId, scope: fleetScope, rootTaskId: sessionId, ...fleetRunLabels(resumeObjective) })
5079
5117
  : undefined;
5080
5118
  let fleetSettled = false;
5081
- const settleFleet = (status) => {
5119
+ const settleFleet = (status, residuals) => {
5082
5120
  if (fleetSettled)
5083
5121
  return;
5084
5122
  fleetSettled = true;
5085
- fleetPub?.onTerminal(status);
5123
+ fleetPub?.onTerminal(status, residuals);
5086
5124
  };
5087
5125
  let result;
5088
5126
  let reopenConfirmedPark = false;
@@ -5445,7 +5483,7 @@ export function createHttpServer(deps) {
5445
5483
  else
5446
5484
  await deps.runStore.setTerminal(taskId, result.status, stripCheckpointToken(result), result.errorMessage ?? null);
5447
5485
  }
5448
- settleFleet(checkpointReopened ? "suspended" : result.status);
5486
+ settleFleet(checkpointReopened ? "suspended" : result.status, fleetRunResiduals(result));
5449
5487
  return {
5450
5488
  status: 200,
5451
5489
  body: {
package/dist/main.js CHANGED
@@ -21,7 +21,7 @@ import { createSendUserFileIssuer } from "./plugins/send-user-file.js";
21
21
  import { withLedgerRecording } from "./plugins/send-file-ledger.js";
22
22
  import { basename, resolve } from "node:path";
23
23
  import { stat as fsStat, readFile as fsReadFile } from "node:fs/promises";
24
- import { buildEnvFacts, egressForRemoteExec, ensureScratchpadDir, purgeScratchpadDir, sweepStaleScratchpads, resumeFactsForLane } from "./env-facts.js";
24
+ import { acceptShellScratchpadDir, buildEnvFacts, egressForRemoteExec, ensureScratchpadDir, purgeScratchpadDir, sweepStaleScratchpads, resumeFactsForLane } from "./env-facts.js";
25
25
  import { normalizeSuggestNextPrompts, normalizeResilience, normalizeAttachments, normalizeResumeAtMode, resolveTaskLimits, taskAgentsSpecFragment, retainBackgroundProcessesFromBody, toolNameListFromBody, promptProfileFromBody } from "./spec-fields.js";
26
26
  import { createBrain, brainSummary } from "./brain.js";
27
27
  import { loadConfig, logConfigDiagnostics } from "./config.js";
@@ -499,6 +499,12 @@ async function main() {
499
499
  }
500
500
  else {
501
501
  memoryEngine = memoryEngineBackendFor(config);
502
+ if (memoryEngine && process.env.LOCAL_DATA_ROOT && !config.memoryEngineDir) {
503
+ logger.warn("memory_engine_dir_defaulted", {
504
+ root: memoryEngine.root,
505
+ hint: "LOCAL_DATA_ROOT is set but MEMORY_ENGINE_DIR is not — the memory engine is sharing the machine-wide default; set MEMORY_ENGINE_DIR to isolate it under your data root",
506
+ });
507
+ }
502
508
  }
503
509
  let rosterStore;
504
510
  {
@@ -2181,9 +2187,13 @@ async function main() {
2181
2187
  effectiveSettings = Object.keys(rest2).length > 0 ? rest2 : undefined;
2182
2188
  }
2183
2189
  const hostSemanticsLane = config.remoteExec === undefined || config.remoteExec.provider === "host";
2184
- const scratchpadDir = hostSemanticsLane && auth?.sessionId
2185
- ? await ensureScratchpadDir(config.localDataRoot ?? localRoot, auth.sessionId)
2186
- : undefined;
2190
+ const shellScratchpad = await acceptShellScratchpadDir(body.scratchpadDir, {
2191
+ requirePrincipal: config.requirePrincipal,
2192
+ hostSemanticsLane,
2193
+ warn: (msg, meta) => logger.warn(msg, { ...(meta ?? {}), sessionId: auth?.sessionId ?? null }),
2194
+ });
2195
+ const scratchpadDir = shellScratchpad ??
2196
+ (hostSemanticsLane && auth?.sessionId ? await ensureScratchpadDir(config.localDataRoot ?? localRoot, auth.sessionId) : undefined);
2187
2197
  if (effectiveSettings) {
2188
2198
  const fsWriteGate = hostSemanticsLane
2189
2199
  ? (() => {
package/dist/runs.d.ts CHANGED
@@ -5,7 +5,7 @@ import type { Metrics } from "./observability/metrics.js";
5
5
  import type { ModelUsageTracker, PromptManifestTracker } from "./budget.js";
6
6
  import type { ElicitationCoordinator } from "./elicitation.js";
7
7
  import type { QuestionCoordinator } from "./question.js";
8
- import type { FleetRunPublisher } from "./fleet/fleet-bus.js";
8
+ import { type FleetRunPublisher } from "./fleet/fleet-bus.js";
9
9
  import { type WorkflowCompletionInbox } from "./orchestration/workflow-completion-inbox.js";
10
10
  export declare function backgroundAgentOutput(registry: typeof defaultTaskRegistry, handle: string, access: {
11
11
  owner: string;
package/dist/runs.js CHANGED
@@ -3,6 +3,7 @@ import { withPrincipal } from "./observability/principal-context.js";
3
3
  import { redactSecrets } from "./trace/redact.js";
4
4
  import { taskNotificationEventData, appendModelUsageDelta, appendPromptManifest, attachModelUsage } from "./trace/project.js";
5
5
  import { createLedgerSink } from "./trace/ledger-sink.js";
6
+ import { fleetRunResiduals } from "./fleet/fleet-bus.js";
6
7
  import { defaultSubagentTailBus, projectTailFrame } from "./fleet/subagent-tail-bus.js";
7
8
  import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotificationStreamKey, NotifiedKeys } from "./orchestration/workflow-completion-inbox.js";
8
9
  export async function backgroundAgentOutput(registry, handle, access, agentStore) {
@@ -365,13 +366,16 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
365
366
  legLive = false;
366
367
  if (fleetPublisher) {
367
368
  if (reached?.kind === "done")
368
- fleetPublisher.onTerminal(reached.result.errorCode === "cancelled" ? "cancelled" : reached.result.status);
369
+ fleetPublisher.onTerminal(reached.result.errorCode === "cancelled" ? "cancelled" : reached.result.status, fleetRunResiduals(reached.result));
369
370
  else if (reached?.kind === "suspended")
370
371
  fleetPublisher.onTerminal("suspended");
371
372
  else if (reached?.kind === "needs_review")
372
373
  fleetPublisher.onTerminal("needs_review");
373
374
  else
374
- fleetPublisher.onTerminal(cancelCtrl.signal.aborted ? "cancelled" : "failed");
375
+ fleetPublisher.onTerminal(cancelCtrl.signal.aborted ? "cancelled" : "failed", {
376
+ ...(cancelCtrl.signal.aborted ? { stoppedBy: "user" } : {}),
377
+ ...(spec.sessionId ? { transcriptId: spec.sessionId } : {}),
378
+ });
375
379
  }
376
380
  if (inflight?.get(taskId) === cancelCtrl)
377
381
  inflight.delete(taskId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/server",
3
- "version": "1.290.0",
3
+ "version": "1.291.0",
4
4
  "description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",