@sema-agent/server 7.75.1 → 7.75.3

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.
@@ -1,5 +1,5 @@
1
1
  import { redactSecrets } from "../trace/redact.js";
2
- import { taskProgressEventData, toolStartEventData, toolEndEventData } from "../trace/project.js";
2
+ import { taskProgressEventData, toolStartEventData, toolEndEventData, textEndEventData } from "../trace/project.js";
3
3
  const TAIL_QUEUE_CAP = 512;
4
4
  export function projectTailFrame(e) {
5
5
  const t = e.type;
@@ -19,14 +19,8 @@ export function projectTailFrame(e) {
19
19
  return { type: "text_delta", delta: td.delta, ...(td.eventId ? { eventId: td.eventId } : {}), ...(td.parentToolCallId ? { parentToolCallId: td.parentToolCallId } : {}) };
20
20
  }
21
21
  if (t === "text_end") {
22
- if (typeof e.content !== "string" || e.content.length === 0)
23
- return null;
24
- return {
25
- type: "text_end",
26
- content: e.content,
27
- ...(typeof e.eventId === "string" ? { eventId: e.eventId } : {}),
28
- ...(typeof e.parentToolCallId === "string" ? { parentToolCallId: e.parentToolCallId } : {}),
29
- };
22
+ const f = textEndEventData(e);
23
+ return f === null ? null : { type: "text_end", ...f };
30
24
  }
31
25
  if (t === "reasoning_delta") {
32
26
  const rd = e;
@@ -12,7 +12,7 @@ import { clearTurnActivity, readTurnActivityMs, recordTurnActivity } from "../..
12
12
  import { mintCancelledResult } from "../../run-cancel-context.js";
13
13
  import { recordResourceWindow } from "../../resource-window.js";
14
14
  import { redactSecrets } from "../../trace/redact.js";
15
- import { contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, wiringManifestOperatorEventData, toolRosterDeltaEventData, humanInputEventData, appendModelUsageDelta, attachModelUsage } from "../../trace/project.js";
15
+ import { contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, wiringManifestOperatorEventData, toolRosterDeltaEventData, humanInputEventData, appendModelUsageDelta, attachModelUsage, textEndEventData } from "../../trace/project.js";
16
16
  import { cascadeConfig, runMeta } from "../run-meta.js";
17
17
  import { scopedIdempotencyKey } from "../idempotency.js";
18
18
  import { sendJson, sendError, sseHeaders } from "../send.js";
@@ -347,8 +347,9 @@ async function handleTasksBody(req, res, url, ctx, miss) {
347
347
  sseData(res, { type: "text_delta", delta: td.delta, ...(td.eventId ? { eventId: td.eventId } : {}), ...(td.parentToolCallId ? { parentToolCallId: td.parentToolCallId } : {}) });
348
348
  }
349
349
  else if (t === "text_end") {
350
- const tend = e;
351
- sseData(res, { type: "text_end", content: tend.content, ...(tend.eventId ? { eventId: tend.eventId } : {}), ...(tend.parentToolCallId ? { parentToolCallId: tend.parentToolCallId } : {}) });
350
+ const tend = textEndEventData(e);
351
+ if (tend !== null)
352
+ sseData(res, { type: "text_end", ...tend });
352
353
  }
353
354
  else if (t === "reasoning_delta") {
354
355
  const rd = e;
@@ -493,16 +494,19 @@ async function handleTasksBody(req, res, url, ctx, miss) {
493
494
  ...(e.sourceTaskId !== undefined ? { sourceTaskId: e.sourceTaskId } : {}),
494
495
  ...(e.bgAgentId !== undefined ? { bgAgentId: e.bgAgentId } : {}),
495
496
  };
497
+ const textEnd = ev.type === "text_end" ? textEndEventData(e) : undefined;
496
498
  const arm = ev.type === "text_delta"
497
499
  ? { type: "text_delta", delta: e.delta, ...ident }
498
500
  : ev.type === "text_end"
499
- ? { type: "text_end", content: e.content, ...ident }
501
+ ?
502
+ textEnd === null || textEnd === undefined ? null : { type: "text_end", ...textEnd }
500
503
  : ev.type === "turn_end"
501
504
  ? { type: "turn_end", ...(e.usage !== undefined ? { usage: e.usage } : {}), ...(e.usageMissing !== undefined ? { usageMissing: e.usageMissing } : {}), ...(e.stopReason !== undefined ? { stopReason: e.stopReason } : {}), ...ident }
502
505
  : ev.type === "message_committed"
503
506
  ? { type: "message_committed", entryId: e.entryId, role: e.role, ...(e.toolCallId !== undefined ? { toolCallId: e.toolCallId } : {}), ...ident }
504
507
  : { type: "context_usage", ...contextUsageEventData(ev), ...ident };
505
- sseData(res, arm);
508
+ if (arm !== null)
509
+ sseData(res, arm);
506
510
  }
507
511
  else {
508
512
  sseData(res, ev);
@@ -187,6 +187,13 @@ export declare function screenedGate(v: unknown): GateOutcome | undefined;
187
187
  * 不改原对象(调用方与账本共享引用):有变更时换一只信封,原记录一个字节不动。
188
188
  */
189
189
  export declare function screenedStoredEventData(type: string, data: unknown): unknown;
190
+ /** `text_end` 段末帧的**唯一**铸点(三腿共用:top stream / 转发子代腿 / 子代 tail 面)。content = 段权威全文 ⇒ 与
191
+ * durable 面(`result` / 账本 `text` 行)同一只 {@link redactModelText}(认出才改写、抛则占位、不设上限)。
192
+ * 此前三处各自逐字透传 —— 同一份文本毫秒后在 `done.result.result` 上已脱,live 面却在凭据生成瞬间明文(B-114)。
193
+ * `text_delta` 仍逐字:跨 chunk 的凭据无法就地判,流式脱敏是独立设计件(S-270)。
194
+ * 坏形(content 非串/空串)⇒ null(丢帧,不铸「权威空段」:core 契约明言空段不发本帧,消费端收到空段会清掉已攒 delta)。
195
+ * 身份四键零字节变换(live 现状带全四键,消费端可能已依赖)。 */
196
+ export declare function textEndEventData(ev: Record<string, unknown>): Record<string, unknown> | null;
190
197
  /** The durable `tool_end` payload (shared by both append sites). E1: the model-facing `output` is UNTRUSTED RAW
191
198
  * from core (file/shell/API bodies) — core only SIZE-bounds it (`truncated`), REDACTION is ours (same contract as
192
199
  * `tool_start.args`); never re-fed to a model. E2: identity. Absent `output` ⇒ the tool produced no content body.
@@ -1,4 +1,4 @@
1
- import { redactErrorMessage } from "../observability/run-terminal-log.js";
1
+ import { redactErrorMessage, redactModelText } from "../observability/run-terminal-log.js";
2
2
  import { MCP_DELIVERY_VERDICTS, screenGateOutcome, ToolRoster, ToolRosterDelta } from "@sema-agent/core";
3
3
  import { Value } from "typebox/value";
4
4
  import { recordFailOpen } from "../observability/fail-open.js";
@@ -173,6 +173,17 @@ function projectSettlement(st) {
173
173
  ...(typeof st.note === "string" ? { note: redactSecrets(st.note).slice(0, SETTLEMENT_NOTE_MAX) } : {}),
174
174
  };
175
175
  }
176
+ export function textEndEventData(ev) {
177
+ if (typeof ev.content !== "string" || ev.content.length === 0)
178
+ return null;
179
+ return {
180
+ content: redactModelText(ev.content),
181
+ ...(ev.eventId !== undefined ? { eventId: ev.eventId } : {}),
182
+ ...(ev.parentToolCallId !== undefined ? { parentToolCallId: ev.parentToolCallId } : {}),
183
+ ...(ev.sourceTaskId !== undefined ? { sourceTaskId: ev.sourceTaskId } : {}),
184
+ ...(ev.bgAgentId !== undefined ? { bgAgentId: ev.bgAgentId } : {}),
185
+ };
186
+ }
176
187
  export function toolEndEventData(ev) {
177
188
  const gate = ev.gate === undefined ? undefined : screenedGate(ev.gate);
178
189
  return {
@@ -30,6 +30,8 @@
30
30
  *
31
31
  * 两组臂,顺序即优先序:{@link FULL_SECRET_PATTERNS}(完整形)在前,{@link FRAGMENT_SECRET_PATTERNS}
32
32
  * ([ref] 前缀残片,上游先截后脱的产物 ⇒ 带 `:partial` 后缀的 token)在后。
33
+ * ⚠️ **一个例外,自 7.75.2**:完整臂里的**裸形段**内部没有优先序 —— 该段全臂一次收候选跨度、解重叠、一次替换
34
+ * ({@link shapeSectionRule}),段内臂序对输出无语义。段**之间**的先后照旧是契约。
33
35
  */
34
36
  /** 本模块替换 token 的**闭形**正则源(`«redacted»` / `«redacted:aws-key»` / `«redacted:gh-token:partial»` …):
35
37
  * URL-userinfo 臂用它把「用户名位上已经是本模块 token」整个当一个用户名单元吃掉。模块加载时对
@@ -207,32 +207,65 @@ function forgetEscapedSeparatorTails() {
207
207
  }
208
208
  const VALUE_ARM_STICKY = new Map();
209
209
  const VALUE_ARM_BODIES = [];
210
- const valueArm = (prefixSrc, valueClass, min) => {
210
+ const valueArm = (prefixSrc, valueClass, min, evidence) => {
211
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
212
  VALUE_ARM_BODIES.push(body);
215
- return g;
213
+ switch (evidence) {
214
+ case "self-evident":
215
+ return new RegExp(body, "g");
216
+ case "needs-left-boundary": {
217
+ const g = new RegExp(`(?<!${WORD_INTERIOR_CLASS})${body}`, "g");
218
+ VALUE_ARM_STICKY.set(g, new RegExp(body, "y"));
219
+ return g;
220
+ }
221
+ default: {
222
+ const unhandled = evidence;
223
+ throw new Error(`redact: unhandled value-arm evidence ${String(unhandled)}`);
224
+ }
225
+ }
216
226
  };
217
227
  let anyValueArm;
218
228
  function anyValueArmRe() {
219
229
  return (anyValueArm ??= new RegExp(`(?:${VALUE_ARM_BODIES.join("|")})`, "y"));
220
230
  }
221
- function runValueArm(g, y, repl, text, wantCount) {
231
+ function collectRegexSpans(g, repl, order, text, out) {
232
+ g.lastIndex = 0;
233
+ for (let m = g.exec(text); m !== null; m = g.exec(text)) {
234
+ const end = m.index + m[0].length;
235
+ out.push({ start: m.index, end, repl, order });
236
+ g.lastIndex = end > m.index ? end : m.index + 1;
237
+ }
238
+ }
239
+ function resolveSpanOverlaps(spans) {
240
+ spans.sort((a, b) => a.start - b.start || b.end - a.end || a.order - b.order);
241
+ const kept = [];
242
+ let covered = 0;
243
+ for (const s of spans) {
244
+ if (s.end <= covered)
245
+ continue;
246
+ kept.push(s.start >= covered ? s : { ...s, start: covered });
247
+ covered = s.end;
248
+ }
249
+ return kept;
250
+ }
251
+ function applySpans(text, spans) {
252
+ if (spans.length === 0)
253
+ return { out: text, count: 0 };
254
+ const parts = [];
255
+ let copied = 0;
256
+ for (const s of spans) {
257
+ parts.push(text.slice(copied, s.start), s.repl);
258
+ copied = s.end;
259
+ }
260
+ parts.push(text.slice(copied));
261
+ return { out: parts.join(""), count: spans.length };
262
+ }
263
+ function collectValueArmSpans(g, y, repl, order, text, out) {
222
264
  const tails = escapedSeparatorTails(text);
223
265
  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 };
266
+ collectRegexSpans(g, repl, order, text, out);
267
+ return;
232
268
  }
233
- let out;
234
- let copied = 0;
235
- let count = 0;
236
269
  let pos = 0;
237
270
  let ti = 0;
238
271
  let bIdx = -1;
@@ -275,15 +308,14 @@ function runValueArm(g, y, repl, text, wantCount) {
275
308
  }
276
309
  else
277
310
  break;
278
- (out ??= []).push(text.slice(copied, idx), repl);
279
- copied = end;
280
- count += 1;
311
+ out.push({ start: idx, end, repl, order });
281
312
  pos = end;
282
313
  }
283
- if (out === undefined)
284
- return { out: text, count: 0 };
285
- out.push(text.slice(copied));
286
- return { out: out.join(""), count };
314
+ }
315
+ function runValueArm(g, y, repl, text) {
316
+ const spans = [];
317
+ collectValueArmSpans(g, y, repl, 0, text, spans);
318
+ return applySpans(text, spans);
287
319
  }
288
320
  const WORD_SEGMENT_CLASS = "[A-Za-z0-9._~+/-]";
289
321
  const keywordArm = (keywordSrc, valueClass, min, tailSrc = "") => new RegExp(`(?:${keywordSrc}\\s+${atLeast(valueClass, min)}${tailSrc}` +
@@ -303,7 +335,6 @@ const KEYWORD_SECRET_PATTERNS = [
303
335
  [keywordArm("[Tt]oken", "[A-Za-z0-9._~+/-]", 20), "token «redacted»"],
304
336
  ];
305
337
  const SHAPE_SECRET_PATTERNS = [
306
- [valueArm("sk-", "[A-Za-z0-9_-]", 16), "«redacted:key»"],
307
338
  [new RegExp(String.raw `(?:ghp|gho|ghu|ghs|ghr)_${atLeast("[A-Za-z0-9]", 20)}`, "g"), "«redacted:gh-token»"],
308
339
  [new RegExp(String.raw `github_pat_[A-Za-z0-9]{22}_${atLeast("[A-Za-z0-9]", 59)}`, "g"), "«redacted:gh-token»"],
309
340
  [new RegExp(String.raw `xox[baprs]-${atLeast("[A-Za-z0-9-]", 10)}`, "g"), "«redacted:slack-token»"],
@@ -311,6 +342,7 @@ const SHAPE_SECRET_PATTERNS = [
311
342
  [new RegExp(String.raw `ASIA${atLeast("[0-9A-Z]", 16)}`, "g"), "«redacted:aws-key»"],
312
343
  [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
344
  [new RegExp(String.raw `xapp-\d-${atLeast("[A-Za-z0-9-]", 20)}`, "g"), "«redacted:slack-token»"],
345
+ [valueArm("sk-", "[A-Za-z0-9_-]", 16, "self-evident"), "«redacted:key»"],
314
346
  ];
315
347
  const FULL_SECRET_PATTERNS = [...STRUCTURAL_SECRET_PATTERNS, ...KEYWORD_SECRET_PATTERNS, ...SHAPE_SECRET_PATTERNS];
316
348
  const PEM_HEADER_IDENT_MAX = 31;
@@ -688,14 +720,14 @@ function redactHeadlessPpkBodies(text) {
688
720
  return count === 0 ? { out: text, count: 0 } : { out: out + text.slice(last), count };
689
721
  }
690
722
  const FRAGMENT_SECRET_PATTERNS = [
691
- [valueArm("sk-", "[A-Za-z0-9_-]", 6), "«redacted:key:partial»"],
692
- [valueArm("(?:ghp|gho|ghu|ghs|ghr)_", "[A-Za-z0-9]", 8), "«redacted:gh-token:partial»"],
723
+ [valueArm("sk-", "[A-Za-z0-9_-]", 6, "needs-left-boundary"), "«redacted:key:partial»"],
724
+ [valueArm("(?:ghp|gho|ghu|ghs|ghr)_", "[A-Za-z0-9]", 8, "needs-left-boundary"), "«redacted:gh-token:partial»"],
693
725
  [new RegExp(String.raw `github_pat_${atLeast("[A-Za-z0-9_]", 8)}`, "g"), "«redacted:gh-token:partial»"],
694
- [valueArm("xox[baprs]-", "[A-Za-z0-9-]", 4), "«redacted:slack-token:partial»"],
695
- [valueArm("AKIA", "[0-9A-Z]", 6), "«redacted:aws-key:partial»"],
696
- [valueArm("ASIA", "[0-9A-Z]", 8), "«redacted:aws-key:partial»"],
697
- [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), "«redacted:key:partial»"],
698
- [valueArm("xapp-\\d-", "[A-Za-z0-9-]", 4), "«redacted:slack-token:partial»"],
726
+ [valueArm("xox[baprs]-", "[A-Za-z0-9-]", 4, "needs-left-boundary"), "«redacted:slack-token:partial»"],
727
+ [valueArm("AKIA", "[0-9A-Z]", 6, "needs-left-boundary"), "«redacted:aws-key:partial»"],
728
+ [valueArm("ASIA", "[0-9A-Z]", 8, "needs-left-boundary"), "«redacted:aws-key:partial»"],
729
+ [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»"],
730
+ [valueArm("xapp-\\d-", "[A-Za-z0-9-]", 4, "needs-left-boundary"), "«redacted:slack-token:partial»"],
699
731
  ];
700
732
  const REDACTION_TOKENS = new Set([...FULL_SECRET_PATTERNS.map(([, r]) => r), PEM_FRAGMENT_TOKEN, ...FRAGMENT_SECRET_PATTERNS.map(([, r]) => r)]
701
733
  .flatMap((r) => r.match(/«[^»]*»/g) ?? []));
@@ -715,6 +747,8 @@ function redactionTokenEndAt(s, p, limit) {
715
747
  return 0;
716
748
  }
717
749
  const labelOfToken = (token) => token.replace(/[«»]/g, "").replace(/^redacted:/, "");
750
+ const NO_HITS = [];
751
+ const singleHit = (label, arm, r) => r.count > 0 ? { out: r.out, hits: [{ label, count: r.count, arm }] } : { out: r.out, hits: NO_HITS };
718
752
  const regexRule = ([re, repl], arm) => {
719
753
  const groupCount = (new RegExp(`${re.source}|`).exec("")?.length ?? 1) - 1;
720
754
  const refs = [...repl.matchAll(/\$(.)/g)].map(([, d]) => d ?? "");
@@ -726,23 +760,20 @@ const regexRule = ([re, repl], arm) => {
726
760
  const sticky = VALUE_ARM_STICKY.get(re);
727
761
  if (sticky !== undefined && refs.length > 0)
728
762
  throw new Error(`redact: value arm replacement ${JSON.stringify(repl)} must not use $N refs`);
729
- return {
730
- label: labelOfToken(repl),
731
- arm,
732
- run: (text, wantCount, json) => {
733
- if (sticky !== undefined)
734
- return runValueArm(re, sticky, repl, text, wantCount);
735
- const rx = re;
736
- if (!wantCount)
737
- return { out: text.replace(rx, repl), count: 0 };
738
- let n = 0;
739
- const out = text.replace(rx, (...args) => {
740
- n += 1;
741
- return expand === undefined ? repl : expand(args);
742
- });
743
- return { out, count: n };
744
- },
763
+ const label = labelOfToken(repl);
764
+ const applyOnce = (text, wantCount) => {
765
+ if (sticky !== undefined)
766
+ return runValueArm(re, sticky, repl, text);
767
+ if (!wantCount)
768
+ return { out: text.replace(re, repl), count: 0 };
769
+ let n = 0;
770
+ const out = text.replace(re, (...args) => {
771
+ n += 1;
772
+ return expand === undefined ? repl : expand(args);
773
+ });
774
+ return { out, count: n };
745
775
  };
776
+ return { run: (text, wantCount) => singleHit(label, arm, applyOnce(text, wantCount)) };
746
777
  };
747
778
  const URL_USERINFO_PATTERN = (() => {
748
779
  const row = FULL_SECRET_PATTERNS.find(([, repl]) => repl.endsWith("»@"));
@@ -937,16 +968,43 @@ function redactPpkContainers(text, record) {
937
968
  }
938
969
  return count === 0 ? { out: text, count: 0 } : { out: out + text.slice(last), count };
939
970
  }
940
- const urlUserinfoRule = { label: labelOfToken(URL_USERINFO_PATTERN[1]), arm: "full", run: (text, _wantCount, json) => redactUrlUserinfo(text, json) };
971
+ const URL_USERINFO_LABEL = labelOfToken(URL_USERINFO_PATTERN[1]);
972
+ const urlUserinfoRule = { run: (text, _wantCount, json) => singleHit(URL_USERINFO_LABEL, "full", redactUrlUserinfo(text, json)) };
973
+ const shapeSectionRule = (() => {
974
+ const arms = SHAPE_SECRET_PATTERNS.map(([re, repl], order) => {
975
+ if (/\$./.test(repl))
976
+ throw new Error(`redact: shape-section replacement ${JSON.stringify(repl)} must not use $N refs`);
977
+ return { g: re, y: VALUE_ARM_STICKY.get(re), repl, order };
978
+ });
979
+ return {
980
+ run: (text, wantCount) => {
981
+ const spans = [];
982
+ for (const a of arms) {
983
+ if (a.y === undefined)
984
+ collectRegexSpans(a.g, a.repl, a.order, text, spans);
985
+ else
986
+ collectValueArmSpans(a.g, a.y, a.repl, a.order, text, spans);
987
+ }
988
+ const kept = resolveSpanOverlaps(spans);
989
+ const out = applySpans(text, kept).out;
990
+ if (!wantCount || kept.length === 0)
991
+ return { out, hits: NO_HITS };
992
+ const byToken = new Map();
993
+ for (const sp of kept)
994
+ byToken.set(sp.repl, (byToken.get(sp.repl) ?? 0) + 1);
995
+ return { out, hits: [...byToken].map(([token, count]) => ({ label: labelOfToken(token), count, arm: "full" })) };
996
+ },
997
+ };
998
+ })();
941
999
  const SECRET_RULES = [
942
1000
  ...STRUCTURAL_SECRET_PATTERNS.map((p) => (p === URL_USERINFO_PATTERN ? urlUserinfoRule : regexRule(p, "full"))),
943
- { label: labelOfToken(PPK_CONTAINER_TOKEN), arm: "full", run: (text, _wantCount, _json, observe) => redactPpkContainers(text, observe) },
944
- { label: labelOfToken(PEM_FRAGMENT_TOKEN), arm: "fragment", run: (text, _wantCount, json) => redactPemFragments(text, json) },
945
- { label: labelOfToken(HEADLESS_PPK_TOKEN), arm: "fragment", run: (text) => redactHeadlessPpkBodies(text) },
1001
+ { run: (text, _wantCount, _json, observe) => singleHit(labelOfToken(PPK_CONTAINER_TOKEN), "full", redactPpkContainers(text, observe)) },
1002
+ { run: (text, _wantCount, json) => singleHit(labelOfToken(PEM_FRAGMENT_TOKEN), "fragment", redactPemFragments(text, json)) },
1003
+ { run: (text) => singleHit(labelOfToken(HEADLESS_PPK_TOKEN), "fragment", redactHeadlessPpkBodies(text)) },
946
1004
  ...KEYWORD_SECRET_PATTERNS.map((p) => regexRule(p, "full")),
947
- ...SHAPE_SECRET_PATTERNS.map((p) => regexRule(p, "full")),
1005
+ shapeSectionRule,
948
1006
  ...FRAGMENT_SECRET_PATTERNS.map((p) => regexRule(p, "fragment")),
949
- { ...urlUserinfoRule, run: (text, _wantCount, json) => ({ out: redactUrlUserinfo(text, json).out, count: 0 }) },
1007
+ { run: (text, _wantCount, json) => ({ out: redactUrlUserinfo(text, json).out, hits: NO_HITS }) },
950
1008
  ];
951
1009
  let onRedaction;
952
1010
  export function setRedactionObserver(fn) {
@@ -958,8 +1016,8 @@ function applyRules(text, wantCount, json, observe) {
958
1016
  for (const rule of SECRET_RULES) {
959
1017
  const r = rule.run(out, wantCount, json, observe);
960
1018
  out = r.out;
961
- if (wantCount && r.count > 0)
962
- hits.push({ label: rule.label, count: r.count, arm: rule.arm });
1019
+ if (wantCount && r.hits.length > 0)
1020
+ hits.push(...r.hits);
963
1021
  }
964
1022
  return { out, hits };
965
1023
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/server",
3
- "version": "7.75.1",
3
+ "version": "7.75.3",
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.17.4",
57
+ "@sema-agent/core": "7.17.5",
58
58
  "@sema-agent/settings-schema": "1.10.0",
59
59
  "e2b": "^2.28.0",
60
60
  "libsodium-wrappers": "^0.8.4",