@sema-agent/server 7.76.2 → 7.78.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/boot/runner-deps.js +3 -1
- package/dist/config-catalog.js +1 -0
- package/dist/config-center/read-warnings.d.ts +10 -1
- package/dist/config-center/read-warnings.js +5 -1
- package/dist/config-provider.js +2 -2
- package/dist/config-types.d.ts +13 -1
- package/dist/config.js +2 -1
- package/dist/fleet/subagent-tail-bus.d.ts +28 -2
- package/dist/fleet/subagent-tail-bus.js +24 -2
- package/dist/http/principal-gate.d.ts +19 -0
- package/dist/http/principal-gate.js +7 -0
- package/dist/http/routes/sessions.js +47 -6
- package/dist/http/routes/tasks.js +25 -11
- package/dist/http/routes/workflows.js +7 -1
- package/dist/http/server.js +18 -23
- package/dist/observability/fail-open.d.ts +8 -0
- package/dist/observability/fail-open.js +8 -0
- package/dist/observability/run-terminal-log.js +1 -0
- package/dist/orchestration/workflow-agent-session-index.d.ts +7 -3
- package/dist/parked-decide.d.ts +27 -12
- package/dist/parked-decide.js +2 -2
- package/dist/plugins/file-run-store.d.ts +6 -1
- package/dist/plugins/file-run-store.js +10 -0
- package/dist/plugins/memory-run-store.d.ts +6 -1
- package/dist/plugins/memory-run-store.js +10 -0
- package/dist/plugins/run-store-sql.d.ts +13 -2
- package/dist/plugins/run-store-sql.js +5 -0
- package/dist/plugins/store-contracts.d.ts +16 -0
- package/dist/plugins/workflow-run-store-sql.d.ts +7 -3
- package/dist/runs.js +4 -10
- package/dist/trace/core-keyset-guard.d.ts +7 -7
- package/dist/trace/ledger-events.d.ts +7 -1
- package/dist/trace/ledger-sink.js +10 -15
- package/dist/trace/model-segment.d.ts +55 -0
- package/dist/trace/model-segment.js +58 -0
- package/dist/trace/project.d.ts +75 -0
- package/dist/trace/project.js +123 -3
- package/dist/trace/redact.js +163 -17
- package/package.json +4 -4
|
@@ -13,6 +13,10 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
13
13
|
readonly cls: "F";
|
|
14
14
|
readonly note: "S-136 合并重扫确认项(`terminal.ts persistedPlaneOf`,持久 blob 的平面读面):盘上一条 `terminal` 因由形不合(`paused` 缺 gate / 词出闭集)让 core 的 `terminalProjection` 抛 ⇒ 本读面对这一行答**全格缺席**而不是整只 500。放行的最坏后果=一条坏行在列表/舰队读面上显示为无终局词;计数 + probe 带原句,便于按行回溯。";
|
|
15
15
|
};
|
|
16
|
+
readonly "server.mcp-panel.last-leg-unreadable": {
|
|
17
|
+
readonly cls: "F";
|
|
18
|
+
readonly note: "S-297(`http/routes/sessions.ts lastLegMcpFor`,`/mcp` 面板的可选键 `lastLegMcp`):按会话取最近 run 的索引读 / 那条 run 的账本读**抛错或超期** ⇒ 该键缺席,面板本体照常 200。放行的最坏后果=这一屏少一格「上一次真挂上了什么」(壳退回翻账本),不是门、不是权限、不是凭证 —— 而反方向(让附加键的读失败打掉整张面板)会给一口此前零 MCP 配置下根本不碰账本的**既有**读面新加一条挂掉的理由。缺席本就是本键 wire 上的一等状态(消费端必须会读),所以这条兜底不产生任何新的消费端形态;计数让「某台部署的账本一直读不出来」显形。";
|
|
19
|
+
};
|
|
16
20
|
readonly "server.workflow-park-index.sync-failed": {
|
|
17
21
|
readonly cls: "P-DEBT";
|
|
18
22
|
readonly note: "S-185 车CM(codex r1-F2 [high],验真后登记):workflow 出身 park 的 join 索引(`workflow_agent_session` / local 账本)在**一次成功的 run 写之后**同步失败 ⇒ 该次同步被吞,重试要等这条 run 的**下一次**写。而 park 恰好常常是这条 run 的**最后**一次写(park = run 终局 `failed`)⇒ 那条 park 可能永久 join 不到,`/decide` 结构性退回本批之前的行为(409 `conflict.resume_context_unavailable`)——**方向是 fail-closed**(不误配、不伪造、不放行任何东西),丢的是「人能不能把这条批准送出去」这个能力。归 P 类的理由是它决定**车道选择**(缺行 ⇒ 选中 legacy 腿),归 DEBT 的理由是真正的解要一条**按子代 checkpoint 键的耐久决议回执 + 重启期回填**(与 codex r1-F3 同一件欠账,另车)。计数非零 = 有 park 因此决不掉,运维要按 detail 里的 runId 去查。";
|
|
@@ -25,6 +29,10 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
25
29
|
readonly cls: "F";
|
|
26
30
|
readonly note: "⑪(core 7.8.0 design/388,`trace/project.ts` 的 `wiring_manifest.tools` 段与 `tool_roster_delta` 构造器共用):载荷过不了 core 自己的 typebox 判形器(`ToolRoster` / `ToolRosterDelta`)⇒ 那一段缺席 / 那一帧不发,**绝不半张脸上 wire**。放行的最坏后果=消费端这条腿看不到工具名册(壳退回按名字分组、用通用审批卡),不影响任何裁决——名册是观测/渲染面,门在引擎里早已判完。反方向(把一份判不了形的名册照发)更坏:消费端会把读不出的行当成「这只工具没挂」。计数让「某个世代的产生者一直在发坏名册」显形。";
|
|
27
31
|
};
|
|
32
|
+
readonly "server.trace.projection-input-malformed": {
|
|
33
|
+
readonly cls: "F";
|
|
34
|
+
readonly note: "7.78.0(core 7.18.0 #741/#786/#789/#790,`trace/project.ts` 的五张新投影脸共用一条规则):`tool_progress` / `tool_disclosure` 两帧的必需位读不出 ⇒ **整帧不发**;`wiring_manifest.hooks[]` / `wiring_manifest.lsp` / `context_usage.sections[]` 三段的容器形或成员形读不出 ⇒ 那一行/那一段被丢、全丢光即键缺席。规则一句话:**在场但读不出 ⇒ 计一次并丢弃;缺席从不计数**(缺席是这几张脸上的一等状态,契约把它定义成一个正面事实 —— `hooks` 缺席 = 「本条腿没有自己的钩子记录」、`sections` 缺席 = 「这条腿没装配出段 IR」、`lsp` 缺席 = 「本部署没接 LSP」)。放行的最坏后果=这条腿的消费端少一张观测/渲染面(壳退回上一份已知状态或通用卡片),**不影响任何裁决**——门在引擎里早已判完。反方向(半张脸照发)严格更坏:消费端会把读不出的行当成那个正面事实。计数是**唯一**能把「坏世代 / 第三方 producer 一直在发坏载荷」与那个正面事实分开的位。";
|
|
35
|
+
};
|
|
28
36
|
readonly "server.task-mcp.source-label-dropped": {
|
|
29
37
|
readonly cls: "F";
|
|
30
38
|
readonly note: "B-036 / S-143(`mcp-source-label.ts readMcpSourceLabel`,请求腿 + center 腿共用):`mcpServers[].source`(分组标签,显示用、开集)坏形——空串 / 超 80 字 / 非字符串——**丢键留 server**。放行的最坏后果=这台 server 在 wiring_manifest.mcp[] 里没有分组标签(难看),不是门/权限/凭证;本地 config.d 腿由 settings-schema 1.7.0 在解析处拒,故本臂只在请求腿与远端 /effective 直读 JSON 腿可达。";
|
|
@@ -4,6 +4,10 @@ export const FAIL_OPEN_TAGS = {
|
|
|
4
4
|
cls: "F",
|
|
5
5
|
note: "S-136 合并重扫确认项(`terminal.ts persistedPlaneOf`,持久 blob 的平面读面):盘上一条 `terminal` 因由形不合(`paused` 缺 gate / 词出闭集)让 core 的 `terminalProjection` 抛 ⇒ 本读面对这一行答**全格缺席**而不是整只 500。放行的最坏后果=一条坏行在列表/舰队读面上显示为无终局词;计数 + probe 带原句,便于按行回溯。",
|
|
6
6
|
},
|
|
7
|
+
"server.mcp-panel.last-leg-unreadable": {
|
|
8
|
+
cls: "F",
|
|
9
|
+
note: "S-297(`http/routes/sessions.ts lastLegMcpFor`,`/mcp` 面板的可选键 `lastLegMcp`):按会话取最近 run 的索引读 / 那条 run 的账本读**抛错或超期** ⇒ 该键缺席,面板本体照常 200。放行的最坏后果=这一屏少一格「上一次真挂上了什么」(壳退回翻账本),不是门、不是权限、不是凭证 —— 而反方向(让附加键的读失败打掉整张面板)会给一口此前零 MCP 配置下根本不碰账本的**既有**读面新加一条挂掉的理由。缺席本就是本键 wire 上的一等状态(消费端必须会读),所以这条兜底不产生任何新的消费端形态;计数让「某台部署的账本一直读不出来」显形。",
|
|
10
|
+
},
|
|
7
11
|
"server.workflow-park-index.sync-failed": {
|
|
8
12
|
cls: "P-DEBT",
|
|
9
13
|
note: "S-185 车CM(codex r1-F2 [high],验真后登记):workflow 出身 park 的 join 索引(`workflow_agent_session` / local 账本)在**一次成功的 run 写之后**同步失败 ⇒ 该次同步被吞,重试要等这条 run 的**下一次**写。而 park 恰好常常是这条 run 的**最后**一次写(park = run 终局 `failed`)⇒ 那条 park 可能永久 join 不到,`/decide` 结构性退回本批之前的行为(409 `conflict.resume_context_unavailable`)——**方向是 fail-closed**(不误配、不伪造、不放行任何东西),丢的是「人能不能把这条批准送出去」这个能力。归 P 类的理由是它决定**车道选择**(缺行 ⇒ 选中 legacy 腿),归 DEBT 的理由是真正的解要一条**按子代 checkpoint 键的耐久决议回执 + 重启期回填**(与 codex r1-F3 同一件欠账,另车)。计数非零 = 有 park 因此决不掉,运维要按 detail 里的 runId 去查。",
|
|
@@ -16,6 +20,10 @@ export const FAIL_OPEN_TAGS = {
|
|
|
16
20
|
cls: "F",
|
|
17
21
|
note: "⑪(core 7.8.0 design/388,`trace/project.ts` 的 `wiring_manifest.tools` 段与 `tool_roster_delta` 构造器共用):载荷过不了 core 自己的 typebox 判形器(`ToolRoster` / `ToolRosterDelta`)⇒ 那一段缺席 / 那一帧不发,**绝不半张脸上 wire**。放行的最坏后果=消费端这条腿看不到工具名册(壳退回按名字分组、用通用审批卡),不影响任何裁决——名册是观测/渲染面,门在引擎里早已判完。反方向(把一份判不了形的名册照发)更坏:消费端会把读不出的行当成「这只工具没挂」。计数让「某个世代的产生者一直在发坏名册」显形。",
|
|
18
22
|
},
|
|
23
|
+
"server.trace.projection-input-malformed": {
|
|
24
|
+
cls: "F",
|
|
25
|
+
note: "7.78.0(core 7.18.0 #741/#786/#789/#790,`trace/project.ts` 的五张新投影脸共用一条规则):`tool_progress` / `tool_disclosure` 两帧的必需位读不出 ⇒ **整帧不发**;`wiring_manifest.hooks[]` / `wiring_manifest.lsp` / `context_usage.sections[]` 三段的容器形或成员形读不出 ⇒ 那一行/那一段被丢、全丢光即键缺席。规则一句话:**在场但读不出 ⇒ 计一次并丢弃;缺席从不计数**(缺席是这几张脸上的一等状态,契约把它定义成一个正面事实 —— `hooks` 缺席 = 「本条腿没有自己的钩子记录」、`sections` 缺席 = 「这条腿没装配出段 IR」、`lsp` 缺席 = 「本部署没接 LSP」)。放行的最坏后果=这条腿的消费端少一张观测/渲染面(壳退回上一份已知状态或通用卡片),**不影响任何裁决**——门在引擎里早已判完。反方向(半张脸照发)严格更坏:消费端会把读不出的行当成那个正面事实。计数是**唯一**能把「坏世代 / 第三方 producer 一直在发坏载荷」与那个正面事实分开的位。",
|
|
26
|
+
},
|
|
19
27
|
"server.task-mcp.source-label-dropped": {
|
|
20
28
|
cls: "F",
|
|
21
29
|
note: "B-036 / S-143(`mcp-source-label.ts readMcpSourceLabel`,请求腿 + center 腿共用):`mcpServers[].source`(分组标签,显示用、开集)坏形——空串 / 超 80 字 / 非字符串——**丢键留 server**。放行的最坏后果=这台 server 在 wiring_manifest.mcp[] 里没有分组标签(难看),不是门/权限/凭证;本地 config.d 腿由 settings-schema 1.7.0 在解析处拒,故本臂只在请求腿与远端 /effective 直读 JSON 腿可达。",
|
|
@@ -101,6 +101,7 @@ const RESULT_KEY_TREATMENT = {
|
|
|
101
101
|
pendingApproval: "verbatim",
|
|
102
102
|
approvalWaitedMs: "verbatim",
|
|
103
103
|
effectiveReadFace: "verbatim",
|
|
104
|
+
effectiveWriteFace: "verbatim",
|
|
104
105
|
effectiveReadDenyPatterns: "verbatim",
|
|
105
106
|
effectiveMemoryScopes: "verbatim",
|
|
106
107
|
stats: "verbatim",
|
|
@@ -32,9 +32,13 @@ export type WorkflowRunParkKey = NonNullable<WorkflowRun["parks"]>[number];
|
|
|
32
32
|
* 盘上 run blob → `parks[]` 的**唯一读者**(宪法 [ref] §2:进程外字节禁裸 `as` 铸形;与本文件
|
|
33
33
|
* {@link readAgentSessionLedgerLine} 同一条姿态)。
|
|
34
34
|
*
|
|
35
|
-
*
|
|
36
|
-
* · 这一位**不是数组**(旧引擎写的记录 / 被投影剥掉)⇒
|
|
37
|
-
*
|
|
35
|
+
* 判据(7.78.0 / core 7.18.0 [ref] 复核后的口径):
|
|
36
|
+
* · 这一位**不是数组**(旧引擎写的记录 / 被投影剥掉)⇒ **本读者**读不出来 ⇒ 答缺席,由调用方响亮拒。
|
|
37
|
+
* ⚠️ 这已**不再**等于「core 也拒 resume」:core 7.18.0 起对 `parks` 缺席的记录从 journal 的 `parked`
|
|
38
|
+
* 臂 + 记录自己 `parked` 腿行的 `parkedCheckpointToken` 派生候选并逐个验真,非空即准入(三臂的第二臂)。
|
|
39
|
+
* 本读者仍答缺席的理由是另一条:那条派生只发生在**准入期**,赎回键从记录上读不出来 —— 服务端要么拒,
|
|
40
|
+
* 要么在自己这边重算 core 的三臂(被 [ref] 统一掉的双读)。恢复动作因此是「去 resume 这条 run」,
|
|
41
|
+
* 逐字见 `parked-decide.ts` 的 `denyWorkflowParksUnreadable` 顶注;
|
|
38
42
|
* · **任一条目形不合 ⇒ 整只读不出来**(整档成败制,与本文件账本行同判)。core 对同一形抛
|
|
39
43
|
* `park_binding_broken` 整条拒 resume;「丢掉坏的、留下好的」会静默铸出一份**比真相少几条 park** 的行集,
|
|
40
44
|
* 而这只读者的消费面正是 park 的赎回路由。
|
package/dist/parked-decide.d.ts
CHANGED
|
@@ -49,16 +49,26 @@ export type ParkOrigin = {
|
|
|
49
49
|
park: WorkflowRunParkKey;
|
|
50
50
|
}
|
|
51
51
|
/**
|
|
52
|
-
* S-252 —— **这条记录的 park
|
|
52
|
+
* S-252 —— **这条记录的 park 真源在本车道读不出来**(第四种出身,不是第四条车道)。
|
|
53
53
|
*
|
|
54
54
|
* 索引行还在、run 行也还在,但记录上没有一份读得出来的 `parks[]`:旧引擎(core < 7.17.0)写下的存量行
|
|
55
|
-
* 根本没有这一位,或者某一条条目的形坏了。而这一位是**引擎自己**的 park
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* ⇒ 这条决定**结构上**送不出去:答 200「已交给宿主的下一跑」就是一张兑付不了的回执。
|
|
55
|
+
* 根本没有这一位,或者某一条条目的形坏了。而这一位是**引擎自己**的 park 责任表,也是本车道把调用方的
|
|
56
|
+
* checkpoint join 到某一条 park 的**单一真源** ⇒ 这条决定**结构上**送不出去:答 200「已交给宿主的
|
|
57
|
+
* 下一跑」就是一张兑付不了的回执。
|
|
59
58
|
* 此前这一格与「索引落后一拍 / 这条 run 已不再负责它」两种**正常** miss 完全同形,于是落 legacy 腿拿
|
|
60
59
|
* 一个错因 409。判别式据真源的**可读性**把这一格分出来,三个消费点各自响亮处置(decide 拒 + 不驱动
|
|
61
60
|
* 宿主续跑;SLA sweep 照旧静默跳过——它同样复核不了)。
|
|
61
|
+
*
|
|
62
|
+
* 🔴 **7.78.0 / core 7.18.0([ref]):本臂的「为什么拒」没变,「拒了之后干什么」变了。**
|
|
63
|
+
* 旧注(与旧拒文)断言 core 的 resume 准入对 `!Array.isArray(prior.parks)` 一律整条拒,并据此告诉运维
|
|
64
|
+
* 「这条 run 恢复不了,开一条新的」。core 7.18.0 把那条准入拆成**三臂**([ref]「走 a″」),该断言
|
|
65
|
+
* 自此为假:①记录带 `parks` ⇒ 那张表独自作数(不变);②**`parks` 缺席 ⇒ 候选从 journal 的 `parked` 臂
|
|
66
|
+
* 与记录自己 `parked` 腿行的 `parkedCheckpointToken` 取并集派生**,非空即准入(每个派生候选仍逐个
|
|
67
|
+
* `checkpointStore.get(token)` 验真);③缺席且并集为空 ⇒ 仍拒(`parks_unreadable`,条件收窄到这一格)。
|
|
68
|
+
* ⇒ 存量记录**多数可以被 resume 救回来**,而旧拒文让人去开新 run = 把一条救得回来的 run 劝弃。
|
|
69
|
+
* 本车道**仍然拒**(core 逐字:那种记录的赎回键是在准入期派生的、**从记录上读不出来**;本腿要么拒、
|
|
70
|
+
* 要么就得在服务端重算 core 的三臂 —— 后者正是这次统一掉的双读),但恢复动作改成真话:**去 resume
|
|
71
|
+
* 这条 run**。准入一旦走完,core 会把 `parks` 写上记录([ref]),下一次 `/decide` 就走正常的 workflow 臂。
|
|
62
72
|
*/
|
|
63
73
|
| {
|
|
64
74
|
kind: "workflow_parks_unreadable";
|
|
@@ -228,13 +238,18 @@ export type WorkflowParkedDecidePlan = {
|
|
|
228
238
|
/**
|
|
229
239
|
* S-252 —— **park 真源读不出来的那条记录的响亮拒**(与上面那族拒臂同一种返回形)。
|
|
230
240
|
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
241
|
+
* 为什么是拒而不是「尽力而为地投递一次」:记录上的 `parks[]` 是本车道把调用方的 checkpoint join 到某一条
|
|
242
|
+
* park 的**单一真源**。读不出来 ⇒ 这条决定结构上送不出去,答 200「已交给宿主的下一跑」就是一张兑付不了的
|
|
243
|
+
* 回执:卡还 pending、人以为决完了。⇒ 拒,并把真话说出来;卡仍 pending,什么都没有被伪造。
|
|
244
|
+
*
|
|
245
|
+
* 🔴 **7.78.0 / core 7.18.0([ref]):真话变了一句,拒没变。** 亲读安装树 7.18.0 的三臂准入(判别式顶注逐字
|
|
246
|
+
* 记着):`parks` 缺席时 core **从 journal 的 `parked` 臂 + 记录自己 `parked` 腿行的 `parkedCheckpointToken`
|
|
247
|
+
* 派生候选**并逐个向 checkpoint 店验真,非空即准入 —— 所以「这条 run 恢复不了」自 7.18.0 起对**多数**存量
|
|
248
|
+
* 记录是假的,照旧文答就是劝人弃掉一条救得回来的 run。本腿仍拒的理由是另一条(core 逐字):那种记录的赎回键
|
|
249
|
+
* 是**在准入期派生的、从记录上读不出来**,服务端要么拒、要么重算 core 的三臂(被这次统一掉的双读)。
|
|
250
|
+
* ⇒ 恢复动作 = **去 resume 这条 run**(`Workflow({resumeFromRunId})`):准入走完 core 会把 `parks` 写上
|
|
251
|
+
* 记录([ref]),下一次 `/decide` 走正常 workflow 臂。若引擎那一步也拒(journal 与腿行都给不出候选,
|
|
252
|
+
* core 的第三臂),那才是真的恢复不了。
|
|
238
253
|
*
|
|
239
254
|
* 这条臂的受害集 = **存量持久数据**(core < 7.17.0 的引擎写下、升级后还 parked 着的 run 记录)。硬 breaking
|
|
240
255
|
* 三句里持久数据那一句要的就是这个形:响亮拒,不做静默双读(读 `agents[]` 补一份身份正是被退役的双算)。
|
package/dist/parked-decide.js
CHANGED
|
@@ -89,7 +89,7 @@ export function denyWorkflowParksUnreadable(origin) {
|
|
|
89
89
|
return {
|
|
90
90
|
status: 409,
|
|
91
91
|
body: {
|
|
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.
|
|
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. This route cannot join your checkpoint to a park without it, so the decision is NOT delivered rather than answered with a receipt nothing can redeem. The approval card stays pending and nothing is forged. Resume the run itself instead: the engine derives the parks such a record owns from its journal and its own parked legs, proves each against the checkpoint store, and writes the list onto the record once that admission completes — after which this route works normally. Only if the engine also refuses that resume is the run truly unrecoverable",
|
|
93
93
|
errorCode: "decide.workflow_parks_unreadable",
|
|
94
94
|
runId: origin.runId,
|
|
95
95
|
},
|
|
@@ -208,7 +208,7 @@ export async function decideParkedAgent(deps, req, match) {
|
|
|
208
208
|
return "retry";
|
|
209
209
|
}
|
|
210
210
|
};
|
|
211
|
-
defaultSubagentTailBus.
|
|
211
|
+
defaultSubagentTailBus.forgetHandle(match.handle);
|
|
212
212
|
let receipt;
|
|
213
213
|
try {
|
|
214
214
|
receipt = await deps.reviveTool.execute(mintReviveArgs(row, deps.knownAgentTypes ?? new Set()), ctx);
|
|
@@ -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 { RunRecord, SessionSummary, RunEvent, PersistedTaskResult } from "./store-contracts.js";
|
|
3
|
+
import type { RunRecord, SessionSummary, RunEvent, PersistedTaskResult, LatestRunRef } from "./store-contracts.js";
|
|
4
4
|
import type { RunStoreCheckpointProbe } from "./memory-run-store.js";
|
|
5
5
|
import type { LedgerEventType } from "../trace/ledger-events.js";
|
|
6
6
|
/**
|
|
@@ -127,6 +127,11 @@ export declare class FileRunStore {
|
|
|
127
127
|
};
|
|
128
128
|
limit: number;
|
|
129
129
|
}): Promise<RunRecord[]>;
|
|
130
|
+
/**
|
|
131
|
+
* S-297 —— 这个会话**最近一条腿**的窄指针({@link SqlRunStore.latestRunForSession} 的孪生:同一个排序口径
|
|
132
|
+
* `createdAt DESC, taskId DESC`,与本店 {@link FileRunStore.listSessions} 挑 `rn=1` 那一行的比较器逐字同一份)。
|
|
133
|
+
*/
|
|
134
|
+
latestRunForSession(sessionId: string): Promise<LatestRunRef | undefined>;
|
|
130
135
|
/** DISTINCT sessions newest-first by last activity (the CC /resume picker): latest run's preview/status + first/last/count. */
|
|
131
136
|
listSessions(opts: {
|
|
132
137
|
owner?: string;
|
|
@@ -443,6 +443,16 @@ export class FileRunStore {
|
|
|
443
443
|
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime() || (a.taskId < b.taskId ? 1 : a.taskId > b.taskId ? -1 : 0));
|
|
444
444
|
return rows.slice(0, opts.limit).map((r) => this.toRecord(r));
|
|
445
445
|
}
|
|
446
|
+
async latestRunForSession(sessionId) {
|
|
447
|
+
let latest;
|
|
448
|
+
for (const r of this.runs.values()) {
|
|
449
|
+
if (r.sessionId !== sessionId)
|
|
450
|
+
continue;
|
|
451
|
+
if (latest === undefined || r.createdAt.getTime() > latest.createdAt.getTime() || (r.createdAt.getTime() === latest.createdAt.getTime() && r.taskId > latest.taskId))
|
|
452
|
+
latest = r;
|
|
453
|
+
}
|
|
454
|
+
return latest === undefined ? undefined : { taskId: latest.taskId, owner: latest.owner, createdAt: latest.createdAt.toISOString() };
|
|
455
|
+
}
|
|
446
456
|
async listSessions(opts) {
|
|
447
457
|
const bySession = new Map();
|
|
448
458
|
for (const r of this.runs.values()) {
|
|
@@ -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 { RunRecord, SessionSummary, RunEvent, PersistedTaskResult } from "./store-contracts.js";
|
|
3
|
+
import type { RunRecord, SessionSummary, RunEvent, PersistedTaskResult, 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
|
|
@@ -76,6 +76,11 @@ export declare class MemoryRunStore {
|
|
|
76
76
|
};
|
|
77
77
|
limit: number;
|
|
78
78
|
}): Promise<RunRecord[]>;
|
|
79
|
+
/**
|
|
80
|
+
* S-297 —— 这个会话**最近一条腿**的窄指针({@link SqlRunStore.latestRunForSession} 的孪生:同一个排序口径
|
|
81
|
+
* `createdAt DESC, taskId DESC`,与本店 {@link MemoryRunStore.listSessions} 挑 `rn=1` 那一行的比较器逐字同一份)。
|
|
82
|
+
*/
|
|
83
|
+
latestRunForSession(sessionId: string): Promise<LatestRunRef | undefined>;
|
|
79
84
|
/** DISTINCT sessions newest-first by last activity (the CC /resume picker): latest run's preview/status + first/last/count. */
|
|
80
85
|
listSessions(opts: {
|
|
81
86
|
owner?: string;
|
|
@@ -190,6 +190,16 @@ export class MemoryRunStore {
|
|
|
190
190
|
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime() || (a.taskId < b.taskId ? 1 : a.taskId > b.taskId ? -1 : 0));
|
|
191
191
|
return rows.slice(0, opts.limit).map((r) => this.toRecord(r));
|
|
192
192
|
}
|
|
193
|
+
async latestRunForSession(sessionId) {
|
|
194
|
+
let latest;
|
|
195
|
+
for (const r of this.runs.values()) {
|
|
196
|
+
if (r.sessionId !== sessionId)
|
|
197
|
+
continue;
|
|
198
|
+
if (latest === undefined || r.createdAt.getTime() > latest.createdAt.getTime() || (r.createdAt.getTime() === latest.createdAt.getTime() && r.taskId > latest.taskId))
|
|
199
|
+
latest = r;
|
|
200
|
+
}
|
|
201
|
+
return latest === undefined ? undefined : { taskId: latest.taskId, owner: latest.owner, createdAt: latest.createdAt.toISOString() };
|
|
202
|
+
}
|
|
193
203
|
async listSessions(opts) {
|
|
194
204
|
const bySession = new Map();
|
|
195
205
|
for (const r of this.runs.values()) {
|
|
@@ -2,10 +2,10 @@ 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 { RunRecord, SessionSummary, RunEvent, PersistedTaskResult } from "./store-contracts.js";
|
|
5
|
+
import type { RunRecord, SessionSummary, RunEvent, PersistedTaskResult, 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
|
-
export type { RunRecord, SessionSummary, RunEvent } from "./store-contracts.js";
|
|
8
|
+
export type { RunRecord, SessionSummary, RunEvent, LatestRunRef } from "./store-contracts.js";
|
|
9
9
|
export declare class SqlRunStore {
|
|
10
10
|
protected readonly db: SqlDriver;
|
|
11
11
|
constructor(db: SqlDriver);
|
|
@@ -149,6 +149,17 @@ export declare class SqlRunStore {
|
|
|
149
149
|
};
|
|
150
150
|
limit: number;
|
|
151
151
|
}): Promise<RunRecord[]>;
|
|
152
|
+
/**
|
|
153
|
+
* S-297 —— 这个会话**最近一条腿**的窄指针(`/mcp` 面板的 `lastLegMcp` 与任何「上一条 run 是谁」的读面共用)。
|
|
154
|
+
*
|
|
155
|
+
* 排序口径与 {@link listSessions} 的 `rn=1` 窗函数**逐字同一句**(`created_at DESC, task_id DESC`)——
|
|
156
|
+
* 两处答的是同一个问题「哪一条是最新的」,口径分叉会让列表面的 `lastRunId` 与本读口指向不同的 run。
|
|
157
|
+
* 走的也是同一条索引路径(`idx_task_run_session_status` 的 `session_id` 前缀),所以**不新加索引**:
|
|
158
|
+
* 会话内行数有界,前缀定位之后的排序只在这一小撮行上。
|
|
159
|
+
*
|
|
160
|
+
* 两方言只差占位符(动态子句为零,不像 listRuns/listSessions 那样需要各自的构造器臂)。
|
|
161
|
+
*/
|
|
162
|
+
latestRunForSession(sessionId: string): Promise<LatestRunRef | undefined>;
|
|
152
163
|
/** GET /v1/sessions (CC /resume picker): DISTINCT sessions newest-first by last activity, keyset-paginated on
|
|
153
164
|
* (last_activity, session_id). Aggregates task_run by session_id — one window pass picks the latest run per
|
|
154
165
|
* session (rn=1) for the preview/status and rolls up first/last/count. Owner-scoped when set (the per-user view).
|
|
@@ -252,6 +252,11 @@ export class SqlRunStore {
|
|
|
252
252
|
const { rows } = await this.db.query(sql, params);
|
|
253
253
|
return rows.map((r) => this.rowToRun(r));
|
|
254
254
|
}
|
|
255
|
+
async latestRunForSession(sessionId) {
|
|
256
|
+
const { rows } = await this.db.query(this.q("SELECT task_id, owner, created_at FROM task_run WHERE session_id = ? ORDER BY created_at DESC, task_id DESC LIMIT 1", "SELECT task_id, owner, created_at FROM task_run WHERE session_id = $1 ORDER BY created_at DESC, task_id DESC LIMIT 1"), [sessionId]);
|
|
257
|
+
const r = rows[0];
|
|
258
|
+
return r === undefined ? undefined : { taskId: String(r.task_id), owner: r.owner ?? null, createdAt: iso(r.created_at) };
|
|
259
|
+
}
|
|
255
260
|
async listSessions(opts) {
|
|
256
261
|
if (this.db.dialect === "tidb") {
|
|
257
262
|
const params = [];
|
|
@@ -244,6 +244,22 @@ export interface SessionSummary {
|
|
|
244
244
|
* titled OR the lister face has no session_meta (the legacy task_run aggregate / memory twins — honest degrade). */
|
|
245
245
|
title: string | null;
|
|
246
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* S-297 —— 「这个会话**最近一条腿**是哪一条 run」的窄读面(`latestRunForSession` 的回值)。
|
|
249
|
+
*
|
|
250
|
+
* 刻意**不是** {@link RunRecord}:那一形携 `result` 整只 blob,而本读面的三个消费点(run 身份 / 属主门 /
|
|
251
|
+
* 排序口径的自证)一个都用不到它 —— 与 `listSessions` 只提标量 `lastTaskRunId` 而不拉 `result` 是同一条
|
|
252
|
+
* 判据([ref]:列表/指针面只拉标量,不把 blob 拖进按会话的高频读)。
|
|
253
|
+
*
|
|
254
|
+
* `owner` = 该 run 的**提交 principal**(可为 null = 无属主的存量行),消费点据它判「这条腿的读面该不该
|
|
255
|
+
* 投给当前调用方」;它与会话的 canonical owner 会分叉,判据与先例逐字见 `run-store-sql.ts deleteBySession`
|
|
256
|
+
* 的头注(F-A)。`createdAt` = 排序键(ISO),三孪生同一口径 `created_at DESC, task_id DESC`。
|
|
257
|
+
*/
|
|
258
|
+
export interface LatestRunRef {
|
|
259
|
+
taskId: string;
|
|
260
|
+
owner: string | null;
|
|
261
|
+
createdAt: string;
|
|
262
|
+
}
|
|
247
263
|
export interface RunEvent {
|
|
248
264
|
seq: number;
|
|
249
265
|
type: string;
|
|
@@ -80,9 +80,13 @@ export declare function withRedactedError<T extends {
|
|
|
80
80
|
*
|
|
81
81
|
* 🔴 **`parks[]` 是这条阶梯唯一不许碰的东西**(core 7.17.0 [ref] 义务②)。
|
|
82
82
|
* 它不是观测位,是**下一次 resume 的准入输入**:core 的 resume 准入在任何 agent 被派发之前读记录上的
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
83
|
+
* 这一位。🔴 **7.78.0 / core 7.18.0([ref])口径更新**:读不到**不再**是整条拒 —— core 改成三臂,缺席时
|
|
84
|
+
* 从 journal 的 `parked` 臂 + 记录自己 `parked` 腿行的 `parkedCheckpointToken` 派生候选逐个验真,非空即
|
|
85
|
+
* 准入,空才拒(`parks_unreadable` 条件收窄到那一格)。但**本店剥它仍是灾难级**:带 `parks` 的记录走
|
|
86
|
+
* 第一臂,「那张表独自作数」—— 剥成缺席就把一条走第一臂的记录推去走派生臂,而派生的两个缓存只装这条 run
|
|
87
|
+
* **自己做出**的 park,继承来、从未跑到的那一条**只存在于被剥掉的那张表里**,于是它要么被漏掉、要么被
|
|
88
|
+
* 当成不存在而在 resume 时活着重跑([ref] 正要消灭的形)。它同时也是本仓 `/decide` 的 workflow 车道复核
|
|
89
|
+
* 「这条 park 属于这条 run」的**单一真源**(S-252)。实现上三级全部走 `{...上一级}` 展开、没有任何一级点名裁它,机器背书 =
|
|
86
90
|
* `test/slim-oversize-run.test.ts` 里那两格(最狠的一级上 `parks` 仍逐字完整 + 负控:记录本来没有
|
|
87
91
|
* 这一位时不许凭空补 `[]`)。缺席 ⇒ 不补:补 `[]` 会把「这版引擎没写这个位」伪造成「这条 run 没有 park」,
|
|
88
92
|
* 而 core 对两者的处置相反。
|
package/dist/runs.js
CHANGED
|
@@ -10,7 +10,7 @@ import { recordTurnActivity } from "./turn-activity.js";
|
|
|
10
10
|
import { CANCELLED_CODE, mintCancelledResult } from "./run-cancel-context.js";
|
|
11
11
|
import { createApprovalCardEmitter, resolveApprovalLeg } from "./tool-approval.js";
|
|
12
12
|
import { fleetRunResiduals, isFleetAgentTerminalNotification } from "./fleet/fleet-bus.js";
|
|
13
|
-
import { defaultSubagentTailBus
|
|
13
|
+
import { defaultSubagentTailBus } from "./fleet/subagent-tail-bus.js";
|
|
14
14
|
import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotificationStreamKey, NotifiedKeys } from "./orchestration/workflow-completion-inbox.js";
|
|
15
15
|
import { recordFailOpen } from "./observability/fail-open.js";
|
|
16
16
|
import { errorText as errText } from "./observability/err-text.js";
|
|
@@ -275,19 +275,13 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
275
275
|
fleetPublisher?.onForwardEvent(e);
|
|
276
276
|
sink.onForwardEvent(e);
|
|
277
277
|
const bg = e.bgAgentId;
|
|
278
|
-
if (bg !== undefined)
|
|
279
|
-
defaultSubagentTailBus.
|
|
280
|
-
if (defaultSubagentTailBus.hasSubscribers(bg)) {
|
|
281
|
-
const f = projectTailFrame(e);
|
|
282
|
-
if (f)
|
|
283
|
-
defaultSubagentTailBus.publish(bg, f);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
278
|
+
if (bg !== undefined)
|
|
279
|
+
defaultSubagentTailBus.onForwardFrame(bg, tailContentMode, e);
|
|
286
280
|
},
|
|
287
281
|
onTaskNotification: (n, opts) => {
|
|
288
282
|
if (n.task_type === "workflow")
|
|
289
283
|
return;
|
|
290
|
-
defaultSubagentTailBus.
|
|
284
|
+
defaultSubagentTailBus.forgetHandle(n.task_id);
|
|
291
285
|
if (defaultSubagentTailBus.hasSubscribers(n.task_id)) {
|
|
292
286
|
defaultSubagentTailBus.publish(n.task_id, { type: "task_settled", taskId: n.task_id, status: n.status, ...(typeof n.seq === "number" ? { seq: n.seq } : {}), ...(n.summary ? { summary: redactSecrets(n.summary) } : {}) });
|
|
293
287
|
}
|
|
@@ -50,7 +50,7 @@ type _GuardAskReverse = AssertAllKeysHandled<Exclude<AskProjected | AskExcluded,
|
|
|
50
50
|
type AskFrameServerMinted = "type" | "approvalId" | "argsOmitted" | "outcome" | "governanceForced" | "expiresAtMs" | "expiresInMs" | "serverNowMs" | "parked" | "inputHasBidi";
|
|
51
51
|
type _GuardAskFrame = AssertAllKeysHandled<Exclude<keyof ToolApprovalFrame, AskProjected | AskFrameServerMinted>>;
|
|
52
52
|
type _GuardAskFrameReverse = AssertAllKeysHandled<Exclude<AskProjected | AskFrameServerMinted, keyof ToolApprovalFrame>>;
|
|
53
|
-
type TaskEventHandled = "text_delta" | "reasoning_delta" | "tool_start" | "tool_end" | "turn_end" | "compacted" | "diagnostics" | "message_committed" | "status" | "task_notification" | "task_progress" | "steering_injected" | "workspace_changed" | "done" | "context_usage" | "compaction_outcome" | "human_input" | "wiring_manifest" | "text_end" | "tool_roster_delta";
|
|
53
|
+
type TaskEventHandled = "text_delta" | "reasoning_delta" | "tool_start" | "tool_end" | "turn_end" | "compacted" | "diagnostics" | "message_committed" | "status" | "task_notification" | "task_progress" | "steering_injected" | "workspace_changed" | "done" | "context_usage" | "compaction_outcome" | "human_input" | "wiring_manifest" | "text_end" | "tool_roster_delta" | "tool_progress" | "tool_disclosure";
|
|
54
54
|
type _GuardTaskEvent = AssertAllKeysHandled<Exclude<TaskEvent["type"], TaskEventHandled>>;
|
|
55
55
|
type _GuardTaskEventReverse = AssertAllKeysHandled<Exclude<TaskEventHandled, TaskEvent["type"]>>;
|
|
56
56
|
type MailboxProjected = "seq" | "from" | "content" | "sentAt" | "hopChain" | "peerMeta";
|
|
@@ -59,7 +59,7 @@ type _GuardMailboxReverse = AssertAllKeysHandled<Exclude<MailboxProjected, keyof
|
|
|
59
59
|
type MailboxAppendProjected = "from" | "content" | "sentAt" | "hopChain" | "peerMeta";
|
|
60
60
|
type _GuardMailboxAppend = AssertAllKeysHandled<Exclude<keyof MailboxAppendMessage, MailboxAppendProjected>>;
|
|
61
61
|
type _GuardMailboxAppendReverse = AssertAllKeysHandled<Exclude<MailboxAppendProjected, keyof MailboxAppendMessage>>;
|
|
62
|
-
type WiringSectionProjected = "schemaVersion" | "leg" | "ask" | "question" | "interaction" | "elicit" | "parkLane" | "session" | "fleet" | "permissionRules" | "modelGate" | "autoMode" | "mcp" | "tools";
|
|
62
|
+
type WiringSectionProjected = "schemaVersion" | "leg" | "ask" | "question" | "interaction" | "elicit" | "parkLane" | "session" | "fleet" | "permissionRules" | "modelGate" | "autoMode" | "mcp" | "tools" | "hooks" | "lsp";
|
|
63
63
|
type WiringSectionExcluded = "governance" | "configFingerprint";
|
|
64
64
|
type _GuardWiringSections = AssertAllKeysHandled<Exclude<keyof WiringManifest, WiringSectionProjected | WiringSectionExcluded>>;
|
|
65
65
|
type _GuardWiringSectionsReverse = AssertAllKeysHandled<Exclude<WiringSectionProjected | WiringSectionExcluded, keyof WiringManifest>>;
|
|
@@ -122,10 +122,10 @@ type SummaryExcluded = "token" | "toolInput" | "toolName" | "toolCallId" | "prev
|
|
|
122
122
|
type _GuardInboxSummary = AssertAllKeysHandled<Exclude<keyof CheckpointSummary, SummaryProjected | SummaryExcluded>>;
|
|
123
123
|
type _GuardInboxSummaryReverse = AssertAllKeysHandled<Exclude<SummaryProjected | SummaryExcluded, keyof CheckpointSummary>>;
|
|
124
124
|
type TaskSpecWired = "objective" | "principal" | "images" | "sessionId" | "requireExistingSession" | "oneShot" | "clientContext" | "resumeAt" | "resumeAtMode" | "model" | "compactionModel" | "thinking" | "systemPrompt" | "appendSystemPrompt" | "promptProvider" | "memory" | "suggestNextPrompts" | "restoreFiles" | "acceptPartialRestore" | "rewindFilesTo" | "tools" | "excludeTools" | "deferTools" | "toolMaterializeStrategy" | "promptProfile" | "toolPolicy" | "onAsk" | "onQuestion" | "hooks" | "durableApproval" | "resourceSuspend" | "resilience" | "finalVerification" | "checkpointStore" | "handsReadOnly" | "memoryPersistenceCapable" | "additionalDirectories" | "additionalReadDirectories" | "enablePlanMode" | "interactiveTools" | "enableFork" | "shellGate" | "mcp" | "a2a" | "skills" | "backgroundScope" | "envFacts" | "limits" | "configOverrides" | "forwardSubagentEvents" | "retainSubagentSessions" | "retainBackgroundProcesses" | "outputSchema" | "outputRetries" | "compaction" | "attachments" | "selfOrchestration" | "getApiKeyAndHeaders" | "interactionPosture" | "agents" | "taskId" | "signal" | "preemptSignal" | "modelRole" | "enableBlockedReport" | "maxSuspends" | "autoModeRequested";
|
|
125
|
-
type TaskSpecUnwired = "excludeAllTools" | "actor" | "roles" | "lspManager" | "tracer" | "readFace" | "readDenyPatterns" | "interactiveQuestionFallback" | "alwaysLoadTools" | "restoreGatedTools" | "deferSelfResolve" | "basePolicyForResumeEdit" | "lspDiagnostics" | "streamingToolExecution" | "toolResultThresholdChars";
|
|
125
|
+
type TaskSpecUnwired = "excludeAllTools" | "writeFace" | "actor" | "roles" | "lspManager" | "tracer" | "readFace" | "readDenyPatterns" | "interactiveQuestionFallback" | "alwaysLoadTools" | "restoreGatedTools" | "deferSelfResolve" | "basePolicyForResumeEdit" | "lspDiagnostics" | "streamingToolExecution" | "toolResultThresholdChars";
|
|
126
126
|
type _GuardTaskSpec = AssertAllKeysHandled<Exclude<keyof TaskSpec, TaskSpecWired | TaskSpecUnwired>>;
|
|
127
127
|
type _GuardTaskSpecReverse = AssertAllKeysHandled<Exclude<TaskSpecWired | TaskSpecUnwired, keyof TaskSpec>>;
|
|
128
|
-
type TaskResultForwarded = "taskId" | "runId" | "sessionId" | "terminal" | "model" | "effectiveReasoning" | "result" | "salvagedOutput" | "remoteEnvFailures" | "apiFailure" | "retryAfterMs" | "degraded" | "structuredOutput" | "rewindNotes" | "haltedOnUserRejection" | "haltedByUser" | "strandedHumanAnswers" | "pendingApproval" | "approvalWaitedMs" | "effectiveReadFace" | "effectiveReadDenyPatterns" | "effectiveMemoryScopes" | "stats" | "editedFiles";
|
|
128
|
+
type TaskResultForwarded = "taskId" | "runId" | "sessionId" | "terminal" | "model" | "effectiveReasoning" | "result" | "salvagedOutput" | "remoteEnvFailures" | "apiFailure" | "retryAfterMs" | "degraded" | "structuredOutput" | "rewindNotes" | "haltedOnUserRejection" | "haltedByUser" | "strandedHumanAnswers" | "pendingApproval" | "approvalWaitedMs" | "effectiveReadFace" | "effectiveReadDenyPatterns" | "effectiveWriteFace" | "effectiveMemoryScopes" | "stats" | "editedFiles";
|
|
129
129
|
type TaskResultStripped = never;
|
|
130
130
|
type _GuardTaskResult = AssertAllKeysHandled<Exclude<keyof TaskResult, TaskResultForwarded | TaskResultStripped>>;
|
|
131
131
|
type _GuardTaskResultReverse = AssertAllKeysHandled<Exclude<TaskResultForwarded | TaskResultStripped, keyof TaskResult>>;
|
|
@@ -139,7 +139,7 @@ type GateOutcomeProjected = "disposition" | "settlement" | "origin";
|
|
|
139
139
|
type _GuardGateOutcome = AssertAllKeysHandled<Exclude<keyof GateOutcome, GateOutcomeProjected>>;
|
|
140
140
|
type _GuardGateOutcomeReverse = AssertAllKeysHandled<Exclude<GateOutcomeProjected, keyof GateOutcome>>;
|
|
141
141
|
type GateDispositionKeys = GateDisposition extends infer D ? (D extends object ? keyof D : never) : never;
|
|
142
|
-
type GateDispositionProjected = "kind" | "deniedBy" | "cause";
|
|
142
|
+
type GateDispositionProjected = "kind" | "deniedBy" | "cause" | "classifier";
|
|
143
143
|
type _GuardGateDisposition = AssertAllKeysHandled<Exclude<GateDispositionKeys, GateDispositionProjected>>;
|
|
144
144
|
type _GuardGateDispositionReverse = AssertAllKeysHandled<Exclude<GateDispositionProjected, GateDispositionKeys>>;
|
|
145
145
|
type SettlementProjected = "kind" | "who" | "when" | "note";
|
|
@@ -148,12 +148,12 @@ type _GuardSettlementReverse = AssertAllKeysHandled<Exclude<SettlementProjected,
|
|
|
148
148
|
type SettlementWhoProjected = "party" | "approver" | "window";
|
|
149
149
|
type _GuardSettlementWho = AssertAllKeysHandled<Exclude<SettlementWhoKeys, SettlementWhoProjected>>;
|
|
150
150
|
type _GuardSettlementWhoReverse = AssertAllKeysHandled<Exclude<SettlementWhoProjected, SettlementWhoKeys>>;
|
|
151
|
-
type WorkflowRunForwarded = "id" | "scope" | "status" | "startedAt" | "endedAt" | "createdAt" | "rev" | "parks" | "name" | "description" | "result" | "error" | "errorCode" | "agentFailures" | "stats" | "phases" | "agents" | "groups";
|
|
151
|
+
type WorkflowRunForwarded = "id" | "scope" | "status" | "startedAt" | "endedAt" | "createdAt" | "rev" | "parks" | "name" | "description" | "result" | "error" | "errorCode" | "agentFailures" | "stats" | "phases" | "agents" | "groups" | "resumeAdmissionIncomplete";
|
|
152
152
|
type WorkflowRunStripped = "sourceTaskId" | "originatingSessionId" | "effectiveArgs" | "budgetOvershoot" | "timeoutInterruption" | "resultFull" | "completionId" | "resume" | "journalSkips";
|
|
153
153
|
type _GuardWorkflowRun = AssertAllKeysHandled<Exclude<keyof WorkflowRun, WorkflowRunForwarded | WorkflowRunStripped>>;
|
|
154
154
|
type _GuardWorkflowRunReverse = AssertAllKeysHandled<Exclude<WorkflowRunForwarded | WorkflowRunStripped, keyof WorkflowRun>>;
|
|
155
155
|
type ParkRowKeys = keyof NonNullable<WorkflowRun["parks"]>[number];
|
|
156
|
-
type ParkRowProjected = "callKey" | "sessionId" | "originRunId";
|
|
156
|
+
type ParkRowProjected = "callKey" | "sessionId" | "originRunId" | "originUnconfirmed";
|
|
157
157
|
type ParkRowWithheld = "token";
|
|
158
158
|
type _GuardParkRow = AssertAllKeysHandled<Exclude<ParkRowKeys, ParkRowProjected | ParkRowWithheld>>;
|
|
159
159
|
type _NoDoubleBookParkRow = AssertAllKeysHandled<Extract<ParkRowProjected, ParkRowWithheld>>;
|
|
@@ -35,7 +35,13 @@ export type LedgerEventType = "reasoning" | "text" | "tool_start" | "tool_end" |
|
|
|
35
35
|
/** S-212(7.71.0):运行期名册变更(core 7.8.0 [ref] ⑪)。**快照语义**——`delta.roster` 是整只
|
|
36
36
|
* 变更后名册,消费端收到即整只替换,所以回放不需要任何合并;它的耐久面因此不是 `wiring_manifest.tools`
|
|
37
37
|
* 的重复,而是那份开腿快照**之后**的每一次改动(修前两条 durable 腿零 case ⇒ 回放只见开头一版)。 */
|
|
38
|
-
| "tool_roster_delta"
|
|
38
|
+
| "tool_roster_delta"
|
|
39
|
+
/** [ref](core 7.18.0):本条腿的**延迟工具普查**快照(policy / thresholdPercent? / deferred[] / activated[])。
|
|
40
|
+
* 与 `tool_roster_delta` 同判、同理由——**快照语义**(整只替换,回放零合并),而开腿那一份住在
|
|
41
|
+
* `wiring_manifest` 之后的首帧、**之后每一次激活**只活在本帧上。整帧缺席 = 这条腿什么都没延迟。
|
|
42
|
+
* 🔴 `tool_progress`([ref])**刻意不在本表**:core 的 `tool_progress.ephemeral` 契约逐字「LIVE ONLY …
|
|
43
|
+
* core never persists this frame」,与 `task_progress` / `status` 同一处置(两条 durable 腿零 case)。 */
|
|
44
|
+
| "tool_disclosure" | "human_input" | "task_progress" | "task_notification" | "turn_end"
|
|
39
45
|
/** park 行名**两个**(`appendParked(kind)` 逐字取自 `result.status`)—— 运行期闭集见
|
|
40
46
|
* {@link PARK_LEDGER_EVENT_TYPES}(本联合是它的类型面,两者由下方的双向断言锁步)。 */
|
|
41
47
|
| "suspended" | "needs_review" | "done" | "failed" | "suggestions" | "model_usage" | "prompt_assembled" | "config_assembled" | "file_link"
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { terminalProjection } from "@sema-agent/core";
|
|
2
|
-
import { modelTextEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, toolRosterDeltaEventData, humanInputEventData, turnEndEventData, contextUsageEventData } from "./project.js";
|
|
2
|
+
import { modelTextEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, toolRosterDeltaEventData, toolDisclosureEventData, humanInputEventData, turnEndEventData, contextUsageEventData } from "./project.js";
|
|
3
3
|
import { NotifiedKeys, taskNotificationStreamKey } from "../orchestration/workflow-completion-inbox.js";
|
|
4
4
|
import { isParkLedgerEventType } from "./ledger-events.js";
|
|
5
|
+
import { isModelSegmentBoundary } from "./model-segment.js";
|
|
5
6
|
export function createSerialLedgerAppend(appendEvent, startSeq) {
|
|
6
7
|
let seq = startSeq;
|
|
7
8
|
let chain = Promise.resolve();
|
|
@@ -43,6 +44,8 @@ export function createLedgerSink(opts) {
|
|
|
43
44
|
};
|
|
44
45
|
const onEvent = async (ev) => {
|
|
45
46
|
beat(ev.type);
|
|
47
|
+
if (isModelSegmentBoundary(ev.type))
|
|
48
|
+
await flush();
|
|
46
49
|
switch (ev.type) {
|
|
47
50
|
case "text_delta": {
|
|
48
51
|
const td = ev;
|
|
@@ -55,15 +58,12 @@ export function createLedgerSink(opts) {
|
|
|
55
58
|
reasoning += ev.delta;
|
|
56
59
|
break;
|
|
57
60
|
case "tool_start":
|
|
58
|
-
await flush();
|
|
59
61
|
await append("tool_start", toolStartEventData(ev));
|
|
60
62
|
break;
|
|
61
63
|
case "tool_end":
|
|
62
|
-
await flush();
|
|
63
64
|
await append("tool_end", toolEndEventData(ev));
|
|
64
65
|
break;
|
|
65
66
|
case "status":
|
|
66
|
-
await flush();
|
|
67
67
|
{
|
|
68
68
|
const statusData = brainStatusEventData(ev);
|
|
69
69
|
beat("status", statusData);
|
|
@@ -71,49 +71,45 @@ export function createLedgerSink(opts) {
|
|
|
71
71
|
}
|
|
72
72
|
break;
|
|
73
73
|
case "context_usage":
|
|
74
|
-
await flush();
|
|
75
74
|
await append("context_usage", contextUsageEventData(ev));
|
|
76
75
|
break;
|
|
77
76
|
case "compacted":
|
|
78
|
-
await flush();
|
|
79
77
|
await append("compacted", compactedEventData(ev));
|
|
80
78
|
break;
|
|
81
79
|
case "diagnostics":
|
|
82
|
-
await flush();
|
|
83
80
|
await append("diagnostics", diagnosticsEventData(ev));
|
|
84
81
|
break;
|
|
85
82
|
case "steering_injected":
|
|
86
|
-
await flush();
|
|
87
83
|
await append("steering_injected", steeringInjectedEventData(ev));
|
|
88
84
|
break;
|
|
89
85
|
case "compaction_outcome":
|
|
90
|
-
await flush();
|
|
91
86
|
await append("compaction_outcome", compactionOutcomeEventData(ev));
|
|
92
87
|
break;
|
|
93
88
|
case "workspace_changed":
|
|
94
|
-
await flush();
|
|
95
89
|
await append("workspace_changed", workspaceChangedEventData(ev));
|
|
96
90
|
break;
|
|
97
91
|
case "wiring_manifest":
|
|
98
|
-
await flush();
|
|
99
92
|
await append("wiring_manifest", wiringManifestEventData(ev));
|
|
100
93
|
break;
|
|
101
94
|
case "tool_roster_delta": {
|
|
102
|
-
await flush();
|
|
103
95
|
const rosterDelta = toolRosterDeltaEventData(ev);
|
|
104
96
|
if (rosterDelta !== undefined)
|
|
105
97
|
await append("tool_roster_delta", rosterDelta);
|
|
106
98
|
break;
|
|
107
99
|
}
|
|
100
|
+
case "tool_disclosure": {
|
|
101
|
+
const disclosure = toolDisclosureEventData(ev);
|
|
102
|
+
if (disclosure !== undefined)
|
|
103
|
+
await append("tool_disclosure", disclosure);
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
108
106
|
case "human_input":
|
|
109
|
-
await flush();
|
|
110
107
|
await append("human_input", humanInputEventData(ev));
|
|
111
108
|
break;
|
|
112
109
|
case "task_progress":
|
|
113
110
|
await append("task_progress", taskProgressEventData(ev));
|
|
114
111
|
break;
|
|
115
112
|
case "task_notification": {
|
|
116
|
-
await flush();
|
|
117
113
|
const tn = ev.notification;
|
|
118
114
|
const tnKey = tn?.task_id ? taskNotificationStreamKey({ task_type: tn.task_type, task_id: tn.task_id, status: String(tn.status), seq: tn.seq }) : undefined;
|
|
119
115
|
const tnPrior = tnKey !== undefined ? keys.get(tnKey) : undefined;
|
|
@@ -126,7 +122,6 @@ export function createLedgerSink(opts) {
|
|
|
126
122
|
break;
|
|
127
123
|
}
|
|
128
124
|
case "turn_end": {
|
|
129
|
-
await flush();
|
|
130
125
|
const usage = ev.usage;
|
|
131
126
|
await append("turn_end", turnEndEventData(ev));
|
|
132
127
|
textEventId = undefined;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 本帧是不是模型文本的段界。**四条消费腿共用这一只**:账本投影 switch(`trace/ledger-sink.ts`)、
|
|
3
|
+
* 顶层 live 流与转发子代腿(`http/routes/tasks.ts`)、子代 tail 面(`fleet/subagent-tail-bus.ts`)。
|
|
4
|
+
* 未知/未来词一律 `false` —— 不是兜底,是上面三道钉保证「未知词不存在」之后剩下的诚实答案
|
|
5
|
+
* (第三方 Runner 塞进来的野帧不该切走宿主的权威段)。
|
|
6
|
+
*/
|
|
7
|
+
export declare function isModelSegmentBoundary(type: string): boolean;
|
|
8
|
+
/** live 面的段末帧(`type` 即事件名;其余键由 `reasoningEndEventData` 白名单铸出)。 */
|
|
9
|
+
export type ReasoningEndFrame = Record<string, unknown> & {
|
|
10
|
+
type: "reasoning_end";
|
|
11
|
+
};
|
|
12
|
+
/** 登记册**读到的全部字段**,逐个写出来(不是 `Record<string, unknown>`):`TaskEvent` 与转发腿的原始帧
|
|
13
|
+
* 都结构性满足它,于是调用点一个宽断言都不必写;将来有人想读第七个键,得先在这里加一行。 */
|
|
14
|
+
export interface ReasoningStreamEvent {
|
|
15
|
+
type?: string;
|
|
16
|
+
delta?: unknown;
|
|
17
|
+
eventId?: unknown;
|
|
18
|
+
parentToolCallId?: unknown;
|
|
19
|
+
sourceTaskId?: unknown;
|
|
20
|
+
bgAgentId?: unknown;
|
|
21
|
+
}
|
|
22
|
+
/** 一条**流**的身份(不含 `eventId`:core 给每个内容事件铸各自的 eventId,拿它当流身份会把每块 delta
|
|
23
|
+
* 切成一段)。三键都缺席 = 宿主自己的流。 */
|
|
24
|
+
export interface ReasoningStreamIdent {
|
|
25
|
+
parentToolCallId?: string;
|
|
26
|
+
sourceTaskId?: string;
|
|
27
|
+
bgAgentId?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 一条 live 腿上**按流身份分账**的 `reasoning` 段登记册。
|
|
31
|
+
*
|
|
32
|
+
* 🔴 **为什么是登记册而不是一只累积器**(codex 对抗复审 R1 两根 [high],亲跑复现后按类修):一条腿上
|
|
33
|
+
* 并发子代的 `reasoning_delta` 是**交错**到达的。一只累积器只有两种活法,两条都错:
|
|
34
|
+
* · 「换身份就收段」⇒ A 说一半、B 插一句、A 说另一半 ⇒ A 被切成两枚权威段,**跨块的凭据正好落在切口上**
|
|
35
|
+
* ——本帧存在的唯一理由当场失效(实测:两枚 `reasoning_end` 拼起来仍是明文密钥);
|
|
36
|
+
* · 「不换就不收」⇒ 两个子代的思考拼成一段,再盖上其中一个的身份键(串味 + 冤枉归属)。
|
|
37
|
+
* 分账之后规则反而**更少**:一条流的段,只被**它自己**的段界或**它自己**的终态收走;别人说什么都与它无关。
|
|
38
|
+
*
|
|
39
|
+
* 状态的界:每条流的条目在它的首块 delta 上建、在收段时删;腿终局 {@link ReasoningSegments.cut} 不带
|
|
40
|
+
* 匹配器 = 全收全删。子代终态那一次带匹配器的收段(转发腿的 `onTaskNotification`)是让**活着的子代数**
|
|
41
|
+
* 而不是「这条 run 一共派过多少子代」成为上界的那一手。
|
|
42
|
+
*/
|
|
43
|
+
export interface ReasoningSegments {
|
|
44
|
+
/** 喂一帧 → 本帧收走的段末帧(0 或 1 枚,且**只可能是本帧自己那条流**的段)。返回数组只为让调用点
|
|
45
|
+
* 与 {@link cut} 同形(`for (const f of …) send(f)`),不是「一帧可能收多段」。 */
|
|
46
|
+
onEvent(ev: ReasoningStreamEvent): ReasoningEndFrame[];
|
|
47
|
+
/** 收段:`match` 缺省 = 本腿**全部**流(腿终局 / 句柄终态);给了就只收匹配的那几条(某个子代终态)。
|
|
48
|
+
* 与账本腿 `LedgerSink.onDone → flush()` 同位同义 —— 段界表里没有终局词,终局走这扇门。 */
|
|
49
|
+
cut(match?: (ident: ReasoningStreamIdent) => boolean): ReasoningEndFrame[];
|
|
50
|
+
}
|
|
51
|
+
/** 「这条流属于这些子代 id 之一」—— 子代终态收段的匹配器(`task_id` 与 `sessionId` 是同一个子代的两个
|
|
52
|
+
* id 域,取并集;`undefined` 成员自动忽略)。腿上共用这一只,免得各写一遍 id 域的并集。 */
|
|
53
|
+
export declare function reasoningStreamOfTask(...ids: ReadonlyArray<string | undefined>): (ident: ReasoningStreamIdent) => boolean;
|
|
54
|
+
export declare function createReasoningSegments(): ReasoningSegments;
|
|
55
|
+
//# sourceMappingURL=model-segment.d.ts.map
|