@sema-agent/client-core 0.64.1 → 0.65.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 +226 -0
- package/README.md +15 -7
- package/dist/adapt/arms.js +125 -7
- package/dist/adapt/ids.d.ts +22 -0
- package/dist/adapt/ids.js +29 -0
- package/dist/adapt/panelTasks.d.ts +22 -0
- package/dist/adapt/panelTasks.js +45 -0
- package/dist/adapt/textStream.js +6 -3
- package/dist/adapt.d.ts +1 -1
- package/dist/adapt.js +3 -0
- package/dist/adapter/downstream/eventToSdkMessage.d.ts +0 -10
- package/dist/adapter/downstream/eventToSdkMessage.js +207 -65
- package/dist/adapter/downstream/terminalToSdkResult.d.ts +4 -4
- package/dist/adapter/downstream/terminalToSdkResult.js +43 -12
- package/dist/adapter/downstream/turnUsageToModelUsage.d.ts +23 -2
- package/dist/adapter/downstream/turnUsageToModelUsage.js +7 -1
- package/dist/adapter/runStream.js +39 -2
- package/dist/adapter/types.d.ts +12 -0
- package/dist/autoModeUnavailable.d.ts +48 -79
- package/dist/autoModeUnavailable.js +70 -99
- package/dist/classifierStatus.d.ts +25 -71
- package/dist/classifierStatus.js +110 -105
- package/dist/cloudConfigWireCaps.js +16 -0
- package/dist/decideReceipt.d.ts +117 -0
- package/dist/decideReceipt.js +142 -0
- package/dist/engineErrorCodes.d.ts +32 -0
- package/dist/engineErrorCodes.js +42 -0
- package/dist/fleet/fleetProjection.d.ts +24 -1
- package/dist/fleet/fleetProjection.js +26 -1
- package/dist/fleetAgentPanelProjection.js +6 -1
- package/dist/gateVocabulary.d.ts +9 -1
- package/dist/gateVocabulary.js +46 -4
- package/dist/hitl/askGateWire.js +22 -1
- package/dist/hitl/gateLedger.d.ts +24 -0
- package/dist/hitl/gateLedger.js +8 -0
- package/dist/hitl/hitlBridge.js +14 -2
- package/dist/hitl/parkResolver.d.ts +23 -2
- package/dist/hitl/parkResolver.js +34 -6
- package/dist/hitl/toolApprovalWire.d.ts +94 -6
- package/dist/hitl/toolApprovalWire.js +127 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.js +16 -6
- package/dist/mcpWireCaps.d.ts +31 -0
- package/dist/mcpWireCaps.js +12 -0
- package/dist/notifications.js +11 -2
- package/dist/runTerminal.d.ts +48 -0
- package/dist/runTerminal.js +59 -0
- package/dist/seam.d.ts +131 -1
- package/dist/seam.js +22 -0
- package/docs/INTEGRATION-CLIENTS.md +934 -61
- package/package.json +2 -2
package/dist/classifierStatus.js
CHANGED
|
@@ -1,120 +1,148 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* src/classifierStatus.ts — auto 分类器的**状态读器**与唯一措辞铸点(0.64.0 件②;
|
|
3
|
-
* core 7.
|
|
3
|
+
* **0.65.0 随 core 7.12.0 收掉熔断族**、同批**把 `armed` 与 `available` 拆成两个词**,
|
|
4
|
+
* 见下方「熔断族退役」与「优先序承重」两段)。
|
|
4
5
|
*
|
|
5
6
|
* -- 它答的是哪一问(与卡面那一问**不是同一问**)----------------------------------------------
|
|
6
7
|
* `autoModeUnavailable.ts` 答的是**卡面**那一问:「这一刻**为什么在问我**」——一次性的、就这只 ask。
|
|
7
8
|
* 本模块答的是**状态面**那一问:「这个**会话**上,auto 分类器现在是什么状态」——诊断行、模型设置页、
|
|
8
9
|
* 权限卡的状态栏读的是它。两问的下一步不同,所以句子也不同(门里有一条反向钉守着两张面的句子零重合)。
|
|
9
10
|
*
|
|
10
|
-
* --
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* 读器,刻意不另铸第二只),所以**本模块自己不该进内核**;而那只窄读是零 import 的纯判据,
|
|
21
|
-
* 放进已经在闭包里的那张**熔断轴表**的同一个文件里,内核只多一件叶子。归属上也更顺:
|
|
22
|
-
* `AUTO_MODE_BREAKER_CAUSES` 本来就在那里,窄读读的正是那条轴。
|
|
11
|
+
* -- 🔴 熔断族退役(0.65.0;core 7.12.0 CHANGELOG「Removed (BREAKING)」)-------------------------
|
|
12
|
+
* core 7.12.0 删掉 `AutoModeBreakerTrip` 与 `WiringManifest.autoMode.breaker`(server 7.70.0 [6927]
|
|
13
|
+
* 读面同批收窄)。⇒ **会话轴的那条腿整只没了**:本模块此前词表里的 `breaker_open` 与它承重的那条
|
|
14
|
+
* 熔断记录一起**干净切**(clean-cut,不留别名、不留兼容读)。留着一个上游再也不会填的状态词,
|
|
15
|
+
* 结果是 /doctor 的 Auto classifier 行**永远出不了那一态**,而它的存在会让读者以为它可能出现 ——
|
|
16
|
+
* 那正是「假 affordance」那一族。
|
|
17
|
+
* ⚠️ **如实登记退役的代价**:0.64.0 的顶注写过「上游若把 breaker 退役,本模块**零改**(缺席臂当天
|
|
18
|
+
* 就是正解)」。那句话对**读数**是对的(读不到就是没有),对**词表与型面**不成立 ——
|
|
19
|
+
* `CLASSIFIER_STATUS_STATES` 里的那个成员、`ClassifierStatusView.breaker` 这一位与它那两句人话
|
|
20
|
+
* 仍然在公面上承诺着一件上游已经不发的事。本批因此不是「零改」,是**按退役条款把承诺一起收掉**。
|
|
23
21
|
*
|
|
24
|
-
* --
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* 在闭集上完备,而**今天没有任何东西铸它**。所以一份 `{armed:false, reason:"no_intent", breaker:{…}}`
|
|
29
|
-
* 是**最常见**的合法形,不是矛盾。
|
|
22
|
+
* -- 一处窄读,复用不重铸 --------------------------------------------------------------------
|
|
23
|
+
* **本轮轴**:一只 ask 上的 `classifierUnavailable.cause` —— 复用 0.63.0 已有的
|
|
24
|
+
* {@link classifierUnavailableOf},**不重铸**。本模块因此有一条值级 import(它不进内核闭包,见
|
|
25
|
+
* `run-client-core-portability-test.mjs` 的内核上限记账)。
|
|
30
26
|
*
|
|
31
|
-
* -- 🔴 优先序承重:本轮事实 > 这条腿的 `armed`
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* ⇒
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* 🔴 **历史记录不因为被压下去就丢掉**:任何一态上 {@link ClassifierStatusView.breaker} 都照带,
|
|
42
|
-
* 端可以在「在跑」那一行后面补一句「这个会话上曾经熔断过」——那是**两条不同的下一步**,所以
|
|
43
|
-
* 措辞铸点为它单出一句。
|
|
27
|
+
* -- 🔴 优先序承重:本轮事实(否定 + 肯定)> 这条腿的 `armed` --------------------------------
|
|
28
|
+
* 判据锚在**真正决定「分类器现在跑不跑」的量**上:
|
|
29
|
+
* · **本轮事实最先** —— 它说的是眼前这一只 ask,最具体也最新。两条:否定事实
|
|
30
|
+
* (`classifierUnavailable` ⇒ `unavailable_this_round`)与**肯定**事实(0.65.0 / B-071:
|
|
31
|
+
* `origin === "denial_limit_fallback"` ⇒ 分类器真的跑过 ⇒ `available`);
|
|
32
|
+
* · **其次是 `armed`** —— core 顶注逐字:「A decider is minted per run (its latch is a RUN fact)」
|
|
33
|
+
* ⇒ 同一会话的**后一条腿**完全可以重新武装,`armed === true` 时这条腿上真的有一只新铸的 decider。
|
|
34
|
+
* 🔴 **0.65.0 / B-071 收窄**:`armed === true` 现在判的是新词 **`armed`**,不再是
|
|
35
|
+
* `available` —— core 顶注把「decider 铸出来了」(装配期)与「这一轮可用」(per-call)
|
|
36
|
+
* 分得很清楚,而本读器此前把前者当成后者答了。
|
|
44
37
|
*
|
|
45
|
-
* -- 🔴
|
|
46
|
-
*
|
|
47
|
-
* 排查 —— 运维拿着它去对引擎日志,而引擎日志也是 UTC。
|
|
48
|
-
* 🔴 `openedAtMs` 是 **wire 来的数**:一个 `1e20` 会让 `toISOString()` 当场抛 `RangeError`,那是
|
|
49
|
-
* **整屏崩**不是一行渲不出([render-path-must-not-throw])。窄读因此按 Date 的真实值域收。
|
|
38
|
+
* -- 🔴 时刻/时区 -----------------------------------------------------------------------------
|
|
39
|
+
* 本模块 0.65.0 起不再渲任何时刻(那是熔断记录带来的唯一时间位,随它一起退役)。
|
|
50
40
|
*
|
|
51
41
|
* -- 退役条款 ---------------------------------------------------------------------------------
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* 与 `available`。这条设计是刻意的:退役一个 additive 键不该逼三端各改一次。
|
|
42
|
+
* `armed` 这一位若哪天也换形,本模块的**缺席臂**仍是正解:读不出就答 `undefined`(**说不出来**),
|
|
43
|
+
* 绝不折成 `available` —— 那是把「没报」渲成「一切正常」。
|
|
55
44
|
*/
|
|
56
|
-
import {
|
|
45
|
+
import { classifierUnavailableOf } from './autoModeUnavailable.js';
|
|
57
46
|
/**
|
|
58
|
-
*
|
|
59
|
-
* `wiring_manifest.autoMode.reason`
|
|
60
|
-
|
|
47
|
+
* 分类器的三态(0.65.0:熔断族退役,`breaker_open` 随之删除;同批 `armed` 与 `available` 拆成
|
|
48
|
+
* 两个词)。**「没武装」仍然不在里面** —— 那一问由 `wiring_manifest.autoMode.reason` 那一格
|
|
49
|
+
* 自己回答(六词闭集),在这里再答一遍会长出第二份台账。
|
|
50
|
+
*
|
|
51
|
+
* 🔴 **0.65.0 / B-071:`armed` 与 `available` 拆成两个词**(此前只有后者,`armed===true` 直接
|
|
52
|
+
* 判 `available`)。core 的 `WiringManifest.autoMode` 顶注把这两件事**明确分开**了:
|
|
53
|
+
* · `armed` ⇔ `reason === "armed"` —— 「**这条腿**铸出了分类器 decider」
|
|
54
|
+
* (逐字 `the classifier decider was minted for this leg`),一个**装配期**事实;
|
|
55
|
+
* · 而 `The classifier's availability is a per-CALL fact`(逐字;下一句还写明「an
|
|
56
|
+
* unavailable round is a deny that says so, and the next round is classified afresh」)。
|
|
57
|
+
* ⇒ 拿 `armed` 去回答「可用吗」= 用装配期读数回答每次调用的问题。武装了的分类器完全可能在
|
|
58
|
+
* 某一轮跑不了(那一轮是一条 deny),下一轮又好了 —— 这正是 core 说「没有会话级 breaker 面」
|
|
59
|
+
* 的理由(0.65.0 起它连那一格也一并删了)。措辞面跟着分两句:「武装了」与「这一次真的跑了」
|
|
60
|
+
* 对用户是两条不同的下一步。
|
|
61
|
+
*
|
|
62
|
+
* 词序 = 判据强度递增的反向读法:`armed`(装配期)→ `available`(本轮肯定事实)→ 坏消息。
|
|
63
|
+
*
|
|
61
64
|
* 🔴 形制:`Object.freeze` 的数组,**不是**只在类型面只读的 `readonly T[]`(同
|
|
62
65
|
* `AUTO_MODE_UNAVAILABLE_CAUSES` 的理由)。
|
|
63
66
|
*/
|
|
64
67
|
export const CLASSIFIER_STATUS_STATES = Object.freeze([
|
|
68
|
+
'armed',
|
|
65
69
|
'available',
|
|
66
|
-
'breaker_open',
|
|
67
70
|
'unavailable_this_round',
|
|
68
71
|
]);
|
|
72
|
+
/**
|
|
73
|
+
* B-071 —— 包今天手上**唯一**一条「分类器在这次调用上真的跑过」的**肯定**证据。
|
|
74
|
+
*
|
|
75
|
+
* core `ask-origin.d.ts` 逐字:`denial_limit_fallback` = 「the auto-mode classifier reached its
|
|
76
|
+
* denial bound and handed the call to a person; **the classifier stands BEFORE this ask**」——
|
|
77
|
+
* 这只 ask 的存在本身就证明分类器在这条调用链上跑过并数过它自己的拒绝。
|
|
78
|
+
*
|
|
79
|
+
* 🔴 **别的 origin 一个都不算**,而且不是保守起见:
|
|
80
|
+
* · `org_*` / `hook` / `ask_rule` / `unresolvable` / `content_question` —— core 的
|
|
81
|
+
* `CLASSIFIER_MAY_ANSWER` 表逐行 `false`,分类器**结构上**就不许碰这些 ask ⇒ 它们的存在
|
|
82
|
+
* 与分类器跑没跑完全无关;
|
|
83
|
+
* · `policy` / `safety_tighten` / `shell_gate_tighten` —— 分类器**可以**答(表里 `true`),
|
|
84
|
+
* 但一只**存活到卡上的** ask 恰恰说明这一轮它**没有**把它解决掉:可能是没跑,也可能是跑了
|
|
85
|
+
* 判 ask。两种情形在这一格上不可分 ⇒ 拿它判 `available` 就是在猜。
|
|
86
|
+
* ⇒ 判不出来时正解是回落到 `armed`(「武装了,本轮如何不知道」),而不是冒充 `available`。
|
|
87
|
+
*/
|
|
88
|
+
const CLASSIFIER_RAN_ORIGINS = new Set(['denial_limit_fallback']);
|
|
89
|
+
/**
|
|
90
|
+
* 一只 ask / 一条 `tool_end.gate` 记录上的 `origin` —— 两条载体同一个词表(sdk 的
|
|
91
|
+
* `AskOrigin` 一张表两个面:`ToolApprovalFrame.origin` 与 `GateOutcome.origin`)。
|
|
92
|
+
* 顶层优先(活卡帧 / durable park 行),其次是门记录里的那一格。读不出即缺席。
|
|
93
|
+
*/
|
|
94
|
+
function askOriginOf(ask) {
|
|
95
|
+
if (typeof ask !== 'object' || ask === null || Array.isArray(ask))
|
|
96
|
+
return undefined;
|
|
97
|
+
const top = ask.origin;
|
|
98
|
+
if (typeof top === 'string' && top.length > 0)
|
|
99
|
+
return top;
|
|
100
|
+
const gate = ask.gate;
|
|
101
|
+
if (typeof gate !== 'object' || gate === null || Array.isArray(gate))
|
|
102
|
+
return undefined;
|
|
103
|
+
const inner = gate.origin;
|
|
104
|
+
return typeof inner === 'string' && inner.length > 0 ? inner : undefined;
|
|
105
|
+
}
|
|
69
106
|
/**
|
|
70
107
|
* 「这个会话上,auto 分类器现在是什么状态」——三态,或 `undefined`(**说不出来**)。
|
|
71
108
|
*
|
|
72
109
|
* @param autoMode `wiring_manifest` 的 `autoMode` 段(投影后的或原始的都吃;本函数自己窄读)
|
|
73
110
|
* @param ask 可选:**本轮**那只 ask(或 durable park 行的 `tool_approval` 载荷)
|
|
74
111
|
*
|
|
75
|
-
* 优先序(承重,理由见模块顶注):**本轮事实 > 这条腿的 `armed
|
|
76
|
-
*
|
|
77
|
-
*
|
|
112
|
+
* 优先序(承重,理由见模块顶注):**本轮事实(否定 + 肯定)> 这条腿的 `armed`**——先看 `ask` 上的
|
|
113
|
+
* 本轮不可用事实,再看 `ask` 上那条「分类器真的跑过」的肯定事实,最后才看这条腿武没武装。
|
|
114
|
+
* `ask` 带本轮事实时,`autoMode` 段缺席也照样答得出(两条本轮臂都不依赖那一段)。
|
|
78
115
|
*
|
|
79
116
|
* 🔴 **`undefined` 是一个诚实的答案,不是一个坏路径**:
|
|
80
117
|
* · `autoMode` 段缺席(老 mint / 外部 derive)⇒ 这一端**没有**分类器的健康读数;
|
|
81
|
-
* ·
|
|
82
|
-
* 它的健康无从谈起。
|
|
118
|
+
* · 段在、但没武装,而且没有本轮事实 ⇒ 分类器**压根没参与**这条腿,它的健康无从谈起。
|
|
83
119
|
* 两种情形都**绝不**折成 `available`(那是把「没报」渲成「一切正常」)。
|
|
84
120
|
* ⚠️ 「没武装」本身仍是一条要渲的事实 —— 但它的出处是 `autoMode.reason`,不是本读器。
|
|
85
121
|
*/
|
|
86
122
|
export function classifierStatusOf(autoMode, ask) {
|
|
87
|
-
const breaker = classifierBreakerOf(autoMode);
|
|
88
|
-
// 🔴 历史记录**任何一态上都照带**(见模块顶注):被压下去的是**判词**,不是那条事实。
|
|
89
|
-
const withTrip = breaker === undefined ? {} : { breaker };
|
|
90
123
|
const round = ask === undefined ? undefined : classifierUnavailableOf(ask);
|
|
91
124
|
if (round !== undefined)
|
|
92
|
-
return { state: 'unavailable_this_round', cause: round.cause
|
|
125
|
+
return { state: 'unavailable_this_round', cause: round.cause };
|
|
126
|
+
// 🔴 B-071:**本轮的肯定事实**(分类器真的跑过)—— 与上面那条否定事实同属 per-call 轴,故同样
|
|
127
|
+
// 压过 `armed`。它**不依赖** `autoMode` 段在不在场:可用性是这次调用的事实,不是装配的事实。
|
|
128
|
+
const origin = ask === undefined ? undefined : askOriginOf(ask);
|
|
129
|
+
if (origin !== undefined && CLASSIFIER_RAN_ORIGINS.has(origin)) {
|
|
130
|
+
return { state: 'available' };
|
|
131
|
+
}
|
|
93
132
|
const armed = typeof autoMode === 'object' && autoMode !== null && !Array.isArray(autoMode)
|
|
94
133
|
? autoMode.armed
|
|
95
134
|
: undefined;
|
|
96
|
-
// 🔴 `armed`
|
|
135
|
+
// 🔴 `armed` 是**这条腿**的事实 —— 同一会话重新武装之后,那条腿上真的有一只新铸的 decider。
|
|
136
|
+
// 但它**只答到「武装了」为止**(B-071):本轮跑没跑是 per-call 的事,没有本轮事实时这一端
|
|
137
|
+
// 说不出来,于是渲 `armed` 而不是 `available`。
|
|
97
138
|
if (armed === true)
|
|
98
|
-
return { state: '
|
|
99
|
-
if (breaker !== undefined)
|
|
100
|
-
return { state: 'breaker_open', breaker };
|
|
139
|
+
return { state: 'armed' };
|
|
101
140
|
return undefined;
|
|
102
141
|
}
|
|
103
|
-
/**
|
|
104
|
-
* 逐熔断成因一句人话(状态面用;与卡面那四句刻意不同 —— 见模块顶注)。
|
|
105
|
-
* 三句逐字互异:三条不同的下一步(去看模型那条腿 / 去调超时或换更快的分类模型 / 去看分类提示词与
|
|
106
|
-
* 契约)。
|
|
107
|
-
*/
|
|
108
|
-
const BREAKER_CAUSE_PHRASES = Object.freeze({
|
|
109
|
-
error: 'the classifier leg errored',
|
|
110
|
-
timeout: 'the classifier timed out',
|
|
111
|
-
parse_error: 'the classifier answered outside its contract',
|
|
112
|
-
});
|
|
113
|
-
/** 逐本轮成因一句人话(状态面用)。 */
|
|
142
|
+
/** 逐本轮成因一句人话(状态面用;与卡面那几句刻意不同 —— 见模块顶注)。 */
|
|
114
143
|
const ROUND_CAUSE_PHRASES = Object.freeze({
|
|
115
144
|
error: 'the classifier leg errored',
|
|
116
145
|
timeout: 'the classifier timed out',
|
|
117
|
-
breaker_open: 'the session latch was already closed, so this round was never sent',
|
|
118
146
|
});
|
|
119
147
|
/**
|
|
120
148
|
* 一个成因词 → 一个短语。**按自有属性查表**(`Object.freeze` 不移除原型,裸下标会让一个来自 wire 的
|
|
@@ -130,49 +158,26 @@ function phraseOf(table, cause) {
|
|
|
130
158
|
const word = typeof cause === 'string' && cause.length > 0 ? cause : '(none)';
|
|
131
159
|
return `it reported ${word}, a cause word newer than this client`;
|
|
132
160
|
}
|
|
133
|
-
/** epoch ms → UTC ISO(入参已由窄读收进 `Date` 值域,所以这里不会抛;见模块顶注)。 */
|
|
134
|
-
function utcIso(ms) {
|
|
135
|
-
return new Date(ms).toISOString();
|
|
136
|
-
}
|
|
137
161
|
/**
|
|
138
162
|
* 一次状态读数 → 一句人话。**唯一措辞铸点**(三端共用;端零自拼)。
|
|
139
163
|
*
|
|
140
|
-
* 三句逐字互异,且与 `classifierUnavailableDetail`
|
|
141
|
-
* 🔴
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
* 复核**那只记录(走同一只窄读),读不出就走「没被告知何时因何」那一句,绝不半渲一个假读数。
|
|
164
|
+
* 三句逐字互异,且与 `classifierUnavailableDetail` 的三句**零重合**(两张面答两个问题)。
|
|
165
|
+
* 🔴 **表外态不抛**:型面挡不住 wire,也挡不住一份从持久态恢复回来的视图 —— 一个
|
|
166
|
+
* `{state:'breaker_open'}`(0.65.0 退役掉的那个词,可能来自旧持久态)必须渲出一句诚实的话,
|
|
167
|
+
* 而不是把整屏带崩,也不冒充三句里的任何一句。
|
|
145
168
|
*/
|
|
146
169
|
export function classifierStatusDetail(view) {
|
|
147
170
|
const state = typeof view?.state === 'string' ? view.state : '';
|
|
171
|
+
if (state === 'armed') {
|
|
172
|
+
// 🔴 B-071:这一句**刻意不说「在跑 / 可用」** —— 它答的是「这条腿铸出了分类器」,而「这一轮
|
|
173
|
+
// 它跑不跑得成」这一端没有证据。说成可用,用户下一步会去查「为什么还在问我」而不是去看
|
|
174
|
+
// 卡上那条 per-call 事实。两句因此是两条不同的下一步,逐字互异。
|
|
175
|
+
return ('auto mode is armed on this session (a classifier is wired for this leg; whether it answers ' +
|
|
176
|
+
'any given round is a per-call fact)');
|
|
177
|
+
}
|
|
148
178
|
if (state === 'available') {
|
|
149
|
-
// 🔴 「在跑」与「在跑、但这个会话上**曾经**熔断过」是**两条不同的下一步**:后者要去看那次失败
|
|
150
|
-
// 为什么发生(它随时可能再来一次),所以单出一句,而**不说**「不新开会话就恢复不了」——
|
|
151
|
-
// 那句话在一条已经重新武装的腿上是**假的**。
|
|
152
|
-
const past = classifierBreakerOf({ breaker: view.breaker });
|
|
153
|
-
if (past !== undefined) {
|
|
154
|
-
return (`the auto-mode classifier is running on this session, but it had latched off earlier — ` +
|
|
155
|
-
`${phraseOf(BREAKER_CAUSE_PHRASES, past.lastCause)} on ${past.failures} consecutive rounds, ` +
|
|
156
|
-
`and that latch closed at ${utcIso(past.openedAtMs)}`);
|
|
157
|
-
}
|
|
158
179
|
return 'the auto-mode classifier is running on this session';
|
|
159
180
|
}
|
|
160
|
-
if (state === 'breaker_open') {
|
|
161
|
-
// 🔴 **渲染入口自己复核那只记录**,不假定调用方一定经过 {@link classifierBreakerOf}
|
|
162
|
-
// (异源对抗复审 r1 finding④ 的真病)。`ClassifierBreakerView.openedAtMs` 在型面上只是
|
|
163
|
-
// `number` —— 一个宿主自建管线、或一份从持久态恢复回来的视图,造得出 `1e20`(`toISOString()`
|
|
164
|
-
// 当场 `RangeError`)与 `null`(`TypeError`),而那是**整屏崩**不是一行渲不出。
|
|
165
|
-
// 复核走的是**同一只窄读**(把它包回 `{breaker}` 的形喂进去),所以两处永远同一套判据 ——
|
|
166
|
-
// 在这里另写一遍范围检查就是第二份会漂的台账。
|
|
167
|
-
const b = classifierBreakerOf({ breaker: view.breaker });
|
|
168
|
-
if (b === undefined) {
|
|
169
|
-
return 'the auto-mode classifier is latched off for this session; this client was not told when it tripped or why';
|
|
170
|
-
}
|
|
171
|
-
// 🔴 **不说「不新开会话就恢复不了」**(异源对抗复审 r2):闩是**一条腿**的事实,同一会话的后一条
|
|
172
|
-
// 腿重新武装时会铸一只新的、闩关着的 decider ⇒ 那句话是编的。这里只说真的发生过的事。
|
|
173
|
-
return (`auto mode fell back to asking on this session — ${phraseOf(BREAKER_CAUSE_PHRASES, b.lastCause)} ` +
|
|
174
|
-
`on ${b.failures} consecutive rounds, and the latch closed at ${utcIso(b.openedAtMs)}`);
|
|
175
|
-
}
|
|
176
181
|
if (state === 'unavailable_this_round') {
|
|
177
182
|
return `the auto-mode classifier did not run this round — ${phraseOf(ROUND_CAUSE_PHRASES, view.cause)}`;
|
|
178
183
|
}
|
|
@@ -98,6 +98,22 @@ export function cloudMcpToSpecs(servers, env) {
|
|
|
98
98
|
transport,
|
|
99
99
|
...(allowTools.length > 0 ? { allowTools } : {}),
|
|
100
100
|
...(s.elicitation === true ? { elicitation: true } : {}),
|
|
101
|
+
// ── 0.64.2 族扫(cli L-167① 的**同形存量第二处**)─────────────────────────────────────
|
|
102
|
+
// 病形与 `mcpWireCaps.mcpConfigToSpec` **逐字同一条**:这也是一次逐键白名单重建,而
|
|
103
|
+
// settings-schema 的 mcp 段上有**解析透明键**(`z.unknown()`;1.10.0 两员 `source` / `toolFaces`)。
|
|
104
|
+
// 上游对 `toolFaces` 的成文是「三条腿(请求 / config-center / 本地 config.d)同一条规则」——
|
|
105
|
+
// 本函数正是 **config-center 那条腿落到请求面**的投影,只修本地那条 = 同形存量留一半:
|
|
106
|
+
// 用 center 下发配置的部署,运维声明的 per-tool 面照样在包边界被静默剥掉。
|
|
107
|
+
// 🔴 **原样过境,不校形**(理由见 `mcpWireCaps.ts` 模块顶注与 `McpConfigLike.toolFaces` 的注):
|
|
108
|
+
// 形的判决属引擎摄入侧;包在这里判一次形,判严了就把运维声明的保护静默吞掉。
|
|
109
|
+
// 🔴 **`source` 同样不在这条腿上**:它是部署面的分组标签,而本函数的产物是**请求面**
|
|
110
|
+
// `McpServerSpec`(sdk 8.8.0 上没有这个键)。两条腿的记账逐字相同。
|
|
111
|
+
// 🔴 **两条腿刻意不共用一个函数,改由门同源**:跨文件共用要么给公面加一个没有外部消费者的
|
|
112
|
+
// 名字,要么把这一行抽成一个新叶子(闭包/单例账各加一笔)。这里的「规则」只有一行
|
|
113
|
+
// (键在场 ⇒ 原样铸),真正会漂的是**哪些键算透明** —— 那张表的属主在上游,而
|
|
114
|
+
// `run-additive-key-passthrough-test.mjs` G7 段是从**实装 schema** 取表、对**两条腿各对一次账**,
|
|
115
|
+
// 任一条漏一个键当天红。⇒ 单一真源在上游 + 机械门,不是两份各自为政的判断。
|
|
116
|
+
...(s.toolFaces !== undefined ? { toolFaces: s.toolFaces } : {}),
|
|
101
117
|
});
|
|
102
118
|
}
|
|
103
119
|
return { specs, missingEnv: [...new Set(missingEnv)], droppedServers };
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/decideReceipt.ts — `/decide` **答了什么**:200 回执的读面 + 三条 workflow 车道拒绝码的
|
|
3
|
+
* 人话铸点(B-070 / L-200,0.65.0;sdk README §9.0.0,server ≥7.69.0)。
|
|
4
|
+
*
|
|
5
|
+
* ── 🔴 这一件修的是什么:**200 ≠ 门已解决** ─────────────────────────────────────────────────
|
|
6
|
+
* sdk README §9.0.0 逐字:「workflow 车道的 200 只是**投递受理** —— 子代的 checkpoint 仍 pending,
|
|
7
|
+
* `/v1/approvals` 上那张卡**可能还在**。据 200 立刻把卡从 UI 抹掉,用户会看到一张『批过了却还在』
|
|
8
|
+
* 的幽灵卡,或者反过来:卡消失了而决定其实在宿主被打断时丢了。」
|
|
9
|
+
* core `runner/contracts.d.ts:1492` 同向:那一步 **launches the run that re-invokes** —— 它启动的是
|
|
10
|
+
* 另一条 run,不是「这件事已经做完了」。
|
|
11
|
+
* ⇒ 本包对 decide 成功的处置因此换了**判据锚**([anchor-on-the-deciding-quantity]):
|
|
12
|
+
* 真正决定「门解没解决」的量是**流上的下一条 running 臂 / 新 run 的事件**,不是那个 200。
|
|
13
|
+
* `hitlBridge.decideRaw` 不再在 200 上清 `active`(见该处注释),`parkResolver` 的重挂腿改按
|
|
14
|
+
* {@link DecideReceiptView.handoffTaskId} 换挂。
|
|
15
|
+
*
|
|
16
|
+
* ── `/decide` 200 的**四种形**(sdk `ApprovalDecisionResult` 顶注逐字)─────────────────────────
|
|
17
|
+
* · **受理形**(≥7.37 durable 部署默认)`{taskId, sessionId, status:"resuming", bindingEnforced:true}`
|
|
18
|
+
* · **终局形**(无 durable run 行 / `verify:true` 的同步长调用)`{taskId?, sessionId, status, errorCode?, …}`
|
|
19
|
+
* · **parked 回执** `{taskId, status:"resuming", decision}`
|
|
20
|
+
* · **幂等回放形**(#368)`{sessionId, idempotent:true, decision, bindingEnforced:true, executionOutcome?}`
|
|
21
|
+
* 🔴 **没有任何键四形都保证** —— 所以本读器对每一位都做诚实缺席,**绝不**靠某一位在不在场去推断
|
|
22
|
+
* 另一件事。
|
|
23
|
+
*
|
|
24
|
+
* ── 🔴 `executionOutcome`:缺席 = 未知,**禁读成 allowed** ───────────────────────────────────
|
|
25
|
+
* sdk `types.d.ts:3809-3813` 逐字:它**只在幂等回放形上**,说的是「那个已决动作**最后被怎么处置**」;
|
|
26
|
+
* 「`decision` 说的是『人批了没有』,这一格说的是『执行时被放行还是被部署复核在 post-CAS 否掉』,
|
|
27
|
+
* 两者**可以相反**」;「🔴 **缺席 = 未知**(判词还没落上 / 记录读不懂 / 命中行之间分歧),**禁读成
|
|
28
|
+
* allowed**」。core `checkpoint-store.d.ts:1785` 同句:「the execution outcome is not known, never
|
|
29
|
+
* 'it was allowed'」。
|
|
30
|
+
* ⇒ 本读器把它读成 `GateOutcomeView | undefined`,**走的是 `gateOutcome.ts` 那一只读器**
|
|
31
|
+
* (把它包回 `{gate: …}` 的形喂进去)——不在这里另写一份门记录读法,否则两处判据必然分叉。
|
|
32
|
+
* 消费端渲卡时:**在场**才说「执行时被放行/被否」,缺席一个字都不说。
|
|
33
|
+
*/
|
|
34
|
+
import { type GateOutcomeView } from './gateOutcome.js';
|
|
35
|
+
/** 一次 `/decide` 200 的读数。**每一位都可能缺席**(四种形没有共同保证的键)。 */
|
|
36
|
+
export interface DecideReceiptView {
|
|
37
|
+
/** 回体上的 `status`(开集透传;受理形与 parked 回执上是 `"resuming"`,回放形上**没有**这一位)。 */
|
|
38
|
+
status?: string;
|
|
39
|
+
/** 回体上的 `taskId`(开集透传;回放形上没有)。 */
|
|
40
|
+
taskId?: string;
|
|
41
|
+
/** 回体上的 `sessionId`(parked 回执上没有)。 */
|
|
42
|
+
sessionId?: string;
|
|
43
|
+
/** `#368` 幂等回放形的判别式(在场即真)。 */
|
|
44
|
+
idempotent?: true;
|
|
45
|
+
/**
|
|
46
|
+
* 🔴 **「该去盯哪条 run」的换挂句柄** —— 受理形/parked 回执上 `status === "resuming"` 时那个
|
|
47
|
+
* `taskId`。sdk README §9.0.0 逐字:workflow 车道上它是「**宿主新铸**的 run id(poll 它,
|
|
48
|
+
* **不要盯那张卡**)」。
|
|
49
|
+
*
|
|
50
|
+
* 🔴 **它与 {@link taskId} 刻意是两个位**(不是别名):`taskId` 是「回体上写着什么」的原样透传,
|
|
51
|
+
* 本位是「**本包判定**这次 decide 把续跑交到了哪条 run 上」—— 后者带着一个判据
|
|
52
|
+
* (`status === "resuming"` ∧ 非空 `taskId`),而把判据折进透传位会让消费端分不出
|
|
53
|
+
* 「引擎报了一个 taskId」与「这次真的换挂了」。
|
|
54
|
+
* 🔴 **缺席 = 没有换挂句柄**(终局形 / 回放形 / 老 server 的终局体),消费端**照旧用原 taskId**;
|
|
55
|
+
* 绝不把缺席折成「那就是原来那条」——那是把两件事说成一件。
|
|
56
|
+
*/
|
|
57
|
+
handoffTaskId?: string;
|
|
58
|
+
/**
|
|
59
|
+
* 那个**已决动作最后被怎么处置**(只在幂等回放形上;sdk ≥8.6.0 / server ≥7.66.0)。
|
|
60
|
+
* 🔴 **缺席 = 未知,禁读成 allowed**(见模块顶注的两处上游逐字)。
|
|
61
|
+
*/
|
|
62
|
+
executionOutcome?: GateOutcomeView;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* `/decide` 的 200 体 → {@link DecideReceiptView};不是一只对象 / 一位都读不出 ⇒ `undefined`,
|
|
66
|
+
* 绝不抛。
|
|
67
|
+
*
|
|
68
|
+
* 🔴 **按自有属性读**:一只来自 wire 的对象可以带原型(`Object.create({status:'resuming'})` 上那个
|
|
69
|
+
* 值不是这次回执带来的事实)。
|
|
70
|
+
* 🔴 **不补默认、不折缺席**:读不出 `status` 不等于终局,读不出 `executionOutcome` 不等于 allowed。
|
|
71
|
+
*/
|
|
72
|
+
export declare function readDecideReceipt(result: unknown): DecideReceiptView | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* 🔴 **这次 decide 的 200 有没有把门解决掉** —— 唯一的诚实答案是「**说不出来**」。
|
|
75
|
+
*
|
|
76
|
+
* 本谓词**刻意不存在**的那个反面(`isDecideResolved`)是 B-070 的病根:不存在任何一位能从 200 体上
|
|
77
|
+
* 读出「门已解决」。所以本包给的是这一个**方向相反**的谓词:它答的是「这个 200 **明说**了续跑还没
|
|
78
|
+
* 做完吗」。
|
|
79
|
+
* · `true` —— 受理形/parked 回执(`status:"resuming"`):引擎**明说**它只是受理了;
|
|
80
|
+
* · `false` —— 别的形:**不代表已解决**,只代表「这个回体没有明说自己是受理形」。
|
|
81
|
+
* ⇒ 两边都不是「解决了」的证据。解决的证据只有一处:**流上的下一条 running 臂 / 新 run 的事件**。
|
|
82
|
+
*/
|
|
83
|
+
export declare function decideAcceptedNotResolved(receipt: DecideReceiptView | undefined): boolean;
|
|
84
|
+
/** {@link decideRefusalFromError} 的结构化读数。 */
|
|
85
|
+
export interface DecideRefusalDetail {
|
|
86
|
+
/** 命中的那一个码({@link DECIDE_WORKFLOW_LANE_CODES} 的成员之一)。 */
|
|
87
|
+
code: string;
|
|
88
|
+
/**
|
|
89
|
+
* 拒体上的 `runId`(sdk README §9.0.0:`DecideWorkflowHostError` 带 `.runId` —— **恢复句柄**)。
|
|
90
|
+
* 🔴 **它不是 `taskId`**:那一节逐字「此前 `DecideError` 的顶注写着『every decide.* body carries
|
|
91
|
+
* `taskId`』——那句话自 7.69.0 起不再成立(workflow 车道的拒体没有 `taskId`,铸一个是假话)」。
|
|
92
|
+
* ⇒ 读不出就缺席,**绝不**拿手上的 taskId 顶上去。
|
|
93
|
+
* 🔴 「两个句柄位**各自只在自己那条车道上在场**,按 `.errorCode` 分,不要互相回落」(同节逐字)。
|
|
94
|
+
*/
|
|
95
|
+
runId?: string;
|
|
96
|
+
/** 这一句人话(**唯一铸点**;三端共用,端零自拼)。 */
|
|
97
|
+
detail: string;
|
|
98
|
+
/**
|
|
99
|
+
* **同一个决定再发一次有没有用**。
|
|
100
|
+
* 🔴 三个码上的取值各有出处(见 `engineErrorCodes.ts` 的逐码顶注),**不是**按严重程度排的:
|
|
101
|
+
* `host_unknown` = `false`(无重试价值);`host_not_parked` / `remember_unsupported` = `true`
|
|
102
|
+
* (前者等宿主再 park,后者去掉 `remember`)。
|
|
103
|
+
* ⚠️ `true` **不带窗**:server 在这三个码上**从不**铸 `retryAfterSec`(它只在
|
|
104
|
+
* `RESUME_RETRY_LATER_CODES` 那两码上铸)⇒ 端**不许**据此渲一个编出来的倒计时。
|
|
105
|
+
*/
|
|
106
|
+
resendable: boolean;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* 一个 decide 失败抛出物 → {@link DecideRefusalDetail};不是这三个码之一 ⇒ `null`。
|
|
110
|
+
*
|
|
111
|
+
* 🔴 **判型是闭集的,消费面是开集的**(与 `resumeRefusalFromError` 逐字同律):不认识的码一律
|
|
112
|
+
* `null` ⇒ 端一个字都不补 ⇒ 机器可读那一行(`API Error: <status> <errorCode> · <msg>`)原样上屏。
|
|
113
|
+
* **绝不按 `decide.` 前缀猜一族码的语义** —— 那个前缀下同时住着「没救」「等时序」「改参数」
|
|
114
|
+
* 三种处置,还住着 `approval_binding_mismatch` 这类**安全信号**(它有自己的 fail-closed 处置,
|
|
115
|
+
* 被这里顺手认领会把一条安全停说成一次可重发的时序问题)。
|
|
116
|
+
*/
|
|
117
|
+
export declare function decideRefusalFromError(e: unknown): DecideRefusalDetail | null;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/decideReceipt.ts — `/decide` **答了什么**:200 回执的读面 + 三条 workflow 车道拒绝码的
|
|
3
|
+
* 人话铸点(B-070 / L-200,0.65.0;sdk README §9.0.0,server ≥7.69.0)。
|
|
4
|
+
*
|
|
5
|
+
* ── 🔴 这一件修的是什么:**200 ≠ 门已解决** ─────────────────────────────────────────────────
|
|
6
|
+
* sdk README §9.0.0 逐字:「workflow 车道的 200 只是**投递受理** —— 子代的 checkpoint 仍 pending,
|
|
7
|
+
* `/v1/approvals` 上那张卡**可能还在**。据 200 立刻把卡从 UI 抹掉,用户会看到一张『批过了却还在』
|
|
8
|
+
* 的幽灵卡,或者反过来:卡消失了而决定其实在宿主被打断时丢了。」
|
|
9
|
+
* core `runner/contracts.d.ts:1492` 同向:那一步 **launches the run that re-invokes** —— 它启动的是
|
|
10
|
+
* 另一条 run,不是「这件事已经做完了」。
|
|
11
|
+
* ⇒ 本包对 decide 成功的处置因此换了**判据锚**([anchor-on-the-deciding-quantity]):
|
|
12
|
+
* 真正决定「门解没解决」的量是**流上的下一条 running 臂 / 新 run 的事件**,不是那个 200。
|
|
13
|
+
* `hitlBridge.decideRaw` 不再在 200 上清 `active`(见该处注释),`parkResolver` 的重挂腿改按
|
|
14
|
+
* {@link DecideReceiptView.handoffTaskId} 换挂。
|
|
15
|
+
*
|
|
16
|
+
* ── `/decide` 200 的**四种形**(sdk `ApprovalDecisionResult` 顶注逐字)─────────────────────────
|
|
17
|
+
* · **受理形**(≥7.37 durable 部署默认)`{taskId, sessionId, status:"resuming", bindingEnforced:true}`
|
|
18
|
+
* · **终局形**(无 durable run 行 / `verify:true` 的同步长调用)`{taskId?, sessionId, status, errorCode?, …}`
|
|
19
|
+
* · **parked 回执** `{taskId, status:"resuming", decision}`
|
|
20
|
+
* · **幂等回放形**(#368)`{sessionId, idempotent:true, decision, bindingEnforced:true, executionOutcome?}`
|
|
21
|
+
* 🔴 **没有任何键四形都保证** —— 所以本读器对每一位都做诚实缺席,**绝不**靠某一位在不在场去推断
|
|
22
|
+
* 另一件事。
|
|
23
|
+
*
|
|
24
|
+
* ── 🔴 `executionOutcome`:缺席 = 未知,**禁读成 allowed** ───────────────────────────────────
|
|
25
|
+
* sdk `types.d.ts:3809-3813` 逐字:它**只在幂等回放形上**,说的是「那个已决动作**最后被怎么处置**」;
|
|
26
|
+
* 「`decision` 说的是『人批了没有』,这一格说的是『执行时被放行还是被部署复核在 post-CAS 否掉』,
|
|
27
|
+
* 两者**可以相反**」;「🔴 **缺席 = 未知**(判词还没落上 / 记录读不懂 / 命中行之间分歧),**禁读成
|
|
28
|
+
* allowed**」。core `checkpoint-store.d.ts:1785` 同句:「the execution outcome is not known, never
|
|
29
|
+
* 'it was allowed'」。
|
|
30
|
+
* ⇒ 本读器把它读成 `GateOutcomeView | undefined`,**走的是 `gateOutcome.ts` 那一只读器**
|
|
31
|
+
* (把它包回 `{gate: …}` 的形喂进去)——不在这里另写一份门记录读法,否则两处判据必然分叉。
|
|
32
|
+
* 消费端渲卡时:**在场**才说「执行时被放行/被否」,缺席一个字都不说。
|
|
33
|
+
*/
|
|
34
|
+
import { gateOutcomeOf } from './gateOutcome.js';
|
|
35
|
+
import { DECIDE_WORKFLOW_HOST_NOT_PARKED, DECIDE_WORKFLOW_HOST_UNKNOWN, DECIDE_WORKFLOW_LANE_CODES, DECIDE_WORKFLOW_REMEMBER_UNSUPPORTED, } from './engineErrorCodes.js';
|
|
36
|
+
/** 受理形/parked 回执上那个**唯一**表示「受理了、还没做完」的状态词(sdk README §9.0.0 逐字)。 */
|
|
37
|
+
const STATUS_RESUMING = 'resuming';
|
|
38
|
+
/** 非空串窄读(与本包其余读面同一条:空串 = 没这一格)。 */
|
|
39
|
+
function str(v) {
|
|
40
|
+
return typeof v === 'string' && v.length > 0 ? v : undefined;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* `/decide` 的 200 体 → {@link DecideReceiptView};不是一只对象 / 一位都读不出 ⇒ `undefined`,
|
|
44
|
+
* 绝不抛。
|
|
45
|
+
*
|
|
46
|
+
* 🔴 **按自有属性读**:一只来自 wire 的对象可以带原型(`Object.create({status:'resuming'})` 上那个
|
|
47
|
+
* 值不是这次回执带来的事实)。
|
|
48
|
+
* 🔴 **不补默认、不折缺席**:读不出 `status` 不等于终局,读不出 `executionOutcome` 不等于 allowed。
|
|
49
|
+
*/
|
|
50
|
+
export function readDecideReceipt(result) {
|
|
51
|
+
if (typeof result !== 'object' || result === null || Array.isArray(result))
|
|
52
|
+
return undefined;
|
|
53
|
+
const r = result;
|
|
54
|
+
const status = Object.hasOwn(r, 'status') ? str(r.status) : undefined;
|
|
55
|
+
const taskId = Object.hasOwn(r, 'taskId') ? str(r.taskId) : undefined;
|
|
56
|
+
const sessionId = Object.hasOwn(r, 'sessionId') ? str(r.sessionId) : undefined;
|
|
57
|
+
const idempotent = Object.hasOwn(r, 'idempotent') && r.idempotent === true ? true : undefined;
|
|
58
|
+
// 🔴 换挂句柄的判据是**两件合取**:引擎说了「受理了、在续跑」**并且**给了一个非空 run id。
|
|
59
|
+
// 少了前半句,一个终局形的 `taskId`(= 原来那条 run)会被当成「换挂到新 run」;
|
|
60
|
+
// 少了后半句,`status:"resuming"` 但没给 id 时会铸一个空句柄。
|
|
61
|
+
const handoffTaskId = status === STATUS_RESUMING && taskId !== undefined ? taskId : undefined;
|
|
62
|
+
// 🔴 走 `gateOutcome.ts` 的**同一只**读器(包回它认的载体名);不在这里另写一份门记录读法。
|
|
63
|
+
const executionOutcome = Object.hasOwn(r, 'executionOutcome')
|
|
64
|
+
? gateOutcomeOf({ gate: r.executionOutcome })
|
|
65
|
+
: undefined;
|
|
66
|
+
if (status === undefined &&
|
|
67
|
+
taskId === undefined &&
|
|
68
|
+
sessionId === undefined &&
|
|
69
|
+
idempotent === undefined &&
|
|
70
|
+
executionOutcome === undefined) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
...(status !== undefined ? { status } : {}),
|
|
75
|
+
...(taskId !== undefined ? { taskId } : {}),
|
|
76
|
+
...(sessionId !== undefined ? { sessionId } : {}),
|
|
77
|
+
...(idempotent !== undefined ? { idempotent } : {}),
|
|
78
|
+
...(handoffTaskId !== undefined ? { handoffTaskId } : {}),
|
|
79
|
+
...(executionOutcome !== undefined ? { executionOutcome } : {}),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* 🔴 **这次 decide 的 200 有没有把门解决掉** —— 唯一的诚实答案是「**说不出来**」。
|
|
84
|
+
*
|
|
85
|
+
* 本谓词**刻意不存在**的那个反面(`isDecideResolved`)是 B-070 的病根:不存在任何一位能从 200 体上
|
|
86
|
+
* 读出「门已解决」。所以本包给的是这一个**方向相反**的谓词:它答的是「这个 200 **明说**了续跑还没
|
|
87
|
+
* 做完吗」。
|
|
88
|
+
* · `true` —— 受理形/parked 回执(`status:"resuming"`):引擎**明说**它只是受理了;
|
|
89
|
+
* · `false` —— 别的形:**不代表已解决**,只代表「这个回体没有明说自己是受理形」。
|
|
90
|
+
* ⇒ 两边都不是「解决了」的证据。解决的证据只有一处:**流上的下一条 running 臂 / 新 run 的事件**。
|
|
91
|
+
*/
|
|
92
|
+
export function decideAcceptedNotResolved(receipt) {
|
|
93
|
+
return receipt?.status === STATUS_RESUMING;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* `/decide` workflow 车道**拒绝码 → 一句人话**的表(三句刻意逐字互异 —— 三条不同的下一步:
|
|
97
|
+
* 没救 / 等宿主再 park / 去掉一个参数重发)。
|
|
98
|
+
*
|
|
99
|
+
* 🔴 **不复述 server 的 message**(与 `resumeRefusalCopy` 同律):那一行已经在屏上了,本表只补
|
|
100
|
+
* message 里没有的那件事 —— 「我现在该做什么」。
|
|
101
|
+
*/
|
|
102
|
+
const DECIDE_REFUSAL_SENTENCES = Object.freeze({
|
|
103
|
+
[DECIDE_WORKFLOW_HOST_UNKNOWN]: 'this workflow run carries no originating session to deliver the decision to, so re-sending the same decision cannot help; the approval is still pending and can be decided again once the run is reachable',
|
|
104
|
+
[DECIDE_WORKFLOW_HOST_NOT_PARKED]: 'the host session is not sitting on a wakeable pause right now, so this delivery had nowhere to land; the approval is still pending — send it again once the host parks, or resume that run directly',
|
|
105
|
+
[DECIDE_WORKFLOW_REMEMBER_UNSUPPORTED]: 'this lane refuses to remember the decision for the session; re-send the same decision without the remember option — nothing was consumed by this refusal',
|
|
106
|
+
});
|
|
107
|
+
/** 按码给出的 `resendable`(单铸;理由逐码写在 `engineErrorCodes.ts` 的顶注里)。 */
|
|
108
|
+
const DECIDE_RESENDABLE = Object.freeze({
|
|
109
|
+
[DECIDE_WORKFLOW_HOST_UNKNOWN]: false,
|
|
110
|
+
[DECIDE_WORKFLOW_HOST_NOT_PARKED]: true,
|
|
111
|
+
[DECIDE_WORKFLOW_REMEMBER_UNSUPPORTED]: true,
|
|
112
|
+
});
|
|
113
|
+
/**
|
|
114
|
+
* 一个 decide 失败抛出物 → {@link DecideRefusalDetail};不是这三个码之一 ⇒ `null`。
|
|
115
|
+
*
|
|
116
|
+
* 🔴 **判型是闭集的,消费面是开集的**(与 `resumeRefusalFromError` 逐字同律):不认识的码一律
|
|
117
|
+
* `null` ⇒ 端一个字都不补 ⇒ 机器可读那一行(`API Error: <status> <errorCode> · <msg>`)原样上屏。
|
|
118
|
+
* **绝不按 `decide.` 前缀猜一族码的语义** —— 那个前缀下同时住着「没救」「等时序」「改参数」
|
|
119
|
+
* 三种处置,还住着 `approval_binding_mismatch` 这类**安全信号**(它有自己的 fail-closed 处置,
|
|
120
|
+
* 被这里顺手认领会把一条安全停说成一次可重发的时序问题)。
|
|
121
|
+
*/
|
|
122
|
+
export function decideRefusalFromError(e) {
|
|
123
|
+
const code = typeof e?.errorCode === 'string'
|
|
124
|
+
? (e.errorCode)
|
|
125
|
+
: undefined;
|
|
126
|
+
if (code === undefined || !DECIDE_WORKFLOW_LANE_CODES.includes(code))
|
|
127
|
+
return null;
|
|
128
|
+
const rawRunId = e.runId;
|
|
129
|
+
const runId = typeof rawRunId === 'string' && rawRunId.length > 0 ? rawRunId : undefined;
|
|
130
|
+
// 🔴 按自有属性查表(冻结不移除原型):一个来自 wire 的 `constructor` 会命中 `Object.prototype`
|
|
131
|
+
// 上的**函数**并被当成一句话。命中闭集之后表里必然有句子 —— 读不出仍给一句诚实兜底,
|
|
132
|
+
// 绝不返回 `undefined` 让端去拼。
|
|
133
|
+
const detail = Object.hasOwn(DECIDE_REFUSAL_SENTENCES, code)
|
|
134
|
+
? DECIDE_REFUSAL_SENTENCES[code]
|
|
135
|
+
: undefined;
|
|
136
|
+
return {
|
|
137
|
+
code,
|
|
138
|
+
...(runId !== undefined ? { runId } : {}),
|
|
139
|
+
detail: detail ?? 'the engine refused this decision on the workflow lane; the approval is still pending',
|
|
140
|
+
resendable: Object.hasOwn(DECIDE_RESENDABLE, code) ? DECIDE_RESENDABLE[code] === true : false,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
@@ -251,6 +251,38 @@ export declare const RESUME_PLACEMENT_MISMATCH = "resume.placement_mismatch";
|
|
|
251
251
|
* 不属内容批射程;本条按现状登记,换形另立一批。本位是**新铸**的,所以在出生那天就用对形。
|
|
252
252
|
*/
|
|
253
253
|
export declare const RESUME_RETRY_LATER_CODES: readonly string[];
|
|
254
|
+
/**
|
|
255
|
+
* `decide.workflow_host_unknown`(409;sdk README §9.0.0 逐字:「run 上没有发起会话锚 ⇒ 无处投递,
|
|
256
|
+
* **无重试价值**」)。
|
|
257
|
+
* 🔴 出路**不是等**:这条 run 上根本没有可投递的宿主锚,重发一百次也一样。人能做的是据拒体的
|
|
258
|
+
* `runId` 自己去看那条 run(或换一条路 resume 它)。
|
|
259
|
+
*/
|
|
260
|
+
export declare const DECIDE_WORKFLOW_HOST_UNKNOWN = "decide.workflow_host_unknown";
|
|
261
|
+
/**
|
|
262
|
+
* `decide.workflow_host_not_parked`(409;同上:「宿主此刻没停在可唤醒的 park 上 ⇒ **等它再 park
|
|
263
|
+
* 后重发**,或据 `runId` 自行 resume」)。
|
|
264
|
+
* 🔴 与上一码**刻意分成两个词**:这一个**可以**重发(宿主是时序问题),上一个不能。
|
|
265
|
+
* ⚠️ 但它**不在** {@link RESUME_RETRY_LATER_CODES} 里,也不该在 —— 那张表闭的是「server 在哪些码上
|
|
266
|
+
* 铸 `retryAfterSec`」,而本码**恒无窗**(server 给不出「等多久」)。可等 ≠ 有窗。
|
|
267
|
+
*/
|
|
268
|
+
export declare const DECIDE_WORKFLOW_HOST_NOT_PARKED = "decide.workflow_host_not_parked";
|
|
269
|
+
/**
|
|
270
|
+
* `decide.workflow_remember_unsupported`(400;同上:「本车道 fail-closed 拒 `remember:"session"`」)。
|
|
271
|
+
* 🔴 出路是**去掉 `remember` 重发**,而且 sdk 顶注逐字保证「零副作用」——这一格与另外两个 409
|
|
272
|
+
* 的分法不是严重程度,是**改什么才能过**(改参数 / 等时序 / 没救)。
|
|
273
|
+
*/
|
|
274
|
+
export declare const DECIDE_WORKFLOW_REMEMBER_UNSUPPORTED = "decide.workflow_remember_unsupported";
|
|
275
|
+
/**
|
|
276
|
+
* `/decide` workflow 车道的拒绝码**闭集**(三员)。
|
|
277
|
+
*
|
|
278
|
+
* 🔴 闭的不是「`decide.*` 一共有几个码」(那仍是开集:`approval_binding_mismatch` /
|
|
279
|
+
* `approval_stale` / `gate_not_tool_approval` 等都不在本表,它们各有既有处置),闭的是
|
|
280
|
+
* 「**这三个码是 workflow 那条新车道独有的、而且各自有一句人话**」。
|
|
281
|
+
* 🔴 **形制**:`Object.freeze` 的数组,不是 `ReadonlySet`(同 {@link RESUME_RETRY_LATER_CODES} 的
|
|
282
|
+
* 已定谳病形 —— `ReadonlySet` 只在类型面只读,而判定查的就是公面上这同一个实例)。
|
|
283
|
+
* 🔴 加成员必须**同批**补那一句人话 + 判据,不许靠 `decide.` 前缀放宽(前缀下住着三种处置)。
|
|
284
|
+
*/
|
|
285
|
+
export declare const DECIDE_WORKFLOW_LANE_CODES: readonly string[];
|
|
254
286
|
/**
|
|
255
287
|
* server 温切 drain 门的 pre-stream 拒收码(503 + `errorCode:"draining"`;server 侧
|
|
256
288
|
* `error:"draining"` 是冻结契约,SDK toApiError 盖成 `errorCode`)。此前壳/包注释各持裸字面。
|