@sema-agent/client-core 0.56.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.
- package/CHANGELOG.md +293 -0
- package/README.md +8 -3
- package/dist/adapter/downstream/eventToSdkMessage.js +19 -0
- package/dist/engineErrorCodes.d.ts +59 -0
- package/dist/engineErrorCodes.js +79 -0
- package/dist/hitl/toolApprovalWire.d.ts +151 -91
- package/dist/hitl/toolApprovalWire.js +174 -20
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/resumeRefusalCopy.d.ts +58 -0
- package/dist/resumeRefusalCopy.js +115 -0
- 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 +378 -9
- package/package.json +3 -3
|
@@ -83,6 +83,14 @@ import { createSessionSlot, DEFAULT_SESSION_KEY } from '../sessionSlot.js';
|
|
|
83
83
|
import { readEngineActiveBgTasks } from '../fleet/fleetLedger.js';
|
|
84
84
|
import { observeCancelByDeny, surfaceRememberNotApplied, surfaceEditNotForwarded, surfaceRuleArmNotSent, surfaceRuleArmRejected } from './hitlHostSurface.js';
|
|
85
85
|
import { approvalCallKey, liveFrameCallKey } from './gateIdentity.js';
|
|
86
|
+
// ── sdk 8.2.0 单源闭词表(S-134 锚②;0.58.0 提货批 / B-025 修根)──────────────────────────────
|
|
87
|
+
// 🔴 **值级 import,不是 type-only**:三张 `as const` 数组是**运行期**判据(窄读器按它判 kind /
|
|
88
|
+
// match 是不是闭集成员)。本包此前把 `match` 的二词闭集**手抄**在读器里,而 core design/382 /
|
|
89
|
+
// #510 把词表扩到四员、batch 成员加了 `directoryRead` 臂 —— 手抄的那份没跟,合法候选被整条/
|
|
90
|
+
// 整只丢掉(B-025)。单源化之后「词表加员」这件事在**上游一处**发生,本包跟着走。
|
|
91
|
+
// 🔴 值级 import 的代价已记账:peer 地板随本批抬到 `>=8.2.0`(run-sdk-floor-test.mjs 的 ①c 把
|
|
92
|
+
// 声明与门常量逐位绑死),包总入口闭包的外部包集合仍恒等于 `{diff, @sema-agent/sdk}`(singleton 门)。
|
|
93
|
+
import { RULE_OFFER_MATCHES, RULE_OFFER_BATCH_MEMBER_KINDS, RULE_OFFER_UNCOVERED_REASONS } from '@sema-agent/sdk';
|
|
86
94
|
/** fs 写权限 gate 判定:未来的一等 kind(tool_approval)或按 toolName(server 桥首批=fs 写三件,
|
|
87
95
|
* [820] 表)。AskUserQuestion 永不进这里(ask 桥先判)。 */
|
|
88
96
|
export function isFsApprovalGate(gate) {
|
|
@@ -132,6 +140,13 @@ export function structurallyEqual(a, b) {
|
|
|
132
140
|
return false;
|
|
133
141
|
return ka.every(k => structurallyEqual(a[k], b[k]));
|
|
134
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* 三张闭词表的**运行期**再导出(sdk 8.2.0 `as const` 单源)。
|
|
145
|
+
*
|
|
146
|
+
* 🔴 **端拿它做判定,别再手抄字面量** —— 本包自己的窄读器就读这两张表(B-025 的根因正是手抄);
|
|
147
|
+
* 再导出让三端与包**共用同一份数组对象**,词表加员时一处改、四处跟。
|
|
148
|
+
*/
|
|
149
|
+
export { RULE_OFFER_MATCHES, RULE_OFFER_BATCH_MEMBER_KINDS, RULE_OFFER_UNCOVERED_REASONS };
|
|
135
150
|
// W1(design/161):sessionKey → 注册表;零参 API = DEFAULT_SESSION_KEY 兼容层(cli 装配不动)。
|
|
136
151
|
const cardPortByKey = createSessionSlot();
|
|
137
152
|
const cardPortMissesByKey = new Map();
|
|
@@ -271,13 +286,13 @@ parkGatedCallId) {
|
|
|
271
286
|
// [C170] 答问②半场(0.29.0):durable 富行的两个展示键随卡透传 —— 修前这里是四位闭集,
|
|
272
287
|
// server 7.16.0 起行上就有的 ruleSuggestions/governanceForced 在「行 → 卡」重铸处整段丢失
|
|
273
288
|
// (feed 行原样透传零丢失,丢的只有这处)。governanceForced 条件 stamp 只认 === true(缺席
|
|
274
|
-
// 纪律与活卡腿同款:缺席=无治理来源证据,绝不写 false);候选走
|
|
289
|
+
// 纪律与活卡腿同款:缺席=无治理来源证据,绝不写 false);候选走 readRuleOfferSupply 同一把
|
|
275
290
|
// 合形窄化,落**只读键**(红线见 ApprovalCardRequest.ruleOffersReadOnly 顶注:/decide
|
|
276
291
|
// 无规则位,落可兑付位=假 affordance)。
|
|
277
292
|
// #334(0.43.0):行上的活键换成 `ruleOffers`(server ≥7.46.0 `PendingCheckpoint.ruleOffers`);
|
|
278
293
|
// SDK 7.2.0 的 `PendingCheckpoint` 声明尚无此键 ⇒ **结构视图读**(与本函数下方 `riskDescriptor.probeCause`
|
|
279
294
|
// 同款姿势),旧键 `ruleSuggestions` 保读兼容(7.44 及更旧引擎仍在场),两键经同一把窄读器归一。
|
|
280
|
-
const ruleOffersReadOnly =
|
|
295
|
+
const ruleOffersReadOnly = readRuleOfferSupply(pending.ruleOffers, pending.ruleSuggestions);
|
|
281
296
|
// #280 件1(0.30.4):durable 行腿的探针因由载体 = `riskDescriptor.probeCause`(server 对
|
|
282
297
|
// riskDescriptor 整体透传,与活卡帧同值)。SDK 6.17.2 的 riskDescriptor 声明尚无此键 ⇒ 结构
|
|
283
298
|
// 视图读(与 caps 防御读同款姿势),类型半场候 SDK 班车;载体形不合 ⇒ 不铸键(内部结构不校,
|
|
@@ -424,16 +439,16 @@ export const TOOL_APPROVAL_FRAME_KEYS_MIRROR = [
|
|
|
424
439
|
'governanceForced',
|
|
425
440
|
// sdk 6.14.0 追平(#225 件1,client-core 0.26.0):server 7.12.0 起真发规则候选 —— 门
|
|
426
441
|
// (run-approval-frame-keys-test.mjs)在 bump SDK 当天恰红抓获本键缺席,本次补齐。
|
|
427
|
-
// ⚠️ **退役键**(server 7.46.0 起同文件 0 命中):留在镜像里是因为 sdk
|
|
442
|
+
// ⚠️ **退役键**(server 7.46.0 起同文件 0 命中):留在镜像里是因为 sdk 8.2.0 的运行期锚仍含它,
|
|
428
443
|
// 删了对账门的「SDK 锚的每个键本仓都有」当场红;本包对它只做归一读(见帧上同名键的 JSDoc)。
|
|
429
444
|
'ruleSuggestions',
|
|
430
445
|
// #334/[5223](client-core 0.43.0):server 7.46.0 起把规则候选整体换成判别联合 `ruleOffers`
|
|
431
446
|
// (core 5.58.0 design/375;engine 7.46.0 fixture 的 `@sema-agent/server/dist/tool-approval.d.ts`
|
|
432
|
-
// 直证,同文件旧键 0 命中)
|
|
433
|
-
//
|
|
447
|
+
// 直证,同文件旧键 0 命中)。✅ **sdk 8.2.0 锚已含本键**(S-134 锚②)⇒ 0.58.0 提货批按退出
|
|
448
|
+
// 条件删掉了对账门里那条 AHEAD_OF_ANCHOR 领先登记,本键回到「逐元素相等」的常态。
|
|
434
449
|
'ruleOffers',
|
|
435
450
|
// #341/[5214]③(client-core 0.43.0):server 7.46.0 起真发 `inputHasBidi`(E-14 Trojan Source 族;
|
|
436
|
-
// 7.44 fixture 同文件 0 命中)
|
|
451
|
+
// 7.44 fixture 同文件 0 命中)。仍**领先** sdk 8.2.0 锚(node 直读 18 项无本键)⇒ AHEAD_OF_ANCHOR 登记。
|
|
437
452
|
'inputHasBidi',
|
|
438
453
|
// 🔴 **同形族扫的产物**(client-core 0.43.0,[same-shape-residue-constitution]):本批的病形是
|
|
439
454
|
// 「镜像滞后于 server 实发键」。族扫 = 把 engine 7.46.0 fixture 的 `ToolApprovalFrame` 23 个声明键
|
|
@@ -463,7 +478,7 @@ export const TOOL_APPROVAL_FRAME_KEYS_MIRROR = [
|
|
|
463
478
|
'requiresRealApproval',
|
|
464
479
|
// [4851] 头号请托(client-core 0.38.2):server 7.34.0 起真发窗三键(#288/[4429]② cli 自己请托,
|
|
465
480
|
// server src/tool-approval.ts 三键声明+条件 stamp)。⚠️ 与 requiresRealApproval 同形=**领先** SDK
|
|
466
|
-
// 运行期锚(sdk
|
|
481
|
+
// 运行期锚(sdk 8.2.0 锚 18 项,node 直读实证无三键)⇒ 进对账门 AHEAD_OF_ANCHOR 带退出条件登记
|
|
467
482
|
// (SDK 追平当天登记自红逼删)。0.38.1 把三键丢在包边界外 = [4845]「卡到期屏幕零反应」的直接成因
|
|
468
483
|
// (壳读器 readAskDeadlineMs 早已预埋,缺的一直是包边界这一格——#283 逐字同形第四例)。
|
|
469
484
|
'expiresInMs',
|
|
@@ -816,8 +831,22 @@ function isWireRecordCarrier(v) {
|
|
|
816
831
|
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
817
832
|
}
|
|
818
833
|
/**
|
|
819
|
-
*
|
|
820
|
-
*
|
|
834
|
+
* `match` 是不是**闭词表成员** —— 表来自 sdk 8.2.0 `RULE_OFFER_MATCHES`(`exact` / `prefix` /
|
|
835
|
+
* `wildcard` / `subpath`),**本包不再手抄字面量**。
|
|
836
|
+
*
|
|
837
|
+
* 🔴 **B-025 的根因就是手抄**:0.43.0 铸这只读器时按当时 server 的两员词表写死
|
|
838
|
+
* `match !== 'exact' && match !== 'prefix'`,core design/382 / #510 扩到四员之后这里没跟 ⇒
|
|
839
|
+
* 一条合法的 `wildcard` / `subpath` 候选被判成坏形丢掉。读同一张 `as const` 数组之后,
|
|
840
|
+
* 「词表加员」在上游一处发生,本包自动跟上;门里另有**双向全等钉**(本包认的词集 ≡ sdk 数组
|
|
841
|
+
* 成员集,任一侧加员而另一侧没跟 ⇒ 当场红)。
|
|
842
|
+
*/
|
|
843
|
+
function isRuleOfferMatch(v) {
|
|
844
|
+
return typeof v === 'string' && RULE_OFFER_MATCHES.includes(v);
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* 一条 `single` offer / 一个 `command` 成员共用的三位窄化(`rule` 非空串 + `match` **四词闭集** +
|
|
848
|
+
* `command` 非空串)。任一不合形 ⇒ `undefined`(半条候选渲出来是一格没有正文、按下去必被 server
|
|
849
|
+
* 拒的选项)。
|
|
821
850
|
*/
|
|
822
851
|
function readRuleTriple(v) {
|
|
823
852
|
if (v === null || typeof v !== 'object')
|
|
@@ -825,13 +854,94 @@ function readRuleTriple(v) {
|
|
|
825
854
|
const o = v;
|
|
826
855
|
if (typeof o.rule !== 'string' || o.rule === '')
|
|
827
856
|
return undefined;
|
|
828
|
-
if (o.match
|
|
857
|
+
if (!isRuleOfferMatch(o.match))
|
|
829
858
|
return undefined;
|
|
830
859
|
// 上游形上 `command` 必填(渲染用的命令模式)——缺席/非串 ⇒ 整项不合形丢弃。
|
|
831
860
|
if (typeof o.command !== 'string' || o.command === '')
|
|
832
861
|
return undefined;
|
|
833
862
|
return { rule: o.rule, match: o.match, command: o.command };
|
|
834
863
|
}
|
|
864
|
+
/**
|
|
865
|
+
* 一个 `batch` 成员的窄化(sdk 8.2.0 判别联合 {@link RuleOfferBatchMember};B-025 修根)。
|
|
866
|
+
*
|
|
867
|
+
* 判别位 `kind` 读 sdk `RULE_OFFER_BATCH_MEMBER_KINDS`(`command` / `directoryRead`),外加一条
|
|
868
|
+
* **pre-B3 兼容臂**:`kind` **缺席**的成员按 `command` 归一(server ≥7.46.0 到 design/382 §2.3 B3
|
|
869
|
+
* 落地之间铸的成员没有判别位 —— 窄读域只许等于或宽于铸点域,绝不更窄)。
|
|
870
|
+
*
|
|
871
|
+
* 🔴 **不识的 `kind`(在场但不是表里的词)⇒ `undefined`,调用方据此丢掉整只 batch**,
|
|
872
|
+
* 绝不丢单个成员:core design/382 §2.3 规范性降级臂逐字 "drops the WHOLE batch offer —
|
|
873
|
+
* never the single member"(少一条成员的合取批把「对 N 条说 yes」渲成「对 N−1 条说 yes」,
|
|
874
|
+
* 比不渲更坏),旁边那条完整诚实的 `single` 照留。
|
|
875
|
+
* 🔴 `segment` 是每个臂都必带的渲染座(非空串);`directoryRead` 臂**没有** `match`/`command`,
|
|
876
|
+
* 拿三元组窄读器去读它正是 0.57.0 把整只 batch 判假的那一形。
|
|
877
|
+
*/
|
|
878
|
+
function readRuleOfferBatchMember(v) {
|
|
879
|
+
if (v === null || typeof v !== 'object')
|
|
880
|
+
return undefined;
|
|
881
|
+
const o = v;
|
|
882
|
+
const seg = o.segment;
|
|
883
|
+
if (typeof seg !== 'string' || seg === '')
|
|
884
|
+
return undefined;
|
|
885
|
+
// pre-B3 兼容臂:判别位**缺席** = 历史形的 `command` 成员(那一代 wire 上根本没有 `kind`)。
|
|
886
|
+
// 🔴 归一放在最前面,后面只有**一个**成员性判据 —— 缺席与在场两条路走同一道闸,不留第二个判官。
|
|
887
|
+
const kind = o.kind === undefined ? 'command' : o.kind;
|
|
888
|
+
// 🔴 **成员性判到 sdk 的闭词表,不手抄字面量**(异源对抗复审 r1 [medium] 逼出的同形残余):
|
|
889
|
+
// 本函数初稿把 `'directoryRead'` / `'command'` 两个词**手写**在分支条件里,导入的
|
|
890
|
+
// `RULE_OFFER_BATCH_MEMBER_KINDS` 只被再导出、运行期一次都没读 —— 那正是 B-025 本身的病形
|
|
891
|
+
// (`match` 手抄两员)在成员这一维上的**残余**,只是当天恰好抄对了。抄对的表明天也会过期。
|
|
892
|
+
if (typeof kind !== 'string')
|
|
893
|
+
return undefined;
|
|
894
|
+
if (!RULE_OFFER_BATCH_MEMBER_KINDS.includes(kind))
|
|
895
|
+
return undefined;
|
|
896
|
+
if (kind === 'directoryRead') {
|
|
897
|
+
const rule = o.rule;
|
|
898
|
+
const dir = o.directory;
|
|
899
|
+
if (typeof rule !== 'string' || rule === '')
|
|
900
|
+
return undefined;
|
|
901
|
+
if (typeof dir !== 'string' || dir === '')
|
|
902
|
+
return undefined;
|
|
903
|
+
return { kind: 'directoryRead', rule, directory: dir, segment: seg };
|
|
904
|
+
}
|
|
905
|
+
if (kind === 'command') {
|
|
906
|
+
const triple = readRuleTriple(v);
|
|
907
|
+
if (triple === undefined)
|
|
908
|
+
return undefined;
|
|
909
|
+
return { kind: 'command', ...triple, segment: seg };
|
|
910
|
+
}
|
|
911
|
+
// 🔴 **表内、但本包还没有读器的 kind** ⇒ 与表外同处置(丢 ⇒ 调用方丢整只 batch),绝不猜一个形。
|
|
912
|
+
// 上游往闭词表加员而本包没跟时走这条路:行为是 fail toward asking(合取批不渲,兄弟 single 照留),
|
|
913
|
+
// 而门 ⑨c 的「⊇ 成员 kind 在读器里走得通」那一腿会**当天红**,逼人补读器 —— 静默降级与显形二者兼得。
|
|
914
|
+
return undefined;
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* `uncoveredDetail`(design/382 §3.5 **additive** 明细座)的窄化。
|
|
918
|
+
*
|
|
919
|
+
* 🔴 **只丢坏行,绝不因为它否决整只 batch**:additive 位到货**不许回头削弱既有位的现行为** ——
|
|
920
|
+
* 老引擎压根不铸这一位,server 自己对坏形的座也是「只丢座不丢批」。整只读空 ⇒ 本位缺席
|
|
921
|
+
* (**不是**空数组:空数组会被端读成「已确认没有未覆盖段」,而真源恒是 `uncoveredSegments`)。
|
|
922
|
+
* 🔴 `reason` 判到 sdk 8.2.0 的闭三词集(`redirection` / `no_rule_form` / `cap_overflow`);
|
|
923
|
+
* 不识的 reason 只丢那一行 —— 它是**给人看的因由**,不驱动任何兑付动作。
|
|
924
|
+
*/
|
|
925
|
+
function readUncoveredDetail(v) {
|
|
926
|
+
if (!Array.isArray(v))
|
|
927
|
+
return undefined;
|
|
928
|
+
if (v.length === 0 || v.length > MAX_RULE_OFFER_UNCOVERED_DETAIL_TOLERATED)
|
|
929
|
+
return undefined;
|
|
930
|
+
const out = [];
|
|
931
|
+
for (const raw of v) {
|
|
932
|
+
if (raw === null || typeof raw !== 'object')
|
|
933
|
+
continue;
|
|
934
|
+
const o = raw;
|
|
935
|
+
if (typeof o.segment !== 'string' || o.segment === '')
|
|
936
|
+
continue;
|
|
937
|
+
if (typeof o.reason !== 'string')
|
|
938
|
+
continue;
|
|
939
|
+
if (!RULE_OFFER_UNCOVERED_REASONS.includes(o.reason))
|
|
940
|
+
continue;
|
|
941
|
+
out.push({ segment: o.segment, reason: o.reason });
|
|
942
|
+
}
|
|
943
|
+
return out.length > 0 ? out : undefined;
|
|
944
|
+
}
|
|
835
945
|
/** offer **条数**的坏形帽。server 侧执法帽是 `MAX_RULE_OFFERS`=4(core 契约本就 ≤2),本包留 8 ——
|
|
836
946
|
* [4050] 随批立的纪律:**窄读域只许等于或宽于铸点域,绝不更窄**;超帽 = 坏形/注入,整只降缺席
|
|
837
947
|
* (截了不留痕会掩盖注入)。 */
|
|
@@ -839,9 +949,38 @@ const MAX_RULE_OFFERS_TOLERATED = 8;
|
|
|
839
949
|
/** 一条 `batch` 的**成员**条数坏形帽(core 契约 1..5,server 容忍帽 8)。与上面那个刻意分家:
|
|
840
950
|
* 两者数的是不同的东西(几个选项 vs 一个选项里几条规则),混成一个数会让上游放宽任一维时误伤另一维。 */
|
|
841
951
|
const MAX_RULE_OFFER_BATCH_MEMBERS_TOLERATED = 8;
|
|
952
|
+
/**
|
|
953
|
+
* `uncoveredDetail` 的**行数**坏形帽(0.58.0)。
|
|
954
|
+
*
|
|
955
|
+
* 🔴 **锚在上游的铸点域上,不是按兄弟帽类比**(异源对抗复审 r1 [medium] 真病修):本位初稿写的是
|
|
956
|
+
* `8`,理由「与成员帽同源同量级」—— 那是**类比不是取证**,而且判错了源。真实铸点域:
|
|
957
|
+
* · server 的执法帽是 `MAX_UNCOVERED_DETAIL_ROWS = **32**`(`@sema-agent/server`
|
|
958
|
+
* `src/approval-card.ts`:zod `.max(32)` 与座级预筛 `screenUncoveredDetailSeat` 同用);
|
|
959
|
+
* · 未覆盖段数**根本不受成员帽约束** —— core 逐字 `uncoveredSegments: uncoveredDetail.length`
|
|
960
|
+
* (`permission-rule-model.ts`),一只**只有 1 个成员**的 batch 可以带 9 条明细(9 个带重定向
|
|
961
|
+
* 的段各自 mint 不出规则形,进不了 `rules[]` 却每段都要给一个「为什么还会问」)。
|
|
962
|
+
* ⇒ 帽 8 是**比铸点域更窄**的窄读域,正是 [4050] 那条纪律禁的形:一只合法的 9~32 行明细座被整座
|
|
963
|
+
* 静默丢掉,人失去「这几段为什么还会问」。边界钉在门 ⑨d(9 / 32 必收、33 才丢)。
|
|
964
|
+
* 🔴 **取等于而不是再加倍**:与 {@link MAX_RULE_OFFERS_TOLERATED}(server 执法帽 4,本包留 8)不同 ——
|
|
965
|
+
* 那一位没有可锚的上游常量,余量是纯容忍;本位有**具名铸点常量**,取等于就是「窄读域等于铸点域」,
|
|
966
|
+
* 再自行加倍反而是又一次凭感觉编数。上游哪天放宽,门 ⑨d 的 33 行负控会当天红,逼人跟着抬。
|
|
967
|
+
*/
|
|
968
|
+
const MAX_RULE_OFFER_UNCOVERED_DETAIL_TOLERATED = 32;
|
|
842
969
|
/**
|
|
843
970
|
* `ruleOffers`(server ≥7.46.0 的判别联合)的结构读。
|
|
844
971
|
*
|
|
972
|
+
* 🔴 **[C228]/L-103(0.57.0)起本口是公面**(additive 导出,语义与字节一字未改)。此前它只经
|
|
973
|
+
* **卡端口**({@link ApprovalCardRequest.ruleOffers})出包 —— 不走卡端口架构的宿主(浏览器端没有
|
|
974
|
+
* Ink 三选卡,自己拿帧渲)只能在自己那边**重铸一遍**同一把窄读器,而这把窄读器承载的是
|
|
975
|
+
* **兑付安全**判据(原始下标不前移、逐条丢坏、闭集 kind),重铸一次 = 多一份会各自漂的判官。
|
|
976
|
+
* ⇒ 公面出口是「判定归包、呈现归端」在这一条腿上的兑现,不是便利函数。
|
|
977
|
+
* 🔴 **两代 wire 键请走 {@link readRuleOfferSupply}**:本函数只读**新键**(server ≥7.46.0 的
|
|
978
|
+
* `ruleOffers`);退役键 `ruleSuggestions`(server ≤7.45)的归一在那一口,两键的取舍序也在那里
|
|
979
|
+
* (新键在场即定局,绝不混编)。手里只有新键才直接用本口。
|
|
980
|
+
* 🔴 **`offerIndex` 的定义域随腿不同**,消费前必读 {@link RuleOffer} 顶注:活卡帧腿上它是合法
|
|
981
|
+
* **选择键**(可当 `persistRule.batchOfferIndex` 回兑),durable 行腿上它只是展示/对账座
|
|
982
|
+
* (server `boundedRuleOffers` 已压紧过一次)——**本函数不知道调用方在哪条腿上**,分辨是调用方的事。
|
|
983
|
+
*
|
|
845
984
|
* 🔴 **逐条丢坏、原始下标不前移**:坏 offer 逐条丢弃(一条坏的不该让另一条真的消失,与 server
|
|
846
985
|
* `boundedRuleOffers` 同向),但留下来的每一条都带**原始 wire 下标** {@link RuleOffer.offerIndex} ——
|
|
847
986
|
* core 的契约原话:做不到保住原始下标的消费端「must suppress its persistence actions entirely」,
|
|
@@ -849,7 +988,7 @@ const MAX_RULE_OFFER_BATCH_MEMBERS_TOLERATED = 8;
|
|
|
849
988
|
* 🔴 `kind` 是**闭集判别位**:不认识的 kind ⇒ 丢这一条(不猜、不降级成 single)。
|
|
850
989
|
* 全部不合形/非数组/超帽 ⇒ 整体缺席(卡不渲「不再询问」档)。
|
|
851
990
|
*/
|
|
852
|
-
function readRuleOffers(v) {
|
|
991
|
+
export function readRuleOffers(v) {
|
|
853
992
|
if (!Array.isArray(v))
|
|
854
993
|
return undefined;
|
|
855
994
|
if (v.length === 0 || v.length > MAX_RULE_OFFERS_TOLERATED)
|
|
@@ -881,19 +1020,28 @@ function readRuleOffers(v) {
|
|
|
881
1020
|
const members = [];
|
|
882
1021
|
let memberShapeOk = true;
|
|
883
1022
|
for (const raw of o.rules) {
|
|
884
|
-
const triple = readRuleTriple(raw);
|
|
885
|
-
const seg = raw?.segment;
|
|
886
1023
|
// 🔴 batch 成员**不逐条丢**:合取批是「对全体成员一次说是」,少一条成员就是另一次授权 ——
|
|
887
|
-
// 渲一个缺了成员的批,人点的与引擎兑的不是同一批规则。任一成员不合形
|
|
888
|
-
|
|
1024
|
+
// 渲一个缺了成员的批,人点的与引擎兑的不是同一批规则。任一成员不合形(含 `kind` 不识)
|
|
1025
|
+
// ⇒ 整条 batch 丢。这是 core design/382 §2.3 的**规范性**降级臂,不是本包的保守选择:
|
|
1026
|
+
// 逐字 "drops the WHOLE batch offer — never the single member ... and never the whole card"。
|
|
1027
|
+
const member = readRuleOfferBatchMember(raw);
|
|
1028
|
+
if (member === undefined) {
|
|
889
1029
|
memberShapeOk = false;
|
|
890
1030
|
break;
|
|
891
1031
|
}
|
|
892
|
-
members.push(
|
|
1032
|
+
members.push(member);
|
|
893
1033
|
}
|
|
894
1034
|
if (!memberShapeOk)
|
|
895
1035
|
continue;
|
|
896
|
-
|
|
1036
|
+
// additive 明细座(0.58.0):只丢坏行、读空即缺席,**绝不**因为它否决整只 batch。
|
|
1037
|
+
const detail = readUncoveredDetail(item.uncoveredDetail);
|
|
1038
|
+
out.push({
|
|
1039
|
+
kind: 'batch',
|
|
1040
|
+
offerIndex: i,
|
|
1041
|
+
rules: members,
|
|
1042
|
+
uncoveredSegments: uncovered,
|
|
1043
|
+
...(detail !== undefined ? { uncoveredDetail: detail } : {}),
|
|
1044
|
+
});
|
|
897
1045
|
continue;
|
|
898
1046
|
}
|
|
899
1047
|
// 不认识的 kind(含 kind 缺席)⇒ 丢这一条,原始下标照旧不给后面的成员用。
|
|
@@ -923,6 +1071,12 @@ function readLegacyRuleSuggestions(v) {
|
|
|
923
1071
|
/**
|
|
924
1072
|
* 两代 wire 供给 → **包内单一形**(#334/[5223],0.43.0):新键优先,新键整只读不出来才看旧键。
|
|
925
1073
|
*
|
|
1074
|
+
* 🔴 **[C228]/L-103(0.57.0)起本口是公面**(additive 导出,语义与字节一字未改;0.56.0 及更早的
|
|
1075
|
+
* 内部名是 `readOfferSupply`,**纯改名**没有第二个消费点)。宿主手里拿到的是**一整帧/一整行**,
|
|
1076
|
+
* 上面同时可能有 `ruleOffers`(新)与 `ruleSuggestions`(旧)两个键 —— 这一口是三端唯一该调的那个:
|
|
1077
|
+
* `readRuleOfferSupply(frame.ruleOffers, frame.ruleSuggestions)`。
|
|
1078
|
+
* 两代键的取舍序是**判据不是便利**(见下面两段红条),端各写一遍必然在 `null` 那一格上各错一遍。
|
|
1079
|
+
*
|
|
926
1080
|
* 🔴 **新键在场即定局,绝不混编**:新键**有载体**而读出空(数组在但全条坏形、或压根不是数组)
|
|
927
1081
|
* 也**不**回落旧键 —— 一台 7.46 引擎不会同时按两代形铸候选,拿旧键顶上去等于把一份异源素材
|
|
928
1082
|
* 冒充成这次 ask 的候选。
|
|
@@ -938,7 +1092,7 @@ function readLegacyRuleSuggestions(v) {
|
|
|
938
1092
|
* ⇒ 取「null == 缺席」。安全面上它**不新增**任何攻击面:一个能塞 `{ruleOffers:null, ruleSuggestions:[…]}`
|
|
939
1093
|
* 的注入面,同样能只塞 `{ruleSuggestions:[…]}`,而后者为了兼容 7.44 本来就必须收。
|
|
940
1094
|
*/
|
|
941
|
-
function
|
|
1095
|
+
export function readRuleOfferSupply(offers, legacy) {
|
|
942
1096
|
// 🔴 `null` 与 `undefined` 同视为缺席(理由见顶注的代价不对称段);其余一切载体 = 新键在场,
|
|
943
1097
|
// 读出什么就是什么,**绝不**再看旧键。
|
|
944
1098
|
if (offers !== undefined && offers !== null)
|
|
@@ -1010,7 +1164,7 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
|
|
|
1010
1164
|
// 显式 `false`,而缺席的语义是「没有治理来源的证据」,不是「这门可以被表态掀掉」)。
|
|
1011
1165
|
// #334(0.43.0):新键 `ruleOffers`(server ≥7.46.0 判别联合)优先,旧键 `ruleSuggestions`
|
|
1012
1166
|
// (≤7.45)归一成 `kind:'single'` 兜底 —— 两代经同一把窄读器,包内出口只有一个形。
|
|
1013
|
-
const ruleOffers =
|
|
1167
|
+
const ruleOffers = readRuleOfferSupply(frame.ruleOffers, frame.ruleSuggestions);
|
|
1014
1168
|
const card = await surfaceApprovalCard({
|
|
1015
1169
|
toolName,
|
|
1016
1170
|
args: args,
|
|
@@ -1055,7 +1209,7 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
|
|
|
1055
1209
|
? { delegation: frame.delegation }
|
|
1056
1210
|
: {}),
|
|
1057
1211
|
// #225 件1 / #334 换形(0.43.0):规则候选透传(合形项;缺席/坏形 ⇒ 键不 stamp,卡形不渲该档)。
|
|
1058
|
-
// 落位是**包内单一形出口** `ruleOffers`,两代 wire 键同经
|
|
1212
|
+
// 落位是**包内单一形出口** `ruleOffers`,两代 wire 键同经 readRuleOfferSupply 归一。
|
|
1059
1213
|
...(ruleOffers !== undefined ? { ruleOffers } : {}),
|
|
1060
1214
|
// #144:被越级的持久规则原文(UNTRUSTED-for-display)。窄化=**非空白串才 stamp**,坏形降缺席
|
|
1061
1215
|
// (server 明说空串不铸键:「空串是坏值不是『空规则』」)—— 一格空白的规则解释比没有解释更坏。
|
package/dist/index.d.ts
CHANGED
|
@@ -215,6 +215,7 @@ export * from './engineSessionParam.js';
|
|
|
215
215
|
export * from './engineWireTarget.js';
|
|
216
216
|
export * from './principalWire.js';
|
|
217
217
|
export * from './wireErrorTriage.js';
|
|
218
|
+
export * from './resumeRefusalCopy.js';
|
|
218
219
|
export * from './sessionMap.js';
|
|
219
220
|
export * from './detachWire.js';
|
|
220
221
|
export * from './workflowMonitor.js';
|
package/dist/index.js
CHANGED
|
@@ -300,6 +300,11 @@ export * from './principalWire.js';
|
|
|
300
300
|
// · wireErrorTriage:turn 错误分型判定半场(A-028.11;文案/渲染归端)+ scenario 拒绝判型
|
|
301
301
|
// (A-028.13;web 逐字节同形过滤行的正主)。码字面引 engineErrorCodes。
|
|
302
302
|
export * from './wireErrorTriage.js';
|
|
303
|
+
// · resumeRefusalCopy:L-102 下半场(0.58.0)—— resume 拒绝的**人话铸点**三端单源。
|
|
304
|
+
// 与上面那口分工:`wireErrorTriage.resumeRetryLaterFromError` 答「能不能等」(事实读数),
|
|
305
|
+
// 本口答「该对人说什么」(文案),`preflight_rejected` 的窗与可等性**转调**前者不复制判定。
|
|
306
|
+
// 壳侧对位 = cli `src/sema/resumeRefusalCopy.ts`(1.0.101 起改薄成适配层)。
|
|
307
|
+
export * from './resumeRefusalCopy.js';
|
|
303
308
|
// · sessionMap:「客户端会话 id ↔ 引擎会话 id」映射单一键形 + merge 判定(A-028.12;存储经
|
|
304
309
|
// SessionMapStorePort 归端 —— cli 文件锁/原子写,web localStorage)。
|
|
305
310
|
export * from './sessionMap.js';
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 本模块认识的 resume 拒绝码(**闭集**)—— 「有人话可补」的那两个。
|
|
3
|
+
*
|
|
4
|
+
* 🔴 **冻结数组,不是 `ReadonlySet`**(与 {@link RESUME_RETRY_LATER_CODES} 同一条已定谳的病形):
|
|
5
|
+
* `ReadonlySet` 只在类型面只读,运行期是一只普通 `Set`,而判定查的就是这个实例 —— 公面上任何
|
|
6
|
+
* JS 消费者一次 `.add('resume.evicted')` 就能让一条「重开吧」的拒绝挂上「你的决定没被消费」
|
|
7
|
+
* 这句安全声明。冻结数组在运行期真的改不动(ESM 恒 strict:`push` / 下标赋值直接抛)。
|
|
8
|
+
* 🔴 新增成员必须**同批**补文案 + 判据,不许靠前缀放宽。
|
|
9
|
+
*/
|
|
10
|
+
export declare const RESUME_REFUSAL_CODES: readonly string[];
|
|
11
|
+
/** {@link resumeRefusalFromError} 的结构化读数。 */
|
|
12
|
+
export interface ResumeRefusalDetail {
|
|
13
|
+
/** 命中的那一个码({@link RESUME_REFUSAL_CODES} 的成员之一)。 */
|
|
14
|
+
code: string;
|
|
15
|
+
/**
|
|
16
|
+
* server 给的**等待秒数**(整数 ∧ ≥1;窄读域与 §21 同律,见模块顶注)。
|
|
17
|
+
* 🔴 **缺席 = 服务端没给窗**,不是 0、不是「立刻」——绝不渲一个编出来的倒计时
|
|
18
|
+
* ([honest-absence-not-fabricated-zero])。`resume.placement_mismatch` 上**恒缺席**
|
|
19
|
+
* (server 只在时间性二码上铸这一位);`resume.preflight_rejected` 上按 server 供给。
|
|
20
|
+
*/
|
|
21
|
+
retryAfterSec?: number;
|
|
22
|
+
/**
|
|
23
|
+
* **等一会儿再赎有没有用**(处置位;文案据它选第三句)。
|
|
24
|
+
*
|
|
25
|
+
* 🔴 **两个码上的 `false` 是两种「不」,别合并读**:
|
|
26
|
+
* · `resume.preflight_rejected` 的 `false` = **不可判**(转调 {@link resumeRetryLaterFromError}
|
|
27
|
+
* 的同名位:core 侧那条 `terminal` 臂与缺省 `retry_later` 臂的判别位只在 message 散文里,
|
|
28
|
+
* 本包不按文案分臂 ⇒ 只认「server 真给了窗」这条正向证据);
|
|
29
|
+
* · `resume.placement_mismatch` 的 `false` = **等没有用**(出路是换参数不是等时间,见该码顶注)。
|
|
30
|
+
* 两者在文案上落到**不同的第三句**,所以消费端不必自己再分。
|
|
31
|
+
*/
|
|
32
|
+
waitable: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 被 catch 的错误 → resume 拒绝读数;不是这两个码 ⇒ `null`(绝不误吃别的 4xx)。
|
|
36
|
+
*
|
|
37
|
+
* 🔴 **duck-typed 不 `instanceof`**:三端注入面可能是宿主的裸 fetch、desktop 的 IPC 转投、web 跨
|
|
38
|
+
* bundle 的 plain object(跨 bundle 同名类是两个类),这些形上 `instanceof` 一律为假而
|
|
39
|
+
* `errorCode` 恒在。键位**只认 `errorCode`**([2055] 死键纪律,退役 `code` 槽不做兼容)。
|
|
40
|
+
* 🔴 **判据只有 `errorCode`,不看 HTTP 数字**(与 §21 同口径):`resume.*` 那一族成员全是 409,
|
|
41
|
+
* 按数字分支等于把几种不同处置压成一种。
|
|
42
|
+
* 🔴 **`preflight_rejected` 的窗与可等性全部转调 §21 那一口**,本文件不复制判定。
|
|
43
|
+
*/
|
|
44
|
+
export declare function resumeRefusalFromError(err: unknown): ResumeRefusalDetail | null;
|
|
45
|
+
/**
|
|
46
|
+
* 结构化读数 → 人话补充行(`·` 分段,与端上 quota / scenario 两个兄弟同形)。
|
|
47
|
+
*
|
|
48
|
+
* 三句的分工固定:①**发生了什么**(用户看得懂的那句)· ②**这次拒绝有没有消费掉你的决定**
|
|
49
|
+
* (安全声明,两条臂都成立时才敢说)· ③**接下来怎么办 / 还能不能再决**。
|
|
50
|
+
*
|
|
51
|
+
* 🔴 第三句由 {@link ResumeRefusalDetail.waitable} 选,**不由 message 文本选**:按上游散文分支
|
|
52
|
+
* 正是「上游改一个词、壳的判定静默空转」的形(wire 锚契约门要挡的就是它)。
|
|
53
|
+
* 🔴 `preflight_rejected` 且 `waitable === false` 时说的是**两条臂都为真**的那句(cli 1.0.x 的现行
|
|
54
|
+
* 文案逐字),把「还能不能再赎」交给屏上那行引擎原文;`waitable === true` 时 server 真给了窗
|
|
55
|
+
* ——那是 `retry_later` 臂的**充分证据**(core 的 `terminal` 臂抛的 `CheckpointError` 一个
|
|
56
|
+
* detail 都不带,窗结构上到不了客户端)⇒ 可以放心说「障碍清掉之后这个 token 还能再赎」。
|
|
57
|
+
*/
|
|
58
|
+
export declare function resumeRefusalContent(detail: ResumeRefusalDetail): string;
|
|
@@ -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
|
+
}
|
|
@@ -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
|
*
|