@sema-agent/client-core 0.30.7 → 0.31.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/CHANGELOG.md +101 -0
- package/README.md +2 -2
- package/dist/adapt/toolCards.d.ts +11 -1
- package/dist/adapter/activeRunSelfHeal.js +5 -2
- package/dist/adapter/downstream/eventToSdkMessage.js +27 -0
- package/dist/agentSession/backgroundView.d.ts +1 -1
- package/dist/fleet/fleetLedger.d.ts +34 -0
- package/dist/fleet/fleetLedger.js +235 -60
- package/dist/hitl/toolApprovalWire.d.ts +48 -1
- package/dist/hitl/toolApprovalWire.js +36 -13
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/limitsWire.d.ts +3 -3
- package/dist/limitsWire.js +3 -3
- package/dist/printToolResultFrame.d.ts +19 -0
- package/dist/subagent/engineSubagentOutput.js +10 -6
- package/dist/subagent/engineSubagentTail.js +14 -9
- package/dist/subagent/engineTaskHandleWire.js +15 -7
- package/dist/subagent/subagentOwnerAbsence.d.ts +4 -0
- package/dist/subagent/subagentOwnerAbsence.js +48 -0
- package/dist/subagentContentStore.d.ts +5 -0
- package/dist/subagentContentStore.js +10 -2
- package/docs/INTEGRATION-CLIENTS.md +9 -9
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,107 @@
|
|
|
16
16
|
> 🔴 **互链**(web [C166]⑦):各版「已知局限」段只记**该版新增**;接入面已知局限的完整台账在
|
|
17
17
|
> `docs/INTEGRATION-CLIENTS.md` §6e/§7 —— **只读其一会漏**,两处都过。
|
|
18
18
|
|
|
19
|
+
## 0.31.0(未发布)
|
|
20
|
+
|
|
21
|
+
**#242 批 3 fleet belt 对账承重批(design-242 §3 批 3;黑板 [4000] Q2=A / Q3=B 裁定执行)。
|
|
22
|
+
🔴 BREAKING(行为面翻面两处,见下);公开面 additive 三件。**
|
|
23
|
+
|
|
24
|
+
- 🔴 **BREAKING②/Q3 三腿翻面**:`getBgParentRun(x) ?? activeEngineRunId()` 的无条件回落**全部
|
|
25
|
+
移除**(`subagent/engineSubagentTail.ts` / `engineTaskHandleWire.ts:resolveHostRun` /
|
|
26
|
+
`engineSubagentOutput.ts`)。台账缺席时三条读面从「能连上(可能连错 run)」变成**「不连」**:
|
|
27
|
+
tail 不开流、`fetchEngineTaskOutput`/`fetchEngineSubagentReport` 回 null、`stopEngineTask` 回
|
|
28
|
+
`{ok:false, reason:'unavailable', detail:'no host run mapping'}`。理由 = resume 腿同口径
|
|
29
|
+
(「指名了行就诚实缺席」):进程级活跃 run 被并发 main/fork 流覆写,回落值可能指向**不相干的
|
|
30
|
+
run** —— 错值比缺席更坏(读错人 / stop 打错 run)。缺席**不静默**:新导出
|
|
31
|
+
`noteBgOwnerAbsence`(每 (腿,taskId) 至多一条 warn,`__resetBgOwnerAbsenceForTests` 测试钩)。
|
|
32
|
+
消费端零施工即得新行为;若端此前**依赖**回落连当前 run(不推荐),需自己喂
|
|
33
|
+
`recordBgParentRun`/`recordSubagentOwner`。
|
|
34
|
+
- 🔴 **BREAKING①/belt 退休姿势根修**(design-242 §4.1 判 bug:belt 写留存池以「行还在 live 集」
|
|
35
|
+
为前提,而 `project()` 对留存条目一律 `taskMap.has ⇒ continue`,两半互斥 ⇒ 行帧丢失场景 belt
|
|
36
|
+
结构性零效果=「行永恒 running」主症不治)。`applyBgNotification` 终态臂现在把命中的 live 行
|
|
37
|
+
**退休**(`taskMap.delete`+`rowMeta.delete`+`retained.set`,与「终态行帧+同毫秒 task_remove」
|
|
38
|
+
逐段同构);**按尾段定位且全部命中**(server 双生行:裸 id 与 `runId childId` 复合形一起退休);
|
|
39
|
+
**零代际账**(不引入周期号/高水位/复活基准,`droppedStaleCycle*` 计数不复活;回声保护=状态面
|
|
40
|
+
方向规则+通知队列既有 `(taskId,status,seq)` 去重,复活保护=非终态行帧即清账,含双生行别名清扫)。
|
|
41
|
+
- **状态面单一判决点**:留存池行 status 与 `recordBgTerminalFacts` 事实台账共用同一份判决,
|
|
42
|
+
不分叉(REF-CC-040 腿三不变量延续)。**洗绿拦截**:已有终态真值 ∈ {failed,killed} 时
|
|
43
|
+
`completed` 通知不得改写(E4-2/G3);升级(completed→killed/failed)照常后到覆盖。
|
|
44
|
+
- **留存池更新不走 rowIds 门**(E4-6/G7):退休后同 task 的第二条终态通知仍能改写留存条目;
|
|
45
|
+
同值幂等重投不续窗(G5)。
|
|
46
|
+
- **通知面与状态面分开裁**(E4-8/G8):状态面判决任何一支都不吞 `enqueueBgChildNotification`。
|
|
47
|
+
- **E4-7 解耦(additive①)**:`EngineActiveBgTask` 新增 `retiredByNotification?: boolean` ——
|
|
48
|
+
belt 退休且未过 `TERMINAL_RETAIN_MS` 窗的行**继续出现在在飞读面**(`readEngineActiveBgTasks`)
|
|
49
|
+
并带此标,消费方自裁;不消费=保守按在飞计(温切/升级门多 defer ≤60s,换「一条回声不能杀掉
|
|
50
|
+
真在跑子代」)。🔴 **残局 killed 合成(cap 强切/respawn)必须跳过带标行** —— 对已 completed
|
|
51
|
+
行合成 killed 是谎报。窗过即从读面消失;行帧车道重新开口(终态覆盖/非终态复活)即撤标。
|
|
52
|
+
- **accepted 钩子(additive②)**:`FleetLedgerHooks.onBgNotificationAccepted?(n, rowIds)` ——
|
|
53
|
+
own/foreign 判别**通过之后**回调(foreign/畸形不回调);`rowIds` = 到达时刻按键域命中的 live
|
|
54
|
+
行 id 快照(退休前取)。端从此零归属逻辑零复刻(壳侧 meta 两位门同批删)。钩子抛错不拆台账。
|
|
55
|
+
- **additive③**:导出 `FleetBgNotificationWire`(钩子参数型)。
|
|
56
|
+
- 常驻门:pure 门新增 `#242批3` 段(G1-G8 可离线序 + Q3 三腿缺席/反钉 + 洗绿方向规则 + 读面
|
|
57
|
+
解耦窗界),floor 零松量抬升。
|
|
58
|
+
|
|
59
|
+
## 0.30.8(2026-08-15)
|
|
60
|
+
|
|
61
|
+
**server 7.23.0 / core 5.35.0 三键透传批(#281 件4b + #263 半场,[4037] 认领件的 client-core 半场)
|
|
62
|
+
——公开面 **additive**,零 BREAKING;唯一非 additive 的一行是 SDK peer 地板(见末段):**
|
|
63
|
+
|
|
64
|
+
- **`tool_end` 的 `settledBy` / `approver` 到得了宿主了**(#263 半场)。`eventToSdkMessage` 的
|
|
65
|
+
`case 'tool_end'` 臂此前是**闭形白名单**,两键在包边界上被整段剥掉 ⇒ 三端无论怎么写都读不到一条
|
|
66
|
+
**已经在 wire 上**的事实(`toolCallId` [1947] / `governanceForced` [2942] 同款病形第三例)。
|
|
67
|
+
- `settledBy` = 这次调用**是怎么结算的**(core ≥5.18.1 #187:`human`/`timeout`/`aborted`);
|
|
68
|
+
`approver` = **是谁结算的**(core ≥5.35.0 design/252 G-7 / server ≥7.23.0:审批通道自报的身份串)。
|
|
69
|
+
- **两键合读**:`settledBy` 说哪一种收场,`approver` 说是谁的 —— `settledBy:"timeout"` 带 approver
|
|
70
|
+
时,名字指的是**超时的那条队列**,不是拒绝的人。
|
|
71
|
+
- 🔴 **开集读**:`settledBy` 的类型面虽是三词闭集,SDK 头注逐字要求按开集读(词表属主是引擎)
|
|
72
|
+
—— 消费方分支已知值、**永远带 default**,绝不写无 default 的穷举 switch;`approver` 本就无词表。
|
|
73
|
+
- 🔴 **缺席不带语义,不许反推**:`settledBy` 缺席既不等于 `human` 也不等于「没走审批」(旧引擎 /
|
|
74
|
+
姿态臂故意不填,两源同形);`approver` 缺席永不意味「没人批」也永不意味「有人批」。⇒ 本包
|
|
75
|
+
**缺席就不铸键**(绝不折成 null/空串/false)。判运行命运请读 run 行 / checkpoint 面。
|
|
76
|
+
- 🔴 `approver` 是 **UNTRUSTED-for-display**:**转录不是认证**(core 只筛形 ≤256 + 拒控制符/双向
|
|
77
|
+
覆盖符,不鉴别不比对不回读;server 过境再打一遍密钥形码)。只渲染,绝不回喂模型/工具入参,
|
|
78
|
+
更不许当鉴权判据。本层**零加工**(不 trim 不截断不改写),宿主看到的就是通道报的那个字节串。
|
|
79
|
+
- 读面类型:`ToolEndResultArmLike` 补 `settledBy?: unknown` / `approver?: unknown`(该形整体就是
|
|
80
|
+
内部臂的**防御性读形**)。**`ToolEndPayload` 刻意不动** —— 它是关卡切片不是臂镜像,加一个没有
|
|
81
|
+
落点的键 = 一句「关卡会用到它」的假话(同 `errorCode` 自 core 5.9.0 起的既有处置)。
|
|
82
|
+
- **`tool_approval.ruleEvidence` 帧 → 卡透传**(#281 件4b;server ≥7.23.0 / core 5.35.0 design/252
|
|
83
|
+
G-2,`"tool_approval"` only)。`ToolApprovalFrame` / `ApprovalCardRequest` 双侧补键 + 活卡帧腿
|
|
84
|
+
re-mint 处 stamp,壳的 `ruleEvidenceNote` 窄读器(已在位)自此有真值可读。
|
|
85
|
+
- 语义:这只 ask 背后的**规则出处证据** —— 组织策略快照 `orgRevision`、命中的组织规则原文
|
|
86
|
+
`orgRule`、被越级的个人规则 add dots `personalRuleDots`(**构造上就是数组**,按数据渲染/对账,
|
|
87
|
+
绝不 join 成标量 id)。每个成员各自是「值 **⊕** 具名缺席」(`…Absent`;上游五词 `not_wired` /
|
|
88
|
+
`not_adjudicated` / `unavailable` / `no_match` / `not_reported`,**按开集读** —— 词表属主是引擎)。
|
|
89
|
+
- 🔴 **消费方必看**:具名缺席 **≠** 省略键,而且 live 实测**无治理车道**部署下引擎对**每一只**
|
|
90
|
+
ask 都 stamp 全具名缺席(`{"orgRevisionAbsent":"not_wired",…}`)——[4051] 定性为**契约行为非
|
|
91
|
+
缺陷**。⇒ 「在场即渲」的天真消费形会给每张审批卡糊三行 not wired。呈现必须带**谓词**:成员
|
|
92
|
+
**有值**、或缺席词**不在良性白名单**(`unavailable` = 审计链断,恒可见;未知词可见)才占卡面
|
|
93
|
+
一行。该形由 [4051] 背书为正解,web/desktop 渲此键照此抄。
|
|
94
|
+
- 🔴 **窄读域只许等于或宽于铸点域,绝不更窄**(随批立的消费纪律):整只拒读语义下「更窄」会**连坐
|
|
95
|
+
吞掉**同记录里兄弟成员的合法证据。
|
|
96
|
+
- 🔴 **包只判载体形**(非 null 非数组对象)+ 原样搬运(类型 = `unknown` 开集,与 `probeCause` 同裁);
|
|
97
|
+
成员级窄读与呈现谓词**单源在端**。两键共用同一把载体判词(`isProbeCauseCarrier` 更名
|
|
98
|
+
`isWireRecordCarrier`,私有函数,公开面零变化)。
|
|
99
|
+
- 🔴 **耐久路今天无对偶**:sdk 7.1.0 只在 `ToolApprovalFrame` 上声明该键,`PendingCheckpoint` /
|
|
100
|
+
`riskDescriptor` 两处都没有 ⇒ durable 行 → 卡那条腿**刻意零 stamp**(不去猜一个载体名:猜对了
|
|
101
|
+
是恒 undefined 的白写,猜错了是拿另一个量冒充同一位),已登记进 durable 门的未投影账。上游补位
|
|
102
|
+
后按 `probeCause` 的双源合流形跟批。
|
|
103
|
+
- **键镜像追平**:`TOOL_APPROVAL_FRAME_KEYS_MIRROR` 16 → **17 键**(补 `ruleEvidence`)。sdk 7.1.0 的
|
|
104
|
+
运行期锚**已含** `probeCause` 与 `ruleEvidence` 两键 ⇒ 0.30.4 立的 `probeCause` **领先登记
|
|
105
|
+
(AHEAD_OF_ANCHOR)按它自己的退出条件同批删除**,领先表**回空**=镜像与锚逐元素相等、零豁免。
|
|
106
|
+
|
|
107
|
+
**🔴 唯一非 additive 的一行 —— SDK peer 地板 `>=6.17.2` → `>=7.1.0`(跨 major)**:
|
|
108
|
+
|
|
109
|
+
- **本包代码面对 6.x 消费者零破坏**(三键全是开集防御读,不依赖 7.x 的任何运行期符号);抬地板是
|
|
110
|
+
**两道门的合取结论**,不是版本洁癖:①`run-approval-frame-keys-test.mjs` 是**等值**门,而
|
|
111
|
+
`ruleEvidence` 只在 sdk ≥7.1.0 的运行期锚上出现,<7.1.0 的实装物上本包镜像会被判「多键」;
|
|
112
|
+
②`run-public-surface-test.mjs` ⑤ 要求**声明地板不低于见证到的最低实装版本** —— devDep 与 sema-cli
|
|
113
|
+
树同批上 7.1.0 后,盘上再无 6.17.2 那条线的实装物,继续声明 `>=6.17.2` 就是承诺一条从没有物料
|
|
114
|
+
证明跑过的下线(README 同段逐字:*"A floor nobody ever ran is a promise, not a contract"*)。
|
|
115
|
+
- **消费侧影响**([4050] 已点名):cli / web 已提 7.1.0;**desktop 仍在 6.16.0**,在它跟提之前装本包
|
|
116
|
+
会看到 peer 警告(不是安装失败,也不是运行期错误)。
|
|
117
|
+
- sdk 7.0.0 的 BREAKING 面(images/bakes 输入收窄 + `ImageSelectResult` 形变)对本包**零受迫**:
|
|
118
|
+
`src/**` 对 `images.` / `bakes` / `ImageSelect` / `.select(` 全树零调用点(本批亲跑直证)。
|
|
119
|
+
|
|
19
120
|
## 0.30.7(2026-08-15)
|
|
20
121
|
|
|
21
122
|
**#242 批 2 subagent 归属单表 + resume 腿归位(A-028.6/.7 收编)——公开面 additive,一处行为面加强:**
|
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ Renamed from **`@sema-agent/wire-cc-adapter`** (0.1.x, deprecated — see *Migra
|
|
|
35
35
|
|
|
36
36
|
## Scope
|
|
37
37
|
|
|
38
|
-
**Version:** 0.
|
|
38
|
+
**Version:** 0.31.0
|
|
39
39
|
|
|
40
40
|
- **Today** — the adapter seam, the whole `adapt()` pipeline (all 14 A-layer arms plus the
|
|
41
41
|
B/D/E tool-card layers), the notification/caps/model families, the adapter kernel (stream driver
|
|
@@ -65,7 +65,7 @@ Renamed from **`@sema-agent/wire-cc-adapter`** (0.1.x, deprecated — see *Migra
|
|
|
65
65
|
`SseIdleError`, `probeHealth`, `APIError` and `TaskStopConflictError` are imported as values in
|
|
66
66
|
five modules, and the browser bundle really bundles the SDK through (the portability guard would
|
|
67
67
|
exit 3 rather than quietly mark it external).
|
|
68
|
-
- The declared floor is `>=
|
|
68
|
+
- The declared floor is `>=7.1.0`, and it is *witnessed*: the guard checks that an actually
|
|
69
69
|
installed SDK at that line still exports every value-level symbol this package imports and still
|
|
70
70
|
declares `TaskStats.costMicroUsd` (the key `costOrNull` reads). A floor nobody ever ran is a
|
|
71
71
|
promise, not a contract.
|
|
@@ -32,7 +32,17 @@ export interface PendingToolUse {
|
|
|
32
32
|
eventId?: string;
|
|
33
33
|
parentToolCallId?: string;
|
|
34
34
|
}
|
|
35
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* `tool_end_result` 帧上**与关卡有关的那几位**(原 `buildToolResult` 的内联入参形,具名化)。
|
|
37
|
+
*
|
|
38
|
+
* 🔴 本形**不是** `tool_end_result` 臂的镜像,是它的**关卡切片** —— 只收「铸这张 tool_result 卡真正
|
|
39
|
+
* 要读的位」。臂上另有若干**开集观察位**按设计**不进本形**:`errorCode`(core 5.9.0 W3)、
|
|
40
|
+
* `settledBy`(core 5.18.1 #187)、`approver`(core 5.35.0 / server 7.23.0 #263)。它们由
|
|
41
|
+
* `eventToSdkMessage` 的 `case 'tool_end'` 臂原样上臂、由**宿主/桥**直接从臂上读(壳 upstreamBridge、
|
|
42
|
+
* print 车道 {@link import('../printToolResultFrame.js').ToolEndResultArmLike}),卡台账一位都不消费。
|
|
43
|
+
* 往本形加一个没有落点的键 = 一句「关卡会用到它」的假话(声明与产物对不上,且没有任何门会响);
|
|
44
|
+
* 真要让转录卡带上结算归属,那是一件**新的呈现设计**(需定 `_sema_` 超集键 + 台账登记),不是透传。
|
|
45
|
+
*/
|
|
36
46
|
export interface ToolEndPayload {
|
|
37
47
|
output?: unknown;
|
|
38
48
|
structured?: unknown;
|
|
@@ -21,8 +21,11 @@
|
|
|
21
21
|
* ② 审批/提问门(human / irreversible_ask / tool_approval)⇒ **重开那张卡**(把决定权还给
|
|
22
22
|
* 用户;cancel+自动重发臂已整退役 —— 实证它是「新消息→cancel→新 run 同 ask 再 park」的
|
|
23
23
|
* 循环病根;重开失败也只如实告知,绝不回退 cancel)。
|
|
24
|
-
* ③ `running`
|
|
25
|
-
*
|
|
24
|
+
* ③ `running` ⇒ **呈 steer/cancel/wait 三选卡**(runningChoiceArm;offerRunningChoice 供给
|
|
25
|
+
* 缺席即回落 not-parked 现状行不动)。持有 claim 的可能是用户背景化的 durable run,正在替
|
|
26
|
+
* 他干活——绝不见 409 就自动 cancel,决定权给用户。新结局五个:running-steered /
|
|
27
|
+
* running-steer-failed / running-cancelled / running-cancel-timeout / running-cancel-failed;
|
|
28
|
+
* cancel 后走有界等 claim 释放腿(waitForClaimRelease,CANCEL_RELEASE_WAIT_MS 封顶)。
|
|
26
29
|
* ④ kind/status 两级都判不出(含今天还不存在的新门/新状态)⇒ 也什么都不做,如实告知。
|
|
27
30
|
* 不确定时不做破坏性动作;新词到货时最坏结果是保守,而不是拿没想过的词做破坏性动作。
|
|
28
31
|
*
|
|
@@ -205,6 +205,26 @@ export function eventToSdkMessage(ev, ctx) {
|
|
|
205
205
|
// 绝不按识别表过滤(过滤 = 替引擎决定哪些原因配得上被看见)。判别用
|
|
206
206
|
// `engineErrorCodes.isInterruptedToolEndCode`,不要在消费点重新写字面比较。
|
|
207
207
|
const toolEndErrorCode = ev.errorCode;
|
|
208
|
+
// ── #263 半场(0.30.8):`settledBy`(core ≥5.18.1,#187)/ `approver`(core ≥5.35.0
|
|
209
|
+
// design/252 G-7,server ≥7.23.0)—— 这次调用**是怎么结算的**与**是谁结算的**。
|
|
210
|
+
// 两键在 SDK 头注里明说要**合读**:`settledBy` 说这次收场是哪一种,`approver` 说是谁的
|
|
211
|
+
// (`settledBy:"timeout"` 带 approver 时,名字指的是**超时的那条队列**,不是拒绝的人)。
|
|
212
|
+
// 此前本臂是闭形白名单,两键在包边界上被整段剥掉 ⇒ 宿主(壳/web/desktop)无论怎么写都读不到
|
|
213
|
+
// 一条**已经在 wire 上**的事实 —— [1947] `toolCallId` / [2942] `governanceForced` 同款病形第三例。
|
|
214
|
+
// 🔴 **开集防御读**(与上面 `errorCode` 同姿势):`settledBy` 的类型面虽是三词闭集,SDK 头注却
|
|
215
|
+
// 逐字要求「按**开集**读、分支已知值、忽略其余」—— 词表属主是引擎,窄读成枚举会在加员当天把
|
|
216
|
+
// 一个**合法**值判没;`approver` 本就是无词表的开集身份串。两键一律「非空串即原样透传」,
|
|
217
|
+
// 绝不按识别表过滤,也绝不 trim/截断/改写(core 只筛形 ≤256 且拒控制符/双向覆盖符,server
|
|
218
|
+
// 过境再打一遍密钥形码;本层任何加工都会让宿主看到的名字不再是通道报的那一个)。
|
|
219
|
+
// 🔴 **缺席不带语义,不许反推**(SDK 头注逐字):`settledBy` 缺席至少两源(旧引擎;姿态臂如
|
|
220
|
+
// headless auto-deny / 整体 `onAsk` 放行 —— 那些结局里没有「结算来源」这回事),既不等于
|
|
221
|
+
// `human` 也不等于「没走审批」;`approver` 缺席 = 通道没报名字,永不意味「没人批」也永不
|
|
222
|
+
// 意味「有人批」。⇒ 缺席就**不铸键**(绝不折成 null / 空串 / false)。判这次运行的命运读
|
|
223
|
+
// run 行 / checkpoint 面,别拿这两键当判据。
|
|
224
|
+
// 🔴 **UNTRUSTED-for-display**:`approver` 是部署侧审批通道**自报**的身份串,转录不是认证
|
|
225
|
+
// (core 不鉴别、不比对、不回读)—— 只渲染,绝不回喂模型/工具入参,更不许当鉴权判据。
|
|
226
|
+
const toolEndSettledBy = ev.settledBy;
|
|
227
|
+
const toolEndApprover = ev.approver;
|
|
208
228
|
return projected(stamp(ctx, armBody({
|
|
209
229
|
type: 'tool_end_result',
|
|
210
230
|
toolCallId: ev.toolCallId,
|
|
@@ -220,6 +240,13 @@ export function eventToSdkMessage(ev, ctx) {
|
|
|
220
240
|
...(ev.output !== undefined ? { output: ev.output } : {}),
|
|
221
241
|
...(structured !== undefined ? { structured } : {}),
|
|
222
242
|
...(ev.truncated !== undefined ? { truncated: ev.truncated } : {}),
|
|
243
|
+
// #263 半场:结算种类 + 结算方身份(开集、缺席不铸、在场原样;顶部大段头注是判据本体)。
|
|
244
|
+
...(typeof toolEndSettledBy === 'string' && toolEndSettledBy.length > 0
|
|
245
|
+
? { settledBy: toolEndSettledBy }
|
|
246
|
+
: {}),
|
|
247
|
+
...(typeof toolEndApprover === 'string' && toolEndApprover.length > 0
|
|
248
|
+
? { approver: toolEndApprover }
|
|
249
|
+
: {}),
|
|
223
250
|
...(ev.eventId !== undefined ? { eventId: ev.eventId } : {}),
|
|
224
251
|
...(ev.parentToolCallId !== undefined
|
|
225
252
|
? { parentToolCallId: ev.parentToolCallId }
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
* `src/subagent/engineTaskHandleWire.ts`(`POST /v1/runs/:id/tasks/:handle/stop?session=`)。
|
|
65
65
|
* **handle 取法**:复合 id `"<runId> <taskId>"` 取尾段 = 引擎 taskId —— 包内已有同语义实现
|
|
66
66
|
* `rowIdTail()`(`src/workflow.ts:24`,导出;`src/fleetAgentPanelProjection.ts:94` 有零依赖叶
|
|
67
|
-
* 的私有同款副本);宿主 run 解析走 `getBgParentRun(handle)
|
|
67
|
+
* 的私有同款副本);宿主 run 解析走 `getBgParentRun(handle)`(#242 批 3 起台账缺席即诚实缺席,绝不回落活跃 run)。
|
|
68
68
|
* **能力门** = `capabilities.taskHandles`(`src/subagent/engineRowStopGate.ts`
|
|
69
69
|
* `engineTaskHandlesCapable()`),🔴 别 trial-by-404。
|
|
70
70
|
* **回执纪律**(同文件 `engineRowNeedsStopConfirm()`):只有 **200 算停了**;409
|
|
@@ -52,6 +52,20 @@ export interface EngineActiveBgTask {
|
|
|
52
52
|
/** fleet 行的 parentId(spawn 该子代的 run 行 id;顶层 run 行缺席)—— killed 残局合成通知的
|
|
53
53
|
* own/foreign 判别用。 */
|
|
54
54
|
parentId?: string;
|
|
55
|
+
/**
|
|
56
|
+
* 🔴 #242 批 3(E4-7 解耦,design-242 §2.2-5):true = 该行由 **belt(终态通知)退休**、且仍在
|
|
57
|
+
* `TERMINAL_RETAIN_MS` 窗内。行帧车道**没有**为它 settle 过(终态行帧丢失,状态由通知车道推定)。
|
|
58
|
+
* 语义与消费纪律:
|
|
59
|
+
* · 这行**按在飞计**是保守默认(在读面里出现 ⇒ 温切/升级门 defer)——通知车道没有代际号,
|
|
60
|
+
* 误退休(旧周期回声退休了复活行)不可排除,而「多等一窗可恢复,杀错在跑子代不可恢复」;
|
|
61
|
+
* · 消费方**可以自裁**:它的 `status` 是终态词(诚实),带本标 ⇒ 「通知说它完了、行帧没确认」。
|
|
62
|
+
* 🔴 残局 killed 合成(温切 cap 强切 / configDrift respawn)必须**跳过**带本标的行 ——
|
|
63
|
+
* 对一条已 completed 的行合成 killed 通知是谎报;
|
|
64
|
+
* · 窗过(`TERMINAL_RETAIN_MS`)即从读面消失;行帧车道一旦重新开口(终态行帧到达覆盖 /
|
|
65
|
+
* 非终态行帧复活)本标即撤。
|
|
66
|
+
* 行帧车道 settle 的行(终态行帧 + task_remove)**从不**带本标,也从不进这个读面(与旧行为同)。
|
|
67
|
+
*/
|
|
68
|
+
retiredByNotification?: boolean;
|
|
55
69
|
}
|
|
56
70
|
export interface EngineActiveBgReading {
|
|
57
71
|
tasks: EngineActiveBgTask[];
|
|
@@ -106,6 +120,18 @@ export interface FleetLedgerHooks {
|
|
|
106
120
|
* 返回值忽略;不给 = 该帧被静默丢弃(端没有承载面时的正确行为)。
|
|
107
121
|
*/
|
|
108
122
|
onHookNotice?(frame: HookNoticeFrame, sessionScoped: boolean): void;
|
|
123
|
+
/**
|
|
124
|
+
* 🔴 #242 批 3(design-242 §2.2-3,壳侧 fleetClient「长线正解已记账:请 client-core 在归属判定
|
|
125
|
+
* **通过之后**给一个 accepted 钩子,壳改接它」的兑现):`bg_notification` 帧经本台账的
|
|
126
|
+
* own/foreign 判别(serverFailClosed / ownByRoot / parentTaskId ∈ own-run 台账)**放行之后**回调。
|
|
127
|
+
* · 被隔离丢弃(foreign)/畸形早退的通知**不**回调 —— 钩子的语义就是「归属判定已通过」;
|
|
128
|
+
* · 终态与非终态通知都回调(状态筛选归消费方);
|
|
129
|
+
* · `rowIds` = 到达时刻 live 集里按键域(整行 id + 尾段)命中的行 id(可能为空 = 行帧车道
|
|
130
|
+
* 没有这行;可能多条 = server 双生行)。快照取在退休动作**之前**;
|
|
131
|
+
* · 钩子抛错不拆台账(try/catch + debug 留痕),与帧消费循环隔离。
|
|
132
|
+
* 端从此**零归属逻辑、零复刻**(壳侧 meta 两位门 [cross-repo-fix-at-source] 同批整条删)。
|
|
133
|
+
*/
|
|
134
|
+
onBgNotificationAccepted?(n: FleetBgNotificationWire, rowIds: readonly string[]): void;
|
|
109
135
|
}
|
|
110
136
|
/** REF-CC-044(fleet2-06)/REF-CC-039(fleet2-01):`createFleetLedger` 的可选装配位。 */
|
|
111
137
|
export interface CreateFleetLedgerOptions {
|
|
@@ -162,3 +188,11 @@ export interface FleetLedger {
|
|
|
162
188
|
* (这条切缝就是设计稿 §2.5 给 fleetClient 划的那一刀:帧体归库、连接归端。)
|
|
163
189
|
*/
|
|
164
190
|
export declare function createFleetLedger(hooks?: FleetLedgerHooks, opts?: CreateFleetLedgerOptions): FleetLedger;
|
|
191
|
+
/**
|
|
192
|
+
* `bg_notification` 帧的通知体 wire 形(SDK `FleetFrame` 联合的具名臂投影)。
|
|
193
|
+
* #242 批 3 起公开导出 —— `FleetLedgerHooks.onBgNotificationAccepted` 的参数型,端(壳/web/桌面)
|
|
194
|
+
* 接钩子要能写出这个签名。开集读纪律不变:键随 SDK 走,消费方防御读。
|
|
195
|
+
*/
|
|
196
|
+
export type FleetBgNotificationWire = Extract<FleetFrame, {
|
|
197
|
+
type: 'bg_notification';
|
|
198
|
+
}>['notification'];
|
|
@@ -4,7 +4,7 @@ import { engineSessionParam } from '../engineSessionParam.js';
|
|
|
4
4
|
// `isHookNoticeFrame` 不在这里 re-export —— 它已由 index.ts 的 `export * from './notifications.js'`
|
|
5
5
|
// 出口(重复 re-export 只会让同名绑定在包根变含糊)。端要先分诊再喂台账,从包根 import 即可。
|
|
6
6
|
import { enqueueBgChildNotification, isOwnWorkflowRun } from '../notifications.js';
|
|
7
|
-
import { isOwnEngineRun, recordBgParentRun, recordBgTerminalFacts, recordOwnEngineRun, } from '../subagentContentStore.js';
|
|
7
|
+
import { clearBgTerminalFacts, getBgTerminalFacts, isOwnEngineRun, recordBgParentRun, recordBgTerminalFacts, recordOwnEngineRun, } from '../subagentContentStore.js';
|
|
8
8
|
import { rowIdTail } from '../workflow.js';
|
|
9
9
|
import { recordEngineTranscriptId } from '../subagent/engineDelegatedPrompt.js';
|
|
10
10
|
import { DEFAULT_SESSION_KEY } from '../sessionSlot.js';
|
|
@@ -111,7 +111,10 @@ export function createFleetLedger(hooks = {}, opts = {}) {
|
|
|
111
111
|
/** [1481]①「elapsed 恒 0s」补偿:记每行**本地接收**时刻(引擎只在活动边界重发行)。
|
|
112
112
|
* REF-CC-039:字段标域改名 `receivedAtMs` —— 装的是 `nowFn()`,绝不是 wire 上的 `frame.ts`。 */
|
|
113
113
|
const rowMeta = new Map();
|
|
114
|
-
/** [1481]②「完成即消失」补偿:终态行进留存池,宽限窗内继续投影。`expireAt` 同样锚本地钟。
|
|
114
|
+
/** [1481]②「完成即消失」补偿:终态行进留存池,宽限窗内继续投影。`expireAt` 同样锚本地钟。
|
|
115
|
+
* #242 批 3:`retiredByNotification` 标 = 该条目由 belt(终态通知)退休写入,行帧车道没为它
|
|
116
|
+
* settle 过 —— 在飞读面(`bgView.read`)对带标且未过窗的条目**继续按在飞计**(E4-7 解耦,
|
|
117
|
+
* 见 `EngineActiveBgTask.retiredByNotification` 顶注);行帧路径写入的条目从不带标。 */
|
|
115
118
|
const retained = new Map();
|
|
116
119
|
/** REF-CC-051(fleet2-13):畸形帧/通知早退计数(与同文件的策略性丢弃 debug 行对称留痕)。 */
|
|
117
120
|
let droppedMalformed = 0;
|
|
@@ -128,27 +131,48 @@ export function createFleetLedger(hooks = {}, opts = {}) {
|
|
|
128
131
|
if (engineWireDebugEnabled())
|
|
129
132
|
hostLog('debug', line);
|
|
130
133
|
};
|
|
134
|
+
/** wire 行 → 在飞读面行的单一投影点(live 臂与 belt 退休臂共用;两臂各写一份就会在 name/status
|
|
135
|
+
* 的三态处理上漂开)。 */
|
|
136
|
+
const toBgTask = (r, retired) => {
|
|
137
|
+
const parentId = wireParentId(r);
|
|
138
|
+
return {
|
|
139
|
+
id: r.id,
|
|
140
|
+
// REF-CC-046(fleet2-08,含 TYPESHAPE-06):与 parentId 同款条件展开 —— `name` 是三态位
|
|
141
|
+
// (server ≥3.5.1 起真的会缺席),无条件落键会让「键在不在」判三态在序列化边界翻面。
|
|
142
|
+
...(typeof r.name === 'string' && r.name.length > 0 ? { name: r.name } : {}),
|
|
143
|
+
// REF-CC-052(fleet2-14):`status` 是 SDK 必填闭集,但脏值/缺席仍可能到达 wire —— 不知道
|
|
144
|
+
// 就说不知道,`'unknown'` 是自描述哨兵,绝不编造一个具体的、看似活跃的 `'running'`。
|
|
145
|
+
status: r.status ?? 'unknown',
|
|
146
|
+
// REF-CC-049(fleet2-11):经 wireParentId 单一铸点(悬空父脏值空串按缺席处理)。eopt:
|
|
147
|
+
// 局部变量先取一次(两次裸调用之间 TS 不共享窄化,双调用会重新变宽成 string|undefined)。
|
|
148
|
+
...(parentId !== undefined ? { parentId } : {}),
|
|
149
|
+
...(retired ? { retiredByNotification: true } : {}),
|
|
150
|
+
};
|
|
151
|
+
};
|
|
131
152
|
const bgView = {
|
|
132
|
-
read: () =>
|
|
133
|
-
|
|
134
|
-
rows: [...taskMap.values()]
|
|
153
|
+
read: () => {
|
|
154
|
+
const rows = [...taskMap.values()]
|
|
135
155
|
.filter(r => !TERMINAL_FLEET_TASK_STATUSES.has(r.status ?? ''))
|
|
136
|
-
.map(r =>
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
156
|
+
.map(r => toBgTask(r, false));
|
|
157
|
+
// 🔴 #242 批 3(E4-7 解耦,design-242 §2.2-5):belt 退休、仍在留存窗内的行**继续出现在
|
|
158
|
+
// 在飞读面**(带 `retiredByNotification` 标,消费方自裁;不消费该标 = 保守按在飞计)。
|
|
159
|
+
// 为什么不能直接消失:这条读面的下游是引擎温切门(`readEngineActiveBgTasks` →
|
|
160
|
+
// `engineSwapGate` 空集即 SIGTERM 旧引擎)—— 通知车道无代际号,误退休(旧周期回声)
|
|
161
|
+
// 不可排除;从读面瞬间消失 = 一条回声就能杀掉真在跑的子代,不可恢复且无留痕。
|
|
162
|
+
// 窗界:`expireAt`(= 退休时刻 + TERMINAL_RETAIN_MS)自查,不依赖 `project()` 的惰性清扫
|
|
163
|
+
// (headless 车道可能整窗不投影);live 行回场(复活/终态行帧)即让位(taskMap 优先)。
|
|
164
|
+
const nowMs = nowFn();
|
|
165
|
+
for (const [id, r] of retained) {
|
|
166
|
+
if (r.retiredByNotification !== true)
|
|
167
|
+
continue;
|
|
168
|
+
if (nowMs > r.expireAt)
|
|
169
|
+
continue;
|
|
170
|
+
if (taskMap.has(id))
|
|
171
|
+
continue;
|
|
172
|
+
rows.push(toBgTask(r.row, true));
|
|
173
|
+
}
|
|
174
|
+
return { connected, rows };
|
|
175
|
+
},
|
|
152
176
|
sessionScoped: () => sessionScopedMeta,
|
|
153
177
|
clearRetained: () => {
|
|
154
178
|
retained.clear();
|
|
@@ -190,6 +214,19 @@ export function createFleetLedger(hooks = {}, opts = {}) {
|
|
|
190
214
|
for (const row of frame.tasks) {
|
|
191
215
|
taskMap.set(row.id, row);
|
|
192
216
|
rowMeta.set(row.id, { receivedAtMs });
|
|
217
|
+
// #242 批 3(codex P-F1,红先绿后=B3-G4b):snapshot(重连 REPLACE)腿与增量 task 帧腿
|
|
218
|
+
// 的复活语义**同权** —— snapshot 带回的非终态行,其全部别名(同尾段)的 belt 退休条目
|
|
219
|
+
// 一并清,否则重连别名切换(裸 id 退休 → 复合 id running 回归)会产出「复合 running +
|
|
220
|
+
// 裸 completed(带标)」双影。终态行不清(与增量腿同判);snapshot 里**没有**的任务,
|
|
221
|
+
// 留存条目照旧保留(R7 反钉:重连不抽走已完成行)。
|
|
222
|
+
if (!TERMINAL_FLEET_TASK_STATUSES.has(row.status ?? '')) {
|
|
223
|
+
const tail = rowIdTail(row.id);
|
|
224
|
+
for (const id of [...retained.keys()]) {
|
|
225
|
+
if (rowIdTail(id) === tail)
|
|
226
|
+
retained.delete(id);
|
|
227
|
+
}
|
|
228
|
+
clearBgTerminalFacts(tail); // 复活=新周期,与增量腿同权(B3-N3)
|
|
229
|
+
}
|
|
193
230
|
}
|
|
194
231
|
for (const row of frame.workflows)
|
|
195
232
|
wfMap.set(row.id, row);
|
|
@@ -242,9 +279,28 @@ export function createFleetLedger(hooks = {}, opts = {}) {
|
|
|
242
279
|
// 的 `!retained.has()` 前提失守,第二周期的终态被首周期的陈旧快照挡在门外。
|
|
243
280
|
if (TERMINAL_FLEET_TASK_STATUSES.has(row.status ?? '')) {
|
|
244
281
|
retained.set(row.id, { row, expireAt: receivedAtMs + TERMINAL_RETAIN_MS });
|
|
282
|
+
// 终态行帧到达=行帧车道开口(顶注契约:本标即撤)——双生行**另一别名**的 belt 退休
|
|
283
|
+
// 条目一并删(0.31.0 扫码 P3,B3-N2),否则同一任务整 60s 窗按在飞多算。
|
|
284
|
+
const tailT = rowIdTail(row.id);
|
|
285
|
+
for (const id of [...retained.keys()]) {
|
|
286
|
+
if (id !== row.id && rowIdTail(id) === tailT)
|
|
287
|
+
retained.delete(id);
|
|
288
|
+
}
|
|
245
289
|
}
|
|
246
290
|
else {
|
|
247
291
|
retained.delete(row.id);
|
|
292
|
+
// #242 批 3(G4 双生行半场):复活 = 这个任务活着,**全部别名**(裸 id ↔ `runId childId`
|
|
293
|
+
// 复合形共享同一尾段 = 引擎 taskId 键域)的陈旧终态快照一并清 —— 只清整 id 会留下另一
|
|
294
|
+
// 别名的 belt 退休条目,project 会把它当第二条行渲出来(一活一死的双影),读面也会把
|
|
295
|
+
// 一条真在跑的任务同时按「退休中」计。尾段全局唯一(引擎 handle 域),不会误清邻行。
|
|
296
|
+
const tail = rowIdTail(row.id);
|
|
297
|
+
for (const id of [...retained.keys()]) {
|
|
298
|
+
if (id !== row.id && rowIdTail(id) === tail)
|
|
299
|
+
retained.delete(id);
|
|
300
|
+
}
|
|
301
|
+
// 复活=新周期:旧周期终态事实作废(0.31.0 扫码 P2,B3-N3)——不清则 factsAccepted
|
|
302
|
+
// 的方向核把上周期 killed 当先例,第二周期合法 completed 被当洗绿回声拒收。
|
|
303
|
+
clearBgTerminalFacts(tail);
|
|
248
304
|
}
|
|
249
305
|
break;
|
|
250
306
|
}
|
|
@@ -320,58 +376,177 @@ export function createFleetLedger(hooks = {}, opts = {}) {
|
|
|
320
376
|
return;
|
|
321
377
|
}
|
|
322
378
|
debug(`[fleet-frame] bg_notification taskId=${n.taskId} status=${n.status} seq=${n.seq ?? '-'}`);
|
|
379
|
+
// 键域(#242 批 3):`n.taskId` = registry handle = 行 id 的**尾段**。live 集按「整行 id 或
|
|
380
|
+
// 尾段命中」收齐**全部**别名(server 双生行:裸 id 与 `runId childId` 复合形同时在场时两条
|
|
381
|
+
// 一起处理)—— 快照取在任何退休动作之前,原样交给 accepted 钩子。首段(runId)绝不作键
|
|
382
|
+
// (同 run 兄弟行共享首段,按它匹配 = 一条通知误退休全部并发兄弟)。
|
|
383
|
+
const rowIds = [];
|
|
384
|
+
for (const id of taskMap.keys()) {
|
|
385
|
+
if (id === n.taskId || rowIdTail(id) === n.taskId)
|
|
386
|
+
rowIds.push(id);
|
|
387
|
+
}
|
|
323
388
|
if (TERMINAL_FLEET_TASK_STATUSES.has(n.status)) {
|
|
324
389
|
if (typeof n.parentTaskId === 'string' && n.parentTaskId) {
|
|
325
390
|
recordBgParentRun(n.taskId, n.parentTaskId);
|
|
326
391
|
}
|
|
327
392
|
// `transcriptId`(server 1.278.0)= 委派 prompt 的取件锚。现势:通知帧是它**今天真的在场**
|
|
328
393
|
// 的主要位置(行帧那条腿已同款接好,引擎一发即亮 —— 见 case 'task')。
|
|
394
|
+
// (寻址/取件锚不随下方状态面判决走:回声通知携带的 id 映射仍是真映射。)
|
|
329
395
|
if (typeof n.transcriptId === 'string' && n.transcriptId.length > 0) {
|
|
330
396
|
recordEngineTranscriptId(n.taskId, n.transcriptId);
|
|
331
397
|
}
|
|
398
|
+
// ── #242 批 3 belt 根修(design-242 §4.1 判 bug;E4-1/2/6/7 覆盖,零代际账)────────────
|
|
399
|
+
// 旧形的配对失配:belt 只在 `taskMap.get(n.taskId)` 命中时写 `retained`,而 `project()` 对
|
|
400
|
+
// 留存条目一律 `taskMap.has(id) ⇒ continue` —— 两条件互斥,belt 写进去的那条当拍必被跳过,
|
|
401
|
+
// 唯一显形路径是随后的 `task_remove`,而 belt 存在的全部理由恰是「终态行帧(与它同毫秒的
|
|
402
|
+
// remove)丢了」⇒ 对主症(行永恒 running)结构性零效果。
|
|
403
|
+
// 新形:通知命中 live 行 ⇒ **退休**(`taskMap.delete` + `retained.set`),与「终态行帧 +
|
|
404
|
+
// 同毫秒 task_remove」那条路逐段同构;定位按尾段且全部命中(双生行一起退休)。
|
|
405
|
+
//
|
|
406
|
+
// 🔴 状态面单一判决点(两读面 —— 留存池行 status 与 `recordBgTerminalFacts` 台账 —— 共用
|
|
407
|
+
// 同一份判决,绝不各判各的;REF-CC-040 腿三「不分叉」不变量的延续):
|
|
408
|
+
// · **洗绿拦截**(E4-2/G3 的可执行形):已有终态真值 ∈ {failed, killed} 时,`completed`
|
|
409
|
+
// 通知不得改写它 —— 把失败洗成成功是伤害型谎报。「已终态行不覆盖 retained」守卫按
|
|
410
|
+
// 方向落地而非按字面:字面全禁会与 REF-CC-040 腿三(行帧 completed 在先、通知 killed
|
|
411
|
+
// 在后 ⇒ 通知是 settle 权威,两读面必须同说 killed)直接矛盾 —— 同为「行车道已终态 +
|
|
412
|
+
// 通知异词」,升级(completed→killed/failed)是新知识,降级洗绿是回声。
|
|
413
|
+
// · **不引入任何代际账**(E4-1/3/4/5 的共同宿主整套不复活):回声保护 = 方向规则(无状态)
|
|
414
|
+
// + `enqueueBgChildNotification` 既有 (taskId,status,seq) 去重;复活保护 = 非终态行帧
|
|
415
|
+
// 即清账(case 'task' else 臂,含双生行别名清扫)。`droppedStaleCycle*` 计数不复活。
|
|
416
|
+
// · **无早退**(E4-8/G8):状态面判决无论落哪支,通知面(`enqueueBgChildNotification`)
|
|
417
|
+
// 与 accepted 钩子照常执行 —— 通知面与状态面分开裁。
|
|
418
|
+
const isWashGreen = (prior, next) => next === 'completed' && (prior === 'failed' || prior === 'killed');
|
|
419
|
+
const coerced = n.status;
|
|
420
|
+
const nowMs = nowFn();
|
|
421
|
+
const writtenIds = new Set();
|
|
422
|
+
let applied = false;
|
|
423
|
+
let washBlocked = false;
|
|
424
|
+
for (const id of rowIds) {
|
|
425
|
+
const known = taskMap.get(id);
|
|
426
|
+
if (!known)
|
|
427
|
+
continue;
|
|
428
|
+
const rowStatus = known.status ?? '';
|
|
429
|
+
if (TERMINAL_FLEET_TASK_STATUSES.has(rowStatus)) {
|
|
430
|
+
// 行车道已 settle 本行(终态行帧在先):retained 已由 case 'task' 写入行帧真值。
|
|
431
|
+
if (isWashGreen(rowStatus, n.status)) {
|
|
432
|
+
washBlocked = true;
|
|
433
|
+
debug(`[fleet-frame] bg_notification WASH-GREEN blocked(row=${id} row-status=${rowStatus} notif=${n.status})`);
|
|
434
|
+
continue; // G3:`task_remove` 之后投影仍是行帧真值(failed),不被通知洗绿
|
|
435
|
+
}
|
|
436
|
+
if (rowStatus !== n.status) {
|
|
437
|
+
// 升级(REF-CC-040 腿三):通知是 settle 权威,两读面同步改写,不分叉。
|
|
438
|
+
retained.set(id, { row: { ...known, status: coerced }, expireAt: nowMs + TERMINAL_RETAIN_MS });
|
|
439
|
+
}
|
|
440
|
+
writtenIds.add(id);
|
|
441
|
+
applied = true;
|
|
442
|
+
// live 终态行不动(remove 帧随后清;终态行不进在飞读面,留着无害且行车道语义完整)。
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
// 退休:live 行离开在飞集,进留存池,带 E4-7 解耦标(见 bgView.read)。
|
|
446
|
+
// elapsed 在退休时刻**铸一次冻结值**(project() 对 live 行的同一公式):行帧丢失场景
|
|
447
|
+
// wire 上最后一帧的 elapsedMs 是陈旧值(常为 0)——不铸的话完成行的计时器渲 0,
|
|
448
|
+
// 与 A-024.8「翻 status 与冻 elapsed 必须同一刀」同病。诚实边界:冻的是通知到达时刻,
|
|
449
|
+
// 是上界不是精确完成时刻(通知不带可信完成时钟)。
|
|
450
|
+
const started = known.startedAt;
|
|
451
|
+
const meta = rowMeta.get(id);
|
|
452
|
+
const frozenElapsed = typeof started === 'number' && started > 0 && started <= nowMs
|
|
453
|
+
? nowMs - started
|
|
454
|
+
: meta !== undefined
|
|
455
|
+
? (known.elapsedMs ?? 0) + Math.max(0, nowMs - meta.receivedAtMs)
|
|
456
|
+
: known.elapsedMs;
|
|
457
|
+
taskMap.delete(id);
|
|
458
|
+
rowMeta.delete(id);
|
|
459
|
+
retained.set(id, {
|
|
460
|
+
row: {
|
|
461
|
+
...known,
|
|
462
|
+
status: coerced,
|
|
463
|
+
...(typeof frozenElapsed === 'number' ? { elapsedMs: frozenElapsed } : {}),
|
|
464
|
+
},
|
|
465
|
+
expireAt: nowMs + TERMINAL_RETAIN_MS,
|
|
466
|
+
retiredByNotification: true,
|
|
467
|
+
});
|
|
468
|
+
writtenIds.add(id);
|
|
469
|
+
applied = true;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
// E4-6(G7):留存池更新**不以 live 行在场为前提**,按尾段独立成路 —— 退休后同 task 的
|
|
473
|
+
// 第二条终态通知(completed→killed)必须仍能改写留存池,否则面板绿而详情页 killed。
|
|
474
|
+
for (const [id, entry] of retained) {
|
|
475
|
+
if (writtenIds.has(id))
|
|
476
|
+
continue;
|
|
477
|
+
if (id !== n.taskId && rowIdTail(id) !== n.taskId)
|
|
478
|
+
continue;
|
|
479
|
+
const prior = entry.row.status ?? '';
|
|
480
|
+
if (prior === n.status) {
|
|
481
|
+
applied = true; // 幂等重投(G5):同值不动、**不续窗**(回声风暴不得无限延长读面在飞窗)
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
if (isWashGreen(prior, n.status)) {
|
|
485
|
+
washBlocked = true;
|
|
486
|
+
debug(`[fleet-frame] bg_notification WASH-GREEN blocked(retained=${id} prior=${prior} notif=${n.status})`);
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
retained.set(id, {
|
|
490
|
+
row: { ...entry.row, status: coerced },
|
|
491
|
+
expireAt: nowMs + TERMINAL_RETAIN_MS, // 状态真变 = 新知识,续窗让修正可见
|
|
492
|
+
...(entry.retiredByNotification === true ? { retiredByNotification: true } : {}),
|
|
493
|
+
});
|
|
494
|
+
applied = true;
|
|
495
|
+
}
|
|
332
496
|
// [1481]① 查看态事实喂给:终态通知带 summary+recentSteps,这是端当前能拿到的关于后台子代
|
|
333
497
|
// 结局的【全部】内容(终报正文不在 wire 上)。查看态对无终报的终态行渲染这些真实事实。
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
// · **非终态行帧晚于 belt** ⇒ 抹掉 belt 结果(`case 'task'` 的 `retained.delete`)。这正是
|
|
365
|
-
// 复活语义:行又活了,首周期的终态快照本就不该继续投影。钉:pure 门「复活(非终态)
|
|
366
|
-
// 一到就清留存条目 ⇒ 首周期陈旧终态行不得复现为幽灵」。
|
|
367
|
-
// 要改成 seq 择新形,先把这两条断言的期望值一起改 —— 它们是这个取舍的可执行形。
|
|
368
|
-
const known = taskMap.get(n.taskId);
|
|
369
|
-
if (known) {
|
|
370
|
-
retained.set(n.taskId, {
|
|
371
|
-
row: { ...known, status: n.status },
|
|
372
|
-
expireAt: nowFn() + TERMINAL_RETAIN_MS,
|
|
498
|
+
// 🔴 与留存池共用上方同一份判决:状态面收下才记账;洗绿回声整条不入事实台账(status 记
|
|
499
|
+
// completed 而面板渲 failed = 同一帧两写分叉,REF-CC-040 腿三的反面)。零命中且零拦截
|
|
500
|
+
// (行帧车道整条丢失、留存已过窗)⇒ 按既有事实台账自身做同方向核(killed/failed 在账,
|
|
501
|
+
// completed 回声不得洗绿它)。
|
|
502
|
+
const factsAccepted = applied ||
|
|
503
|
+
(!washBlocked &&
|
|
504
|
+
(() => {
|
|
505
|
+
const prior = getBgTerminalFacts(n.taskId);
|
|
506
|
+
return !(prior !== undefined && isWashGreen(prior.status, n.status));
|
|
507
|
+
})());
|
|
508
|
+
if (factsAccepted) {
|
|
509
|
+
recordBgTerminalFacts(n.taskId, {
|
|
510
|
+
status: n.status,
|
|
511
|
+
...(typeof n.summary === 'string' ? { summary: n.summary } : {}),
|
|
512
|
+
// 🔴 SDK 0.0.117 把 `recentSteps` 的三键 declare 成**必填** string,但那是**声称**不是保证:
|
|
513
|
+
// 帧从 wire 上来,脏项(null / 缺键 / 非串)在类型面之外仍可能到达,而 BgTerminalFacts
|
|
514
|
+
// 那侧三键**全可选** ⇒ 这里的逐键 typeof 不是「防御读没退干净」,是 wire 脏值过滤(与
|
|
515
|
+
// fleetProjection 的 `wire*` 值域校验同一档次)。谓词类型必须是元素型本身,不能是
|
|
516
|
+
// `Record<string, unknown>`(那不是参数型的子型 ⇒ TS2677)。
|
|
517
|
+
...(Array.isArray(n.recentSteps)
|
|
518
|
+
? {
|
|
519
|
+
recentSteps: n.recentSteps
|
|
520
|
+
.filter((s) => Boolean(s) && typeof s === 'object')
|
|
521
|
+
.map(s => ({
|
|
522
|
+
...(typeof s.tool === 'string' ? { tool: s.tool } : {}),
|
|
523
|
+
...(typeof s.target === 'string' ? { target: s.target } : {}),
|
|
524
|
+
...(typeof s.outcome === 'string' ? { outcome: s.outcome } : {}),
|
|
525
|
+
})),
|
|
526
|
+
}
|
|
527
|
+
: {}),
|
|
373
528
|
});
|
|
374
529
|
}
|
|
530
|
+
else {
|
|
531
|
+
debug(`[fleet-frame] bg_notification state-plane rejected as wash-green echo(taskId=${n.taskId} notif=${n.status})—— 通知面照常入队`);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
// accepted 钩子(#242 批 3):归属判别已通过;终态/非终态都回调,状态筛选归消费方。
|
|
535
|
+
// 钩子抛错不拆帧消费(留痕即可)。
|
|
536
|
+
try {
|
|
537
|
+
// 钩子类型虽 void,宿主传 async 函数合法(P-F3):返回 thenable 时必须承接拒绝,
|
|
538
|
+
// 否则 async 消费方一炸就是 unhandledRejection = Node 缺省整进程崩(TUI 最坏形,
|
|
539
|
+
// engineSubagentTail meta sink 同族先例)。同步抛与异步拒同一条留痕出口。
|
|
540
|
+
const out = hooks.onBgNotificationAccepted?.(n, rowIds);
|
|
541
|
+
if (out && typeof out.then === 'function') {
|
|
542
|
+
void Promise.resolve(out).catch((e) => {
|
|
543
|
+
hostLog('debug', `[fleet-frame] onBgNotificationAccepted hook rejected: ${String(e).slice(0, 160)}`);
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
catch (e) {
|
|
548
|
+
// 消费端钩子的异常不许拆帧消费循环;留痕走 hostLog(端的 debug 汇集面)。
|
|
549
|
+
hostLog('debug', `[fleet-frame] onBgNotificationAccepted hook threw: ${String(e).slice(0, 160)}`);
|
|
375
550
|
}
|
|
376
551
|
enqueueBgChildNotification({
|
|
377
552
|
taskId: n.taskId,
|
|
@@ -250,6 +250,24 @@ export interface ApprovalCardRequest {
|
|
|
250
250
|
* 卡形字节不变。UNTRUSTED-for-display:code/roots 是工具作文面,只渲染绝不回喂模型/工具入参。
|
|
251
251
|
*/
|
|
252
252
|
probeCause?: unknown;
|
|
253
|
+
/**
|
|
254
|
+
* 规则出处证据(#281 件4b,0.30.8;server ≥7.23.0 / core 5.35.0 design/252 G-2)——原样来自
|
|
255
|
+
* {@link ToolApprovalFrame.ruleEvidence} 的**载体形合形值**(单源:活卡帧腿;durable 行腿今天
|
|
256
|
+
* 无对偶,见帧上同名键的 JSDoc 末段)。缺席 = 老 server / 坏载体形(两者同形,不猜),卡形与
|
|
257
|
+
* 0.30.7 字节不变。
|
|
258
|
+
*
|
|
259
|
+
* 🔴 **开集(unknown),包只搬运**:成员级窄读(具名缺席词、矛盾形丢成员、整只拒读)与**呈现
|
|
260
|
+
* 谓词**全部归端。呈现谓词不是可选项 —— 无治理车道部署下引擎对**每一只** ask 都 stamp 全具名
|
|
261
|
+
* 缺席([4050] live 实测 / [4051] 定性为契约行为),「在场即渲」会给每张卡糊三行 not wired。
|
|
262
|
+
* 端侧形照 [4051] 背书的正解:成员**有值**、或缺席词**不在良性白名单**(`unavailable` = 审计链
|
|
263
|
+
* 断,恒可见;未知词可见)才占卡面一行;窄读侧仍如实留全部成员。
|
|
264
|
+
* 🔴 **展示/对账元数据,永不是裁决输入**:它不参与也不改变本次决断的任何字节。
|
|
265
|
+
* 🔴 **不是身份通道**(与 {@link persistedRuleShadowed} 同族警示):`orgRule` 是脱敏后的展示文本、
|
|
266
|
+
* `personalRuleDots` 是耐久身份**数组**——下游删除/撤销规则面不许拿 `orgRule` 当规则身份,也不许
|
|
267
|
+
* 把 dots join 成一个标量 id;要操作规则走规则店自己的枚举与标识。
|
|
268
|
+
* UNTRUSTED-for-display:只渲染,绝不回喂模型/工具入参。
|
|
269
|
+
*/
|
|
270
|
+
ruleEvidence?: unknown;
|
|
253
271
|
}
|
|
254
272
|
/**
|
|
255
273
|
* 🔴 **拆缝口** —— 弹「三选卡」并等人的决断。壳 = vendored CC `PermissionRequest`;
|
|
@@ -386,6 +404,35 @@ export interface ToolApprovalFrame {
|
|
|
386
404
|
* UNTRUSTED-for-display:code/roots 是工具作文面(server 已 redact),端呈前消毒,只渲染绝不回喂。
|
|
387
405
|
*/
|
|
388
406
|
probeCause?: unknown;
|
|
407
|
+
/**
|
|
408
|
+
* server ≥7.23.0 / core 5.35.0 design/252 G-2(#281 件4b,0.30.8;**ADDITIVE**,`"tool_approval"`
|
|
409
|
+
* only。运行期锚直证 = sdk 7.1.0 `TOOL_APPROVAL_FRAME_KEYS` 已含本键)——这只 ask 背后的
|
|
410
|
+
* **规则出处证据**:组织策略快照 `orgRevision`、命中的组织规则原文 `orgRule`、被越级的个人规则
|
|
411
|
+
* add dots `personalRuleDots`(规则的耐久身份,**构造上就是数组**——按数据渲染/对账,绝不 join
|
|
412
|
+
* 成一个标量 id)。
|
|
413
|
+
*
|
|
414
|
+
* 🔴 **每个成员各自是「值 或 具名缺席」**(`…Absent`,上游五词 + 开集读:词表属主是引擎)。
|
|
415
|
+
* `not_wired` 没有这条车道 / `not_adjudicated` 车道在但这次没问 / `unavailable` **问了而源读不出**
|
|
416
|
+
* (唯一意味「审计链断了」而不是「空」的那个词——手上这只 ask 正是随后的 fail-closed 收紧) /
|
|
417
|
+
* `no_match` 问了没人说话 / `not_reported` 缝答了但没带身份。
|
|
418
|
+
* 🔴 **具名缺席 ≠ 省略键**,而且 [4050] live 实测:**无治理车道**部署下 7.23.0 对**每一只** ask 都
|
|
419
|
+
* stamp 本键为全具名缺席(`{"orgRevisionAbsent":"not_wired",…}`)——[4051] 定性为**契约行为非缺陷**
|
|
420
|
+
* (把「没接治理」从裸 undefined 里拆出来正是该键的设计承重)。⇒ 「在场即渲」的天真消费形会给每张
|
|
421
|
+
* 审批卡糊三行 not wired;呈现必须带**谓词**(成员有值、或缺席词不在良性白名单 —— `unavailable`
|
|
422
|
+
* 恒可见、未知词可见),那一层归端(壳 `ruleEvidenceNote` 单源,[4051] 背书为正解形)。
|
|
423
|
+
* 🔴 **展示/对账元数据,永不是裁决输入**;server 只转录引擎盖的记录,自己绝不铸缺席词。
|
|
424
|
+
* 🔴 **本包刻意不拥有这个形**(类型 = unknown 开集,与 {@link probeCause} 同裁):载体形判
|
|
425
|
+
* (非 null 非数组对象)之外零校验零改写。成员级窄读**单源在端**——包内校严一档,就会把上游
|
|
426
|
+
* additive 成员挡在边界外,还让「谁把成员窄没的」在排障时说不清;而端侧窄读器有一条更硬的纪律:
|
|
427
|
+
* **窄读域只许等于或宽于铸点域,绝不更窄**([4050] 随批立)——整只拒读语义下「更窄」会连坐吞掉
|
|
428
|
+
* 同记录里兄弟成员的合法证据。
|
|
429
|
+
* 🔴 **缺席 ≠「没有治理」**:帧上无本键 = 老 server,或窄读丢了一条畸形记录 —— 两者同形,不猜。
|
|
430
|
+
* 耐久路今天**无对偶**(sdk 7.1.0 的 `PendingCheckpoint` / `riskDescriptor` 均未声明本键),故
|
|
431
|
+
* durable 行 → 卡那条腿不 stamp;上游补位后按 {@link probeCause} 的双源合流形跟批。
|
|
432
|
+
* UNTRUSTED-for-display:`orgRule` 是策略作文面(core `inlineUntrusted` + server redact 后上帧),
|
|
433
|
+
* 端呈前消毒,只渲染绝不回喂模型/工具入参。
|
|
434
|
+
*/
|
|
435
|
+
ruleEvidence?: unknown;
|
|
389
436
|
}
|
|
390
437
|
/** {@link ToolApprovalFrame.delegation} 的形(命名形,不用内联匿名 —— typeshape 门 B4 棘轮口径)。 */
|
|
391
438
|
export interface ToolApprovalDelegation {
|
|
@@ -401,7 +448,7 @@ export interface ToolApprovalDelegation {
|
|
|
401
448
|
* `TOOL_APPROVAL_FRAME_KEYS` 比对——SDK additive 增键时对账当天红,不再人肉追平。
|
|
402
449
|
* 下面两个类型钉保证镜像与 interface 本身不可能漂移(少键/多键都是编译错)。
|
|
403
450
|
*/
|
|
404
|
-
export declare const TOOL_APPROVAL_FRAME_KEYS_MIRROR: readonly ["type", "approvalId", "toolCallId", "toolName", "sourceTaskId", "fromSubagent", "sourceAgentName", "message", "args", "argsOmitted", "governanceForced", "ruleSuggestions", "persistedRuleShadowed", "probeCause", "delegation", "outcome"];
|
|
451
|
+
export declare const TOOL_APPROVAL_FRAME_KEYS_MIRROR: readonly ["type", "approvalId", "toolCallId", "toolName", "sourceTaskId", "fromSubagent", "sourceAgentName", "message", "args", "argsOmitted", "governanceForced", "ruleSuggestions", "persistedRuleShadowed", "probeCause", "ruleEvidence", "delegation", "outcome"];
|
|
405
452
|
/** 子代帧判别:显式键 fromSubagent(core 1.378 RB-39②)优先;缺席退 sourceTaskId 在场性权宜式
|
|
406
453
|
* (server 1.258 [1549]①3,旧代际兼容)。 */
|
|
407
454
|
export declare function isFromSubagent(frame: ToolApprovalFrame): boolean;
|
|
@@ -258,7 +258,13 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
|
|
|
258
258
|
// #280 件1(0.30.4):durable 行腿的探针因由载体 = `riskDescriptor.probeCause`(server 对
|
|
259
259
|
// riskDescriptor 整体透传,与活卡帧同值)。SDK 6.17.2 的 riskDescriptor 声明尚无此键 ⇒ 结构
|
|
260
260
|
// 视图读(与 caps 防御读同款姿势),类型半场候 SDK 班车;载体形不合 ⇒ 不铸键(内部结构不校,
|
|
261
|
-
// 归呈卡端窄读器 ——
|
|
261
|
+
// 归呈卡端窄读器 —— isWireRecordCarrier 顶注)。
|
|
262
|
+
// 🔴 **`ruleEvidence` 在本腿刻意零 stamp**(#281 件4b,0.30.8):sdk 7.1.0 的 `PendingCheckpoint`
|
|
263
|
+
// 与 `riskDescriptor` 都**没有**这一位 —— 上游今天只在活卡帧上铸它,耐久路无对偶。凭一个
|
|
264
|
+
// 「大概也叫这个名字」的猜测去读 `riskDescriptor.ruleEvidence`,读到的恒是 undefined(白写),
|
|
265
|
+
// 而万一上游选了别的载体名/别的语义,那就是拿一个不同的量冒充同一位。⇒ 诚实零投影 + 登记
|
|
266
|
+
// (run-durable-card-display-keys-test.mjs 的 NOT_PROJECTED 账),上游补位后按 probeCause 的
|
|
267
|
+
// 双源合流形跟批。
|
|
262
268
|
const durableProbeCause = pending.riskDescriptor?.probeCause;
|
|
263
269
|
const card = await surfaceApprovalCard({
|
|
264
270
|
toolName,
|
|
@@ -267,7 +273,7 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
|
|
|
267
273
|
...(signal ? { signal } : {}),
|
|
268
274
|
...(pending.governanceForced === true ? { governanceForced: true } : {}),
|
|
269
275
|
...(ruleSuggestionsReadOnly !== undefined ? { ruleSuggestionsReadOnly } : {}),
|
|
270
|
-
...(
|
|
276
|
+
...(isWireRecordCarrier(durableProbeCause) ? { probeCause: durableProbeCause } : {}),
|
|
271
277
|
});
|
|
272
278
|
switch (card.kind) {
|
|
273
279
|
case 'failed':
|
|
@@ -354,10 +360,16 @@ export const TOOL_APPROVAL_FRAME_KEYS_MIRROR = [
|
|
|
354
360
|
// 领先登记(AHEAD_OF_ANCHOR):SDK 锚一旦补上,那条登记当场红,逼人删登记而不是让豁免长住。
|
|
355
361
|
'persistedRuleShadowed',
|
|
356
362
|
// #280 件1(client-core 0.30.4):server 7.21.0-rc(#253)起真发 `probeCause`(直证 = engine
|
|
357
|
-
// 7.22.0 fixture 的 `@sema-agent/server/dist/tool-approval.d.ts`)
|
|
358
|
-
// 一代(sdk 6.17.2 尚无)⇒
|
|
359
|
-
//
|
|
363
|
+
// 7.22.0 fixture 的 `@sema-agent/server/dist/tool-approval.d.ts`)。当时本键**领先** SDK 运行期锚
|
|
364
|
+
// 一代(sdk 6.17.2 尚无)⇒ 对账门里挂着一条带退出条件的领先登记(AHEAD_OF_ANCHOR)。
|
|
365
|
+
// ✅ **锚已追平**(0.30.8 提货,sdk 7.1.0 的 `TOOL_APPROVAL_FRAME_KEYS` 已含本键)⇒ 那条登记按它
|
|
366
|
+
// 自己的退出条件当场翻红、同批删除,回到逐元素相等 —— #229 那批删 `persistedRuleShadowed`
|
|
367
|
+
// 登记时走的是同一条路。领先表回空 = 健康态。
|
|
360
368
|
'probeCause',
|
|
369
|
+
// #281 件4b(client-core 0.30.8):server 7.23.0 / core 5.35.0 design/252 G-2 起真发 `ruleEvidence`。
|
|
370
|
+
// ⚠️ 与上面两例**不同形**:sdk 7.1.0 的运行期锚**已经含**本键(node 直读实证)⇒ 这是一次**追平**,
|
|
371
|
+
// 不是领先,故**不**进 AHEAD_OF_ANCHOR(往那张表里塞一个锚已有的键,它的第二条退出条件当场红)。
|
|
372
|
+
'ruleEvidence',
|
|
361
373
|
'delegation',
|
|
362
374
|
'outcome',
|
|
363
375
|
];
|
|
@@ -470,13 +482,18 @@ function pathFromGateMessage(message) {
|
|
|
470
482
|
* 掩盖注入,整体降缺席)。
|
|
471
483
|
*/
|
|
472
484
|
/**
|
|
473
|
-
*
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
*
|
|
477
|
-
*
|
|
485
|
+
* **开集记录键的载体形判**(#280 件1 起 `probeCause` 用;#281 件4b 起 `ruleEvidence` 同用):
|
|
486
|
+
* 非 null 非数组对象才搬运 —— 这两键的 wire 契约载体都是**记录**,串/数/数组/null 是坏载体,
|
|
487
|
+
* 不是「另一种因由 / 另一种证据」。
|
|
488
|
+
*
|
|
489
|
+
* 🔴 刻意**只判到载体这一层**:内部成员的校验(probeCause 的 code/roots/further;ruleEvidence 的
|
|
490
|
+
* 值 ⊕ 具名缺席词、矛盾形、整只拒读)归**呈卡端窄读器**(壳 `probeCauseNote.readProbeCauseKey` /
|
|
491
|
+
* `ruleEvidenceNote.readRuleEvidenceKey`,各自与 server 铸点 `readProbeCause` / `readRuleEvidence`
|
|
492
|
+
* 同向)。包内校严一档就会把上游 additive 内键挡在边界外,还让「谁把键窄没的」在排障时多一层嫌疑。
|
|
493
|
+
* 🔴 **一把判词服务多键**是设计而非省事:两条决断腿(活帧 / durable 行)× 多个开集记录键共用同一个
|
|
494
|
+
* 载体判据,判词绝不各写各的(各写一份 = 同一个问题有了两个属主,分叉那天没人发现)。
|
|
478
495
|
*/
|
|
479
|
-
function
|
|
496
|
+
function isWireRecordCarrier(v) {
|
|
480
497
|
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
481
498
|
}
|
|
482
499
|
function readRuleSuggestions(v) {
|
|
@@ -592,8 +609,14 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
|
|
|
592
609
|
? { persistedRuleShadowed: frame.persistedRuleShadowed }
|
|
593
610
|
: {}),
|
|
594
611
|
// #280 件1(0.30.4):探针因由随卡透传 —— 载体形判之外零校验零改写(内部结构归呈卡端窄读器,
|
|
595
|
-
//
|
|
596
|
-
...(
|
|
612
|
+
// isWireRecordCarrier 顶注);缺席/坏载体 ⇒ 键不 stamp,卡形字节不变。
|
|
613
|
+
...(isWireRecordCarrier(frame.probeCause) ? { probeCause: frame.probeCause } : {}),
|
|
614
|
+
// #281 件4b(0.30.8):规则出处证据随卡透传 —— 同上,只判载体形、**成员级一概不校**
|
|
615
|
+
// (值 ⊕ 具名缺席词、未知缺席词、矛盾形一律原样上卡:判决权单源在端侧窄读器,
|
|
616
|
+
// 且端侧窄读域只许 ≥ 铸点域)。缺席/坏载体 ⇒ 键不 stamp,卡形与 0.30.7 字节不变。
|
|
617
|
+
// 🔴 呈现谓词归端(无治理部署下每只 ask 都带全具名缺席,[4050] live 实测 / [4051] 定性);
|
|
618
|
+
// 本层若替端做「全 not_wired 就别给了」的过滤,就是替引擎把一条真事实湮灭掉。
|
|
619
|
+
...(isWireRecordCarrier(frame.ruleEvidence) ? { ruleEvidence: frame.ruleEvidence } : {}),
|
|
597
620
|
});
|
|
598
621
|
const decision = card.kind === 'allow' ? (card.allowSession ? 'allow_session' : 'allow') : 'deny';
|
|
599
622
|
if (card.kind === 'failed') {
|
package/dist/index.d.ts
CHANGED
|
@@ -207,6 +207,7 @@ export * from './subagent/engineSubagentResume.js';
|
|
|
207
207
|
export * from './subagent/engineTaskHandleWire.js';
|
|
208
208
|
export * from './subagent/engineCompactWire.js';
|
|
209
209
|
export * from './subagent/engineSubagentTail.js';
|
|
210
|
+
export * from './subagent/subagentOwnerAbsence.js';
|
|
210
211
|
export * from './engineSessionParam.js';
|
|
211
212
|
export * from './engineWireTarget.js';
|
|
212
213
|
export * from './principalWire.js';
|
package/dist/index.js
CHANGED
|
@@ -269,6 +269,8 @@ export * from './subagent/engineSubagentResume.js';
|
|
|
269
269
|
export * from './subagent/engineTaskHandleWire.js';
|
|
270
270
|
export * from './subagent/engineCompactWire.js';
|
|
271
271
|
export * from './subagent/engineSubagentTail.js';
|
|
272
|
+
// #242 批 3([4000] Q3=B):三腿宿主 run 台账缺席的响亮留痕单源(honest-absence 翻面配套)。
|
|
273
|
+
export * from './subagent/subagentOwnerAbsence.js';
|
|
272
274
|
export * from './engineSessionParam.js';
|
|
273
275
|
export * from './engineWireTarget.js';
|
|
274
276
|
// ── A-028 族E(#244 F3,2026-08-15)────────────────────────────────────────────────────────────
|
package/dist/limitsWire.d.ts
CHANGED
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
* value (integer domain below; a mis-typed budget bound must not quietly run unbounded).
|
|
43
43
|
* b) settings env lane (settings.json `env` block → 1a-envseed → process.env):
|
|
44
44
|
* `SEMA_HEADLESS_DEADLINE_SEC` / `SEMA_HEADLESS_MAX_TURNS` / `SEMA_HEADLESS_MAX_TOKENS`
|
|
45
|
-
* →
|
|
46
|
-
*
|
|
47
|
-
*
|
|
45
|
+
* → 同 a) FAIL-LOUD on an invalid value(#132 / test [2508] 2026-08-04 推翻旧 batch-E
|
|
46
|
+
* 「静默降级」形;见 headlessLimitsFromEnv 的 JSDoc——非法值 {ok:false} 上抛,caller exit 1,
|
|
47
|
+
* 常驻门 run-limits-env-failloud-test.mjs 守此形)。
|
|
48
48
|
* c) neither → NO stamp = today's behaviour, verbatim.
|
|
49
49
|
*
|
|
50
50
|
* DOMAINS (integer, inclusive): deadline 30..86400 **秒**(→ 30_000..86_400_000 ms 上 wire)·
|
package/dist/limitsWire.js
CHANGED
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
* value (integer domain below; a mis-typed budget bound must not quietly run unbounded).
|
|
43
43
|
* b) settings env lane (settings.json `env` block → 1a-envseed → process.env):
|
|
44
44
|
* `SEMA_HEADLESS_DEADLINE_SEC` / `SEMA_HEADLESS_MAX_TURNS` / `SEMA_HEADLESS_MAX_TOKENS`
|
|
45
|
-
* →
|
|
46
|
-
*
|
|
47
|
-
*
|
|
45
|
+
* → 同 a) FAIL-LOUD on an invalid value(#132 / test [2508] 2026-08-04 推翻旧 batch-E
|
|
46
|
+
* 「静默降级」形;见 headlessLimitsFromEnv 的 JSDoc——非法值 {ok:false} 上抛,caller exit 1,
|
|
47
|
+
* 常驻门 run-limits-env-failloud-test.mjs 守此形)。
|
|
48
48
|
* c) neither → NO stamp = today's behaviour, verbatim.
|
|
49
49
|
*
|
|
50
50
|
* DOMAINS (integer, inclusive): deadline 30..86400 **秒**(→ 30_000..86_400_000 ms 上 wire)·
|
|
@@ -58,6 +58,25 @@ export interface ToolEndResultArmLike {
|
|
|
58
58
|
/** 引擎 CC-shaped details(core 1.199+ AgentToolResult.details;Bash 时带 exitCode)。 */
|
|
59
59
|
structured?: unknown;
|
|
60
60
|
truncated?: unknown;
|
|
61
|
+
/**
|
|
62
|
+
* 这次调用**是怎么结算的**(core ≥5.18.1 #187;`human`/`timeout`/`aborted` + 开集读)与
|
|
63
|
+
* **是谁结算的**(core ≥5.35.0 design/252 G-7 / server ≥7.23.0 #263;审批通道自报的身份串,
|
|
64
|
+
* 无词表)。#263 半场(0.30.8)起由 `eventToSdkMessage` 的 `case 'tool_end'` 臂原样透传到本臂,
|
|
65
|
+
* 宿主据此把结算归属渲到工具卡/审计面。
|
|
66
|
+
*
|
|
67
|
+
* 🔴 两键**合读**:`settledBy` 说这次收场是哪一种,`approver` 说是谁的 —— `settledBy:"timeout"`
|
|
68
|
+
* 带 approver 时,名字指的是**超时的那条队列**,不是拒绝的人。
|
|
69
|
+
* 🔴 **缺席不带语义,不许反推**:`settledBy` 缺席既不等于 `human` 也不等于「没走审批」(旧引擎 /
|
|
70
|
+
* 姿态臂故意不填,两源同形);`approver` 缺席 = 通道没报名字,永不意味「没人批」也永不意味
|
|
71
|
+
* 「有人批」。判这次运行的命运读 run 行 / checkpoint 面。
|
|
72
|
+
* 🔴 `approver` 是 **UNTRUSTED-for-display**:转录不是认证(core 只筛形不鉴别不比对不回读),
|
|
73
|
+
* 可信度 = 那条审批通道的可信度;只渲染,绝不回喂模型/工具入参,更不许当鉴权判据。
|
|
74
|
+
* 🔴 本形是**防御性读形**(全员 `unknown`):`settledBy` 的类型面虽是三词闭集,但词表属主是引擎,
|
|
75
|
+
* 消费方必须**分支已知值 + 永远带 default**,绝不写无 default 的穷举 switch。
|
|
76
|
+
*/
|
|
77
|
+
settledBy?: unknown;
|
|
78
|
+
/** 见 {@link settledBy} —— 两键同批同源(#263 半场,0.30.8),合读、缺席不反推、转录不是认证。 */
|
|
79
|
+
approver?: unknown;
|
|
61
80
|
parentToolCallId?: unknown;
|
|
62
81
|
uuid?: unknown;
|
|
63
82
|
session_id?: unknown;
|
|
@@ -33,7 +33,8 @@ import { hostLog } from '../host.js';
|
|
|
33
33
|
import { makeEngineWireClient } from '../engineWireSdk.js';
|
|
34
34
|
import { engineWireTarget } from '../engineWireTarget.js';
|
|
35
35
|
import { getBgParentRun } from '../subagentContentStore.js';
|
|
36
|
-
import {
|
|
36
|
+
import { engineSessionParam } from '../engineSessionParam.js';
|
|
37
|
+
import { noteBgOwnerAbsence } from './subagentOwnerAbsence.js';
|
|
37
38
|
// ── caps 探测(true 固化 / false TTL / 失败不缓存)────────────────────────────────────────────
|
|
38
39
|
const capByBase = new Map();
|
|
39
40
|
const CAP_FALSE_RETRY_TTL_MS = 5 * 60_000;
|
|
@@ -64,12 +65,15 @@ export async function fetchEngineSubagentReport(taskId, opts) {
|
|
|
64
65
|
const cfg = engineWireTarget();
|
|
65
66
|
if (!cfg)
|
|
66
67
|
return null;
|
|
67
|
-
// 🔴
|
|
68
|
-
// `resolveOwnerRunId`(
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
// 🔴 #242 批 3([4000] Q3=B 裁定执行,行为翻面):台账缺席即**诚实缺席,绝不回落**
|
|
69
|
+
// `activeEngineRunId()` —— 与 resume 腿 `resolveOwnerRunId` 同口径(错值比缺席更坏:回落
|
|
70
|
+
// 到「此刻在飞的 run」对 session-bound 旧 run 是 404 撞墙,对同名子代是读错人)。缺席不
|
|
71
|
+
// 静默(noteBgOwnerAbsence warn 留痕);调用方按既有 null 回落 bg 事实渲染,fail-soft。
|
|
72
|
+
const runId = getBgParentRun(taskId);
|
|
73
|
+
if (!runId) {
|
|
74
|
+
noteBgOwnerAbsence('subagent-output', taskId);
|
|
72
75
|
return null;
|
|
76
|
+
}
|
|
73
77
|
const client = makeEngineWireClient({
|
|
74
78
|
baseUrl: cfg.baseUrl,
|
|
75
79
|
...(cfg.token ? { token: cfg.token } : {}),
|
|
@@ -43,14 +43,16 @@
|
|
|
43
43
|
*
|
|
44
44
|
* 生命周期:查看态进入开,退出/终态/行消失关;每 taskId 至多一条(幂等);caps 探测
|
|
45
45
|
* capabilities.subagentStream(true 固化/false TTL/失败不缓存,engineSubagentOutput 同纪律);
|
|
46
|
-
* 寻址 runId=getBgParentRun
|
|
46
|
+
* 寻址 runId=getBgParentRun **唯一源**(#242 批 3 起台账缺席即诚实缺席不回落,warn 留痕)、
|
|
47
|
+
* 无条件带 `?session=`(同 [1498]③ 纪律)。
|
|
47
48
|
* 一切失败 fail-soft:tail 断=回到占位行现状,绝不 throw。
|
|
48
49
|
*/
|
|
49
50
|
import { hostLog } from '../host.js';
|
|
50
51
|
import { makeEngineWireClient } from '../engineWireSdk.js';
|
|
51
52
|
import { engineWireTarget } from '../engineWireTarget.js';
|
|
52
|
-
import {
|
|
53
|
+
import { engineSessionParam } from '../engineSessionParam.js';
|
|
53
54
|
import { coerceOutput, getBgParentRun, parentToolCallIdOf, publishSubagentContentEvent, settleSubagentContent, } from '../subagentContentStore.js';
|
|
55
|
+
import { noteBgOwnerAbsence } from './subagentOwnerAbsence.js';
|
|
54
56
|
// ── caps 探测(true 固化 / false TTL / 失败不缓存)────────────────────────────────────────────
|
|
55
57
|
const capByBase = new Map();
|
|
56
58
|
const CAP_FALSE_RETRY_TTL_MS = 5 * 60_000;
|
|
@@ -114,14 +116,17 @@ export function tailEngineSubagent(taskId) {
|
|
|
114
116
|
const cfg = engineWireTarget();
|
|
115
117
|
if (!cfg)
|
|
116
118
|
return;
|
|
117
|
-
// 🔴
|
|
118
|
-
// 与 resume
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
119
|
+
// 🔴 #242 批 3([4000] Q3=B 裁定执行,行为翻面):台账缺席即**诚实缺席,绝不回落**
|
|
120
|
+
// `activeEngineRunId()` —— 与 resume 腿 `resolveOwnerRunId` 同口径(错值比缺席更坏:回落值
|
|
121
|
+
// 被并发 main/fork 流覆写,可能连到不相干的 run)。缺席不静默:留痕见 noteBgOwnerAbsence。
|
|
122
|
+
// 供给面:本表由 fleet 行帧 parentId / bg 通知 parentTaskId / task_progress tick 三腿喂
|
|
123
|
+
// (fleetLedger.ts:394/:496 + recordSubagentOwnerFromProgress),正常 bg 子代出生帧即有值;
|
|
124
|
+
// 缺席 = 真不知道宿主(壳重启且行帧未重播等),此时连「当前活跃 run」纯属赌。
|
|
125
|
+
const runId = getBgParentRun(taskId);
|
|
126
|
+
if (!runId) {
|
|
127
|
+
noteBgOwnerAbsence('subagent-tail', taskId);
|
|
124
128
|
return;
|
|
129
|
+
}
|
|
125
130
|
const client = makeEngineWireClient({
|
|
126
131
|
baseUrl: cfg.baseUrl,
|
|
127
132
|
...(cfg.token ? { token: cfg.token } : {}),
|
|
@@ -41,9 +41,10 @@ import { TaskStopConflictError } from '@sema-agent/sdk';
|
|
|
41
41
|
import { hostLog } from '../host.js';
|
|
42
42
|
import { makeEngineWireClient } from '../engineWireSdk.js';
|
|
43
43
|
import { engineWireDebugEnabled, engineWireTarget } from '../engineWireTarget.js';
|
|
44
|
-
import {
|
|
44
|
+
import { engineSessionParamSpread } from '../engineSessionParam.js';
|
|
45
45
|
import { getBgParentRun } from '../subagentContentStore.js';
|
|
46
46
|
import { engineTaskHandlesCapable } from './engineRowStopGate.js';
|
|
47
|
+
import { noteBgOwnerAbsence } from './subagentOwnerAbsence.js';
|
|
47
48
|
// REF-CC-域词表-06 提单源:spool 全量/增量判读的单一真源现在在 toolResult.ts(它也消费同一份
|
|
48
49
|
// 协议标记表 PROTOCOL_MARKERS)—— 本文件不再自己 `.includes()` 抄一份判读。
|
|
49
50
|
import { spoolMarkerOf } from '../toolResult.js';
|
|
@@ -54,13 +55,20 @@ import { STOP_CONFLICT_CODES, STOP_NOT_LANDED, STOP_NOT_LOCAL, STOP_PARKED, STOP
|
|
|
54
55
|
// ⚠️ 此处**不再 re-export**——它与原定义同进 index.ts 的 `export *` barrel 会构成双出口,
|
|
55
56
|
// esbuild 对 star-export 歧义直接 build 失败(tsc 同源 symbol 不报=假绿;0.8.0 壳收批实撞,
|
|
56
57
|
// idle-terminal-settle 套逮住)。包内消费者直接 import 原叶。
|
|
57
|
-
/** 宿主 run 解析:fleet 行/通知帧喂的 parent
|
|
58
|
-
*
|
|
58
|
+
/** 宿主 run 解析:fleet 行/通知帧喂的 parent 映射(行 id=`${runId} ${handle}` 的 parentId 投影,
|
|
59
|
+
* engineSide bash/monitor 行的出生帧即带)。
|
|
60
|
+
* 🔴 #242 批 3([4000] Q3=B 裁定执行,行为翻面):台账缺席即**诚实缺席**,绝不回落
|
|
61
|
+
* `activeEngineRunId()` —— 旧回落读的是 fetch 时刻的活跃 run,不是 register 时刻的宿主(头注
|
|
62
|
+
* 旧说法与实现不符):跨 turn 读旧句柄时回落值必错(404 撞别的 run),stop 打错 run 更坏。
|
|
63
|
+
* 缺席不静默(noteBgOwnerAbsence warn 留痕);两个消费口各自 fail-soft(output→null 回落
|
|
64
|
+
* receipt/outputFile 渲染,stop→unavailable + detail,UI 不翻行)。 */
|
|
59
65
|
function resolveHostRun(handle) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
const runId = getBgParentRun(handle);
|
|
67
|
+
if (!runId) {
|
|
68
|
+
noteBgOwnerAbsence('task-handle', handle);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
return runId;
|
|
64
72
|
}
|
|
65
73
|
// 游标累积(按 handle;进程内存态,与查看态生命周期同级——端重启即空,重读从引擎再取)
|
|
66
74
|
const accumulated = new Map();
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* subagentOwnerAbsence — #242 批 3(黑板 [4000] Q3=B 裁定落地):bg 子代宿主 run 台账缺席时的
|
|
3
|
+
* **响亮留痕**单源。
|
|
4
|
+
*
|
|
5
|
+
* 三条读面腿(`engineSubagentTail` / `engineTaskHandleWire` / `engineSubagentOutput`)此前一律
|
|
6
|
+
* `getBgParentRun(x) ?? activeEngineRunId()` 无条件回落 —— 与 resume 腿的 `resolveOwnerRunId`
|
|
7
|
+
* (指名了行就诚实缺席,绝不回落)口径相反。Q3 裁定统一到 resume 口径:**台账缺席即诚实缺席,
|
|
8
|
+
* 绝不回落**。理由(壳 `subagentOwnerLedger` 对抗复审结论,已入包表头注):进程级
|
|
9
|
+
* `activeEngineRunId()` 被并发 main/fork 流互相覆写、又被每个 query 的 finally 清空,回落出来的
|
|
10
|
+
* 宿主可能是**别的 run** —— 错值比缺席更坏(读面打到不相干的 run;stop 打错 run 甚至可能停掉
|
|
11
|
+
* 别人的同名句柄)。
|
|
12
|
+
*
|
|
13
|
+
* 但缺席也不许**静默空转**(clay 原话「有问题必暴露」):本模块对每个 (腿, taskId) 组合 warn
|
|
14
|
+
* **一次**(查看态 watcher / 面板 tick 会按拍重试同一缺席,逐次 warn = 刷屏;去重集有界 FIFO)。
|
|
15
|
+
* 消费方照旧 fail-soft 返回缺席值(null/不开流)—— 响亮的是留痕,不是抛错。
|
|
16
|
+
*
|
|
17
|
+
* 🔴 独立小叶而不进 `subagentContentStore`(台账属主):那个 store 是**零 import 的内核闭包件**
|
|
18
|
+
* (portability 门的 runStream 闭包文件数棘轮只许降),拉 `host.js` 进去会撑大内核闭包;三条腿
|
|
19
|
+
* 都在 `subagent/`、都已依赖 host,落这里零新增图边。
|
|
20
|
+
*/
|
|
21
|
+
import { hostLog } from '../host.js';
|
|
22
|
+
const noted = new Set();
|
|
23
|
+
const MAX_NOTED = 512;
|
|
24
|
+
/** 台账缺席留痕(每 (leg, taskId) 至多一条 warn;绝不 throw)。 */
|
|
25
|
+
export function noteBgOwnerAbsence(leg, taskId) {
|
|
26
|
+
const key = `${leg}:${taskId}`;
|
|
27
|
+
if (noted.has(key))
|
|
28
|
+
return;
|
|
29
|
+
if (noted.size >= MAX_NOTED) {
|
|
30
|
+
const oldest = noted.values().next().value;
|
|
31
|
+
if (oldest !== undefined)
|
|
32
|
+
noted.delete(oldest);
|
|
33
|
+
}
|
|
34
|
+
noted.add(key);
|
|
35
|
+
try {
|
|
36
|
+
hostLog('warn', `[subagent-owner] ${leg}: no host-run mapping for ${taskId} — not connecting (owner ledger absent ⇒ honest absence; active-run fallback removed per [4000] Q3)`);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// 诊断端口自己抛错(P-F4):①绝不向三腿调用方外溢(留痕是旁路,不是三腿的失败模式);
|
|
40
|
+
// ②撤销去重键 —— warn 没真发出去,留痕机会不许被一次坏端口永久烧掉,端口恢复后
|
|
41
|
+
// 下一次同键缺席仍会 warn 一次。
|
|
42
|
+
noted.delete(key);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** 测试钩:清留痕去重集(同进程多组断言之间互不污染)。 */
|
|
46
|
+
export function __resetBgOwnerAbsenceForTests() {
|
|
47
|
+
noted.clear();
|
|
48
|
+
}
|
|
@@ -191,6 +191,11 @@ export interface BgTerminalFacts {
|
|
|
191
191
|
}>;
|
|
192
192
|
}
|
|
193
193
|
export declare function recordBgTerminalFacts(taskId: string, facts: BgTerminalFacts): void;
|
|
194
|
+
/** 复活清账(#242 批 3 / 0.31.0 扫码 P2):非终态行帧/snapshot 复活 = 新周期开启,旧周期的
|
|
195
|
+
* 终态事实作废 —— 不清账时 factsAccepted 的方向核会拿上周期的 killed/failed 当先例,把
|
|
196
|
+
* 第二周期的合法 completed 整条拒收(status 与 summary 都不入账)。只删本 taskId 键;
|
|
197
|
+
* taskToParent 映射不动(寻址面与事实面分离)。 */
|
|
198
|
+
export declare function clearBgTerminalFacts(taskId: string): void;
|
|
194
199
|
export declare function getBgTerminalFacts(taskId: string): BgTerminalFacts | undefined;
|
|
195
200
|
/** 一行子代的宿主记录。`sessionId` = 登记时刻那条流的会话(per-id 读面要拼 `?session=`)。 */
|
|
196
201
|
export interface SubagentOwnerRecord {
|
|
@@ -351,6 +351,13 @@ export function recordBgTerminalFacts(taskId, facts) {
|
|
|
351
351
|
bgFacts.set(taskId, facts);
|
|
352
352
|
scheduleNotify(taskId); // 打开中的查看态借既有 notify 链重建消息
|
|
353
353
|
}
|
|
354
|
+
/** 复活清账(#242 批 3 / 0.31.0 扫码 P2):非终态行帧/snapshot 复活 = 新周期开启,旧周期的
|
|
355
|
+
* 终态事实作废 —— 不清账时 factsAccepted 的方向核会拿上周期的 killed/failed 当先例,把
|
|
356
|
+
* 第二周期的合法 completed 整条拒收(status 与 summary 都不入账)。只删本 taskId 键;
|
|
357
|
+
* taskToParent 映射不动(寻址面与事实面分离)。 */
|
|
358
|
+
export function clearBgTerminalFacts(taskId) {
|
|
359
|
+
bgFacts.delete(taskId);
|
|
360
|
+
}
|
|
354
361
|
export function getBgTerminalFacts(taskId) {
|
|
355
362
|
const key = bgFacts.has(taskId) ? taskId : (taskToParent.get(taskId) ?? '');
|
|
356
363
|
touchLru(bgFacts, key);
|
|
@@ -371,8 +378,9 @@ export function getBgTerminalFacts(taskId) {
|
|
|
371
378
|
// 所以合表不是二选一,是把两条 lane 的事实并进同一个答案。
|
|
372
379
|
// 键域两侧本就同域,合表零改键:fleet 腿写 `rowIdTail(row.id)` = 裸引擎 taskId、通知腿写
|
|
373
380
|
// `n.taskId` = registry handle(同一裸 id)、tick 腿写 `task_progress.taskId`。
|
|
374
|
-
//
|
|
375
|
-
//
|
|
381
|
+
// ✅ #242 批 3([4000] Q3=B 裁定):三处 `?? activeEngineRunId()` 回落已全部翻面 —— tail /
|
|
382
|
+
// taskOutput·taskStop / subagentOutput 三腿一律「台账缺席即诚实缺席,绝不回落」,缺席
|
|
383
|
+
// warn 留痕(`subagent/subagentOwnerAbsence.ts`),与 resume 腿口径统一。
|
|
376
384
|
//
|
|
377
385
|
// 🔴 写入纪律(壳表头注 :20-22 的教训**原文搬**):宿主 run **必须由持有 stream-local 值的
|
|
378
386
|
// 调用方显式传入**,本模块绝不从进程级 `activeEngineRunId()` 推断 —— 那个值被并发的
|
|
@@ -15,19 +15,19 @@
|
|
|
15
15
|
|
|
16
16
|
## §0 版本锚与重扫纪律
|
|
17
17
|
|
|
18
|
-
### 0a. 版本锚(2026-08-
|
|
18
|
+
### 0a. 版本锚(2026-08-16)
|
|
19
19
|
|
|
20
20
|
| 项 | 值 | 真源 |
|
|
21
21
|
|---|---|---|
|
|
22
|
-
| 本包 | `@sema-agent/client-core` **0.
|
|
22
|
+
| 本包 | `@sema-agent/client-core` **0.31.0** | `package.json` `version` |
|
|
23
23
|
| peer:wire 契约 | `@sema-agent/sdk` **>=6.17.2**(value-level,非 type-only) | `package.json` `peerDependencies` |
|
|
24
24
|
| peer:会话词汇表 | `@sema-agent/agent-types` **>=0.2.0**(type-only,零运行时) | 同上 |
|
|
25
25
|
| runtime dep | `diff` ^9.0.0(**唯一**一条;portability 门按**等值**钉死) | `package.json` `dependencies` |
|
|
26
|
-
| 公开导出面 | **
|
|
26
|
+
| 公开导出面 | **740** 个运行期符号(+ 37 个测试钩) | `scripts/public-export-baseline.json` 的 `count` / `testHookCount` —— **别手抄进别处,以该文件为准** |
|
|
27
27
|
| 常驻门 | 以 `scripts/gates-manifest.json` 的 `suites` 长度为准(**本档不抄这个数**) | `scripts/gates-manifest.json`;`npm test` 的名单等值门与它逐名对账 |
|
|
28
28
|
| 沿革档 | 0.29.0 起建 `CHANGELOG.md`;更早批次记账在 `src/index.ts` 文件头 + `docs/REFACTOR-LEDGER.md` | — |
|
|
29
29
|
|
|
30
|
-
⚠️ **本表描述的是工作树(即将发布的 0.
|
|
30
|
+
⚠️ **本表描述的是工作树(即将发布的 0.31.0),不是 npm 上那一版**:npm 的 `0.29.0` 是发布 commit
|
|
31
31
|
`0ab959d`,它的 peer floor 是 **`>=6.16.0`**,也没有 0.30.0 段里那几件(relay 形 / durable 卡两展示键 /
|
|
32
32
|
SDK 行形 type 再导出 / P-26 铸口 / #158 移交)。装着 npm `0.29.0` 的端**按 `CHANGELOG.md` 的
|
|
33
33
|
`## 0.29.0` 段对表**,不要按本表 —— 本表的组合在 npm 上今天还不存在。
|
|
@@ -99,7 +99,7 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
|
|
|
99
99
|
|
|
100
100
|
## §2 公共导出面地图(按域)
|
|
101
101
|
|
|
102
|
-
> 全集真源 = `scripts/public-export-baseline.json` 的 `names`(**
|
|
102
|
+
> 全集真源 = `scripts/public-export-baseline.json` 的 `names`(**740** 项)。
|
|
103
103
|
> 本节**不逐名抄**,只给「域 → 承重导出 → 用途 → 实现锚」。承重导出 = 一个端为了让这个域干活
|
|
104
104
|
> **必须**直接调到的那几个符号;其余是它们的类型、变体与辅助位。
|
|
105
105
|
> 单一入口:`import { … } from '@sema-agent/client-core'`(`exports` 只有 `.` 一个;
|
|
@@ -109,7 +109,7 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
|
|
|
109
109
|
|
|
110
110
|
`public-export-baseline.json` 由 **`dist/index.js` 的运行期导出**生成(生成口径自述见
|
|
111
111
|
`scripts/run-client-core-typeshape-test.mjs`,双向精确集合门在 `scripts/run-public-surface-test.mjs`)。
|
|
112
|
-
实测:
|
|
112
|
+
实测:740 项 **100% 是运行期导出,零 type-only**。
|
|
113
113
|
|
|
114
114
|
**推论(端必须知道)**:
|
|
115
115
|
- barrel 导出的**类型**面比 707 大得多,且**不被这道门看守** —— `AdapterContext` / `SeamEvent` /
|
|
@@ -118,19 +118,19 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
|
|
|
118
118
|
端依赖这些类型是合法的,但**不要**拿基线 diff 当"类型面没变"的证据。
|
|
119
119
|
- `src/agentSession/contract.ts` 对基线贡献 **0** 项(纯类型模块,`export *` 在 dist 里是空转发)。
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
740 项的内部构成(帮助端估读表大小):**218** 项是 `SCREAMING_SNAKE` 常量数据表/词汇表
|
|
122
122
|
(矩阵、键集、env 名、锚串)而非可调用物;**4** 项是 PascalCase 运行期值
|
|
123
123
|
(`ControlRouter` / `ControlSafetyError` / `HitlBridge` / `HitlSafetyError`);
|
|
124
124
|
**39** 项是 `*For(sessionKey, …)` 的 per-session 变体(§6;其中 `engineNamespaceKeyFor` 是命名巧合 —— 参数是 baseUrl 不是 sessionKey,见域 14)。
|
|
125
125
|
|
|
126
|
-
### 2b. 域图(16 域,逐域计数之和 =
|
|
126
|
+
### 2b. 域图(16 域,逐域计数之和 = 740)
|
|
127
127
|
|
|
128
128
|
| # | 域 | 名数 | 承重导出 | 用途 | 实现锚 |
|
|
129
129
|
|---|---|---|---|---|---|
|
|
130
130
|
| 1 | **适配内核(下行主链)** | 29 | `adapt` · `createWireToCcAdapter` · `runStream` · `eventToSdkMessage` · `terminalToSdkResult` · `turnUsageToModelUsage` · `isRunStreamActive` · `ADAPTER_DIVERGENCES` | 引擎 SSE `AgentEvent` → 端要渲的**双面输出**:transcript(`SDKMessage`)+ chrome(瞬态 `ChromeEvent`)。**本包存在的理由** | `src/adapt.ts`、`src/adapt/{arms,wireShapes,panelTasks}.ts`(经 `adapt.ts` 再导出)、`src/adapter/runStream.ts`、`src/adapter/downstream/*`、`src/adapter/types.ts` |
|
|
131
131
|
| 2 | **seam 公共契约** | 2(其余为 type-only) | `CHROME_ARMS` · `deriveTranscriptId` | 公共词汇 + **id 确定性不变量**(同一条流重放 ⇒ 同一串 id)。`CHROME_ARMS` = 端「我要消费哪些 chrome 臂」的对照清单 | `src/seam.ts` |
|
|
132
132
|
| 3 | **HITL 决断卡链**(§4/§5 主战场) | 119 | `makeHitlCanUseTool` · `HitlBridge` · `findPendingForTask` · `HitlSafetyError` · `bridgeAskUserQuestionGates` · `surfaceToolApprovalFrameAndRespond` / `surfaceFsApprovalAndDecide` · `readToolApprovalRespondAck` · `installApprovalCardPort(For)` · `installHitlHostSurface(For)` · `armPlanReviewApproval` · `reopenPlanReviewCard` · `decidePlanReview` · `startApprovalsFeed` · `pendingRowIsOwnedByThisSession` · `approvalCallKey`/`liveFrameCallKey`/`planReviewQuestionId` · `registerArmedGateFor`/`wasGateArmedFor`/`clearArmedGateFor` · `waitForGateArmed(For)`/`onGateArmed(For)`/`gateArmedWaitMs`(#244 F1 呈现回执事件源) · `planReviewArmedKey(For)`/`notePlanReviewAnswered(For)`/`notePlanReviewAnsweredIfDecisive(For)`(A-024.4 plan 呈现分代) · `toolEndOutputText` · `isAskTool` · `waitForParkRowBirth` · `classifyAskParkRows` / `askParkRowArm` / `classifyAskParkChainFailure` · `readDecisionNoteAudit` / `decisionNoteAuditLine` · `resumeRunningOptions` / `resumeChoiceFromLabels` · `persistedRulesLaneAvailable`/`persistedRulesGovernanceAvailable` · `classifyRulesFailure` · `listAllPersistedRules` · `classifySkippedReason` · `readRulePersistOutcome`(#244 F2:persist-ack 读口与 `readToolApprovalRespondAck` 合成一处) · `parseLocalAllowRule`(durable 腿本地落规则窄化骨架,谓词经 `LocalAllowRuleDeps` 注入) | suspended→decide→resume 环。🔴 **D-1 两元组 verbatim 回显**是字节级断言的安全不变量,端**不许重实现它的任何一段**。🔴 键空间边界(web [C1] d3 拦截):`gateIdentity` 四常量两函数只覆盖 HITL questionId/callKey 空间;seat 的 `TOOL_PERMISSION_REQUEST_ID_DOMAINS`(`plan:` 等)是另一键空间,**两者绝不合并**(合并=座位校验器静默拒全部 plan-review 卡) | `src/hitl/hitlBridge.ts`、`toolApprovalWire.ts`、`askGateWire.ts`、`planReviewWire.ts`、`hitlHostSurface.ts`、`gateIdentity.ts`、`armedGateRegistry.ts`、`parkOwnership.ts`、`parkResolver.ts`、`approvalsFeed.ts`、`frameRouter.ts`(**只挑名导出** `toolEndOutputText`/`ENGINE_ABORT_TOOL_RESULT`/`isAskTool`/`HITL_REJECT_MESSAGE`)、`parkRowBirthWait.ts`、`approvalDecisionNoteAudit.ts`、`askParkRowRouting.ts`、`resumeRunningCard.ts`(#265 上收的判定层)、`persistedRulesWire.ts`、`localAllowRule.ts`(#244 F2 规则侧) |
|
|
133
|
-
| 4 | **子代 wire + 面板侧信道台账** |
|
|
133
|
+
| 4 | **子代 wire + 面板侧信道台账** | 81 | `tailEngineSubagent` · `installSubagentActivitySink` · `installSubagentTailMetaSink`(#280 件2:tail meta 帧发布口,`contentFrames` 判别位载体)· `stopEngineTask` + `classifyTaskStopConflict` · `fetchEngineSubagentReport` · `steerEngineSubagent` · `resumeSettledSubagent` + `resolveSubagentResumeContext` + `resolveOwnerRunId` + `classifySubagentResumeFailure` + `subagentResumeAvailable`(#242 批 2 A-028.7:resume 判定半场上收,与 steer 孪生同居;取址三态 = 台账有行用行值 / 指名了行但台账缺席则**诚实缺席绝不回落在飞 run** / 没指名行才回落。出路文案归端)· `recordSubagentOwnerFromProgress` + `getBgParentRunOwner`(A-028.6:「子代 → 宿主 run」**单表**,宿主 run 必须由持 stream-local 值的调用方显式传入,包内绝不从 `activeEngineRunId()` 推断)· `noteBgOwnerAbsence`(#242 批 3 [4000] Q3=B:tail/taskOutput·taskStop/subagentOutput 三腿台账缺席即诚实缺席**绝不回落在飞 run**,缺席 warn 留痕每 (腿,taskId) 一条)· `auditRetainWithoutWake`([4000] Q5:引擎宣示 `subagentResume` + 本端在付 `retainSubagentSessions` + 端未实现 `wakeSubagent` ⇒ 响亮一条;`CLIENT_VERBS.wakeSubagent` 维持 fail-soft)· `subscribeSubagentContent` · `subscribeEngineAgentPanel` · `publishQuestionFrame` / `respondToQuestion` | 驱动与观测委派子代;经 module 级台账喂活体 agent/task 面板。全部**能力位 gate**(§5b) | `src/subagent/*.ts`、`src/subagentContentStore.ts`、`src/engineAgentPanelStore.ts`、`src/engineInlineTaskStats.ts`、`src/engineToolLabelStore.ts`、`src/liveQuestionStore.ts` |
|
|
134
134
|
| 5 | **fleet 投影** | 43 | `createFleetLedger` · `projectTasks` · `projectWorkflows` · `projectFleetAgentRows` · `readEngineActiveBgTasks` · `FLEET_TASK_VIEW_KEYS` | 老 `fleetClient` 那一刀的成品:**帧体归库、连接归端** —— 端持 SSE 连接,库做行投影 + 保留台账 | `src/fleet/fleetProjection.ts`、`src/fleet/fleetLedger.ts`、`src/fleetAgentPanelProjection.ts`、`src/fleetTaskDesc.ts` |
|
|
135
135
|
| 6 | **请求装配(上行唯一构造口)** | 8 | `buildTaskRequest` · `REQUEST_FIELD_MATRIX` · `unregisteredRequestKeys` · `applyLiveRequestDefaults` · `taskNotificationToPrintFrame` | 两条车道(`interactive`/`print`)出站请求的**唯一**构造器;`unregisteredRequestKeys` 是可执行门 —— 端偷带一个未登记键上 wire 就红 | `src/request/taskRequest.ts`、`src/request/printNotification.ts` |
|
|
136
136
|
| 7 | **通知与 outstanding 台账** | 37 | `installNotificationQueuePort` · `normalizeTaskNotification` · `taskNotificationDedupKeyFromWire` · `registerOutstandingBgTask` / `registerOutstandingWorkflowRun` · `notificationQueuePortMisses` · `subscribeOutstandingWorkflows` · `outstandingDeliverableWorkflowCount` | `task_notification` 归一 + 去重 + 投递进宿主命令队列的**一把闸**;`outstandingDeliverableWorkflowCount()` 是 headless `-p` 的**退出门** | `src/notifications.ts`(11 个 module 台账) |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/client-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "Client-side session runtime shared by every sema human client (TUI / web / desktop): sema wire frames (AgentEvent) -> CC session vocabulary (SDKMessage) with dual-plane output (transcript/chrome), deterministic transcript ids, lane discipline as a type, and the notification/dedup ledgers. Every CC-skin shape is collected here so the wire itself stays neutral. Blackboard [1832] design axioms; [1651]/[1652]/[1653] signed seam design. Renamed from @sema-agent/wire-cc-adapter (0.1.x).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -31,12 +31,12 @@
|
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"@sema-agent/agent-types": ">=0.2.0",
|
|
34
|
-
"@sema-agent/sdk": ">=
|
|
34
|
+
"@sema-agent/sdk": ">=7.1.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@sema-agent/agent-types": "^0.2.0",
|
|
38
38
|
"@sema-agent/core": "^5.20.0",
|
|
39
|
-
"@sema-agent/sdk": "^
|
|
39
|
+
"@sema-agent/sdk": "^7.1.0",
|
|
40
40
|
"esbuild": "^0.27.4",
|
|
41
41
|
"typescript": "^6.0.2"
|
|
42
42
|
}
|