@sema-agent/client-core 0.74.1 → 0.74.3
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 +40 -0
- package/README.md +5 -1
- package/dist/adapt/arms.js +7 -1
- package/dist/adapt/panelTasks.d.ts +2 -1
- package/dist/adapt/panelTasks.js +8 -6
- package/dist/approvalsStreamLiveCapability.js +24 -13
- package/dist/decideReceipt.d.ts +15 -0
- package/dist/decideReceipt.js +21 -1
- package/dist/deviceExecutorManagementCapability.d.ts +53 -0
- package/dist/deviceExecutorManagementCapability.js +121 -0
- package/dist/engineAgentPanelStore.d.ts +54 -0
- package/dist/engineAgentPanelStore.js +220 -0
- package/dist/engineCapsGenerationGuard.d.ts +6 -0
- package/dist/engineCapsGenerationGuard.js +30 -0
- package/dist/executionLaneCapability.js +24 -13
- package/dist/fleet/fleetProjection.d.ts +5 -0
- package/dist/fleet/fleetProjection.js +8 -0
- package/dist/fleetAgentPanelProjection.d.ts +2 -0
- package/dist/fleetAgentPanelProjection.js +7 -0
- package/dist/hitl/hitlBridge.d.ts +9 -0
- package/dist/hitl/hitlBridge.js +11 -2
- package/dist/hitl/suspendedReopen.d.ts +30 -0
- package/dist/hitl/suspendedReopen.js +27 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +8 -0
- package/dist/runCancelContext.d.ts +49 -0
- package/dist/runCancelContext.js +81 -0
- package/dist/sqlEngineCapability.js +33 -19
- package/dist/webSearchBackendCapability.js +24 -13
- package/dist/writeProtectionCapability.js +33 -19
- package/docs/INTEGRATION-CLIENTS.md +105 -11
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -49,6 +49,46 @@
|
|
|
49
49
|
> 挡住 ⇒ 本批把它机械化——④a0 对 `pending` 行**要求段头已是日期形**(`(未发布)` 直接红),阶段一
|
|
50
50
|
> commit 漏转在发布前就红,不再靠人记。
|
|
51
51
|
|
|
52
|
+
## 0.74.3(2026-09-19)
|
|
53
|
+
|
|
54
|
+
> 主题:**面板子代身份归一**(CC-70;cli L-407 / test 定谳:同一后台子代在 wire 上两把身份)+ **run 记录取消现场读口与成因闭集**(CC-71;cli L-212 (a))+ **workflow 列表行 run 级 errorCode**(CC-72;core 7.23.5 #932)+ **reopen 族第二臂两件**(CC-73;cli L-56)。**patch**:型面纯 additive(+2 常量 +7 函数,公面 1052 → 1061;测试钩 50 → 51;`EngineAgentPanelEvent` 三臂各加可选键;零 wire 新键;peer 不动)。接入面 §71。
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
|
|
58
|
+
- **面板事件的子代身份归一(CC-70)**:`publishEngineAgentPanelEvent` 成为三条 lane 的唯一漏斗 —— `fleet-row` 登记两把钥匙(`transcriptId` 主 / `parentToolCallId` 退路),`tick` / `end` 上的 `task_progress` UUID 查得到就改写成 fleet 行 id 尾段(= 注册表 task_id = 宿主本地 agentId),原词进新键 `wireTaskId`,`taskIdOrigin:'fleet-row'`;查不到:绑卡的 tick(新提示键 `cardBound`,同步委派形)直发 `taskIdOrigin:'wire'`,未绑的 tick **缓一拍**等行帧(同 UUID 的 fleet-row 到 ⇒ 先行帧再放 tick;第二条 tick ⇒ 新的原样发旧的丢;`end` ⇒ 先 tick 后 end;满 64 条 ⇒ 最旧的原样发;别的 fleet-row / end / sweep 过了 `MAX_HELD_WIRE_TICK_BEATS`(16)拍 ⇒ 原样发;`sweep` 本身不放 —— 没行可结)。钥匙带周期身份:归一后的 `end` 自己不带 `cycleSeq` / `startedAt` 时借登记它的那条 fleet-row 的,复活后迟到的 `end{旧 UUID}` 因此带旧代际、`isStaleEngineAgentPanelEnd` 判得出;经退路键归一过的 UUID 记进主表,随后不带 `parentToolCallId` 的 `end{UUID}` 同样归一(三条皆异源对抗复审 R1 所抓)。常驻标随归一从 UUID 迁到尾段(此前 UUID 记、17 位清 ⇒ 永不清)。新读口 `resolveEnginePanelTaskId(wireTaskId, parentToolCallId?)`(带钥匙上的周期身份);`fleet-row` 事件与 `FleetAgentRowLike` +`parentToolCallId?`;tick 臂 +`parentToolCallId?` / `cardBound?`。**宿主零改**:面板按 `ev.taskId` 落表即可,同一子代不再长成两行。
|
|
59
|
+
- **`src/runCancelContext.ts`(CC-71;server ≥7.87.3 `PersistedTaskResult.cancelContext` 四座,sdk 无型 ⇒ 结构读)**:`readRunCancelContext(record)` —— 缺席 ⇒ `not_reported`(**不折成「未取消」**);四座各自可缺席各自防御读(有 age 无 kind 合法;`elapsedMs` 绝不折 0;`lastBrainStatus` 原样带出);坏形 ⇒ `undefined`。`classifyRunAbortCause(record)` 成因闭集四词:`cancelled`(码 `cancelled`,带现场)/ `engine_error`(别的码,原码开集不枚举)/ `run_still_live`(记录仍非终态 —— 断连是客户端侧事实,不是 run 的成因)/ `unknown`(不猜)。常量 `RUN_CANCELLED_CODE`。
|
|
60
|
+
- **`FleetWorkflowView.errorCode?`(CC-72)**:workflow 列表行的 run 级 errorCode 原词逐字过境(开集);缺席 = 无码,**不是成功也不是失败**;sdk 未声明前按结构读(空串 / 非串 ⇒ 缺席)。
|
|
61
|
+
- **`lastFlagValue` 上公面(CC-80;归层)**:argv 末次旗值扫描原语(`--name v` / `--name=v`、裸 `--` 之后不扫、重复旗末次胜、缺值策略 `invalidate` / `latch-previous` 调用方选)—— 包内 limits / sandbox / scenario / headlessPermissionMode 四处早已收编,端侧仍在手写同一循环;现在从 `index` 导出,`FlagValue` / `OnMissingFlagValue` 型同出。
|
|
62
|
+
- **reopen 族第二臂(CC-73)**:① `DecideReceiptView` +`errorCode?` / `retriable?`(`/decide` 200 **终局形**才有;在场逐字,`retriable` 严格布尔)+ `decideReceiptReopen(receipt)` —— 与 `resumeReopenFromError` **同一只**判官(`status === 'failed'` ∧ 码 ∈ `RESUME_REOPEN_CODES` ∧ `retriable === true`,缺一件 ⇒ null);② `suspendedReopenOf(ev)` 读 durable `suspended.reopened` 三态(`reopened{code}` / `not_reopened`(显式 null)/ `unstated`(键缺席 —— 不冒充 null));`HitlBridge.currentGateReopen()` 把它挂在挂起座上,每条 `suspended` 重读、随座清。
|
|
63
|
+
|
|
64
|
+
### Fixed
|
|
65
|
+
|
|
66
|
+
- **异源对抗复审两轮抓的六条(CC-70 / CC-71)**:① 经退路键归一的 tick 之后 `end{UUID}` 归不了一(行永不结、报告丢)⇒ 归一时把 UUID → 尾段记进主表;② 复活后迟到的 `end{旧 UUID}` 不带周期 ⇒ 钥匙带周期身份、`end` 借用;③ 缓着的 tick 无时限 ⇒ 节拍上限 `MAX_HELD_WIRE_TICK_BEATS`(别的 tick 不算拍);④ 钥匙表 FIFO 会把仍在飞的子代挤掉 ⇒ LRU 续命、上限 2048;⑤ 第 16 拍恰是自己的行帧(退路键)时先记拍会让它过期 ⇒ 行帧先登记先放再记拍;⑥ 终态 tick / 通知的清标只清 UUID ⇒ 两把键都清;⑦(R3)前代 UUID 的终态清掉复活行的常驻标 ⇒ 下一次 sweep 假结它 ⇒ 清标走 `clearEnginePanelTaskResidentByWire`:归一键只在终态属当代时才清(判据与 `end` 陈旧判据同一只)。`classifyRunAbortCause`:result 已终态而外层 `status` 仍在飞词 = 证据矛盾 ⇒ `unknown`(带终态词),不说「还活着」。
|
|
67
|
+
- **0.74.2 补审(六只能力位读器)**:`projectDeviceExecutorManagementCapability` 外层 `deviceExecutor` 此前 undefined 判与取值各读一次 ⇒ 变化的 getter 把第一次观测到的「车道缺席」丢掉,现在只读一次;六只 doctor 的 `unobserved` 句此前说「还没收到 caps 回体」—— 坏形删格后也这么说 = 把解析失败说成没启动,改成「本进程没有可用的读数(没收到,或最后一次读不懂)」(措辞变化,消费方不接线零影响)。
|
|
68
|
+
- 面板常驻标「UUID 记、17 位清」永不清(CC-70 同根):tick 臂现在把常驻标记在归一后的键下(`noteResidency(taskId, residentKey)`),通知车道按尾段清得掉。
|
|
69
|
+
- `classifyRunAbortCause` 对平面 `running` / `queued` 记录:平面读器对在飞词答 `unknown{word}`,那不是「读不出」而是「还没结」⇒ 先看 `status` 再落 `unknown`。
|
|
70
|
+
|
|
71
|
+
## 0.74.2(2026-09-19)
|
|
72
|
+
|
|
73
|
+
> 主题:**`capabilities.deviceExecutor.management` 四态读口**(CC-69;server 7.88.0 点名本包)。**patch**:型面纯 additive(+6 函数,公面 1046 → 1052;测试钩 49 → 50;零 wire 新键;peer 不动)。
|
|
74
|
+
|
|
75
|
+
### Added
|
|
76
|
+
|
|
77
|
+
- **`src/deviceExecutorManagementCapability.ts`(CC-69;server ≥7.88.0,契约附录 B.1 `deviceExecutor` 行 + §14)**:「`/v1/devices/*` 的管理动词在不在」的四态读面,与 sql / writeProtection / webSearch.backend / executionLane / approvalsStreamLive 五只兄弟同构六导出 —— `projectDeviceExecutorManagementCapability` / `noteEngineCapsForDeviceExecutorManagement` / `observedDeviceExecutorManagement` / `forgetDeviceExecutorManagementReading` / `deviceExecutorManagementDoctorDetail` / `deviceManagementVerbsAvailable`(+ 测试钩)。sdk 9.7.1 对该位无型 ⇒ 按契约结构读,sdk 补型当拍对表。
|
|
78
|
+
- 🔴 读法:`management` **键缺席 ≠ false**(真字节:7.87.3 车道真开而无此键)⇒ `not_reported`(`why: 'management_absent'`);`deviceExecutor` 键整个不存在(更老 server)⇒ `not_reported`(`why: 'capability_absent'`,只有夹具、无真版本见证);`deviceExecutor: false`(键在、值为布尔 false)⇒ `lane_absent`;对象且 `management` 严格布尔 ⇒ `present`。判在场用 own-property,不用 `in` 判真值;畸形删格。
|
|
79
|
+
- 「管理动词能不能用」判据归包 `deviceManagementVerbsAvailable(reading)`:只有 present ∧ true 才 `'yes'`;present ∧ false 或 `lane_absent` ⇒ `'no'`(明确的否);其余 ⇒ `'unknown'`(端按 501 试探,不预判)。
|
|
80
|
+
- doctor 措辞单源六句;`not_reported` 两句都不暗示「没有管理面」。
|
|
81
|
+
- 门:新 `scripts/run-device-executor-management-capability-test.mjs`(含 test 三形真字节夹具);变异反证八 + 六枚逐格见红。
|
|
82
|
+
|
|
83
|
+
### Changed(记账)
|
|
84
|
+
|
|
85
|
+
- **代际号的取值规则成文**(0.74.0 起的行为,补一句书面承诺):`fleet-row.cycleSeq` / `end.cycleSeq` / 后台通知 `seq` **只认正整数**;坏形(0 / 负数 / 小数 / 非数 / null)与缺席一律按**缺席**处理 —— 键不出,不钳、不编。去重键内部的归一(缺席归一成首周期)只服务去重,不上屏。
|
|
86
|
+
|
|
87
|
+
### Fixed(六只能力位读器族扫)
|
|
88
|
+
|
|
89
|
+
- **抛 getter / Proxy 的 caps 回体不再让上一台引擎的读数留在格里**:六只能力位读器(sql / writeProtection / webSearch.backend / executionLane / approvalsStreamLive / deviceExecutor.management)的 tee 此前把整段投影包在一个只求不抛的 `catch` 里,回体上一只抛出的 getter 会让投影中途退出而**格不动** —— 上一次的 `present` / `lane_absent` 继续答现在(与「投影答 undefined 就删格」的契约相反)。现在投影抛出与投影答 `undefined` 同一处置:删格 ⇒ `unobserved`;陈旧代际的抛体仍不动格;并且投影**之后再核一次代际**(投影会执行回体上不可信的 getter,它可以在中途让这一格换代)—— 换代后无论返回还是抛出,都不动新代际的格;代际只在入口**快照一次**(`opts` 是调用方的对象,回体 getter 改它也绕不过末核),读 opts / 读代际抛出一律不动格、不逃出 tee。
|
|
90
|
+
- **校验与返回同一次读**(sql / writeProtection / deviceExecutor.management):此前对同一属性各读一次,变化的 getter 可以让校验读到合形、返回读到别的形,`present` 里带出非布尔 / 非串。现在读进局部量再校验再返回。
|
|
91
|
+
|
|
52
92
|
## 0.74.1(2026-09-19)
|
|
53
93
|
|
|
54
94
|
> 主题:**审批卡的无决断收口**(CC-67)+ **流内帧腿的结局回交**(CC-68)+ headless 处置成文。**patch**:型面纯 additive(+1 常量,公面 1045 → 1046;`ApprovalCardDecision` +1 联合成员;`ToolApprovalFrameOutcome` / `FsApprovalOutcome` 各 +1;`AskGateWireDeps` +1 可选回调;零 wire 新键;peer 不动)。
|
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ Renamed from **`@sema-agent/wire-cc-adapter`** (0.1.x, deprecated — see *Migra
|
|
|
35
35
|
|
|
36
36
|
## Scope
|
|
37
37
|
|
|
38
|
-
**Version:** 0.74.
|
|
38
|
+
**Version:** 0.74.3
|
|
39
39
|
|
|
40
40
|
- **Today** — the adapter seam, the whole `adapt()` pipeline (all 14 A-layer arms plus the
|
|
41
41
|
B/D/E tool-card layers), the notification/caps/model families, the adapter kernel (stream driver
|
|
@@ -313,6 +313,10 @@ public-surface guard checks that last one).
|
|
|
313
313
|
| `scripts/run-tool-disclosure-progress-projection-test.mjs` | The two wire arms sdk 9.6.0 adds — `tool_disclosure` (name-only tool census: open-set `policy`, `thresholdPercent` absent ≠ default, `deferred`/`activated` full snapshots) and `tool_progress` (one frame, two beats: Bash ticks carry an output tail with `totalLines`/`totalBytes` that come and go together; other tools carry only `elapsedSeconds`) — project to neutral internal arms plus chrome arms. Required keys missing ⇒ `malformed`; bad optional keys drop only themselves; the sub-flow three-key gate keeps child frames off the leader lane; both arms are `required: false` in the arm table with duties stated (the output tail is untrusted raw and must never be fed back to the model). |
|
|
314
314
|
| `scripts/run-mcp-panel-projection-test.mjs` | The `GET /v1/sessions/:id/mcp` panel reader (`projectMcpPanel`; server >=7.77.0 adds the optional `lastLegMcp` key) and the single wording mint for its "last leg" line. Absence of `lastLegMcp` is one literal sentence that never blames the engine version (a new session, a leg outside the retention window, a leg without a manifest and an older engine all look the same on the wire); a key that is present but unreadable is a different sentence plus a `lastLegMcpUnreadable: true` mark, never folded into absence. The `mcp[]` roster goes through the same reader as the live `wiring_manifest` third section, so a replayed roster and a live one have one shape. The two faces of the panel (`servers[]` and the last-leg roster) may legitimately differ, so the view carries no agreement flag and none of the five sentences mentions `servers`. Required keys are pinned to the SDK `openapi.yaml` component bytes **0.69.0:** `fetchMcpPanel` fetches the panel through the SDK client's own `sessions.mcp` call (same transport and auth as every other read) and projects it; transport failure, an unreadable body and an empty session id all come back as `undefined`, never as a fabricated empty panel 0.71.0 adds section K: `mcpEngineLegPresence(view)` — the engine-side MCP presence tri-state read only off the panel view (`unknown` when the view could not be read, never rendered as "no MCP configured") |
|
|
315
315
|
| `scripts/run-absence-fold-census-test.mjs` | A package-wide census of the "absence folded into a positive outcome" defect shape, so that fixing the six sites this release does not merely move the shape somewhere else. The defect is defined by position, not syntax: a fallback position (the unconditional tail return, the `default:` arm, the literal minted when there is nothing to pass on, the value returned from an error path) may only say `unknown` or stay absent, never a positive word. Detection walks the syntax tree of every source file, so comments, strings and multi-line spellings cannot hide or fake a hit, and covers five forms: the right arm of `??` / `||`, the else arm of a ternary, the first return of an explicit `default:`, a `catch` block or `.catch(() => …)` arrow returning a healthy value, and a function whose last statement returns a positive word after other returns. Every remaining hit must be registered with a written reason, an unregistered hit fails the gate naming the file and line, the registered count must equal the real count so a cleared site cannot leave a spare allowance behind, and the gate proves its own teeth behind a fence (a failed self-proof refuses to report any count): each form injected into an in-memory copy must add exactly one hit, two correct spellings are pinned as non-hits, and samples inside comments or strings do not count. It also pins the headline site: the fleet panel projection no longer mints an `end` with `isError: false` on absence |
|
|
316
|
+
| `scripts/run-device-executor-management-capability-test.mjs` | The engine's device-management self-description (`capabilities.deviceExecutor.management`, engine ≥7.88.0), read the same four-state way as its five sibling capability readers: an absent `management` key is reported as not reported (never folded into `false`; an older engine really ships the lane object without it), an absent `deviceExecutor` key is likewise not reported, `deviceExecutor: false` is the lane being absent, presence is judged by own-property not truthiness, the value must be a strict boolean, the tee never throws and drops stale generations, and the package owns the verdict on whether the `/v1/devices` management verbs are usable (`yes` only when present and true, `no` when present-false or lane-absent, otherwise `unknown`) |
|
|
317
|
+
| `scripts/run-run-cancel-context-test.mjs` | The run record's `cancelContext` side-note (engine ≥7.87.3) read structurally, and the cause of a `turn_aborted{engine_error}` classified from machine-readable evidence only: `cancelled` (code `cancelled`, with the cancel-time context when present) / `engine_error` (any other failure code, passed through verbatim) / `run_still_live` (the record is not terminal — a dropped stream is a client-side fact, not the run's cause) / `unknown` (never guessed). An absent `cancelContext` reads as *not reported*, never as "not cancelled"; `elapsedMs` is never folded to 0. |
|
|
318
|
+
| `scripts/run-suspended-reopen-projection-test.mjs` | The durable `suspended` event's `reopened` key read as three distinct states — `reopened` (with the engine's code, verbatim), `not_reopened` (an explicit `null`), `unstated` (key absent or unreadable) — and carried on the HITL bridge's active gate (`currentGateReopen()`), re-read on every `suspended` and cleared with the gate. |
|
|
319
|
+
| `scripts/run-panel-identity-normalization-test.mjs` | One background subagent has two ids on the wire — the fleet row id tail and the `task_progress` task id (its transcript id). Every panel event goes through one funnel that rewrites the `tick` / `end` task id onto the fleet row's id once a `fleet-row` has registered the key (`transcriptId` first, `parentToolCallId` as the fallback), carrying the original as `wireTaskId` and marking `taskIdOrigin`; an unbound tick whose row has not arrived yet waits one beat (bounded) and is released verbatim on the next tick / `end`, when the buffer is full, or after `MAX_HELD_WIRE_TICK_BEATS` other fleet-row / end / sweep events (a `sweep` itself leaves it alone: there is no row to settle yet); a normalized `end` that carries no cycle identity borrows the registering row's, so a late close of a revived task is recognized as stale; the key table is an LRU (a task that keeps ticking is never evicted by newer registrations); the residency mark migrates with the id and both keys are cleared on settle — except that a stale (previous-cycle) terminal never clears the revived row's mark — so the notification lane can clear it. |
|
|
316
320
|
| `scripts/run-approval-card-retract-test.mjs` | The approval card's **decision-free retraction** and the in-stream frame leg's **outcome hand-back**: a host that must withdraw a card that no longer has a decision channel (session switch, engine switch, a tracker reporting the ask gone) answers `{ kind: 'retracted' }` and the package sends nothing on any of the three legs (in-stream frame, suspended ask, durable park), reporting `decision: 'unresolved'` with a `retracted` flag; `aborted` / `failed` / `deny` keep their meaning (a real deny is still posted), and `onToolApprovalOutcome` hands every in-stream outcome back to the host exactly once, tolerating a throwing or rejecting callback |
|
|
317
321
|
| `scripts/run-panel-cycle-identity-test.mjs` | The **cycle identity** on agent-panel events and the fleet ledger's **departure read-out**: a background agent may be revived under the same id, so `fleet-row` and `end` events now carry the wire's own `cycleSeq` / `startedAt` when present (absent means the row has no notion of generations, never "generation one"), `isStaleEngineAgentPanelEnd` is the single rule for ignoring a late `end` from a previous cycle (only when both sides carry a comparable identity; absence never drops a real terminal), a changed `cycleSeq` is a new cycle for usage stickiness and buffer coalescing, the notification lane carries `cycleSeq` only when the wire really sent `seq`, and `task_remove` frames reach the host through `onTaskRemoved` with `removeReason` / `cycleSeq` verbatim, a stale previous-generation removal leaving the newer row in place |
|
|
318
322
|
| `scripts/run-workflow-size-warning-test.mjs` | The **workflow size warning** verdict shared by every host footer / panel: a three-state result (`warn` / `ok` / `unknown`) read off the optional fleet view keys, where an unknown size is never reported as a normal one (absent `totalCount` / `tokens` without positive over-cap evidence is `unknown`, naming the missing keys), positive evidence on either axis wins regardless of absent keys, the per-agent denominator uses the engine's started count only when it is not below done+failed (a smaller value is a stale reading), otherwise falls back to the done+failed lower bound only when both keys are present — and a lower-bound denominator only yields an upper bound of the projection, which can prove *within cap* (`ok`, flagged) but never *over cap* (`unknown`, with the upper bound exposed) — and the prior is used only when the engine itself reports zero started agents; cap precedence env > explicit guideline > default, prototype keys never act as a guideline, the env reader is pure and does not fall through to the second name on a bad first value; caps, guideline table, env names and the three copy variants are single-sourced |
|
package/dist/adapt/arms.js
CHANGED
|
@@ -10,6 +10,7 @@ import { readAsyncLaunchedAgentReceipt } from '../toolResult.js';
|
|
|
10
10
|
import { isWorkflowAgentTaskId, recordWorkflowAgentTaskId } from '../workflow.js';
|
|
11
11
|
// L-215+(0.65.0;core [6908]):终局状态词的单铸谓词(三处同形一次改齐)。
|
|
12
12
|
import { isTerminalStatus } from '../runTerminal.js';
|
|
13
|
+
import { resolveEnginePanelTaskId } from '../engineAgentPanelStore.js';
|
|
13
14
|
import { chrome, MAIN, transcript, messageIdentityOf } from './ids.js';
|
|
14
15
|
import { CANCEL_MESSAGE, decisionOf, flattenWireOutput, REJECT_MESSAGE, sanitizeToolUseBlock, shortTaskLabel, TASK_TOOL_NAMES, WORKFLOW_TOOL_NAMES, } from './wireShapes.js';
|
|
15
16
|
// ══ M2/M1 —— assistant(tool_use 半场 + durable 文本/思考半场)══════════════════════════════════
|
|
@@ -831,7 +832,9 @@ const taskProgressArm = function* (m, { ctx, cards, panel, inst }) {
|
|
|
831
832
|
yield* panel.start(taskId, agentType);
|
|
832
833
|
}
|
|
833
834
|
// #6 常驻台账的两个写口整臂让位给 M3(module store 不许两个模块各写各的)。
|
|
834
|
-
|
|
835
|
+
// 0.74.3 CC-70:常驻标记在**归一后的键**下(fleet 尾段 = 通知车道清标用的那把);此前 UUID 记、17 位清 ⇒ 永不清。
|
|
836
|
+
const identity = resolveEnginePanelTaskId(taskId, explicitParent);
|
|
837
|
+
panel.noteResidency(taskId, identity.taskId);
|
|
835
838
|
panel.markLive(taskId);
|
|
836
839
|
yield chrome({
|
|
837
840
|
kind: 'panel_task',
|
|
@@ -843,6 +846,9 @@ const taskProgressArm = function* (m, { ctx, cards, panel, inst }) {
|
|
|
843
846
|
...(description !== undefined ? { description } : {}),
|
|
844
847
|
...(prompt !== undefined ? { prompt } : {}),
|
|
845
848
|
...(currentAction !== undefined ? { currentAction } : {}),
|
|
849
|
+
// 0.74.3 CC-70:身份归一的两把钥匙 —— 退路键 + 发布方提示(绑卡 = 同步委派,漏斗不用等 fleet 行)。
|
|
850
|
+
...(explicitParent !== undefined ? { parentToolCallId: explicitParent } : {}),
|
|
851
|
+
...(panel.isLiveBound(taskId) ? { cardBound: true } : {}),
|
|
846
852
|
// 🔴 这里的 `: 0` 与 `fleetAgentPanelProjection` 那句「绝不在本层 `?? 0`」**不矛盾**,
|
|
847
853
|
// 因为不是同一条 lane 的同一种供给形(两层的分层实情写在 `engineAgentPanelStore.ts`
|
|
848
854
|
// 的 `PANEL_TOOLUSES_LANE_POLICY` 上,两条 lane 的可选性由编译钉锁住):
|
|
@@ -59,7 +59,8 @@ export interface PanelTaskLedger {
|
|
|
59
59
|
/** 登记「本 turn 发布过面板行」。 */
|
|
60
60
|
markLive(taskId: string): void;
|
|
61
61
|
/** #6 常驻台账:live-bound ⇒ 清常驻;inert/unbound ⇒ 记常驻(module store 的**唯一**写口之一)。 */
|
|
62
|
-
|
|
62
|
+
/** 0.74.3 CC-70:`residentKey` = 常驻标该记在哪把键下(归一后的 fleet 尾段);缺省 = taskId。绑卡判定仍按 tick 的 taskId。 */
|
|
63
|
+
noteResidency(taskId: string, residentKey?: string): void;
|
|
63
64
|
/** SubagentStart(类型恒记,fire 一生一次)。 */
|
|
64
65
|
start(taskId: string, agentType: string): Generator<AdapterOutput>;
|
|
65
66
|
/** 关卡 settle(cardId)或 turn 末/abort 防御 sweep(cardId===null)。 */
|
package/dist/adapt/panelTasks.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { clearEnginePanelTaskResident, markEnginePanelTaskResident } from '../engineAgentPanelStore.js';
|
|
1
|
+
import { clearEnginePanelTaskResident, clearEnginePanelTaskResidentByWire, markEnginePanelTaskResident } from '../engineAgentPanelStore.js';
|
|
2
2
|
import { chrome, MAIN } from './ids.js';
|
|
3
3
|
import { SUBAGENT_TOOL_NAMES } from './wireShapes.js';
|
|
4
4
|
export function createPanelTaskLedger(ctx, cards, inst) {
|
|
@@ -16,6 +16,8 @@ export function createPanelTaskLedger(ctx, cards, inst) {
|
|
|
16
16
|
* 与 unbound 行是 **session 常驻**:终态只认 task_notification / bg_notification。
|
|
17
17
|
*/
|
|
18
18
|
const liveBoundPanelTasks = new Set();
|
|
19
|
+
/** 0.74.3 CC-70(异源复审 R2 [medium] / R3 [high]):常驻标可能记在归一键(fleet 尾段)下 —— 两把键都清,但前代终态不清当代的尾段键(判据在 store,单源)。 */
|
|
20
|
+
const clearResidencyBothKeys = (taskId) => clearEnginePanelTaskResidentByWire(taskId);
|
|
19
21
|
/** taskId → 子代类型(Stop hook 查它;cli 同为 turn 局部)。 */
|
|
20
22
|
const subagentTypeById = new Map();
|
|
21
23
|
const laneOf = (taskId) => {
|
|
@@ -98,11 +100,11 @@ export function createPanelTaskLedger(ctx, cards, inst) {
|
|
|
98
100
|
},
|
|
99
101
|
// #6 session 常驻台账:live-bound(卡本 turn 开着)= 本 turn 生命周期,turn 末可 sweep;
|
|
100
102
|
// inert/unbound(跨 turn bg 子代形)= 常驻,两处 sweep 都放行,终态只认通知帧。
|
|
101
|
-
noteResidency: (taskId) => {
|
|
103
|
+
noteResidency: (taskId, residentKey = taskId) => {
|
|
102
104
|
if (liveBoundPanelTasks.has(taskId))
|
|
103
|
-
clearEnginePanelTaskResident(
|
|
105
|
+
clearEnginePanelTaskResident(residentKey);
|
|
104
106
|
else
|
|
105
|
-
markEnginePanelTaskResident(
|
|
107
|
+
markEnginePanelTaskResident(residentKey);
|
|
106
108
|
},
|
|
107
109
|
/** SubagentStart(cli fireSubagentStartHook 的臂化):类型恒记,fire 一生一次。 */
|
|
108
110
|
*start(taskId, agentType) {
|
|
@@ -173,7 +175,7 @@ export function createPanelTaskLedger(ctx, cards, inst) {
|
|
|
173
175
|
*settleFromNotification(taskId, isError) {
|
|
174
176
|
// #6 通知-settle 边(推送半场):session 常驻的 bg 子代行不再被 turn sweep 假结,
|
|
175
177
|
// 真终态在此落行(failed/killed ⇒ isError 真归因)。
|
|
176
|
-
|
|
178
|
+
clearResidencyBothKeys(taskId);
|
|
177
179
|
if (endedPanelTasks.has(taskId))
|
|
178
180
|
return;
|
|
179
181
|
endedPanelTasks.add(taskId);
|
|
@@ -209,7 +211,7 @@ export function createPanelTaskLedger(ctx, cards, inst) {
|
|
|
209
211
|
if (endedPanelTasks.has(taskId))
|
|
210
212
|
return;
|
|
211
213
|
endedPanelTasks.add(taskId);
|
|
212
|
-
|
|
214
|
+
clearResidencyBothKeys(taskId);
|
|
213
215
|
const lane = laneOf(taskId);
|
|
214
216
|
const card = taskCardBinding.get(taskId);
|
|
215
217
|
if (card !== undefined) {
|
|
@@ -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 { snapshotCapsGeneration, capsGenerationStillCurrent } from './engineCapsGenerationGuard.js';
|
|
17
17
|
/** caps 回体 → 本格读数;畸形一律 `undefined`(= 这一格不写 ⇒ 读口答 `unobserved`)。 */
|
|
18
18
|
export function projectApprovalsStreamLiveCapability(caps) {
|
|
19
19
|
if (caps === null || typeof caps !== 'object')
|
|
@@ -30,21 +30,32 @@ export function projectApprovalsStreamLiveCapability(caps) {
|
|
|
30
30
|
const readingByBase = new Map();
|
|
31
31
|
/** 宿主 caps probe 的读面 tee 落点(与四只兄弟并列)。绝不 throw;畸形 ⇒ 删格;`opts.generation` 关掉旧探测覆盖新读数的竞态。 */
|
|
32
32
|
export function noteEngineCapsForApprovalsStreamLive(baseUrl, caps, opts) {
|
|
33
|
+
if (typeof baseUrl !== 'string' || baseUrl === '')
|
|
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;
|
|
33
43
|
try {
|
|
34
|
-
|
|
35
|
-
return;
|
|
36
|
-
if (opts?.generation !== undefined && opts.generation !== engineCapsGeneration(baseUrl))
|
|
37
|
-
return;
|
|
38
|
-
const reading = projectApprovalsStreamLiveCapability(caps);
|
|
39
|
-
if (reading === undefined) {
|
|
40
|
-
readingByBase.delete(baseUrl);
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
readingByBase.set(baseUrl, reading);
|
|
44
|
+
reading = projectApprovalsStreamLiveCapability(caps);
|
|
44
45
|
}
|
|
45
46
|
catch {
|
|
46
|
-
|
|
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;
|
|
47
57
|
}
|
|
58
|
+
readingByBase.set(baseUrl, reading);
|
|
48
59
|
}
|
|
49
60
|
/** 本进程观测到的读数;这一格空缺 ⇒ `{kind:'unobserved'}`。 */
|
|
50
61
|
export function observedApprovalsStreamLive(baseUrl = engineWireTarget()?.baseUrl) {
|
|
@@ -63,7 +74,7 @@ export function livePendingNeedsReconcile(reading) {
|
|
|
63
74
|
export function approvalsStreamLiveDoctorDetail(reading) {
|
|
64
75
|
switch (reading.kind) {
|
|
65
76
|
case 'unobserved':
|
|
66
|
-
return 'live approval push not observed — the engine reports it on /v1/capabilities; this process has
|
|
77
|
+
return 'live approval push not observed — the engine reports it on /v1/capabilities; this process has no usable capabilities reading cached for it (none received, or the last one was unreadable), so suspended asks are pulled by the reconcile cadence';
|
|
67
78
|
case 'not_reported':
|
|
68
79
|
return 'live approval push not reported by this engine — only newer engines advertise it; this does not say whether it pushes, so suspended asks are pulled by the reconcile cadence';
|
|
69
80
|
case 'present':
|
package/dist/decideReceipt.d.ts
CHANGED
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
* 消费端渲卡时:**在场**才说「执行时被放行/被否」,缺席一个字都不说。
|
|
33
33
|
*/
|
|
34
34
|
import { type GateOutcomeView } from './gateOutcome.js';
|
|
35
|
+
import { type ResumeReopenDetail } from './resumeRefusalCopy.js';
|
|
35
36
|
/** 一次 `/decide` 200 的读数。**每一位都可能缺席**(四种形没有共同保证的键)。 */
|
|
36
37
|
export interface DecideReceiptView {
|
|
37
38
|
/** 回体上的 `status`(开集透传;受理形与 parked 回执上是 `"resuming"`,回放形上**没有**这一位)。 */
|
|
@@ -60,6 +61,13 @@ export interface DecideReceiptView {
|
|
|
60
61
|
* 🔴 **缺席 = 未知,禁读成 allowed**(见模块顶注的两处上游逐字)。
|
|
61
62
|
*/
|
|
62
63
|
executionOutcome?: GateOutcomeView;
|
|
64
|
+
/**
|
|
65
|
+
* 0.74.3 CC-73:**终局形**才有的两位(契约:`verify:true` / 无 run store 的同步长调用在 200 体上回
|
|
66
|
+
* `{ status:"failed", errorCode, retriable:true }`;受理形刻意不带)。在场逐字,缺席不落键;`retriable` 严格布尔,
|
|
67
|
+
* `false` 是 server 说的照带。判「这是不是 reopen 类失败」走 {@link decideReceiptReopen},别在端上凭码猜。
|
|
68
|
+
*/
|
|
69
|
+
errorCode?: string;
|
|
70
|
+
retriable?: boolean;
|
|
63
71
|
}
|
|
64
72
|
/**
|
|
65
73
|
* `/decide` 的 200 体 → {@link DecideReceiptView};不是一只对象 / 一位都读不出 ⇒ `undefined`,
|
|
@@ -70,6 +78,13 @@ export interface DecideReceiptView {
|
|
|
70
78
|
* 🔴 **不补默认、不折缺席**:读不出 `status` 不等于终局,读不出 `executionOutcome` 不等于 allowed。
|
|
71
79
|
*/
|
|
72
80
|
export declare function readDecideReceipt(result: unknown): DecideReceiptView | undefined;
|
|
81
|
+
/**
|
|
82
|
+
* 0.74.3 CC-73(cli L-56 ①):`/decide` 200 **终局形**是不是 reopen 类失败 —— 三件合取,与 {@link resumeReopenFromError}
|
|
83
|
+
* **同一只**判官(码 ∈ `RESUME_REOPEN_CODES` ∧ `retriable === true`),外加终局形自己的 `status === 'failed'`。
|
|
84
|
+
* 任一件缺 ⇒ `null`:受理形带同码不算(它不是终局)、`retriable` 缺席 / false 不算(凭码不说「卡仍 pending」)。
|
|
85
|
+
* 读数在场 ⇒ 卡仍 pending、决定没被消费、可再决;文案走 `resumeReopenContent(detail)`。
|
|
86
|
+
*/
|
|
87
|
+
export declare function decideReceiptReopen(receipt: DecideReceiptView | undefined): ResumeReopenDetail | null;
|
|
73
88
|
/**
|
|
74
89
|
* 🔴 **这次 decide 的 200 有没有把门解决掉** —— 唯一的诚实答案是「**说不出来**」。
|
|
75
90
|
*
|
package/dist/decideReceipt.js
CHANGED
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
* 消费端渲卡时:**在场**才说「执行时被放行/被否」,缺席一个字都不说。
|
|
33
33
|
*/
|
|
34
34
|
import { gateOutcomeOf } from './gateOutcome.js';
|
|
35
|
+
import { resumeReopenFromError } from './resumeRefusalCopy.js';
|
|
35
36
|
import { DECIDE_WORKFLOW_HOST_UNKNOWN, DECIDE_WORKFLOW_LANE_CODES, DECIDE_WORKFLOW_REMEMBER_UNSUPPORTED, } from './engineErrorCodes.js';
|
|
36
37
|
/** 受理形/parked 回执上那个**唯一**表示「受理了、还没做完」的状态词(sdk README §9.0.0 逐字)。 */
|
|
37
38
|
const STATUS_RESUMING = 'resuming';
|
|
@@ -63,11 +64,15 @@ export function readDecideReceipt(result) {
|
|
|
63
64
|
const executionOutcome = Object.hasOwn(r, 'executionOutcome')
|
|
64
65
|
? gateOutcomeOf({ gate: r.executionOutcome })
|
|
65
66
|
: undefined;
|
|
67
|
+
const errorCode = Object.hasOwn(r, 'errorCode') ? str(r.errorCode) : undefined;
|
|
68
|
+
const retriable = Object.hasOwn(r, 'retriable') && typeof r.retriable === 'boolean' ? r.retriable : undefined;
|
|
66
69
|
if (status === undefined &&
|
|
67
70
|
taskId === undefined &&
|
|
68
71
|
sessionId === undefined &&
|
|
69
72
|
idempotent === undefined &&
|
|
70
|
-
executionOutcome === undefined
|
|
73
|
+
executionOutcome === undefined &&
|
|
74
|
+
errorCode === undefined &&
|
|
75
|
+
retriable === undefined) {
|
|
71
76
|
return undefined;
|
|
72
77
|
}
|
|
73
78
|
return {
|
|
@@ -77,8 +82,23 @@ export function readDecideReceipt(result) {
|
|
|
77
82
|
...(idempotent !== undefined ? { idempotent } : {}),
|
|
78
83
|
...(handoffTaskId !== undefined ? { handoffTaskId } : {}),
|
|
79
84
|
...(executionOutcome !== undefined ? { executionOutcome } : {}),
|
|
85
|
+
...(errorCode !== undefined ? { errorCode } : {}),
|
|
86
|
+
...(retriable !== undefined ? { retriable } : {}),
|
|
80
87
|
};
|
|
81
88
|
}
|
|
89
|
+
/** 200 终局形上「这是不是 reopen 类失败」的判据词(契约:reopen 类失败仍 `suspended` 且锁在手,200 体 `status:"failed"`)。 */
|
|
90
|
+
const STATUS_FAILED = 'failed';
|
|
91
|
+
/**
|
|
92
|
+
* 0.74.3 CC-73(cli L-56 ①):`/decide` 200 **终局形**是不是 reopen 类失败 —— 三件合取,与 {@link resumeReopenFromError}
|
|
93
|
+
* **同一只**判官(码 ∈ `RESUME_REOPEN_CODES` ∧ `retriable === true`),外加终局形自己的 `status === 'failed'`。
|
|
94
|
+
* 任一件缺 ⇒ `null`:受理形带同码不算(它不是终局)、`retriable` 缺席 / false 不算(凭码不说「卡仍 pending」)。
|
|
95
|
+
* 读数在场 ⇒ 卡仍 pending、决定没被消费、可再决;文案走 `resumeReopenContent(detail)`。
|
|
96
|
+
*/
|
|
97
|
+
export function decideReceiptReopen(receipt) {
|
|
98
|
+
if (receipt === undefined || receipt.status !== STATUS_FAILED)
|
|
99
|
+
return null;
|
|
100
|
+
return resumeReopenFromError(receipt);
|
|
101
|
+
}
|
|
82
102
|
/**
|
|
83
103
|
* 🔴 **这次 decide 的 200 有没有把门解决掉** —— 唯一的诚实答案是「**说不出来**」。
|
|
84
104
|
*
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* deviceExecutorManagementCapability — `GET /v1/capabilities.deviceExecutor.management`(server ≥7.88.0;契约附录 B.1
|
|
3
|
+
* `deviceExecutor` 行 + §14)的三端共用读面(0.74.2;与 sql / writeProtection / webSearch.backend / executionLane /
|
|
4
|
+
* approvalsStreamLive 五只兄弟同一套四态词汇)。sdk 对该位无型(9.7.1)⇒ 按契约结构读,sdk 补型当拍对表。
|
|
5
|
+
*
|
|
6
|
+
* ── 这一位答的是哪一个问题 ────────────────────────────────────────────────────────────────
|
|
7
|
+
* 「`/v1/devices/*` 的管理动词在这台 server 上在不在」(present-iff:与该族 501 判据逐字同源)。它挂在 `deviceExecutor`
|
|
8
|
+
* 对象里:`deviceExecutor === false` = 本部署**没有 device 车道**(管理面无从谈起,六动词一律 501 `capability.device_lane_required`);
|
|
9
|
+
* 对象在场但 `management` 键缺席 = 7.88.0 之前的 server,**判不出**,只能按 501 试探。
|
|
10
|
+
*
|
|
11
|
+
* ── 🔴 读法 ─────────────────────────────────────────────────────────────────────────────────
|
|
12
|
+
* ① `management` **键缺席 ≠ false**(真字节:7.87.3 车道真开而无此键)⇒ `not_reported`;`deviceExecutor` 键整个不存在
|
|
13
|
+
* (更老 server)同样 `not_reported`(判别子 `why` 分开记,后者只有夹具、无真版本见证)。
|
|
14
|
+
* ② `deviceExecutor: false` 是**键在、值为布尔 false**(不是键被省掉)⇒ `lane_absent`;判在场用 `hasOwn`,不用 `in` 判真值。
|
|
15
|
+
* ③ `management` 严格布尔;`null` / 串 / 数 / 数组 / 对象 ⇒ 畸形(删格 ⇒ `unobserved`);`deviceExecutor` 非 false 非对象同畸形。
|
|
16
|
+
* ④ 「管理动词能不能用」的判据归包:{@link deviceManagementVerbsAvailable} —— 只有 present ∧ true 才 `'yes'`;
|
|
17
|
+
* present ∧ false 与 `lane_absent` ⇒ `'no'`(明确的否);其余 ⇒ `'unknown'`(别当 no,也别当 yes)。
|
|
18
|
+
*/
|
|
19
|
+
/** 四态读数。`unobserved` 由读口在这一格空缺时铸,不由投影铸。 */
|
|
20
|
+
export type DeviceExecutorManagementReading = {
|
|
21
|
+
kind: 'unobserved';
|
|
22
|
+
}
|
|
23
|
+
/** `why`:`capability_absent` = `deviceExecutor` 键整个不存在(更老 server);`management_absent` = 车道对象在场、位缺席(7.88.0 之前)。 */
|
|
24
|
+
| {
|
|
25
|
+
kind: 'not_reported';
|
|
26
|
+
why: 'capability_absent' | 'management_absent';
|
|
27
|
+
}
|
|
28
|
+
/** `deviceExecutor === false`:本部署没有 device 车道。 */
|
|
29
|
+
| {
|
|
30
|
+
kind: 'lane_absent';
|
|
31
|
+
} | {
|
|
32
|
+
kind: 'present';
|
|
33
|
+
management: boolean;
|
|
34
|
+
};
|
|
35
|
+
/** caps 回体 → 本格读数;畸形一律 `undefined`(= 这一格不写 ⇒ 读口答 `unobserved`)。 */
|
|
36
|
+
export declare function projectDeviceExecutorManagementCapability(caps: unknown): DeviceExecutorManagementReading | undefined;
|
|
37
|
+
/** 宿主 caps probe 的读面 tee 落点(与五只兄弟并列)。绝不 throw;畸形 ⇒ 删格;`opts.generation` 关掉旧探测覆盖新读数的竞态。 */
|
|
38
|
+
export declare function noteEngineCapsForDeviceExecutorManagement(baseUrl: string, caps: unknown, opts?: {
|
|
39
|
+
generation?: number;
|
|
40
|
+
}): void;
|
|
41
|
+
/** 本进程观测到的读数;这一格空缺 ⇒ `{kind:'unobserved'}`。 */
|
|
42
|
+
export declare function observedDeviceExecutorManagement(baseUrl?: string | undefined): DeviceExecutorManagementReading;
|
|
43
|
+
/**
|
|
44
|
+
* 「`/v1/devices/*` 管理动词能不能用」的判据单源(三态):只有引擎明说在场(present ∧ true)才 `'yes'`;
|
|
45
|
+
* 明说不在(present ∧ false)或没有 device 车道 ⇒ `'no'`;老 server / 从没观测 ⇒ `'unknown'`(端按 501 试探,不预判)。
|
|
46
|
+
*/
|
|
47
|
+
export declare function deviceManagementVerbsAvailable(reading: DeviceExecutorManagementReading): 'yes' | 'no' | 'unknown';
|
|
48
|
+
/** doctor 那一行的 detail 串,唯一措辞真源。🔴 `not_reported` 句不许暗示「没有管理面」。 */
|
|
49
|
+
export declare function deviceExecutorManagementDoctorDetail(reading: DeviceExecutorManagementReading): string;
|
|
50
|
+
/** 换代失效口(引擎温切成功后调):清成未观测。空串 ⇒ no-op;绝不 throw。 */
|
|
51
|
+
export declare function forgetDeviceExecutorManagementReading(baseUrl: string | undefined): void;
|
|
52
|
+
/** 测试钩子。 */
|
|
53
|
+
export declare function __resetDeviceExecutorManagementReadingsForTests(): void;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* deviceExecutorManagementCapability — `GET /v1/capabilities.deviceExecutor.management`(server ≥7.88.0;契约附录 B.1
|
|
3
|
+
* `deviceExecutor` 行 + §14)的三端共用读面(0.74.2;与 sql / writeProtection / webSearch.backend / executionLane /
|
|
4
|
+
* approvalsStreamLive 五只兄弟同一套四态词汇)。sdk 对该位无型(9.7.1)⇒ 按契约结构读,sdk 补型当拍对表。
|
|
5
|
+
*
|
|
6
|
+
* ── 这一位答的是哪一个问题 ────────────────────────────────────────────────────────────────
|
|
7
|
+
* 「`/v1/devices/*` 的管理动词在这台 server 上在不在」(present-iff:与该族 501 判据逐字同源)。它挂在 `deviceExecutor`
|
|
8
|
+
* 对象里:`deviceExecutor === false` = 本部署**没有 device 车道**(管理面无从谈起,六动词一律 501 `capability.device_lane_required`);
|
|
9
|
+
* 对象在场但 `management` 键缺席 = 7.88.0 之前的 server,**判不出**,只能按 501 试探。
|
|
10
|
+
*
|
|
11
|
+
* ── 🔴 读法 ─────────────────────────────────────────────────────────────────────────────────
|
|
12
|
+
* ① `management` **键缺席 ≠ false**(真字节:7.87.3 车道真开而无此键)⇒ `not_reported`;`deviceExecutor` 键整个不存在
|
|
13
|
+
* (更老 server)同样 `not_reported`(判别子 `why` 分开记,后者只有夹具、无真版本见证)。
|
|
14
|
+
* ② `deviceExecutor: false` 是**键在、值为布尔 false**(不是键被省掉)⇒ `lane_absent`;判在场用 `hasOwn`,不用 `in` 判真值。
|
|
15
|
+
* ③ `management` 严格布尔;`null` / 串 / 数 / 数组 / 对象 ⇒ 畸形(删格 ⇒ `unobserved`);`deviceExecutor` 非 false 非对象同畸形。
|
|
16
|
+
* ④ 「管理动词能不能用」的判据归包:{@link deviceManagementVerbsAvailable} —— 只有 present ∧ true 才 `'yes'`;
|
|
17
|
+
* present ∧ false 与 `lane_absent` ⇒ `'no'`(明确的否);其余 ⇒ `'unknown'`(别当 no,也别当 yes)。
|
|
18
|
+
*/
|
|
19
|
+
import { engineWireTarget } from './engineWireTarget.js';
|
|
20
|
+
import { snapshotCapsGeneration, capsGenerationStillCurrent } from './engineCapsGenerationGuard.js';
|
|
21
|
+
/** caps 回体 → 本格读数;畸形一律 `undefined`(= 这一格不写 ⇒ 读口答 `unobserved`)。 */
|
|
22
|
+
export function projectDeviceExecutorManagementCapability(caps) {
|
|
23
|
+
if (caps === null || typeof caps !== 'object')
|
|
24
|
+
return undefined;
|
|
25
|
+
const c = caps;
|
|
26
|
+
if (!Object.hasOwn(c, 'deviceExecutor'))
|
|
27
|
+
return { kind: 'not_reported', why: 'capability_absent' };
|
|
28
|
+
// 🔴 外层也只读一次(0.74.2 补审 [medium]):hasOwn 之后 `deviceExecutor` 读进局部量,undefined / false / 对象三判都对同一次读;
|
|
29
|
+
// 此前 undefined 判与取值各读一次,变化的 getter 会把第一次观测到的「车道缺席」丢掉。
|
|
30
|
+
const d = c.deviceExecutor;
|
|
31
|
+
if (d === undefined)
|
|
32
|
+
return { kind: 'not_reported', why: 'capability_absent' };
|
|
33
|
+
if (d === false)
|
|
34
|
+
return { kind: 'lane_absent' };
|
|
35
|
+
if (d === null || typeof d !== 'object' || Array.isArray(d))
|
|
36
|
+
return undefined;
|
|
37
|
+
const o = d;
|
|
38
|
+
if (!Object.hasOwn(o, 'management'))
|
|
39
|
+
return { kind: 'not_reported', why: 'management_absent' };
|
|
40
|
+
// 🔴 只读一次(异源对抗复审 R1):校验与返回若各读一次,变化的 getter 会让校验读到布尔、返回读到串。
|
|
41
|
+
const mgmt = o.management;
|
|
42
|
+
if (mgmt === undefined)
|
|
43
|
+
return { kind: 'not_reported', why: 'management_absent' };
|
|
44
|
+
if (typeof mgmt !== 'boolean')
|
|
45
|
+
return undefined;
|
|
46
|
+
return { kind: 'present', management: mgmt };
|
|
47
|
+
}
|
|
48
|
+
const readingByBase = new Map();
|
|
49
|
+
/** 宿主 caps probe 的读面 tee 落点(与五只兄弟并列)。绝不 throw;畸形 ⇒ 删格;`opts.generation` 关掉旧探测覆盖新读数的竞态。 */
|
|
50
|
+
export function noteEngineCapsForDeviceExecutorManagement(baseUrl, caps, opts) {
|
|
51
|
+
if (typeof baseUrl !== 'string' || baseUrl === '')
|
|
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);
|
|
77
|
+
}
|
|
78
|
+
/** 本进程观测到的读数;这一格空缺 ⇒ `{kind:'unobserved'}`。 */
|
|
79
|
+
export function observedDeviceExecutorManagement(baseUrl = engineWireTarget()?.baseUrl) {
|
|
80
|
+
if (typeof baseUrl !== 'string' || baseUrl === '')
|
|
81
|
+
return { kind: 'unobserved' };
|
|
82
|
+
return readingByBase.get(baseUrl) ?? { kind: 'unobserved' };
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 「`/v1/devices/*` 管理动词能不能用」的判据单源(三态):只有引擎明说在场(present ∧ true)才 `'yes'`;
|
|
86
|
+
* 明说不在(present ∧ false)或没有 device 车道 ⇒ `'no'`;老 server / 从没观测 ⇒ `'unknown'`(端按 501 试探,不预判)。
|
|
87
|
+
*/
|
|
88
|
+
export function deviceManagementVerbsAvailable(reading) {
|
|
89
|
+
if (reading.kind === 'present')
|
|
90
|
+
return reading.management ? 'yes' : 'no';
|
|
91
|
+
if (reading.kind === 'lane_absent')
|
|
92
|
+
return 'no';
|
|
93
|
+
return 'unknown';
|
|
94
|
+
}
|
|
95
|
+
/** doctor 那一行的 detail 串,唯一措辞真源。🔴 `not_reported` 句不许暗示「没有管理面」。 */
|
|
96
|
+
export function deviceExecutorManagementDoctorDetail(reading) {
|
|
97
|
+
switch (reading.kind) {
|
|
98
|
+
case 'unobserved':
|
|
99
|
+
return 'device management face not observed — the engine reports it on /v1/capabilities (deviceExecutor.management); this process has no usable capabilities reading cached for it (none received, or the last one was unreadable)';
|
|
100
|
+
case 'not_reported':
|
|
101
|
+
return reading.why === 'management_absent'
|
|
102
|
+
? 'device management face not reported by this engine — the device lane is advertised but only newer engines say whether the /v1/devices management verbs exist; this does not say whether they exist, probe them directly'
|
|
103
|
+
: 'device executor capability not reported by this engine — only newer engines advertise the device lane at all; this does not say whether it exists';
|
|
104
|
+
case 'lane_absent':
|
|
105
|
+
return 'no device lane on this deployment — the /v1/devices management verbs are unavailable (capability.device_lane_required)';
|
|
106
|
+
case 'present':
|
|
107
|
+
return reading.management
|
|
108
|
+
? 'device management face on — the /v1/devices management verbs are available on this engine'
|
|
109
|
+
: 'device lane on but the /v1/devices management verbs are off on this deployment';
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/** 换代失效口(引擎温切成功后调):清成未观测。空串 ⇒ no-op;绝不 throw。 */
|
|
113
|
+
export function forgetDeviceExecutorManagementReading(baseUrl) {
|
|
114
|
+
if (typeof baseUrl !== 'string' || baseUrl === '')
|
|
115
|
+
return;
|
|
116
|
+
readingByBase.delete(baseUrl);
|
|
117
|
+
}
|
|
118
|
+
/** 测试钩子。 */
|
|
119
|
+
export function __resetDeviceExecutorManagementReadingsForTests() {
|
|
120
|
+
readingByBase.clear();
|
|
121
|
+
}
|
|
@@ -37,6 +37,27 @@ export type EngineAgentPanelEvent = {
|
|
|
37
37
|
currentAction?: string;
|
|
38
38
|
toolUses: number;
|
|
39
39
|
totalTokens: number;
|
|
40
|
+
/**
|
|
41
|
+
* 0.74.3 CC-70:tick 上带的委派卡 id(wire `task_progress.parentToolCallId`,在场才带)—— 身份归一的**退路键**
|
|
42
|
+
* (`transcriptId` 查不到时按它反查 fleet 尾段)。
|
|
43
|
+
*/
|
|
44
|
+
parentToolCallId?: string;
|
|
45
|
+
/**
|
|
46
|
+
* 0.74.3 CC-70(**发布方提示键**,消费端忽略):`true` = 这条 tick 绑在本 turn 一张仍开着的委派卡上(同步委派形)。
|
|
47
|
+
* 身份归一的漏斗据此决定「查不到 fleet 尾段时直发还是缓一拍」:绑卡的子代不会有 fleet 行要等。
|
|
48
|
+
*/
|
|
49
|
+
cardBound?: true;
|
|
50
|
+
/**
|
|
51
|
+
* 0.74.3 CC-70:**归一后**才有 —— 这条事件在 wire 上原来的 `taskId`(引擎 UUIDv7 = transcriptId);
|
|
52
|
+
* `taskId` 已改写成 fleet 行 id 尾段。在场 = 改写过;缺席 = 原样。
|
|
53
|
+
*/
|
|
54
|
+
wireTaskId?: string;
|
|
55
|
+
/**
|
|
56
|
+
* 0.74.3 CC-70:这条 tick 的 `taskId` 是哪一把身份 —— `'fleet-row'` = 已归一到 fleet 尾段(与 fleet-row / 通知 end /
|
|
57
|
+
* 壳本地 agentId 同键);`'wire'` = 反查不到,原样发(同步委派子代 / 老 server 行帧无 transcriptId / fleet 行还没到)。
|
|
58
|
+
* tick 恒带(它只有一个铸点 = task_progress);`end` 只在归一时带。
|
|
59
|
+
*/
|
|
60
|
+
taskIdOrigin?: 'fleet-row' | 'wire';
|
|
40
61
|
} | {
|
|
41
62
|
/**
|
|
42
63
|
* 行帧 lane 投影(2026-07-26 「后台 Agent 子代不进 Background 面板」案)。
|
|
@@ -100,6 +121,11 @@ export type EngineAgentPanelEvent = {
|
|
|
100
121
|
* 🔴 缺席 = 这一行**没有代际概念**(同步委派子代 / workflow agent / 顶层 run / 老引擎),**不是第一代**。
|
|
101
122
|
*/
|
|
102
123
|
cycleSeq?: number;
|
|
124
|
+
/**
|
|
125
|
+
* 0.74.3 CC-70:行帧上的委派卡 id(sdk `FleetTaskRow.parentToolCallId`,在场才带)—— 与 tick 的同名键是同一把钥匙,
|
|
126
|
+
* 身份归一的退路(`transcriptId` 缺席时凭它把 task_progress 的 UUID 反查到本行)。
|
|
127
|
+
*/
|
|
128
|
+
parentToolCallId?: string;
|
|
103
129
|
} | {
|
|
104
130
|
kind: 'end';
|
|
105
131
|
taskId: string;
|
|
@@ -116,6 +142,10 @@ export type EngineAgentPanelEvent = {
|
|
|
116
142
|
* explicit close arm carries it; the defensive sweeps settle without one). The consumer persists it
|
|
117
143
|
* on the row (task.result) + appends it to a live-viewed transcript (拍1 查看态 seed). */
|
|
118
144
|
report?: string;
|
|
145
|
+
/** 0.74.3 CC-70:同 tick 臂 —— 归一后才有(流内 close 臂 / 终态 tick 的 `end` 带的是 UUID,改写成 fleet 尾段时记原词)。 */
|
|
146
|
+
wireTaskId?: string;
|
|
147
|
+
/** 0.74.3 CC-70:只在归一时带 `'fleet-row'`;通知车道 / fleet 投影铸的 `end` 本来就是 fleet 尾段,不带。 */
|
|
148
|
+
taskIdOrigin?: 'fleet-row';
|
|
119
149
|
} | {
|
|
120
150
|
/** Exception-path safety net (published by seamQuery after every turn, throw or return):
|
|
121
151
|
* the consumer settles ALL rows it owns that are still running. Idempotent. */
|
|
@@ -146,6 +176,30 @@ export interface EngineAgentPanelCycleIdentity {
|
|
|
146
176
|
* 陈旧的 `end`:忽略(不 settle、不写 report、不摘所有权)。
|
|
147
177
|
*/
|
|
148
178
|
export declare function isStaleEngineAgentPanelEnd(end: EngineAgentPanelCycleIdentity | null | undefined, current: EngineAgentPanelCycleIdentity | null | undefined): boolean;
|
|
179
|
+
/** 缓一拍的节拍上限:别的 fleet-row / end / sweep 每过一条算一拍(别的 tick 不算)。 */
|
|
180
|
+
export declare const MAX_HELD_WIRE_TICK_BEATS = 16;
|
|
181
|
+
/** 身份归一的读数:`taskId` = 该用的键;`origin` = 它是哪一把。 */
|
|
182
|
+
export interface EnginePanelTaskIdentity {
|
|
183
|
+
readonly taskId: string;
|
|
184
|
+
readonly origin: 'fleet-row' | 'wire';
|
|
185
|
+
/** 钥匙上记的周期身份(登记它的那条 fleet-row 带的);`origin:'wire'` 时恒缺席。 */
|
|
186
|
+
readonly cycleSeq?: number;
|
|
187
|
+
readonly startedAt?: number;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* 0.74.3 CC-70:把 wire 上的子代 id(task_progress 的 UUID)反查成 fleet 行 id 尾段;`transcriptId` 表优先,退 `parentToolCallId` 表;
|
|
191
|
+
* 都查不到 ⇒ 原样(`origin:'wire'`)。fleet 尾段本身传进来也原样(它就是目标键)。
|
|
192
|
+
*/
|
|
193
|
+
export declare function resolveEnginePanelTaskId(wireTaskId: string, parentToolCallId?: string): EnginePanelTaskIdentity;
|
|
194
|
+
/**
|
|
195
|
+
* 0.74.3 CC-70(异源复审 R3 [high]):按 wire 上的子代 id 清常驻标 —— 原键照清;归一到 fleet 尾段的那把键**只在这条终态属当代时**才清:
|
|
196
|
+
* 复活后迟到的前代终态(旧 UUID 的流内 close / 终态 tick)归一后带旧代际,清了尾段键 = 撤掉复活行的 sweep 保护 ⇒ 下一次 sweep 假结它。
|
|
197
|
+
* 判据与 `end` 的陈旧判据同一只(`isStaleEngineAgentPanelEnd`,拿钥匙上的周期对最近一条行帧的周期);判不出 ⇒ 照清(不因缺席留下永久常驻)。
|
|
198
|
+
*/
|
|
199
|
+
export declare function clearEnginePanelTaskResidentByWire(wireTaskId: string): void;
|
|
200
|
+
/** 测试钩:清两张钥匙表、缓一拍与周期水位。 */
|
|
201
|
+
export declare function __resetEngineAgentPanelIdentityForTests(): void;
|
|
202
|
+
/** 宿主的唯一发布口(三条 lane 都经这里):先归一身份,再进主通道(订阅者 / 缓冲)。 */
|
|
149
203
|
export declare function publishEngineAgentPanelEvent(ev: EngineAgentPanelEvent): void;
|
|
150
204
|
/** Single consumer (the panel hook). Replays any buffered events, returns an unsubscribe. */
|
|
151
205
|
export declare function subscribeEngineAgentPanel(fn: Listener): () => void;
|