@sema-agent/server 7.76.2 → 7.78.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 (39) hide show
  1. package/dist/boot/runner-deps.js +3 -1
  2. package/dist/config-catalog.js +1 -0
  3. package/dist/config-center/read-warnings.d.ts +10 -1
  4. package/dist/config-center/read-warnings.js +5 -1
  5. package/dist/config-provider.js +2 -2
  6. package/dist/config-types.d.ts +13 -1
  7. package/dist/config.js +2 -1
  8. package/dist/fleet/subagent-tail-bus.d.ts +28 -2
  9. package/dist/fleet/subagent-tail-bus.js +24 -2
  10. package/dist/http/principal-gate.d.ts +19 -0
  11. package/dist/http/principal-gate.js +7 -0
  12. package/dist/http/routes/sessions.js +47 -6
  13. package/dist/http/routes/tasks.js +25 -11
  14. package/dist/http/routes/workflows.js +7 -1
  15. package/dist/http/server.js +18 -23
  16. package/dist/observability/fail-open.d.ts +8 -0
  17. package/dist/observability/fail-open.js +8 -0
  18. package/dist/observability/run-terminal-log.js +1 -0
  19. package/dist/orchestration/workflow-agent-session-index.d.ts +7 -3
  20. package/dist/parked-decide.d.ts +27 -12
  21. package/dist/parked-decide.js +2 -2
  22. package/dist/plugins/file-run-store.d.ts +6 -1
  23. package/dist/plugins/file-run-store.js +10 -0
  24. package/dist/plugins/memory-run-store.d.ts +6 -1
  25. package/dist/plugins/memory-run-store.js +10 -0
  26. package/dist/plugins/run-store-sql.d.ts +13 -2
  27. package/dist/plugins/run-store-sql.js +5 -0
  28. package/dist/plugins/store-contracts.d.ts +16 -0
  29. package/dist/plugins/workflow-run-store-sql.d.ts +7 -3
  30. package/dist/runs.js +4 -10
  31. package/dist/trace/core-keyset-guard.d.ts +7 -7
  32. package/dist/trace/ledger-events.d.ts +7 -1
  33. package/dist/trace/ledger-sink.js +10 -15
  34. package/dist/trace/model-segment.d.ts +55 -0
  35. package/dist/trace/model-segment.js +58 -0
  36. package/dist/trace/project.d.ts +75 -0
  37. package/dist/trace/project.js +123 -3
  38. package/dist/trace/redact.js +163 -17
  39. package/package.json +4 -4
@@ -0,0 +1,58 @@
1
+ import { reasoningEndEventData } from "./project.js";
2
+ const BOUNDARY = new Set([
3
+ "tool_start", "tool_end", "status", "context_usage", "compacted", "diagnostics",
4
+ "steering_injected", "compaction_outcome", "workspace_changed", "wiring_manifest",
5
+ "tool_roster_delta", "human_input", "task_notification", "turn_end", "tool_disclosure",
6
+ ]);
7
+ export function isModelSegmentBoundary(type) {
8
+ return BOUNDARY.has(type);
9
+ }
10
+ export function reasoningStreamOfTask(...ids) {
11
+ const set = new Set(ids.filter((v) => typeof v === "string" && v !== ""));
12
+ return (ident) => (ident.sourceTaskId !== undefined && set.has(ident.sourceTaskId)) || (ident.bgAgentId !== undefined && set.has(ident.bgAgentId));
13
+ }
14
+ function streamKeyOf(ident) {
15
+ return `${ident.parentToolCallId ?? ""}\u0000${ident.sourceTaskId ?? ""}\u0000${ident.bgAgentId ?? ""}`;
16
+ }
17
+ function identOf(ev) {
18
+ const s = (v) => (typeof v === "string" ? v : undefined);
19
+ return { parentToolCallId: s(ev.parentToolCallId), sourceTaskId: s(ev.sourceTaskId), bgAgentId: s(ev.bgAgentId) };
20
+ }
21
+ export function createReasoningSegments() {
22
+ const open = new Map();
23
+ const cutKey = (key) => {
24
+ const seg = open.get(key);
25
+ if (seg === undefined)
26
+ return [];
27
+ open.delete(key);
28
+ const data = reasoningEndEventData({ ...seg.ident, eventId: seg.eventId, content: seg.buf });
29
+ return data === null ? [] : [{ type: "reasoning_end", ...data }];
30
+ };
31
+ return {
32
+ onEvent: (ev) => {
33
+ if (ev.type === "reasoning_delta") {
34
+ if (typeof ev.delta !== "string" || ev.delta === "")
35
+ return [];
36
+ const ident = identOf(ev);
37
+ const key = streamKeyOf(ident);
38
+ const seg = open.get(key);
39
+ if (seg === undefined)
40
+ open.set(key, { ident, eventId: ev.eventId, buf: ev.delta });
41
+ else
42
+ seg.buf += ev.delta;
43
+ return [];
44
+ }
45
+ return isModelSegmentBoundary(ev.type ?? "") ? cutKey(streamKeyOf(identOf(ev))) : [];
46
+ },
47
+ cut: (match) => {
48
+ const out = [];
49
+ for (const [key, seg] of [...open]) {
50
+ if (match !== undefined && !match(seg.ident))
51
+ continue;
52
+ out.push(...cutKey(key));
53
+ }
54
+ return out;
55
+ },
56
+ };
57
+ }
58
+ //# sourceMappingURL=model-segment.js.map
@@ -194,6 +194,23 @@ export declare function screenedStoredEventData(type: string, data: unknown): un
194
194
  * 坏形(content 非串/空串)⇒ null(丢帧,不铸「权威空段」:core 契约明言空段不发本帧,消费端收到空段会清掉已攒 delta)。
195
195
  * 身份四键零字节变换(live 现状带全四键,消费端可能已依赖)。 */
196
196
  export declare function textEndEventData(ev: Record<string, unknown>): Record<string, unknown> | null;
197
+ /**
198
+ * `reasoning_end` 段末帧的**唯一**铸点(三条 live 腿共用:top stream / 转发子代腿 / 子代 tail 面)——
199
+ * `text_end` 的推理面孪生({@link textEndEventData}),形逐字同(共用 {@link segmentEndFields})。
200
+ *
201
+ * 🔴 **本帧由 server 自铸,core 不发**(`TaskEvent` 上只有 `reasoning_delta`)。`content` = 该段
202
+ * `reasoning_delta` 的累积原文,经与 durable 面**同一只** `redactModelText` —— 这正是本帧存在的理由:
203
+ * 逐 chunk 的 `reasoning_delta` 对**跨 chunk** 的凭据形无能为力(半截 `sk-` 在任何一块里都不成形),
204
+ * 而账本腿攒到 flush 边界的那一整段早就脱得干净;live 面此前没有这份权威副本,壳无可替换。
205
+ * 段界与账本腿**同一只谓词**(`trace/model-segment.ts` 的 `isModelSegmentBoundary`),所以同一次运行里
206
+ * 「账本 `reasoning` 行」与本帧的正文逐字节相等。
207
+ *
208
+ * **空段不发**,判据比 `text_end` 宽一格(那边是 core 契约的「空段不发」,只需判空串):本帧的正文由
209
+ * 本仓攒出,一段**全空白**的思考(模型吐了几个换行就去调工具)没有可替换的权威正文,而消费律是
210
+ * 「整段替换已攒的 delta」—— 发一枚空白权威段 = 让消费端把已渲染的思考清成空白。坏形(`content` 非串)
211
+ * 同样丢帧,理由与 `text_end` 逐字同源。
212
+ */
213
+ export declare function reasoningEndEventData(ev: Record<string, unknown>): Record<string, unknown> | null;
197
214
  /** The durable `tool_end` payload (shared by both append sites). E1: the model-facing `output` is UNTRUSTED RAW
198
215
  * from core (file/shell/API bodies) — core only SIZE-bounds it (`truncated`), REDACTION is ours (same contract as
199
216
  * `tool_start.args`); never re-fed to a model. E2: identity. Absent `output` ⇒ the tool produced no content body.
@@ -219,6 +236,63 @@ export declare function toolEndEventData(ev: {
219
236
  eventId?: string;
220
237
  parentToolCallId?: string;
221
238
  }): Record<string, unknown>;
239
+ /**
240
+ * `tool_progress` 的白名单投影(core 7.18.0 [ref])—— 一只**仍在跑**的工具调用的存活 tick。
241
+ *
242
+ * ## 这一帧只上 live,不落账(判据是 core 的契约,不是本仓的偏好)
243
+ * core `@contract tool_progress.ephemeral` 逐字:「LIVE ONLY, like `task_progress`/`status`: core never
244
+ * persists this frame (no session-tree entry, no checkpoint state, no resume replay)」。本仓对
245
+ * `task_progress` / `status` 正是同一处置(两条 durable 腿零 case),本帧照抄。
246
+ * 另一条 core 契约把「不落账」从偏好抬成正确性:`@contract tool_progress.cadence` 逐字「A call that
247
+ * finishes inside one interval emits NO frame at all」⇒ **帧的缺席从来不是证据**;落账会让回放面凭空
248
+ * 多出一份「这只调用跑了多久」的账,而那件事的真源是 `tool_end`(core 明写 `elapsedTimeSeconds`
249
+ * **不等于** `tool_end` 的 duration)。
250
+ *
251
+ * ## 信任划线
252
+ * · `toolCallId` / `toolName` —— 引擎铸的 id 与名册里的工具名(与 `tool_start` 逐字同源)⇒ verbatim。
253
+ * · `elapsedTimeSeconds` —— 引擎铸的整秒,单调、一 call 一个原点 ⇒ 有限数才铸键。
254
+ * · `output` —— 🔴 **UNTRUSTED RAW**,core 逐字「exactly as `tool_end.output`: process output that may
255
+ * carry secrets, paths or PII; core bounds only its SIZE, and a consumer MUST redact before persisting
256
+ * or displaying it」⇒ 与 `tool_end.output` **同一只** `redactDeep`,一个字节的例外都不开。
257
+ * · `totalLines` / `totalBytes` —— 引擎计数;core 的缺席律逐字:三者**同进同退**(在场=「观察到了输出」,
258
+ * 缺席=「一个字节都没观察到」,**从不是**「与上一帧相比没变」)。所以本投影按 `output` 的在场判三者:
259
+ * `output` 读不出 ⇒ 三键一起缺席,不单独留下两个计数(那会把「没输出」谎报成「有输出但读不出」)。
260
+ */
261
+ export declare function toolProgressEventData(ev: {
262
+ toolCallId?: unknown;
263
+ toolName?: unknown;
264
+ elapsedTimeSeconds?: unknown;
265
+ output?: unknown;
266
+ totalLines?: unknown;
267
+ totalBytes?: unknown;
268
+ eventId?: unknown;
269
+ parentToolCallId?: unknown;
270
+ }): Record<string, unknown> | undefined;
271
+ /**
272
+ * `tool_disclosure` 的白名单投影(core 7.18.0 [ref])—— 本条腿的**延迟工具普查**:哪些挂载的工具今天只是
273
+ * 名字占位、模型已经激活了哪些、以及是哪条策略判的。
274
+ *
275
+ * ## 三腿对称(与 `tool_roster_delta` 同判、同理由)
276
+ * 开腿那一刻的快照住在 `wiring_manifest` 之后的**第一帧**,而**之后每一次激活**只活在本帧上。不落账 ⇒
277
+ * 断线重连 / 后台跑完再回来的耐久面永远停在开腿版本,而壳正是据它判「这只工具现在还是不是占位名」。
278
+ * 本帧是**整只快照**语义(`deferred` / `activated` 全量,消费端整只替换),所以回放不需要合并。
279
+ *
280
+ * ## 缺席语义(core 逐字,别读错)
281
+ * · **整帧缺席** = 「这条腿什么都没延迟」,**不是**「这个 build 没有这个面」、也不是「未知」;
282
+ * · `thresholdPercent` 缺席 = `always` / `never` 两档下**根本没查过阈值**,不许读成「取了默认百分比」;
283
+ * · 一条 resume 腿的**首帧** `activated` 可能非空(引擎重播的),**不许**读成「这条腿激活了它们」。
284
+ *
285
+ * 成员全是闭形值(策略闭词 + 数字 + 名册里的工具名)⇒ 零脱敏,与 `tool_roster_delta` 的名册行同待遇。
286
+ * `policy` **不在本仓枚举**(闭集词表属主在 core;抄一份会把 core 加的新词吞成缺席)。
287
+ */
288
+ export declare function toolDisclosureEventData(ev: {
289
+ policy?: unknown;
290
+ thresholdPercent?: unknown;
291
+ deferred?: unknown;
292
+ activated?: unknown;
293
+ eventId?: unknown;
294
+ parentToolCallId?: unknown;
295
+ }): Record<string, unknown> | undefined;
222
296
  /** The `task_progress` payload (a subagent's live progress tick) — EXPLICIT WHITELIST shared by all
223
297
  * three legs (bg runs.ts + resume append + the sync live SSE frame): taskId/parent linkage/token rollup verbatim,
224
298
  * `name` UNTRUSTED (core sanitizes for rendering, does NOT secret-redact — a taskName can be secret-shaped) →
@@ -662,6 +736,7 @@ export declare function contextUsageEventData(ev: {
662
736
  usedTokens?: unknown;
663
737
  windowTokens?: unknown;
664
738
  compactAtTokens?: unknown;
739
+ sections?: unknown;
665
740
  eventId?: unknown;
666
741
  parentToolCallId?: unknown;
667
742
  }): Record<string, unknown>;
@@ -110,8 +110,12 @@ function screenedGateOrThrow(v) {
110
110
  recordFailOpen("server.trace.gate-outcome-defective", "disposition is not one of the two arms");
111
111
  return undefined;
112
112
  }
113
+ const classifierFallbackRaw = d.classifier !== null && typeof d.classifier === "object" ? d.classifier.fallback : undefined;
113
114
  const shapeDefect = (d.kind === "denied" && typeof d.deniedBy !== "string" && "disposition.deniedBy is not a string") ||
114
115
  (d.kind === "denied" && d.cause !== undefined && typeof d.cause !== "string" && "disposition.cause is not a string") ||
116
+ (d.classifier !== undefined && (d.classifier === null || typeof d.classifier !== "object") && "disposition.classifier is not an object") ||
117
+ (classifierFallbackRaw !== undefined && (classifierFallbackRaw === null || typeof classifierFallbackRaw !== "object") && "disposition.classifier.fallback is not an object") ||
118
+ (classifierFallbackRaw !== undefined && classifierFallbackRaw !== null && typeof classifierFallbackRaw === "object" && typeof classifierFallbackRaw.cause !== "string" && "disposition.classifier.fallback.cause is not a string") ||
115
119
  (g.settlement !== undefined && (g.settlement === null || typeof g.settlement !== "object") && "settlement is not an object") ||
116
120
  (g.settlement !== undefined && g.settlement !== null && typeof g.settlement.kind !== "string" && "settlement.kind is not a string") ||
117
121
  (g.origin !== undefined && typeof g.origin !== "string" && "origin is not a string") ||
@@ -125,6 +129,16 @@ function screenedGateOrThrow(v) {
125
129
  recordFailOpen("server.trace.gate-outcome-defective", defects.join("; "));
126
130
  return undefined;
127
131
  }
132
+ const attribution = d.classifier;
133
+ const classifier = attribution === undefined
134
+ ? undefined
135
+ : {
136
+ modelRequested: attribution.modelRequested,
137
+ modelUsed: attribution.modelUsed,
138
+ ...(attribution.fallback === undefined
139
+ ? {}
140
+ : { fallback: { from: attribution.fallback.from, to: attribution.fallback.to, cause: attribution.fallback.cause } }),
141
+ };
128
142
  const settlement = g.settlement === undefined ? undefined : projectSettlement(g.settlement);
129
143
  if (g.settlement !== undefined && settlement === undefined) {
130
144
  recordFailOpen("server.trace.gate-outcome-defective", "settlement.who is not an object with a party in the closed set");
@@ -132,8 +146,8 @@ function screenedGateOrThrow(v) {
132
146
  }
133
147
  return {
134
148
  disposition: d.kind === "denied"
135
- ? { kind: "denied", deniedBy: d.deniedBy, ...(d.cause === undefined ? {} : { cause: d.cause }) }
136
- : { kind: "allowed" },
149
+ ? { kind: "denied", deniedBy: d.deniedBy, ...(d.cause === undefined ? {} : { cause: d.cause }), ...(classifier === undefined ? {} : { classifier }) }
150
+ : { kind: "allowed", ...(classifier === undefined ? {} : { classifier }) },
137
151
  ...(settlement === undefined ? {} : { settlement }),
138
152
  ...(g.origin === undefined ? {} : { origin: g.origin }),
139
153
  };
@@ -176,14 +190,22 @@ function projectSettlement(st) {
176
190
  export function textEndEventData(ev) {
177
191
  if (typeof ev.content !== "string" || ev.content.length === 0)
178
192
  return null;
193
+ return segmentEndFields(ev.content, ev);
194
+ }
195
+ function segmentEndFields(content, ev) {
179
196
  return {
180
- content: redactModelText(ev.content),
197
+ content: redactModelText(content),
181
198
  ...(ev.eventId !== undefined ? { eventId: ev.eventId } : {}),
182
199
  ...(ev.parentToolCallId !== undefined ? { parentToolCallId: ev.parentToolCallId } : {}),
183
200
  ...(ev.sourceTaskId !== undefined ? { sourceTaskId: ev.sourceTaskId } : {}),
184
201
  ...(ev.bgAgentId !== undefined ? { bgAgentId: ev.bgAgentId } : {}),
185
202
  };
186
203
  }
204
+ export function reasoningEndEventData(ev) {
205
+ if (typeof ev.content !== "string" || ev.content.trim().length === 0)
206
+ return null;
207
+ return segmentEndFields(ev.content, ev);
208
+ }
187
209
  export function toolEndEventData(ev) {
188
210
  const gate = ev.gate === undefined ? undefined : screenedGate(ev.gate);
189
211
  return {
@@ -202,6 +224,43 @@ export function toolEndEventData(ev) {
202
224
  ...identityFields(ev),
203
225
  };
204
226
  }
227
+ const dropProjectedFace = (what) => {
228
+ recordFailOpen("server.trace.projection-input-malformed", what);
229
+ return undefined;
230
+ };
231
+ export function toolProgressEventData(ev) {
232
+ const id = typeof ev.toolCallId === "string" && ev.toolCallId !== "" ? ev.toolCallId : undefined;
233
+ const name = typeof ev.toolName === "string" && ev.toolName !== "" ? ev.toolName : undefined;
234
+ if (id === undefined || name === undefined)
235
+ return dropProjectedFace("tool_progress is missing toolCallId/toolName");
236
+ const n = (v) => (typeof v === "number" && Number.isFinite(v) ? v : undefined);
237
+ const hasOutput = typeof ev.output === "string";
238
+ return {
239
+ toolCallId: id,
240
+ toolName: name,
241
+ ...(n(ev.elapsedTimeSeconds) !== undefined ? { elapsedTimeSeconds: n(ev.elapsedTimeSeconds) } : {}),
242
+ ...(hasOutput ? { output: redactDeep(ev.output) } : {}),
243
+ ...(hasOutput && n(ev.totalLines) !== undefined ? { totalLines: n(ev.totalLines) } : {}),
244
+ ...(hasOutput && n(ev.totalBytes) !== undefined ? { totalBytes: n(ev.totalBytes) } : {}),
245
+ ...identityFields(ev),
246
+ };
247
+ }
248
+ export function toolDisclosureEventData(ev) {
249
+ const policy = typeof ev.policy === "string" && ev.policy !== "" ? ev.policy : undefined;
250
+ const names = (v) => (Array.isArray(v) ? v.filter((x) => typeof x === "string" && x !== "") : undefined);
251
+ const deferred = names(ev.deferred);
252
+ const activated = names(ev.activated);
253
+ if (policy === undefined || deferred === undefined || activated === undefined)
254
+ return dropProjectedFace("tool_disclosure is missing policy/deferred/activated");
255
+ const threshold = typeof ev.thresholdPercent === "number" && Number.isFinite(ev.thresholdPercent) ? ev.thresholdPercent : undefined;
256
+ return {
257
+ policy,
258
+ ...(threshold !== undefined ? { thresholdPercent: threshold } : {}),
259
+ deferred,
260
+ activated,
261
+ ...identityFields(ev),
262
+ };
263
+ }
205
264
  export function taskProgressEventData(ev) {
206
265
  return {
207
266
  taskId: ev.taskId,
@@ -382,6 +441,41 @@ export function wiringManifestEventData(ev) {
382
441
  })();
383
442
  const toolsIn = m.tools;
384
443
  const tools = toolsIn === undefined ? undefined : Value.Check(ToolRoster, toolsIn) ? toolsIn : (recordFailOpen("server.trace.tool-roster-malformed", "wiring_manifest.tools did not fit core ToolRoster"), undefined);
444
+ const hooksIn = m.hooks;
445
+ const hooks = (() => {
446
+ if (hooksIn === undefined)
447
+ return undefined;
448
+ if (!Array.isArray(hooksIn))
449
+ return dropProjectedFace("wiring_manifest.hooks is not an array");
450
+ const rows = [];
451
+ for (const raw of hooksIn) {
452
+ const e = sec(raw);
453
+ if (e === undefined) {
454
+ dropProjectedFace("wiring_manifest.hooks[] row is not an object");
455
+ continue;
456
+ }
457
+ const family = str(e.family);
458
+ const owner = str(e.owner);
459
+ if (family === undefined || owner === undefined) {
460
+ dropProjectedFace("wiring_manifest.hooks[] row is missing family/owner");
461
+ continue;
462
+ }
463
+ rows.push({ family, owner, ...(e.observational === true ? { observational: true } : {}) });
464
+ }
465
+ return rows.length > 0 ? rows : undefined;
466
+ })();
467
+ const lspIn = sec(m.lsp);
468
+ const lsp = (() => {
469
+ if (m.lsp === undefined)
470
+ return undefined;
471
+ if (lspIn === undefined)
472
+ return dropProjectedFace("wiring_manifest.lsp is not an object");
473
+ const mounted = bool(lspIn.mounted);
474
+ if (mounted === undefined)
475
+ return dropProjectedFace("wiring_manifest.lsp.mounted is not a boolean");
476
+ const lane = str(lspIn.lane);
477
+ return { mounted, ...(lane === undefined ? {} : { lane }) };
478
+ })();
385
479
  const mcpIn = m.mcp;
386
480
  const mcp = (() => {
387
481
  if (!Array.isArray(mcpIn))
@@ -422,6 +516,8 @@ export function wiringManifestEventData(ev) {
422
516
  ...(autoMode !== undefined ? { autoMode } : {}),
423
517
  ...(tools !== undefined ? { tools } : {}),
424
518
  ...(mcp !== undefined ? { mcp } : {}),
519
+ ...(hooks !== undefined ? { hooks } : {}),
520
+ ...(lsp !== undefined ? { lsp } : {}),
425
521
  ...identityFields(ev),
426
522
  };
427
523
  }
@@ -527,9 +623,32 @@ export function contextUsageEventData(ev) {
527
623
  ...(n(ev.usedTokens) !== undefined ? { usedTokens: n(ev.usedTokens) } : {}),
528
624
  ...(n(ev.windowTokens) !== undefined ? { windowTokens: n(ev.windowTokens) } : {}),
529
625
  ...(n(ev.compactAtTokens) !== undefined ? { compactAtTokens: n(ev.compactAtTokens) } : {}),
626
+ ...contextUsageSectionsField(ev.sections),
530
627
  ...identityFields(ev),
531
628
  };
532
629
  }
630
+ function contextUsageSectionsField(sections) {
631
+ if (sections === undefined)
632
+ return {};
633
+ if (!Array.isArray(sections)) {
634
+ dropProjectedFace("context_usage.sections is not an array");
635
+ return {};
636
+ }
637
+ const out = [];
638
+ for (const raw of sections) {
639
+ if (typeof raw !== "object" || raw === null) {
640
+ dropProjectedFace("context_usage.sections[] element is not an object");
641
+ continue;
642
+ }
643
+ const { id, tokens, kind } = raw;
644
+ if (typeof id !== "string" || id === "" || typeof tokens !== "number" || !Number.isFinite(tokens)) {
645
+ dropProjectedFace("context_usage.sections[] element is missing id/tokens");
646
+ continue;
647
+ }
648
+ out.push({ id, tokens, ...(typeof kind === "string" && kind !== "" ? { kind } : {}) });
649
+ }
650
+ return out.length > 0 ? { sections: out } : {};
651
+ }
533
652
  export function turnEndEventData(ev) {
534
653
  return {
535
654
  ...(ev.usage ? { usage: ev.usage } : {}),
@@ -703,6 +822,7 @@ function mapTraceEventInner(type, seq, data) {
703
822
  case "wiring_manifest":
704
823
  case "human_input":
705
824
  case "tool_roster_delta":
825
+ case "tool_disclosure":
706
826
  return null;
707
827
  default:
708
828
  return null;
@@ -233,12 +233,150 @@ let anyValueArm;
233
233
  function anyValueArmRe() {
234
234
  return (anyValueArm ??= new RegExp(`(?:${VALUE_ARM_BODIES.join("|")})`, "y"));
235
235
  }
236
- function collectRegexSpans(g, repl, order, text, out) {
236
+ const HEAD_CLASS_ESCAPES = new Set(["d", "D", "w", "W", "s", "S"]);
237
+ const HEAD_LITERAL_ESCAPES = new Set(["\\", "/", "-", ".", "+", "*", "?", "(", ")", "[", "]", "{", "}", "|", "^", "$"]);
238
+ function headOfAtoms(atoms, src) {
239
+ const tail = atoms[atoms.length - 1];
240
+ if (tail === undefined || !tail.unbounded || tail.cls === undefined) {
241
+ throw new Error(`redact: arm-pool pattern /${src}/ must end in a greedy unbounded single-class value run`);
242
+ }
243
+ let pos = 0;
244
+ let head = 0;
245
+ for (let k = 0; k < atoms.length - 1; k++) {
246
+ const a = atoms[k];
247
+ if (a.cls !== tail.cls)
248
+ head = pos + a.len;
249
+ pos += a.len;
250
+ }
251
+ return head;
252
+ }
253
+ const HEAD_GROUP_OPEN = /^\((\?:|\?=|\?!|\?<=|\?<!)/;
254
+ function parseHeadAlternative(src, from) {
255
+ let seqs = [[]];
256
+ let i = from;
257
+ let closed = false;
258
+ while (i < src.length) {
259
+ const c = src[i];
260
+ if (c === "|" || c === ")")
261
+ break;
262
+ if (closed)
263
+ throw new Error(`redact: arm-pool pattern /${src}/ has trailing syntax after its value run`);
264
+ let base;
265
+ if (c === "(") {
266
+ const open = HEAD_GROUP_OPEN.exec(src.slice(i));
267
+ if (open === null)
268
+ throw new Error(`redact: head derivation cannot bound group at ${i} in /${src}/`);
269
+ const mark = open[1];
270
+ const inner = parseHeadAlternatives(src, i + 1 + mark.length);
271
+ if (src[inner.next] !== ")")
272
+ throw new Error(`redact: unbalanced group at ${i} in /${src}/`);
273
+ i = inner.next + 1;
274
+ if (mark !== "?:")
275
+ continue;
276
+ if (inner.alts.some((alt) => alt.some((a) => a.unbounded))) {
277
+ seqs = inner.alts.flatMap((alt) => seqs.map((s) => [...s, ...alt]));
278
+ closed = true;
279
+ continue;
280
+ }
281
+ base = { len: Math.max(...inner.alts.map((alt) => alt.reduce((n, a) => n + a.len, 0))), cls: undefined };
282
+ }
283
+ else if (c === "[") {
284
+ let k = i + 1;
285
+ while (k < src.length && src[k] !== "]")
286
+ k += src[k] === "\\" ? 2 : 1;
287
+ if (k >= src.length)
288
+ throw new Error(`redact: unterminated character class at ${i} in /${src}/`);
289
+ base = { len: 1, cls: src.slice(i, k + 1) };
290
+ i = k + 1;
291
+ }
292
+ else if (c === "\\") {
293
+ const e = src[i + 1];
294
+ if (e === undefined)
295
+ throw new Error(`redact: dangling escape at ${i} in /${src}/`);
296
+ if (HEAD_CLASS_ESCAPES.has(e))
297
+ base = { len: 1, cls: `\\${e}` };
298
+ else if (HEAD_LITERAL_ESCAPES.has(e))
299
+ base = { len: 1, cls: undefined };
300
+ else
301
+ throw new Error(`redact: head derivation cannot bound escape \\${e} at ${i} in /${src}/`);
302
+ i += 2;
303
+ }
304
+ else if (c === "." || c === "^" || c === "$" || c === "*" || c === "+" || c === "?" || c === "{") {
305
+ throw new Error(`redact: head derivation cannot bound ${JSON.stringify(c)} at ${i} in /${src}/`);
306
+ }
307
+ else {
308
+ base = { len: 1, cls: undefined };
309
+ i += 1;
310
+ }
311
+ let atom = { ...base, unbounded: false };
312
+ const q = src[i];
313
+ if (q === "*" || q === "+") {
314
+ atom = { ...base, unbounded: true };
315
+ i += 1;
316
+ }
317
+ else if (q === "?") {
318
+ i += 1;
319
+ }
320
+ else if (q === "{") {
321
+ const m = /^\{(\d+)(,(\d*))?\}/.exec(src.slice(i));
322
+ if (m === null)
323
+ throw new Error(`redact: head derivation cannot bound quantifier at ${i} in /${src}/`);
324
+ const lo = Number(m[1]);
325
+ const hi = m[2] === undefined ? lo : m[3] === "" ? -1 : Number(m[3]);
326
+ atom = hi < 0 ? { ...base, unbounded: true } : { len: base.len * hi, cls: base.cls, unbounded: false };
327
+ i += m[0].length;
328
+ }
329
+ if (src[i] === "?")
330
+ throw new Error(`redact: head derivation cannot bound a lazy quantifier at ${i} in /${src}/`);
331
+ if (atom.unbounded)
332
+ closed = true;
333
+ for (const s of seqs)
334
+ s.push(atom);
335
+ }
336
+ return { seqs, next: i };
337
+ }
338
+ function parseHeadAlternatives(src, from) {
339
+ const alts = [];
340
+ let i = from;
341
+ for (;;) {
342
+ const one = parseHeadAlternative(src, i);
343
+ alts.push(...one.seqs);
344
+ i = one.next;
345
+ if (src[i] !== "|")
346
+ break;
347
+ i += 1;
348
+ }
349
+ return { alts, next: i };
350
+ }
351
+ function literalHeadLength(re) {
352
+ if (/[uv]/.test(re.flags))
353
+ throw new Error(`redact: head derivation does not support the u/v flags on /${re.source}/`);
354
+ const src = re.source;
355
+ const parsed = parseHeadAlternatives(src, 0);
356
+ if (parsed.next !== src.length)
357
+ throw new Error(`redact: head derivation stopped at ${parsed.next} in /${src}/`);
358
+ return Math.max(...parsed.alts.map((alt) => headOfAtoms(alt, src)));
359
+ }
360
+ function scanResumeAfterHit(start, end, head) {
361
+ return Math.max(start + 1, end - Math.max(head - 1, 0));
362
+ }
363
+ function headWindowCandidates(at, maxHead) {
364
+ const out = [];
365
+ let last = -1;
366
+ for (const e of at) {
367
+ for (let p = Math.max(e - Math.max(maxHead - 1, 0), last + 1, 0); p <= e; p++) {
368
+ out.push({ p, back: e - p });
369
+ last = p;
370
+ }
371
+ }
372
+ return out;
373
+ }
374
+ function collectRegexSpans(g, head, repl, order, text, out) {
237
375
  g.lastIndex = 0;
238
376
  for (let m = g.exec(text); m !== null; m = g.exec(text)) {
239
377
  const end = m.index + m[0].length;
240
378
  out.push({ start: m.index, end, repl, order });
241
- g.lastIndex = end > m.index ? end : m.index + 1;
379
+ g.lastIndex = scanResumeAfterHit(m.index, end, head);
242
380
  }
243
381
  }
244
382
  function resolveSpanOverlaps(spans) {
@@ -265,10 +403,10 @@ function applySpans(text, spans) {
265
403
  parts.push(text.slice(copied));
266
404
  return { out: parts.join(""), count: spans.length };
267
405
  }
268
- function collectValueArmSpans(g, y, repl, order, text, out) {
406
+ function collectValueArmSpans(g, y, head, repl, order, text, out) {
269
407
  const tails = escapedSeparatorTails(text);
270
408
  if (tails === undefined) {
271
- collectRegexSpans(g, repl, order, text, out);
409
+ collectRegexSpans(g, head, repl, order, text, out);
272
410
  return;
273
411
  }
274
412
  let pos = 0;
@@ -314,12 +452,14 @@ function collectValueArmSpans(g, y, repl, order, text, out) {
314
452
  else
315
453
  break;
316
454
  out.push({ start: idx, end, repl, order });
317
- pos = end;
455
+ pos = scanResumeAfterHit(idx, end, head);
318
456
  }
319
457
  }
320
- function collectStickySpans(y, repl, order, text, at, out) {
458
+ function collectStickySpans(y, head, repl, order, text, at, out) {
321
459
  let pos = 0;
322
- for (const p of at) {
460
+ for (const { p, back } of at) {
461
+ if (back >= head)
462
+ continue;
323
463
  if (p < pos)
324
464
  continue;
325
465
  y.lastIndex = p;
@@ -328,7 +468,7 @@ function collectStickySpans(y, repl, order, text, at, out) {
328
468
  continue;
329
469
  const end = p + m[0].length;
330
470
  out.push({ start: p, end, repl, order });
331
- pos = end;
471
+ pos = scanResumeAfterHit(p, end, head);
332
472
  }
333
473
  }
334
474
  const WORD_SEGMENT_CLASS = "[A-Za-z0-9._~+/-]";
@@ -358,7 +498,7 @@ const SHAPE_SECRET_PATTERNS = [
358
498
  [new RegExp(String.raw `xapp-\d-${atLeast("[A-Za-z0-9-]", 20)}`, "g"), "«redacted:slack-token»"],
359
499
  [valueArm("sk-", "[A-Za-z0-9_-]", 16, "self-evident"), "«redacted:key»"],
360
500
  ];
361
- const FULL_SECRET_PATTERNS = [...STRUCTURAL_SECRET_PATTERNS, ...KEYWORD_SECRET_PATTERNS, ...SHAPE_SECRET_PATTERNS];
501
+ const FULL_ARM_PATTERNS = [...STRUCTURAL_SECRET_PATTERNS, ...KEYWORD_SECRET_PATTERNS, ...SHAPE_SECRET_PATTERNS];
362
502
  const PEM_HEADER_IDENT_MAX = 31;
363
503
  const PEM_BODY_RUN_MIN = 8;
364
504
  const isSepCode = (c) => c === CH_SPACE || c === CH_TAB || c === CH_CR || c === CH_LF;
@@ -743,7 +883,7 @@ const FRAGMENT_SECRET_PATTERNS = [
743
883
  [valueArm("(?:gl(?:pat|dt|rt|rtr|cbt|ptt|oas|ft|imt|agent|wt|soat|ffct)-|(?:sk|rk)_(?:live|test|org)_|pypi-)", "[A-Za-z0-9_-]", 8, "needs-left-boundary"), "«redacted:key:partial»"],
744
884
  [valueArm("xapp-\\d-", "[A-Za-z0-9-]", 4, "needs-left-boundary"), "«redacted:slack-token:partial»"],
745
885
  ];
746
- const REDACTION_TOKENS = new Set([...FULL_SECRET_PATTERNS.map(([, r]) => r), PEM_FRAGMENT_TOKEN, ...FRAGMENT_SECRET_PATTERNS.map(([, r]) => r)]
886
+ const REDACTION_TOKENS = new Set([...FULL_ARM_PATTERNS.map(([, r]) => r), PEM_FRAGMENT_TOKEN, ...FRAGMENT_SECRET_PATTERNS.map(([, r]) => r)]
747
887
  .flatMap((r) => r.match(/«[^»]*»/g) ?? []));
748
888
  const REDACTION_TOKEN_MAX = Math.max(...[...REDACTION_TOKENS].map((t) => t.length));
749
889
  {
@@ -802,9 +942,9 @@ const regexRule = ([re, repl]) => {
802
942
  return { run: (text, wantCount) => singleHit(label, arm, applyOnce(text, wantCount)) };
803
943
  };
804
944
  const URL_USERINFO_PATTERN = (() => {
805
- const row = FULL_SECRET_PATTERNS.find(([, repl]) => repl.endsWith("»@"));
945
+ const row = FULL_ARM_PATTERNS.find(([, repl]) => repl.endsWith("»@"));
806
946
  if (row === undefined)
807
- throw new Error("redact: URL-userinfo rule missing from FULL_SECRET_PATTERNS");
947
+ throw new Error("redact: URL-userinfo rule missing from FULL_ARM_PATTERNS");
808
948
  return row;
809
949
  })();
810
950
  const isRegexSpace = (c) => c === 32 || (c >= 9 && c <= 13) || c === 0xa0 || c === 0x1680 || (c >= 0x2000 && c <= 0x200a) || c === 0x2028 || c === 0x2029 || c === 0x202f || c === 0x205f || c === 0x3000 || c === 0xfeff;
@@ -1000,17 +1140,22 @@ const armPoolRule = (() => {
1000
1140
  const arms = [...SHAPE_SECRET_PATTERNS, ...FRAGMENT_SECRET_PATTERNS].map(([re, repl], order) => {
1001
1141
  if (/\$./.test(repl))
1002
1142
  throw new Error(`redact: arm-pool replacement ${JSON.stringify(repl)} must not use $N refs`);
1003
- return { g: re, y: VALUE_ARM_STICKY.get(re), repl, order };
1143
+ const y = VALUE_ARM_STICKY.get(re);
1144
+ const head = literalHeadLength(re);
1145
+ if (y !== undefined && head === 0)
1146
+ throw new Error(`redact: boundary arm /${re.source}/ has a zero-length structural head — the head-window screen would skip every candidate position`);
1147
+ return { g: re, y, head, repl, order };
1004
1148
  });
1005
- const boundaryArms = arms.flatMap((a) => (a.y === undefined ? [] : [{ y: a.y, repl: a.repl, order: a.order }]));
1149
+ const boundaryArms = arms.flatMap((a) => (a.y === undefined ? [] : [{ y: a.y, head: a.head, repl: a.repl, order: a.order }]));
1150
+ const maxBoundaryHead = Math.max(1, ...boundaryArms.map((a) => a.head));
1006
1151
  return {
1007
1152
  run: (text, wantCount) => {
1008
1153
  const spans = [];
1009
1154
  for (const a of arms) {
1010
1155
  if (a.y === undefined)
1011
- collectRegexSpans(a.g, a.repl, a.order, text, spans);
1156
+ collectRegexSpans(a.g, a.head, a.repl, a.order, text, spans);
1012
1157
  else
1013
- collectValueArmSpans(a.g, a.y, a.repl, a.order, text, spans);
1158
+ collectValueArmSpans(a.g, a.y, a.head, a.repl, a.order, text, spans);
1014
1159
  }
1015
1160
  const tried = new Set();
1016
1161
  const freshEnds = (from) => {
@@ -1026,8 +1171,9 @@ const armPoolRule = (() => {
1026
1171
  };
1027
1172
  for (let at = freshEnds(0); at.length > 0;) {
1028
1173
  const before = spans.length;
1174
+ const cand = headWindowCandidates(at, maxBoundaryHead);
1029
1175
  for (const a of boundaryArms)
1030
- collectStickySpans(a.y, a.repl, a.order, text, at, spans);
1176
+ collectStickySpans(a.y, a.head, a.repl, a.order, text, cand, spans);
1031
1177
  at = freshEnds(before);
1032
1178
  }
1033
1179
  const kept = resolveSpanOverlaps(spans);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/server",
3
- "version": "7.76.2",
3
+ "version": "7.78.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,8 +54,8 @@
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.17.7",
58
- "@sema-agent/settings-schema": "1.10.0",
57
+ "@sema-agent/core": "7.18.0",
58
+ "@sema-agent/settings-schema": "2.1.0",
59
59
  "e2b": "^2.28.0",
60
60
  "libsodium-wrappers": "^0.8.4",
61
61
  "mysql2": "^3.22.4",
@@ -69,7 +69,7 @@
69
69
  "sharp": "^0.35.3"
70
70
  },
71
71
  "devDependencies": {
72
- "@sema-agent/sdk": "9.3.0",
72
+ "@sema-agent/sdk": "9.4.0",
73
73
  "@types/libsodium-wrappers": "^0.7.14",
74
74
  "@types/node": "22.10.2",
75
75
  "@types/pg": "^8.20.0",