@sema-agent/server 7.109.0 → 7.111.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/MIGRATION.md +25 -0
- package/README.md +8 -0
- package/README.zh-CN.md +5 -0
- package/USAGE.md +2 -2
- package/dist/approval-ask-audit-store.d.ts +38 -25
- package/dist/approval-ask-audit-store.js +26 -59
- package/dist/approval-card.d.ts +6 -5
- package/dist/approval-card.js +8 -11
- package/dist/bench/s1/run-firm.d.ts +11 -0
- package/dist/bench/s1/run-firm.js +47 -32
- package/dist/boot/coordinators.js +1 -1
- package/dist/boot/reapers.d.ts +3 -0
- package/dist/boot/reapers.js +3 -2
- package/dist/boot/resolve-spec.js +3 -4
- package/dist/boot/session-faces.d.ts +4 -0
- package/dist/boot/session-faces.js +3 -1
- package/dist/boot/stage-07-capability-layer.d.ts +2 -0
- package/dist/boot/stage-07-capability-layer.js +18 -3
- package/dist/boot/stage-08-reapers.d.ts +2 -0
- package/dist/boot/stage-08-reapers.js +2 -2
- package/dist/boot/stage-09-leader.d.ts +2 -0
- package/dist/boot/stage-09-leader.js +2 -1
- package/dist/boot/stage-10-http-server.d.ts +2 -0
- package/dist/boot/stage-10-http-server.js +2 -1
- package/dist/boot/workflow-orchestration.js +1 -1
- package/dist/config-catalog.js +1 -1
- package/dist/config-types.d.ts +3 -2
- package/dist/config.js +19 -7
- package/dist/deployment-governance.d.ts +8 -2
- package/dist/deployment-governance.js +9 -8
- package/dist/hooks/hook-runner.d.ts +2 -2
- package/dist/hooks/hook-runner.js +40 -26
- package/dist/http/durable-run-leg.js +1 -1
- package/dist/http/resume-legs.js +12 -4
- package/dist/http/routes/runs.d.ts +0 -1
- package/dist/http/routes/runs.js +36 -1
- package/dist/http/routes/sessions.js +12 -2
- package/dist/http/routes/tasks.js +13 -7
- package/dist/http/server.d.ts +5 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/instruction-selection.d.ts +34 -0
- package/dist/instruction-selection.js +23 -0
- package/dist/instruction-source-notice.d.ts +3 -3
- package/dist/memory-layer-legacy-lock.d.ts +3 -3
- package/dist/model-entry-headers.d.ts +5 -2
- package/dist/model-entry-headers.js +4 -4
- package/dist/model-unreachable-notice.d.ts +2 -2
- package/dist/observability/corrupt-read-seat.d.ts +24 -0
- package/dist/observability/corrupt-read-seat.js +24 -0
- package/dist/observability/fail-open.d.ts +25 -1
- package/dist/observability/fail-open.js +25 -1
- package/dist/observability/metrics.js +2 -1
- package/dist/observability/run-terminal.js +10 -0
- package/dist/operator-ask.d.ts +9 -8
- package/dist/operator-ask.js +9 -1
- package/dist/orchestration/workflow-agent-session-index.d.ts +12 -23
- package/dist/orchestration/workflow-agent-session-index.js +11 -50
- package/dist/orchestration/workflow-notify-journal.d.ts +11 -8
- package/dist/orchestration/workflow-notify-journal.js +20 -36
- package/dist/plugins/file-outcome-sink.d.ts +4 -1
- package/dist/plugins/file-outcome-sink.js +9 -3
- package/dist/plugins/file-run-store.js +4 -4
- package/dist/plugins/memory-run-store.d.ts +1 -1
- package/dist/plugins/memory-run-store.js +2 -1
- package/dist/plugins/pg-pool.js +15 -0
- package/dist/plugins/retention-store-sql.d.ts +1 -1
- package/dist/plugins/retention-store-sql.js +2 -0
- package/dist/plugins/run-store-sql.d.ts +1 -1
- package/dist/plugins/run-store-sql.js +3 -2
- package/dist/plugins/schema-indexes.js +2 -0
- package/dist/plugins/store-backend.d.ts +4 -0
- package/dist/plugins/store-backend.js +9 -0
- package/dist/plugins/store-contracts.d.ts +24 -0
- package/dist/plugins/store-contracts.js +10 -0
- package/dist/plugins/tidb-pool.js +17 -0
- package/dist/plugins/user-input-ledger-file.d.ts +35 -0
- package/dist/plugins/user-input-ledger-file.js +108 -0
- package/dist/plugins/user-input-ledger-sql.d.ts +54 -0
- package/dist/plugins/user-input-ledger-sql.js +129 -0
- package/dist/project-memory.d.ts +10 -2
- package/dist/project-memory.js +5 -2
- package/dist/run-local.d.ts +18 -5
- package/dist/run-local.js +24 -9
- package/dist/runs.d.ts +8 -1
- package/dist/runs.js +11 -5
- package/dist/runtime-governance.d.ts +20 -8
- package/dist/runtime-governance.js +7 -12
- package/dist/session-records-skipped-notice.d.ts +4 -3
- package/dist/task-settings.d.ts +0 -6
- package/dist/task-settings.js +0 -1
- package/dist/tool-approval.d.ts +8 -1
- package/dist/tool-approval.js +4 -1
- package/dist/trace/core-keyset-guard.d.ts +16 -1
- package/dist/trace/engine-notice-wire.d.ts +30 -35
- package/dist/trace/engine-notice-wire.js +23 -23
- package/dist/trace/ledger-sink.d.ts +4 -0
- package/dist/trace/ledger-sink.js +1 -1
- package/dist/trace/project.d.ts +11 -3
- package/dist/trace/project.js +6 -5
- package/dist/trace/self-minted-wire.d.ts +40 -0
- package/dist/trace/self-minted-wire.js +15 -0
- package/dist/trace/wire-projection-faces.d.ts +3 -3
- package/dist/trace/wire-projection-faces.js +4 -2
- package/dist/user-input-ledger.d.ts +349 -0
- package/dist/user-input-ledger.js +354 -0
- package/package.json +3 -3
|
@@ -264,7 +264,7 @@ export const FAIL_OPEN_TAGS = {
|
|
|
264
264
|
"server.redact.ppk-eat-to-end": { cls: "F", note: "S-107 第三十二轮重扫:PuTTY .ppk 容器扫描器(redact.ts redactPpkContainers)在「无合格终止 / 收口后同容器范围再见私钥字段 / 收口后到下一版本头之间夹非空白」三形下从头**吃到输入末尾**(fail-closed 取证丢弃)——终态 errorMessage 座上=整条错误文案变成 «redacted:private-key»,与 redactions_applied_total{private-key} 的诚实替换同形不可分,故要有自己的计数/探针;影子扫不计;计数=多少次整段吃尾。" },
|
|
265
265
|
"server.approval-card.closed-word-out-of-set": {
|
|
266
266
|
cls: "F",
|
|
267
|
-
note: "S-249(`approval-card.ts` 的边界窄读族;今天三个成员:`readRuleOffersAbsence`、S-934 ② 起的 `readAskClassPair`(core 7.37.0 #1214 的类对 `askClass` / `classes`,活卡帧 / 呈卡帧的卡 /
|
|
267
|
+
note: "S-249(`approval-card.ts` 的边界窄读族;今天三个成员:`readRuleOffersAbsence`、S-934 ② 起的 `readAskClassPair`(core 7.37.0 #1214 的类对 `askClass` / `classes`,活卡帧 / 呈卡帧的卡 / 运维队列行同一只,判法 = core 屏 `screenAskClassMembers`(S-955 ⑥ / core 7.38.0 #1249,本仓零自写对判),两键同在同缺,半对 / 集外 / 首员不符 / 重复员 = 屏拒 = 读不出;detail 前缀 `askClass=` / `classes=`),与 S-721 起的 `readClosedWordSeat` —— 一座一只 core 成员筛,座是参数:`mandate`(core 7.32.0 #1092,活卡帧 / 活挂起行 / 运维队列行同一只,闭集筛 = core `isPersistedRuleMandate`)与 S-755 起的 `origin`(运维队列行读 park 行孪生座,闭集筛 = core `isAskOrigin`,与 inbox 行 core 投影处同一只筛);detail 前缀 `ruleOffersAbsence=` / `mandate=` / `origin=` 判别是哪一座):活卡帧 / `card_json` / (S-700 起)运维队列行(`parkRowFacts` 读寄存行上的 park 孪生座;队列是热读面 ⇒ 同一行每读一次计一次)的一个**闭集词**座上,键在场而词不在本仓认得的那张表里(世代差,或第三方 producer)⇒ 该座**按缺席**处置。放行的最坏后果=卡上少一行「为什么这次没有规则候选」的解释(壳退回不显示该行),它**不改变任何裁决**——门在引擎里早已判完,本座是展示/观测面;反方向(把一个消费端词表外的值原样上卡)更坏:下游按闭集写的分支会落进 default 臂或渲染出生词。本臂存在的全部理由是**判别**:修前「上游发了个生词」与「上游根本没发这个键」在遥测上同形,而前者意味着本仓的词表该跟着 core 加词了(型面另有两向编译围栏,但那只在**重新编译**时说话,跑在线上的那一版不会自己红)。detail 带座名与那个词(经 redactSecrets 截 40);数 / 布尔等原始值原样,对象 / 数组 / 函数 / null 只报形名(`<object>` / `<array>` / `<function>` / `<null>`,S-700 codex r1:格式化从不调用值自己的转换方法 —— 持久层里一格 `{\"toString\":null}` 曾让诊断本身抛出、打掉整只队列)。",
|
|
268
268
|
},
|
|
269
269
|
"server.pg-pool.idle-client-error": {
|
|
270
270
|
cls: "F",
|
|
@@ -290,6 +290,30 @@ export const FAIL_OPEN_TAGS = {
|
|
|
290
290
|
cls: "F",
|
|
291
291
|
note: "S-582(`plugins/send-file-ledger.ts`,local 车道的发文件账本):一条账行读不出形(`42` / 缺 id·scope·objectKey 三元组 / 中间半截 JSON)⇒ 跳过它继续。最坏后果 = 那条已签发的链接在它租户的列表里**看不见**(对象本身没动;账本本来就不存 URL)。治理面少一行是可见性损失,不是放行 ⇒ F;但它恰是治理面要看的那一类事实,所以必须留痕 —— 修前零披露。",
|
|
292
292
|
},
|
|
293
|
+
"server.approval-ask-audit.corrupt-record-skipped": {
|
|
294
|
+
cls: "F",
|
|
295
|
+
note: "S-967(`approval-ask-audit-store.ts`,local 车道 File ask 审计店的 boot 重放;登记名 `approval-ask-audit`):账本**中间**一行读不出形(`42` / 数组 / 缺键 / 词表外 `kind`·`outcome`·`orphanState` / 半截 JSON;撕尾不走本 tag)⇒ 跳过它继续。最坏后果按行种:丢 `ask` ⇒ 那只孤儿在崩溃收敛读面缺席(无身份可列);丢 `settle` ⇒ 该组按 pending 臂收(审批门在执行之前 ⇒ 若这条 settle 其实是 `allowed` 且工具已执行,`resumeSafe:true` 是错的 —— 与头注「双故障窗」同族);丢 `leg_closed` ⇒ decided 臂多收一行(保守方向);丢 `crash_converged` ⇒ 下一次 boot 重收(幂等);丢 `interrupted_by_shutdown` / `late_settle` ⇒ 成因注退回 `crashed_before_park` / 少一行取证。审计店不开门、不改任何审批结算 ⇒ F。修前:重放坏行只计入 `convergeCrashedAsks().corruptLines`(boot 日志一个数),**无逐条披露、无 tag**。",
|
|
296
|
+
},
|
|
297
|
+
"server.workflow-notify-journal.corrupt-record-skipped": {
|
|
298
|
+
cls: "F",
|
|
299
|
+
note: "S-977(`orchestration/workflow-notify-journal.ts`,local 车道 File notify journal 的 boot 重放;登记名 `workflow-notify-journal`):账本**中间**一行读不出形(`42` / 数组 / 缺 runId·scope / `acked` 非布尔 / 半截 JSON;撕尾不走本 tag)⇒ 跳过它继续。最坏后果按行种:丢 `pending` 行(`record` 写下的那条)⇒ 那条 run 不在恢复积压里,崩溃丢掉的终态通告 boot 恢复扫描不再重导(live 进程内通告与 `WorkflowStatus` 轮询地板不受影响);丢 `acked` 行 ⇒ 该 run 读回 pending(或前一份已知良好的条目),多一次幂等重投(接收方按 runId 去重)。账本不开门、不改任何 run 的执行或授权 ⇒ F。修前:坏行 / 缺键行**静默**(解析失败 `continue`、只判 `typeof runId === \"string\"`)。",
|
|
300
|
+
},
|
|
301
|
+
"server.workflow-agent-session-index.corrupt-record-skipped": {
|
|
302
|
+
cls: "F",
|
|
303
|
+
note: "S-977(`orchestration/workflow-agent-session-index.ts`,local 车道 workflow 出身 park 的 join 索引账本的 boot 重放;登记名 `workflow-agent-session-index`):账本**中间**一行读不出形(`42` / 数组 / 缺 `runId`·`rows` / 任一行成员缺身份位 / 半截 JSON;撕尾不走本 tag)⇒ **整行**跳过,那条 run 保留上一份已知良好的快照(不做「丢坏成员、留好成员」—— 那会铸出比真相少几条 park 的行集)。最坏后果 = 该 run 的 park 在索引里缺行 ⇒ `/decide` 找不到它、退回 legacy 腿(409 `conflict.resume_context_unavailable`):诚实 miss,不是错配 —— 赎回键由记录上的 `parks[]` 逐字比对,不读本索引。索引不开门 ⇒ F。修前:形不对的整行响亮(`workflow_agent_session_ledger_line_rejected`,同事件名保留)但**无 tag**,带换行的半截 JSON 静默 `continue`。",
|
|
304
|
+
},
|
|
305
|
+
"server.user-input-ledger.append-failed": {
|
|
306
|
+
cls: "F",
|
|
307
|
+
note: "S-928(`src/user-input-ledger.ts`):200 承诺**之后**的一次账本写失败 —— detail 的 `op=` 点名是哪一种:`settle`(腿在本进程收场)/ `withdraw`(引擎没收下这次 steer)/ `converge`(收敛器判孤儿)/ `announce`(通告已投出,记「告知过」)。放行的最坏后果逐个说清:settle / withdraw 漏写 ⇒ 这次受理在账上仍是 open —— 本进程下一次收敛扫描补结清(本进程写的、已不在活腿上 ⇒ leg_over);进程在那之前就崩了 ⇒ 下一次 boot 的收敛读 run 店判(S-969 L8 起两车道同一只判词:承载它的 run 正常翻面 / 已 park ⇒ leg_over 补结清、不告知;落款死亡判决或写者已死仍 running ⇒ crashed)—— 仍会多说一句的只剩两形:withdraw 漏写后那条腿崩了(一次没变成 200 的受理被告知一次);settle 漏写后本进程续跑了同一条 run、续跑腿崩了(run 行说的是同一 taskId 上**最新**那条腿)。方向都是多说,不漏;converge 漏写 ⇒ 留在 open,下一次扫描再判(延迟,不丢);announce 漏写 ⇒ 下一条 run 再说一遍(至多一次退化成至少一次)。都是**告知面**的偏差,不开任何门、不改任何 run 的执行 ⇒ F。计数非零 = 这台机器的账本写不动了(local:数据根盘满 / 只读;SQL:库不可达),steer 此刻多半也在回 503。",
|
|
308
|
+
},
|
|
309
|
+
"server.user-input-ledger.read-failed": {
|
|
310
|
+
cls: "F",
|
|
311
|
+
note: "S-928(`src/user-input-ledger.ts` / `http/routes/sessions.ts`):一次账本**读**失败 —— `op=converge`(收敛扫描读不出 open 受理 ⇒ 本轮不收敛,下一个 tick 再来)/ `op=judge`(判词要读 run 店 —— S-969 L8 起两车道都读;一趟内同一 taskId 只读一次,读失败 ⇒ 同 run 的每一行各记一笔、留在 open、下一次扫描再判)/ `op=announce`(新 run 首拍读不出本会话的待告知受理 ⇒ 这一拍不告知,run 照跑,下一条 run 再试)/ `op=session-view`(`GET /v1/sessions/:id` 的 additive 键 `userInputUndrained` 读不出 ⇒ 键缺席、会话视图照常 200)。放行的最坏后果 = 一次「你的输入因崩溃丢了」的告知被推迟,或会话视图上这一格读作零条;不阻断任何 run、不改任何回执。反方向(读失败就拒跑 run / 整只会话视图 500)是拿一个告知面的抖动去打掉主路径,方向更坏。",
|
|
312
|
+
},
|
|
313
|
+
"server.user-input-ledger.corrupt-record-skipped": {
|
|
314
|
+
cls: "F",
|
|
315
|
+
note: "S-928(`plugins/user-input-ledger-file.ts`,local 车道 File 孪生的 boot 重放;登记名 `user-input-ledger`):账本中间一行读不出形(`42` / 缺键 / 词表外的 `kind`·`input`·`how`·`cause` / 半截 JSON;撕尾不走本 tag)⇒ 跳过它继续。S-969 L6 起 SQL 孪生(`plugins/user-input-ledger-sql.ts` `readRows`)同一只 tag:三只读口取到的行上读不出的列(`input_kind` 词表外 —— 新版本写的输入族 / 手改;收敛行缺 `state_at_ms`)⇒ 逐行跳过、其余照交,`listOpen` 的续翻游标按原始行推进(修前整页抛:收敛断点不前进、同会话通告与读面一并失效);坏行留在表里 ⇒ 每读一次计一次,非零 = 表里有本版本读不懂的行。最坏后果按行种:丢 `accepted` ⇒ 那次受理若真随崩溃丢了,不会被告知(= 本账之前的状态);丢 `settled` ⇒ 该受理在账上仍 open,下一趟收敛按 run 行判(S-969 L8,两车道同一只判词):本进程写的 / run 行已正常收场、已 park、易手 ⇒ 补结清、不告知;只有 run 行仍 `running` 且持有者是写者(local)、带死亡落款且持有者是写者、或行不在(local:File 店藏了读不出的行)⇒ 当成崩溃丢失告知一次;丢 `converged` ⇒ 下一次 boot 重收一次(幂等);丢 `announced` ⇒ 再告知一次;丢 `erased`(S-965,7.111.0 起的会话级墓碑;缺 `sessionId` 的那一行**不**读成「擦全部」)⇒ 被删会话的受理行在本进程的折叠里复现 —— 只有身份 + 时刻(无用户文本),读面 / 通告按 sessionId 取,同一个 id 被重新登记时新住客可见上一位的 `crashed` 告知(= 7.110.x 无级联时的状态);撕尾的 `erased` 不在此列:墓碑写在会话本体删除**之前**,撕掉 ⇒ DELETE 没有成功、会话还在,幂等重试重写。全在告知面,不开门 ⇒ F。修前不存在(本账本 S-928 新立)。",
|
|
316
|
+
},
|
|
293
317
|
"server.outbound.connect-observer-threw": {
|
|
294
318
|
cls: "F",
|
|
295
319
|
note: "S-925(`outbound-dispatcher.ts` 的 undici 诊断通道订阅者:`undici:request:create` / `:headers` / `:error`):一只订阅回调抛了。通道在发布方的调用栈里同步回调订阅者,抛出去在 Node 上是未捕获异常(整进程倒),所以每只回调整体 try、落本 tag。最坏后果 = 这一条请求的建连失败没被记下:少一行 `outbound_connect_failed`、诊断段 `outbound.lastConnectFailure` 不更新、那次模型调用不铸 `server.model_unreachable` 用户通告 —— 请求本身、它的错误、run 的终态一个字节不受影响(观察只读)。计数恒零是证据;非零 = undici 通道载荷的形变了,去看 detail 的 site。",
|
|
@@ -215,7 +215,7 @@ export function createMetrics() {
|
|
|
215
215
|
m.counter("http_requests_total", "HTTP requests by route/method/status");
|
|
216
216
|
m.histogram("http_request_duration_seconds", "HTTP request duration");
|
|
217
217
|
m.counter("rate_limited_total", "Requests rejected by the rate limiter");
|
|
218
|
-
m.counter("brain_echo_dropped_total", "Provider echo fields (response request-id header / reported model name) the engine's brain dropped at write time for failing the echo shape (visible ASCII, 1-128 chars), by field (requestId|responseModel); non-zero = the endpoint or a gateway in front of it echoes out-of-shape bytes");
|
|
218
|
+
m.counter("brain_echo_dropped_total", "Provider echo fields (response request-id header / reported model name) the engine's brain dropped at write time for failing the echo shape (visible ASCII, 1-128 chars), by field (requestId|responseModel|apiErrorCode — the last is the provider's own refusal word on a 4xx, core 7.38.0); non-zero = the endpoint or a gateway in front of it echoes out-of-shape bytes");
|
|
219
219
|
m.counter("mcp_probe_total", `No-run MCP status face outcomes by outcome (${MCP_PROBE_OUTCOMES.join("/")} — see src/capabilities/mcp-probe.ts MCP_PROBE_OUTCOMES; rate_limited has TWO causes: the per-identity fixed window AND the replica-wide in-flight cap, which can refuse caller B because of caller A)`);
|
|
220
220
|
m.counter("tasks_total", "Finished tasks by terminal status");
|
|
221
221
|
m.counter("task_tokens_total", "Total model tokens consumed by tasks");
|
|
@@ -265,6 +265,7 @@ export function createMetrics() {
|
|
|
265
265
|
m.counter("images_omitted_total", "Tasks whose images will be degraded to text placeholders (no-vision model), by model (S6)");
|
|
266
266
|
m.counter("runs_reaped_total", "Run rows flipped by the background reapers (S7), by kind (stale/suspended/expired_checkpoint)");
|
|
267
267
|
m.counter("checkpoints_reaped_total", "Durable checkpoints expired by the backstop reaper (S7)");
|
|
268
|
+
m.counter("user_input_converged_total", "User-input acceptances (S-928 ledger) converged to crashed by the maintenance tick (the boot pass reports its count on the user_input_ledger_crash_converged warn line instead): steers accepted whose writer process ended before the run settled");
|
|
268
269
|
m.counter("bakes_reaped_total", "Image bakes failed by the stale-lease reaper (S7)");
|
|
269
270
|
m.counter("counter_flush_failed_total", "Write-behind counter flush/refresh failures (S9), by table and kind");
|
|
270
271
|
m.gauge("counter_flush_fail_streak", "Current consecutive write-behind flush/refresh failures (S9), by table and kind");
|
|
@@ -176,6 +176,11 @@ const STRANDED_ANSWER_TREATMENT = {
|
|
|
176
176
|
deliveryId: "verbatim",
|
|
177
177
|
toolCallId: "provider-join-key",
|
|
178
178
|
};
|
|
179
|
+
const TERMINAL_FAILURE_DETAIL_TREATMENT = {
|
|
180
|
+
kind: "verbatim",
|
|
181
|
+
status: "verbatim",
|
|
182
|
+
code: "provider-echo",
|
|
183
|
+
};
|
|
179
184
|
const PROVIDER_ECHO_SHAPERS = {
|
|
180
185
|
responseModel: (v) => (admitProviderEcho(v, "responseModel") ? v : undefined),
|
|
181
186
|
apiFailure: (v) => shapeEchoMembers(v, API_FAILURE_TREATMENT, "apiFailure"),
|
|
@@ -238,6 +243,11 @@ export function redactTerminalResult(result) {
|
|
|
238
243
|
const g = gateOf(np);
|
|
239
244
|
if (g?.changed === true)
|
|
240
245
|
nt = { ...base, nestedPause: { ...np, gate: g.gate } };
|
|
246
|
+
const cur = nt ?? base;
|
|
247
|
+
const detail = cur.detail;
|
|
248
|
+
const shaped = shapeEchoMembers(detail, TERMINAL_FAILURE_DETAIL_TREATMENT, "terminal.detail");
|
|
249
|
+
if (shaped !== detail)
|
|
250
|
+
nt = { ...cur, detail: shaped };
|
|
241
251
|
}
|
|
242
252
|
if (nt !== undefined) {
|
|
243
253
|
out.terminal = nt;
|
package/dist/operator-ask.d.ts
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
* (`dist/core/gate-fold.js`),那是 `allowLayerMayClear` 的另一条独立豁免;本仓再盖一次是第二个写者。
|
|
34
34
|
* · **问答工具**(`deployment-governance.ts` 的 `AskUserQuestion`)—— core 的谓词把保留问答工具单列豁免。
|
|
35
35
|
*/
|
|
36
|
-
import type
|
|
36
|
+
import { type PermissionResult, type ToolPolicy } from "@sema-agent/core";
|
|
37
37
|
/** 本文件两只铸点的返回形 = core `PermissionResult` 的 ask 臂(窄型:`authoredBy` 只长在这一臂上)。 */
|
|
38
38
|
export type OperatorAskResult = Extract<PermissionResult, {
|
|
39
39
|
action: "ask";
|
|
@@ -63,12 +63,13 @@ export declare function operatorAsk(rule: string, message: string): OperatorAskR
|
|
|
63
63
|
*/
|
|
64
64
|
export declare function operatorAlwaysHumanAsk(rule: string, message: string): OperatorAskResult;
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
66
|
+
* 🔴 **S-958 —— 治理层的叶策略包装**:`leaf` 产的每一只 ask 盖部署出身(经 {@link withDeploymentAuthorship}),allow / deny 原物交回;
|
|
67
|
+
* 成员(`projection` / `pathDenyRules` / `writeTargetGuard` / `nameSets`)经 core `wrapToolPolicy` 随行(契约「包装至少拒内层所拒」:
|
|
68
|
+
* 只加一位、从不放宽)。同步叶保持同步返回;叶抛照抛(fail-closed 的属主仍是叶)。
|
|
69
|
+
*
|
|
70
|
+
* **只收叶**:治理层(`runtime-governance.ts` `applyRuntimeGovernance`)对每一片叶各盖一次,再交 core `combinePolicies` 折**一次**
|
|
71
|
+
* (折叠是最外层,其输出原样交回 —— core 把落选 ask 的类(含本位带出的 `operator_forced`)写进 `foldedAskClasses` 并担保那只对象)。
|
|
72
|
+
* 把一只折叠交给本函数 = 在折叠的输出上展开 —— 那正是 S-958 修掉的病(7.39.0 起按名拒,响亮、fail-closed,不是静默)。
|
|
72
73
|
*/
|
|
73
|
-
export declare function
|
|
74
|
+
export declare function createDeploymentAuthoredPolicy(leaf: ToolPolicy): ToolPolicy;
|
|
74
75
|
//# sourceMappingURL=operator-ask.d.ts.map
|
package/dist/operator-ask.js
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
|
+
import { wrapToolPolicy } from "@sema-agent/core";
|
|
1
2
|
export function operatorAsk(rule, message) {
|
|
2
3
|
return { action: "ask", message, decisionReason: "rule", matchedAskRule: rule };
|
|
3
4
|
}
|
|
4
5
|
export function operatorAlwaysHumanAsk(rule, message) {
|
|
5
6
|
return withDeploymentAuthorship(operatorAsk(rule, message));
|
|
6
7
|
}
|
|
7
|
-
|
|
8
|
+
function withDeploymentAuthorship(ask) {
|
|
8
9
|
return { ...ask, authoredBy: "deployment" };
|
|
9
10
|
}
|
|
11
|
+
export function createDeploymentAuthoredPolicy(leaf) {
|
|
12
|
+
const authored = (d) => (d.action === "ask" ? withDeploymentAuthorship(d) : d);
|
|
13
|
+
return wrapToolPolicy(leaf, (req, signal) => {
|
|
14
|
+
const decision = leaf.check(req, signal);
|
|
15
|
+
return decision instanceof Promise ? decision.then(authored) : authored(decision);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
10
18
|
//# sourceMappingURL=operator-ask.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type WorkflowRun } from "@sema-agent/core";
|
|
2
|
+
import type { Logger } from "../observability/logger.js";
|
|
2
3
|
/** 一条索引行:**子会话 id 是主键**(会话 id 全局唯一 ⇒ 读口天然是单行精确查,无分页假阴性)。 */
|
|
3
4
|
export interface WorkflowAgentSessionRow {
|
|
4
5
|
/** parked 的那只 workflow 子代自己的会话 id(= `WorkflowAgentRun.sessionId` = 那条 checkpoint 行的 `sessionId`;
|
|
@@ -101,38 +102,26 @@ export declare function readAgentSessionLedgerLine(v: unknown): WorkflowAgentSes
|
|
|
101
102
|
* 单机车道的索引(`FileWorkflowRunStore` / `InMemoryWorkflowRunStore` 的同室孪生)。
|
|
102
103
|
*
|
|
103
104
|
* `root` 在场 ⇒ 追加式 JSONL 账本(与姊妹 {@link import("./workflow-notify-journal.js").FileWorkflowNotifyJournalStore}
|
|
104
|
-
* 逐字同形:0o700 目录 + 0o600
|
|
105
|
+
* 逐字同形:0o700 目录 + 0o600 账本、按行重放),重启后 park 仍可 join;缺席 ⇒ 纯内存
|
|
105
106
|
* (`WORKFLOW_RUN_STORE=memory` 的显式易失车道,与它的 run 店同寿命)。
|
|
106
107
|
*
|
|
107
108
|
* 账本的一行 = **一次 `syncRun` 的整份行集**(按 runId 后写覆盖),所以重放即收敛,不需要压缩腿。
|
|
109
|
+
*
|
|
110
|
+
* 追加 / 重放走本仓 JSONL 店的**共用口**(S-977;此前 ctor 里自写 `truncateTornTail` + 手写 `split("\n") + JSON.parse` 重放):
|
|
111
|
+
* 追加 = core `AppendLog`(惰性打开、撕尾在**打开与每次追加前**修 —— 修前只在 boot 修,运行期半写后的下一次成功写仍会被粘进残尾),
|
|
112
|
+
* 重放 = `replayJsonlSkipping`(登记座 `workflow-agent-session-index`:中间坏行 —— 含半截 JSON 与形不对的整行 —— 跳过 + 逐条披露 + F tag;
|
|
113
|
+
* 撕尾不计、不披露)。盘上字节 / 0o600 账本 / 0o700 目录与修前逐字同,升级零迁移。
|
|
108
114
|
*/
|
|
109
115
|
export declare class LocalWorkflowAgentSessionIndex implements WorkflowAgentSessionIndex {
|
|
110
116
|
private readonly fsyncEnabled;
|
|
111
|
-
/** 坏账本行的**响亮**出口(可选;缺席 ⇒ 那一行照样不入索引,只是没人听见)。形与 workflow 店族的
|
|
112
|
-
* `onWarn` 逐字同源(`workflow-completion-inbox.ts` 的那一只),不另立第二种披露通道。 */
|
|
113
|
-
private readonly onWarn?;
|
|
114
117
|
private readonly ledgerPath;
|
|
115
118
|
private readonly byRun;
|
|
116
119
|
private readonly bySession;
|
|
117
|
-
private
|
|
120
|
+
private log;
|
|
118
121
|
constructor(root?: string, fsyncEnabled?: boolean,
|
|
119
|
-
/** 坏账本行的**响亮**出口(可选;缺席 ⇒
|
|
120
|
-
*
|
|
121
|
-
onWarn?:
|
|
122
|
-
/**
|
|
123
|
-
* 打开账本时把**撕裂尾**(崩在 append 中途、末尾没有换行的那截字节)截回最后一个完整行边界。
|
|
124
|
-
*
|
|
125
|
-
* 🔴 为什么必须在**任何 append 之前**做(codex 修复验证轮 [medium],故障注入复现):残尾留在盘上时,
|
|
126
|
-
* 下一次 `syncRun` 会紧贴着它落字节 ⇒ `<残尾><新记录>` 在 replay 里是**一行**,JSON 解析失败被整行丢掉。
|
|
127
|
-
* 丢的**不是**那截未提交的残尾(丢它是对的),而是紧随其后那次**成功重试**写下的行 —— 于是「写失败了
|
|
128
|
-
* 下一次写会重试救回来」这条本模块与 `syncAgentSessionIndex` 都在依赖的性质当场失效,park 索引静默消失。
|
|
129
|
-
* 截尾之后 append 永远从一个干净的行边界开始,重试才真的救得回来。
|
|
130
|
-
*
|
|
131
|
-
* 截掉的只可能是**未提交**的字节(一条记录的耐久性以它那个换行为界,与 notify journal 同一条崩溃序),
|
|
132
|
-
* 所以这里不是丢数据;但它是一次真实的崩溃留痕,**响亮**一次([ref]:安全轴不许静默)。
|
|
133
|
-
*/
|
|
134
|
-
private truncateTornTail;
|
|
135
|
-
private replay;
|
|
122
|
+
/** 坏账本行的**响亮**出口(可选;缺席 ⇒ 重放属主退回 `console.warn`,不退回沉默)。形与日志座 `Logger["warn"]` 逐字同源,
|
|
123
|
+
* 不另立第二种披露通道。 */
|
|
124
|
+
onWarn?: Logger["warn"]);
|
|
136
125
|
/** 内存两张表的收敛(重放与写路径共用一份逻辑)。 */
|
|
137
126
|
private apply;
|
|
138
127
|
syncRun(runId: string, rows: readonly WorkflowAgentSessionRow[]): Promise<void>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { existsSync, fsyncSync, mkdirSync, openSync, readFileSync, truncateSync, writeSync } from "node:fs";
|
|
2
1
|
import { join } from "node:path";
|
|
2
|
+
import { AppendLog, ensureDir } from "@sema-agent/core";
|
|
3
|
+
import { replayJsonlSkipping } from "../observability/corrupt-read-seat.js";
|
|
3
4
|
const nonEmptyString = (v) => typeof v === "string" && v !== "";
|
|
4
5
|
export function readRunParks(run) {
|
|
5
6
|
if (typeof run !== "object" || run === null)
|
|
@@ -72,64 +73,27 @@ function readAgentSessionRow(v) {
|
|
|
72
73
|
return undefined;
|
|
73
74
|
return { sessionId, runId, agentKey, scope };
|
|
74
75
|
}
|
|
76
|
+
const LEDGER_LINE_SHAPE = {
|
|
77
|
+
what: "a workflow agent-session index line {runId, rows[]}",
|
|
78
|
+
read: readAgentSessionLedgerLine,
|
|
79
|
+
};
|
|
75
80
|
export class LocalWorkflowAgentSessionIndex {
|
|
76
81
|
fsyncEnabled;
|
|
77
|
-
onWarn;
|
|
78
82
|
ledgerPath;
|
|
79
83
|
byRun = new Map();
|
|
80
84
|
bySession = new Map();
|
|
81
|
-
|
|
85
|
+
log;
|
|
82
86
|
constructor(root, fsyncEnabled = true, onWarn) {
|
|
83
87
|
this.fsyncEnabled = fsyncEnabled;
|
|
84
|
-
this.onWarn = onWarn;
|
|
85
88
|
if (root === undefined) {
|
|
86
89
|
this.ledgerPath = undefined;
|
|
87
90
|
return;
|
|
88
91
|
}
|
|
89
92
|
const dir = join(root, "workflow-agent-sessions");
|
|
90
|
-
|
|
93
|
+
ensureDir(dir);
|
|
91
94
|
this.ledgerPath = join(dir, "index.jsonl");
|
|
92
|
-
this.
|
|
93
|
-
this.replay();
|
|
94
|
-
}
|
|
95
|
-
truncateTornTail() {
|
|
96
|
-
const path = this.ledgerPath;
|
|
97
|
-
if (path === undefined || !existsSync(path))
|
|
98
|
-
return;
|
|
99
|
-
const bytes = readFileSync(path);
|
|
100
|
-
if (bytes.length === 0 || bytes[bytes.length - 1] === 0x0a)
|
|
101
|
-
return;
|
|
102
|
-
const keep = bytes.lastIndexOf(0x0a) + 1;
|
|
103
|
-
truncateSync(path, keep);
|
|
104
|
-
this.onWarn?.("workflow_agent_session_ledger_torn_tail_truncated", {
|
|
105
|
-
path,
|
|
106
|
-
droppedBytes: bytes.length - keep,
|
|
107
|
-
note: "the last append never finished (no trailing newline) — those uncommitted bytes were cut back to the last complete line so the NEXT append starts on a clean boundary. Without the cut, the retry that follows would be glued onto the broken suffix and dropped as one unparsable line, i.e. the retry — not the torn write — is what would be lost.",
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
replay() {
|
|
111
|
-
if (this.ledgerPath === undefined || !existsSync(this.ledgerPath))
|
|
112
|
-
return;
|
|
113
|
-
for (const line of readFileSync(this.ledgerPath, "utf8").split("\n")) {
|
|
114
|
-
if (!line.trim())
|
|
115
|
-
continue;
|
|
116
|
-
let rec;
|
|
117
|
-
try {
|
|
118
|
-
rec = JSON.parse(line);
|
|
119
|
-
}
|
|
120
|
-
catch {
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
const entry = readAgentSessionLedgerLine(rec);
|
|
124
|
-
if (entry === undefined) {
|
|
125
|
-
this.onWarn?.("workflow_agent_session_ledger_line_rejected", {
|
|
126
|
-
path: this.ledgerPath,
|
|
127
|
-
note: "a committed ledger line did not read back as {runId, rows[]} — that run keeps its previous snapshot; a /decide for a park it should have indexed falls back to the legacy lane (honest miss, never a mis-route)",
|
|
128
|
-
});
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
95
|
+
for (const entry of replayJsonlSkipping(this.ledgerPath, "workflow-agent-session-index", LEDGER_LINE_SHAPE, onWarn).records)
|
|
131
96
|
this.apply(entry.runId, entry.rows);
|
|
132
|
-
}
|
|
133
97
|
}
|
|
134
98
|
apply(runId, rows) {
|
|
135
99
|
for (const old of this.byRun.get(runId) ?? []) {
|
|
@@ -145,11 +109,8 @@ export class LocalWorkflowAgentSessionIndex {
|
|
|
145
109
|
}
|
|
146
110
|
async syncRun(runId, rows) {
|
|
147
111
|
if (this.ledgerPath !== undefined) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
writeSync(this.fd, `${JSON.stringify({ runId, rows })}\n`);
|
|
151
|
-
if (this.fsyncEnabled)
|
|
152
|
-
fsyncSync(this.fd);
|
|
112
|
+
this.log ??= new AppendLog(this.ledgerPath);
|
|
113
|
+
this.log.append({ runId, rows }, this.fsyncEnabled);
|
|
153
114
|
}
|
|
154
115
|
this.apply(runId, rows);
|
|
155
116
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type WorkflowRun, type WorkflowRunStore, type WorkflowRunStatus, type WorkflowRunSummary } from "@sema-agent/core";
|
|
2
2
|
import { type FleetEventBus } from "../fleet/fleet-bus.js";
|
|
3
3
|
import { type WorkflowAgentSessionIndex } from "./workflow-agent-session-index.js";
|
|
4
|
+
import type { Logger } from "../observability/logger.js";
|
|
4
5
|
/** A workflow completion notification's terminal payload — the bounded, redacted shape core's notifier seam
|
|
5
6
|
* carries, re-derivable from a durable {@link import("@sema-agent/core").WorkflowRun} on the recovery path. */
|
|
6
7
|
export interface WorkflowCompletionPayload {
|
|
@@ -201,6 +202,11 @@ export declare class WorkflowNotifyGate {
|
|
|
201
202
|
* appends, atomic single-line records. Single-instance / TOC-local: a cross-replica TiDB/PG port is a follow-on
|
|
202
203
|
* (the recovery sweep + gate are backend-agnostic).
|
|
203
204
|
*
|
|
205
|
+
* 追加 / 重放走本仓 JSONL 店的**共用口**(S-977;此前是第二 / 第三份私有实现):追加 = core `AppendLog`(惰性打开;单条 `writeSync`
|
|
206
|
+
* 循环写满、失败的写截回开写处;**撕尾在打开与每次追加前修**,所以崩在一次 append 中途之后的下一条成功写不会被粘进残尾),
|
|
207
|
+
* 重放 = `replayJsonlSkipping`(登记座 `workflow-notify-journal`:中间坏行跳过 + 逐条披露 + F tag;撕尾不计 —— 它是崩溃的正常落盘形;
|
|
208
|
+
* 末行 JSON 完整但缺换行的认下、下一次追加补换行)。盘上字节 / 0o600 账本 / 0o700 目录与修前逐字同,升级零迁移。
|
|
209
|
+
*
|
|
204
210
|
* The ledger only ever grows by one line per `record`/`ack`; an acked entry's line is not deleted (the replay
|
|
205
211
|
* folds it), so retention is a future compaction concern — the backlog `listPending` walks is bounded by
|
|
206
212
|
* IN-FLIGHT + un-swept runs, not history.
|
|
@@ -209,12 +215,9 @@ export declare class FileWorkflowNotifyJournalStore implements WorkflowNotifyJou
|
|
|
209
215
|
private readonly fsyncEnabled;
|
|
210
216
|
private readonly ledgerPath;
|
|
211
217
|
private readonly entries;
|
|
212
|
-
private
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
* (a crash mid-append) by dropping an unparsable trailing record — the same crash-safety FileWorkflowRunStore
|
|
216
|
-
* relies on (a partial append is simply not yet committed state). */
|
|
217
|
-
private replay;
|
|
218
|
+
private log;
|
|
219
|
+
/** @param warn 坏账本行的披露座(缺席 ⇒ 重放属主退回 `console.warn`,不退回沉默)。 */
|
|
220
|
+
constructor(root: string, fsyncEnabled?: boolean, warn?: Logger["warn"]);
|
|
218
221
|
/** Commit one entry: append the JSON line (fsync) THEN flip the in-memory map (crash-safe ordering — the
|
|
219
222
|
* durable record lands before the in-memory state the caller observes). */
|
|
220
223
|
private commit;
|
|
@@ -228,7 +231,7 @@ export declare class FileWorkflowNotifyJournalStore implements WorkflowNotifyJou
|
|
|
228
231
|
ack(runId: string, ackedAt: number): Promise<void>;
|
|
229
232
|
listPending(): Promise<WorkflowNotifyJournalEntry[]>;
|
|
230
233
|
get(runId: string): Promise<WorkflowNotifyJournalEntry | null>;
|
|
231
|
-
/** Release the append handle
|
|
234
|
+
/** Release the append handle — called on shutdown. core `AppendLog.close()` swallows its own `closeSync` error (never throws). */
|
|
232
235
|
close(): void;
|
|
233
236
|
/** Test/inspection: total entries (acked + pending). */
|
|
234
237
|
get size(): number;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { mkdirSync, readFileSync, existsSync, openSync, writeSync, fsyncSync, closeSync } from "node:fs";
|
|
2
1
|
import { join } from "node:path";
|
|
2
|
+
import { AppendLog, ensureDir } from "@sema-agent/core";
|
|
3
3
|
import { buildFleetWorkflowRow } from "../fleet/fleet-bus.js";
|
|
4
4
|
import { isTerminalWorkflowRowStatus, seedTerminalWorkflowRow } from "../fleet/fleet-terminal-window.js";
|
|
5
5
|
import { buildParkedAgentSessionRows, sameAgentSessionRows } from "./workflow-agent-session-index.js";
|
|
6
6
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
7
|
+
import { replayJsonlSkipping } from "../observability/corrupt-read-seat.js";
|
|
7
8
|
const SUMMARY_MAX = 500;
|
|
8
9
|
export function recoverySummary(status, error) {
|
|
9
10
|
if (status === "completed")
|
|
@@ -162,42 +163,31 @@ export class WorkflowNotifyGate {
|
|
|
162
163
|
return { scanned: pending.length, redelivered, stillRunning, abandoned };
|
|
163
164
|
}
|
|
164
165
|
}
|
|
166
|
+
const JOURNAL_ENTRY_SHAPE = {
|
|
167
|
+
what: "a notify-journal entry {runId: string, scope: string, acked: boolean}",
|
|
168
|
+
read(record) {
|
|
169
|
+
const { runId, scope, acked } = record;
|
|
170
|
+
if (typeof runId !== "string" || typeof scope !== "string" || typeof acked !== "boolean")
|
|
171
|
+
return undefined;
|
|
172
|
+
return record;
|
|
173
|
+
},
|
|
174
|
+
};
|
|
165
175
|
export class FileWorkflowNotifyJournalStore {
|
|
166
176
|
fsyncEnabled;
|
|
167
177
|
ledgerPath;
|
|
168
178
|
entries = new Map();
|
|
169
|
-
|
|
170
|
-
constructor(root, fsyncEnabled = true) {
|
|
179
|
+
log;
|
|
180
|
+
constructor(root, fsyncEnabled = true, warn) {
|
|
171
181
|
this.fsyncEnabled = fsyncEnabled;
|
|
172
182
|
const dir = join(root, "workflow-notify");
|
|
173
|
-
|
|
183
|
+
ensureDir(dir);
|
|
174
184
|
this.ledgerPath = join(dir, "journal.jsonl");
|
|
175
|
-
this.
|
|
176
|
-
|
|
177
|
-
replay() {
|
|
178
|
-
if (!existsSync(this.ledgerPath))
|
|
179
|
-
return;
|
|
180
|
-
const text = readFileSync(this.ledgerPath, "utf8");
|
|
181
|
-
for (const line of text.split("\n")) {
|
|
182
|
-
if (!line.trim())
|
|
183
|
-
continue;
|
|
184
|
-
let ev;
|
|
185
|
-
try {
|
|
186
|
-
ev = JSON.parse(line);
|
|
187
|
-
}
|
|
188
|
-
catch {
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
191
|
-
if (ev && typeof ev.runId === "string")
|
|
192
|
-
this.entries.set(ev.runId, ev);
|
|
193
|
-
}
|
|
185
|
+
for (const ev of replayJsonlSkipping(this.ledgerPath, "workflow-notify-journal", JOURNAL_ENTRY_SHAPE, warn).records)
|
|
186
|
+
this.entries.set(ev.runId, ev);
|
|
194
187
|
}
|
|
195
188
|
commit(entry) {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
writeSync(this.fd, `${JSON.stringify(entry)}\n`);
|
|
199
|
-
if (this.fsyncEnabled)
|
|
200
|
-
fsyncSync(this.fd);
|
|
189
|
+
this.log ??= new AppendLog(this.ledgerPath);
|
|
190
|
+
this.log.append(entry, this.fsyncEnabled);
|
|
201
191
|
this.entries.set(entry.runId, entry);
|
|
202
192
|
}
|
|
203
193
|
async record(input) {
|
|
@@ -226,14 +216,8 @@ export class FileWorkflowNotifyJournalStore {
|
|
|
226
216
|
return this.entries.get(runId) ?? null;
|
|
227
217
|
}
|
|
228
218
|
close() {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
closeSync(this.fd);
|
|
232
|
-
}
|
|
233
|
-
catch {
|
|
234
|
-
}
|
|
235
|
-
this.fd = undefined;
|
|
236
|
-
}
|
|
219
|
+
this.log?.close();
|
|
220
|
+
this.log = undefined;
|
|
237
221
|
}
|
|
238
222
|
get size() {
|
|
239
223
|
return this.entries.size;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type TaskOutcome as CoreTaskOutcome } from "@sema-agent/core";
|
|
2
2
|
/** The one write verb every outcome sink (TiDB/Pg ledger adapters + this File twin) exposes to main.ts,
|
|
3
3
|
* plus the OPTIONAL read aggregate backing `GET /v1/outcomes` (P2) — present on the SQL ledgers (their
|
|
4
4
|
* existing per-(signature, model) summary); absent here (the JSONL file IS the local dataset — a dashboard
|
|
@@ -18,7 +18,10 @@ export interface OutcomeSink {
|
|
|
18
18
|
}
|
|
19
19
|
export declare class FileOutcomeSink implements OutcomeSink {
|
|
20
20
|
private readonly path;
|
|
21
|
+
private log;
|
|
21
22
|
constructor(root: string);
|
|
22
23
|
recordCore(o: CoreTaskOutcome): Promise<void>;
|
|
24
|
+
/** 收追加口(`StoreBackend.close` 的优雅关闭腿;之后再写会重开)。core `AppendLog.close()` 自己吞 `closeSync` 的错,不抛。 */
|
|
25
|
+
dispose(): void;
|
|
23
26
|
}
|
|
24
27
|
//# sourceMappingURL=file-outcome-sink.d.ts.map
|
|
@@ -1,14 +1,20 @@
|
|
|
1
|
-
import { mkdirSync, appendFileSync } from "node:fs";
|
|
2
1
|
import { join } from "node:path";
|
|
2
|
+
import { AppendLog, ensureDir } from "@sema-agent/core";
|
|
3
3
|
export class FileOutcomeSink {
|
|
4
4
|
path;
|
|
5
|
+
log;
|
|
5
6
|
constructor(root) {
|
|
6
7
|
const dir = join(root, "outcomes");
|
|
7
|
-
|
|
8
|
+
ensureDir(dir);
|
|
8
9
|
this.path = join(dir, "outcomes.jsonl");
|
|
9
10
|
}
|
|
10
11
|
async recordCore(o) {
|
|
11
|
-
|
|
12
|
+
this.log ??= new AppendLog(this.path);
|
|
13
|
+
this.log.append({ at: Date.now(), ...o }, false);
|
|
14
|
+
}
|
|
15
|
+
dispose() {
|
|
16
|
+
this.log?.close();
|
|
17
|
+
this.log = undefined;
|
|
12
18
|
}
|
|
13
19
|
}
|
|
14
20
|
//# sourceMappingURL=file-outcome-sink.js.map
|
|
@@ -7,7 +7,7 @@ import { randomBytes } from "node:crypto";
|
|
|
7
7
|
import { AppendLog, atomicWriteFile, ensureDir, isProcessLive, processFingerprint, sanitizePathComponent, writeThenLink } from "@sema-agent/core";
|
|
8
8
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
9
9
|
import { replayJsonlSkipping } from "../observability/corrupt-read-seat.js";
|
|
10
|
-
import { StoreBootRefusalError } from "./store-contracts.js";
|
|
10
|
+
import { RUN_ORPHANED_ERROR, RUN_STALLED_ERROR, StoreBootRefusalError, buildRunOrphanedError } from "./store-contracts.js";
|
|
11
11
|
import { notifyRunTerminal } from "../observability/run-terminal.js";
|
|
12
12
|
import { migrateRetiredAskOrigin } from "./one-time-migrations.js";
|
|
13
13
|
const ownerEq = (a, b) => (a ?? null) === (b ?? null);
|
|
@@ -592,7 +592,7 @@ export class FileRunStore {
|
|
|
592
592
|
}
|
|
593
593
|
async reapStale(olderThanMs) {
|
|
594
594
|
const cutoff = Date.now() - olderThanMs;
|
|
595
|
-
return this.reapRunning((r) => r.updatedAt.getTime() < cutoff,
|
|
595
|
+
return this.reapRunning((r) => r.updatedAt.getTime() < cutoff, RUN_STALLED_ERROR);
|
|
596
596
|
}
|
|
597
597
|
async reclaimOrphanedAtBoot(isResumable) {
|
|
598
598
|
const orphans = [];
|
|
@@ -603,7 +603,7 @@ export class FileRunStore {
|
|
|
603
603
|
for (const r of orphans) {
|
|
604
604
|
r.status = "failed";
|
|
605
605
|
r.result = null;
|
|
606
|
-
r.error =
|
|
606
|
+
r.error = RUN_ORPHANED_ERROR;
|
|
607
607
|
r.updatedAt = new Date();
|
|
608
608
|
this.persist(r);
|
|
609
609
|
}
|
|
@@ -808,7 +808,7 @@ export class FileRunStore {
|
|
|
808
808
|
pidStillRunning: owner !== undefined && isProcessLive({ pid: owner.pid }),
|
|
809
809
|
note: "the claim's owner process is provably not the one that wrote it (fingerprint falsified, or the pid is gone) and the row has nothing left to resume — taking the claim over",
|
|
810
810
|
});
|
|
811
|
-
return { kind: "stale", reason:
|
|
811
|
+
return { kind: "stale", reason: buildRunOrphanedError(who), snapshot };
|
|
812
812
|
}
|
|
813
813
|
takeOverClaim(sessionId, reason, snapshot) {
|
|
814
814
|
const nowClaim = readClaimRecord(singleRecordOf(this.readRecords(this.activePath(sessionId), CLAIM_RECORD_SHAPE)), sessionId);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type UsageRow } from "../usage-analytics.js";
|
|
2
2
|
import type { TaskStatus } from "@sema-agent/core";
|
|
3
|
-
import type
|
|
3
|
+
import { type RunRecord, type SessionSummary, type RunEvent, type PersistedTaskResult, type LatestRunRef } from "./store-contracts.js";
|
|
4
4
|
import type { LedgerEventType } from "../trace/ledger-events.js";
|
|
5
5
|
/**
|
|
6
6
|
* [ref] P0 — the local lane's stand-in for the SQL twins' checkpoint-table JOIN. The file/memory run stores
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { redactErrorMessage, redactLedgerEventData, sealRunRowResult } from "../observability/run-terminal.js";
|
|
2
2
|
import { outwardPlane } from "../terminal.js";
|
|
3
3
|
import { projectUsageStats, USAGE_SCAN_LIMIT } from "../usage-analytics.js";
|
|
4
|
+
import { RUN_STALLED_ERROR } from "./store-contracts.js";
|
|
4
5
|
import { notifyRunTerminal } from "../observability/run-terminal.js";
|
|
5
6
|
const ownerEq = (a, b) => (a ?? null) === (b ?? null);
|
|
6
7
|
export class MemoryRunStore {
|
|
@@ -298,7 +299,7 @@ export class MemoryRunStore {
|
|
|
298
299
|
if (r.status === "running" && r.updatedAt.getTime() < cutoff) {
|
|
299
300
|
r.status = "failed";
|
|
300
301
|
r.result = null;
|
|
301
|
-
r.error =
|
|
302
|
+
r.error = RUN_STALLED_ERROR;
|
|
302
303
|
r.updatedAt = new Date();
|
|
303
304
|
reaped += 1;
|
|
304
305
|
}
|
package/dist/plugins/pg-pool.js
CHANGED
|
@@ -126,6 +126,21 @@ export const PG_SCHEMA_STATEMENTS = [
|
|
|
126
126
|
consumed_at_ms BIGINT NOT NULL,
|
|
127
127
|
expires_at_ms BIGINT NOT NULL,
|
|
128
128
|
PRIMARY KEY (session_id, nonce)
|
|
129
|
+
)`,
|
|
130
|
+
`CREATE TABLE IF NOT EXISTS user_input_acceptance (
|
|
131
|
+
accept_id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
132
|
+
input_kind VARCHAR(16) COLLATE "C" NOT NULL,
|
|
133
|
+
message_id VARCHAR(128) COLLATE "C" NOT NULL,
|
|
134
|
+
session_id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
135
|
+
task_id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
136
|
+
writer VARCHAR(64) COLLATE "C" NOT NULL,
|
|
137
|
+
accepted_at_ms BIGINT NOT NULL,
|
|
138
|
+
state VARCHAR(16) COLLATE "C" NOT NULL,
|
|
139
|
+
state_at_ms BIGINT,
|
|
140
|
+
settle_how VARCHAR(16) COLLATE "C",
|
|
141
|
+
announced_at_ms BIGINT,
|
|
142
|
+
announced_task_id VARCHAR(64) COLLATE "C",
|
|
143
|
+
PRIMARY KEY (accept_id)
|
|
129
144
|
)`,
|
|
130
145
|
`CREATE TABLE IF NOT EXISTS sendfile_link (
|
|
131
146
|
id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* core 把破坏性级联整个下沉成 store 方法(`expireCheckpoints` / `deleteExpiredSessions` /
|
|
9
9
|
* `deleteOrphanToolResults`),并点名 server 的 session-purge 顺序契约(E21,`boot/session-faces.ts` 的
|
|
10
10
|
* `purgeSession` 协调器)为参考实现。那条顺序契约横跨**十余张表**(run 账本 / checkpoint / tool_result /
|
|
11
|
-
* anchor / exemption / policy / attachment / snapshot / workflow inbox / session_meta+event),而设计稿
|
|
11
|
+
* anchor / exemption / policy / attachment / 用户输入受理账本 / snapshot / workflow inbox / session_meta+event),而设计稿
|
|
12
12
|
* §6 的 F5 又要求「审计行与删除同一个 DB 事务」—— 一个事务 ⇒ 一条连接 ⇒ **一个属主**。
|
|
13
13
|
*
|
|
14
14
|
* ⇒ 布局裁定:本能力**不能**挂在 session / checkpoint / tool-result 任何一只店上(每只店只拥有自己的表,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mysqlDriver, pgDriver } from "./sql-driver.js";
|
|
2
2
|
import { TASK_LIST_ITEM_TABLE, TASK_LIST_META_TABLE, taskListKeyFor } from "./task-list-store-sql.js";
|
|
3
|
+
import { USER_INPUT_ACCEPTANCE_TABLE } from "./user-input-ledger-sql.js";
|
|
3
4
|
import { ensureIndexes } from "./ensure-index.js";
|
|
4
5
|
export const MANAGED_RETENTION = "managed";
|
|
5
6
|
export const UNMANAGED_RETENTION = "none";
|
|
@@ -382,6 +383,7 @@ export class SqlRetentionStore {
|
|
|
382
383
|
["session_policy", "session_id"],
|
|
383
384
|
["session_policy_rev_mark", "session_id"],
|
|
384
385
|
...(this.opts.taskAttachmentTable ? [["task_attachment", "session_id"]] : []),
|
|
386
|
+
[USER_INPUT_ACCEPTANCE_TABLE, "session_id"],
|
|
385
387
|
["file_history_tracked", "scope"],
|
|
386
388
|
["file_history_version", "scope"],
|
|
387
389
|
["file_history_boundary", "scope"],
|
|
@@ -2,7 +2,7 @@ import type { TaskStatus } from "@sema-agent/core";
|
|
|
2
2
|
import type { Pool as MySqlPool } from "mysql2/promise";
|
|
3
3
|
import type { Pool as PgPool } from "pg";
|
|
4
4
|
import { type UsageRow } from "../usage-analytics.js";
|
|
5
|
-
import type
|
|
5
|
+
import { type RunRecord, type SessionSummary, type RunEvent, type PersistedTaskResult, type LatestRunRef } from "./store-contracts.js";
|
|
6
6
|
import { type SqlDriver } from "./sql-driver.js";
|
|
7
7
|
import type { LedgerEventType } from "../trace/ledger-events.js";
|
|
8
8
|
export type { RunRecord, SessionSummary, RunEvent, LatestRunRef } from "./store-contracts.js";
|