@sema-agent/client-core 0.57.0 → 0.59.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 +280 -0
- package/README.md +9 -4
- package/dist/adapt/arms.js +6 -1
- package/dist/adapter/downstream/eventToSdkMessage.d.ts +27 -0
- package/dist/adapter/downstream/eventToSdkMessage.js +87 -1
- package/dist/engineCapsCache.d.ts +40 -0
- package/dist/engineCapsCache.js +31 -0
- package/dist/engineErrorCodes.d.ts +15 -0
- package/dist/engineErrorCodes.js +15 -0
- package/dist/hitl/toolApprovalWire.d.ts +234 -92
- package/dist/hitl/toolApprovalWire.js +231 -14
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7 -0
- package/dist/printToolResultFrame.d.ts +18 -0
- package/dist/resumeRefusalCopy.d.ts +58 -0
- package/dist/resumeRefusalCopy.js +115 -0
- package/dist/seam.d.ts +25 -5
- package/dist/seam.js +5 -3
- package/dist/sqlEngineCapability.d.ts +131 -0
- package/dist/sqlEngineCapability.js +191 -0
- package/docs/INTEGRATION-CLIENTS.md +408 -16
- package/package.json +3 -3
|
@@ -83,6 +83,19 @@ 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
|
+
// ── sdk 8.3.0 单源闭词表(S-125③ / S-114 / S-15 第五单;0.59.0 提货批)────────────────────────
|
|
94
|
+
// 同上一条纪律的第二例:`ruleOffersAbsence` 的三词、`denialLimitFallback.limit` 的二词都是
|
|
95
|
+
// **上游属主**的闭集。本包**不拿它们做窄读判定**(两处都按开集透传,见各自帧键 JSDoc),再导出
|
|
96
|
+
// 的用途是让三端写 `switch` 时有一份**与包同一个数组对象**的词表可数,而不是各自抄字面量 ——
|
|
97
|
+
// B-025 的根因正是手抄。⇒ 值级 import 让 peer 地板在编译期真被钉到 8.3.0。
|
|
98
|
+
import { RULE_OFFER_MATCHES, RULE_OFFER_BATCH_MEMBER_KINDS, RULE_OFFER_UNCOVERED_REASONS, RULE_OFFERS_ABSENCE_REASONS, DENIAL_LIMIT_KINDS, } from '@sema-agent/sdk';
|
|
86
99
|
/** fs 写权限 gate 判定:未来的一等 kind(tool_approval)或按 toolName(server 桥首批=fs 写三件,
|
|
87
100
|
* [820] 表)。AskUserQuestion 永不进这里(ask 桥先判)。 */
|
|
88
101
|
export function isFsApprovalGate(gate) {
|
|
@@ -132,6 +145,22 @@ export function structurallyEqual(a, b) {
|
|
|
132
145
|
return false;
|
|
133
146
|
return ka.every(k => structurallyEqual(a[k], b[k]));
|
|
134
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* 三张闭词表的**运行期**再导出(sdk 8.2.0 `as const` 单源)。
|
|
150
|
+
*
|
|
151
|
+
* 🔴 **端拿它做判定,别再手抄字面量** —— 本包自己的窄读器就读这两张表(B-025 的根因正是手抄);
|
|
152
|
+
* 再导出让三端与包**共用同一份数组对象**,词表加员时一处改、四处跟。
|
|
153
|
+
*/
|
|
154
|
+
export { RULE_OFFER_MATCHES, RULE_OFFER_BATCH_MEMBER_KINDS, RULE_OFFER_UNCOVERED_REASONS };
|
|
155
|
+
/**
|
|
156
|
+
* 两张新闭词表的**运行期**再导出(sdk 8.3.0 `as const` 单源;与上面三张同一条纪律)。
|
|
157
|
+
*
|
|
158
|
+
* ⚠️ **本包自己不拿它们做窄读判定**,这与 `RULE_OFFER_MATCHES` 一族刻意不同,理由写在两处帧键的
|
|
159
|
+
* JSDoc 里:`origin` 与 `ruleOffersAbsence` 的词表属主是 core、server 侧已按闭集拒过词表外的值,
|
|
160
|
+
* 包再校一遍只会在 core 加员当天把一个**合法**值判没(`wiring_manifest.autoMode.reason` 的
|
|
161
|
+
* 透传纪律逐字同规)。再导出是给端的 `switch` 一份可数的表 —— 端仍必须带 `default` 臂。
|
|
162
|
+
*/
|
|
163
|
+
export { RULE_OFFERS_ABSENCE_REASONS, DENIAL_LIMIT_KINDS };
|
|
135
164
|
// W1(design/161):sessionKey → 注册表;零参 API = DEFAULT_SESSION_KEY 兼容层(cli 装配不动)。
|
|
136
165
|
const cardPortByKey = createSessionSlot();
|
|
137
166
|
const cardPortMissesByKey = new Map();
|
|
@@ -424,16 +453,29 @@ export const TOOL_APPROVAL_FRAME_KEYS_MIRROR = [
|
|
|
424
453
|
'governanceForced',
|
|
425
454
|
// sdk 6.14.0 追平(#225 件1,client-core 0.26.0):server 7.12.0 起真发规则候选 —— 门
|
|
426
455
|
// (run-approval-frame-keys-test.mjs)在 bump SDK 当天恰红抓获本键缺席,本次补齐。
|
|
427
|
-
// ⚠️ **退役键**(server 7.46.0 起同文件 0 命中):留在镜像里是因为 sdk
|
|
456
|
+
// ⚠️ **退役键**(server 7.46.0 起同文件 0 命中):留在镜像里是因为 sdk 8.2.0 的运行期锚仍含它,
|
|
428
457
|
// 删了对账门的「SDK 锚的每个键本仓都有」当场红;本包对它只做归一读(见帧上同名键的 JSDoc)。
|
|
429
458
|
'ruleSuggestions',
|
|
430
459
|
// #334/[5223](client-core 0.43.0):server 7.46.0 起把规则候选整体换成判别联合 `ruleOffers`
|
|
431
460
|
// (core 5.58.0 design/375;engine 7.46.0 fixture 的 `@sema-agent/server/dist/tool-approval.d.ts`
|
|
432
|
-
// 直证,同文件旧键 0 命中)
|
|
433
|
-
//
|
|
461
|
+
// 直证,同文件旧键 0 命中)。✅ **sdk 8.2.0 锚已含本键**(S-134 锚②)⇒ 0.58.0 提货批按退出
|
|
462
|
+
// 条件删掉了对账门里那条 AHEAD_OF_ANCHOR 领先登记,本键回到「逐元素相等」的常态。
|
|
434
463
|
'ruleOffers',
|
|
464
|
+
// S-15 第五单(client-core 0.59.0):server 7.55.0 起真发 `ruleOffersAbsence`(core #490 修②;
|
|
465
|
+
// engine 7.60.0 fixture 直证)。⚠️ 与 `ruleOffers` **同形不同命**:它是**追平**不是领先 ——
|
|
466
|
+
// sdk 8.3.0 的运行期锚已含本键(node 直读 27 项实证)⇒ **不**进 AHEAD_OF_ANCHOR
|
|
467
|
+
// (往那张表里塞一个锚已有的键,它的第二条退出条件当场红)。
|
|
468
|
+
'ruleOffersAbsence',
|
|
469
|
+
// S-114(client-core 0.59.0):server 7.57.0 起真发 `denialLimitFallback`(core 7.4.0 #548;
|
|
470
|
+
// engine 7.60.0 fixture 直证)。同上一条:sdk 8.3.0 锚已含本键 ⇒ 追平,不进领先表。
|
|
471
|
+
// 🔴 本键与 `requiresRealApproval` 是孪生键,但两者在镜像里各占一格 —— 合并会让「只有一位在场」
|
|
472
|
+
// 的坏形无处显形。
|
|
473
|
+
'denialLimitFallback',
|
|
474
|
+
// S-125③/#564(client-core 0.59.0):server 7.57.0 起真发 `origin`(core 7.5.0 `ASK_ORIGINS`
|
|
475
|
+
// 八词;engine 7.60.0 fixture 直证)。同上:sdk 8.3.0 锚已含本键 ⇒ 追平,不进领先表。
|
|
476
|
+
'origin',
|
|
435
477
|
// #341/[5214]③(client-core 0.43.0):server 7.46.0 起真发 `inputHasBidi`(E-14 Trojan Source 族;
|
|
436
|
-
// 7.44 fixture 同文件 0 命中)
|
|
478
|
+
// 7.44 fixture 同文件 0 命中)。仍**领先** sdk 8.2.0 锚(node 直读 18 项无本键)⇒ AHEAD_OF_ANCHOR 登记。
|
|
437
479
|
'inputHasBidi',
|
|
438
480
|
// 🔴 **同形族扫的产物**(client-core 0.43.0,[same-shape-residue-constitution]):本批的病形是
|
|
439
481
|
// 「镜像滞后于 server 实发键」。族扫 = 把 engine 7.46.0 fixture 的 `ToolApprovalFrame` 23 个声明键
|
|
@@ -463,7 +505,7 @@ export const TOOL_APPROVAL_FRAME_KEYS_MIRROR = [
|
|
|
463
505
|
'requiresRealApproval',
|
|
464
506
|
// [4851] 头号请托(client-core 0.38.2):server 7.34.0 起真发窗三键(#288/[4429]② cli 自己请托,
|
|
465
507
|
// server src/tool-approval.ts 三键声明+条件 stamp)。⚠️ 与 requiresRealApproval 同形=**领先** SDK
|
|
466
|
-
// 运行期锚(sdk
|
|
508
|
+
// 运行期锚(sdk 8.2.0 锚 18 项,node 直读实证无三键)⇒ 进对账门 AHEAD_OF_ANCHOR 带退出条件登记
|
|
467
509
|
// (SDK 追平当天登记自红逼删)。0.38.1 把三键丢在包边界外 = [4845]「卡到期屏幕零反应」的直接成因
|
|
468
510
|
// (壳读器 readAskDeadlineMs 早已预埋,缺的一直是包边界这一格——#283 逐字同形第四例)。
|
|
469
511
|
'expiresInMs',
|
|
@@ -816,8 +858,56 @@ function isWireRecordCarrier(v) {
|
|
|
816
858
|
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
817
859
|
}
|
|
818
860
|
/**
|
|
819
|
-
*
|
|
820
|
-
*
|
|
861
|
+
* `denialLimitFallback` 的**窄读器**(S-114,0.59.0)—— wire 是 JSON:注入面 / 旧 server / 比本包新
|
|
862
|
+
* 一版的 server 都可能给别的形,坏形一律降**缺席**(与 `readToolApprovalDelegation` 同族纪律)。
|
|
863
|
+
*
|
|
864
|
+
* 🔴 **四成员全必填,缺一整只丢**(与 server `approval-card.ts` 的 `readDenialLimitFallback`
|
|
865
|
+
* `.strict()` 同判据,与 core 的形一字不差):半只对象上的 `consecutive` 会被人当成**真实的
|
|
866
|
+
* 连续拒次数**读,而它可能只是一个恰好在场的键。「一格空白的计数」比不渲这张卡更坏。
|
|
867
|
+
* 🔴 **两个计数与窗按「有限非负数」判,不折 0**:`Number.isFinite` 单独会放行负数,而三者的定义域
|
|
868
|
+
* 按上游契约本就非负(计数是次数,窗是 ms 且 `0` = 不武装,是**合法读数**不是缺席)。
|
|
869
|
+
* NaN / Infinity / 负数 = 坏形降缺席,**绝不猜**(同文件 `delegation.depth` 的旧教训)。
|
|
870
|
+
* 🔴 **`limit` 按开集读**(非空串即收):闭二词表 `DENIAL_LIMIT_KINDS` 只作再导出给端数,
|
|
871
|
+
* 包内**不拿它做判定** —— 词表属主是 core,抄一份就是给自己立第二个判官(B-025 的病形);
|
|
872
|
+
* server 侧已按闭集拒过词表外的值,包再校一遍只会在 core 加员当天把一个合法值判没。
|
|
873
|
+
* ⚠️ 这条与「四成员全必填」不矛盾:必填说的是**在场性**,开集说的是**取值域**。
|
|
874
|
+
*/
|
|
875
|
+
function readDenialLimitFallback(v) {
|
|
876
|
+
if (!isWireRecordCarrier(v))
|
|
877
|
+
return undefined;
|
|
878
|
+
const d = v;
|
|
879
|
+
if (!isNonNegativeFinite(d.consecutive))
|
|
880
|
+
return undefined;
|
|
881
|
+
if (!isNonNegativeFinite(d.total))
|
|
882
|
+
return undefined;
|
|
883
|
+
if (!isNonNegativeFinite(d.autoDenyAfterMs))
|
|
884
|
+
return undefined;
|
|
885
|
+
if (typeof d.limit !== 'string' || d.limit === '')
|
|
886
|
+
return undefined;
|
|
887
|
+
return {
|
|
888
|
+
consecutive: d.consecutive,
|
|
889
|
+
total: d.total,
|
|
890
|
+
limit: d.limit,
|
|
891
|
+
autoDenyAfterMs: d.autoDenyAfterMs,
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* `match` 是不是**闭词表成员** —— 表来自 sdk 8.2.0 `RULE_OFFER_MATCHES`(`exact` / `prefix` /
|
|
896
|
+
* `wildcard` / `subpath`),**本包不再手抄字面量**。
|
|
897
|
+
*
|
|
898
|
+
* 🔴 **B-025 的根因就是手抄**:0.43.0 铸这只读器时按当时 server 的两员词表写死
|
|
899
|
+
* `match !== 'exact' && match !== 'prefix'`,core design/382 / #510 扩到四员之后这里没跟 ⇒
|
|
900
|
+
* 一条合法的 `wildcard` / `subpath` 候选被判成坏形丢掉。读同一张 `as const` 数组之后,
|
|
901
|
+
* 「词表加员」在上游一处发生,本包自动跟上;门里另有**双向全等钉**(本包认的词集 ≡ sdk 数组
|
|
902
|
+
* 成员集,任一侧加员而另一侧没跟 ⇒ 当场红)。
|
|
903
|
+
*/
|
|
904
|
+
function isRuleOfferMatch(v) {
|
|
905
|
+
return typeof v === 'string' && RULE_OFFER_MATCHES.includes(v);
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* 一条 `single` offer / 一个 `command` 成员共用的三位窄化(`rule` 非空串 + `match` **四词闭集** +
|
|
909
|
+
* `command` 非空串)。任一不合形 ⇒ `undefined`(半条候选渲出来是一格没有正文、按下去必被 server
|
|
910
|
+
* 拒的选项)。
|
|
821
911
|
*/
|
|
822
912
|
function readRuleTriple(v) {
|
|
823
913
|
if (v === null || typeof v !== 'object')
|
|
@@ -825,13 +915,94 @@ function readRuleTriple(v) {
|
|
|
825
915
|
const o = v;
|
|
826
916
|
if (typeof o.rule !== 'string' || o.rule === '')
|
|
827
917
|
return undefined;
|
|
828
|
-
if (o.match
|
|
918
|
+
if (!isRuleOfferMatch(o.match))
|
|
829
919
|
return undefined;
|
|
830
920
|
// 上游形上 `command` 必填(渲染用的命令模式)——缺席/非串 ⇒ 整项不合形丢弃。
|
|
831
921
|
if (typeof o.command !== 'string' || o.command === '')
|
|
832
922
|
return undefined;
|
|
833
923
|
return { rule: o.rule, match: o.match, command: o.command };
|
|
834
924
|
}
|
|
925
|
+
/**
|
|
926
|
+
* 一个 `batch` 成员的窄化(sdk 8.2.0 判别联合 {@link RuleOfferBatchMember};B-025 修根)。
|
|
927
|
+
*
|
|
928
|
+
* 判别位 `kind` 读 sdk `RULE_OFFER_BATCH_MEMBER_KINDS`(`command` / `directoryRead`),外加一条
|
|
929
|
+
* **pre-B3 兼容臂**:`kind` **缺席**的成员按 `command` 归一(server ≥7.46.0 到 design/382 §2.3 B3
|
|
930
|
+
* 落地之间铸的成员没有判别位 —— 窄读域只许等于或宽于铸点域,绝不更窄)。
|
|
931
|
+
*
|
|
932
|
+
* 🔴 **不识的 `kind`(在场但不是表里的词)⇒ `undefined`,调用方据此丢掉整只 batch**,
|
|
933
|
+
* 绝不丢单个成员:core design/382 §2.3 规范性降级臂逐字 "drops the WHOLE batch offer —
|
|
934
|
+
* never the single member"(少一条成员的合取批把「对 N 条说 yes」渲成「对 N−1 条说 yes」,
|
|
935
|
+
* 比不渲更坏),旁边那条完整诚实的 `single` 照留。
|
|
936
|
+
* 🔴 `segment` 是每个臂都必带的渲染座(非空串);`directoryRead` 臂**没有** `match`/`command`,
|
|
937
|
+
* 拿三元组窄读器去读它正是 0.57.0 把整只 batch 判假的那一形。
|
|
938
|
+
*/
|
|
939
|
+
function readRuleOfferBatchMember(v) {
|
|
940
|
+
if (v === null || typeof v !== 'object')
|
|
941
|
+
return undefined;
|
|
942
|
+
const o = v;
|
|
943
|
+
const seg = o.segment;
|
|
944
|
+
if (typeof seg !== 'string' || seg === '')
|
|
945
|
+
return undefined;
|
|
946
|
+
// pre-B3 兼容臂:判别位**缺席** = 历史形的 `command` 成员(那一代 wire 上根本没有 `kind`)。
|
|
947
|
+
// 🔴 归一放在最前面,后面只有**一个**成员性判据 —— 缺席与在场两条路走同一道闸,不留第二个判官。
|
|
948
|
+
const kind = o.kind === undefined ? 'command' : o.kind;
|
|
949
|
+
// 🔴 **成员性判到 sdk 的闭词表,不手抄字面量**(异源对抗复审 r1 [medium] 逼出的同形残余):
|
|
950
|
+
// 本函数初稿把 `'directoryRead'` / `'command'` 两个词**手写**在分支条件里,导入的
|
|
951
|
+
// `RULE_OFFER_BATCH_MEMBER_KINDS` 只被再导出、运行期一次都没读 —— 那正是 B-025 本身的病形
|
|
952
|
+
// (`match` 手抄两员)在成员这一维上的**残余**,只是当天恰好抄对了。抄对的表明天也会过期。
|
|
953
|
+
if (typeof kind !== 'string')
|
|
954
|
+
return undefined;
|
|
955
|
+
if (!RULE_OFFER_BATCH_MEMBER_KINDS.includes(kind))
|
|
956
|
+
return undefined;
|
|
957
|
+
if (kind === 'directoryRead') {
|
|
958
|
+
const rule = o.rule;
|
|
959
|
+
const dir = o.directory;
|
|
960
|
+
if (typeof rule !== 'string' || rule === '')
|
|
961
|
+
return undefined;
|
|
962
|
+
if (typeof dir !== 'string' || dir === '')
|
|
963
|
+
return undefined;
|
|
964
|
+
return { kind: 'directoryRead', rule, directory: dir, segment: seg };
|
|
965
|
+
}
|
|
966
|
+
if (kind === 'command') {
|
|
967
|
+
const triple = readRuleTriple(v);
|
|
968
|
+
if (triple === undefined)
|
|
969
|
+
return undefined;
|
|
970
|
+
return { kind: 'command', ...triple, segment: seg };
|
|
971
|
+
}
|
|
972
|
+
// 🔴 **表内、但本包还没有读器的 kind** ⇒ 与表外同处置(丢 ⇒ 调用方丢整只 batch),绝不猜一个形。
|
|
973
|
+
// 上游往闭词表加员而本包没跟时走这条路:行为是 fail toward asking(合取批不渲,兄弟 single 照留),
|
|
974
|
+
// 而门 ⑨c 的「⊇ 成员 kind 在读器里走得通」那一腿会**当天红**,逼人补读器 —— 静默降级与显形二者兼得。
|
|
975
|
+
return undefined;
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* `uncoveredDetail`(design/382 §3.5 **additive** 明细座)的窄化。
|
|
979
|
+
*
|
|
980
|
+
* 🔴 **只丢坏行,绝不因为它否决整只 batch**:additive 位到货**不许回头削弱既有位的现行为** ——
|
|
981
|
+
* 老引擎压根不铸这一位,server 自己对坏形的座也是「只丢座不丢批」。整只读空 ⇒ 本位缺席
|
|
982
|
+
* (**不是**空数组:空数组会被端读成「已确认没有未覆盖段」,而真源恒是 `uncoveredSegments`)。
|
|
983
|
+
* 🔴 `reason` 判到 sdk 8.2.0 的闭三词集(`redirection` / `no_rule_form` / `cap_overflow`);
|
|
984
|
+
* 不识的 reason 只丢那一行 —— 它是**给人看的因由**,不驱动任何兑付动作。
|
|
985
|
+
*/
|
|
986
|
+
function readUncoveredDetail(v) {
|
|
987
|
+
if (!Array.isArray(v))
|
|
988
|
+
return undefined;
|
|
989
|
+
if (v.length === 0 || v.length > MAX_RULE_OFFER_UNCOVERED_DETAIL_TOLERATED)
|
|
990
|
+
return undefined;
|
|
991
|
+
const out = [];
|
|
992
|
+
for (const raw of v) {
|
|
993
|
+
if (raw === null || typeof raw !== 'object')
|
|
994
|
+
continue;
|
|
995
|
+
const o = raw;
|
|
996
|
+
if (typeof o.segment !== 'string' || o.segment === '')
|
|
997
|
+
continue;
|
|
998
|
+
if (typeof o.reason !== 'string')
|
|
999
|
+
continue;
|
|
1000
|
+
if (!RULE_OFFER_UNCOVERED_REASONS.includes(o.reason))
|
|
1001
|
+
continue;
|
|
1002
|
+
out.push({ segment: o.segment, reason: o.reason });
|
|
1003
|
+
}
|
|
1004
|
+
return out.length > 0 ? out : undefined;
|
|
1005
|
+
}
|
|
835
1006
|
/** offer **条数**的坏形帽。server 侧执法帽是 `MAX_RULE_OFFERS`=4(core 契约本就 ≤2),本包留 8 ——
|
|
836
1007
|
* [4050] 随批立的纪律:**窄读域只许等于或宽于铸点域,绝不更窄**;超帽 = 坏形/注入,整只降缺席
|
|
837
1008
|
* (截了不留痕会掩盖注入)。 */
|
|
@@ -839,6 +1010,23 @@ const MAX_RULE_OFFERS_TOLERATED = 8;
|
|
|
839
1010
|
/** 一条 `batch` 的**成员**条数坏形帽(core 契约 1..5,server 容忍帽 8)。与上面那个刻意分家:
|
|
840
1011
|
* 两者数的是不同的东西(几个选项 vs 一个选项里几条规则),混成一个数会让上游放宽任一维时误伤另一维。 */
|
|
841
1012
|
const MAX_RULE_OFFER_BATCH_MEMBERS_TOLERATED = 8;
|
|
1013
|
+
/**
|
|
1014
|
+
* `uncoveredDetail` 的**行数**坏形帽(0.58.0)。
|
|
1015
|
+
*
|
|
1016
|
+
* 🔴 **锚在上游的铸点域上,不是按兄弟帽类比**(异源对抗复审 r1 [medium] 真病修):本位初稿写的是
|
|
1017
|
+
* `8`,理由「与成员帽同源同量级」—— 那是**类比不是取证**,而且判错了源。真实铸点域:
|
|
1018
|
+
* · server 的执法帽是 `MAX_UNCOVERED_DETAIL_ROWS = **32**`(`@sema-agent/server`
|
|
1019
|
+
* `src/approval-card.ts`:zod `.max(32)` 与座级预筛 `screenUncoveredDetailSeat` 同用);
|
|
1020
|
+
* · 未覆盖段数**根本不受成员帽约束** —— core 逐字 `uncoveredSegments: uncoveredDetail.length`
|
|
1021
|
+
* (`permission-rule-model.ts`),一只**只有 1 个成员**的 batch 可以带 9 条明细(9 个带重定向
|
|
1022
|
+
* 的段各自 mint 不出规则形,进不了 `rules[]` 却每段都要给一个「为什么还会问」)。
|
|
1023
|
+
* ⇒ 帽 8 是**比铸点域更窄**的窄读域,正是 [4050] 那条纪律禁的形:一只合法的 9~32 行明细座被整座
|
|
1024
|
+
* 静默丢掉,人失去「这几段为什么还会问」。边界钉在门 ⑨d(9 / 32 必收、33 才丢)。
|
|
1025
|
+
* 🔴 **取等于而不是再加倍**:与 {@link MAX_RULE_OFFERS_TOLERATED}(server 执法帽 4,本包留 8)不同 ——
|
|
1026
|
+
* 那一位没有可锚的上游常量,余量是纯容忍;本位有**具名铸点常量**,取等于就是「窄读域等于铸点域」,
|
|
1027
|
+
* 再自行加倍反而是又一次凭感觉编数。上游哪天放宽,门 ⑨d 的 33 行负控会当天红,逼人跟着抬。
|
|
1028
|
+
*/
|
|
1029
|
+
const MAX_RULE_OFFER_UNCOVERED_DETAIL_TOLERATED = 32;
|
|
842
1030
|
/**
|
|
843
1031
|
* `ruleOffers`(server ≥7.46.0 的判别联合)的结构读。
|
|
844
1032
|
*
|
|
@@ -893,19 +1081,28 @@ export function readRuleOffers(v) {
|
|
|
893
1081
|
const members = [];
|
|
894
1082
|
let memberShapeOk = true;
|
|
895
1083
|
for (const raw of o.rules) {
|
|
896
|
-
const triple = readRuleTriple(raw);
|
|
897
|
-
const seg = raw?.segment;
|
|
898
1084
|
// 🔴 batch 成员**不逐条丢**:合取批是「对全体成员一次说是」,少一条成员就是另一次授权 ——
|
|
899
|
-
// 渲一个缺了成员的批,人点的与引擎兑的不是同一批规则。任一成员不合形
|
|
900
|
-
|
|
1085
|
+
// 渲一个缺了成员的批,人点的与引擎兑的不是同一批规则。任一成员不合形(含 `kind` 不识)
|
|
1086
|
+
// ⇒ 整条 batch 丢。这是 core design/382 §2.3 的**规范性**降级臂,不是本包的保守选择:
|
|
1087
|
+
// 逐字 "drops the WHOLE batch offer — never the single member ... and never the whole card"。
|
|
1088
|
+
const member = readRuleOfferBatchMember(raw);
|
|
1089
|
+
if (member === undefined) {
|
|
901
1090
|
memberShapeOk = false;
|
|
902
1091
|
break;
|
|
903
1092
|
}
|
|
904
|
-
members.push(
|
|
1093
|
+
members.push(member);
|
|
905
1094
|
}
|
|
906
1095
|
if (!memberShapeOk)
|
|
907
1096
|
continue;
|
|
908
|
-
|
|
1097
|
+
// additive 明细座(0.58.0):只丢坏行、读空即缺席,**绝不**因为它否决整只 batch。
|
|
1098
|
+
const detail = readUncoveredDetail(item.uncoveredDetail);
|
|
1099
|
+
out.push({
|
|
1100
|
+
kind: 'batch',
|
|
1101
|
+
offerIndex: i,
|
|
1102
|
+
rules: members,
|
|
1103
|
+
uncoveredSegments: uncovered,
|
|
1104
|
+
...(detail !== undefined ? { uncoveredDetail: detail } : {}),
|
|
1105
|
+
});
|
|
909
1106
|
continue;
|
|
910
1107
|
}
|
|
911
1108
|
// 不认识的 kind(含 kind 缺席)⇒ 丢这一条,原始下标照旧不给后面的成员用。
|
|
@@ -1029,6 +1226,9 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
|
|
|
1029
1226
|
// #334(0.43.0):新键 `ruleOffers`(server ≥7.46.0 判别联合)优先,旧键 `ruleSuggestions`
|
|
1030
1227
|
// (≤7.45)归一成 `kind:'single'` 兜底 —— 两代经同一把窄读器,包内出口只有一个形。
|
|
1031
1228
|
const ruleOffers = readRuleOfferSupply(frame.ruleOffers, frame.ruleSuggestions);
|
|
1229
|
+
// S-114(0.59.0):限额回落卡的四成员窄读**算一次**(读器是纯函数,但两次调用会让「上卡的那一份
|
|
1230
|
+
// 与判在场的那一份是不是同一个对象」在读代码时需要推理 —— 单点求值把它变成显然的)。
|
|
1231
|
+
const denialLimitFallback = readDenialLimitFallback(frame.denialLimitFallback);
|
|
1032
1232
|
const card = await surfaceApprovalCard({
|
|
1033
1233
|
toolName,
|
|
1034
1234
|
args: args,
|
|
@@ -1101,6 +1301,23 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
|
|
|
1101
1301
|
// 「没扫到」渲成「已确认干净」,而这一位存在的全部理由就是「人眼读到的顺序 ≠ 真跑的字节顺序」。
|
|
1102
1302
|
// 🔴 披露不清洗:args 字节在本层一个都不改(清洗会让卡上显示的与真跑的不是同一个东西)。
|
|
1103
1303
|
...(frame.inputHasBidi === true ? { inputHasBidi: true } : {}),
|
|
1304
|
+
// ── S-15 第五单(0.59.0):报价缺席因由透传 ────────────────────────────────────────────────
|
|
1305
|
+
// **非空串才 stamp**,词表**不校**(开集;server 已按闭集拒过词表外的值,包再校一遍只会在
|
|
1306
|
+
// core 加员当天把合法值判没 —— `autoMode.reason` 的透传纪律逐字同规)。缺席/坏形 ⇒ 键不 stamp。
|
|
1307
|
+
// 🔴 与 `ruleOffers` 引擎侧互斥,但本层**不据此互删**:两者同时在场是上游坏形,原样上卡让端
|
|
1308
|
+
// 看得见,包替上游把矛盾湮灭掉只会让排障说不清是谁窄没的。
|
|
1309
|
+
...(typeof frame.ruleOffersAbsence === 'string' && frame.ruleOffersAbsence !== ''
|
|
1310
|
+
? { ruleOffersAbsence: frame.ruleOffersAbsence }
|
|
1311
|
+
: {}),
|
|
1312
|
+
// ── S-114(0.59.0):auto 分类器限额回落卡透传 ──────────────────────────────────────────────
|
|
1313
|
+
// 经 `readDenialLimitFallback` 四成员窄读(缺一整只丢;判据本体在该函数顶注)。
|
|
1314
|
+
// 🔴 stamp 的是**窄读产物**而不是原对象:端拿到的四座恒是有限非负数 + 非空 limit 串,
|
|
1315
|
+
// 不必各自再写一遍同样的判断(三端各写一遍 = 三份会漂的判官)。
|
|
1316
|
+
...(denialLimitFallback !== undefined ? { denialLimitFallback } : {}),
|
|
1317
|
+
// ── S-125③/#564(0.59.0):ask 出身透传 ────────────────────────────────────────────────────
|
|
1318
|
+
// 非空串才 stamp,词表**不校**(同 ruleOffersAbsence)。🔴 缺席 ⇒ 键不 stamp,**绝不折成
|
|
1319
|
+
// `"policy"`**:那是一个正面事实(出自部署 ToolPolicy),把「老引擎没报」折进去 = 替引擎编话。
|
|
1320
|
+
...(typeof frame.origin === 'string' && frame.origin !== '' ? { origin: frame.origin } : {}),
|
|
1104
1321
|
});
|
|
1105
1322
|
const decision = card.kind === 'allow' ? (card.allowSession ? 'allow_session' : 'allow') : 'deny';
|
|
1106
1323
|
if (card.kind === 'failed') {
|
package/dist/index.d.ts
CHANGED
|
@@ -148,6 +148,7 @@ export * from './liveQuestionStore.js';
|
|
|
148
148
|
export * from './engineInlineTaskStats.js';
|
|
149
149
|
export * from './printToolResultFrame.js';
|
|
150
150
|
export * from './engineCapsCache.js';
|
|
151
|
+
export * from './sqlEngineCapability.js';
|
|
151
152
|
export * from './engineToolLabelStore.js';
|
|
152
153
|
export * from './fleetTaskDesc.js';
|
|
153
154
|
export type * from './types/engineState.js';
|
|
@@ -215,6 +216,7 @@ export * from './engineSessionParam.js';
|
|
|
215
216
|
export * from './engineWireTarget.js';
|
|
216
217
|
export * from './principalWire.js';
|
|
217
218
|
export * from './wireErrorTriage.js';
|
|
219
|
+
export * from './resumeRefusalCopy.js';
|
|
218
220
|
export * from './sessionMap.js';
|
|
219
221
|
export * from './detachWire.js';
|
|
220
222
|
export * from './workflowMonitor.js';
|
package/dist/index.js
CHANGED
|
@@ -160,6 +160,8 @@ export * from './liveQuestionStore.js';
|
|
|
160
160
|
export * from './engineInlineTaskStats.js';
|
|
161
161
|
export * from './printToolResultFrame.js';
|
|
162
162
|
export * from './engineCapsCache.js';
|
|
163
|
+
// S-131(0.59.0):`Capabilities.sql` 的四态窄读器 —— 壳侧那份逐字上收(导出名同名 = drift-lock)。
|
|
164
|
+
export * from './sqlEngineCapability.js';
|
|
163
165
|
export * from './engineToolLabelStore.js';
|
|
164
166
|
export * from './fleetTaskDesc.js';
|
|
165
167
|
// ── B2 批:通知族合并 / caps-wire 门族 / 模型面纯逻辑 / control 路由(2026-07-27)──────────────
|
|
@@ -300,6 +302,11 @@ export * from './principalWire.js';
|
|
|
300
302
|
// · wireErrorTriage:turn 错误分型判定半场(A-028.11;文案/渲染归端)+ scenario 拒绝判型
|
|
301
303
|
// (A-028.13;web 逐字节同形过滤行的正主)。码字面引 engineErrorCodes。
|
|
302
304
|
export * from './wireErrorTriage.js';
|
|
305
|
+
// · resumeRefusalCopy:L-102 下半场(0.58.0)—— resume 拒绝的**人话铸点**三端单源。
|
|
306
|
+
// 与上面那口分工:`wireErrorTriage.resumeRetryLaterFromError` 答「能不能等」(事实读数),
|
|
307
|
+
// 本口答「该对人说什么」(文案),`preflight_rejected` 的窗与可等性**转调**前者不复制判定。
|
|
308
|
+
// 壳侧对位 = cli `src/sema/resumeRefusalCopy.ts`(1.0.101 起改薄成适配层)。
|
|
309
|
+
export * from './resumeRefusalCopy.js';
|
|
303
310
|
// · sessionMap:「客户端会话 id ↔ 引擎会话 id」映射单一键形 + merge 判定(A-028.12;存储经
|
|
304
311
|
// SessionMapStorePort 归端 —— cli 文件锁/原子写,web localStorage)。
|
|
305
312
|
export * from './sessionMap.js';
|
|
@@ -105,6 +105,24 @@ export interface ToolEndResultArmLike {
|
|
|
105
105
|
* 🔴 本形同样是**防御性读形**(`unknown`):九词是引擎的闭集,消费方分支已知值 + 永远带 default。
|
|
106
106
|
*/
|
|
107
107
|
resolution?: unknown;
|
|
108
|
+
/**
|
|
109
|
+
* 这次 deny 是**引擎的窗自己拒的**(S-125⑧,0.59.0;core 7.4.0 auto 模式分类器的限额回落窗 →
|
|
110
|
+
* server ≥7.57.0 `tool_end.autoDenied`)。`eventToSdkMessage` 的 `case 'tool_end'` 臂**严格
|
|
111
|
+
* `true` 才上臂**,与 {@link settledBy}/{@link approver}/{@link resolution} 同款「臂带、卡不带」。
|
|
112
|
+
*
|
|
113
|
+
* 🔴 **它答的是既有两键答不了的那一问**:`settledBy:"timeout"` 与 `resolution:"window_expired"`
|
|
114
|
+
* 覆盖**一切**审批窗到期(普通 ask 的 TTL 也在内),分不出「这一次到期是**自动拒**收的场」还是
|
|
115
|
+
* 「park 了等人」。要渲「自动拒(限额回落)」而不是「已转后台候批」,判据只有这一位。
|
|
116
|
+
* 🔴 **缺席不带语义,不许反推**:缺席同时覆盖「不是自动拒」「老引擎(<7.57.0)不报」「这次根本
|
|
117
|
+
* 没走审批」三形 ⇒ **禁**读成「是人拒的」。
|
|
118
|
+
* 🔴 **机读位是二值的**:`true` 才有意义,「在场但不是 true」没有语义 —— 消费端同样只认严格 `true`。
|
|
119
|
+
* 🔴 **不据它自铸第二只定时器/第二张资格表**:窗的执行全在引擎(上游
|
|
120
|
+
* `tool_approval.denialLimitFallback` 顶注的同一条禁令),本位是**事后**的判别位。
|
|
121
|
+
* ⇄ 卡面那一侧的孪生位 = `ApprovalCardRequest.denialLimitFallback`(带触限计数与窗),两者是
|
|
122
|
+
* 同一件事的**事前 / 事后**两个面:卡上告诉人「再拒就到限额了,这一次必须你来批」,本位告诉人
|
|
123
|
+
* 「那张卡的窗走完了,引擎替你拒了」。
|
|
124
|
+
*/
|
|
125
|
+
autoDenied?: unknown;
|
|
108
126
|
parentToolCallId?: unknown;
|
|
109
127
|
uuid?: unknown;
|
|
110
128
|
session_id?: unknown;
|
|
@@ -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
|
+
}
|