@sema-agent/server 7.74.0 → 7.75.1
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/README.md +13 -0
- package/README.zh-CN.md +10 -0
- package/USAGE.md +24 -0
- package/dist/http/routes/runs.js +4 -1
- package/dist/http/server.js +6 -6
- 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 +44 -5
- package/dist/observability/run-terminal-log.js +51 -5
- 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/ledger-events.d.ts +12 -0
- package/dist/trace/ledger-events.js +6 -0
- package/dist/trace/ledger-sink.js +3 -4
- package/dist/trace/project.d.ts +25 -2
- package/dist/trace/project.js +3 -0
- package/dist/trace/redact.d.ts +20 -0
- package/dist/trace/redact.js +294 -19
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -194,6 +194,19 @@ boundary) — the side-by-side table is in `USAGE.md`, worth reading before pick
|
|
|
194
194
|
Web search (deployment `WEB_SEARCH_*` env, per-request `settings.webSearch`, the two-lane precedence
|
|
195
195
|
and the tool-level error forms) is documented in `USAGE.md` §9.5.
|
|
196
196
|
|
|
197
|
+
## Security & privacy note
|
|
198
|
+
|
|
199
|
+
> 🔴 **Trace redaction ≠ egress control** (anchor: 脱敏 ≠ 出站控制). Trace redaction (the `«redacted»` markers you see in the
|
|
200
|
+
> ledger / SSE / turns / approval previews) only rewrites the **persistence and display** surfaces —
|
|
201
|
+
> **the request body carrying a tool result to the model's actual route is the original, unredacted
|
|
202
|
+
> text.** That route is not just `MODEL_GATEWAY_BASEURL`: it can also be the fallback gateway list
|
|
203
|
+
> (`MODEL_GATEWAY_FALLBACK_URLS`), an independent Anthropic route for `provider:"anthropic"` models
|
|
204
|
+
> (defaults to `https://api.anthropic.com` unless `ANTHROPIC_BASEURL` is set), or a per-model `baseUrl`
|
|
205
|
+
> override the registry can hand a single catalog model (see `docs/ARCHITECTURE.md` §10). To keep
|
|
206
|
+
> content from leaving the machine, rely on the **read-side sensitive-path gate** and confirm **every
|
|
207
|
+
> route this deployment can select** points at infrastructure you control — pointing only the default
|
|
208
|
+
> gateway env at a private endpoint is not sufficient. Verification method: `USAGE.md` §9.6.
|
|
209
|
+
|
|
197
210
|
## HTTP API overview
|
|
198
211
|
|
|
199
212
|
One row per endpoint family (not exhaustive):
|
package/README.zh-CN.md
CHANGED
|
@@ -173,6 +173,16 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
|
|
|
173
173
|
Web search(部署 env `WEB_SEARCH_*` 七键、per-request `settings.webSearch`、两车道优先级、工具层错误形)
|
|
174
174
|
见 `USAGE.md` §9.5。
|
|
175
175
|
|
|
176
|
+
## 安全与隐私提示
|
|
177
|
+
|
|
178
|
+
> 🔴 **trace 脱敏 ≠ 出站控制。** trace 脱敏(你在账本 / SSE / turns / 审批预览里看到的 `«redacted»`
|
|
179
|
+
> 标记)只作用于**持久化与展示面**——**发往「该 run 实际选中的模型路由」的请求体是工具结果原文**。
|
|
180
|
+
> 这条路由不止 `MODEL_GATEWAY_BASEURL` 一根:还可能是备用网关列表(`MODEL_GATEWAY_FALLBACK_URLS`)、
|
|
181
|
+
> `provider:"anthropic"` 模型的独立直连路由(缺省根 `https://api.anthropic.com`,除非显式配
|
|
182
|
+
> `ANTHROPIC_BASEURL`)、或配置中心可对单个目录模型下发的 per-model `baseUrl` 覆盖(见
|
|
183
|
+
> `docs/ARCHITECTURE.md` §10)。要让内容不出机器,靠**读侧敏感路径门**,并核对**本部署会被选中的每一条
|
|
184
|
+
> 路由**都指向自己控制的基础设施——只把默认网关 env 指到私有端点是不够的。验法见 `USAGE.md` §9.6。
|
|
185
|
+
|
|
176
186
|
## HTTP API 概览
|
|
177
187
|
|
|
178
188
|
一行一个端点族(非全量):
|
package/USAGE.md
CHANGED
|
@@ -1512,3 +1512,27 @@ TOC 本地形)时被读取;`REQUIRE_PRINCIPAL=true`(多租户)上这个键**结
|
|
|
1512
1512
|
`SEMA_WEBSEARCH_KEY_<PROVIDER>` 这样的命名空间存放每个 provider 的 key,由壳在本地读出后把明文塞进请求体;
|
|
1513
1513
|
这纯粹是**客户端约定**,本仓不读取、不校验、也不感知任何这类客户端侧 env 命名(这条命名事实来自任务书,
|
|
1514
1514
|
本车未读 cli 源码核实,列入收车档「未闭环」)。
|
|
1515
|
+
|
|
1516
|
+
### 9.6 trace 脱敏的射程:与出站请求体无关
|
|
1517
|
+
|
|
1518
|
+
🔴 **脱敏 ≠ 出站控制**:trace 脱敏(账本 / SSE / turns / 审批预览里的 `«redacted»` 标记)只作用于
|
|
1519
|
+
**持久化与展示面**——**工具结果发往「该 run 实际选中的模型路由」的请求体是原文**。⚠️ 「实际选中的路由」
|
|
1520
|
+
不等于 `MODEL_GATEWAY_BASEURL` 一根:本部署的模型出口至少四条——默认网关(`MODEL_GATEWAY_BASEURL`)、
|
|
1521
|
+
备用网关(`MODEL_GATEWAY_FALLBACK_URLS`,failover 拓扑参与)、`provider:"anthropic"` 模型的**独立直连
|
|
1522
|
+
路由**(缺省根 `https://api.anthropic.com`,除非显式配 `ANTHROPIC_BASEURL`)、以及配置中心可对**单个
|
|
1523
|
+
目录模型**下发的 per-model `baseUrl` 覆盖(见 `docs/ARCHITECTURE.md` §10「Trace/redaction」)。要让内容
|
|
1524
|
+
不出机器,靠**读侧敏感路径门**(`READ_DENY_PATTERNS` 等,见 §「执行 lane 与租户门」附近)并核对**上述
|
|
1525
|
+
四条路由每一条**都指向自己控制的基础设施——只把 `MODEL_GATEWAY_BASEURL` 指到私有网关、放着
|
|
1526
|
+
`provider:"anthropic"` 或某个目录模型的 `baseUrl` 覆盖不管,那部分流量仍走公网原厂,不靠
|
|
1527
|
+
`src/trace/redact.ts` 这个脱敏器——它自述「不是穷举的」,即便是穷举的也只管持久化/展示这一面(且它同时
|
|
1528
|
+
被项目记忆注入、hook asyncRewake 唤醒文本这两条**反方向**调用复用——那两条是"先脱敏、脱敏结果才给模型
|
|
1529
|
+
看",与工具结果这一条方向相反,验法时别混)。
|
|
1530
|
+
|
|
1531
|
+
**怎么验(运维口令)**:用自控转发代理截获**每一条生效路由**的出站体核对——不能只把 `MODEL_GATEWAY_BASEURL`
|
|
1532
|
+
指向自己起的转发代理,还要按部署实际配置的路由数量逐条起代理(默认网关一个 + 每条备用网关一个 +
|
|
1533
|
+
`ANTHROPIC_BASEURL` 一个,如果 `provider:"anthropic"` 在用);代理记录经过的每个请求体后原样转发给真
|
|
1534
|
+
网关,跑一条会产出敏感格式(如一个明文 token)的工具结果,比对代理记下的出站 `messages[]` 里
|
|
1535
|
+
`role:"tool"` 的内容与该 run 的 trace/SSE 拷贝:出站体应为**原文**,trace 拷贝应为 `«redacted…»`。两者
|
|
1536
|
+
不同,即验证「脱敏只发生在持久化/展示面,不发生在出站面」这句话为真——**若代理本身继续把请求转发到外部
|
|
1537
|
+
模型服务,即便截获成功,也不满足「内容不出机器」这个更高的目标,截获只证明了脱敏的射程,不证明数据没有
|
|
1538
|
+
离开你控制的边界**。
|
package/dist/http/routes/runs.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { persistedPlaneOf } from "../../terminal.js";
|
|
2
2
|
import { memoRedactedRead, memoRedactTerminalResult, redactErrorMessage, redactHead, redactLedgerEventData, redactTerminalResult } from "../../observability/run-terminal-log.js";
|
|
3
|
+
import { isModelTextLedgerEventType } from "../../trace/ledger-events.js";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
import { uuidv7, mintCheckpointToken, CheckpointError, validatePendingSteer } from "@sema-agent/core";
|
|
5
6
|
import { isTerminalRunStatus, isParkedRunStatus } from "../../plugins/store-contracts.js";
|
|
@@ -139,7 +140,9 @@ async function streamRunEvents(req, res, deps, runStore, taskId, staleMs) {
|
|
|
139
140
|
const type = normalizeRunEventType(ev.type);
|
|
140
141
|
const redacted = type === "done" && ev.data && typeof ev.data === "object" && ev.data.result && typeof ev.data.result === "object"
|
|
141
142
|
? { ...ev.data, result: memoRedactTerminalResult("ev.done.result", ev.data.result) }
|
|
142
|
-
: type === "failed" ? memoRedactedRead("ev.failed", ev.data, (d) => redactLedgerEventData(type, d))
|
|
143
|
+
: type === "failed" ? memoRedactedRead("ev.failed", ev.data, (d) => redactLedgerEventData(type, d))
|
|
144
|
+
: isModelTextLedgerEventType(type) ? memoRedactedRead("ev.model-text", ev.data, (d) => redactLedgerEventData(type, d))
|
|
145
|
+
: redactLedgerEventData(type, ev.data);
|
|
143
146
|
const data = screenedStoredEventData(type, redacted);
|
|
144
147
|
return { id: ev.seq, event: type, data: { type, ...(data ?? {}) } };
|
|
145
148
|
},
|
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";
|
|
@@ -33,7 +33,7 @@ import { TERMINAL_PARK_GATE_KINDS } from "../plugins/checkpoint-store-sql.js";
|
|
|
33
33
|
import { createWallClockJumpGuard } from "../wall-clock-jump-guard.js";
|
|
34
34
|
import {} from "../observability/rate-limit.js";
|
|
35
35
|
import { withPrincipal } from "../observability/principal-context.js";
|
|
36
|
-
import { turnEndEventData, contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, toolRosterDeltaEventData, humanInputEventData, appendModelUsageDelta, appendPromptManifest, attachModelUsage } from "../trace/project.js";
|
|
36
|
+
import { modelTextEventData, turnEndEventData, contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, toolRosterDeltaEventData, humanInputEventData, appendModelUsageDelta, appendPromptManifest, attachModelUsage } from "../trace/project.js";
|
|
37
37
|
import { redactSecrets } from "../trace/redact.js";
|
|
38
38
|
import { createLedgerSink, createSerialLedgerAppend } from "../trace/ledger-sink.js";
|
|
39
39
|
import { registerEngineNoticeLeg } from "../trace/engine-notice-wire.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(() => { });
|
|
@@ -1561,11 +1561,11 @@ export function createHttpServer(rawDeps) {
|
|
|
1561
1561
|
const anchor = new TurnAnchorCapture(captureTurnAnchor, () => deps.metrics?.inc("resume_anchor_capture_failed"), captureUserMessageAnchor);
|
|
1562
1562
|
const flush = async () => {
|
|
1563
1563
|
if (reasoning) {
|
|
1564
|
-
await append("reasoning", { text:
|
|
1564
|
+
await append("reasoning", modelTextEventData({ text: reasoning }));
|
|
1565
1565
|
reasoning = "";
|
|
1566
1566
|
}
|
|
1567
1567
|
if (text) {
|
|
1568
|
-
await append("text", { text,
|
|
1568
|
+
await append("text", modelTextEventData({ text, eventId: anchor.firstTextEventId }));
|
|
1569
1569
|
text = "";
|
|
1570
1570
|
}
|
|
1571
1571
|
};
|
|
@@ -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";
|
|
@@ -53,6 +54,34 @@ export declare const REDACT_INPUT_CAP = 65536;
|
|
|
53
54
|
* 脱敏**之后**按字节封顶(占位已替换完,切在占位中间不是泄漏)并带标记。 */
|
|
54
55
|
export declare const REDACT_OUTPUT_MAX_BYTES = 65000;
|
|
55
56
|
export declare function redactErrorMessage(raw: string | undefined): string | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* 🔴 **模型自由文本的唯一装载口**(B-106 / S-268,7.75.1)——`TaskResult.result` / `TaskResult.salvagedOutput`
|
|
59
|
+
* 与账本的 `text` / `reasoning` 两行,四座同调本函数。
|
|
60
|
+
*
|
|
61
|
+
* 它**不是第二只脱敏器**:零自有规则表、零自有词表,判「什么算凭据」的仍然只有 {@link redactSecrets} 一只。
|
|
62
|
+
* 本函数只定两件**写回**的事,而这两件事在三条既有纪律面前各只有一个正确答案:
|
|
63
|
+
*
|
|
64
|
+
* ① **认出凭据形才改写字节;没认出就回原串**(codex 对抗复审 r1 [medium],亲核实测后修)。
|
|
65
|
+
* `redactSecrets` 自 S-103 起**无条件先剥** `\p{Cf}`,所以 `redactSecrets(x) !== x` 对任何含 Cf 的文本都为真 ——
|
|
66
|
+
* 实测:`👨👩👧`(ZWJ 家庭 emoji)被拆成三个独立 emoji、波斯语 `میخواهم`(ZWNJ)被粘成 `میخواهم`。
|
|
67
|
+
* 诊断文本上这是成文代价(那一族本就要「先扫描后渲染」);**用户最终答复**上它是每一条含 ZWJ/ZWNJ 的
|
|
68
|
+
* 正常答案被永久改坏,而本件对外的承诺逐字是「不含凭据形的文本逐字节不变」。判据用的是本模块**自己成文的
|
|
69
|
+
* 那一条**(`redact.ts` 的 `normalizeForRedaction` 顶注:「正确的谓词是 `redactSecrets(x) !== normalizeForRedaction(x)`」),
|
|
70
|
+
* 既有消费方 `tool-approval.ts` 的规则车道闸用的也是它 —— 一条谓词两处用,不是新规矩。
|
|
71
|
+
* ⚠️ **命中那一次仍然给剥后文本**(S-103 的「先扫描后渲染」不动摇):把零宽字符塞进密钥中间的那条文本,
|
|
72
|
+
* 渲染的必须是剥后那份,否则消费端把残片拼回原钥。
|
|
73
|
+
* ② **扫描器抛 ⇒ 登记过的占位(fail-closed),不把整条 run 拖下水**(codex r1 [high],亲核实测后修)。
|
|
74
|
+
* `redactSecrets` 的 JSON 秘密字段臂仍含多字符循环体(S-106 既有未闭:`(?:\\.[^"\\]*)*`),实测
|
|
75
|
+
* `{"password":"<400 万个 \\n 转义对>"}`(8MB)抛 `RangeError`。`result` 接进来之前这条路不存在;接进来之后,
|
|
76
|
+
* 裸调用会让异常穿过 `redactTerminalResult` → 三孪生 `setTerminal`,**一条跑成功的 run 落成 failed + result:null**
|
|
77
|
+
* (实测读数)。⇒ 与本文件既有的 {@link redactErrorMessage} / `redactDiagnosticText` / `redactArtifactText`
|
|
78
|
+
* 三座**同一条**处置:抛就换占位、计数、留痕([ref] 的 loud,不是静默 fail-open)。丢的是这一条病态文本,
|
|
79
|
+
* 不是这条 run。根因(那条正则)是 S-106 的独立车,本函数不在复审轮里改它。
|
|
80
|
+
* ③ **不设体量上限**(裁定):`result` 住在 `result` JSON/JSONB 列(亲核 `plugins/tidb-pool.ts` 与
|
|
81
|
+
* `plugins/pg-pool.ts` 的 DDL),数 MB 终稿是正常形 —— {@link redactErrorMessage} 的两道上限是为
|
|
82
|
+
* `task_run.error` 那一列 `TEXT(65 535 字节)` 定的,套到这里等于用静默的数据损坏换一次脱敏。
|
|
83
|
+
*/
|
|
84
|
+
export declare function redactModelText(raw: string): string;
|
|
56
85
|
/** {@link redactTerminalResult} 会碰的全部顶层键 = 现行形的 `"redacted"` 行 ∪ 旧形平面键;
|
|
57
86
|
* `memoRedactTerminalResult` 的记忆键由它派生(两处同源,见 `RESULT_KEY_TREATMENT` 顶注)。 */
|
|
58
87
|
export declare const REDACTABLE_RESULT_LEAVES: readonly string[];
|
|
@@ -65,6 +94,10 @@ export declare function redactTerminalResult<T extends {
|
|
|
65
94
|
remoteEnvFailures?: readonly {
|
|
66
95
|
message?: string | undefined;
|
|
67
96
|
}[] | undefined;
|
|
97
|
+
/** B-106:模型自由文本两叶 + 引擎披露散文一族。形上同样取 `unknown`(读面纪律,见下)。 */
|
|
98
|
+
result?: unknown;
|
|
99
|
+
salvagedOutput?: unknown;
|
|
100
|
+
rewindNotes?: unknown;
|
|
68
101
|
/** 🔴 形上**故意**是 `unknown`:本函数也是**读面**(旧持久 blob / 非 core Runner 的形不可信),
|
|
69
102
|
* 声明成 `{path,edits}[]` 会逼每一个喂坏形的调用点铸一次宽松断言 —— 而「坏形从哪来」正是本座要
|
|
70
103
|
* 处理的事(同一份不信任在 `remoteEnvFailures` 的实现里也已成文)。运行期逐项判形,不抛。 */
|
|
@@ -120,12 +153,18 @@ export declare function redactLeaderResultWithFlag(result: unknown): {
|
|
|
120
153
|
changed: boolean;
|
|
121
154
|
};
|
|
122
155
|
export declare function memoRedactedRead<T>(seat: string, raw: T, redact: (v: T) => T): T;
|
|
123
|
-
/** run 终态 result 的读面记忆只按**可脱敏的那几叶**(
|
|
124
|
-
* `
|
|
125
|
-
* 第十六轮重扫:按整块 result 摘要每次轮询要 stringify 一份可能数 MB 的 result,且大 result
|
|
156
|
+
/** run 终态 result 的读面记忆只按**可脱敏的那几叶**({@link REDACTABLE_RESULT_LEAVES},属主是
|
|
157
|
+
* `RESULT_KEY_TREATMENT`)做键与留体——
|
|
158
|
+
* 第十六轮重扫:按整块 result 摘要每次轮询要 stringify 一份可能数 MB 的 result,且大 result 的脏行永远存不进去。叶全缺席
|
|
126
159
|
* ⇒ O(1) 直返(`redactTerminalResult` 本就同引用)。
|
|
127
160
|
* 🔴 S-136(core 7.6.0 D-8):新形把前两片搬进了 `terminal`(同属一个键,按它整只做叶更省);旧形的两片
|
|
128
|
-
* **照留**——盘上真有那样的行,而读面纵深正是为它们存在的(codex 对抗复审 r1 [high] 验真后补)。
|
|
161
|
+
* **照留**——盘上真有那样的行,而读面纵深正是为它们存在的(codex 对抗复审 r1 [high] 验真后补)。
|
|
162
|
+
* 🔴 **B-106 起「叶全缺席」这条捷径实际上不再触发**(如实写明,不是漏改):`result` 在 core 类型上是
|
|
163
|
+
* **必填**串,进了叶集就恒在场 ⇒ 每次读面都要对叶子做一次 sha256。代价是有意接受的,因为省下的那条
|
|
164
|
+
* 捷径对**含自由文本的 run(即全部 run)**本来就不成立 —— 不把 `result` 放进叶集才是真的漏:
|
|
165
|
+
* `memoRedactTerminalResult` 会对它 O(1) 直返明文,读面纵深对这一叶恒不生效(这正是 [ref] 立案时
|
|
166
|
+
* `editedFiles` 那一格的病)。读数:8MB result 的一次摘要 ~10ms 量级,远小于一次全扫;干净行仍只摘要
|
|
167
|
+
* 一次即进 clean 集,脏行超过 {@link READ_MEMO_DIRTY_MAX_ENTRY_BYTES} 时退化为每次重扫(既有形)。 */
|
|
129
168
|
export declare function memoRedactTerminalResult<T extends Parameters<typeof redactTerminalResult>[0]>(seat: string, result: T): T;
|
|
130
169
|
export declare function diffScanShadowCountForTest(): number;
|
|
131
170
|
export declare function resetDiffScanShadowCountForTest(): void;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { redactSecrets } from "../trace/redact.js";
|
|
3
|
-
import { isParkLedgerEventType } from "../trace/ledger-events.js";
|
|
2
|
+
import { redactSecrets, normalizeForRedaction } from "../trace/redact.js";
|
|
3
|
+
import { isModelTextLedgerEventType, isParkLedgerEventType } from "../trace/ledger-events.js";
|
|
4
4
|
import { isUnsuccessfulRunStatus } from "../plugins/store-contracts.js";
|
|
5
5
|
import { errorText } from "./err-text.js";
|
|
6
6
|
import { recordFailOpen } from "./fail-open.js";
|
|
@@ -64,6 +64,20 @@ export function redactErrorMessage(raw) {
|
|
|
64
64
|
return OVERSIZED_REDACTION_PLACEHOLDER;
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
+
export function redactModelText(raw) {
|
|
68
|
+
try {
|
|
69
|
+
const out = redactSecrets(raw);
|
|
70
|
+
if (out === raw)
|
|
71
|
+
return raw;
|
|
72
|
+
if (out === normalizeForRedaction(raw))
|
|
73
|
+
return raw;
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
recordFailOpen("server.run-terminal.redact-threw", `model-text len=${raw.length} err=${errorText(err)}`);
|
|
78
|
+
return OVERSIZED_REDACTION_PLACEHOLDER;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
67
81
|
const MAX_EDITED_FILES = 1000;
|
|
68
82
|
const RESULT_KEY_TREATMENT = {
|
|
69
83
|
terminal: "redacted",
|
|
@@ -74,13 +88,13 @@ const RESULT_KEY_TREATMENT = {
|
|
|
74
88
|
sessionId: "verbatim",
|
|
75
89
|
model: "verbatim",
|
|
76
90
|
effectiveReasoning: "verbatim",
|
|
77
|
-
result: "
|
|
78
|
-
salvagedOutput: "
|
|
91
|
+
result: "redacted",
|
|
92
|
+
salvagedOutput: "redacted",
|
|
79
93
|
apiFailure: "verbatim",
|
|
80
94
|
retryAfterMs: "verbatim",
|
|
81
95
|
degraded: "verbatim",
|
|
82
96
|
structuredOutput: "verbatim",
|
|
83
|
-
rewindNotes: "
|
|
97
|
+
rewindNotes: "redacted",
|
|
84
98
|
haltedOnUserRejection: "verbatim",
|
|
85
99
|
haltedByUser: "verbatim",
|
|
86
100
|
strandedHumanAnswers: "verbatim",
|
|
@@ -167,6 +181,31 @@ export function redactTerminalResult(result) {
|
|
|
167
181
|
: r.editedFiles;
|
|
168
182
|
changed = true;
|
|
169
183
|
}
|
|
184
|
+
if (typeof r.result === "string") {
|
|
185
|
+
const next = redactModelText(r.result);
|
|
186
|
+
if (next !== r.result) {
|
|
187
|
+
out.result = next;
|
|
188
|
+
changed = true;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (typeof r.salvagedOutput === "string") {
|
|
192
|
+
const next = redactModelText(r.salvagedOutput);
|
|
193
|
+
if (next !== r.salvagedOutput) {
|
|
194
|
+
out.salvagedOutput = next;
|
|
195
|
+
changed = true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (r.rewindNotes !== undefined) {
|
|
199
|
+
out.rewindNotes = Array.isArray(r.rewindNotes)
|
|
200
|
+
? r.rewindNotes.map((n) => {
|
|
201
|
+
if (n === null || typeof n !== "object")
|
|
202
|
+
return n;
|
|
203
|
+
const message = n.message;
|
|
204
|
+
return typeof message === "string" ? { ...n, message: redactErrorMessage(message) } : n;
|
|
205
|
+
})
|
|
206
|
+
: r.rewindNotes;
|
|
207
|
+
changed = true;
|
|
208
|
+
}
|
|
170
209
|
return changed ? out : result;
|
|
171
210
|
}
|
|
172
211
|
export function redactLedgerEventData(type, data) {
|
|
@@ -184,6 +223,13 @@ export function redactLedgerEventData(type, data) {
|
|
|
184
223
|
return data;
|
|
185
224
|
return { ...data, errorMessage: redactErrorMessage(msg) };
|
|
186
225
|
}
|
|
226
|
+
if (isModelTextLedgerEventType(type)) {
|
|
227
|
+
const text = data.text;
|
|
228
|
+
if (typeof text !== "string")
|
|
229
|
+
return data;
|
|
230
|
+
const next = redactModelText(text);
|
|
231
|
+
return next === text ? data : { ...data, text: next };
|
|
232
|
+
}
|
|
187
233
|
if (isParkLedgerEventType(type)) {
|
|
188
234
|
const gate = data.gate;
|
|
189
235
|
const next = redactCheckpointGate(gate);
|
|
@@ -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
|
}
|