@sema-agent/client-core 0.59.2 → 0.60.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 +192 -0
- package/README.md +3 -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 +3 -0
- package/dist/index.js +7 -0
- package/dist/printToolResultFrame.d.ts +34 -49
- 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 +325 -11
- package/package.json +3 -3
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// 「这次调用没在这里结算」的**第二形**判据(见 {@link isGateParkedToolEnd});与 `frameRouter` 的
|
|
2
|
+
// 连坐/中断判据读同一个常量(单源见该常量顶注)。
|
|
3
|
+
import { GATE_PARKED_ERROR_CODE } from './engineErrorCodes.js';
|
|
4
|
+
/** 非空串窄化(空串按缺席归一)。 */
|
|
5
|
+
function str(v) {
|
|
6
|
+
return typeof v === 'string' && v.length > 0 ? v : undefined;
|
|
7
|
+
}
|
|
8
|
+
/** `who` 的结构窄读;`party` 读不出 ⇒ 整只结算不成形(见 {@link readSettlement})。 */
|
|
9
|
+
function readWho(v) {
|
|
10
|
+
if (typeof v !== 'object' || v === null || Array.isArray(v))
|
|
11
|
+
return undefined;
|
|
12
|
+
const w = v;
|
|
13
|
+
const party = str(w.party);
|
|
14
|
+
if (party === undefined)
|
|
15
|
+
return undefined;
|
|
16
|
+
const approver = str(w.approver);
|
|
17
|
+
const window = str(w.window);
|
|
18
|
+
return {
|
|
19
|
+
party,
|
|
20
|
+
...(approver !== undefined ? { approver } : {}),
|
|
21
|
+
...(window !== undefined ? { window } : {}),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* `settlement` 的结构窄读。`kind` 或 `who` 读不出 ⇒ **整只缺席**(不铸半只):一只没有
|
|
26
|
+
* 「怎么结束的」或「谁结束的」的结算,消费端拿它渲不出任何一句可信的话,而半只会被读成
|
|
27
|
+
* 「结算过了,只是没说是谁」——那是一句本读面证明不了的断言。
|
|
28
|
+
*/
|
|
29
|
+
function readSettlement(v) {
|
|
30
|
+
if (typeof v !== 'object' || v === null || Array.isArray(v))
|
|
31
|
+
return undefined;
|
|
32
|
+
const s = v;
|
|
33
|
+
const kind = str(s.kind);
|
|
34
|
+
const who = readWho(s.who);
|
|
35
|
+
if (kind === undefined || who === undefined)
|
|
36
|
+
return undefined;
|
|
37
|
+
const when = typeof s.when === 'number' && Number.isFinite(s.when) ? s.when : undefined;
|
|
38
|
+
const note = str(s.note);
|
|
39
|
+
return {
|
|
40
|
+
kind,
|
|
41
|
+
who,
|
|
42
|
+
...(when !== undefined ? { when } : {}),
|
|
43
|
+
...(note !== undefined ? { note } : {}),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* `tool_end` 帧(或本包投影出的 `tool_end_result` 内部臂、耐久行的 resolved outcome)→ 门记录。
|
|
48
|
+
*
|
|
49
|
+
* 入参是**帧**而不是 `frame.gate`,刻意如此:端不必知道这条记录挂在哪个键上(那正是下一次形变
|
|
50
|
+
* 会动的东西),也不必自己判「这一帧带不带门」。
|
|
51
|
+
*
|
|
52
|
+
* 🔴 `disposition` 是**唯一的承重座**:它读不出来 ⇒ 整只 `undefined`。一条只有 `settlement` 而
|
|
53
|
+
* 说不出「放行还是拒了」的记录,消费端能从它得到的只有误导。
|
|
54
|
+
* 🔴 **不校 I1–I4 那四条不变量**(`settlement` ⇔ `origin` 同在、`ask_resolution` ⇒ 有拒绝类结算……):
|
|
55
|
+
* 筛子的属主是引擎,server 对过不了筛的记录**整条不投**。包再校一遍,只会在上游合法演进的当天
|
|
56
|
+
* 把一条真记录判没,而本层给不出比「原样交给端」更好的降级动作(`wiring_manifest.autoMode`
|
|
57
|
+
* 那一段的反向先例是**同一条帧上的段内自洽**,不是跨系统的不变量,两者刻意不同处置)。
|
|
58
|
+
*/
|
|
59
|
+
export function gateOutcomeOf(frame) {
|
|
60
|
+
if (typeof frame !== 'object' || frame === null || Array.isArray(frame))
|
|
61
|
+
return undefined;
|
|
62
|
+
const g = frame.gate;
|
|
63
|
+
if (typeof g !== 'object' || g === null || Array.isArray(g))
|
|
64
|
+
return undefined;
|
|
65
|
+
const raw = g;
|
|
66
|
+
const d = raw.disposition;
|
|
67
|
+
if (typeof d !== 'object' || d === null || Array.isArray(d))
|
|
68
|
+
return undefined;
|
|
69
|
+
const dr = d;
|
|
70
|
+
const dk = str(dr.kind);
|
|
71
|
+
let disposition;
|
|
72
|
+
if (dk === 'allowed') {
|
|
73
|
+
disposition = { kind: 'allowed' };
|
|
74
|
+
}
|
|
75
|
+
else if (dk === 'denied') {
|
|
76
|
+
const deniedBy = str(dr.deniedBy);
|
|
77
|
+
// 「拒了但说不出谁拒的」在上游**写不出来**(两臂在类型上就分了家)⇒ 到得了这里的这一形是坏记录。
|
|
78
|
+
if (deniedBy === undefined)
|
|
79
|
+
return undefined;
|
|
80
|
+
disposition = { kind: 'denied', deniedBy };
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
// 处置词本身不认识 ⇒ 整只不成形:`allowed` / `denied` 是这条记录的**判别位**,猜不得。
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
const settlement = readSettlement(raw.settlement);
|
|
87
|
+
const origin = str(raw.origin);
|
|
88
|
+
return {
|
|
89
|
+
disposition,
|
|
90
|
+
...(settlement !== undefined ? { settlement } : {}),
|
|
91
|
+
...(origin !== undefined ? { origin } : {}),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 这次调用是**被门 park 了**吗 —— 「没在这里结算」的**第二形**(engine 7.64.0 真机黑盒直证)。
|
|
96
|
+
*
|
|
97
|
+
* 🔴 **为什么非有它不可**:审批窗走完(默认 300s)未决而走 park 路径时,`tool_end` 帧上
|
|
98
|
+
* **没有 `gate` 字段** —— 只有 `errorCode: "gate.parked"`。⇒ 单锚 {@link gateOutcomeOf} 的
|
|
99
|
+
* 消费点在这一形上什么都读不到,而这一形**恰恰是端最想说一句话的那一格**(「已转后台候批」)。
|
|
100
|
+
* 🔴 **两形合起来才是「这次调用没在这里结算」的完整判据**,端要判这件事写:
|
|
101
|
+
* `isApprovalWindowExpiredGate(gateOutcomeOf(f)) || isGateParkedToolEnd(f)`。
|
|
102
|
+
* 两形语义**不同**、不许互相顶替:前者是「窗走完了,拒了」,后者是「park 了,还没人决」。
|
|
103
|
+
* 🔴 **入参是整只帧**(与 {@link gateOutcomeOf} 同姿势):端不必知道这一位挂在哪个键上。
|
|
104
|
+
* 🔴 缺席不带语义:大多数 `tool_end` 都不带这个码,缺席 ≠「已经结算过了」。
|
|
105
|
+
*/
|
|
106
|
+
export function isGateParkedToolEnd(frame) {
|
|
107
|
+
if (typeof frame !== 'object' || frame === null || Array.isArray(frame))
|
|
108
|
+
return false;
|
|
109
|
+
return frame.errorCode === GATE_PARKED_ERROR_CODE;
|
|
110
|
+
}
|
|
111
|
+
/** 这次门是不是**拒**(缺席 ⇒ `false` —— 缺席不带语义,见模块顶注)。 */
|
|
112
|
+
export function isDeniedGate(g) {
|
|
113
|
+
return g?.disposition.kind === 'denied';
|
|
114
|
+
}
|
|
115
|
+
/** 拒这次的是**哪一层**(`policy` / `hook` / `org` / …;开集)。非拒 / 缺席 ⇒ `undefined`。 */
|
|
116
|
+
export function gateDeniedBy(g) {
|
|
117
|
+
return g?.disposition.kind === 'denied' ? g.disposition.deniedBy : undefined;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* 这次结算是**谁**的(审批通道自报的身份串)。UNTRUSTED-for-display。
|
|
121
|
+
* 🔴 缺席 = 通道没报名字 —— 永不意味「没人批」,也永不意味「有人批」。
|
|
122
|
+
*/
|
|
123
|
+
export function gateApprover(g) {
|
|
124
|
+
return g?.settlement?.who.approver;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* 这次拒是**审批窗自己走完**的吗 —— 端此前自拼的那个两键合取(`settledBy === 'timeout' &&
|
|
128
|
+
* resolution === 'window_expired'`)的**继任单源**。
|
|
129
|
+
*
|
|
130
|
+
* 判据 = `settlement.kind === 'approval_window_expired'`(引擎自己的审批工厂窗,或同步宿主
|
|
131
|
+
* approver 报「我的窗到了」)。
|
|
132
|
+
* 🔴 **刻意不含另外两条窗词**:`denial_limit_window_expired` 是**分类器限额回落**的自动拒窗
|
|
133
|
+
* (端要渲的是另一句话,见 {@link isDenialLimitAutoDeniedGate}),`park_sla_expired` 是**耐久 park**
|
|
134
|
+
* 的 SLA 到期(结束它的是宿主的扫,不是这次调用在等的那只审批窗)。旧形上这三件事共用一个
|
|
135
|
+
* `settledBy:"timeout"`,分不出来正是要靠 `resolution` 补第二个键的原因;形变把它们分成了三个词,
|
|
136
|
+
* 合并读回去等于把刚修好的判别力扔掉。
|
|
137
|
+
* 🔴 **正面匹配,绝不写不等式**:不等式对缺席与任何未来新词都为真。
|
|
138
|
+
*/
|
|
139
|
+
export function isApprovalWindowExpiredGate(g) {
|
|
140
|
+
return g?.settlement?.kind === 'approval_window_expired';
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* 这次拒是**引擎的限额回落窗自己拒的**吗(不是人拒的)—— 端此前读的 `tool_end.autoDenied === true`
|
|
144
|
+
* 的**继任单源**。
|
|
145
|
+
*
|
|
146
|
+
* 判据 = `settlement.kind === 'denial_limit_window_expired'`(auto 模式分类器的拒绝上限回落窗:
|
|
147
|
+
* 那张必须真人批的卡在 `autoDenyAfterMs` 走完之后由**引擎**判 deny)。
|
|
148
|
+
* 🔴 它答的正是 {@link isApprovalWindowExpiredGate} 答不了的那一问:「这一次到期是**自动拒**收的场,
|
|
149
|
+
* 还是 park 了等人」。端要渲的两句话(「自动拒(限额回落)」vs「已转后台候批」)卡在这一格上。
|
|
150
|
+
* 🔴 **不据它自铸第二只定时器/第二张资格表**:窗的执行全在引擎,本位是**事后**的判别位。
|
|
151
|
+
*/
|
|
152
|
+
export function isDenialLimitAutoDeniedGate(g) {
|
|
153
|
+
return g?.settlement?.kind === 'denial_limit_window_expired';
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* 这次结算是**耐久 park 的 SLA 到期**被宿主的扫判成拒的吗。
|
|
157
|
+
* 🔴 与上面两条并列而不合并:它说的是「那一行在队列里坐过了 deadline」,与「这次调用在等的审批窗
|
|
158
|
+
* 走完了」是两件不同的事,出路也不同(前者要重新提这一步,后者可以再决一次)。
|
|
159
|
+
*/
|
|
160
|
+
export function isParkSlaExpiredGate(g) {
|
|
161
|
+
return g?.settlement?.kind === 'park_sla_expired';
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* 这次门记录是**人**做的决定吗(`human_allowed` / `human_refused`)。
|
|
165
|
+
* 🔴 「人批了之后被复查否决」(上游不变量 I3)在这里读作:本谓词为 `true` **且** {@link isDeniedGate}
|
|
166
|
+
* 也为 `true` —— 那时 `deniedBy` 指的是复查那一层,approver 留在结算上,**不是** approver 的拒。
|
|
167
|
+
*/
|
|
168
|
+
export function isHumanSettledGate(g) {
|
|
169
|
+
const k = g?.settlement?.kind;
|
|
170
|
+
return k === 'human_allowed' || k === 'human_refused';
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* **编译期对账钉**(不出公面):sdk 的 `GateOutcome` 必须能赋给本视图 —— 上游给记录加位/改形时,
|
|
174
|
+
* 这一行是本包第一个红的地方。
|
|
175
|
+
*
|
|
176
|
+
* 🔴 反向**刻意不钉**(本视图不必能赋给 sdk 形):本视图按开集放宽了三处词表
|
|
177
|
+
* (`deniedBy` / `settlement.kind` / `origin`),那是**故意比铸点宽**(窄读域只许等于或宽于铸点域,
|
|
178
|
+
* B-025 的定谳);钉反向会把这条纪律反过来判成错。
|
|
179
|
+
*/
|
|
180
|
+
const _gateOutcomeShapePin = (w) => w;
|
|
181
|
+
void _gateOutcomeShapePin;
|
|
@@ -17,6 +17,8 @@ function debugLog(msg) {
|
|
|
17
17
|
}
|
|
18
18
|
/** 引擎终态词表全集(core 1.298 TaskStatus;server runs.js setTerminal(taskId, safe.status, …)
|
|
19
19
|
* 把引擎 status【原样】写进 run row ⇒ row.status 可以是全部 6 词,不只 completed/failed)。
|
|
20
|
+
* 🔴 **run 行自己的 `status` 列 engine 7.64.0 没动**(BREAKING 帖逐字:变的是引擎结果 blob,
|
|
21
|
+
* 行的列照旧说五词,只是数据源改成从因由投影一次)⇒ 本表原样有效,不随 `TaskResult` 形变。
|
|
20
22
|
* [909]B3 复核发现的真缺口:此前只认 completed/failed,timeout/blocked 终局的 run row 永不
|
|
21
23
|
* 合成终帧 → 续收环重试到预算耗尽 → 假判死(终局明明就在 row 里)。 */
|
|
22
24
|
const RUN_ROW_TERMINAL_STATUSES = new Set([
|
|
@@ -37,8 +39,13 @@ export function terminalEventFromRunRow(row) {
|
|
|
37
39
|
return null;
|
|
38
40
|
if (RUN_ROW_TERMINAL_STATUSES.has(row.status)) {
|
|
39
41
|
if (row.result && typeof row.result === 'object') {
|
|
40
|
-
// sync 腿 setTerminal 存的就是终帧的 TaskResult
|
|
41
|
-
//
|
|
42
|
+
// sync 腿 setTerminal 存的就是终帧的 TaskResult —— 与 tasks/stream 终帧 done{result} 字节同构,
|
|
43
|
+
// terminalToSdkResult 文案线原样生效。
|
|
44
|
+
// 🔴 **这是一条读盘面**(0.60.0 起要点名):`GET /v1/runs/:id` 的 `result` 是存储 blob 的**逐字
|
|
45
|
+
// 透传**,server **不补** `terminal` ⇒ 一台跨 engine 7.64.0 升级过的部署上,升级**之前**落盘
|
|
46
|
+
// 的那些行读出来仍是**扁平**形(退役的八个平面键)。两代字节由 `runTerminal.readRunTerminal`
|
|
47
|
+
// 的判别位 `"terminal" in result` 各归各位,所以本函数**照旧原样透传、一个键都不补**:补一个
|
|
48
|
+
// `terminal` 出来就是替停产的写者重新裁定一次形状(server 自己也刻意不做)。
|
|
42
49
|
return { type: 'done', result: row.result };
|
|
43
50
|
}
|
|
44
51
|
if (row.status === 'failed') {
|
|
@@ -81,6 +81,8 @@ export interface AskParkRowIdentity {
|
|
|
81
81
|
export declare function askParkRowIdentity(taskId: string, row: PendingCheckpoint): AskParkRowIdentity;
|
|
82
82
|
/**
|
|
83
83
|
* ③ 这一行该走哪条臂。判据锚在**决定量**(这行到底有没有问句)上,不锚门种的名字 —— 理由见文件头。
|
|
84
|
+
* · **toolName 明说是 ask 工具 ⇒ 问答臂,门种一律不再看**(0.60.0 补的**最先**一条,理由见函数体内注:
|
|
85
|
+
* 工具臂对这样一行会弹一张没有问句的卡、并送出不带 answer 的 approve —— 一次静默的错动作);
|
|
84
86
|
* · kind 缺席(pre-3.0.0 旧行)⇒ 维持 toolName 判据(旧行没有别的信号);
|
|
85
87
|
* · kind 是 ask 门族里 `human` 之外的词(tool_approval / irreversible_ask / policy_ask)⇒ 工具臂;
|
|
86
88
|
* · kind = `human` ⇒ 三选一命中即问答臂:toolName 明说是 ask 工具 / 行真带问句 payload /
|
|
@@ -66,6 +66,8 @@ export function askParkRowIdentity(taskId, row) {
|
|
|
66
66
|
}
|
|
67
67
|
/**
|
|
68
68
|
* ③ 这一行该走哪条臂。判据锚在**决定量**(这行到底有没有问句)上,不锚门种的名字 —— 理由见文件头。
|
|
69
|
+
* · **toolName 明说是 ask 工具 ⇒ 问答臂,门种一律不再看**(0.60.0 补的**最先**一条,理由见函数体内注:
|
|
70
|
+
* 工具臂对这样一行会弹一张没有问句的卡、并送出不带 answer 的 approve —— 一次静默的错动作);
|
|
69
71
|
* · kind 缺席(pre-3.0.0 旧行)⇒ 维持 toolName 判据(旧行没有别的信号);
|
|
70
72
|
* · kind 是 ask 门族里 `human` 之外的词(tool_approval / irreversible_ask / policy_ask)⇒ 工具臂;
|
|
71
73
|
* · kind = `human` ⇒ 三选一命中即问答臂:toolName 明说是 ask 工具 / 行真带问句 payload /
|
|
@@ -76,12 +78,22 @@ export function askParkRowIdentity(taskId, row) {
|
|
|
76
78
|
export function askParkRowArm(row) {
|
|
77
79
|
const rowGateKind = askParkRowGateKind(row);
|
|
78
80
|
const toolNameIsAsk = isAskTool(row.toolName ?? undefined);
|
|
81
|
+
// 🔴 **ask 工具身份优先于门种**(0.60.0,异源对抗复审 [medium] 采纳,真病):此前 `human` /
|
|
82
|
+
// `policy_ask` 之外的门种在读 toolName 之前就早退成工具臂,于是一行
|
|
83
|
+
// `gateKind:"irreversible_ask"` + `toolName:"AskUserQuestion"` 会被送进工具臂 —— 那条臂铸的卡
|
|
84
|
+
// **没有问句**、decide 送的是**不带 answer 的 approve**:一次静默的错动作(用户从没被问过,
|
|
85
|
+
// 引擎却收到了「批准」)。而本函数自己的判据就写着「问答臂要的东西是**问句**」——
|
|
86
|
+
// 一行 toolName 明说是 ask 工具时,那件事已经确定,门种再说什么都改不了它。
|
|
87
|
+
// ⚠️ 这也是与 `frameRouter` 那条 park 分流**同一个次序**(那里也是 ask 恒先判);两处次序一旦
|
|
88
|
+
// 不同,同一行在「谁来接」和「怎么决」两层上会得到相反的答案。
|
|
89
|
+
if (toolNameIsAsk)
|
|
90
|
+
return 'question';
|
|
79
91
|
if (rowGateKind === null)
|
|
80
|
-
return
|
|
92
|
+
return 'tool-gate';
|
|
81
93
|
if (!GENERIC_DECISION_GATE_KINDS.includes(rowGateKind))
|
|
82
94
|
return 'tool-gate';
|
|
83
95
|
const rowHasToolName = typeof row.toolName === 'string' && row.toolName.trim().length > 0;
|
|
84
|
-
return
|
|
96
|
+
return askQuestionsFromPending(row) !== null || !rowHasToolName ? 'question' : 'tool-gate';
|
|
85
97
|
}
|
|
86
98
|
/**
|
|
87
99
|
* 「泛化的**要人来决定**」那一族 kind —— 它们**不自带**「这是工具 / 这是问句」的答案,所以必须按
|
|
@@ -96,6 +108,9 @@ export function askParkRowArm(row) {
|
|
|
96
108
|
* 按名字硬判工具臂 = `human` 那一病换个 kind 复发(二次评审 R3 [high] 命中)。
|
|
97
109
|
* · `irreversible_ask` 的 core 形是 `{kind, reason, toolName}`(toolName **必填**)、`tool_approval`
|
|
98
110
|
* 按定义就是工具审批 ⇒ 两者不进这张表,直接工具臂。
|
|
111
|
+
* ⚠️ **本表不再是路由的第一道判据**(0.60.0):`askParkRowArm` 现在**先**看 toolName 是不是 ask 工具。
|
|
112
|
+
* 本表回答的仍是「这个门种自不自带『工具/问句』的答案」,只是它在一行**明说自己是 ask 工具**时
|
|
113
|
+
* 已经不需要被问到了。
|
|
99
114
|
*/
|
|
100
115
|
const GENERIC_DECISION_GATE_KINDS = ['human', 'policy_ask'];
|
|
101
116
|
/**
|
|
@@ -178,8 +178,8 @@ export interface HeldFlushInterruptEvidence {
|
|
|
178
178
|
* 两臂 stamp CC REJECT 文案),那两臂排在 hold-poison **之前**、帧根本不进扣留表,本包装够不着。
|
|
179
179
|
* fail-soft 的其余原因(no_pending / 传输失败 / hop 用尽)三腿全不命中 ⇒ 诚实红照旧。
|
|
180
180
|
*
|
|
181
|
-
* 🔴 **只改 `output` 一个键**:`isError` / `errorCode` / `toolCallId` / `toolName` / `
|
|
182
|
-
* `
|
|
181
|
+
* 🔴 **只改 `output` 一个键**:`isError` / `errorCode` / `toolCallId` / `toolName` / `gate` /
|
|
182
|
+
* `delivered` / `gatedCallId` / `structured` 以及 `flushHeld` 自己盖的
|
|
183
183
|
* `_sema_collateral_abort` 机读位全部原样过境(展开赋值只覆盖 `output`)—— 端侧的连坐折叠锚的是
|
|
184
184
|
* 机读位不是文案,所以折叠语义不受影响,只是被折那一行的正文换成了 CC 中断形。
|
|
185
185
|
*/
|
package/dist/hitl/frameRouter.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { hostLog } from '../host.js';
|
|
2
2
|
import { isToolApprovalGate, toolNameIsFsWrite, toolNameIsShellExec, isToolApprovalFrame, isFromSubagent, surfaceToolApprovalFrameAndRespond, } from './toolApprovalWire.js';
|
|
3
3
|
import { classifierDenyFromToolEnd } from '../classifierVerdictWire.js';
|
|
4
|
+
import { GATE_PARKED_ERROR_CODE } from '../engineErrorCodes.js';
|
|
5
|
+
// 0.60.0(engine ≥7.64.0 / sdk 8.4.0):终局/park 读数的单一读器(两代字节 → 一个带标因由)。
|
|
6
|
+
import { readRunTerminal, runTerminalCode, runTerminalGateKind, runTerminalGateToolName } from '../runTerminal.js';
|
|
4
7
|
import { surfaceForCurrentSession } from './hitlHostSurface.js';
|
|
5
8
|
/**
|
|
6
9
|
* CC `utils/messages.ts` 的 `REJECT_MESSAGE` **逐字**(B7 搬迁差分 2)。deny 后重放 tool_end 的
|
|
@@ -128,7 +131,10 @@ function isFsOrShellToolName(name) {
|
|
|
128
131
|
* 就等于把那几族 park 的连坐帧判成「认不出」。(core 5.55 的 `tool_end.gatedCallId` **顶层键**
|
|
129
132
|
* 到货后可用于「折叠归组」那一面,与本判据是两件事,跟车另批。)
|
|
130
133
|
*/
|
|
131
|
-
|
|
134
|
+
// 🔴 **单源**(0.60.0):字面量收编进 `engineErrorCodes.GATE_PARKED_ERROR_CODE` —— 本文件的连坐/中断
|
|
135
|
+
// 判据与 `gateOutcome.isGateParkedToolEnd`(端的「这次调用没在这里结算」第二形谓词)读同一个常量。
|
|
136
|
+
// 两处各写一份必漂,而漂的那一天两边会对同一帧给出相反的判决。
|
|
137
|
+
const ENGINE_GATE_PARKED_ERROR_CODE = GATE_PARKED_ERROR_CODE;
|
|
132
138
|
/**
|
|
133
139
|
* 「这一帧是**引擎因 gate 而中止**的毒化帧」——双判据 OR(件⑤,2026-08-22,[4970] G1 端到端 RED)。
|
|
134
140
|
*
|
|
@@ -211,26 +217,36 @@ function isDenyStampCandidate(ev) {
|
|
|
211
217
|
return true;
|
|
212
218
|
return toolEndOutputText(ev.output) === ENGINE_ABORT_TOOL_RESULT;
|
|
213
219
|
}
|
|
214
|
-
/**
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
220
|
+
/**
|
|
221
|
+
* done 帧的 park 门读数(0.60.0:判据从退役的 `status:"suspended"` + `checkpointGate` 两个平面键
|
|
222
|
+
* 换成带标因由 `terminal.paused` —— 读法单源 {@link readRunTerminal},本文件零平面键直读)。
|
|
223
|
+
*
|
|
224
|
+
* 🔴 两条 park 臂共用**一次**读取:此前两个谓词各自把 result 强转一遍再各判一次 `status`,
|
|
225
|
+
* 两处的窄读域一旦分叉就会出现「ask 臂认得、fs 臂认不得」的半场(paired-mechanisms 病形)。
|
|
226
|
+
* 返回 `null` = 这一帧不是 park(或连 taskId 都读不出 ⇒ 两条臂本来就都驱动不了)。
|
|
227
|
+
*
|
|
228
|
+
* ⚠️ **修前那条 `status === 'suspended'` 前置条件去哪了**(如实交代,不是漏掉):因由形下 park 只带
|
|
229
|
+
* 门,「哪个 park 读作 suspended、哪个读作 needs_review」由**引擎的 pause registry** 决定、不再
|
|
230
|
+
* 出现在这条 wire 上。而那条前置条件真正在挡的是**复核族 park**(plan_review / dry_run_review /
|
|
231
|
+
* needs_review 门)——它们本来就过不了下面两个门谓词(复核门不带 toolName,`isAskToolLoose`
|
|
232
|
+
* 与 `isToolApprovalGate` 两条都判 false)⇒ 语义由**更精确的**门种判据原样承接,不是放宽。
|
|
233
|
+
*/
|
|
234
|
+
function parkGateOf(result) {
|
|
235
|
+
const read = readRunTerminal(result);
|
|
236
|
+
if (read === null || read.kind !== 'paused')
|
|
237
|
+
return null;
|
|
238
|
+
const taskId = result?.taskId;
|
|
239
|
+
if (typeof taskId !== 'string' || taskId.length === 0)
|
|
240
|
+
return null;
|
|
241
|
+
const toolName = runTerminalGateToolName(read);
|
|
242
|
+
// Ask gate 恒先判(问答 overlay 原路)。
|
|
243
|
+
if (isAskToolLoose(toolName))
|
|
244
|
+
return { taskId, gate: 'ask', toolName };
|
|
245
|
+
// 工具审批 park([816] 放宽腿 + [2150] Bash 半场):门指 fs 写三件 / Bash / 一等
|
|
246
|
+
// kind==='tool_approval'(超集谓词 isToolApprovalGate,decide 腿对两族同构、卡形归壳分发)。
|
|
247
|
+
if (isToolApprovalGate({ kind: runTerminalGateKind(read), toolName }))
|
|
248
|
+
return { taskId, gate: 'fs', toolName };
|
|
249
|
+
return null;
|
|
234
250
|
}
|
|
235
251
|
/**
|
|
236
252
|
* park 帧上的**待批 call 身份**(server ≥7.41.0;[4914] 施工帖逐字:数据源 = checkpoint 本体的
|
|
@@ -288,8 +304,9 @@ function isUserInterruptTerminalOrSignal(ev, signal) {
|
|
|
288
304
|
return ev.errorCode === RUN_CANCELLED_ERROR_CODE;
|
|
289
305
|
}
|
|
290
306
|
if (ev.type === 'done') {
|
|
291
|
-
|
|
292
|
-
|
|
307
|
+
// 0.60.0:cancel settle 的码在因由形下**结构上属于失败臂**(`terminal.failed.code`);扁平形
|
|
308
|
+
// 回放的历史行仍把它平铺在 `errorCode` 上 —— 两代都由 `runTerminalCode` 归一。
|
|
309
|
+
return runTerminalCode(readRunTerminal(ev.result)) === RUN_CANCELLED_ERROR_CODE;
|
|
293
310
|
}
|
|
294
311
|
return false;
|
|
295
312
|
}
|
|
@@ -329,8 +346,8 @@ function isUserInterruptRewritable(ev, allowGateParked) {
|
|
|
329
346
|
* 两臂 stamp CC REJECT 文案),那两臂排在 hold-poison **之前**、帧根本不进扣留表,本包装够不着。
|
|
330
347
|
* fail-soft 的其余原因(no_pending / 传输失败 / hop 用尽)三腿全不命中 ⇒ 诚实红照旧。
|
|
331
348
|
*
|
|
332
|
-
* 🔴 **只改 `output` 一个键**:`isError` / `errorCode` / `toolCallId` / `toolName` / `
|
|
333
|
-
* `
|
|
349
|
+
* 🔴 **只改 `output` 一个键**:`isError` / `errorCode` / `toolCallId` / `toolName` / `gate` /
|
|
350
|
+
* `delivered` / `gatedCallId` / `structured` 以及 `flushHeld` 自己盖的
|
|
334
351
|
* `_sema_collateral_abort` 机读位全部原样过境(展开赋值只覆盖 `output`)—— 端侧的连坐折叠锚的是
|
|
335
352
|
* 机读位不是文案,所以折叠语义不受影响,只是被折那一行的正文换成了 CC 中断形。
|
|
336
353
|
*/
|
|
@@ -615,16 +632,13 @@ function routeDone(ev, ctx) {
|
|
|
615
632
|
// [4914]:sync 腿的待批 call 身份在 `done{suspended}` 的 **result 顶层**(与 checkpointId 同位)。
|
|
616
633
|
const gatedCallId = parkGatedCallId(ev.result);
|
|
617
634
|
// 两条 park 臂都登记(理由同 routeSuspended)。
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
// 准入证:主语是 checkpointGate **本体**的 toolName,不是任何 tool_end 帧的。
|
|
626
|
-
led.noteParkGate({ fsOrShellFamily: isFsOrShellToolName(ev.result.checkpointGate?.toolName) });
|
|
627
|
-
return { kind: 'park', gate: 'fs', pendingDone: ev, ...(gatedCallId !== undefined ? { gatedCallId } : {}) };
|
|
635
|
+
const park = parkGateOf(ev.result);
|
|
636
|
+
if (park !== null) {
|
|
637
|
+
ctx.taskId.current = ctx.taskId.current ?? park.taskId;
|
|
638
|
+
// 准入证:主语是**门本体**的 toolName,不是任何 tool_end 帧的(ask 臂恒 false —— 问答门不属
|
|
639
|
+
// fs/shell 族)。
|
|
640
|
+
led.noteParkGate({ fsOrShellFamily: park.gate === 'fs' && isFsOrShellToolName(park.toolName) });
|
|
641
|
+
return { kind: 'park', gate: park.gate, pendingDone: ev, ...(gatedCallId !== undefined ? { gatedCallId } : {}) };
|
|
628
642
|
}
|
|
629
643
|
// 真错(非 gate)的 Ask tool_end 此刻诚实渲染;件 B:纯用户中断批在这个出口把文案归一成 CC 中断形。
|
|
630
644
|
const events = [...flushHeldWithInterruptRewrite(led, { terminal: ev, signal: ctx.signal })];
|
|
@@ -141,6 +141,34 @@ export interface PlanReviewOutcome {
|
|
|
141
141
|
editedPlan?: string;
|
|
142
142
|
reason?: string;
|
|
143
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* {@link HitlSafetyError.code} 的闭集(0.60.0 抽名,**成员一个没动**)。
|
|
146
|
+
*
|
|
147
|
+
* 🔴 抽名的理由是**消费面**:决断腿的失败结局要把「本地安全停」与「引擎的 wire 拒绝」结构分开
|
|
148
|
+
* (`FsApprovalOutcome.safetyCode` / `GateOutcome.code`),而那两处此前只能各自把这五个词再抄一遍
|
|
149
|
+
* ——抄一份就是给自己立第二个判官(B-025 的病形)。加员仍必须**同批**改齐
|
|
150
|
+
* `parkResolver.GATE_FAILURE_CODES` 白名单(见下方原注)。
|
|
151
|
+
*/
|
|
152
|
+
export type HitlSafetyCode = 'binding_mismatch' | 'no_pending' | 'wrong_gate' | 'bad_plan_edit' | 'empty_answer';
|
|
153
|
+
/**
|
|
154
|
+
* 一次决断链失败**停在哪一段**(0.60.0;异源对抗复审 [medium] 采纳,B-037 候包件 ② 的完整兑现)。
|
|
155
|
+
*
|
|
156
|
+
* ── 病 ──────────────────────────────────────────────────────────────────────────────────────
|
|
157
|
+
* 两条决断腿(fs / ask)的 `failed` 结局把 typed 错**拍平成一个 `reason` 串**,于是四类完全不同的
|
|
158
|
+
* 失败在消费点**同形**:「取件那一拍没有可决行」「卡口没装」「引擎语义拒绝」「出站瞬断耗尽」——
|
|
159
|
+
* 分流只能去正则匹配那句拍平文案(里面嵌的是 `String(e)`,上游改一个词就静默空转)。而这四类的
|
|
160
|
+
* 出路完全不同:重探 / 装口 / 重呈或换参数 / 重呈不判死。
|
|
161
|
+
*
|
|
162
|
+
* ── 词与判据 ────────────────────────────────────────────────────────────────────────────────
|
|
163
|
+
* · `fetch` —— 取件腿没给出可决行,或读面自己炸了(**一个字节都没送出去**);
|
|
164
|
+
* · `input` —— 行上没有可呈现的载荷(工具入参 / 问句),卡渲不出来(同样没送出去);
|
|
165
|
+
* · `card` —— 卡口那一段失败(没装卡口 / 卡口自己报失败 / 没有问答 overlay);
|
|
166
|
+
* · `decide` —— 决断**已经发出去了**(引擎语义拒绝,或出站瞬断耗尽);
|
|
167
|
+
* · `orchestration` —— 上面四段都不是:编排层在若干轮重挂之后收口(轮数/park 数硬上限、连续零进展)。
|
|
168
|
+
* 🔴 **只有 `decide` 代表「引擎那一头知道这次决断」** —— 安全停的第一问就是它,前三段一个字节都没送出。
|
|
169
|
+
* 🔴 **必填**:一个可选的阶段位等于又留了一格「读不出就猜」,而这一位存在的全部理由就是消灭那一格。
|
|
170
|
+
*/
|
|
171
|
+
export type HitlFailureStage = 'fetch' | 'input' | 'card' | 'decide' | 'orchestration';
|
|
144
172
|
/** A safety stop: a contract law was about to be violated (e.g. a binding mismatch, a wrong-gate route).
|
|
145
173
|
* The caller MUST re-present to the human or surface the error — NEVER silently retry or auto-decide
|
|
146
174
|
* (contract/04 §9.1 fail-closed law). */
|
|
@@ -150,14 +178,14 @@ export declare class HitlSafetyError extends Error {
|
|
|
150
178
|
/** 🔴 闭集(FIX② 2026-08-07 加 `empty_answer`,第 5 员)。加员必须**同批**改齐
|
|
151
179
|
* `parkResolver.GATE_FAILURE_CODES` 白名单 —— 那道白名单把不认得的码降成 `undefined`,
|
|
152
180
|
* 一边加一边不改另一边 = 新码在消费端被静默吞掉(比不加还坏:本地拦住了,判词却丢了)。 */
|
|
153
|
-
readonly code:
|
|
181
|
+
readonly code: HitlSafetyCode;
|
|
154
182
|
constructor(message: string,
|
|
155
183
|
/** A stable code the shell can branch on (REF-CC-036,2026-08-02:闭集联合,不再是开集 string —
|
|
156
184
|
* 与同族兄弟 `controlRouter.ts` 的 `ControlSafetyError.code` 同形)。 */
|
|
157
185
|
/** 🔴 闭集(FIX② 2026-08-07 加 `empty_answer`,第 5 员)。加员必须**同批**改齐
|
|
158
186
|
* `parkResolver.GATE_FAILURE_CODES` 白名单 —— 那道白名单把不认得的码降成 `undefined`,
|
|
159
187
|
* 一边加一边不改另一边 = 新码在消费端被静默吞掉(比不加还坏:本地拦住了,判词却丢了)。 */
|
|
160
|
-
code:
|
|
188
|
+
code: HitlSafetyCode);
|
|
161
189
|
}
|
|
162
190
|
/**
|
|
163
191
|
* decide 409 `approval_stale` 拒体上的 **additive 指路键** `currentPending` —— 本会话**当前**那条
|
|
@@ -219,6 +247,25 @@ export type GateCurrentPending = Readonly<NonNullable<ApprovalStaleError['curren
|
|
|
219
247
|
* 铸一个残形只会让消费点以为「有指路」。`boundInputHash` 是**行上有才带**的可选位,缺席照铸。
|
|
220
248
|
*/
|
|
221
249
|
export declare function readDecideCurrentPending(e: unknown): GateCurrentPending | undefined;
|
|
250
|
+
/**
|
|
251
|
+
* 从一次 decide / 取件失败(任意抛出物)里读出**引擎的机器码** —— 读不出即 `undefined`。
|
|
252
|
+
*
|
|
253
|
+
* 🔴 **为什么这一位非透出不可**(0.60.0;上游候包件 ②,与 0.47.0 补 `currentPending` **同形**):
|
|
254
|
+
* 本包把 typed 错**拍平成 `reason` 串**交给消费点(`FsApprovalOutcome.failed.reason`),于是
|
|
255
|
+
* 「只读 outcome 形」的消费者(壳的重开链、迟到决断代理)对机读码**全盲** —— 要分辨
|
|
256
|
+
* `resume.preflight_rejected` / `approval_stale` / 传输类失败,只能去正则匹配那句拍平文案,
|
|
257
|
+
* 而那句文案里嵌的是 `String(e)`(上游一改措辞就静默空转)。上游为此在壳里绕了一整圈
|
|
258
|
+
* (把 client 套进代理、站在拍平**之前**读)——那圈本该由这一位省掉。
|
|
259
|
+
* 🔴 **结构视图读,不 `instanceof`**:理由与 {@link readDecideCurrentPending} 逐字同一条 ——
|
|
260
|
+
* 抛出来的是不是 SDK 的错误类由**宿主注入的传输层**决定(跨 realm / 双实例)。
|
|
261
|
+
* 🔴 **键位只认 `errorCode`**([2055] 死键纪律,退役的 `code` 槽不做兼容);非空串才算数,
|
|
262
|
+
* 其余一律诚实缺席。
|
|
263
|
+
* 🔴 **只搬运不解释**:本读口不判任何码属于哪一族(词表属主在 server/core,开集)——消费点分支
|
|
264
|
+
* 已知码、永远带 `default`。
|
|
265
|
+
* 🔴 **绝不把它当凭证面**:`errorCode` 是机器码,不是错误正文;错误正文(可能含凭据/内部路径)
|
|
266
|
+
* 照旧**不**经本位外流。
|
|
267
|
+
*/
|
|
268
|
+
export declare function readWireErrorCode(e: unknown): string | undefined;
|
|
222
269
|
/** 测试钩:把超时类重试总窗调小(传 undefined 还原缺省)。 */
|
|
223
270
|
export declare function __setDecideTimeoutRetryBudgetForTests(ms?: number): void;
|
|
224
271
|
/**
|
package/dist/hitl/hitlBridge.js
CHANGED
|
@@ -35,7 +35,6 @@ export function denyReasonForWire(reason, tag) {
|
|
|
35
35
|
`(server caps approval reason at ${MAX_DENY_REASON_CHARS}; over-cap is a 413 reason_too_large that would drop the DECISION, not just the reason)`);
|
|
36
36
|
return cut;
|
|
37
37
|
}
|
|
38
|
-
// ── Errors the bridge surfaces (fail-closed; never auto-retried) ───────────────
|
|
39
38
|
/** A safety stop: a contract law was about to be violated (e.g. a binding mismatch, a wrong-gate route).
|
|
40
39
|
* The caller MUST re-present to the human or surface the error — NEVER silently retry or auto-decide
|
|
41
40
|
* (contract/04 §9.1 fail-closed law). */
|
|
@@ -102,6 +101,28 @@ export function readDecideCurrentPending(e) {
|
|
|
102
101
|
: {}),
|
|
103
102
|
};
|
|
104
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* 从一次 decide / 取件失败(任意抛出物)里读出**引擎的机器码** —— 读不出即 `undefined`。
|
|
106
|
+
*
|
|
107
|
+
* 🔴 **为什么这一位非透出不可**(0.60.0;上游候包件 ②,与 0.47.0 补 `currentPending` **同形**):
|
|
108
|
+
* 本包把 typed 错**拍平成 `reason` 串**交给消费点(`FsApprovalOutcome.failed.reason`),于是
|
|
109
|
+
* 「只读 outcome 形」的消费者(壳的重开链、迟到决断代理)对机读码**全盲** —— 要分辨
|
|
110
|
+
* `resume.preflight_rejected` / `approval_stale` / 传输类失败,只能去正则匹配那句拍平文案,
|
|
111
|
+
* 而那句文案里嵌的是 `String(e)`(上游一改措辞就静默空转)。上游为此在壳里绕了一整圈
|
|
112
|
+
* (把 client 套进代理、站在拍平**之前**读)——那圈本该由这一位省掉。
|
|
113
|
+
* 🔴 **结构视图读,不 `instanceof`**:理由与 {@link readDecideCurrentPending} 逐字同一条 ——
|
|
114
|
+
* 抛出来的是不是 SDK 的错误类由**宿主注入的传输层**决定(跨 realm / 双实例)。
|
|
115
|
+
* 🔴 **键位只认 `errorCode`**([2055] 死键纪律,退役的 `code` 槽不做兼容);非空串才算数,
|
|
116
|
+
* 其余一律诚实缺席。
|
|
117
|
+
* 🔴 **只搬运不解释**:本读口不判任何码属于哪一族(词表属主在 server/core,开集)——消费点分支
|
|
118
|
+
* 已知码、永远带 `default`。
|
|
119
|
+
* 🔴 **绝不把它当凭证面**:`errorCode` 是机器码,不是错误正文;错误正文(可能含凭据/内部路径)
|
|
120
|
+
* 照旧**不**经本位外流。
|
|
121
|
+
*/
|
|
122
|
+
export function readWireErrorCode(e) {
|
|
123
|
+
const raw = e?.errorCode;
|
|
124
|
+
return typeof raw === 'string' && raw.length > 0 ? raw : undefined;
|
|
125
|
+
}
|
|
105
126
|
// ── decide 出站的瞬断分诊与有界重试(Inkglow-1085 P0a,2026-08-19;[4664] server 定谳后对表)────
|
|
106
127
|
//
|
|
107
128
|
// 病形(案卷 INKGLOW-1085 车1 全链):`approvals.decide` 是**非 submit POST** —— SDK transport 对它
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* 在驱动的消费点上必然显形,而不是被本文件自己吞掉。
|
|
14
14
|
*/
|
|
15
15
|
import type { AgentEvent } from '@sema-agent/sdk';
|
|
16
|
-
import { type GateCurrentPending } from './hitlBridge.js';
|
|
16
|
+
import { type GateCurrentPending, type HitlFailureStage } from './hitlBridge.js';
|
|
17
17
|
import { type QuestionAnswer } from '../liveQuestionStore.js';
|
|
18
18
|
import type { AskAnsweredOutput, GateLedger } from './gateLedger.js';
|
|
19
19
|
import { type AskGateWireDeps, type GatePark } from './frameRouter.js';
|
|
@@ -69,9 +69,11 @@ export type GateOutcome = {
|
|
|
69
69
|
gatedCallId?: string | undefined;
|
|
70
70
|
} | {
|
|
71
71
|
kind: 'failed';
|
|
72
|
+
stage: HitlFailureStage;
|
|
72
73
|
gatedCallId?: string | undefined;
|
|
73
74
|
reason: string;
|
|
74
75
|
code?: GateFailureCode | undefined;
|
|
76
|
+
errorCode?: string | undefined;
|
|
75
77
|
retryExhausted?: true;
|
|
76
78
|
currentPending?: GateCurrentPending | undefined;
|
|
77
79
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DecideTransportRetryExhaustedError, HitlBridge, HitlSafetyError, findPendingForTask, readDecideCurrentPending, } from './hitlBridge.js';
|
|
1
|
+
import { DecideTransportRetryExhaustedError, HitlBridge, HitlSafetyError, findPendingForTask, readDecideCurrentPending, readWireErrorCode, } from './hitlBridge.js';
|
|
2
2
|
import { publishQuestionFrame, registerLocalQuestionResponder, hasQuestionOverlay, } from '../liveQuestionStore.js';
|
|
3
3
|
import { hostLog } from '../host.js';
|
|
4
4
|
import { surfaceFsApprovalAndDecide } from './toolApprovalWire.js';
|
|
@@ -164,7 +164,7 @@ async function surfaceGateAndDecide(deps, taskId, askArgsByCall, signal,
|
|
|
164
164
|
* 与 fs 腿同一条,见 `toolApprovalWire.surfaceFsApprovalAndDecide` 的同名参数头注。 */
|
|
165
165
|
parkGatedCallId, onPresented) {
|
|
166
166
|
if (!hasQuestionOverlay()) {
|
|
167
|
-
return { kind: 'failed', reason: 'no question overlay mounted (print/non-REPL mode)' };
|
|
167
|
+
return { kind: 'failed', stage: 'card', reason: 'no question overlay mounted (print/non-REPL mode)' };
|
|
168
168
|
}
|
|
169
169
|
// pending 行 = 问题 payload 的权威源 + D-1 绑定的唯一 surface(PendingCheckpoint.input)。
|
|
170
170
|
// REF-CC-029(单源取件)+ REF-CC-033(no_pending 判别码):findPendingForTask 是唯一取件口
|
|
@@ -174,7 +174,7 @@ parkGatedCallId, onPresented) {
|
|
|
174
174
|
// 本腿的族就是 ask 一族(整行可读闸的 ask 半场,与 fs 腿的 `isToolApprovalGate` 对称)。
|
|
175
175
|
(row) => isAskTool(typeof row.toolName === 'string' ? row.toolName : undefined));
|
|
176
176
|
if (!found.ok)
|
|
177
|
-
return { kind: 'failed', reason: found.reason, ...(found.code !== undefined ? { code: found.code } : {}) };
|
|
177
|
+
return { kind: 'failed', stage: 'fetch', reason: found.reason, ...(found.code !== undefined ? { code: found.code } : {}) };
|
|
178
178
|
const { pending, gatedCallId } = found;
|
|
179
179
|
// 问题 payload:优先流上 tool_start.args(零额外语义),缺则 pending.input(service 已 redact + 限长)。
|
|
180
180
|
const fromArgs = gatedCallId
|
|
@@ -189,6 +189,7 @@ parkGatedCallId, onPresented) {
|
|
|
189
189
|
const truncated = pending.input?.truncated === true;
|
|
190
190
|
return {
|
|
191
191
|
kind: 'failed',
|
|
192
|
+
stage: 'input',
|
|
192
193
|
gatedCallId,
|
|
193
194
|
reason: truncated
|
|
194
195
|
? 'question payload exceeded the engine input cap (input={truncated:true}) — dialog cannot be re-rendered; run stays suspended for out-of-band decide'
|
|
@@ -268,14 +269,18 @@ parkGatedCallId, onPresented) {
|
|
|
268
269
|
const code = e instanceof HitlSafetyError && isGateFailureCode(e.code) ? e.code : undefined;
|
|
269
270
|
// #363:S-02 指路键原样透传 —— 与 `toolApprovalWire` 两条 decide 失败腿同形同源。
|
|
270
271
|
const currentPending = readDecideCurrentPending(e);
|
|
272
|
+
// 0.60.0(候包件 ②):引擎机器码原样透传 —— 与 fs 腿两条 decide 失败腿同形同源。
|
|
273
|
+
const wireCode = readWireErrorCode(e);
|
|
271
274
|
return {
|
|
272
275
|
kind: 'failed',
|
|
276
|
+
stage: 'decide',
|
|
273
277
|
gatedCallId,
|
|
274
278
|
reason: `decide failed: ${String(e)}`,
|
|
275
279
|
...(code !== undefined ? { code } : {}),
|
|
276
280
|
// Inkglow-1085 P0a:ask 腿与 fs 腿同形 —— 瞬断耗尽给判别位,resolvePark 走重呈臂。
|
|
277
281
|
...(e instanceof DecideTransportRetryExhaustedError ? { retryExhausted: true } : {}),
|
|
278
282
|
...(currentPending !== undefined ? { currentPending } : {}),
|
|
283
|
+
...(wireCode !== undefined ? { errorCode: wireCode } : {}),
|
|
279
284
|
};
|
|
280
285
|
}
|
|
281
286
|
}
|
|
@@ -369,6 +374,7 @@ export async function resolvePark(park, ctx) {
|
|
|
369
374
|
closingCardTried = true;
|
|
370
375
|
outcome = {
|
|
371
376
|
kind: 'failed',
|
|
377
|
+
stage: 'orchestration',
|
|
372
378
|
reason: `${ctx.unverifiableParks ?? 0} parks in this single turn resolved to no identifiable approval (hard cap ${MAX_TOTAL_PARKS}; last: ${ctx.lastStallReason ?? 'the engine kept parking'})`,
|
|
373
379
|
};
|
|
374
380
|
}
|
|
@@ -380,14 +386,14 @@ export async function resolvePark(park, ctx) {
|
|
|
380
386
|
`(last: ${ctx.lastStallReason ?? 'unknown'})`;
|
|
381
387
|
if (ctx.lastRoundPresentedCard === true) {
|
|
382
388
|
// 上一轮已经把卡呈给用户、用户也答了、引擎还是原样 park 回来 —— 再呈一次只是把同一张失效卡再问一遍。
|
|
383
|
-
outcome = { kind: 'failed', reason: stalledReason };
|
|
389
|
+
outcome = { kind: 'failed', stage: 'orchestration', reason: stalledReason };
|
|
384
390
|
}
|
|
385
391
|
else {
|
|
386
392
|
const closing = await surfaceParkGate(park, ctx, park.gatedCallId, witness);
|
|
387
393
|
outcome =
|
|
388
394
|
closing.kind === 'decided' || closing.kind === 'aborted'
|
|
389
395
|
? closing
|
|
390
|
-
: { kind: 'failed', reason: `${stalledReason}; closing re-read: ${closing.reason}` };
|
|
396
|
+
: { kind: 'failed', stage: 'orchestration', reason: `${stalledReason}; closing re-read: ${closing.reason}` };
|
|
391
397
|
}
|
|
392
398
|
}
|
|
393
399
|
else {
|
|
@@ -482,6 +488,7 @@ export async function resolvePark(park, ctx) {
|
|
|
482
488
|
`[criterion: ${streakKey}${reasonToken !== undefined ? ` via reason token "${reasonToken}"` : ''}]`);
|
|
483
489
|
outcome = {
|
|
484
490
|
kind: 'failed',
|
|
491
|
+
stage: 'orchestration',
|
|
485
492
|
...(outcome.gatedCallId !== undefined ? { gatedCallId: outcome.gatedCallId } : {}),
|
|
486
493
|
reason: stalled,
|
|
487
494
|
};
|
|
@@ -508,6 +515,9 @@ export async function resolvePark(park, ctx) {
|
|
|
508
515
|
}
|
|
509
516
|
outcome = {
|
|
510
517
|
kind: 'failed',
|
|
518
|
+
// 🔴 `orchestration` 而不是 `decide`:这一格是**重呈臂用尽之后**编排层的收口,不是某一次
|
|
519
|
+
// decide 的语义结局(那一次的结局在 `outcome.reason` 里被引述)。
|
|
520
|
+
stage: 'orchestration',
|
|
511
521
|
...(outcome.gatedCallId !== undefined ? { gatedCallId: outcome.gatedCallId } : {}),
|
|
512
522
|
reason: `the decide call failed on transport ${streak} times in a row while the run stayed parked (${outcome.reason})`,
|
|
513
523
|
};
|