@sema-agent/server 7.92.1 → 7.92.2

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.
package/dist/runs.js CHANGED
@@ -1,5 +1,5 @@
1
- import { runWithVerification, runCascade, defaultTaskRegistry, terminalProjection, DURABLE_AGENT_HANDLE_RE } from "@sema-agent/core";
2
- import { failureCodeOf, stripResumeToken } from "./terminal.js";
1
+ import { runWithVerification, runCascade, defaultTaskRegistry, DURABLE_AGENT_HANDLE_RE } from "@sema-agent/core";
2
+ import { failureCodeOf, sealResultForExit, outwardPlane } from "./terminal.js";
3
3
  import { GOVERNANCE_HTTP_STATUS } from "./governance-http-status.js";
4
4
  import { redactErrorMessage } from "./observability/run-terminal.js";
5
5
  import { withPrincipal } from "./observability/principal-context.js";
@@ -311,10 +311,10 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
311
311
  await flush();
312
312
  evictIfConflict(runner, spec.sessionId, result);
313
313
  await flushPromptManifest();
314
- const safe = await withModelUsage(stripResumeToken(result));
314
+ const safe = await withModelUsage(sealResultForExit(result));
315
315
  await append("done", { result: safe });
316
316
  reached = { kind: "done", result: safe };
317
- const plane = terminalProjection(safe.terminal);
317
+ const plane = outwardPlane(safe.terminal);
318
318
  await runStore.setTerminal(taskId, plane.status, safe, plane.errorMessage ?? null);
319
319
  recordDone(safe, plane.status);
320
320
  return;
@@ -324,10 +324,10 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
324
324
  await flush();
325
325
  evictIfConflict(runner, spec.sessionId, result);
326
326
  await flushPromptManifest();
327
- const safe = await withModelUsage(stripResumeToken(result));
327
+ const safe = await withModelUsage(sealResultForExit(result));
328
328
  await append("done", { result: safe });
329
329
  reached = { kind: "done", result: safe };
330
- const plane = terminalProjection(safe.terminal);
330
+ const plane = outwardPlane(safe.terminal);
331
331
  await runStore.setTerminal(taskId, plane.status, safe, plane.errorMessage ?? null);
332
332
  recordDone(safe, plane.status);
333
333
  return;
@@ -358,7 +358,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
358
358
  }
359
359
  if (ev.type === "done") {
360
360
  await flush();
361
- const parkStatus = ev.result.terminal.kind === "paused" ? terminalProjection(ev.result.terminal).status : undefined;
361
+ const parkStatus = ev.result.terminal.kind === "paused" ? outwardPlane(ev.result.terminal).status : undefined;
362
362
  if (parkStatus === "suspended") {
363
363
  await sink.appendParked("suspended", await graftParkToolCallId(ev.result, parkCheckpointReader));
364
364
  await flushModelUsage();
@@ -376,10 +376,10 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
376
376
  {
377
377
  const settled = cancelledAtDone ? cancelledResult(ev.result) : ev.result;
378
378
  settledAsCancel = failureCodeOf(settled.terminal) === CANCELLED_CODE;
379
- const safe = await withModelUsage(stripResumeToken(settled));
379
+ const safe = await withModelUsage(sealResultForExit(settled));
380
380
  await sink.appendDone(safe);
381
381
  reached = { kind: "done", result: safe };
382
- const plane = terminalProjection(safe.terminal);
382
+ const plane = outwardPlane(safe.terminal);
383
383
  await runStore.setTerminal(taskId, plane.status, safe, plane.errorMessage ?? null);
384
384
  recordDone(safe, failureCodeOf(safe.terminal) === CANCELLED_CODE ? CANCELLED_CODE : plane.status);
385
385
  }
@@ -436,7 +436,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
436
436
  recordFailOpen("server.runs.terminal-ledger-write-failed", `arm=append-clean-cancel ${errText(e)}`);
437
437
  }
438
438
  reached = { kind: "done", result: c };
439
- await runStore.setTerminal(taskId, "failed", c, terminalProjection(c.terminal).errorMessage ?? null);
439
+ await runStore.setTerminal(taskId, "failed", c, outwardPlane(c.terminal).errorMessage ?? null);
440
440
  recordDone(c, CANCELLED_CODE);
441
441
  }
442
442
  }
@@ -450,7 +450,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
450
450
  recordFailOpen("server.runs.terminal-ledger-write-failed", `arm=flush-catch ${errText(e)}`);
451
451
  }
452
452
  if (reached?.kind === "done") {
453
- const plane = terminalProjection(reached.result.terminal);
453
+ const plane = outwardPlane(reached.result.terminal);
454
454
  settledAsCancel = failureCodeOf(reached.result.terminal) === CANCELLED_CODE;
455
455
  await runStore.setTerminal(taskId, plane.status, reached.result, plane.errorMessage ?? null);
456
456
  }
@@ -469,7 +469,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
469
469
  catch (e) {
470
470
  recordFailOpen("server.runs.terminal-ledger-write-failed", `arm=append-thrown-cancel ${errText(e)}`);
471
471
  }
472
- await runStore.setTerminal(taskId, "failed", c, terminalProjection(c.terminal).errorMessage ?? null);
472
+ await runStore.setTerminal(taskId, "failed", c, outwardPlane(c.terminal).errorMessage ?? null);
473
473
  }
474
474
  else {
475
475
  settledAsCancel = false;
@@ -486,7 +486,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
486
486
  catch {
487
487
  }
488
488
  if (reached?.kind === "done")
489
- recordDone(reached.result, failureCodeOf(reached.result.terminal) === CANCELLED_CODE ? CANCELLED_CODE : terminalProjection(reached.result.terminal).status);
489
+ recordDone(reached.result, failureCodeOf(reached.result.terminal) === CANCELLED_CODE ? CANCELLED_CODE : outwardPlane(reached.result.terminal).status);
490
490
  else if (reached?.kind === "suspended") {
491
491
  metrics?.addGauge("runs_active", -1);
492
492
  metrics?.inc("tasks_total", { status: "suspended" });
@@ -503,7 +503,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
503
503
  unregisterEngineNotice();
504
504
  if (fleetPublisher) {
505
505
  if (reached?.kind === "done")
506
- fleetPublisher.onTerminal(failureCodeOf(reached.result.terminal) === CANCELLED_CODE ? CANCELLED_CODE : terminalProjection(reached.result.terminal).status, fleetRunResiduals(reached.result));
506
+ fleetPublisher.onTerminal(failureCodeOf(reached.result.terminal) === CANCELLED_CODE ? CANCELLED_CODE : outwardPlane(reached.result.terminal).status, fleetRunResiduals(reached.result));
507
507
  else if (reached?.kind === "suspended")
508
508
  fleetPublisher.onTerminal("suspended");
509
509
  else if (reached?.kind === "needs_review")
@@ -16,9 +16,9 @@
16
16
  * (run 行的 `status` / `error` 列、`tasks_total{status}` 计量标签、fleet 行的终态词)。出口之外不拼。
17
17
  *
18
18
  * wire 面(HTTP 任务体、SSE `done.result`)**就是结果本身**,所以它跟着 core 一起丢八键、带 `terminal`
19
- * —— 那里没有投影,只有转发(见 {@link stripResumeToken} 的例外)。
19
+ * —— 那里没有投影,只有转发(见 {@link sealResultForExit} 的例外)。
20
20
  */
21
- import { type PausedCause, type TaskStatus, type TerminalCause } from "@sema-agent/core";
21
+ import { terminalProjection, type PausedCause, type TaskStatus, type TerminalCause } from "@sema-agent/core";
22
22
  /**
23
23
  * 这次失败带的**机器码**(`terminal.failed.code`);非失败终局 ⇒ `undefined`。
24
24
  *
@@ -31,7 +31,46 @@ import { type PausedCause, type TaskStatus, type TerminalCause } from "@sema-age
31
31
  */
32
32
  export declare function failureCodeOf(terminal: TerminalCause): string | undefined;
33
33
  /**
34
- * 把**耐久恢复能力凭据**(`CheckpointToken`)从一条结果里摘掉,再让它进可重放账本 / run 行 / wire。
34
+ * 本仓**对外机器码**的词法(S-548)。附录 A 那张表、core 的点名族(`limits.*` / `config.*` /
35
+ * `resume_at.*` / `output.*` / brain 扁平码)、工具层自注册码 —— 全体成员都是这一形:**小写**的
36
+ * `a-z0-9_`,用 `.` 分族。门 {@link OUTWARD_CODE_SHAPE} 在 `test/terminal-store-error-fold.test.ts`
37
+ * 里对**目录里真有的每一个码**断言这条词法(判据由 catalog 机器派生,不是手抄的一张清单)。
38
+ */
39
+ export declare const OUTWARD_CODE_SHAPE: RegExp;
40
+ /** 域外失败码的**唯一**落点(附录外的新码;原码进日志与 message,不丢)。 */
41
+ export declare const STORE_ERROR_CODE = "internal.store_error";
42
+ /**
43
+ * 一个失败码要上 wire 之前的**归一**(S-548)。在词法内 ⇒ 原样;词法外 ⇒ {@link STORE_ERROR_CODE}。
44
+ *
45
+ * ── 病(test T-47,三库一致)────────────────────────────────────────────────────────────────────
46
+ * 本仓的 SQL 店把**驱动原生错误**原样抛回引擎,而 core 的 `errorCodeOf` 沿 `err.code`/`cause` 链取
47
+ * **第一个非空字符串**当这次失败的机器码 —— 于是 mysql2 的 `ER_BAD_FIELD_ERROR` / node-pg 的 SQLSTATE
48
+ * `42703` **逐字**成了 `POST /v1/tasks` 回给消费端的 `terminal.failed.code`。消费端拿到的是一个它的码表里
49
+ * 根本没有、而且随数据库厂商/版本变化的串。
50
+ *
51
+ * ── 为什么判据是**词法**而不是「在不在附录 A 那张表里」────────────────────────────────────────
52
+ * 亲读实现证伪了白名单形:`output.invalid` / `suspend.loop` / `resume_at.*` 这些**合法的引擎终局码**
53
+ * 今天**不在**附录 A(那张表的辖域是 4xx/5xx 错误体,不是 run 终局),白名单会把它们一起折掉 ——
54
+ * 那不仅丢诊断,还会当场打死 `resumeAtHttpStatus` 的 404/422 分诊腿。词法判据则是一条**正面**规则:
55
+ * 「本仓与引擎的码全是小写点分」,而驱动码全是 SCREAMING_SNAKE(`ER_*` / `PROTOCOL_*` / `ECONNRESET`)
56
+ * 或五位 SQLSTATE(`42703`)—— 两个字符集不相交,一条规则覆盖整类,**零逐码特判**。
57
+ *
58
+ * ⚠️ **辖域如实**:本臂触发条件是「码出了本仓的词法」,所以任何依赖铸出的域外码都落这里,不只 SQL 驱动。
59
+ * 今天可观察的产出方**只有**耐久店那一层(引擎自己的传输/brain 错误在 core 侧已被分类成小写码),故码名
60
+ * 取 `internal.store_error`;原码逐字进 message 与 warn 行,归因从不丢失。
61
+ */
62
+ export declare function outwardFailureCode(code: string): string;
63
+ /**
64
+ * **一条结果离开引擎、进入本仓(可重放账本 / run 行 / wire)的唯一封口** —— 两件事,一个出口:
65
+ * ①摘掉**耐久恢复能力凭据**(`CheckpointToken`);②把**域外的失败码**折成本仓的 `internal.store_error`
66
+ * ({@link outwardFailureCode},S-548)。
67
+ *
68
+ * 🔴 为什么两件事合在一只函数里而不是并列两只(7.92.2 改名 `stripResumeToken` → `sealResultForExit`):
69
+ * 它们是**同一个出口上的同一件事** —— 「这条结果可以见人了吗」。并列两只 = 六个结算点各自要记得调两次,
70
+ * 而漏调的那一次不会红、只会在某天的 wire 上露出一个不该露的东西(凭据或驱动码)。名字跟着职责改:
71
+ * 旧名只说了摘凭据那一半,一个只做一半的名字会让下一个人在另一半上另开出口。
72
+ *
73
+ * ── ① 摘凭据 ──
35
74
  * token 就是 resume 凭据(token-as-auth,search [18] Q1b),**永不离开本服务**;非密的 `gate` /
36
75
  * `checkpointId` / `restoreMode` 有意保留(对账位)。
37
76
  *
@@ -49,9 +88,24 @@ export declare function failureCodeOf(terminal: TerminalCause): string | undefin
49
88
  *
50
89
  * 对任何输入安全(非普通对象 / 无 `terminal` / 因由不带 token ⇒ **同一引用**原样返回,不重塑)。
51
90
  */
52
- export declare function stripResumeToken<T extends {
91
+ export declare function sealResultForExit<T extends {
53
92
  terminal: TerminalCause;
54
93
  }>(result: T): T;
94
+ /**
95
+ * **投影出口的唯一属主**(S-548)—— core 的 `terminalProjection` + 对外码归一,一次。
96
+ *
97
+ * `docs/ASSISTANT-WIRE-CONTRACT.md` §5.0 说清了终局有**两张脸**:①结果**本身**(`done.result` / 200 体 /
98
+ * `GET /v1/runs/:id → result`,零投影)与②**五词投影**(run 行的 `status` / `error_code` 列、fleet 终态词、
99
+ * 决策腿 200 体的三格平面词)。两张脸各有一个属主:①是 {@link sealResultForExit},②就是本函数。
100
+ * **两个属主都折码**,否则同一次失败在两张脸上说两个不同的码 —— 而缺的那一张正是 S-548 修前仍在泄漏的
101
+ * 那条路(codex 对抗复审本轮 [medium],亲读证实:决策腿的 200 体从**未封口的** result 投影 `errorCode`,
102
+ * 于是驱动原生码照旧上 wire,尽管同一段代码落库那份已经折过)。
103
+ *
104
+ * ⇒ 纪律:**`src/` 里除本文件外不许再直接调 core 的 `terminalProjection`**(门在
105
+ * `test/terminal-store-error-fold.test.ts` ④)。折叠**幂等**(折过的码再折还是它自己),所以对一个已经封口的
106
+ * 结果调本函数是安全的 no-op —— 调用方不需要记得"这个 result 封过了没有"。
107
+ */
108
+ export declare function outwardPlane(terminal: TerminalCause): ReturnType<typeof terminalProjection>;
55
109
  /**
56
110
  * 这条腿的结果里**藏着的那次耐久暂停** —— 嵌套边界(verify / cascade)遇到自己驱动不了的耐久 park 时,
57
111
  * 在**它的**边界上如实报 `failed`(码是 `unexpected.suspended` / `unexpected.needs_review`),而把那次
package/dist/terminal.js CHANGED
@@ -3,15 +3,37 @@ import { recordFailOpen } from "./observability/fail-open.js";
3
3
  export function failureCodeOf(terminal) {
4
4
  return terminal.kind === "failed" ? terminal.code : undefined;
5
5
  }
6
- export function stripResumeToken(result) {
6
+ export const OUTWARD_CODE_SHAPE = /^[a-z][a-z0-9_]*(?:\.[a-z0-9_]+)*$/;
7
+ export const STORE_ERROR_CODE = "internal.store_error";
8
+ export function outwardFailureCode(code) {
9
+ return OUTWARD_CODE_SHAPE.test(code) ? code : STORE_ERROR_CODE;
10
+ }
11
+ export function sealResultForExit(result) {
7
12
  const t = result?.terminal;
8
13
  if (t === null || typeof t !== "object")
9
14
  return result;
10
15
  if (t.kind === "paused")
11
16
  return { ...result, terminal: stripPausedToken(t) };
12
- if (t.kind === "failed" && t.nestedPause !== undefined)
13
- return { ...result, terminal: { ...t, nestedPause: stripPausedToken(t.nestedPause) } };
14
- return result;
17
+ if (t.kind !== "failed")
18
+ return result;
19
+ let terminal = t;
20
+ if (t.nestedPause !== undefined)
21
+ terminal = { ...terminal, nestedPause: stripPausedToken(t.nestedPause) };
22
+ if (t.code !== undefined) {
23
+ const outward = outwardFailureCode(t.code);
24
+ if (outward !== t.code) {
25
+ console.warn(`[terminal] outward_failure_code_folded original=${t.code} outward=${outward}`, { message: t.message });
26
+ terminal = { ...terminal, code: outward, message: `${t.message ?? "the durable store failed"} [original code: ${t.code}]` };
27
+ }
28
+ }
29
+ return terminal === t ? result : { ...result, terminal };
30
+ }
31
+ export function outwardPlane(terminal) {
32
+ const plane = terminalProjection(terminal);
33
+ if (plane.errorCode === undefined)
34
+ return plane;
35
+ const outward = outwardFailureCode(plane.errorCode);
36
+ return outward === plane.errorCode ? plane : { ...plane, errorCode: outward };
15
37
  }
16
38
  function stripPausedToken(paused) {
17
39
  const { token: _drop, ...rest } = paused;
@@ -30,7 +52,7 @@ export function persistedPlaneOf(result) {
30
52
  if (r.terminal !== null && typeof r.terminal === "object") {
31
53
  let projected;
32
54
  try {
33
- projected = terminalProjection(r.terminal);
55
+ projected = outwardPlane(r.terminal);
34
56
  }
35
57
  catch (e) {
36
58
  recordFailOpen("server.terminal.persisted-plane-malformed", e instanceof Error ? e.message : String(e));
@@ -47,7 +69,7 @@ export function persistedPlaneOf(result) {
47
69
  }
48
70
  return {
49
71
  ...(typeof r.status === "string" ? { status: r.status } : {}),
50
- ...(typeof r.errorCode === "string" ? { errorCode: r.errorCode } : {}),
72
+ ...(typeof r.errorCode === "string" ? { errorCode: outwardFailureCode(r.errorCode) } : {}),
51
73
  ...(typeof r.errorMessage === "string" ? { errorMessage: r.errorMessage } : {}),
52
74
  ...(typeof r.blockedReason === "string" ? { blockedReason: r.blockedReason } : {}),
53
75
  };
@@ -1,4 +1,5 @@
1
- import { terminalProjection } from "@sema-agent/core";
1
+ import {} from "@sema-agent/core";
2
+ import { outwardPlane } from "../terminal.js";
2
3
  import { modelTextEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, toolRosterDeltaEventData, toolDisclosureEventData, humanInputEventData, turnEndEventData, contextUsageEventData } from "./project.js";
3
4
  import { NotifiedKeys, taskNotificationStreamKey } from "../orchestration/workflow-completion-inbox.js";
4
5
  import { isParkLedgerEventType } from "./ledger-events.js";
@@ -164,7 +165,7 @@ export function createLedgerSink(opts) {
164
165
  const onDone = async (result) => {
165
166
  await flush();
166
167
  if (result.terminal.kind === "paused") {
167
- const parkKind = terminalProjection(result.terminal).status;
168
+ const parkKind = outwardPlane(result.terminal).status;
168
169
  if (isParkLedgerEventType(parkKind)) {
169
170
  await appendParked(parkKind, result);
170
171
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/server",
3
- "version": "7.92.1",
3
+ "version": "7.92.2",
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",
@@ -59,7 +59,7 @@
59
59
  "build:binary:run-local:darwin-arm64": "bun build --compile --target=bun-darwin-arm64 src/run-local.ts --outfile dist/run-local-darwin-arm64"
60
60
  },
61
61
  "dependencies": {
62
- "@sema-agent/core": "7.25.0",
62
+ "@sema-agent/core": "7.25.1",
63
63
  "@sema-agent/settings-schema": "3.0.0",
64
64
  "e2b": "^2.28.0",
65
65
  "libsodium-wrappers": "^0.8.4",
@@ -74,7 +74,7 @@
74
74
  "sharp": "^0.35.3"
75
75
  },
76
76
  "devDependencies": {
77
- "@sema-agent/sdk": "10.0.0",
77
+ "@sema-agent/sdk": "11.0.1",
78
78
  "@types/libsodium-wrappers": "^0.7.14",
79
79
  "@types/node": "22.10.2",
80
80
  "@types/pg": "^8.20.0",