@sema-agent/client-core 0.38.0 → 0.38.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -17
- package/README.md +2 -1
- package/dist/adapter/activeRunSelfHeal.d.ts +1 -1
- package/dist/adapter/activeRunSelfHeal.js +6 -6
- package/dist/adapter/downstream/eventToSdkMessage.js +1 -1
- package/dist/agentSession/backgroundView.d.ts +1 -1
- package/dist/agentSession/backgroundView.js +1 -1
- package/dist/fleet/fleetLedger.d.ts +1 -1
- package/dist/fleet/fleetLedger.js +3 -3
- package/dist/hitl/armedGateRegistry.d.ts +1 -1
- package/dist/hitl/armedGateRegistry.js +2 -2
- package/dist/hitl/hitlBridge.js +5 -5
- package/dist/hitl/persistedRulesWire.d.ts +1 -1
- package/dist/hitl/persistedRulesWire.js +4 -4
- package/dist/request/taskRequest.d.ts +1 -1
- package/dist/request/taskRequest.js +8 -8
- package/dist/subagent/engineSubagentResume.d.ts +2 -2
- package/dist/subagent/engineSubagentResume.js +1 -1
- package/dist/subagent/engineSubagentSteer.d.ts +1 -1
- package/dist/subagent/engineSubagentSteer.js +1 -1
- package/dist/subagent/subagentOwnerAbsence.js +1 -1
- package/dist/subagentContentStore.js +1 -1
- package/dist/wireErrorTriage.js +1 -1
- package/docs/INTEGRATION-CLIENTS.md +4 -4
- package/docs/REFACTOR-LEDGER.md +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -23,11 +23,21 @@
|
|
|
23
23
|
> 既有行)⇒ 标题**不回改、且永远不会被改**(`b165f3f` 那个 commit 的字节是历史,任何未来提交
|
|
24
24
|
> 都改不到它)⇒ 这条勘误与门侧窄豁免(`run-integration-doc-freshness-test.mjs` ④b
|
|
25
25
|
> `KNOWN_HEADING_ERRATA`,登记 `version: '0.36.0', releasedAt: 'b165f3f'`)**都是永久的**,不是
|
|
26
|
-
> 「下一版删掉」的临时态(
|
|
26
|
+
> 「下一版删掉」的临时态(对抗复审 finding①:那样写会让豁免一删,门在**任何**后续版本上
|
|
27
27
|
> 都会重新对这个永久冻结的标题判红,退休条件不可能被满足)。门侧核验两件事把这条勘误钉死、
|
|
28
28
|
> 不许悄悄漂:豁免登记的 `releasedAt` 与 `FROZEN` 账上 0.36.0 那一行逐字相等;本段(点名版本号
|
|
29
29
|
> `0.36.0` + 关键字「勘误」)必须还在这份头注里 —— 删掉本段而不同批把门侧豁免一起处理,门当场红。
|
|
30
30
|
|
|
31
|
+
## 0.38.1(2026-08-21)
|
|
32
|
+
|
|
33
|
+
**patch:纯出包卫生版,零 API/行为变化。**
|
|
34
|
+
|
|
35
|
+
- **出包面词汇消毒**:dist 注释(tsc 编译保留注释)与随包 Markdown 全量按发布词汇规范清理;
|
|
36
|
+
历史 CHANGELOG 段同批词面映射(零语义改动),冻结账按消毒纪元(commit `a0488b2`)重钉,
|
|
37
|
+
`run-integration-doc-freshness` ④a2s 新增消毒等价核 —— 借消毒偷加行/改语义照样红。
|
|
38
|
+
- **新门 `run-package-hygiene`**:`package.json` `files` 全实体(dist js/d.ts + 随包 md)逐行过
|
|
39
|
+
发布词汇禁表(自检 + allow 双向),gates-manifest 32 套。
|
|
40
|
+
|
|
31
41
|
## 0.38.0(2026-08-21)
|
|
32
42
|
|
|
33
43
|
**#318 四件合批 + SDK 7.2.0 提货跟车。行为面五条**:一条新投影臂(additive)、一条超时预算收窄
|
|
@@ -40,7 +50,7 @@
|
|
|
40
50
|
> `isDelegationCapCode` · `wireCycleSeq` / `wireRetiredBy`。
|
|
41
51
|
> 🔴 **peer 地板抬到 `>=7.2.0`**(见下「SDK 7.2.0 提货」);装 <7.2.0 SDK 的端提货前先抬依赖。
|
|
42
52
|
|
|
43
|
-
- **🆕 件① `engine_notice` 投影臂(additive,server ≥7.36 / #310
|
|
53
|
+
- **🆕 件① `engine_notice` 投影臂(additive,server ≥7.36 / #310;协作板 [4679] 发布、[4682] cli 认领)**:
|
|
44
54
|
引擎的结构化通告里被 server 判为面向**本会话终端用户**的那一小撮,现在有了投影口 ——
|
|
45
55
|
`eventToSdkMessage` → 中性内部臂 `engine_notice` → `adapt()` → chrome 臂
|
|
46
56
|
`{ kind:'engine_notice', code, message, detail, sessionId?, ts?, eventId? }`。**不铸 transcript 行**
|
|
@@ -54,7 +64,7 @@
|
|
|
54
64
|
消费纪律五条(按 code+detail 渲 / message 仅 fallback / 未知码不许丢 / durable 重放幂等 /
|
|
55
65
|
`harvest_quarantined` 的 `moved` 与 `escalated` **不可相减**)写在 `seam.ts` 臂注释与
|
|
56
66
|
`docs/INTEGRATION-CLIENTS.md` §3f。**壳渲染半场不在本批**(cli 0.38.0 提货时做)。
|
|
57
|
-
🔴 **重放身份两个键(
|
|
67
|
+
🔴 **重放身份两个键(对抗复审 [medium] 采纳,当窗修)**:首版只带 `eventId`,于是 body 不带
|
|
58
68
|
它时留给端的唯一去重口是 `code+ts` —— 而 `ts` 是 **server 观察时刻(ms)**,同毫秒同码的两条真通告
|
|
59
69
|
会被折成一条、跨重连的同一条又可能重复。而 SDK 的 SSE parser **本来就把 durable `id:`
|
|
60
70
|
(= `task_event.seq`)stamp 到 `ev.id` 上**(`dist/sse.js`),本包也早有规范访问口
|
|
@@ -82,7 +92,7 @@
|
|
|
82
92
|
🔴 **两条残余同步形如实登记**(收窄不等于长调用消失了,[4687] 逐字):终局形仍存在于 ①pre-7.37
|
|
83
93
|
的 server、②≥7.37 但没有 durable run 行可跟的部署。本包是三端共用件,故重试环保留、总窗不设 0;
|
|
84
94
|
这两形上窗尽的代价是**重呈臂**(同一张卡重交用户)而不是把 turn 判死。
|
|
85
|
-
🔴 **「至少重试一次」改由发数保证(
|
|
95
|
+
🔴 **「至少重试一次」改由发数保证(对抗复审 [medium] 采纳,当窗修)**:per-attempt 帽是
|
|
86
96
|
**宿主构造 client 时**定的(本桥吃注入 client,`HitlClientLike` 根本不暴露 `timeoutMs`)⇒
|
|
87
97
|
「总窗 ÷ 帽 = 发数」这条推导只在 SDK **缺省值**上成立,不能当本共用件的性质 —— 一个 120s client
|
|
88
98
|
的宿主在首发超时那一刻 elapsed 已 ≥ 窗,**一次重试都拿不到**,而上面那两条残余同步形恰恰最需要
|
|
@@ -217,7 +227,7 @@
|
|
|
217
227
|
`settleByBase`(`engineCapsSettled` 的语义是「等**当前这一次**探测落地」,抽走在途 promise 会让
|
|
218
228
|
await 方拿到假「已落地」);空串 ⇒ no-op,从没探过也没失效过的 base ⇒ **真 no-op 不留代际条目**;
|
|
219
229
|
绝不 throw。**per-base**,不连坐别的 base。
|
|
220
|
-
🔴 **代际计数 `genByBase`(
|
|
230
|
+
🔴 **代际计数 `genByBase`(对抗复审 [high] 采纳,同批修)**:失效口清了 `inFlight` 之后同
|
|
221
231
|
base 立刻可以再 kick ⇒ **两次探测并发跑在同一份无版本共享态上**。没有代际时两个真后果,都发生在
|
|
222
232
|
「重启撞上一次慢 capabilities 请求」这个最该正确的时刻 —— ①旧引擎那次**后**落地会把 caps 覆盖回
|
|
223
233
|
旧引擎的位(失效等于没做);②旧探测的 `finally` 删掉的是**新 run** 的 `inFlight`/`settleByBase`
|
|
@@ -290,7 +300,7 @@
|
|
|
290
300
|
判死是三条出路里唯一不可逆的,只配给引擎答了且拒了的语义失败。引擎真死时失败尽快显形:下一轮
|
|
291
301
|
approvals.list 当场失败走既有诚实红。门:`run-hitl-gate-honesty-test.mjs` 新 F6 组(红先绿后 +
|
|
292
302
|
四变异各自抓红);耗尽错 message 有负控钉「不落 isAlreadyResolvedGateReason 词表」。
|
|
293
|
-
⚠️ **请托 SDK 半场(
|
|
303
|
+
⚠️ **请托 SDK 半场(候协作板)**:decide 的 per-call 面今天无 timeout 旋钮(只有 signal,合流只能
|
|
294
304
|
收短)—— 若 SDK 给 decide 开 per-call timeoutMs(或对 HITL 面单列长缺省),超时类重试环可整段
|
|
295
305
|
收敛成一发长等待;#316(快速 ack)落地后总窗亦可回收。
|
|
296
306
|
- **🔴 P0b:Session busy 幽灵 running 的三选卡半场(存活对账 + 已呈现登记)**。病(案卷车2):
|
|
@@ -316,7 +326,7 @@
|
|
|
316
326
|
[4664] 四个洞(park 态不在 reapStale 射程 / updated_at 误杀窗 / 409 与 poll 口径短暂相左 /
|
|
317
327
|
LOCAL 无周期腿)在 `ActiveRunBusySignal.activeTaskStatus` 与存活对账腿的注释上逐条引用 ——
|
|
318
328
|
409 的 `running` 不是绝对真值。
|
|
319
|
-
④
|
|
329
|
+
④ **对抗复审跟修(同批,真病三件)**:[high] 对账读回**终态词**(completed/failed/blocked/
|
|
320
330
|
timeout)不再落 not-parked(其默认文案对已终结的 run 说「wait for it to finish」= 永远等不到的
|
|
321
331
|
假话)—— 专属结局 **`running-settled`**(诚实说 claim 陈旧/释放中 + 重发指引);[medium] 对账
|
|
322
332
|
期间用户 Esc ⇒ 探测归来后零派臂/零出卡/零登记(零动作现状行收口);[medium 轻形] 登记表 FIFO
|
|
@@ -508,7 +518,7 @@
|
|
|
508
518
|
|
|
509
519
|
## 0.34.0(2026-08-17)
|
|
510
520
|
|
|
511
|
-
**#292 P1(cli
|
|
521
|
+
**#292 P1(cli 协作板 [4285]/[4309] 定谳)——`REQUEST_FIELD_MATRIX` 的 `settings.<resolved>` 行补
|
|
512
522
|
`print` 车道、撤 `gap:true`。行为面**一条**:print 车道的 `buildTaskRequest` 从此会把端给的
|
|
513
523
|
`input.settings.resolved` 摊开上 wire(端不给值 ⇒ 仍零 stamp,不会凭空出现)。**
|
|
514
524
|
|
|
@@ -562,7 +572,7 @@
|
|
|
562
572
|
留痕(腿名 `subagent-steer`,每 (腿,taskId) 至多一条 warn),绝不回落在飞 run;**没指名行**
|
|
563
573
|
(既有两参调用)⇒ 才回落在飞 run。⚠️ 端侧收益要**端传了 `childTaskId` 才到达用户**:壳/web/桌面
|
|
564
574
|
凡有行上下文的 steer 调用点(壳 `REPL.tsx` 的 `onAgentSubmit` 已持 `task.id`)应跟一行把它传下来。
|
|
565
|
-
**会话参数同批改二态**(
|
|
575
|
+
**会话参数同批改二态**(对抗复审 medium):行登记时捕到了会话 ⇒ 用**行的会话**(与行的 runId
|
|
566
576
|
同源同拍,是唯一自洽的组合;旧形「台账 run + 现势 session」在 session-bound run 上是确定性
|
|
567
577
|
404,等于修好了 run 定位却仍然 steer 不到);行没有随行会话(`recordBgParentRun` 只写 run,
|
|
568
578
|
今天 bg 行多数是这一格)⇒ 退现势会话。刻意**不**跟 resume 腿的「没捕到就干脆不带」——本腿遵
|
|
@@ -580,14 +590,14 @@
|
|
|
580
590
|
`'server_fail_closed' | 'own_root' | 'own_parent' | 'absent_parent'`,按**实际命中的放行臂**铸值
|
|
581
591
|
(臂序 = 放行判据求值序,多臂同时成立报第一条)。旧签名把四条**强度不同**的臂压成一个「已放行」
|
|
582
592
|
布尔事实,端因此无法分级处置。
|
|
583
|
-
🔴 **三档强度,不是四档递减**(
|
|
593
|
+
🔴 **三档强度,不是四档递减**(对抗复审 high 采纳;首版注把中间一格写成「硬证据」是过度声称):
|
|
584
594
|
**会话级证明** = `server_fail_closed` / `own_root`;**进程级成员证明** = `own_parent` —— own-run 台账是
|
|
585
595
|
进程级 `Set`、按会话零分区,`/clear` 或换会话后**旧会话**的 run 仍命中(= 在册局限 **P-13** 在
|
|
586
596
|
通知面的同一张脸),端**不得**把它读成「属于当前会话」;**非证据** = `absent_parent`(通知没带
|
|
587
597
|
`parentTaskId`;老引擎/老帧形不带该键,fail-closed 会把自家通知整批吞掉)。端拿归属做有副作用的事
|
|
588
598
|
(落库/跨会话搬运/翻别人的卡)时,后两格都应自裁为「未证明当前会话归属」。
|
|
589
599
|
两参消费方(0.31.0 形)零改动照旧;接入说明见 `docs/INTEGRATION-CLIENTS.md` §6d。
|
|
590
|
-
- **#284 尾件 / `parentTaskId` 脏形 fail-closed(
|
|
600
|
+
- **#284 尾件 / `parentTaskId` 脏形 fail-closed(行为面收紧,对抗复审第四轮 high)**:
|
|
591
601
|
own/foreign 隔离门写成 `typeof === 'string' ∧ length>0 ∧ !isOwnEngineRun(…)` —— 对 number /
|
|
592
602
|
object / array / boolean 这类脏值**整条不成立**,门不响、外来通知照收(还会被新 evidence 标成
|
|
593
603
|
`absent_parent`,而那一格的语义是「老引擎**没带**这个键」= 谎报)。fleet 帧从 wire 上来、SDK 只
|
|
@@ -615,7 +625,7 @@
|
|
|
615
625
|
|
|
616
626
|
## 0.31.0(2026-08-16)
|
|
617
627
|
|
|
618
|
-
**#242 批 3 fleet belt 对账承重批(design-242 §3 批 3
|
|
628
|
+
**#242 批 3 fleet belt 对账承重批(design-242 §3 批 3;协作板 [4000] Q2=A / Q3=B 裁定执行)。
|
|
619
629
|
🔴 BREAKING(行为面翻面两处,见下);公开面 additive 三件。**
|
|
620
630
|
|
|
621
631
|
- 🔴 **BREAKING②/Q3 三腿翻面**:`getBgParentRun(x) ?? activeEngineRunId()` 的无条件回落**全部
|
|
@@ -787,7 +797,7 @@
|
|
|
787
797
|
|
|
788
798
|
- **#244 F4 族D:配置/模型表单源化(A-028.17/.18/.20 三件单源上收 + A-028.19 对账钉,additive,
|
|
789
799
|
零 BREAKING)**(2026-08-15;公开导出 707 → 724,+17 全 additive;常驻钉 = pure 门 F4D 段 35
|
|
790
|
-
checks + F4D-seat-catalog-pin 条件腿 4 checks(
|
|
800
|
+
checks + F4D-seat-catalog-pin 条件腿 4 checks(红先绿后:壳钉落地前该腿以断言红留证;复审
|
|
791
801
|
复审两处 [medium] 采纳 —— 腿剥注释锚初始化式防「删钉留注释」假绿、钉补精确等型轴防
|
|
792
802
|
readonly/any 漂移,后者以 d.ts 变异 readonly 化 → 壳 typecheck 红实证)):
|
|
793
803
|
- **件1 `model/providerCatalog.ts`(A-028.17)**:46 家 provider 表的**规范折表层** ——
|
|
@@ -823,7 +833,7 @@
|
|
|
823
833
|
- **#280 移交两件:probeCause 随卡透传 + tail meta 帧发布口(additive,零 BREAKING)**(2026-08-15;
|
|
824
834
|
红先绿后,常驻钉 = durable-card-display-keys ⑧ 段 10 checks + pure B7 probeCause 八钉 / B6
|
|
825
835
|
tail-meta 六钉(含 fail-soft 两钉:sink 同步抛错不撕裂 tail / async 拒绝有界观察零
|
|
826
|
-
unhandledRejection ——
|
|
836
|
+
unhandledRejection —— 对抗复审 [high] 采纳);壳 #280 围栏 T6 现状锁候本批包透传落地
|
|
827
837
|
翻红接线):
|
|
828
838
|
- **件1 `ApprovalCardRequest.probeCause` 透传**(server ≥7.21.0-rc #253 的结构化探针因由,
|
|
829
839
|
`{code, roots:{shown,total}, further?}`):0.30.3 的两处「帧→卡入参」显式挑键把上游键丢弃 ——
|
|
@@ -892,7 +902,7 @@
|
|
|
892
902
|
缺省读法留壳)· `RulesFailure` + `classifyRulesFailure`(处置分类,404 两支绝不共用一格)·
|
|
893
903
|
`listAllPersistedRules` + `RulesFacade`(keyset 翻页收口:显式 limit 200 / 游标失效丢游标
|
|
894
904
|
重列一次 / 页数硬帽,绝不交半份清单)· `classifySkippedReason`(散文前缀分类)。
|
|
895
|
-
⚠️ 相对 cli 源形收紧两处(
|
|
905
|
+
⚠️ 相对 cli 源形收紧两处(复审 F2 两轮,各有门回归钉):
|
|
896
906
|
① `listAllPersistedRules` 的 params **不收调用方 cursor**(类型 `Omit<…,'cursor'>` + 运行期
|
|
897
907
|
剥除留痕)—— 源形的 `...params` 会把外来 cursor 原样送出,「列全」从中途起步却报完整清单;
|
|
898
908
|
② 页体 **fail-closed 窄化** —— 源形把坏形 2xx 页(`{rev:9}` 无 rules 数组 / rev 非有限数 /
|
|
@@ -945,7 +955,7 @@
|
|
|
945
955
|
镜像脏值的正面证据 ⇒ 不 stand down 放行真分诊;>0 / 抛错 / 读口缺席 ⇒ 保守维持(存量装配零影响)。
|
|
946
956
|
另导出 `INTERACTIVE_WAY_OUT`(默认出路串单源 —— 宿主 resume 对账腿等失败半场要说同一句真出路,
|
|
947
957
|
各写一份必漂;覆盖语义照旧走 `ActiveRunSelfHealCopy.wayOut`)。
|
|
948
|
-
- **#244 F1 · 复审跟修批**(2026-08-14;同窗
|
|
958
|
+
- **#244 F1 · 复审跟修批**(2026-08-14;同窗 复审 轮一三条 + cli 换装批 G-c 尾竞态,红先绿后
|
|
949
959
|
G9⑥/⑦ 四断言):① `waitForClaimRelease` 被**窗自己的截止**掐断的探测不再作废上一发完成读数
|
|
950
960
|
(404/传输错是「回答了不知道」才作废;不区分则 `confirmedHeld` 在窗尾随机翻 false,同输入两跑
|
|
951
961
|
两种文案);② 假死锁复核读口加 **4s 有界窗**并合流 `deps.signal`(probe 恒不落地时整棵分诊树
|
|
@@ -972,7 +982,7 @@
|
|
|
972
982
|
|
|
973
983
|
## 0.30.0 — 2026-08-14
|
|
974
984
|
|
|
975
|
-
> 发车门清账完成:0.30.0 发版扫描 36 条 confirmed 全处置(
|
|
985
|
+
> 发车门清账完成:0.30.0 发版扫描 36 条 confirmed 全处置(复审 八轮终轮零新),
|
|
976
986
|
> 最后一门 P-30(`findPendingForTask` 门种闸)红先绿后闭环,29/29 门绿后发布。
|
|
977
987
|
|
|
978
988
|
- **P-30:`findPendingForTask` 门种闸**(2026-08-14;**行为面** + 1 新导出;0.30.0 发车前最后一门):
|
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.
|
|
38
|
+
**Version:** 0.38.1
|
|
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
|
|
@@ -249,6 +249,7 @@ public-surface guard checks that last one).
|
|
|
249
249
|
| `scripts/run-catalog-loader-gates-test.mjs` | The model-catalog candidate chain (`loadCatalogWithSources`) and the provider device-code seam: offline ⇒ `bundled` with an honest `online.reason`, a good source ⇒ `online` plus a cache write, a second offline run ⇒ `cacheHit`; the three hostile source shapes (malformed JSON, `schemaVersion: 99`, off-domain `http`) each fall through to the bundled table, and an off-allowlist target is **never dialled** — including a `302` to another host, proven by a real loopback server's hit counter staying at zero; a one-byte edit to `catalog.sha256` drops that source while an unavailable sidecar only warns; and the device-code poller's `pending → ok` / `expired` arms run against a real loopback HTTP server with an injected clock |
|
|
250
250
|
| `scripts/run-abortable-sleep-test.mjs` | The shared `abortableSleep(ms, signal)` leaf (consumed by `workflowClient.ts` and `agentSession/backgroundView.ts`'s poll backoff): normal timeout resolution, immediate wake-up on `abort` mid-wait, `clearTimeout` really firing on that path, and a post-resolve late abort staying a no-op |
|
|
251
251
|
| `scripts/run-durable-card-display-keys-test.mjs` | The durable approval row's two display keys survive the row→card recast in `surfaceFsApprovalAndDecide`: `governanceForced` stamps on strict `true` only (absence is "no evidence", never `false`), `ruleSuggestions` passes through the same shape-narrowing reader as the live-frame leg and lands on the **read-only** card key — plus a standing pin that the durable leg never stamps the redeemable `ruleSuggestions` card position (the `/decide` body has no rule slot; offering a "don't ask again" option there would be an affordance nothing can honour) |
|
|
252
|
+
| `scripts/run-package-hygiene-test.mjs` | Everything `package.json` `files` ships — dist JS/typings and the Markdown docs — is screened line-by-line against a deny-list of strings that must never appear in a published artefact. The guard first proves each pattern still bites on a constructed sample (a screen that cannot fail is worse than none) and honours a per-pattern allow-list for legitimate product vocabulary, so the verdict is "clean surface", not "quiet grep". |
|
|
252
253
|
| `scripts/run-integration-doc-freshness-test.mjs` | The **integration contract** (`docs/INTEGRATION-CLIENTS.md`) and the **changelog** (`CHANGELOG.md`) checked against the code, because a document with no guard rots — this one had a whole nest of drift found on it within a day of being written. Five directions, each a claim a machine can actually evaluate. (1) *Counting discipline*: the version-anchor row for the guard count may no longer carry a hand-copied number at all — it changes every time a guard is added, and writing it down is planting a timer; the export counts that are still hand-copied (the surface total, the test-hook count, the sentence describing the surface's internal composition, the sum of the sixteen domain rows, and the three sub-counts) are each compared against a value **derived** from `public-export-baseline.json`, which is the drift a human reviewer caught last time. (2) *Coordinates alive*: every `src/` `scripts/` `docs/` path the doc quotes must be on disk **and tracked by git** — on disk is not in the repo, and a doc that points readers at a file living only in its author's working tree sends every clone to nothing. A file landing in the same commit takes a named carve-out that **stops applying** the moment the file is really tracked (it can no longer let anything through, and the guard prints a line asking for it to be deleted) — deliberately not a red, since turning red on the very commit that lands the file would just manufacture a break that only a follow-up commit could clear. (3) *Arm tables*: the `hitl_out_of_slice` row and the `not_in_slice` fenced list must equal, name for name and in **both** directions, the case labels that really fall into those two buckets — read through the **TypeScript AST**, since which bucket an arm lands in is decided by the argument to `nothing(...)` and by nothing a comment says. The extractor is anchored to the one production projector: exactly one function named `eventToSdkMessage`, exactly one `switch (ev.type)` inside it, and no repeated case label — anything else is a broken anchor rather than a verdict, because a second same-shaped switch elsewhere in the file would otherwise overwrite the real one's conclusions and leave the doc agreeing with a switch nobody runs. The list is delimited by a machine-readable fence rather than by section headings, because the same section also names the terminal arms as a counter-example and prose boundaries cannot tell a member from a foil. (4) *Released sections are frozen*: an **append-only ledger** carries every version ever published — its number, the commit it was published from, and the sha256 of its section — and each one is checked, not just the current release, since pinning only the latest would set every earlier version free the moment the next one ships. The ledger cannot vouch for itself either: each recorded hash is **re-derived from that release commit** through git, so editing an old section and its constant together no longer passes — and the commit the row names is in turn checked against the `gitHead` npm recorded at publish time, which is the one value this repository cannot rewrite, so pointing an old version at a freshly written commit does not pass either. The *set* of versions that must be frozen comes from the registry too, so deleting an old row together with its section — which would otherwise remove that version from every set the guard looks at — is red rather than invisible. A failed registry call is classified rather than swallowed, and the classification consults the registry's own status code *before* it considers connection-level symptoms, so an auth refusal whose body happens to mention the network is still red rather than a skip. The version set is compared as full SemVer including prereleases — matching only `x.y.z` would silently drop a published `0.30.0-beta.1` and reopen the very hole this direction closes — and section headings are matched on a whole-version boundary so a stable release cannot bind itself to the release-candidate section sitting above it. Publishing itself is a two-phase protocol rather than a paradox: before a release, exactly one row may be marked pending and must name the current `package.json` version, exempt from the checks whose inputs do not exist yet; once the registry has that version the row must be promoted, so the temporary state cannot survive its own release. And because the pending exemption rests entirely on "this version is not out yet," it is refused outright when the registry cannot be reached to confirm that — an unverifiable premise is not a licence. Three reverse directions close the rest: a section claiming to be released but absent from the ledger, a ledger entry whose section has vanished, and a `package.json` version that was never frozen. Publishing appends a row; it never rewrites one. (6) *Sentinels*: the readers §5a hands hosts for "is this port installed" are checked against what the source actually declares it returns — `hasXxx()` is a `boolean`, the card port / HITL surface / wire target return `T | null`, the `installHost` family returns `T | undefined`. Testing a `null`-returning reader for `!== undefined` is *always true*, and a self-check that passes whether or not the port is installed is worse than none, because hosts retire their own fallback on the strength of it. Both directions are red: an implementation that changes its sentinel without the doc following, and a doc that names the wrong one. The roster covers the zero-argument readers and their `*For` variants alike — a multi-session host reads the variants, so leaving them off would let exactly the surface desktop depends on drift unwatched — and the §5a table and the §8-B checklist line are each checked against the source, because hosts tick the checklist, and a guard that only watches the prose table misses the line people actually follow. (5) *Packaging*: the README ships with the package and opens by pointing hosts at the integration doc, and the checklist names two more files as required reading before an upgrade — all three must really appear in the `npm pack` manifest, or an npm consumer follows a relative link that npmjs rewrites onto a private repository. Missing tooling never takes the whole verdict down with it: when git, npm or the registry is unreachable those legs print the `SKIPPED-SECTION` marker and the rest still judges, while a release commit the ledger names but git cannot resolve is red rather than skipped. The guard says in its own header what it does **not** do: it judges counts, coordinates, arm sets, released bytes and the packing list — whether a sentence is *right* is still for review and for the hosts to report |
|
|
253
254
|
| `scripts/run-type-superset-ledger-test.mjs` | The type/wire **superset ledger** (`docs/type-superset.json`): positions this package adds on top of a CC-shaped contract, each carrying the evidence for what CC's own type surface does or does not have there. Completeness is deliberately uneven and the ledger says so. The `_sema_*` private-key class is checked in **both** directions (a key in the source that never entered the ledger is red, naming key and file; a ledger row whose key left the source is red) — but only for keys written as literals, which is the convention the ledger mandates. A key assembled by string arithmetic is beyond what any static rule can enumerate, so the guard fails closed on every shape it *can* decide (a bare `_sema_` prefix is red wherever it appears, save one pinned guard site) and leaves the rest as a convention violation for review to catch, rather than claiming a completeness it does not have. The two hand-surveyed classes are only checked for coordinate and evidence integrity, never discovered. Both directions read the source through the **TypeScript AST**, not a text scan, and they read two different sets out of it. A *key site* is an identifier, or a string whose whole value is the key — so `'_sema_decision-v2'` is carried whole rather than truncated at the first non-identifier character into some *other* key that happens to be registered. A *mention* is the key appearing inside a longer string, which is prose, not usage. The staleness direction counts key sites only: a comment or a doc sentence left behind after the last real mint site is deleted must not keep the row alive (mutation-proven — with both the comment and the prose string untouched, removing the one real site turns the guard red). And because a prefix can be concatenated or interpolated into a key no static set will ever see, the bare `_sema_` literal is refused outright rather than traced: every occurrence is red except the single inline `startsWith` guard the sanitizer needs, because the set of expressions a bare prefix can travel through on its way to a concatenation is open-ended and enumerating it is always one form behind. Every row's `host` must still resolve, with the key being a real **member of that declaration** rather than a string occurring somewhere in the same file — `governanceForced`/`delegation` each live on two different shapes in one file, and a member commented out is a member deleted, which a text-shaped check happily reads as still present. And the direction worth the most: each machine-form `ccAbsenceEvidence` is re-derived from the row's own `key` — the ledger's recorded string must match that derivation verbatim, since a row quietly witnessing `\bnever_present\b` is green forever while watching nothing (mutation-proven: the same edit passes the unbound form and is caught by the bound one) — and the check runs against the names the installed `@sema-agent/agent-types` `.d.ts` set actually declares, parsed with the TypeScript AST rather than grepped, so a name CC merely mentions in a comment cannot force the row into the manual escape hatch and thereby retire the very witness that was supposed to fire the day CC declares that name for real. That escape hatch is gated by an allowlist living **in the guard**, not the ledger, so claiming it costs a reviewed diff. Missing material never reads as a pass, and the verdict splits by *why* it is missing: no TypeScript parser skips the suite before it starts; a missing `agent-types` still runs and prints the first three directions, then exits **1** when `package.json` declares the mirror but it is not installed — a broken install must not retire the repository's only "the day CC declares this name" alarm, and reporting it as a skip would leave "never evaluated" and "evaluated, no drift" indistinguishable to the runner — and exits 3 only when nothing declares the mirror at all, which is the one case where the direction genuinely does not apply. Either way a run that evaluated no witness is never counted as one that did. When the mirror *is* present its **installed version** is witnessed too (the two declared floors must agree with each other and the installed copy must meet them), since four preflight probes are satisfied by an arbitrarily stale mirror — they prove the extractor speaks, not that it is current. Every direction carries a positive control — known-present CC symbols, a comment-only sample proving the extractor distinguishes declaration from mention, and synthetic corpora fed through the **same** discriminator function the real verdict uses, so a verdict quietly rewritten to return nothing takes its own control down with it |
|
|
254
255
|
| `scripts/run-rules-side-test.mjs` | The persisted-permission-rules lane's shared decision half. The two capability bits are checked as **two independent gates** — a worker can honestly advertise the rules lane while predating the revoke routes, and that shape must *hide* the governance surface rather than render a dead entry. Failure classification is by **disposition, not cause**: the two 404s (route missing vs. dead ticket) never share a bucket, a 503 `rule_import_retry` means *the ticket is still alive* (the opposite handling of a dead one), and a stale-cursor 400 drops the cursor and re-lists from the top exactly once — never resuming a stale keyset, never surfacing a partial governance list, and never paging past the hard cap. The persist-ack reader is **merged into** `readToolApprovalRespondAck`: the three-state verdict (`persisted` / `refused` / `unknown`) is derived only from an ack that passed the package's structural narrowing, and a half-shaped object such as `{rulePersisted: true}` with no `delivery` reads as `unknown` — the pre-merge shell read would have said `persisted`, which is precisely the double-ledger drift this file closes, so that case is pinned in reverse. The local-allow-rule skeleton pins all five narrowings (whole-tool, tool-name match, literal anchor with the escaped-star counter-example, bare interpreter prefix consulted only for Bash, and the canonical dangerous-pattern overlay) **with their refusal strings byte-for-byte** — the cli's 128-assertion suite anchors the same strings, so a one-character edit here changes observable behaviour on three clients — and asserts the parse is a pure function of its input, because the same call backs both "render the option" and "resolve the selected value" |
|
|
@@ -256,7 +256,7 @@ export type SelfHealOutcome =
|
|
|
256
256
|
taskId: string;
|
|
257
257
|
}
|
|
258
258
|
/**
|
|
259
|
-
* 存活对账腿(
|
|
259
|
+
* 存活对账腿(对抗复审 [high] 采纳,2026-08-19):wire 409 说这条 run `running`,而出卡前的
|
|
260
260
|
* `runs.get` 读回**终态词**({@link CLAIM_RELEASED_STATES})—— run 已终结,claim 是陈旧/释放中的
|
|
261
261
|
* 投影([4664] 洞③:409 与 poll 口径短暂相左)。三选卡不呈(steer/cancel 对已终结的 run 全是假
|
|
262
262
|
* affordance),也**不自动重发**(claim 可能尚未真正落地释放)—— 如实说 + 「再发一次」真出路。
|
|
@@ -219,7 +219,7 @@ const RUNNING_LIVENESS_RECHECK_TIMEOUT_MS = 4_000;
|
|
|
219
219
|
// 走 {@link clearRunningChoiceOffer}。
|
|
220
220
|
// run 身份变化天然换键(taskId 不同);同 run 状态迁移到 park 态会走别的臂,登记不拦。
|
|
221
221
|
const runningChoiceDeclined = new Set();
|
|
222
|
-
/** 登记表上限(
|
|
222
|
+
/** 登记表上限(对抗复审轻形采纳):超限 FIFO 驱逐最老条目 —— 驱逐代价 = 卡重弹一次(修前行为)。 */
|
|
223
223
|
const RUNNING_CHOICE_LEDGER_CAP = 512;
|
|
224
224
|
function runningChoiceDeclineKey(sessionId, taskId) {
|
|
225
225
|
// NUL 分隔:两段都是外来串,可打印分隔符在「sessionId 里恰有它」时会串键。
|
|
@@ -378,7 +378,7 @@ export async function attemptActiveRunSelfHeal(signal, runs, deps) {
|
|
|
378
378
|
// [3892]-[3899] 假死锁防御(deps.listOwnedPendingApprovals 头注全案):镜像说有卡 ⇒ 向引擎
|
|
379
379
|
// 复核一次属主 pending 行数。恰 0 = 镜像脏值的**正面证据**(引擎侧没有任何等这个用户的决定)
|
|
380
380
|
// ⇒ 放行真分诊;抛错/非 0 ⇒ 保守维持(真卡在屏时 stand down 是对的)。
|
|
381
|
-
// 🔴 复核必须**有界且可回收**(
|
|
381
|
+
// 🔴 复核必须**有界且可回收**(复审 #244 F1 轮一 [high] + 轮二 [medium]×2):
|
|
382
382
|
// · probe 恒不落地(引擎挂死且读口无自带超时)不许把整棵分诊树挂死 —— 假死锁防御自己变成
|
|
383
383
|
// 真死锁;窗尽/调用方中止 = 分不出真卡与幽灵,与抛错同臂保守 stand down;
|
|
384
384
|
// · 合流 signal **真交到 probe 上**(超时/中止掐的是底层请求,不是只掐调用方的 await);
|
|
@@ -553,7 +553,7 @@ async function runningChoiceArm(taskId, status, busy, statusFromWire, runs, deps
|
|
|
553
553
|
}
|
|
554
554
|
if (ghost)
|
|
555
555
|
return { kind: 'running-not-found', taskId };
|
|
556
|
-
// (
|
|
556
|
+
// (对抗复审 [medium] 采纳)对账期间用户中止 ⇒ 后续一切动作(派臂/出卡/登记)都不做 ——
|
|
557
557
|
// Esc 之后还弹卡/重开 = 用户说停还在动。零动作现状行收口(与呈卡腿抛错同一条诚实收口)。
|
|
558
558
|
if (probeCallerAborted())
|
|
559
559
|
return notParked;
|
|
@@ -562,7 +562,7 @@ async function runningChoiceArm(taskId, status, busy, statusFromWire, runs, deps
|
|
|
562
562
|
return planReviewArm(taskId, busy, deps);
|
|
563
563
|
if (ASK_PARK_STATES.includes(fresh))
|
|
564
564
|
return askParkArm(taskId, busy, deps);
|
|
565
|
-
// (
|
|
565
|
+
// (对抗复审 [high] 采纳)终态词 ⇒ 专属结局:not-parked 的「wait for it to finish」对一条
|
|
566
566
|
// 已终结的 run 是永远等不到的假话。表外的未知词仍落 not-parked 如实说(不替引擎断言终结)。
|
|
567
567
|
if (CLAIM_RELEASED_STATES.includes(fresh))
|
|
568
568
|
return { kind: 'running-settled', taskId, status: fresh };
|
|
@@ -590,7 +590,7 @@ async function runningChoiceArm(taskId, status, busy, statusFromWire, runs, deps
|
|
|
590
590
|
if (choice === 'wait') {
|
|
591
591
|
if (offerDelivered) {
|
|
592
592
|
runningChoiceDeclined.add(declineKey);
|
|
593
|
-
// (
|
|
593
|
+
// (对抗复审 [medium] 轻形采纳)FIFO 有界:长命桌面/多会话宿主不许无界长住。驱逐最老条目的
|
|
594
594
|
// 代价 = 那条 run 的卡重弹一次(= 修前行为,方向安全);Set 按插入序迭代,首项即最老。
|
|
595
595
|
if (runningChoiceDeclined.size > RUNNING_CHOICE_LEDGER_CAP) {
|
|
596
596
|
const oldest = runningChoiceDeclined.values().next().value;
|
|
@@ -961,7 +961,7 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut) {
|
|
|
961
961
|
`longer exists). sema did not offer to steer or cancel it: there is nothing left to act on. ` +
|
|
962
962
|
`Your message was NOT sent; send it again (if the session really is free it will just run), or ${wayOut}.`);
|
|
963
963
|
case 'running-settled':
|
|
964
|
-
//
|
|
964
|
+
// 对抗复审 [high] 采纳:run 已终结而 claim 仍报 running([4664] 洞③的窗口形)——
|
|
965
965
|
// 「wait for it to finish」对它是永远等不到的假话;真出路 = 再发一次(claim 落地即跑)。
|
|
966
966
|
return (`Run ${outcome.taskId} has already finished (status ${outcome.status}), but the engine still ` +
|
|
967
967
|
`reported it as holding this session — that claim looks stale or mid-release. sema did not offer ` +
|
|
@@ -632,7 +632,7 @@ function engineNoticeProjection(ev, ctx) {
|
|
|
632
632
|
// · `eventSeq` —— **SDK 从 SSE `id:` 字段 stamp 上来的 durable 序号**(= `task_event.seq`,
|
|
633
633
|
// 见 sdk `dist/sse.js` 的 `ev.id = frame.id`;本包的规范访问口就是 `adapter/types.eventSeq`)。
|
|
634
634
|
// 它对「同一条账本行」是稳定的,重放会带同一个值。
|
|
635
|
-
// 🔴
|
|
635
|
+
// 🔴 对抗复审 [medium] 采纳(2026-08-21):首版只带 `eventId`,于是 body 不带它时本臂
|
|
636
636
|
// 给消费端留的唯一去重口是 `code+ts` —— 而 `ts` 是**server 观察时刻(ms)**,同一毫秒里同码
|
|
637
637
|
// 的两条不同通告会被折成一条(真事实丢失),跨重连的同一条又可能因为观察时刻不同而重复。
|
|
638
638
|
// 明明有一个稳定序号在手却不带,是本层自己把可靠性降级了。
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* 能探则探:`GET /v1/capabilities` 的 **`approvals`** 位为 false ⇒ 'not-configured';
|
|
27
27
|
* caps 面自己不可达 ⇒ 判别缺席,**如实回 'ok'**(绝不编造判别)。
|
|
28
28
|
*
|
|
29
|
-
* 🔴 件 3
|
|
29
|
+
* 🔴 件 3 取证(读错键的定谳,协作板 [C33]③.3 → server [C34],server 3.13.0 已把辖域写进
|
|
30
30
|
* 代码注与 ARCHITECTURE):本实现此前读 `scheduler`,那是**错的键**。
|
|
31
31
|
* · `/v1/assistant/*` 的挂载条件是 `checkpointStore`(server `routes/approvals-assistant.ts:67`
|
|
32
32
|
* 的分支头:`if (deps.checkpointStore && (url.startsWith("/v1/approvals") ||
|
|
@@ -169,7 +169,7 @@ export function createBackgroundView(client, opts) {
|
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
171
|
/** caps 只需探到一次;失败保持 null 下轮重试(caps 面挂 ≠ assistant 面挂,判别缺席不编造)。
|
|
172
|
-
* 🔴 读的是 `approvals` 不是 `scheduler` ——
|
|
172
|
+
* 🔴 读的是 `approvals` 不是 `scheduler` —— 取证见头注「'not-configured' 判别」节件 3。 */
|
|
173
173
|
async function probeCaps() {
|
|
174
174
|
if (capsApprovals !== null)
|
|
175
175
|
return;
|
|
@@ -121,7 +121,7 @@ export type HookNoticeFrame = Extract<FleetFrame, {
|
|
|
121
121
|
* 端因此无法对不同强度的证据分级处置(强证据可直接落库/翻卡,弱证据宜先渲染、别拿它当归属结论)。
|
|
122
122
|
* 判据锚在**实际命中的那条臂**上,不是锚在「有没有 parentTaskId」这类前置条件上。
|
|
123
123
|
*
|
|
124
|
-
* 🔴 **每个词命名的是「放行臂」,不是「归属结论」**(
|
|
124
|
+
* 🔴 **每个词命名的是「放行臂」,不是「归属结论」**(对抗复审两轮收紧:首版把 `own_parent` 写成
|
|
125
125
|
* 「硬证据」、二版只给 `own_root` 挂了会话锚警告,都是过度声称)。强度分档如下 —— 前两格的
|
|
126
126
|
* 会话级读法**带前提**,第三格永远只是进程级,第四格根本不是证据。
|
|
127
127
|
*
|
|
@@ -214,7 +214,7 @@ export function createFleetLedger(hooks = {}, opts = {}) {
|
|
|
214
214
|
for (const row of frame.tasks) {
|
|
215
215
|
taskMap.set(row.id, row);
|
|
216
216
|
rowMeta.set(row.id, { receivedAtMs });
|
|
217
|
-
// #242 批 3(
|
|
217
|
+
// #242 批 3(复审 P-F1,红先绿后=B3-G4b):snapshot(重连 REPLACE)腿与增量 task 帧腿
|
|
218
218
|
// 的复活语义**同权** —— snapshot 带回的非终态行,其全部别名(同尾段)的 belt 退休条目
|
|
219
219
|
// 一并清,否则重连别名切换(裸 id 退休 → 复合 id running 回归)会产出「复合 running +
|
|
220
220
|
// 裸 completed(带标)」双影。终态行不清(与增量腿同判);snapshot 里**没有**的任务,
|
|
@@ -350,7 +350,7 @@ export function createFleetLedger(hooks = {}, opts = {}) {
|
|
|
350
350
|
debug('[fleet-frame] MALFORMED bg_notification dropped(taskId 或 status 缺席/非串)');
|
|
351
351
|
return;
|
|
352
352
|
}
|
|
353
|
-
// 🔴 #284 尾件(
|
|
353
|
+
// 🔴 #284 尾件(对抗复审第四轮 high,红先绿后=B3-DIRTY):`parentTaskId` 是**隔离判据的锚**,
|
|
354
354
|
// 它的脏形必须先于判据处置。下面那道 own/foreign 门写成
|
|
355
355
|
// `typeof === 'string' && length > 0 && !isOwnEngineRun(…)` —— 对 number/object/boolean
|
|
356
356
|
// 这类脏值**整条不成立** ⇒ 门不响、通知照收,而新的 evidence 还会把它标成 `absent_parent`
|
|
@@ -400,7 +400,7 @@ export function createFleetLedger(hooks = {}, opts = {}) {
|
|
|
400
400
|
// `parentTaskId` 在不在分开(走到这里且带非空 parentTaskId ⇒ 它必然 ∈ own-run 台账,
|
|
401
401
|
// 否则上面已丢弃)。多臂同时成立报第一条 —— 报的是「这一帧**实际**靠哪条过的门」,
|
|
402
402
|
// 不是「最强的那条理论上也成立」。
|
|
403
|
-
// 🔴 非默认 sessionKey 封顶(0.32.0 发包前扫码采
|
|
403
|
+
// 🔴 非默认 sessionKey 封顶(0.32.0 发包前扫码采 复审 折中,P-31 的诚实半步):前两格的
|
|
404
404
|
// 会话级读法前提=「这条流是按本 ledger 的会话开的」,而开流参数只认默认槽 ⇒ keyed ledger
|
|
405
405
|
// 上该前提**无法成立也无法校验**。封顶词=`session_anchor_untrusted`(不降到 own_parent:
|
|
406
406
|
// 经 serverFailClosed/ownByRoot 放行的帧,其 parentTaskId 可能是 foreign——标进程成员是
|
|
@@ -43,7 +43,7 @@ export declare function waitForGateArmedFor(sessionKey: string, keys: readonly s
|
|
|
43
43
|
/** 宿主呈现面登记口:收到可渲染 question 帧即记(键归一见 gateIdentity;坏形静默忽略 ——
|
|
44
44
|
* id 是 wire/合成位,入参按边界收 unknown,本函数就是窄化动作本身)。
|
|
45
45
|
*
|
|
46
|
-
* 尝试级回执(#250
|
|
46
|
+
* 尝试级回执(#250 复审 轮二 [medium]):原始帧 id 自带 `#reopen-*` 尝试序号,是现成的
|
|
47
47
|
* attempt token —— 归一键之外**事件级**再发一枪原始 id(不入 Set,台账词汇保持归一键)。
|
|
48
48
|
* 重开臂锚它,同 gate 两次在飞重开时一次真入队只唤对应那次尝试,不再同键互唤。 */
|
|
49
49
|
export declare function registerArmedGateFromQuestionId(questionId: unknown): void;
|
|
@@ -231,7 +231,7 @@ function armedKeyForQuestionIdFor(sessionKey, questionId) {
|
|
|
231
231
|
/** 宿主呈现面登记口:收到可渲染 question 帧即记(键归一见 gateIdentity;坏形静默忽略 ——
|
|
232
232
|
* id 是 wire/合成位,入参按边界收 unknown,本函数就是窄化动作本身)。
|
|
233
233
|
*
|
|
234
|
-
* 尝试级回执(#250
|
|
234
|
+
* 尝试级回执(#250 复审 轮二 [medium]):原始帧 id 自带 `#reopen-*` 尝试序号,是现成的
|
|
235
235
|
* attempt token —— 归一键之外**事件级**再发一枪原始 id(不入 Set,台账词汇保持归一键)。
|
|
236
236
|
* 重开臂锚它,同 gate 两次在飞重开时一次真入队只唤对应那次尝试,不再同键互唤。 */
|
|
237
237
|
export function registerArmedGateFromQuestionId(questionId) {
|
|
@@ -241,7 +241,7 @@ export function registerArmedGateFromQuestionId(questionId) {
|
|
|
241
241
|
export function registerArmedGateFromQuestionIdFor(sessionKey, questionId) {
|
|
242
242
|
if (typeof questionId !== 'string' || questionId.length === 0)
|
|
243
243
|
return;
|
|
244
|
-
// 🔴 canonical 复用的去重记号过期(
|
|
244
|
+
// 🔴 canonical 复用的去重记号过期(复审 #244 F1 轮一 [medium]):arm 臂对同 run 每只 plan gate
|
|
245
245
|
// 都复用 `plan-review:<taskId>` 这个 canonical id —— 决断记账按完整 questionId 去重,若记号不随
|
|
246
246
|
// 卡换代过期,第二只 canonical 卡的决断会撞上第一只留下的记号 ⇒ 不推代 ⇒ 第三只门的重开又谎报
|
|
247
247
|
// 「被关过」。**新 canonical 卡的呈现**就是「上一张同 id 卡已消解、这是新实例」的宿主侧可见时刻,
|
package/dist/hitl/hitlBridge.js
CHANGED
|
@@ -91,7 +91,7 @@ export class HitlSafetyError extends Error {
|
|
|
91
91
|
// {@link DecideTransportRetryExhaustedError} → **重呈臂**(re-attach ⇒ durable 流重放 park ⇒
|
|
92
92
|
// 同一张卡重交用户),而不是把 turn 判死;若第一发其实已送达,重呈的下一步会撞
|
|
93
93
|
// `isAlreadyResolvedGateReason` 的已解决判据被救回。
|
|
94
|
-
// 🔴 请托半场(
|
|
94
|
+
// 🔴 请托半场(候协作板):SDK decide 若开 per-call timeoutMs(或对 HITL 面单列长缺省),本层的
|
|
95
95
|
// 超时类重试环可整段收敛成一发长等待。
|
|
96
96
|
/** 瞬断重试的起始退避(×2 递增,封顶 {@link DECIDE_RETRY_BACKOFF_MAX_MS};别把 decide 打成连发)。 */
|
|
97
97
|
const DECIDE_TRANSPORT_RETRY_BACKOFF_MS = 750;
|
|
@@ -99,7 +99,7 @@ const DECIDE_RETRY_BACKOFF_MAX_MS = 5_000;
|
|
|
99
99
|
/**
|
|
100
100
|
* SDK 的 **per-attempt 超时帽**(`AbortSignal.timeout(timeoutMs)`)的**缺省值**。
|
|
101
101
|
*
|
|
102
|
-
* 🔴 **它是「观察到的缺省」,不是本包能保证的量**(
|
|
102
|
+
* 🔴 **它是「观察到的缺省」,不是本包能保证的量**(对抗复审 [medium] 采纳,2026-08-21)。
|
|
103
103
|
* per-call opts 只有 `signal`(与之合流取先,只能收短不能放长),但 `timeoutMs` 是 **client
|
|
104
104
|
* 构造级**旋钮,而本桥吃的是**宿主注入的** client(`HitlClientLike` 根本不暴露它)⇒ 一个 web/桌面
|
|
105
105
|
* 宿主完全可以用 30s 或 120s 的 client 构造本桥。所以任何「总窗 ÷ 帽 = 发数」的推导都只在缺省值
|
|
@@ -133,13 +133,13 @@ const DECIDE_TIMEOUT_MIN_ATTEMPTS = 2;
|
|
|
133
133
|
* (120s 恰等于 2 帽 + 退避,会让发数悬在退避时序的一根头发上)。
|
|
134
134
|
* ⇒ 在缺省帽上可预算的最坏墙钟 ≈ 60s(首发)+ 0.75s(退避)+ 60s(重发)≈ 121s,而不是旧口径的 10 分钟。
|
|
135
135
|
*
|
|
136
|
-
* 🔴 **本窗只是墙钟上界,不承诺发数**(
|
|
136
|
+
* 🔴 **本窗只是墙钟上界,不承诺发数**(复审 [medium] 采纳):宿主可以用非缺省 `timeoutMs` 构造
|
|
137
137
|
* client(见 {@link DECIDE_ATTEMPT_TIMEOUT_CAP_MS}),那时「窗 ÷ 帽」得出的发数与这里写的不同。
|
|
138
138
|
* 与宿主无关的那条性质(**至少重试一次**)由 {@link DECIDE_TIMEOUT_MIN_ATTEMPTS} 单独保证 ——
|
|
139
139
|
* 两个界合取:**发数达标 ∧ 窗尽** 才停。所以在 120s client 上是「2 发、~240s」,在 30s client 上
|
|
140
140
|
* 是「3 发、~92s」,在缺省 60s 上是「2 发、~121s」—— 三者都有界,且都拿得到那一次重试。
|
|
141
141
|
*
|
|
142
|
-
* 🔴 窗的语义是**不再起新发**,刻意不掐在飞那一发(
|
|
142
|
+
* 🔴 窗的语义是**不再起新发**,刻意不掐在飞那一发(对抗复审议题,驳回后成文):给一发可能已被
|
|
143
143
|
* server 受理的 decide 塞截止 signal 换不来任何安全 —— server 侧照跑,客户端只多制造一个「送达
|
|
144
144
|
* 未知」。
|
|
145
145
|
* 🔴 **写成「帽 × 系数」而不是裸 90_000**:上面那段推导只有在两者绑在一起时才会随 SDK 改帽自动
|
|
@@ -516,7 +516,7 @@ export class HitlBridge {
|
|
|
516
516
|
// **是真异常**,不再是「server 还在跑 resume」的常态 —— 所以总窗只留恰一次重试的量
|
|
517
517
|
// (推导见 DECIDE_TIMEOUT_RETRY_TOTAL_BUDGET_MS)。重发仍然安全:重复 decide 由 server CAS
|
|
518
518
|
// 保证不双跑,首发其实送达时下一发只会撞 4xx(conflict/not-found ⇒ 上抛,已解决判据接手)。
|
|
519
|
-
// 🔴 **两个界合取**(
|
|
519
|
+
// 🔴 **两个界合取**(复审 [medium] 采纳):发数没达标就一定再发一次(与宿主的
|
|
520
520
|
// per-attempt timeoutMs 无关),达标之后才由墙钟窗决定还发不发。少了前半句,
|
|
521
521
|
// 120s client 的宿主一次重试都拿不到;少了后半句,30s client 会一路重试到分钟级。
|
|
522
522
|
if (attempts >= DECIDE_TIMEOUT_MIN_ATTEMPTS && Date.now() - startedAt >= decideTimeoutRetryBudgetMs()) {
|
|
@@ -123,7 +123,7 @@ export type ListAllPersistedRulesOutcome = {
|
|
|
123
123
|
* 🔴 **游标绑 `(rev, principal, scope)`**:两页之间有人加/删了规则 ⇒ 第二页 400
|
|
124
124
|
* `request.query_invalid`。处置 = **丢游标从头列一次**(静默重置成「接着上一页」会得到一份既漏行
|
|
125
125
|
* 又重行的清单);从头再撞一次 ⇒ 如实报 cursor-stale,由调用方(人按 r 刷新)决定。
|
|
126
|
-
* 🔴 **调用方 cursor 不收**(
|
|
126
|
+
* 🔴 **调用方 cursor 不收**(复审 F2 对抗复审 [medium]):类型上剔掉 `cursor` 还不够 —— JS
|
|
127
127
|
* 调用方仍能塞进来,而首页的 `...params` 会把它原样送出 ⇒ 「列全」从**中途**开始却报 `ok:true`
|
|
128
128
|
* 完整清单(治理面据此藏掉仍然生效的规则)。运行期显式剥除 + 留痕:drain 恒从第一页起,
|
|
129
129
|
* 「接着别人的 keyset」证明不了完整性,与本函数的契约(rules 恒完整)结构性冲突。
|
|
@@ -58,7 +58,7 @@ export function classifyRulesFailure(e) {
|
|
|
58
58
|
return { kind: 'forbidden', message };
|
|
59
59
|
return { kind: 'error', message };
|
|
60
60
|
}
|
|
61
|
-
/** 一页要多少条。**必须显式给**(
|
|
61
|
+
/** 一页要多少条。**必须显式给**(对抗复审 [medium] 实撞):server 缺省是 **50**,而页帽
|
|
62
62
|
* 按「200/页」算 ⇒ 真实上界只有 1250 条,一位规则多于 1250 的 principal 会恒拿到「翻不完」的
|
|
63
63
|
* 失败、整个治理面打不开,而注释还写着 5000。夹取语义在 server(非数/越界夹进 1..200),所以给
|
|
64
64
|
* 上限最省往返、也让页帽的算术与现实一致。 */
|
|
@@ -73,7 +73,7 @@ const MAX_PAGES = 25;
|
|
|
73
73
|
* 🔴 **游标绑 `(rev, principal, scope)`**:两页之间有人加/删了规则 ⇒ 第二页 400
|
|
74
74
|
* `request.query_invalid`。处置 = **丢游标从头列一次**(静默重置成「接着上一页」会得到一份既漏行
|
|
75
75
|
* 又重行的清单);从头再撞一次 ⇒ 如实报 cursor-stale,由调用方(人按 r 刷新)决定。
|
|
76
|
-
* 🔴 **调用方 cursor 不收**(
|
|
76
|
+
* 🔴 **调用方 cursor 不收**(复审 F2 对抗复审 [medium]):类型上剔掉 `cursor` 还不够 —— JS
|
|
77
77
|
* 调用方仍能塞进来,而首页的 `...params` 会把它原样送出 ⇒ 「列全」从**中途**开始却报 `ok:true`
|
|
78
78
|
* 完整清单(治理面据此藏掉仍然生效的规则)。运行期显式剥除 + 留痕:drain 恒从第一页起,
|
|
79
79
|
* 「接着别人的 keyset」证明不了完整性,与本函数的契约(rules 恒完整)结构性冲突。
|
|
@@ -104,7 +104,7 @@ export async function listAllPersistedRules(facade, params = {}, opts) {
|
|
|
104
104
|
}
|
|
105
105
|
return { ok: false, failure };
|
|
106
106
|
}
|
|
107
|
-
// 🔴 页体 fail-closed 窄化(
|
|
107
|
+
// 🔴 页体 fail-closed 窄化(复审 F2 轮二 [high]):SDK 传输层只 JSON.parse,不做运行期
|
|
108
108
|
// schema 校验 —— 一个 2xx 的 `{rev:9}`(无 rules 数组)在旧读法下会被认证成「完整的空清单」
|
|
109
109
|
// (`ok:true, rules:[]`),治理面据此宣称「没有持久规则」而活规则不可见、无法撤销
|
|
110
110
|
// (版本偏斜/后端降级下静默发生)。坏形页 = 判不出,绝不当「读到了空的」:
|
|
@@ -124,7 +124,7 @@ export async function listAllPersistedRules(facade, params = {}, opts) {
|
|
|
124
124
|
},
|
|
125
125
|
};
|
|
126
126
|
}
|
|
127
|
-
// 🔴 跨页 rev 钉(
|
|
127
|
+
// 🔴 跨页 rev 钉(复审 F2 轮三 [high]):游标契约上绑 rev —— 清单变了,续页**该** 400
|
|
128
128
|
// cursor-stale。一个 2xx 却换了 rev 的续页 = server 违约或降级形,拼起来是**混合快照**;
|
|
129
129
|
// 认证它为完整清单,比 400 那条腿(丢游标重列)更坏 —— 这里不猜不修补,如实 failure。
|
|
130
130
|
if (rev !== null && res.rev !== rev) {
|
|
@@ -170,7 +170,7 @@ export type BuiltTaskRequest = TaskRequest;
|
|
|
170
170
|
* `settings.<resolved>` 的车道(#292 P1 起 = **两条**)那份 resolver effective 快照是**开放集**,
|
|
171
171
|
* 所以**表外**子键(permissions/env/model/… 不可枚举)放行。
|
|
172
172
|
*
|
|
173
|
-
* 🔴 开放集**不是**整只 `settings` 免检(#292 P1
|
|
173
|
+
* 🔴 开放集**不是**整只 `settings` 免检(#292 P1 对抗复审第二轮 [medium] 采纳):表里点名了、
|
|
174
174
|
* 而 lanes 不含本车道的子键(如 print 车道的 `settings.ultracode`)照旧**点名** —— 那些键是
|
|
175
175
|
* **可枚举的已知量**,放过它们等于让「ultracode 仅 interactive」这条登记在真 wire 面失效。
|
|
176
176
|
* `buildTaskRequest` 内的 `resolvedSnapshotForWire` 只管住构造器自己那一份;端在构造之后仍能往请求体上
|
|
@@ -129,7 +129,7 @@ const shapeTag = (v) => {
|
|
|
129
129
|
/**
|
|
130
130
|
* plain object 判别 —— 按 **prototype** 判,不按 `Object.prototype.toString` 的标签判。
|
|
131
131
|
*
|
|
132
|
-
* 🔴 标签判法是**假的**(0.35.0
|
|
132
|
+
* 🔴 标签判法是**假的**(0.35.0 对抗复审 [high] 采纳):`[object Object]` 对**普通类实例**同样
|
|
133
133
|
* 成立,而 `Symbol.toStringTag` 还能让任意载体自报这个标签。放它过去之后,下面的摊开用的是
|
|
134
134
|
* `Object.entries`(**只取自有可枚举键**)—— 于是一个把 `permissions.deny/ask` 挂在**原型 getter**
|
|
135
135
|
* 上的载体会摊出一个**空快照**,请求照发 = 权限静默变宽,正是本节要堵的那个 fail-open 形。
|
|
@@ -139,7 +139,7 @@ const shapeTag = (v) => {
|
|
|
139
139
|
* `Map` / `Date` / boxed 包装对象 / 数组一律拒 —— 它们要么内容不在自有键上,要么摊开就是垃圾键;
|
|
140
140
|
* 「上游产出坏了」比「悄悄发一个更宽的权限面」更该被人看见。
|
|
141
141
|
*
|
|
142
|
-
* 🔴 判据 **realm 无关**(0.35.0
|
|
142
|
+
* 🔴 判据 **realm 无关**(0.35.0 对抗复审第八轮 [medium] 采纳):不拿「**本** realm 的
|
|
143
143
|
* `Object.prototype`」做身份比较 —— iframe / `node:vm` / 另一个渲染进程里的对象字面量各有**自己的**
|
|
144
144
|
* `Object.prototype`,身份比较会把这些**完全合法、JSON 忠实**的载体误判成异形,在 web / desktop 宿主
|
|
145
145
|
* 上变成提交前的硬失败。
|
|
@@ -242,7 +242,7 @@ const materializeJsonFaithful = (v, path, seen = new Set()) => {
|
|
|
242
242
|
}
|
|
243
243
|
return { value: items }; // 重建:原数组(可能是子类/带访问器/带覆盖方法)不出门
|
|
244
244
|
}
|
|
245
|
-
// 🔴 重建成**无原型**记录(0.35.0
|
|
245
|
+
// 🔴 重建成**无原型**记录(0.35.0 对抗复审第七轮 [high] 的可采半场):
|
|
246
246
|
// ① 忠实 —— 源本来就允许 `Object.create(null)` 字典,重建成 `{}` 等于把载体形换掉了;
|
|
247
247
|
// ② 少一条改写面 —— 无原型记录不会继承任何**事后**装到 `Object.prototype` 上的 `toJSON`。
|
|
248
248
|
// ⚠️ 但这只关掉了 record 那一半:重建出来的**数组**必须是真数组(`Array.isArray` / 序列化成
|
|
@@ -264,7 +264,7 @@ const materializeJsonFaithful = (v, path, seen = new Set()) => {
|
|
|
264
264
|
// `Object.prototype` 的 setter 上:键整个丢掉、还顺手换了目标对象的原型。
|
|
265
265
|
Object.defineProperty(rec, k, { value: r.value, enumerable: true, writable: true, configurable: true });
|
|
266
266
|
}
|
|
267
|
-
// 🔴 判据的读法**一律排在捕获之后**(0.35.0
|
|
267
|
+
// 🔴 判据的读法**一律排在捕获之后**(0.35.0 对抗复审第十二轮 [high] 采纳):`toJSON` 走属性
|
|
268
268
|
// 查找、`isPlainRecord` 走 `getPrototypeOf`/`constructor` —— 这三种读法**都可以带副作用**
|
|
269
269
|
// (`getPrototypeOf` 陷阱在被问的那一刻把 `deny` 清空),而**原生 `JSON.stringify` 从不问原型**。
|
|
270
270
|
// 校在捕获之前 = 本层自己的读法成了丢内容的那一环(比被动撒谎更糟:那是我们引入的丢失面)。
|
|
@@ -305,7 +305,7 @@ const materializeJsonFaithful = (v, path, seen = new Set()) => {
|
|
|
305
305
|
* `TypeError` 带形状描述(合法载体的判据见 {@link isPlainRecord} —— 「摊得全」是它的全部理由)。
|
|
306
306
|
* (本包是发出去的 npm 公开面:JS 调用方与版本偏斜的宿主都到得了这里,型面拦不住。)
|
|
307
307
|
*
|
|
308
|
-
* ## ③ 权限面**子树**同样要摊得全(0.35.0
|
|
308
|
+
* ## ③ 权限面**子树**同样要摊得全(0.35.0 对抗复审第二轮 [high] 采纳)
|
|
309
309
|
*
|
|
310
310
|
* 只校最外层是不够的:`{ permissions: new Map([['deny',['Write']]]) }` 的外层是合法字面量,可
|
|
311
311
|
* `JSON.stringify` 把那只 Map 序列化成 `"permissions":{}` —— deny 规则**静默消失**,与 ② 要堵的
|
|
@@ -335,7 +335,7 @@ const resolvedSnapshotForWire = (resolved) => {
|
|
|
335
335
|
// 当场物化成脱钩副本),原件的形状判据留到最后;校不过就整体拒,拒绝面一字未变。
|
|
336
336
|
const named = namedSettingsSubKeys();
|
|
337
337
|
const out = {};
|
|
338
|
-
// 🔴 **键先捕获、值逐个即读即处理**(0.35.0
|
|
338
|
+
// 🔴 **键先捕获、值逐个即读即处理**(0.35.0 对抗复审第六轮 [high]):`Object.entries(resolved)`
|
|
339
339
|
// 会把**所有兄弟键**的值先读齐 —— 于是一个 `env`/`model` 位上的 getter 能在 `permissions` 被
|
|
340
340
|
// 重建**之前**把它的 deny 数组清空(拿到的是同一个引用)。这与内层那条(见
|
|
341
341
|
// {@link materializeJsonFaithful} 第五条)是同一个病、只是高一层:两层都必须按 stringify 的
|
|
@@ -365,7 +365,7 @@ const resolvedSnapshotForWire = (resolved) => {
|
|
|
365
365
|
out[k] = checked.value;
|
|
366
366
|
continue;
|
|
367
367
|
}
|
|
368
|
-
// 🔴 函数值一律不进(#292 P1
|
|
368
|
+
// 🔴 函数值一律不进(#292 P1 对抗复审第三轮 [medium] 采纳):JSON wire 上没有函数,而一个
|
|
369
369
|
// **自有 `toJSON`** 会在序列化那一刻整只改写 `settings` 的字节 —— 实测
|
|
370
370
|
// `resolved={permissions:…, toJSON(){return {ultracode:true}}}` 能让 `JSON.stringify(req)` 出
|
|
371
371
|
// `settings:{ultracode:true}`,把上面那道车道剥离(以及一切按对象查的判据)整体绕过。
|
|
@@ -550,7 +550,7 @@ export function applyLiveRequestDefaults(req, host) {
|
|
|
550
550
|
* `settings.<resolved>` 的车道(#292 P1 起 = **两条**)那份 resolver effective 快照是**开放集**,
|
|
551
551
|
* 所以**表外**子键(permissions/env/model/… 不可枚举)放行。
|
|
552
552
|
*
|
|
553
|
-
* 🔴 开放集**不是**整只 `settings` 免检(#292 P1
|
|
553
|
+
* 🔴 开放集**不是**整只 `settings` 免检(#292 P1 对抗复审第二轮 [medium] 采纳):表里点名了、
|
|
554
554
|
* 而 lanes 不含本车道的子键(如 print 车道的 `settings.ultracode`)照旧**点名** —— 那些键是
|
|
555
555
|
* **可枚举的已知量**,放过它们等于让「ultracode 仅 interactive」这条登记在真 wire 面失效。
|
|
556
556
|
* `buildTaskRequest` 内的 `resolvedSnapshotForWire` 只管住构造器自己那一份;端在构造之后仍能往请求体上
|
|
@@ -149,7 +149,7 @@ export interface SubagentResumeContext {
|
|
|
149
149
|
* 取运行 → 无则 no-run;makeEngineWireClient)—— 两条腿共用同一套装配,免得 steer 能到达而 resume
|
|
150
150
|
* 到不了(或反过来),那种不对称在用户面上表现为「同一个 agent 有时能说话有时不能」。
|
|
151
151
|
*
|
|
152
|
-
* 🔴 `session` 的三态(#242 批 2
|
|
152
|
+
* 🔴 `session` 的三态(#242 批 2 定口径,对抗复审 medium 收紧):
|
|
153
153
|
* · 台账有这一行**且**捕到了它的会话 ⇒ 用**行登记的**会话(resume 是 per-id 读面,配错的
|
|
154
154
|
* `{runId, session}` 组合在 session-bound run 上 fail-closed 404);
|
|
155
155
|
* · 台账**有这一行但没捕到会话**(fleet 行帧腿/通知腿今天不传 session)⇒ **不带 `?session=`**,
|
|
@@ -173,7 +173,7 @@ export declare function resumeSettledSubagent(facade: SubagentResumeFacade | nul
|
|
|
173
173
|
signal?: AbortSignal;
|
|
174
174
|
}): Promise<SubagentResumeOutcome>;
|
|
175
175
|
/**
|
|
176
|
-
* {@link auditRetainWithoutWake} 的判决(
|
|
176
|
+
* {@link auditRetainWithoutWake} 的判决(对抗复审 low 收紧的**返回契约**)。
|
|
177
177
|
*
|
|
178
178
|
* 两位分别回答两个不同的问题,**不许合成一个**:
|
|
179
179
|
* · `message` = 组合成不成立(成立才有这条记录;`undefined` 返回 = 不成立);
|
|
@@ -103,7 +103,7 @@ export function resolveOwnerRunId(childTaskId, activeRunId = activeEngineRunId()
|
|
|
103
103
|
* 取运行 → 无则 no-run;makeEngineWireClient)—— 两条腿共用同一套装配,免得 steer 能到达而 resume
|
|
104
104
|
* 到不了(或反过来),那种不对称在用户面上表现为「同一个 agent 有时能说话有时不能」。
|
|
105
105
|
*
|
|
106
|
-
* 🔴 `session` 的三态(#242 批 2
|
|
106
|
+
* 🔴 `session` 的三态(#242 批 2 定口径,对抗复审 medium 收紧):
|
|
107
107
|
* · 台账有这一行**且**捕到了它的会话 ⇒ 用**行登记的**会话(resume 是 per-id 读面,配错的
|
|
108
108
|
* `{runId, session}` 组合在 session-bound run 上 fail-closed 404);
|
|
109
109
|
* · 台账**有这一行但没捕到会话**(fleet 行帧腿/通知腿今天不传 session)⇒ **不带 `?session=`**,
|
|
@@ -20,7 +20,7 @@ export type SubagentSteerOutcome = {
|
|
|
20
20
|
* 指名了行而台账缺席 ⇒ `no-run` + `noteBgOwnerAbsence` 留痕(绝不回落在飞 run);
|
|
21
21
|
* **没指名行**(既有两参调用)⇒ 才回落在飞 run —— 老调用方逐字零行为变化,本参数是 additive。
|
|
22
22
|
*
|
|
23
|
-
* 🔴 会话参数**二态**(
|
|
23
|
+
* 🔴 会话参数**二态**(对抗复审 medium 采纳:「取了台账的 run 却配现势 session」是确定性 404):
|
|
24
24
|
* · 行登记时**捕到了会话** ⇒ 用**行的会话**(与行的 runId 同源同拍,是唯一自洽的组合);
|
|
25
25
|
* · 行**没有**随行会话(`recordBgParentRun` 只写 run —— 今天 bg 行多数是这一格)⇒ 退**现势会话**。
|
|
26
26
|
* 刻意**不**跟 resume 腿的「没捕到就干脆不带」:本腿与 tail/subagentOutput 同族,遵 [1498]③
|
|
@@ -45,7 +45,7 @@ import { noteBgOwnerAbsence } from './subagentOwnerAbsence.js';
|
|
|
45
45
|
* 指名了行而台账缺席 ⇒ `no-run` + `noteBgOwnerAbsence` 留痕(绝不回落在飞 run);
|
|
46
46
|
* **没指名行**(既有两参调用)⇒ 才回落在飞 run —— 老调用方逐字零行为变化,本参数是 additive。
|
|
47
47
|
*
|
|
48
|
-
* 🔴 会话参数**二态**(
|
|
48
|
+
* 🔴 会话参数**二态**(对抗复审 medium 采纳:「取了台账的 run 却配现势 session」是确定性 404):
|
|
49
49
|
* · 行登记时**捕到了会话** ⇒ 用**行的会话**(与行的 runId 同源同拍,是唯一自洽的组合);
|
|
50
50
|
* · 行**没有**随行会话(`recordBgParentRun` 只写 run —— 今天 bg 行多数是这一格)⇒ 退**现势会话**。
|
|
51
51
|
* 刻意**不**跟 resume 腿的「没捕到就干脆不带」:本腿与 tail/subagentOutput 同族,遵 [1498]③
|
|
@@ -391,7 +391,7 @@ export function getBgTerminalFacts(taskId) {
|
|
|
391
391
|
// 🔴 有界:键来自 wire(不可信),无界表 = 引擎乱发 taskId 就能吃掉宿主内存。上限取**两表
|
|
392
392
|
// 上限之和**(壳表 256 + 本表原 `MAX_TASKS*2`=64)—— 合表**不许缩窄既有保障**:收编前两条
|
|
393
393
|
// lane 各有各的池子,能同时记 256 条 tick + 64 条 fleet;若按 256 钉,「tick 池已满 + 再来一条
|
|
394
|
-
// fleet 行」当场淘汰一条 tick,那条子代的 resume 入口凭空消失,而收编前它还在(
|
|
394
|
+
// fleet 行」当场淘汰一条 tick,那条子代的 resume 入口凭空消失,而收编前它还在(对抗复审
|
|
395
395
|
// medium 实证形)。两读口的 LRU touch 也会让高频 fleet 行持续挤压 tick 历史,同一条道理。
|
|
396
396
|
// 满了按 LRU 淘汰,淘汰后果只是那一行退回「不知道宿主 run」= 诚实缺席,不是错值。
|
|
397
397
|
const MAX_BG_PARENT_RUNS = 320;
|
package/dist/wireErrorTriage.js
CHANGED
|
@@ -50,7 +50,7 @@ export function classifyTurnWireError(err) {
|
|
|
50
50
|
const message = typeof e.message === 'string' && e.message.length > 0 ? e.message : String(err);
|
|
51
51
|
return { kind: 'http', status: e.status, ...(errorCode !== undefined ? { errorCode } : {}), message };
|
|
52
52
|
}
|
|
53
|
-
// park 判别按 `name` **结构读**,不附加 instanceof Error(
|
|
53
|
+
// park 判别按 `name` **结构读**,不附加 instanceof Error(复审 F3 [medium] 真病修):壳源形的
|
|
54
54
|
// `err instanceof Error &&` 合取在单进程壳里恒真无害,但本件是三端共用面 —— desktop IPC 序列化 /
|
|
55
55
|
// web 跨 bundle 的同名错误是 plain object,instanceof 合取会把「候人决断」误诊成「客户端 bug +
|
|
56
56
|
// 建议重跑」(重跑=重复 turn,且掩盖真待决态)。壳注释自陈的意图本就是「name 判别(跨包
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> **本档是什么**:`@sema-agent/client-core` 作为**上游包**,对它的三个宿主端(`sema-cli` TUI /
|
|
4
4
|
> `sema-web` 聊天区 / `sema-desktop` session-host)的**正式接入文档**。按接入文档宪法(clay 08-12,
|
|
5
|
-
>
|
|
5
|
+
> 协作板 [3680])立档:消费上游先要详细全接入文档,**文档报错可直接打回**。
|
|
6
6
|
>
|
|
7
7
|
> **本档不是什么**:它**不是契约源**。契约源 = `src/**` 的实现本身 + 各文件头注/JSDoc。
|
|
8
8
|
> 本档的每一节都给**实现锚**(文件 + 符号名),读者据锚对账;对不上以真码为准,**当场改本档**。
|
|
@@ -769,13 +769,13 @@ CHANGELOG 0.29.0「已知局限」段与相应 JSDoc 都有成文。**别在读
|
|
|
769
769
|
| **P-12** | low | canonical 重呈短路臂**沿用 arm responder** ⇒ `ReopenPlanReviewOpts.deliverDecision` 注入口**不生效**(成文例外 + debug 留痕) | `src/hitl/planReviewWire.ts`(`mintFreshQuestionId:false` 分支) | 任何**包装决断投递**的端(重试/退避/上屏定序,cli 的 `decideRetry` 是参照)必须走默认 `mintFreshQuestionId` 臂 —— 否则你的包装被静默旁路,跑的是裸 `decidePlanReview` 的 fire-and-forget |
|
|
770
770
|
| **P-13** | 成文局限(不改行为) | 默认键下的 own-run 归属缺省腿是**进程级**证据,**不区分同一宿主进程内的会话代际** —— `/clear` 前登记的 run 在新会话语境下**仍判 owned**。最坏后果逐字:`用户看到自己旧会话的审批卡` | `src/hitl/parkOwnership.ts`(`ParkOwnershipDeps.isOwnRun` JSDoc);`docs/REFACTOR-LEDGER.md` 记为**驳为成文局限** | 多会话端必须**自注入**会话粒度的 `isOwnRun`;或传非默认 `sessionKey` 并接受缺省腿被整条跳过(代价 = 多一次诚实的 reopen-failed) |
|
|
771
771
|
| **P-14** | high(打包面) | `activeReopenResponders` 的**单活纪律是 module 单例**:两份实例 ⇒ 各退各的,跨份的旧卡退役不掉 —— **退化回修复前的重复活卡形** | `src/hitl/planReviewWire.ts`(`activeReopenResponders`,singleton-manifest 在册) | 见 §8-G:必须保证 bundle 里只有**一份** `@sema-agent/client-core` |
|
|
772
|
-
| **P-31** | med(0.32.0
|
|
772
|
+
| **P-31** | med(0.32.0 未发布登记;对抗复审第二/三轮抓出) | **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 槽判定 ⇒ 反而丢自己的通知),故本批**不动行为**,登记待裁 |
|
|
773
773
|
|
|
774
774
|
### 7d. 请求面与其它在册件
|
|
775
775
|
|
|
776
776
|
| ID | 级别 | 一句话现状 | 实现锚 | 端**今天**必须怎么办 |
|
|
777
777
|
|---|---|---|---|---|
|
|
778
|
-
| **P-15** | med(6 → **5**,#292 P1 结清一条) | `REQUEST_FIELD_MATRIX` 有 **5 个字段登记为 `gap: true`**(表内 `gap:true` 的定义逐字 = 「这条差异**没有正当理由,是漏的**」),全部是 **print/headless 车道缺席**:`settings.ultracode` · `reasoningEffort` · `model` · `clientContext` · `scratchpadDir`。表内点名的后果:`clientContext` 缺席 ⇒ **引擎误标 (UTC)**;`scratchpadDir` 缺席 ⇒ **`-p` 的工具写不进 exemptDir**。**已结清**:`settings.<resolved>`(0.34.0 / #292 P1 —— 版本号与 CHANGELOG
|
|
778
|
+
| **P-15** | med(6 → **5**,#292 P1 结清一条) | `REQUEST_FIELD_MATRIX` 有 **5 个字段登记为 `gap: true`**(表内 `gap:true` 的定义逐字 = 「这条差异**没有正当理由,是漏的**」),全部是 **print/headless 车道缺席**:`settings.ultracode` · `reasoningEffort` · `model` · `clientContext` · `scratchpadDir`。表内点名的后果:`clientContext` 缺席 ⇒ **引擎误标 (UTC)**;`scratchpadDir` 缺席 ⇒ **`-p` 的工具写不进 exemptDir**。**已结清**:`settings.<resolved>`(0.34.0 / #292 P1 —— 版本号与 CHANGELOG 段**同一个**,对抗复审 [low] 抓的正是两处不一致)—— 它的缺席是**权限方向**的(`-p` 上用户 settings 的 `permissions.deny/ask` 整体不被引擎求值,cli [4208] 实测),现两车道都 stamp | `src/request/taskRequest.ts`(`REQUEST_FIELD_MATRIX` 的 `gap` 列) | headless 车道上这五项**确实不上 wire**。端不要在 print 车道假设它们在场;补齐是**行为改动**,要单独一条测试,不许端侧偷加。`settings.<resolved>` 反过来:print 车道现在**会**摊开 resolver 快照 ⇒ 端必须把值放进 `input.settings.resolved`(端不给值仍是零 stamp,不会凭空出现),且该车道 `settings` 子键走**开放集**口径:`unregisteredRequestKeys` 只放行**表外**动态子键(快照自己的 permissions/env/model/… 不可枚举),**表内但不属于本车道**的子键(今天 = print 的 `settings.ultracode`)仍会被点名 —— 端不许把它白名单化,那条红是真的;另:`settings` 子键值为**函数**(如自有 `toJSON`,能在序列化时整只改写字节)恒被点名且构造器不 stamp。🔴 **0.35.0 起快照通道对具名通道让位**:见 P-15c |
|
|
779
779
|
| **P-15b** | 🔴 权限方向 | `REQUEST_FIELD_MATRIX` 的 stamp 门对**未登记键静默丢弃** —— 表里点名的真实危险形逐字:**「用户显式排除的工具被静默放回」(权限方向回归,类型层不报)**。`excludeTools` 是真 wire 键、早在 seatContract 的 `START_SESSION_OPTION_KEYS` 里,却曾长期在矩阵外;**今天只有 desktop 在发它** | `src/request/taskRequest.ts`(`excludeTools` 行)、`src/seatContract.ts`(`START_SESSION_OPTION_KEYS`) | 端自拼 taskReq 的键**必须**先进矩阵;上 wire 前跑 `unregisteredRequestKeys(req, lane)` 并**当红对待**,别当 lint |
|
|
780
780
|
| **P-15c** | 🔴 治理方向(0.35.0 行为改动) | `settings.<resolved>` 快照是**开放集 spread**(子键即 wire 键)⇒ 它天生是一条**第二通道**。0.34.0 只剥「车道异名」子键,于是**两条车道都登记**的具名子键剥不到 —— 而它们各有治理门:`hooksForWire()` 是 fail-closed(无 `SettingsPort` / 工作区未受信 / 管理侧关停全部 hooks / 检查抛错 ⇒ 返 `undefined`),此时快照里那份**没过门**的 `hooks` 照样上 wire = 关停令等于没下。让位修前靠**合并序**(具名键覆盖快照),而合并序只在具名通道**有值**时管用,门否决时恰恰**没值**。0.35.0 改**结构剥离**:凡表里有 `settings.<sub>` 行的子键(`hooks`/`webSearch`/`ultracode`),快照一概不产;表外子键(`permissions`/`env`/`model`/…)原样摊开 | `src/request/taskRequest.ts`(`namedSettingsSubKeys` / `resolvedSnapshotForWire`) | ① 具名键**必须走具名位**:`input.settings.hooks` / `.webSearch` / `.ultracode` —— 只塞进 `input.settings.resolved` 的宿主从 0.35.0 起那两个键**不再上 wire**(两条车道对称,不是新差异面);② `input.settings.resolved` 只放**表外**的 resolver 产物;③ 该位为 `undefined`/`null` = 合法缺席(照旧降空照发),**合法载体只有对象字面量与 `null` 原型字典**;其余形(数组/原始值/boxed 包装对象/`Map`/**类实例**)抛 `TypeError`。判据锚 **prototype 层数(realm 无关 —— iframe/vm/另一渲染进程的字面量照过)不锚自报标签**:类实例与 `Symbol.toStringTag` 伪造都能自报 `[object Object]`,而摊开走 `Object.entries`(只取自有可枚举键)⇒ 权限面挂在原型 getter 上会摊出空快照、请求照发。这一位摊开的是已解析权限面,降空 = 带着被剥掉的 `deny/ask` 发出去,故 fail-closed;端别 catch 掉它当没事,那是上游产出坏了 —— 把快照**平摊成对象字面量**再传即可;④ `permissions` **子树**也递归校「序列化后还是同一份内容吗」:嵌套 `Map`/`Set`/类实例/boxed/环、**任何一层**上可 call 的 `toJSON`(自有/非枚举/原型链/数组子类)、**非有限数**(`NaN`/`±Infinity`)都 ⇒ `TypeError`,报路径如 `permissions.deny[0]`,不猜 schema;射程刻意只到 `permissions`(整体深净化 = 独立工单)。⚠️ 两条**成文边界**(各有判据钉住现状,不是漏):① 构造之后污染 `Array.prototype`(重建出来的数组必须是真数组);② `getPrototypeOf` 被 Proxy 陷阱撒谎的载体(同 realm 内无可移植的 Proxy 读法)——两者都**不新增丢失面**(发的字节 = 原生序列化那一份),要关得靠「受信 resolver 出口发烙印/已物化记录」那个结构 |
|
|
781
781
|
| **P-15d** | 已知缺口(权限方向;0.35.0 登记,**刻意未在本批闭合**) | 0.35.0 把「序列化后还是同一份内容吗」这条不变量**只**落到 `settings.resolved.permissions` 子树(校 + 就地重建)。**同一条论证对其它带权限含义的位一样成立,而它们今天没有等价强制**:`permissionMode`(字符串位;非串载体的 `toJSON` 能自己决定 wire 上那个词)· `excludeTools`(丢一个元素 = **用户显式排除的工具被放回**,与 P-15b 同一方向)· `additionalDirectories` / `additionalReadDirectories`(读写边界根)· `agents` / `hooks` / `attachments`(对象位,同款 `toJSON` 改写面)。**为什么不在本批一起做**:逐个挑两三个字段补,只会造出下一个同样任意的边界;正解是**一次**把「wire 载荷 JSON-safe 规范化」做成包级闸口(该工单自 0.34.0 起在册),覆盖所有位并同批建判据 | `src/request/taskRequest.ts`(`materializeJsonFaithful` 的射程 = `permissions`;边界本身有一条判据钉着:非权限位的同款畸形**不拦**) | 端**不要**推断「本包会替我把请求体洗干净」——今天只有 `permissions` 子树有这个保证。上 wire 的值请自己保证是 JSON 原生形(字面量 / 数组 / 字符串 / 有限数):别拿类实例、`Map`、带 `toJSON` 的包装对象、访问器对象当载体。尤其 `permissionMode` / `excludeTools`:前者决定整会话的审批姿态,后者丢一个元素就是权限变宽 |
|
|
@@ -793,7 +793,7 @@ CHANGELOG 0.29.0「已知局限」段与相应 JSDoc 都有成文。**别在读
|
|
|
793
793
|
| **P-27** | 立票设计件(web [C1] 疑点③,族A 二段票同批) | `ToolPermissionDecision` **无 note 席位**且座位宿主无 caps 缓存读口 ⇒ #229 回决备注在两座位端(web/desktop)**结构性无法供给**。修形二选一未裁:decision 形补 `note?` + 能力位随 `ToolPermissionRequest` 下发,或宿主侧统一判 | `src/seatContract.ts`(`ToolPermissionDecision`,682 行域) | 座位端今天**不要**渲 note 输入位(渲了也送不出去=假 affordance);候本条落地随提货单换 |
|
|
794
794
|
| **P-28** | 🔴 med(浏览器面) | ⚠️ 2026-08-14 补记:除下面那 10 处外,**第二个未放宽的入参面** `EngineProbeOpts.authToken`(`src/engineWireSdk.ts`)另喂 2 处探针(`agentsWireCaps.ts` 的 `engineSupportsTaskAgents` ⇒ `undefined` / `liveInitToolFace.ts` 的 `probeScenarioTools` ⇒ `null`,均属 C 档静默),合计 **12** 处 —— 见 §5d 末尾那段。 **`same-origin-relay` 只放宽了 2 个入参面,装配入口没跟**:`EngineWireClientConfig.token` 与 `LiveWorkflowConfig.authToken` 收了 `\| { mode:'same-origin-relay' }`,而 `EngineWireTarget.token` 仍是 `string` —— `installEngineWireTarget()` 恰恰是**非 Node 宿主唯一**的装配入口。经 `engineWireTarget()` 取址再构造 client 的 **10 处**(`hitl/planReviewWire.ts` ×2 · `subagent/engineSubagentTail.ts` · `engineSubagentSteer.ts` · `engineSubagentOutput.ts` · `engineCompactWire.ts` ×2 · `engineTaskHandleWire.ts` ×2 · `engineDelegatedPrompt.ts`)在同源反代部署下**没有合法凭证形可传**。⚠️ 失效形**逐点不同**(§5d 末两表:A 响亮 + **有条件**用户可见 = `decidePlanReview`,可见性取决于通知队列口装没装 / B 结构化 reason = steer 与 taskStop / C 无条件静默 = 其余七处),但**构造失败一律吞成 null、从不抛异常** | `src/engineWireTarget.ts`(`EngineWireTarget.token`)· `src/engineWireSdk.ts`(`makeEngineWireClient` 的 catch 臂)· §5d 的两表 | 浏览器同源宿主今天**只能**走 §5d 上表那两条自带入参面的路径(直调 `makeEngineWireClient` / `createLiveWorkflowSource`);走 `engineWireTarget()` 的子代与 plan-review 动词**别指望在 relay 部署下发得出去**,也**不要**把「动词没反应」读成「引擎没这个能力」(⚠️ plan-review 那条**只在通知队列口装上时**才到达用户,见 §5d 末表 —— 队列口没装就退回零用户通道,端的兜底告知别急着撤)。🔴 正位解在包侧(放宽 `EngineWireTarget.token` + 10 处透传),**不许端侧侧路补救**(跨仓缺陷源头修复);要它落地按 [C162] 令④ 回 C 板 |
|
|
795
795
|
| **P-29** | low(自检面) | **通知队列口没有存在性读口**:审批卡口有 `hasApprovalCardPort(For)`、HITL 面有 `hitlHostSurfaceFor`、宿主端口族有 `hostSettings()` 等无副作用读口(见 §5a 的 (a) 表),**唯独 `installNotificationQueuePort()` 没有对偶谓词**。而它的 `notificationQueuePortMisses()` 与同族几个 miss 计数一样**初值为 0**,只有真发生过一次「用到了但没装」才递增 ⇒ 「完全没装 + 还没有任何投递」照样是 0。拿它做**启动装配自检**必然假绿 —— §5a 此前正是这么写的(#252 复审 R3/R4 命中,已按端口拆成「存在性读口」与「回归探针」两类) | `src/notifications.ts`(`installNotificationQueuePort` 无对偶读口;`queuePortMisses` 初值与 `port()` 的 null 分支) | 队列口:按 §8-B 真调 `installNotificationQueuePort()`,miss 计数只当**跑过真流量之后**的回归探针用;其余端口按 §5a (a) 表用各自的存在性读口做启动校验。要队列口的读口按 [C162] 令④ 回 C 板提(正位解在包侧:补一个 `hasNotificationQueuePort()` 谓词) |
|
|
796
|
-
| **P-30** | med(HITL 路由面) | **durable 审批腿不按 `gateKind` 路由,且取行有「同 taskId 任意行」回落** (0.30.0 发包扫描
|
|
796
|
+
| **P-30** | med(HITL 路由面) | **durable 审批腿不按 `gateKind` 路由,且取行有「同 taskId 任意行」回落** (0.30.0 发包扫描 对抗复审 finding① 坐实,**非本窗引入**):`findPendingForTask` 在工具名谓词无命中时走 `?? rows.find(r => r.taskId === taskId)`,而 `surfaceFsApprovalAndDecide` 拿到行之后**不校 `gateKind`** ⇒ 同一 task 上同时停着 `plan_review` / `resource_limit` 行时,会弹出一张 `toolName` 为空串的**工具审批卡**。⚠️ **不会误批**(server 侧 fail-closed):本腿打的是 `POST /v1/approvals/:sessionId/decide`,非工具门在该端点上回 **409 `gate_not_tool_approval`**(SDK `dist/errors.d.ts`;⚠️ **不是** `gate_not_resumable` / `gate_not_plan_review` —— 那两个分别是 `/resume` 与 plan-review 端点的守卫,2026-08-14 对抗复审 R2 订正本条初稿的错码)。🔴 **但后果不止「一次失败的决断」**:decide 抛错 ⇒ `surfaceFsApprovalAndDecide` 折成 `{kind:'failed'}` ⇒ `parkResolver` 走 fail-soft 结束**本次客户端 turn**;而 server 侧 checkpoint 因为 fail-closed **没被消费**,run/session 仍 suspended、仍持 claim ⇒ 重试还会再撞一次。⚠️ **终帧按入口分两形,排障别只等一个码**(2026-08-14 对抗复审 R3 订正本条初稿的单一描述):① **初始 park 入口**(`done{…park…}` 经 `frameRouter.routeDone` 进来,`park.pendingDone` **在场**)⇒ 先 `led.flushHeld()` 吐出 park 期被 HOLD 的**毒化帧**(`tool_end{isError:true, output:'Operation aborted'}`,`frameRouter.ENGINE_ABORT_TOOL_RESULT`),再原样回吐那条 `done` —— **没有**合成 `failed` 终帧、**没有** `hitl_unanswered` 错误码,可观察到的失败信号只有那条 isError 的 `tool_end`。⚠️ **它与「用户真按了拒绝」可以分辨,按 `output` 分**(2026-08-14 对抗复审 R5 订正本条初稿的「同形不可分」):fail-soft 这条是 `flushHeld()` 原样吐出的**毒化帧**,`output` 逐字是 `ENGINE_ABORT_TOOL_RESULT`(`'Operation aborted'`);真 deny 走 `frameRouter` 的 `denied-call` / `deny-stamp-next` 臂,`output` 被改写成 `HITL_REJECT_MESSAGE`(CC `REJECT_MESSAGE` 逐字)。端做归因按 `output` 判,别只看 `isError`;② **续流 / durable re-attach 入口**(`suspended` 进来,无 `pendingDone`)⇒ 才合成 `failed{errorCode:'hitl_unanswered'}`。⇒ 端做告警/埋点时**不要**只锚 `hitl_unanswered`,①那条路径上它根本不出现。⚠️ 定性要分清:这条 fail-soft 链是 durable 腿**通用**的失败路径(设计如此 —— 替代方案是谎报成功,更坏),**不是**本缺口独有;本缺口的**增量**是「弹了一张 `toolName` 为空的卡 + 发了一次注定 409 的 decide + 把用户的一次表态浪费掉」 | `src/hitl/hitlBridge.ts`(`findPendingForTask` 的第二条 `rows.find`)· `src/hitl/toolApprovalWire.ts`(`surfaceFsApprovalAndDecide` 全程零 `gateKind` 读)· 常驻登记见 `scripts/run-durable-card-display-keys-test.mjs` ⑦ 段 `gateKind` 那条未投影理由 | 端**不要**把「durable 卡弹出来了」读成「这一定是个工具门」;拿到 `toolName` 为空串的卡按异常处置、别渲成可决断卡。🔴 正位解在包侧(本腿按 `gateKind` 严格路由 + 回落收窄),要同批想好 pre-`gate_kind` 历史行 `gateKind` 缺席时的降级 —— 属独立设计件,按 [C162] 令④ 回 C 板提 |
|
|
797
797
|
|
|
798
798
|
### 7e. 缺口的共同形状(值得单独说)
|
|
799
799
|
|
package/docs/REFACTOR-LEDGER.md
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
> **终态:167/167 全清**(F 族→B 族→wave1 五卡→G 族两车→wave2 六卡→收官批→A 族压轴双车)。
|
|
4
4
|
> 收官判据:21 门全绿(merge 后主树 20/20 套含 SEMA_CLI_ROOT 真差分,另 _gate-lib 加载期自测)/
|
|
5
5
|
> 导出基线 525 双向 / 六 strict 旋钮全开 / singleton manifest 双向 / typeshape 棘轮 b4=21·unknown 203(逐条登记)·裸返回 0。
|
|
6
|
-
> 收货形=refactor-pipeline-form(sonnet·opus
|
|
6
|
+
> 收货形=refactor-pipeline-form(sonnet·opus 车+双镜头 双镜头+主会话亲审亲变异);A 族压轴双车三方判决+
|
|
7
7
|
> 收货修两笔(args 快照/单读·快照·惰性三处)见 aaec8a7/c73fdf7 两 merge。残余台账=WAVE1-RESIDUALS.md+
|
|
8
8
|
> 各族「P2 裁决」段 keep-with-reason 条目。本档由 P2 工作底稿原位收卷,逐条裁决与坐标全部保留如下。
|
|
9
9
|
|
|
10
10
|
# (原)client-core 规范重构 P1→P2 裁决工作底稿
|
|
11
11
|
|
|
12
|
-
统一重排号 REF-CC-001 起连号,原镜头 id 括注保留。**裁决进度**:F 族(075-080)=GO 已落地(merge 2626369,四旋钮+20 红清零,变异实证红→绿);A 族=全 GO 但候 r2 修订稿(
|
|
12
|
+
统一重排号 REF-CC-001 起连号,原镜头 id 括注保留。**裁决进度**:F 族(075-080)=GO 已落地(merge 2626369,四旋钮+20 红清零,变异实证红→绿);A 族=全 GO 但候 r2 修订稿(对抗复审四 verdict 折入,见 A-FAMILY-ADVERSARIAL-REVIEW.md);B 族 notif-01/02/03=GO 紧急批在飞。并单已按 critic(agent5)点名 + 复核发现的同文件同行段同修法项执行(见文末统计的「并单前/后」)。P2裁决列留空,由裁决人填 GO/HOLD/REJECT + 备注。
|
|
13
13
|
|
|
14
14
|
来源镜头:agent0=dup-invariant(13) / agent1=giant-split(17) / agent2=type-shape(17) / agent3=gate-p4(38) / agent4=lexicon/域词表(21) / agent5=critic完备性(14) / sup0=midband(7) / sup1=xlate(17) / sup2=notif(14) / sup3=hitl2(17,实14条finding+3条coverage段) / sup4=fleet2(15)。
|
|
15
15
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/client-core",
|
|
3
|
-
"version": "0.38.
|
|
3
|
+
"version": "0.38.1",
|
|
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",
|