@sema-agent/server 7.55.0 → 7.57.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 (129) hide show
  1. package/MIGRATION.md +3 -3
  2. package/README.md +14 -7
  3. package/README.zh-CN.md +10 -6
  4. package/USAGE.md +207 -59
  5. package/deploy/sema-up/chart/values.yaml +1 -1
  6. package/dist/approval-ask-audit-store.d.ts +100 -1
  7. package/dist/approval-ask-audit-store.js +103 -2
  8. package/dist/approval-card.d.ts +148 -13
  9. package/dist/approval-card.js +82 -13
  10. package/dist/approval-content-kind.d.ts +22 -0
  11. package/dist/approval-content-kind.js +5 -0
  12. package/dist/approval.d.ts +31 -0
  13. package/dist/approval.js +18 -0
  14. package/dist/auto-mode-face.d.ts +111 -0
  15. package/dist/auto-mode-face.js +99 -0
  16. package/dist/bench/s1/arms.js +5 -5
  17. package/dist/bench/s1/live-deps.js +11 -11
  18. package/dist/bench/s1/run-firm.js +3 -0
  19. package/dist/bench/s1/runner-ctx.d.ts +4 -0
  20. package/dist/bench/s1/runner-ctx.js +7 -0
  21. package/dist/boot/config-center.js +23 -1
  22. package/dist/boot/coordinators.js +13 -1
  23. package/dist/boot/leader.d.ts +21 -0
  24. package/dist/boot/leader.js +6 -0
  25. package/dist/boot/parked-revive-gate.d.ts +7 -2
  26. package/dist/boot/parked-revive-gate.js +12 -1
  27. package/dist/boot/resolve-spec.d.ts +3 -0
  28. package/dist/boot/resolve-spec.js +3 -1
  29. package/dist/boot/runner-deps.d.ts +12 -0
  30. package/dist/boot/runner-deps.js +28 -3
  31. package/dist/boot/runtime-caps.d.ts +19 -8
  32. package/dist/boot/runtime-caps.js +50 -20
  33. package/dist/boot/session-shell-gate-registry.d.ts +39 -0
  34. package/dist/boot/session-shell-gate-registry.js +21 -0
  35. package/dist/config-catalog.js +14 -5
  36. package/dist/config-center/types.d.ts +2 -1
  37. package/dist/config-provider.js +1 -0
  38. package/dist/config-types.d.ts +41 -10
  39. package/dist/config.d.ts +21 -0
  40. package/dist/config.js +40 -5
  41. package/dist/http/route-ctx.d.ts +90 -3
  42. package/dist/http/routes/a2a-serve.js +5 -3
  43. package/dist/http/routes/approvals-assistant.js +21 -8
  44. package/dist/http/routes/capabilities.js +21 -4
  45. package/dist/http/routes/leader.js +2 -2
  46. package/dist/http/routes/memory-origin.d.ts +2 -2
  47. package/dist/http/routes/rules.js +3 -2
  48. package/dist/http/routes/runs.d.ts +0 -14
  49. package/dist/http/routes/runs.js +20 -8
  50. package/dist/http/routes/tasks.js +34 -8
  51. package/dist/http/routes/workflows.js +20 -5
  52. package/dist/http/server.d.ts +11 -1
  53. package/dist/http/server.js +220 -34
  54. package/dist/http/wire-types.d.ts +9 -4
  55. package/dist/leader/diffout.js +2 -1
  56. package/dist/leader/endpoint.js +49 -15
  57. package/dist/leader/fanout.js +6 -5
  58. package/dist/leader/leader.js +17 -14
  59. package/dist/leader/merge.js +17 -12
  60. package/dist/leader/planner.js +3 -2
  61. package/dist/leader/repair-oracle.js +6 -4
  62. package/dist/leader/repair-wire.js +5 -3
  63. package/dist/leader/wire.d.ts +80 -1
  64. package/dist/leader/wire.js +55 -26
  65. package/dist/main.js +44 -8
  66. package/dist/observability/err-text.d.ts +6 -0
  67. package/dist/observability/err-text.js +10 -0
  68. package/dist/observability/fail-open.d.ts +40 -0
  69. package/dist/observability/fail-open.js +19 -0
  70. package/dist/observability/metrics.js +1 -1
  71. package/dist/observability/run-terminal-log.d.ts +120 -0
  72. package/dist/observability/run-terminal-log.js +360 -0
  73. package/dist/orchestration/workflow-completion-inbox.d.ts +9 -0
  74. package/dist/orchestration/workflow-completion-inbox.js +8 -0
  75. package/dist/permission-rule-vocab.d.ts +40 -0
  76. package/dist/permission-rule-vocab.js +17 -0
  77. package/dist/plugins/checkpoint-store-sql.d.ts +26 -0
  78. package/dist/plugins/checkpoint-store-sql.js +23 -4
  79. package/dist/plugins/file-run-store.d.ts +3 -34
  80. package/dist/plugins/file-run-store.js +21 -0
  81. package/dist/plugins/local-checkpoint-store.d.ts +10 -0
  82. package/dist/plugins/local-checkpoint-store.js +5 -0
  83. package/dist/plugins/local-session-store.d.ts +9 -9
  84. package/dist/plugins/local-session-store.js +5 -3
  85. package/dist/plugins/memory-run-store.d.ts +3 -15
  86. package/dist/plugins/memory-run-store.js +21 -0
  87. package/dist/plugins/permission-rule-store-file.d.ts +22 -4
  88. package/dist/plugins/permission-rule-store-file.js +25 -11
  89. package/dist/plugins/permission-rule-store-sql.d.ts +60 -30
  90. package/dist/plugins/permission-rule-store-sql.js +39 -23
  91. package/dist/plugins/pg-session-storage.js +17 -13
  92. package/dist/plugins/remote-scratchpad.js +3 -2
  93. package/dist/plugins/run-store-sql.d.ts +3 -42
  94. package/dist/plugins/run-store-sql.js +36 -5
  95. package/dist/plugins/store-backend.d.ts +1 -1
  96. package/dist/plugins/store-contracts.d.ts +70 -1
  97. package/dist/plugins/tidb-session-store.js +18 -14
  98. package/dist/plugins/workflow-run-store-sql.d.ts +5 -0
  99. package/dist/plugins/workflow-run-store-sql.js +10 -2
  100. package/dist/rules-consent.d.ts +5 -4
  101. package/dist/rules-consent.js +25 -46
  102. package/dist/run-cancel-context.d.ts +13 -0
  103. package/dist/run-cancel-context.js +15 -0
  104. package/dist/run-local.js +2 -2
  105. package/dist/runs.d.ts +4 -1
  106. package/dist/runs.js +41 -10
  107. package/dist/runtime-caps-resolver.d.ts +133 -17
  108. package/dist/runtime-caps-resolver.js +44 -4
  109. package/dist/security.d.ts +7 -0
  110. package/dist/task-settings.d.ts +57 -3
  111. package/dist/task-settings.js +78 -5
  112. package/dist/task-workflow.d.ts +32 -2
  113. package/dist/task-workflow.js +8 -3
  114. package/dist/tool-approval.d.ts +38 -63
  115. package/dist/tool-approval.js +102 -74
  116. package/dist/trace/core-keyset-guard.d.ts +2 -2
  117. package/dist/trace/ledger-sink.d.ts +13 -4
  118. package/dist/trace/ledger-sink.js +14 -6
  119. package/dist/trace/project.d.ts +13 -0
  120. package/dist/trace/project.js +10 -1
  121. package/dist/trace/redact.d.ts +22 -11
  122. package/dist/trace/redact.js +655 -20
  123. package/dist/trace/sema-provenance.d.ts +26 -0
  124. package/dist/trace/sema-provenance.js +11 -0
  125. package/dist/turn-activity.d.ts +32 -2
  126. package/dist/turn-activity.js +29 -4
  127. package/package.json +3 -3
  128. package/skills/find-skills.md +1 -1
  129. package/skills/loop.md +1 -1
@@ -1,3 +1,4 @@
1
+ import { memoRedactedRead, memoRedactTerminalResult, redactErrorMessage, redactHead, redactLedgerEventData, redactTerminalResult } from "../../observability/run-terminal-log.js";
1
2
  import { z } from "zod";
2
3
  import { uuidv7, mintCheckpointToken, CheckpointError, validatePendingSteer } from "@sema-agent/core";
3
4
  import { isTerminalRunStatus, isParkedRunStatus } from "../../plugins/store-contracts.js";
@@ -22,10 +23,12 @@ import { normalizeRunEventType } from "../../trace/project.js";
22
23
  import { sendJson, sendError, httpErrorCode, sseHeaders } from "../send.js";
23
24
  import { buildActiveRunConflict } from "../active-run-conflict.js";
24
25
  import { clearTurnActivity, readTurnActivityMs } from "../../turn-activity.js";
26
+ import { buildCancelContext } from "../../run-cancel-context.js";
25
27
  import { buildCrossSliceUsage, readResourceWindow, recordResourceWindow } from "../../resource-window.js";
26
28
  import { headerStr, gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
27
29
  import { ActorAssertionWire } from "@sema-agent/settings-schema";
28
30
  import { RUN_NOT_FOUND_MESSAGE, runNotFoundMessage } from "../route-ctx.js";
31
+ import { withPrincipal } from "../../observability/principal-context.js";
29
32
  export const RUN_ID_RE = /^\/v1\/runs\/([^/]+)(\/events)?$/;
30
33
  export const RUN_CANCEL_RE = /^\/v1\/runs\/([^/]+)\/cancel$/;
31
34
  export const RUN_STEER_RE = /^\/v1\/runs\/([^/]+)\/steer$/;
@@ -136,7 +139,10 @@ async function streamRunEvents(req, res, deps, runStore, taskId, staleMs) {
136
139
  retainedFrom: (id) => runStore.retainedFrom(id),
137
140
  formatEvent: (ev) => {
138
141
  const type = normalizeRunEventType(ev.type);
139
- return { id: ev.seq, event: type, data: { type, ...(ev.data ?? {}) } };
142
+ const data = type === "done" && ev.data && typeof ev.data === "object" && ev.data.result && typeof ev.data.result === "object"
143
+ ? { ...ev.data, result: memoRedactTerminalResult("ev.done.result", ev.data.result) }
144
+ : type === "failed" ? memoRedactedRead("ev.failed", ev.data, (d) => redactLedgerEventData(type, d)) : redactLedgerEventData(type, ev.data);
145
+ return { id: ev.seq, event: type, data: { type, ...(data ?? {}) } };
140
146
  },
141
147
  staleFrame: () => ({ event: "failed", data: { type: "failed", errorMessage: "run stalled (instance lost?)" } }),
142
148
  }, taskId, staleMs);
@@ -251,11 +257,11 @@ async function handleRunsBody(req, res, url, ctx, miss) {
251
257
  status: stale ? "failed" : run.status,
252
258
  ...(lastActivityAt !== undefined ? { msSinceLastActivity: Math.max(0, Date.now() - lastActivityAt) } : {}),
253
259
  ...(crossSliceUsage !== undefined ? { crossSliceUsage } : {}),
254
- result: run.result ?? undefined,
260
+ result: isTerminalRunStatus(run.status) ? memoRedactTerminalResult("run.result", run.result ?? undefined) : redactTerminalResult(run.result ?? undefined),
255
261
  supervisorCost,
256
262
  suggestions,
257
263
  errorCode: stale ? undefined : (run.result?.errorCode ?? undefined),
258
- error: stale ? "run stalled (instance lost?)" : run.error ?? undefined,
264
+ error: stale ? "run stalled (instance lost?)" : isTerminalRunStatus(run.status) ? memoRedactedRead("run.error", run.error ?? undefined, redactErrorMessage) : redactErrorMessage(run.error ?? undefined),
259
265
  jobId: run.jobId ?? undefined,
260
266
  source: run.source ?? undefined,
261
267
  });
@@ -324,7 +330,8 @@ async function handleRunsBody(req, res, url, ctx, miss) {
324
330
  }
325
331
  const err = note;
326
332
  try {
327
- const result = { taskId, sessionId: run.sessionId, status: "failed", errorCode: "cancelled", errorMessage: err, stats: { turns: 0, tokens: 0 } };
333
+ const cancelContext = buildCancelContext(taskId, Date.parse(run.createdAt));
334
+ const result = { taskId, sessionId: run.sessionId, status: "failed", errorCode: "cancelled", errorMessage: err, stats: { turns: 0, tokens: 0 }, ...(cancelContext ? { cancelContext } : {}) };
328
335
  let lastErr;
329
336
  for (let attempt = 0;; attempt++) {
330
337
  try {
@@ -870,7 +877,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
870
877
  deps.logger?.[level]?.("manual_compact_outcome", { taskId, outcome, note: outcome === "compacted" ? "a compacted{trigger:'manual'} event rode the run stream" : `no event (see compaction.${outcome === "mooted" ? "mooted" : outcome} trace / task terminal events)` });
871
878
  }, (e) => {
872
879
  if (e.code !== "steering.not_running") {
873
- deps.logger?.warn?.("manual_compact_failed", { taskId, err: e instanceof Error ? e.message : String(e) });
880
+ deps.logger?.warn?.("manual_compact_failed", { taskId, err: redactHead(e instanceof Error ? e.message : String(e), 2000) });
874
881
  }
875
882
  else {
876
883
  deps.logger?.info?.("manual_compact_not_running", { taskId, note: "stream settled between lookup and compact() — benign unless systematic" });
@@ -934,7 +941,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
934
941
  }
935
942
  async function handleRunVerbsBody(req, res, url, ctx, miss) {
936
943
  const { deps } = ctx;
937
- const { readJson, rateLimited, quotaExceeded, leaseDenied, safeDecode, runSessionAcceptOk } = ctx.helpers;
944
+ const { readJson, rateLimited, quotaExceeded, ownerQuotaDenied, leaseDenied, ownerLeaseDenied, safeDecode, runSessionAcceptOk } = ctx.helpers;
938
945
  const subOutputMatch = req.method === "GET" ? RUN_SUBAGENT_OUTPUT_RE.exec(url) : null;
939
946
  if (subOutputMatch) {
940
947
  if (!deps.runStore || !deps.subagentTaskOutput) {
@@ -1159,7 +1166,12 @@ async function handleRunVerbsBody(req, res, url, ctx, miss) {
1159
1166
  send409("resume.retain_off", "the parent run did not retain sub-agent sessions (set retainSubagentSessions on the run to enable revival)");
1160
1167
  return;
1161
1168
  }
1162
- const marker = await handle.resume(redacted);
1169
+ if (ownerQuotaDenied(res, run.owner ?? undefined))
1170
+ return;
1171
+ if (await ownerLeaseDenied(res, run.owner ?? undefined))
1172
+ return;
1173
+ const resumeFn = handle.resume;
1174
+ const marker = await withPrincipal(run.owner ?? undefined, () => resumeFn(redacted));
1163
1175
  sendJson(res, 200, { taskId: runId, target, status: "running", delivery: "applied", marker, note: `Message queued for delivery to ${handle.agentName ?? target}; it will continue in the background.` });
1164
1176
  return;
1165
1177
  }
@@ -1593,7 +1605,7 @@ export async function createDurableRun(req, res, ctx, prepared, idemKey) {
1593
1605
  backend: deps.backend,
1594
1606
  parkFacility: deps.checkpointStore !== undefined,
1595
1607
  }).active,
1596
- windowMs: deps.config.streamApproval?.windowMs ?? 0,
1608
+ windowMs: deps.config.streamApproval?.windowMs,
1597
1609
  windowMarginMs: deps.config.streamAskWindowMarginMs,
1598
1610
  }
1599
1611
  : undefined, deps.checkpointStore);
@@ -1,3 +1,4 @@
1
+ import { redactErrorMessage, redactHead, redactTerminalResult } from "../../observability/run-terminal-log.js";
1
2
  import { runWithVerification, runCascade, uuidv7 } from "@sema-agent/core";
2
3
  import { graftParkToolCallId, markChildrenStoppedByUserOnAbort, resumeAtHttpStatus, stripCheckpointToken, HEARTBEAT_MS } from "../../runs.js";
3
4
  import { withPrincipal } from "../../observability/principal-context.js";
@@ -7,6 +8,7 @@ import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotific
7
8
  import { createLedgerSink } from "../../trace/ledger-sink.js";
8
9
  import { registerEngineNoticeLeg } from "../../trace/engine-notice-wire.js";
9
10
  import { clearTurnActivity, readTurnActivityMs, recordTurnActivity } from "../../turn-activity.js";
11
+ import { buildCancelContext } from "../../run-cancel-context.js";
10
12
  import { recordResourceWindow } from "../../resource-window.js";
11
13
  import { redactSecrets } from "../../trace/redact.js";
12
14
  import { contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, wiringManifestOperatorEventData, humanInputEventData, appendModelUsageDelta, attachModelUsage } from "../../trace/project.js";
@@ -47,6 +49,13 @@ async function handleTasksBody(req, res, url, ctx, miss) {
47
49
  const { prepareSpec, finalizeTaskResult } = ctx.legs;
48
50
  const reqState = ctx.req;
49
51
  const source = ctx.req.source;
52
+ const noteRunOutcome = (r) => {
53
+ if (!r?.status)
54
+ return;
55
+ reqState.runStatus = r.status;
56
+ if (r.errorCode)
57
+ reqState.runErrorCode = r.errorCode;
58
+ };
50
59
  if (req.method === "POST" && (url === "/v1/tasks" || url === "/v1/tasks/stream")) {
51
60
  const rawIdem = headerStr(req.headers["idempotency-key"]);
52
61
  const idemKey = rawIdem ? scopedIdempotencyKey(rawIdem, source, gatedPrincipal(req, deps.config)) : undefined;
@@ -88,6 +97,7 @@ async function handleTasksBody(req, res, url, ctx, miss) {
88
97
  return;
89
98
  }
90
99
  const earlyDurableTid = deps.runStore && prepared.spec.sessionId ? uuidv7() : undefined;
100
+ const streamLegStartedAt = Date.now();
91
101
  const detachOnDisconnect = headerStr(req.headers["x-detach-on-disconnect"]) === "true";
92
102
  if (detachOnDisconnect && earlyDurableTid === undefined) {
93
103
  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");
@@ -178,6 +188,19 @@ async function handleTasksBody(req, res, url, ctx, miss) {
178
188
  let runRowSettled = false;
179
189
  let durableTaskId;
180
190
  let durableHeartbeat;
191
+ const mintCancelledResult = (errorMessage) => {
192
+ const ctx = durableTaskId === undefined ? undefined : buildCancelContext(durableTaskId, streamLegStartedAt);
193
+ return {
194
+ taskId: durableTaskId ?? "",
195
+ sessionId: prepared.spec.sessionId ?? "",
196
+ status: "failed",
197
+ result: "",
198
+ errorCode: "cancelled",
199
+ errorMessage,
200
+ stats: { turns: 0, tokens: 0 },
201
+ ...(ctx ? { cancelContext: ctx } : {}),
202
+ };
203
+ };
181
204
  let userMsgEntryId;
182
205
  let anchorPut = false;
183
206
  const putRewindAnchor = () => {
@@ -211,7 +234,7 @@ async function handleTasksBody(req, res, url, ctx, miss) {
211
234
  if (detachOnDisconnect && durableTaskId && deps.runStore) {
212
235
  const rs = deps.runStore;
213
236
  const tid = durableTaskId;
214
- ledgerSink = createLedgerSink({ appendEvent: (seq, type, data) => rs.appendEvent(tid, seq, type, data), persistThinking: deps.config.traceThinking, onActivity: () => recordTurnActivity(tid) });
237
+ ledgerSink = createLedgerSink({ appendEvent: (seq, type, data) => rs.appendEvent(tid, seq, type, data), persistThinking: deps.config.traceThinking, onActivity: (kind, statusData) => recordTurnActivity(tid, kind, statusData) });
215
238
  usageKey = durableTaskId;
216
239
  if (usageKey)
217
240
  deps.modelUsage?.register(usageKey);
@@ -380,13 +403,14 @@ async function handleTasksBody(req, res, url, ctx, miss) {
380
403
  if (ev.type === "message_committed" && ev.role === "user")
381
404
  userMsgEntryId = ev.entryId;
382
405
  if (ev.type === "done") {
383
- finalResult = stripCheckpointToken(await graftParkToolCallId(ev.result, deps.checkpointStore));
406
+ finalResult = redactTerminalResult(stripCheckpointToken(await graftParkToolCallId(ev.result, deps.checkpointStore)));
384
407
  if (durableTaskId && finalResult)
385
408
  finalResult = { ...finalResult, taskId: durableTaskId };
386
409
  if (durableTaskId && cancelledViaVerb.has(durableTaskId) && finalResult?.status === "failed" && !finalResult.errorCode) {
387
410
  finalResult = { ...finalResult, errorCode: "cancelled" };
388
411
  }
389
412
  finalizeTaskResult(ev.result, principal, prepared.spec.objective, prepared.spec.sessionId);
413
+ noteRunOutcome(finalResult);
390
414
  putRewindAnchor();
391
415
  settleFleet(finalResult?.status ?? "completed", fleetRunResiduals(finalResult));
392
416
  if (ledgerSink && finalResult) {
@@ -530,7 +554,7 @@ async function handleTasksBody(req, res, url, ctx, miss) {
530
554
  const legWalltimeMs = typeof prepared.spec.limits?.maxWalltimeMs === "number" ? prepared.spec.limits.maxWalltimeMs : undefined;
531
555
  const approvalLeg = resolveApprovalLeg({
532
556
  streamApprovalOn,
533
- windowMs: deps.config.streamApproval?.windowMs ?? 0,
557
+ windowMs: deps.config.streamApproval?.windowMs,
534
558
  windowMarginMs: deps.config.streamAskWindowMarginMs,
535
559
  ...(legWalltimeMs !== undefined ? { legWalltimeMs } : {}),
536
560
  nowMonotonicMs: performance.now(),
@@ -618,13 +642,14 @@ async function handleTasksBody(req, res, url, ctx, miss) {
618
642
  }
619
643
  else {
620
644
  const dcErr = "stream client disconnected before completion (run aborted)";
621
- const dc = { taskId: durableTaskId, sessionId: prepared.spec.sessionId ?? "", status: "failed", errorCode: "cancelled", errorMessage: dcErr, stats: { turns: 0, tokens: 0 } };
645
+ const dc = mintCancelledResult(dcErr);
622
646
  if (ledgerSink && !ledgerTerminal) {
623
647
  ledgerTerminal = true;
624
648
  await ledgerSink.flush().catch(() => undefined);
625
649
  await ledgerSink.append("failed", { errorMessage: dcErr, errorCode: "cancelled" }).catch(() => undefined);
626
650
  }
627
651
  await deps.runStore.setTerminal(durableTaskId, "failed", dc, dcErr);
652
+ noteRunOutcome(dc);
628
653
  settleFleet("failed");
629
654
  }
630
655
  }
@@ -668,7 +693,7 @@ async function handleTasksBody(req, res, url, ctx, miss) {
668
693
  }
669
694
  else if (ac.signal.aborted) {
670
695
  const err = "stream aborted before completion (run cancelled)";
671
- const c = { taskId: durableTaskId, sessionId: prepared.spec.sessionId ?? "", status: "failed", errorCode: "cancelled", errorMessage: err, stats: { turns: 0, tokens: 0 } };
696
+ const c = mintCancelledResult(err);
672
697
  if (ledgerSink && !ledgerTerminal) {
673
698
  ledgerTerminal = true;
674
699
  await ledgerSink.flush().catch(() => undefined);
@@ -699,7 +724,7 @@ async function handleTasksBody(req, res, url, ctx, miss) {
699
724
  resp = await idemCache.run(idemKey, runStreamSubmitLeg, (r) => r.body?.status === "completed");
700
725
  }
701
726
  catch (e) {
702
- deps.logger?.error("stream_error", { closed, err: e instanceof Error ? e.message : String(e) });
727
+ deps.logger?.error("stream_error", { closed, err: redactHead(e instanceof Error ? e.message : String(e), 2000) });
703
728
  if (!closed)
704
729
  throw e;
705
730
  }
@@ -790,7 +815,7 @@ async function handleTasksBody(req, res, url, ctx, miss) {
790
815
  const retryAfterSec = result.errorCode === "memory.admission_required" && result.retryAfterMs !== undefined
791
816
  ? Math.max(1, Math.ceil(result.retryAfterMs / 1000))
792
817
  : undefined;
793
- return { status: resumeAtStatus, body: { errorCode: result.errorCode, error: result.errorMessage ?? "resume-at failed", ...(retryAfterSec !== undefined ? { retryAfterSec } : {}) } };
818
+ return { status: resumeAtStatus, body: { errorCode: result.errorCode, error: redactErrorMessage(result.errorMessage) ?? "resume-at failed", ...(retryAfterSec !== undefined ? { retryAfterSec } : {}) } };
794
819
  }
795
820
  if (result.status === "suspended" && deps.checkpointStore && deps.runStore && prepared.spec.sessionId) {
796
821
  if (durableTaskId)
@@ -805,7 +830,8 @@ async function handleTasksBody(req, res, url, ctx, miss) {
805
830
  if (durableTaskId && deps.runStore)
806
831
  await deps.runStore.setTerminal(durableTaskId, result.status, stripCheckpointToken(result), result.errorMessage ?? null);
807
832
  finalizeTaskResult(result, principal, prepared.spec.objective, prepared.spec.sessionId);
808
- return { status: 200, body: withDiscoveryNotice(stripCheckpointToken(result)) };
833
+ noteRunOutcome(result);
834
+ return { status: 200, body: withDiscoveryNotice(redactTerminalResult(stripCheckpointToken(result))) };
809
835
  };
810
836
  const resp = await idemCache.run(idemKey, runSyncSubmitLeg, (r) => r.status < 400);
811
837
  sendSubmitResult(res, resp);
@@ -2,6 +2,10 @@ import { z } from "zod";
2
2
  import { getWorkflowRun, subscribeWorkflow, deriveAgentDisplayStatus, callKeyOrdinal } from "@sema-agent/core";
3
3
  import { redactSteerIn, STEER_IN_MAX_CHARS, STEER_IN_MAX_REQUEST_CHARS } from "../../orchestration/workflow-agent-steer.js";
4
4
  import { redactSecrets } from "../../trace/redact.js";
5
+ import { createHash } from "node:crypto";
6
+ import { redactErrorMessage, redactHead } from "../../observability/run-terminal-log.js";
7
+ const JOURNAL_PROJECTION_MEMO_CAP = 1024;
8
+ const journalProjectionMemo = new Map();
5
9
  import { sendJson, sendError, sseHeaders } from "../send.js";
6
10
  import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
7
11
  const WORKFLOWS_RE = /^\/v1\/workflows(?:\/([^/]+)(\/stream|\/journal)?)?$/;
@@ -68,16 +72,27 @@ async function handleWorkflowsReadBody(req, res, url, ctx, miss) {
68
72
  const jLimit = Math.min(50, Math.max(1, Number(jq.get("limit") ?? 20) || 20));
69
73
  const jOffset = Math.max(0, Number(jq.get("offset") ?? 0) || 0);
70
74
  const MAX_ROW_BYTES = 64 * 1024;
71
- const firstLine = (t) => (t ? redactSecrets(t.split("\n")[0].slice(0, 300)) : undefined);
75
+ const firstLine = (t) => (t ? redactHead(t.split("\n")[0], 300) : undefined);
72
76
  const MAX_RESULT_CHARS = 2000;
73
- const projectResult = (callKey, r) => ({
77
+ const projectResult = (callKey, r) => {
78
+ const memoKey = `${wfId}\u0000${callKey}\u0000${createHash("sha256").update(JSON.stringify(r)).digest("hex")}`;
79
+ const hit = journalProjectionMemo.get(memoKey);
80
+ if (hit)
81
+ return hit;
82
+ const out = projectResultUncached(callKey, r);
83
+ if (journalProjectionMemo.size >= JOURNAL_PROJECTION_MEMO_CAP)
84
+ journalProjectionMemo.delete(journalProjectionMemo.keys().next().value);
85
+ journalProjectionMemo.set(memoKey, out);
86
+ return out;
87
+ };
88
+ const projectResultUncached = (callKey, r) => ({
74
89
  callKey,
75
90
  ordinal: callKeyOrdinal(callKey),
76
91
  status: r.status,
77
92
  ...(r.errorMessage ? { error: firstLine(r.errorMessage) } : {}),
78
93
  ...(r.result
79
94
  ? {
80
- result: redactSecrets(r.result.slice(0, MAX_RESULT_CHARS)),
95
+ result: redactHead(r.result, MAX_RESULT_CHARS),
81
96
  ...(r.result.length > MAX_RESULT_CHARS ? { resultTruncated: true, resultChars: r.result.length } : {}),
82
97
  }
83
98
  : {}),
@@ -196,7 +211,7 @@ async function handleWorkflowAgentSteerBody(req, res, url, ctx, miss) {
196
211
  sendNotRunningWf(!wfRun
197
212
  ? "workflow agent is not running on this replica (no live handle)"
198
213
  : wfRun.status === "running"
199
- ? "workflow agent is active on another replica cross-replica live-steer is not yet supported"
214
+ ? "no live handle for this workflow agent on this replica — the run may be live on another replica (cross-replica live-steer is not yet supported), or this replica holds no steerable stream for it; read the workflow status faces for progress"
200
215
  : `workflow is ${wfRun.status} — agent is not running`);
201
216
  return;
202
217
  }
@@ -310,7 +325,7 @@ function summarizeWorkflowDetail(run) {
310
325
  createdAt: run.createdAt,
311
326
  durationMs: durationOf(run),
312
327
  rev: run.rev,
313
- error: run.error,
328
+ ...(run.error !== undefined ? { error: redactErrorMessage(run.error) ?? "" } : {}),
314
329
  ...(run.result !== undefined ? { result: redactSecrets(run.result) } : {}),
315
330
  phases,
316
331
  ...(unphasedView ? { unphased: unphasedView } : {}),
@@ -18,7 +18,7 @@ import { type FleetEventBus } from "../fleet/fleet-bus.js";
18
18
  import type { ElicitationCoordinator } from "../elicitation.js";
19
19
  import type { QuestionCoordinator } from "../question.js";
20
20
  import { type ParkedAskRedeem, type ToolApprovalCoordinator } from "../tool-approval.js";
21
- import type { ApprovalAskAuditReadFace } from "../approval-ask-audit-store.js";
21
+ import type { ApprovalAskAuditReadFace, ApprovalLateSettleSink } from "../approval-ask-audit-store.js";
22
22
  import type { PlanCacheProbe } from "../plan-cache-probe.js";
23
23
  import type { Logger } from "../observability/logger.js";
24
24
  import type { Metrics } from "../observability/metrics.js";
@@ -210,6 +210,11 @@ export interface ServiceCoordinatorDeps {
210
210
  * SQL 车道恒缺席(那里有真 ask 店,[ref])—— 键缺席 = 本部署无此面,`[]` = 有面且无崩溃残账,
211
211
  * 两义与 `livePending` 同律可判别。 */
212
212
  approvalAskAudit?: ApprovalAskAuditReadFace;
213
+ /** [ref] 迟到人工结算的**写面**(与 {@link approvalAskAudit} 同一个店实例,两把窄口:读面喂
214
+ * `GET /v1/approvals` 的 `crashConverged`,写面只有 `recordLateSettle` 一个动词 —— 铸造/决议/腿闭
215
+ * 三点的属主是协调器,路由层拿到它们只会长出第二个写者)。在场判据与读面逐字同源(local 车道 ∧
216
+ * 审批面开着);缺席 ⇒ 两条 decide 腿**不写、不抛**,行为与修前逐字相同(SQL 车道恒缺席)。 */
217
+ approvalLateSettle?: ApprovalLateSettleSink;
213
218
  /** SVC-5 ([ref] CORE-5 #6): the process-local registry of STEERABLE workflow-agent handles
214
219
  * (`ctx.agentStream`) live on THIS replica. Enables POST /v1/workflows/:id/agents/:label/steer. See
215
220
  * workflow-agent-steer.ts for the handle-visibility seam status (the registry is real + ready; core does not
@@ -539,6 +544,11 @@ export interface ServiceGovernanceDeps {
539
544
  * pre-admission 读窗用(主力 /v1/runs 是 202 异步,core 进场拒发生在 202 之后永远变不成 429;
540
545
  * 这里是异步车道唯一的 429 出路)。enforcement 真源在 core(RunnerDeps 同店),此门是快速座。 */
541
546
  usageWindowStore?: import("@sema-agent/core").UsageWindowStore;
547
+ /** [ref] ④([ref]/[ref],core [ref] 合修):auto 模式自查读面的三件装配事实(席位在不在 / 合成后的
548
+ * per-principal caps resolver / 分类器配置路由)。`/v1/capabilities.permissionModeAuto.{armed,reason,model}`
549
+ * 只消费它,不自算 —— 席位与 resolver 都必须是递给 `RunnerDeps` 的**同一只**(main.ts 装配),否则又是
550
+ * 「诊断说亮、位说暗」。缺席(桩/一次性 CLI)⇒ 读面如实答 `deployment_incapable`。 */
551
+ autoModeFace?: import("../auto-mode-face.js").AutoModeFaceDeps;
542
552
  }
543
553
  /** **部署自述面**:只被 /health、/v1/capabilities 与提交前置门读的部署事实(多为 live getter,
544
554
  * 不是快照——/health 必须回答「此刻」)。 */