@sema-agent/client-core 0.71.4 → 0.72.1

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +3 -3
  3. package/dist/adapter/activeRunSelfHeal.d.ts +0 -2
  4. package/dist/adapter/activeRunSelfHeal.js +1 -1
  5. package/dist/engineErrorCodes.d.ts +12 -20
  6. package/dist/engineErrorCodes.js +18 -6
  7. package/dist/finalVerifyWire.d.ts +0 -6
  8. package/dist/finalVerifyWire.js +1 -1
  9. package/dist/fleet/fleetProjection.d.ts +0 -44
  10. package/dist/fleet/fleetProjection.js +9 -9
  11. package/dist/hitl/armedGateRegistry.d.ts +0 -6
  12. package/dist/hitl/armedGateRegistry.js +3 -3
  13. package/dist/hitl/gateIdentity.d.ts +0 -4
  14. package/dist/hitl/gateIdentity.js +2 -2
  15. package/dist/hitl/hitlBridge.d.ts +0 -2
  16. package/dist/hitl/hitlBridge.js +1 -1
  17. package/dist/hitl/planReviewWire.d.ts +0 -2
  18. package/dist/hitl/planReviewWire.js +1 -1
  19. package/dist/model/catalogLoader.d.ts +0 -6
  20. package/dist/model/catalogLoader.js +3 -3
  21. package/dist/model/providerAuth.d.ts +0 -8
  22. package/dist/model/providerAuth.js +4 -4
  23. package/dist/model/providerCatalog.d.ts +0 -2
  24. package/dist/model/providerCatalog.js +1 -1
  25. package/dist/modelCapabilityProbe.d.ts +0 -4
  26. package/dist/modelCapabilityProbe.js +2 -2
  27. package/dist/oneShotWireCaps.d.ts +0 -2
  28. package/dist/oneShotWireCaps.js +1 -1
  29. package/dist/peerFrames.d.ts +0 -11
  30. package/dist/peerFrames.js +1 -1
  31. package/dist/promptProfileWireCaps.d.ts +0 -1
  32. package/dist/promptProfileWireCaps.js +1 -1
  33. package/dist/resumeRefusalCopy.d.ts +74 -0
  34. package/dist/resumeRefusalCopy.js +42 -1
  35. package/dist/skillsWireCaps.d.ts +31 -1
  36. package/dist/skillsWireCaps.js +28 -7
  37. package/dist/toolResult.d.ts +0 -17
  38. package/dist/toolResult.js +1 -1
  39. package/dist/ultracodeWireCaps.d.ts +0 -3
  40. package/dist/ultracodeWireCaps.js +1 -1
  41. package/docs/INTEGRATION-CLIENTS.md +79 -23
  42. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -49,6 +49,28 @@
49
49
  > 挡住 ⇒ 本批把它机械化——④a0 对 `pending` 行**要求段头已是日期形**(`(未发布)` 直接红),阶段一
50
50
  > commit 漏转在发布前就红,不再靠人记。
51
51
 
52
+ ## 0.72.1(2026-09-17)
53
+
54
+ > 主题:**server 7.80.1 提货**(发车帖 [7430] @client-core 三条;本包 CC-33)。**patch**:型面纯 additive(公面 956 → 960;零 `_sema_` 键;peer 不动)。接入面 §48。
55
+
56
+ ### Added
57
+
58
+ - **`resumeContextUnavailableFromError(err) → { code, staleAfterSec?, runId? } | null`** + **`resumeContextUnavailableContent(d)`** + 码常量 **`RESUME_CONTEXT_UNAVAILABLE`**(`conflict.resume_context_unavailable`):批准等太久、发起它的会话已不保留上下文 —— server 7.80.1 起体带 `staleAfterSec`(整数秒 = 部署当下的会话保留时长真值)。读口 duck-typed 只认 `errorCode`;`staleAfterSec` 窄读域整数 ≥1、坏值降缺席不降 0;`runId` 非空串才在场;码不进 `RESUME_RETRY_LATER_CODES` / `RESUME_REFUSAL_CODES`(不可等,也不是换参数);三句人话零内部车道词、不铸秒数(保留时长由端按 `staleAfterSec` 单独渲,缺席不渲);**只凭码不推断原因**(server 在上下文被回收或读取失败时都发本码 ⇒ 第一句只说「当前没有可用的上下文」),第三句按 `runId` 在场与否给恢复指引、调大保留时长只作预防不作恢复承诺(发车前异源对抗复审两条 medium 实抓)。**sdk 9.6.0 的 `ConflictError` 尚未把这两个体键带上来 ⇒ 经 sdk 的端今天恒缺席**,sdk 出键之日自动填满。门 `run-resume-refusal-copy-test` ⑧ 段 +11 格。
59
+ - **`SkillSpec.baseDir`(server ≥7.80.1 / core 7.20.0 #799)**:`skillCommandsToSpecs(cmds, resolveBody, { toolsRunHere })` 第三参 —— 只在工具跑在本机的车道带 `baseDir = command.skillRoot`,缺席 = fail-closed(沙箱 / 远端车道上本机路径不存在;引擎不加首行不做 `${SKILL_ROOT}` 替换,结果与此前逐字节相同);`skillBaseDirOf(root, platform = 'posix')` 窄读 = server 顶层门同一只结构判据(「绝对」按 **server 所在平台**的 `node:path.isAbsolute` 判:posix 只认 `/…`,`win32` 才收盘符与 UNC(win32 分支比 server 严:`/a` / `\\a` / `//host/share` 不发)—— 复审实抓:盘符路径在 posix server 上送出去必 400;无 `..` 段、无 NUL、≤ 4096,不做存在性检查);第三参 `{ toolsRunHere, platform? }`;出口型 `SkillSpecOnWire = SkillSpec & { baseDir?: string }`。`..` 段两个平台都按 `/` 与 `\\` 拆(server `isValidCwd` 同律)。pure 门 skills 段 +8 格。
60
+
61
+ ## 0.72.0(2026-09-17)
62
+
63
+ > 主题:**退役批**(CC-32;[7419] §45 预告 → cli [7422] 同意 → web-client / desktop 过节拍沉默 = 同意)。**minor,型面 BREAKING**:39 个零消费导出退出公面(995 → 956);运行期零 BREAKING;peer 不动。接入面 §47。
64
+
65
+ ### Removed
66
+
67
+ - 🔴 **39 个运行期导出退出公面**(声明保留为模块私有,行为零改):`AUTHORITY_ENVELOPE_TAGS` `CANCEL_RELEASE_WAIT_MS` `CATALOG_CACHE_RELATIVE_PATH` `CATALOG_CACHE_STALE_MS` `DEFAULT_CATALOG_TIMEOUT_MS` `DEVICE_CODE_BACKOFF_STEP_MS` `DEVICE_CODE_HTTP_TIMEOUT_MS` `DEVICE_CODE_MAX_LIFETIME_MS` `DEVICE_CODE_MIN_INTERVAL_MS` `HITL_ASK_QUESTION_ID_PREFIX` `HITL_FRAME_CALL_KEY_PREFIX` `LIMITS_ERROR_CODE_PREFIX` `MAX_DENY_REASON_CHARS` `ONE_SHOT_ENV` `PROBE_MAX_TOKENS` `PROBE_PROMPT` `PROMPT_PROFILE_ENV` `TOOL_END_INTERRUPTED_CODES` `WORKFLOW_DEFER_ENV` `WORKFLOW_PARK_BINDING_BROKEN` `WORKFLOW_PARK_NOT_PENDING` `WORKFLOW_PARK_REQUIRES_RUN_STORE` `WORKFLOW_PARK_TRUTH_UNREADABLE` `clearArmedGateFor` `deriveAgentLabel` `notePlanReviewAnsweredIfDecisiveFor` `onGateArmedFor` `parseBackgroundReceipt` `parseFinalVerifyArgv` `providerApiShortName` `registerArmedGateFromQuestionIdFor` `wireCurrentTool` `wireEditedFiles` `wireParentToolCallId` `wireRetiredBy` `wireStartedCount` `wireStoppedBy` `wireToolUses` `wireTranscriptId`。判据 = export-liveness 门 `retire` 登记(四端 `src`/`ui`/`bff` **origin/main 快照** AST 逐名零 import:cli f5bd354c / web-client 9cccce4 / desktop 5c99611 / web-admin 9ab7c32;本仓门零代码位引用)。仍 import 其中任一名的端换钉后编译当场红 ⇒ 回帖点名,本包单名回退随 patch。
68
+ - 随删:公面基线 −39(23 常量 + 16 函数);`export-liveness.json` retire 行删(117 → 78,棘轮随降);typeshape unknown 出境 345 → 343(`parseBackgroundReceipt` / `parseFinalVerifyArgv` 退出公面);§2 计数与十六域名数随减。`internal` 34 个 `export *` 放大件本版**保留**,候模块拆分再收回。
69
+
70
+ ### Added
71
+
72
+ - **已退役名账 + 两道反钉**(发车前异源对抗复审第 1 轮发现:接入档 §2b / §6b 承重导出列仍把 `clearArmedGateFor` / `registerArmedGateFromQuestionIdFor` / `wireRetiredBy` 等当可用导出推荐,端按档 import 会扑空;已订正 §0a / §2b / §6b / P-36 / 探针段六处):`scripts/export-liveness.json` 新增按版本分账的 `removed`(0.72.0:39 名,只增不删);export-liveness 门 G 段三格(分账有序无重复 / 🔴 已退役名不许回到基线或登记 / 版本不许晚于 CHANGELOG 顶段);冻结账门 ⑧c 三格(🔴 已退役名按标识符边界扫全档 —— 代码块签名行 / inline code / 散文一视同仁,子串不算;先剥不可见文字(跨行 HTML 注释 / 链接目标 / 引用式链接标签与定义 / 标签属性);注记「退役 / 退出公面」必须在名字**之后**、同一片段内(`|` `。` `;` `,` 切),不许跨过另一个独立的已退役名去借(紧邻名字组尾一条注记盖整组),或表格行首格本身是退役标签;判别力自证十二种抓得住形 + 六种放行形 —— 发车前异源对抗复审三十轮逐轮抓到的形全部入自证 —— 代码块签名行 / 复合 inline code / 同行无关注记 / 借注记 / 漏中文分号 / HTML 注释·属性·hidden·details 里的注记 / 嵌套与大小写 / 伪自闭合 / 未闭合与错配闭标签 / 行内代码与转义反引号 / 段落·标题·Setext·容器·HTML 块等 Markdown 块边界 / 第 1 类 HTML 块的精确闭标签;可见视图 = 单趟引号感知 HTML 词法 + 栈配对 + CommonMark 代码区与 HTML 块规则,宁严勿松)。随批订正接入档 §2 计数行三处历史子句加注记、§6 决断 `reason` 上限行去常量名、§28 探针签名代码块两行改注释。
73
+
52
74
  ## 0.71.4(2026-09-17)
53
75
 
54
76
  > 主题:**core 7.20.1 提货**(提货单 [7415];本包 [7411] 认领)。**patch**:型面纯 additive(一个 `_sema_` 超集键 + 通告闭集 +1),公面 995 不变,peer 不动;devDep core `~7.20.1`。接入面 §46。
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.71.4
38
+ **Version:** 0.72.1
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
@@ -307,7 +307,7 @@ public-surface guard checks that last one).
307
307
  | `scripts/run-engine-identity-test.mjs` | The engine generation anchors on `/health` (`pid`, `instanceId`, `startedAt`; engine >=7.67.0). `/health` is the one unauthenticated door and its heartbeat is always green, so "another host restarted the shared engine" used to be discoverable only by having some authenticated request hit a 401 first — a path that misreads a restart as a network fault. The reader narrows each anchor independently (one malformed field never hides the other two) and always hands back a reading object rather than an absence, because the caller is asking which anchors answered, not whether there was a response. The comparison is a three-word verdict, not a boolean: `unknown` when the two readings share no comparable anchor at all — an empty intersection means nothing could be compared, never that nothing changed — and the boolean convenience is pinned so that only `true` is an assertion. Any comparable anchor differing decides `changed`, so a reading whose `startedAt` matches while its `instanceId` does not cannot be waved through as the same life; precedence only decides which anchor gets named in the diagnosis |
308
308
  | `scripts/run-posture-knob-projection-test.mjs` | The three deployment knobs on the operator face (`serverGates.durableApproval` / `streamAskWindowMs` / `sessionAutoTitle`, engine >=7.67.0), each read as a value **plus who set it plus one operator-facing pointer** rather than a bare value — a bare boolean cannot answer why this particular machine is on this setting or how to pin it back, and a default that flips with the deployment shape is invisible without that. A worker too old to report readings still sends a bare boolean; the reader folds it into the same shell so consumers keep one branch, but raises a `legacy` bit, answers `undefined` from the machine-readable source accessor, and mints a sentence that contains no source word at all — claiming a source nobody reported is worse than admitting the worker cannot say. The other two knobs are honestly absent on such a worker rather than defaulted, a malformed side knob drops only itself while the anchor knob drops the whole reading, and the four sentences are pinned literally distinct so an operator can tell "not observed" from "not reported" from a real value. The last leg reads the installed SDK's `openapi.yaml` and `types.d.ts` directly, including a pin that exactly one knob on this face is numeric — the premise the millisecond-to-prose rendering rests on |
309
309
  | `scripts/run-terminal-facts-projection-test.mjs` | The four unconsumed terminal-receipt facts: `TaskResult.effectiveReasoning` / `effectiveMemoryScopes` are narrowed into `_sema_effective_reasoning` / `_sema_effective_memory_scopes` on the CC-shaped `result` (success and error envelopes alike; a malformed value mints nothing, never a default tier), the resume **reopen** family (`resume.env_failed` / `tool_unavailable` / `tool_contract_mismatch`) is a frozen closed set with a reader and three-sentence copy that is disjoint from the refusal and retry-later sets, and `routePairingVerdict` reads `ModelInfo.routePairing` as ok / broken / unknown without policing the open set. |
310
- | `scripts/run-export-liveness-test.mjs` | Every runtime export in the public baseline must be **alive**: referenced by some gate, or explicitly registered in `scripts/export-liveness.json` as `contract` (consumed by a client with no gate yet), `internal` (an internal helper amplified onto the public surface by `export *`), `candidate` (with ticket + retire-by) or `retire` (dead; retire-by version). Registration is accounting, not exemption: a row for a name a gate already references is stale and must go, a row for a name no longer exported is red, `retire`/`candidate` rows go red the moment `package.json` reaches their retire-by version, and the row count only ratchets down. When the sibling client trees are on disk the consumption evidence is checked by name — a `contract` row's claimed consumers must equal the real set, and a `retire` name must not be imported by any client. |
310
+ | `scripts/run-export-liveness-test.mjs` | Every runtime export in the public baseline must be **alive**: referenced by some gate, or explicitly registered in `scripts/export-liveness.json` as `contract` (consumed by a client with no gate yet), `internal` (an internal helper amplified onto the public surface by `export *`), `candidate` (with ticket + retire-by) or `retire` (dead; retire-by version). Registration is accounting, not exemption: a row for a name a gate already references is stale and must go, a row for a name no longer exported is red, `retire`/`candidate` rows go red the moment `package.json` reaches their retire-by version, and the row count only ratchets down. When the sibling client trees are on disk the consumption evidence is checked by name — a `contract` row's claimed consumers must equal the real set, and a `retire` name must not be imported by any client. Names that have already left the surface are kept in a per-version `removed` ledger: they must never reappear in the baseline or the registry, and the ledger's versions must not run ahead of the changelog. |
311
311
  | `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). |
312
312
  | `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") |
313
313
  | `scripts/run-core-value-ports-test.mjs` | The port-injection seam for ten **engine value-level** facilities (autonomous-loop prompt assembly, permission-rule loosening, tool-policy composition, protocol/retired-name/grammar lookups, rule compilation, the discussion workflow name). This package cannot re-export them (the engine barrel drags Node built-ins into the browser bundle), so it declares the ports and honest-absence readers; a Node host installs the engine's own functions verbatim. The guard pins: every reader returns `undefined` when nothing is installed (never a fabricated empty array or default policy), arguments and results pass through by reference, engine errors propagate unchanged, partial installs read partially, restore functions unwind to the previous bag, and the module source has zero engine imports |
@@ -342,7 +342,7 @@ public-surface guard checks that last one).
342
342
  | `scripts/run-crash-converged-projection-test.mjs` | The `crashConverged` read face on `GET /v1/approvals` (L-38): what the *previous life* of a crashed local engine left behind, projected for every client. Three judgements are pinned. First, **absence is not an empty list** — a missing key (an older server, deps not present, or a carrier that is not an array at all) returns `undefined`, and the client renders nothing; an empty array returns a present zero-count object, which is the server actually saying "none". Folding the first into `{total:0}` would have the client assert "nothing was left behind" on a surface a person uses to decide whether it is safe to re-run something — the worst possible direction for a false statement — so the two cases are pinned to different **return shapes** and a test asserts the two verdicts are unequal. Second, bucketing is a **four-term conjunction**: `orphanState === 'pending'` *and* `resumeSafe === true` *and* both approval-evidence keys (`originalDecision`, `decidedAtMs`) absent. A fifth term rejects any row carrying an **accessor**, and accessors are never invoked at all — reading one means synchronously running someone else's code, and `catch` catches throwing, not *never returning*, so a looping getter would pin the startup thread forever (the row cap does nothing against that shape). The same rule covers the three untrusted reads outside the row as well — the envelope's `crashConverged` key, the carrier's `length`, and every numeric index are read as own property *descriptors* and only data descriptors are used, so accessors and prototype entries read as absent and are never invoked. Such a key is treated as absent: if it was a required field the row is counted as dropped, if it was optional or additive the row survives without it. That also closes the ordering attack, since spreading runs getters in property order and an earlier one could `delete` the approval evidence before it is ever copied (measured before the fix: such a row reached the resume-safe bucket), and the check therefore moves ahead of the read, onto the property descriptors — from which the snapshot is then built directly, because checking descriptors and *then* spreading is two independent observations of the same row, and a non-throwing proxy can make the two `ownKeys` calls disagree (first showing `originalDecision: 'approve'` so the row reads as plain data, then omitting that configurable key so the snapshot loses the evidence; measured before the fix: the dangerous row reached the resume-safe bucket after exactly two enumerations, and after it, one). Keys are written with `Object.defineProperty` rather than plain assignment, because `'__proto__'` is a legal own enumerable key and `o['__proto__'] = x` does not store a value — it calls the prototype setter, letting a row whose own properties are all plain data (so the accessor gate never fires) inject a prototype whose `sessionId` getter deletes the approval evidence from the snapshot during validation; `defineProperty` fires no setter, so the key survives as ordinary additive data and the snapshot keeps `Object.prototype`. A row that simply arrives with a custom prototype is treated the same way, since the snapshot only enumerates own properties: approval evidence sitting on the prototype would never reach it, and a perfectly ordinary object with no proxy and no accessors could otherwise be called safe to re-run — real bodies come from `JSON.parse` and always carry `Object.prototype`, so nothing genuine trips it). Validation itself runs on a **null-prototype** dictionary and the bucketing verdict is carried out of that same pass rather than re-read from the delivered row, because every property lookup on an ordinary `{}` reaches `Object.prototype`: a polluted `sessionId` getter there would delete the approval evidence from the snapshot mid-validation and send the row to the safe bucket (measured before the fix). The row handed to the client is still an ordinary object — the null prototype is an implementation detail of the check, not of the value) — real JSON bodies are all data properties, so only a middle-layer-synthesised payload ever trips it, and it too lands in the human bucket rather than being dropped. The `decided` arm means the human had already approved and side effects may be half-landed, so it always goes to the human bucket, as does `resumeSafe === false` and — the last two terms — any row whose own fields contradict each other, since `pending` claims nothing ran while that evidence says somebody pressed approve. Deciding "not safe" costs one extra question (recoverable); deciding "safe" wrongly has somebody re-run work that already partly happened (not). A 2x2 truth table pins that exactly one cell is resume-safe, so reading either key alone turns red, and the contradictory rows are routed to the human bucket rather than dropped — they are real orphans, and the ones most worth showing. Third, unreadable rows are **dropped and counted**, never thrown and never passed through: the product is declared as `CrashConvergedRow`, so letting a row missing a required field — or carrying one of the wrong type — past would be a lie at the type level, and the closed literal discriminators (`decision` / `cause` / `orphanState`) decide family membership rather than being an open vocabulary. The measuring stick stops at the **type** floor, though: degenerate-but-well-typed values (`ts: NaN`, an empty `toolName`) are kept, because swallowing a real orphan over a decorative field is the worse direction, and the one deliberate exception is `approvalId`, which must be non-empty to be a row identity at all. `dropped` is kept separate from `total` so unreadable rows never inflate "N approvals were affected"; each row is a **one-shot snapshot** — every own enumerable key is read exactly once, and validation, bucketing and the handed-back value all read that same snapshot, so additive upstream keys survive while a **non-idempotent** getter (one that never throws, just answers differently on a second read) can no longer erase the approval evidence between the check and the bucketing (measured before the fix: such a row landed in the resume-safe bucket while its checked value was `"approve"`). Hostile carriers are counted rather than allowed to reject: **every** touch of the carrier is guarded — envelope property reads, `Array.isArray` itself (it throws on a revoked proxy), the `length` read, each indexed read and each row's property reads — and a traversal that dies halfway returns absence rather than a half-counted total. A row that cannot be read never takes the batch with it: its own shape check is inside its own guard, so one revoked-proxy row costs a `dropped` tick rather than collapsing the whole projection to absence — which a client would have read as "this deployment does not offer the surface". Traversal goes by **numeric index, never the carrier's own iterator protocol**, because `for...of` hands the carrier the question of which rows exist: an array carrying an overridden `Symbol.iterator` can yield nothing (measured before the fix: a real orphan became `{total:0}`, which a client reads as "the server said there are none") or swap a dangerous `decided` row for a safe-looking one (measured: `fake-safe` was returned in place of `real-danger`). Row count is capped at 100000 and the cap is checked **before** the walk: requiring only a non-negative integer `length` does not stop a proxy trap reporting a billion, and this surface runs on the startup / `--resume` path, where a synchronous spin freezes the thread (measured before the cap: twenty million rows took 18.3 seconds and twenty million index reads; a billion does not come back). The honest boundary is stated rather than overclaimed — a proxy can still lie in its `length` or index traps, which is the same thing as a host injecting a lying transport — and the widening of `ApprovalsResourceLike.list()` is proven **additive** by really running tsc over a legacy `{ pending }` mock *and* over the real `AgentClient` path — the projector takes `unknown` precisely because a parameter shaped as "an object with an optional `crashConverged`" is a TypeScript weak type that the installed SDK's own `list()` return shape shares no property with, which only a real-client compile would have caught — with a known-red control so a clean run means the checker spoke |
343
343
  | `scripts/run-self-orchestration-denial-test.mjs` | The three judgements behind a **denied self-orchestration request** (server 7.57.0), each of which all three clients would otherwise get wrong on their own. First, whether to retry at all is a **conjunction that may not be loosened**: HTTP 501 *and* an `errorCode` that is **exactly** `capability.self_orchestration_required`. That code shares its shape with every other `capability.*` 501, so dispatching on the prefix would drag "some other capability is not wired up" into the retry arm — those requests do not become acceptable once the two keys are gone, so the client would spend a request and then tell the user the wrong reason. Negative controls cover all four directions: a sibling `capability.*` code, a truncated or suffixed variant of the right one, a codeless 501 (it decides nothing, so it decides nothing — no guessing), and the right code under 500 / 400 / 503 or a string `"501"`. The classifier reads structurally rather than by `instanceof` (a host may inject its own transport; across realms or duplicate SDK instances an understandable error would read as unreadable), so a class instance, a bare `{status, errorCode}` literal and an error carrying those fields on its **prototype** all reach the same verdict — and a hostile proxy or a throwing getter yields `null` instead of throwing, because this classifier runs inside a `catch` block where anything it throws escapes the caller's own guard. Second, removing the intent is a **structural** operation, not wording: `selfOrchestration` sits at the top level while `ultracode` sits under `settings` — two different stamping legs — and a client hand-writing `delete` will miss the second one, which costs the user the same failure twice. The single stripper is pinned to touch exactly those two: other `settings` sub-keys and their values survive byte for byte, `deferTools` is left alone (pulling `Workflow` out would be a behaviour change, not a removal of intent), additive unknown keys survive at both levels, the input object is never mutated, `settings` is only dropped entirely when `ultracode` was really there and nothing else remains (an already-empty one is left as is), a non-object `settings` is not touched at all, an `ultracode` that only exists on the prototype does not count, and the whole thing is idempotent. The end-to-end leg runs a real `buildTaskRequest` product through it and asserts the stripped body still passes the registration gate key by key. Third, on the capabilities body, **absence is not "switched off"**: a pre-7.57 server has no `workflowsGate` key at all, so reading absence as "the engine says no" asserts something the server never said, and the mirror-image disease is folding an **unrecognised** `denial` into `null`, which would have the client render "nothing was denied" when the truth is "denied, for a reason I do not recognise". Five shapes are pinned — caps unreadable, gate absent, closed-set member, unknown value, accessor — with the unknown arm carrying the raw token (or an empty one when the value is not even a string) and never collapsing to `null`. All four untrusted reads go through own **data descriptors** only, and the guard pins the getter invocation count at zero, since `catch` catches throwing but not *never returning*; a descriptor trap that throws and a revoked proxy both yield honest absence rather than an exception — though *what* absence means differs by field, and the guard pins that split rather than a blanket rule: an accessor on `workflows`, `workflowsGate` or `engineCan` reads as absent, while an accessor on `denial` reads as `{unknown:''}`, because a key that is **not there** is the gate saying "nothing was denied" whereas a key that is there but cannot be read is "denied, and I could not read why" — folding the second into the first is exactly the false statement this face exists to prevent. Two further pins came out of an adversarial review. The exported retry list is **frozen at runtime**, not merely `as const`: the verdict hands out that same reference, so any consumer splicing it once would poison every later verdict in the process — the guard asserts `Object.isFrozen`, that four different mutation attempts leave it byte-identical, and that a verdict issued *after* those attempts still carries the original two entries. And the classifier reads `denial` only **after** both criteria have passed, since it is not a criterion but an extra field on the verdict: the guard pins the getter invocation count at zero for any error that does not match and at most one for an error that does. The scope line is drawn explicitly rather than overclaimed — "no getter ever runs" holds for `projectWorkflowsGate`, which reads **wire JSON** where every field is an own data property by definition, but not for the classifier, which reads a **thrown value** that may well be an SDK `APIError` class instance carrying `status` and `errorCode` on its prototype; insisting on own data descriptors there would report a perfectly readable error as unreadable, so that side promises only that it never throws. A final pin covers the **integration document's own worked example** rather than the library: the shipped SDK's `tasks.stream()` is an `async` generator, so calling it issues no request at all — the POST happens inside `streamRaw` on the first iteration, and a `try` wrapped around the `stream(...)` call itself can never catch the 501. A client following a submit-shaped recipe on the streaming leg would never run the classifier, and the whole strip-and-retry path would silently do nothing. The guard drives the **real** `TasksResource` against a fake transport, offline, and pins both halves: the synchronous leg is in flight the moment it is called, the streaming leg has issued zero requests after the call and raises on the first `next()` — and it does so through the **real** error path, with `openStream` returning an actual 501 `Response` that the SDK's own `errorFromResponse` turns into the typed error, pinning the `openStream`→`errorFrom` call order so a transport that stops minting `errorCode` cannot pass. The documented recipe is then **executed** rather than keyword-counted: exactly one retry, a second body that really lost both keys while every other setting survives byte for byte, the caller's own request object left untouched, one disclosure and only one, a second 501 propagating with the request count still at two, and — after the first 501 — an abort leaving the count at one with nothing disclosed. A last leg is type-level: `stripSelfOrchestrationIntent` carries an SDK `TaskRequest` overload, because the wide `Record<string, unknown>` form erases the caller's type and the document's "strip and resubmit" line would not compile without an unsafe cast; a real tsc run over a virtual file proves both the narrow and the wide path, with a known-red control — and it compiles the document's two recipes **verbatim**, extracted from the section itself, because a recipe that does not compile is a recipe that was never given: `{ transientOk: true, signal }` is a TS2379 under `exactOptionalPropertyTypes`, which no amount of prose review had caught. The last thing pinned is the one that would have been quietest of all: the SDK's `stream()` returns only on a `done` or `failed` frame, so a stream truncated mid-run — or yielding nothing at all — ends the `for await` just as normally as a completed one. The documented `runOnce` therefore tracks whether it ever saw a terminal frame and raises when it did not, the guard's success fixture emits a real terminal and asserts the handler received it, and a truncated-stream control asserts that shape is reported as a failure with no retry and nothing disclosed. That terminal-frame rule then needed one more turn of its own: the underlying reader returns *normally* when the signal is aborted, so the check as first written rewrote a user's cancellation into a generic stream fault — a client keying off `AbortError` to suppress the error would instead have shown a failure, or resubmitted. Cancellation is therefore checked first, a real-SDK case aborts from inside the handler and asserts the original `AbortError` survives with no retry and nothing disclosed, and the document is checked for that ordering. The harness runs the documented `handle` and `transcript.note` as real spies rather than pushing frames itself, the drive loop rethrows exactly as the document does, and the disclosure ledger is proven to be the caller's own array by a positive identity assertion — without which the cancellation leg's "nothing disclosed" would have been vacuously true. Each recipe is compiled **on its own**, with a preamble that declares only what a host supplies and injects no library symbol, since compiling them together let the second one borrow the first one's imports, and the preamble's own types are decoupled from what the recipes import so the "remove the imports and it must fail" control fails for the right reason — which is checked by attribution, not merely by redness. Ordering is the last thing to get right: the cancellation check must come before the truncation error but **both** must sit behind the terminal-frame test, because a cancellation that lands after the run already reported `done` would otherwise overwrite a real outcome — one that may have already had effects — with "cancelled", and a person reading that will run it again. Aborting from inside `handle(done)` and `handle(failed)` are both pinned to still report success, and the ordering assertion is anchored inside the streaming `runOnce` body rather than the section, since the section's first `throwIfAborted` belongs to the synchronous recipe and would have made a reversed streaming recipe pass — and that ordering check is now anchored on the TypeScript AST rather than on text, since a comment reproducing the two statements in the right order let a genuinely reversed body pass. One more timing fact had to be written into the recipe: a single SSE read buffers several frames and the SDK yields them back to back, so checking the signal only after the loop lets a cancelled run keep consuming the rest of the chunk — measured, an abort inside `handle(turn_start)` still swallowed the `done` that followed and reported success. The recipe therefore re-checks after every non-terminal frame. Finally, the behavioural matrix is no longer run against a copy of the recipe: both recipes are extracted from the document, transpiled, and **executed** with injected host objects, so the disclosure assertion really exercises the document's own `transcript.note(disclose(...))` line, and the synchronous leg gets the same full matrix the streaming one does |
344
344
  | `scripts/run-package-hygiene-test.mjs` | Everything `package.json` `files` ships — dist JS/typings and the Markdown docs — is screened line-by-line against a deny-list of strings that must never appear in a published artefact. The guard first proves each pattern still bites on a constructed sample (a screen that cannot fail is worse than none) and honours a per-pattern allow-list for legitimate product vocabulary, so the verdict is "clean surface", not "quiet grep". |
345
- | `scripts/run-integration-doc-freshness-test.mjs` | The **integration contract** (`docs/INTEGRATION-CLIENTS.md`) and the **changelog** (`CHANGELOG.md`) checked against the code, because a document with no guard rots — this one had a whole nest of drift found on it within a day of being written. Five directions, each a claim a machine can actually evaluate. (1) *Counting discipline*: the version-anchor row for the guard count may no longer carry a hand-copied number at all — it changes every time a guard is added, and writing it down is planting a timer; the export counts that are still hand-copied (the surface total, the test-hook count, the sentence describing the surface's internal composition, the sum of the sixteen domain rows, and the three sub-counts) are each compared against a value **derived** from `public-export-baseline.json`, which is the drift a human reviewer caught last time. (2) *Coordinates alive*: every `src/` `scripts/` `docs/` path the doc quotes must be on disk **and tracked by git** — on disk is not in the repo, and a doc that points readers at a file living only in its author's working tree sends every clone to nothing. A file landing in the same commit takes a named carve-out that **stops applying** the moment the file is really tracked (it can no longer let anything through, and the guard prints a line asking for it to be deleted) — deliberately not a red, since turning red on the very commit that lands the file would just manufacture a break that only a follow-up commit could clear. (3) *Arm tables*: the `hitl_out_of_slice` row and the `not_in_slice` fenced list must equal, name for name and in **both** directions, the case labels that really fall into those two buckets — read through the **TypeScript AST**, since which bucket an arm lands in is decided by the argument to `nothing(...)` and by nothing a comment says. The extractor is anchored to the one production projector: exactly one function named `eventToSdkMessage`, exactly one `switch (ev.type)` inside it, and no repeated case label — anything else is a broken anchor rather than a verdict, because a second same-shaped switch elsewhere in the file would otherwise overwrite the real one's conclusions and leave the doc agreeing with a switch nobody runs. The list is delimited by a machine-readable fence rather than by section headings, because the same section also names the terminal arms as a counter-example and prose boundaries cannot tell a member from a foil. (4) *Released sections are frozen*: an **append-only ledger** carries every version ever published — its number, the commit it was published from, and the sha256 of its section — and each one is checked, not just the current release, since pinning only the latest would set every earlier version free the moment the next one ships. The ledger cannot vouch for itself either: each recorded hash is **re-derived from that release commit** through git, so editing an old section and its constant together no longer passes — and the commit the row names is in turn checked against the `gitHead` npm recorded at publish time, which is the one value this repository cannot rewrite, so pointing an old version at a freshly written commit does not pass either. The *set* of versions that must be frozen comes from the registry too, so deleting an old row together with its section — which would otherwise remove that version from every set the guard looks at — is red rather than invisible. A failed registry call is classified rather than swallowed, and the classification consults the registry's own status code *before* it considers connection-level symptoms, so an auth refusal whose body happens to mention the network is still red rather than a skip. The version set is compared as full SemVer including prereleases — matching only `x.y.z` would silently drop a published `0.30.0-beta.1` and reopen the very hole this direction closes — and section headings are matched on a whole-version boundary so a stable release cannot bind itself to the release-candidate section sitting above it. Publishing itself is a two-phase protocol rather than a paradox: before a release, exactly one row may be marked pending and must name the current `package.json` version, exempt from the checks whose inputs do not exist yet; once the registry has that version the row must be promoted, so the temporary state cannot survive its own release. And because the pending exemption rests entirely on "this version is not out yet," it is refused outright when the registry cannot be reached to confirm that — an unverifiable premise is not a licence. Three reverse directions close the rest: a section claiming to be released but absent from the ledger, a ledger entry whose section has vanished, and a `package.json` version that was never frozen. Publishing appends a row; it never rewrites one. (6) *Sentinels*: the readers §5a hands hosts for "is this port installed" are checked against what the source actually declares it returns — `hasXxx()` is a `boolean`, the card port / HITL surface / wire target return `T | null`, the `installHost` family returns `T | undefined`. Testing a `null`-returning reader for `!== undefined` is *always true*, and a self-check that passes whether or not the port is installed is worse than none, because hosts retire their own fallback on the strength of it. Both directions are red: an implementation that changes its sentinel without the doc following, and a doc that names the wrong one. The roster covers the zero-argument readers and their `*For` variants alike — a multi-session host reads the variants, so leaving them off would let exactly the surface desktop depends on drift unwatched — and the §5a table and the §8-B checklist line are each checked against the source, because hosts tick the checklist, and a guard that only watches the prose table misses the line people actually follow. (5) *Packaging*: the README ships with the package and opens by pointing hosts at the integration doc, and the checklist names two more files as required reading before an upgrade — all three must really appear in the `npm pack` manifest, or an npm consumer follows a relative link that npmjs rewrites onto a private repository. Missing tooling never takes the whole verdict down with it: when git, npm or the registry is unreachable those legs print the `SKIPPED-SECTION` marker and the rest still judges, while a release commit the ledger names but git cannot resolve is red rather than skipped. The guard says in its own header what it does **not** do: it judges counts, coordinates, arm sets, released bytes and the packing list — whether a sentence is *right* is still for review and for the hosts to report |
345
+ | `scripts/run-integration-doc-freshness-test.mjs` | The **integration contract** (`docs/INTEGRATION-CLIENTS.md`) and the **changelog** (`CHANGELOG.md`) checked against the code, because a document with no guard rots — this one had a whole nest of drift found on it within a day of being written. Five directions, each a claim a machine can actually evaluate. (1) *Counting discipline*: the version-anchor row for the guard count may no longer carry a hand-copied number at all — it changes every time a guard is added, and writing it down is planting a timer; the export counts that are still hand-copied (the surface total, the test-hook count, the sentence describing the surface's internal composition, the sum of the sixteen domain rows, and the three sub-counts) are each compared against a value **derived** from `public-export-baseline.json`, which is the drift a human reviewer caught last time. (2) *Coordinates alive*: every `src/` `scripts/` `docs/` path the doc quotes must be on disk **and tracked by git** — on disk is not in the repo, and a doc that points readers at a file living only in its author's working tree sends every clone to nothing. A file landing in the same commit takes a named carve-out that **stops applying** the moment the file is really tracked (it can no longer let anything through, and the guard prints a line asking for it to be deleted) — deliberately not a red, since turning red on the very commit that lands the file would just manufacture a break that only a follow-up commit could clear. (3) *Arm tables*: the `hitl_out_of_slice` row and the `not_in_slice` fenced list must equal, name for name and in **both** directions, the case labels that really fall into those two buckets — read through the **TypeScript AST**, since which bucket an arm lands in is decided by the argument to `nothing(...)` and by nothing a comment says. The extractor is anchored to the one production projector: exactly one function named `eventToSdkMessage`, exactly one `switch (ev.type)` inside it, and no repeated case label — anything else is a broken anchor rather than a verdict, because a second same-shaped switch elsewhere in the file would otherwise overwrite the real one's conclusions and leave the doc agreeing with a switch nobody runs. The list is delimited by a machine-readable fence rather than by section headings, because the same section also names the terminal arms as a counter-example and prose boundaries cannot tell a member from a foil. (4) *Released sections are frozen*: an **append-only ledger** carries every version ever published — its number, the commit it was published from, and the sha256 of its section — and each one is checked, not just the current release, since pinning only the latest would set every earlier version free the moment the next one ships. The ledger cannot vouch for itself either: each recorded hash is **re-derived from that release commit** through git, so editing an old section and its constant together no longer passes — and the commit the row names is in turn checked against the `gitHead` npm recorded at publish time, which is the one value this repository cannot rewrite, so pointing an old version at a freshly written commit does not pass either. The *set* of versions that must be frozen comes from the registry too, so deleting an old row together with its section — which would otherwise remove that version from every set the guard looks at — is red rather than invisible. A failed registry call is classified rather than swallowed, and the classification consults the registry's own status code *before* it considers connection-level symptoms, so an auth refusal whose body happens to mention the network is still red rather than a skip. The version set is compared as full SemVer including prereleases — matching only `x.y.z` would silently drop a published `0.30.0-beta.1` and reopen the very hole this direction closes — and section headings are matched on a whole-version boundary so a stable release cannot bind itself to the release-candidate section sitting above it. Publishing itself is a two-phase protocol rather than a paradox: before a release, exactly one row may be marked pending and must name the current `package.json` version, exempt from the checks whose inputs do not exist yet; once the registry has that version the row must be promoted, so the temporary state cannot survive its own release. And because the pending exemption rests entirely on "this version is not out yet," it is refused outright when the registry cannot be reached to confirm that — an unverifiable premise is not a licence. Three reverse directions close the rest: a section claiming to be released but absent from the ledger, a ledger entry whose section has vanished, and a `package.json` version that was never frozen. Publishing appends a row; it never rewrites one. (6) *Sentinels*: the readers §5a hands hosts for "is this port installed" are checked against what the source actually declares it returns — `hasXxx()` is a `boolean`, the card port / HITL surface / wire target return `T | null`, the `installHost` family returns `T | undefined`. Testing a `null`-returning reader for `!== undefined` is *always true*, and a self-check that passes whether or not the port is installed is worse than none, because hosts retire their own fallback on the strength of it. Both directions are red: an implementation that changes its sentinel without the doc following, and a doc that names the wrong one. The roster covers the zero-argument readers and their `*For` variants alike — a multi-session host reads the variants, so leaving them off would let exactly the surface desktop depends on drift unwatched — and the §5a table and the §8-B checklist line are each checked against the source, because hosts tick the checklist, and a guard that only watches the prose table misses the line people actually follow. (5) *Packaging*: the README ships with the package and opens by pointing hosts at the integration doc, and the checklist names two more files as required reading before an upgrade — all three must really appear in the `npm pack` manifest, or an npm consumer follows a relative link that npmjs rewrites onto a private repository. Missing tooling never takes the whole verdict down with it: when git, npm or the registry is unreachable those legs print the `SKIPPED-SECTION` marker and the rest still judges, while a release commit the ledger names but git cannot resolve is red rather than skipped. The guard says in its own header what it does **not** do: it judges counts, coordinates, arm sets, released bytes and the packing list — whether a sentence is *right* is still for review and for the hosts to report (7) *Retired names*: every name in the per-version `removed` ledger of `scripts/export-liveness.json` may appear in the integration doc only where a retirement note follows the name inside the same clause (or the table row's label cell is itself a retirement label); the scan is by identifier boundary after invisible text (HTML comments, link targets, reference-link labels, tag attributes) has been stripped, so a signature line in a code block, an inline `NAME = 4096`, a hidden note, or a note that belongs to a neighbouring name all count as a bare recommendation and go red. |
346
346
  | `scripts/run-type-superset-ledger-test.mjs` | The type/wire **superset ledger** (`docs/type-superset.json`): positions this package adds on top of a CC-shaped contract, each carrying the evidence for what CC's own type surface does or does not have there. Completeness is deliberately uneven and the ledger says so. The `_sema_*` private-key class is checked in **both** directions (a key in the source that never entered the ledger is red, naming key and file; a ledger row whose key left the source is red) — but only for keys written as literals, which is the convention the ledger mandates. A key assembled by string arithmetic is beyond what any static rule can enumerate, so the guard fails closed on every shape it *can* decide (a bare `_sema_` prefix is red wherever it appears, save one pinned guard site) and leaves the rest as a convention violation for review to catch, rather than claiming a completeness it does not have. The two hand-surveyed classes are only checked for coordinate and evidence integrity, never discovered. Both directions read the source through the **TypeScript AST**, not a text scan, and they read two different sets out of it. A *key site* is an identifier, or a string whose whole value is the key — so `'_sema_decision-v2'` is carried whole rather than truncated at the first non-identifier character into some *other* key that happens to be registered. A *mention* is the key appearing inside a longer string, which is prose, not usage. The staleness direction counts key sites only: a comment or a doc sentence left behind after the last real mint site is deleted must not keep the row alive (mutation-proven — with both the comment and the prose string untouched, removing the one real site turns the guard red). And because a prefix can be concatenated or interpolated into a key no static set will ever see, the bare `_sema_` literal is refused outright rather than traced: every occurrence is red except the single inline `startsWith` guard the sanitizer needs, because the set of expressions a bare prefix can travel through on its way to a concatenation is open-ended and enumerating it is always one form behind. Every row's `host` must still resolve, with the key being a real **member of that declaration** rather than a string occurring somewhere in the same file — `governanceForced`/`delegation` each live on two different shapes in one file, and a member commented out is a member deleted, which a text-shaped check happily reads as still present. And the direction worth the most: each machine-form `ccAbsenceEvidence` is re-derived from the row's own `key` — the ledger's recorded string must match that derivation verbatim, since a row quietly witnessing `\bnever_present\b` is green forever while watching nothing (mutation-proven: the same edit passes the unbound form and is caught by the bound one) — and the check runs against the names the installed `@sema-agent/agent-types` `.d.ts` set actually declares, parsed with the TypeScript AST rather than grepped, so a name CC merely mentions in a comment cannot force the row into the manual escape hatch and thereby retire the very witness that was supposed to fire the day CC declares that name for real. That escape hatch is gated by an allowlist living **in the guard**, not the ledger, so claiming it costs a reviewed diff. Missing material never reads as a pass, and the verdict splits by *why* it is missing: no TypeScript parser skips the suite before it starts; a missing `agent-types` still runs and prints the first three directions, then exits **1** when `package.json` declares the mirror but it is not installed — a broken install must not retire the repository's only "the day CC declares this name" alarm, and reporting it as a skip would leave "never evaluated" and "evaluated, no drift" indistinguishable to the runner — and exits 3 only when nothing declares the mirror at all, which is the one case where the direction genuinely does not apply. Either way a run that evaluated no witness is never counted as one that did. When the mirror *is* present its **installed version** is witnessed too (the two declared floors must agree with each other and the installed copy must meet them), since four preflight probes are satisfied by an arbitrarily stale mirror — they prove the extractor speaks, not that it is current. Every direction carries a positive control — known-present CC symbols, a comment-only sample proving the extractor distinguishes declaration from mention, and synthetic corpora fed through the **same** discriminator function the real verdict uses, so a verdict quietly rewritten to return nothing takes its own control down with it |
347
347
  | `scripts/run-rules-side-test.mjs` | The persisted-permission-rules lane's shared decision half. The two capability bits are checked as **two independent gates** — a worker can honestly advertise the rules lane while predating the revoke routes, and that shape must *hide* the governance surface rather than render a dead entry. Failure classification is by **disposition, not cause**: the two 404s (route missing vs. dead ticket) never share a bucket, a 503 `rule_import_retry` means *the ticket is still alive* (the opposite handling of a dead one), and a stale-cursor 400 drops the cursor and re-lists from the top exactly once — never resuming a stale keyset, never surfacing a partial governance list, and never paging past the hard cap. The persist-ack reader is **merged into** `readToolApprovalRespondAck`: the three-state verdict (`persisted` / `refused` / `unknown`) is derived only from an ack that passed the package's structural narrowing, and a half-shaped object such as `{rulePersisted: true}` with no `delivery` reads as `unknown` — the pre-merge shell read would have said `persisted`, which is precisely the double-ledger drift this file closes, so that case is pinned in reverse. The local-allow-rule skeleton pins all five narrowings (whole-tool, tool-name match, literal anchor with the escaped-star counter-example, bare interpreter prefix consulted only for Bash, and the canonical dangerous-pattern overlay) **with their refusal strings byte-for-byte** — the cli's 128-assertion suite anchors the same strings, so a one-character edit here changes observable behaviour on three clients — and asserts the parse is a pure function of its input, because the same call backs both "render the option" and "resolve the selected value" |
348
348
  | `scripts/run-park-decision-layer-test.mjs` | The decision layer behind the "stuck behind a card" family, shared by every client. A pending row that is **not in the queue** is three states, not one: a bounded, interruptible re-probe loop distinguishes *a decidable row*, *not born yet* (no positive evidence that anything settled — an empty queue proves nothing) and *settled elsewhere*, always probes at least once so a zero budget keeps the pre-fix semantics verbatim, cuts a hung read face off at the window rather than only noticing afterwards, and reports the honest failure when the window is spent instead of inventing a decision. The decision-note reader is likewise three-state: an explicit `noteRecorded: false` outranks an echoed note body, absence renders **no line at all**, and untrusted note text is flattened and bounded before it ever reaches a renderer. Row routing anchors on the deciding quantity — a row carrying `gateKind: "human"` with `toolName: "Write"` is a tool gate, because `human` is the engine's *generic* "someone must decide", not a synonym for a question — and the queue scan refuses to surface a row it cannot positively prove belongs to this session. A chain that fails after the row vanished is split by whether a card was ever presented: decided-elsewhere, or not-its-turn-yet. A row-level single-flight makes "at most one card per pending item" structural rather than incidental. The resume three-way card pins the option **order** (the zero-effect choice sits at index 0, because the frame carries no default-focus field and a stray Enter must not attach or cancel), renders only options the wired verbs can honour, collapses every ambiguous answer to zero action, omits the liveness line entirely when the engine gave no evidence, and — when there is no card lane at all — prints three real routes and exits on a dedicated code rather than reporting success |
@@ -542,8 +542,6 @@ export declare function readSteerDelivery(receipt: unknown): string | null;
542
542
  * `delivery:'queued'` 时它是**唯一**能分出「在等哪一类门」的材料(契约逐字:`"suspended"` OR
543
543
  * `"needs_review"`),读不出即 null(不猜一个门种)。 */
544
544
  export declare function readSteerReceiptStatus(receipt: unknown): string | null;
545
- /** cancel 之后**有界**等那条 run 交出会话的缺省窗(`POST …/cancel` 是 202 异步 —— 收下 ≠ 已停)。 */
546
- export declare const CANCEL_RELEASE_WAIT_MS = 10000;
547
545
  /** 清掉某条 run 的「Do nothing」登记 —— 宿主「重新打开操作菜单」入口(下一次 409 重新整卡呈现)。
548
546
  * `sessionId` 与当时喂给 {@link ActiveRunSelfHealDeps.sessionId} 的值同源(缺席 = 默认键)。
549
547
  * 🔴 三选卡与两选卡(L-93)的登记**一起清**:这个入口的语义是「让我重新对这条 run 表态」,
@@ -257,7 +257,7 @@ export function readSteerReceiptStatus(receipt) {
257
257
  return typeof s === 'string' && s.length > 0 ? s : null;
258
258
  }
259
259
  /** cancel 之后**有界**等那条 run 交出会话的缺省窗(`POST …/cancel` 是 202 异步 —— 收下 ≠ 已停)。 */
260
- export const CANCEL_RELEASE_WAIT_MS = 10_000;
260
+ const CANCEL_RELEASE_WAIT_MS = 10_000;
261
261
  /** 出卡前存活对账那一发 `runs.get` 的等待上界(Inkglow-1085 P0b②)——与假死锁复核读口同款
262
262
  * 「慢网也回得来」的诚实预算;窗尽/传输错 = 读不到 ⇒ 保守维持出卡(见 runningChoiceArm 注)。 */
263
263
  const RUNNING_LIVENESS_RECHECK_TIMEOUT_MS = 4_000;
@@ -33,8 +33,6 @@ export declare const LIMITS_MAX_COST_EXCEEDED = "limits.max_cost_exceeded";
33
33
  export declare const LIMITS_MAX_TURNS_EXCEEDED = "limits.max_turns_exceeded";
34
34
  /** 墙钟预算到限(5.8.0 起是**响亮终局** `status:'failed'`;`status:'timeout'` 该终态词整体退役)。 */
35
35
  export declare const LIMITS_MAX_WALLTIME_EXCEEDED = "limits.max_walltime_exceeded";
36
- /** 限额族的**族前缀**(单源;上面四个常量都以它开头)。 */
37
- export declare const LIMITS_ERROR_CODE_PREFIX = "limits.";
38
36
  /**
39
37
  * 该码是否属「引擎侧治理限额到限」族。**开集前缀判**(与 {@link isConfigRefusalCode} 同款):
40
38
  * core 每加一根新的限额轴(`limits.max_*`)判别自动跟上,按成员判的消费点则要跟车。
@@ -121,11 +119,6 @@ export declare function isConfigRefusalCode(code: string | undefined): boolean;
121
119
  export declare const TOOL_END_INTERRUPTED_NEVER_STARTED = "interrupted_never_started";
122
120
  /** 该 call 的结局未知(持有它的进程死了 —— 副作用可能已经发生,**不可**当作没跑过)。 */
123
121
  export declare const TOOL_END_INTERRUPTED_OUTCOME_UNKNOWN = "interrupted_outcome_unknown";
124
- /**
125
- * 中断码识别表(**开集**:`tool_end.errorCode` 整体是开集,未来可能有第三种中断成因)。
126
- * 🔴 两员**语义不对称**,别合并处置:`never_started` 可以重发,`outcome_unknown` 不可以。
127
- */
128
- export declare const TOOL_END_INTERRUPTED_CODES: ReadonlySet<string>;
129
122
  /** 这条 `tool_end` 是不是「中断留下的合成收口帧」。未知码 ⇒ false(开集:不认得就不认得,
130
123
  * 绝不猜)。 */
131
124
  export declare function isInterruptedToolEndCode(code: string | undefined): boolean;
@@ -225,6 +218,18 @@ export declare const RESUME_PREFLIGHT_REJECTED = "resume.preflight_rejected";
225
218
  * (「有人话可补」的拒绝),两个闭集刻意分家 —— 一个回答「能不能等」,一个回答「该对人说什么」。
226
219
  */
227
220
  export declare const RESUME_PLACEMENT_MISMATCH = "resume.placement_mismatch";
221
+ /**
222
+ * 409 `conflict.resume_context_unavailable`(server ≥7.80.1 S-376 起体带结构位;码本身更早就有):**这个批准无处投递** ——
223
+ * 发起这条 run 的会话当前没有可用的上下文来继续它。常见原因是部署按 `REAP_RUN_STALE_SEC` 回收了无活动会话,但 server 在
224
+ * **上下文读取失败**(如存储故障)时也发同一个码,wire 上没有判别位 ⇒ 本包只报事实不推断原因。什么都没被决定,卡仍 pending。
225
+ * 🔴 它不在 `resume.*` 族(前缀是 `conflict.`),也不进 {@link RESUME_RETRY_LATER_CODES} / `RESUME_REFUSAL_CODES`:
226
+ * 能不能等 wire 上没有判别位(回收了就等不回来,读取失败则可能恢复)⇒ 不进「可等」集;出路是「用体上的 `runId` 自己恢复这条 run」,
227
+ * 调大保留时长只能避免以后再发生。
228
+ * 🔴 体上的 `staleAfterSec`(整数秒 = 该部署当下的 `REAP_RUN_STALE_SEC` 真值)是 7.80.1 的 additive 键;sdk 9.6.0 的
229
+ * `ConflictError` **还没把它带上来**(只带 activeTaskId 三件)⇒ 经 sdk 到端今天恒缺席,sdk 出键之日本包读口自动填满
230
+ * (读口按结构读 `errorCode` + `staleAfterSec` + `runId`,见 `resumeRefusalCopy.resumeContextUnavailableFromError`)。
231
+ */
232
+ export declare const RESUME_CONTEXT_UNAVAILABLE = "conflict.resume_context_unavailable";
228
233
  /**
229
234
  * 时间性拒绝族的**闭集**。
230
235
  *
@@ -295,19 +300,6 @@ export declare const DECIDE_WORKFLOW_REMEMBER_UNSUPPORTED = "decide.workflow_rem
295
300
  * 家族由 sdk 类承载);端收到未知 `decide.*` 码一律走开集通用行(机器行原样上屏),本表不为已退役码留位。
296
301
  */
297
302
  export declare const DECIDE_WORKFLOW_LANE_CODES: readonly string[];
298
- /** 这条 workflow 的 park 真相**读不出来**(店抛了 / 超时 / 记录不在 / 记录还在跑)。
299
- * `detail.reason` 开集(`checkpoint_store_threw` / `store_threw` / `record_missing` /
300
- * `record_not_terminal`…;词表属主在 core)⇒ 读得出即原样带,读不懂**不折**已知词。 */
301
- export declare const WORKFLOW_PARK_TRUTH_UNREADABLE = "workflow.park_truth_unreadable";
302
- /** 记录说这一序号 park 着,而 checkpoint 店说那只审批**已决 / 已过期 / 被回收**。
303
- * `detail.status` 三词(`absent` / `expired` / `resolved`)+ `detail.ordinal`;
304
- * `detail.checkpointId` **可缺席**(店里没 id)—— 缺席 ≠ 「没有 checkpoint」。 */
305
- export declare const WORKFLOW_PARK_NOT_PENDING = "workflow.park_not_pending";
306
- /** 记录里的 park **绑不回**它的 checkpoint(记录自相矛盾)。`detail.ordinal` 在场。 */
307
- export declare const WORKFLOW_PARK_BINDING_BROKEN = "workflow.park_binding_broken";
308
- /** 这条 workflow 的子代会 park,而部署**没有 run 店**(或没有 checkpoint 店)⇒ 没有耐久行可供
309
- * 宿主按它路由那只停着的审批。`detail` 恒空对象(这是**配置**问题,不是某一条 run 的事实)。 */
310
- export declare const WORKFLOW_PARK_REQUIRES_RUN_STORE = "workflow.park_requires_run_store";
311
303
  /**
312
304
  * workflow **park 真相**拒绝码的闭集(四员;core 7.17.0 `workflow.ts:602` 码集)。
313
305
  *
@@ -35,7 +35,7 @@ export const LIMITS_MAX_TURNS_EXCEEDED = 'limits.max_turns_exceeded';
35
35
  /** 墙钟预算到限(5.8.0 起是**响亮终局** `status:'failed'`;`status:'timeout'` 该终态词整体退役)。 */
36
36
  export const LIMITS_MAX_WALLTIME_EXCEEDED = 'limits.max_walltime_exceeded';
37
37
  /** 限额族的**族前缀**(单源;上面四个常量都以它开头)。 */
38
- export const LIMITS_ERROR_CODE_PREFIX = 'limits.';
38
+ const LIMITS_ERROR_CODE_PREFIX = 'limits.';
39
39
  /**
40
40
  * 该码是否属「引擎侧治理限额到限」族。**开集前缀判**(与 {@link isConfigRefusalCode} 同款):
41
41
  * core 每加一根新的限额轴(`limits.max_*`)判别自动跟上,按成员判的消费点则要跟车。
@@ -173,7 +173,7 @@ export const TOOL_END_INTERRUPTED_OUTCOME_UNKNOWN = 'interrupted_outcome_unknown
173
173
  * 中断码识别表(**开集**:`tool_end.errorCode` 整体是开集,未来可能有第三种中断成因)。
174
174
  * 🔴 两员**语义不对称**,别合并处置:`never_started` 可以重发,`outcome_unknown` 不可以。
175
175
  */
176
- export const TOOL_END_INTERRUPTED_CODES = new Set([
176
+ const TOOL_END_INTERRUPTED_CODES = new Set([
177
177
  TOOL_END_INTERRUPTED_NEVER_STARTED,
178
178
  TOOL_END_INTERRUPTED_OUTCOME_UNKNOWN,
179
179
  ]);
@@ -309,6 +309,18 @@ export const RESUME_PREFLIGHT_REJECTED = 'resume.preflight_rejected';
309
309
  * (「有人话可补」的拒绝),两个闭集刻意分家 —— 一个回答「能不能等」,一个回答「该对人说什么」。
310
310
  */
311
311
  export const RESUME_PLACEMENT_MISMATCH = 'resume.placement_mismatch';
312
+ /**
313
+ * 409 `conflict.resume_context_unavailable`(server ≥7.80.1 S-376 起体带结构位;码本身更早就有):**这个批准无处投递** ——
314
+ * 发起这条 run 的会话当前没有可用的上下文来继续它。常见原因是部署按 `REAP_RUN_STALE_SEC` 回收了无活动会话,但 server 在
315
+ * **上下文读取失败**(如存储故障)时也发同一个码,wire 上没有判别位 ⇒ 本包只报事实不推断原因。什么都没被决定,卡仍 pending。
316
+ * 🔴 它不在 `resume.*` 族(前缀是 `conflict.`),也不进 {@link RESUME_RETRY_LATER_CODES} / `RESUME_REFUSAL_CODES`:
317
+ * 能不能等 wire 上没有判别位(回收了就等不回来,读取失败则可能恢复)⇒ 不进「可等」集;出路是「用体上的 `runId` 自己恢复这条 run」,
318
+ * 调大保留时长只能避免以后再发生。
319
+ * 🔴 体上的 `staleAfterSec`(整数秒 = 该部署当下的 `REAP_RUN_STALE_SEC` 真值)是 7.80.1 的 additive 键;sdk 9.6.0 的
320
+ * `ConflictError` **还没把它带上来**(只带 activeTaskId 三件)⇒ 经 sdk 到端今天恒缺席,sdk 出键之日本包读口自动填满
321
+ * (读口按结构读 `errorCode` + `staleAfterSec` + `runId`,见 `resumeRefusalCopy.resumeContextUnavailableFromError`)。
322
+ */
323
+ export const RESUME_CONTEXT_UNAVAILABLE = 'conflict.resume_context_unavailable';
312
324
  /**
313
325
  * 时间性拒绝族的**闭集**。
314
326
  *
@@ -413,16 +425,16 @@ export const DECIDE_WORKFLOW_LANE_CODES = Object.freeze([
413
425
  /** 这条 workflow 的 park 真相**读不出来**(店抛了 / 超时 / 记录不在 / 记录还在跑)。
414
426
  * `detail.reason` 开集(`checkpoint_store_threw` / `store_threw` / `record_missing` /
415
427
  * `record_not_terminal`…;词表属主在 core)⇒ 读得出即原样带,读不懂**不折**已知词。 */
416
- export const WORKFLOW_PARK_TRUTH_UNREADABLE = 'workflow.park_truth_unreadable';
428
+ const WORKFLOW_PARK_TRUTH_UNREADABLE = 'workflow.park_truth_unreadable';
417
429
  /** 记录说这一序号 park 着,而 checkpoint 店说那只审批**已决 / 已过期 / 被回收**。
418
430
  * `detail.status` 三词(`absent` / `expired` / `resolved`)+ `detail.ordinal`;
419
431
  * `detail.checkpointId` **可缺席**(店里没 id)—— 缺席 ≠ 「没有 checkpoint」。 */
420
- export const WORKFLOW_PARK_NOT_PENDING = 'workflow.park_not_pending';
432
+ const WORKFLOW_PARK_NOT_PENDING = 'workflow.park_not_pending';
421
433
  /** 记录里的 park **绑不回**它的 checkpoint(记录自相矛盾)。`detail.ordinal` 在场。 */
422
- export const WORKFLOW_PARK_BINDING_BROKEN = 'workflow.park_binding_broken';
434
+ const WORKFLOW_PARK_BINDING_BROKEN = 'workflow.park_binding_broken';
423
435
  /** 这条 workflow 的子代会 park,而部署**没有 run 店**(或没有 checkpoint 店)⇒ 没有耐久行可供
424
436
  * 宿主按它路由那只停着的审批。`detail` 恒空对象(这是**配置**问题,不是某一条 run 的事实)。 */
425
- export const WORKFLOW_PARK_REQUIRES_RUN_STORE = 'workflow.park_requires_run_store';
437
+ const WORKFLOW_PARK_REQUIRES_RUN_STORE = 'workflow.park_requires_run_store';
426
438
  /**
427
439
  * workflow **park 真相**拒绝码的闭集(四员;core 7.17.0 `workflow.ts:602` 码集)。
428
440
  *
@@ -40,12 +40,6 @@ export declare const HEADLESS_FINAL_VERIFY_ENV = "SEMA_HEADLESS_FINAL_VERIFY";
40
40
  * never flags,scenarioWire 同款纪律)。Boolean flag,无值形态,重复无害。
41
41
  */
42
42
  export declare function parseNoFinalVerifyArgv(argv: string[]): boolean;
43
- /**
44
- * Parse the explicit opt-in `--final-verify` out of an argv slice(同 `--no-final-verify` 的扫描
45
- * 纪律:bare `--` 后是 positionals)。默认关时代的唯一 flag 开口;`--no-final-verify` 恒赢它
46
- * (off 优先纪律)。壳侧 commander 需在 0.17.x 提货时注册本 flag(提货单点名)。
47
- */
48
- export declare function parseFinalVerifyArgv(argv: string[]): boolean;
49
43
  /**
50
44
  * The settings-lane off-switch:`SEMA_HEADLESS_FINAL_VERIFY` 设成 {@link envFlagOff} 拼写集
51
45
  * (REF-CC-141 dup-02 单源:`0`/`false`/`no`/`off`/`none`,大小写不敏感)disables the stamp。
@@ -20,7 +20,7 @@ export function parseNoFinalVerifyArgv(argv) {
20
20
  * 纪律:bare `--` 后是 positionals)。默认关时代的唯一 flag 开口;`--no-final-verify` 恒赢它
21
21
  * (off 优先纪律)。壳侧 commander 需在 0.17.x 提货时注册本 flag(提货单点名)。
22
22
  */
23
- export function parseFinalVerifyArgv(argv) {
23
+ function parseFinalVerifyArgv(argv) {
24
24
  for (const a of argv) {
25
25
  if (a === '--')
26
26
  break; // positionals — never flags
@@ -195,15 +195,6 @@ export declare function coerceTaskStatus(s: string): FleetTaskStatus;
195
195
  * 在这里为「哪一席」分叉只会长出一条没有用户面差别的分支。
196
196
  */
197
197
  export declare function coerceWorkflowStatus(s: string | undefined): FleetTaskStatus;
198
- /**
199
- * 187 给 fleet 行命名用的是 agent 的**身份**,从不是它的 objective:
200
- * `tjl(e) = e.type==="in_process_teammate" ? e.identity.agentName : e.agentType`
201
- * (ui-modules/0616_053_uiux_ejl.js:20-21)。SDK `FleetTaskRow` 带 `agentType`/`agentName`
202
- * (§K-7),所以这里**重建** tjl:`agentName`(in-process teammate 实例名)优先,否则 `agentType`
203
- * (subagent_type/teammate 类型名)。只有引擎还没发身份时才退到 objective 派生的 `name` ——
204
- * 折成单行、绝不整段多行 prompt、绝不空(187 的显示名恒是非空短身份)。全空 → 短 id 尾。
205
- */
206
- export declare function deriveAgentLabel(agentType: string | undefined, agentName: string | undefined, name: string | undefined, id: string): string;
207
198
  /**
208
199
  * 引擎把 fleet 行的 `description` 设成 agent 的**最后一个工具名**(service fleet-bus
209
200
  * `description = ev.toolName`)。187 的行正文是进度摘要、从不是工具动词,所以控制面/合成动词
@@ -232,23 +223,6 @@ export declare const CONTROL_TOOL_VERBS: ReadonlySet<string>;
232
223
  * 返回的呈现串上。空/非空面不变:可见转义不产生空白,`|| fallback` 的真假面照旧。
233
224
  */
234
225
  export declare function projectDescription(description: string | undefined): string;
235
- /**
236
- * `toolUses` —— 子代**累计**工具调用数。语义两条(接错会显示一个「看起来很合理」的错数字):
237
- * ① **累计不是增量** —— bg 子代 sink 的 usage 是累计口径,与 `turn_end` 的每轮增量相反。直取,绝不累加。
238
- * ② **缺席 ≠ 0** —— 老 server 不发这个键。有键 ⇒ 真值(含真 0);无键 ⇒ undefined(不知道)。
239
- */
240
- export declare function wireToolUses(r: Pick<FleetTaskRow, 'toolUses'>): number | undefined;
241
- /** `transcriptId` —— 子代转录锚(core 铸的 childSessionId);委派 prompt 凭它去转录读面自取。 */
242
- export declare function wireTranscriptId(r: Pick<FleetTaskRow, 'transcriptId'>): string | undefined;
243
- /** `currentTool: {toolName, target?}` —— `currentAction` 的结构化同源体(core 1.429 → server 1.288)。 */
244
- export declare function wireCurrentTool(r: Pick<FleetTaskRow, 'currentTool'>): {
245
- toolName: string;
246
- target?: string;
247
- } | undefined;
248
- /** `startedCount` —— 「已起过的 agent 数」。🔴 缺席 ≠ 0(见 FleetWorkflowView 键注)。 */
249
- export declare function wireStartedCount(r: Pick<FleetWorkflowRow, 'startedCount'>): number | undefined;
250
- /** `parentToolCallId`(P1-2)—— 归属直读键。空串按缺席处理(引擎不该发,发了也别当真)。 */
251
- export declare function wireParentToolCallId(r: Pick<FleetTaskRow, 'parentToolCallId'>): string | undefined;
252
226
  /**
253
227
  * `parentId` —— 悬空父的存在性判定唯一铸点(REF-CC-049,fleet2-11)。B10 禁真值判定当存在性判定:
254
228
  * 空串是脏值形(SDK 只承诺「悬空不发」,那是 server 当下行为不是类型保证),按缺席处理。
@@ -258,13 +232,6 @@ export declare function wireParentToolCallId(r: Pick<FleetTaskRow, 'parentToolCa
258
232
  export declare function wireParentId(r: Pick<FleetTaskRow, 'parentId'>): string | undefined;
259
233
  /** 终态四键之 `usage`(数值 verbatim,只挑数字位;全脏 ⇒ 整键缺席,不造空对象)。 */
260
234
  export declare function wireRowUsage(r: Pick<FleetTaskRow, 'usage'>): FleetRowUsage | undefined;
261
- /** 终态四键之 `editedFiles`(bg 子代专属;脏项逐条丢,不整键丢)。 */
262
- export declare function wireEditedFiles(r: Pick<FleetTaskRow, 'editedFiles'>): Array<{
263
- path: string;
264
- edits: number;
265
- }> | undefined;
266
- /** 终态四键之 `stoppedBy`(开放枚举 verbatim:"user"/"parent"/"system"/…)。 */
267
- export declare function wireStoppedBy(r: Pick<FleetTaskRow, 'stoppedBy'>): string | undefined;
268
235
  /**
269
236
  * #261 §2① `cycleSeq`(server ≥7.25.0 / core 5.36.0 #258;SDK **7.2.0** 才把它声明进
270
237
  * `FleetTaskRow` ⇒ 本包 0.38.0 提货补投)—— 这一行的**代际号**,与 `FleetBgNotification.seq` /
@@ -276,17 +243,6 @@ export declare function wireStoppedBy(r: Pick<FleetTaskRow, 'stoppedBy'>): strin
276
243
  * 非整数都不是合法代际号),绝不 `?? 0`、绝不 `?? 1`。
277
244
  */
278
245
  export declare function wireCycleSeq(r: Pick<FleetTaskRow, 'cycleSeq'>): number | undefined;
279
- /**
280
- * #261 §2② `retiredBy`(server ≥7.25.0;SDK 7.2.0 声明)—— 这一帧的终态**不是发布方亲报**,
281
- * 而是对账腿从 durable run 行**投影**出来的(发布方死了,行本会永久僵在活跃集里当幽灵)。
282
- *
283
- * 🔴 **发布方亲报的终态帧恒不带此键** ⇒ 两种终态在 wire 上可判:要区分「引擎说它完了」与
284
- * 「我们从库里读出来它完了」时,这是**唯一**的判据([ghost-rows-need-upstream-liveness] 的
285
- * wire 侧对位物 —— 此前本层把这一位整个丢掉,两种终态在视图上同形)。
286
- * service 侧今天是单词闭集(`"reconcile"`),**读侧开集**:未知词原样透传(将来的第二个投影者
287
- * 会是一个新词而不是改义),消费端 branch 已知值 + 通渲兜底,**绝不**按成员判死。
288
- */
289
- export declare function wireRetiredBy(r: Pick<FleetTaskRow, 'retiredBy'>): string | undefined;
290
246
  /** 终态四键之 `resumable`(仅 bg 子代有源;非 boolean ⇒ 缺席,**不当 false**)。 */
291
247
  export declare function wireResumable(r: Pick<FleetTaskRow, 'resumable'>): boolean | undefined;
292
248
  export interface ProjectTasksOptions {
@@ -109,7 +109,7 @@ export function coerceWorkflowStatus(s) {
109
109
  * (subagent_type/teammate 类型名)。只有引擎还没发身份时才退到 objective 派生的 `name` ——
110
110
  * 折成单行、绝不整段多行 prompt、绝不空(187 的显示名恒是非空短身份)。全空 → 短 id 尾。
111
111
  */
112
- export function deriveAgentLabel(agentType, agentName, name, id) {
112
+ function deriveAgentLabel(agentType, agentName, name, id) {
113
113
  const identity = collapseLabel(agentName ?? '') || collapseLabel(agentType ?? '');
114
114
  if (identity)
115
115
  return identity;
@@ -167,17 +167,17 @@ export function projectDescription(description) {
167
167
  * ① **累计不是增量** —— bg 子代 sink 的 usage 是累计口径,与 `turn_end` 的每轮增量相反。直取,绝不累加。
168
168
  * ② **缺席 ≠ 0** —— 老 server 不发这个键。有键 ⇒ 真值(含真 0);无键 ⇒ undefined(不知道)。
169
169
  */
170
- export function wireToolUses(r) {
170
+ function wireToolUses(r) {
171
171
  const v = r.toolUses;
172
172
  return typeof v === 'number' && Number.isFinite(v) && v >= 0 ? v : undefined;
173
173
  }
174
174
  /** `transcriptId` —— 子代转录锚(core 铸的 childSessionId);委派 prompt 凭它去转录读面自取。 */
175
- export function wireTranscriptId(r) {
175
+ function wireTranscriptId(r) {
176
176
  const v = r.transcriptId;
177
177
  return typeof v === 'string' && v.length > 0 ? v : undefined;
178
178
  }
179
179
  /** `currentTool: {toolName, target?}` —— `currentAction` 的结构化同源体(core 1.429 → server 1.288)。 */
180
- export function wireCurrentTool(r) {
180
+ function wireCurrentTool(r) {
181
181
  const v = r.currentTool;
182
182
  if (typeof v !== 'object' || v === null)
183
183
  return undefined;
@@ -191,12 +191,12 @@ export function wireCurrentTool(r) {
191
191
  };
192
192
  }
193
193
  /** `startedCount` —— 「已起过的 agent 数」。🔴 缺席 ≠ 0(见 FleetWorkflowView 键注)。 */
194
- export function wireStartedCount(r) {
194
+ function wireStartedCount(r) {
195
195
  const v = r.startedCount;
196
196
  return typeof v === 'number' && Number.isFinite(v) && v >= 0 ? v : undefined;
197
197
  }
198
198
  /** `parentToolCallId`(P1-2)—— 归属直读键。空串按缺席处理(引擎不该发,发了也别当真)。 */
199
- export function wireParentToolCallId(r) {
199
+ function wireParentToolCallId(r) {
200
200
  const v = r.parentToolCallId;
201
201
  return typeof v === 'string' && v.length > 0 ? v : undefined;
202
202
  }
@@ -224,7 +224,7 @@ export function wireRowUsage(r) {
224
224
  return Object.keys(out).length > 0 ? out : undefined;
225
225
  }
226
226
  /** 终态四键之 `editedFiles`(bg 子代专属;脏项逐条丢,不整键丢)。 */
227
- export function wireEditedFiles(r) {
227
+ function wireEditedFiles(r) {
228
228
  const v = r.editedFiles;
229
229
  if (!Array.isArray(v))
230
230
  return undefined;
@@ -239,7 +239,7 @@ export function wireEditedFiles(r) {
239
239
  return out.length > 0 ? out : undefined;
240
240
  }
241
241
  /** 终态四键之 `stoppedBy`(开放枚举 verbatim:"user"/"parent"/"system"/…)。 */
242
- export function wireStoppedBy(r) {
242
+ function wireStoppedBy(r) {
243
243
  const v = r.stoppedBy;
244
244
  return typeof v === 'string' && v.length > 0 ? v : undefined;
245
245
  }
@@ -267,7 +267,7 @@ export function wireCycleSeq(r) {
267
267
  * service 侧今天是单词闭集(`"reconcile"`),**读侧开集**:未知词原样透传(将来的第二个投影者
268
268
  * 会是一个新词而不是改义),消费端 branch 已知值 + 通渲兜底,**绝不**按成员判死。
269
269
  */
270
- export function wireRetiredBy(r) {
270
+ function wireRetiredBy(r) {
271
271
  const v = r.retiredBy;
272
272
  return typeof v === 'string' && v.length > 0 ? v : undefined;
273
273
  }
@@ -10,8 +10,6 @@ export declare function wasGateArmedFor(sessionKey: string, key: string | null |
10
10
  /** 消费一个身份键(plan 族的决断消费请优先走 {@link notePlanReviewAnswered} 族 —— 它同时推代;
11
11
  * 本口保留给「只清账不推代」的宿主场景与 0.30.0 存量消费方,语义不变)。 */
12
12
  export declare function clearArmedGate(key: string | null | undefined): void;
13
- /** W1 带 key 变体。 */
14
- export declare function clearArmedGateFor(sessionKey: string, key: string | null | undefined): void;
15
13
  /**
16
14
  * 订阅 arm 事件(#269 上收):返回退订钩,调用方**必须**在自己的生命周期末调它(监听器挂在
17
15
  * 模块级长存表上,漏退 = 闭包泄漏)。
@@ -22,8 +20,6 @@ export declare function clearArmedGateFor(sessionKey: string, key: string | null
22
20
  * 给出 false ⇒ 把一次真呈现误判成「行从未出生」⇒ 回环再铸一张卡,同一个待决项两张可按的卡。
23
21
  */
24
22
  export declare function onGateArmed(listener: (key: string) => void): () => void;
25
- /** W1 带 key 变体。 */
26
- export declare function onGateArmedFor(sessionKey: string, listener: (key: string) => void): () => void;
27
23
  /** 测试用:缩短回执看门狗(null 复位)。 */
28
24
  export declare function _setGateArmedWaitMsForTest(ms: number | null): void;
29
25
  /** 回执看门狗现值(重开臂的缺省等待窗)。 */
@@ -47,8 +43,6 @@ export declare function waitForGateArmedFor(sessionKey: string, keys: readonly s
47
43
  * attempt token —— 归一键之外**事件级**再发一枪原始 id(不入 Set,台账词汇保持归一键)。
48
44
  * 重开臂锚它,同 gate 两次在飞重开时一次真入队只唤对应那次尝试,不再同键互唤。 */
49
45
  export declare function registerArmedGateFromQuestionId(questionId: unknown): void;
50
- /** W1 带 key 变体。 */
51
- export declare function registerArmedGateFromQuestionIdFor(sessionKey: string, questionId: unknown): void;
52
46
  /**
53
47
  * plan_review 的台账键(当代)。0 代 = `planReviewQuestionId(taskId)`(canonical 原形 ——
54
48
  * overlay 对**原卡**(done 帧首扎)的登记落进同一键);≥1 代 = 同形 + `#g<N>` 尾。
@@ -144,7 +144,7 @@ export function clearArmedGate(key) {
144
144
  clearArmedGateFor(DEFAULT_SESSION_KEY, key);
145
145
  }
146
146
  /** W1 带 key 变体。 */
147
- export function clearArmedGateFor(sessionKey, key) {
147
+ function clearArmedGateFor(sessionKey, key) {
148
148
  if (typeof key === 'string' && key.length > 0)
149
149
  armedGatesByKey.get(sessionKey)?.delete(key);
150
150
  }
@@ -161,7 +161,7 @@ export function onGateArmed(listener) {
161
161
  return onGateArmedFor(DEFAULT_SESSION_KEY, listener);
162
162
  }
163
163
  /** W1 带 key 变体。 */
164
- export function onGateArmedFor(sessionKey, listener) {
164
+ function onGateArmedFor(sessionKey, listener) {
165
165
  const listeners = listenersFor(sessionKey);
166
166
  listeners.add(listener);
167
167
  return () => {
@@ -238,7 +238,7 @@ export function registerArmedGateFromQuestionId(questionId) {
238
238
  registerArmedGateFromQuestionIdFor(DEFAULT_SESSION_KEY, questionId);
239
239
  }
240
240
  /** W1 带 key 变体。 */
241
- export function registerArmedGateFromQuestionIdFor(sessionKey, questionId) {
241
+ function registerArmedGateFromQuestionIdFor(sessionKey, questionId) {
242
242
  if (typeof questionId !== 'string' || questionId.length === 0)
243
243
  return;
244
244
  // 🔴 canonical 复用的去重记号过期(复审 #244 F1 轮一 [medium]):arm 臂对同 run 每只 plan gate
@@ -20,12 +20,8 @@
20
20
  *
21
21
  * 🔴 零 import 纯函数叶(portability:不进任何闭包新边;index 闭包 +1 文件已登记)。
22
22
  */
23
- /** overlay 问答帧的 ask-gate 命名空间前缀(`parkResolver.surfaceGateAndDecide` 合成帧 id 用)。 */
24
- export declare const HITL_ASK_QUESTION_ID_PREFIX = "hitl-ask:";
25
23
  /** plan_review 审批卡的合成 questionId 前缀(`planReviewWire` 首次 arm 与重开腿共用)。 */
26
24
  export declare const PLAN_REVIEW_QUESTION_ID_PREFIX = "plan-review:";
27
- /** live `tool_approval` 帧腿的卡口 callKey 前缀(帧上无 gatedCallId 可对齐,以 approvalId 铸)。 */
28
- export declare const HITL_FRAME_CALL_KEY_PREFIX = "hitl-frame:";
29
25
  /** 重开腿铸新身份的尾分隔符(`…#reopen-<suffix>`);键归一时从这里剥尾,首呈与重开落同一键。 */
30
26
  export declare const REOPEN_ID_TAIL = "#reopen-";
31
27
  /**
@@ -21,11 +21,11 @@
21
21
  * 🔴 零 import 纯函数叶(portability:不进任何闭包新边;index 闭包 +1 文件已登记)。
22
22
  */
23
23
  /** overlay 问答帧的 ask-gate 命名空间前缀(`parkResolver.surfaceGateAndDecide` 合成帧 id 用)。 */
24
- export const HITL_ASK_QUESTION_ID_PREFIX = 'hitl-ask:';
24
+ const HITL_ASK_QUESTION_ID_PREFIX = 'hitl-ask:';
25
25
  /** plan_review 审批卡的合成 questionId 前缀(`planReviewWire` 首次 arm 与重开腿共用)。 */
26
26
  export const PLAN_REVIEW_QUESTION_ID_PREFIX = 'plan-review:';
27
27
  /** live `tool_approval` 帧腿的卡口 callKey 前缀(帧上无 gatedCallId 可对齐,以 approvalId 铸)。 */
28
- export const HITL_FRAME_CALL_KEY_PREFIX = 'hitl-frame:';
28
+ const HITL_FRAME_CALL_KEY_PREFIX = 'hitl-frame:';
29
29
  /** 重开腿铸新身份的尾分隔符(`…#reopen-<suffix>`);键归一时从这里剥尾,首呈与重开落同一键。 */
30
30
  export const REOPEN_ID_TAIL = '#reopen-';
31
31
  /**
@@ -69,8 +69,6 @@ import type { AgentEvent, ApprovalDecision, ApprovalStaleError, PendingCheckpoin
69
69
  import type { ApprovalsListEnvelope } from './crashConverged.js';
70
70
  /** durable `/decide` 腿的既有缺省拒因(不带归因时逐字不变 —— 0.27.0 及之前的 wire 字节)。 */
71
71
  export declare const DEFAULT_DENY_REASON = "The user rejected this tool use";
72
- /** server 两条腿共用的 reason 字符上限(超限 413,决断被打回)。 */
73
- export declare const MAX_DENY_REASON_CHARS = 4096;
74
72
  /**
75
73
  * 归因原文 → 可上 wire 的形。缺席/非串/纯空白 ⇒ `undefined`(调用方自定回落:deny 腿落
76
74
  * {@link DEFAULT_DENY_REASON},plan-review 腿键不落);超上限 ⇒ 截到上限并 debug 留痕。
@@ -11,7 +11,7 @@ import { abortableSleep } from '../abortableSleep.js';
11
11
  /** durable `/decide` 腿的既有缺省拒因(不带归因时逐字不变 —— 0.27.0 及之前的 wire 字节)。 */
12
12
  export const DEFAULT_DENY_REASON = 'The user rejected this tool use';
13
13
  /** server 两条腿共用的 reason 字符上限(超限 413,决断被打回)。 */
14
- export const MAX_DENY_REASON_CHARS = 4096;
14
+ const MAX_DENY_REASON_CHARS = 4096;
15
15
  /**
16
16
  * 归因原文 → 可上 wire 的形。缺席/非串/纯空白 ⇒ `undefined`(调用方自定回落:deny 腿落
17
17
  * {@link DEFAULT_DENY_REASON},plan-review 腿键不落);超上限 ⇒ 截到上限并 debug 留痕。
@@ -65,8 +65,6 @@ export declare function planReviewDecisionFromAnswer(answer: QuestionAnswer | nu
65
65
  * (它们没有 dismissed 臂会到达记账行)。
66
66
  */
67
67
  export declare function notePlanReviewAnsweredIfDecisive(questionId: unknown, answer: QuestionAnswer | null | undefined): void;
68
- /** W1 带 key 变体。 */
69
- export declare function notePlanReviewAnsweredIfDecisiveFor(sessionKey: string, questionId: unknown, answer: QuestionAnswer | null | undefined): void;
70
68
  /**
71
69
  * done 帧的 plan_review park 形状(结构性读,别的终局一律 false)。
72
70
  *
@@ -89,7 +89,7 @@ export function notePlanReviewAnsweredIfDecisive(questionId, answer) {
89
89
  notePlanReviewAnsweredIfDecisiveFor(DEFAULT_SESSION_KEY, questionId, answer);
90
90
  }
91
91
  /** W1 带 key 变体。 */
92
- export function notePlanReviewAnsweredIfDecisiveFor(sessionKey, questionId, answer) {
92
+ function notePlanReviewAnsweredIfDecisiveFor(sessionKey, questionId, answer) {
93
93
  if (planReviewDecisionFromAnswer(answer ?? { answers: [] }) === 'dismissed')
94
94
  return;
95
95
  notePlanReviewAnsweredFor(sessionKey, questionId);