@sema-agent/client-core 0.31.0 → 0.32.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
CHANGED
|
@@ -16,7 +16,75 @@
|
|
|
16
16
|
> 🔴 **互链**(web [C166]⑦):各版「已知局限」段只记**该版新增**;接入面已知局限的完整台账在
|
|
17
17
|
> `docs/INTEGRATION-CLIENTS.md` §6e/§7 —— **只读其一会漏**,两处都过。
|
|
18
18
|
|
|
19
|
-
## 0.
|
|
19
|
+
## 0.32.0(2026-08-16)
|
|
20
|
+
|
|
21
|
+
**1.0.80 发包前扫码三件(#280 件A / #281 / #284)。公开面 additive 两件 + 行为面修一件;
|
|
22
|
+
无 BREAKING —— 两处签名都是 additive,老调用方逐字零行为变化。**
|
|
23
|
+
|
|
24
|
+
- **#280 件A / steer 取址并入 [4000] Q3=B(行为面,additive 参数)**:
|
|
25
|
+
`steerEngineSubagent(target, text, childTaskId?)` 补第三参。`engineSubagentSteer.ts` 此前直取
|
|
26
|
+
`activeEngineRunId()` —— 它是取址**第四腿**,而 tail / `taskOutput`·`taskStop` / `subagentOutput`
|
|
27
|
+
三腿已在 0.31.0 翻面,孪生的 resume 腿更是从 #242 批 2 起就按行锚(`resolveOwnerRunId`)。
|
|
28
|
+
这处不对称正是 `engineSubagentResume` 头注点名的那一条:**同一个 agent 有时能说话有时不能** ——
|
|
29
|
+
进程级活跃 run 在宿主 turn 收尾即清空(后台子代还在跑,消息却发不出去 = `no-run`),另一条
|
|
30
|
+
turn 正飞时它有值**但可能是别的 run**(打到不相干的 run 上,错值比缺席更坏)。
|
|
31
|
+
新形三态,与 resume 腿**共用同一个判据函数**(不是第二份手抄):台账有这一行 ⇒ 用行值(哪怕
|
|
32
|
+
此刻另有 run 在飞);**指名了行**而台账缺席 ⇒ `{ok:false, reason:'no-run'}` + `noteBgOwnerAbsence`
|
|
33
|
+
留痕(腿名 `subagent-steer`,每 (腿,taskId) 至多一条 warn),绝不回落在飞 run;**没指名行**
|
|
34
|
+
(既有两参调用)⇒ 才回落在飞 run。⚠️ 端侧收益要**端传了 `childTaskId` 才到达用户**:壳/web/桌面
|
|
35
|
+
凡有行上下文的 steer 调用点(壳 `REPL.tsx` 的 `onAgentSubmit` 已持 `task.id`)应跟一行把它传下来。
|
|
36
|
+
**会话参数同批改二态**(codex 复审 medium):行登记时捕到了会话 ⇒ 用**行的会话**(与行的 runId
|
|
37
|
+
同源同拍,是唯一自洽的组合;旧形「台账 run + 现势 session」在 session-bound run 上是确定性
|
|
38
|
+
404,等于修好了 run 定位却仍然 steer 不到);行没有随行会话(`recordBgParentRun` 只写 run,
|
|
39
|
+
今天 bg 行多数是这一格)⇒ 退现势会话。刻意**不**跟 resume 腿的「没捕到就干脆不带」——本腿遵
|
|
40
|
+
tail/subagentOutput 的 [1498]③ 无条件带 session 纪律,resume 更严是因为它是 AT-MOST-ONCE 的
|
|
41
|
+
叫醒(不可回收),代价不对称。
|
|
42
|
+
- **#281 / `applyBgNotification` 升级臂的 `task_remove` 前分叉窗(行为面修)**:
|
|
43
|
+
「终态行帧(completed)在先 + 升级通知(killed)在后 + `task_remove` 丢失」时,旧形只改
|
|
44
|
+
`retained` 不动 live 行,而 `project()` 对留存条目一律 `taskMap.has(id) ⇒ continue` ⇒ 面板读的是
|
|
45
|
+
live 行的**旧终态词**、事实台账已是新词:E4-6 注里点名不许出现的「面板绿而详情页 killed」换了
|
|
46
|
+
条路复现。常态下同毫秒的 remove 把窗压到不可见,remove 单独丢失且不重连时它**长驻**。
|
|
47
|
+
修:升级时 `retained` 与 `taskMap` **同改**(仅升级方向;洗绿回声照旧拦截,live 行不动)。
|
|
48
|
+
在飞读面语义不变(升级后仍是终态词,照旧不进在飞集),`task_remove` 语义不变。
|
|
49
|
+
- **#284 / `onBgNotificationAccepted` 补 `evidence` 第三参(公开面 additive)**:
|
|
50
|
+
新 type-only 导出 `BgNotificationAcceptEvidence` =
|
|
51
|
+
`'server_fail_closed' | 'own_root' | 'own_parent' | 'absent_parent'`,按**实际命中的放行臂**铸值
|
|
52
|
+
(臂序 = 放行判据求值序,多臂同时成立报第一条)。旧签名把四条**强度不同**的臂压成一个「已放行」
|
|
53
|
+
布尔事实,端因此无法分级处置。
|
|
54
|
+
🔴 **三档强度,不是四档递减**(codex 复审 high 采纳;首版注把中间一格写成「硬证据」是过度声称):
|
|
55
|
+
**会话级证明** = `server_fail_closed` / `own_root`;**进程级成员证明** = `own_parent` —— own-run 台账是
|
|
56
|
+
进程级 `Set`、按会话零分区,`/clear` 或换会话后**旧会话**的 run 仍命中(= 在册局限 **P-13** 在
|
|
57
|
+
通知面的同一张脸),端**不得**把它读成「属于当前会话」;**非证据** = `absent_parent`(通知没带
|
|
58
|
+
`parentTaskId`;老引擎/老帧形不带该键,fail-closed 会把自家通知整批吞掉)。端拿归属做有副作用的事
|
|
59
|
+
(落库/跨会话搬运/翻别人的卡)时,后两格都应自裁为「未证明当前会话归属」。
|
|
60
|
+
两参消费方(0.31.0 形)零改动照旧;接入说明见 `docs/INTEGRATION-CLIENTS.md` §6d。
|
|
61
|
+
- **#284 尾件 / `parentTaskId` 脏形 fail-closed(行为面收紧,codex 复审第四轮 high)**:
|
|
62
|
+
own/foreign 隔离门写成 `typeof === 'string' ∧ length>0 ∧ !isOwnEngineRun(…)` —— 对 number /
|
|
63
|
+
object / array / boolean 这类脏值**整条不成立**,门不响、外来通知照收(还会被新 evidence 标成
|
|
64
|
+
`absent_parent`,而那一格的语义是「老引擎**没带**这个键」= 谎报)。fleet 帧从 wire 上来、SDK 只
|
|
65
|
+
`JSON.parse` + rest-spread 不校型,这条路真实可达。现在三态分开:**键缺席 / `null` / 空串**照旧走
|
|
66
|
+
absent-放行臂(与 `wireParentId`「悬空父脏值空串按缺席处理」同规矩,**零新增 drop**);**键在场却
|
|
67
|
+
不是串** ⇒ 与畸形 `taskId`/`status` 同档 fail-closed:计 `droppedMalformed`、不写状态面、
|
|
68
|
+
不回调 accepted 钩子、不入通知队列。无正当生产者会发这种形,故不计 BREAKING。
|
|
69
|
+
- **已知局限(本版新增登记,不改行为)**:**P-31**(`docs/INTEGRATION-CLIENTS.md` §7c)——
|
|
70
|
+
fleet 面的会话锚整条走**默认槽**,不跟 ledger 的 `sessionKey`,**两条腿都中招**:
|
|
71
|
+
①`ownByRoot` 判据用 `engineSessionParam()`(= `hostSessionFor(DEFAULT_SESSION_KEY)`);
|
|
72
|
+
②更强的一条 —— `serverFailClosed`(meta 两位)**根本不读会话端口**,它信的是「这条流替谁开的」,
|
|
73
|
+
而本包的开流参数 `fleetStreamOptions()` / `fleetSnapshotOptions()` 是 module 级、只认默认槽会话,
|
|
74
|
+
连 `sessionKey` 都拿不到;**生产 meta 组合命中的正是这一格**。⇒ **keyed 多会话宿主**上,一条按
|
|
75
|
+
默认槽开的流接到非默认键 ledger 时,默认槽会话的通知会被放行并报 `evidence` 的前两格。
|
|
76
|
+
单会话宿主(cli 及今天的三端)不受影响 —— 流与 ledger 恒同会话。
|
|
77
|
+
🔴 正位解 = fleet 面**整条**换 per-key 会话锚(开流参数 + 归属判据一起动);只改判据那一半会
|
|
78
|
+
「按默认槽开流、按 keyed 槽判定」自相矛盾、反而丢自己的通知 —— 故本批只登记 + 钉现状,不动行为。
|
|
79
|
+
同批钉住的另一条:`recordBgParentRun` 今天不写随行会话(resume 腿自己记过的「供给面欠账」),
|
|
80
|
+
补它要先落 P-31 的会话锚,否则会把**错的**会话写进 owner 记录(比今天不写更坏)。
|
|
81
|
+
- **门**:`scripts/run-client-core-pure-test.mjs` #242 批 3 段 +37 条(B3-Q3c 9 / B3-P3 7 / B3-HKe 11 /
|
|
82
|
+
B3-DIRTY 10),地板 62→99(零松量)。三件各自红先绿后,反钉覆盖 additive 不回归、洗绿方向不放行、
|
|
83
|
+
台账在场仍照常发起、foreign 丢弃仍零回调、`own_parent` 强度边界不被读成会话证明。
|
|
84
|
+
变异自证四发(逐发只由目标格抓红,cp 复原后逐字节 cmp 一致):撤 `taskMap.set(id, upgraded)` /
|
|
85
|
+
steer 取址退回 `activeEngineRunId()` / `absent_parent` 铸成 `own_parent` / 撤行会话优先。
|
|
86
|
+
|
|
87
|
+
## 0.31.0(2026-08-16)
|
|
20
88
|
|
|
21
89
|
**#242 批 3 fleet belt 对账承重批(design-242 §3 批 3;黑板 [4000] Q2=A / Q3=B 裁定执行)。
|
|
22
90
|
🔴 BREAKING(行为面翻面两处,见下);公开面 additive 三件。**
|
|
@@ -114,6 +114,49 @@ export declare function fleetSnapshotOptions(opts?: {
|
|
|
114
114
|
export type HookNoticeFrame = Extract<FleetFrame, {
|
|
115
115
|
type: 'hook_notice';
|
|
116
116
|
}>;
|
|
117
|
+
/**
|
|
118
|
+
* 🔴 #284:`onBgNotificationAccepted` 的**证据等级** —— 这条通知是**凭哪一条**放行的。
|
|
119
|
+
*
|
|
120
|
+
* 为什么钩子要说这个:四条放行臂的证据强度**不一样**,而旧签名把它们压成一个「已放行」布尔事实,
|
|
121
|
+
* 端因此无法对不同强度的证据分级处置(强证据可直接落库/翻卡,弱证据宜先渲染、别拿它当归属结论)。
|
|
122
|
+
* 判据锚在**实际命中的那条臂**上,不是锚在「有没有 parentTaskId」这类前置条件上。
|
|
123
|
+
*
|
|
124
|
+
* 🔴 **每个词命名的是「放行臂」,不是「归属结论」**(codex 复审两轮收紧:首版把 `own_parent` 写成
|
|
125
|
+
* 「硬证据」、二版只给 `own_root` 挂了会话锚警告,都是过度声称)。强度分档如下 —— 前两格的
|
|
126
|
+
* 会话级读法**带前提**,第三格永远只是进程级,第四格根本不是证据。
|
|
127
|
+
*
|
|
128
|
+
* 🔴 **前两格的共同前提:喂给本 ledger 的那条 fleet 流,是按本 ledger 的会话开的。**
|
|
129
|
+
* 本 ledger **自己不开流**(帧体归库、连接归端),所以它无从校验这一点;而本包给出的开流参数
|
|
130
|
+
* `fleetStreamOptions()` / `fleetSnapshotOptions()` 是 **module 级函数、只认默认槽会话**
|
|
131
|
+
* (`engineSessionParam()` = `hostSessionFor(DEFAULT_SESSION_KEY)`),连 `opts.sessionKey` 都拿不到。
|
|
132
|
+
* ⇒ **单会话宿主**(cli 及今天的三端)前提恒成立,前两格就是会话级证明;
|
|
133
|
+
* ⇒ **keyed 多会话宿主**上,一条按默认槽开的流被接到非默认键 ledger 时,前两格会替**别的会话**
|
|
134
|
+
* 作证(`server_fail_closed` 尤其是**生产 meta 组合下的那一格**)。在册局限 **P-31**
|
|
135
|
+
* (`docs/INTEGRATION-CLIENTS.md` §7c);正位解 = 开流参数与本判据**一起**换 per-key 会话锚,
|
|
136
|
+
* 只改一半会让「按默认槽开流、按 keyed 槽判定」自相矛盾、反而丢自己的通知。
|
|
137
|
+
*
|
|
138
|
+
* · **会话级证明(带上述前提)**:
|
|
139
|
+
* · `server_fail_closed` — server 侧注入路已 fail-closed(meta `bgNotifyFailClosed` ∧ 本连接
|
|
140
|
+
* session-bound):最强,到达即**这条流的**会话,端侧台账判别整体让位([1510])。
|
|
141
|
+
* ⚠️ 这一臂**完全不读会话端口**,它信的就是「这条流是替谁开的」——前提破了它也不会报错;
|
|
142
|
+
* · `own_root` — 通知带 `rootSessionId`(委托树 root 宿主会话,固定点语义)=== `engineSessionParam()`
|
|
143
|
+
* (**默认槽**的 SessionPort,不是本 ledger 的 `sessionKey` 槽)。
|
|
144
|
+
* · **进程级成员证明**(只证明「本进程曾亲手驱动过这条 run」,🔴 **不区分会话代际**):
|
|
145
|
+
* · `own_parent` — `parentTaskId`(spawn 该子代的 leader run)∈ 本端 own-run 台账。台账真源
|
|
146
|
+
* `subagentContentStore.ownEngineRuns` 是**进程级 `Set`、按会话零分区**(它自己的头注:
|
|
147
|
+
* 「进程内存态,壳重启即空」),所以 `/clear` 或换会话之后,**旧会话**登记的 run 仍然命中
|
|
148
|
+
* —— 这正是在册局限 **P-13**(`docs/INTEGRATION-CLIENTS.md` §7c)在通知面的同一张脸。
|
|
149
|
+
* ⇒ 端不得把它当作「属于当前会话」的证明;要按会话归属做事,自注入会话粒度的 own-run
|
|
150
|
+
* 判据(P-13 给的出路),或只认上面那两格。
|
|
151
|
+
* · **非证据**:
|
|
152
|
+
* · `absent_parent` — 通知**没带** `parentTaskId`:🔴 **这不是证据,是 absent-放行姿势**
|
|
153
|
+
* (老引擎/老帧形不带该键,fail-closed 会把自家通知整批吞掉,故按放行处理)。端要拿归属做
|
|
154
|
+
* 有副作用的事(落库、跨会话搬运、翻别人的卡)时,这一格应当自裁为「未证明」。
|
|
155
|
+
*
|
|
156
|
+
* 臂序 = `applyBgNotification` 放行判据的求值序(多臂同时成立时报**第一条**);开集只在本包加臂时
|
|
157
|
+
* 扩,端按未知词兜底(`default` 当 `absent_parent` 一档处理最安全)。
|
|
158
|
+
*/
|
|
159
|
+
export type BgNotificationAcceptEvidence = 'server_fail_closed' | 'own_root' | 'session_anchor_untrusted' | 'own_parent' | 'absent_parent';
|
|
117
160
|
export interface FleetLedgerHooks {
|
|
118
161
|
/**
|
|
119
162
|
* hook_notice 帧的**写 store** 半场(判定在 `classifyHookNoticeFrame`,本台账只做分发)。
|
|
@@ -128,10 +171,13 @@ export interface FleetLedgerHooks {
|
|
|
128
171
|
* · 终态与非终态通知都回调(状态筛选归消费方);
|
|
129
172
|
* · `rowIds` = 到达时刻 live 集里按键域(整行 id + 尾段)命中的行 id(可能为空 = 行帧车道
|
|
130
173
|
* 没有这行;可能多条 = server 双生行)。快照取在退休动作**之前**;
|
|
174
|
+
* · `evidence`(#284,additive 第三参)= **凭哪一条臂放行的**,见
|
|
175
|
+
* {@link BgNotificationAcceptEvidence} —— 四条臂证据强度不同,`absent_parent` 尤其**不是
|
|
176
|
+
* 归属证明**。两参消费方(0.31.0 形)零改动照旧;
|
|
131
177
|
* · 钩子抛错不拆台账(try/catch + debug 留痕),与帧消费循环隔离。
|
|
132
178
|
* 端从此**零归属逻辑、零复刻**(壳侧 meta 两位门 [cross-repo-fix-at-source] 同批整条删)。
|
|
133
179
|
*/
|
|
134
|
-
onBgNotificationAccepted?(n: FleetBgNotificationWire, rowIds: readonly string[]): void;
|
|
180
|
+
onBgNotificationAccepted?(n: FleetBgNotificationWire, rowIds: readonly string[], evidence: BgNotificationAcceptEvidence): void;
|
|
135
181
|
}
|
|
136
182
|
/** REF-CC-044(fleet2-06)/REF-CC-039(fleet2-01):`createFleetLedger` 的可选装配位。 */
|
|
137
183
|
export interface CreateFleetLedgerOptions {
|
|
@@ -350,6 +350,26 @@ export function createFleetLedger(hooks = {}, opts = {}) {
|
|
|
350
350
|
debug('[fleet-frame] MALFORMED bg_notification dropped(taskId 或 status 缺席/非串)');
|
|
351
351
|
return;
|
|
352
352
|
}
|
|
353
|
+
// 🔴 #284 尾件(codex 复审第四轮 high,红先绿后=B3-DIRTY):`parentTaskId` 是**隔离判据的锚**,
|
|
354
|
+
// 它的脏形必须先于判据处置。下面那道 own/foreign 门写成
|
|
355
|
+
// `typeof === 'string' && length > 0 && !isOwnEngineRun(…)` —— 对 number/object/boolean
|
|
356
|
+
// 这类脏值**整条不成立** ⇒ 门不响、通知照收,而新的 evidence 还会把它标成 `absent_parent`
|
|
357
|
+
// (「老引擎没带这个键」的兼容臂):键**带了**,只是坏的,标成缺席是谎报。帧从 wire 上来、
|
|
358
|
+
// SDK 只 JSON.parse + rest-spread 不校型(同 `recentSteps` 处的威胁模型),这条路真实可达。
|
|
359
|
+
// 三态分开,只收紧真正的脏形:
|
|
360
|
+
// · 键缺席 / `undefined` / `null` / 空串 ⇒ absent-放行臂**照旧**(与 `wireParentId`
|
|
361
|
+
// 「悬空父脏值空串按缺席处理」同一规矩;零新增 drop —— 老引擎与「本来就没有父」不受伤);
|
|
362
|
+
// · 键在场却**不是串**(含 `null`,0.32.0 扫码收紧)⇒ 与 taskId/status 同档 fail-closed:
|
|
363
|
+
// 计 `droppedMalformed`、不写状态面、不回调 accepted 钩子、不入通知队列(隔离优先,
|
|
364
|
+
// 坏值不许买路)。`null` 不入兼容臂的理由:wire schema 只允许**缺席或 string**——
|
|
365
|
+
// 老引擎是不带这个键,不是带 `null`;把 null 归入缺席等于给「外来 parent 锚被置 null」
|
|
366
|
+
// 这一腐败/漂移形留一条绕过 foreign 检查的路(空串是 string 的退化形,真老形,保留)。
|
|
367
|
+
if (n.parentTaskId !== undefined && typeof n.parentTaskId !== 'string') {
|
|
368
|
+
droppedMalformed++;
|
|
369
|
+
debug(`[fleet-frame] MALFORMED bg_notification dropped(parentTaskId 键在场但非串:${typeof n.parentTaskId}` +
|
|
370
|
+
` —— 隔离判据的锚不许被坏值旁路;taskId=${n.taskId})`);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
353
373
|
// 🔴 own/foreign 判别(双开泄漏案帧级防线;[1498]④ 换锚重写):payload `sessionId` 是**子代
|
|
354
374
|
// 自身** session uuid,宿主会话在 ownerSessionId 上且出 wire 前被剥 —— 所以判别锚
|
|
355
375
|
// `parentTaskId`(spawn 该子代的 leader run id)对 own-run 台账:
|
|
@@ -375,7 +395,29 @@ export function createFleetLedger(hooks = {}, opts = {}) {
|
|
|
375
395
|
debug(`[fleet-frame] bg_notification DROPPED foreign/unowned parentRun(taskId=${n.taskId} parentRun=${n.parentTaskId})`);
|
|
376
396
|
return;
|
|
377
397
|
}
|
|
378
|
-
|
|
398
|
+
// 🔴 #284:放行**凭据**定级(additive 第三参喂 accepted 钩子)。臂序 = 上面那道放行判据的
|
|
399
|
+
// 求值序,逐臂对位:`serverFailClosed` 先短路 → `ownByRoot` 次之 → 剩下两格由
|
|
400
|
+
// `parentTaskId` 在不在分开(走到这里且带非空 parentTaskId ⇒ 它必然 ∈ own-run 台账,
|
|
401
|
+
// 否则上面已丢弃)。多臂同时成立报第一条 —— 报的是「这一帧**实际**靠哪条过的门」,
|
|
402
|
+
// 不是「最强的那条理论上也成立」。
|
|
403
|
+
// 🔴 非默认 sessionKey 封顶(0.32.0 发包前扫码采 codex 折中,P-31 的诚实半步):前两格的
|
|
404
|
+
// 会话级读法前提=「这条流是按本 ledger 的会话开的」,而开流参数只认默认槽 ⇒ keyed ledger
|
|
405
|
+
// 上该前提**无法成立也无法校验**。封顶词=`session_anchor_untrusted`(不降到 own_parent:
|
|
406
|
+
// 经 serverFailClosed/ownByRoot 放行的帧,其 parentTaskId 可能是 foreign——标进程成员是
|
|
407
|
+
// 比错标会话更糟的谎报;也不标 absent_parent:键可能在场)。语义=「这帧被会话级臂放行,
|
|
408
|
+
// 但本 ledger 无法信任那个会话锚」——消费方按非证据档自裁。单会话宿主(默认 key,今日
|
|
409
|
+
// 三端)逐字不变;P-31 正位解(per-key 会话锚全套)落地时撤此封顶。
|
|
410
|
+
const sessionEvidenceTrusted = sessionKey === DEFAULT_SESSION_KEY;
|
|
411
|
+
const evidence = serverFailClosed || ownByRoot
|
|
412
|
+
? sessionEvidenceTrusted
|
|
413
|
+
? serverFailClosed
|
|
414
|
+
? 'server_fail_closed'
|
|
415
|
+
: 'own_root'
|
|
416
|
+
: 'session_anchor_untrusted'
|
|
417
|
+
: typeof n.parentTaskId === 'string' && n.parentTaskId.length > 0
|
|
418
|
+
? 'own_parent'
|
|
419
|
+
: 'absent_parent';
|
|
420
|
+
debug(`[fleet-frame] bg_notification taskId=${n.taskId} status=${n.status} seq=${n.seq ?? '-'} accept=${evidence}`);
|
|
379
421
|
// 键域(#242 批 3):`n.taskId` = registry handle = 行 id 的**尾段**。live 集按「整行 id 或
|
|
380
422
|
// 尾段命中」收齐**全部**别名(server 双生行:裸 id 与 `runId childId` 复合形同时在场时两条
|
|
381
423
|
// 一起处理)—— 快照取在任何退休动作之前,原样交给 accepted 钩子。首段(runId)绝不作键
|
|
@@ -435,11 +477,20 @@ export function createFleetLedger(hooks = {}, opts = {}) {
|
|
|
435
477
|
}
|
|
436
478
|
if (rowStatus !== n.status) {
|
|
437
479
|
// 升级(REF-CC-040 腿三):通知是 settle 权威,两读面同步改写,不分叉。
|
|
438
|
-
|
|
480
|
+
// 🔴 #281(0.31.0 扫码 P3 的真窗,红先绿后=B3-P3):`retained` **和** live 行一起改。
|
|
481
|
+
// 只写 retained 的旧形有一个 remove 之前的分叉窗 —— `project()` 对留存条目一律
|
|
482
|
+
// `taskMap.has(id) ⇒ continue`,于是面板读的是 live 行的**旧终态词**(completed)
|
|
483
|
+
// 而事实台账已是 killed:正是 E4-6 注里点名不许出现的「面板绿而详情页 killed」。
|
|
484
|
+
// 常态下同毫秒的 `task_remove` 会把窗压到不可见,但 remove 单独丢失且不重连时它
|
|
485
|
+
// **长驻**(自愈只能等重连 REPLACE 或整窗过期)。live 行改写后两读面当拍同说;
|
|
486
|
+
// 在飞读面不受影响(升级后仍是终态词,照旧不进在飞集),`task_remove` 语义不变。
|
|
487
|
+
const upgraded = { ...known, status: coerced };
|
|
488
|
+
retained.set(id, { row: upgraded, expireAt: nowMs + TERMINAL_RETAIN_MS });
|
|
489
|
+
taskMap.set(id, upgraded);
|
|
439
490
|
}
|
|
440
491
|
writtenIds.add(id);
|
|
441
492
|
applied = true;
|
|
442
|
-
// live
|
|
493
|
+
// live 终态行不退休(remove 帧随后清;终态行不进在飞读面)——只在升级时同步它的状态词。
|
|
443
494
|
}
|
|
444
495
|
else {
|
|
445
496
|
// 退休:live 行离开在飞集,进留存池,带 E4-7 解耦标(见 bgView.read)。
|
|
@@ -537,7 +588,7 @@ export function createFleetLedger(hooks = {}, opts = {}) {
|
|
|
537
588
|
// 钩子类型虽 void,宿主传 async 函数合法(P-F3):返回 thenable 时必须承接拒绝,
|
|
538
589
|
// 否则 async 消费方一炸就是 unhandledRejection = Node 缺省整进程崩(TUI 最坏形,
|
|
539
590
|
// engineSubagentTail meta sink 同族先例)。同步抛与异步拒同一条留痕出口。
|
|
540
|
-
const out = hooks.onBgNotificationAccepted?.(n, rowIds);
|
|
591
|
+
const out = hooks.onBgNotificationAccepted?.(n, rowIds, evidence);
|
|
541
592
|
if (out && typeof out.then === 'function') {
|
|
542
593
|
void Promise.resolve(out).catch((e) => {
|
|
543
594
|
hostLog('debug', `[fleet-frame] onBgNotificationAccepted hook rejected: ${String(e).slice(0, 160)}`);
|
|
@@ -6,5 +6,30 @@ export type SubagentSteerOutcome = {
|
|
|
6
6
|
reason: 'no-wire' | 'no-run' | 'not-running' | 'error';
|
|
7
7
|
detail?: string;
|
|
8
8
|
};
|
|
9
|
-
/**
|
|
10
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Steer a running engine subagent (target = its parentToolCallId, or agentName).
|
|
11
|
+
*
|
|
12
|
+
* 🔴 #280 件A(取址第四腿并入 [4000] Q3=B 口径):`childTaskId` 在场时**台账优先、缺席即诚实缺席**,
|
|
13
|
+
* 与 tail / taskOutput·taskStop / subagentOutput 三腿同姿势,也与孪生的 resume 腿共用**同一个**
|
|
14
|
+
* 取址判据(`resolveOwnerRunId`,不是第二份手抄)。此前本腿直取 `activeEngineRunId()` —— 正是
|
|
15
|
+
* `engineSubagentResume` 头注点名的那处不对称(「同一个 agent 有时能说话有时不能」):
|
|
16
|
+
* · 进程级活跃 run 被并发 main/fork 流互相覆写、又在每个 query 的 finally 清空 ⇒ 后台子代在
|
|
17
|
+
* 宿主 turn 收尾之后仍在跑时,这里恒空(no-run,消息发不出去);另一条 turn 正飞时它有值
|
|
18
|
+
* 但**是别的 run**(打到不相干的 run 上 —— 错值比缺席更坏,与 Q3 同一条理由)。
|
|
19
|
+
* 三态(`resolveOwnerRunId` 的单一判据):台账有这一行 ⇒ 用行值(哪怕此刻另有 run 在飞);
|
|
20
|
+
* 指名了行而台账缺席 ⇒ `no-run` + `noteBgOwnerAbsence` 留痕(绝不回落在飞 run);
|
|
21
|
+
* **没指名行**(既有两参调用)⇒ 才回落在飞 run —— 老调用方逐字零行为变化,本参数是 additive。
|
|
22
|
+
*
|
|
23
|
+
* 🔴 会话参数**二态**(codex 复审 medium 采纳:「取了台账的 run 却配现势 session」是确定性 404):
|
|
24
|
+
* · 行登记时**捕到了会话** ⇒ 用**行的会话**(与行的 runId 同源同拍,是唯一自洽的组合);
|
|
25
|
+
* · 行**没有**随行会话(`recordBgParentRun` 只写 run —— 今天 bg 行多数是这一格)⇒ 退**现势会话**。
|
|
26
|
+
* 刻意**不**跟 resume 腿的「没捕到就干脆不带」:本腿与 tail/subagentOutput 同族,遵 [1498]③
|
|
27
|
+
* 无条件带 session 的双版本兼容纪律;runId 既已由台账钉死,配错的 `{runId, session}` 组合在
|
|
28
|
+
* session-bound run 上是 server 干净的 fail-closed 404(落既有 `not-running` 出路),**不会**把消息
|
|
29
|
+
* 投到别的会话 —— 而「没捕到就不带」会让今天最常见的那一格整批 404。resume 那边更严是因为它是
|
|
30
|
+
* AT-MOST-ONCE 的叫醒(真跑一轮,不可回收),两者的代价不对称。
|
|
31
|
+
* 诚实边界:行无会话且端已切到别的会话时,对旧行 steer 会得到 `not-running`(而非静默投错人)。
|
|
32
|
+
*
|
|
33
|
+
* @param childTaskId 这一行子代的 taskId(fleet 行 id 尾段;端有行上下文时**应当**传)。
|
|
34
|
+
*/
|
|
35
|
+
export declare function steerEngineSubagent(target: string, text: string, childTaskId?: string): Promise<SubagentSteerOutcome>;
|
|
@@ -27,15 +27,51 @@
|
|
|
27
27
|
import { hostLog } from '../host.js';
|
|
28
28
|
import { makeEngineWireClient } from '../engineWireSdk.js';
|
|
29
29
|
import { engineWireTarget } from '../engineWireTarget.js';
|
|
30
|
-
import {
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
import { engineSessionParamSpread } from '../engineSessionParam.js';
|
|
31
|
+
import { getBgParentRunOwner } from '../subagentContentStore.js';
|
|
32
|
+
import { resolveOwnerRunId } from './engineSubagentResume.js';
|
|
33
|
+
import { noteBgOwnerAbsence } from './subagentOwnerAbsence.js';
|
|
34
|
+
/**
|
|
35
|
+
* Steer a running engine subagent (target = its parentToolCallId, or agentName).
|
|
36
|
+
*
|
|
37
|
+
* 🔴 #280 件A(取址第四腿并入 [4000] Q3=B 口径):`childTaskId` 在场时**台账优先、缺席即诚实缺席**,
|
|
38
|
+
* 与 tail / taskOutput·taskStop / subagentOutput 三腿同姿势,也与孪生的 resume 腿共用**同一个**
|
|
39
|
+
* 取址判据(`resolveOwnerRunId`,不是第二份手抄)。此前本腿直取 `activeEngineRunId()` —— 正是
|
|
40
|
+
* `engineSubagentResume` 头注点名的那处不对称(「同一个 agent 有时能说话有时不能」):
|
|
41
|
+
* · 进程级活跃 run 被并发 main/fork 流互相覆写、又在每个 query 的 finally 清空 ⇒ 后台子代在
|
|
42
|
+
* 宿主 turn 收尾之后仍在跑时,这里恒空(no-run,消息发不出去);另一条 turn 正飞时它有值
|
|
43
|
+
* 但**是别的 run**(打到不相干的 run 上 —— 错值比缺席更坏,与 Q3 同一条理由)。
|
|
44
|
+
* 三态(`resolveOwnerRunId` 的单一判据):台账有这一行 ⇒ 用行值(哪怕此刻另有 run 在飞);
|
|
45
|
+
* 指名了行而台账缺席 ⇒ `no-run` + `noteBgOwnerAbsence` 留痕(绝不回落在飞 run);
|
|
46
|
+
* **没指名行**(既有两参调用)⇒ 才回落在飞 run —— 老调用方逐字零行为变化,本参数是 additive。
|
|
47
|
+
*
|
|
48
|
+
* 🔴 会话参数**二态**(codex 复审 medium 采纳:「取了台账的 run 却配现势 session」是确定性 404):
|
|
49
|
+
* · 行登记时**捕到了会话** ⇒ 用**行的会话**(与行的 runId 同源同拍,是唯一自洽的组合);
|
|
50
|
+
* · 行**没有**随行会话(`recordBgParentRun` 只写 run —— 今天 bg 行多数是这一格)⇒ 退**现势会话**。
|
|
51
|
+
* 刻意**不**跟 resume 腿的「没捕到就干脆不带」:本腿与 tail/subagentOutput 同族,遵 [1498]③
|
|
52
|
+
* 无条件带 session 的双版本兼容纪律;runId 既已由台账钉死,配错的 `{runId, session}` 组合在
|
|
53
|
+
* session-bound run 上是 server 干净的 fail-closed 404(落既有 `not-running` 出路),**不会**把消息
|
|
54
|
+
* 投到别的会话 —— 而「没捕到就不带」会让今天最常见的那一格整批 404。resume 那边更严是因为它是
|
|
55
|
+
* AT-MOST-ONCE 的叫醒(真跑一轮,不可回收),两者的代价不对称。
|
|
56
|
+
* 诚实边界:行无会话且端已切到别的会话时,对旧行 steer 会得到 `not-running`(而非静默投错人)。
|
|
57
|
+
*
|
|
58
|
+
* @param childTaskId 这一行子代的 taskId(fleet 行 id 尾段;端有行上下文时**应当**传)。
|
|
59
|
+
*/
|
|
60
|
+
export async function steerEngineSubagent(target, text, childTaskId) {
|
|
33
61
|
const cfg = engineWireTarget();
|
|
34
62
|
if (!cfg)
|
|
35
63
|
return { ok: false, reason: 'no-wire' };
|
|
36
|
-
|
|
37
|
-
|
|
64
|
+
// 空串/非串按「没指名行」处理(与 resolveOwnerRunId 的 named 判据同口径);窄成 string|undefined
|
|
65
|
+
// 而不是 boolean —— 布尔位不给 TS 窄化,留痕那一行会被迫 cast。
|
|
66
|
+
const named = typeof childTaskId === 'string' && childTaskId !== '' ? childTaskId : undefined;
|
|
67
|
+
const runId = resolveOwnerRunId(named);
|
|
68
|
+
if (!runId) {
|
|
69
|
+
// 指名了行才算「台账缺席」——不指名的通用调用只是没有在飞 run,不是宿主映射缺席(两者出路
|
|
70
|
+
// 同为 no-run,但只有前者是 Q3 要响亮的那一类)。
|
|
71
|
+
if (named !== undefined)
|
|
72
|
+
noteBgOwnerAbsence('subagent-steer', named);
|
|
38
73
|
return { ok: false, reason: 'no-run' };
|
|
74
|
+
}
|
|
39
75
|
const client = makeEngineWireClient({
|
|
40
76
|
baseUrl: cfg.baseUrl,
|
|
41
77
|
...(cfg.token ? { token: cfg.token } : {}),
|
|
@@ -43,9 +79,13 @@ export async function steerEngineSubagent(target, text) {
|
|
|
43
79
|
});
|
|
44
80
|
if (!client)
|
|
45
81
|
return { ok: false, reason: 'no-wire' };
|
|
82
|
+
// 会话二态(见头注):行登记的会话优先,缺则退现势会话。这里是同一张表的第二次同步读
|
|
83
|
+
// (与上面 `resolveOwnerRunId` 之间无 await ⇒ 同一拍),取址判据本身仍只有那一处,不另铸。
|
|
84
|
+
const rowSession = named !== undefined ? getBgParentRunOwner(named)?.sessionId : undefined;
|
|
85
|
+
const session = rowSession !== undefined ? { session: rowSession } : engineSessionParamSpread();
|
|
46
86
|
try {
|
|
47
87
|
// service 1.89 receipt shape: { note: "Message queued for delivery to <name> …", delivery, … }
|
|
48
|
-
const body = await client.runs.steerSubagent(runId, target, { content: text },
|
|
88
|
+
const body = await client.runs.steerSubagent(runId, target, { content: text }, session);
|
|
49
89
|
let receipt = 'Message queued for delivery at the next tool round.';
|
|
50
90
|
const copy = body?.note ?? body?.message;
|
|
51
91
|
if (typeof copy === 'string' && copy.length > 0)
|
|
@@ -23,7 +23,7 @@
|
|
|
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
|
+
| 公开导出面 | **741** 个运行期符号(+ 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
|
|
|
@@ -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`(**741** 项)。
|
|
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
|
+
实测:741 项 **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
|
+
741 项的内部构成(帮助端估读表大小):**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 域,逐域计数之和 = 741)
|
|
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 + 面板侧信道台账** | 82 | `tailEngineSubagent` · `installSubagentActivitySink` · `installSubagentTailMetaSink`(#280 件2:tail meta 帧发布口,`contentFrames` 判别位载体)· `stopEngineTask` + `classifyTaskStopConflict` · `fetchEngineSubagentReport` · `steerEngineSubagent`(0.32.0 未发布 #280 件A:additive 第三参 `childTaskId` —— 端有行上下文时**应当**传,传了就走「台账优先 / 缺席即诚实缺席 + `noteBgOwnerAbsence` 留痕」的 Q3 口径,与 tail·taskOutput·subagentOutput 三腿同姿势、与孪生 resume 腿共用同一个 `resolveOwnerRunId` 判据;**不传**则逐字维持旧行为=回落在飞 run)· `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) 一条)· `clearBgTerminalFacts`(#242 批 3 扫码修:复活=新周期,旧周期终态事实作废——fleetLedger 复活两腿按尾段清账,factsAccepted 方向核不再拿上周期终态当先例)· `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 台账) |
|
|
@@ -644,9 +644,35 @@ HITL 面有 `hitlHostSurfaceFor` —— 见 §5a 的 (a) 表;队列口没有,登
|
|
|
644
644
|
(fail-closed,跨会话不认领)。desktop 的 `sessionForEngineId` 单腿判定应收敛到本判据,
|
|
645
645
|
**传 `sessionKey` + 自注入 `isOwnRun`**。
|
|
646
646
|
|
|
647
|
+
#### `onBgNotificationAccepted` 的 `evidence` 参数(0.32.0 未发布,additive 第三参)
|
|
648
|
+
|
|
649
|
+
`createFleetLedger` 的 `FleetLedgerHooks.onBgNotificationAccepted(n, rowIds, evidence)` 在 `bg_notification`
|
|
650
|
+
帧**通过归属判别之后**回调(被隔离丢弃/畸形早退的不回调)。0.32.0 起补第三参 `evidence`
|
|
651
|
+
(`BgNotificationAcceptEvidence`,type-only 导出):这一帧**凭哪一条臂**放行的。
|
|
652
|
+
**两参消费方(0.31.0 形)零改动照旧** —— 多出来的实参 JS 侧自然忽略。
|
|
653
|
+
|
|
654
|
+
🔴 **每个词命名的是「放行臂」,不是「归属结论」**;三档强度,中间两格**不同档**。
|
|
655
|
+
|
|
656
|
+
🔴 **前两格的会话级读法带一个前提:喂给这个 ledger 的 fleet 流,是按这个 ledger 的会话开的。**
|
|
657
|
+
ledger **自己不开流**(帧体归库、连接归端),无从校验;而本包的开流参数 `fleetStreamOptions()` /
|
|
658
|
+
`fleetSnapshotOptions()` 是 **module 级、只认默认槽会话**,连 `sessionKey` 都拿不到。
|
|
659
|
+
⇒ **单会话宿主**(cli 及今天的三端)前提恒成立;⇒ **keyed 多会话宿主**必须自己保证流与 ledger 同会话,
|
|
660
|
+
否则前两格会替**别的会话**作证 —— 在册局限 **P-31**(§7c)。
|
|
661
|
+
|
|
662
|
+
| `evidence` | 放行臂 | 强度 | 端该怎么读 |
|
|
663
|
+
|---|---|---|---|
|
|
664
|
+
| `server_fail_closed` | meta `bgNotifyFailClosed` ∧ 本连接 session-bound | **会话级证明**(带上述流前提) | 最强:server 侧注入路已 fail-closed,到达即**这条流的**会话。⚠️ 这一臂**完全不读会话端口**,信的就是「这条流替谁开的」;前提破了它不会报错,而它正是**生产 meta 组合**下命中的那一格 |
|
|
665
|
+
| `own_root` | 通知 `rootSessionId` === `engineSessionParam()`(固定点语义) | **会话级证明**(带上述流前提) | 判据读的是 **`DEFAULT_SESSION_KEY` 槽**的 `SessionPort`,**不是**本 ledger 的 `sessionKey` 槽 —— 单会话宿主两者恒同 |
|
|
666
|
+
| `own_parent` | `parentTaskId`(spawn 该子代的 leader run)∈ own-run 台账 | **进程级成员证明** | 🔴 只证明「本进程曾亲手驱动过这条 run」,**不区分会话代际** —— own-run 台账是进程级 `Set`、按会话零分区,`/clear` 或换会话后**旧会话**的 run 仍命中(= 在册局限 **P-13** 在通知面的同一张脸)。**不得**当作「属于当前会话」;要按会话归属做事,按 P-13 的出路自注入会话粒度判据,或只认上面两格 |
|
|
667
|
+
| `absent_parent` | 通知**没带** `parentTaskId` | **非证据** | 🔴 absent-放行姿势(老引擎/老帧形不带该键,fail-closed 会把自家通知整批吞掉)。端要拿归属做有副作用的事(落库/跨会话搬运/翻别人的卡)时,这一格应自裁为「未证明」 |
|
|
668
|
+
|
|
669
|
+
臂序 = 包内放行判据的**求值序**,多臂同时成立报**实际过门的第一条**(不报「最强的那条理论上也成立」)。
|
|
670
|
+
词表开集:端对未知词按 `absent_parent` 一档兜底最安全。真源 = `src/fleet/fleetLedger.ts` 的
|
|
671
|
+
`BgNotificationAcceptEvidence` 顶注。
|
|
672
|
+
|
|
647
673
|
### 6e. 🔴 已知局限(多会话端**接之前必读**)
|
|
648
674
|
|
|
649
|
-
见 §7c 的 **P-10 ~ P-14**
|
|
675
|
+
见 §7c 的 **P-10 ~ P-14 + P-31** 六条 —— 全部是 sessionKey 面的在册局限,
|
|
650
676
|
CHANGELOG 0.29.0「已知局限」段与相应 JSDoc 都有成文。**别在读完它们之前接卡口与 plan review。**
|
|
651
677
|
|
|
652
678
|
其中两条会直接把多会话端坑到「装了但不生效」:
|
|
@@ -691,6 +717,7 @@ CHANGELOG 0.29.0「已知局限」段与相应 JSDoc 都有成文。**别在读
|
|
|
691
717
|
| **P-12** | low | canonical 重呈短路臂**沿用 arm responder** ⇒ `ReopenPlanReviewOpts.deliverDecision` 注入口**不生效**(成文例外 + debug 留痕) | `src/hitl/planReviewWire.ts`(`mintFreshQuestionId:false` 分支) | 任何**包装决断投递**的端(重试/退避/上屏定序,cli 的 `decideRetry` 是参照)必须走默认 `mintFreshQuestionId` 臂 —— 否则你的包装被静默旁路,跑的是裸 `decidePlanReview` 的 fire-and-forget |
|
|
692
718
|
| **P-13** | 成文局限(不改行为) | 默认键下的 own-run 归属缺省腿是**进程级**证据,**不区分同一宿主进程内的会话代际** —— `/clear` 前登记的 run 在新会话语境下**仍判 owned**。最坏后果逐字:`用户看到自己旧会话的审批卡` | `src/hitl/parkOwnership.ts`(`ParkOwnershipDeps.isOwnRun` JSDoc);`docs/REFACTOR-LEDGER.md` 记为**驳为成文局限** | 多会话端必须**自注入**会话粒度的 `isOwnRun`;或传非默认 `sessionKey` 并接受缺省腿被整条跳过(代价 = 多一次诚实的 reopen-failed) |
|
|
693
719
|
| **P-14** | high(打包面) | `activeReopenResponders` 的**单活纪律是 module 单例**:两份实例 ⇒ 各退各的,跨份的旧卡退役不掉 —— **退化回修复前的重复活卡形** | `src/hitl/planReviewWire.ts`(`activeReopenResponders`,singleton-manifest 在册) | 见 §8-G:必须保证 bundle 里只有**一份** `@sema-agent/client-core` |
|
|
720
|
+
| **P-31** | med(0.32.0 未发布登记;codex 复审第二/三轮抓出) | **fleet 面的会话锚整条走默认槽,不跟 ledger 的 `sessionKey`** —— 两条腿都中招:①`bg_notification` 的 `ownByRoot` 判据用 `engineSessionParam()` = `hostSessionFor(DEFAULT_SESSION_KEY)`;②更强的一条 —— `serverFailClosed`(meta 两位)**根本不读会话端口**,它信的是「这条流是替谁开的」,而本包给出的开流参数 `fleetStreamOptions()` / `fleetSnapshotOptions()` 是 **module 级函数、只认默认槽会话**,连 `sessionKey` 都拿不到。⇒ keyed 多会话宿主上,一条按默认槽开的流接到**非默认键** ledger 时,属于默认槽会话的通知会被放行并以 `evidence:'server_fail_closed'`(🔴 **生产 meta 组合下命中的正是这一格**)或 `'own_root'` 交给 hook | `src/fleet/fleetLedger.ts`(`serverFailClosed` / `ownByRoot` / `fleetStreamOptions`);`src/host.ts`(`hostSession` = 默认键) | 单会话宿主(cli 及今天的三端)**不受影响**——默认槽就是它的会话,流与 ledger 恒同会话。**keyed 多会话宿主**:①必须自己保证「流按哪个会话开、就接给哪个 ledger」;②在①落实之前,不要把 `evidence` 的前两格当作「属于本 ledger 会话」的证明去做有副作用的事(落库/翻卡/跨会话搬运)。🔴 正位解 = fleet 面**整条**换 per-key 会话锚(开流参数 + 归属判据一起动);**只改判据这一半会自相矛盾**(按默认槽开流、按 keyed 槽判定 ⇒ 反而丢自己的通知),故本批**不动行为**,登记待裁 |
|
|
694
721
|
|
|
695
722
|
### 7d. 请求面与其它在册件
|
|
696
723
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/client-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.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",
|