@sema-agent/server 7.73.1 → 7.75.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 (46) hide show
  1. package/USAGE.md +8 -0
  2. package/dist/boot/runner-deps.d.ts +0 -4
  3. package/dist/boot/runner-deps.js +1 -2
  4. package/dist/boot/session-faces.js +0 -2
  5. package/dist/boot/stores.js +3 -16
  6. package/dist/config-types.d.ts +1 -1
  7. package/dist/deployment-governance.d.ts +23 -3
  8. package/dist/deployment-governance.js +23 -2
  9. package/dist/env-facts.d.ts +8 -1
  10. package/dist/http/route-ctx.d.ts +9 -8
  11. package/dist/http/routes/approvals-assistant.js +2 -2
  12. package/dist/http/routes/runs.js +1 -1
  13. package/dist/http/routes/workflows.js +4 -0
  14. package/dist/http/server.js +4 -3
  15. package/dist/main.js +0 -1
  16. package/dist/observability/fail-open.d.ts +2 -2
  17. package/dist/observability/fail-open.js +2 -2
  18. package/dist/observability/run-terminal-log.d.ts +2 -1
  19. package/dist/orchestration/workflow-agent-session-index.d.ts +41 -83
  20. package/dist/orchestration/workflow-agent-session-index.js +14 -33
  21. package/dist/parked-decide.d.ts +30 -33
  22. package/dist/parked-decide.js +11 -13
  23. package/dist/plugins/approval-ask-store-sql.js +1 -1
  24. package/dist/plugins/blob-backend.d.ts +6 -7
  25. package/dist/plugins/checkpoint-store-sql.d.ts +13 -10
  26. package/dist/plugins/checkpoint-store-sql.js +6 -24
  27. package/dist/plugins/file-run-store.d.ts +18 -0
  28. package/dist/plugins/file-run-store.js +27 -0
  29. package/dist/plugins/one-time-migrations.d.ts +66 -0
  30. package/dist/plugins/one-time-migrations.js +193 -0
  31. package/dist/plugins/pg-pool.d.ts +6 -0
  32. package/dist/plugins/pg-pool.js +5 -14
  33. package/dist/plugins/retention-store-sql.js +0 -1
  34. package/dist/plugins/store-backend.d.ts +11 -15
  35. package/dist/plugins/store-backend.js +7 -26
  36. package/dist/plugins/task-attachment-store.d.ts +2 -2
  37. package/dist/plugins/tidb-pool.js +11 -23
  38. package/dist/plugins/workflow-run-store-sql.d.ts +21 -21
  39. package/dist/plugins/workflow-run-store-sql.js +2 -16
  40. package/dist/task-cwd.d.ts +12 -1
  41. package/dist/task-settings.d.ts +6 -0
  42. package/dist/trace/core-keyset-guard.d.ts +11 -2
  43. package/dist/trace/project.d.ts +31 -31
  44. package/dist/trace/project.js +12 -17
  45. package/dist/trace/redact.js +295 -16
  46. package/package.json +3 -3
@@ -122,8 +122,9 @@ export declare function toolStartEventData(ev: {
122
122
  /**
123
123
  * 🔴 **筛子是一只总函数**(S-239,codex 对抗复审 r1 [medium] 亲核复现后修)—— 上面纪律②许诺的
124
124
  * 「坏形 ⇒ report + withhold,**不抛**」此前是靠**逐条枚举**坏形兑现的,枚举不到的那些照样抛:
125
- * `projectSettlement` 对 `note` / `who.approver` 先 `redactSecrets` 再截断,而 `redactSecrets` 在 ≥2^23
126
- * 的单段上爆回溯栈(`RangeError`,S-102/S-106 登记的既有上界,见 `redact.ts` 该行顶注),两条叶各能抛一次。
125
+ * `projectSettlement` 对 `note` / `who.approver` 先 `redactSecrets` 再截断,而 `redactSecrets` 会在
126
+ * 病态输入上爆回溯栈(`RangeError`:≥2^23 单段那一形已由 S-219 的 `atLeast` 铸点闭掉,仍能抛的是 JSON 秘密字段臂的
127
+ * 转义对循环,S-102/S-106 登记的既有上界,见 `redact.ts` 该行顶注),两条叶各能抛一次。
127
128
  *
128
129
  * 抛出去的代价按腿算,而且一条比一条重:写腿落账那处的 `.catch` 看不到**同步**投影期的异常;读腿上它
129
130
  * 直接逃出 `formatEvent` / 投影函数 —— 账本裸回放腿的那一次会当场断流(那一行不上帧、游标不前进、后面的
@@ -135,41 +136,30 @@ export declare function toolStartEventData(ev: {
135
136
  * `note` 一条叶(`approver` 同病)—— 那是给一个类打两个特判,正是本仓要消掉的形。
136
137
  */
137
138
  export declare function screenedGate(v: unknown): GateOutcome | undefined;
138
- /**
139
- * 🔴 **耐久行的**门记录读口(7.72.0;`screenedGate` 的读腿孪生)—— 先把本仓自己写过的**旧拼法**读成它
140
- * 今天的名字({@link RETIRED_ASK_ORIGIN_SPELLINGS}),再走与写腿**同一只**筛子。
141
- *
142
- * 为什么读腿要多这一步而写腿不要:读到的字节可能是**本仓上一个版本**写下的(账本回放、park 行的
143
- * `resolvedOutcome`),而 core 7.14.0 [ref] C2 把 `ASK_ORIGINS` 的一个词改了名 —— 不认旧拼法,那些行就整条
144
- * withhold,连 `disposition` / `settlement`(谁拒的、人怎么结的)一起在回放面上消失。写腿面对的则是**当下**
145
- * 引擎铸的记录,它铸不出退役词,所以那里出现退役词 = producer 契约漂了 = 要 report + withhold 的事。
146
- *
147
- * 不改原对象(调用方与账本共享引用):归一时换一只信封,原记录一个字节不动。
148
- */
149
- export declare function screenedStoredGate(v: unknown): GateOutcome | undefined;
150
139
  /**
151
140
  * 🔴 **账本裸回放面的读腿一步**(S-239)—— `GET /v1/runs/:id/events`(`http/routes/runs.ts` 的
152
- * `formatEvent`)对每一行走这一口,把行上的 `GateOutcome` 交给**同一只** {@link screenedStoredGate}。
141
+ * `formatEvent`)对每一行走这一口,把行上的 `GateOutcome` 交给**同一只** {@link screenedGate}。
153
142
  *
154
143
  * ## 它补的是哪一格
155
144
  * 7.72.0 把耐久读边界收成一只筛子,但点名的读腿只有两条({@link toolResultFieldsOf} 的投影回放、
156
145
  * `/decide` 幂等回放体)。**账本裸回放**是第三条,而它此前只过 `redactLedgerEventData` —— 那只函数
157
146
  * 对 `tool_end` 型**逐字不动**(它管的是 `done.result` / `failed.errorMessage` / park 行三族)。
158
- * 于是同一条账本行在本仓三张读面上有**两种**读法:
159
- * · 本仓 7.64.0–7.71.0 自己写下的 `origin:"unresolvable"` —— 投影面读成今天的名字,裸回放面原样
160
- * 透出一个 core 今天判**非成员**的词(消费端按闭集分派 ⇒ 落进它的 default 臂);
161
- * · 第三方 producer / 坏世代写下的集外词与破不变量的记录 —— 投影面整条 withhold + 计数,裸回放面
162
- * 直接上 wire,而 `gate-outcome-defective` 那一格不涨(运维看不见有 producer 在发坏记录)。
147
+ * 于是第三方 producer / 坏世代写下的集外词与破不变量的记录在投影面整条 withhold + 计数,裸回放面却
148
+ * 直接上 wire,而 `gate-outcome-defective` 那一格不涨(运维看不见有 producer 在发坏记录)。
163
149
  *
164
150
  * ## 为什么是这一形(而不是在路由里加一只筛)
165
- * 筛子仍然只有一只,归一表仍然只有一张 —— 本函数只是把**第三条读腿**接到它上面,规则因此从
166
- * 「三条读腿里有两条筛」回到「凡耐久读腿上的 `GateOutcome` 一律过 `screenedStoredGate`,零例外」。
167
- * 它按**型**分派而不是按调用点,所以将来第二个携带 `GateOutcome` 的账本型只需把
168
- * {@link GATE_BEARING_LEDGER_EVENT_TYPE} 扩成词表,读腿一行都不用改。
169
- *
170
- * ## 与写腿的关系(别再走回头路)
171
- * 写腿的对偶是 `redactLedgerEventData`(三孪生 store 的 `appendEvent` 落库前),它**不该**长出本函数
172
- * 这一步:归一只认读腿,写腿保持严格(理由全文见 {@link RETIRED_ASK_ORIGIN_SPELLINGS} 的边界划清二)。
151
+ * 筛子只有一只 —— 本函数只是把**第三条读腿**接到它上面,规则因此是「凡耐久读腿上的 `GateOutcome`
152
+ * 一律过 {@link screenedGate},零例外」。它按**型**分派而不是按调用点,所以将来第二个携带
153
+ * `GateOutcome` 的账本型只需把 {@link GATE_BEARING_LEDGER_EVENT_TYPE} 扩成词表,读腿一行都不用改。
154
+ *
155
+ * ## 读腿与写腿现在是**同一只**筛(7.74.0 起,零双读)
156
+ * 7.72.0–7.73.0 期间读腿多一步「认本服务上一版写下的退役词旧拼法」(`unresolvable` → `ancestor_marked`),
157
+ * 那是一条**长期双读臂**:同一份字节在写腿与读腿上有两种理解,而且没有到期日。7.74.0 把它换成**一次性
158
+ * 迁移**(启动期把存量字节原地改名,`plugins/one-time-migrations.ts`,随 7.75.0 删)⇒ 读腿退回写腿那只
159
+ * 严格筛子,集外词一律整条 withhold + 计数,不再有第二种读法。
160
+ *
161
+ * ## 与写腿脱敏汇点的关系(别再走回头路)
162
+ * 写腿的对偶是 `redactLedgerEventData`(三孪生 store 的 `appendEvent` 落库前),它管的是脱敏、不是判形;
173
163
  * 两只函数因此是「共用的脱敏汇点 + 读腿独有的筛」这一层关系,不是同一只函数的两种模式。
174
164
  *
175
165
  * 不改原对象(调用方与账本共享引用):有变更时换一只信封,原记录一个字节不动。
@@ -616,7 +606,9 @@ export interface TraceTurn {
616
606
  * 于是一次 usage 半缺(某些 provider 只回 output 侧)在面板上显示成一个可核对的真数字。
617
607
  * 0 是一个数,未知不是 —— 两者同形正是本仓 `boundCallId`/`gateKind`/`contentKind` 一族一直在防的事。
618
608
  * ⚠️ wire 面(`GET /v1/tasks/:id/turns` 与 trace SSE 的 `turn` 事件)因此可能给出**只有一侧**的
619
- * `tokens`;做算术的消费端要按「键可能缺席」读(`tokens` 整只可缺席早就是契约的一部分)。 */
609
+ * `tokens`;做算术的消费端要按「键可能缺席」读(`tokens` 整只可缺席早就是契约的一部分)。
610
+ * 🔴 **7.74.0 / core 7.17.0([ref])**:引擎自陈 `usageMissing` 的一轮(usage 是六个 0 的占位)⇒ 整只缺席 ——
611
+ * 「未知」在这张脸上只有缺席这一种拼法,不加第二枚座位(见 {@link toContractTokens})。 */
620
612
  tokens?: {
621
613
  in?: number;
622
614
  out?: number;
@@ -673,8 +665,16 @@ export declare function turnEndEventData(ev: {
673
665
  * 🔴 S-213⑥:**一条规则管五个子键** —— 源侧在场就铸、缺席就不铸,没有 `?? 0`。此前 `in`/`out` 两座
674
666
  * 各带一个默认 0,于是 usage 半缺(provider 只回 output 侧)被投影成 `in:0` = 一个看起来可核对的真
675
667
  * 数字;`uncachedIn`/`cacheRead`/`cacheWrite` 三键从第一天起就是缺席制,`in`/`out` 只是漏进了默认值。
676
- * 早退分支语义不变:输入两键(择一)与 `outputTokens` 全缺 ⇒ 整只 `tokens` 缺席(不铸空对象)。 */
677
- export declare function toContractTokens(usage: unknown): TraceTurn["tokens"] | undefined;
668
+ * 早退分支语义不变:输入两键(择一)与 `outputTokens` 全缺 ⇒ 整只 `tokens` 缺席(不铸空对象)。
669
+ *
670
+ * 🔴 **7.74.0 / core 7.17.0([ref]):`usageMissing` 是本投影的输入,不只是事件面的一枚标。**引擎自该版起
671
+ * 在「这一轮 provider 没回 usage 帧」时也**恒发** `turn_end.usage` —— 内容是六个 0 的占位对象,旁边挂
672
+ * `usageMissing: true`。只看 usage 会把「未测量」投成一只全 0 对象,与「真的零用量」在
673
+ * `GET /v1/tasks/:id/turns` 与 trace SSE 的 `turn` 事件上逐字节同形 —— 正是 S-213⑥「0 是一个数,未知不是」
674
+ * 要消灭的那一形。契约里「未知 = `tokens` 整只缺席」早就是唯一读法 ⇒ 标在场就**不铸**(与 7.73.1 及以前的
675
+ * wire 读数逐字节相同),不按「六个 0」的值形猜、也不给 `TraceTurn.tokens` 加第二枚「未知」座(同一事实两种
676
+ * 拼法)。标的域是 core 的 `true | 缺席`(永不 `false`)⇒ 严判 `=== true`,别的值不是标。 */
677
+ export declare function toContractTokens(usage: unknown, usageMissing?: unknown): TraceTurn["tokens"] | undefined;
678
678
  /** [ref]C-13:tool_start/tool_end 的**共享挑键**——`projectEvents`(turns 面)与 `mapTraceEvent`
679
679
  * (trace SSE 面)对同一账本行的字段选择逐字同构,此前两处独立手写,structured/totalChars/label
680
680
  * 三次扩字段各改两遍(见两处对偶注)。收一份:两面各自包装键名皮(id/name/input vs 同名),
@@ -86,21 +86,21 @@ export function toolStartEventData(ev) {
86
86
  return { toolCallId: ev.toolCallId, toolName: ev.toolName, ...(ev.label !== undefined ? { label: ev.label } : {}), args: redactDeep(ev.args), ...identityFields(ev) };
87
87
  }
88
88
  const SETTLEMENT_NOTE_MAX = 500;
89
- const RETIRED_ASK_ORIGIN_SPELLINGS = {
90
- unresolvable: "ancestor_marked",
91
- };
92
89
  export function screenedGate(v) {
93
90
  try {
94
91
  return screenedGateOrThrow(v);
95
92
  }
96
93
  catch (e) {
97
- recordFailOpen("server.trace.gate-outcome-defective", `projection threw: ${e instanceof Error ? `${e.name}: ${e.message}` : "non-Error throw"}`);
94
+ recordFailOpen("server.trace.gate-outcome-defective", `projection threw: ${e instanceof Error ? e.name : "non-Error throw"}`);
98
95
  return undefined;
99
96
  }
100
97
  }
101
98
  function screenedGateOrThrow(v) {
102
- if (v === null || typeof v !== "object")
99
+ if (v === null || typeof v !== "object") {
100
+ if (v !== undefined)
101
+ recordFailOpen("server.trace.gate-outcome-defective", "gate is not an object");
103
102
  return undefined;
103
+ }
104
104
  const g = v;
105
105
  const d = g.disposition;
106
106
  if (d === null || typeof d !== "object" || (d.kind !== "allowed" && d.kind !== "denied")) {
@@ -135,13 +135,6 @@ function screenedGateOrThrow(v) {
135
135
  ...(g.origin === undefined ? {} : { origin: g.origin }),
136
136
  };
137
137
  }
138
- export function screenedStoredGate(v) {
139
- if (v === null || typeof v !== "object")
140
- return undefined;
141
- const g = v;
142
- const renamed = typeof g.origin === "string" ? RETIRED_ASK_ORIGIN_SPELLINGS[g.origin] : undefined;
143
- return screenedGate(renamed === undefined ? g : { ...g, origin: renamed });
144
- }
145
138
  const GATE_BEARING_LEDGER_EVENT_TYPE = "tool_end";
146
139
  export function screenedStoredEventData(type, data) {
147
140
  if (type !== GATE_BEARING_LEDGER_EVENT_TYPE)
@@ -151,7 +144,7 @@ export function screenedStoredEventData(type, data) {
151
144
  const d = data;
152
145
  if (d.gate === undefined)
153
146
  return data;
154
- const gate = screenedStoredGate(d.gate);
147
+ const gate = screenedGate(d.gate);
155
148
  if (gate === undefined) {
156
149
  const withheld = { ...d };
157
150
  delete withheld.gate;
@@ -531,7 +524,9 @@ export function turnEndEventData(ev) {
531
524
  ...identityFields(ev),
532
525
  };
533
526
  }
534
- export function toContractTokens(usage) {
527
+ export function toContractTokens(usage, usageMissing) {
528
+ if (usageMissing === true)
529
+ return undefined;
535
530
  if (!usage || typeof usage !== "object")
536
531
  return undefined;
537
532
  const u = usage;
@@ -556,7 +551,7 @@ export function toolCallFieldsOf(d) {
556
551
  };
557
552
  }
558
553
  export function toolResultFieldsOf(d) {
559
- const gate = d.gate === undefined ? undefined : screenedStoredGate(d.gate);
554
+ const gate = d.gate === undefined ? undefined : screenedGate(d.gate);
560
555
  return {
561
556
  callId: String(d.toolCallId ?? ""),
562
557
  ...(typeof d.label === "string" ? { label: d.label } : {}),
@@ -615,7 +610,7 @@ export function projectEvents(events, opts = {}) {
615
610
  });
616
611
  break;
617
612
  case "turn_end":
618
- flushTurn(ev.seq, ev.ts, toContractTokens(d.usage));
613
+ flushTurn(ev.seq, ev.ts, toContractTokens(d.usage, d.usageMissing));
619
614
  break;
620
615
  }
621
616
  }
@@ -677,7 +672,7 @@ function mapTraceEventInner(type, seq, data) {
677
672
  case "tool_end":
678
673
  return { event: "tool-result", data: { seq, ...toolResultFieldsOf(data), isError: Boolean(data.isError), ...(data.output !== undefined ? { output: data.output } : {}) } };
679
674
  case "turn_end": {
680
- const tokens = toContractTokens(data.usage);
675
+ const tokens = toContractTokens(data.usage, data.usageMissing);
681
676
  return { event: "turn", data: { seq, ...(tokens ? { tokens } : {}) } };
682
677
  }
683
678
  case "prompt_assembled":
@@ -2,14 +2,292 @@ import { stripFormatCharacters } from "@sema-agent/core";
2
2
  import { recordFailOpen } from "../observability/fail-open.js";
3
3
  export const normalizeForRedaction = stripFormatCharacters;
4
4
  const REDACTION_TOKEN_RE_SRC = String.raw `«redacted(?::[a-z-]{1,16}){0,2}»`;
5
+ const atLeast = (cls, min) => `${cls}{${min}}${cls}*`;
5
6
  const ENTROPY_SCAN_MAX = 96;
6
7
  const twoStrongAhead = (valueClass) => String.raw `(?=${valueClass}{0,${ENTROPY_SCAN_MAX}}[0-9A-Z]${valueClass}{0,${ENTROPY_SCAN_MAX}}[0-9A-Z])`;
7
8
  const IDENT_CONNECTORS = "[_./-]";
8
9
  const noConnectorAhead = (valueClass) => String.raw `(?!${valueClass}{0,${ENTROPY_SCAN_MAX}}${IDENT_CONNECTORS}${valueClass})`;
9
- const valueArm = (prefixSrc, valueClass, min) => new RegExp(`${prefixSrc}${twoStrongAhead(valueClass)}${valueClass}{${min},}`, "g");
10
+ const WORD_INTERIOR_CLASS = "[A-Za-z0-9]";
11
+ const CH_TAB = 9, CH_LF = 10, CH_CR = 13, CH_ESC = 27, CH_SPACE = 32, CH_PLUS = 43, CH_MINUS = 45, CH_SLASH = 47;
12
+ const CH_0 = 48, CH_9 = 57, CH_COLON = 58, CH_EQ = 61, CH_AT = 64, CH_A = 65, CH_Z = 90;
13
+ const CH_LBRACKET = 91, CH_BACKSLASH = 92, CH_UNDERSCORE = 95;
14
+ const CH_a = 97, CH_b = 98, CH_f = 102, CH_n = 110, CH_r = 114, CH_t = 116, CH_u = 117, CH_z = 122, CH_TILDE = 126;
15
+ const CH_QUOTE = 34;
16
+ const CH_CSI8 = 155;
17
+ const CH_LAQUO = 171;
18
+ const ESC_CHAR = String.fromCharCode(CH_ESC);
19
+ const CSI8_CHAR = String.fromCharCode(CH_CSI8);
20
+ const WORD_INTERIOR_BYTES = (() => {
21
+ const one = new RegExp(`^${WORD_INTERIOR_CLASS}$`);
22
+ const table = new Uint8Array(256);
23
+ for (let c = 0; c < table.length; c++)
24
+ if (one.test(String.fromCharCode(c)))
25
+ table[c] = 1;
26
+ return table;
27
+ })();
28
+ function hexDigitValue(c) {
29
+ if (c >= CH_0 && c <= CH_9)
30
+ return c - CH_0;
31
+ if (c >= CH_A && c <= CH_A + 5)
32
+ return c - CH_A + 10;
33
+ if (c >= CH_a && c <= CH_a + 5)
34
+ return c - CH_a + 10;
35
+ return -1;
36
+ }
37
+ function c1LeadLen(s, i, code) {
38
+ if (s.charCodeAt(i) === code)
39
+ return 1;
40
+ if (s.charCodeAt(i) !== CH_BACKSLASH || s.charCodeAt(i + 1) !== CH_u)
41
+ return 0;
42
+ let v = 0;
43
+ for (let k = i + 2; k < i + 6; k++) {
44
+ const d = hexDigitValue(s.charCodeAt(k));
45
+ if (d < 0)
46
+ return 0;
47
+ v = v * 16 + d;
48
+ }
49
+ return v === code ? 6 : 0;
50
+ }
51
+ const CSI_PARAM_RUN = /[0-?]*/y;
52
+ const INTERMEDIATE_RUN = /[ -/]*/y;
53
+ function skipClassRun(re, s, p) {
54
+ if (p >= s.length)
55
+ return p;
56
+ re.lastIndex = p;
57
+ return re.exec(s) === null ? p : re.lastIndex;
58
+ }
59
+ function scanIntermediateChain(s, p, termLo, termHi, mark) {
60
+ const n = s.length;
61
+ for (;;) {
62
+ p = skipClassRun(INTERMEDIATE_RUN, s, p);
63
+ if (p >= n)
64
+ return p;
65
+ const c = s.charCodeAt(p);
66
+ if (c >= termLo && c <= termHi)
67
+ mark(p + 1);
68
+ if (c !== CH_BACKSLASH)
69
+ return p;
70
+ let k = p;
71
+ while (k < n && s.charCodeAt(k) === CH_BACKSLASH)
72
+ k += 1;
73
+ if (k >= n || s.charCodeAt(k) !== CH_QUOTE)
74
+ return p;
75
+ p = k + 1;
76
+ }
77
+ }
78
+ const ESCAPED_SEPARATOR_UNITS = [
79
+ {
80
+ leads: ["\\b", "\\f", "\\n", "\\r", "\\t", "\\u"],
81
+ scan: (s, i, mark) => {
82
+ if (s.charCodeAt(i) !== CH_BACKSLASH)
83
+ return i;
84
+ const c = s.charCodeAt(i + 1);
85
+ if (c === CH_b || c === CH_f || c === CH_n || c === CH_r || c === CH_t)
86
+ mark(i + 2);
87
+ else if (c === CH_u && hexDigitValue(s.charCodeAt(i + 2)) >= 0 && hexDigitValue(s.charCodeAt(i + 3)) >= 0
88
+ && hexDigitValue(s.charCodeAt(i + 4)) >= 0 && hexDigitValue(s.charCodeAt(i + 5)) >= 0)
89
+ mark(i + 6);
90
+ return i;
91
+ },
92
+ },
93
+ {
94
+ leads: [ESC_CHAR, CSI8_CHAR, "\\u"],
95
+ scan: (s, i, mark) => {
96
+ const esc = c1LeadLen(s, i, CH_ESC);
97
+ let p;
98
+ if (esc > 0) {
99
+ if (s.charCodeAt(i + esc) !== CH_LBRACKET)
100
+ return i;
101
+ p = i + esc + 1;
102
+ }
103
+ else {
104
+ const csi8 = c1LeadLen(s, i, CH_CSI8);
105
+ if (csi8 === 0)
106
+ return i;
107
+ p = i + csi8;
108
+ }
109
+ return scanIntermediateChain(s, skipClassRun(CSI_PARAM_RUN, s, p), CH_AT, CH_TILDE, mark);
110
+ },
111
+ },
112
+ {
113
+ leads: [ESC_CHAR, "\\u"],
114
+ scan: (s, i, mark) => {
115
+ const esc = c1LeadLen(s, i, CH_ESC);
116
+ return esc === 0 ? i : scanIntermediateChain(s, i + esc, CH_0, CH_TILDE, mark);
117
+ },
118
+ },
119
+ ];
120
+ const ESCAPED_SEPARATOR_NEEDLES = [...new Set(ESCAPED_SEPARATOR_UNITS.flatMap((u) => u.leads))];
121
+ const NEEDLE_FIRST_CHARS = [...new Set(ESCAPED_SEPARATOR_NEEDLES.map((n) => n.slice(0, 1)))];
122
+ const RUN_SKIPPABLE_BYTES = (() => {
123
+ const table = new Uint8Array(256);
124
+ for (const first of NEEDLE_FIRST_CHARS) {
125
+ const code = first.charCodeAt(0);
126
+ if (code >= table.length)
127
+ continue;
128
+ const single = ESCAPED_SEPARATOR_NEEDLES.some((n) => n.length === 1 && n.charCodeAt(0) === code);
129
+ const selfPair = ESCAPED_SEPARATOR_NEEDLES.some((n) => n.length > 1 && n.charCodeAt(0) === code && n.charCodeAt(1) === code);
130
+ if (!single && !selfPair)
131
+ table[code] = 1;
132
+ }
133
+ return table;
134
+ })();
135
+ function markEscapedSeparatorTails(s) {
136
+ const any = anyValueArmRe();
137
+ let ends;
138
+ let ascending = true;
139
+ const mark = (end) => {
140
+ if (end > s.length)
141
+ return;
142
+ const tail = s.charCodeAt(end - 1);
143
+ if (tail < WORD_INTERIOR_BYTES.length && WORD_INTERIOR_BYTES[tail] !== 1)
144
+ return;
145
+ any.lastIndex = end;
146
+ if (any.exec(s) === null)
147
+ return;
148
+ if (ends === undefined)
149
+ ends = [end];
150
+ else {
151
+ if (end < ends[ends.length - 1])
152
+ ascending = false;
153
+ ends.push(end);
154
+ }
155
+ };
156
+ const nextAt = new Array(NEEDLE_FIRST_CHARS.length).fill(-1);
157
+ const nextLead = (from) => {
158
+ let best = Number.POSITIVE_INFINITY;
159
+ for (let k = 0; k < NEEDLE_FIRST_CHARS.length; k++) {
160
+ if (nextAt[k] < from) {
161
+ const hit = s.indexOf(NEEDLE_FIRST_CHARS[k], from);
162
+ nextAt[k] = hit === -1 ? Number.POSITIVE_INFINITY : hit;
163
+ }
164
+ if (nextAt[k] < best)
165
+ best = nextAt[k];
166
+ }
167
+ return best === Number.POSITIVE_INFINITY ? -1 : best;
168
+ };
169
+ const n = s.length;
170
+ for (let i = nextLead(0); i >= 0;) {
171
+ let resume = i;
172
+ for (const unit of ESCAPED_SEPARATOR_UNITS) {
173
+ const consumed = unit.scan(s, i, mark);
174
+ if (consumed > resume)
175
+ resume = consumed;
176
+ }
177
+ if (resume === i) {
178
+ const code = s.charCodeAt(i);
179
+ if (code < RUN_SKIPPABLE_BYTES.length && RUN_SKIPPABLE_BYTES[code] === 1) {
180
+ let j = i + 1;
181
+ while (j < n && s.charCodeAt(j) === code)
182
+ j += 1;
183
+ if (j > i + 1)
184
+ resume = j - 1;
185
+ }
186
+ }
187
+ i = nextLead(resume > i ? resume : i + 1);
188
+ }
189
+ if (ends === undefined)
190
+ return undefined;
191
+ if (!ascending)
192
+ ends.sort((x, y) => x - y);
193
+ return ends;
194
+ }
195
+ let escapedTailsText;
196
+ let escapedTailsValue;
197
+ function escapedSeparatorTails(s) {
198
+ if (escapedTailsText !== s) {
199
+ escapedTailsValue = markEscapedSeparatorTails(s);
200
+ escapedTailsText = s;
201
+ }
202
+ return escapedTailsValue;
203
+ }
204
+ function forgetEscapedSeparatorTails() {
205
+ escapedTailsText = undefined;
206
+ escapedTailsValue = undefined;
207
+ }
208
+ const VALUE_ARM_STICKY = new Map();
209
+ const VALUE_ARM_BODIES = [];
210
+ const valueArm = (prefixSrc, valueClass, min) => {
211
+ const body = `${prefixSrc}${twoStrongAhead(valueClass)}${atLeast(valueClass, min)}`;
212
+ const g = new RegExp(`(?<!${WORD_INTERIOR_CLASS})${body}`, "g");
213
+ VALUE_ARM_STICKY.set(g, new RegExp(body, "y"));
214
+ VALUE_ARM_BODIES.push(body);
215
+ return g;
216
+ };
217
+ let anyValueArm;
218
+ function anyValueArmRe() {
219
+ return (anyValueArm ??= new RegExp(`(?:${VALUE_ARM_BODIES.join("|")})`, "y"));
220
+ }
221
+ function runValueArm(g, y, repl, text, wantCount) {
222
+ const tails = escapedSeparatorTails(text);
223
+ if (tails === undefined) {
224
+ if (!wantCount)
225
+ return { out: text.replace(g, repl), count: 0 };
226
+ let hits = 0;
227
+ const replaced = text.replace(g, () => {
228
+ hits += 1;
229
+ return repl;
230
+ });
231
+ return { out: replaced, count: hits };
232
+ }
233
+ let out;
234
+ let copied = 0;
235
+ let count = 0;
236
+ let pos = 0;
237
+ let ti = 0;
238
+ let bIdx = -1;
239
+ let bEnd = -1;
240
+ const nextB = (from) => {
241
+ while (ti < tails.length) {
242
+ const p = tails[ti];
243
+ ti += 1;
244
+ if (p < from)
245
+ continue;
246
+ y.lastIndex = p;
247
+ const m = y.exec(text);
248
+ if (m !== null) {
249
+ bIdx = p;
250
+ bEnd = p + m[0].length;
251
+ return;
252
+ }
253
+ }
254
+ bIdx = -1;
255
+ };
256
+ nextB(0);
257
+ g.lastIndex = 0;
258
+ let a = g.exec(text);
259
+ for (;;) {
260
+ if (a !== null && a.index < pos) {
261
+ g.lastIndex = pos;
262
+ a = g.exec(text);
263
+ }
264
+ if (bIdx >= 0 && bIdx < pos)
265
+ nextB(pos);
266
+ let idx;
267
+ let end;
268
+ if (a !== null && (bIdx < 0 || a.index <= bIdx)) {
269
+ idx = a.index;
270
+ end = a.index + a[0].length;
271
+ }
272
+ else if (bIdx >= 0) {
273
+ idx = bIdx;
274
+ end = bEnd;
275
+ }
276
+ else
277
+ break;
278
+ (out ??= []).push(text.slice(copied, idx), repl);
279
+ copied = end;
280
+ count += 1;
281
+ pos = end;
282
+ }
283
+ if (out === undefined)
284
+ return { out: text, count: 0 };
285
+ out.push(text.slice(copied));
286
+ return { out: out.join(""), count };
287
+ }
10
288
  const WORD_SEGMENT_CLASS = "[A-Za-z0-9._~+/-]";
11
- const keywordArm = (keywordSrc, valueClass, min, tailSrc = "") => new RegExp(`(?:${keywordSrc}\\s+${valueClass}{${min},}${tailSrc}` +
12
- `|(?<!${WORD_SEGMENT_CLASS})${keywordSrc}(?:${twoStrongAhead(valueClass)}|${noConnectorAhead(valueClass)})${valueClass}{${min},}${tailSrc})`, "g");
289
+ const keywordArm = (keywordSrc, valueClass, min, tailSrc = "") => new RegExp(`(?:${keywordSrc}\\s+${atLeast(valueClass, min)}${tailSrc}` +
290
+ `|(?<!${WORD_SEGMENT_CLASS})${keywordSrc}(?:${twoStrongAhead(valueClass)}|${noConnectorAhead(valueClass)})${atLeast(valueClass, min)}${tailSrc})`, "g");
13
291
  const STRUCTURAL_SECRET_PATTERNS = [
14
292
  [/-----BEGIN(?:[^-](?:[^-](?:[^-]|-(?!-))*)?)?PRIVATE KEY(?: BLOCK)?-----[\s\S]{0,8192}?-----END(?:[^-](?:[^-](?:[^-]|-(?!-))*)?)?PRIVATE KEY(?: BLOCK)?-----/g, "«redacted:private-key»"],
15
293
  [/---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----[\s\S]{0,8192}?---- END SSH2 ENCRYPTED PRIVATE KEY ----/g, "«redacted:private-key»"],
@@ -26,22 +304,17 @@ const KEYWORD_SECRET_PATTERNS = [
26
304
  ];
27
305
  const SHAPE_SECRET_PATTERNS = [
28
306
  [valueArm("sk-", "[A-Za-z0-9_-]", 16), "«redacted:key»"],
29
- [/(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{20,}/g, "«redacted:gh-token»"],
30
- [/github_pat_[A-Za-z0-9]{22}_[A-Za-z0-9]{59,}/g, "«redacted:gh-token»"],
31
- [/xox[baprs]-[A-Za-z0-9-]{10,}/g, "«redacted:slack-token»"],
32
- [/AKIA[0-9A-Z]{16,}/g, "«redacted:aws-key»"],
33
- [/ASIA[0-9A-Z]{16,}/g, "«redacted:aws-key»"],
34
- [/(?:gl(?:pat|dt|rt|rtr|cbt|ptt|oas|ft|imt|agent|wt|soat|ffct)-[A-Za-z0-9_-]{20,}|(?:sk|rk)_(?:live|test|org)_[A-Za-z0-9]{24,}|pypi-[A-Za-z0-9_-]{85,})/g, "«redacted:key»"],
35
- [/xapp-\d-[A-Za-z0-9-]{20,}/g, "«redacted:slack-token»"],
307
+ [new RegExp(String.raw `(?:ghp|gho|ghu|ghs|ghr)_${atLeast("[A-Za-z0-9]", 20)}`, "g"), "«redacted:gh-token»"],
308
+ [new RegExp(String.raw `github_pat_[A-Za-z0-9]{22}_${atLeast("[A-Za-z0-9]", 59)}`, "g"), "«redacted:gh-token»"],
309
+ [new RegExp(String.raw `xox[baprs]-${atLeast("[A-Za-z0-9-]", 10)}`, "g"), "«redacted:slack-token»"],
310
+ [new RegExp(String.raw `AKIA${atLeast("[0-9A-Z]", 16)}`, "g"), "«redacted:aws-key»"],
311
+ [new RegExp(String.raw `ASIA${atLeast("[0-9A-Z]", 16)}`, "g"), "«redacted:aws-key»"],
312
+ [new RegExp(String.raw `(?:gl(?:pat|dt|rt|rtr|cbt|ptt|oas|ft|imt|agent|wt|soat|ffct)-${atLeast("[A-Za-z0-9_-]", 20)}|(?:sk|rk)_(?:live|test|org)_${atLeast("[A-Za-z0-9]", 24)}|pypi-${atLeast("[A-Za-z0-9_-]", 85)})`, "g"), "«redacted:key»"],
313
+ [new RegExp(String.raw `xapp-\d-${atLeast("[A-Za-z0-9-]", 20)}`, "g"), "«redacted:slack-token»"],
36
314
  ];
37
315
  const FULL_SECRET_PATTERNS = [...STRUCTURAL_SECRET_PATTERNS, ...KEYWORD_SECRET_PATTERNS, ...SHAPE_SECRET_PATTERNS];
38
316
  const PEM_HEADER_IDENT_MAX = 31;
39
317
  const PEM_BODY_RUN_MIN = 8;
40
- const CH_TAB = 9, CH_LF = 10, CH_CR = 13, CH_SPACE = 32, CH_PLUS = 43, CH_MINUS = 45, CH_SLASH = 47;
41
- const CH_0 = 48, CH_9 = 57, CH_COLON = 58, CH_EQ = 61, CH_A = 65, CH_Z = 90, CH_BACKSLASH = 92, CH_UNDERSCORE = 95;
42
- const CH_a = 97, CH_n = 110, CH_r = 114, CH_t = 116, CH_z = 122;
43
- const CH_QUOTE = 34;
44
- const CH_LAQUO = 171;
45
318
  const isSepCode = (c) => c === CH_SPACE || c === CH_TAB || c === CH_CR || c === CH_LF;
46
319
  const isBase64Code = (c) => (c >= CH_A && c <= CH_Z) || (c >= CH_a && c <= CH_z) || (c >= CH_0 && c <= CH_9) || c === CH_PLUS || c === CH_SLASH || c === CH_EQ;
47
320
  const isIdentStartCode = (c) => (c >= CH_A && c <= CH_Z) || (c >= CH_a && c <= CH_z);
@@ -417,7 +690,7 @@ function redactHeadlessPpkBodies(text) {
417
690
  const FRAGMENT_SECRET_PATTERNS = [
418
691
  [valueArm("sk-", "[A-Za-z0-9_-]", 6), "«redacted:key:partial»"],
419
692
  [valueArm("(?:ghp|gho|ghu|ghs|ghr)_", "[A-Za-z0-9]", 8), "«redacted:gh-token:partial»"],
420
- [/github_pat_[A-Za-z0-9_]{8,}/g, "«redacted:gh-token:partial»"],
693
+ [new RegExp(String.raw `github_pat_${atLeast("[A-Za-z0-9_]", 8)}`, "g"), "«redacted:gh-token:partial»"],
421
694
  [valueArm("xox[baprs]-", "[A-Za-z0-9-]", 4), "«redacted:slack-token:partial»"],
422
695
  [valueArm("AKIA", "[0-9A-Z]", 6), "«redacted:aws-key:partial»"],
423
696
  [valueArm("ASIA", "[0-9A-Z]", 8), "«redacted:aws-key:partial»"],
@@ -450,10 +723,15 @@ const regexRule = ([re, repl], arm) => {
450
723
  throw new Error(`redact: replacement ${JSON.stringify(repl)} uses unsupported ref $${d}`);
451
724
  }
452
725
  const expand = refs.length === 0 ? undefined : (args) => repl.replace(/\$([1-9])/g, (_, d) => args[Number(d)] ?? "");
726
+ const sticky = VALUE_ARM_STICKY.get(re);
727
+ if (sticky !== undefined && refs.length > 0)
728
+ throw new Error(`redact: value arm replacement ${JSON.stringify(repl)} must not use $N refs`);
453
729
  return {
454
730
  label: labelOfToken(repl),
455
731
  arm,
456
732
  run: (text, wantCount, json) => {
733
+ if (sticky !== undefined)
734
+ return runValueArm(re, sticky, repl, text, wantCount);
457
735
  const rx = re;
458
736
  if (!wantCount)
459
737
  return { out: text.replace(rx, repl), count: 0 };
@@ -700,6 +978,7 @@ export function redactSecrets(text, opts) {
700
978
  const wantCount = onRedaction !== undefined && (opts?.observe ?? true);
701
979
  const json = opts?.json === true;
702
980
  const r = applyRules(normalizeForRedaction(text), wantCount, json, opts?.observe ?? true);
981
+ forgetEscapedSeparatorTails();
703
982
  reportHits(r.hits);
704
983
  return r.out;
705
984
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/server",
3
- "version": "7.73.1",
3
+ "version": "7.75.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",
@@ -54,7 +54,7 @@
54
54
  "build:binary:run-local:darwin-arm64": "bun build --compile --target=bun-darwin-arm64 src/run-local.ts --outfile dist/run-local-darwin-arm64"
55
55
  },
56
56
  "dependencies": {
57
- "@sema-agent/core": "7.16.0",
57
+ "@sema-agent/core": "7.17.2",
58
58
  "@sema-agent/settings-schema": "1.10.0",
59
59
  "e2b": "^2.28.0",
60
60
  "libsodium-wrappers": "^0.8.4",
@@ -69,7 +69,7 @@
69
69
  "sharp": "^0.35.3"
70
70
  },
71
71
  "devDependencies": {
72
- "@sema-agent/sdk": "9.1.0",
72
+ "@sema-agent/sdk": "9.2.0",
73
73
  "@types/libsodium-wrappers": "^0.7.14",
74
74
  "@types/node": "22.10.2",
75
75
  "@types/pg": "^8.20.0",