@sema-agent/client-core 0.37.0 → 0.38.1
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 +159 -14
- package/README.md +3 -2
- package/dist/adapt/arms.js +40 -0
- package/dist/adapt.d.ts +1 -1
- package/dist/adapt.js +2 -0
- package/dist/adapter/activeRunSelfHeal.d.ts +1 -1
- package/dist/adapter/activeRunSelfHeal.js +6 -6
- package/dist/adapter/downstream/eventToSdkMessage.js +94 -0
- package/dist/agentSession/backgroundView.d.ts +1 -1
- package/dist/agentSession/backgroundView.js +1 -1
- package/dist/engineCapsCache.d.ts +33 -1
- package/dist/engineCapsCache.js +26 -4
- package/dist/engineErrorCodes.d.ts +20 -0
- package/dist/engineErrorCodes.js +44 -0
- package/dist/fleet/fleetLedger.d.ts +1 -1
- package/dist/fleet/fleetLedger.js +3 -3
- package/dist/fleet/fleetProjection.d.ts +34 -0
- package/dist/fleet/fleetProjection.js +40 -0
- package/dist/hitl/armedGateRegistry.d.ts +1 -1
- package/dist/hitl/armedGateRegistry.js +2 -2
- package/dist/hitl/hitlBridge.js +77 -18
- package/dist/hitl/persistedRulesWire.d.ts +1 -1
- package/dist/hitl/persistedRulesWire.js +4 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -0
- package/dist/model/modelSupplyRules.d.ts +102 -0
- package/dist/model/modelSupplyRules.js +149 -0
- package/dist/model/providerPresets.js +68 -12
- package/dist/request/taskRequest.d.ts +1 -1
- package/dist/request/taskRequest.js +8 -8
- package/dist/seam.d.ts +48 -1
- package/dist/seam.js +7 -0
- package/dist/subagent/engineSubagentResume.d.ts +20 -2
- package/dist/subagent/engineSubagentResume.js +8 -1
- package/dist/subagent/engineSubagentSteer.d.ts +1 -1
- package/dist/subagent/engineSubagentSteer.js +1 -1
- package/dist/subagent/subagentOwnerAbsence.js +1 -1
- package/dist/subagentContentStore.js +1 -1
- package/dist/wireErrorTriage.js +1 -1
- package/docs/INTEGRATION-CLIENTS.md +70 -20
- package/docs/REFACTOR-LEDGER.md +2 -2
- package/package.json +3 -3
|
@@ -34,7 +34,39 @@ export declare function kickEngineCapsProbe(baseUrl: string, probe: () => Promis
|
|
|
34
34
|
* 绝不 reject(探测失败=位维持未判,调用方按缺席降级)。
|
|
35
35
|
*/
|
|
36
36
|
export declare function engineCapsSettled(baseUrl: string | undefined): Promise<void>;
|
|
37
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* 能力位的**可分辨读数**(#318 件③,cli [4752] 自领缺口2)——{@link engineCapTrue} 的三态化底座。
|
|
39
|
+
*
|
|
40
|
+
* 病:`engineCapTrue` 只回 `true`/`false`,于是 `false` 同时承载**四件互不相同**的事 ——
|
|
41
|
+
* 「引擎明说没有」「还没探」「探测在飞」「探测失败」。对**放行判据**而言这个塌缩是正确且刻意的
|
|
42
|
+
* (缺席一律 fail-closed 不渲 affordance,见 engineCapTrue 的头注),但对**自检/诊断面**(doctor)
|
|
43
|
+
* 它是致命的:doctor 要报的正是「这台引擎到底说了什么」,把「不知道」印成「没有」就是谎报。
|
|
44
|
+
* 这也是 doctor 此前无法复用本共享缓存、只能自己再探一遍的直接原因。
|
|
45
|
+
*
|
|
46
|
+
* 🔴 **闭集,且每个成员各自可行动**(A1 禁哨兵值双义):
|
|
47
|
+
* · `true` / `false` —— caps **已落地**且该键字面为该布尔值 = **引擎明说**;
|
|
48
|
+
* · `unprobed` —— 该 base 此刻没有已落地的 caps(没 kick / 在飞 / 探测失败 / 刚被
|
|
49
|
+
* {@link invalidateEngineCaps} 作废)。🔴 四种成因**故意合并**:它们对调用方是同一个动作 ——
|
|
50
|
+
* `await` 一次 {@link engineCapsSettled} 再读,或按「不知道」呈现。分开需要引擎没给的信息;
|
|
51
|
+
* · `absent` —— caps 已落地,但**没有这个键**:引擎比本包旧(还没这条车道)或比本包新(改名了);
|
|
52
|
+
* · `non_boolean` —— caps 已落地、键也在,但值不是布尔。这一档**不并进 `absent`**:那会是假话
|
|
53
|
+
* (键在),而它指向的是真问题(引擎申报了一个本口读不动的形 —— 比如把 caps 位写成了字符串),
|
|
54
|
+
* 诊断面该看见它。
|
|
55
|
+
*
|
|
56
|
+
* 🔴 **本读口不改变任何放行语义**:{@link engineCapTrue} 逐字等价于 `engineCapState(...) === 'true'`
|
|
57
|
+
* (下面就是这么实现的,单源)。放行面**继续**用 `engineCapTrue` —— 拿三态去开放行分支,等于把
|
|
58
|
+
* fail-closed 改成「按成因区别对待」,那是另一件事,不在本口的授权内。
|
|
59
|
+
*/
|
|
60
|
+
export type EngineCapState = 'true' | 'false' | 'unprobed' | 'absent' | 'non_boolean';
|
|
61
|
+
/**
|
|
62
|
+
* 同步读口(三态化):该 base 的 caps 里 key 的**可分辨**读数。语义逐条见 {@link EngineCapState}。
|
|
63
|
+
*
|
|
64
|
+
* `baseUrl` 缺席 ⇒ `unprobed`(没有 base 就没有任何一次探测,这不是「引擎说没有」)。
|
|
65
|
+
*/
|
|
66
|
+
export declare function engineCapState(baseUrl: string | undefined, key: string): EngineCapState;
|
|
67
|
+
/** 同步读口:该 base 的 caps 里 key 是否字面 true(未判/缺键 = false)。
|
|
68
|
+
* 🔴 **语义一字不变**(#318 件③ 三态化后改为经 {@link engineCapState} 单源实现):放行面读的就是
|
|
69
|
+
* 这一口,四种「不知道」继续一律折成 `false` = fail-closed。要分辨成因请读 {@link engineCapState}。 */
|
|
38
70
|
export declare function engineCapTrue(baseUrl: string | undefined, key: string): boolean;
|
|
39
71
|
/**
|
|
40
72
|
* 同步读口(字符串键):该 base 的 caps 里 key 的字符串值,未判/缺键/非字符串 ⇒ undefined。
|
package/dist/engineCapsCache.js
CHANGED
|
@@ -143,11 +143,33 @@ export async function engineCapsSettled(baseUrl) {
|
|
|
143
143
|
return;
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
|
-
/**
|
|
147
|
-
|
|
146
|
+
/**
|
|
147
|
+
* 同步读口(三态化):该 base 的 caps 里 key 的**可分辨**读数。语义逐条见 {@link EngineCapState}。
|
|
148
|
+
*
|
|
149
|
+
* `baseUrl` 缺席 ⇒ `unprobed`(没有 base 就没有任何一次探测,这不是「引擎说没有」)。
|
|
150
|
+
*/
|
|
151
|
+
export function engineCapState(baseUrl, key) {
|
|
148
152
|
if (!baseUrl)
|
|
149
|
-
return
|
|
150
|
-
|
|
153
|
+
return 'unprobed';
|
|
154
|
+
const caps = capsByBase.get(baseUrl);
|
|
155
|
+
if (caps === undefined)
|
|
156
|
+
return 'unprobed';
|
|
157
|
+
// 🔴 `in` 而不是 `!== undefined`:引擎显式发 `{"foo": undefined}` 在 JSON 上不可能,但本表也接
|
|
158
|
+
// 宿主注入的对象;`in` 问的是「键在不在」,正是 `absent` 这一档要答的问题。
|
|
159
|
+
if (!(key in caps))
|
|
160
|
+
return 'absent';
|
|
161
|
+
const v = caps[key];
|
|
162
|
+
if (v === true)
|
|
163
|
+
return 'true';
|
|
164
|
+
if (v === false)
|
|
165
|
+
return 'false';
|
|
166
|
+
return 'non_boolean';
|
|
167
|
+
}
|
|
168
|
+
/** 同步读口:该 base 的 caps 里 key 是否字面 true(未判/缺键 = false)。
|
|
169
|
+
* 🔴 **语义一字不变**(#318 件③ 三态化后改为经 {@link engineCapState} 单源实现):放行面读的就是
|
|
170
|
+
* 这一口,四种「不知道」继续一律折成 `false` = fail-closed。要分辨成因请读 {@link engineCapState}。 */
|
|
171
|
+
export function engineCapTrue(baseUrl, key) {
|
|
172
|
+
return engineCapState(baseUrl, key) === 'true';
|
|
151
173
|
}
|
|
152
174
|
/**
|
|
153
175
|
* 同步读口(字符串键):该 base 的 caps 里 key 的字符串值,未判/缺键/非字符串 ⇒ undefined。
|
|
@@ -59,11 +59,31 @@ export declare const CONFIG_STRATEGY_FIND_LIMIT_INVALID = "config.strategy_find_
|
|
|
59
59
|
export declare const CONFIG_STRATEGY_MAX_SIZE_INVALID = "config.strategy_max_size_invalid";
|
|
60
60
|
/** 未知的 limits 键(5.8.0 起:同时发新旧两代键会在这里当场失败,所以写面只发单一新形)。 */
|
|
61
61
|
export declare const CONFIG_LIMIT_UNKNOWN_KEY = "config.limit_unknown_key";
|
|
62
|
+
/**
|
|
63
|
+
* `RunnerDeps.delegationEntryCaps`(core 5.48.0 / design/323,[4743] @cli 点名件)的坏值拒。
|
|
64
|
+
*
|
|
65
|
+
* 两种拒绝形:成员不是正整数(NaN / 0 / 负数 / 分数 / 非数),或**解出的一对**满足
|
|
66
|
+
* `maxConcurrent > maxCumulativePerSession`(一棵树不可能同时跑得比它这辈子能创建的还多 ——
|
|
67
|
+
* 这是矛盾不是偏好)。缺席成员取 CC 对齐缺省(20 / 200)。
|
|
68
|
+
* 🔴 与本族其余成员同律:**坏旋钮响亮拒,绝不静默折回缺省**。
|
|
69
|
+
*/
|
|
70
|
+
export declare const CONFIG_DELEGATION_ENTRY_CAPS = "config.delegation_entry_caps";
|
|
62
71
|
/**
|
|
63
72
|
* 已知的配置拒绝码(**识别表,非白名单**)。判「这是不是一条配置拒绝」请用
|
|
64
73
|
* {@link isConfigRefusalCode} —— 它按 `config.` 前缀判,未来新成员自动落进来。
|
|
65
74
|
*/
|
|
66
75
|
export declare const CONFIG_REFUSAL_CODES: ReadonlySet<string>;
|
|
76
|
+
/** 并发帽:这棵树此刻活着的委派席位已达 `delegationEntryCaps.maxConcurrent`(CC 对齐缺省 20)。
|
|
77
|
+
* 🔴 处置 = **可等**(兄弟结束即有位),别渲成「配置要改」。 */
|
|
78
|
+
export declare const DELEGATION_CONCURRENCY_CAP = "delegation.concurrency_cap";
|
|
79
|
+
/** 会话累计帽:这棵树累计创建的委派席位已达 `maxCumulativePerSession`(CC 对齐缺省 200)。
|
|
80
|
+
* 🔴 处置 = **等也没用**(配额是这条会话这辈子的),别渲成「稍后重试」。 */
|
|
81
|
+
export declare const DELEGATION_SESSION_CAP = "delegation.session_cap";
|
|
82
|
+
/** 委派席位到限码识别表(**开集**:core 可能再加第三根轴)。两员处置不对称,消费点禁合并分支。 */
|
|
83
|
+
export declare const DELEGATION_CAP_CODES: ReadonlySet<string>;
|
|
84
|
+
/** `delegation.` 前缀谓词 —— **开集**判别(与 {@link isConfigRefusalCode} 同款)。缺席/空串 ⇒ false。
|
|
85
|
+
* 🔴 它只回答「这是不是一条委派席位拒绝」;**该等还是该换会话**必须按成员分,见两码各自的注释。 */
|
|
86
|
+
export declare function isDelegationCapCode(code: string | undefined): boolean;
|
|
67
87
|
/** `config.` 前缀谓词 —— **开集**判别:5.10.0 之后每一波「不许静默折叠」都会往这一族加词,
|
|
68
88
|
* 按前缀判的消费点不必跟车,按成员判的必须跟车。缺席/空串 ⇒ false。 */
|
|
69
89
|
export declare function isConfigRefusalCode(code: string | undefined): boolean;
|
package/dist/engineErrorCodes.js
CHANGED
|
@@ -67,6 +67,15 @@ export const CONFIG_STRATEGY_FIND_LIMIT_INVALID = 'config.strategy_find_limit_in
|
|
|
67
67
|
export const CONFIG_STRATEGY_MAX_SIZE_INVALID = 'config.strategy_max_size_invalid';
|
|
68
68
|
/** 未知的 limits 键(5.8.0 起:同时发新旧两代键会在这里当场失败,所以写面只发单一新形)。 */
|
|
69
69
|
export const CONFIG_LIMIT_UNKNOWN_KEY = 'config.limit_unknown_key';
|
|
70
|
+
/**
|
|
71
|
+
* `RunnerDeps.delegationEntryCaps`(core 5.48.0 / design/323,[4743] @cli 点名件)的坏值拒。
|
|
72
|
+
*
|
|
73
|
+
* 两种拒绝形:成员不是正整数(NaN / 0 / 负数 / 分数 / 非数),或**解出的一对**满足
|
|
74
|
+
* `maxConcurrent > maxCumulativePerSession`(一棵树不可能同时跑得比它这辈子能创建的还多 ——
|
|
75
|
+
* 这是矛盾不是偏好)。缺席成员取 CC 对齐缺省(20 / 200)。
|
|
76
|
+
* 🔴 与本族其余成员同律:**坏旋钮响亮拒,绝不静默折回缺省**。
|
|
77
|
+
*/
|
|
78
|
+
export const CONFIG_DELEGATION_ENTRY_CAPS = 'config.delegation_entry_caps';
|
|
70
79
|
/**
|
|
71
80
|
* 已知的配置拒绝码(**识别表,非白名单**)。判「这是不是一条配置拒绝」请用
|
|
72
81
|
* {@link isConfigRefusalCode} —— 它按 `config.` 前缀判,未来新成员自动落进来。
|
|
@@ -76,7 +85,42 @@ export const CONFIG_REFUSAL_CODES = new Set([
|
|
|
76
85
|
CONFIG_STRATEGY_FIND_LIMIT_INVALID,
|
|
77
86
|
CONFIG_STRATEGY_MAX_SIZE_INVALID,
|
|
78
87
|
CONFIG_LIMIT_UNKNOWN_KEY,
|
|
88
|
+
CONFIG_DELEGATION_ENTRY_CAPS,
|
|
79
89
|
]);
|
|
90
|
+
// ── 委派席位到限族(core 5.48.0 design/323,[4743] @cli 点名的「两新 coded 拒绝」)──────────────
|
|
91
|
+
//
|
|
92
|
+
// 语义:一次 Task/SendMessage 委派因为**席位帽**被拒 —— 不是配置坏了(那是上面的
|
|
93
|
+
// `config.delegation_entry_caps`),也不是失败了,而是「现在不行」。两码**处置不同,禁合并**:
|
|
94
|
+
// · 并发帽 ⇒ **等**:活着的兄弟结束就有位,同一条命令过一会儿照样成;
|
|
95
|
+
// · 会话累计帽 ⇒ **等也没用**:这棵树这辈子的配额用完了,要么换会话要么抬 deps 旋钮。
|
|
96
|
+
// 把两者渲成同一句「委派失败」会让第一种情形的用户去改配置,第二种情形的用户去干等。
|
|
97
|
+
//
|
|
98
|
+
// 🔴 **载体如实登记(2026-08-21 亲验,别按 `tool_end.errorCode` 写消费码)**:core 把这两码铸进
|
|
99
|
+
// Task 工具结果体的 `details.error`(`subagent.ts` 的 `capRefusal`),而 core 的 `errorCode`
|
|
100
|
+
// 抬升腿(`runner/runtask.ts`)只读 `details.code` → `details.errorKind` **两个拼法**,
|
|
101
|
+
// `structuredFrom` 又要求 `details.type` 落在 `CC_DETAIL_TYPES` 里(这条 detail 连 `type` 都没有)
|
|
102
|
+
// ⇒ **今天这两码在 wire 上既不在 `tool_end.errorCode`、也不在 `structured`**,只剩模型面文案
|
|
103
|
+
// ("Sub-agent not started in background: …")。
|
|
104
|
+
// ⇒ 本词表**先立词、不落消费分支**:按文案反解正是本文件存在的理由要根除的东西
|
|
105
|
+
// ([cross-repo-fix-at-source-constitution]:载体缺口在 core,下游不许侧路补救)。
|
|
106
|
+
// 上游诉求已登记(见 CHANGELOG 0.38.0 段):core 补 `details.code` 孪生拼法(或抬升腿兼读
|
|
107
|
+
// `details.error`)之后,消费分支在本包同批接上,**判定归本包、文案归端**。
|
|
108
|
+
/** 并发帽:这棵树此刻活着的委派席位已达 `delegationEntryCaps.maxConcurrent`(CC 对齐缺省 20)。
|
|
109
|
+
* 🔴 处置 = **可等**(兄弟结束即有位),别渲成「配置要改」。 */
|
|
110
|
+
export const DELEGATION_CONCURRENCY_CAP = 'delegation.concurrency_cap';
|
|
111
|
+
/** 会话累计帽:这棵树累计创建的委派席位已达 `maxCumulativePerSession`(CC 对齐缺省 200)。
|
|
112
|
+
* 🔴 处置 = **等也没用**(配额是这条会话这辈子的),别渲成「稍后重试」。 */
|
|
113
|
+
export const DELEGATION_SESSION_CAP = 'delegation.session_cap';
|
|
114
|
+
/** 委派席位到限码识别表(**开集**:core 可能再加第三根轴)。两员处置不对称,消费点禁合并分支。 */
|
|
115
|
+
export const DELEGATION_CAP_CODES = new Set([
|
|
116
|
+
DELEGATION_CONCURRENCY_CAP,
|
|
117
|
+
DELEGATION_SESSION_CAP,
|
|
118
|
+
]);
|
|
119
|
+
/** `delegation.` 前缀谓词 —— **开集**判别(与 {@link isConfigRefusalCode} 同款)。缺席/空串 ⇒ false。
|
|
120
|
+
* 🔴 它只回答「这是不是一条委派席位拒绝」;**该等还是该换会话**必须按成员分,见两码各自的注释。 */
|
|
121
|
+
export function isDelegationCapCode(code) {
|
|
122
|
+
return typeof code === 'string' && code.startsWith('delegation.');
|
|
123
|
+
}
|
|
80
124
|
/** `config.` 前缀谓词 —— **开集**判别:5.10.0 之后每一波「不许静默折叠」都会往这一族加词,
|
|
81
125
|
* 按前缀判的消费点不必跟车,按成员判的必须跟车。缺席/空串 ⇒ false。 */
|
|
82
126
|
export function isConfigRefusalCode(code) {
|
|
@@ -121,7 +121,7 @@ export type HookNoticeFrame = Extract<FleetFrame, {
|
|
|
121
121
|
* 端因此无法对不同强度的证据分级处置(强证据可直接落库/翻卡,弱证据宜先渲染、别拿它当归属结论)。
|
|
122
122
|
* 判据锚在**实际命中的那条臂**上,不是锚在「有没有 parentTaskId」这类前置条件上。
|
|
123
123
|
*
|
|
124
|
-
* 🔴 **每个词命名的是「放行臂」,不是「归属结论」**(
|
|
124
|
+
* 🔴 **每个词命名的是「放行臂」,不是「归属结论」**(对抗复审两轮收紧:首版把 `own_parent` 写成
|
|
125
125
|
* 「硬证据」、二版只给 `own_root` 挂了会话锚警告,都是过度声称)。强度分档如下 —— 前两格的
|
|
126
126
|
* 会话级读法**带前提**,第三格永远只是进程级,第四格根本不是证据。
|
|
127
127
|
*
|
|
@@ -214,7 +214,7 @@ export function createFleetLedger(hooks = {}, opts = {}) {
|
|
|
214
214
|
for (const row of frame.tasks) {
|
|
215
215
|
taskMap.set(row.id, row);
|
|
216
216
|
rowMeta.set(row.id, { receivedAtMs });
|
|
217
|
-
// #242 批 3(
|
|
217
|
+
// #242 批 3(复审 P-F1,红先绿后=B3-G4b):snapshot(重连 REPLACE)腿与增量 task 帧腿
|
|
218
218
|
// 的复活语义**同权** —— snapshot 带回的非终态行,其全部别名(同尾段)的 belt 退休条目
|
|
219
219
|
// 一并清,否则重连别名切换(裸 id 退休 → 复合 id running 回归)会产出「复合 running +
|
|
220
220
|
// 裸 completed(带标)」双影。终态行不清(与增量腿同判);snapshot 里**没有**的任务,
|
|
@@ -350,7 +350,7 @@ export function createFleetLedger(hooks = {}, opts = {}) {
|
|
|
350
350
|
debug('[fleet-frame] MALFORMED bg_notification dropped(taskId 或 status 缺席/非串)');
|
|
351
351
|
return;
|
|
352
352
|
}
|
|
353
|
-
// 🔴 #284 尾件(
|
|
353
|
+
// 🔴 #284 尾件(对抗复审第四轮 high,红先绿后=B3-DIRTY):`parentTaskId` 是**隔离判据的锚**,
|
|
354
354
|
// 它的脏形必须先于判据处置。下面那道 own/foreign 门写成
|
|
355
355
|
// `typeof === 'string' && length > 0 && !isOwnEngineRun(…)` —— 对 number/object/boolean
|
|
356
356
|
// 这类脏值**整条不成立** ⇒ 门不响、通知照收,而新的 evidence 还会把它标成 `absent_parent`
|
|
@@ -400,7 +400,7 @@ export function createFleetLedger(hooks = {}, opts = {}) {
|
|
|
400
400
|
// `parentTaskId` 在不在分开(走到这里且带非空 parentTaskId ⇒ 它必然 ∈ own-run 台账,
|
|
401
401
|
// 否则上面已丢弃)。多臂同时成立报第一条 —— 报的是「这一帧**实际**靠哪条过的门」,
|
|
402
402
|
// 不是「最强的那条理论上也成立」。
|
|
403
|
-
// 🔴 非默认 sessionKey 封顶(0.32.0 发包前扫码采
|
|
403
|
+
// 🔴 非默认 sessionKey 封顶(0.32.0 发包前扫码采 复审 折中,P-31 的诚实半步):前两格的
|
|
404
404
|
// 会话级读法前提=「这条流是按本 ledger 的会话开的」,而开流参数只认默认槽 ⇒ keyed ledger
|
|
405
405
|
// 上该前提**无法成立也无法校验**。封顶词=`session_anchor_untrusted`(不降到 own_parent:
|
|
406
406
|
// 经 serverFailClosed/ownByRoot 放行的帧,其 parentTaskId 可能是 foreign——标进程成员是
|
|
@@ -93,6 +93,18 @@ export interface FleetTaskView {
|
|
|
93
93
|
path: string;
|
|
94
94
|
edits: number;
|
|
95
95
|
}>;
|
|
96
|
+
/**
|
|
97
|
+
* #261 §2① 代际号(0.38.0 提货补投;取值规则见 {@link wireCycleSeq})。
|
|
98
|
+
* 同 id 帧 `cycleSeq` 更大 ⇒ **复活**(新代际,行内累计量重置);更小 ⇒ 前代迟到帧,忽略;
|
|
99
|
+
* **缺席 ⇒ 这条行没有代际概念**(不是第一代)。
|
|
100
|
+
*/
|
|
101
|
+
cycleSeq?: number;
|
|
102
|
+
/**
|
|
103
|
+
* #261 §2② 非亲报终态的投影者(0.38.0 提货补投;取值规则见 {@link wireRetiredBy})。
|
|
104
|
+
* **在场 = 这条终态是对账腿从 durable run 行读出来的**(发布方死了),缺席 = 发布方亲报。
|
|
105
|
+
* 读侧开集,未知词通渲。
|
|
106
|
+
*/
|
|
107
|
+
retiredBy?: string;
|
|
96
108
|
}
|
|
97
109
|
/** 终态行帧 / bg 通知帧共用的 usage 形(SDK `FleetTaskRow.usage` ≡ `FleetBgNotification.usage`)。 */
|
|
98
110
|
export interface FleetRowUsage {
|
|
@@ -230,6 +242,28 @@ export declare function wireEditedFiles(r: Pick<FleetTaskRow, 'editedFiles'>): A
|
|
|
230
242
|
}> | undefined;
|
|
231
243
|
/** 终态四键之 `stoppedBy`(开放枚举 verbatim:"user"/"parent"/"system"/…)。 */
|
|
232
244
|
export declare function wireStoppedBy(r: Pick<FleetTaskRow, 'stoppedBy'>): string | undefined;
|
|
245
|
+
/**
|
|
246
|
+
* #261 §2① `cycleSeq`(server ≥7.25.0 / core 5.36.0 #258;SDK **7.2.0** 才把它声明进
|
|
247
|
+
* `FleetTaskRow` ⇒ 本包 0.38.0 提货补投)—— 这一行的**代际号**,与 `FleetBgNotification.seq` /
|
|
248
|
+
* `task_progress.seq` **同域同轴**(fresh spawn 就是 cycle 1,每次 SendMessage 复活翻 +1)。
|
|
249
|
+
*
|
|
250
|
+
* 🔴 **缺席 = 「无此概念」,不是「第一代」**(SDK 头注逐字):没有 `a*` registry 行的 run ——
|
|
251
|
+
* 同步委派子代 / workflow `wa*` agent / **顶层 run 行** —— 根本没有代际。把缺席读成 1 的消费端
|
|
252
|
+
* 会把「首帧迟到」误判成「复活」。所以这里对**非正整数**一律整键缺席(0 / 负数 / 非有限数 /
|
|
253
|
+
* 非整数都不是合法代际号),绝不 `?? 0`、绝不 `?? 1`。
|
|
254
|
+
*/
|
|
255
|
+
export declare function wireCycleSeq(r: Pick<FleetTaskRow, 'cycleSeq'>): number | undefined;
|
|
256
|
+
/**
|
|
257
|
+
* #261 §2② `retiredBy`(server ≥7.25.0;SDK 7.2.0 声明)—— 这一帧的终态**不是发布方亲报**,
|
|
258
|
+
* 而是对账腿从 durable run 行**投影**出来的(发布方死了,行本会永久僵在活跃集里当幽灵)。
|
|
259
|
+
*
|
|
260
|
+
* 🔴 **发布方亲报的终态帧恒不带此键** ⇒ 两种终态在 wire 上可判:要区分「引擎说它完了」与
|
|
261
|
+
* 「我们从库里读出来它完了」时,这是**唯一**的判据([ghost-rows-need-upstream-liveness] 的
|
|
262
|
+
* wire 侧对位物 —— 此前本层把这一位整个丢掉,两种终态在视图上同形)。
|
|
263
|
+
* service 侧今天是单词闭集(`"reconcile"`),**读侧开集**:未知词原样透传(将来的第二个投影者
|
|
264
|
+
* 会是一个新词而不是改义),消费端 branch 已知值 + 通渲兜底,**绝不**按成员判死。
|
|
265
|
+
*/
|
|
266
|
+
export declare function wireRetiredBy(r: Pick<FleetTaskRow, 'retiredBy'>): string | undefined;
|
|
233
267
|
/** 终态四键之 `resumable`(仅 bg 子代有源;非 boolean ⇒ 缺席,**不当 false**)。 */
|
|
234
268
|
export declare function wireResumable(r: Pick<FleetTaskRow, 'resumable'>): boolean | undefined;
|
|
235
269
|
export interface ProjectTasksOptions {
|
|
@@ -218,6 +218,34 @@ export function wireStoppedBy(r) {
|
|
|
218
218
|
const v = r.stoppedBy;
|
|
219
219
|
return typeof v === 'string' && v.length > 0 ? v : undefined;
|
|
220
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* #261 §2① `cycleSeq`(server ≥7.25.0 / core 5.36.0 #258;SDK **7.2.0** 才把它声明进
|
|
223
|
+
* `FleetTaskRow` ⇒ 本包 0.38.0 提货补投)—— 这一行的**代际号**,与 `FleetBgNotification.seq` /
|
|
224
|
+
* `task_progress.seq` **同域同轴**(fresh spawn 就是 cycle 1,每次 SendMessage 复活翻 +1)。
|
|
225
|
+
*
|
|
226
|
+
* 🔴 **缺席 = 「无此概念」,不是「第一代」**(SDK 头注逐字):没有 `a*` registry 行的 run ——
|
|
227
|
+
* 同步委派子代 / workflow `wa*` agent / **顶层 run 行** —— 根本没有代际。把缺席读成 1 的消费端
|
|
228
|
+
* 会把「首帧迟到」误判成「复活」。所以这里对**非正整数**一律整键缺席(0 / 负数 / 非有限数 /
|
|
229
|
+
* 非整数都不是合法代际号),绝不 `?? 0`、绝不 `?? 1`。
|
|
230
|
+
*/
|
|
231
|
+
export function wireCycleSeq(r) {
|
|
232
|
+
const v = r.cycleSeq;
|
|
233
|
+
return typeof v === 'number' && Number.isInteger(v) && v > 0 ? v : undefined;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* #261 §2② `retiredBy`(server ≥7.25.0;SDK 7.2.0 声明)—— 这一帧的终态**不是发布方亲报**,
|
|
237
|
+
* 而是对账腿从 durable run 行**投影**出来的(发布方死了,行本会永久僵在活跃集里当幽灵)。
|
|
238
|
+
*
|
|
239
|
+
* 🔴 **发布方亲报的终态帧恒不带此键** ⇒ 两种终态在 wire 上可判:要区分「引擎说它完了」与
|
|
240
|
+
* 「我们从库里读出来它完了」时,这是**唯一**的判据([ghost-rows-need-upstream-liveness] 的
|
|
241
|
+
* wire 侧对位物 —— 此前本层把这一位整个丢掉,两种终态在视图上同形)。
|
|
242
|
+
* service 侧今天是单词闭集(`"reconcile"`),**读侧开集**:未知词原样透传(将来的第二个投影者
|
|
243
|
+
* 会是一个新词而不是改义),消费端 branch 已知值 + 通渲兜底,**绝不**按成员判死。
|
|
244
|
+
*/
|
|
245
|
+
export function wireRetiredBy(r) {
|
|
246
|
+
const v = r.retiredBy;
|
|
247
|
+
return typeof v === 'string' && v.length > 0 ? v : undefined;
|
|
248
|
+
}
|
|
221
249
|
/** 终态四键之 `resumable`(仅 bg 子代有源;非 boolean ⇒ 缺席,**不当 false**)。 */
|
|
222
250
|
export function wireResumable(r) {
|
|
223
251
|
return typeof r.resumable === 'boolean' ? r.resumable : undefined;
|
|
@@ -257,6 +285,8 @@ export function projectTasks(allRows, opts) {
|
|
|
257
285
|
const usage = wireRowUsage(r);
|
|
258
286
|
const resumable = wireResumable(r);
|
|
259
287
|
const editedFiles = wireEditedFiles(r);
|
|
288
|
+
const cycleSeq = wireCycleSeq(r);
|
|
289
|
+
const retiredBy = wireRetiredBy(r);
|
|
260
290
|
const task = {
|
|
261
291
|
id: r.id,
|
|
262
292
|
name: deriveAgentLabel(r.agentType, r.agentName, r.name, r.id),
|
|
@@ -286,6 +316,11 @@ export function projectTasks(allRows, opts) {
|
|
|
286
316
|
...(usage !== undefined ? { usage } : {}),
|
|
287
317
|
...(resumable !== undefined ? { resumable } : {}),
|
|
288
318
|
...(editedFiles !== undefined ? { editedFiles } : {}),
|
|
319
|
+
// #261 §2 两位:代际号与「非亲报终态」判别位。两者都是**在场才落键** —— cycleSeq 缺席是
|
|
320
|
+
// 「这条行没有代际概念」、retiredBy 缺席是「发布方亲报」,任何一个补默认值都会把一个诚实
|
|
321
|
+
// 缺席翻译成一句假话。
|
|
322
|
+
...(cycleSeq !== undefined ? { cycleSeq } : {}),
|
|
323
|
+
...(retiredBy !== undefined ? { retiredBy } : {}),
|
|
289
324
|
};
|
|
290
325
|
return task;
|
|
291
326
|
});
|
|
@@ -341,6 +376,8 @@ const FLEET_TASK_VIEW_KEY_TUPLE = [
|
|
|
341
376
|
'usage',
|
|
342
377
|
'resumable',
|
|
343
378
|
'editedFiles',
|
|
379
|
+
'cycleSeq',
|
|
380
|
+
'retiredBy',
|
|
344
381
|
];
|
|
345
382
|
/** `FleetTaskView` 的键名清单(运行期物;编译期与 `keyof FleetTaskView` 双向等值)。 */
|
|
346
383
|
export const FLEET_TASK_VIEW_KEYS = FLEET_TASK_VIEW_KEY_TUPLE;
|
|
@@ -382,6 +419,9 @@ const FLEET_TASK_ROW_WIRE_KEY_TUPLE = [
|
|
|
382
419
|
'usage',
|
|
383
420
|
'resumable',
|
|
384
421
|
'editedFiles',
|
|
422
|
+
// SDK 7.2.0 新声明的两位(#261 §2,server ≥7.25.0 早已在 wire 上 —— 这是**类型跟车**不是新能力)。
|
|
423
|
+
'cycleSeq',
|
|
424
|
+
'retiredBy',
|
|
385
425
|
];
|
|
386
426
|
/** SDK `FleetTaskRow` 的**入口**键名清单(编译期与 `keyof FleetTaskRow` 双向等值 ⇒ SDK 加一个
|
|
387
427
|
* wire 键,本元组不跟就编译红;跟了之后覆盖账那条腿再逼你表态「投不投」)。 */
|
|
@@ -43,7 +43,7 @@ export declare function waitForGateArmedFor(sessionKey: string, keys: readonly s
|
|
|
43
43
|
/** 宿主呈现面登记口:收到可渲染 question 帧即记(键归一见 gateIdentity;坏形静默忽略 ——
|
|
44
44
|
* id 是 wire/合成位,入参按边界收 unknown,本函数就是窄化动作本身)。
|
|
45
45
|
*
|
|
46
|
-
* 尝试级回执(#250
|
|
46
|
+
* 尝试级回执(#250 复审 轮二 [medium]):原始帧 id 自带 `#reopen-*` 尝试序号,是现成的
|
|
47
47
|
* attempt token —— 归一键之外**事件级**再发一枪原始 id(不入 Set,台账词汇保持归一键)。
|
|
48
48
|
* 重开臂锚它,同 gate 两次在飞重开时一次真入队只唤对应那次尝试,不再同键互唤。 */
|
|
49
49
|
export declare function registerArmedGateFromQuestionId(questionId: unknown): void;
|
|
@@ -231,7 +231,7 @@ function armedKeyForQuestionIdFor(sessionKey, questionId) {
|
|
|
231
231
|
/** 宿主呈现面登记口:收到可渲染 question 帧即记(键归一见 gateIdentity;坏形静默忽略 ——
|
|
232
232
|
* id 是 wire/合成位,入参按边界收 unknown,本函数就是窄化动作本身)。
|
|
233
233
|
*
|
|
234
|
-
* 尝试级回执(#250
|
|
234
|
+
* 尝试级回执(#250 复审 轮二 [medium]):原始帧 id 自带 `#reopen-*` 尝试序号,是现成的
|
|
235
235
|
* attempt token —— 归一键之外**事件级**再发一枪原始 id(不入 Set,台账词汇保持归一键)。
|
|
236
236
|
* 重开臂锚它,同 gate 两次在飞重开时一次真入队只唤对应那次尝试,不再同键互唤。 */
|
|
237
237
|
export function registerArmedGateFromQuestionId(questionId) {
|
|
@@ -241,7 +241,7 @@ export function registerArmedGateFromQuestionId(questionId) {
|
|
|
241
241
|
export function registerArmedGateFromQuestionIdFor(sessionKey, questionId) {
|
|
242
242
|
if (typeof questionId !== 'string' || questionId.length === 0)
|
|
243
243
|
return;
|
|
244
|
-
// 🔴 canonical 复用的去重记号过期(
|
|
244
|
+
// 🔴 canonical 复用的去重记号过期(复审 #244 F1 轮一 [medium]):arm 臂对同 run 每只 plan gate
|
|
245
245
|
// 都复用 `plan-review:<taskId>` 这个 canonical id —— 决断记账按完整 questionId 去重,若记号不随
|
|
246
246
|
// 卡换代过期,第二只 canonical 卡的决断会撞上第一只留下的记号 ⇒ 不推代 ⇒ 第三只门的重开又谎报
|
|
247
247
|
// 「被关过」。**新 canonical 卡的呈现**就是「上一张同 id 卡已消解、这是新实例」的宿主侧可见时刻,
|
package/dist/hitl/hitlBridge.js
CHANGED
|
@@ -61,12 +61,15 @@ export class HitlSafetyError extends Error {
|
|
|
61
61
|
// timeoutMs 是 client 构造级旋钮,本包对已构造的注入 client 不可配)。于是一次网络瞬断/超时 =
|
|
62
62
|
// decide 单发即死 → 上层拼 `failed` → parkResolver 合成 `hitl_unanswered` 终帧 → ask 死局。
|
|
63
63
|
//
|
|
64
|
-
// 🔴
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
64
|
+
// 🔴 **口径换代(#318 件②,2026-08-21):超时从「常态」变「真异常」** —— [4664] 的长调用口径作废。
|
|
65
|
+
// server #316([4687] 点名 BREAKING,随 7.37.0 发车)把任务级 decide 的 200 体从**终局形**改成
|
|
66
|
+
// **受理回执** `{taskId, sessionId, status:"resuming", bindingEnforced:true}`:受理点设在 core
|
|
67
|
+
// `resumeStream` 解析之后,一切会变成拒绝的判定(lease 429 / markResuming CAS 409 / core pre-CAS
|
|
68
|
+
// 守卫 / 绑定不符 409 / 卡不在 404)**仍同步发生**,受理后只剩模型段异步跑。实测受理即回 ≈23ms。
|
|
69
|
+
// ⇒ 对 ≥7.37 的部署,一次 60s 超时**不再是**「server 还在跑 resume」的常态,而是**真异常**
|
|
70
|
+
// (网络路径断在半途 / server 病态卡死 / 见下两条残余形)。口径随之收窄:
|
|
71
|
+
// · **超时类**(TimeoutError = SDK per-attempt 帽掐断)⇒ 仍带退避重试(重试环结构不动),但总窗
|
|
72
|
+
// 从 10 分钟收到 {@link DECIDE_TIMEOUT_RETRY_TOTAL_BUDGET_MS}(十秒级,见该常量的取值推导);
|
|
70
73
|
// · **网络断类**(ECONNREFUSED / fetch failed 等:连语义答复都没拿到,引擎多半真死)⇒ 重试
|
|
71
74
|
// **恰一次**,让真死尽快显形;
|
|
72
75
|
// · 语义答复类(带 HTTP status 的 4xx/5xx —— 引擎收到并回答了)⇒ 零重试,原样上抛;
|
|
@@ -81,17 +84,69 @@ export class HitlSafetyError extends Error {
|
|
|
81
84
|
// (typed 判别位)—— 消费方(toolApprovalWire / parkResolver)据此走**重呈臂**而不是把 turn 判死。
|
|
82
85
|
// 引擎真死时失败也会尽快显形:重呈臂的下一步(approvals.list / runs.events)对死引擎当场失败,
|
|
83
86
|
// 走既有诚实红。
|
|
84
|
-
// 🔴
|
|
85
|
-
//
|
|
87
|
+
// 🔴 **两条残余的同步形如实登记**(收窄不是「长调用消失了」,[4687] 逐字):终局形仍存在于
|
|
88
|
+
// ① **pre-7.37 的 server**(所有 decide 都是终局形 = 真长调用);② **≥7.37 但没有 durable run 行
|
|
89
|
+
// 可跟的部署**(那里提前受理 = 把结果扔掉,所以腿如实保持同步)。本包是三端共用件,面向的是
|
|
90
|
+
// 任意部署 —— 所以重试环**保留**、总窗**不设 0**。这两形上窗尽的代价是可接受的:耗尽走的是
|
|
91
|
+
// {@link DecideTransportRetryExhaustedError} → **重呈臂**(re-attach ⇒ durable 流重放 park ⇒
|
|
92
|
+
// 同一张卡重交用户),而不是把 turn 判死;若第一发其实已送达,重呈的下一步会撞
|
|
93
|
+
// `isAlreadyResolvedGateReason` 的已解决判据被救回。
|
|
94
|
+
// 🔴 请托半场(候协作板):SDK decide 若开 per-call timeoutMs(或对 HITL 面单列长缺省),本层的
|
|
95
|
+
// 超时类重试环可整段收敛成一发长等待。
|
|
86
96
|
/** 瞬断重试的起始退避(×2 递增,封顶 {@link DECIDE_RETRY_BACKOFF_MAX_MS};别把 decide 打成连发)。 */
|
|
87
97
|
const DECIDE_TRANSPORT_RETRY_BACKOFF_MS = 750;
|
|
88
98
|
const DECIDE_RETRY_BACKOFF_MAX_MS = 5_000;
|
|
89
|
-
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
|
|
99
|
+
/**
|
|
100
|
+
* SDK 的 **per-attempt 超时帽**(`AbortSignal.timeout(timeoutMs)`)的**缺省值**。
|
|
101
|
+
*
|
|
102
|
+
* 🔴 **它是「观察到的缺省」,不是本包能保证的量**(对抗复审 [medium] 采纳,2026-08-21)。
|
|
103
|
+
* per-call opts 只有 `signal`(与之合流取先,只能收短不能放长),但 `timeoutMs` 是 **client
|
|
104
|
+
* 构造级**旋钮,而本桥吃的是**宿主注入的** client(`HitlClientLike` 根本不暴露它)⇒ 一个 web/桌面
|
|
105
|
+
* 宿主完全可以用 30s 或 120s 的 client 构造本桥。所以任何「总窗 ÷ 帽 = 发数」的推导都只在缺省值
|
|
106
|
+
* 上成立,**不能当成本共用件的性质**。
|
|
107
|
+
* ⇒ 本常量只用来推导下面那个**墙钟上界**;「至少重试一次」这条**性质**改由
|
|
108
|
+
* {@link DECIDE_TIMEOUT_MIN_ATTEMPTS} 用**发数**保证,与宿主的 timeoutMs 无关。
|
|
109
|
+
*/
|
|
110
|
+
const DECIDE_ATTEMPT_TIMEOUT_CAP_MS = 60_000;
|
|
111
|
+
/**
|
|
112
|
+
* 超时类**最少发数**(首发 + 至少一次重试)—— 与墙钟窗**两个独立的界**,不是第二层节奏。
|
|
113
|
+
*
|
|
114
|
+
* 🔴 为什么必须有它([4687] 登记的两条残余同步形是承重理由):终局形仍存在于 pre-7.37 的 server
|
|
115
|
+
* 与「≥7.37 但没有 durable run 行可跟」的部署 —— 那两形上一次超时**极可能是真的还在跑**,
|
|
116
|
+
* 至少给一次重试是这条腿唯一的补救。若只用墙钟窗判,宿主拿 120s 的 client 构造本桥时首发超时
|
|
117
|
+
* 那一刻 elapsed 已经 ≥ 窗 ⇒ **一次重试都没有**,而这件事在代码里毫无痕迹(注释还写着「恰一次重试」)。
|
|
118
|
+
* 🔴 这**不是** [4675] 说的「第二层节奏叠乘」:没有新增任何定时器/退避层,退避仍是同一条
|
|
119
|
+
* `backoff` 链;这只是同一个循环上的第二个**终止条件**(发数尽 ∧ 窗尽,两者都满足才停)。
|
|
120
|
+
*/
|
|
121
|
+
const DECIDE_TIMEOUT_MIN_ATTEMPTS = 2;
|
|
122
|
+
/**
|
|
123
|
+
* 超时类重试的总窗(#318 件② 收窄:`10 * 60_000` → 本值)。
|
|
124
|
+
*
|
|
125
|
+
* ── 取值推导(锚在 {@link DECIDE_ATTEMPT_TIMEOUT_CAP_MS} 上,不是拍脑袋的整数)────────────────
|
|
126
|
+
* 窗的语义是**不再起新发**(见下),而每一发超时类失败**本身**就要吃满一个 per-attempt 帽 ⇒
|
|
127
|
+
* 实际发数由「总窗 ÷ 帽」决定,且量化得很粗:
|
|
128
|
+
* · 窗 ≤ 1 帽 ⇒ 窗判本身当场耗尽;
|
|
129
|
+
* · 1 帽 < 窗 ≤ 2 帽 ⇒ 窗判允许恰一次重试(共 2 发);
|
|
130
|
+
* · > 2 帽 ⇒ 3 发起步,一路回到分钟级。
|
|
131
|
+
* 受理形下超时是**真异常**(不是「还在跑」),所以取**恰一次重试**那一档:一次重试足够吃掉单次
|
|
132
|
+
* 网络抖动,再多就是对着一个病态 server 空等。取 90s = 1.5 帽,**刻意落在区间中部**而不是边界
|
|
133
|
+
* (120s 恰等于 2 帽 + 退避,会让发数悬在退避时序的一根头发上)。
|
|
134
|
+
* ⇒ 在缺省帽上可预算的最坏墙钟 ≈ 60s(首发)+ 0.75s(退避)+ 60s(重发)≈ 121s,而不是旧口径的 10 分钟。
|
|
135
|
+
*
|
|
136
|
+
* 🔴 **本窗只是墙钟上界,不承诺发数**(复审 [medium] 采纳):宿主可以用非缺省 `timeoutMs` 构造
|
|
137
|
+
* client(见 {@link DECIDE_ATTEMPT_TIMEOUT_CAP_MS}),那时「窗 ÷ 帽」得出的发数与这里写的不同。
|
|
138
|
+
* 与宿主无关的那条性质(**至少重试一次**)由 {@link DECIDE_TIMEOUT_MIN_ATTEMPTS} 单独保证 ——
|
|
139
|
+
* 两个界合取:**发数达标 ∧ 窗尽** 才停。所以在 120s client 上是「2 发、~240s」,在 30s client 上
|
|
140
|
+
* 是「3 发、~92s」,在缺省 60s 上是「2 发、~121s」—— 三者都有界,且都拿得到那一次重试。
|
|
141
|
+
*
|
|
142
|
+
* 🔴 窗的语义是**不再起新发**,刻意不掐在飞那一发(对抗复审议题,驳回后成文):给一发可能已被
|
|
143
|
+
* server 受理的 decide 塞截止 signal 换不来任何安全 —— server 侧照跑,客户端只多制造一个「送达
|
|
144
|
+
* 未知」。
|
|
145
|
+
* 🔴 **写成「帽 × 系数」而不是裸 90_000**:上面那段推导只有在两者绑在一起时才会随 SDK 改帽自动
|
|
146
|
+
* 跟手;写裸整数的话,SDK 哪天把帽改成 30s,注释里的「恰一次重试」当天变成假话而代码全绿。
|
|
147
|
+
*/
|
|
148
|
+
const DECIDE_TIMEOUT_RETRY_BUDGET_ATTEMPT_MULTIPLE = 1.5;
|
|
149
|
+
const DECIDE_TIMEOUT_RETRY_TOTAL_BUDGET_MS = DECIDE_ATTEMPT_TIMEOUT_CAP_MS * DECIDE_TIMEOUT_RETRY_BUDGET_ATTEMPT_MULTIPLE;
|
|
95
150
|
let decideTimeoutRetryBudgetOverrideMs;
|
|
96
151
|
/** 测试钩:把超时类重试总窗调小(传 undefined 还原缺省)。 */
|
|
97
152
|
export function __setDecideTimeoutRetryBudgetForTests(ms) {
|
|
@@ -457,10 +512,14 @@ export class HitlBridge {
|
|
|
457
512
|
if (!isTransientDecideTransportFailure(e) || callerAborted())
|
|
458
513
|
throw e;
|
|
459
514
|
if (isDecideAttemptTimeout(e)) {
|
|
460
|
-
//
|
|
461
|
-
//
|
|
462
|
-
//
|
|
463
|
-
|
|
515
|
+
// #318 件② 后口径:受理形(server ≥7.37)下 decide 受理即回,一次 per-attempt 帽掐断
|
|
516
|
+
// **是真异常**,不再是「server 还在跑 resume」的常态 —— 所以总窗只留恰一次重试的量
|
|
517
|
+
// (推导见 DECIDE_TIMEOUT_RETRY_TOTAL_BUDGET_MS)。重发仍然安全:重复 decide 由 server CAS
|
|
518
|
+
// 保证不双跑,首发其实送达时下一发只会撞 4xx(conflict/not-found ⇒ 上抛,已解决判据接手)。
|
|
519
|
+
// 🔴 **两个界合取**(复审 [medium] 采纳):发数没达标就一定再发一次(与宿主的
|
|
520
|
+
// per-attempt timeoutMs 无关),达标之后才由墙钟窗决定还发不发。少了前半句,
|
|
521
|
+
// 120s client 的宿主一次重试都拿不到;少了后半句,30s client 会一路重试到分钟级。
|
|
522
|
+
if (attempts >= DECIDE_TIMEOUT_MIN_ATTEMPTS && Date.now() - startedAt >= decideTimeoutRetryBudgetMs()) {
|
|
464
523
|
throw new DecideTransportRetryExhaustedError(attempts, String(e));
|
|
465
524
|
}
|
|
466
525
|
}
|
|
@@ -123,7 +123,7 @@ export type ListAllPersistedRulesOutcome = {
|
|
|
123
123
|
* 🔴 **游标绑 `(rev, principal, scope)`**:两页之间有人加/删了规则 ⇒ 第二页 400
|
|
124
124
|
* `request.query_invalid`。处置 = **丢游标从头列一次**(静默重置成「接着上一页」会得到一份既漏行
|
|
125
125
|
* 又重行的清单);从头再撞一次 ⇒ 如实报 cursor-stale,由调用方(人按 r 刷新)决定。
|
|
126
|
-
* 🔴 **调用方 cursor 不收**(
|
|
126
|
+
* 🔴 **调用方 cursor 不收**(复审 F2 对抗复审 [medium]):类型上剔掉 `cursor` 还不够 —— JS
|
|
127
127
|
* 调用方仍能塞进来,而首页的 `...params` 会把它原样送出 ⇒ 「列全」从**中途**开始却报 `ok:true`
|
|
128
128
|
* 完整清单(治理面据此藏掉仍然生效的规则)。运行期显式剥除 + 留痕:drain 恒从第一页起,
|
|
129
129
|
* 「接着别人的 keyset」证明不了完整性,与本函数的契约(rules 恒完整)结构性冲突。
|
|
@@ -58,7 +58,7 @@ export function classifyRulesFailure(e) {
|
|
|
58
58
|
return { kind: 'forbidden', message };
|
|
59
59
|
return { kind: 'error', message };
|
|
60
60
|
}
|
|
61
|
-
/** 一页要多少条。**必须显式给**(
|
|
61
|
+
/** 一页要多少条。**必须显式给**(对抗复审 [medium] 实撞):server 缺省是 **50**,而页帽
|
|
62
62
|
* 按「200/页」算 ⇒ 真实上界只有 1250 条,一位规则多于 1250 的 principal 会恒拿到「翻不完」的
|
|
63
63
|
* 失败、整个治理面打不开,而注释还写着 5000。夹取语义在 server(非数/越界夹进 1..200),所以给
|
|
64
64
|
* 上限最省往返、也让页帽的算术与现实一致。 */
|
|
@@ -73,7 +73,7 @@ const MAX_PAGES = 25;
|
|
|
73
73
|
* 🔴 **游标绑 `(rev, principal, scope)`**:两页之间有人加/删了规则 ⇒ 第二页 400
|
|
74
74
|
* `request.query_invalid`。处置 = **丢游标从头列一次**(静默重置成「接着上一页」会得到一份既漏行
|
|
75
75
|
* 又重行的清单);从头再撞一次 ⇒ 如实报 cursor-stale,由调用方(人按 r 刷新)决定。
|
|
76
|
-
* 🔴 **调用方 cursor 不收**(
|
|
76
|
+
* 🔴 **调用方 cursor 不收**(复审 F2 对抗复审 [medium]):类型上剔掉 `cursor` 还不够 —— JS
|
|
77
77
|
* 调用方仍能塞进来,而首页的 `...params` 会把它原样送出 ⇒ 「列全」从**中途**开始却报 `ok:true`
|
|
78
78
|
* 完整清单(治理面据此藏掉仍然生效的规则)。运行期显式剥除 + 留痕:drain 恒从第一页起,
|
|
79
79
|
* 「接着别人的 keyset」证明不了完整性,与本函数的契约(rules 恒完整)结构性冲突。
|
|
@@ -104,7 +104,7 @@ export async function listAllPersistedRules(facade, params = {}, opts) {
|
|
|
104
104
|
}
|
|
105
105
|
return { ok: false, failure };
|
|
106
106
|
}
|
|
107
|
-
// 🔴 页体 fail-closed 窄化(
|
|
107
|
+
// 🔴 页体 fail-closed 窄化(复审 F2 轮二 [high]):SDK 传输层只 JSON.parse,不做运行期
|
|
108
108
|
// schema 校验 —— 一个 2xx 的 `{rev:9}`(无 rules 数组)在旧读法下会被认证成「完整的空清单」
|
|
109
109
|
// (`ok:true, rules:[]`),治理面据此宣称「没有持久规则」而活规则不可见、无法撤销
|
|
110
110
|
// (版本偏斜/后端降级下静默发生)。坏形页 = 判不出,绝不当「读到了空的」:
|
|
@@ -124,7 +124,7 @@ export async function listAllPersistedRules(facade, params = {}, opts) {
|
|
|
124
124
|
},
|
|
125
125
|
};
|
|
126
126
|
}
|
|
127
|
-
// 🔴 跨页 rev 钉(
|
|
127
|
+
// 🔴 跨页 rev 钉(复审 F2 轮三 [high]):游标契约上绑 rev —— 清单变了,续页**该** 400
|
|
128
128
|
// cursor-stale。一个 2xx 却换了 rev 的续页 = server 违约或降级形,拼起来是**混合快照**;
|
|
129
129
|
// 认证它为完整清单,比 400 那条腿(丢游标重列)更坏 —— 这里不猜不修补,如实 failure。
|
|
130
130
|
if (rev !== null && res.rev !== rev) {
|
package/dist/index.d.ts
CHANGED
|
@@ -246,6 +246,7 @@ export * from './model/catalogLoader.js';
|
|
|
246
246
|
export * from './model/providerAuth.js';
|
|
247
247
|
export * from './model/providerCatalog.js';
|
|
248
248
|
export * from './model/tierVocabulary.js';
|
|
249
|
+
export * from './model/modelSupplyRules.js';
|
|
249
250
|
export * from './websearch/searchProviderPresets.js';
|
|
250
251
|
export * from './env/localeGeo.js';
|
|
251
252
|
export * from './env/localeTag.js';
|
package/dist/index.js
CHANGED
|
@@ -428,6 +428,10 @@ export * from './model/providerCatalog.js';
|
|
|
428
428
|
// 🔴 `model/tierVocabulary.ts` = 档位五档词表 + CC 别名 + isTier 校验 + resolveTierBinding
|
|
429
429
|
// fail-open 降档派生。此前跨两仓三份;settings 读写那半场仍归宿主(cli tierCore 只留存储)。
|
|
430
430
|
export * from './model/tierVocabulary.js';
|
|
431
|
+
// 🔴 `model/modelSupplyRules.ts` = **Model Hub 供给面的三端公共判定**(#318 件③ 上收,cli [4752]
|
|
432
|
+
// 预告的三纯函数):vision 生效值+来源三态 / 删除断链核(拒删)/ 删除降级后果(照删但必说)。
|
|
433
|
+
// 零 IO —— 读盘那半场留各端(壳 modelChannels 读完再调这里);`doc === null` 的两义由调用方分流。
|
|
434
|
+
export * from './model/modelSupplyRules.js';
|
|
431
435
|
// ── 搜索 provider 目录 + 地域预选(2026-07-31)──────────────────────────────────────────────────
|
|
432
436
|
// `websearch/searchProviderPresets` = model 目录的**同形不同表**姊妹件:数据在 json、类型与查询
|
|
433
437
|
// 在 ts。它编译出来的不是模型目录而是**引擎部署 env**(`WEB_SEARCH_*`)。
|