@sema-agent/client-core 0.76.0 → 0.76.2
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 +30 -0
- package/README.md +7 -1
- package/dist/adapt/arms.js +51 -48
- package/dist/adapt/ids.d.ts +29 -2
- package/dist/adapt/ids.js +29 -2
- package/dist/adapt/panelTasks.d.ts +4 -3
- package/dist/adapt/panelTasks.js +29 -11
- package/dist/adapt/textStream.js +5 -5
- package/dist/adapt/toolCards.js +2 -2
- package/dist/adapt/turnFlags.js +5 -5
- package/dist/adapt.d.ts +1 -1
- package/dist/adapt.js +8 -8
- package/dist/adapter/downstream/eventToSdkMessage.d.ts +12 -0
- package/dist/adapter/downstream/eventToSdkMessage.js +13 -20
- package/dist/adapter/runStream.js +14 -5
- package/dist/adapter/types.d.ts +12 -0
- package/dist/adapter/types.js +30 -0
- package/dist/approvalsStreamLiveCapability.js +15 -35
- package/dist/deviceExecutorManagementCapability.js +15 -35
- package/dist/engineCapReader.d.ts +77 -0
- package/dist/engineCapReader.js +87 -0
- package/dist/executionLaneCapability.js +15 -38
- package/dist/hitl/persistedRulesWire.d.ts +20 -0
- package/dist/hitl/persistedRulesWire.js +23 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +22 -1
- package/dist/mcpLiveness.d.ts +179 -0
- package/dist/mcpLiveness.js +218 -0
- package/dist/mcpPanel.d.ts +17 -0
- package/dist/mcpPanel.js +21 -0
- package/dist/memoryComplianceCapability.d.ts +73 -0
- package/dist/memoryComplianceCapability.js +109 -0
- package/dist/memoryEntriesWire.d.ts +302 -0
- package/dist/memoryEntriesWire.js +595 -0
- package/dist/memoryOriginCapability.d.ts +68 -0
- package/dist/memoryOriginCapability.js +102 -0
- package/dist/peerLaneCapability.d.ts +62 -0
- package/dist/peerLaneCapability.js +100 -0
- package/dist/permissionRulesWriteCapability.d.ts +64 -0
- package/dist/permissionRulesWriteCapability.js +98 -0
- package/dist/selfOrchestrationDenial.js +7 -2
- package/dist/sqlEngineCapability.js +15 -35
- package/dist/webSearchBackendCapability.js +15 -35
- package/dist/writeProtectionCapability.js +15 -35
- package/docs/INTEGRATION-CLIENTS.md +107 -11
- package/package.json +1 -1
package/dist/adapt/panelTasks.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { clearEnginePanelTaskResident, clearEnginePanelTaskResidentByWire, markEnginePanelTaskResident } from '../engineAgentPanelStore.js';
|
|
2
|
-
import { chrome,
|
|
2
|
+
import { chrome, mainLane } from './ids.js';
|
|
3
3
|
import { SUBAGENT_TOOL_NAMES } from './wireShapes.js';
|
|
4
4
|
export function createPanelTaskLedger(ctx, cards, inst) {
|
|
5
5
|
/** taskId → 绑定的 open tool_use id。 */
|
|
@@ -22,7 +22,25 @@ export function createPanelTaskLedger(ctx, cards, inst) {
|
|
|
22
22
|
const subagentTypeById = new Map();
|
|
23
23
|
const laneOf = (taskId) => {
|
|
24
24
|
const card = taskCardBinding.get(taskId);
|
|
25
|
-
return card !== undefined ? { lane: 'subagent', parentToolCallId: card } :
|
|
25
|
+
return card !== undefined ? { lane: 'subagent', parentToolCallId: card } : mainLane();
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* 一条行要发**多条**事件时的车道证明来源(0.76.1,异源对抗复审 R1 [medium] 实测逼出)。
|
|
29
|
+
*
|
|
30
|
+
* 病形:终态那两处(关卡 / 终态 tick)此前 `const lane = laneOf(taskId)` 取一次,然后 stop 与
|
|
31
|
+
* end **两条事件共用那一只** —— 消费者拿到 stop 后改它的归属,同一条行的 end 跟着变;逐条消费
|
|
32
|
+
* 的宿主甚至能在 end 那一次 `yield` **之前**就把它污染掉(生成器在两条之间是挂起的)。
|
|
33
|
+
*
|
|
34
|
+
* 修法**不是**在每条事件那里各调一次 `laneOf`:两次调用之间生成器会挂起,消费者若在这中间绑了卡,
|
|
35
|
+
* 同一条行的两条事件就会**分流到两个归属**上(一条算主车道、一条算子代)——那比别名更坏。
|
|
36
|
+
* ⇒ **归属判一次、证明铸多只**:这里把归属(绑到哪张卡 / 没绑)先定死,返回的铸口每次交出一只
|
|
37
|
+
* 新对象、值恒相同。
|
|
38
|
+
*/
|
|
39
|
+
const laneMinterFor = (taskId) => {
|
|
40
|
+
const card = taskCardBinding.get(taskId);
|
|
41
|
+
return card !== undefined
|
|
42
|
+
? () => ({ lane: 'subagent', parentToolCallId: card })
|
|
43
|
+
: mainLane;
|
|
26
44
|
};
|
|
27
45
|
return {
|
|
28
46
|
laneOf,
|
|
@@ -131,7 +149,7 @@ export function createPanelTaskLedger(ctx, cards, inst) {
|
|
|
131
149
|
yield chrome({ kind: 'inline_task_stats', laneProof: { lane: 'subagent', parentToolCallId: cardId }, op: 'settle', cardId, isError });
|
|
132
150
|
}
|
|
133
151
|
else {
|
|
134
|
-
yield chrome({ kind: 'inline_task_stats', laneProof:
|
|
152
|
+
yield chrome({ kind: 'inline_task_stats', laneProof: mainLane(), op: 'settle_all' });
|
|
135
153
|
}
|
|
136
154
|
for (const taskId of livePanelTasks) {
|
|
137
155
|
if (cardId !== null && taskCardBinding.get(taskId) !== cardId)
|
|
@@ -144,18 +162,18 @@ export function createPanelTaskLedger(ctx, cards, inst) {
|
|
|
144
162
|
if (endedPanelTasks.has(taskId))
|
|
145
163
|
continue;
|
|
146
164
|
endedPanelTasks.add(taskId);
|
|
147
|
-
const
|
|
165
|
+
const mintLane = laneMinterFor(taskId);
|
|
148
166
|
const agentType = subagentTypeById.get(taskId);
|
|
149
167
|
yield chrome({
|
|
150
168
|
kind: 'subagent_lifecycle',
|
|
151
|
-
laneProof:
|
|
169
|
+
laneProof: mintLane(),
|
|
152
170
|
phase: 'stop',
|
|
153
171
|
taskId,
|
|
154
172
|
...(agentType !== undefined ? { agentType } : {}),
|
|
155
173
|
});
|
|
156
174
|
yield chrome({
|
|
157
175
|
kind: 'panel_task',
|
|
158
|
-
laneProof:
|
|
176
|
+
laneProof: mintLane(),
|
|
159
177
|
event: {
|
|
160
178
|
kind: 'end',
|
|
161
179
|
taskId,
|
|
@@ -184,7 +202,7 @@ export function createPanelTaskLedger(ctx, cards, inst) {
|
|
|
184
202
|
endedPanelTasks.add(taskId);
|
|
185
203
|
yield chrome({
|
|
186
204
|
kind: 'panel_task',
|
|
187
|
-
laneProof:
|
|
205
|
+
laneProof: mainLane(),
|
|
188
206
|
event: { kind: 'end', taskId, isError },
|
|
189
207
|
});
|
|
190
208
|
// SubagentStop 只对**真 fire 过 Start** 的子代(workflow runId / 外来 id 不配对乱 fire)。
|
|
@@ -193,7 +211,7 @@ export function createPanelTaskLedger(ctx, cards, inst) {
|
|
|
193
211
|
if (inst.hasFiredSubagentStart(taskId)) {
|
|
194
212
|
yield chrome({
|
|
195
213
|
kind: 'subagent_lifecycle',
|
|
196
|
-
laneProof:
|
|
214
|
+
laneProof: mainLane(),
|
|
197
215
|
phase: 'stop',
|
|
198
216
|
taskId,
|
|
199
217
|
guard: 'if-started',
|
|
@@ -215,7 +233,7 @@ export function createPanelTaskLedger(ctx, cards, inst) {
|
|
|
215
233
|
return;
|
|
216
234
|
endedPanelTasks.add(taskId);
|
|
217
235
|
clearResidencyBothKeys(taskId);
|
|
218
|
-
const
|
|
236
|
+
const mintLane = laneMinterFor(taskId);
|
|
219
237
|
const card = taskCardBinding.get(taskId);
|
|
220
238
|
if (card !== undefined) {
|
|
221
239
|
yield chrome({
|
|
@@ -231,7 +249,7 @@ export function createPanelTaskLedger(ctx, cards, inst) {
|
|
|
231
249
|
const agentType = subagentTypeById.get(taskId);
|
|
232
250
|
yield chrome({
|
|
233
251
|
kind: 'subagent_lifecycle',
|
|
234
|
-
laneProof:
|
|
252
|
+
laneProof: mintLane(),
|
|
235
253
|
phase: 'stop',
|
|
236
254
|
taskId,
|
|
237
255
|
...(agentType !== undefined ? { agentType } : {}),
|
|
@@ -240,7 +258,7 @@ export function createPanelTaskLedger(ctx, cards, inst) {
|
|
|
240
258
|
}
|
|
241
259
|
yield chrome({
|
|
242
260
|
kind: 'panel_task',
|
|
243
|
-
laneProof:
|
|
261
|
+
laneProof: mintLane(),
|
|
244
262
|
event: { kind: 'end', taskId, isError },
|
|
245
263
|
});
|
|
246
264
|
},
|
package/dist/adapt/textStream.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { chrome,
|
|
1
|
+
import { chrome, mainLane, transcript, messageIdentityOf } from './ids.js';
|
|
2
2
|
import { estimateCjkTokens, FLUSH_INTERVAL_MS } from './wireShapes.js';
|
|
3
3
|
// L-318:六形判决单源(两条车道同吃;落账各归各层,见 textSegmentAuthority.ts 头注)。
|
|
4
4
|
import { resolveTextSegmentAuthority } from './textSegmentAuthority.js';
|
|
@@ -196,7 +196,7 @@ export function createTextStream(ctx, idOf) {
|
|
|
196
196
|
thinkingAnchor = null;
|
|
197
197
|
thinkingBlockOpen = false;
|
|
198
198
|
// P2d:活体 "∴ Thinking…" 行在 committed 块落地的**同一刻**收掉(先收行再上消息,cli 同序)
|
|
199
|
-
yield chrome({ kind: 'thinking_activity', laneProof:
|
|
199
|
+
yield chrome({ kind: 'thinking_activity', laneProof: mainLane(), active: false });
|
|
200
200
|
yield transcript(msg, ctx.now());
|
|
201
201
|
}
|
|
202
202
|
/** 段提交的**唯一本体**(两个入口 `takeAnswerSegment` / `takeAnswerSegmentOnIdle` 共用;
|
|
@@ -266,7 +266,7 @@ export function createTextStream(ctx, idOf) {
|
|
|
266
266
|
thinkingBlockOpen = true;
|
|
267
267
|
yield chrome({
|
|
268
268
|
kind: 'stream_delta',
|
|
269
|
-
laneProof:
|
|
269
|
+
laneProof: mainLane(),
|
|
270
270
|
channel: 'thinking',
|
|
271
271
|
text: thinkingPending,
|
|
272
272
|
estimatedTokens: estimateCjkTokens(thinkingPending),
|
|
@@ -283,7 +283,7 @@ export function createTextStream(ctx, idOf) {
|
|
|
283
283
|
ctx.probe?.('stream', `${ctx.now()} text_flush len=${textPending.length}`);
|
|
284
284
|
yield chrome({
|
|
285
285
|
kind: 'stream_delta',
|
|
286
|
-
laneProof:
|
|
286
|
+
laneProof: mainLane(),
|
|
287
287
|
channel: 'text',
|
|
288
288
|
text: textPending,
|
|
289
289
|
estimatedTokens: estimateCjkTokens(textPending),
|
|
@@ -328,7 +328,7 @@ export function createTextStream(ctx, idOf) {
|
|
|
328
328
|
// 对照物只在 `reasoning_end` 用过之后清(段界由引擎说了算),turn 结束整只丢,天然有界。
|
|
329
329
|
// P2d:elapsed 锚在**首条** leader 推理增量(token 累计由 stream_delta.estimatedTokens
|
|
330
330
|
// 承载,宿主自加;>30s 无增量的 stall 提示同样由宿主按增量时间戳判)。
|
|
331
|
-
yield chrome({ kind: 'thinking_activity', laneProof:
|
|
331
|
+
yield chrome({ kind: 'thinking_activity', laneProof: mainLane(), active: true });
|
|
332
332
|
}
|
|
333
333
|
thinking += delta;
|
|
334
334
|
thinkingPending += delta;
|
package/dist/adapt/toolCards.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { deriveTranscriptId } from '../seam.js';
|
|
2
2
|
import { degradedToolResultBody, detectEngineBgShellReceipt, reportFindingsToolUseResult, structuredToToolUseResult, todoWriteToolUseResult, wireOutputToBody, } from '../toolResult.js';
|
|
3
|
-
import { chrome,
|
|
3
|
+
import { chrome, mainLane, transcript } from './ids.js';
|
|
4
4
|
import { flattenWireOutput } from './wireShapes.js';
|
|
5
5
|
export function createToolCardLedger(ctx, idOf) {
|
|
6
6
|
const pending = new Map();
|
|
@@ -26,7 +26,7 @@ export function createToolCardLedger(ctx, idOf) {
|
|
|
26
26
|
if ((p.name === 'Bash' || p.name === 'Monitor') && !isError && end?.output !== undefined) {
|
|
27
27
|
const reg = detectEngineBgShellReceipt(p.name, p.rawInput, flattenWireOutput(end.output));
|
|
28
28
|
if (reg) {
|
|
29
|
-
yield chrome({ kind: 'bgshell_register', laneProof:
|
|
29
|
+
yield chrome({ kind: 'bgshell_register', laneProof: mainLane(), taskId: reg.taskId, registration: reg });
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
// B 层:structured → 逐工具 typed toolUseResult(卡的 outputSchema safeParse 过才渲富卡)。
|
package/dist/adapt/turnFlags.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { chrome,
|
|
1
|
+
import { chrome, mainLane } from './ids.js';
|
|
2
2
|
export function createTurnFlags(ctx, turnStartAt) {
|
|
3
3
|
let messageStartEmitted = false;
|
|
4
4
|
let endEmitted = false;
|
|
@@ -29,20 +29,20 @@ export function createTurnFlags(ctx, turnStartAt) {
|
|
|
29
29
|
// (主 brain 仍可能在退避等待,#47 矩阵 #5 同族判别)。落旗后 `takeRetryOverlay()` 会对随后
|
|
30
30
|
// 到达的 `recovered` 直报帧返回 false ⇒ 不补第二发(两条腿合计恰好一发)。
|
|
31
31
|
retryOverlayActive = false;
|
|
32
|
-
yield chrome({ kind: 'retry_status', laneProof:
|
|
32
|
+
yield chrome({ kind: 'retry_status', laneProof: mainLane(), status: null });
|
|
33
33
|
}
|
|
34
34
|
if (m.type === 'stream_event' && !messageStartEmitted) {
|
|
35
35
|
messageStartEmitted = true;
|
|
36
36
|
yield chrome({
|
|
37
37
|
kind: 'response_metrics',
|
|
38
|
-
laneProof:
|
|
38
|
+
laneProof: mainLane(),
|
|
39
39
|
phase: 'start',
|
|
40
40
|
ttftMs: ctx.now() - turnStartAt,
|
|
41
41
|
});
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
*emitTurnUsageEnd(outputTokens) {
|
|
45
|
-
yield chrome({ kind: 'response_metrics', laneProof:
|
|
45
|
+
yield chrome({ kind: 'response_metrics', laneProof: mainLane(), phase: 'end', outputTokens });
|
|
46
46
|
endEmitted = true;
|
|
47
47
|
messageStartEmitted = false; // 下一 model round 重新压基线(与 noteModelRoundBoundary 同一件事)
|
|
48
48
|
},
|
|
@@ -55,7 +55,7 @@ export function createTurnFlags(ctx, turnStartAt) {
|
|
|
55
55
|
const outputTokens = readOutputTokens();
|
|
56
56
|
if (outputTokens === undefined)
|
|
57
57
|
return;
|
|
58
|
-
yield chrome({ kind: 'response_metrics', laneProof:
|
|
58
|
+
yield chrome({ kind: 'response_metrics', laneProof: mainLane(), phase: 'end', outputTokens });
|
|
59
59
|
endEmitted = true;
|
|
60
60
|
},
|
|
61
61
|
noteRetryOverlay: () => {
|
package/dist/adapt.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* ── A 族拆分后的地图(REF-CC-SPLIT-01~05 / FIX-05,2026-08-02;设计=docs/refactor/ADAPT-RW-MATRIX.md)
|
|
17
17
|
* `adapt()` 曾是 1139 行单方法(26 个 turn 级可变量 + 14 个闭包 + 15 个 case)。现在:
|
|
18
18
|
* 本文件 = 公面契约表 + 适配器实例(台账)+ **驱动壳**(迭代器竞速 / abort / finally / 收口三形)
|
|
19
|
-
* adapt/ids.ts 零状态共用件(双平面包装、Frame
|
|
19
|
+
* adapt/ids.ts 零状态共用件(双平面包装、Frame、主车道证明出厂口、id 派生工厂)
|
|
20
20
|
* adapt/wireShapes.ts 帧形词表 + 纯投影助手(公面十件由本文件具名再导出)
|
|
21
21
|
* adapt/textStream.ts M1 流合并器(矩阵 §1.1 十二行状态 + 竞速/节拍两处判据)
|
|
22
22
|
* adapt/toolCards.ts M2 工具卡台账(§1.2 三行;S1 复位收进 close() 第一个 yield 前)
|
package/dist/adapt.js
CHANGED
|
@@ -9,7 +9,7 @@ import { isEngineWorkflowNotified, isWorkflowCompletionCardEnqueued, listNotifie
|
|
|
9
9
|
*/
|
|
10
10
|
export { CANCEL_MESSAGE, estimateCjkTokens, flattenWireOutput, FLUSH_INTERVAL_MS, IDLE_FLUSH_MS, REJECT_MESSAGE, sanitizeToolUseBlock, shortTaskLabel, SUBAGENT_TOOL_NAMES, WORKFLOW_TOOL_NAMES, } from './adapt/wireShapes.js';
|
|
11
11
|
import { IDLE_FLUSH_MS } from './adapt/wireShapes.js';
|
|
12
|
-
import { chrome,
|
|
12
|
+
import { chrome, mainLane, makeIdOf } from './adapt/ids.js';
|
|
13
13
|
import { createTextStream } from './adapt/textStream.js';
|
|
14
14
|
import { createToolCardLedger } from './adapt/toolCards.js';
|
|
15
15
|
import { createPanelTaskLedger } from './adapt/panelTasks.js';
|
|
@@ -237,10 +237,10 @@ class WireToCcAdapterImpl {
|
|
|
237
237
|
const deps = { ctx, idOf, text, cards, panel, flags, inst };
|
|
238
238
|
// turn 开场卫生(T38 三清,cli 逐条同序):清上一 turn 的 retry 覆盖层 + **上一轮的建议批** +
|
|
239
239
|
// 思考活动行,再发 request-start(T39)。三清缺一都是"上一轮的东西挂着不掉"。
|
|
240
|
-
yield chrome({ kind: 'retry_status', laneProof:
|
|
241
|
-
yield chrome({ kind: 'prompt_suggestions', laneProof:
|
|
242
|
-
yield chrome({ kind: 'thinking_activity', laneProof:
|
|
243
|
-
yield chrome({ kind: 'request_start', laneProof:
|
|
240
|
+
yield chrome({ kind: 'retry_status', laneProof: mainLane(), status: null });
|
|
241
|
+
yield chrome({ kind: 'prompt_suggestions', laneProof: mainLane(), suggestions: null });
|
|
242
|
+
yield chrome({ kind: 'thinking_activity', laneProof: mainLane(), active: false });
|
|
243
|
+
yield chrome({ kind: 'request_start', laneProof: mainLane() });
|
|
244
244
|
// ── T40/T58/T59:手动 iterator + IDLE-FLUSH 竞速 + abort 断流 ────────────────────────────────
|
|
245
245
|
// `for await` 换成手动 next():① 才能与 idle 计时器 race(T40);② 才能在 abort 时立刻停;
|
|
246
246
|
// 代价是失去 for-await 的隐式 cleanup ⇒ finally 里手动 return()(T58,少了它 = SSE 不拆)。
|
|
@@ -342,8 +342,8 @@ class WireToCcAdapterImpl {
|
|
|
342
342
|
// 渲 InterruptedByUser chrome,那是宿主的事)。但**瞬态覆盖层必须收干净**,否则 Esc 之后
|
|
343
343
|
// 上一轮的 retry 行/思考活动行会一直挂着。
|
|
344
344
|
if (ctx.signal?.aborted) {
|
|
345
|
-
yield chrome({ kind: 'retry_status', laneProof:
|
|
346
|
-
yield chrome({ kind: 'thinking_activity', laneProof:
|
|
345
|
+
yield chrome({ kind: 'retry_status', laneProof: mainLane(), status: null });
|
|
346
|
+
yield chrome({ kind: 'thinking_activity', laneProof: mainLane(), active: false });
|
|
347
347
|
// 复审修 #2a:被打断的 turn 也**必须**结掉自己的面板行,否则 Esc 之后 footer 药丸永远
|
|
348
348
|
// 显示 "1 local agent"(sweep 点 3/4;四点各有正控,见 pure 门 SPLIT 段钉①)。
|
|
349
349
|
yield* panel.settle(null, false);
|
|
@@ -353,7 +353,7 @@ class WireToCcAdapterImpl {
|
|
|
353
353
|
// T60 —— turn 收口(cli 尾部同序,覆盖"流结束了但没有 result 臂"的路径):
|
|
354
354
|
// 泄尾增量 → 清 liveness → 提交尾思考 → 提交尾文本段。
|
|
355
355
|
yield* text.drainLive();
|
|
356
|
-
yield chrome({ kind: 'retry_status', laneProof:
|
|
356
|
+
yield chrome({ kind: 'retry_status', laneProof: mainLane(), status: null });
|
|
357
357
|
// 复审修 #2a 孪生:没有 `result` 臂的收尾路径同样 sweep(sweep 点 4/4)。
|
|
358
358
|
yield* panel.settle(null, false);
|
|
359
359
|
// B5:无 `result` 收尾腿同样关掉还开着的卡(cli 同位;abort 路径**不做**,见上)。
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
*/
|
|
36
36
|
import type { AgentEvent, CheckpointGate } from '@sema-agent/sdk';
|
|
37
37
|
import { type SDKMessage, type EmitContext, type ModelUsage } from '../types.js';
|
|
38
|
+
import { type McpLivenessView } from '../../mcpLiveness.js';
|
|
38
39
|
/**
|
|
39
40
|
* 本适配器铸出的**内部臂** `type` 全集(A-004.4 上游半场,2026-08-08)——「内部臂」= 桥面
|
|
40
41
|
* (bridge-facing)消息,**不是** CC SDK stdout 联合体里的帧形(那些是 assistant/stream_event/
|
|
@@ -230,6 +231,17 @@ export interface WiringManifestMcpEntryView {
|
|
|
230
231
|
* 包再校一遍只会在上游合法演进的当天把一个真读数判没(与 `errorCode` 的开集纪律同规)。
|
|
231
232
|
*/
|
|
232
233
|
httpStatus?: number;
|
|
234
|
+
/**
|
|
235
|
+
* 引擎最后一次**活性观察**(CC-36;第六键,引擎侧自 core 7.24.3 / server 7.91.2 起)。判据、词表与
|
|
236
|
+
* 「缺席 = 没有可用观察记录」那四句话的单一铸点在 {@link readMcpLiveness} 的顶注。
|
|
237
|
+
* 🔴 **缺席不铸**,且缺席**不是** `unknown`:后者是一句更强的断言(引擎看了,看不出来)。
|
|
238
|
+
* 🔴 与同行的 {@link status} / re-dial 回执**各说各的**,三者不合并、不互证(`status:'failed'` 与
|
|
239
|
+
* `liveness.state:'reachable'` 同行是真行:活着,且配错了)。
|
|
240
|
+
*/
|
|
241
|
+
liveness?: McpLivenessView;
|
|
242
|
+
/** never false:这一行的活性格**在场而读不出**(与整格缺席不是一句话 —— 「送来一份我读不懂的」要
|
|
243
|
+
* 能与「引擎没报」分开,否则腿级判词会拿读不出当可达的证据)。 */
|
|
244
|
+
livenessUnreadable?: true;
|
|
233
245
|
}
|
|
234
246
|
export declare function projectMcpSection(raw: unknown): WiringManifestMcpEntryView[] | undefined;
|
|
235
247
|
/** core 7.20.1(0.71.4):`wiring_manifest.writeProtection` 第七段 —— 写保护判官自报的目标视图(present-iff 这条腿编译出了判官)。 */
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { stamp, } from '../types.js';
|
|
1
|
+
import { stamp, snapshotSegmentIdentity, } from '../types.js';
|
|
2
2
|
import { turnUsageToModelUsage } from './turnUsageToModelUsage.js';
|
|
3
3
|
// 0.60.0(engine ≥7.64.0 / sdk 8.4.0):`tool_end.gate` 的三端共用窄读器(判定归包,端不许自拼)。
|
|
4
4
|
import { gateOutcomeOf } from '../../gateOutcome.js';
|
|
5
5
|
// CC-08(0.71.0):名册与名册增量的窄读器归 toolRoster.ts(0.63.0 起在),本层只接线。
|
|
6
6
|
import { projectToolRoster, projectToolRosterDelta } from '../../toolRoster.js';
|
|
7
|
+
import { readMcpLiveness } from '../../mcpLiveness.js';
|
|
7
8
|
import { SEGMENT_END_IDENTITY_KEYS } from '../types.js';
|
|
8
9
|
// 0.65.0(core 7.12.0 CHANGELOG「Removed (BREAKING)」`WiringManifest.autoMode.breaker`):
|
|
9
10
|
// 熔断族整只退役 ⇒ 本臂此前对 `autoMode.breaker` 的窄读(`classifierBreakerOf`)与它的型位一并
|
|
@@ -948,15 +949,8 @@ export function eventToSdkMessage(ev, ctx) {
|
|
|
948
949
|
* ⚠️ 仍按 `ev[k]` 读(**走原型链**:进程内对象的身份键常长在原型上),只是每键只读一次。
|
|
949
950
|
*/
|
|
950
951
|
function identitySnapshot(ev) {
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
for (const k of ['eventId', ...SEGMENT_END_IDENTITY_KEYS])
|
|
954
|
-
snap[k] = ev[k];
|
|
955
|
-
}
|
|
956
|
-
catch {
|
|
957
|
-
return null; // 读身份都会抛的帧不可信 ⇒ 调用方判坏(绝不带着半份身份继续走)
|
|
958
|
-
}
|
|
959
|
-
return snap;
|
|
952
|
+
// 0.76.1:单源 —— 与 adapt 侧各臂共用同一只快照助手(见 adapter/types.ts 的 snapshotSegmentIdentity 头注)。
|
|
953
|
+
return snapshotSegmentIdentity(ev);
|
|
960
954
|
}
|
|
961
955
|
/** CC-08:三键身份(sdk `EventIdentity`)—— 在场非串 ⇒ 坏值不买路;在场(含空串)⇒ 原样透传;`eventId` 非空才带。
|
|
962
956
|
* 🔴 判据与抄写都取 {@link identitySnapshot} 的**同一份**读数(每键只读一次)。 */
|
|
@@ -1119,18 +1113,14 @@ function segmentEndProjection(ev, ctx, arm) {
|
|
|
1119
1113
|
// 只带 `sourceTaskId`(无 `parentToolCallId`)是合法的子代帧形。修前只透 `parentToolCallId`,臂只按它断闸 ⇒ 那一形的
|
|
1120
1114
|
// 子代段末帧被当 leader 投上来,端据它换 leader 的思考/文本行 = 子代推理覆盖 leader 推理面。⇒ 三键**原样穿上内部臂**,
|
|
1121
1115
|
// 任一键在场却非串 ⇒ 整帧 malformed(与 `parentToolCallId` 同一条 B3-DIRTY 律:坏值不许买路)。
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
}
|
|
1116
|
+
// 0.76.1(外部验收方对 0.76.0 的 F2):此前在同一个表达式里把每只身份键读了两次 ⇒ 「每键恰读一次」在这两条臂上不成立
|
|
1117
|
+
// (方向 fail-closed:值随读变的合法子代帧被判 malformed)。改走与其余七条内部臂同一只 identityOrMalformed(一次性快照:
|
|
1118
|
+
// 在场非串 ⇒ malformed;在场含空串 ⇒ 原样;`eventId` 非空才带)。
|
|
1119
|
+
const { identity, bad } = identityOrMalformed(ev, arm);
|
|
1120
|
+
if (bad !== null)
|
|
1121
|
+
return bad;
|
|
1126
1122
|
if (content.length === 0)
|
|
1127
1123
|
return nothing('empty_payload');
|
|
1128
|
-
const identity = {
|
|
1129
|
-
...(typeof ev.eventId === 'string' ? { eventId: ev.eventId } : {}),
|
|
1130
|
-
...(typeof ev.parentToolCallId === 'string' ? { parentToolCallId: ev.parentToolCallId } : {}),
|
|
1131
|
-
...(typeof ev.sourceTaskId === 'string' ? { sourceTaskId: ev.sourceTaskId } : {}),
|
|
1132
|
-
...(typeof ev.bgAgentId === 'string' ? { bgAgentId: ev.bgAgentId } : {}),
|
|
1133
|
-
};
|
|
1134
1124
|
// 两处字面量铸点(不是一处参数化):内部臂词表门按 `armBody({ type: '<字面量>'` 抽铸点,参数化会让两臂在账上「多登记」。
|
|
1135
1125
|
return arm === 'text_end'
|
|
1136
1126
|
? projected(stamp(ctx, armBody({ type: 'text_end', content, ...identity })))
|
|
@@ -1159,6 +1149,9 @@ export function projectMcpSection(raw) {
|
|
|
1159
1149
|
// S6-B(0.60.0):两位与既有五位同律 —— 逐键挑、开集读、缺席不铸。
|
|
1160
1150
|
...(typeof r.delivered === 'string' && r.delivered.length > 0 ? { delivered: r.delivered } : {}),
|
|
1161
1151
|
...(typeof r.httpStatus === 'number' && Number.isFinite(r.httpStatus) ? { httpStatus: r.httpStatus } : {}),
|
|
1152
|
+
// CC-36(0.76.1):第六位活性格 —— 判据不在这里写第二遍,全在 readMcpLiveness 那一只铸点里
|
|
1153
|
+
// (三向读数:缺席不铸 / 在场读不出立 never-false 位 / 读得出铸那次观察)。
|
|
1154
|
+
...((cell) => cell.kind === 'observation' ? { liveness: cell.liveness } : cell.kind === 'unreadable' ? { livenessUnreadable: true } : {})(readMcpLiveness(r)),
|
|
1162
1155
|
});
|
|
1163
1156
|
}
|
|
1164
1157
|
// 🔴 见顶注:`raw.length === 0` 是**正面事实**(照铸 `[]`);非空却零行幸存 = 读不出来 ⇒ 段缺席。
|
|
@@ -12,8 +12,17 @@ import { coerceOutput, publishSubagentContentEvent } from '../subagentContentSto
|
|
|
12
12
|
import { ACTIVE_RUN_BUSY_ERROR_CODE, OUTPUT_INVALID, isLimitsExceededCode } from '../engineErrorCodes.js';
|
|
13
13
|
// 0.60.0(engine ≥7.64.0 / sdk 8.4.0):终局读数的单一读器(纯叶子:零 Node、零宿主端口)。
|
|
14
14
|
import { isReviewPark, readRunTerminal, runTerminalCode } from '../runTerminal.js';
|
|
15
|
-
/**
|
|
16
|
-
|
|
15
|
+
/**
|
|
16
|
+
* 本文件发的 chrome 事件全在 leader lane(子代内容在上面就被 divert 走了)。
|
|
17
|
+
*
|
|
18
|
+
* 🔴 **出厂口而不是常量**(0.76.1,与 A 层那半场同一条修法):车道证明在下游会被就地改写,
|
|
19
|
+
* 一只模块级常量对象会让本文件三个发点(用量臂 / 停靠臂 / 对账臂)与**别的流**的同名事件共享
|
|
20
|
+
* 同一只证明,一次改写打穿整个进程。每次新建一只。
|
|
21
|
+
* 🔴 **刻意是本地的一行、不去 import A 层那只同名出厂口**:本文件是适配内核的闭包起点,闭包件数
|
|
22
|
+
* 是只许降的棘轮 —— 为一行 `{lane:'main'}` 把 A 层(或 seam)拖进内核闭包是拿棘轮换重复,
|
|
23
|
+
* 这一行字面量本来就是 `LaneProof` 主臂自己的形,不构成第二份判据。
|
|
24
|
+
*/
|
|
25
|
+
const mainLane = () => ({ lane: 'main' });
|
|
17
26
|
/**
|
|
18
27
|
* **fire-and-forget 的 chrome 发射口**(0.66.0 / 异源对抗复审 [high] 逼出的同形族扫)。
|
|
19
28
|
*
|
|
@@ -639,7 +648,7 @@ async function* runStreamInner(events, ctx, handle = {}) {
|
|
|
639
648
|
// (与 turn_end.usage 本身「整体 optional,缺席≠空对象」同一语义,不是「键在值 undefined」)。
|
|
640
649
|
emitChromeFireAndForget(ctx, {
|
|
641
650
|
kind: 'last_turn_usage',
|
|
642
|
-
laneProof:
|
|
651
|
+
laneProof: mainLane(),
|
|
643
652
|
usage,
|
|
644
653
|
// 🔴 0.68.0:`engineUsage` 的条件 spread 退役 —— `turn_end.usage` 恒在场
|
|
645
654
|
// (缺席已在违约闸里整帧收口),这里再判一次就是给一个不可能的形留座位。
|
|
@@ -916,7 +925,7 @@ async function* runStreamInner(events, ctx, handle = {}) {
|
|
|
916
925
|
// 同步 park 腿的 plan-review 卡不弹。因由形那一半覆盖 done 帧的引擎结果,两半各管一条腿。
|
|
917
926
|
if (ev.type === 'done' && isReviewPark(doneTerminal) && ctx.emitChrome) {
|
|
918
927
|
try {
|
|
919
|
-
await ctx.emitChrome({ kind: 'plan_review_park', laneProof:
|
|
928
|
+
await ctx.emitChrome({ kind: 'plan_review_park', laneProof: mainLane(), result: ev.result });
|
|
920
929
|
}
|
|
921
930
|
catch { /* fail-soft — the terminal frame below still renders */ }
|
|
922
931
|
}
|
|
@@ -937,7 +946,7 @@ async function* runStreamInner(events, ctx, handle = {}) {
|
|
|
937
946
|
// `terminalToSdkResult(..., observed)` 是**同一份**读数。
|
|
938
947
|
const costFacts = readRunCostFacts(doneStats, { usageMissingObserved });
|
|
939
948
|
if (costFacts !== undefined) {
|
|
940
|
-
emitChromeFireAndForget(ctx, { kind: 'run_cost_reconciled', laneProof:
|
|
949
|
+
emitChromeFireAndForget(ctx, { kind: 'run_cost_reconciled', laneProof: mainLane(), ...costFacts.reconcile });
|
|
941
950
|
}
|
|
942
951
|
}
|
|
943
952
|
// L-228 / 🔴 0.67.2 件 I-2b 订正:分表**不再挂到 `ctx` 上**,而是与观测位一起随第三参按值交给
|
package/dist/adapter/types.d.ts
CHANGED
|
@@ -129,3 +129,15 @@ export declare const SEGMENT_END_IDENTITY_KEYS: readonly ["parentToolCallId", "s
|
|
|
129
129
|
* 参数收任意对象(帧型不限;typeshape 纪律:边界层不让 `unknown` 出境)。
|
|
130
130
|
*/
|
|
131
131
|
export declare function isSubFlowSegmentEnd(m: object): boolean;
|
|
132
|
+
/**
|
|
133
|
+
* 身份四键(`eventId` + 三只子流键)的**一次性快照**:每键恰读一次,读取抛错 ⇒ `null`(= 整帧不可信,调用方拒帧)。
|
|
134
|
+
*
|
|
135
|
+
* 🔴 投影口与 adapt 侧的**每一条**读身份的臂都必须走这一只(0.76.1;外部验收方对 0.76.0 的两条发现):
|
|
136
|
+
* 0.76.0 只把投影口换成了一次性快照,adapt 侧的 compaction 臂仍先判子流再取 `parentToolCallId`(读两次),
|
|
137
|
+
* text_end / reasoning_end 的投影更是在同一个表达式里读两次 ⇒ 值随读变的帧让「投影口 → 内部臂」与
|
|
138
|
+
* 「直喂 adapt()」两条路分叉 —— 正是一次性快照要消灭的那一形,却只修了一侧。
|
|
139
|
+
* 读了两次的两个值不能相信任何一个;读一次的那一份,两条路都拿同一份。
|
|
140
|
+
*/
|
|
141
|
+
export declare function snapshotSegmentIdentity(m: object): Record<string, unknown> | null;
|
|
142
|
+
/** 与 {@link isSubFlowSegmentEnd} 同一条判据,但吃的是快照(不再回头读帧)。 */
|
|
143
|
+
export declare function snapshotIsSubFlow(snap: Record<string, unknown>): boolean;
|
package/dist/adapter/types.js
CHANGED
|
@@ -39,3 +39,33 @@ export function isSubFlowSegmentEnd(m) {
|
|
|
39
39
|
return true;
|
|
40
40
|
return false;
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* 身份四键(`eventId` + 三只子流键)的**一次性快照**:每键恰读一次,读取抛错 ⇒ `null`(= 整帧不可信,调用方拒帧)。
|
|
44
|
+
*
|
|
45
|
+
* 🔴 投影口与 adapt 侧的**每一条**读身份的臂都必须走这一只(0.76.1;外部验收方对 0.76.0 的两条发现):
|
|
46
|
+
* 0.76.0 只把投影口换成了一次性快照,adapt 侧的 compaction 臂仍先判子流再取 `parentToolCallId`(读两次),
|
|
47
|
+
* text_end / reasoning_end 的投影更是在同一个表达式里读两次 ⇒ 值随读变的帧让「投影口 → 内部臂」与
|
|
48
|
+
* 「直喂 adapt()」两条路分叉 —— 正是一次性快照要消灭的那一形,却只修了一侧。
|
|
49
|
+
* 读了两次的两个值不能相信任何一个;读一次的那一份,两条路都拿同一份。
|
|
50
|
+
*/
|
|
51
|
+
export function snapshotSegmentIdentity(m) {
|
|
52
|
+
const rec = m;
|
|
53
|
+
const snap = {};
|
|
54
|
+
try {
|
|
55
|
+
for (const k of ['eventId', ...SEGMENT_END_IDENTITY_KEYS])
|
|
56
|
+
snap[k] = rec[k];
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// §C1 带理由的吞:读身份键本身抛 = 这一帧连自己是谁都说不出 ⇒ 整帧拒,由调用方决定「丢」的姿势
|
|
60
|
+
// (投影口判 malformed 留痕、adapt 侧零 chrome);再向上抛会从两个入口逸出、打断整条 adapt 流。
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return snap;
|
|
64
|
+
}
|
|
65
|
+
/** 与 {@link isSubFlowSegmentEnd} 同一条判据,但吃的是快照(不再回头读帧)。 */
|
|
66
|
+
export function snapshotIsSubFlow(snap) {
|
|
67
|
+
for (const k of SEGMENT_END_IDENTITY_KEYS)
|
|
68
|
+
if (snap[k] !== undefined)
|
|
69
|
+
return true;
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* ③ 「要不要对账」的判据归包:{@link livePendingNeedsReconcile} —— 只有 present 且 true 才 false;其余一律 true(少查一次 = 一只等人的 ask 迟迟不出现)。
|
|
14
14
|
*/
|
|
15
15
|
import { engineWireTarget } from './engineWireTarget.js';
|
|
16
|
-
import {
|
|
16
|
+
import { createEngineCapReader } from './engineCapReader.js';
|
|
17
17
|
/** caps 回体 → 本格读数;畸形一律 `undefined`(= 这一格不写 ⇒ 读口答 `unobserved`)。 */
|
|
18
18
|
export function projectApprovalsStreamLiveCapability(caps) {
|
|
19
19
|
if (caps === null || typeof caps !== 'object')
|
|
@@ -27,41 +27,23 @@ export function projectApprovalsStreamLiveCapability(caps) {
|
|
|
27
27
|
return undefined;
|
|
28
28
|
return { kind: 'present', live: v };
|
|
29
29
|
}
|
|
30
|
-
|
|
30
|
+
/**
|
|
31
|
+
* 本格的 per-baseUrl 读账 + tee / 读口 / 失效口 / 测试钩四口 —— 各只能力位读器**共用同一份实现**
|
|
32
|
+
* ({@link createEngineCapReader});本文件只留这一格真正不同的部分:投影函数 {@link projectApprovalsStreamLiveCapability}、doctor 措辞表,以及本位自己的判据助手(如有)。
|
|
33
|
+
* 🔴 Map 在工厂闭包里,**一只读器一张**:收到读不懂的回体只删自己这一格,绝不连坐别的能力面。
|
|
34
|
+
*/
|
|
35
|
+
const reader = createEngineCapReader({
|
|
36
|
+
name: 'approvalsStreamLive',
|
|
37
|
+
project: projectApprovalsStreamLiveCapability,
|
|
38
|
+
makeUnobserved: () => ({ kind: 'unobserved' }),
|
|
39
|
+
});
|
|
31
40
|
/** 宿主 caps probe 的读面 tee 落点(与四只兄弟并列)。绝不 throw;畸形 ⇒ 删格;`opts.generation` 关掉旧探测覆盖新读数的竞态。 */
|
|
32
41
|
export function noteEngineCapsForApprovalsStreamLive(baseUrl, caps, opts) {
|
|
33
|
-
|
|
34
|
-
return;
|
|
35
|
-
// 🔴 代际只在入口**快照一次**(异源对抗复审 R3):opts 是调用方的对象,回体上的 getter 能在投影中途改它;首末两次核对都用这份快照,
|
|
36
|
-
// 读 opts / 读代际抛出 ⇒ 不动格(与陈旧代际同一处置)。
|
|
37
|
-
const gen = snapshotCapsGeneration(opts);
|
|
38
|
-
if (gen === null)
|
|
39
|
-
return; // 调用方 opts 的 getter 抛出 = 供给坏了 ⇒ 不动格
|
|
40
|
-
if (!capsGenerationStillCurrent(baseUrl, gen))
|
|
41
|
-
return;
|
|
42
|
-
let reading;
|
|
43
|
-
try {
|
|
44
|
-
reading = projectApprovalsStreamLiveCapability(caps);
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
// 🔴 0.74.2 同形存量(CC-69 复审 R1 族扫):抛 getter / Proxy 的回体 = 畸形,与「投影答 undefined」同一处置 —— 删格,不许留着上一台的读数。
|
|
48
|
-
reading = undefined;
|
|
49
|
-
}
|
|
50
|
-
// 🔴 投影之后**再核一次代际**(异源对抗复审 R2):投影会执行回体上不可信的 getter,它可以在中途让这一格换代
|
|
51
|
-
// (invalidate + 新代际的 tee 已写入);此时无论投影返回还是抛出,都不许动新代际的格。
|
|
52
|
-
if (!capsGenerationStillCurrent(baseUrl, gen))
|
|
53
|
-
return;
|
|
54
|
-
if (reading === undefined) {
|
|
55
|
-
readingByBase.delete(baseUrl);
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
readingByBase.set(baseUrl, reading);
|
|
42
|
+
reader.note(baseUrl, caps, opts);
|
|
59
43
|
}
|
|
60
44
|
/** 本进程观测到的读数;这一格空缺 ⇒ `{kind:'unobserved'}`。 */
|
|
61
45
|
export function observedApprovalsStreamLive(baseUrl = engineWireTarget()?.baseUrl) {
|
|
62
|
-
|
|
63
|
-
return { kind: 'unobserved' };
|
|
64
|
-
return readingByBase.get(baseUrl) ?? { kind: 'unobserved' };
|
|
46
|
+
return reader.observed(baseUrl);
|
|
65
47
|
}
|
|
66
48
|
/**
|
|
67
49
|
* feed 消费方「要不要靠对账节拍拉悬挂 ask」的判据单源:只有引擎明说会推(present ∧ true)才 `false`;
|
|
@@ -85,11 +67,9 @@ export function approvalsStreamLiveDoctorDetail(reading) {
|
|
|
85
67
|
}
|
|
86
68
|
/** 换代失效口(引擎温切成功后调):清成未观测。空串 ⇒ no-op;绝不 throw。 */
|
|
87
69
|
export function forgetApprovalsStreamLiveReading(baseUrl) {
|
|
88
|
-
|
|
89
|
-
return;
|
|
90
|
-
readingByBase.delete(baseUrl);
|
|
70
|
+
reader.forget(baseUrl);
|
|
91
71
|
}
|
|
92
72
|
/** 测试钩子。 */
|
|
93
73
|
export function __resetApprovalsStreamLiveReadingsForTests() {
|
|
94
|
-
|
|
74
|
+
reader.__resetForTests();
|
|
95
75
|
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* present ∧ false 与 `lane_absent` ⇒ `'no'`(明确的否);其余 ⇒ `'unknown'`(别当 no,也别当 yes)。
|
|
18
18
|
*/
|
|
19
19
|
import { engineWireTarget } from './engineWireTarget.js';
|
|
20
|
-
import {
|
|
20
|
+
import { createEngineCapReader } from './engineCapReader.js';
|
|
21
21
|
/** caps 回体 → 本格读数;畸形一律 `undefined`(= 这一格不写 ⇒ 读口答 `unobserved`)。 */
|
|
22
22
|
export function projectDeviceExecutorManagementCapability(caps) {
|
|
23
23
|
if (caps === null || typeof caps !== 'object')
|
|
@@ -45,41 +45,23 @@ export function projectDeviceExecutorManagementCapability(caps) {
|
|
|
45
45
|
return undefined;
|
|
46
46
|
return { kind: 'present', management: mgmt };
|
|
47
47
|
}
|
|
48
|
-
|
|
48
|
+
/**
|
|
49
|
+
* 本格的 per-baseUrl 读账 + tee / 读口 / 失效口 / 测试钩四口 —— 各只能力位读器**共用同一份实现**
|
|
50
|
+
* ({@link createEngineCapReader});本文件只留这一格真正不同的部分:投影函数 {@link projectDeviceExecutorManagementCapability}、doctor 措辞表,以及本位自己的判据助手(如有)。
|
|
51
|
+
* 🔴 Map 在工厂闭包里,**一只读器一张**:收到读不懂的回体只删自己这一格,绝不连坐别的能力面。
|
|
52
|
+
*/
|
|
53
|
+
const reader = createEngineCapReader({
|
|
54
|
+
name: 'deviceExecutor.management',
|
|
55
|
+
project: projectDeviceExecutorManagementCapability,
|
|
56
|
+
makeUnobserved: () => ({ kind: 'unobserved' }),
|
|
57
|
+
});
|
|
49
58
|
/** 宿主 caps probe 的读面 tee 落点(与五只兄弟并列)。绝不 throw;畸形 ⇒ 删格;`opts.generation` 关掉旧探测覆盖新读数的竞态。 */
|
|
50
59
|
export function noteEngineCapsForDeviceExecutorManagement(baseUrl, caps, opts) {
|
|
51
|
-
|
|
52
|
-
return;
|
|
53
|
-
// 🔴 代际只在入口**快照一次**(异源对抗复审 R3):opts 是调用方的对象,回体上的 getter 能在投影中途改它;首末两次核对都用这份快照,
|
|
54
|
-
// 读 opts / 读代际抛出 ⇒ 不动格(与陈旧代际同一处置)。
|
|
55
|
-
const gen = snapshotCapsGeneration(opts);
|
|
56
|
-
if (gen === null)
|
|
57
|
-
return; // 调用方 opts 的 getter 抛出 = 供给坏了 ⇒ 不动格
|
|
58
|
-
if (!capsGenerationStillCurrent(baseUrl, gen))
|
|
59
|
-
return;
|
|
60
|
-
let reading;
|
|
61
|
-
try {
|
|
62
|
-
reading = projectDeviceExecutorManagementCapability(caps);
|
|
63
|
-
}
|
|
64
|
-
catch {
|
|
65
|
-
// 🔴 抛 getter / Proxy 的回体 = 畸形(异源对抗复审 R1):与「投影答 undefined」同一处置 —— 删格,不许留着上一台的 yes / no。
|
|
66
|
-
reading = undefined;
|
|
67
|
-
}
|
|
68
|
-
// 🔴 投影之后**再核一次代际**(异源对抗复审 R2):投影会执行回体上不可信的 getter,它可以在中途让这一格换代
|
|
69
|
-
// (invalidate + 新代际的 tee 已写入);此时无论投影返回还是抛出,都不许动新代际的格。
|
|
70
|
-
if (!capsGenerationStillCurrent(baseUrl, gen))
|
|
71
|
-
return;
|
|
72
|
-
if (reading === undefined) {
|
|
73
|
-
readingByBase.delete(baseUrl);
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
readingByBase.set(baseUrl, reading);
|
|
60
|
+
reader.note(baseUrl, caps, opts);
|
|
77
61
|
}
|
|
78
62
|
/** 本进程观测到的读数;这一格空缺 ⇒ `{kind:'unobserved'}`。 */
|
|
79
63
|
export function observedDeviceExecutorManagement(baseUrl = engineWireTarget()?.baseUrl) {
|
|
80
|
-
|
|
81
|
-
return { kind: 'unobserved' };
|
|
82
|
-
return readingByBase.get(baseUrl) ?? { kind: 'unobserved' };
|
|
64
|
+
return reader.observed(baseUrl);
|
|
83
65
|
}
|
|
84
66
|
/**
|
|
85
67
|
* 「`/v1/devices/*` 管理动词能不能用」的判据单源(三态):只有引擎明说在场(present ∧ true)才 `'yes'`;
|
|
@@ -111,11 +93,9 @@ export function deviceExecutorManagementDoctorDetail(reading) {
|
|
|
111
93
|
}
|
|
112
94
|
/** 换代失效口(引擎温切成功后调):清成未观测。空串 ⇒ no-op;绝不 throw。 */
|
|
113
95
|
export function forgetDeviceExecutorManagementReading(baseUrl) {
|
|
114
|
-
|
|
115
|
-
return;
|
|
116
|
-
readingByBase.delete(baseUrl);
|
|
96
|
+
reader.forget(baseUrl);
|
|
117
97
|
}
|
|
118
98
|
/** 测试钩子。 */
|
|
119
99
|
export function __resetDeviceExecutorManagementReadingsForTests() {
|
|
120
|
-
|
|
100
|
+
reader.__resetForTests();
|
|
121
101
|
}
|