@sema-agent/client-core 0.30.0 → 0.30.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +89 -0
- package/README.md +2 -1
- package/dist/adapter/activeRunSelfHeal.d.ts +21 -0
- package/dist/adapter/activeRunSelfHeal.js +54 -4
- package/dist/hitl/armedGateRegistry.d.ts +62 -4
- package/dist/hitl/armedGateRegistry.js +226 -14
- package/dist/hitl/localAllowRule.d.ts +80 -0
- package/dist/hitl/localAllowRule.js +96 -0
- package/dist/hitl/parkOwnership.d.ts +2 -1
- package/dist/hitl/parkOwnership.js +11 -3
- package/dist/hitl/persistedRulesWire.d.ts +169 -0
- package/dist/hitl/persistedRulesWire.js +236 -0
- package/dist/hitl/planReviewWire.d.ts +31 -1
- package/dist/hitl/planReviewWire.js +69 -30
- package/dist/hitl/toolApprovalWire.d.ts +5 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +13 -0
- package/docs/INTEGRATION-CLIENTS.md +9 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,95 @@
|
|
|
16
16
|
> 🔴 **互链**(web [C166]⑦):各版「已知局限」段只记**该版新增**;接入面已知局限的完整台账在
|
|
17
17
|
> `docs/INTEGRATION-CLIENTS.md` §6e/§7 —— **只读其一会漏**,两处都过。
|
|
18
18
|
|
|
19
|
+
## 0.30.2
|
|
20
|
+
|
|
21
|
+
- **#244 F2 HITL 规则侧上收 · 包半场(A-028.14 + parseLocalAllowRule 硬排期件;hitl/ 新两件,
|
|
22
|
+
+7 公面导出)**(2026-08-14;源形 = cli `src/sema/persistedRulesWire.ts` /
|
|
23
|
+
`src/sema/localAllowRuleWrite.ts` 的纯判定面;常驻门 `scripts/run-rules-side-test.mjs`,收官形 86 checks):
|
|
24
|
+
- **`hitl/persistedRulesWire.ts`** —— 持久规则车道的通用判定半场:
|
|
25
|
+
`persistedRulesLaneAvailable` / `persistedRulesGovernanceAvailable`(能力位双段 gate,
|
|
26
|
+
`permissionRulesRevoke` 存在性 × `permissionRules` 真值合取;baseUrl 由宿主显式传入,env
|
|
27
|
+
缺省读法留壳)· `RulesFailure` + `classifyRulesFailure`(处置分类,404 两支绝不共用一格)·
|
|
28
|
+
`listAllPersistedRules` + `RulesFacade`(keyset 翻页收口:显式 limit 200 / 游标失效丢游标
|
|
29
|
+
重列一次 / 页数硬帽,绝不交半份清单)· `classifySkippedReason`(散文前缀分类)。
|
|
30
|
+
⚠️ 相对 cli 源形收紧两处(codex F2 两轮,各有门回归钉):
|
|
31
|
+
① `listAllPersistedRules` 的 params **不收调用方 cursor**(类型 `Omit<…,'cursor'>` + 运行期
|
|
32
|
+
剥除留痕)—— 源形的 `...params` 会把外来 cursor 原样送出,「列全」从中途起步却报完整清单;
|
|
33
|
+
② 页体 **fail-closed 窄化** —— 源形把坏形 2xx 页(`{rev:9}` 无 rules 数组 / rev 非有限数 /
|
|
34
|
+
nextCursor 空串或非串)静默认证成「完整(空)清单」,治理面据此宣称零规则而活规则不可见;
|
|
35
|
+
现坏形页一律 `{ok:false, failure:{kind:'error'}}`,nextCursor 只认「缺席=终页/非空串=续页」;
|
|
36
|
+
轮三追钉:跨页 rev 漂移(2xx 换 rev = 混合快照)与坏规则行(撤销承重两键 `rule`/`scope`
|
|
37
|
+
非空串,坏行=坏页;展示键不过度收紧,未知附加键照收)同臂 fail-closed。
|
|
38
|
+
- **persist-ack 读口合成一处(A-028.14 收口本体,⚠️ 行为收紧)**:`readRulePersistOutcome`
|
|
39
|
+
的三态判决改从 `readToolApprovalRespondAck` 的结构窄化产物导出 —— `rulePersisted` /
|
|
40
|
+
`ruleRefusal` 从此单一台账。**半形 ack(如 `{rulePersisted:true}` 无 `delivery` /
|
|
41
|
+
`approvalId` / 闭集 `decision`)从「读成 persisted」收紧为 `unknown`**(一张连回执身份都
|
|
42
|
+
不成形的 ack 不驱动「已保存」告知);真 server ack 恒合形,真车道零行为差。门有反向钉。
|
|
43
|
+
- **`hitl/localAllowRule.ts`** —— durable 腿「不再询问」本地落规则的判定骨架
|
|
44
|
+
`parseLocalAllowRule(rule, expectedToolName, deps)`:窄化①(整工具拒)②(工具名对齐)
|
|
45
|
+
③(字面锚,转义星是字面量)⑤(裸解释器/包装器前缀,只对 Bash 族咨询)⑤b(canonical
|
|
46
|
+
危险规则谓词叠加,[3925] P0 跟修的包位)。规则语法(parse/format)与危险谓词经
|
|
47
|
+
`LocalAllowRuleDeps` 注入(parkOwnership deps 同形)—— 谓词表本体(BARE_SHELL_PREFIXES /
|
|
48
|
+
dangerousPatterns)留宿主,包内绝不自建第二份名单。🔴 拒绝集文案是三端可观察行为
|
|
49
|
+
(cli 128 断言套逐字锚),门内逐字节钉。
|
|
50
|
+
- 消费提示(cli 1.0.77 起换装;desktop/web 接 durable 行第三态时直接消费本骨架):壳侧
|
|
51
|
+
`persistedRulesWire` 的 CC settings 三层 fs 读、SDK facade 装配、通知呈现均留壳原位。
|
|
52
|
+
|
|
53
|
+
## 0.30.1
|
|
54
|
+
|
|
55
|
+
- **#244 F1 决断卡链换装批 · 包半场(hitl/armedGateRegistry + hitl/planReviewWire;A-028.3/.4 二段,
|
|
56
|
+
+12 公面导出 +1 测试钩)**(2026-08-14;红先绿后:`run-selfheal-reopen-test.mjs` G9 修前红
|
|
57
|
+
(exit 1,分代/事件源/回执三位皆缺)→ 补位后 278 checks 绿):
|
|
58
|
+
- **arm 事件源上收**(cli `src/sema/armedGateRegistry.ts` 的呈现回执半场):`registerArmedGate(For)`
|
|
59
|
+
每次调用发事件(不是集合成员测试 —— 复见重开的第二次呈现也要有回执);新导出
|
|
60
|
+
`waitForGateArmed(For)`(带窗一次性回执,timer 刻意不 unref)· `onGateArmed(For)`(#269 无窗
|
|
61
|
+
事实订阅)· `gateArmedWaitMs`(缺省 5000ms 看门狗,`_setGateArmedWaitMsForTest` 覆盖)。
|
|
62
|
+
`registerArmedGateFromQuestionId(For)` 对**原始帧 id**(带 `#reopen-*` 尾)在归一键之外事件级
|
|
63
|
+
加发一枪(尝试级回执;不入 Set)。
|
|
64
|
+
- **plan_review 呈现分代上收**(A-024.4;语义照 cli 现实现):wire 上没有 gate 实例位,恒定键会让
|
|
65
|
+
同 run 第二只 plan 门 firstSight 恒 false(对从未呈现过的新卡谎报「被关过」)。新导出
|
|
66
|
+
`planReviewArmedKey(For)`(0 代与 canonical 字节同形,≥1 代接 `#g<N>` 尾)·
|
|
67
|
+
`notePlanReviewAnswered(For)`(决断记账:当代键清 + 推代,按完整 questionId 去重 —— overlay
|
|
68
|
+
answerAndRelease 与 responder 两条通路对同一张卡各报一次恰推一代)·
|
|
69
|
+
`notePlanReviewAnsweredIfDecisive(For)`(决断性门控:Esc/空答是 dismissal,不推代)。
|
|
70
|
+
包内两个 responder(arm 臂 + 重开臂)的呈现史消费由 `clearArmedGate` 改走分代记账
|
|
71
|
+
(`wasGateArmed(canonical)` 在 0 代的决断后读面逐字兼容;`clearArmedGate(For)` 导出保留,语义不变)。
|
|
72
|
+
- **`reopenPlanReviewCard` 呈现回执模式**(新可选位 `ReopenPlanReviewOpts.presentationReceiptMs`;
|
|
73
|
+
在场 ⇒ 返回 `Promise<ReopenCardVerdict>`,重载声明,存量同步调用零影响):publish 之后等宿主
|
|
74
|
+
呈现面的登记事件(键=原始帧 id),回执到手才 `{reopened:true}`;窗尽如实 `{reopened:false}`
|
|
75
|
+
且**零登记**(修前:发布即登记即报 true —— 帧被 overlay 去重集/hook 窗吃掉时,下一次重开会
|
|
76
|
+
谎称复见);responder 窗尽后故意保留(迟到卡必须仍可答)。firstSight/登记键同批切到分代键。
|
|
77
|
+
- **#244 F1 · 409 自愈分诊树补假死锁复核读口(adapter/activeRunSelfHeal;A-028.1 二段,+1 导出)**
|
|
78
|
+
(2026-08-14):`ActiveRunSelfHealDeps` 新增 `listOwnedPendingApprovals?`([3892]-[3899] P0 防御,
|
|
79
|
+
语义照 cli 现实现)—— `hasPendingDecision` 镜像说有卡时向引擎复核**属主** pending 行数,恰 0 =
|
|
80
|
+
镜像脏值的正面证据 ⇒ 不 stand down 放行真分诊;>0 / 抛错 / 读口缺席 ⇒ 保守维持(存量装配零影响)。
|
|
81
|
+
另导出 `INTERACTIVE_WAY_OUT`(默认出路串单源 —— 宿主 resume 对账腿等失败半场要说同一句真出路,
|
|
82
|
+
各写一份必漂;覆盖语义照旧走 `ActiveRunSelfHealCopy.wayOut`)。
|
|
83
|
+
- **#244 F1 · 复审跟修批**(2026-08-14;同窗 codex 轮一三条 + cli 换装批 G-c 尾竞态,红先绿后
|
|
84
|
+
G9⑥/⑦ 四断言):① `waitForClaimRelease` 被**窗自己的截止**掐断的探测不再作废上一发完成读数
|
|
85
|
+
(404/传输错是「回答了不知道」才作废;不区分则 `confirmedHeld` 在窗尾随机翻 false,同输入两跑
|
|
86
|
+
两种文案);② 假死锁复核读口加 **4s 有界窗**并合流 `deps.signal`(probe 恒不落地时整棵分诊树
|
|
87
|
+
不再挂死 —— 窗尽=分不出真卡与幽灵,保守 stand down);③ plan 分代的去重记号随**新 canonical
|
|
88
|
+
卡呈现**过期(arm 臂对同 run 每只 gate 复用 canonical id,记号不过期则第二只门的决断被第一只的
|
|
89
|
+
记号吃掉 ⇒ 第三只门重开又谎报「被关过」);④ `reopenPlanReviewCard` 重载收紧:同步臂静态排除
|
|
90
|
+
`presentationReceiptMs`(宽类型带回执位的调用不再被谎报成同步),另给宽形联合兜底重载。
|
|
91
|
+
轮二跟修(G9⑦c/⑦d 红先绿后):⑤ 复核竞速改**可回收**形 —— `listOwnedPendingApprovals` 契约
|
|
92
|
+
加 `opts.signal`(合流 4s 窗 × `deps.signal`,装配方透传进真实读面;忽略该参的存量实现照常
|
|
93
|
+
工作),finally 统一 abort(快成功不留 4s 悬挂计时器拖 `-p` 进程、窗尽真掐在飞请求);
|
|
94
|
+
⑥ 已中止(进门前/竞速期间)一律不采信 `count===0` —— Esc 之后绝不接着放行重呈。
|
|
95
|
+
轮二 [medium]「canonical 记号过期让旧卡迟到记账消费新卡」**驳回存档**:该窗(迟到重复记账
|
|
96
|
+
跨越一次同 id 新卡呈现)的失真方向是首见话术 —— 零历史断言的诚实安全侧(反向才编造「被关
|
|
97
|
+
过」);wire 无 gate 实例位([3664]),per-instance token 在答卡通路上结构性不可得,已在
|
|
98
|
+
`registerArmedGateFromQuestionIdFor` 注内成文。
|
|
99
|
+
- **行为增强(hitl/parkOwnership)**:`ParkOwnershipDeps.isOwnRun` 注入语义从**顶替**缺省台账腿改为
|
|
100
|
+
**并联**(默认会话键下,注入腿与包内 `isOwnEngineRun` 缺省腿任一命中即 owned)。修前「端为补一条
|
|
101
|
+
自己的正向腿」会顺手关掉进程内台账腿,消费端必须记得自 OR(cli 1.0.76 扫码 P1 实翻,主板 [3925])。
|
|
102
|
+
fail-closed 方向不变(并联只多不少地要求正向证明);非默认 sessionKey 下缺省腿照旧整条跳过。
|
|
103
|
+
已自 OR 的存量消费端不受影响(幂等)。门=park-decision-layer G6 四断言(修前 G6① 红留证)。
|
|
104
|
+
- **JSDoc 收敛(hitl/toolApprovalWire)**:`ruleSuggestionsReadOnly` 顶注红线从「绝不渲成可选中项」
|
|
105
|
+
收敛到真不变量「决断字节绝不带规则位」——渲成可选中项允许,前提=兑付走客户端本地 settings 写
|
|
106
|
+
(cli 1.0.76 三态卡实形;`ef1be51`,随本版首次出包)。
|
|
107
|
+
|
|
19
108
|
## 0.30.0 — 2026-08-14
|
|
20
109
|
|
|
21
110
|
> 发车门清账完成:0.30.0 发版扫描 36 条 confirmed 全处置(codex 八轮终轮零新),
|
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.30.
|
|
38
|
+
**Version:** 0.30.2
|
|
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
|
|
@@ -251,6 +251,7 @@ public-surface guard checks that last one).
|
|
|
251
251
|
| `scripts/run-durable-card-display-keys-test.mjs` | The durable approval row's two display keys survive the row→card recast in `surfaceFsApprovalAndDecide`: `governanceForced` stamps on strict `true` only (absence is "no evidence", never `false`), `ruleSuggestions` passes through the same shape-narrowing reader as the live-frame leg and lands on the **read-only** card key — plus a standing pin that the durable leg never stamps the redeemable `ruleSuggestions` card position (the `/decide` body has no rule slot; offering a "don't ask again" option there would be an affordance nothing can honour) |
|
|
252
252
|
| `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 |
|
|
253
253
|
| `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 |
|
|
254
|
+
| `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" |
|
|
254
255
|
| `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 |
|
|
255
256
|
| `scripts/run-selfheal-reopen-test.mjs` | The 409 active-run self-heal decision chain: `governanceForced` narrows on strict `true` only; triage prefers the wire's `pendingGate.kind` and falls back to the status table (an off-table kind is never guessed into a card arm — hands-off plus the honest wording); a first-sight card makes zero closed/reopened claims and a host presentation receipt of `presented: false` demotes the outcome to reopen-failed; park-row ownership is a fail-closed positive proof (own-run ledger or session id — unprovable is not owned); the three gate-identity key literals live in exactly one mint (`hitl/gateIdentity.ts`, AST string-token scan); the armed-gate presentation ledger is per-session; and the `plan_review` reopen arm shares the arm arm's card body, three-state verdict and delivery pipe, consuming the presentation history once a decision is delivered. The same chain also carries the `running` three-way card: both plan-family gate kinds route to the plan arm and all four ask-family kinds to the ask arm (an off-table kind still never gets guessed into either); the card is offered only for verbs that can actually be honoured and a missing presenter means zero action rather than a silent cancel; a steer is sent **exactly once** with its three delivery outcomes worded apart (a `queued` receipt is the wire correcting the triage input, so the named park word decides which card gets reopened, and an unrecognised park word drives neither arm), and a steer failure is split into *provably not delivered* (4xx) and *delivery unknown*, because telling a user to resend a non-idempotent instruction that may already have landed is how duplicates get made. After a user-chosen cancel, "the session is free" is asserted only from a whitelist of terminal states — park states hold the claim, an unrecognised state word is not a release, a failed read is *unknown* rather than a release, and only a 404 counts as one — and the honest timeout line quotes how long it really waited |
|
|
256
257
|
|
|
@@ -153,6 +153,22 @@ export interface RunningChoiceRequest {
|
|
|
153
153
|
export interface ActiveRunSelfHealDeps {
|
|
154
154
|
/** 屏幕上是否已有一张待决卡(有 ⇒ 结局 = decision-pending,绝不重开第二张)。 */
|
|
155
155
|
hasPendingDecision?: () => boolean;
|
|
156
|
+
/**
|
|
157
|
+
* [3892]-[3899] P0 假死锁防御(#244 F1 上收,语义照 cli 现实现):`hasPendingDecision` 的真值
|
|
158
|
+
* 常来自渲染队列长度的**本地镜像**,它在 `--resume` 后可能卡成脏值(队列里躺着一张从未渲上屏
|
|
159
|
+
* 的幽灵卡)—— 布尔真 + 屏上零真卡 ⇒ 每条消息被 decision-pending 臂闸死,而引擎侧那个 run 早已
|
|
160
|
+
* completed。本读口 = **引擎侧属主 pending 行数**(宿主装配点带归属过滤:引擎全局队列可躺着
|
|
161
|
+
* 别家会话的陈年行,裸计数恒非零 = 防御恒不触发,等于没修)。
|
|
162
|
+
* 判据:镜像说有卡时复核一次,**属主行数恰 0 = 镜像脏值的正面证据** ⇒ 不 stand down,继续真
|
|
163
|
+
* 分诊(放行重呈);>0 / 抛错 / 读口缺席 / 窗尽 / 调用方中止 ⇒ 保守维持 stand down(分不出
|
|
164
|
+
* 真卡与幽灵时,旧行为对真卡是对的;存量装配不注入时行为逐字不变)。
|
|
165
|
+
* 🔴 `opts.signal` = 本层合流的截止/中止口(4s 有界窗 × `deps.signal`),装配方**必须**把它
|
|
166
|
+
* 透传进真实读面(approvals.list)—— 只掐调用方的 await 不掐底层请求,「有界」就只是判决面
|
|
167
|
+
* 的说法(忽略该参的存量实现照常工作,只是失去被掐能力)。
|
|
168
|
+
*/
|
|
169
|
+
listOwnedPendingApprovals?: (opts?: {
|
|
170
|
+
signal?: AbortSignal;
|
|
171
|
+
}) => Promise<number>;
|
|
156
172
|
/** plan_review park 的重开口(生产 = 包 `hitl/planReviewWire.reopenPlanReviewCard`);
|
|
157
173
|
* 判决形见 {@link ReopenCardVerdict}。同步/异步两式通吃(端的生产口可能要等呈现回执)。 */
|
|
158
174
|
reopenPlanReview?: (taskId: string) => ReopenCardVerdict | Promise<ReopenCardVerdict>;
|
|
@@ -391,6 +407,11 @@ export interface ActiveRunSelfHealCopy {
|
|
|
391
407
|
/** 整行覆盖(headless 面):返回 undefined = 落回默认串。 */
|
|
392
408
|
headlessRowFor?: (signal: ActiveRunBusySignal) => string | undefined;
|
|
393
409
|
}
|
|
410
|
+
/** cli 的唯一真出路(`/clear` = 新会话 id ⇒ 引擎侧全新 session ⇒ 不受旧 claim 影响)。
|
|
411
|
+
* 导出(#244 F1):宿主的 resume 冷启动对账腿等失败半场要说**同一句**真出路 —— 各写一份必漂
|
|
412
|
+
* (一句「按某个键」而那个键没注册,正是本文件头在骂的形)。覆盖语义照旧走
|
|
413
|
+
* {@link ActiveRunSelfHealCopy.wayOut},本常量只是默认串的单源。 */
|
|
414
|
+
export declare const INTERACTIVE_WAY_OUT = "run /clear to keep working in a fresh session";
|
|
394
415
|
/**
|
|
395
416
|
* 每种结局的上屏整行。**每一句出路都真的接了线**;没有真出路的分支就直说「这个会话暂时无法
|
|
396
417
|
* 继续」。重开成功那条也要上屏 —— 卡是替用户重新打开的,他得知道去答哪张、答完做什么。
|
|
@@ -176,6 +176,9 @@ export function readSteerReceiptStatus(receipt) {
|
|
|
176
176
|
}
|
|
177
177
|
/** cancel 之后**有界**等那条 run 交出会话的缺省窗(`POST …/cancel` 是 202 异步 —— 收下 ≠ 已停)。 */
|
|
178
178
|
export const CANCEL_RELEASE_WAIT_MS = 10_000;
|
|
179
|
+
/** 假死锁复核读口(`deps.listOwnedPendingApprovals`)的等待上界:一发 approvals 列表读,4s 是
|
|
180
|
+
* 「慢网也回得来」的诚实预算;窗尽=分不出真卡与幽灵 ⇒ 保守 stand down(见调用点注)。 */
|
|
181
|
+
const OWNED_PENDING_RECHECK_TIMEOUT_MS = 4_000;
|
|
179
182
|
/** 轮询退避:200ms 起、×1.5、封顶 2s(短退避,别把一次 cancel 打成连发)。 */
|
|
180
183
|
const CANCEL_POLL_START_MS = 200;
|
|
181
184
|
const CANCEL_POLL_MAX_MS = 2_000;
|
|
@@ -267,11 +270,19 @@ export async function waitForClaimRelease(taskId, deps) {
|
|
|
267
270
|
}));
|
|
268
271
|
}
|
|
269
272
|
catch {
|
|
270
|
-
// 这一发读不出来 ⇒ 最新证据是「不知道」,陈旧的成功读数当场作废(见 lastStatus 头注)。
|
|
271
|
-
lastStatus = null;
|
|
272
273
|
lease.release();
|
|
273
274
|
if (isAborted())
|
|
274
275
|
return { released: false, waitedMs: waited(), aborted: true, lastStatus };
|
|
276
|
+
// 🔴 本发是被**窗自己的截止**掐掉的(lease 到点;caller 未中止)⇒ 没有读到任何回答 ——
|
|
277
|
+
// 既不是释放证据,也不是「读不出来」的新证据,上一发**完成了的**读数保持在座(#244 F1
|
|
278
|
+
// 换装批 G-c 尾竞态定谳:R2 [medium] 的「只记最近一次」指最近一次**有回答**的探测 ——
|
|
279
|
+
// 404/传输错是回答了「不知道」,截止掐断连回答都没有;不区分则 confirmedHeld 在窗尾随机
|
|
280
|
+
// 翻 false,同输入两跑两种文案)。回循环顶按墙钟收口。
|
|
281
|
+
if (lease.signal.aborted)
|
|
282
|
+
continue;
|
|
283
|
+
// 这一发**有回答但读不出**(404/传输错)⇒ 最新证据是「不知道」,陈旧的成功读数当场作废
|
|
284
|
+
// (见 lastStatus 头注)。
|
|
285
|
+
lastStatus = null;
|
|
275
286
|
// 🔴 **404 也只是「读不到」,不是「释放了」**(二次评审 [high] 处置,2026-08-14):这个读口的
|
|
276
287
|
// 404 在 SDK 契约上是 `not_found.run` —— 「这条 run 不存在」与「它不是你的」**共用同一个码,
|
|
277
288
|
// 且是刻意设计的不可分辨**(跨租户不给存在性预言机)。凭它断言「那条 run 已经不占着会话」,
|
|
@@ -302,6 +313,41 @@ export async function attemptActiveRunSelfHeal(signal, runs, deps) {
|
|
|
302
313
|
catch {
|
|
303
314
|
pending = false; // 读不到就当没有 —— 这一项只用来**减少**破坏性动作,不该反过来卡住自愈
|
|
304
315
|
}
|
|
316
|
+
// 🔴 经函数读(waitForClaimRelease 的 isAborted 同注):`aborted` 会在 await 两侧变,直接读两次
|
|
317
|
+
// 会被 tsc 的控制流分析把第二次窄成恒假比较(TS2367)。
|
|
318
|
+
const callerAborted = () => deps?.signal?.aborted === true;
|
|
319
|
+
if (pending && typeof deps?.listOwnedPendingApprovals === 'function' && !callerAborted()) {
|
|
320
|
+
// [3892]-[3899] 假死锁防御(deps.listOwnedPendingApprovals 头注全案):镜像说有卡 ⇒ 向引擎
|
|
321
|
+
// 复核一次属主 pending 行数。恰 0 = 镜像脏值的**正面证据**(引擎侧没有任何等这个用户的决定)
|
|
322
|
+
// ⇒ 放行真分诊;抛错/非 0 ⇒ 保守维持(真卡在屏时 stand down 是对的)。
|
|
323
|
+
// 🔴 复核必须**有界且可回收**(codex #244 F1 轮一 [high] + 轮二 [medium]×2):
|
|
324
|
+
// · probe 恒不落地(引擎挂死且读口无自带超时)不许把整棵分诊树挂死 —— 假死锁防御自己变成
|
|
325
|
+
// 真死锁;窗尽/调用方中止 = 分不出真卡与幽灵,与抛错同臂保守 stand down;
|
|
326
|
+
// · 合流 signal **真交到 probe 上**(超时/中止掐的是底层请求,不是只掐调用方的 await);
|
|
327
|
+
// · 竞速败方必须收口:快成功时 4s sleep 的普通计时器不清,-p 车道进程会被它多拖活一窗
|
|
328
|
+
// (finally 统一 abort ⇒ abortableSleep 清 timer、在飞 probe 被掐);
|
|
329
|
+
// · 已中止(进门前/竞速期间)一律不采信 count —— Esc 之后接着放行重呈 = 用户说停还弹卡。
|
|
330
|
+
const recheckCtl = new AbortController();
|
|
331
|
+
const onCallerAbort = () => {
|
|
332
|
+
recheckCtl.abort(deps.signal?.reason);
|
|
333
|
+
};
|
|
334
|
+
deps.signal?.addEventListener('abort', onCallerAbort, { once: true });
|
|
335
|
+
try {
|
|
336
|
+
const count = await Promise.race([
|
|
337
|
+
deps.listOwnedPendingApprovals({ signal: recheckCtl.signal }),
|
|
338
|
+
claimPollSleep(OWNED_PENDING_RECHECK_TIMEOUT_MS, recheckCtl.signal).then(() => null),
|
|
339
|
+
]);
|
|
340
|
+
if (count === 0 && !callerAborted())
|
|
341
|
+
pending = false;
|
|
342
|
+
}
|
|
343
|
+
catch {
|
|
344
|
+
/* 复核失败分不出真卡与幽灵 —— 维持 stand down,下一条消息再走一遍本腿 */
|
|
345
|
+
}
|
|
346
|
+
finally {
|
|
347
|
+
deps.signal?.removeEventListener('abort', onCallerAbort);
|
|
348
|
+
recheckCtl.abort(new Error('owned-pending recheck settled'));
|
|
349
|
+
}
|
|
350
|
+
}
|
|
305
351
|
if (pending)
|
|
306
352
|
return { kind: 'decision-pending', taskId: signal.activeTaskId };
|
|
307
353
|
const taskId = signal.activeTaskId;
|
|
@@ -513,8 +559,12 @@ async function runningChoiceArm(taskId, status, runs, deps) {
|
|
|
513
559
|
// 用户选了 ③ / 卡没答上 / 选了一条本轮不可兑现的路 ⇒ 零动作,如实转述现状。
|
|
514
560
|
return notParked;
|
|
515
561
|
}
|
|
516
|
-
/** cli 的唯一真出路(`/clear` = 新会话 id ⇒ 引擎侧全新 session ⇒ 不受旧 claim 影响)。
|
|
517
|
-
|
|
562
|
+
/** cli 的唯一真出路(`/clear` = 新会话 id ⇒ 引擎侧全新 session ⇒ 不受旧 claim 影响)。
|
|
563
|
+
* 导出(#244 F1):宿主的 resume 冷启动对账腿等失败半场要说**同一句**真出路 —— 各写一份必漂
|
|
564
|
+
* (一句「按某个键」而那个键没注册,正是本文件头在骂的形)。覆盖语义照旧走
|
|
565
|
+
* {@link ActiveRunSelfHealCopy.wayOut},本常量只是默认串的单源。 */
|
|
566
|
+
export const INTERACTIVE_WAY_OUT = 'run /clear to keep working in a fresh session';
|
|
567
|
+
const DEFAULT_WAY_OUT = INTERACTIVE_WAY_OUT;
|
|
518
568
|
const DEFAULT_FRESH_SESSION = 'start a new session (drop --resume/--continue)';
|
|
519
569
|
/**
|
|
520
570
|
* 「在等哪一种决定」的人话 —— **只由 wire 给的 `pendingGate.kind` 铸**。引擎对未知 gate 种类
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
/** 登记一个已呈现的决断卡身份键(空/非法输入静默忽略 —— 登记面绝不炸渲染链)。
|
|
1
|
+
/** 登记一个已呈现的决断卡身份键(空/非法输入静默忽略 —— 登记面绝不炸渲染链)。
|
|
2
|
+
* 每次调用都发事件(复见重开的第二次呈现也要有回执;Set 成员级当不了事件源)。 */
|
|
2
3
|
export declare function registerArmedGate(key: string | null | undefined): void;
|
|
3
4
|
/** W1 带 key 变体(多会话宿主每会话一键,互不串账)。 */
|
|
4
5
|
export declare function registerArmedGateFor(sessionKey: string, key: string | null | undefined): void;
|
|
@@ -6,14 +7,71 @@ export declare function registerArmedGateFor(sessionKey: string, key: string | n
|
|
|
6
7
|
export declare function wasGateArmed(key: string | null | undefined): boolean;
|
|
7
8
|
/** W1 带 key 变体。 */
|
|
8
9
|
export declare function wasGateArmedFor(sessionKey: string, key: string | null | undefined): boolean;
|
|
9
|
-
/** 消费一个身份键(
|
|
10
|
+
/** 消费一个身份键(plan 族的决断消费请优先走 {@link notePlanReviewAnswered} 族 —— 它同时推代;
|
|
11
|
+
* 本口保留给「只清账不推代」的宿主场景与 0.30.0 存量消费方,语义不变)。 */
|
|
10
12
|
export declare function clearArmedGate(key: string | null | undefined): void;
|
|
11
13
|
/** W1 带 key 变体。 */
|
|
12
14
|
export declare function clearArmedGateFor(sessionKey: string, key: string | null | undefined): void;
|
|
15
|
+
/**
|
|
16
|
+
* 订阅 arm 事件(#269 上收):返回退订钩,调用方**必须**在自己的生命周期末调它(监听器挂在
|
|
17
|
+
* 模块级长存表上,漏退 = 闭包泄漏)。
|
|
18
|
+
*
|
|
19
|
+
* 🔴 为什么不复用 {@link waitForGateArmed}:那是**带窗**的一次性回执(超时即 resolve false),
|
|
20
|
+
* 它答的是「看门狗窗内呈现了没有」;#269 的 tool 门臂另外要问一个**无窗**的事实 ——「这条链的
|
|
21
|
+
* 整个寿命里,这张卡到底有没有呈现过」。用带窗的那只当事实源,会在「迟到卡」(窗后才入队)上
|
|
22
|
+
* 给出 false ⇒ 把一次真呈现误判成「行从未出生」⇒ 回环再铸一张卡,同一个待决项两张可按的卡。
|
|
23
|
+
*/
|
|
24
|
+
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
|
+
/** 测试用:缩短回执看门狗(null 复位)。 */
|
|
28
|
+
export declare function _setGateArmedWaitMsForTest(ms: number | null): void;
|
|
29
|
+
/** 回执看门狗现值(重开臂的缺省等待窗)。 */
|
|
30
|
+
export declare function gateArmedWaitMs(): number;
|
|
31
|
+
/**
|
|
32
|
+
* 等「这几个键里任意一个被 arm」的**事件**(#250 件1 呈现回执)。resolve true = 回执到手;
|
|
33
|
+
* false = 超时(呈现链没走到真入队 —— hook 自动应答/去重丢帧/enqueue 失败/处理器僵死)。
|
|
34
|
+
*
|
|
35
|
+
* 🔴 timer **不 unref**(包 `abortableSleep` 同判据):调用方(重开臂)正 await 本 promise 收
|
|
36
|
+
* verdict —— 回执窗里这只 timer 可能是事件循环里唯一的活,unref 会让 `-p` 车道进程在 await
|
|
37
|
+
* 中途直接退出,诚实的 reopen-failed 行一并蒸发。代价上限 = 一次 timeoutMs 的进程存活延长,
|
|
38
|
+
* 换判决必达。(浏览器宿主无 unref 概念,裸 setTimeout 两端行为一致 —— portability 零分支。)
|
|
39
|
+
*/
|
|
40
|
+
export declare function waitForGateArmed(keys: readonly string[], timeoutMs: number): Promise<boolean>;
|
|
41
|
+
/** W1 带 key 变体。 */
|
|
42
|
+
export declare function waitForGateArmedFor(sessionKey: string, keys: readonly string[], timeoutMs: number): Promise<boolean>;
|
|
13
43
|
/** 宿主呈现面登记口:收到可渲染 question 帧即记(键归一见 gateIdentity;坏形静默忽略 ——
|
|
14
|
-
* id 是 wire/合成位,入参按边界收 unknown,本函数就是窄化动作本身)。
|
|
44
|
+
* id 是 wire/合成位,入参按边界收 unknown,本函数就是窄化动作本身)。
|
|
45
|
+
*
|
|
46
|
+
* 尝试级回执(#250 codex 轮二 [medium]):原始帧 id 自带 `#reopen-*` 尝试序号,是现成的
|
|
47
|
+
* attempt token —— 归一键之外**事件级**再发一枪原始 id(不入 Set,台账词汇保持归一键)。
|
|
48
|
+
* 重开臂锚它,同 gate 两次在飞重开时一次真入队只唤对应那次尝试,不再同键互唤。 */
|
|
15
49
|
export declare function registerArmedGateFromQuestionId(questionId: unknown): void;
|
|
16
50
|
/** W1 带 key 变体。 */
|
|
17
51
|
export declare function registerArmedGateFromQuestionIdFor(sessionKey: string, questionId: unknown): void;
|
|
18
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* plan_review 的台账键(当代)。0 代 = `planReviewQuestionId(taskId)`(canonical 原形 ——
|
|
54
|
+
* overlay 对**原卡**(done 帧首扎)的登记落进同一键);≥1 代 = 同形 + `#g<N>` 尾。
|
|
55
|
+
*/
|
|
56
|
+
export declare function planReviewArmedKey(taskId: string): string;
|
|
57
|
+
/** W1 带 key 变体。 */
|
|
58
|
+
export declare function planReviewArmedKeyFor(sessionKey: string, taskId: string): string;
|
|
59
|
+
/**
|
|
60
|
+
* 记「一张 plan 问答卡被答掉了」⇒ 当代门已被消费,同 run 的**下一只** plan 门是新实例,落新键。
|
|
61
|
+
*
|
|
62
|
+
* ── 为什么锚在「答卡」而不是「决断送达」(局限成文,A-024.4)────────────────────────────────
|
|
63
|
+
* wire 上没有 gate 实例位,宿主能看见的最晚可靠时刻是作答(送达成败在 responder 返回之后才
|
|
64
|
+
* 揭晓)。所以按「答卡」推代。方向代价:决断若终败(重试后仍没送达),门 1 其实还 pending,
|
|
65
|
+
* 下一次重开会判成首见 ——「呈上了一张卡」零历史断言,诚实方向安全;反向(不推代)才会编造
|
|
66
|
+
* 「被关过」。当代键同时从台账清掉(`clearArmedGate` 的 0.30.0 语义在 0 代逐字保持:决断递交后
|
|
67
|
+
* `wasGateArmed(canonical)` 读回 false)。
|
|
68
|
+
*
|
|
69
|
+
* 🔴 决断性门控请走 {@link notePlanReviewAnsweredIfDecisive}(`planReviewWire`):Esc/空答是
|
|
70
|
+
* dismissal 不是决断 —— 那时推代会把「被关过」翻成「首见」,恰是 A-024.4 在最常见路径(Esc
|
|
71
|
+
* 关卡)上要保住的区分。本口只做记账,不做分类。
|
|
72
|
+
*/
|
|
73
|
+
export declare function notePlanReviewAnswered(questionId: unknown): void;
|
|
74
|
+
/** W1 带 key 变体。 */
|
|
75
|
+
export declare function notePlanReviewAnsweredFor(sessionKey: string, questionId: unknown): void;
|
|
76
|
+
/** 测试用:清全部会话的台账(含监听器/代数/答卡记账 —— 半清会让跨用例判决互相污染)。 */
|
|
19
77
|
export declare function _resetArmedGateRegistryForTest(): void;
|