@sema-agent/client-core 0.56.0 → 0.57.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 +119 -0
- package/README.md +3 -1
- package/dist/engineErrorCodes.d.ts +44 -0
- package/dist/engineErrorCodes.js +64 -0
- package/dist/hitl/toolApprovalWire.d.ts +48 -0
- package/dist/hitl/toolApprovalWire.js +24 -6
- package/dist/subagent/engineSubagentResume.d.ts +82 -6
- package/dist/subagent/engineSubagentResume.js +19 -2
- package/dist/wireErrorTriage.d.ts +73 -0
- package/dist/wireErrorTriage.js +40 -1
- package/docs/INTEGRATION-CLIENTS.md +178 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -49,6 +49,125 @@
|
|
|
49
49
|
> 挡住 ⇒ 本批把它机械化——④a0 对 `pending` 行**要求段头已是日期形**(`(未发布)` 直接红),阶段一
|
|
50
50
|
> commit 漏转在发布前就红,不再靠人记。
|
|
51
51
|
|
|
52
|
+
## 0.57.0(2026-09-05)
|
|
53
|
+
|
|
54
|
+
> 内容批 D(2026-09-05,隔离树交付):三件 —— L-103/[C228] 窄读器公面化、L-102 resume 时间性拒绝
|
|
55
|
+
> 判型上收、[C233]① SDK 型面缺口登记。**本段只记内容**:`package.json.version` 未动、README Version
|
|
56
|
+
> 行未动、`FROZEN` 未动 —— bump 与段头转日期形归**发包批**(阶段一义务见本档头注)。
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
### 新增 —— 「不再询问」候选窄读器的公面(L-103 / [C228]、[C233]②;additive 导出,语义零变化)
|
|
60
|
+
|
|
61
|
+
两代 wire 键(`ruleOffers` server ≥7.46.0 / 退役键 `ruleSuggestions` ≤7.45)的归一窄读器此前是
|
|
62
|
+
**模块内私有函数**,包外唯一读得到归一形的路径是卡端口(`ApprovalCardRequest.ruleOffers` /
|
|
63
|
+
`.ruleOffersReadOnly`)。不走卡端口架构的宿主(浏览器端自己拿帧渲,没有 Ink 三选卡)因此只能在
|
|
64
|
+
自己那边**重铸一遍** —— 而这把读器承载的是**兑付安全**判据,不是格式化:
|
|
65
|
+
|
|
66
|
+
- 逐条丢坏、**原始 wire 下标不前移**(压紧 = 人点的第 k 个与服务端兑的第 k 个指向两条不同规则);
|
|
67
|
+
- `kind` 是闭集判别位(不认识的臂不猜、不降级成 `single`);
|
|
68
|
+
- 两代取舍序(新键在场即定局、有载体读不出**不**回落旧键、`null` ≡ 缺席)。
|
|
69
|
+
|
|
70
|
+
新公面(两个函数,**签名与函数体一字未改**,只是加了 `export`):
|
|
71
|
+
|
|
72
|
+
- `readRuleOffers(v)` —— 只读新键;
|
|
73
|
+
- `readRuleOfferSupply(offers, legacy)` —— 两代键的取舍口,三端该调的那一个
|
|
74
|
+
(0.56.0 及更早的内部名是 `readOfferSupply`,**纯改名**,零外部消费点)。
|
|
75
|
+
|
|
76
|
+
归一形 `RuleOffer` / `RuleOfferMatch` / `RuleOfferBatchMember` 与入参形 `WireRuleOffer` 照旧导出 ——
|
|
77
|
+
端拿得到值也拿得到形,不必再自铸一份会漂的同名结构。
|
|
78
|
+
|
|
79
|
+
🔴 **`offerIndex` 的定义域随腿不同**,消费前必读 §20c:活卡帧腿上它是合法**选择键**
|
|
80
|
+
(批臂按它回兑 `persistRule.batchOfferIndex`),durable 行腿上它只是展示/对账座(该腿无兑付口)。
|
|
81
|
+
|
|
82
|
+
### 新增 —— resume 族「时间性拒绝」二码的判型(L-102;server ≥7.47.0 / ≥7.51.0,SDK 8.1.0)
|
|
83
|
+
|
|
84
|
+
`resume.*` 这一族的九个成员里,`resume.usage_window_exhausted`(部署治理窗满)与
|
|
85
|
+
`resume.preflight_rejected`(部署自己的 resume 预检拒)是**唯一带得出「等多久」**的两个:409 体
|
|
86
|
+
additive 携 `retryAfterSec`(秒,`ceil`,下限 1)。这是它们被收成一个闭集的**全部理由**。
|
|
87
|
+
🔴 **闭集不是「哪些码可以等」的名单**(异源对抗复审第三轮 [medium] 真病修 —— 本段初稿写成
|
|
88
|
+
「只有这两码可能是『过一会儿行』」,是排他性错断):族内有明确反例 `resume.row_recycling`
|
|
89
|
+
(core 铸文逐字 "this clears on its own; send again in a moment",本包处置 `row-contended`)——
|
|
90
|
+
它可等,只是 server 给不出秒数。⇒ 新读口返回 `null` 只意味着**没命中这两码**,不意味着「等也没用」。
|
|
91
|
+
🔴 命中之后也**不等于一定可等** —— 后一码另有一条 `terminal` 臂(行已被结清、token 不可再赎),
|
|
92
|
+
所以处置不是一格而是**按证据分两格**,见下。
|
|
93
|
+
|
|
94
|
+
修前:本包的 `classifySubagentResumeFailure` 把两码双双落进开集兜底 `error` ⇒ server 明明给了等待窗,
|
|
95
|
+
到端只剩一句泛泛失败;而壳侧自己手接了一份判型 —— 判定长在端里,三端各写一遍必然各错一遍
|
|
96
|
+
(与 0.38.0 收 `resume.row_recycling` / `resume.row_gone` 那次同形)。
|
|
97
|
+
|
|
98
|
+
- 新词表(`src/engineErrorCodes.ts`):`RESUME_USAGE_WINDOW_EXHAUSTED` / `RESUME_PREFLIGHT_REJECTED` +
|
|
99
|
+
闭集 `RESUME_RETRY_LATER_CODES`。**闭的不是「`resume.*` 有几个码」**(那仍是开集),闭的是
|
|
100
|
+
「server 在哪些码上铸 `retryAfterSec`」;**绝不放宽成 `resume.` 前缀判**。
|
|
101
|
+
🔴 形制 = **`Object.freeze` 的只读数组,不是 `ReadonlySet`**(异源对抗复审 [medium] 真病修):
|
|
102
|
+
后者只在类型面只读,运行期就是普通 Set 而判定查的是**同一个实例** ⇒ 公面消费者一行
|
|
103
|
+
`.add('resume.row_gone')` 就能把一条「等也没用」的拒绝翻成带窗的 `retry-later`(实测)。与 #363
|
|
104
|
+
二轮把 `interactiveHalt.RUN_LEVEL_STOP_ERROR_CODES` 改冻结数组是**同一条已定谳的病形**。
|
|
105
|
+
⚠️ **同形存量登记(只登记不顺手改)**:同文件 `CONFIG_REFUSAL_CODES` / `DELEGATION_CAP_CODES` /
|
|
106
|
+
`TOOL_END_INTERRUPTED_CODES` 三张表仍是 `ReadonlySet`,同病;它们已在公面上且消费点用 `.has()`
|
|
107
|
+
⇒ 换形是下游 **BREAKING**(签名变更),不属内容批射程,**另立一批**。
|
|
108
|
+
- 新读口(`src/wireErrorTriage.ts`):`resumeRetryLaterFromError(err)` → `ResumeRetryLaterDetail | null`。
|
|
109
|
+
结构读不 `instanceof`(客户端是宿主注入的,跨 bundle 的同名类是两个类);键位只认 `errorCode`。
|
|
110
|
+
`retryAfterSec` 的窄读域 = **server 的铸键域**(整数 ∧ ≥1),`0` / 负数 / 小数 / `NaN` / 串一律
|
|
111
|
+
**降缺席**(不降 0)——放行 `0` 就是对消费端说「立刻重试」,而 resume 是 AT-MOST-ONCE 的有副作用动作。
|
|
112
|
+
- 🔴 **「命中本族」不等于「可等」**(异源对抗复审 [medium] 真病修,本批当场改形):
|
|
113
|
+
`resume.preflight_rejected` 在 core 侧有两条臂 —— `retry_later`(行留 pending,同一 token 可再赎)
|
|
114
|
+
与 `terminal`(行已被单发 expire CAS 结清,**token 不可再赎**),而**判别位在 message 散文里**。
|
|
115
|
+
仅凭码就宣告「稍后重试」会把一个终局说成暂时等待;而按文案分臂又正是上游改一个词就静默空转的形。
|
|
116
|
+
⇒ 读口新增**处置位** `waitable`(全包对这个问题的单一判断点),只认**正向证据**:
|
|
117
|
+
`resume.usage_window_exhausted` 恒 `true`(core 铸文的不变量就是证据);`resume.preflight_rejected`
|
|
118
|
+
只在 server 真给了窗时 `true`。`false` 读作**不可判**,不是「不可重试」。
|
|
119
|
+
- 分类器新增**两格**(`SubagentResumeFailureKind`):有证据的落 `retry-later`,无证据的预检拒落
|
|
120
|
+
`refused-preflight`(端说两臂都成立的那句 —— 「这一拒发生在提交之前,你的决定没被消费」,
|
|
121
|
+
把「还能不能再赎」交给引擎那行原文)。判决与 `resumeSettledSubagent()` 的失败臂 additive 携
|
|
122
|
+
`code?` 与 `retryAfterSec?`;**既有各格零挪位**,本层照旧**一格都不重试**。
|
|
123
|
+
- `classifySubagentResumeFailure` 的返回位由两成员内联形抽成具名 `SubagentResumeFailureVerdict`
|
|
124
|
+
(结构逐字兼容,TS 结构化类型 ⇒ 消费端零差异)。
|
|
125
|
+
|
|
126
|
+
### 登记 —— SDK 型面缺口([C233]①,只记账不改码)
|
|
127
|
+
|
|
128
|
+
SDK 8.1.0 的 `ToolApprovalsResource.respond` 体型只锚到 `persistRule: { rule: string }`,server ≥7.46.0
|
|
129
|
+
已发的 `edited` / `batchOfferIndex` 两位没跟;同族锚滞后还有 `ToolApprovalFrame.ruleOffers` /
|
|
130
|
+
`PendingCheckpoint.ruleOffers` 与 ack 的三个回显位。🔴 **本包侧零 cast、也不需要 cast** —— 本包不铸
|
|
131
|
+
respond 的 wire 体(`RespondToolApprovalFn` 是宿主注入的函数型,包只交扁平兄弟位),吃这个缺口的是
|
|
132
|
+
三端的注入面。登记进 `docs/INTEGRATION-CLIENTS.md` §7d **P-44**,正位解在 sdk。
|
|
133
|
+
|
|
134
|
+
### 门(三件同批)
|
|
135
|
+
|
|
136
|
+
- 新门 `scripts/run-rule-offers-reader-test.mjs`(109 判据):公面在场(d.ts 导出形)· 两代键各一正控 ·
|
|
137
|
+
逐条丢坏且原始下标不前移(头/中/尾三个方向 × ****十九条丢坏路径各走一遍** —— 坏 single 两形 / 坏 batch 十三形(rules 非数组·空·超帽 + 成员十形:缺 segment / rule 空串·非串 / match 表外词 / 缺 command·空串 / segment 空串·非串 / 成员 null·undefined·非对象)/ 非对象 / null / 不认识的 kind,外加旧键归一臂七形 —— **后两族专门够 `readRuleTriple` 自己那两道守卫**(外层的 null/非对象过滤在 offer 级就把它们拦掉了,成员级与旧键项才喂得到);三轮变异实发的逃逸全在这一格上:只拿「不认识的 kind」当坏条时读器里另外几条 `continue` 改成整只 `return undefined` 照样全绿、成员窄化换成裸 cast 照样全绿、删掉 `readRuleTriple` 的载体守卫照样全绿(而那一形会当场抛 TypeError))· batch 下标语义与成员不逐条丢 · `uncoveredSegments`
|
|
138
|
+
六种坏值 · 空数组/非数组/超帽/全坏 · 两代取舍序(含 `null` ≡ 缺席的明示裁定)· **单一铸点对账**
|
|
139
|
+
(公面读口与活卡帧腿/durable 行腿两条卡端口出口对同一份素材逐字相等)· 纯函数不改写入参。
|
|
140
|
+
- 新门 `scripts/run-resume-retry-later-test.mjs`(115 判据):两码正控(带窗/不带窗)· 闭集恰两员 ·
|
|
141
|
+
负控 16 形(同族七个「等也没用」的码、429 同名不同门、两个下划线族、文本引用、坏载体)·
|
|
142
|
+
`retryAfterSec` 十一种脏值消毒 + 下边界 1 · `waitable` 的证据判据(治理窗满恒 true / 无窗预检拒
|
|
143
|
+
必须 false / 脏窗消毒后不许升格)· **闭集运行期改不动**(试改 + 试改后判定没漂,两半都断言)·
|
|
144
|
+
分类器两格分立与既有各格零挪位 · 未知码上的窗不被采信 · **`null` ≠「等也没用」的可执行反钉**
|
|
145
|
+
(同族 `resume.row_recycling` 在读口上是 `null`、在分类器上却必须落可等的 `row-contended`)·
|
|
146
|
+
端到端三形(带窗 / 无窗预检拒 / 「等也没用」的码)且**一格都不重试**。
|
|
147
|
+
- 两门同批登记 `scripts/gates-manifest.json` + README `Guards` 表(公开文案)。
|
|
148
|
+
- 登记面:`scripts/public-export-baseline.json` 815→821(六个新公面名)· `docs/refactor/p1-scan/singleton-manifest.json` 279→280(`RESUME_RETRY_LATER_CODES` 只读词表,dupRisk low,high 上限不动)·
|
|
149
|
+
`scripts/run-client-core-typeshape-test.mjs` 的 `unknownExport` 棘轮 268→272(四个边界读口入参位,
|
|
150
|
+
逐条登记理由;**B4 恒 20、裸 unknown 返回恒 0 不动**)· `docs/INTEGRATION-CLIENTS.md` §0a/§2/§2b 计数。
|
|
151
|
+
|
|
152
|
+
### 🔴 老宿主必读
|
|
153
|
+
|
|
154
|
+
`SubagentResumeFailureKind` 从八员变**十**员(`retry-later` + `refused-preflight`):
|
|
155
|
+
**结构形免动**(读 `reason`/`detail` 的宿主一字不用改),
|
|
156
|
+
但对该联合做**穷尽 switch**(`never` 兜底)的宿主提货时会编译红 —— 那是设计:一个新的处置落进
|
|
157
|
+
`default` 而无人处理,就是把「等一会儿就好」渲成「失败了」。同 0.55.0 `RetryStatus` 那条老宿主纪律。
|
|
158
|
+
|
|
159
|
+
### 已知局限(本版新增)
|
|
160
|
+
|
|
161
|
+
- **本包不铸 `ResumeRetryLaterError`,也不 `instanceof` 它**:该类是 SDK 8.1.0 的,而本包 peer 地板
|
|
162
|
+
仍是 `>=7.4.0`。挂在 ≤8.0 SDK 上的宿主,这两码走的是无字段的族基类 ⇒ `retryAfterSec` 在错误对象上
|
|
163
|
+
根本不存在,本包如实报缺席(**缺席 ≠ 0**)。
|
|
164
|
+
- **`resume.preflight_rejected` 的两条上游臂(`retry_later` / `terminal`)在客户端分辨不出来** ——
|
|
165
|
+
判别位在 message 散文里,本包**不按文案分臂**,改按**正向证据**分格(见上)。所以 `refused-preflight`
|
|
166
|
+
这一格的语义是**不可判**而不是「不可重试」:端只说两臂都成立的那句,「还能不能再赎」交给引擎那行
|
|
167
|
+
原文。**候上游给机读判别位**(有了它这一格就能再分成两格,届时是 additive 加员)。
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
52
171
|
## 0.56.0(2026-09-05)
|
|
53
172
|
|
|
54
173
|
> 发包批阶段一(2026-09-05,主会话):`package.json` 0.55.0→0.56.0、README Version 行、本段头转日期形、`FROZEN` pending 行同批;内容由 1.0.100 提货车 B 在隔离树交付,主会话收货补包侧门与 eventId 修钉(见「门与收货修钉」)。
|
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.57.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
|
|
@@ -265,6 +265,8 @@ public-surface guard checks that last one).
|
|
|
265
265
|
| `scripts/run-esc-halt-plan-test.mjs` | The Esc stop decision every client shares: fire the **turn-level** halt first, and escalate to a **run-level** cancel in exactly two cases — the engine itself answered with a 409 from the closed code set (it is saying "there is no in-flight turn here; use cancel for a run-level stop"), or that shot came back with no verdict at all *and* the shell can independently prove a permission card was on screen. Everything else does not escalate. The asymmetry is the whole point and every negative control guards the same direction — deciding *not* to escalate costs the user one more choice on a busy-session card (recoverable), deciding to escalate wrongly tears down a run that was alive and takes every in-flight tool with it (not). So: the closed code set is a **frozen** value, not a `ReadonlySet` — type-level immutability does not stop a consumer's `.add()`, and the guard proves it by really trying to mutate the exported value and then checking the verdict did not drift; the escalation gate is the **conjunction** of that closed set and the 409 status, since honouring the code alone lets a 500 that merely quotes it drive a destructive call; `interrupt.not_held` and `steering.not_running` are deliberately outside the set (the first means *this replica* has no live face — the run may be perfectly alive on another); an unreadable code falls to the no-escalation side; a `parked` flag never overrides a verdict the engine did give, and only strict `true` counts when it did not. The first shot is unconditional by construction — it does not consult `parked`, because the 409 it earns is exactly the verdict the gate wants — and the verdict itself is a closed machine-readable reason word, not display copy |
|
|
266
266
|
| `scripts/run-peer-frame-projection-test.mjs` | The three engine-injected lanes design/385 puts on the **one** `task_notification` carrier, which are not the same kind of thing at all: a delegated child's uplink (`agentMessage`), another session's message drained from this session's own box (`crossSessionMessage`), and a receipt about one of *this* session's own outbound messages (`crossSessionNotice`). The engine renders none of them inside a `<task-notification>` shell, so a client that projects them as the generic completion card shows "background task finished" while the model read a colleague's sentence — two faces describing different events. The discriminator is pinned to the **typed carrier being present**, never to the `summary` text: those carriers can only be minted by the engine's injection legs (the external `notify()` input is a strict subset of the payload and can wear none of them), while `summary` is filled by every notification there is — so anchoring on text would let any background task impersonate a colleague's message by writing `<agent-message from="…">` into its own summary, and a positive control asserts exactly that payload still projects as the generic card. Fail-closed has two tiers rather than one: a broken **required** field (empty `from`, a non-string `body`, a notice `kind` outside the closed set) returns absence so the caller falls back to the generic card — an honest downgrade where the user still sees the notification — while a broken **optional** field drops only itself, because losing an attribution note and losing a colleague's whole message are not the same magnitude. The provenance side record is **required and must agree on four points** (`kind` matches the lane; `from`/`taskId`/`seq` are present and equal the carrier/payload — each equality is anchored on a core mint site and pinned by the cli wire-anchor A-K24), so a carrier signed with a trusted name but a disagreeing provenance falls back to the generic card; peer bodies pass the same authority-envelope neutralization core applies (`<task-notification>` etc. are defused) so a colleague's text can never seed the resume dedup ledger. Lane precedence copies the engine renderer's own order, because the model already read the frame in that order and a client ordering of its own would put a card on screen that disagrees with the frame the model saw. Rendering and parsing of the transcript line live in the same module and are round-tripped in both directions, including a body carrying a forged closing tag (a parser fooled there hands half a message to the next row) and a quote inside the sender label (which must not forge a second attribute); the notice lane is deliberately kept **out** of the parser, since recognising it would mean anchoring the `[Cross-session …]` prefix and a user typing that same line would be rendered as engine speech. Hostile carriers are read as own **data** descriptors only and accessors are never invoked at all — `catch` catches throwing, not never returning — proven by a counting getter that must stay at zero calls, alongside a revoked proxy and a prototype-only carrier; and four legacy payload shapes assert the no-carrier path is byte-identical to before, which is the executable form of "zero difference for an older host" |
|
|
267
267
|
| `scripts/run-wiring-manifest-projection-test.mjs` | The two end-user facts carried on the engine's `wiring_manifest` frame (`modelGate`: which tools this run's model gate removed and the verbatim restore hint; `autoMode`: whether auto mode is actually armed and the engine's own reason word). Projection: both sections ride as `_sema_`-prefixed superset keys, verbatim, and no SDK-named key is minted; a frame where neither section is well-formed projects to `none/not_in_slice` (no empty arm); `modelGate` needs all three keys and treats `removed: []` as a bad value rather than a reading; `autoMode` needs a boolean plus a non-empty reason that agrees with it, and the reason word is never mapped onto the capabilities vocabulary; the frame is flat (a nested `manifest:{}` wrapper is not a supply); `eventId` rides like every other arm. Adapter: exactly one chrome event on the main lane, a sub-flow frame (any `parentToolCallId`, `null` included) yields nothing, and an absent `eventId` leaves the key absent. Added at receiving time because the shell-side gate could not see this package's behaviour: two mutations (empty `removed` accepted, sub-flow gate removed) had passed the package suite untouched |
|
|
268
|
+
| `scripts/run-rule-offers-reader-test.mjs` | The narrowing reader behind the "don't ask again" options, now a public entry point rather than a card-port-only one. Hosts that render the frame themselves (a browser has no three-way terminal card) previously had to rebuild this reader on their side, and what it carries is a **redemption-safety** judgement, not a convenience: the batch arm is redeemed by **index**, so a reader that compacts the array after dropping a malformed entry makes the k-th option a person clicked and the k-th rule the server writes two different rules. So: a bad entry is dropped **on its own** (one bad option must not make a real one disappear) while every surviving entry keeps its **original wire index** — pinned from both ends, with the bad entries leading and trailing. A batch's *members* are the opposite: any malformed member drops the whole batch, because a conjunctive batch is one "yes" to all of them and a batch missing a member is a different grant; its honest-remainder count is a reading, not decoration, so a non-integer or negative value drops the batch rather than rendering a fabricated zero. An empty array, a non-array, an over-cap array and an all-bad array all read as **absence** rather than an empty list, because an empty list renders as "there is an option lane with nothing in it". The two wire generations are ordered by a rule, not a preference: the newer key wins outright, a newer key that is **present but unreadable** does not fall back to the retired key (borrowing the older material would pass someone else's options off as this request's), and a `null` newer key reads as absence so a relaying layer that serialises "missing" as null cannot delete the whole lane on older engines. The public entry is finally reconciled against **both** card-port legs on the same material, byte for byte, so the exported reader and the one the card sees can never become two |
|
|
269
|
+
| `scripts/run-resume-retry-later-test.mjs` | The two resume refusals that carry a **wait quantity** — the only members of that refusal family that do, which is the whole reason they form a closed set. Carrying a wait is not the same as being the only ones worth waiting on: a sibling refusal in the same family clears on its own and the engine says so in words, it just cannot put a number on it, so *not recognised here* must never be read as *waiting will not help*. One of the two also has a *terminal* upstream branch that arrives under the same code with the distinguishing detail only in prose, so recognition alone is not permission to say "try again": the disposition is decided by **positive evidence** and pinned from both directions — the quota-window code is evidence in itself, the preflight code counts only when the server really supplied a wait (an upstream fact, not a convention: the terminal branch throws with no detail at all, so a wait value cannot reach the client on that path), and a preflight refusal with no wait reads as *undecidable* (say what is true of both branches — nothing was consumed — and leave redeemability to the engine's own line) rather than being rendered as either a retry or an ending. Every other member means waiting will not help (change a setting, relaunch, the retained session is gone), so the recognition is a **closed set of two codes**: widening it to a family prefix would tell half the users to wait and the other half to keep waiting for something that will never arrive, and the negative controls drive exactly those codes through it, plus a same-named code on a different door (the submission-side quota refusal), the two underscore-form siblings, and a code merely quoted inside a message body. The wait value is narrowed to the same domain the server mints it in (a whole number of seconds, at least one): zero, a negative, a fraction and a non-number all read as **no window given** rather than as zero, because a zero tells the caller to retry immediately and the wake-up it would retry is an at-most-once action with real side effects. Reading is structural rather than `instanceof`, since the client is host-injected and the same class name across two bundles is two classes, and a null-prototype plain object must still be recognised. The failure classifier gains this one disposition without any existing one moving, an unknown code still falls to the honest open-set arm and its wait value is **not** believed, and an end-to-end call proves the disposition and the window reach the host while the call itself is still attempted exactly once. The recognised code set is a **frozen array**, not a type-level readonly set: the latter is a plain mutable collection at runtime and the decision reads the same instance, so one `.add` from any consumer would turn a refusal that waiting cannot fix into one that claims it can — the guard proves it by really trying to mutate the exported value and then checking the verdict did not drift |
|
|
268
270
|
|
|
269
271
|
Each suite carries a floor that only moves up — a refactor that stops executing a group of
|
|
270
272
|
assertions is a failure, not a quieter pass. Guards anchor on the **installed artefact's content**
|
|
@@ -180,6 +180,50 @@ export declare const RESUME_AT_ERROR_CODE_PREFIX = "resume_at.";
|
|
|
180
180
|
export declare const REWIND_ERROR_CODE_PREFIXES: readonly ["resume_at.", "rewind_snapshot."];
|
|
181
181
|
/** 该码是否属 rewind/resume 可自解族。缺席 ⇒ false。 */
|
|
182
182
|
export declare function isRewindFamilyCode(code: string | undefined): boolean;
|
|
183
|
+
/**
|
|
184
|
+
* `resume.usage_window_exhausted`(#449 G1,core 5.60.1;server ≥7.47.0)—— 这一行的账本键上,
|
|
185
|
+
* 本部署的**治理窗**满了。core 铸文的两句不变量:**什么都没消费、什么都没解钉** ⇒ 同一个 token
|
|
186
|
+
* 带同一个决议在窗放开后可**直兑**(所以处置是「等」,不是「重开」也不是「改配置」)。
|
|
187
|
+
* ⚠️ 与 429 的 {@link USAGE_WINDOW_EXHAUSTED}(`usage.window_exhausted`,提交面 pre-admission)
|
|
188
|
+
* **同一本账、不同门、不同码**:本码是 resume/decide 腿的 pre-CAS 拒。别把两者合并判。
|
|
189
|
+
*/
|
|
190
|
+
export declare const RESUME_USAGE_WINDOW_EXHAUSTED = "resume.usage_window_exhausted";
|
|
191
|
+
/**
|
|
192
|
+
* `resume.preflight_rejected`(#376,core 5.65 retry-later 形;server ≥7.51.0)—— 部署自己的
|
|
193
|
+
* `RunnerDeps.resumePreflight` 拒了这次 resume(显式拒 / 抛 / 超时 / 答案读不动,四臂一律
|
|
194
|
+
* fail-closed)。它是 CAS 前的**最后一档**,所以什么都没被消费。
|
|
195
|
+
* 🔴 **core 侧另有一条 `terminal` 臂**(行已被这次拒绝的单发 expire CAS 结清、token 不可再赎),
|
|
196
|
+
* 而两臂的**判别位在 message 散文里**。本包**不按文案分臂** —— 按文案分支正是上游改一个词就
|
|
197
|
+
* 静默空转的形。⇒ 消费端能诚实说的只有两臂都成立的那句:「这一拒发生在提交之前,你的决定
|
|
198
|
+
* 没被消费」;「还能不能再赎」交给引擎那行原文去说,别替它下结论。
|
|
199
|
+
*/
|
|
200
|
+
export declare const RESUME_PREFLIGHT_REJECTED = "resume.preflight_rejected";
|
|
201
|
+
/**
|
|
202
|
+
* 时间性拒绝族的**闭集**。
|
|
203
|
+
*
|
|
204
|
+
* 🔴 这是本文件的第二个闭集,但闭的**不是**「`resume.*` 一共有几个码」(那仍是开集,新码照旧
|
|
205
|
+
* 落 {@link isRewindFamilyCode} 之外的开集兜底),闭的是「**server 在哪些码上铸 `retryAfterSec`**」:
|
|
206
|
+
* server 的铸键判据逐字 =「本码 ∧ 有限正数」,两码之外恒缺席;SDK 8.1.0 `classifyApiError` 同样
|
|
207
|
+
* 按这**两个具名码**铸 `ResumeRetryLaterError`(具名分支排在 `resume.` 前缀兜底**之前**)。
|
|
208
|
+
* 🔴 **绝不放宽成 `resume.` 前缀判**:那会把 `retain_off` / `evicted` / `row_gone` 这些**等也没用**
|
|
209
|
+
* 的码一起说成「过会儿再试」—— 一半用户白等,另一半白重开(与 `row_recycling`/`row_gone` 禁合并
|
|
210
|
+
* 同一条纪律)。加成员 = 上游真在新码上铸了 `retryAfterSec`,必须同批带判据。
|
|
211
|
+
*
|
|
212
|
+
* 🔴 **为什么是 `Object.freeze` 的数组而不是 `ReadonlySet`**(异源对抗复审 [medium] 采纳,真病;
|
|
213
|
+
* 与 `interactiveHalt.RUN_LEVEL_STOP_ERROR_CODES`(#363 二轮)**同一条已定谳的病形**):
|
|
214
|
+
* `ReadonlySet<string>` 只在**类型面**只读 —— 运行期它就是一只普通 `Set`,而判定查的是**同一个
|
|
215
|
+
* 实例**。任何 JS 消费者(公面上它是导出的)`.add('resume.row_gone')` 之后,一条「等也没用」的
|
|
216
|
+
* 拒绝就会当场变成带窗的 `retry-later`(实测:`row_gone` + `retryAfterSec:30` 从 `row-gone` 翻成
|
|
217
|
+
* `retry-later`)—— 闭集与「只认正向证据」两道约束一起被绕过。冻结数组在**运行期**真的改不动
|
|
218
|
+
* (ESM 恒 strict:`push`/下标赋值直接抛),于是「公开面」与「判定源」可以安全地是同一个物。
|
|
219
|
+
* ⚠️ 判据形随之从 `.has()` 改成 `.includes()`(与 `parkResolver.GATE_FAILURE_CODES` 同姿势;
|
|
220
|
+
* 闭集只有两员,查找成本不是这里的量)。
|
|
221
|
+
* ⚠️ **同形存量登记**(只登记不顺手改):同文件的 `CONFIG_REFUSAL_CODES` / `DELEGATION_CAP_CODES` /
|
|
222
|
+
* `TOOL_END_INTERRUPTED_CODES` 三张表今天仍是 `ReadonlySet`,同病。它们**已经在公面上**且消费点
|
|
223
|
+
* 用 `.has()` ⇒ 换形是下游 BREAKING(签名从 `ReadonlySet<string>` 变 `readonly string[]`),
|
|
224
|
+
* 不属内容批射程;本条按现状登记,换形另立一批。本位是**新铸**的,所以在出生那天就用对形。
|
|
225
|
+
*/
|
|
226
|
+
export declare const RESUME_RETRY_LATER_CODES: readonly string[];
|
|
183
227
|
/**
|
|
184
228
|
* server 温切 drain 门的 pre-stream 拒收码(503 + `errorCode:"draining"`;server 侧
|
|
185
229
|
* `error:"draining"` 是冻结契约,SDK toApiError 盖成 `errorCode`)。此前壳/包注释各持裸字面。
|
package/dist/engineErrorCodes.js
CHANGED
|
@@ -246,6 +246,70 @@ export const REWIND_ERROR_CODE_PREFIXES = [RESUME_AT_ERROR_CODE_PREFIX, 'rewind_
|
|
|
246
246
|
export function isRewindFamilyCode(code) {
|
|
247
247
|
return typeof code === 'string' && REWIND_ERROR_CODE_PREFIXES.some((p) => code.startsWith(p));
|
|
248
248
|
}
|
|
249
|
+
// ── resume 族的**时间性拒绝**二码(L-102;server ≥7.47.0 / ≥7.51.0,SDK 8.1.0 `ResumeRetryLaterError`)──
|
|
250
|
+
//
|
|
251
|
+
// ⚠️ 与上面的 `resume_at.`(**下划线**,rewind 取址族)是**两族** —— 这里是 `resume.`(**点**,
|
|
252
|
+
// design/122 D2 的 409 合同拒绝族)。同一个词在两个族里,判别靠分隔符,别按 `resume` 子串猜。
|
|
253
|
+
//
|
|
254
|
+
// 语义:下面两码是这一族里**唯一携带可执行等待量**(`retryAfterSec`)的两码 —— 这就是它们成为
|
|
255
|
+
// 一个闭集的**全部理由**。
|
|
256
|
+
// 🔴 **本闭集回答的不是「哪些码可以等」**(异源对抗复审 [medium] 真病修:上一版这段写成「只有下面
|
|
257
|
+
// 两码可能是『现在不行、过一会儿行』」,是**排他性错断**)。族内**明确的反例**就在本文件视野内:
|
|
258
|
+
// `resume.row_recycling` 的 core 铸文逐字「this clears on its own; send again in a moment」——
|
|
259
|
+
// 它可等,只是 server 给不出秒数,所以它**不在**本闭集里、也**不该**在。
|
|
260
|
+
// ⇒ 「本读口返回 `null`」只意味着**没命中这两码**,绝不意味着「等也没用」;闭集外的码照旧走
|
|
261
|
+
// `classifySubagentResumeFailure` 的既有各格(`row-contended` 就是可等的那一格)。
|
|
262
|
+
// 🔴 命中之后**也不等于一定可等**:后一码另有一条 `terminal` 臂(见该码顶注)⇒ 处置由
|
|
263
|
+
// `wireErrorTriage` 的 `waitable` 按**正向证据**判,不由「命中本族」判。此前它们双双落进 `classifySubagentResumeFailure`
|
|
264
|
+
// 的开集兜底 `error` ⇒ server 明明给了「等多久」,到客户端只剩一句泛泛失败(与 0.38.0 收
|
|
265
|
+
// `row_recycling`/`row_gone` 那次同形)。
|
|
266
|
+
/**
|
|
267
|
+
* `resume.usage_window_exhausted`(#449 G1,core 5.60.1;server ≥7.47.0)—— 这一行的账本键上,
|
|
268
|
+
* 本部署的**治理窗**满了。core 铸文的两句不变量:**什么都没消费、什么都没解钉** ⇒ 同一个 token
|
|
269
|
+
* 带同一个决议在窗放开后可**直兑**(所以处置是「等」,不是「重开」也不是「改配置」)。
|
|
270
|
+
* ⚠️ 与 429 的 {@link USAGE_WINDOW_EXHAUSTED}(`usage.window_exhausted`,提交面 pre-admission)
|
|
271
|
+
* **同一本账、不同门、不同码**:本码是 resume/decide 腿的 pre-CAS 拒。别把两者合并判。
|
|
272
|
+
*/
|
|
273
|
+
export const RESUME_USAGE_WINDOW_EXHAUSTED = 'resume.usage_window_exhausted';
|
|
274
|
+
/**
|
|
275
|
+
* `resume.preflight_rejected`(#376,core 5.65 retry-later 形;server ≥7.51.0)—— 部署自己的
|
|
276
|
+
* `RunnerDeps.resumePreflight` 拒了这次 resume(显式拒 / 抛 / 超时 / 答案读不动,四臂一律
|
|
277
|
+
* fail-closed)。它是 CAS 前的**最后一档**,所以什么都没被消费。
|
|
278
|
+
* 🔴 **core 侧另有一条 `terminal` 臂**(行已被这次拒绝的单发 expire CAS 结清、token 不可再赎),
|
|
279
|
+
* 而两臂的**判别位在 message 散文里**。本包**不按文案分臂** —— 按文案分支正是上游改一个词就
|
|
280
|
+
* 静默空转的形。⇒ 消费端能诚实说的只有两臂都成立的那句:「这一拒发生在提交之前,你的决定
|
|
281
|
+
* 没被消费」;「还能不能再赎」交给引擎那行原文去说,别替它下结论。
|
|
282
|
+
*/
|
|
283
|
+
export const RESUME_PREFLIGHT_REJECTED = 'resume.preflight_rejected';
|
|
284
|
+
/**
|
|
285
|
+
* 时间性拒绝族的**闭集**。
|
|
286
|
+
*
|
|
287
|
+
* 🔴 这是本文件的第二个闭集,但闭的**不是**「`resume.*` 一共有几个码」(那仍是开集,新码照旧
|
|
288
|
+
* 落 {@link isRewindFamilyCode} 之外的开集兜底),闭的是「**server 在哪些码上铸 `retryAfterSec`**」:
|
|
289
|
+
* server 的铸键判据逐字 =「本码 ∧ 有限正数」,两码之外恒缺席;SDK 8.1.0 `classifyApiError` 同样
|
|
290
|
+
* 按这**两个具名码**铸 `ResumeRetryLaterError`(具名分支排在 `resume.` 前缀兜底**之前**)。
|
|
291
|
+
* 🔴 **绝不放宽成 `resume.` 前缀判**:那会把 `retain_off` / `evicted` / `row_gone` 这些**等也没用**
|
|
292
|
+
* 的码一起说成「过会儿再试」—— 一半用户白等,另一半白重开(与 `row_recycling`/`row_gone` 禁合并
|
|
293
|
+
* 同一条纪律)。加成员 = 上游真在新码上铸了 `retryAfterSec`,必须同批带判据。
|
|
294
|
+
*
|
|
295
|
+
* 🔴 **为什么是 `Object.freeze` 的数组而不是 `ReadonlySet`**(异源对抗复审 [medium] 采纳,真病;
|
|
296
|
+
* 与 `interactiveHalt.RUN_LEVEL_STOP_ERROR_CODES`(#363 二轮)**同一条已定谳的病形**):
|
|
297
|
+
* `ReadonlySet<string>` 只在**类型面**只读 —— 运行期它就是一只普通 `Set`,而判定查的是**同一个
|
|
298
|
+
* 实例**。任何 JS 消费者(公面上它是导出的)`.add('resume.row_gone')` 之后,一条「等也没用」的
|
|
299
|
+
* 拒绝就会当场变成带窗的 `retry-later`(实测:`row_gone` + `retryAfterSec:30` 从 `row-gone` 翻成
|
|
300
|
+
* `retry-later`)—— 闭集与「只认正向证据」两道约束一起被绕过。冻结数组在**运行期**真的改不动
|
|
301
|
+
* (ESM 恒 strict:`push`/下标赋值直接抛),于是「公开面」与「判定源」可以安全地是同一个物。
|
|
302
|
+
* ⚠️ 判据形随之从 `.has()` 改成 `.includes()`(与 `parkResolver.GATE_FAILURE_CODES` 同姿势;
|
|
303
|
+
* 闭集只有两员,查找成本不是这里的量)。
|
|
304
|
+
* ⚠️ **同形存量登记**(只登记不顺手改):同文件的 `CONFIG_REFUSAL_CODES` / `DELEGATION_CAP_CODES` /
|
|
305
|
+
* `TOOL_END_INTERRUPTED_CODES` 三张表今天仍是 `ReadonlySet`,同病。它们**已经在公面上**且消费点
|
|
306
|
+
* 用 `.has()` ⇒ 换形是下游 BREAKING(签名从 `ReadonlySet<string>` 变 `readonly string[]`),
|
|
307
|
+
* 不属内容批射程;本条按现状登记,换形另立一批。本位是**新铸**的,所以在出生那天就用对形。
|
|
308
|
+
*/
|
|
309
|
+
export const RESUME_RETRY_LATER_CODES = Object.freeze([
|
|
310
|
+
RESUME_USAGE_WINDOW_EXHAUSTED,
|
|
311
|
+
RESUME_PREFLIGHT_REJECTED,
|
|
312
|
+
]);
|
|
249
313
|
// ── drain / 场景执法族(A-028.11/.13 单源化,#244 族E,2026-08-15)────────────────────────────
|
|
250
314
|
/**
|
|
251
315
|
* server 温切 drain 门的 pre-stream 拒收码(503 + `errorCode:"draining"`;server 侧
|
|
@@ -1087,6 +1087,54 @@ export declare function readToolApprovalRespondRefusal(err: unknown): ToolApprov
|
|
|
1087
1087
|
* `AgentEvent` 的臂了(durable 腿也回放),所以结构识别与 union 收窄两条路都成立;本函数仍按
|
|
1088
1088
|
* 结构读(不依赖类型收窄),因为它同时服务 raw SSE 与 durable 回放两条入口。 */
|
|
1089
1089
|
export declare function isToolApprovalFrame(ev: unknown): ev is ToolApprovalFrame;
|
|
1090
|
+
/**
|
|
1091
|
+
* `ruleOffers`(server ≥7.46.0 的判别联合)的结构读。
|
|
1092
|
+
*
|
|
1093
|
+
* 🔴 **[C228]/L-103(0.57.0)起本口是公面**(additive 导出,语义与字节一字未改)。此前它只经
|
|
1094
|
+
* **卡端口**({@link ApprovalCardRequest.ruleOffers})出包 —— 不走卡端口架构的宿主(浏览器端没有
|
|
1095
|
+
* Ink 三选卡,自己拿帧渲)只能在自己那边**重铸一遍**同一把窄读器,而这把窄读器承载的是
|
|
1096
|
+
* **兑付安全**判据(原始下标不前移、逐条丢坏、闭集 kind),重铸一次 = 多一份会各自漂的判官。
|
|
1097
|
+
* ⇒ 公面出口是「判定归包、呈现归端」在这一条腿上的兑现,不是便利函数。
|
|
1098
|
+
* 🔴 **两代 wire 键请走 {@link readRuleOfferSupply}**:本函数只读**新键**(server ≥7.46.0 的
|
|
1099
|
+
* `ruleOffers`);退役键 `ruleSuggestions`(server ≤7.45)的归一在那一口,两键的取舍序也在那里
|
|
1100
|
+
* (新键在场即定局,绝不混编)。手里只有新键才直接用本口。
|
|
1101
|
+
* 🔴 **`offerIndex` 的定义域随腿不同**,消费前必读 {@link RuleOffer} 顶注:活卡帧腿上它是合法
|
|
1102
|
+
* **选择键**(可当 `persistRule.batchOfferIndex` 回兑),durable 行腿上它只是展示/对账座
|
|
1103
|
+
* (server `boundedRuleOffers` 已压紧过一次)——**本函数不知道调用方在哪条腿上**,分辨是调用方的事。
|
|
1104
|
+
*
|
|
1105
|
+
* 🔴 **逐条丢坏、原始下标不前移**:坏 offer 逐条丢弃(一条坏的不该让另一条真的消失,与 server
|
|
1106
|
+
* `boundedRuleOffers` 同向),但留下来的每一条都带**原始 wire 下标** {@link RuleOffer.offerIndex} ——
|
|
1107
|
+
* core 的契约原话:做不到保住原始下标的消费端「must suppress its persistence actions entirely」,
|
|
1108
|
+
* 因为 `batch` 臂的兑付键就是下标。压紧 = 人点的第 k 个与服务端兑的第 k 个指向两条不同规则。
|
|
1109
|
+
* 🔴 `kind` 是**闭集判别位**:不认识的 kind ⇒ 丢这一条(不猜、不降级成 single)。
|
|
1110
|
+
* 全部不合形/非数组/超帽 ⇒ 整体缺席(卡不渲「不再询问」档)。
|
|
1111
|
+
*/
|
|
1112
|
+
export declare function readRuleOffers(v: unknown): RuleOffer[] | undefined;
|
|
1113
|
+
/**
|
|
1114
|
+
* 两代 wire 供给 → **包内单一形**(#334/[5223],0.43.0):新键优先,新键整只读不出来才看旧键。
|
|
1115
|
+
*
|
|
1116
|
+
* 🔴 **[C228]/L-103(0.57.0)起本口是公面**(additive 导出,语义与字节一字未改;0.56.0 及更早的
|
|
1117
|
+
* 内部名是 `readOfferSupply`,**纯改名**没有第二个消费点)。宿主手里拿到的是**一整帧/一整行**,
|
|
1118
|
+
* 上面同时可能有 `ruleOffers`(新)与 `ruleSuggestions`(旧)两个键 —— 这一口是三端唯一该调的那个:
|
|
1119
|
+
* `readRuleOfferSupply(frame.ruleOffers, frame.ruleSuggestions)`。
|
|
1120
|
+
* 两代键的取舍序是**判据不是便利**(见下面两段红条),端各写一遍必然在 `null` 那一格上各错一遍。
|
|
1121
|
+
*
|
|
1122
|
+
* 🔴 **新键在场即定局,绝不混编**:新键**有载体**而读出空(数组在但全条坏形、或压根不是数组)
|
|
1123
|
+
* 也**不**回落旧键 —— 一台 7.46 引擎不会同时按两代形铸候选,拿旧键顶上去等于把一份异源素材
|
|
1124
|
+
* 冒充成这次 ask 的候选。
|
|
1125
|
+
*
|
|
1126
|
+
* 🔴 **`null` 与 `undefined` 同视为「新键缺席」**(异源对抗复审 [medium] 追问后的**明示裁定**,
|
|
1127
|
+
* 不是漏判):判据是**代价不对称**——
|
|
1128
|
+
* · 认 null 为缺席的失效面:一台 **7.46** 引擎把新键发成 `null` **且**同时发了旧键。这不可能
|
|
1129
|
+
* 发生:7.46 的三腿上旧键一个字都不铸(engine fixture 直证,同文件 0 命中)⇒ 回落读到的
|
|
1130
|
+
* 只会是 `undefined`,结果与「整只缺席」逐字节相同,没有异源素材可混;
|
|
1131
|
+
* · 认 null 为坏形的失效面:任何把「缺席」序列化成 `null` 的中转层(JSON 规范化包装器、
|
|
1132
|
+
* 某些 SQL/JSONB 读面、mock)会让**所有 ≤7.45 引擎**的「不再询问」档整段消失 —— 那正是
|
|
1133
|
+
* [5223] 这一批要修的病本身,只是换了个触发条件。
|
|
1134
|
+
* ⇒ 取「null == 缺席」。安全面上它**不新增**任何攻击面:一个能塞 `{ruleOffers:null, ruleSuggestions:[…]}`
|
|
1135
|
+
* 的注入面,同样能只塞 `{ruleSuggestions:[…]}`,而后者为了兼容 7.44 本来就必须收。
|
|
1136
|
+
*/
|
|
1137
|
+
export declare function readRuleOfferSupply(offers: unknown, legacy: unknown): RuleOffer[] | undefined;
|
|
1090
1138
|
/** 帧腿的宿主车道参数(#229 respond-note 批,0.29.0)。 */
|
|
1091
1139
|
export interface ToolApprovalFrameLaneOpts {
|
|
1092
1140
|
/**
|
|
@@ -271,13 +271,13 @@ parkGatedCallId) {
|
|
|
271
271
|
// [C170] 答问②半场(0.29.0):durable 富行的两个展示键随卡透传 —— 修前这里是四位闭集,
|
|
272
272
|
// server 7.16.0 起行上就有的 ruleSuggestions/governanceForced 在「行 → 卡」重铸处整段丢失
|
|
273
273
|
// (feed 行原样透传零丢失,丢的只有这处)。governanceForced 条件 stamp 只认 === true(缺席
|
|
274
|
-
// 纪律与活卡腿同款:缺席=无治理来源证据,绝不写 false);候选走
|
|
274
|
+
// 纪律与活卡腿同款:缺席=无治理来源证据,绝不写 false);候选走 readRuleOfferSupply 同一把
|
|
275
275
|
// 合形窄化,落**只读键**(红线见 ApprovalCardRequest.ruleOffersReadOnly 顶注:/decide
|
|
276
276
|
// 无规则位,落可兑付位=假 affordance)。
|
|
277
277
|
// #334(0.43.0):行上的活键换成 `ruleOffers`(server ≥7.46.0 `PendingCheckpoint.ruleOffers`);
|
|
278
278
|
// SDK 7.2.0 的 `PendingCheckpoint` 声明尚无此键 ⇒ **结构视图读**(与本函数下方 `riskDescriptor.probeCause`
|
|
279
279
|
// 同款姿势),旧键 `ruleSuggestions` 保读兼容(7.44 及更旧引擎仍在场),两键经同一把窄读器归一。
|
|
280
|
-
const ruleOffersReadOnly =
|
|
280
|
+
const ruleOffersReadOnly = readRuleOfferSupply(pending.ruleOffers, pending.ruleSuggestions);
|
|
281
281
|
// #280 件1(0.30.4):durable 行腿的探针因由载体 = `riskDescriptor.probeCause`(server 对
|
|
282
282
|
// riskDescriptor 整体透传,与活卡帧同值)。SDK 6.17.2 的 riskDescriptor 声明尚无此键 ⇒ 结构
|
|
283
283
|
// 视图读(与 caps 防御读同款姿势),类型半场候 SDK 班车;载体形不合 ⇒ 不铸键(内部结构不校,
|
|
@@ -842,6 +842,18 @@ const MAX_RULE_OFFER_BATCH_MEMBERS_TOLERATED = 8;
|
|
|
842
842
|
/**
|
|
843
843
|
* `ruleOffers`(server ≥7.46.0 的判别联合)的结构读。
|
|
844
844
|
*
|
|
845
|
+
* 🔴 **[C228]/L-103(0.57.0)起本口是公面**(additive 导出,语义与字节一字未改)。此前它只经
|
|
846
|
+
* **卡端口**({@link ApprovalCardRequest.ruleOffers})出包 —— 不走卡端口架构的宿主(浏览器端没有
|
|
847
|
+
* Ink 三选卡,自己拿帧渲)只能在自己那边**重铸一遍**同一把窄读器,而这把窄读器承载的是
|
|
848
|
+
* **兑付安全**判据(原始下标不前移、逐条丢坏、闭集 kind),重铸一次 = 多一份会各自漂的判官。
|
|
849
|
+
* ⇒ 公面出口是「判定归包、呈现归端」在这一条腿上的兑现,不是便利函数。
|
|
850
|
+
* 🔴 **两代 wire 键请走 {@link readRuleOfferSupply}**:本函数只读**新键**(server ≥7.46.0 的
|
|
851
|
+
* `ruleOffers`);退役键 `ruleSuggestions`(server ≤7.45)的归一在那一口,两键的取舍序也在那里
|
|
852
|
+
* (新键在场即定局,绝不混编)。手里只有新键才直接用本口。
|
|
853
|
+
* 🔴 **`offerIndex` 的定义域随腿不同**,消费前必读 {@link RuleOffer} 顶注:活卡帧腿上它是合法
|
|
854
|
+
* **选择键**(可当 `persistRule.batchOfferIndex` 回兑),durable 行腿上它只是展示/对账座
|
|
855
|
+
* (server `boundedRuleOffers` 已压紧过一次)——**本函数不知道调用方在哪条腿上**,分辨是调用方的事。
|
|
856
|
+
*
|
|
845
857
|
* 🔴 **逐条丢坏、原始下标不前移**:坏 offer 逐条丢弃(一条坏的不该让另一条真的消失,与 server
|
|
846
858
|
* `boundedRuleOffers` 同向),但留下来的每一条都带**原始 wire 下标** {@link RuleOffer.offerIndex} ——
|
|
847
859
|
* core 的契约原话:做不到保住原始下标的消费端「must suppress its persistence actions entirely」,
|
|
@@ -849,7 +861,7 @@ const MAX_RULE_OFFER_BATCH_MEMBERS_TOLERATED = 8;
|
|
|
849
861
|
* 🔴 `kind` 是**闭集判别位**:不认识的 kind ⇒ 丢这一条(不猜、不降级成 single)。
|
|
850
862
|
* 全部不合形/非数组/超帽 ⇒ 整体缺席(卡不渲「不再询问」档)。
|
|
851
863
|
*/
|
|
852
|
-
function readRuleOffers(v) {
|
|
864
|
+
export function readRuleOffers(v) {
|
|
853
865
|
if (!Array.isArray(v))
|
|
854
866
|
return undefined;
|
|
855
867
|
if (v.length === 0 || v.length > MAX_RULE_OFFERS_TOLERATED)
|
|
@@ -923,6 +935,12 @@ function readLegacyRuleSuggestions(v) {
|
|
|
923
935
|
/**
|
|
924
936
|
* 两代 wire 供给 → **包内单一形**(#334/[5223],0.43.0):新键优先,新键整只读不出来才看旧键。
|
|
925
937
|
*
|
|
938
|
+
* 🔴 **[C228]/L-103(0.57.0)起本口是公面**(additive 导出,语义与字节一字未改;0.56.0 及更早的
|
|
939
|
+
* 内部名是 `readOfferSupply`,**纯改名**没有第二个消费点)。宿主手里拿到的是**一整帧/一整行**,
|
|
940
|
+
* 上面同时可能有 `ruleOffers`(新)与 `ruleSuggestions`(旧)两个键 —— 这一口是三端唯一该调的那个:
|
|
941
|
+
* `readRuleOfferSupply(frame.ruleOffers, frame.ruleSuggestions)`。
|
|
942
|
+
* 两代键的取舍序是**判据不是便利**(见下面两段红条),端各写一遍必然在 `null` 那一格上各错一遍。
|
|
943
|
+
*
|
|
926
944
|
* 🔴 **新键在场即定局,绝不混编**:新键**有载体**而读出空(数组在但全条坏形、或压根不是数组)
|
|
927
945
|
* 也**不**回落旧键 —— 一台 7.46 引擎不会同时按两代形铸候选,拿旧键顶上去等于把一份异源素材
|
|
928
946
|
* 冒充成这次 ask 的候选。
|
|
@@ -938,7 +956,7 @@ function readLegacyRuleSuggestions(v) {
|
|
|
938
956
|
* ⇒ 取「null == 缺席」。安全面上它**不新增**任何攻击面:一个能塞 `{ruleOffers:null, ruleSuggestions:[…]}`
|
|
939
957
|
* 的注入面,同样能只塞 `{ruleSuggestions:[…]}`,而后者为了兼容 7.44 本来就必须收。
|
|
940
958
|
*/
|
|
941
|
-
function
|
|
959
|
+
export function readRuleOfferSupply(offers, legacy) {
|
|
942
960
|
// 🔴 `null` 与 `undefined` 同视为缺席(理由见顶注的代价不对称段);其余一切载体 = 新键在场,
|
|
943
961
|
// 读出什么就是什么,**绝不**再看旧键。
|
|
944
962
|
if (offers !== undefined && offers !== null)
|
|
@@ -1010,7 +1028,7 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
|
|
|
1010
1028
|
// 显式 `false`,而缺席的语义是「没有治理来源的证据」,不是「这门可以被表态掀掉」)。
|
|
1011
1029
|
// #334(0.43.0):新键 `ruleOffers`(server ≥7.46.0 判别联合)优先,旧键 `ruleSuggestions`
|
|
1012
1030
|
// (≤7.45)归一成 `kind:'single'` 兜底 —— 两代经同一把窄读器,包内出口只有一个形。
|
|
1013
|
-
const ruleOffers =
|
|
1031
|
+
const ruleOffers = readRuleOfferSupply(frame.ruleOffers, frame.ruleSuggestions);
|
|
1014
1032
|
const card = await surfaceApprovalCard({
|
|
1015
1033
|
toolName,
|
|
1016
1034
|
args: args,
|
|
@@ -1055,7 +1073,7 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
|
|
|
1055
1073
|
? { delegation: frame.delegation }
|
|
1056
1074
|
: {}),
|
|
1057
1075
|
// #225 件1 / #334 换形(0.43.0):规则候选透传(合形项;缺席/坏形 ⇒ 键不 stamp,卡形不渲该档)。
|
|
1058
|
-
// 落位是**包内单一形出口** `ruleOffers`,两代 wire 键同经
|
|
1076
|
+
// 落位是**包内单一形出口** `ruleOffers`,两代 wire 键同经 readRuleOfferSupply 归一。
|
|
1059
1077
|
...(ruleOffers !== undefined ? { ruleOffers } : {}),
|
|
1060
1078
|
// #144:被越级的持久规则原文(UNTRUSTED-for-display)。窄化=**非空白串才 stamp**,坏形降缺席
|
|
1061
1079
|
// (server 明说空串不铸键:「空串是坏值不是『空规则』」)—— 一格空白的规则解释比没有解释更坏。
|
|
@@ -42,6 +42,17 @@
|
|
|
42
42
|
* 这两码在 core 侧早已在铸,是 [4743] #323「completed bg 子代跨重启 SendMessage 复活」把复活裁决
|
|
43
43
|
* 腿变成常走路径之后才真正会被用户撞见;此前它们双双落进开集兜底,两个不同的下一步被压成一句
|
|
44
44
|
* 泛泛失败。
|
|
45
|
+
* ⚠️ **0.57.0(L-102)补第九、十格**:`resume.usage_window_exhausted` / `resume.preflight_rejected`
|
|
46
|
+
* 是这一族里**唯一带得出「等多久」**的两码(`retryAfterSec`,server ≥7.47.0 / ≥7.51.0 起在 409 体上
|
|
47
|
+
* additive 携;SDK 8.1.0 起铸 `ResumeRetryLaterError`)。⚠️ **「唯一带得出等待量」≠「唯一可等」**:
|
|
48
|
+
* 同族的 `resume.row_recycling` 也可等(core 铸文逐字「this clears on its own; send again in a
|
|
49
|
+
* moment」= 本文件的 `row-contended` 格),只是 server 给不出秒数 —— 闭集之外不等于「等也没用」。同上一次的机理:两码此前双双落进开集兜底,
|
|
50
|
+
* server 明明给了等待窗,到端只剩一句泛泛失败。判型读口在
|
|
51
|
+
* `wireErrorTriage.resumeRetryLaterFromError`(三端共用,不走本腿的宿主也吃这两码)。
|
|
52
|
+
* 🔴 **两码不是一格而是两格**(异源对抗复审 [medium] 真病修):`resume.preflight_rejected` 有一条
|
|
53
|
+
* `terminal` 臂(token 不可再赎)而判别位只在 message 散文里 ⇒ 仅凭码就归 `retry-later` 会把终局
|
|
54
|
+
* 说成暂时等待。按**正向证据**分:有证据 ⇒ `retry-later`;没有 ⇒ `refused-preflight`(**不可判**,
|
|
55
|
+
* 不是「不可重试」)。证据判据收在读口的 `waitable` 一位上,本层不复判。
|
|
45
56
|
*
|
|
46
57
|
* ── UNTRUSTED ───────────────────────────────────────────────────────────────────────────────
|
|
47
58
|
* 收据 `note` 是 server 铸的文案(引擎会把子代名字拼进去,名字是 spawning model 的自由文本)——
|
|
@@ -106,6 +117,39 @@ export type SubagentResumeFailureKind =
|
|
|
106
117
|
* 两码合并 = 一半用户白等、另一半白重开。
|
|
107
118
|
*/
|
|
108
119
|
| 'row-gone'
|
|
120
|
+
/**
|
|
121
|
+
* `resume.usage_window_exhausted` / `resume.preflight_rejected`(L-102,0.57.0;server ≥7.47.0 /
|
|
122
|
+
* ≥7.51.0,SDK 8.1.0 `ResumeRetryLaterError`)—— **有证据表明现在不行、过一会儿行**,也是这一族里
|
|
123
|
+
* **唯一带得出「等多久」**的一格({@link SubagentResumeFailureVerdict.retryAfterSec})。
|
|
124
|
+
* ⚠️ 「唯一带得出等待量」**不是**「唯一可等」——`row-contended` 那一格同样可等(窗口自清),
|
|
125
|
+
* 只是 server 给不出秒数。两件事别混。
|
|
126
|
+
*
|
|
127
|
+
* 🔴 与 `row-contended` **不合并**(两者都是「等」,但不是同一件事,也不是同一个量):
|
|
128
|
+
* `row-contended` 是**行级**的瞬时争用(另一个复活 claim / 一次 reap sweep 正持着它),窗口
|
|
129
|
+
* 自清、server 给不出秒数;本格是**部署级**的时间性拒绝(治理窗满 / 预检拒),server 明确给了
|
|
130
|
+
* 建议等待窗。合并会把一个有确定等待量的格说成「过一会儿再试试」,或者反过来给一个没有窗的
|
|
131
|
+
* 格编一个倒计时。
|
|
132
|
+
* 🔴 与 `retention-lapsed` / `row-gone` / `retain-off` **方向相反**:那三格**等也没用**。
|
|
133
|
+
* 🔴 处置 = **等,不是重发**:resume 是 AT-MOST-ONCE 的有副作用动作,本层照旧一格都不重试 ——
|
|
134
|
+
* 「可以再试」是说给**人**听的,不是给自动重试腿的授权。
|
|
135
|
+
* 🔴 **本格只收有正向证据的那些**(异源对抗复审 [medium] 真病修):`resume.usage_window_exhausted`
|
|
136
|
+
* 恒进本格(core 铸文的不变量就是证据);`resume.preflight_rejected` 只在 server 真给了等待窗
|
|
137
|
+
* 时进本格,否则落 {@link SubagentResumeFailureKind} 的 `'refused-preflight'`。
|
|
138
|
+
*/
|
|
139
|
+
| 'retry-later'
|
|
140
|
+
/**
|
|
141
|
+
* `resume.preflight_rejected` **且 server 没给等待窗**(L-102,0.57.0)—— 部署自己的 resume 预检拒了
|
|
142
|
+
* 这次,它是提交前的最后一档,所以**什么都没被消费**;但**还能不能再赎不可判**。
|
|
143
|
+
*
|
|
144
|
+
* 🔴 与 `retry-later` **禁合并**(异源对抗复审 [medium] 立的格):core 在这一码上有两条臂 ——
|
|
145
|
+
* `retry_later`(行留 pending,同一 token 可再赎)与 `terminal`(行已被单发 expire CAS 结清,
|
|
146
|
+
* token 不可再赎)——而**判别位在 message 散文里**,wire 上没有机读位。把没有证据的那些一律
|
|
147
|
+
* 渲成「稍后重试」,就是把一个终局说成暂时等待;而本包**不按文案分臂**(按文案分支 = 上游改
|
|
148
|
+
* 一个词就静默空转)。⇒ 诚实的第三条路:**说两条臂都成立的那句**,把「还能不能再赎」交给
|
|
149
|
+
* 引擎那行原文({@link SubagentResumeFailureVerdict.detail})。
|
|
150
|
+
* 🔴 **它不是「不可重试」**:`false` 只是「不可判」。端**不许**渲成终局,也**不许**渲成「稍后重试」。
|
|
151
|
+
*/
|
|
152
|
+
| 'refused-preflight'
|
|
109
153
|
/** 404 —— 未知 run / 非属主(**无存在性谕示**,两者同形)。 */
|
|
110
154
|
| 'not-found'
|
|
111
155
|
/** 400 —— 空 content 等入参问题。 */
|
|
@@ -115,20 +159,52 @@ export type SubagentResumeFailureKind =
|
|
|
115
159
|
export type SubagentResumeOutcome = {
|
|
116
160
|
ok: true;
|
|
117
161
|
receipt: string;
|
|
118
|
-
}
|
|
162
|
+
}
|
|
163
|
+
/** 失败臂 = `ok:false` + {@link SubagentResumeFailureVerdict} 的四位(0.57.0 起含 `retryAfterSec`
|
|
164
|
+
* 与 `code`;两位都只在 L-102 那两格上可能在场。additive,既有按 `reason`/`detail` 读的宿主
|
|
165
|
+
* 一字不用改)。 */
|
|
166
|
+
| {
|
|
119
167
|
ok: false;
|
|
120
168
|
reason: SubagentResumeFailureKind;
|
|
121
169
|
detail: string;
|
|
170
|
+
retryAfterSec?: number;
|
|
171
|
+
code?: string;
|
|
122
172
|
};
|
|
123
173
|
/**
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
* 分得开,按数字分支等于把六种处置压成一种)。
|
|
174
|
+
* {@link classifySubagentResumeFailure} 的判决(命名形;0.57.0 因 `retryAfterSec` 位达 3 成员抽名 ——
|
|
175
|
+
* 导出签名里不留 ≥3 成员的内联匿名形,typeshape B4 口径。结构与 0.56.0 的内联形逐字兼容)。
|
|
127
176
|
*/
|
|
128
|
-
export
|
|
177
|
+
export interface SubagentResumeFailureVerdict {
|
|
129
178
|
reason: SubagentResumeFailureKind;
|
|
179
|
+
/** server 的原话(UNTRUSTED;呈前由调用方消毒截长)。 */
|
|
130
180
|
detail: string;
|
|
131
|
-
|
|
181
|
+
/**
|
|
182
|
+
* **只在** `reason === 'retry-later'` 上可能在场 —— server 给的建议等待秒数(整数 ≥1)。
|
|
183
|
+
* 🔴 **缺席 ≠ 0**:缺席 = 服务端没给窗(老引擎 / 老 SDK 映射 / 该腿不发头),绝不渲一个编出来的
|
|
184
|
+
* 倒计时;别的 `reason` 上本位恒缺席。窄读域见 `resumeRetryLaterFromError`(同包
|
|
185
|
+
* `wireErrorTriage.ts`)。
|
|
186
|
+
* 🔴 **缺席时该说什么由 `reason` 决定,不由本位决定**:`retry-later` 且窗缺席(= 治理窗满而 server
|
|
187
|
+
* 没给数字)⇒ 说「稍后重试」但不给数字;`refused-preflight` ⇒ **不许**说「稍后重试」。
|
|
188
|
+
* 🔴 `reason === 'refused-preflight'` 上本位**恒缺席**——它正是「没有窗」那一格的定义。
|
|
189
|
+
*/
|
|
190
|
+
retryAfterSec?: number;
|
|
191
|
+
/**
|
|
192
|
+
* **只在** `reason` 是 `'retry-later'` / `'refused-preflight'` 两格上在场 —— 命中的那一个 wire 码
|
|
193
|
+
* (`resume.usage_window_exhausted` / `resume.preflight_rejected`)。
|
|
194
|
+
*
|
|
195
|
+
* 🔴 **为什么处置分了格还要把码带出来**:`retry-later` 这一格今天有两个来源(治理窗满 / 带窗的
|
|
196
|
+
* 预检拒),而端的诚实措辞不同 —— 前者说「本部署的用量窗满了」,后者说「部署的预检暂时拒了」。
|
|
197
|
+
* `detail` 是 server 的散文,**不是**机读位,别从它反解码。
|
|
198
|
+
* 别的 `reason` 上本位恒缺席(闭集之外本包不认得码,也就不替 server 声明成因)。
|
|
199
|
+
*/
|
|
200
|
+
code?: string;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* resume 失败 → 处置分类。机器轴 = `errorCode`(开集);`status` 只用于**码缺席**时的两格粗分
|
|
204
|
+
* (404 无存在性谕示 / 400 入参),绝不用数字去猜某个具体 409 成因(那些 409 只有 `errorCode`
|
|
205
|
+
* 分得开,按数字分支等于把好几种处置压成一种)。
|
|
206
|
+
*/
|
|
207
|
+
export declare function classifySubagentResumeFailure(e: unknown): SubagentResumeFailureVerdict;
|
|
132
208
|
/**
|
|
133
209
|
* 「这一行子代该打到哪条 run」——**纯函数,单点判据**(对抗复审 H1 的修 + 它的可证伪点)。
|
|
134
210
|
*
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { hostLog } from '../host.js';
|
|
2
|
+
import { resumeRetryLaterFromError } from '../wireErrorTriage.js';
|
|
2
3
|
import { engineCapTrue } from '../engineCapsCache.js';
|
|
3
4
|
import { makeEngineWireClient } from '../engineWireSdk.js';
|
|
4
5
|
import { engineWireTargetFor } from '../engineWireTarget.js';
|
|
@@ -51,11 +52,27 @@ function shapeOf(e) {
|
|
|
51
52
|
}
|
|
52
53
|
/**
|
|
53
54
|
* resume 失败 → 处置分类。机器轴 = `errorCode`(开集);`status` 只用于**码缺席**时的两格粗分
|
|
54
|
-
* (404 无存在性谕示 / 400 入参),绝不用数字去猜某个具体 409 成因(
|
|
55
|
-
*
|
|
55
|
+
* (404 无存在性谕示 / 400 入参),绝不用数字去猜某个具体 409 成因(那些 409 只有 `errorCode`
|
|
56
|
+
* 分得开,按数字分支等于把好几种处置压成一种)。
|
|
56
57
|
*/
|
|
57
58
|
export function classifySubagentResumeFailure(e) {
|
|
58
59
|
const { status, errorCode, message } = shapeOf(e);
|
|
60
|
+
// L-102(0.57.0):时间性拒绝二码 —— **先判具名闭集**(与 SDK `classifyApiError` 同序:具名码排在
|
|
61
|
+
// `resume.` 前缀兜底之前)。此前这两码双双落进末尾的开集兜底 `error`,于是 server 明明给了
|
|
62
|
+
// 「等多久」,到端只剩一句泛泛失败(与 #318 件④ 收 `row_recycling`/`row_gone` 那次同形)。
|
|
63
|
+
// 读口在 `wireErrorTriage`,不在这里就地重判:同一把窄读器还要服务不走本腿的宿主(decide/park
|
|
64
|
+
// 腿也吃这两码),两处各写一遍就是两个会各自漂的判官。
|
|
65
|
+
const retryLater = resumeRetryLaterFromError(e);
|
|
66
|
+
if (retryLater !== null) {
|
|
67
|
+
// 🔴 **按证据分两格,不按码分**:`waitable` 是读口那一位(全包单一判断点)——`terminal` 臂的
|
|
68
|
+
// 预检拒没有可等证据,归 `refused-preflight`(不可判),绝不渲成「稍后重试」。
|
|
69
|
+
return {
|
|
70
|
+
reason: retryLater.waitable ? 'retry-later' : 'refused-preflight',
|
|
71
|
+
detail: message,
|
|
72
|
+
code: retryLater.code,
|
|
73
|
+
...(retryLater.retryAfterSec !== undefined ? { retryAfterSec: retryLater.retryAfterSec } : {}),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
59
76
|
if (errorCode === 'resume.retain_off')
|
|
60
77
|
return { reason: 'retain-off', detail: message };
|
|
61
78
|
if (errorCode !== undefined && RETENTION_LAPSED_CODES.has(errorCode)) {
|
|
@@ -115,3 +115,76 @@ export interface ScenarioDenyDetail {
|
|
|
115
115
|
* 🔴 键位([2055] 死键纪律):只认 `errorCode`,退役 `code` 键不做兼容(clean-cut)。
|
|
116
116
|
*/
|
|
117
117
|
export declare function scenarioDenyFromError(err: unknown): ScenarioDenyDetail | null;
|
|
118
|
+
/**
|
|
119
|
+
* {@link resumeRetryLaterFromError} 的结构化读数。
|
|
120
|
+
*
|
|
121
|
+
* 判定层**不依赖 SDK 类型面**(与 {@link ScenarioDenyDetail} 同款):注入面可能是宿主的裸 fetch、
|
|
122
|
+
* desktop 的 IPC 转投、web 跨 bundle 的 plain object,也可能是比本包新一版的 SDK ——
|
|
123
|
+
* 这些形上 `instanceof ResumeRetryLaterError` 一律为假,而 `errorCode` 恒在。
|
|
124
|
+
*/
|
|
125
|
+
export interface ResumeRetryLaterDetail {
|
|
126
|
+
/**
|
|
127
|
+
* 命中的那一个码({@link RESUME_RETRY_LATER_CODES} 的成员之一)。
|
|
128
|
+
* 🔴 **两码不是同一个处置**:能不能等看 {@link waitable}(本码 ∧ 有没有窗),本位只回答「是哪一个
|
|
129
|
+
* 成因」—— 治理窗满(与部署的账本配额有关)还是部署预检拒(与部署自己那只 `resumePreflight`
|
|
130
|
+
* 有关)。排障要分得清,措辞也要分得清;**别拿本位反推可等性**。
|
|
131
|
+
*/
|
|
132
|
+
code: string;
|
|
133
|
+
/**
|
|
134
|
+
* server 给的**等待秒数**。
|
|
135
|
+
* 🔴 **缺席 = 服务端没给窗**,不是 0、不是「立刻」—— 绝不渲一个编出来的倒计时
|
|
136
|
+
* ([honest-absence-not-fabricated-zero])。≤7.46 引擎、以及**把这条错误经 SDK ≤8.0 映射
|
|
137
|
+
* 过来**的宿主(那些版本上本码落无字段的族基类 `SubagentResumeConflictError`)都恒缺席。
|
|
138
|
+
* 🔴 **「缺席时该说什么」不由本位决定,由 {@link waitable} 决定**:`waitable === true` 而窗缺席
|
|
139
|
+
* (治理窗满、或老 SDK 把窗吞了)⇒ 说「稍后重试」但不给数字;`waitable === false` ⇒ **不许**
|
|
140
|
+
* 说「稍后重试」(见那一位顶注:那是不可判,不是可等)。
|
|
141
|
+
*/
|
|
142
|
+
retryAfterSec?: number;
|
|
143
|
+
/**
|
|
144
|
+
* **等一会儿到底有没有用** —— 本口唯一的处置位,也是全包对这个问题的**单一判断点**。
|
|
145
|
+
*
|
|
146
|
+
* 🔴 **为什么不是「命中本族即可等」**(异源对抗复审 [medium] 真病修):`resume.preflight_rejected`
|
|
147
|
+
* 在 core 侧有**两条臂** —— 缺省的 `retry_later`(行留 pending,同一 token 障碍清除后仍可赎)
|
|
148
|
+
* 与显式 `terminal`(行已被这次拒绝的单发 expire CAS 结清,token **不可再赎**)——而**判别位
|
|
149
|
+
* 在 message 散文里**,wire 上没有机读位。仅凭码就宣告「稍后重试」,会把一个终局说成暂时等待。
|
|
150
|
+
* 本包**不按文案分臂**(按文案分支正是上游改一个词就静默空转的形),所以只认**正向证据**:
|
|
151
|
+
* · `resume.usage_window_exhausted` ⇒ 恒 `true` —— core 铸文的不变量是「什么都没消费、
|
|
152
|
+
* 什么都没解钉,同一 token 带同一决议在窗放开后可直兑」,这一码本身就是证据;
|
|
153
|
+
* · `resume.preflight_rejected` ⇒ **只有** server 给了等待窗({@link retryAfterSec} 在场)才 `true`;
|
|
154
|
+
* 窗缺席时是 `false` = **不可判**,不是「不可重试」。
|
|
155
|
+
* 🔴 **「有窗 ⇒ 一定是 retry_later 臂」是直证不是推断**(core 7.3.1 fixture 直读
|
|
156
|
+
* `@sema-agent/core/dist/core/runner/runtask.js` 的 `resumePreflight` 拒绝段):
|
|
157
|
+
* · `terminal` 臂(`disposition === 'terminal'` ∧ 有可读 message ⇒ 单发 `store.expire` CAS 结清、
|
|
158
|
+
* 文末逐字 "the token is not redeemable")抛的 `CheckpointError` **一个 detail 都不带** ⇒
|
|
159
|
+
* `retryAfterMs` 结构上不存在 ⇒ server 那一侧无从铸 `retryAfterSec`;
|
|
160
|
+
* · `retry_later` 臂(超时/崩溃/普通拒三支,文末逐字 "the checkpoint stays pending and the same
|
|
161
|
+
* token is redeemable once the obstacle clears")**只在部署真给了 `retryAfterMs` 时**带 detail。
|
|
162
|
+
* ⇒ 窗**在场**是 retry_later 的充分证据;窗**缺席**两臂都可能(retry_later 也常常没有建议),
|
|
163
|
+
* 所以缺席只能读作「不知道」。
|
|
164
|
+
* 🔴 `false` 的正确读法是「**我不知道还能不能再赎**,读引擎那行原文」——**不是**「一定不能」。
|
|
165
|
+
* 两臂都成立的那句话仍可放心说:这一拒发生在提交之前,人的决定没被消费。
|
|
166
|
+
*/
|
|
167
|
+
waitable: boolean;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* 被 catch 的错误 → resume **时间性拒绝**读数;不是那两个码 ⇒ `null`(绝不误吃这一族里别的 409)。
|
|
171
|
+
*
|
|
172
|
+
* 🔴 **判据只有 `errorCode`,不看 HTTP 数字**:与 `classifySubagentResumeFailure`(同包
|
|
173
|
+
* `subagent/engineSubagentResume.ts`)的既有口径同律(`resume.*` 那一族成员全是 409,按数字分支
|
|
174
|
+
* 等于把几种不同处置压成一种);而本口的
|
|
175
|
+
* 处置是**告诉人等一会儿**——非破坏性,不需要 {@link scenarioDenyFromError} 那种「码 ∧ 状态」的
|
|
176
|
+
* 合取闸(那一条守的是别让一个只是**引用**了该码的响应驱动一次真动作)。
|
|
177
|
+
* 🔴 **结构读不 `instanceof`**:见 {@link ResumeRetryLaterDetail} 顶注(三端注入面 / 跨 bundle 同名类
|
|
178
|
+
* 是两个实例)。键位只认 `errorCode`([2055] 死键纪律,退役 `code` 槽不做兼容)。
|
|
179
|
+
* 🔴 **本口只报事实,处置位是 {@link ResumeRetryLaterDetail.waitable}**:命中本族 **≠** 一定可等 ——
|
|
180
|
+
* `resume.preflight_rejected` 有一条 `terminal` 臂(token 不可再赎)而判别位只在 message 散文里。
|
|
181
|
+
* 「等一会儿有没有用」的判断收在 `waitable` 这一位上(全包单一判断点),名字里的 RetryLater 是
|
|
182
|
+
* 上游 SDK 的类名锚,**不是**本口对每一次命中的断言。
|
|
183
|
+
* 🔴 **`retryAfterSec` 的窄读域 = server 的铸键域**(整数 ∧ ≥1),不更宽也不更窄:
|
|
184
|
+
* · server 铸键逐字是「ms → 秒**向上取整**、**下限 1**」⇒ 真供给里不存在 0 / 负数 / 小数;
|
|
185
|
+
* · 放行 0 就是对消费端说「立刻重试」,而 resume 是 AT-MOST-ONCE 的有副作用动作
|
|
186
|
+
* (叫醒 = 真跑一轮)—— 一个 0 会把「等一会儿」变成热循环;
|
|
187
|
+
* · 放行小数会让端渲出「等 0.4 秒」这种上游从未说过的量。
|
|
188
|
+
* 坏值一律**降缺席**(不是降 0、不是取绝对值):没读到窗与读到一个假窗,前者诚实。
|
|
189
|
+
*/
|
|
190
|
+
export declare function resumeRetryLaterFromError(err: unknown): ResumeRetryLaterDetail | null;
|
package/dist/wireErrorTriage.js
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* `failed to fetch`/`networkerror`/`network error`/`load failed`,那是宿主词,由 web 在自己那半场
|
|
25
25
|
* 叠加)。web 的 run-error-table-parity-test(逐 token 读壳源码)随本件退役为「共用同一 import」。
|
|
26
26
|
*/
|
|
27
|
-
import { DRAINING_ERROR_CODE, RESUME_AT_ERROR_CODE_PREFIX, SCENARIO_NOT_ALLOWED_ERROR_CODE, } from './engineErrorCodes.js';
|
|
27
|
+
import { DRAINING_ERROR_CODE, RESUME_AT_ERROR_CODE_PREFIX, RESUME_RETRY_LATER_CODES, RESUME_USAGE_WINDOW_EXHAUSTED, SCENARIO_NOT_ALLOWED_ERROR_CODE, } from './engineErrorCodes.js';
|
|
28
28
|
/**
|
|
29
29
|
* 网络/传输层失败的词面基表(壳 seamQuery「件2c transport 收窄」的那条正则逐字)。
|
|
30
30
|
* 判据变更义务:加词=各端跟批;删词/改形=先与消费端对表(web 叠加宿主词的半场见其
|
|
@@ -215,3 +215,42 @@ export function scenarioDenyFromError(err) {
|
|
|
215
215
|
: [];
|
|
216
216
|
return { allowlist };
|
|
217
217
|
}
|
|
218
|
+
/**
|
|
219
|
+
* 被 catch 的错误 → resume **时间性拒绝**读数;不是那两个码 ⇒ `null`(绝不误吃这一族里别的 409)。
|
|
220
|
+
*
|
|
221
|
+
* 🔴 **判据只有 `errorCode`,不看 HTTP 数字**:与 `classifySubagentResumeFailure`(同包
|
|
222
|
+
* `subagent/engineSubagentResume.ts`)的既有口径同律(`resume.*` 那一族成员全是 409,按数字分支
|
|
223
|
+
* 等于把几种不同处置压成一种);而本口的
|
|
224
|
+
* 处置是**告诉人等一会儿**——非破坏性,不需要 {@link scenarioDenyFromError} 那种「码 ∧ 状态」的
|
|
225
|
+
* 合取闸(那一条守的是别让一个只是**引用**了该码的响应驱动一次真动作)。
|
|
226
|
+
* 🔴 **结构读不 `instanceof`**:见 {@link ResumeRetryLaterDetail} 顶注(三端注入面 / 跨 bundle 同名类
|
|
227
|
+
* 是两个实例)。键位只认 `errorCode`([2055] 死键纪律,退役 `code` 槽不做兼容)。
|
|
228
|
+
* 🔴 **本口只报事实,处置位是 {@link ResumeRetryLaterDetail.waitable}**:命中本族 **≠** 一定可等 ——
|
|
229
|
+
* `resume.preflight_rejected` 有一条 `terminal` 臂(token 不可再赎)而判别位只在 message 散文里。
|
|
230
|
+
* 「等一会儿有没有用」的判断收在 `waitable` 这一位上(全包单一判断点),名字里的 RetryLater 是
|
|
231
|
+
* 上游 SDK 的类名锚,**不是**本口对每一次命中的断言。
|
|
232
|
+
* 🔴 **`retryAfterSec` 的窄读域 = server 的铸键域**(整数 ∧ ≥1),不更宽也不更窄:
|
|
233
|
+
* · server 铸键逐字是「ms → 秒**向上取整**、**下限 1**」⇒ 真供给里不存在 0 / 负数 / 小数;
|
|
234
|
+
* · 放行 0 就是对消费端说「立刻重试」,而 resume 是 AT-MOST-ONCE 的有副作用动作
|
|
235
|
+
* (叫醒 = 真跑一轮)—— 一个 0 会把「等一会儿」变成热循环;
|
|
236
|
+
* · 放行小数会让端渲出「等 0.4 秒」这种上游从未说过的量。
|
|
237
|
+
* 坏值一律**降缺席**(不是降 0、不是取绝对值):没读到窗与读到一个假窗,前者诚实。
|
|
238
|
+
*/
|
|
239
|
+
export function resumeRetryLaterFromError(err) {
|
|
240
|
+
if (typeof err !== 'object' || err === null)
|
|
241
|
+
return null;
|
|
242
|
+
const e = err;
|
|
243
|
+
const code = e.errorCode;
|
|
244
|
+
if (typeof code !== 'string' || !RESUME_RETRY_LATER_CODES.includes(code))
|
|
245
|
+
return null;
|
|
246
|
+
const sec = e.retryAfterSec;
|
|
247
|
+
// 局部名刻意不叫 `window`:本包出浏览器包(portability 门真跑 esbuild --platform=browser),
|
|
248
|
+
// 一个遮蔽宿主全局的同名局部变量在阅读期就是噪音。
|
|
249
|
+
const windowSec = typeof sec === 'number' && Number.isInteger(sec) && sec >= 1 ? sec : undefined;
|
|
250
|
+
return {
|
|
251
|
+
code,
|
|
252
|
+
// 见 {@link ResumeRetryLaterDetail.waitable}:治理窗满这一码本身即证据;预检拒只认「server 真给了窗」。
|
|
253
|
+
waitable: code === RESUME_USAGE_WINDOW_EXHAUSTED || windowSec !== undefined,
|
|
254
|
+
...(windowSec !== undefined ? { retryAfterSec: windowSec } : {}),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
| peer:wire 契约 | `@sema-agent/sdk` **>=7.4.0**(value-level,非 type-only;0.48.0 抬版,四条硬理由见 `CHANGELOG.md` 0.48.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
|
-
| 公开导出面 | **
|
|
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` —— **别手抄进别处,以该文件为准** |
|
|
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`(**
|
|
114
|
+
> 全集真源 = `scripts/public-export-baseline.json` 的 `names`(**821** 项)。
|
|
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
|
-
|
|
138
|
+
821 项的内部构成(帮助端估读表大小):**247** 项是 `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 域,逐域计数之和 =
|
|
143
|
+
### 2b. 域图(16 域,逐域计数之和 = 821)
|
|
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 主战场) |
|
|
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 崩溃收敛读面) |
|
|
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` |
|
|
@@ -158,8 +158,8 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
|
|
|
158
158
|
| 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`) |
|
|
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
|
-
| 15 | **控制面与传输** |
|
|
162
|
-
| 16 | **引擎词汇表与包自检** |
|
|
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` |
|
|
163
163
|
|
|
164
164
|
🔴 **`engineErrorCodes` 的开集纪律**(该文件头注逐字):这些 `ReadonlySet` / 前缀谓词一律是**识别表**,
|
|
165
165
|
回答的是「我认不认得这个码」,**绝不是**「合法码只有这些」。消费点 `switch` **必须留 `default`**,
|
|
@@ -1527,6 +1527,8 @@ 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 板提 |
|
|
1531
|
+
|
|
1530
1532
|
### 7e. 缺口的共同形状(值得单独说)
|
|
1531
1533
|
|
|
1532
1534
|
**P-1 / P-2 / P-3 / P-4 / P-5 / P-6 / P-7 是同一类**:上游(server / SDK)已经把材料铸到 wire 上了,
|
|
@@ -2867,3 +2869,172 @@ CC 2.1.250 语料**无同形提示行** —— `first token` / `firstToken` / `f
|
|
|
2867
2869
|
`CHROME_ARM_TABLE` 加员)。
|
|
2868
2870
|
**常驻门**:`scripts/run-type-superset-ledger-test.mjs`(两个 `_sema_` 键登记)、
|
|
2869
2871
|
`scripts/run-integration-doc-freshness-test.mjs` ③ §3c(臂表双向对账)。
|
|
2872
|
+
|
|
2873
|
+
---
|
|
2874
|
+
|
|
2875
|
+
## §20 🆕 「不再询问」候选窄读器的公面(0.57.0;L-103 / [C228]、[C233]②)
|
|
2876
|
+
|
|
2877
|
+
### 20a. 修的是哪一形
|
|
2878
|
+
|
|
2879
|
+
两代 wire 键(`ruleOffers`,server ≥7.46.0 / 退役键 `ruleSuggestions`,≤7.45)在 0.56.0 及更早
|
|
2880
|
+
**只经卡端口出包**:归一形 `RuleOffer[]` 唯一的公面落点是 `ApprovalCardRequest.ruleOffers`
|
|
2881
|
+
(活卡帧腿)与 `ApprovalCardRequest.ruleOffersReadOnly`(durable 行腿)。窄读器本身
|
|
2882
|
+
(`readRuleOffers` / `readRuleOfferSupply`)是**模块内私有函数**。
|
|
2883
|
+
|
|
2884
|
+
后果:**不走卡端口架构的宿主**(浏览器端自己拿帧渲,没有 Ink 三选卡)只能在自己那边重铸一遍
|
|
2885
|
+
同一把窄读器。而这把读器承载的不是格式化,是**兑付安全**判据:
|
|
2886
|
+
|
|
2887
|
+
| 判据 | 重铸错了会怎样 |
|
|
2888
|
+
|---|---|
|
|
2889
|
+
| 逐条丢坏、**原始 wire 下标不前移** | 人点的第 k 个与服务端兑的第 k 个指向**两条不同规则** |
|
|
2890
|
+
| `kind` 是闭集判别位 | 不认识的臂被猜成 `single`,渲出一个按下去必被拒的选项 |
|
|
2891
|
+
| 两代取舍序(新键在场即定局 / `null` ≡ 缺席) | 拿旧键素材冒充这次 ask 的候选;或把中转层的 `null` 判成坏形,整段打掉 ≤7.45 引擎的「不再询问」档 |
|
|
2892
|
+
|
|
2893
|
+
0.57.0 把两个口 **additive 推上公面**,语义与字节一字未改。
|
|
2894
|
+
|
|
2895
|
+
### 20b. 端该怎么用(两步)
|
|
2896
|
+
|
|
2897
|
+
1. **手里是一整帧/一整行** ⇒ 调 `readRuleOfferSupply(frame.ruleOffers, frame.ruleSuggestions)`
|
|
2898
|
+
—— 这是三端唯一该调的那个口,两代键的取舍序在它里面。
|
|
2899
|
+
2. **手里只有新键** ⇒ 调 `readRuleOffers(offers)`。
|
|
2900
|
+
|
|
2901
|
+
返回 `RuleOffer[]`(至少一条)或 `undefined`(= 这张卡没有可渲的「不再询问」档)。
|
|
2902
|
+
🔴 **`undefined` 不是空数组**:空数组会被渲成「有这个档、里面一条都没有」,而真相是「没有档」。
|
|
2903
|
+
|
|
2904
|
+
### 20c. 🔴 五条读法(端不许自己重判,也不许自己放宽)
|
|
2905
|
+
|
|
2906
|
+
1. **`offerIndex` 的定义域随腿不同,函数不知道你在哪条腿上**:
|
|
2907
|
+
· **活卡帧腿**(`tool_approval` 帧的 `ruleOffers`)—— server 同步腿是纯前缀截、零逐条丢弃 ⇒
|
|
2908
|
+
它恒等于引擎侧的 offer index,**是**合法选择键,批臂按它回兑
|
|
2909
|
+
(respond 体 `persistRule.batchOfferIndex`);
|
|
2910
|
+
· **durable 行腿**(`/v1/approvals` 行的 `ruleOffers`)—— server 已逐条丢弃并压紧过一次 ⇒
|
|
2911
|
+
行上的下标本就不是引擎的 offer index,而这条腿**根本没有兑付口**(`/decide` 体无规则位)。
|
|
2912
|
+
那一位只是展示/对账座,**禁**当选择键。
|
|
2913
|
+
2. **逐条丢坏,不整只拒**:一条坏 offer 不该让另一条真 offer 消失(与 server 侧同向)。
|
|
2914
|
+
但**留下来的每一条都带原始下标**——压紧下标是本节表里第一行那条失效形。
|
|
2915
|
+
3. **batch 的成员不逐条丢**:合取批是「对全体成员一次说是」,任一成员不合形 ⇒ **整条 batch 丢**
|
|
2916
|
+
(兄弟 offer 不连坐)。渲一个缺了成员的批 = 人点的与引擎兑的不是同一批规则。
|
|
2917
|
+
4. **`uncoveredSegments` 是诚实余量披露,不是装饰位**:非「有限非负整数」⇒ 整条 batch 丢。
|
|
2918
|
+
渲一个编出来的 `0` 等于对用户说「兑完这批就全覆盖了」,而那句话没有证据。
|
|
2919
|
+
`0` 本身是**合法读数**。
|
|
2920
|
+
5. **超帽整只降缺席、不截断**:条数 > 8(server 执法帽是 4,包侧刻意留宽)⇒ 整只缺席。
|
|
2921
|
+
截了不留痕会掩盖注入。
|
|
2922
|
+
|
|
2923
|
+
### 20d. 形校验(包侧已做,端不必重做)
|
|
2924
|
+
|
|
2925
|
+
- 一条 offer / 一个 batch 成员共用三位窄化:`rule` 非空串 ∧ `match` ∈ {`exact`,`prefix`} ∧
|
|
2926
|
+
`command` 非空串;batch 成员另加 `segment` 非空串(渲染座,**永不参与裁决**)。
|
|
2927
|
+
- `rule` / `command` / `segment` 三位都是 **UNTRUSTED-for-display**(原始 post-rewrite 命令字节)——
|
|
2928
|
+
包不消毒(消毒单源在端的展示层),端渲之前照自己那套控制字符/长度纪律处理。
|
|
2929
|
+
- 空数组 / 非数组 / `null` / 全条坏形 / 超帽 ⇒ `undefined`。
|
|
2930
|
+
|
|
2931
|
+
### 20e. 与卡端口的关系(不是二选一,是同一把读器的两个出口)
|
|
2932
|
+
|
|
2933
|
+
已经走卡端口的宿主**什么都不用改**:`ApprovalCardRequest.ruleOffers` /
|
|
2934
|
+
`.ruleOffersReadOnly` 仍是同一把读器的产物,字节零变化。公面出口是给**没有卡端口**的宿主准备的
|
|
2935
|
+
第二个观察面。常驻门 ⑦ 段把两者对同一份素材逐字对账 —— 哪天有人在其中一条腿上就地改窄读,当场红。
|
|
2936
|
+
|
|
2937
|
+
**实现锚**:`src/hitl/toolApprovalWire.ts`(`readRuleOffers` / `readRuleOfferSupply` 两个 `export`;
|
|
2938
|
+
归一形 `RuleOffer` / `RuleOfferMatch` / `RuleOfferBatchMember` 与入参形 `WireRuleOffer` 照旧导出)。
|
|
2939
|
+
**常驻门**:`scripts/run-rule-offers-reader-test.mjs`(公面在场 + 两代正控 + 下标不前移 +
|
|
2940
|
+
batch 语义 + 坏形处置 + 取舍序 + 与两条卡端口腿的单一铸点对账)。
|
|
2941
|
+
|
|
2942
|
+
---
|
|
2943
|
+
|
|
2944
|
+
## §21 🆕 resume 族「时间性拒绝」二码的判型(0.57.0;L-102,server ≥7.47.0 / ≥7.51.0,SDK 8.1.0)
|
|
2945
|
+
|
|
2946
|
+
### 21a. 修的是哪一形
|
|
2947
|
+
|
|
2948
|
+
`resume.*` 这一族(409 合同拒绝族)今天有九个成员。下面两个是这一族里**唯一带得出「等多久」**
|
|
2949
|
+
(`retryAfterSec`)的两个 —— 这是它们被收成一个闭集的**全部理由**:
|
|
2950
|
+
|
|
2951
|
+
| 码 | 语义 | server 起版 |
|
|
2952
|
+
|---|---|---|
|
|
2953
|
+
| `resume.usage_window_exhausted` | 这一行的账本键上,本部署的**治理窗**满了。什么都没消费、什么都没解钉 ⇒ 同一个 token 带同一个决议在窗放开后**可直兑** | ≥7.47.0 |
|
|
2954
|
+
| `resume.preflight_rejected` | 部署自己的 resume 预检拒了这次(显式拒 / 抛 / 超时 / 读不动,四臂一律 fail-closed)。它是提交前的**最后一档**,所以什么都没被消费 | ≥7.51.0 |
|
|
2955
|
+
|
|
2956
|
+
两码的 409 体 additive 携 `retryAfterSec`(秒,`ceil`,**下限 1**;server 的铸键判据闭集恰是
|
|
2957
|
+
「本码 ∧ 有限正数」,两码之外恒缺席)。SDK 8.1.0 起按这两个具名码铸 `ResumeRetryLaterError`。
|
|
2958
|
+
|
|
2959
|
+
🔴 **本闭集不是「哪些码可以等」的名单**(端最容易读错的一句):族内有**明确的可等反例** ——
|
|
2960
|
+
`resume.row_recycling`(core 铸文逐字 "this clears on its own; send again in a moment",本包既有
|
|
2961
|
+
处置 `row-contended`),它可等,只是 server 给不出秒数,所以它**不在**这个闭集里。
|
|
2962
|
+
⇒ `resumeRetryLaterFromError()` 返回 `null` 只意味着**没命中这两码**,**不意味着「等也没用」**;
|
|
2963
|
+
闭集外的码照旧按既有分类结果处置。把 `null` 读成「不可等」会把一个自清的瞬时争用说成「去重开一个
|
|
2964
|
+
新 agent」——正是本包 0.38.0 收 `row_recycling`/`row_gone` 那批要根治的病换了个方向。
|
|
2965
|
+
|
|
2966
|
+
修前:本包的 resume 失败分类器把两码双双落进开集兜底 `error` ⇒ server 明明给了等待窗,到端只剩
|
|
2967
|
+
一句泛泛失败;而壳侧自己手接了一份判型 —— 判定长在端里,三端各写一遍必然各错一遍。
|
|
2968
|
+
|
|
2969
|
+
### 21b. 端该怎么用(两条路,按你消费哪一层选)
|
|
2970
|
+
|
|
2971
|
+
1. **只要判型**(自己组织重试/文案,或者你消费的不是子代复活腿而是别的 resume 面)⇒
|
|
2972
|
+
`resumeRetryLaterFromError(err)` → `{ code, waitable, retryAfterSec? } | null`。
|
|
2973
|
+
🔴 **命中 ≠ 可等**:处置位是 `waitable`,不是「返回了非 null」。
|
|
2974
|
+
2. **走子代复活腿** ⇒ 照旧读 `resumeSettledSubagent()` 的结局:失败臂新增**两格**
|
|
2975
|
+
`reason: 'retry-later'` / `'refused-preflight'`,并 additive 携 `code?` 与 `retryAfterSec?`。
|
|
2976
|
+
|
|
2977
|
+
**两格怎么分**(判据在包里,端零重判):
|
|
2978
|
+
|
|
2979
|
+
| `reason` | 什么时候 | 端能说什么 |
|
|
2980
|
+
|---|---|---|
|
|
2981
|
+
| `retry-later` | `resume.usage_window_exhausted`(该码本身即证据)**或** `resume.preflight_rejected` **且 server 给了窗** | 「等一会儿(有窗就说等多久)再试」 |
|
|
2982
|
+
| `refused-preflight` | `resume.preflight_rejected` **且没有窗** | 「这一拒发生在提交之前,你的决定没被消费」+ 把「还能不能再赎」交给引擎那行原文。**不许**渲成「稍后重试」,**也不许**渲成终局 |
|
|
2983
|
+
|
|
2984
|
+
### 21c. 🔴 四条读法
|
|
2985
|
+
|
|
2986
|
+
1. **`retryAfterSec` 缺席 ≠ 0**:缺席 = 服务端没给窗(老引擎 / 老 SDK 映射把本码塌进无字段的族
|
|
2987
|
+
基类 / 该腿不发 `Retry-After` 头)。**绝不渲一个编出来的倒计时**。
|
|
2988
|
+
🔴 **缺席时该说什么由 `reason` 决定,不由这一位决定**:`retry-later` 且窗缺席 ⇒ 说「稍后重试」
|
|
2989
|
+
但不给数字;`refused-preflight` ⇒ **不许**说「稍后重试」(见上表)。把两者说成同一句,
|
|
2990
|
+
就是本节要修的病换了个方向。
|
|
2991
|
+
2. **「可以再试」是说给人听的,不是自动重试腿的授权**:resume 是 **AT-MOST-ONCE** 的有副作用动作
|
|
2992
|
+
(叫醒 = 真跑一轮),本包在这一格上照旧**一次都不重试**。端要做自动重试请自己承担幂等论证。
|
|
2993
|
+
3. **`preflight_rejected` 的两条上游臂在客户端分辨不出来**(`retry_later` / `terminal` 的判别位在
|
|
2994
|
+
message 散文里)。本包**不按文案分臂** —— 按文案分支正是上游改一个词就静默空转的形。
|
|
2995
|
+
⇒ 正解是**按正向证据分格**:有窗 ⇒ `retry-later`;没窗 ⇒ `refused-preflight` = **不可判**。
|
|
2996
|
+
证据成立是**直证**(core 7.3.1 `dist/core/runner/runtask.js` 的 `resumePreflight` 拒绝段直读):
|
|
2997
|
+
`terminal` 臂抛的错**一个 detail 都不带**(它走的是单发 `expire` CAS,文末逐字 "the token is not
|
|
2998
|
+
redeemable")⇒ 结构上不可能有 `retryAfterMs` ⇒ server 无从铸 `retryAfterSec`;而 `retry_later` 臂
|
|
2999
|
+
只在部署真给了等待建议时带它。⇒ **有窗是 retry_later 的充分证据,没窗两臂都可能**。
|
|
3000
|
+
🔴 `refused-preflight` **不是**「不可重试」,它是「我不知道还能不能再赎」——两者渲成同一句话
|
|
3001
|
+
就又回到了本条要修的病(一个把终局说成暂时,一个把暂时说成终局)。
|
|
3002
|
+
4. **别把它与 429 的 `usage.window_exhausted` 合并判**:同一本账、**不同门、不同码** —— 那一个是
|
|
3003
|
+
提交面的准入拒,这两个是 resume/decide 腿的**提交前**拒。
|
|
3004
|
+
🔴 **「卡仍 pending、token 仍可兑」只对 `retry-later` 那一格成立**:`resume.usage_window_exhausted`
|
|
3005
|
+
有 core 的不变量背书(什么都没消费、什么都没解钉);而 `refused-preflight` 那一格**不能**这么说 ——
|
|
3006
|
+
`terminal` 臂恰恰已经把行用单发 `expire` CAS 结清了。端在无窗预检拒上只许陈述**这一次拒绝本身**
|
|
3007
|
+
(发生在提交之前、这一拒没有消费你的决定),**不许**顺带承诺卡还在、token 还能兑。
|
|
3008
|
+
|
|
3009
|
+
### 21d. 形校验(包侧已做)
|
|
3010
|
+
|
|
3011
|
+
- 判型是**闭集**的(恰这两个码),**绝不放宽成 `resume.` 前缀判**:放宽会把 `retain_off` /
|
|
3012
|
+
`evicted` / `row_gone` 这些「等也没用」的码一起说成「过会儿再试」——一半用户白等、另一半白重开。
|
|
3013
|
+
上游哪天真在新码上铸 `retryAfterSec`,加成员必须同批带判据。
|
|
3014
|
+
- `retryAfterSec` 的窄读域 = **server 的铸键域**(整数 ∧ ≥1),不更宽也不更窄:`0` / 负数 / 小数 /
|
|
3015
|
+
`NaN` / `Infinity` / 数字串一律**降缺席**(不降 0、不取绝对值)。放行 `0` 就是对消费端说
|
|
3016
|
+
「立刻重试」,而上面第 2 条说了那意味着什么。
|
|
3017
|
+
- **结构读不 `instanceof`**:客户端是宿主注入的,跨 bundle 的同名类是两个类;键位只认 `errorCode`
|
|
3018
|
+
(退役 `code` 槽不做兼容)。null 原型的 plain object(desktop IPC / web 转投的真形)照样命中。
|
|
3019
|
+
- **闭集是 `Object.freeze` 的只读数组,不是 `ReadonlySet`**:后者只在类型面只读,运行期就是普通
|
|
3020
|
+
`Set`,而判定查的是**同一个实例** ⇒ 端一行 `.add('resume.row_gone')` 就能把一条「等也没用」的
|
|
3021
|
+
拒绝翻成带窗的 `retry-later`。端**可以**读它(对表/留痕),**改不动**它(ESM strict 下 `push` /
|
|
3022
|
+
下标赋值直接抛)—— 要加成员请回上游,不是在消费侧撑开闭集。
|
|
3023
|
+
|
|
3024
|
+
### 21e. 🔴 老宿主:穷尽形必须加一条 case
|
|
3025
|
+
|
|
3026
|
+
`SubagentResumeFailureKind` 从八员变**十**员(`retry-later` + `refused-preflight`)。
|
|
3027
|
+
**结构形免动**(读 `reason`/`detail` 的宿主一字不用改),
|
|
3028
|
+
但对该联合做**穷尽 switch**(TS `never` 兜底)的宿主会在提货时编译红 —— 那是设计:一个新的处置
|
|
3029
|
+
落进 `default` 而无人处理,就是把「等一会儿就好」渲成「失败了」。同 §18c 的老宿主纪律。
|
|
3030
|
+
|
|
3031
|
+
### 21f. 上游供给的诚实边界(本版新增的已知局限)
|
|
3032
|
+
|
|
3033
|
+
- **本包不铸 `ResumeRetryLaterError`,也不 `instanceof` 它**:该类是 SDK 8.1.0 的,而本包 peer 地板
|
|
3034
|
+
仍是 `>=7.4.0`。⇒ 挂在 ≤8.0 SDK 上的宿主,这两码走的是无字段的族基类,`retryAfterSec` 在错误对象上
|
|
3035
|
+
**根本不存在** ⇒ 本包如实报缺席。要拿到窗,宿主得把 SDK 抬到 8.1.0(或自己从 409 体里读出来,
|
|
3036
|
+
按同一条窄读域喂进来)。
|
|
3037
|
+
- **本包不替端排重试节拍**:窗只是 server 给的**建议下界**。
|
|
3038
|
+
- **`refused-preflight` 这一格今天在多数部署上结构性不可达**:`resume.preflight_rejected` 要有
|
|
3039
|
+
部署自己的 resume 预检座才发得出来。它是**预置臂**,不是当版可触发的行为面 —— 端接上去是为了
|
|
3040
|
+
「码到货那天不至于只会印一行裸码」,不要据此推断本部署已经在用预检。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/client-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.57.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",
|