@sema-agent/client-core 0.59.2 → 0.61.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 +245 -0
- package/README.md +4 -2
- package/dist/adapt/toolCards.d.ts +2 -1
- package/dist/adapter/downstream/eventToSdkMessage.d.ts +24 -4
- package/dist/adapter/downstream/eventToSdkMessage.js +42 -70
- package/dist/adapter/downstream/terminalToSdkResult.d.ts +27 -22
- package/dist/adapter/downstream/terminalToSdkResult.js +47 -30
- package/dist/adapter/runStream.js +27 -11
- package/dist/engineCapsCache.d.ts +27 -0
- package/dist/engineCapsCache.js +29 -0
- package/dist/engineErrorCodes.d.ts +12 -0
- package/dist/engineErrorCodes.js +13 -0
- package/dist/gateOutcome.d.ts +157 -0
- package/dist/gateOutcome.js +181 -0
- package/dist/headlessReconnectWire.js +9 -2
- package/dist/hitl/askParkRowRouting.d.ts +2 -0
- package/dist/hitl/askParkRowRouting.js +17 -2
- package/dist/hitl/frameRouter.d.ts +2 -2
- package/dist/hitl/frameRouter.js +49 -35
- package/dist/hitl/hitlBridge.d.ts +49 -2
- package/dist/hitl/hitlBridge.js +22 -1
- package/dist/hitl/parkResolver.d.ts +3 -1
- package/dist/hitl/parkResolver.js +15 -5
- package/dist/hitl/planReviewWire.d.ts +8 -1
- package/dist/hitl/planReviewWire.js +18 -7
- package/dist/hitl/toolApprovalWire.d.ts +37 -3
- package/dist/hitl/toolApprovalWire.js +36 -9
- package/dist/index.d.ts +4 -0
- package/dist/index.js +12 -0
- package/dist/printToolResultFrame.d.ts +34 -49
- package/dist/readFacePosture.d.ts +53 -0
- package/dist/readFacePosture.js +120 -0
- package/dist/resumeRefusalCopy.d.ts +12 -4
- package/dist/resumeRefusalCopy.js +15 -5
- package/dist/runTerminal.d.ts +194 -0
- package/dist/runTerminal.js +189 -0
- package/dist/sqlEngineCapability.d.ts +11 -1
- package/dist/sqlEngineCapability.js +13 -1
- package/dist/writeProtectionCapability.d.ts +142 -0
- package/dist/writeProtectionCapability.js +224 -0
- package/docs/INTEGRATION-CLIENTS.md +433 -11
- package/package.json +3 -3
|
@@ -67,7 +67,14 @@ export declare function planReviewDecisionFromAnswer(answer: QuestionAnswer | nu
|
|
|
67
67
|
export declare function notePlanReviewAnsweredIfDecisive(questionId: unknown, answer: QuestionAnswer | null | undefined): void;
|
|
68
68
|
/** W1 带 key 变体。 */
|
|
69
69
|
export declare function notePlanReviewAnsweredIfDecisiveFor(sessionKey: string, questionId: unknown, answer: QuestionAnswer | null | undefined): void;
|
|
70
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* done 帧的 plan_review park 形状(结构性读,别的终局一律 false)。
|
|
72
|
+
*
|
|
73
|
+
* 🔴 0.60.0(engine ≥7.64.0 / sdk 8.4.0):判据从退役的两个平面键(`status:"needs_review"` +
|
|
74
|
+
* `checkpointGate.kind`)换成带标因由 `terminal.paused.gate.kind`,读法单源
|
|
75
|
+
* {@link readRunTerminal}。门种仍**逐字只认 `plan_review`**(不顺手放宽到 `dry_run_review` /
|
|
76
|
+
* `needs_review`):本臂驱动的是那一个 resume verb,放宽等于把别的门送进一个它答不了的入口。
|
|
77
|
+
*/
|
|
71
78
|
export declare function isPlanReviewPark(result: unknown): result is {
|
|
72
79
|
taskId: string;
|
|
73
80
|
sessionId?: string;
|
|
@@ -44,6 +44,9 @@ import { enqueuePlanReviewOutcome } from '../notifications.js';
|
|
|
44
44
|
import { planReviewQuestionId, REOPEN_ID_TAIL } from './gateIdentity.js';
|
|
45
45
|
import { notePlanReviewAnswered, notePlanReviewAnsweredFor, planReviewArmedKeyFor, registerArmedGateFor, waitForGateArmedFor, wasGateArmedFor, } from './armedGateRegistry.js';
|
|
46
46
|
import { DEFAULT_SESSION_KEY } from '../sessionSlot.js';
|
|
47
|
+
import { PLAN_REVIEW_GATE_KIND } from '../adapter/activeRunSelfHeal.js';
|
|
48
|
+
// 0.60.0(engine ≥7.64.0 / sdk 8.4.0):终局/park 读数的单一读器。
|
|
49
|
+
import { readRunTerminal, runTerminalGateKind } from '../runTerminal.js';
|
|
47
50
|
/** resume 是 SYNC 驱动到终态的(可能分钟级)——原 raw fetch 无超时;SDK Transport 恒有每请求
|
|
48
51
|
* 超时,取 6h 宽上界(诚实预算:超过它基本是引擎僵死,超时文案与网络失败同走 outcome 通知)。 */
|
|
49
52
|
const PLAN_REVIEW_RESUME_TIMEOUT_MS = 6 * 60 * 60_000;
|
|
@@ -91,14 +94,22 @@ export function notePlanReviewAnsweredIfDecisiveFor(sessionKey, questionId, answ
|
|
|
91
94
|
return;
|
|
92
95
|
notePlanReviewAnsweredFor(sessionKey, questionId);
|
|
93
96
|
}
|
|
94
|
-
/**
|
|
97
|
+
/**
|
|
98
|
+
* done 帧的 plan_review park 形状(结构性读,别的终局一律 false)。
|
|
99
|
+
*
|
|
100
|
+
* 🔴 0.60.0(engine ≥7.64.0 / sdk 8.4.0):判据从退役的两个平面键(`status:"needs_review"` +
|
|
101
|
+
* `checkpointGate.kind`)换成带标因由 `terminal.paused.gate.kind`,读法单源
|
|
102
|
+
* {@link readRunTerminal}。门种仍**逐字只认 `plan_review`**(不顺手放宽到 `dry_run_review` /
|
|
103
|
+
* `needs_review`):本臂驱动的是那一个 resume verb,放宽等于把别的门送进一个它答不了的入口。
|
|
104
|
+
*/
|
|
95
105
|
export function isPlanReviewPark(result) {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
106
|
+
const read = readRunTerminal(result);
|
|
107
|
+
if (read === null || read.kind !== 'paused')
|
|
108
|
+
return false;
|
|
109
|
+
if (runTerminalGateKind(read) !== PLAN_REVIEW_GATE_KIND)
|
|
110
|
+
return false;
|
|
111
|
+
const taskId = result?.taskId;
|
|
112
|
+
return typeof taskId === 'string' && taskId.length > 0;
|
|
102
113
|
}
|
|
103
114
|
/**
|
|
104
115
|
* REF-CC-027:已武装未消解的 taskId 去重集(与 `HitlBridge`/`liveQuestionStore` 同款「进程内单例
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
* 静默截断);真落行与否看 ack 的 `noteRecorded`(缺席 ≠ false)。
|
|
78
78
|
* 🔴 方向纪律:reason/note 只做归因,绝不参与裁决;缺席 ⇒ 现状字节不变。
|
|
79
79
|
*/
|
|
80
|
-
import { type GateCurrentPending, type HitlClientLike } from './hitlBridge.js';
|
|
80
|
+
import { type GateCurrentPending, type HitlFailureStage, type HitlSafetyCode, type HitlClientLike } from './hitlBridge.js';
|
|
81
81
|
import { RULE_OFFER_MATCHES, RULE_OFFER_BATCH_MEMBER_KINDS, RULE_OFFER_UNCOVERED_REASONS, RULE_OFFERS_ABSENCE_REASONS, DENIAL_LIMIT_KINDS } from '@sema-agent/sdk';
|
|
82
82
|
import type { RuleSuggestion, ToolApprovalRespondAck, PersistedRuleAnchor as SdkPersistedRuleAnchor, RuleOfferMatch as SdkRuleOfferMatch, RuleOfferBatchMember as SdkRuleOfferBatchMember, RuleOfferUncoveredDetail as SdkRuleOfferUncoveredDetail, RuleOfferUncoveredReason as SdkRuleOfferUncoveredReason, AskOrigin as SdkAskOrigin, RuleOffersAbsence as SdkRuleOffersAbsence, DenialLimitKind as SdkDenialLimitKind, DenialLimitFallback as SdkDenialLimitFallback } from '@sema-agent/sdk';
|
|
83
83
|
/** fs 写权限 gate 判定:未来的一等 kind(tool_approval)或按 toolName(server 桥首批=fs 写三件,
|
|
@@ -122,11 +122,43 @@ export type FsApprovalOutcome = {
|
|
|
122
122
|
* 不是 stale 臂,四种情形在 wire 上同形(server 自己的 F 类留痕账逐字如此)。
|
|
123
123
|
* 🔴 它**不参与任何裁决** —— 见 `GateCurrentPending` 顶注;消费方拿它当指路,不当放行凭据。
|
|
124
124
|
* 🔴 **取件腿的 `no_pending` 恒不带这一位**:那一腿连 decide 都没发,谈不上「你决的那条过期了」。 */
|
|
125
|
+
/** `errorCode`(0.60.0,上游候包件 ②;与 0.47.0 补 `currentPending` **同形**):这次失败的
|
|
126
|
+
* **引擎机器码**,原样透传(`readWireErrorCode` 单源读口)。
|
|
127
|
+
* 🔴 **它与 {@link code} 是两个命名空间,绝不合并**:`code` 是**本包铸**的取件腿判别位(闭集,
|
|
128
|
+
* 今天只有 `no_pending`),本位是 **server/core 铸**的 wire 码(开集,`resume.preflight_rejected`
|
|
129
|
+
* / `approval_stale` / …)。合成一个位会让消费点拿两种语义当一种用。
|
|
130
|
+
* 🔴 **为什么非补不可**:本类型把 typed 错**拍平成 `reason` 串**,于是只读 outcome 形的消费者
|
|
131
|
+
* 对机读码全盲,要分辨拒绝族只能去正则匹配那句拍平文案(里面嵌的是 `String(e)`,上游改一个
|
|
132
|
+
* 词就静默空转)。
|
|
133
|
+
* 🔴 **缺席不带语义**:取件腿的失败、卡口失败、以及抛出物身上根本没有码的传输类失败,三形同样
|
|
134
|
+
* 缺席 —— 缺席 ≠「不是引擎拒的」。
|
|
135
|
+
* 🔴 **只搬码,不搬正文**:错误正文可能含凭据/内部路径,照旧只进 `reason` 的既有拍平位,
|
|
136
|
+
* 不经本位外流。 */
|
|
137
|
+
/** `stage`(0.60.0,异源对抗复审 [medium] 采纳:B-037 候包件 ② 的**完整**兑现):这次失败**停在
|
|
138
|
+
* 链条的哪一段**。四类失败此前在本形上不可分 —— 「取件那一拍没有可决行」「卡口没装」「引擎语义
|
|
139
|
+
* 拒绝」「出站瞬断耗尽」拍平之后**只剩一个 `reason` 串**,消费点要分流只能去正则匹配那句文案
|
|
140
|
+
* (里面嵌的是 `String(e)`,上游改一个词就静默空转)。⇒ 本位是**必填**的:一个可选的阶段位等于
|
|
141
|
+
* 又留了一格「读不出就猜」。
|
|
142
|
+
* · `fetch` —— 取件腿(`findPendingForTask`)没给出可决行,或读面自己炸了;
|
|
143
|
+
* · `input` —— 行上没有可呈现的工具入参(卡渲不出来);
|
|
144
|
+
* · `card` —— 卡口那一段失败(没装卡口 / 卡口自己报失败);
|
|
145
|
+
* · `decide` —— 决断已经发出去了(引擎语义拒绝、或出站瞬断耗尽)。
|
|
146
|
+
* 🔴 **只有 `decide` 这一段代表「引擎那一头知道这次决断」**——安全停(是否要重呈、能否重试)
|
|
147
|
+
* 的第一问就是它,而前三段一个字节都没送出去。 */
|
|
148
|
+
/** `safetyCode`(0.60.0,同批):本包**自己**的安全停判别位({@link HitlSafetyCode} 闭五词)。
|
|
149
|
+
* 🔴 与 {@link errorCode}(server/core 铸的 **wire** 码,开集)、{@link code}(取件腿的闭集判别位)
|
|
150
|
+
* 是**三个命名空间**,绝不合并 —— 「本地拦下的」「引擎拒的」「这一拍没有行」是三件不同的事,
|
|
151
|
+
* 出路也不同(重呈 / 换参数或等 / 重探)。
|
|
152
|
+
* 🔴 与 ask 腿 `parkResolver.GateOutcome.failed.code` **同形同源**(那一腿早有这一位,fs 腿此前没有
|
|
153
|
+
* —— 同形存量清剿:两件 hitl 文件一次改齐)。 */
|
|
125
154
|
| {
|
|
126
155
|
kind: 'failed';
|
|
156
|
+
stage: HitlFailureStage;
|
|
127
157
|
gatedCallId?: string | undefined;
|
|
128
158
|
reason: string;
|
|
129
159
|
code?: 'no_pending' | undefined;
|
|
160
|
+
errorCode?: string | undefined;
|
|
161
|
+
safetyCode?: HitlSafetyCode | undefined;
|
|
130
162
|
retryExhausted?: true;
|
|
131
163
|
currentPending?: GateCurrentPending | undefined;
|
|
132
164
|
};
|
|
@@ -809,8 +841,10 @@ export interface ToolApprovalFrame {
|
|
|
809
841
|
* 反向不成立(安全类 ask 有别的出身)。
|
|
810
842
|
* 🔴 **四成员全必填**(core 形上没有可选位):少一位不是「老 core」而是**坏值**,server 整键不铸,
|
|
811
843
|
* 本包的窄读器 {@link readDenialLimitFallback} 同判 —— 绝不半铸一张让人误读计数的卡。
|
|
812
|
-
* 🔴 **`autoDenyAfterMs` 只用来渲倒计时**:窗的执行全在引擎(窗到即 deny
|
|
813
|
-
* `
|
|
844
|
+
* 🔴 **`autoDenyAfterMs` 只用来渲倒计时**:窗的执行全在引擎(窗到即 deny,并在 `tool_end.gate`
|
|
845
|
+
* 上盖 `settlement.kind:"denial_limit_window_expired"` —— engine ≥7.64.0 起这一个词就说完了
|
|
846
|
+
* 「是引擎的限额回落窗自己拒的」,不再需要旧形那三个键的合取;包面谓词
|
|
847
|
+
* `isDenialLimitAutoDeniedGate`)。
|
|
814
848
|
* 消费端**不得**据它自铸第二只定时器 —— 两只窗重叠比原缺陷更坏且静默(server/sdk 两侧同一条禁令)。
|
|
815
849
|
* `0` = 不武装(部署把旋钮关了,或 TOTAL 档的卡恒 0 等人)。
|
|
816
850
|
* 🔴 **缺席不是断言**:绝大多数 ask 根本不是回落卡。
|
|
@@ -77,7 +77,13 @@
|
|
|
77
77
|
* 静默截断);真落行与否看 ack 的 `noteRecorded`(缺席 ≠ false)。
|
|
78
78
|
* 🔴 方向纪律:reason/note 只做归因,绝不参与裁决;缺席 ⇒ 现状字节不变。
|
|
79
79
|
*/
|
|
80
|
-
import { DEFAULT_DENY_REASON, DecideTransportRetryExhaustedError, HitlBridge, HitlSafetyError, denyReasonForWire, findPendingForTask, readDecideCurrentPending, } from './hitlBridge.js';
|
|
80
|
+
import { DEFAULT_DENY_REASON, DecideTransportRetryExhaustedError, HitlBridge, HitlSafetyError, denyReasonForWire, findPendingForTask, readDecideCurrentPending, readWireErrorCode, } from './hitlBridge.js';
|
|
81
|
+
// B-042(0.60.0):取件身份闸与**行路由同源**的单一谓词。见 `surfaceFsApprovalAndDecide` 里的调用点注。
|
|
82
|
+
// ⚠️ 这条 import 与 `frameRouter → toolApprovalWire` 构成一个 ESM 环(askParkRowRouting → frameRouter
|
|
83
|
+
// → 本文件)。环安全的判据不是「跑起来没炸」而是**结构**:三个模块在环上的顶层求值都只铸函数声明
|
|
84
|
+
// 与字面量常量,没有任何一处在**模块体**里读另一模块的绑定(读全发生在函数体内,那时环早已闭合)。
|
|
85
|
+
// 单例门与 portability 门各自盯着这条性质的一半;新增顶层 `const X = <另一模块的绑定>` 会破坏它。
|
|
86
|
+
import { askParkRowArm } from './askParkRowRouting.js';
|
|
81
87
|
import { hostLog } from '../host.js';
|
|
82
88
|
import { createSessionSlot, DEFAULT_SESSION_KEY } from '../sessionSlot.js';
|
|
83
89
|
import { readEngineActiveBgTasks } from '../fleet/fleetLedger.js';
|
|
@@ -215,7 +221,7 @@ export function surfaceApprovalCard(req) {
|
|
|
215
221
|
const cardPort = approvalCardPortFor(DEFAULT_SESSION_KEY);
|
|
216
222
|
if (cardPort === null) {
|
|
217
223
|
bumpCardPortMiss(DEFAULT_SESSION_KEY);
|
|
218
|
-
return Promise.resolve({ kind: 'failed', reason: cardPortMissReason() });
|
|
224
|
+
return Promise.resolve({ kind: 'failed', stage: 'card', reason: cardPortMissReason() });
|
|
219
225
|
}
|
|
220
226
|
return cardPort(req);
|
|
221
227
|
}
|
|
@@ -276,14 +282,24 @@ parkGatedCallId) {
|
|
|
276
282
|
// 🔴 常驻判据不是「看得见 pending 这个实参」,是 pure 门那四条「approvals.list 全程恰好 1 次」——
|
|
277
283
|
// 漏传一条腿,形状断言全绿而计数当场翻红(回炉车前的实况:只有 aborted 臂传了)。
|
|
278
284
|
const found = await findPendingForTask(deps.client, taskId, (toolName) => toolName !== undefined && (toolNameIsFsWrite(toolName) || toolNameIsShellExec(toolName)), signal ? { signal } : undefined, parkGatedCallId, // 见本参数头注:身份在场 ⇒ 逐字命中优先于「同族第一行」;缺席/不命中 ⇒ 原两腿
|
|
279
|
-
//
|
|
280
|
-
//
|
|
281
|
-
// kind
|
|
282
|
-
|
|
285
|
+
// ── B-042 修根(0.60.0):身份闸与**行路由同源** ────────────────────────────────────────────
|
|
286
|
+
// 病形 = [paired-mechanisms-must-share-premise]:三端把一行**路由**进本臂用的是
|
|
287
|
+
// `askParkRowRouting.askParkRowArm`(kind ∈ ask 门族 ∧ 不是 ask 工具 ⇒ `tool-gate`),而这里的
|
|
288
|
+
// 身份闸此前是 `isToolApprovalGate`,只有两条腿(一等 `kind === 'tool_approval'` 放行任意
|
|
289
|
+
// toolName,或 fs 写 / shell 的**名字腿**)。于是 `gateKind:"irreversible_ask"` +
|
|
290
|
+
// 工具名不在 fs 三件 + Bash 里的行(MCP 远端工具 / WebFetch / NotebookEdit / agent 工具 …)
|
|
291
|
+
// **路由说「是我的」、取件说「不是」**:身份逐字命中却被族闸判出局 ⇒ `identityConflicts` 收紧
|
|
292
|
+
// 候选池 ⇒ 池空 ⇒ `no_pending` ⇒ 卡永远重开不了。内建 fs/Bash 行只是恰好走了名字腿才没中招。
|
|
293
|
+
// 🔴 修 = 让这一格读**同一只**谓词:`askParkRowArm(row) === 'tool-gate'`。名字腿**只留给**
|
|
294
|
+
// 上面那个 `matches`(它服务的是 leg 2「同族第一行」,那一腿本来就该按工具名挑)。
|
|
295
|
+
// 🔴 **不会顺手放宽到外族**:`findPendingForTask` 先用 `askParkForeignGateKind` 把
|
|
296
|
+
// plan_review / resource_limit / 未来新词整族滤掉,本闸只在那批**可决行**里再分问答/工具两臂
|
|
297
|
+
// —— 两道闸串联的语义与修前逐字相同,变的只是「工具臂认不认得非 fs/Bash 的工具名」。
|
|
298
|
+
(row) => askParkRowArm(row) === 'tool-gate');
|
|
283
299
|
// #357:取件腿的结构化判别位原样透传(见 `FsApprovalOutcome.code` 头注)——「这一拍队列里没有
|
|
284
300
|
// 本 run 的可决行」与「approvals.list 自身炸了」自此对消费点结构可分,不再靠文案子串猜。
|
|
285
301
|
if (!found.ok)
|
|
286
|
-
return { kind: 'failed', reason: found.reason, ...(found.code !== undefined ? { code: found.code } : {}) };
|
|
302
|
+
return { kind: 'failed', stage: 'fetch', reason: found.reason, ...(found.code !== undefined ? { code: found.code } : {}) };
|
|
287
303
|
const { pending, gatedCallId } = found;
|
|
288
304
|
const toolName = typeof pending.toolName === 'string' ? pending.toolName : '';
|
|
289
305
|
// 工具入参:优先流上 tool_start.args(完整、零额外语义);缺则 pending.input(service 已 redact+限长
|
|
@@ -292,7 +308,7 @@ parkGatedCallId) {
|
|
|
292
308
|
pending.input ??
|
|
293
309
|
{};
|
|
294
310
|
if (typeof args !== 'object' || args === null) {
|
|
295
|
-
return { kind: 'failed', gatedCallId, reason: 'gate has no tool input payload' };
|
|
311
|
+
return { kind: 'failed', stage: 'input', gatedCallId, reason: 'gate has no tool input payload' };
|
|
296
312
|
}
|
|
297
313
|
const bridge = new HitlBridge(deps.client, taskId);
|
|
298
314
|
// A-028.3:卡身份键经 gateIdentity 唯一铸口(呈现台账/壳侧重开腿同源,字面不再各铸各的)。
|
|
@@ -343,7 +359,7 @@ parkGatedCallId) {
|
|
|
343
359
|
});
|
|
344
360
|
switch (card.kind) {
|
|
345
361
|
case 'failed':
|
|
346
|
-
return { kind: 'failed', gatedCallId, reason: card.reason };
|
|
362
|
+
return { kind: 'failed', stage: 'card', gatedCallId, reason: card.reason };
|
|
347
363
|
case 'aborted':
|
|
348
364
|
// turn 被中断(Esc/Ctrl+C):把挂着的 ask 用一次 **TOOL 级 deny** 结算掉(server
|
|
349
365
|
// `ASSISTANT-WIRE-CONTRACT.md` §4a;0.42.0 撤稿 —— 原文写的是「cancel-by-deny(contract/04
|
|
@@ -399,13 +415,19 @@ parkGatedCallId) {
|
|
|
399
415
|
catch (e) {
|
|
400
416
|
// #363:S-02 指路键原样透传(读不出 ⇒ 键不铸,形状逐字节不变)。见 FsApprovalOutcome 同名位。
|
|
401
417
|
const currentPending = readDecideCurrentPending(e);
|
|
418
|
+
// 0.60.0(候包件 ②):引擎机器码原样透传(单源 `readWireErrorCode`;读不出 ⇒ 键不铸)。
|
|
419
|
+
const wireCode = readWireErrorCode(e);
|
|
420
|
+
const safetyCode = e instanceof HitlSafetyError ? e.code : undefined;
|
|
402
421
|
return {
|
|
403
422
|
kind: 'failed',
|
|
423
|
+
stage: 'decide',
|
|
404
424
|
gatedCallId,
|
|
405
425
|
reason: `decide(approve) failed: ${String(e)}`,
|
|
406
426
|
// Inkglow-1085 P0a:瞬断耗尽的判别位(在场即真)—— 语义失败缺席,reason 字节不变。
|
|
407
427
|
...(e instanceof DecideTransportRetryExhaustedError ? { retryExhausted: true } : {}),
|
|
408
428
|
...(currentPending !== undefined ? { currentPending } : {}),
|
|
429
|
+
...(wireCode !== undefined ? { errorCode: wireCode } : {}),
|
|
430
|
+
...(safetyCode !== undefined ? { safetyCode } : {}),
|
|
409
431
|
};
|
|
410
432
|
}
|
|
411
433
|
case 'deny':
|
|
@@ -420,12 +442,17 @@ parkGatedCallId) {
|
|
|
420
442
|
catch (e) {
|
|
421
443
|
// #363:与 allow 臂**同形同源**(同形存量清剿:两条 decide 失败腿一次改齐,不许只修当格)。
|
|
422
444
|
const currentPending = readDecideCurrentPending(e);
|
|
445
|
+
const wireCode = readWireErrorCode(e);
|
|
446
|
+
const safetyCode = e instanceof HitlSafetyError ? e.code : undefined;
|
|
423
447
|
return {
|
|
424
448
|
kind: 'failed',
|
|
449
|
+
stage: 'decide',
|
|
425
450
|
gatedCallId,
|
|
426
451
|
reason: `decide(deny) failed: ${String(e)}`,
|
|
427
452
|
...(e instanceof DecideTransportRetryExhaustedError ? { retryExhausted: true } : {}),
|
|
428
453
|
...(currentPending !== undefined ? { currentPending } : {}),
|
|
454
|
+
...(wireCode !== undefined ? { errorCode: wireCode } : {}),
|
|
455
|
+
...(safetyCode !== undefined ? { safetyCode } : {}),
|
|
429
456
|
};
|
|
430
457
|
}
|
|
431
458
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -149,6 +149,10 @@ export * from './engineInlineTaskStats.js';
|
|
|
149
149
|
export * from './printToolResultFrame.js';
|
|
150
150
|
export * from './engineCapsCache.js';
|
|
151
151
|
export * from './sqlEngineCapability.js';
|
|
152
|
+
export * from './writeProtectionCapability.js';
|
|
153
|
+
export * from './runTerminal.js';
|
|
154
|
+
export * from './readFacePosture.js';
|
|
155
|
+
export * from './gateOutcome.js';
|
|
152
156
|
export * from './engineToolLabelStore.js';
|
|
153
157
|
export * from './fleetTaskDesc.js';
|
|
154
158
|
export type * from './types/engineState.js';
|
package/dist/index.js
CHANGED
|
@@ -162,6 +162,18 @@ export * from './printToolResultFrame.js';
|
|
|
162
162
|
export * from './engineCapsCache.js';
|
|
163
163
|
// S-131(0.59.0):`Capabilities.sql` 的四态窄读器 —— 壳侧那份逐字上收(导出名同名 = drift-lock)。
|
|
164
164
|
export * from './sqlEngineCapability.js';
|
|
165
|
+
// S-138(0.60.0;engine ≥7.63.0 / sdk 8.4.0):`Capabilities.writeProtection` 的四态窄读器 +
|
|
166
|
+
// operator 面 `diagnostics.wiring.writeProtection` 的行表投影。与 `sqlEngineCapability` 同构同纪律
|
|
167
|
+
// (四态词汇 / tee+换代失效口 / 唯一措辞铸点),端零自铸 —— 壳的 doctor 两处留位由它填。
|
|
168
|
+
export * from './writeProtectionCapability.js';
|
|
169
|
+
// 0.60.0(engine ≥7.64.0 / sdk 8.4.0):终局因由与门记录两只**判定归包**的读器。
|
|
170
|
+
export * from './runTerminal.js';
|
|
171
|
+
// 0.61.0(engine ≥7.65.0 / sdk 8.5.0 / S-167):READ 容纳面档位 + 来源的 operator 面窄读器
|
|
172
|
+
// (`WiringDiagnostics.readFace`)。与 `writeProtectionCapability` / `gateOutcome` 同构同纪律
|
|
173
|
+
// (防御读 / 唯一措辞铸点 / UNTRUSTED-for-display);与租户面 `capabilities.readFace` 刻意不合流,
|
|
174
|
+
// 只带一个纯比较函数,渲染归端。
|
|
175
|
+
export * from './readFacePosture.js';
|
|
176
|
+
export * from './gateOutcome.js';
|
|
165
177
|
export * from './engineToolLabelStore.js';
|
|
166
178
|
export * from './fleetTaskDesc.js';
|
|
167
179
|
// ── B2 批:通知族合并 / caps-wire 门族 / 模型面纯逻辑 / control 路由(2026-07-27)──────────────
|
|
@@ -60,7 +60,7 @@ export interface ToolEndResultArmLike {
|
|
|
60
60
|
truncated?: unknown;
|
|
61
61
|
/**
|
|
62
62
|
* 这次调用失败的**机器码**(开集;`eventToSdkMessage` 的 `case 'tool_end'` 臂原样上臂,宿主从
|
|
63
|
-
* 臂上直读 —— 与 {@link
|
|
63
|
+
* 臂上直读 —— 与 {@link gate}/{@link delivered} 同款「臂带、卡不带」姿势)。已知族:中断
|
|
64
64
|
* 合成收口两码(`interrupted_never_started` / `interrupted_outcome_unknown`,core 5.10.0)、
|
|
65
65
|
* 委派席位到限两码(`delegation.*`)与 `mcp.server_revoked`(两族 core ≥5.50.0 孪生拼法起才
|
|
66
66
|
* 真到达 —— 更旧引擎恒缺席,缺席≠没拒过)。词表与判定单源 `engineErrorCodes.ts`
|
|
@@ -68,61 +68,46 @@ export interface ToolEndResultArmLike {
|
|
|
68
68
|
*/
|
|
69
69
|
errorCode?: unknown;
|
|
70
70
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* 宿主据此把结算归属渲到工具卡/审计面。
|
|
71
|
+
* 这次调用**整条门记录**(0.60.0 / engine ≥7.64.0 / sdk 8.4.0)—— `eventToSdkMessage` 的
|
|
72
|
+
* `case 'tool_end'` 臂经包内单源窄读器 `gateOutcomeOf` 铸出后原样上臂,宿主据此把处置/结算归属
|
|
73
|
+
* 渲到工具卡与审计面。
|
|
75
74
|
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* 🔴
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
75
|
+
* 它取代了退役的四个正交词 `settledBy` / `resolution` / `autoDenied` / `approver`
|
|
76
|
+
* (**四键全删,无 alias**);那四个词的配对规则只活在注释里,消费端要靠**缺席**推语义。现在是
|
|
77
|
+
* 一条记录:`disposition`(放行 / 被哪一**层**拒)+ `settlement?`(这次消费掉的等待怎么结束的)
|
|
78
|
+
* + `origin?`(谁问的),后两位**同在同缺**。
|
|
79
|
+
*
|
|
80
|
+
* 🔴 **判定归包,别在宿主自拼**:「这次是不是审批窗自己走完的拒绝」「是不是引擎的限额回落窗
|
|
81
|
+
* 自动拒的」两问各有一个包面谓词(`isApprovalWindowExpiredGate` / `isDenialLimitAutoDeniedGate`)
|
|
82
|
+
* —— 旧形上这个判定是端各自拼的两键合取,而拼错的方式有好几种且错法静默。
|
|
83
|
+
* 🔴 **缺席不带语义,不许反推**:上游逐字列了四种在 wire 上不可分辨的成因(门没看见这次调用 /
|
|
84
|
+
* 延后重发 / reconcile 捡回的孤儿 / 记录过不了引擎的筛而 server 整条不投)⇒ 一律退回
|
|
85
|
+
* `isError` + 文案。
|
|
86
|
+
* ⚠️ **第五种成因有可读标记**(engine 7.64.0 真机黑盒 + server 认定为契约设计):审批窗走完
|
|
87
|
+
* 未决而走 **park** 路径时,本臂上没有 `gate`、只有 {@link errorCode} `"gate.parked"`。宿主判
|
|
88
|
+
* 「这次调用没在这里结算」必须**两形合读**(`isApprovalWindowExpiredGate` ∪
|
|
89
|
+
* `isGateParkedToolEnd`);两形语义不同 —— 前者「窗走完拒了」,后者「park 了还没人决」。
|
|
90
|
+
* 🔴 **UNTRUSTED-for-display**:`settlement.who.approver`(通道自报的身份串)与 `settlement.note`
|
|
91
|
+
* (拒批人自写)只渲染,绝不回喂模型/工具入参,更不许当鉴权判据 —— 转录不是认证。
|
|
92
|
+
* 🔴 本形是**防御性读形**(`unknown`):词表属主是引擎,消费方分支已知值 + 永远带 `default`。
|
|
85
93
|
*/
|
|
86
|
-
|
|
87
|
-
/** 见 {@link settledBy} —— 两键同批同源(#263 半场,0.30.8),合读、缺席不反推、转录不是认证。 */
|
|
88
|
-
approver?: unknown;
|
|
94
|
+
gate?: unknown;
|
|
89
95
|
/**
|
|
90
|
-
* 这次
|
|
91
|
-
* `human_refused` / `window_expired` / `no_approver` / `blanket_allow_refused` /
|
|
92
|
-
* `approver_unavailable` / `task_aborted` / `presentation_failed` / `approver_error` /
|
|
93
|
-
* `approver_contract`。
|
|
96
|
+
* 这次 **MCP** 失败**到没到那台服务器**(core ≥7.6.0 S6-B;`yes` / `no` / `unknown` + 开集读)。
|
|
94
97
|
*
|
|
95
|
-
* 🔴 与 {@link
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
* 🔴
|
|
99
|
-
*
|
|
100
|
-
* **绝不**写 `resolution !== 'approver_contract'` —— 那个不等式对**缺席**(旧引擎不发这一位)和
|
|
101
|
-
* 对**任何未来新码**都为真,于是「不知道是怎么拒的」会被折成「窗口自然结束」,正是本位存在要防的
|
|
102
|
-
* 那件事;两条正交轴在那种写法里被一个否定比较替代掉了。缺席与未知值一律进 `default` 分支。
|
|
103
|
-
* 🔴 **缺席不带语义,不许反推**:每一次真执行了的调用、每一次 policy/hook 直拒、以及
|
|
104
|
-
* durable/decide 腿的结算都不带这一位;缺席 ≠「不是拒绝」也 ≠「人拒的」。
|
|
105
|
-
* 🔴 本形同样是**防御性读形**(`unknown`):九词是引擎的闭集,消费方分支已知值 + 永远带 default。
|
|
98
|
+
* 🔴 与 {@link errorCode} **合读**才有意义:同一个 `connection_closed`,`no` 可以直接重试
|
|
99
|
+
* (什么都没发出去)、`unknown` 必须先去查(请求**可能**已经执行了)——带写副作用的工具在
|
|
100
|
+
* `unknown` 上必须先核实再重试。
|
|
101
|
+
* 🔴 **缺席不带语义**:每一次执行成功的调用、每一次非 MCP 失败都不带它。
|
|
102
|
+
* ⚠️ `no` 说的是**调用方的工具调用**没发出去,**不**保证「那台服务器一个字节都没收到」。
|
|
106
103
|
*/
|
|
107
|
-
|
|
104
|
+
delivered?: unknown;
|
|
108
105
|
/**
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
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
|
-
* 「那张卡的窗走完了,引擎替你拒了」。
|
|
106
|
+
* 耐久 park 正扣着**哪一个** tool call 的 id(core ≥5.55.0 #333)。引擎从已提交 checkpoint 的
|
|
107
|
+
* pendingAction 直读(**禁由工具自报**)。tool-less park(resource_limit / plan_review / task_done)
|
|
108
|
+
* 上**整键缺席**,契约逐字 "ABSENT, never guessed" —— 缺席不铸键、也不猜。
|
|
124
109
|
*/
|
|
125
|
-
|
|
110
|
+
gatedCallId?: unknown;
|
|
126
111
|
parentToolCallId?: unknown;
|
|
127
112
|
uuid?: unknown;
|
|
128
113
|
session_id?: unknown;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* operator 面读数(sdk `ReadFacePosture` 的防御读视图;结构逐字同源,见文件尾编译期对账钉)。
|
|
3
|
+
*/
|
|
4
|
+
export interface ReadFacePostureView {
|
|
5
|
+
/** 这台部署此刻的生效档。`null` = 没钉,引擎默认接管——与租户面同一份产物、同一句「没钉」。 */
|
|
6
|
+
face: 'open' | 'roots' | null;
|
|
7
|
+
/** 这一档是谁定的。server 闭四词(`env` / `center` / `posture` / `engine-default`),**按开集读**
|
|
8
|
+
* ——server 闭集之外的值原样透传,不窄读成枚举(那会在 server 加词当天把一个合法读数判没)。 */
|
|
9
|
+
source: string;
|
|
10
|
+
/** 给运维的指路句。UNTRUSTED-for-display:呈前消毒 + 封长,见 {@link readFacePostureDetail}。 */
|
|
11
|
+
note: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* `wiring.readFace` → 读数;**畸形一律 `undefined`**,绝不抛出。
|
|
15
|
+
*
|
|
16
|
+
* 🔴 整键缺席(老 worker,server <7.65.0)与在场但形坏,消费端拿到的都是同一个 `undefined` ——
|
|
17
|
+
* 两者对端说的是同一句话(「这一面这一次答不出来」)。「为什么答不出来」不是这一位该回答的,
|
|
18
|
+
* 是调用方自己知道的事(它有没有拿到 operator 响应;见 {@link readFacePostureDetail} 的
|
|
19
|
+
* `opts.reachable` 参数,那条信息只能由调用方——不是本读器——提供)。
|
|
20
|
+
* ⚠️ `face` 只认三态字面量(闭集,坏词一律判畸形);`source` 非空串即收(开集,理由见上);`note`
|
|
21
|
+
* 允许空串(它是自由文本,空文本本身也是一句读数,不是「读不出」)。
|
|
22
|
+
*/
|
|
23
|
+
export declare function projectReadFacePosture(wiring: unknown): ReadFacePostureView | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* 三态措辞的**唯一铸点**(三端共用一句话;别在各端的行装配里另写一遍——与
|
|
26
|
+
* `writeProtectionDoctorDetail` / `sqlEngineDoctorDetail` 同一条纪律)。
|
|
27
|
+
*
|
|
28
|
+
* 🔴 三句刻意逐字互异(黑盒锚):
|
|
29
|
+
* ① `view` 在场 —— `read face <open|roots|not pinned> (source: <source> — <note>)`;
|
|
30
|
+
* ② `opts.reachable === false` —— 「未观测」:这次进程没读到 operator 响应,与「这台部署没有
|
|
31
|
+
* READ 档」是两件事,消费端不许把它读成后者;
|
|
32
|
+
* ③ `reachable:true` 但 `view` 仍缺席 —— 「不报」:响应读到了,只是这一位读不出来。
|
|
33
|
+
* 🔴 **不武断咎为版本**:`projectReadFacePosture` 把「整键缺席(老引擎,<7.65.0)」与
|
|
34
|
+
* 「在场但形坏(≥7.65.0 的引擎送出一个本读器解不出来的响应)」折成同一个 `undefined`
|
|
35
|
+
* (见该函数顶注),句子因此**不能**替其中一种情形撒谎——「需要更新引擎」对第二种情形是一句
|
|
36
|
+
* 误导故障排查的假话。措辞同时点出两种成因,不擅自替调用方选一种。
|
|
37
|
+
*/
|
|
38
|
+
export declare function readFacePostureDetail(view: ReadFacePostureView | undefined, opts: {
|
|
39
|
+
reachable: boolean;
|
|
40
|
+
}): string;
|
|
41
|
+
/**
|
|
42
|
+
* 租户面 `capabilities.readFace` 与本面 `posture.face` 分歧时的一句话;**只答分歧,不答一致**
|
|
43
|
+
* ——一致不是新闻(两位的在场时点天然不同源,「相符」证不了什么也不该被渲成保证),本函数只在
|
|
44
|
+
* 两者**确实不同**时才开口。
|
|
45
|
+
*
|
|
46
|
+
* 🔴 两个入参**任一**答不出来 ⇒ `undefined`(诚实缺席,不是「没有分歧」——见不到两位就判不了):
|
|
47
|
+
* `posture === undefined`(operator 面这一次读不到,见 {@link projectReadFacePosture})或
|
|
48
|
+
* `capFace` 既不是三态字面量也不是任意字符串(租户面那一位整键缺席时调用方会传 `undefined`,
|
|
49
|
+
* 这里按同一条闸处理,不单独分支)。
|
|
50
|
+
* 🔴 `capFace` 按**开集**读,与租户面型面同宽:除 `open`/`roots`/`null` 外的任意字符串仍当一个
|
|
51
|
+
* **能判别的词**收(不是「读不懂」),因为 sdk 的租户面型面本就留了同一个 `(string & {})` 逃生口。
|
|
52
|
+
*/
|
|
53
|
+
export declare function readFaceDisagreement(capFace: unknown, posture: ReadFacePostureView | undefined): string | undefined;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { escapeDisplayControlChars } from './fleetTaskDesc.js';
|
|
2
|
+
/** `note` 上屏前的封长(UTF-16 单元,按**转义后**的字节数算——见 {@link capForDisplay})。它是
|
|
3
|
+
* 一句人话不是一个词,给得比 source/face 宽。 */
|
|
4
|
+
const READ_FACE_NOTE_MAX = 256;
|
|
5
|
+
/** `source` / 分歧句里词形的封长(与本包其余 detail 铸点同值同理由;同样按转义后字节数算)。 */
|
|
6
|
+
const READ_FACE_WORD_MAX = 40;
|
|
7
|
+
/**
|
|
8
|
+
* UNTRUSTED-for-display 呈前处理:**先转义、后按转义结果封长**——顺序反过来会让预算失守
|
|
9
|
+
* (`escapeDisplayControlChars` 把每个不可见字符改写成 6 字符的 `\uXXXX`,截长在前会把一段纯
|
|
10
|
+
* 控制字符的原文送成六倍长的显示串;文件头注有完整推导)。
|
|
11
|
+
*
|
|
12
|
+
* 封长点额外避开两种会留下断裂片段的位置:
|
|
13
|
+
* · 一枚 `escapeDisplayControlChars` 铸出的 `\uXXXX` token 中间(留半截 `\u00` 比整枚不渲更坏 ——
|
|
14
|
+
* 它看起来像一个完整答案的开头);
|
|
15
|
+
* · 一对合法代理对(如 emoji)中间——`DISPLAY_UNSAFE` 只转义**孤**代理项,合法对被放行原样透传,
|
|
16
|
+
* 在这里截断会人为地把它拆成一枚裸高位代理项留在末尾。
|
|
17
|
+
*/
|
|
18
|
+
function capForDisplay(raw, max) {
|
|
19
|
+
const escaped = escapeDisplayControlChars(raw);
|
|
20
|
+
if (escaped.length <= max)
|
|
21
|
+
return escaped;
|
|
22
|
+
let cut = max;
|
|
23
|
+
const tokenStart = escaped.lastIndexOf('\\', cut - 1);
|
|
24
|
+
if (tokenStart >= 0 && tokenStart + 6 > cut && /^\\u[0-9A-F]{4}$/.test(escaped.slice(tokenStart, tokenStart + 6))) {
|
|
25
|
+
cut = tokenStart;
|
|
26
|
+
}
|
|
27
|
+
if (cut > 0) {
|
|
28
|
+
const code = escaped.charCodeAt(cut - 1);
|
|
29
|
+
if (code >= 0xd800 && code <= 0xdbff)
|
|
30
|
+
cut -= 1;
|
|
31
|
+
}
|
|
32
|
+
return escaped.slice(0, cut);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* `wiring.readFace` → 读数;**畸形一律 `undefined`**,绝不抛出。
|
|
36
|
+
*
|
|
37
|
+
* 🔴 整键缺席(老 worker,server <7.65.0)与在场但形坏,消费端拿到的都是同一个 `undefined` ——
|
|
38
|
+
* 两者对端说的是同一句话(「这一面这一次答不出来」)。「为什么答不出来」不是这一位该回答的,
|
|
39
|
+
* 是调用方自己知道的事(它有没有拿到 operator 响应;见 {@link readFacePostureDetail} 的
|
|
40
|
+
* `opts.reachable` 参数,那条信息只能由调用方——不是本读器——提供)。
|
|
41
|
+
* ⚠️ `face` 只认三态字面量(闭集,坏词一律判畸形);`source` 非空串即收(开集,理由见上);`note`
|
|
42
|
+
* 允许空串(它是自由文本,空文本本身也是一句读数,不是「读不出」)。
|
|
43
|
+
*/
|
|
44
|
+
export function projectReadFacePosture(wiring) {
|
|
45
|
+
if (typeof wiring !== 'object' || wiring === null || Array.isArray(wiring))
|
|
46
|
+
return undefined;
|
|
47
|
+
if (!('readFace' in wiring))
|
|
48
|
+
return undefined;
|
|
49
|
+
const rf = wiring.readFace;
|
|
50
|
+
if (typeof rf !== 'object' || rf === null || Array.isArray(rf))
|
|
51
|
+
return undefined;
|
|
52
|
+
const r = rf;
|
|
53
|
+
if (r.face !== 'open' && r.face !== 'roots' && r.face !== null)
|
|
54
|
+
return undefined;
|
|
55
|
+
if (typeof r.source !== 'string' || r.source.length === 0)
|
|
56
|
+
return undefined;
|
|
57
|
+
if (typeof r.note !== 'string')
|
|
58
|
+
return undefined;
|
|
59
|
+
return { face: r.face, source: r.source, note: r.note };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 三态措辞的**唯一铸点**(三端共用一句话;别在各端的行装配里另写一遍——与
|
|
63
|
+
* `writeProtectionDoctorDetail` / `sqlEngineDoctorDetail` 同一条纪律)。
|
|
64
|
+
*
|
|
65
|
+
* 🔴 三句刻意逐字互异(黑盒锚):
|
|
66
|
+
* ① `view` 在场 —— `read face <open|roots|not pinned> (source: <source> — <note>)`;
|
|
67
|
+
* ② `opts.reachable === false` —— 「未观测」:这次进程没读到 operator 响应,与「这台部署没有
|
|
68
|
+
* READ 档」是两件事,消费端不许把它读成后者;
|
|
69
|
+
* ③ `reachable:true` 但 `view` 仍缺席 —— 「不报」:响应读到了,只是这一位读不出来。
|
|
70
|
+
* 🔴 **不武断咎为版本**:`projectReadFacePosture` 把「整键缺席(老引擎,<7.65.0)」与
|
|
71
|
+
* 「在场但形坏(≥7.65.0 的引擎送出一个本读器解不出来的响应)」折成同一个 `undefined`
|
|
72
|
+
* (见该函数顶注),句子因此**不能**替其中一种情形撒谎——「需要更新引擎」对第二种情形是一句
|
|
73
|
+
* 误导故障排查的假话。措辞同时点出两种成因,不擅自替调用方选一种。
|
|
74
|
+
*/
|
|
75
|
+
export function readFacePostureDetail(view, opts) {
|
|
76
|
+
if (view !== undefined) {
|
|
77
|
+
const faceWord = view.face === null ? 'not pinned' : view.face;
|
|
78
|
+
const source = capForDisplay(view.source, READ_FACE_WORD_MAX);
|
|
79
|
+
const note = capForDisplay(view.note, READ_FACE_NOTE_MAX);
|
|
80
|
+
return note.length > 0
|
|
81
|
+
? `read face ${faceWord} (source: ${source} — ${note})`
|
|
82
|
+
: `read face ${faceWord} (source: ${source})`;
|
|
83
|
+
}
|
|
84
|
+
if (!opts.reachable) {
|
|
85
|
+
return "read face not observed (this end could not read the engine's diagnostics)";
|
|
86
|
+
}
|
|
87
|
+
return 'read face not reported by this engine (an engine below 7.65.0, or a response this end could not parse)';
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* 租户面 `capabilities.readFace` 与本面 `posture.face` 分歧时的一句话;**只答分歧,不答一致**
|
|
91
|
+
* ——一致不是新闻(两位的在场时点天然不同源,「相符」证不了什么也不该被渲成保证),本函数只在
|
|
92
|
+
* 两者**确实不同**时才开口。
|
|
93
|
+
*
|
|
94
|
+
* 🔴 两个入参**任一**答不出来 ⇒ `undefined`(诚实缺席,不是「没有分歧」——见不到两位就判不了):
|
|
95
|
+
* `posture === undefined`(operator 面这一次读不到,见 {@link projectReadFacePosture})或
|
|
96
|
+
* `capFace` 既不是三态字面量也不是任意字符串(租户面那一位整键缺席时调用方会传 `undefined`,
|
|
97
|
+
* 这里按同一条闸处理,不单独分支)。
|
|
98
|
+
* 🔴 `capFace` 按**开集**读,与租户面型面同宽:除 `open`/`roots`/`null` 外的任意字符串仍当一个
|
|
99
|
+
* **能判别的词**收(不是「读不懂」),因为 sdk 的租户面型面本就留了同一个 `(string & {})` 逃生口。
|
|
100
|
+
*/
|
|
101
|
+
export function readFaceDisagreement(capFace, posture) {
|
|
102
|
+
if (posture === undefined)
|
|
103
|
+
return undefined;
|
|
104
|
+
if (capFace !== null && typeof capFace !== 'string')
|
|
105
|
+
return undefined;
|
|
106
|
+
if (capFace === posture.face)
|
|
107
|
+
return undefined;
|
|
108
|
+
const capWord = capFace === null ? 'not pinned' : capForDisplay(capFace, READ_FACE_WORD_MAX);
|
|
109
|
+
const wireWord = posture.face === null ? 'not pinned' : posture.face;
|
|
110
|
+
return `capabilities says ${capWord}, wiring says ${wireWord}`;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* **编译期对账钉**(不出公面):sdk 的 `ReadFacePosture` 必须能赋给本视图 —— 这是本包 sdk 地板抬到
|
|
114
|
+
* 8.5.0 的直接理由:<8.5.0 上这个名字根本不存在,`tsc` 报「没有导出成员」。
|
|
115
|
+
*
|
|
116
|
+
* 🔴 反向**刻意不钉**(本视图不必能赋给 sdk 形):`source` 在本视图上放宽成任意 `string`(开集读,
|
|
117
|
+
* 见字段注),那是**故意比铸点宽**——窄读域只许等于或宽于铸点域,钉反向会把这条纪律反过来判成错。
|
|
118
|
+
*/
|
|
119
|
+
const _readFacePostureShapePin = (w) => w;
|
|
120
|
+
void _readFacePostureShapePin;
|
|
@@ -50,9 +50,17 @@ export declare function resumeRefusalFromError(err: unknown): ResumeRefusalDetai
|
|
|
50
50
|
*
|
|
51
51
|
* 🔴 第三句由 {@link ResumeRefusalDetail.waitable} 选,**不由 message 文本选**:按上游散文分支
|
|
52
52
|
* 正是「上游改一个词、壳的判定静默空转」的形(wire 锚契约门要挡的就是它)。
|
|
53
|
-
* 🔴 `preflight_rejected` 且 `waitable === false`
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
53
|
+
* 🔴 `preflight_rejected` 且 `waitable === false` 时说的是**两条臂都为真**的那句;`waitable === true`
|
|
54
|
+
* 时 server 真给了窗 ——那是 `retry_later` 臂的**充分证据**(core 的 `terminal` 臂抛的
|
|
55
|
+
* `CheckpointError` 一个 detail 都不带,窗结构上到不了客户端)⇒ 可以放心说「障碍清掉之后这个
|
|
56
|
+
* token 还能再赎」。
|
|
57
|
+
*
|
|
58
|
+
* 🔴 **三句每一句都必须自足**(0.60.0 修;上游候包件 ①):无窗那一句此前写的是「The engine line
|
|
59
|
+
* above says whether this token can still be redeemed」—— 它把答案**外包给了屏上另一行**,而
|
|
60
|
+
* 那一行只有 **turn 级**报错路径才有。同一份文案在**决断路径**(审批卡 decide 的 409)上也照发,
|
|
61
|
+
* 那条路上引擎原文按凭证纪律**根本不上屏** ⇒ 用户读到的是一句指向不存在的东西的指路话。
|
|
62
|
+
* ⇒ 措辞改成只陈述**本读面自己知道的事实**(「引擎没给窗,所以这次拒绝没说它还能不能再赎」),
|
|
63
|
+
* 在两条路上都为真。**不是**把那句话删掉换成沉默:「不知道」本身就是用户要的答案之一
|
|
64
|
+
* ([honest-absence-not-fabricated-zero])。
|
|
57
65
|
*/
|
|
58
66
|
export declare function resumeRefusalContent(detail: ResumeRefusalDetail): string;
|
|
@@ -93,10 +93,18 @@ export function resumeRefusalFromError(err) {
|
|
|
93
93
|
*
|
|
94
94
|
* 🔴 第三句由 {@link ResumeRefusalDetail.waitable} 选,**不由 message 文本选**:按上游散文分支
|
|
95
95
|
* 正是「上游改一个词、壳的判定静默空转」的形(wire 锚契约门要挡的就是它)。
|
|
96
|
-
* 🔴 `preflight_rejected` 且 `waitable === false`
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
96
|
+
* 🔴 `preflight_rejected` 且 `waitable === false` 时说的是**两条臂都为真**的那句;`waitable === true`
|
|
97
|
+
* 时 server 真给了窗 ——那是 `retry_later` 臂的**充分证据**(core 的 `terminal` 臂抛的
|
|
98
|
+
* `CheckpointError` 一个 detail 都不带,窗结构上到不了客户端)⇒ 可以放心说「障碍清掉之后这个
|
|
99
|
+
* token 还能再赎」。
|
|
100
|
+
*
|
|
101
|
+
* 🔴 **三句每一句都必须自足**(0.60.0 修;上游候包件 ①):无窗那一句此前写的是「The engine line
|
|
102
|
+
* above says whether this token can still be redeemed」—— 它把答案**外包给了屏上另一行**,而
|
|
103
|
+
* 那一行只有 **turn 级**报错路径才有。同一份文案在**决断路径**(审批卡 decide 的 409)上也照发,
|
|
104
|
+
* 那条路上引擎原文按凭证纪律**根本不上屏** ⇒ 用户读到的是一句指向不存在的东西的指路话。
|
|
105
|
+
* ⇒ 措辞改成只陈述**本读面自己知道的事实**(「引擎没给窗,所以这次拒绝没说它还能不能再赎」),
|
|
106
|
+
* 在两条路上都为真。**不是**把那句话删掉换成沉默:「不知道」本身就是用户要的答案之一
|
|
107
|
+
* ([honest-absence-not-fabricated-zero])。
|
|
100
108
|
*/
|
|
101
109
|
export function resumeRefusalContent(detail) {
|
|
102
110
|
const parts = [];
|
|
@@ -110,6 +118,8 @@ export function resumeRefusalContent(detail) {
|
|
|
110
118
|
parts.push('The refusal happened before the commit — your decision was not consumed');
|
|
111
119
|
parts.push(detail.waitable
|
|
112
120
|
? 'The engine supplied a wait, so this token stays redeemable once the obstacle clears'
|
|
113
|
-
:
|
|
121
|
+
: // 🔴 自足措辞(见函数顶注):只说本读面知道的事 —— 引擎没给窗,所以这次拒绝**没有**表态
|
|
122
|
+
// 这个 token 还能不能再赎。绝不指向「上面那一行」(决断路径上没有那一行)。
|
|
123
|
+
'The engine supplied no wait, so this refusal does not say whether the token stays redeemable');
|
|
114
124
|
return parts.join(' · ');
|
|
115
125
|
}
|