@sema-agent/client-core 0.35.0 → 0.36.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 +63 -0
- package/README.md +1 -1
- package/dist/adapter/runStream.d.ts +49 -0
- package/dist/adapter/runStream.js +34 -0
- package/dist/hitl/toolApprovalWire.d.ts +47 -1
- package/dist/hitl/toolApprovalWire.js +10 -0
- package/docs/INTEGRATION-CLIENTS.md +17 -15
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,69 @@
|
|
|
16
16
|
> 🔴 **互链**(web [C166]⑦):各版「已知局限」段只记**该版新增**;接入面已知局限的完整台账在
|
|
17
17
|
> `docs/INTEGRATION-CLIENTS.md` §6e/§7 —— **只读其一会漏**,两处都过。
|
|
18
18
|
|
|
19
|
+
## 0.36.0(未发布)
|
|
20
|
+
|
|
21
|
+
**server 7.33.0 两 wire 键的过境批 + 一条跨仓文档失真的订正。行为面**两条**,都是 additive 透传
|
|
22
|
+
(缺席臂逐字节不变):安全类 ask 出身位到得了卡口;park 门第一次在 wire 上有了非秘密身份。**
|
|
23
|
+
|
|
24
|
+
- **`requiresRealApproval` 帧键镜像 + 卡口透传(#283,[4390]③ 请托的正位解)**:core 5.37 起在
|
|
25
|
+
`AskRequest` 上铸的**安全类 ask 出身位**(生产者 = `createUnverifiableDeletePolicy` /
|
|
26
|
+
`createTranscriptIntegrityPolicy` 与 PreToolUse hook 族),server **7.33.0** 起投到 `tool_approval`
|
|
27
|
+
帧**顶层**。此前 `ToolApprovalFrame` 与 `ApprovalCardRequest` 都不含它 ⇒ 包边界的逐键 stamp 白名单
|
|
28
|
+
把它剥掉 ⇒ 壳侧早已预埋的读器(认顶层 + `risk.*` 两拼法)读到的**恒是缺席**,「这门拒绝
|
|
29
|
+
blanket-allow」这条出身在端上不可达。本版补齐两处 + `TOOL_APPROVAL_FRAME_KEYS_MIRROR` 17→**18** 键。
|
|
30
|
+
🔴 **契约 = 真才带,缺席绝不编 `false`**(故类型是 `true` 而非 `boolean`,与 `governanceForced`
|
|
31
|
+
同族):缺席 = 这不是一次安全类 ask,是**正常的否定形**,不是坏形也不是「老 server」的同义词。
|
|
32
|
+
⚠️ **与卡内那一位的在场契约刻意不同、别混**:durable `ApprovalCard.risk.requiresRealApproval` 是
|
|
33
|
+
**恒在布尔**,本键走帧顶层只在为真时在场 ⇒ 「帧上没有」与「卡上是 false」是两条不同的陈述。
|
|
34
|
+
⚠️ **耐久腿刻意零 stamp**:sdk 7.1.0 的 `PendingCheckpoint` / `riskDescriptor` 两处都**没有**这一位
|
|
35
|
+
(server 那份在**卡内** `risk` 子树,不是行上的键)⇒ 与 `ruleEvidence` 同裁,诚实零投影 + 写明理由,
|
|
36
|
+
绝不猜载体名;行为钉在 `run-durable-card-display-keys-test.mjs` ⑨ 段(四种最像的载体名摆在行上,
|
|
37
|
+
卡入参必须一个都不长出来)。上游补位后按 `probeCause` 的双源合流形跟批。
|
|
38
|
+
⚠️ **领先 SDK 锚一代**:sdk 7.1.0 的 `TOOL_APPROVAL_FRAME_KEYS` 尚无本键 ⇒ 对账门
|
|
39
|
+
(`run-approval-frame-keys-test.mjs`)记一条 **AHEAD_OF_ANCHOR 带退出条件登记**,SDK 追平当天
|
|
40
|
+
该登记自红逼删(#144 `persistedRuleShadowed` / #280 `probeCause` 同形第三例)。
|
|
41
|
+
- **`pendingGate.checkpointId` 窄读 + 单向同一性谓词(#285 件2)**:server 7.33.0 起把 core 5.42.0 的
|
|
42
|
+
`Checkpoint.checkpointId`(`mintCheckpointId` 铸 = `cp_`+uuid)投到 409 `conflict.session_active_run`
|
|
43
|
+
体 + 其 SSE done 帧的 `pendingGate` 上。这是 park 门在 wire 上的**第一个非秘密稳定身份** —— 此前唯一
|
|
44
|
+
单射的键是 `token`,而 token 是秘密能力永不上 wire,消费端只能拿 `(sessionId, kind)` 这种非单射组合
|
|
45
|
+
去猜。`ActiveRunPendingGate` 补 `checkpointId?: string`,窄读口径与 `kind`/`decidePath` 同族
|
|
46
|
+
(**非空串才置键**,坏形降缺席)。
|
|
47
|
+
🔴 **刻意不做 `cp_`+UUID 格式正则、也不叠长度上限**,与 server 铸点同裁:上游亲跑验过,严格格式钉
|
|
48
|
+
**买不到安全性**(32 位 hex token 按 uuid 分组后逐字满足该正则,剥掉装饰就是完整能力键),只额外
|
|
49
|
+
买来「core 换形我方静默丢键」的代价;长度域已在 server 投影处夹取,客户端再叠一道**更窄**的域 =
|
|
50
|
+
上游放宽当天我方静默丢键,而丢键与 legacy 行在运维面上不可分辨。
|
|
51
|
+
🔴 **新导出 `pendingGateIsProvablyDifferent(prev, next)`**(公面 748→**749**):`true` ⇔ 两侧都带
|
|
52
|
+
`checkpointId` 且不等,其余一律 `false`。**`false` 的语义是「证不出」,不是「同一道门」** —— 上游
|
|
53
|
+
写死了可用性边界(server 选行是**无序 `LIMIT 1`** 且同 session 可并存多条 pending 行,同一情形连续
|
|
54
|
+
两次 409 完全可能报出不同 id;也可能 id 不变而 `activeTaskId` 已换),只背书「id 变了 ⇒ 确实不是
|
|
55
|
+
刚才那一行」这**一个**方向。归包的理由 = 这个谓词的价值全在**它拒绝回答的那一半**:三端各自手搓时
|
|
56
|
+
「两个 id 相等就当同一张卡去重」是最自然也最容易写出的一行,而它恰恰是上游明说买不到的方向。
|
|
57
|
+
⚠️ **辖域如实,别预支「两面能对上」**:上游本批只投到这**一个**载体;另外三条 park 读面
|
|
58
|
+
(`/v1/assistant/inbox`、`GET /v1/approvals`、`/v1/approvals/stream`)今天都没有这一格(后两条要给
|
|
59
|
+
checkpoint 表反范式一列 = SQL 面双库门)⇒ 拿本键去 join durable 队列行必然落空,而落空与 legacy
|
|
60
|
+
行同形。**缺席是三成因合流**(legacy 行 / 值没过 server 的信任边界校验 / 整只材料读取失败),
|
|
61
|
+
读作「不知道这道门叫什么」,不是「这道门没有身份」,更不是「这一定是条老行」。
|
|
62
|
+
- **文档订正三件(无行为面)**:
|
|
63
|
+
- `docs/INTEGRATION-CLIENTS.md` **§4 cancel 语义**与 §8-B checklist 的对应行改写为**两动词语义表**
|
|
64
|
+
—— 原文「取消一个 suspended run 必须用 deny,绝不 `runs.cancel`(对 suspended run 会 409)」已被
|
|
65
|
+
装机 SDK 7.1.0 标 **stale**(`runs.d.ts` `cancel` JSDoc 逐字:服务端 [868] 起直接就地取消
|
|
66
|
+
suspended/needs_review 跑,**409 现在只剩 CAS race**)。照旧文档做会把「停」实现成「放它接着跑」。
|
|
67
|
+
新表按**意图**分:`deny` = 否掉**这一道门**(run 继续,模型拿到拒绝继续跑);`cancel` = 终结
|
|
68
|
+
**整条 run**。同批加一条 **SDK 声明对账格**(`run-integration-doc-freshness-test.mjs` ⑦ 段):
|
|
69
|
+
档里这段的判据锚在装机 SDK 的 `cancel` JSDoc 上,上游哪天改回去/改别的说法,门当天红。
|
|
70
|
+
- **勘误(#[4399]④)**:`## 0.33.0` 段把 `injectedSubmissionRow` 列进了
|
|
71
|
+
`adapter/activeRunSelfHeal` 的导出清单,**实为 module-private**(`src/adapter/activeRunSelfHeal.ts`
|
|
72
|
+
的 `function injectedSubmissionRow(...)`,无 `export`,亦不在 `public-export-baseline.json` 里)。
|
|
73
|
+
该段是**冻结面**不可改写 ⇒ 勘误记在此。三端按名收账时不必找这个导出:同批的
|
|
74
|
+
`RejectedSubmissionOrigin` / `SelfHealSubmissionDisposition` / `selfHealSubmissionDisposition()`
|
|
75
|
+
才是真导出,行为经 `activeRunSelfHealRow` 的第四参进。
|
|
76
|
+
- **§0a 版本锚**刷到本版(此前停在 0.31.0 / peer floor `>=6.17.2`,与实况 0.35.0 / `>=7.1.0` 两处皆漂)。
|
|
77
|
+
|
|
78
|
+
**已知局限(本版新增)**:`requiresRealApproval` 与 `checkpointId` 都**只有活卡帧/409 这一条腿**,
|
|
79
|
+
耐久面各自无对偶(理由与代价见上,均已在门里登记)。接入面已知局限的完整台账见
|
|
80
|
+
`docs/INTEGRATION-CLIENTS.md` §6e/§7,**只读本档会漏**。
|
|
81
|
+
|
|
19
82
|
## 0.35.0(2026-08-18)
|
|
20
83
|
|
|
21
84
|
**0.34.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
|
+
**Version:** 0.36.0
|
|
39
39
|
|
|
40
40
|
- **Today** — the adapter seam, the whole `adapt()` pipeline (all 14 A-layer arms plus the
|
|
41
41
|
B/D/E tool-card layers), the notification/caps/model families, the adapter kernel (stream driver
|
|
@@ -50,7 +50,56 @@ export interface ActiveRunPendingGate {
|
|
|
50
50
|
* 证据」。消费方只做**在场才说**的加法:纯归因一句,不参与任何分诊/动作。
|
|
51
51
|
*/
|
|
52
52
|
readonly governanceForced?: true;
|
|
53
|
+
/**
|
|
54
|
+
* 这道 park 门的**非秘密稳定身份**(#285 件2,0.36.0;server ≥7.33.0 `PendingGateMaterial.checkpointId`
|
|
55
|
+
* / core 5.42.0 `Checkpoint.checkpointId`,`mintCheckpointId` 铸 = `cp_`+uuid)。逐字过境,本层零加工。
|
|
56
|
+
*
|
|
57
|
+
* 🔴 **它补的是一个真实的空洞**:park 门此前在 wire 上**没有任何稳定身份** —— 唯一单射的键是
|
|
58
|
+
* `token`,而 token 是秘密能力、永不上 wire,于是消费端只能拿 `(sessionId, kind)` 这种非单射组合
|
|
59
|
+
* 去猜「这次的门」和「上次的门」是不是同一道。本键是为此存在的非秘密孪生身份(identification vs
|
|
60
|
+
* capability,两条独立的轴;**独立铸造,不是 token 摘要**)。
|
|
61
|
+
*
|
|
62
|
+
* 🔴 **缺席是三成因合流的一个形**(server 逐条成文,别只记第一条):① 该键诞生前 park 的 legacy 行;
|
|
63
|
+
* ② 行上有值但没过 server 的信任边界校验(空串/超长/**含 token**)⇒ 数据污染,而它在运维面上
|
|
64
|
+
* 与 ① **不可分辨**(server 自己登记的残留);③ 整只 `pendingGate` 读取失败(那时本键连位置都没有)。
|
|
65
|
+
* ⇒ 读作「**不知道这道门叫什么**」,**不是**「这道门没有身份」,更**不是**「这一定是条老行」。
|
|
66
|
+
*
|
|
67
|
+
* 🔴 **不是跨调用的稳定去重键**(逐条出自 server 侧 `PendingGateMaterial.checkpointId` 的顶注 ——
|
|
68
|
+
* 那段是上游自己的对抗复审验真后如实登记的,不是本包的推测):
|
|
69
|
+
* 同 session 可并存两条 pending checkpoint(表上**没有** per-session 唯一约束),
|
|
70
|
+
* 而 server 选行是**无序 `LIMIT 1`** ⇒ 同一情形连续两次 409 完全可能报出**不同**的 id(选中了另一行),
|
|
71
|
+
* 也可能 id 不变而 `activeTaskId` 已换。本材料的**每一格**(kind/decidePath/governanceForced/本键)
|
|
72
|
+
* 共享这条归属不确定性,本键是**继承**它、不是引入它。
|
|
73
|
+
* ⇒ 可安全用于:落日志/排障关联,以及 {@link pendingGateIsProvablyDifferent} 那个**单向**判断。
|
|
74
|
+
* **不可**用于:跨 turn 的卡去重键、「id 相同 ⇒ 同一道门」的反向推理。
|
|
75
|
+
*
|
|
76
|
+
* ⚠️ **辖域如实**:上游本批只把这一位投到**这一个载体**(409 `conflict.session_active_run` 体 +
|
|
77
|
+
* 其 SSE done 帧)。另外三条 park 读面(`/v1/assistant/inbox`、`GET /v1/approvals`、
|
|
78
|
+
* `/v1/approvals/stream`)今天**都没有**这一格,且补它们的代价不同(后两条要给 checkpoint 表反范式
|
|
79
|
+
* 一列 = SQL 面双库门)。⇒ **别在消费码里预支「两面能对上」** —— 拿本键去 join durable 队列行必然
|
|
80
|
+
* 落空,而落空与 legacy 行同形。
|
|
81
|
+
*/
|
|
82
|
+
readonly checkpointId?: string;
|
|
53
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* `pendingGate` 的**单向**同一性判断(#285 件2,0.36.0):「我现在看到的这道门,能不能**证明**它
|
|
86
|
+
* 不是刚才那一道?」
|
|
87
|
+
*
|
|
88
|
+
* `true` ⇔ 两侧都带 {@link ActiveRunPendingGate.checkpointId} **且**两者不等。其余一律 `false`。
|
|
89
|
+
*
|
|
90
|
+
* 🔴 **`false` 的语义是「证不出」,不是「同一道门」** —— 这不是措辞洁癖,是上游写死的可用性边界:
|
|
91
|
+
* server 选行是无序 `LIMIT 1` 且同 session 可并存多条 pending 行,所以「id 相同」推不出「同一道门」
|
|
92
|
+
* (id 不变而 `activeTaskId` 已换是成文的可能形),「id 缺席」更只是「不知道它叫什么」(三成因合流)。
|
|
93
|
+
* 上游明确只背书**这一个方向**:「id 变了 ⇒ 我看到的确实不是刚才那一行」。
|
|
94
|
+
*
|
|
95
|
+
* 🔴 **为什么归包而不是让三端各写一遍**:这个谓词的价值全在**它拒绝回答的那一半**。三端各自手搓时,
|
|
96
|
+
* 「两个 id 相等就当同一张卡去重」是最自然、也最容易写出来的一行 —— 而它恰恰是上游明说买不到的那个
|
|
97
|
+
* 方向,写出来就是一条按错误前提去重的卡链。把可答的那半做成唯一入口,不可答的那半就没有顺手的写法。
|
|
98
|
+
*
|
|
99
|
+
* ⚠️ 它**不做**任何 kind/decidePath 比较:那两位回答的是「哪种门/去哪决议」,与「是不是同一行」正交
|
|
100
|
+
* (同一行的 kind 不会变,不同行的 kind 完全可能相同)。要判种类变化请直接读 `kind`。
|
|
101
|
+
*/
|
|
102
|
+
export declare function pendingGateIsProvablyDifferent(prev: ActiveRunPendingGate | null | undefined, next: ActiveRunPendingGate | null | undefined): boolean;
|
|
54
103
|
/** #114/[C65] 单源化:三端共用的 409 active-run 拒收终帧判别(富信号形,包级唯一权威)。
|
|
55
104
|
* 🔧 2026-08-02 事实纠正([C77]③):原文写的是「与壳/desktop **各自**照抄件同义……各端换包
|
|
56
105
|
* 导入删本地抄件」,而 desktop 全树扫描(`activeRunBusySignal` / `session_active_run` /
|
|
@@ -10,6 +10,32 @@ import { coerceOutput, publishSubagentContentEvent } from '../subagentContentSto
|
|
|
10
10
|
import { ACTIVE_RUN_BUSY_ERROR_CODE, OUTPUT_INVALID, isLimitsExceededCode } from '../engineErrorCodes.js';
|
|
11
11
|
/** 本文件发的 chrome 事件全在 leader lane(子代内容在上面就被 divert 走了)。 */
|
|
12
12
|
const MAIN = { lane: 'main' };
|
|
13
|
+
/**
|
|
14
|
+
* `pendingGate` 的**单向**同一性判断(#285 件2,0.36.0):「我现在看到的这道门,能不能**证明**它
|
|
15
|
+
* 不是刚才那一道?」
|
|
16
|
+
*
|
|
17
|
+
* `true` ⇔ 两侧都带 {@link ActiveRunPendingGate.checkpointId} **且**两者不等。其余一律 `false`。
|
|
18
|
+
*
|
|
19
|
+
* 🔴 **`false` 的语义是「证不出」,不是「同一道门」** —— 这不是措辞洁癖,是上游写死的可用性边界:
|
|
20
|
+
* server 选行是无序 `LIMIT 1` 且同 session 可并存多条 pending 行,所以「id 相同」推不出「同一道门」
|
|
21
|
+
* (id 不变而 `activeTaskId` 已换是成文的可能形),「id 缺席」更只是「不知道它叫什么」(三成因合流)。
|
|
22
|
+
* 上游明确只背书**这一个方向**:「id 变了 ⇒ 我看到的确实不是刚才那一行」。
|
|
23
|
+
*
|
|
24
|
+
* 🔴 **为什么归包而不是让三端各写一遍**:这个谓词的价值全在**它拒绝回答的那一半**。三端各自手搓时,
|
|
25
|
+
* 「两个 id 相等就当同一张卡去重」是最自然、也最容易写出来的一行 —— 而它恰恰是上游明说买不到的那个
|
|
26
|
+
* 方向,写出来就是一条按错误前提去重的卡链。把可答的那半做成唯一入口,不可答的那半就没有顺手的写法。
|
|
27
|
+
*
|
|
28
|
+
* ⚠️ 它**不做**任何 kind/decidePath 比较:那两位回答的是「哪种门/去哪决议」,与「是不是同一行」正交
|
|
29
|
+
* (同一行的 kind 不会变,不同行的 kind 完全可能相同)。要判种类变化请直接读 `kind`。
|
|
30
|
+
*/
|
|
31
|
+
export function pendingGateIsProvablyDifferent(prev, next) {
|
|
32
|
+
const a = prev?.checkpointId;
|
|
33
|
+
const b = next?.checkpointId;
|
|
34
|
+
// 任一侧缺席 ⇒ 证不出(缺席 = 不知道这道门叫什么,三成因合流;绝不当成「不同」)。
|
|
35
|
+
if (typeof a !== 'string' || typeof b !== 'string')
|
|
36
|
+
return false;
|
|
37
|
+
return a !== b;
|
|
38
|
+
}
|
|
13
39
|
/**
|
|
14
40
|
* 判别一个**原始 AgentEvent** 是不是 409 active-run 拒收终帧;非 busy ⇒ null。
|
|
15
41
|
* 判据=结构两腿(canonical `errorCode` 优先 → `activeTaskId` 在场);人话文案腿已随 #117 提货
|
|
@@ -51,10 +77,18 @@ export function activeRunBusySignal(ev) {
|
|
|
51
77
|
// 出身位(A-028.1):**只认严格 true**。任何别的形(false / 'true' / 1 / 缺席)一律不置键 ——
|
|
52
78
|
// 键在场即渲徽标类文案,把一个含糊值读成「治理强制」= 对用户下一个证不出的断言。
|
|
53
79
|
const g = rawGate.governanceForced;
|
|
80
|
+
// 身份位(#285 件2):**非空串才置键**,坏形一律降缺席 —— 与 kind/decidePath 同族口径。
|
|
81
|
+
// 🔴 **刻意不做 `cp_`+UUID 格式正则、也不叠长度上限**,与 server 铸点同裁:上游亲跑验过,严格
|
|
82
|
+
// 格式钉**买不到安全性**(32 位 hex token 按 uuid 分组后逐字满足该正则,剥掉装饰就是完整能力键),
|
|
83
|
+
// 只额外买来「core 换形我方静默丢键」的代价;长度域已在 server 投影处夹取,客户端再叠一道
|
|
84
|
+
// **更窄**的域 = 上游放宽当天我方静默丢键,而丢键与 legacy 行在运维面上不可分辨。
|
|
85
|
+
// 窄读域只许等于或宽于铸点域([4050] 随批立的纪律)。
|
|
86
|
+
const cp = rawGate.checkpointId;
|
|
54
87
|
pendingGate = {
|
|
55
88
|
kind: typeof k === 'string' && k.length > 0 ? k : null,
|
|
56
89
|
decidePath: typeof d === 'string' && d.length > 0 ? d : null,
|
|
57
90
|
...(g === true ? { governanceForced: true } : {}),
|
|
91
|
+
...(typeof cp === 'string' && cp.length > 0 ? { checkpointId: cp } : {}),
|
|
58
92
|
};
|
|
59
93
|
}
|
|
60
94
|
return {
|
|
@@ -268,6 +268,28 @@ export interface ApprovalCardRequest {
|
|
|
268
268
|
* UNTRUSTED-for-display:只渲染,绝不回喂模型/工具入参。
|
|
269
269
|
*/
|
|
270
270
|
ruleEvidence?: unknown;
|
|
271
|
+
/**
|
|
272
|
+
* 安全类 ask 出身位(#283,0.36.0;server ≥7.33.0 / core 5.37)——原样来自
|
|
273
|
+
* {@link ToolApprovalFrame.requiresRealApproval}(**单源:活卡帧腿**;durable 行腿今天无对偶,
|
|
274
|
+
* 见帧上同名键的 JSDoc 末段)。在场 = 这只 ask **拒绝 blanket-allow**:一切自动放行必须让位
|
|
275
|
+
* (含记住的规则、`allow_session`、bypass 姿态)——那正是[4390]③ 预埋壳侧读器时写死的消费语义。
|
|
276
|
+
* 缺席 = 这不是一次安全类 ask / 老 server(两者同形,不猜),卡形与 0.35.0 字节不变。
|
|
277
|
+
*
|
|
278
|
+
* 🔴 **缺席绝不折成 `false`**(类型 `true` 而非 `boolean`,与 {@link governanceForced} 同族纪律):
|
|
279
|
+
* 折成 false 会把「没有证据」渲成「已确认这是普通 ask」,而这一位的整个存在理由就是不让自动放行
|
|
280
|
+
* 在安全类门上悄悄生效。
|
|
281
|
+
* 🔴 **与 {@link governanceForced} 分键不合流**:那位答「门是运维治理层下的」,本位答「引擎侧安全
|
|
282
|
+
* 策略/hook 判这门必须真人过目」。两句话对用户的下一步建议不同(改不改 permissionMode),
|
|
283
|
+
* 合并即谎报出身。
|
|
284
|
+
* 🔴 **展示 + 放行闸,不是裁决输入**:它不改变本次决断的任何字节,只约束宿主**自动**决断的资格;
|
|
285
|
+
* 人真按下的 allow 仍是 allow。
|
|
286
|
+
* 🔴 **分工写死,别把「包搬运了」读成「包挡住了」**:本包只保证这一位**到得了卡口**,
|
|
287
|
+
* 它**不**改写、不否决、也不收窄你回给 wire 的决断动词 —— `surfaceToolApprovalFrameAndRespond`
|
|
288
|
+
* 对 {@link ApprovalCardDecision} 的映射与 0.35.0 逐字节相同。⇒ 「在场时不要走自动放行」「在场时
|
|
289
|
+
* 卡上要不要还给人一个会话级选项」这两条策略**属于宿主**,包不替你执行;要它变成包级强制,是一次
|
|
290
|
+
* **行为面**改动(会改到人已按下的决断的字节),按宪法三问单独立项,不在本过境批内。
|
|
291
|
+
*/
|
|
292
|
+
requiresRealApproval?: true;
|
|
271
293
|
}
|
|
272
294
|
/**
|
|
273
295
|
* 🔴 **拆缝口** —— 弹「三选卡」并等人的决断。壳 = vendored CC `PermissionRequest`;
|
|
@@ -433,6 +455,30 @@ export interface ToolApprovalFrame {
|
|
|
433
455
|
* 端呈前消毒,只渲染绝不回喂模型/工具入参。
|
|
434
456
|
*/
|
|
435
457
|
ruleEvidence?: unknown;
|
|
458
|
+
/**
|
|
459
|
+
* server ≥7.33.0(#283,core 5.37 起铸;**ADDITIVE**,`"tool_approval"` only。来源锚 = engine fixture
|
|
460
|
+
* `@sema-agent/server/dist/tool-approval.d.ts` 的同名键)——这只 ask 是一次**安全类** ask:
|
|
461
|
+
* 它**拒绝 blanket-allow**。出身是 core 自己的两条安全策略(`createUnverifiableDeletePolicy` /
|
|
462
|
+
* `createTranscriptIntegrityPolicy`)与 PreToolUse hook 族在 `AskRequest.requiresRealApproval` 上铸的位。
|
|
463
|
+
*
|
|
464
|
+
* 🔴 **契约 = 真才带,缺席绝不编 `false`**({@link governanceForced} 同形,故类型是 `true` 而不是
|
|
465
|
+
* `boolean`):缺席 = 这不是一次安全类 ask —— 那是**正常的否定形**,不是坏形、也不是「老 server」的
|
|
466
|
+
* 同义词(两者今天同形,不猜)。
|
|
467
|
+
* 🔴 **与卡内那一位的在场契约刻意不同、别混**(server `ApprovalCardSchema` 顶注逐字):durable
|
|
468
|
+
* `ApprovalCard.risk.requiresRealApproval` 是**恒在布尔**(投影层已把缺席按 `=== true` 归一化),
|
|
469
|
+
* 而本键走帧顶层、只在为真时在场。⇒ 「帧上没有」与「卡上是 false」是两条不同的陈述,别互相推导。
|
|
470
|
+
* 🔴 **与 {@link governanceForced} 刻意分列不合并**:那个键答「门是谁下的」(运维治理层
|
|
471
|
+
* AUTONOMY/commandPolicy/守卫集),本键答「这门为什么掀不掉」(引擎侧安全类策略/hook 出身)。
|
|
472
|
+
* 合并会让任一方谎报出身 —— 而消费端对这两句话的正确反应不同:治理位该劝「别去改
|
|
473
|
+
* permissionMode」,本位该劝「这一次必须真人过目,记住的规则与 bypass 姿态都让位」。
|
|
474
|
+
* 🔴 本键**领先** SDK 运行期锚一代(sdk 7.1.0 的 `TOOL_APPROVAL_FRAME_KEYS` 尚无)⇒ 对账门
|
|
475
|
+
* (run-approval-frame-keys-test.mjs)AHEAD_OF_ANCHOR 带退出条件登记,#144 `persistedRuleShadowed`
|
|
476
|
+
* 与 #280 `probeCause` 同形先例:SDK 锚补上当天登记自红逼删。
|
|
477
|
+
* 耐久路今天**无对偶**(sdk 7.1.0 的 `PendingCheckpoint` / `riskDescriptor` 均未声明本键;server 侧
|
|
478
|
+
* 那份在**卡内** `risk` 子树,不是行上的键)⇒ durable 行 → 卡那条腿不 stamp,行为钉在
|
|
479
|
+
* run-durable-card-display-keys-test.mjs ⑨ 段;上游补位后按 {@link probeCause} 的双源合流形跟批。
|
|
480
|
+
*/
|
|
481
|
+
requiresRealApproval?: true;
|
|
436
482
|
}
|
|
437
483
|
/** {@link ToolApprovalFrame.delegation} 的形(命名形,不用内联匿名 —— typeshape 门 B4 棘轮口径)。 */
|
|
438
484
|
export interface ToolApprovalDelegation {
|
|
@@ -448,7 +494,7 @@ export interface ToolApprovalDelegation {
|
|
|
448
494
|
* `TOOL_APPROVAL_FRAME_KEYS` 比对——SDK additive 增键时对账当天红,不再人肉追平。
|
|
449
495
|
* 下面两个类型钉保证镜像与 interface 本身不可能漂移(少键/多键都是编译错)。
|
|
450
496
|
*/
|
|
451
|
-
export declare const TOOL_APPROVAL_FRAME_KEYS_MIRROR: readonly ["type", "approvalId", "toolCallId", "toolName", "sourceTaskId", "fromSubagent", "sourceAgentName", "message", "args", "argsOmitted", "governanceForced", "ruleSuggestions", "persistedRuleShadowed", "probeCause", "ruleEvidence", "delegation", "outcome"];
|
|
497
|
+
export declare const TOOL_APPROVAL_FRAME_KEYS_MIRROR: readonly ["type", "approvalId", "toolCallId", "toolName", "sourceTaskId", "fromSubagent", "sourceAgentName", "message", "args", "argsOmitted", "governanceForced", "ruleSuggestions", "persistedRuleShadowed", "probeCause", "ruleEvidence", "requiresRealApproval", "delegation", "outcome"];
|
|
452
498
|
/** 子代帧判别:显式键 fromSubagent(core 1.378 RB-39②)优先;缺席退 sourceTaskId 在场性权宜式
|
|
453
499
|
* (server 1.258 [1549]①3,旧代际兼容)。 */
|
|
454
500
|
export declare function isFromSubagent(frame: ToolApprovalFrame): boolean;
|
|
@@ -370,6 +370,11 @@ export const TOOL_APPROVAL_FRAME_KEYS_MIRROR = [
|
|
|
370
370
|
// ⚠️ 与上面两例**不同形**:sdk 7.1.0 的运行期锚**已经含**本键(node 直读实证)⇒ 这是一次**追平**,
|
|
371
371
|
// 不是领先,故**不**进 AHEAD_OF_ANCHOR(往那张表里塞一个锚已有的键,它的第二条退出条件当场红)。
|
|
372
372
|
'ruleEvidence',
|
|
373
|
+
// #283(client-core 0.36.0):server 7.33.0 起真发 `requiresRealApproval`(core 5.37 铸的安全类 ask
|
|
374
|
+
// 出身位,投到帧顶层)。⚠️ 与 `ruleEvidence` **不同形**、与 `persistedRuleShadowed`/`probeCause` 同形:
|
|
375
|
+
// sdk 7.1.0 的运行期锚**尚无**本键(node 直读实证:锚 17 项)⇒ 这是一次**领先**,进对账门的
|
|
376
|
+
// AHEAD_OF_ANCHOR 带退出条件登记(SDK 追平当天那条登记自红逼删,回到逐元素相等)。
|
|
377
|
+
'requiresRealApproval',
|
|
373
378
|
'delegation',
|
|
374
379
|
'outcome',
|
|
375
380
|
];
|
|
@@ -617,6 +622,11 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
|
|
|
617
622
|
// 🔴 呈现谓词归端(无治理部署下每只 ask 都带全具名缺席,[4050] live 实测 / [4051] 定性);
|
|
618
623
|
// 本层若替端做「全 not_wired 就别给了」的过滤,就是替引擎把一条真事实湮灭掉。
|
|
619
624
|
...(isWireRecordCarrier(frame.ruleEvidence) ? { ruleEvidence: frame.ruleEvidence } : {}),
|
|
625
|
+
// #283(0.36.0):安全类 ask 出身位透传([4390]③ 请托的正位解 —— 壳侧读器早已预埋,缺的一直是
|
|
626
|
+
// 包边界这一格)。条件 stamp **只认严格 true**,与 governanceForced 同一条纪律:缺席的语义是
|
|
627
|
+
// 「这不是一次安全类 ask」,把它折成显式 false 会让宿主把「没有证据」读成「已确认可自动放行」,
|
|
628
|
+
// 而本键存在的全部理由正是在这种门上把自动放行(记住的规则 / allow_session / bypass 姿态)拦下。
|
|
629
|
+
...(frame.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
620
630
|
});
|
|
621
631
|
const decision = card.kind === 'allow' ? (card.allowSession ? 'allow_session' : 'allow') : 'deny';
|
|
622
632
|
if (card.kind === 'failed') {
|
|
@@ -15,22 +15,23 @@
|
|
|
15
15
|
|
|
16
16
|
## §0 版本锚与重扫纪律
|
|
17
17
|
|
|
18
|
-
### 0a. 版本锚(2026-08-
|
|
18
|
+
### 0a. 版本锚(2026-08-18)
|
|
19
19
|
|
|
20
20
|
| 项 | 值 | 真源 |
|
|
21
21
|
|---|---|---|
|
|
22
|
-
| 本包 | `@sema-agent/client-core` **0.
|
|
23
|
-
| peer:wire 契约 | `@sema-agent/sdk` **>=
|
|
22
|
+
| 本包 | `@sema-agent/client-core` **0.36.0** | `package.json` `version` |
|
|
23
|
+
| peer:wire 契约 | `@sema-agent/sdk` **>=7.1.0**(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
|
+
| 公开导出面 | **749** 个运行期符号(+ 37 个测试钩) | `scripts/public-export-baseline.json` 的 `count` / `testHookCount` —— **别手抄进别处,以该文件为准** |
|
|
27
27
|
| 常驻门 | 以 `scripts/gates-manifest.json` 的 `suites` 长度为准(**本档不抄这个数**) | `scripts/gates-manifest.json`;`npm test` 的名单等值门与它逐名对账 |
|
|
28
28
|
| 沿革档 | 0.29.0 起建 `CHANGELOG.md`;更早批次记账在 `src/index.ts` 文件头 + `docs/REFACTOR-LEDGER.md` | — |
|
|
29
29
|
|
|
30
|
-
⚠️ **本表描述的是工作树(即将发布的 0.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
⚠️ **本表描述的是工作树(即将发布的 0.36.0),不是 npm 上那一版**:npm 上今天最新的是 `0.35.0`
|
|
31
|
+
(发布 commit `9e21c3f`),它**没有** 0.36.0 段里那两件 wire 过境
|
|
32
|
+
(`ToolApprovalFrame.requiresRealApproval` 帧键镜像 + 卡口透传 / `ActiveRunPendingGate.checkpointId`
|
|
33
|
+
与 `pendingGateIsProvablyDifferent`)。装着 npm `0.35.0` 的端**按 `CHANGELOG.md` 的 `## 0.35.0` 段
|
|
34
|
+
对表**,不要按本表 —— 本表的组合在 npm 上今天还不存在。
|
|
34
35
|
|
|
35
36
|
🔴 **本表里仍然手抄的数字都有门看着**(#252,2026-08-14):`scripts/run-integration-doc-freshness-test.mjs`
|
|
36
37
|
① 段把 638 / 32 / §2b 十六域名数之和 / 191 / 4 / 33 逐个对 `public-export-baseline.json` 算出来的值,
|
|
@@ -99,7 +100,7 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
|
|
|
99
100
|
|
|
100
101
|
## §2 公共导出面地图(按域)
|
|
101
102
|
|
|
102
|
-
> 全集真源 = `scripts/public-export-baseline.json` 的 `names`(**
|
|
103
|
+
> 全集真源 = `scripts/public-export-baseline.json` 的 `names`(**749** 项)。
|
|
103
104
|
> 本节**不逐名抄**,只给「域 → 承重导出 → 用途 → 实现锚」。承重导出 = 一个端为了让这个域干活
|
|
104
105
|
> **必须**直接调到的那几个符号;其余是它们的类型、变体与辅助位。
|
|
105
106
|
> 单一入口:`import { … } from '@sema-agent/client-core'`(`exports` 只有 `.` 一个;
|
|
@@ -109,7 +110,7 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
|
|
|
109
110
|
|
|
110
111
|
`public-export-baseline.json` 由 **`dist/index.js` 的运行期导出**生成(生成口径自述见
|
|
111
112
|
`scripts/run-client-core-typeshape-test.mjs`,双向精确集合门在 `scripts/run-public-surface-test.mjs`)。
|
|
112
|
-
实测:
|
|
113
|
+
实测:749 项 **100% 是运行期导出,零 type-only**。
|
|
113
114
|
|
|
114
115
|
**推论(端必须知道)**:
|
|
115
116
|
- barrel 导出的**类型**面比 707 大得多,且**不被这道门看守** —— `AdapterContext` / `SeamEvent` /
|
|
@@ -118,12 +119,12 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
|
|
|
118
119
|
端依赖这些类型是合法的,但**不要**拿基线 diff 当"类型面没变"的证据。
|
|
119
120
|
- `src/agentSession/contract.ts` 对基线贡献 **0** 项(纯类型模块,`export *` 在 dist 里是空转发)。
|
|
120
121
|
|
|
121
|
-
|
|
122
|
+
749 项的内部构成(帮助端估读表大小):**221** 项是 `SCREAMING_SNAKE` 常量数据表/词汇表
|
|
122
123
|
(矩阵、键集、env 名、锚串)而非可调用物;**4** 项是 PascalCase 运行期值
|
|
123
124
|
(`ControlRouter` / `ControlSafetyError` / `HitlBridge` / `HitlSafetyError`);
|
|
124
125
|
**39** 项是 `*For(sessionKey, …)` 的 per-session 变体(§6;其中 `engineNamespaceKeyFor` 是命名巧合 —— 参数是 baseUrl 不是 sessionKey,见域 14)。
|
|
125
126
|
|
|
126
|
-
### 2b. 域图(16 域,逐域计数之和 =
|
|
127
|
+
### 2b. 域图(16 域,逐域计数之和 = 749)
|
|
127
128
|
|
|
128
129
|
| # | 域 | 名数 | 承重导出 | 用途 | 实现锚 |
|
|
129
130
|
|---|---|---|---|---|---|
|
|
@@ -141,7 +142,7 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
|
|
|
141
142
|
| 12 | **workflow 与后台工作视图** | 19 | `projectWorkflowRun` · `createLiveWorkflowSource` · `ensureWorkflowActivityLedger` · `readWorkflowActivityLedger` · `stopWorkflowActivityLedger` · `resetWorkflowActivityLedgers` · `createBackgroundView` · `projectBackgroundView` · `recordWorkflowAgentTaskId` · `agentDisplayStatus` | 活过一个 turn 的长任务读面:workflow run + 跨 session 后台任务归一表(`assistant.tasks` 与 fleet SSE **两源独立降级**) | `src/workflow.ts`、`src/workflowClient.ts`、`src/workflowMonitor.ts`、`src/agentSession/backgroundView.ts`(+ 纯类型 `src/agentSession/contract.ts`) |
|
|
142
143
|
| 13 | **座位 IPC 契约** | 33 | `LOCAL_SESSIONS_SPEC` · `SEAT_METHOD_NAMES` · `SEAT_EVENT_TYPES` · `isLocalSessionEvent` · `isToolPermissionRequest` · `toolPermissionRequestId` · `SEAT_VALIDATOR_KEY_COVERAGE` | desktop↔web 座位 IPC 契约的**单一真源**(此前两边各一份、名字零重合 ⇒ 编译器永远不会告诉你它们漂了)。🔴 加 verb 忘了加 `LOCAL_SESSIONS_SPEC` **不报错**:preload 不注册 channel、渲染端读到 `undefined` | `src/seatContract.ts`(**零 import**,纯类型 + 常量 + 纯谓词) |
|
|
143
144
|
| 14 | **宿主端口与会话槽** | 26 | `installHost` · `installHostFor` · `hostPortMisses(For)` · `DEFAULT_SESSION_KEY` · `hostEnv` · `unrefTimer` · `parseLocaleTag` / `pickUiLanguage`(#244 F4 族D A-028.20:locale tag 手术单源 + UI 语言判定;与 `resolveRegionHint` 双出口成文 —— 语言偏好域 en/zh ≠ 地址可达域 cn/intl/unknown,`zh-Hant` 前者 zh 后者 intl 是设计)· `engineNamespaceKeyFor` / `mergeSessionMapRecord` / `mergeEngineEntry`(A-028.12:会话 id 映射单一键形 + merge 判定;存储经 `SessionMapStorePort` 归端 —— cli 文件锁/原子写,web localStorage)| 进程/端级装配层(settings/fs/queue/timers/session/log/probe),与 per-turn 的 `AdapterContext` **分层**。头注的判定规则:**这个能力每 turn 都会变吗?** 会 ⇒ `ctx`;不会 ⇒ `installHost` | `src/host.ts`、`src/hostEnv.ts`、`src/sessionSlot.ts`、`src/unrefTimer.ts`、`src/env/{localeGeo,localeTag,uiLanguage}.ts`、`src/sessionMap.ts` |
|
|
144
|
-
| 15 | **控制面与传输** |
|
|
145
|
+
| 15 | **控制面与传输** | 70 | `ControlRouter`(+ `ControlSafetyError`)· `makeEngineWireClient` + `resolveWireAuth` · `installEngineWireTarget` · `diagnoseSseIdleTear` / `isSseIdleError` · `attemptActiveRunSelfHeal` + `activeRunBusySignal` + `activeRunSelfHealRow` / `activeRunBusyHeadlessRow` · `kickEngineCapsProbe` / `engineCapTrue` · `mapBrainStatusToRetry` · `waitForClaimRelease` + `CLAIM_RELEASED_STATES` / `CLAIM_HELD_STATES` · `atMostOnceFailureClass` / `readSteerDelivery` · `INTERACTIVE_WAY_OUT`(默认出路串单源)· `normalizeWirePrincipal`(A-028.10:principal 在场性 trim 原语 —— 全空白=缺席不发头,engineWireTarget 两臂/makeEngineWireClient/壳 livePrincipal 同尺)· `classifyTurnWireError` / `isWireTransportError` / `isPreStreamDrainingReject` / `isResumeAtRejection` / `drainingRetryDelayMs` / `scenarioDenyFromError` + `WIRE_NETWORK_ERROR_PATTERN`(A-028.11/.13:turn 错误分型判定半场,人话文案与渲染归端) | 上行通道的**监管**半场(submit / steer / kill / 队列命令定序)+ 传输构造、caps 探测、SSE 断流分诊、**409 active-run 自愈** | `src/controlRouter.ts`、`steering.ts`、`sseIdleTriage.ts`、`retryStatus.ts`、`diagnostics.ts`、`engineWireSdk.ts`、`engineWireTarget.ts`、`src/principalWire.ts`、`src/wireErrorTriage.ts`、`engineSessionParam.ts`、`engineCapsCache.ts`、`liveInitToolFace.ts`、`adapter/activeRunSelfHeal.ts` |
|
|
145
146
|
| 16 | **引擎词汇表与包自检** | 41 | `CONFIG_REFUSAL_CODES` / `isConfigRefusalCode` · `STOP_CONFLICT_CODES` · `isInterruptedToolEndCode` · `isRewindFamilyCode` · `CLIENT_VERBS` · `compensationSplitViolations` | 三端分臂共用的**去字面化** `errorCode` 词表(病根正是三端各抄一份字面);编译期 verb 闭合门;搬迁补偿登记表 | `src/engineErrorCodes.ts`(34 项;A-028.11/.13 补 `DRAINING_ERROR_CODE`/`SCENARIO_NOT_ALLOWED_ERROR_CODE`/`RESUME_AT_ERROR_CODE_PREFIX`)、`src/classifierVerdictWire.ts`、`src/compensations.ts`、`src/clientSlice.ts` |
|
|
146
147
|
|
|
147
148
|
🔴 **`engineErrorCodes` 的开集纪律**(该文件头注逐字):这些 `ReadonlySet` / 前缀谓词一律是**识别表**,
|
|
@@ -369,7 +370,7 @@ durable park 腿走 `HitlBridge.decideTool(outcome, toolUseID, opts, preResolved
|
|
|
369
370
|
| **D-1 两元组 verbatim 回显** | `boundCallId` + `boundInputHash` 逐字回显进 `decide`,**绝不本地重算 hash**(`bindingOf`)。409 ⇒ `HitlSafetyError('binding_mismatch')`,调用方**重新呈现,绝不自动重试**(一次 decide 绝不双act)。pure 门 B7 段对这两段做**字节级**断言,改一个字符就红 |
|
|
370
371
|
| `reason` 上限 | `MAX_DENY_REASON_CHARS = 4096`;超限 server **413 `reason_too_large`**,丢的不是归因而是**整次决断**(413 ⇒ 决断没送达 ⇒ run 留 suspended)。包内 `denyReasonForWire(reason, tag)` 截断 + 留痕 |
|
|
371
372
|
| 缺省拒因 | `DEFAULT_DENY_REASON = 'The user rejected this tool use'`(不带归因时逐字不变) |
|
|
372
|
-
| **cancel
|
|
373
|
+
| **cancel vs deny(两动词,按意图选)** | 🔴 **按「你要停的是哪一样」选,不是按 run 的状态选**。`deny`(本节两条决断腿)= 否掉**这一道门**:决断送达后 run **继续**,模型拿到一条拒绝继续跑 —— 这是「不许它做这件事」。`runs.cancel` = 终结**整条 run**:server [868] 起对 `suspended` / `needs_review` 的 run **就地取消**(把待决 checkpoint 结清 + 行终态化,ack 带 `errorCode:"cancelled"`),这是「别跑了」。⚠️ **本行 0.36.0 前的原文是失真的**(SDK 7.1.0 已标 stale):它写的是「取消 suspended run 必须用 deny,绝不 `runs.cancel`(对 suspended run 会 409)」—— 照那句做会把「停」实现成「放它接着跑」(deny 只关掉一道门,run 照跑)。`cancel` 的 409 **今天只剩 lost CAS race**(待决门被并发决掉/过期 ⇒ 重读再试),**不再**是「suspended 一律 409」。判据锚 = 装机 SDK `dist/resources/runs.d.ts` 的 `cancel` JSDoc(常驻门 ⑦ 段对账,上游改说法当天红) |
|
|
373
374
|
| 空作答 fail-loud | `answerQuestion` 三形一律抛 `HitlSafetyError('empty_answer')`、**一次 decide 都不发**:空 `answers[]` / 任一条 `selected[]` 为空 / 任一条 `header` 为空串。理由:`{answers:[]}` 在 wire 上另有确切含义(question 域 deny 的 NO_HUMAN 形),当 approve 发出去 = **拿 deny 的载荷冒充 approve** |
|
|
374
375
|
| **回执** | ⚠️ `decideTool` / `answerQuestion` 的返回型是 **`Promise<unknown>`** —— 本包**不结构化读** durable `/decide` 的响应体,**没有** 4a 那样的 ack 消费层。见 §7b 缺口 **P-7**(不是 P-6:P-6 是 `compaction_outcome`) |
|
|
375
376
|
| TOCTOU | 调用方若已经用 `findPendingForTask` 取过 pending 行(呈卡用的那一行),**必须**经 `preResolvedPending` 传进来 —— 否则本方法自己再 `approvals.list()` 一次,两次独立取数可能落在**不同的行**上(「人看到的行」≠「decide 解析的行」) |
|
|
@@ -700,6 +701,7 @@ CHANGELOG 0.29.0「已知局限」段与相应 JSDoc 都有成文。**别在读
|
|
|
700
701
|
| **P-4** | med | **`approval_revoke` 在 SDK union 里连成员都没有**(SDK 顶注:known asymmetry,`Registering it is an open item for the next batch`)⇒ 本包不可能有 case ⇒ 运行期落 `dropped('unknown_arm')`;审批链也看不见它(`isToolApprovalFrame` 只认两帧)。全仓 `grep -rn "revoke"` = **0** | `src/adapter/downstream/eventToSdkMessage.ts` 的 `default` 臂;`src/hitl/toolApprovalWire.ts` 的 `isToolApprovalFrame` | 🔴 **引擎撤卡时本包不会替你撤那张卡** —— 被撤的 ask 会一直留在屏上,直到它自己的 5 分钟 TTL / deny 路径触发。要 revoke 语义的端只能自己接 raw named-SSE 腿并撤自己的卡(`unknown_arm` 的 drop 至少留了一行痕) |
|
|
701
702
|
| **P-5** | 真缺口(未定级) | **durable 重放腿上,一条被重放的 `question` 今天不会再打开覆盖层** —— 覆盖层的入口是 `liveQuestionStore` 的 **live demux 写口**,不是投影函数。交互 REPL 无损,「断线后按 `Last-Event-ID` 续读」场景下是真缺口。补它属**行为面**改动(先要答「重放一条已过 5min TTL 的问题该不该弹窗」),按宪法三问单独走 | `src/adapter/downstream/eventToSdkMessage.ts` 的 `question`/`question_complete`/`elicitation`/`elicitation_complete` 臂(缺口逐字记在该处);`src/liveQuestionStore.ts` 头注(`LIVE-ONLY + SAME-REPLICA … No durable resume anchor`) | `respondToQuestion` 当 best-effort 用(404 = 「已经放掉了」,dismiss,**绝不重试**);重连后的恢复走 **409 自愈树 + 自己的 `/v1/approvals` 列举**,**不要**指望重放的 `question` 帧能弹出覆盖层 |
|
|
702
703
|
| **P-6** | low | `compaction_outcome`(压缩**非成功结局**:mooted/failed)在本切片 `not_in_slice`;「压缩失败让用户看见」是 chrome/HUD 面的活,**今天两端都还没接**(adapt 臂表同样无此臂) | `src/adapter/downstream/eventToSdkMessage.ts` 的 `case 'compaction_outcome'` | 压缩失败对用户**不可见**。🔴 **不许**由投影切片顺手编一个假 transcript 形来假装接上了 |
|
|
704
|
+
| **P-32** | 在册局限(0.36.0 两 wire 键过境后的**辖域**,非缺陷) @cli @web @desktop | **两键各只有一条腿,别按「两面能对上」写码**。① **`requiresRealApproval`**(#283):只在**活卡帧**腿(`ToolApprovalFrame` → `ApprovalCardRequest`);**耐久腿零 stamp** —— sdk 7.1.0 的 `PendingCheckpoint` / `riskDescriptor` 都没有这一位(server 那份在**卡内** `risk` 子树,不是行上的键),包侧刻意不猜载体名。② **`checkpointId`**(#285 件2):只在 **409 `conflict.session_active_run` 体 + 其 SSE done 帧**的 `pendingGate`;另外三条 park 读面(`/v1/assistant/inbox`、`GET /v1/approvals`、`/v1/approvals/stream`)**都没有**这一格(后两条要给 checkpoint 表反范式一列 = SQL 面双库门,属另一批) | `src/hitl/toolApprovalWire.ts`(帧腿 stamp / durable 腿刻意零 stamp,两处 JSDoc 逐条写明理由)· `src/adapter/runStream.ts`(`ActiveRunPendingGate.checkpointId` + `pendingGateIsProvablyDifferent`)· 行为钉:`run-durable-card-display-keys-test.mjs` ⑨ 段(四种最像的载体名摆在行上,卡入参一个都不许长出来)· `run-selfheal-reopen-test.mjs` G1/G1b | ① **`requiresRealApproval` 在场 ⇒ 一切自动放行让位**(记住的规则 / `allow_session` / bypass 姿态),**缺席绝不读成 `false`**(缺席 = 不是安全类 ask **或** 老 server,两者同形不猜);durable 卡上今天**恒缺席**,别据此认为「durable 门都不是安全类」。② **`checkpointId` 只做两件事**:落日志/排障关联,以及经 `pendingGateIsProvablyDifferent(prev, next)` 做**单向**判断「id 变了 ⇒ 不是刚才那一行」。🔴 **`false` 是「证不出」不是「同一道门」**,🔴 **绝不**当跨调用去重键(server 选行是**无序 `LIMIT 1`**、同 session 可并存多条 pending 行 ⇒ 同一情形连续两次 409 可能报不同 id;也可能 id 不变而 `activeTaskId` 已换),🔴 **绝不**拿它去 join durable 队列行(必然落空,且落空与 legacy 行同形)。缺席是**三成因合流**(legacy 行 / 值没过 server 信任边界校验 / 整只材料读取失败),读作「不知道这道门叫什么」 |
|
|
703
705
|
|
|
704
706
|
### 7b. 决断链缺口
|
|
705
707
|
|
|
@@ -843,7 +845,7 @@ reason 里写明「枚举器盲区形」。已知两形:
|
|
|
843
845
|
- [ ] 接审批帧腿时**填 `AskGateWireDeps.approvalLane`**(缺席 = `note` 恒不发,fail-closed 且不报错)
|
|
844
846
|
- [ ] ack 五位按 §4a 三列表消费:**缺席一律当未知**,`rememberApplied === false` 与 `updatedInputForwarded === false` 必须响亮告知
|
|
845
847
|
- [ ] durable 腿:`decideTool` 前把**呈卡用的那一行** pending 经 `preResolvedPending` 传进去(TOCTOU)
|
|
846
|
-
- [ ] durable
|
|
848
|
+
- [ ] durable 腿:**按意图**选动词 —— 否掉这一道门用 **deny**(run 继续),终结整条 run 用 **`runs.cancel`**(server [868] 起对 suspended/needs_review 就地取消;409 只剩 CAS race)。⚠️ 0.36.0 修:原行写的「取消 suspended 必须用 deny、绝不 cancel」已被 SDK 7.1.0 标 stale,详见 §4 「cancel vs deny」行
|
|
847
849
|
- [ ] `HitlSafetyError` 按 **`.code` 结构化判型**,`instanceof` 只作加强(跨 realm / 双实例)
|
|
848
850
|
- [ ] `ReopenCardVerdict.presented`:要用这个位就自己在呈现层实现回执;不实现就**缺席**(缺席不降级),**别填 `false` 当占位**
|
|
849
851
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/client-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.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",
|