@sema-agent/server 7.74.0 → 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.
- package/dist/http/server.js +3 -3
- package/dist/observability/fail-open.d.ts +1 -1
- package/dist/observability/fail-open.js +1 -1
- package/dist/observability/run-terminal-log.d.ts +2 -1
- package/dist/orchestration/workflow-agent-session-index.d.ts +39 -82
- package/dist/orchestration/workflow-agent-session-index.js +14 -33
- package/dist/parked-decide.d.ts +30 -33
- package/dist/parked-decide.js +11 -13
- package/dist/plugins/workflow-run-store-sql.d.ts +17 -26
- package/dist/plugins/workflow-run-store-sql.js +2 -16
- package/dist/trace/project.d.ts +3 -2
- package/dist/trace/redact.js +294 -19
- package/package.json +3 -3
package/dist/http/server.js
CHANGED
|
@@ -3,7 +3,7 @@ import { redactErrorMessage, redactHead } from "../observability/run-terminal-lo
|
|
|
3
3
|
import { once } from "node:events";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
import { uuidv7, isThinkingLevel, expandTiers, resumeWithVerification, CheckpointError, defaultTaskRegistry, validatePendingSteer, subscribeWorkflow, GLOBAL_USAGE_KEY, usageRetryAfterMs, terminalProjection, PAUSE_REGISTRY } from "@sema-agent/core";
|
|
6
|
-
import { decideParkedAgent,
|
|
6
|
+
import { decideParkedAgent, denyWorkflowParksUnreadable, findParkOrigin, planWorkflowParkedDecide } from "../parked-decide.js";
|
|
7
7
|
import { isModelAllowlisted, matchCatalogModel } from "../model-select.js";
|
|
8
8
|
import {} from "../config-center/facade.js";
|
|
9
9
|
import { HttpError, principalFrom, verifiedPrincipal, setSsoPrincipal, ssoVerifiedPrincipal, setSsoScope, isUuidV7, isDestructiveSessionWrite, decodeCheckpointScope, CHECKPOINT_PUBLIC_SCOPE, PRINCIPAL_TOKEN_HEADER, APPROVAL_MAC_HEADER, APPROVAL_MAC_KID_HEADER } from "../security.js";
|
|
@@ -1211,9 +1211,9 @@ export function createHttpServer(rawDeps) {
|
|
|
1211
1211
|
noteLateSettleIfAccepted(parked);
|
|
1212
1212
|
return parked;
|
|
1213
1213
|
}
|
|
1214
|
-
if (origin?.kind === "
|
|
1214
|
+
if (origin?.kind === "workflow_parks_unreadable") {
|
|
1215
1215
|
void ctxPromise.catch(() => { });
|
|
1216
|
-
return
|
|
1216
|
+
return denyWorkflowParksUnreadable(origin);
|
|
1217
1217
|
}
|
|
1218
1218
|
if (origin?.kind === "workflow") {
|
|
1219
1219
|
void ctxPromise.catch(() => { });
|
|
@@ -199,7 +199,7 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
199
199
|
};
|
|
200
200
|
readonly "server.run-terminal.redact-threw": {
|
|
201
201
|
readonly cls: "F";
|
|
202
|
-
readonly note: "四个发射座(errorMessage 终态文案 / 诊断文本 diag / 补丁头 patch-head / 工件 artifact,probe 前缀判别;普查 56 行):S-105 最终轮 [medium]:终态 errorMessage/blockedReason/remoteEnvFailures[].message 脱敏抛出(
|
|
202
|
+
readonly note: "四个发射座(errorMessage 终态文案 / 诊断文本 diag / 补丁头 patch-head / 工件 artifact,probe 前缀判别;普查 56 行):S-105 最终轮 [medium]:终态 errorMessage/blockedReason/remoteEnvFailures[].message 脱敏抛出(RangeError:关键字/前缀臂那一形已由 S-219 的 atLeast 铸点闭掉,仍能抛的是 JSON 秘密字段臂的转义对循环,既有未闭,S-106)⇒ 落固定占位 «redacted:oversized»——绝不落原文(安全轴 fail-closed),也绝不阻断终态翻转与 claim 释放(可用性)。方向刻意:一条超大错误体不得把 run 卡在 running。";
|
|
203
203
|
};
|
|
204
204
|
readonly "server.run-terminal.redact-capped": {
|
|
205
205
|
readonly cls: "F";
|
|
@@ -190,7 +190,7 @@ export const FAIL_OPEN_TAGS = {
|
|
|
190
190
|
},
|
|
191
191
|
"server.run-terminal.redact-threw": {
|
|
192
192
|
cls: "F",
|
|
193
|
-
note: "四个发射座(errorMessage 终态文案 / 诊断文本 diag / 补丁头 patch-head / 工件 artifact,probe 前缀判别;普查 56 行):S-105 最终轮 [medium]:终态 errorMessage/blockedReason/remoteEnvFailures[].message 脱敏抛出(
|
|
193
|
+
note: "四个发射座(errorMessage 终态文案 / 诊断文本 diag / 补丁头 patch-head / 工件 artifact,probe 前缀判别;普查 56 行):S-105 最终轮 [medium]:终态 errorMessage/blockedReason/remoteEnvFailures[].message 脱敏抛出(RangeError:关键字/前缀臂那一形已由 S-219 的 atLeast 铸点闭掉,仍能抛的是 JSON 秘密字段臂的转义对循环,既有未闭,S-106)⇒ 落固定占位 «redacted:oversized»——绝不落原文(安全轴 fail-closed),也绝不阻断终态翻转与 claim 释放(可用性)。方向刻意:一条超大错误体不得把 run 卡在 running。",
|
|
194
194
|
},
|
|
195
195
|
"server.run-terminal.redact-capped": { cls: "F", note: "S-107:终态自由文本(errorMessage/blockedReason/remoteEnvFailures[].message/error 列/done·failed 行)长度 > REDACT_INPUT_CAP(64KB)⇒ 整段换 «redacted:oversized»(fail-closed 取证丢弃,不截断不扫);计数=多少条终态文案被整段丢弃" },
|
|
196
196
|
"server.run-terminal.artifact-capped": { cls: "F", note: "S-107:leader_run 行上的工件字节(salvaged[].patch / candidatePatch.patch / merge.candidatePatch / merge.rej)长度 > ARTIFACT_INPUT_CAP(256KB)⇒ 整份换 «redacted:oversized-artifact»(fail-closed 取证丢弃;原字节仍在 worker 分支/merge 咽喉);计数=多少份工件被整份丢弃" },
|
|
@@ -38,7 +38,8 @@ export declare function notifyRunTerminal(rec: RunTerminalRecord): void;
|
|
|
38
38
|
/** S-105(test [ref] 旁生):终态**持久化**的 `errorMessage` 与日志副本同一脱敏口——provider 错误体(403 体带回显请求头/
|
|
39
39
|
* 订阅链接)里的密钥形不得原文落 run 行再由 `GET /v1/runs/:id` 直出。三 run store 的 `setTerminal` 写入前同调此函数;
|
|
40
40
|
* `undefined` 透传(缺席仍缺席),不截断(日志上限是日志的事,行上保留全文供取证)。 */
|
|
41
|
-
/** r4 最终轮 [medium]:`redactSecrets`
|
|
41
|
+
/** r4 最终轮 [medium]:`redactSecrets` 会抛 `RangeError`(关键字/前缀臂那一形已由 S-219 的 `atLeast` 铸点闭掉;仍能抛的是
|
|
42
|
+
* JSON 秘密字段臂的转义对循环,既有未闭,S-106 登记);S-105 把脱敏
|
|
42
43
|
* 挪到 `setTerminal` **入口**后,这个抛会先于 CAS/claim 释放 ⇒ run 卡 `running`、会话 claim 悬挂到 reaper。这里 **fail-closed
|
|
43
44
|
* 不抛**:脱敏失败 ⇒ 落固定占位 `«redacted:oversized»`(绝不落原文,也绝不阻断终态翻转),并走既有 fail-open 遥测 tag。 */
|
|
44
45
|
export declare const OVERSIZED_REDACTION_PLACEHOLDER = "\u00ABredacted:oversized\u00BB";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { WorkflowRun } from "@sema-agent/core";
|
|
1
2
|
/** 一条索引行:**子会话 id 是主键**(会话 id 全局唯一 ⇒ 读口天然是单行精确查,无分页假阴性)。 */
|
|
2
3
|
export interface WorkflowAgentSessionRow {
|
|
3
4
|
/** parked 的那只 workflow 子代自己的会话 id(= `WorkflowAgentRun.sessionId` = 那条 checkpoint 行的 `sessionId`;
|
|
@@ -20,101 +21,57 @@ export interface WorkflowAgentSessionIndex {
|
|
|
20
21
|
syncRun(runId: string, rows: readonly WorkflowAgentSessionRow[]): Promise<void>;
|
|
21
22
|
findByAgentSession(sessionId: string): Promise<WorkflowAgentSessionRow | null>;
|
|
22
23
|
}
|
|
23
|
-
/** 一条 wa* 行在**恢复面**上的读形(本模块两个派生点共用的最小入参 —— 只声明恢复判据用得到的位)。 */
|
|
24
|
-
export interface ParkedAgentReadFace {
|
|
25
|
-
label?: unknown;
|
|
26
|
-
status?: unknown;
|
|
27
|
-
sessionId?: unknown;
|
|
28
|
-
callKey?: unknown;
|
|
29
|
-
queuedAt?: unknown;
|
|
30
|
-
startedAt?: unknown;
|
|
31
|
-
parkedCheckpointToken?: unknown;
|
|
32
|
-
}
|
|
33
24
|
/**
|
|
34
|
-
* 🔴
|
|
35
|
-
* {@link buildParkedAgentSessionRows} 里,而店层的瘦身腿需要**同一条**判据 —— 两处各写一遍就是两份会漂的
|
|
36
|
-
* 定义,而它们的消费面是同一件事:park 的赎回路由)。
|
|
25
|
+
* 🔴 **park 恢复身份的单一真源**(core 7.17.0 [ref])—— 引擎写在记录上的 `parks[]` 的元素形。
|
|
37
26
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
27
|
+
* 型从 core 的那一位取、不在本仓复述:core 往元素上加一位 ⇒ `trace/core-keyset-guard.ts` 的 ⑬b 成员门
|
|
28
|
+
* 与下面那只读者的构造式**同时**红,加键的人二选一,没有第三条静默的路。
|
|
40
29
|
*/
|
|
41
|
-
export
|
|
42
|
-
sessionId: string;
|
|
43
|
-
parkedCheckpointToken: string;
|
|
44
|
-
};
|
|
45
|
-
/** LLM 撰写的 `label` 在**恢复身份行**上的字符上界。core 的铸点对它零钳零脱(脚本经 `opts.label` 供,
|
|
46
|
-
* 缺席才落 auto 名;`parked-decide.ts` 拒把它内插进可信帧时亲核过这一点),而这条行存在的理由是**装得下**
|
|
47
|
-
* —— 把一段无界展示文本原样带进来会让「留身份」这件事本身把行撑爆。 */
|
|
48
|
-
export declare const PARKED_IDENTITY_LABEL_MAX = 200;
|
|
30
|
+
export type WorkflowRunParkKey = NonNullable<WorkflowRun["parks"]>[number];
|
|
49
31
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* 它是一条**合法的** `WorkflowAgentRun`(core 的必填四位 `label`/`callKey`/`status`/`queuedAt` 齐全),
|
|
53
|
-
* 加上赎回路由要的两位(`sessionId` = 那条 checkpoint 行的 `sessionId`,`parkedCheckpointToken` = 赎回键)。
|
|
54
|
-
* 展示载荷(`prompt`/`output`/`activity`/`errorMessage`/`phase`/`model`…)一律不带 —— 那正是被裁掉的东西。
|
|
32
|
+
* 盘上 run blob → `parks[]` 的**唯一读者**(宪法 [ref] §2:进程外字节禁裸 `as` 铸形;与本文件
|
|
33
|
+
* {@link readAgentSessionLedgerLine} 同一条姿态)。
|
|
55
34
|
*
|
|
56
|
-
*
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
labelMax?: number): Record<string, unknown>;
|
|
63
|
-
/**
|
|
64
|
-
* 一只 run 的 parked 行的恢复身份集(顺序 = 原行序;非 park / 无身份的行不产出)。
|
|
35
|
+
* 判据与 core 的 resume 准入**同源**(亲读安装树 `dist/orchestration/workflow.js:2217-2226`):
|
|
36
|
+
* · 这一位**不是数组**(旧引擎写的记录 / 被投影剥掉)⇒ 读不出来。core 对这样的记录整条拒 resume
|
|
37
|
+
* (`workflow.park_truth_unreadable` / `parks_unreadable`),本仓因此也兑付不了它 —— 答缺席,由调用方响亮拒;
|
|
38
|
+
* · **任一条目形不合 ⇒ 整只读不出来**(整档成败制,与本文件账本行同判)。core 对同一形抛
|
|
39
|
+
* `park_binding_broken` 整条拒 resume;「丢掉坏的、留下好的」会静默铸出一份**比真相少几条 park** 的行集,
|
|
40
|
+
* 而这只读者的消费面正是 park 的赎回路由。
|
|
65
41
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* 「放弃展示名」排在「放弃身份」**之前** —— 传 0 即得只剩身份的最小行(`redactHead(x, 0)` 恒得空串)。
|
|
42
|
+
* ⚠️ 「读不出来」与「读得出来、只是空的」是**两个状态**:后者 = 这条 run 不负责任何 park(诚实 miss),
|
|
43
|
+
* 前者 = 证不了,两者的处置相反 ⇒ 缺席永远不折成 `[]`。
|
|
69
44
|
*/
|
|
70
|
-
export declare function
|
|
71
|
-
/**
|
|
72
|
-
* 🔴 **park 恢复身份「保不住」的显式标记**(S-192)。
|
|
73
|
-
*
|
|
74
|
-
* ## 为什么它住在 run 的 blob 上、而且键名带 `server` 前缀
|
|
75
|
-
* core 的 `WorkflowRun`(`orchestration/workflow-types.d.ts`)**没有合法座** —— 亲读全部可选成员
|
|
76
|
-
* (`agentFailures` / `budgetOvershoot` / `timeoutInterruption` / `resume` / `journalSkips` …)全是 core 自己的
|
|
77
|
-
* 观测席,没有一个说得了「本仓的店把这条 run 的 park 身份裁掉了」。而这条事实的**唯一知情点**是
|
|
78
|
-
* `plugins/workflow-run-store-sql.ts` 的瘦身腿(店内),它的**唯一消费点**是 `/decide` 的 workflow 车道
|
|
79
|
-
* (读 `WorkflowRunStore.get`)—— 两点之间已经有的、且只有的那条通路就是 run 的 blob。
|
|
80
|
-
* (本仓的 park join 索引 {@link WorkflowAgentSessionIndex} 承载不了它:索引的**单写者**是
|
|
81
|
-
* `JournalingWorkflowRunStore` 的 put/update 观察点,它拿到的是**内存里的整只 run**,结构上看不见店内那次
|
|
82
|
-
* 瘦身;把这条事实塞过去要么加第二个写者、要么把店的字节上界漏进装饰器,两者都比现在这条更坏。)
|
|
83
|
-
* 键名前缀 `server` 是**诚实标注**:它是本仓铸的位,不是 core 契约的一部分,也因此永远不会与 core 后来加的
|
|
84
|
-
* 成员撞名。
|
|
45
|
+
export declare function readRunParks(run: unknown): WorkflowRunParkKey[] | undefined;
|
|
46
|
+
/** 派生的**唯一属主**:一只 `WorkflowRun` 的 park **路由**行。
|
|
85
47
|
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
/** 被裁掉的 parked 行数(诊断口径,不含身份位)。 */
|
|
95
|
-
parks: number;
|
|
96
|
-
}
|
|
97
|
-
/** 铸点(普通数据)。 */
|
|
98
|
-
export declare function buildParkIdentityLoss(parks: number): ParkIdentityLoss;
|
|
99
|
-
/**
|
|
100
|
-
* 盘上 run blob → {@link ParkIdentityLoss} 的**唯一读者**(宪法 [ref] §2:进程外字节禁裸 `as` 铸形;
|
|
101
|
-
* 与本文件 {@link readAgentSessionLedgerLine} 同一条姿态)。
|
|
48
|
+
* 🔴 **S-252 的分工线**(两轮 codex [high] 各证伪了一次「把这两件事并成一件」的尝试,红格在
|
|
49
|
+
* `test/workflow-park-decide-lane.test.ts`):
|
|
50
|
+
* · **路由**(本函数)回答「**哪一跑**把这个子会话 park 了」—— 一件关于**这一跑执行过什么**的事实。
|
|
51
|
+
* 它只有一个可靠的载体:`agents[]` 上那一行的执行状态(core 的 `parkAgentLeg` 在写 `parks` 条目的
|
|
52
|
+
* 同一拍把 `rec.status = "parked"` / `rec.sessionId` 落上去)。
|
|
53
|
+
* · **赎回权威**(「这个 token 是不是这条 run 负责的一条 park」)只由记录上的 `parks[]` 回答
|
|
54
|
+
* ——{@link readRunParks} 是它唯一的读者,消费点是 `parked-decide.ts` 的判别式。
|
|
55
|
+
* ⇒ 恢复身份(赎回键)自此**只有一个家**;本函数一个 token 字节都不读、也不存(索引只回答「去哪条 run 找」)。
|
|
102
56
|
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
57
|
+
* 为什么路由不能改读 `parks[]`(两条都是真跑复现出来的,别再合并):
|
|
58
|
+
* · `parks` 含**继承而未由本跑执行**的条目:core 在读 checkpoint 真值**之前**就把它们写进新一跑的记录
|
|
59
|
+
* (`dist/orchestration/workflow.js:2244`,读真值的循环在 `:2251` 起),那一读抛 ⇒ `:2340` 的 catch 落一条
|
|
60
|
+
* **带 parks 而 journal 为空**的 `failed` 记录。跟着 `parks` 走 ⇒ 坐标被它抢走 ⇒ `/decide` 让宿主 resume 它
|
|
61
|
+
* ⇒ 已完成的前缀**整段重跑**(非幂等副作用重复执行)。
|
|
62
|
+
* · 而「只收 `originRunId === runId`」同样不成立:re-park 时 core 按 callKey 就地替换条目、`originRunId`
|
|
63
|
+
* **沿用继承来的那一个**,只在 `journalAppendParked` 成功之后才改写成本跑 id(`:1027-1042`),而
|
|
64
|
+
* `persist("update")` 打在 append **之前**。⇒ 那条判据会在 append 失败(以及 append 前的那一拍)上把
|
|
65
|
+
* 真正持有**新 token** 的那一跑整只排除,而上一跑的记录里只有**旧** token ⇒ 这条审批再也送不出去。
|
|
108
66
|
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
67
|
+
* 准入判据(两位,与 `parkAgentLeg` 的落位一一对应):行 `status === "parked"` ∧ 有非空 `sessionId`
|
|
68
|
+
* (checkpoint 那一侧的 join 键就是它)。`callKey` 缺席写空串 —— 它是行自描述,不是 join 键。 */
|
|
111
69
|
export declare function buildParkedAgentSessionRows(runId: string, run: {
|
|
112
70
|
scope: string;
|
|
113
71
|
agents?: ReadonlyArray<{
|
|
114
|
-
status?:
|
|
115
|
-
sessionId?:
|
|
116
|
-
callKey?:
|
|
117
|
-
parkedCheckpointToken?: string;
|
|
72
|
+
status?: unknown;
|
|
73
|
+
sessionId?: unknown;
|
|
74
|
+
callKey?: unknown;
|
|
118
75
|
}>;
|
|
119
76
|
}): WorkflowAgentSessionRow[];
|
|
120
77
|
/** 两处(装饰器的变更检测、local 后端的重放)共用的行集等值判据 —— 顺序无关的集合比较,单一属主。 */
|
|
@@ -1,46 +1,27 @@
|
|
|
1
1
|
import { existsSync, fsyncSync, mkdirSync, openSync, readFileSync, truncateSync, writeSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { redactHead } from "../observability/run-terminal-log.js";
|
|
4
3
|
const nonEmptyString = (v) => typeof v === "string" && v !== "";
|
|
5
|
-
export function
|
|
6
|
-
return a.status === "parked" && nonEmptyString(a.sessionId) && nonEmptyString(a.parkedCheckpointToken);
|
|
7
|
-
}
|
|
8
|
-
export const PARKED_IDENTITY_LABEL_MAX = 200;
|
|
9
|
-
export function buildParkedRecoveryIdentity(a, labelMax = PARKED_IDENTITY_LABEL_MAX) {
|
|
10
|
-
return {
|
|
11
|
-
label: typeof a.label === "string" ? redactHead(a.label, labelMax) : "",
|
|
12
|
-
callKey: typeof a.callKey === "string" ? a.callKey : "",
|
|
13
|
-
status: "parked",
|
|
14
|
-
queuedAt: typeof a.queuedAt === "number" && Number.isFinite(a.queuedAt) ? a.queuedAt : 0,
|
|
15
|
-
...(typeof a.startedAt === "number" && Number.isFinite(a.startedAt) ? { startedAt: a.startedAt } : {}),
|
|
16
|
-
sessionId: a.sessionId,
|
|
17
|
-
parkedCheckpointToken: a.parkedCheckpointToken,
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
export function buildParkedRecoveryIdentities(agents, labelMax = PARKED_IDENTITY_LABEL_MAX) {
|
|
21
|
-
return (agents ?? []).filter(isRecoverableParkedAgent).map((a) => buildParkedRecoveryIdentity(a, labelMax));
|
|
22
|
-
}
|
|
23
|
-
export const PARK_IDENTITY_LOSS_KEY = "serverParkIdentityLoss";
|
|
24
|
-
export function buildParkIdentityLoss(parks) {
|
|
25
|
-
return { reason: "oversize_slim", parks };
|
|
26
|
-
}
|
|
27
|
-
export function readParkIdentityLoss(run) {
|
|
4
|
+
export function readRunParks(run) {
|
|
28
5
|
if (typeof run !== "object" || run === null)
|
|
29
6
|
return undefined;
|
|
30
|
-
const v = run
|
|
31
|
-
if (
|
|
32
|
-
return undefined;
|
|
33
|
-
const { reason, parks } = v;
|
|
34
|
-
if (reason !== "oversize_slim")
|
|
7
|
+
const v = run.parks;
|
|
8
|
+
if (!Array.isArray(v))
|
|
35
9
|
return undefined;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
10
|
+
const out = [];
|
|
11
|
+
for (const raw of v) {
|
|
12
|
+
if (typeof raw !== "object" || raw === null)
|
|
13
|
+
return undefined;
|
|
14
|
+
const { callKey, sessionId, token, originRunId } = raw;
|
|
15
|
+
if (!nonEmptyString(callKey) || !nonEmptyString(sessionId) || !nonEmptyString(token) || !nonEmptyString(originRunId))
|
|
16
|
+
return undefined;
|
|
17
|
+
out.push({ callKey, sessionId, token, originRunId });
|
|
18
|
+
}
|
|
19
|
+
return out;
|
|
39
20
|
}
|
|
40
21
|
export function buildParkedAgentSessionRows(runId, run) {
|
|
41
22
|
const rows = [];
|
|
42
23
|
for (const a of run.agents ?? []) {
|
|
43
|
-
if (!
|
|
24
|
+
if (a.status !== "parked" || !nonEmptyString(a.sessionId))
|
|
44
25
|
continue;
|
|
45
26
|
rows.push({ sessionId: a.sessionId, runId, agentKey: typeof a.callKey === "string" ? a.callKey : "", scope: run.scope });
|
|
46
27
|
}
|
package/dist/parked-decide.d.ts
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* 行永 parked、生命周期分叉)。
|
|
10
10
|
*/
|
|
11
11
|
import type { HostDecision } from "./host-decision.js";
|
|
12
|
-
import type { BackgroundAgentRecord, BackgroundAgentStore, CheckpointStore, QuestionAnswer, ResumeOutcome, TaskRegistry, ToolSpec,
|
|
13
|
-
import { type
|
|
12
|
+
import type { BackgroundAgentRecord, BackgroundAgentStore, CheckpointStore, QuestionAnswer, ResumeOutcome, TaskRegistry, ToolSpec, WorkflowRun, WorkflowRunStore } from "@sema-agent/core";
|
|
13
|
+
import { type WorkflowRunParkKey, type WorkflowAgentSessionIndex } from "./orchestration/workflow-agent-session-index.js";
|
|
14
14
|
export interface ParkedAgentMatch {
|
|
15
15
|
handle: string;
|
|
16
16
|
row: BackgroundAgentRecord;
|
|
@@ -40,37 +40,37 @@ export type ParkOrigin = {
|
|
|
40
40
|
handle: string;
|
|
41
41
|
row: BackgroundAgentRecord;
|
|
42
42
|
}
|
|
43
|
-
/** `
|
|
44
|
-
*
|
|
43
|
+
/** `park` 是**权威条目**(从 run 记录的真源 `parks[]` 里按赎回键复核出来的那一条);身份位一律从它读
|
|
44
|
+
* —— 索引行是可被改盘的派生投影,不做第二个身份写者(7.69.0 合并重扫 c1 同族收口)。 */
|
|
45
45
|
| {
|
|
46
46
|
kind: "workflow";
|
|
47
47
|
runId: string;
|
|
48
48
|
run: WorkflowRun;
|
|
49
|
-
|
|
49
|
+
park: WorkflowRunParkKey;
|
|
50
50
|
}
|
|
51
51
|
/**
|
|
52
|
-
* S-
|
|
52
|
+
* S-252 —— **这条记录的 park 真源读不出来**(第四种出身,不是第四条车道)。
|
|
53
53
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
54
|
+
* 索引行还在、run 行也还在,但记录上没有一份读得出来的 `parks[]`:旧引擎(core < 7.17.0)写下的存量行
|
|
55
|
+
* 根本没有这一位,或者某一条条目的形坏了。而这一位是**引擎自己**的 park 责任表 —— core 的 resume 准入
|
|
56
|
+
* 读不到它就整条拒(`workflow.park_truth_unreadable` / `parks_unreadable`,亲读安装树
|
|
57
|
+
* `dist/orchestration/workflow.js:2217-2218`;条目形坏 ⇒ `park_binding_broken`,`:2222-2224`)。
|
|
58
|
+
* ⇒ 这条决定**结构上**送不出去:答 200「已交给宿主的下一跑」就是一张兑付不了的回执。
|
|
59
|
+
* 此前这一格与「索引落后一拍 / 这条 run 已不再负责它」两种**正常** miss 完全同形,于是落 legacy 腿拿
|
|
60
|
+
* 一个错因 409。判别式据真源的**可读性**把这一格分出来,三个消费点各自响亮处置(decide 拒 + 不驱动
|
|
61
|
+
* 宿主续跑;SLA sweep 照旧静默跳过——它同样复核不了)。
|
|
61
62
|
*/
|
|
62
63
|
| {
|
|
63
|
-
kind: "
|
|
64
|
+
kind: "workflow_parks_unreadable";
|
|
64
65
|
runId: string;
|
|
65
66
|
run: WorkflowRun;
|
|
66
|
-
loss: ParkIdentityLoss;
|
|
67
67
|
};
|
|
68
68
|
/** 判别式的两个源。各自可缺席(部署没接就是没接)—— 缺席 ⇒ 那条臂不判,不是失败。 */
|
|
69
69
|
export interface ParkOriginSources {
|
|
70
70
|
agentStore?: BackgroundAgentStore;
|
|
71
71
|
/** S-185:workflow 出身的**有界** join 索引(契约见 orchestration/workflow-agent-session-index.ts)。 */
|
|
72
72
|
workflowIndex?: WorkflowAgentSessionIndex;
|
|
73
|
-
/** 索引只回答「去哪条 run
|
|
73
|
+
/** 索引只回答「去哪条 run 找」,条目本体(含赎回键)仍以 run 记录的 `parks[]` 为准 ⇒ 两件必须成对在场。 */
|
|
74
74
|
workflowRunStore?: Pick<WorkflowRunStore, "get">;
|
|
75
75
|
warn?: (event: string, fields: Record<string, unknown>) => void;
|
|
76
76
|
}
|
|
@@ -226,26 +226,23 @@ export type WorkflowParkedDecidePlan = {
|
|
|
226
226
|
deny: ParkedDecideResult;
|
|
227
227
|
};
|
|
228
228
|
/**
|
|
229
|
-
* S-
|
|
229
|
+
* S-252 —— **park 真源读不出来的那条记录的响亮拒**(与上面那族拒臂同一种返回形)。
|
|
230
230
|
*
|
|
231
|
-
* 为什么是拒而不是「尽力而为地投递一次」(
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
* :1327/:1727),不取自 run blob;不带决定的 resume 用同一 token **重新 park**(`parkAgentLeg(reparkRec, …)`,
|
|
239
|
-
* :1345/:1738),不是重跑。blob 的 parked 行在引擎侧唯一的读点是 `prior.agents` 那道「journal 缺条目」防线
|
|
240
|
-
* (:2118-2130)—— 身份被裁掉后失明的正是它:**只有 journal 也缺该条目时**,resume 才会把那次调用活跑。
|
|
241
|
-
* ⇒ 拒,并把真话与真恢复路径说出来:先不带决定 resume(引擎按 journal re-park 进一只新的、小的 run 行),
|
|
242
|
-
* 再对同一 checkpoint decide;re-park 后能否再赎回取决于新 blob 是否装得下身份行(不下 ⇒ 再落本臂)。
|
|
243
|
-
* 码是新的,与既有 `decide.workflow_*` 同族。
|
|
231
|
+
* 为什么是拒而不是「尽力而为地投递一次」(机理按安装树 core 7.17.1 `dist/orchestration/workflow.js` 亲读):
|
|
232
|
+
* · 记录上的 `parks[]` 是**引擎自己**的 park 责任表,而且是**下一次 resume 的准入输入**:
|
|
233
|
+
* `!Array.isArray(prior.parks)` ⇒ 整条拒 resume(`workflow.park_truth_unreadable` / `parks_unreadable`,
|
|
234
|
+
* :2217-2218);某一条条目形坏 ⇒ `park_binding_broken`(:2222-2224)。两者都不是「少一条 park」,
|
|
235
|
+
* 是**这条 run 再也 resume 不了** —— 而本车道的投递腿恰恰是让宿主去 `Workflow({resumeFromRunId})`。
|
|
236
|
+
* ⇒ 决定结构上送不出去。答 200「已交给宿主的下一跑」就是一张兑付不了的回执:卡还 pending、人以为决完了。
|
|
237
|
+
* ⇒ 拒,并把真话说出来:这条 run 恢复不了,开一条新的;卡仍 pending,什么都没有被伪造。
|
|
244
238
|
*
|
|
245
|
-
*
|
|
239
|
+
* 这条臂的受害集 = **存量持久数据**(core < 7.17.0 的引擎写下、升级后还 parked 着的 run 记录)。硬 breaking
|
|
240
|
+
* 三句里持久数据那一句要的就是这个形:响亮拒,不做静默双读(读 `agents[]` 补一份身份正是被退役的双算)。
|
|
241
|
+
*
|
|
242
|
+
* 体上只带 `runId`(那是操作员唯一用得上的坐标;真源都读不出来,任何计数都是编的)。
|
|
246
243
|
*/
|
|
247
|
-
export declare function
|
|
248
|
-
kind: "
|
|
244
|
+
export declare function denyWorkflowParksUnreadable(origin: Extract<ParkOrigin, {
|
|
245
|
+
kind: "workflow_parks_unreadable";
|
|
249
246
|
}>): ParkedDecideResult;
|
|
250
247
|
export declare function planWorkflowParkedDecide(origin: Extract<ParkOrigin, {
|
|
251
248
|
kind: "workflow";
|
package/dist/parked-decide.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readRunParks } from "./orchestration/workflow-agent-session-index.js";
|
|
2
2
|
import { defaultSubagentTailBus } from "./fleet/subagent-tail-bus.js";
|
|
3
3
|
import { backgroundScopesForCheckpointScope, decodeCheckpointScope } from "./security.js";
|
|
4
4
|
import { ASK_USER_QUESTION_TOOL_NAME } from "./approval-content-kind.js";
|
|
@@ -25,14 +25,13 @@ export async function findParkOrigin(sources, cp) {
|
|
|
25
25
|
const run = await runStore.get(row.runId);
|
|
26
26
|
if (run === null)
|
|
27
27
|
return undefined;
|
|
28
|
-
const
|
|
29
|
-
if (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (agent.parkedCheckpointToken !== cp.token)
|
|
28
|
+
const parks = readRunParks(run);
|
|
29
|
+
if (parks === undefined)
|
|
30
|
+
return { kind: "workflow_parks_unreadable", runId: row.runId, run };
|
|
31
|
+
const park = parks.find((p) => p.sessionId === cp.sessionId && p.token === cp.token);
|
|
32
|
+
if (park === undefined)
|
|
34
33
|
return undefined;
|
|
35
|
-
return { kind: "workflow", runId: row.runId, run,
|
|
34
|
+
return { kind: "workflow", runId: row.runId, run, park };
|
|
36
35
|
}
|
|
37
36
|
catch (e) {
|
|
38
37
|
quiet(sources.warn, "workflow_park_probe_failed", { error: e instanceof Error ? e.message : String(e) });
|
|
@@ -86,14 +85,13 @@ export function buildParkedResumeOutcome(req) {
|
|
|
86
85
|
hostDecision: req.hostDecision,
|
|
87
86
|
};
|
|
88
87
|
}
|
|
89
|
-
export function
|
|
88
|
+
export function denyWorkflowParksUnreadable(origin) {
|
|
90
89
|
return {
|
|
91
90
|
status: 409,
|
|
92
91
|
body: {
|
|
93
|
-
error: "this parked workflow agent's
|
|
94
|
-
errorCode: "decide.
|
|
92
|
+
error: "this parked workflow agent's run record carries no readable list of the parks the run owns — the engine writes that list when it parks, and it is the only proof that this checkpoint token belongs to that run. A record written by an engine older than the one deployed here (or one whose entries are damaged) is refused by a resume of the run itself for the same reason, so the decision is NOT delivered rather than answered with a receipt nothing can redeem. The approval card stays pending and nothing is forged: this run cannot be resumed — start a new run",
|
|
93
|
+
errorCode: "decide.workflow_parks_unreadable",
|
|
95
94
|
runId: origin.runId,
|
|
96
|
-
parksDropped: origin.loss.parks,
|
|
97
95
|
},
|
|
98
96
|
};
|
|
99
97
|
}
|
|
@@ -131,7 +129,7 @@ export function planWorkflowParkedDecide(origin, req) {
|
|
|
131
129
|
hostSessionId,
|
|
132
130
|
runId: origin.runId,
|
|
133
131
|
outcome: buildParkedResumeOutcome(req),
|
|
134
|
-
message: `The human approval that workflow run "${origin.runId}" parked on (agent callKey "${origin.
|
|
132
|
+
message: `The human approval that workflow run "${origin.runId}" parked on (agent callKey "${origin.park.callKey}") has been ${verdict}. ` +
|
|
135
133
|
`Resume that run now: re-invoke Workflow with the SAME script source you launched it from (script / name / scriptPath) ` +
|
|
136
134
|
`AND resumeFromRunId: "${origin.runId}" — the decision is already held by the deployment and will be applied to the parked ` +
|
|
137
135
|
`agent; completed steps replay from the journal. Do not re-issue the original call.`,
|
|
@@ -78,38 +78,29 @@ export declare function withRedactedError<T extends {
|
|
|
78
78
|
* write (including the TERMINAL one) keeps failing → the durable row is stuck `running` forever and the 24h
|
|
79
79
|
* orphan sweep finalizes a genuinely-completed run as `abandoned`. Instead degrade the PAYLOAD, not the write.
|
|
80
80
|
*
|
|
81
|
-
* 🔴
|
|
82
|
-
* 病(修前逐字):step2 写作 `agents: []` ⇒ 一只 parked 的 workflow 子代的 `callKey` / `sessionId` /
|
|
83
|
-
* `parkedCheckpointToken` 随整段消失。server 侧复核「这条 park 仍在此 token 下」的依据**只有这一行**(join 索引
|
|
84
|
-
* 刻意不存它——它是能力;引擎自己的赎回键另住 resume journal),于是 `/decide` 的 workflow 车道在 `run.agents`
|
|
85
|
-
* 里找不到那一行 ⇒ 落 legacy 腿拿一个**错因**的 409 `conflict.resume_context_unavailable`,人的批准无处兑付;
|
|
86
|
-
* 同时 core resume 路径上那道「blob 里 parked 而 journal 缺条目 ⇒ 拒 resume」的防线(`prior.agents`)也随之失明。
|
|
87
|
-
*
|
|
88
|
-
* 🔴 **7.74.0(core 7.17.0 [ref] 义务②):`parks[]` 排在恢复身份之上 —— 一级都不裁。**
|
|
81
|
+
* 🔴 **`parks[]` 是这条阶梯唯一不许碰的东西**(core 7.17.0 [ref] 义务②)。
|
|
89
82
|
* 它不是观测位,是**下一次 resume 的准入输入**:core 的 resume 准入在任何 agent 被派发之前读记录上的
|
|
90
83
|
* 这一位,读不到就整条拒(`workflow.park_truth_unreadable` / `parks_unreadable`)——不是「少一条 park」,
|
|
91
|
-
* 是**这条 run 再也 resume
|
|
92
|
-
*
|
|
93
|
-
* `test/slim-oversize-run.test.ts` 里那两格(
|
|
84
|
+
* 是**这条 run 再也 resume 不了**;它同时也是本仓 `/decide` 的 workflow 车道复核「这条 park 属于这条 run」
|
|
85
|
+
* 的**单一真源**(S-252)。实现上三级全部走 `{...上一级}` 展开、没有任何一级点名裁它,机器背书 =
|
|
86
|
+
* `test/slim-oversize-run.test.ts` 里那两格(最狠的一级上 `parks` 仍逐字完整 + 负控:记录本来没有
|
|
94
87
|
* 这一位时不许凭空补 `[]`)。缺席 ⇒ 不补:补 `[]` 会把「这版引擎没写这个位」伪造成「这条 run 没有 park」,
|
|
95
88
|
* 而 core 对两者的处置相反。
|
|
96
89
|
*
|
|
97
|
-
*
|
|
90
|
+
* 🔴 **S-252(7.75.0):阶梯不再为 park 另存第二份恢复身份。** 7.71.0 的 S-192 曾在 `agents[]` 里逐行保住
|
|
91
|
+
* `callKey`/`sessionId`/`parkedCheckpointToken`(外加两级 `label` 预算与一枚不可恢复标记),因为当时
|
|
92
|
+
* `/decide` 的复核只认那一行。core 7.17.0 之后同一条记录上有了 `parks[]`,那份保留就成了**第二个会漂的
|
|
93
|
+
* 定义**(真案:最后一级清空 `agents` 之后 token 仍躺在同一个 blob 的 `parks` 里,而 `/decide` 照拒)。
|
|
94
|
+
* ⇒ 整段退役,判别式改读单一真源。退役后的阶梯每一级都**不大于**退役前的对应级,物理下界不劣化。
|
|
95
|
+
*
|
|
96
|
+
* 阶梯 = 一张**按价值排序的放弃清单**,一级一级试到装得下为止:
|
|
98
97
|
* step 1 —— 两个无界字符串(`result` / `error`)截断带标记;
|
|
99
|
-
* step 2 —— `phases` / `
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* 一边原样留着它们、一边把唯一那条 parked 行删掉盖标记 —— 自己违反了自己那条判据,
|
|
106
|
-
* 一条本来赎得回的审批被判死(生产上界 4 MiB 上真复现);
|
|
107
|
-
* step 4 —— 恢复身份行上的**展示名**(`label`)—— 它是这条行上唯一可弃的展示物,身份四位恒在
|
|
108
|
-
* (codex r2-[high],红先复现后修:修前阶梯一边留着每行 200 字符的 label、一边把**全部**身份
|
|
109
|
-
* 删掉盖标记 —— 同一条判据第二次被自己违反;6000 条 CJK label 的 parked 行在生产上界上
|
|
110
|
-
* 实测 `agents:[]` + `parks:6000`,而清空 label 后 6000 条身份**全都装得下**);
|
|
111
|
-
* step 5 —— 连只剩身份的行都装不下 ⇒ 丢掉它们并落**显式不可恢复标记**({@link PARK_IDENTITY_LOSS_KEY}),
|
|
112
|
-
* `/decide` 与 SLA sweep 读到它就**响亮拒**,而不是让一条赎回不了的 park 看起来像普通 miss。
|
|
98
|
+
* step 2 —— 展示载荷整段丢(`phases` / `agents` / `groups`),`error` 收成短串;
|
|
99
|
+
* step 3 —— 顶层**观测载荷**:`effectiveArgs` 与 `resultFull` 整段丢、`name` / `description` 先脱后切。
|
|
100
|
+
* core 对 `effectiveArgs` 的成文口径逐字是 OBSERVATION-ONLY(never a gate input,never re-read by
|
|
101
|
+
* the engine;resume 的身份由 journal 的 callKey 携带,不是这个字段),`resultFull` 同类(取回面),
|
|
102
|
+
* 两者都**无界**;`name` / `description` 是脚本(LLM 撰写)经 `export const meta` 供的展示头,
|
|
103
|
+
* 同样无界 ⇒ **先脱后切**(切口会把读面脱敏所依赖的闭合引号一起切掉,半截凭据原样上 wire)。
|
|
113
104
|
* Returns null only when even the marked skeleton is oversize (physics — caller keeps the prior revision,
|
|
114
105
|
* matching the old behavior for that corner). Dialect-neutral (pure JS, no SQL) — shared verbatim by both twins. */
|
|
115
106
|
export declare function slimOversizeRun(run: WorkflowRun & {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { redactErrorMessage, redactHead } from "../observability/run-terminal-log.js";
|
|
2
2
|
import { summarizeWorkflowRun, isTerminalWorkflowStatus, WorkflowRunStoreError } from "@sema-agent/core";
|
|
3
3
|
import { foldKeyFamily, MAX_PENDING_PER_SESSION, PURGE_FENCE_MS, SERVED_FENCE_MS, } from "../orchestration/workflow-completion-inbox.js";
|
|
4
|
-
import {
|
|
4
|
+
import {} from "../orchestration/workflow-agent-session-index.js";
|
|
5
5
|
import { mysqlDriver, pgDriver } from "./sql-driver.js";
|
|
6
6
|
import { isDupKeyError } from "./sql-errors.js";
|
|
7
7
|
export const MAX_RUN_BLOB_BYTES = 4 * 1024 * 1024;
|
|
@@ -24,9 +24,8 @@ export function slimOversizeRun(run, maxBytes) {
|
|
|
24
24
|
let blob = JSON.stringify(step1);
|
|
25
25
|
if (Buffer.byteLength(blob) <= maxBytes)
|
|
26
26
|
return blob;
|
|
27
|
-
const identities = buildParkedRecoveryIdentities(run.agents);
|
|
28
27
|
const shortError = step1.error ? String(step1.error).slice(0, 1000) + MARK : MARK;
|
|
29
|
-
const step2 = { ...step1, phases: [], agents:
|
|
28
|
+
const step2 = { ...step1, phases: [], agents: [], groups: [], error: shortError };
|
|
30
29
|
blob = JSON.stringify(step2);
|
|
31
30
|
if (Buffer.byteLength(blob) <= maxBytes)
|
|
32
31
|
return blob;
|
|
@@ -38,19 +37,6 @@ export function slimOversizeRun(run, maxBytes) {
|
|
|
38
37
|
if (typeof step3.description === "string")
|
|
39
38
|
step3.description = redactHead(step3.description, SLIM_HEADER_MAX);
|
|
40
39
|
blob = JSON.stringify(step3);
|
|
41
|
-
if (Buffer.byteLength(blob) <= maxBytes)
|
|
42
|
-
return blob;
|
|
43
|
-
const bareIdentities = buildParkedRecoveryIdentities(run.agents, 0);
|
|
44
|
-
const step4 = { ...step3, agents: bareIdentities };
|
|
45
|
-
blob = JSON.stringify(step4);
|
|
46
|
-
if (Buffer.byteLength(blob) <= maxBytes)
|
|
47
|
-
return blob;
|
|
48
|
-
const step5 = {
|
|
49
|
-
...step4,
|
|
50
|
-
agents: [],
|
|
51
|
-
...(identities.length > 0 ? { [PARK_IDENTITY_LOSS_KEY]: buildParkIdentityLoss(identities.length) } : {}),
|
|
52
|
-
};
|
|
53
|
-
blob = JSON.stringify(step5);
|
|
54
40
|
return Buffer.byteLength(blob) <= maxBytes ? blob : null;
|
|
55
41
|
}
|
|
56
42
|
export class SqlWorkflowRunStore {
|
package/dist/trace/project.d.ts
CHANGED
|
@@ -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`
|
|
126
|
-
*
|
|
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` / 投影函数 —— 账本裸回放腿的那一次会当场断流(那一行不上帧、游标不前进、后面的
|
package/dist/trace/redact.js
CHANGED
|
@@ -2,18 +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
10
|
const WORD_INTERIOR_CLASS = "[A-Za-z0-9]";
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
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
|
+
}
|
|
14
288
|
const WORD_SEGMENT_CLASS = "[A-Za-z0-9._~+/-]";
|
|
15
|
-
const keywordArm = (keywordSrc, valueClass, min, tailSrc = "") => new RegExp(`(?:${keywordSrc}\\s+${valueClass
|
|
16
|
-
`|(?<!${WORD_SEGMENT_CLASS})${keywordSrc}(?:${twoStrongAhead(valueClass)}|${noConnectorAhead(valueClass)})${valueClass
|
|
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");
|
|
17
291
|
const STRUCTURAL_SECRET_PATTERNS = [
|
|
18
292
|
[/-----BEGIN(?:[^-](?:[^-](?:[^-]|-(?!-))*)?)?PRIVATE KEY(?: BLOCK)?-----[\s\S]{0,8192}?-----END(?:[^-](?:[^-](?:[^-]|-(?!-))*)?)?PRIVATE KEY(?: BLOCK)?-----/g, "«redacted:private-key»"],
|
|
19
293
|
[/---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----[\s\S]{0,8192}?---- END SSH2 ENCRYPTED PRIVATE KEY ----/g, "«redacted:private-key»"],
|
|
@@ -30,22 +304,17 @@ const KEYWORD_SECRET_PATTERNS = [
|
|
|
30
304
|
];
|
|
31
305
|
const SHAPE_SECRET_PATTERNS = [
|
|
32
306
|
[valueArm("sk-", "[A-Za-z0-9_-]", 16), "«redacted:key»"],
|
|
33
|
-
[
|
|
34
|
-
[
|
|
35
|
-
[
|
|
36
|
-
[
|
|
37
|
-
[
|
|
38
|
-
[
|
|
39
|
-
[
|
|
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»"],
|
|
40
314
|
];
|
|
41
315
|
const FULL_SECRET_PATTERNS = [...STRUCTURAL_SECRET_PATTERNS, ...KEYWORD_SECRET_PATTERNS, ...SHAPE_SECRET_PATTERNS];
|
|
42
316
|
const PEM_HEADER_IDENT_MAX = 31;
|
|
43
317
|
const PEM_BODY_RUN_MIN = 8;
|
|
44
|
-
const CH_TAB = 9, CH_LF = 10, CH_CR = 13, CH_SPACE = 32, CH_PLUS = 43, CH_MINUS = 45, CH_SLASH = 47;
|
|
45
|
-
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;
|
|
46
|
-
const CH_a = 97, CH_n = 110, CH_r = 114, CH_t = 116, CH_z = 122;
|
|
47
|
-
const CH_QUOTE = 34;
|
|
48
|
-
const CH_LAQUO = 171;
|
|
49
318
|
const isSepCode = (c) => c === CH_SPACE || c === CH_TAB || c === CH_CR || c === CH_LF;
|
|
50
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;
|
|
51
320
|
const isIdentStartCode = (c) => (c >= CH_A && c <= CH_Z) || (c >= CH_a && c <= CH_z);
|
|
@@ -421,7 +690,7 @@ function redactHeadlessPpkBodies(text) {
|
|
|
421
690
|
const FRAGMENT_SECRET_PATTERNS = [
|
|
422
691
|
[valueArm("sk-", "[A-Za-z0-9_-]", 6), "«redacted:key:partial»"],
|
|
423
692
|
[valueArm("(?:ghp|gho|ghu|ghs|ghr)_", "[A-Za-z0-9]", 8), "«redacted:gh-token:partial»"],
|
|
424
|
-
[
|
|
693
|
+
[new RegExp(String.raw `github_pat_${atLeast("[A-Za-z0-9_]", 8)}`, "g"), "«redacted:gh-token:partial»"],
|
|
425
694
|
[valueArm("xox[baprs]-", "[A-Za-z0-9-]", 4), "«redacted:slack-token:partial»"],
|
|
426
695
|
[valueArm("AKIA", "[0-9A-Z]", 6), "«redacted:aws-key:partial»"],
|
|
427
696
|
[valueArm("ASIA", "[0-9A-Z]", 8), "«redacted:aws-key:partial»"],
|
|
@@ -454,10 +723,15 @@ const regexRule = ([re, repl], arm) => {
|
|
|
454
723
|
throw new Error(`redact: replacement ${JSON.stringify(repl)} uses unsupported ref $${d}`);
|
|
455
724
|
}
|
|
456
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`);
|
|
457
729
|
return {
|
|
458
730
|
label: labelOfToken(repl),
|
|
459
731
|
arm,
|
|
460
732
|
run: (text, wantCount, json) => {
|
|
733
|
+
if (sticky !== undefined)
|
|
734
|
+
return runValueArm(re, sticky, repl, text, wantCount);
|
|
461
735
|
const rx = re;
|
|
462
736
|
if (!wantCount)
|
|
463
737
|
return { out: text.replace(rx, repl), count: 0 };
|
|
@@ -704,6 +978,7 @@ export function redactSecrets(text, opts) {
|
|
|
704
978
|
const wantCount = onRedaction !== undefined && (opts?.observe ?? true);
|
|
705
979
|
const json = opts?.json === true;
|
|
706
980
|
const r = applyRules(normalizeForRedaction(text), wantCount, json, opts?.observe ?? true);
|
|
981
|
+
forgetEscapedSeparatorTails();
|
|
707
982
|
reportHits(r.hits);
|
|
708
983
|
return r.out;
|
|
709
984
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "7.
|
|
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.17.
|
|
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.
|
|
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",
|