@sema-agent/client-core 0.57.0 → 0.58.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.
@@ -0,0 +1,115 @@
1
+ /**
2
+ * src/resumeRefusalCopy.ts — resume 族**拒绝文案的三端单一铸点**(L-102 下半场,0.58.0)。
3
+ *
4
+ * ── 这一件上收的是什么 ──────────────────────────────────────────────────────────────────────
5
+ * 0.57.0 已经把 L-102 的**事实读数**上收了({@link resumeRetryLaterFromError}:命中哪个码、
6
+ * server 给没给窗、等一会儿有没有用)。留在壳里的另一半是**人话** —— cli 1.0.x 的
7
+ * `src/sema/resumeRefusalCopy.ts` 自铸了一份判型 + 三句 `·` 分段文案,而 web-client / desktop
8
+ * 各自要么没有、要么将来会再抄一份。三端各抄一份文案 = 同一次拒绝在三个端上说三句不一样的话,
9
+ * 而这三句话回答的是**同一个安全问题**:「这次拒绝有没有消费掉我的决定 / 这张卡还能不能再决」。
10
+ * ⇒ 文案与判型同属**判定归包、呈现归端**里的「判定」那一半,本模块是它的单一铸点。
11
+ *
12
+ * ── 与 0.57.0 §21 那一口的分工(两个闭集刻意分家)──────────────────────────────────────────
13
+ * · {@link RESUME_RETRY_LATER_CODES}(engineErrorCodes.ts,0.57.0)闭的是「**server 在哪些码上铸
14
+ * `retryAfterSec`**」——它回答**能不能等**;
15
+ * · {@link RESUME_REFUSAL_CODES}(本文件)闭的是「**哪些 resume 拒绝有人话可补**」——它回答
16
+ * **该对人说什么**。
17
+ * 两集**交于** `resume.preflight_rejected` 一码、**各有**一个独占成员
18
+ * (`resume.usage_window_exhausted` 只在前者 / `resume.placement_mismatch` 只在后者),所以它们
19
+ * 不是同一张表的两个名字。🔴 **本模块不复制判定**:`preflight_rejected` 的窗与可等性一律**转调**
20
+ * `resumeRetryLaterFromError`,本文件里没有第二个 `retryAfterSec` 窄读器
21
+ * ([paired-mechanisms-must-share-premise]:两处各自判必然在某一格上分叉)。
22
+ *
23
+ * ── `retryAfterSec` 的窄读域比 cli 1.0.x 现行的更窄(端提货时要知道的差分)──────────────────
24
+ * cli 那份自铸读器收的是「有限数 ∧ ≥0」;本口沿用 §21 的域 =「**整数 ∧ ≥1**」,坏值一律降缺席:
25
+ * · server 的铸键逐字是「ms → 秒**向上取整**、**下限 1**」⇒ 真供给里不存在 0 / 负数 / 小数;
26
+ * · 放行 `0` 就是对端说「立刻重试」,而 resume 是 AT-MOST-ONCE 的**有副作用**动作(叫醒 = 真跑
27
+ * 一轮)—— 一个 0 会把「等一会儿」变成热循环;
28
+ * · 放行小数会让端渲出「等 0.4 秒」这种上游从未说过的量。
29
+ * ⇒ 壳提货后在这三形上的**行为会变**(从「渲一个 0 秒等待」变成「不渲等待行」),这是**修好**
30
+ * 不是回归([verdict-must-accept-stronger-form])。
31
+ *
32
+ * ── 开集纪律(与 cli 那份逐字同律)────────────────────────────────────────────────────────
33
+ * 🔴 判型是**闭集**的(只认下面两个码),消费面是**开集**的:不认识的码一律 `null` ⇒ 端一个字
34
+ * 都不补 ⇒ 机器可读那一行(`API Error: <status> <errorCode> · <msg>`)原样上屏。
35
+ * **绝不按前缀/子串猜一族码的语义** —— `resume.` 前缀下同时住着「等就好」「换参数」「没救了」
36
+ * 三种处置。
37
+ * 🔴 文案**不复述** server 的 message(那一行已经在屏上了),只补 message 里没有的那件事。
38
+ * 🔴 文案里**不铸等待的秒数**:那一行由端用 {@link ResumeRefusalDetail.retryAfterSec} 单独渲
39
+ * (cli 的 `retryAfterHint` 是那一位的唯一取值口),在这里再说一遍就是同一个数字上屏两遍。
40
+ */
41
+ import { RESUME_PLACEMENT_MISMATCH, RESUME_PREFLIGHT_REJECTED, } from './engineErrorCodes.js';
42
+ import { resumeRetryLaterFromError } from './wireErrorTriage.js';
43
+ /**
44
+ * 本模块认识的 resume 拒绝码(**闭集**)—— 「有人话可补」的那两个。
45
+ *
46
+ * 🔴 **冻结数组,不是 `ReadonlySet`**(与 {@link RESUME_RETRY_LATER_CODES} 同一条已定谳的病形):
47
+ * `ReadonlySet` 只在类型面只读,运行期是一只普通 `Set`,而判定查的就是这个实例 —— 公面上任何
48
+ * JS 消费者一次 `.add('resume.evicted')` 就能让一条「重开吧」的拒绝挂上「你的决定没被消费」
49
+ * 这句安全声明。冻结数组在运行期真的改不动(ESM 恒 strict:`push` / 下标赋值直接抛)。
50
+ * 🔴 新增成员必须**同批**补文案 + 判据,不许靠前缀放宽。
51
+ */
52
+ export const RESUME_REFUSAL_CODES = Object.freeze([
53
+ RESUME_PLACEMENT_MISMATCH,
54
+ RESUME_PREFLIGHT_REJECTED,
55
+ ]);
56
+ /**
57
+ * 被 catch 的错误 → resume 拒绝读数;不是这两个码 ⇒ `null`(绝不误吃别的 4xx)。
58
+ *
59
+ * 🔴 **duck-typed 不 `instanceof`**:三端注入面可能是宿主的裸 fetch、desktop 的 IPC 转投、web 跨
60
+ * bundle 的 plain object(跨 bundle 同名类是两个类),这些形上 `instanceof` 一律为假而
61
+ * `errorCode` 恒在。键位**只认 `errorCode`**([2055] 死键纪律,退役 `code` 槽不做兼容)。
62
+ * 🔴 **判据只有 `errorCode`,不看 HTTP 数字**(与 §21 同口径):`resume.*` 那一族成员全是 409,
63
+ * 按数字分支等于把几种不同处置压成一种。
64
+ * 🔴 **`preflight_rejected` 的窗与可等性全部转调 §21 那一口**,本文件不复制判定。
65
+ */
66
+ export function resumeRefusalFromError(err) {
67
+ if (typeof err !== 'object' || err === null)
68
+ return null;
69
+ const code = err.errorCode;
70
+ if (typeof code !== 'string' || !RESUME_REFUSAL_CODES.includes(code))
71
+ return null;
72
+ if (code === RESUME_PLACEMENT_MISMATCH) {
73
+ // 时间性闭集里没有本码,也不该有(见该码顶注):无窗、且等一会儿对它毫无用处。
74
+ return { code, waitable: false };
75
+ }
76
+ // `resume.preflight_rejected` —— 两集的交点,事实读数**只有一个来源**。
77
+ const timed = resumeRetryLaterFromError(err);
78
+ // 🔴 `null` 在今天不可达(本码同在两个闭集里,交点由门双向钉死)。真出现 = 上游把本码移出了
79
+ // 时间性闭集 ⇒ 「有没有窗」这件事**本包不再知道**,按诚实缺席读作不可判,绝不自己再判一次。
80
+ if (timed === null)
81
+ return { code, waitable: false };
82
+ return {
83
+ code,
84
+ waitable: timed.waitable,
85
+ ...(timed.retryAfterSec !== undefined ? { retryAfterSec: timed.retryAfterSec } : {}),
86
+ };
87
+ }
88
+ /**
89
+ * 结构化读数 → 人话补充行(`·` 分段,与端上 quota / scenario 两个兄弟同形)。
90
+ *
91
+ * 三句的分工固定:①**发生了什么**(用户看得懂的那句)· ②**这次拒绝有没有消费掉你的决定**
92
+ * (安全声明,两条臂都成立时才敢说)· ③**接下来怎么办 / 还能不能再决**。
93
+ *
94
+ * 🔴 第三句由 {@link ResumeRefusalDetail.waitable} 选,**不由 message 文本选**:按上游散文分支
95
+ * 正是「上游改一个词、壳的判定静默空转」的形(wire 锚契约门要挡的就是它)。
96
+ * 🔴 `preflight_rejected` 且 `waitable === false` 时说的是**两条臂都为真**的那句(cli 1.0.x 的现行
97
+ * 文案逐字),把「还能不能再赎」交给屏上那行引擎原文;`waitable === true` 时 server 真给了窗
98
+ * ——那是 `retry_later` 臂的**充分证据**(core 的 `terminal` 臂抛的 `CheckpointError` 一个
99
+ * detail 都不带,窗结构上到不了客户端)⇒ 可以放心说「障碍清掉之后这个 token 还能再赎」。
100
+ */
101
+ export function resumeRefusalContent(detail) {
102
+ const parts = [];
103
+ if (detail.code === RESUME_PLACEMENT_MISMATCH) {
104
+ parts.push('This resume named a different placement root than the one recorded when the work was parked');
105
+ parts.push('The engine refused it before consuming anything — the parked work is untouched');
106
+ parts.push('Retry with the recorded root, or omit the placement root to inherit it');
107
+ return parts.join(' · ');
108
+ }
109
+ parts.push("The deployment's resume preflight refused this attempt");
110
+ parts.push('The refusal happened before the commit — your decision was not consumed');
111
+ parts.push(detail.waitable
112
+ ? 'The engine supplied a wait, so this token stays redeemable once the obstacle clears'
113
+ : 'The engine line above says whether this token can still be redeemed');
114
+ return parts.join(' · ');
115
+ }
@@ -15,15 +15,15 @@
15
15
 
16
16
  ## §0 版本锚与重扫纪律
17
17
 
18
- ### 0a. 版本锚(2026-09-05)
18
+ ### 0a. 版本锚(2026-09-06)
19
19
 
20
20
  | 项 | 值 | 真源 |
21
21
  |---|---|---|
22
22
  | 本包 | `@sema-agent/client-core` **0.55.0**(工作树**未发**;npm 最新 = **0.54.0**。B-003 `waiting_first_token` 那一批进 `CHANGELOG.md` 的 `## 0.55.0(2026-09-05)` 段,冻结账已按两阶段协议插 `pending` 行) | `package.json` `version` |
23
- | peer:wire 契约 | `@sema-agent/sdk` **>=7.4.0**(value-level,非 type-only;0.48.0 抬版,四条硬理由见 `CHANGELOG.md` 0.48.0 段末的地板影响面账) | `package.json` `peerDependencies` |
23
+ | peer:wire 契约 | `@sema-agent/sdk` **>=8.2.0**(value-level,非 type-only;**0.58.0 抬版**,四条硬理由见 §22a 与 `scripts/run-sdk-floor-test.mjs` 的 `FLOOR` 注;上一次是 0.48.0 的 `>=7.4.0`) | `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
- | 公开导出面 | **821** 个运行期符号(+ 41 个测试钩;= 工作树当下的值 = 0.57.0 内容批 D 三件 additive;`0.56.0`/`0.55.0` 是 **815**;已发 `0.54.0`(design/385 十件 additive 含 `AUTHORITY_ENVELOPE_TAGS`);`0.52.0` 是 **805**(L-69⑨ 两件);`0.51.0` 是 **803**;`0.50.0` 是 **800**(S-81 五件);`0.49.0` 是 **795**;`0.48.0` 是 **794**;npm `0.47.0` 是 **790**,`0.46.0` 是 **787**,`0.44.0` 是 **783**,`0.43.1`/`0.43.0` 是 **776**,`0.42.0` 是 **771**,`0.41.0` 是 **767**,`0.39.0` 是 **766**,`0.38.0` 是 **764**;`0.37.0` 是 **753**,见 `CHANGELOG.md`) | `scripts/public-export-baseline.json` 的 `count` / `testHookCount` —— **别手抄进别处,以该文件为准** |
26
+ | 公开导出面 | **828** 个运行期符号(+ 41 个测试钩;= 工作树当下的值 = 0.58.0 三件 additive:`RULE_OFFER_MATCHES`/`RULE_OFFER_BATCH_MEMBER_KINDS`/`RULE_OFFER_UNCOVERED_REASONS` 三张闭词表再导出 + `RESUME_REFUSAL_CODES`/`RESUME_PLACEMENT_MISMATCH`/`resumeRefusalFromError`/`resumeRefusalContent` 四件文案铸点;`0.57.0` 是 **821**;`0.56.0`/`0.55.0` 是 **815**;已发 `0.54.0`(design/385 十件 additive 含 `AUTHORITY_ENVELOPE_TAGS`);`0.52.0` 是 **805**(L-69⑨ 两件);`0.51.0` 是 **803**;`0.50.0` 是 **800**(S-81 五件);`0.49.0` 是 **795**;`0.48.0` 是 **794**;npm `0.47.0` 是 **790**,`0.46.0` 是 **787**,`0.44.0` 是 **783**,`0.43.1`/`0.43.0` 是 **776**,`0.42.0` 是 **771**,`0.41.0` 是 **767**,`0.39.0` 是 **766**,`0.38.0` 是 **764**;`0.37.0` 是 **753**,见 `CHANGELOG.md`) | `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
 
@@ -111,7 +111,7 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
111
111
 
112
112
  ## §2 公共导出面地图(按域)
113
113
 
114
- > 全集真源 = `scripts/public-export-baseline.json` 的 `names`(**821** 项)。
114
+ > 全集真源 = `scripts/public-export-baseline.json` 的 `names`(**828** 项)。
115
115
  > 本节**不逐名抄**,只给「域 → 承重导出 → 用途 → 实现锚」。承重导出 = 一个端为了让这个域干活
116
116
  > **必须**直接调到的那几个符号;其余是它们的类型、变体与辅助位。
117
117
  > 单一入口:`import { … } from '@sema-agent/client-core'`(`exports` 只有 `.` 一个;
@@ -135,18 +135,18 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
135
135
  `WorkflowsGateUnknownDenial` 四形**不在**基线里,`src/selfOrchestrationDenial.ts` 对基线贡献
136
136
  **4** 项运行期导出(三个函数 + `SELF_ORCHESTRATION_RETRY_WITHOUT`)。
137
137
 
138
- 821 项的内部构成(帮助端估读表大小):**247** 项是 `SCREAMING_SNAKE` 常量数据表/词汇表
138
+ 828 项的内部构成(帮助端估读表大小):**252** 项是 `SCREAMING_SNAKE` 常量数据表/词汇表
139
139
  (矩阵、键集、env 名、锚串)而非可调用物;**5** 项是 PascalCase 运行期值
140
140
  (`ControlRouter` / `ControlSafetyError` / `HitlBridge` / `HitlSafetyError` / `DecideTransportRetryExhaustedError`);
141
141
  **41** 项是 `*For(sessionKey, …)` 的 per-session 变体(§6;其中 `engineNamespaceKeyFor` 是命名巧合 —— 参数是 baseUrl 不是 sessionKey,见域 14)。
142
142
 
143
- ### 2b. 域图(16 域,逐域计数之和 = 821)
143
+ ### 2b. 域图(16 域,逐域计数之和 = 828)
144
144
 
145
145
  | # | 域 | 名数 | 承重导出 | 用途 | 实现锚 |
146
146
  |---|---|---|---|---|---|
147
147
  | 1 | **适配内核(下行主链)** | 33 | `adapt` · `createWireToCcAdapter` · `runStream` · `eventToSdkMessage` · `terminalToSdkResult` · `turnUsageToModelUsage` · `isRunStreamActive` · `ADAPTER_DIVERGENCES` | 引擎 SSE `AgentEvent` → 端要渲的**双面输出**:transcript(`SDKMessage`)+ chrome(瞬态 `ChromeEvent`)。**本包存在的理由** | `src/adapt.ts`、`src/adapt/{arms,wireShapes,panelTasks}.ts`(经 `adapt.ts` 再导出)、`src/adapter/runStream.ts`、`src/adapter/downstream/*`、`src/adapter/types.ts` |
148
148
  | 2 | **seam 公共契约** | 2(其余为 type-only) | `CHROME_ARMS` · `deriveTranscriptId` | 公共词汇 + **id 确定性不变量**(同一条流重放 ⇒ 同一串 id)。`CHROME_ARMS` = 端「我要消费哪些 chrome 臂」的对照清单 | `src/seam.ts` |
149
- | 3 | **HITL 决断卡链**(§4/§5 主战场) | 138 | `makeHitlCanUseTool` · `HitlBridge` · `findPendingForTask` · `HitlSafetyError` · `bridgeAskUserQuestionGates` · `surfaceToolApprovalFrameAndRespond` / `surfaceFsApprovalAndDecide` · `readToolApprovalRespondAck` · `installApprovalCardPort(For)` · `installHitlHostSurface(For)` · `armPlanReviewApproval` · `reopenPlanReviewCard` · `decidePlanReview` · `startApprovalsFeed` · `pendingRowIsOwnedByThisSession` · `approvalCallKey`/`liveFrameCallKey`/`planReviewQuestionId` · `registerArmedGateFor`/`wasGateArmedFor`/`clearArmedGateFor` · `waitForGateArmed(For)`/`onGateArmed(For)`/`gateArmedWaitMs`(#244 F1 呈现回执事件源) · `planReviewArmedKey(For)`/`notePlanReviewAnswered(For)`/`notePlanReviewAnsweredIfDecisive(For)`(A-024.4 plan 呈现分代) · `toolEndOutputText` · `isAskTool` · `waitForParkRowBirth` · `classifyAskParkRows` / `askParkRowArm` / `classifyAskParkChainFailure` · `readDecisionNoteAudit` / `decisionNoteAuditLine` · `resumeRunningOptions` / `resumeChoiceFromLabels` · `persistedRulesLaneAvailable`/`persistedRulesGovernanceAvailable` · `classifyRulesFailure` · `listAllPersistedRules` · `classifySkippedReason` · `readRulePersistOutcome`(#244 F2:persist-ack 读口与 `readToolApprovalRespondAck` 合成一处) · `parseLocalAllowRule`(durable 腿本地落规则窄化骨架,谓词经 `LocalAllowRuleDeps` 注入) · `readToolApprovalRespondRefusal`(#225 件5,0.42.0:respond 抛错的结构化原文读口 —— 三位各自防御读、各自缺席不铸、**三位皆缺席时整只返 `undefined`**;原样交还零加工,UNTRUSTED-for-display)· `installEditedRuleTextPrechecker` / `hasEditedRuleTextPrechecker` / `precheckEditedRuleText`([5076] 转出口,0.42.0:core 5.57.0 `precheckEditedRuleText` 的**端口注入形** —— 类型面 + 注入口 + 诚实缺席读口。🔴 **不是** value 级 re-export,理由见 §7 缺口 **P-34**;未装 ⇒ 返 `undefined`,绝不编一个 `{ok:true}`)· `surfaceRuleArmNotSent` / `RULE_NOT_SENT_WARN_TEXT` + `surfaceRuleArmRejected` / `RULE_NOT_SENT_REJECTED_WARN_TEXT`(#334,0.43.0:人在卡上按下的「不再询问」被整条丢弃时的诚实告知——**两条刻意分开**:前者=**引擎能力位未确认**(换台引擎/等探测就好),后者=**这次选择没过包内表核/互斥核**(表外文本/坏下标/两臂同场,换引擎也不会变) —— 编辑臂 `respondFreeFormRules` 与批臂 `respondBatchRuleOffers` 两条同形存量共用一条,与 `surfaceRememberNotApplied` 同族纪律:决断照送、只是规则没存,静默丢掉用户明确意图 = 让人以为功能坏了)· `projectCrashConverged`(L-38,0.49.0:`/v1/approvals` additive 键 `crashConverged` 的分桶投影 —— 崩溃收敛的孤儿审批读面,**缺席 ≠ 空数组**、分桶恰一个合取、坏行丢弃并计数,详见 §12;同批把 `ApprovalsResourceLike.list()` 的返回位 additive 放宽成 `ApprovalsListEnvelope`,老形 `{pending}` 仍可赋值)· `readRuleOffers` / `readRuleOfferSupply`(L-103/[C228],0.57.0 additive 导出:「不再询问」候选两代 wire 键的窄读器**公面化** —— 此前只经卡端口出包,不走卡端口架构的宿主只能自己重铸一把,而它承载的是兑付安全判据(原始下标不前移/逐条丢坏/闭集 kind/两代取舍序),详见 §20)· `DecideTransportRetryExhaustedError`(Inkglow-1085 P0a:decide 出站瞬断重试耗尽的 typed 判别 —— HitlBridge 内建单次退避重试,耗尽走重呈臂不判死 turn;端一般只消费行为,不需要 instanceof) | suspended→decide→resume 环。🔴 **D-1 两元组 verbatim 回显**是字节级断言的安全不变量,端**不许重实现它的任何一段**。🔴 键空间边界(web [C1] d3 拦截):`gateIdentity` 四常量两函数只覆盖 HITL questionId/callKey 空间;seat 的 `TOOL_PERMISSION_REQUEST_ID_DOMAINS`(`plan:` 等)是另一键空间,**两者绝不合并**(合并=座位校验器静默拒全部 plan-review 卡) | `src/hitl/hitlBridge.ts`、`toolApprovalWire.ts`、`askGateWire.ts`、`planReviewWire.ts`、`hitlHostSurface.ts`、`gateIdentity.ts`、`armedGateRegistry.ts`、`parkOwnership.ts`、`parkResolver.ts`、`approvalsFeed.ts`、`frameRouter.ts`(**只挑名导出** `toolEndOutputText`/`ENGINE_ABORT_TOOL_RESULT`/`isAskTool`/`HITL_REJECT_MESSAGE`/`HITL_INTERRUPT_MESSAGE_FOR_TOOL_USE`)、`parkRowBirthWait.ts`、`approvalDecisionNoteAudit.ts`、`askParkRowRouting.ts`、`resumeRunningCard.ts`(#265 上收的判定层)、`persistedRulesWire.ts`、`localAllowRule.ts`(#244 F2 规则侧)、`crashConverged.ts`(L-38 崩溃收敛读面) |
149
+ | 3 | **HITL 决断卡链**(§4/§5 主战场) | 141 | `makeHitlCanUseTool` · `HitlBridge` · `findPendingForTask` · `HitlSafetyError` · `bridgeAskUserQuestionGates` · `surfaceToolApprovalFrameAndRespond` / `surfaceFsApprovalAndDecide` · `readToolApprovalRespondAck` · `installApprovalCardPort(For)` · `installHitlHostSurface(For)` · `armPlanReviewApproval` · `reopenPlanReviewCard` · `decidePlanReview` · `startApprovalsFeed` · `pendingRowIsOwnedByThisSession` · `approvalCallKey`/`liveFrameCallKey`/`planReviewQuestionId` · `registerArmedGateFor`/`wasGateArmedFor`/`clearArmedGateFor` · `waitForGateArmed(For)`/`onGateArmed(For)`/`gateArmedWaitMs`(#244 F1 呈现回执事件源) · `planReviewArmedKey(For)`/`notePlanReviewAnswered(For)`/`notePlanReviewAnsweredIfDecisive(For)`(A-024.4 plan 呈现分代) · `toolEndOutputText` · `isAskTool` · `waitForParkRowBirth` · `classifyAskParkRows` / `askParkRowArm` / `classifyAskParkChainFailure` · `readDecisionNoteAudit` / `decisionNoteAuditLine` · `resumeRunningOptions` / `resumeChoiceFromLabels` · `persistedRulesLaneAvailable`/`persistedRulesGovernanceAvailable` · `classifyRulesFailure` · `listAllPersistedRules` · `classifySkippedReason` · `readRulePersistOutcome`(#244 F2:persist-ack 读口与 `readToolApprovalRespondAck` 合成一处) · `parseLocalAllowRule`(durable 腿本地落规则窄化骨架,谓词经 `LocalAllowRuleDeps` 注入) · `readToolApprovalRespondRefusal`(#225 件5,0.42.0:respond 抛错的结构化原文读口 —— 三位各自防御读、各自缺席不铸、**三位皆缺席时整只返 `undefined`**;原样交还零加工,UNTRUSTED-for-display)· `installEditedRuleTextPrechecker` / `hasEditedRuleTextPrechecker` / `precheckEditedRuleText`([5076] 转出口,0.42.0:core 5.57.0 `precheckEditedRuleText` 的**端口注入形** —— 类型面 + 注入口 + 诚实缺席读口。🔴 **不是** value 级 re-export,理由见 §7 缺口 **P-34**;未装 ⇒ 返 `undefined`,绝不编一个 `{ok:true}`)· `surfaceRuleArmNotSent` / `RULE_NOT_SENT_WARN_TEXT` + `surfaceRuleArmRejected` / `RULE_NOT_SENT_REJECTED_WARN_TEXT`(#334,0.43.0:人在卡上按下的「不再询问」被整条丢弃时的诚实告知——**两条刻意分开**:前者=**引擎能力位未确认**(换台引擎/等探测就好),后者=**这次选择没过包内表核/互斥核**(表外文本/坏下标/两臂同场,换引擎也不会变) —— 编辑臂 `respondFreeFormRules` 与批臂 `respondBatchRuleOffers` 两条同形存量共用一条,与 `surfaceRememberNotApplied` 同族纪律:决断照送、只是规则没存,静默丢掉用户明确意图 = 让人以为功能坏了)· `projectCrashConverged`(L-38,0.49.0:`/v1/approvals` additive 键 `crashConverged` 的分桶投影 —— 崩溃收敛的孤儿审批读面,**缺席 ≠ 空数组**、分桶恰一个合取、坏行丢弃并计数,详见 §12;同批把 `ApprovalsResourceLike.list()` 的返回位 additive 放宽成 `ApprovalsListEnvelope`,老形 `{pending}` 仍可赋值)· `readRuleOffers` / `readRuleOfferSupply`(L-103/[C228],0.57.0 additive 导出:「不再询问」候选两代 wire 键的窄读器**公面化** —— 此前只经卡端口出包,不走卡端口架构的宿主只能自己重铸一把,而它承载的是兑付安全判据(原始下标不前移/逐条丢坏/闭集 kind/两代取舍序),详见 §20)· `DecideTransportRetryExhaustedError`(Inkglow-1085 P0a:decide 出站瞬断重试耗尽的 typed 判别 —— HitlBridge 内建单次退避重试,耗尽走重呈臂不判死 turn;端一般只消费行为,不需要 instanceof) | suspended→decide→resume 环。🔴 **D-1 两元组 verbatim 回显**是字节级断言的安全不变量,端**不许重实现它的任何一段**。🔴 键空间边界(web [C1] d3 拦截):`gateIdentity` 四常量两函数只覆盖 HITL questionId/callKey 空间;seat 的 `TOOL_PERMISSION_REQUEST_ID_DOMAINS`(`plan:` 等)是另一键空间,**两者绝不合并**(合并=座位校验器静默拒全部 plan-review 卡) | `src/hitl/hitlBridge.ts`、`toolApprovalWire.ts`、`askGateWire.ts`、`planReviewWire.ts`、`hitlHostSurface.ts`、`gateIdentity.ts`、`armedGateRegistry.ts`、`parkOwnership.ts`、`parkResolver.ts`、`approvalsFeed.ts`、`frameRouter.ts`(**只挑名导出** `toolEndOutputText`/`ENGINE_ABORT_TOOL_RESULT`/`isAskTool`/`HITL_REJECT_MESSAGE`/`HITL_INTERRUPT_MESSAGE_FOR_TOOL_USE`)、`parkRowBirthWait.ts`、`approvalDecisionNoteAudit.ts`、`askParkRowRouting.ts`、`resumeRunningCard.ts`(#265 上收的判定层)、`persistedRulesWire.ts`、`localAllowRule.ts`(#244 F2 规则侧)、`crashConverged.ts`(L-38 崩溃收敛读面) |
150
150
  | 4 | **子代 wire + 面板侧信道台账** | 84 | `tailEngineSubagent` · `installSubagentActivitySink` · `installSubagentTailMetaSink`(#280 件2:tail meta 帧发布口,`contentFrames` 判别位载体)· `stopEngineTask` + `classifyTaskStopConflict` · `fetchEngineSubagentReport` · `steerEngineSubagent`(0.32.0 未发布 #280 件A:additive 第三参 `childTaskId` —— 端有行上下文时**应当**传,传了就走「台账优先 / 缺席即诚实缺席 + `noteBgOwnerAbsence` 留痕」的 Q3 口径,与 tail·taskOutput·subagentOutput 三腿同姿势、与孪生 resume 腿共用同一个 `resolveOwnerRunId` 判据;**不传**则逐字维持旧行为=回落在飞 run)· `resumeSettledSubagent` + `resolveSubagentResumeContext` + `resolveOwnerRunId` + `classifySubagentResumeFailure` + `subagentResumeAvailable`(#242 批 2 A-028.7:resume 判定半场上收,与 steer 孪生同居;取址三态 = 台账有行用行值 / 指名了行但台账缺席则**诚实缺席绝不回落在飞 run** / 没指名行才回落。出路文案归端)· `recordSubagentOwnerFromProgress` + `getBgParentRunOwner`(A-028.6:「子代 → 宿主 run」**单表**,宿主 run 必须由持 stream-local 值的调用方显式传入,包内绝不从 `activeEngineRunId()` 推断)· `noteBgOwnerAbsence`(#242 批 3 [4000] Q3=B:tail/taskOutput·taskStop/subagentOutput 三腿台账缺席即诚实缺席**绝不回落在飞 run**,缺席 warn 留痕每 (腿,taskId) 一条)· `clearBgTerminalFacts`(#242 批 3 扫码修:复活=新周期,旧周期终态事实作废——fleetLedger 复活两腿按尾段清账,factsAccepted 方向核不再拿上周期终态当先例)· `auditRetainWithoutWake`([4000] Q5:引擎宣示 `subagentResume` + 本端在付 `retainSubagentSessions` + 端未实现 `wakeSubagent` ⇒ 响亮一条;`CLIENT_VERBS.wakeSubagent` 维持 fail-soft)· `subscribeSubagentContent` · `subscribeEngineAgentPanel` · `publishQuestionFrame` / `respondToQuestion` | 驱动与观测委派子代;经 module 级台账喂活体 agent/task 面板。全部**能力位 gate**(§5b) | `src/subagent/*.ts`、`src/subagentContentStore.ts`、`src/engineAgentPanelStore.ts`、`src/engineInlineTaskStats.ts`、`src/engineToolLabelStore.ts`、`src/liveQuestionStore.ts` |
151
151
  | 5 | **fleet 投影** | 46 | `createFleetLedger` · `projectTasks` · `projectWorkflows` · `projectFleetAgentRows` · `readEngineActiveBgTasks` · `FLEET_TASK_VIEW_KEYS` · `escapeDisplayControlChars`(不可见字符可见化,行标签/描述消毒的共享底座)· `wireCycleSeq` / `wireRetiredBy`(0.38.0 提货补投的 #261 §2 两位:代际号 = SendMessage 复活即 +1,**缺席 ≠ 第一代**;`retiredBy` 在场 = 这条终态是对账腿从 durable run 行投影出来的、**不是**发布方亲报 —— 幽灵行与正常收尾唯一的 wire 判据。两位都只在场才落键) | 老 `fleetClient` 那一刀的成品:**帧体归库、连接归端** —— 端持 SSE 连接,库做行投影 + 保留台账 | `src/fleet/fleetProjection.ts`、`src/fleet/fleetLedger.ts`、`src/fleetAgentPanelProjection.ts`、`src/fleetTaskDesc.ts` |
152
152
  | 6 | **请求装配(上行唯一构造口)** | 8 | `buildTaskRequest` · `REQUEST_FIELD_MATRIX` · `unregisteredRequestKeys` · `applyLiveRequestDefaults` · `taskNotificationToPrintFrame` | 两条车道(`interactive`/`print`)出站请求的**唯一**构造器;`unregisteredRequestKeys` 是可执行门 —— 端偷带一个未登记键上 wire 就红 | `src/request/taskRequest.ts`、`src/request/printNotification.ts` |
@@ -159,7 +159,7 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
159
159
  | 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**,纯类型 + 常量 + 纯谓词)。🔴 **证据等级标注(0.42.0,test [5087] 的「语料**种类**缺口」/ cli [5088] 认领件)**:该文件里所有以「CC 如何如何」为形的断言(`212 methods` / `854-channel census` / 方法名逐字保留 / `fQe` 逐字段对照 / 一切 `.vite/build/index.chunk-*.js` 坐标)**证据等级 = 桌面 unpack,本地语料库不可复验** —— 本仓手边可复验的参照语料**只覆盖终端 CLI 形态**的静态产物,拿它去 grep 桌面壳里的符号只会零命中,而零命中在这里**既不证真也不证伪**。复核这些断言**不得**拿本仓语料当反证 |
160
160
  | 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` |
161
161
  | 15 | **控制面与传输** | 83 | `ControlRouter`(+ `ControlSafetyError`)· `makeEngineWireClient` + `resolveWireAuth` · `installEngineWireTarget` · `diagnoseSseIdleTear` / `isSseIdleError` · `attemptActiveRunSelfHeal` + `activeRunBusySignal` + `activeRunSelfHealRow` / `activeRunBusyHeadlessRow` · `kickEngineCapsProbe` / `engineCapTrue` / `invalidateEngineCaps(baseUrl, probe?)`(#307 S25:引擎温切后的 caps 生产失效口 —— kick 自带幂等闸,同 baseUrl 重启后不显式失效就永远读到旧引擎那一版能力位;调用方 = 壳的 respawn/restartEngine。🔴 **推荐两参形**:第二参给替代探测则「推进代际 + 注册新探测」在同一同步块内完成,失效与下一次 kick 之间那个「等待者读到未判」的窗按构造不存在;单参形保留给「只丢缓存、这一刻没有替代探测」的调用方,那种情形下读到未判是诚实结局) · `mapBrainStatusToRetry` · `waitForClaimRelease` + `CLAIM_RELEASED_STATES` / `CLAIM_HELD_STATES` · `atMostOnceFailureClass` / `readSteerDelivery` · `clearRunningChoiceOffer`(Inkglow-1085 P0b①:「Do nothing」登记的清口 —— 端的「重新打开操作菜单」入口;登记在场时 attemptActiveRunSelfHeal 不整卡重弹,not-parked 结局带 `alreadyOffered: true` 判别位,端据此降级渲一行)· `INTERACTIVE_WAY_OUT`(默认出路串单源)· `engineSessionParamFor`(design/285 批 0:`?session=` 派生的 **per-key** 形 —— `hostSessionFor(sessionKey)?.currentSessionId()` + [1501] 空串归一;零参 `engineSessionParam()` = 默认槽兼容层,取值链逐字等价)· `normalizeWirePrincipal`(A-028.10:principal 在场性 trim 原语 —— 全空白=缺席不发头,engineWireTarget 两臂/makeEngineWireClient/壳 livePrincipal 同尺)· `readSessionMemoryStatus` / `classifyMemoryStatusFailure` / `readCaptureOptOut` / `readLastCapture`(S-53 会话记忆姿态读面,0.48.0:失败分诊**码优先**——两个 404 分道 `not_found.session` / `not_found.route`,无码 404 不猜落 failed;五键逐键缺席语义两个合读器,`lastCapture` 三态的判别材料是 `committedCount` 不是本键;IO 归宿主注入 `MemoryStatusClientLike`,详见 §11) · `classifyTurnWireError` / `isWireTransportError` / `isPreStreamDrainingReject` / `isResumeAtRejection` / `drainingRetryDelayMs` / `scenarioDenyFromError` + `WIRE_NETWORK_ERROR_PATTERN`(A-028.11/.13:turn 错误分型判定半场,人话文案与渲染归端)· `resumeRetryLaterFromError`(L-102,0.57.0:resume 族**时间性拒绝**二码的判型半场 —— 这一族里唯一**带得出「等多久」**(`retryAfterSec`)的两个码。🔴 闭集**不是**「哪些码可以等」的名单:同族 `resume.row_recycling` 同样可等(窗口自清)只是没有秒数 ⇒ 本读口返回 `null` 只意味着没命中这两码,详见 §21) | 上行通道的**监管**半场(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`、`src/sessionMemoryStatus.ts` |
162
- | 16 | **引擎词汇表与包自检** | 54 | `CONFIG_REFUSAL_CODES` / `isConfigRefusalCode` · `STOP_CONFLICT_CODES` · `isInterruptedToolEndCode` · `isRewindFamilyCode` · `CLIENT_VERBS` · `compensationSplitViolations` · `DELEGATION_CAP_CODES` / `isDelegationCapCode` / `DELEGATION_CONCURRENCY_CAP` / `DELEGATION_SESSION_CAP`(0.38.0 #318 件④:core 5.48.0 design/323 委派席位到限**两码,处置不对称禁合并** —— 并发帽=**可等**(兄弟结束即有位)/ 会话累计帽=**等也没用**(这条会话的配额用尽))· `CONFIG_DELEGATION_ENTRY_CAPS`(同批入 `CONFIG_REFUSAL_CODES` 识别表)· `delegationCapDispositionOf` / `MCP_SERVER_REVOKED`(0.39.0 载体到货消费件:core 5.50.0 补 `{ error: code, code }` 孪生拼法后两码真上 `tool_end.errorCode`,0.38.0「先立词不落消费分支」的已知局限自此解除;处置轴 `wait-for-slot` / `reuse-existing-or-await-reap` 机器可读(累计帽=retained-window 帐,行回收配额即回,处置=SendMessage 复用,**非**「换会话/永久耗尽」——0.38.0 段该句系勘误),未知 `delegation.*` 码 ⇒ `undefined`;`mcp.server_revoked` = 操作员 mid-session 吊销 server 后的工具面本地闸(被吊销的 server **名**今天不过 wire 境:detail.server 是进程内位,抬升腿只 lift code——归因渲染候 core 补 typed detail,已点名);载体门 = engine-vocab G3 腿锚 core dist 铸点)· `CAPABILITY_SELF_ORCHESTRATION_REQUIRED`(S-81,server 7.57.0:提交面的 selfOrchestration 准入拒绝码。🔴 **复用码** —— 与其它 `capability.*` 501 同体形而处置不同,消费点必须按**恰等**判、绝不放宽成前缀判;判型与「去键重发一次」归 `src/selfOrchestrationDenial.ts`,详见 §13) | 三端分臂共用的**去字面化** `errorCode` 词表(病根正是三端各抄一份字面);编译期 verb 闭合门;搬迁补偿登记表 | `src/engineErrorCodes.ts`(计数以 `scripts/public-export-baseline.json` 为准,别手抄;A-028.11/.13 补 `DRAINING_ERROR_CODE`/`SCENARIO_NOT_ALLOWED_ERROR_CODE`/`RESUME_AT_ERROR_CODE_PREFIX`;#318 件④ 补 `delegation.*` 族四位 + `config.delegation_entry_caps`;0.39.0 补三新码消费件三位);S-81 补 `capability.self_orchestration_required` 一位;L-102 补 `resume.*` 时间性拒绝二码 + 闭集 `RESUME_RETRY_LATER_CODES` 三位)、`src/classifierVerdictWire.ts`、`src/compensations.ts`、`src/clientSlice.ts` |
162
+ | 16 | **引擎词汇表与包自检** | 58 | `CONFIG_REFUSAL_CODES` / `isConfigRefusalCode` · `STOP_CONFLICT_CODES` · `isInterruptedToolEndCode` · `isRewindFamilyCode` · `CLIENT_VERBS` · `compensationSplitViolations` · `DELEGATION_CAP_CODES` / `isDelegationCapCode` / `DELEGATION_CONCURRENCY_CAP` / `DELEGATION_SESSION_CAP`(0.38.0 #318 件④:core 5.48.0 design/323 委派席位到限**两码,处置不对称禁合并** —— 并发帽=**可等**(兄弟结束即有位)/ 会话累计帽=**等也没用**(这条会话的配额用尽))· `CONFIG_DELEGATION_ENTRY_CAPS`(同批入 `CONFIG_REFUSAL_CODES` 识别表)· `delegationCapDispositionOf` / `MCP_SERVER_REVOKED`(0.39.0 载体到货消费件:core 5.50.0 补 `{ error: code, code }` 孪生拼法后两码真上 `tool_end.errorCode`,0.38.0「先立词不落消费分支」的已知局限自此解除;处置轴 `wait-for-slot` / `reuse-existing-or-await-reap` 机器可读(累计帽=retained-window 帐,行回收配额即回,处置=SendMessage 复用,**非**「换会话/永久耗尽」——0.38.0 段该句系勘误),未知 `delegation.*` 码 ⇒ `undefined`;`mcp.server_revoked` = 操作员 mid-session 吊销 server 后的工具面本地闸(被吊销的 server **名**今天不过 wire 境:detail.server 是进程内位,抬升腿只 lift code——归因渲染候 core 补 typed detail,已点名);载体门 = engine-vocab G3 腿锚 core dist 铸点)· `CAPABILITY_SELF_ORCHESTRATION_REQUIRED`(S-81,server 7.57.0:提交面的 selfOrchestration 准入拒绝码。🔴 **复用码** —— 与其它 `capability.*` 501 同体形而处置不同,消费点必须按**恰等**判、绝不放宽成前缀判;判型与「去键重发一次」归 `src/selfOrchestrationDenial.ts`,详见 §13) | 三端分臂共用的**去字面化** `errorCode` 词表(病根正是三端各抄一份字面);编译期 verb 闭合门;搬迁补偿登记表 | `src/engineErrorCodes.ts`(计数以 `scripts/public-export-baseline.json` 为准,别手抄;A-028.11/.13 补 `DRAINING_ERROR_CODE`/`SCENARIO_NOT_ALLOWED_ERROR_CODE`/`RESUME_AT_ERROR_CODE_PREFIX`;#318 件④ 补 `delegation.*` 族四位 + `config.delegation_entry_caps`;0.39.0 补三新码消费件三位);S-81 补 `capability.self_orchestration_required` 一位;L-102 补 `resume.*` 时间性拒绝二码 + 闭集 `RESUME_RETRY_LATER_CODES` 三位)、`src/classifierVerdictWire.ts`、`src/compensations.ts`、`src/clientSlice.ts` |
163
163
 
164
164
  🔴 **`engineErrorCodes` 的开集纪律**(该文件头注逐字):这些 `ReadonlySet` / 前缀谓词一律是**识别表**,
165
165
  回答的是「我认不认得这个码」,**绝不是**「合法码只有这些」。消费点 `switch` **必须留 `default`**,
@@ -1527,7 +1527,7 @@ CHANGELOG 0.29.0「已知局限」段与相应 JSDoc 都有成文。**别在读
1527
1527
 
1528
1528
  | **P-43** | med(治理面)@cli @web @desktop | 🆕 **`hooksForWire()` 只守住了本地执行器四条治理腿里的三条**(0.52.0 L-67④ 补了第三条,第四条**在册未修**):cli 仓那份本地执行器(`utils/hooks/hooksConfigSnapshot.ts` 的 `getHooksFromAllowedSources`,**cli 仓坐标不是本仓坐标**)的第四条腿 = `disableAllHooks` 出现在**非** managed 来源(user/project/local)时按 CC 语义降级成「只跑 managed hooks」。本包今天**只读** `policySettings.disableAllHooks` ⇒ 该形在引擎腿上**不成立**(用户把自己的 hooks 关了,引擎照投照跑)。**为什么不做单边近似**:cli 那条腿读的是**合并后**的标量(四源按 policy→user→project→local 后写覆盖前写),而本包 `SettingsPort` 只有 per-source 读口 —— 拿「任一来源为 true」去近似会在「user 写 `true`、local 写 `false`」这一形上**判反**(cli 那边是**不**限制)。忠实复刻要给 `SettingsPort` 加一个合并读口 = **公面改动**(端要跟车实现),属另一批 | `src/hooksWireCaps.ts`(头注「在册缺口」段);§15b/§15d | 端**不要**假定「用户 settings 里的 `disableAllHooks` 会挡住引擎腿的 hooks」——今天只有 **managed(policySettings)** 那一份挡得住。要它落地按 [C162] 令④ 回 C 板提(正位解在包侧:`SettingsPort` 补合并读口 + 本函数补第四条腿) |
1529
1529
 
1530
- | **P-44** | low(型面)@sdk @cli @web @desktop | 🆕 **SDK 8.1.0 的 `ToolApprovalsResource.respond` 体型只锚到 `persistRule: { rule: string }`,server ≥7.46.0 已发的两位没跟**([C233]①,0.57.0 登记):①`persistRule.edited?: true`(编辑臂,server #340 / [5071]);②`persistRule.batchOfferIndex?: number`(批臂,server ≥7.46.0 / design/377)。同一族锚滞后还有三处:`ToolApprovalFrame.ruleOffers` `PendingCheckpoint.ruleOffers`(server ≥7.46.0 的判别联合;SDK 8.1.0 全树 `ruleOffers`/`RuleOffer` **零命中**,亲验)、`ToolApprovalRespondAck` `persistedRule`/`persistedRules`/`persistedRuleAnchors`(server ≥7.44 / ≥7.46.0 / ≥7.48.0)。🔴 **本包侧零 cast、也不需要 cast**:本包**不铸 respond 的 wire 体** —— `RespondToolApprovalFn` 是**宿主注入**的函数型,包只把扁平兄弟位(`persistRule: string` / `persistRuleEdited?: true` / `persistRuleBatchOfferIndex?: number`)交给注入面,由注入面按 `RespondToolApprovalOpts.persistRuleBatchOfferIndex` 头注写的那段优先序合成嵌套体。所以**吃这个型面缺口的是三端的注入面**(它们直接调 `client.toolApprovals.respond`),不是本包。帧侧那一半本包自铸归一形 `RuleOffer` 并在 `scripts/run-approval-frame-keys-test.mjs` `AHEAD_OF_ANCHOR` 带退出条件登记(SDK 补上当天自红逼删),0.57.0 起窄读器 additive 上公面20) | `src/hitl/toolApprovalWire.ts`(`RespondToolApprovalOpts.persistRuleBatchOfferIndex` / `ToolApprovalRespondAckView` 两处头注已写清「候 sdk 补锚」)· `scripts/run-approval-frame-keys-test.mjs` `AHEAD_OF_ANCHOR` 登记 | 端的注入面今天要合成那个嵌套体:**用宽形声明自己的 respond 包装**(或就地 `as`),并在**改动处写清锚到哪个 sdk 版本**、SDK 补锚当天回收。🔴 **别为了绕过型面缺口去改本包的扁平位**(那是既有 `persistRule: string` 消费者的 BREAKING,而这一批的纲领是 additive);也别在端侧自铸一个同名 `RuleOffer` —— 归一形与它的伴生类型自 0.57.0 起都在本包公面上。正位解在 sdk(补型),按 [C162] 令④ 回 C 板提 |
1530
+ | **P-44** | ✅ **已收**(0.58.0,sdk 8.2.0 提货)@cli @web @desktop | **上游已补齐,本条转已收** —— sdk **8.2.0**(S-134 五锚)把这一族型面缺口一次补完:①`respond` `persistRule` 由单臂 `{rule}` 换成具名**两臂互斥**联合 `PersistRuleSelection`(`{rule, edited?}` `{batchOfferIndex}`,互斥用 `?: never` 钉在编译期 server 那句 `PERSIST_RULE_BATCH_EXCLUSIVE_ERROR` 400 永远打不到线上);②`ToolApprovalFrame` / `ApprovalCard` / `PendingCheckpoint` 三腿同一个 `RuleOffer` 判别联合(含 `match` 四员闭词表、成员 `command`/`directoryRead` 两臂、additive `uncoveredDetail`);③`ToolApprovalRespondAck` `persistedRule` / `persistedRules` / `persistedRuleAnchors` 三位。🔴 **本包侧本来就零 cast、也不需要 cast**(0.57.0 原文,逐字仍成立):本包**不铸 respond 的 wire 体** —— `RespondToolApprovalFn` 是**宿主注入**的函数型,包只把扁平兄弟位(`persistRule: string` / `persistRuleEdited?: true` / `persistRuleBatchOfferIndex?: number`)交给注入面。⇒ 吃这个缺口的一直是**三端的注入面**。本包 0.58.0 这一侧的动作是:归一形 `RuleOffer` 一族改由 sdk 派生、`ToolApprovalRespondAckView` 与 `PersistedRuleAnchor` 两个包内自铸形按各自写下的退役条款回收成 sdk 形的别名、`AHEAD_OF_ANCHOR` 里那条 `ruleOffers` 领先登记到期自红删除(详见 **§22a**)。🔴 **三端还要做的那一半(本条转已收之后的剩余动作)**:注入面那处宽 `cast`(或自铸的 respond 包装形)现在可以删掉,改直接用 sdk 8.2.0 的 `PersistRuleSelection` —— 两臂互斥由编译期钉,`{rule, batchOfferIndex}` 同场会**编译红**(这正是要的);`batchOfferIndex: 0` 逐字进 body(`0` 不被 falsy 吞)。同样别再在端侧自铸同名 `RuleOffer` 形:归一形与它的伴生类型自 0.57.0 起在本包公面上,三张闭词表自 0.58.0 起也在22b) | `src/hitl/toolApprovalWire.ts`(两处「候 sdk 补锚」头注已随本批锚到 8.2.0 / 回收)· `scripts/run-approval-frame-keys-test.mjs`(该条 `AHEAD_OF_ANCHOR` 登记已删)· `scripts/run-sdk-floor-test.mjs`(地板 8.2.0) | 端按上一栏「剩余动作」删自己的宽 cast;peer 地板随本包抬到 `>=8.2.0`,提货前先抬依赖 |
1531
1531
 
1532
1532
  ### 7e. 缺口的共同形状(值得单独说)
1533
1533
 
@@ -2912,8 +2912,11 @@ CC 2.1.250 语料**无同形提示行** —— `first token` / `firstToken` / `f
2912
2912
  那一位只是展示/对账座,**禁**当选择键。
2913
2913
  2. **逐条丢坏,不整只拒**:一条坏 offer 不该让另一条真 offer 消失(与 server 侧同向)。
2914
2914
  但**留下来的每一条都带原始下标**——压紧下标是本节表里第一行那条失效形。
2915
- 3. **batch 的成员不逐条丢**:合取批是「对全体成员一次说是」,任一成员不合形 **整条 batch 丢**
2916
- (兄弟 offer 不连坐)。渲一个缺了成员的批 = 人点的与引擎兑的不是同一批规则。
2915
+ 3. **batch 的成员不逐条丢**:合取批是「对全体成员一次说是」,任一成员不合形(含 `kind` 不识)
2916
+ ⇒ **整条 batch 丢**(兄弟 offer 不连坐,也**不**整卡丢)。渲一个缺了成员的批 = 人点的与引擎
2917
+ 兑的不是同一批规则。⚠️ 这条**不是本包的保守选择,是上游的规范性条款**:core design/382 §2.3
2918
+ 逐字 "drops the WHOLE batch offer — never the single member ... and never the whole card"
2919
+ (sdk 8.2.0 `RULE_OFFER_BATCH_MEMBER_KINDS` 顶注同文)—— 端**不许**自行改成丢单个成员。
2917
2920
  4. **`uncoveredSegments` 是诚实余量披露,不是装饰位**:非「有限非负整数」⇒ 整条 batch 丢。
2918
2921
  渲一个编出来的 `0` 等于对用户说「兑完这批就全覆盖了」,而那句话没有证据。
2919
2922
  `0` 本身是**合法读数**。
@@ -2922,8 +2925,12 @@ CC 2.1.250 语料**无同形提示行** —— `first token` / `firstToken` / `f
2922
2925
 
2923
2926
  ### 20d. 形校验(包侧已做,端不必重做)
2924
2927
 
2925
- - 一条 offer / 一个 batch 成员共用三位窄化:`rule` 非空串 ∧ `match` ∈ {`exact`,`prefix`} ∧
2926
- `command` 非空串;batch 成员另加 `segment` 非空串(渲染座,**永不参与裁决**)。
2928
+ - 一条 `single` offer / 一个 `command` 成员共用三位窄化:`rule` 非空串 ∧ `match` ∈ **闭词表**
2929
+ (⚠️ **0.58.0 起是四员** `exact` / `prefix` / `wildcard` / `subpath`,由 sdk 8.2.0
2930
+ `RULE_OFFER_MATCHES` 单源派生;0.43.0–0.57.0 这里手抄成两员,合法的 `wildcard` 候选被整条丢掉
2931
+ —— B-025,详见 §22)∧ `command` 非空串;成员另加 `segment` 非空串(渲染座,**永不参与裁决**)。
2932
+ - ⚠️ **0.58.0 起 batch 成员是判别联合**(`kind: 'command' | 'directoryRead'`,§22b):
2933
+ `directoryRead` 臂**没有** `match`/`command` 两座,渲之前先读 `kind`。
2927
2934
  - `rule` / `command` / `segment` 三位都是 **UNTRUSTED-for-display**(原始 post-rewrite 命令字节)——
2928
2935
  包不消毒(消毒单源在端的展示层),端渲之前照自己那套控制字符/长度纪律处理。
2929
2936
  - 空数组 / 非数组 / `null` / 全条坏形 / 超帽 ⇒ `undefined`。
@@ -2935,7 +2942,9 @@ CC 2.1.250 语料**无同形提示行** —— `first token` / `firstToken` / `f
2935
2942
  第二个观察面。常驻门 ⑦ 段把两者对同一份素材逐字对账 —— 哪天有人在其中一条腿上就地改窄读,当场红。
2936
2943
 
2937
2944
  **实现锚**:`src/hitl/toolApprovalWire.ts`(`readRuleOffers` / `readRuleOfferSupply` 两个 `export`;
2938
- 归一形 `RuleOffer` / `RuleOfferMatch` / `RuleOfferBatchMember` 与入参形 `WireRuleOffer` 照旧导出)。
2945
+ 归一形 `RuleOffer` / `RuleOfferMatch` / `RuleOfferBatchMember` 与入参形 `WireRuleOffer` 照旧导出。
2946
+ 🆕 0.58.0 起 `RuleOfferMatch` / `RuleOfferBatchMember` / `RuleOfferUncoveredDetail` /
2947
+ `RuleOfferUncoveredReason` 四形**由 sdk 8.2.0 派生**,三张闭词表随之公面再导出,见 §22)。
2939
2948
  **常驻门**:`scripts/run-rule-offers-reader-test.mjs`(公面在场 + 两代正控 + 下标不前移 +
2940
2949
  batch 语义 + 坏形处置 + 取舍序 + 与两条卡端口腿的单一铸点对账)。
2941
2950
 
@@ -3038,3 +3047,192 @@ batch 语义 + 坏形处置 + 取舍序 + 与两条卡端口腿的单一铸点
3038
3047
  - **`refused-preflight` 这一格今天在多数部署上结构性不可达**:`resume.preflight_rejected` 要有
3039
3048
  部署自己的 resume 预检座才发得出来。它是**预置臂**,不是当版可触发的行为面 —— 端接上去是为了
3040
3049
  「码到货那天不至于只会印一行裸码」,不要据此推断本部署已经在用预检。
3050
+
3051
+ ---
3052
+
3053
+ ## §22 🆕 sdk 8.2.0 提货 · 规则报价窄读器修根(B-025)· resume 拒绝文案铸点上收(0.58.0)
3054
+
3055
+ 本节三件同一批,分三段。**共同的形**都是「上游已经把单源摆出来了,而消费侧还留着一份自己的抄本」。
3056
+
3057
+ ### 22a. sdk 8.2.0 提货:peer 地板 `>=7.4.0` → `>=8.2.0`(本版唯一的非 additive 面)
3058
+
3059
+ | 面 | 0.57.0 | 0.58.0 | 端要做什么 |
3060
+ |---|---|---|---|
3061
+ | peer 地板 | `@sema-agent/sdk >=7.4.0` | **`>=8.2.0`** | 提货前先抬自己的 sdk 依赖;装 <8.2.0 会看到 peer 警告,且本包**运行期真的需要** 8.2.0(见下) |
3062
+ | sdk 消费姿势 | 值级 5 个符号 | 值级 **8** 个(新增三张 `as const` 闭词表) | 无(本包内部) |
3063
+ | `RuleOfferMatch` | 本包手抄 `'exact' \| 'prefix'` | **sdk 派生**,四员 | 🔴 对它写穷尽 `switch` 的端**会编译红** —— 那正是要显形的东西(见 22b) |
3064
+ | `RuleOfferBatchMember` | 本包自铸 interface(四座) | **sdk 派生**判别联合 | 🔴 渲成员前先读 `kind`(见 22b) |
3065
+ | `ToolApprovalRespondAckView` | sdk 形的 additive 超集 | **= sdk `ToolApprovalRespondAck` 本身**(别名) | 无(名字保留,读法逐字不变) |
3066
+ | `PersistedRuleAnchor` | 本包自铸(三位 `readonly`) | **sdk 派生**(同三位,跟随 sdk 的可变性声明) | 无(读方源码兼容) |
3067
+
3068
+ 🔴 **地板抬升的四条硬理由**(不是顺手跟版本;逐条写在 `scripts/run-sdk-floor-test.mjs` 的 `FLOOR` 注里):
3069
+ ① 三张闭词表是**运行期**判据 ⇒ 值级 import,<8.2.0 上这三个名字不存在,整包编译不过;
3070
+ ② `RuleOffer` 一族五个类型面(本包归一形由它们派生);
3071
+ ③ `ToolApprovalRespondAck` 上的 `persistedRule` / `persistedRules` / `persistedRuleAnchors` 三位
3072
+ (0.43.0 起的包内超集按它自己写下的退役条款回收);
3073
+ ④ `AgentEvent` 新增 `approval_revoke` 臂 —— 臂一进 union,下行投影的编译期穷举断言当场红。
3074
+
3075
+ **同批退役的两笔记账**(都是「带退出条件的登记到期自红」,不是清理):
3076
+ - `run-approval-frame-keys-test.mjs` 的 `AHEAD_OF_ANCHOR` 里那条 `ruleOffers` 领先登记 ——
3077
+ sdk 8.2.0 的运行期锚 `TOOL_APPROVAL_FRAME_KEYS` 已含该键(18 项),门先自红一次再删;
3078
+ ⚠️ 同表其余 6 条(`requiresRealApproval` / 窗三键 / `inputHasBidi` / `parked`)**仍在**,
3079
+ 锚上依旧没有它们 —— 退出条件是**逐条**的,不是整表的。
3080
+ - §7d **P-44 转「已收」**:sdk 8.1.0 那一族型面缺口(respond 体 `persistRule` 单臂、帧/行的
3081
+ `ruleOffers`、ack 三位)在 8.2.0 全部补齐。🔴 **本包侧本来就零 cast**(P-44 原文已写清:本包不铸
3082
+ respond 的 wire 体,吃这个缺口的是三端**注入面**)⇒ 本包这一侧的动作只是把归一形改成派生 +
3083
+ 回收超集;**三端注入面**现在可以删掉自己那处宽 `cast`,改用 sdk 8.2.0 的
3084
+ `PersistRuleSelection`(两臂互斥 `{rule, edited?}` ∣ `{batchOfferIndex}`,互斥用 `?: never` 钉在编译期)。
3085
+
3086
+ **`approval_revoke` 的处置(如实记账)**:本包**今天零消费口** —— `isToolApprovalFrame` 只认
3087
+ legacy 的 `tool_approval` / `tool_approval_complete`。新臂与它的兄弟 `approval_request` 同处置:
3088
+ `dropped/unsupported_arm`(**有痕**,经 `reportDroppedFrame` 走宿主 sink),不是静默的
3089
+ `none/not_in_slice`。端**不要**把「撤卡帧没反应」读成「引擎没发」;接那条新卡链是另一批。
3090
+
3091
+ ### 22b. B-025 修根:「不再询问」候选窄读器不再手抄上游闭词表
3092
+
3093
+ **病形**(0.43.0 铸、0.57.0 仍在;由 sdk [6464] 两侧源码对读点名,坐标 `toolApprovalWire.ts:1772` / `:209`):
3094
+
3095
+ | 症状 | 0.57.0 实际行为 | 用户看到什么 |
3096
+ |---|---|---|
3097
+ | ① `match` 词表手抄成两员 | 一条合法的 `match:"wildcard"`(或 `"subpath"`)single **整条丢** | 卡上那一格「不再询问」**凭空消失** |
3098
+ | ② batch 成员没有 `directoryRead` 臂 | 一只带 `cd` 段目录只读授权的 batch **整只丢** | 同上,且与该函数自己注释承诺的「逐条丢坏、不整拒」相反 |
3099
+ | ③ 没有 `uncoveredDetail` 座 | design/382 §3.5 的 additive 明细整段读不到 | 「为什么这段还会问」无从渲 |
3100
+
3101
+ **修**=三张闭词表**单源化**到 sdk 8.2.0,本包不再有第二份:
3102
+
3103
+ ```ts
3104
+ import { RULE_OFFER_MATCHES, RULE_OFFER_BATCH_MEMBER_KINDS, RULE_OFFER_UNCOVERED_REASONS } from '@sema-agent/client-core'
3105
+ // RULE_OFFER_MATCHES = ['exact', 'prefix', 'wildcard', 'subpath']
3106
+ // RULE_OFFER_BATCH_MEMBER_KINDS = ['command', 'directoryRead']
3107
+ // RULE_OFFER_UNCOVERED_REASONS = ['redirection', 'no_rule_form', 'cap_overflow']
3108
+ ```
3109
+
3110
+ 🔴 **端拿它们判定,别再手抄字面量** —— 三端与包共用同一份数组对象,词表加员时上游一处改、四处跟。
3111
+ 常驻门对 `match` 做**双向全等钉**:表里每个词都必须被读器认下(⊇;手抄的窄表在新词上当场红),
3112
+ 构造出来保证在表外的词必须被丢(⊆;读器放宽成「是串就收」时当场红)。
3113
+
3114
+ **batch 成员的新形**(sdk 8.2.0 `RuleOfferBatchMember`,core design/382 §2.3 B3):
3115
+
3116
+ ```ts
3117
+ type RuleOfferBatchMember =
3118
+ | { kind: 'command'; rule: string; match: RuleOfferMatch; command: string; segment: string }
3119
+ | { kind: 'directoryRead'; rule: string; directory: string; segment: string }
3120
+ ```
3121
+
3122
+ - `directoryRead` = `cd <dir>` 段铸的**目录只读授权**:`rule` 是规范 `Read(//dir/**)` 文本,
3123
+ `directory` 是词法规范绝对目录(渲染座,免二次解析)。本臂**没有** `match`/`command` 两座 ——
3124
+ 拿「三元组」窄读器去读它会整只判假,那正是症状②。
3125
+ - 🔴 **成员 `kind` 不识 ⇒ 丢整只 batch,绝不丢单个成员,也绝不丢整卡**(core 的**规范性**降级臂,
3126
+ §20c 第 3 条已引原文)。端不许自行改成成员级容错。
3127
+ - ⚠️ **本包另收一条 pre-B3 兼容臂**:`kind` **缺席**的成员按 `kind:'command'` 归一(server ≥7.46.0
3128
+ 到 B3 落地之间铸的成员没有判别位;窄读域只许等于或宽于铸点域)。⇒ **归一形上 `kind` 恒在场**,
3129
+ 端拿到的成员永远是判别联合,零分支差异。
3130
+
3131
+ **additive `uncoveredDetail`**(0.58.0 起透传到 `RuleOffer` 的 batch 臂):
3132
+
3133
+ ```ts
3134
+ uncoveredDetail?: readonly { segment: string; reason: 'redirection' | 'no_rule_form' | 'cap_overflow' }[]
3135
+ ```
3136
+
3137
+ - 🔴 **缺席 ≠ 「没有未覆盖段」**:server 对坏形的座**只丢座不丢批**,老引擎更是压根不铸 ——
3138
+ 真源恒是 `uncoveredSegments` 那个 **count**。本包缺席时**不铸空数组**(空数组会被端读成
3139
+ 「已确认没有未覆盖段」)。
3140
+ - 行数与 `uncoveredSegments` **不强制相等**(见上一条);坏行**只丢那一行**,坏载体只让**本位**
3141
+ 降缺席,**绝不**因为一个 additive 位否决整只 batch。
3142
+ - **行数帽 = 32**,锚在上游铸点域上(server `MAX_UNCOVERED_DETAIL_ROWS`)。⚠️ 未覆盖段数**不受
3143
+ 成员帽约束**:一只只有 1 个成员的 batch 可以带 9 条明细(9 个带重定向的段各自 mint 不出规则形)
3144
+ —— 端若自己再设一个更窄的帽,就会把合法明细整座吞掉(本包初稿的 8 正是这么错的,异源对抗复审
3145
+ 逮到)。33 行及以上 ⇒ 本位降缺席(与 server 座级预筛同判)。
3146
+ - `segment` 与其余文本座同属 **UNTRUSTED-for-display**。
3147
+
3148
+ **端的迁移动作**(逐条,都很小):
3149
+ 1. 对 `RuleOfferMatch` 写穷尽 `switch` 的端:补 `wildcard` / `subpath` 两格(编译红会指给你)。
3150
+ ⚠️ 这两格此前在端上是**静默不可达**的(包在读器里就丢掉了),不是「新增的空格子」。
3151
+ 2. 渲 batch 成员前先读 `kind`;`directoryRead` 臂渲 `directory`(它没有 `command` 可渲)。
3152
+ 3. 想渲「为什么这段还会问」的端:读 `uncoveredDetail`,**缺席就什么都别说**(别渲成 0)。
3153
+ 4. 已走卡端口的宿主:`ApprovalCardRequest.ruleOffers` / `.ruleOffersReadOnly` 是同一把读器的产物,
3154
+ 新形自动到位;常驻门 ⑨e 段把公面与两条卡腿对同一份新形素材逐字对账。
3155
+
3156
+ ### 22c. L-102 下半场:resume 拒绝**文案铸点**上收(壳侧改薄成适配层)
3157
+
3158
+ 0.57.0 上收的是**事实读数**(§21 的 `resumeRetryLaterFromError`:命中哪个码 / server 给没给窗 /
3159
+ 等一会儿有没有用)。留在壳里的另一半是**人话** —— cli 1.0.x 的 `sema/resumeRefusalCopy.ts`(⚠️ **cli 仓坐标,不是本仓坐标**)自铸了
3160
+ 一份判型 + 三句 `·` 分段文案,而 web-client / desktop 要么没有、要么将来会再抄一份。三端各抄一份
3161
+ = 同一次拒绝在三个端上说三句不一样的话,而这三句回答的是**同一个安全问题**:
3162
+ 「这次拒绝有没有消费掉我的决定 / 这张卡还能不能再决」。
3163
+
3164
+ **新公面**(`src/resumeRefusalCopy.ts`):
3165
+
3166
+ ```ts
3167
+ export const RESUME_REFUSAL_CODES: readonly string[] // 冻结数组,恰两员
3168
+ export interface ResumeRefusalDetail { code: string; retryAfterSec?: number; waitable: boolean }
3169
+ export function resumeRefusalFromError(err: unknown): ResumeRefusalDetail | null
3170
+ export function resumeRefusalContent(detail: ResumeRefusalDetail): string
3171
+ // 同批新增码常量:RESUME_PLACEMENT_MISMATCH(engineErrorCodes.ts)
3172
+ ```
3173
+
3174
+ 🔴 **两个闭集刻意分家**(端别把它们当同一张表的两个名字):
3175
+
3176
+ | 闭集 | 闭的是什么 | 成员 |
3177
+ |---|---|---|
3178
+ | `RESUME_RETRY_LATER_CODES`(§21,0.57.0) | **server 在哪些码上铸 `retryAfterSec`** ⇒ 答「能不能等」 | `resume.usage_window_exhausted` · `resume.preflight_rejected` |
3179
+ | `RESUME_REFUSAL_CODES`(本节,0.58.0) | **哪些 resume 拒绝有人话可补** ⇒ 答「该对人说什么」 | `resume.placement_mismatch` · `resume.preflight_rejected` |
3180
+
3181
+ 两集**交于** `preflight_rejected` 一码、**各有**一个独占成员:
3182
+ - `usage_window_exhausted` 只在前者 —— 它没有专属人话,「你可以等」就是全部信息;
3183
+ - `placement_mismatch` 只在后者 —— server 不给它铸窗,而且**等一会儿对它毫无用处**:出路是
3184
+ 「用记录里的那个 root 重来,或干脆不传 `internals.placementRoot` 继承记录值」,是**换参数**
3185
+ 不是**等时间**。把它放进时间性闭集 = 让人白等一个永远不会自己好的拒绝。
3186
+
3187
+ 🔴 **本口不复制判定**:`preflight_rejected` 的窗与可等性一律**转调** `resumeRetryLaterFromError`,
3188
+ 本模块里没有第二个 `retryAfterSec` 窄读器。常驻门用九形入参驱动两口,`waitable` 与 `retryAfterSec`
3189
+ 必须**逐字相同**(含「缺席」那一格)。
3190
+
3191
+ **`waitable` 的两种 `false` 落到不同的第三句**(端不必自己再分):
3192
+
3193
+ | 码 | `waitable` | 第三句 |
3194
+ |---|---|---|
3195
+ | `placement_mismatch` | 恒 `false`(等没用) | `Retry with the recorded root, or omit the placement root to inherit it` |
3196
+ | `preflight_rejected` ∧ 无窗 | `false`(**不可判**) | `The engine line above says whether this token can still be redeemed` |
3197
+ | `preflight_rejected` ∧ 有窗 | `true`(正向证据) | `The engine supplied a wait, so this token stays redeemable once the obstacle clears` |
3198
+
3199
+ 🔴 第三句由 `waitable` 选,**绝不**由 message 文本选:按上游散文分支正是「上游改一个词、端的判定
3200
+ 静默空转」的形。门里把两族原话(terminal / retry_later)都灌进 message 而窗不动,文案必须一字不动。
3201
+
3202
+ ⚠️ **`retryAfterSec` 的窄读域比 cli 1.0.x 那份自铸的更窄**(端提货要知道的行为差分):
3203
+
3204
+ | | cli 1.0.x 自铸 | 本口(与 §21 同律) |
3205
+ |---|---|---|
3206
+ | 域 | 有限数 ∧ ≥0 | **整数 ∧ ≥1** |
3207
+ | `0` / 负数 / 小数 | 透传(`0` 会渲成「立刻重试」) | **降缺席**(不渲等待行) |
3208
+
3209
+ server 的铸键逐字是「ms → 秒**向上取整**、**下限 1**」⇒ 真供给里不存在 0 / 负数 / 小数。放行 `0`
3210
+ 就是对端说「立刻重试」,而 resume 是 **AT-MOST-ONCE** 的有副作用动作(叫醒 = 真跑一轮)—— 一个 0
3211
+ 会把「等一会儿」变成热循环。⇒ 壳提货后这三形上的行为**会变**,这是**修好**不是回归。
3212
+
3213
+ **端该怎么接**(cli 半场 = 1.0.101,本车不改壳):
3214
+
3215
+ 1. cli 仓 `sema/resumeRefusalCopy.ts` **改薄成适配层**(或整个删掉,直接从包里 re-export):
3216
+ `RESUME_REFUSAL_CODES` / `resumeRefusalFromError` / `resumeRefusalContent` 三个名字与壳现有的
3217
+ **逐字同名同形**,壳侧的 `ResumeRefusalDetail` 多出一位 `waitable`(additive)。
3218
+ 2. cli 仓 `sema/apiErrorSupplement.ts` 的 61-73 行那条**互斥链**改消费包面:`resumeRefusalFromError` 换成
3219
+ 包导出的那一个,其余一字不动 —— 段序、互斥序(quota → scenario → resume → device)、
3220
+ `retryAfterHint` 的 `??` 取值口全部照旧。
3221
+ 3. 🔴 **等待那一行仍归端渲**:包里的文案**不铸秒数**(写在里面 = 同一个数字上屏两遍);
3222
+ 壳继续用 `retryAfterHint(err, quotaDetail?.retryAfterSec ?? resumeRefusal?.retryAfterSec)`。
3223
+ 4. 开集兜底一字不动:不认识的码 ⇒ `null` ⇒ 端一个字都不补 ⇒
3224
+ `API Error: <status> <errorCode> · <msg>` 原样上屏。
3225
+
3226
+ **可达性如实登记**(不许把预置说成到货):两码在 server 7.51 当版**结构性不可达**(四条腿都不供
3227
+ `placementRoot`,也没有 `resumePreflight` 座)。本模块与壳那份一样是**预置臂**,不是当版可触发的
3228
+ 行为面 —— 收进包是为了「码到货那天三端不至于各说各话」。
3229
+
3230
+ ### 22d. 常驻门
3231
+
3232
+ | 门 | 守什么 |
3233
+ |---|---|
3234
+ | `scripts/run-rule-offers-reader-test.mjs` | 段 ⑨ 新增:三张闭词表单源化与再导出逐字相等 · match 词集双向全等钉 · `directoryRead` 臂认回与四座逐字 · pre-B3 成员归一 · 成员 `kind` 不识丢整只(七形)· `uncoveredDetail` 透传/只丢坏行/缺席不铸空 · 卡端口两腿同吃新形 |
3235
+ | `scripts/run-resume-refusal-copy-test.mjs` | 🆕 公面在场 · **两个闭集关系双向钉** · 不复制判定(九形入参两口逐字相同)· `placement_mismatch` 恒无窗恒不可等且带窗也不翻 · 窄读域「整数 ∧ ≥1」· 文案三句由 `waitable` 选而非 message · 负控与坏载体 · 纯函数 |
3236
+ | `scripts/run-approval-frame-keys-test.mjs` | `ruleOffers` 领先登记退役后回到逐元素相等(其余 6 条领先登记仍在) |
3237
+ | `scripts/run-sdk-floor-test.mjs` | 地板 `8.2.0` 与 peer 声明**三位逐位等值**互绑 |
3238
+ | `scripts/run-public-surface-test.mjs` | peer 地板 / README 三处逐字核 + 公面导出基线 **821 → 828** |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/client-core",
3
- "version": "0.57.0",
3
+ "version": "0.58.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. Renamed from @sema-agent/wire-cc-adapter (0.1.x).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -31,12 +31,12 @@
31
31
  },
32
32
  "peerDependencies": {
33
33
  "@sema-agent/agent-types": ">=0.2.0",
34
- "@sema-agent/sdk": ">=7.4.0"
34
+ "@sema-agent/sdk": ">=8.2.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@sema-agent/agent-types": "^0.2.0",
38
38
  "@sema-agent/core": "~7.3.0",
39
- "@sema-agent/sdk": "^7.4.0",
39
+ "@sema-agent/sdk": "^8.2.0",
40
40
  "esbuild": "^0.27.4",
41
41
  "typescript": "^6.0.2"
42
42
  }