@khorsheed/dsh-ankh-guard 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/README.en.md +4 -4
- package/README.i18n.yaml +2 -2
- package/README.md +4 -4
- package/lib/cli.js +1 -1
- package/lib/client.js +104 -17
- package/lib/index.js +29 -6
- package/lib/{restart-context-D6nISh28.js → restart-context-DlaDZWKq.js} +12 -7
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/index.js +108 -24
- package/lib/types/index.js +39 -6
- package/lib/types/restart-context.js +11 -6
- package/package.json +9 -9
- package/lib/processes-BjZgJjQr.js +0 -344
- package/lib/state-4f7yny39.js +0 -286
- package/lib/test-seam-DnvLWTeO.js +0 -119
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# 变更记录
|
|
2
2
|
|
|
3
|
+
## 0.3.0(2026-09-11)
|
|
4
|
+
|
|
5
|
+
适配宿主 0.1.5 线。
|
|
6
|
+
|
|
7
|
+
- **BREAKING**:minHost 前移至 `0.1.5-rc.1`;宿主 `0.1.2-rc.1` ~ `0.1.4.x` 的用户请停留在 0.2.x 线(末版 `0.2.0`)
|
|
8
|
+
- 冷读(用户输入停靠探测与 resume preset 推导)改走 0.1.5 的 handle 制 `sessionPersistence`(`open(id, 'read')` → `read` → `close`,与其他宿主面一样按调用探测);0.1.5 已移除的一次性 `inspect` 会让重启 resume 静默降级
|
|
9
|
+
- 生命周期漂移 lane 的通过计数改为环境感知:缺少部署 profile / 工具链缓存时合理跳过的绊线测试不再被硬编码期望数误伤(修 CI 红)
|
|
10
|
+
- 开发基线随仓内 pin 对齐官方 `0.1.5-rc.1` / cordis `4.0.2`(单实例 cordis 终结 4.0.1/4.0.2 图分裂);peer 范围保持宽松
|
|
11
|
+
|
|
3
12
|
## 0.2.0(2026-09-10)
|
|
4
13
|
|
|
5
14
|
- **BREAKING**:minHost 前移至 `0.1.2-rc.1`;宿主 `0.1.0-rc.6` ~ `0.1.1-rc.2` 的用户请停留在 0.1.x 线(末版 `0.1.1`)
|
package/README.en.md
CHANGED
|
@@ -184,7 +184,7 @@ dsh-ankh-guard restart \
|
|
|
184
184
|
|
|
185
185
|
Mounted as a cordis plugin (base bundle), the same surface is available as the `selfRestartGuard` service for in-app gates. Config: `maxAgeMinutes` (default 10), `stateDir`, `repoDir`, `reportRestartContext` (default `followup`), `fallbackGraceMs` (default 300000).
|
|
186
186
|
|
|
187
|
-
Beyond the verify/record/canary gates, the service exposes `requestRestart({ start, profile, initiator })` — the in-process restart trigger for UI-grade callers (e.g. the mode switcher): `initiator` is required (the requesting session's real id) and the port comes from the launch record. With no live watchdog it drives `restart`; supervised instances drive the `reconfigure` transactional cutover (the only safe way to change the launch command under a watchdog). The credential/preflight/marker/lock gate chain is the CLI's own, refusals return a structured `{ accepted, stage, reason }` and never stop the instance. On the browser side, beside the cutover receipt channel there is a boot-generation channel: after an ordinary restart or a crash respawn, open tabs notice the process's boot id changed over the handoff long-poll and reload once to pick up the new bundle;
|
|
187
|
+
Beyond the verify/record/canary gates, the service exposes `requestRestart({ start, profile, initiator })` — the in-process restart trigger for UI-grade callers (e.g. the mode switcher): `initiator` is required (the requesting session's real id) and the port comes from the launch record. With no live watchdog it drives `restart`; supervised instances drive the `reconfigure` transactional cutover (the only safe way to change the launch command under a watchdog). The credential/preflight/marker/lock gate chain is the CLI's own, refusals return a structured `{ accepted, stage, reason }` and never stop the instance. On the browser side, beside the cutover receipt channel there is a boot-generation channel: after an ordinary restart or a crash respawn, open tabs notice the process's boot id changed over the handoff long-poll and reload once to pick up the new bundle; visible sustained failures (~5 s) show a non-blocking connection banner with Retry; only confirmed cutovers use the restart overlay. Background/offline time does not count as failure. Returning to the foreground or online immediately resets backoff and restarts polling; stalled requests time out after 35 s and stale responses cannot trigger a reload.
|
|
188
188
|
|
|
189
189
|
## Model Experience
|
|
190
190
|
|
|
@@ -196,11 +196,11 @@ None.
|
|
|
196
196
|
|
|
197
197
|
## Compatibility
|
|
198
198
|
|
|
199
|
-
- npm release line (`@deepseek-ai/dsh@0.1.
|
|
199
|
+
- npm release line (`@deepseek-ai/dsh@0.1.5-rc.1`): ⚠️ degraded — everything works; the composition-preflight gate runs through the standalone `preflight-runner` (composing through the published `@deepseek-ai/dsh-app-boot` primitives with a drift tripwire, since 0.1.5-rc.1 still does not export `composeProfile`) wherever a dsh app layout resolves — `--harness-root`, the durable launch spec, `DSH_HARNESS`, or the default checkout. On a pure npm deployment with no harness checkout the gate reports a notice and proceeds instead. The original-tab bridge feature-probes the optional WebServer/connection authentication seams; hosts without token auth naturally take the existing-cookie path. Cold reads (the parked probe and the preset derivation) ride the 0.1.5 handle-based sessionPersistence (`open(id, 'read')` → `read` → `close`; the one-shot `inspect` is gone). Every other capability is intact on the npm line. minHost moves up to 0.1.5-rc.1 — older hosts stay on the previous release line.
|
|
200
200
|
- Historical verification: a live npm-host 0.1.1-rc.2 → 0.1.2-alpha.4 isolated cutover passed (transition preflight removed a v3 whole-unit projection cache with an old-schema record from a home copy, live apply quarantined the old file, and target completed Token URL → 303 → cookie 200, the ownership stability window, and canary at zero retries; the old file remained byte-exact in the cutover directory. An untransitioned control over the same home failed on the missing Alpha.4 record fields, demonstrating that acceptance covered the real schema break).
|
|
201
|
-
- source line (deepseek-harness master, fork or upstream): ✅ (verifiedHost: 0.1.
|
|
201
|
+
- source line (deepseek-harness master, fork or upstream): ✅ (verifiedHost: 0.1.5-rc.1) — the gate runs through the standalone `preflight-runner` (resolves the published `@deepseek-ai/dsh-app-boot` etc. from the live checkout), so no fork patch is required.
|
|
202
202
|
|
|
203
|
-
**Version line mapping**: 0.2.0
|
|
203
|
+
**Version line mapping**: the first release after 0.2.0 supports host `0.1.5-rc.1` and later; hosts on `0.1.2-rc.1` stay on `0.2.0`, hosts on `0.1.0-rc.6` ~ `0.1.1-rc.2` stay on the 0.1.x release line (last release `0.1.1`).
|
|
204
204
|
|
|
205
205
|
## Known Limitations and Deferred Work
|
|
206
206
|
|
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# last confirmed-consistent state. Both languages carry equal authority; after
|
|
3
3
|
# editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/ankh-guard/README.en.md
|
|
5
|
-
packages/ankh-guard/README.en.md:
|
|
6
|
-
packages/ankh-guard/README.md:
|
|
5
|
+
packages/ankh-guard/README.en.md: 0fcdb11556dd49ca9046648075ffcde6cab9e894
|
|
6
|
+
packages/ankh-guard/README.md: 3cba2785e56bdfdf3566255aa3629e47c44f8570
|
package/README.md
CHANGED
|
@@ -183,7 +183,7 @@ dsh-ankh-guard restart \
|
|
|
183
183
|
|
|
184
184
|
以 cordis 插件挂载(base bundle)后,同一套能力以 `selfRestartGuard` 服务的形式供应用内闸门使用。配置:`maxAgeMinutes`(默认 10)、`stateDir`、`repoDir`、`reportRestartContext`(默认 `followup`)、`fallbackGraceMs`(默认 300000)。
|
|
185
185
|
|
|
186
|
-
除 verify/record/canary 等闸门外,服务还暴露 `requestRestart({ start, profile, initiator })`——UI 级调用方(如 mode-switcher)的进程内重启触发缝:`initiator` 必填(发起会话的真实 id),端口从 launch 记录自知;无活 watchdog 走 `restart`,受监督走 `reconfigure` 事务 cutover(受监督下唯一安全的换命令通道),凭证/preflight/marker/lock 全套闸门与 CLI 同源,拒绝返回结构化 `{ accepted, stage, reason }` 且绝不停机。浏览器侧,除 cutover receipt 通道外还有 boot 代际通道:普通重启或崩溃救回后,打开的标签页经 handoff 长轮询发现进程 boot id 已变,自动全页刷新一次拿到新 bundle
|
|
186
|
+
除 verify/record/canary 等闸门外,服务还暴露 `requestRestart({ start, profile, initiator })`——UI 级调用方(如 mode-switcher)的进程内重启触发缝:`initiator` 必填(发起会话的真实 id),端口从 launch 记录自知;无活 watchdog 走 `restart`,受监督走 `reconfigure` 事务 cutover(受监督下唯一安全的换命令通道),凭证/preflight/marker/lock 全套闸门与 CLI 同源,拒绝返回结构化 `{ accepted, stage, reason }` 且绝不停机。浏览器侧,除 cutover receipt 通道外还有 boot 代际通道:普通重启或崩溃救回后,打开的标签页经 handoff 长轮询发现进程 boot id 已变,自动全页刷新一次拿到新 bundle;仅前台持续失败约 5 秒后显示带重试按钮的轻量连接提示;只有确认的 cutover 使用重启遮罩。后台或离线时间不计入失败时长;回前台或恢复联网立即清除退避并重新轮询;挂起的请求在 35 秒后超时,过期响应不能触发刷新。
|
|
187
187
|
|
|
188
188
|
## Model Experience
|
|
189
189
|
|
|
@@ -195,11 +195,11 @@ dsh-ankh-guard restart \
|
|
|
195
195
|
|
|
196
196
|
## Compatibility
|
|
197
197
|
|
|
198
|
-
- npm 发布线(`@deepseek-ai/dsh@0.1.
|
|
198
|
+
- npm 发布线(`@deepseek-ai/dsh@0.1.5-rc.1`):⚠️ 降级——一切可用;composition-preflight 门禁通过独立的 `preflight-runner` 运行(0.1.5-rc.1 仍未导出 `composeProfile`,runner 改经已发布的 `@deepseek-ai/dsh-app-boot` 原语组装,带漂移绊线测试),只要能解析到 dsh app 布局——`--harness-root`、耐久 launch spec、`DSH_HARNESS` 或默认检出路径——就完整运行。没有 harness 检出的纯 npm 部署下门禁退化为提示后放行。原标签页桥会探测可选 WebServer/connection 认证 seam,不使用 token 认证的宿主自然走现有 Cookie 路径;冷读(停靠探测与 preset 推导)走 0.1.5 的 handle 制 sessionPersistence(`open(id, 'read')` → `read` → `close`,一次性 `inspect` 已移除)。其余能力在 npm 线上完整。minHost 前移至 0.1.5-rc.1,旧宿主请停留在旧发布线。
|
|
199
199
|
- 历史验证:npm host 的 0.1.1-rc.2 → 0.1.2-alpha.4 隔离切换已通过(transition preflight 在 home 副本上移开带旧 schema record 的 v3 whole-unit projection cache,live apply 隔离旧文件,target 以零重试完成 Token URL → 303 → Cookie 200、ownership 稳定窗口与 canary;旧文件逐字节保留在 cutover 目录。相同 home 的无 transition 对照因缺少 Alpha.4 record 字段而拒绝,证明验收覆盖了真实 schema 断裂面)。
|
|
200
|
-
- 源码线(deepseek-harness master,fork 或上游):✅(verifiedHost: 0.1.
|
|
200
|
+
- 源码线(deepseek-harness master,fork 或上游):✅(verifiedHost: 0.1.5-rc.1)——门禁通过独立的 `preflight-runner` 运行(从在线 checkout 解析已发布的 `@deepseek-ai/dsh-app-boot` 等),不再需要 fork 补丁。
|
|
201
201
|
|
|
202
|
-
**版本线对照**:0.2.0
|
|
202
|
+
**版本线对照**:0.2.0 之后的首个发布起支持宿主 `0.1.5-rc.1` 及以后;宿主 `0.1.2-rc.1` 请停留在 `0.2.0`,宿主 `0.1.0-rc.6` ~ `0.1.1-rc.2` 请停留在 0.1.x 发布线(末版 `0.1.1`)。
|
|
203
203
|
|
|
204
204
|
## Known Limitations and Deferred Work
|
|
205
205
|
|
package/lib/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { i as resolveStateDir, n as isDirectInvocation, r as resolveRepoDir, t as SRC_ARTIFACT_PATTERN } from "./defaults-CduvOcGq.js";
|
|
3
|
-
import { A as writeStableLaunchSpec, C as prepareLaunchCutover, D as selectedLaunchSpec, E as recordCutoverEvent, F as workingTreeChanges, M as currentHead, N as isWorkingTreeClean, O as summarizeLaunchState, P as resetToCheckpoint, T as readLaunchState, _ as writeRestartOutcome, b as activeCutover, c as readInstanceLaunch, f as writeAdoptionRecord, h as writeInstanceLaunchAsSupervisor, j as commitCheckpoint, k as writeCutoverControl, p as writeCompositionRecovery, u as readSkillRegistration, w as readCutoverReceipt, x as commandSha256, y as writeUnexpectedExitRecord } from "./restart-context-
|
|
3
|
+
import { A as writeStableLaunchSpec, C as prepareLaunchCutover, D as selectedLaunchSpec, E as recordCutoverEvent, F as workingTreeChanges, M as currentHead, N as isWorkingTreeClean, O as summarizeLaunchState, P as resetToCheckpoint, T as readLaunchState, _ as writeRestartOutcome, b as activeCutover, c as readInstanceLaunch, f as writeAdoptionRecord, h as writeInstanceLaunchAsSupervisor, j as commitCheckpoint, k as writeCutoverControl, p as writeCompositionRecovery, u as readSkillRegistration, w as readCutoverReceipt, x as commandSha256, y as writeUnexpectedExitRecord } from "./restart-context-DlaDZWKq.js";
|
|
4
4
|
import { _ as processIdentity, c as appendTestLifecycleEvent, d as registerTestProcess, f as discoverLaunchCommand, h as killPidTree, l as appendTestLifecycleEventForProcess, m as findPidOnPort, n as TEST_PROCESS_ROLE_ENV, p as findOwnedListener, r as TEST_PROCESS_TEMP_ROOT_ENV, t as TEST_PROCESS_PORT_ENV, u as registerCurrentTestProcess, v as processIdentityMatches } from "./test-seam-dwvaKjRp.js";
|
|
5
5
|
import { a as setProvenDeployment, c as lastGoodBootRevision, d as pidAlive, f as stateFile, i as setCheckpoint, l as livePidIn, n as loadState, r as recordCredential, s as verifyCredential, t as clearCredential, u as liveWatchdogPid } from "./state-CZMypGkB.js";
|
|
6
6
|
import { execFileSync, spawn } from "node:child_process";
|
package/lib/client.js
CHANGED
|
@@ -13,8 +13,9 @@ window.__ModuleLoader__.load({
|
|
|
13
13
|
const ACTIVE_RETRY_MS = 250;
|
|
14
14
|
const ERROR_RETRY_MIN_MS = 1e3;
|
|
15
15
|
const ERROR_RETRY_MAX_MS = 3e4;
|
|
16
|
-
/** Sustained-failure threshold for the
|
|
17
|
-
const
|
|
16
|
+
/** Sustained-failure threshold for the connection notice; transient blips never show it. */
|
|
17
|
+
const DISCONNECTED_NOTICE_MS = 5e3;
|
|
18
|
+
const REQUEST_TIMEOUT_MS = 35e3;
|
|
18
19
|
/** No Cordis services are needed; this is a same-origin browser lifecycle observer. */
|
|
19
20
|
const inject = [];
|
|
20
21
|
function encodeBase64Url(bytes) {
|
|
@@ -80,16 +81,17 @@ window.__ModuleLoader__.load({
|
|
|
80
81
|
writePending(pending);
|
|
81
82
|
return pending;
|
|
82
83
|
}
|
|
83
|
-
function waitingOverlay(kind = "restarting") {
|
|
84
|
+
function waitingOverlay(kind = "restarting", retry) {
|
|
84
85
|
const existing = document.getElementById("ankh-guard-browser-handoff");
|
|
85
|
-
if (existing
|
|
86
|
+
if (existing?.dataset.kind === "restarting" && kind === "disconnected") return existing;
|
|
87
|
+
const element = existing ?? document.createElement("div");
|
|
86
88
|
const zh = navigator.language.toLowerCase().startsWith("zh");
|
|
87
|
-
const element = document.createElement("div");
|
|
88
89
|
element.id = "ankh-guard-browser-handoff";
|
|
90
|
+
element.dataset.kind = kind;
|
|
89
91
|
element.setAttribute("role", "status");
|
|
90
92
|
element.setAttribute("aria-live", "polite");
|
|
91
|
-
element.textContent = kind === "disconnected" ? zh ? "
|
|
92
|
-
element.style.cssText = [
|
|
93
|
+
element.textContent = kind === "disconnected" ? zh ? "连接暂时中断,正在重试…" : "Connection interrupted; retrying…" : zh ? "dsh 正在重启;此标签页会自动恢复。" : "dsh is restarting; this tab will recover automatically.";
|
|
94
|
+
element.style.cssText = kind === "restarting" ? [
|
|
93
95
|
"position:fixed",
|
|
94
96
|
"inset:0",
|
|
95
97
|
"z-index:2147483647",
|
|
@@ -100,20 +102,48 @@ window.__ModuleLoader__.load({
|
|
|
100
102
|
"color:#fff",
|
|
101
103
|
"font:500 16px/1.5 system-ui,sans-serif",
|
|
102
104
|
"text-align:center"
|
|
105
|
+
].join(";") : [
|
|
106
|
+
"position:fixed",
|
|
107
|
+
"top:calc(env(safe-area-inset-top,0px) + 12px)",
|
|
108
|
+
"left:12px",
|
|
109
|
+
"right:12px",
|
|
110
|
+
"max-width:520px",
|
|
111
|
+
"margin:0 auto",
|
|
112
|
+
"box-sizing:border-box",
|
|
113
|
+
"z-index:2147483647",
|
|
114
|
+
"display:flex",
|
|
115
|
+
"align-items:center",
|
|
116
|
+
"justify-content:space-between",
|
|
117
|
+
"gap:12px",
|
|
118
|
+
"padding:12px 16px",
|
|
119
|
+
"border:1px solid var(--dsw-alias-border-l2,#8885)",
|
|
120
|
+
"border-radius:16px",
|
|
121
|
+
"background:var(--dsw-alias-bg-base,#fff)",
|
|
122
|
+
"color:var(--dsw-alias-label-primary,#181a20)",
|
|
123
|
+
"box-shadow:0 4px 24px #0002",
|
|
124
|
+
"font:400 15px/1.5 system-ui,sans-serif"
|
|
103
125
|
].join(";");
|
|
104
|
-
|
|
126
|
+
if (kind === "disconnected" && retry) {
|
|
127
|
+
const button = document.createElement("button");
|
|
128
|
+
button.textContent = zh ? "重试" : "Retry";
|
|
129
|
+
button.style.cssText = "flex:none;border:0;background:transparent;color:var(--dsw-alias-state-business-primary,#4d6bfe);font:inherit;min-height:36px;cursor:pointer";
|
|
130
|
+
button.onclick = retry;
|
|
131
|
+
element.append(button);
|
|
132
|
+
}
|
|
133
|
+
if (!existing) (document.body ?? document.documentElement).append(element);
|
|
105
134
|
return element;
|
|
106
135
|
}
|
|
107
|
-
async function post(body) {
|
|
136
|
+
async function post(body, signal) {
|
|
108
137
|
return fetch(ROUTE, {
|
|
109
138
|
method: "POST",
|
|
139
|
+
signal,
|
|
110
140
|
credentials: "same-origin",
|
|
111
141
|
cache: "no-store",
|
|
112
142
|
headers: { "content-type": "application/json" },
|
|
113
143
|
body: JSON.stringify(body)
|
|
114
144
|
});
|
|
115
145
|
}
|
|
116
|
-
async function acknowledge(pending) {
|
|
146
|
+
async function acknowledge(pending, signal) {
|
|
117
147
|
return (await post({
|
|
118
148
|
version: 1,
|
|
119
149
|
operation: "ack",
|
|
@@ -121,7 +151,7 @@ window.__ModuleLoader__.load({
|
|
|
121
151
|
channel: pending.channel,
|
|
122
152
|
authentication: pending.authentication,
|
|
123
153
|
...pending.capability !== void 0 ? { capability: pending.capability } : {}
|
|
124
|
-
})).status === 204;
|
|
154
|
+
}, signal)).status === 204;
|
|
125
155
|
}
|
|
126
156
|
/** Install the polling/reload loop for this tab's lifetime. */
|
|
127
157
|
function apply(_ctx) {
|
|
@@ -131,21 +161,61 @@ window.__ModuleLoader__.load({
|
|
|
131
161
|
let cap;
|
|
132
162
|
let errorRetryMs = ERROR_RETRY_MIN_MS;
|
|
133
163
|
let firstFailureAt;
|
|
164
|
+
let request;
|
|
165
|
+
let lastWakeAt = -Infinity;
|
|
166
|
+
const foreground = () => document.visibilityState !== "hidden" && navigator.onLine !== false;
|
|
167
|
+
const clearConnectionNotice = () => {
|
|
168
|
+
const notice = document.getElementById("ankh-guard-browser-handoff");
|
|
169
|
+
if (notice?.dataset.kind === "disconnected") notice.remove();
|
|
170
|
+
};
|
|
171
|
+
const pause = () => {
|
|
172
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
173
|
+
timer = void 0;
|
|
174
|
+
const previous = request;
|
|
175
|
+
request = void 0;
|
|
176
|
+
previous?.abort();
|
|
177
|
+
firstFailureAt = void 0;
|
|
178
|
+
errorRetryMs = ERROR_RETRY_MIN_MS;
|
|
179
|
+
clearConnectionNotice();
|
|
180
|
+
};
|
|
181
|
+
const wake = () => {
|
|
182
|
+
if (disposed || !foreground() || Date.now() - lastWakeAt < 500) return;
|
|
183
|
+
lastWakeAt = Date.now();
|
|
184
|
+
pause();
|
|
185
|
+
schedule();
|
|
186
|
+
};
|
|
187
|
+
const visibility = () => {
|
|
188
|
+
if (!foreground()) {
|
|
189
|
+
lastWakeAt = -Infinity;
|
|
190
|
+
pause();
|
|
191
|
+
} else wake();
|
|
192
|
+
};
|
|
193
|
+
const page = (event) => {
|
|
194
|
+
if (event.persisted) wake();
|
|
195
|
+
};
|
|
134
196
|
const noteDisconnected = () => {
|
|
135
197
|
firstFailureAt ??= Date.now();
|
|
136
|
-
if (Date.now() - firstFailureAt >=
|
|
198
|
+
if (Date.now() - firstFailureAt >= DISCONNECTED_NOTICE_MS) waitingOverlay("disconnected", wake);
|
|
137
199
|
};
|
|
138
200
|
const schedule = (delayMs = 0) => {
|
|
139
|
-
if (
|
|
201
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
202
|
+
if (!disposed && foreground()) timer = setTimeout(() => {
|
|
203
|
+
timer = void 0;
|
|
140
204
|
tick();
|
|
141
205
|
}, delayMs);
|
|
142
206
|
};
|
|
143
207
|
const tick = async () => {
|
|
144
|
-
if (disposed) return;
|
|
208
|
+
if (disposed || !foreground() || request) return;
|
|
209
|
+
const current = new AbortController();
|
|
210
|
+
request = current;
|
|
211
|
+
const stale = () => disposed || request !== current || !foreground();
|
|
212
|
+
const deadline = setTimeout(() => current.abort(), REQUEST_TIMEOUT_MS);
|
|
145
213
|
try {
|
|
146
214
|
if (pending !== null) {
|
|
147
215
|
waitingOverlay();
|
|
148
|
-
|
|
216
|
+
const acknowledged = await acknowledge(pending, current.signal);
|
|
217
|
+
if (stale()) return;
|
|
218
|
+
if (acknowledged) {
|
|
149
219
|
const returnPath = pending.returnPath;
|
|
150
220
|
const originalTab = pending.channel === "original-tab";
|
|
151
221
|
sessionStorage.removeItem(PENDING_KEY);
|
|
@@ -169,7 +239,8 @@ window.__ModuleLoader__.load({
|
|
|
169
239
|
operation: "poll",
|
|
170
240
|
capability: tabCapability,
|
|
171
241
|
...knownBootId === null ? {} : { knownBootId }
|
|
172
|
-
});
|
|
242
|
+
}, current.signal);
|
|
243
|
+
if (stale()) return;
|
|
173
244
|
if (!response.ok) {
|
|
174
245
|
noteDisconnected();
|
|
175
246
|
schedule(errorRetryMs);
|
|
@@ -179,6 +250,7 @@ window.__ModuleLoader__.load({
|
|
|
179
250
|
errorRetryMs = ERROR_RETRY_MIN_MS;
|
|
180
251
|
firstFailureAt = void 0;
|
|
181
252
|
const result = await response.json();
|
|
253
|
+
if (stale()) return;
|
|
182
254
|
if (typeof result.bootId === "string") sessionStorage.setItem(BOOT_ID_KEY, result.bootId);
|
|
183
255
|
if (result.state === "idle") {
|
|
184
256
|
document.getElementById("ankh-guard-browser-handoff")?.remove();
|
|
@@ -232,17 +304,32 @@ window.__ModuleLoader__.load({
|
|
|
232
304
|
return;
|
|
233
305
|
}
|
|
234
306
|
} catch {
|
|
307
|
+
if (stale()) return;
|
|
235
308
|
noteDisconnected();
|
|
236
309
|
schedule(errorRetryMs);
|
|
237
310
|
errorRetryMs = Math.min(errorRetryMs * 2, ERROR_RETRY_MAX_MS);
|
|
238
311
|
return;
|
|
312
|
+
} finally {
|
|
313
|
+
clearTimeout(deadline);
|
|
314
|
+
if (request === current) request = void 0;
|
|
239
315
|
}
|
|
240
316
|
schedule(ACTIVE_RETRY_MS);
|
|
241
317
|
};
|
|
318
|
+
document.addEventListener("visibilitychange", visibility);
|
|
319
|
+
window.addEventListener("online", wake);
|
|
320
|
+
window.addEventListener("offline", visibility);
|
|
321
|
+
window.addEventListener("pageshow", page);
|
|
322
|
+
window.addEventListener("dsh-mobile-foreground", wake);
|
|
242
323
|
tick();
|
|
243
324
|
return () => {
|
|
244
325
|
disposed = true;
|
|
245
|
-
|
|
326
|
+
pause();
|
|
327
|
+
document.removeEventListener("visibilitychange", visibility);
|
|
328
|
+
window.removeEventListener("online", wake);
|
|
329
|
+
window.removeEventListener("offline", visibility);
|
|
330
|
+
window.removeEventListener("pageshow", page);
|
|
331
|
+
window.removeEventListener("dsh-mobile-foreground", wake);
|
|
332
|
+
document.getElementById("ankh-guard-browser-handoff")?.remove();
|
|
246
333
|
};
|
|
247
334
|
}
|
|
248
335
|
//#endregion
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as resolveStateDir, r as resolveRepoDir, t as SRC_ARTIFACT_PATTERN } from "./defaults-CduvOcGq.js";
|
|
2
|
-
import { M as currentHead, N as isWorkingTreeClean, P as resetToCheckpoint, S as cutoverBlocksWake, a as interruptedSnapshotFile, b as activeCutover, c as readInstanceLaunch, d as restartContextText, g as writeInterruptedSnapshot, i as continueInterruptedText, j as commitCheckpoint, l as readInterruptedSnapshot, m as writeInstanceLaunch, n as buildLaunchCommand, o as isParkedOnUserInput, r as continueAndReportText, s as pendingRestartRecord, t as acknowledgeRestartRecord, v as writeSkillRegistration, w as readCutoverReceipt } from "./restart-context-
|
|
2
|
+
import { M as currentHead, N as isWorkingTreeClean, P as resetToCheckpoint, S as cutoverBlocksWake, a as interruptedSnapshotFile, b as activeCutover, c as readInstanceLaunch, d as restartContextText, g as writeInterruptedSnapshot, i as continueInterruptedText, j as commitCheckpoint, l as readInterruptedSnapshot, m as writeInstanceLaunch, n as buildLaunchCommand, o as isParkedOnUserInput, r as continueAndReportText, s as pendingRestartRecord, t as acknowledgeRestartRecord, v as writeSkillRegistration, w as readCutoverReceipt } from "./restart-context-DlaDZWKq.js";
|
|
3
3
|
import { _ as processIdentity, g as listeningPortsForPid, v as processIdentityMatches } from "./test-seam-dwvaKjRp.js";
|
|
4
4
|
import { f as stateFile, i as setCheckpoint, n as loadState, r as recordCredential, s as verifyCredential, t as clearCredential, u as liveWatchdogPid } from "./state-CZMypGkB.js";
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
@@ -649,6 +649,25 @@ function deriveSessionPreset(host, session) {
|
|
|
649
649
|
}
|
|
650
650
|
return host.resolveSessionPreset?.(session);
|
|
651
651
|
}
|
|
652
|
+
/** Probe the session-persistence service's cold-read face. */
|
|
653
|
+
function probeColdReader(ctx) {
|
|
654
|
+
const service = ctx.get("sessionPersistence");
|
|
655
|
+
if (service === void 0 || service === null) return void 0;
|
|
656
|
+
return typeof service.open === "function" ? service : void 0;
|
|
657
|
+
}
|
|
658
|
+
/** Read one persisted session's header and full log through a read handle. */
|
|
659
|
+
async function readColdLog(persistence, id) {
|
|
660
|
+
const handle = await persistence.open(id, "read");
|
|
661
|
+
try {
|
|
662
|
+
const cold = await handle.read(0);
|
|
663
|
+
return {
|
|
664
|
+
meta: handle.header,
|
|
665
|
+
events: cold.events
|
|
666
|
+
};
|
|
667
|
+
} finally {
|
|
668
|
+
await handle.close();
|
|
669
|
+
}
|
|
670
|
+
}
|
|
652
671
|
const Config = z.object({
|
|
653
672
|
maxAgeMinutes: z.natural().min(1).default(10),
|
|
654
673
|
stateDir: z.string().default(""),
|
|
@@ -789,9 +808,9 @@ function apply(ctx, config) {
|
|
|
789
808
|
if (probe === void 0) {
|
|
790
809
|
probe = (async () => {
|
|
791
810
|
try {
|
|
792
|
-
const persistence = ctx
|
|
811
|
+
const persistence = probeColdReader(ctx);
|
|
793
812
|
if (persistence === void 0) return false;
|
|
794
|
-
const { events } = await persistence
|
|
813
|
+
const { events } = await readColdLog(persistence, id);
|
|
795
814
|
return isParkedOnUserInput(events);
|
|
796
815
|
} catch {
|
|
797
816
|
return false;
|
|
@@ -812,7 +831,11 @@ function apply(ctx, config) {
|
|
|
812
831
|
if (cutoverBlocksWake(stateDir)) return;
|
|
813
832
|
const id = agent.id;
|
|
814
833
|
const exitAt = pendingContinue.get(id);
|
|
815
|
-
|
|
834
|
+
let record = followupReport ? pendingRestartRecord(stateDir) : null;
|
|
835
|
+
if (record !== null && record.initiator === void 0 && record.unexpected !== true && record.compositionRecovered !== true && record.error === void 0) {
|
|
836
|
+
acknowledgeRestartRecord(stateDir, record, Date.now());
|
|
837
|
+
record = null;
|
|
838
|
+
}
|
|
816
839
|
const owesReport = record !== null && (record.initiator === void 0 || id === record.initiator);
|
|
817
840
|
if (exitAt !== void 0) {
|
|
818
841
|
(async () => {
|
|
@@ -876,9 +899,9 @@ function apply(ctx, config) {
|
|
|
876
899
|
if (selection?.model !== void 0) agentOptions.model = selection.model;
|
|
877
900
|
let setup;
|
|
878
901
|
const presets = ctx.get("agentPresets");
|
|
879
|
-
const persistence = ctx
|
|
902
|
+
const persistence = probeColdReader(ctx);
|
|
880
903
|
if (presets !== void 0 && persistence !== void 0) {
|
|
881
|
-
const inspected = await persistence
|
|
904
|
+
const inspected = await readColdLog(persistence, id);
|
|
882
905
|
const presetId = deriveSessionPreset(agentPresetsHost, {
|
|
883
906
|
header: inspected.meta,
|
|
884
907
|
events: inspected.events
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { f as stateFile } from "./state-CZMypGkB.js";
|
|
2
2
|
import { execFileSync } from "node:child_process";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
4
|
import { chmodSync, copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
@@ -902,12 +902,17 @@ function activeCutover(stateDir) {
|
|
|
902
902
|
* Restart-record context injection and interrupted-session continuity. After
|
|
903
903
|
* a scheduled restart, the FULL report waits for the initiating session's
|
|
904
904
|
* root agent, whenever it resumes (session restore is lazy, so no other
|
|
905
|
-
* session is ever woken for reporting)
|
|
906
|
-
*
|
|
907
|
-
*
|
|
908
|
-
*
|
|
909
|
-
*
|
|
910
|
-
*
|
|
905
|
+
* session is ever woken for reporting). A record without an initiator splits
|
|
906
|
+
* by kind: one carrying diagnostics someone must hear about (an unplanned
|
|
907
|
+
* recovery, a composition rollback, a failed restart) is claimed by the
|
|
908
|
+
* first root agent created, while a bare planned outcome — the restart was
|
|
909
|
+
* driven from outside the host and the operator's terminal already has the
|
|
910
|
+
* announcement — is settled without waking anyone. Separately, a snapshot
|
|
911
|
+
* written at SIGTERM time (`interrupted-sessions.json`) records which root
|
|
912
|
+
* sessions had a live turn when the process stopped, so the next restart
|
|
913
|
+
* boot can resume those sessions and queue a "continue" turn. Pure logic
|
|
914
|
+
* reads the durable files; the plugin wires them into `agent/created` and
|
|
915
|
+
* `agent.followup`.
|
|
911
916
|
*/
|
|
912
917
|
/** Absolute path of the restart record inside a state directory. */
|
|
913
918
|
function restartRecordFile(stateDir) {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Browser half of the original-tab launch-cutover handoff, plus the
|
|
3
3
|
* boot-generation reload loop: polls carry the last-seen boot id, a stale id
|
|
4
4
|
* with no cutover in flight reloads the tab once, and a sustained disconnection
|
|
5
|
-
* (not a transient blip) raises a
|
|
5
|
+
* (not a transient blip) raises a non-blocking connection notice.
|
|
6
6
|
*/
|
|
7
7
|
import type { Context as ClientContext } from '@deepseek-ai/cordis';
|
|
8
8
|
/** No Cordis services are needed; this is a same-origin browser lifecycle observer. */
|
|
@@ -6,8 +6,10 @@ const FALLBACK_HASH_KEY = 'ankh-guard-handoff';
|
|
|
6
6
|
const ACTIVE_RETRY_MS = 250;
|
|
7
7
|
const ERROR_RETRY_MIN_MS = 1_000;
|
|
8
8
|
const ERROR_RETRY_MAX_MS = 30_000;
|
|
9
|
-
/** Sustained-failure threshold for the
|
|
10
|
-
const
|
|
9
|
+
/** Sustained-failure threshold for the connection notice; transient blips never show it. */
|
|
10
|
+
const DISCONNECTED_NOTICE_MS = 5_000;
|
|
11
|
+
// Server idle polls hold for 25s; bound a stalled browser request beyond that.
|
|
12
|
+
const REQUEST_TIMEOUT_MS = 35_000;
|
|
11
13
|
/** No Cordis services are needed; this is a same-origin browser lifecycle observer. */
|
|
12
14
|
export const inject = [];
|
|
13
15
|
function encodeBase64Url(bytes) {
|
|
@@ -88,38 +90,55 @@ function consumeFallbackFragment() {
|
|
|
88
90
|
writePending(pending);
|
|
89
91
|
return pending;
|
|
90
92
|
}
|
|
91
|
-
function waitingOverlay(kind = 'restarting') {
|
|
93
|
+
function waitingOverlay(kind = 'restarting', retry) {
|
|
92
94
|
const existing = document.getElementById('ankh-guard-browser-handoff');
|
|
93
|
-
|
|
95
|
+
// A confirmed restart cannot be downgraded by a subsequent transport failure.
|
|
96
|
+
if (existing?.dataset.kind === 'restarting' && kind === 'disconnected')
|
|
94
97
|
return existing;
|
|
98
|
+
const element = existing ?? document.createElement('div');
|
|
95
99
|
const zh = navigator.language.toLowerCase().startsWith('zh');
|
|
96
|
-
const element = document.createElement('div');
|
|
97
100
|
element.id = 'ankh-guard-browser-handoff';
|
|
101
|
+
element.dataset.kind = kind;
|
|
98
102
|
element.setAttribute('role', 'status');
|
|
99
103
|
element.setAttribute('aria-live', 'polite');
|
|
100
|
-
// 'disconnected' covers failures without a known restart in flight (a bare
|
|
101
|
-
// exit may never come back) — neutral copy, no auto-recovery promise.
|
|
102
104
|
element.textContent = kind === 'disconnected'
|
|
103
|
-
? (zh ? '
|
|
105
|
+
? (zh ? '连接暂时中断,正在重试…' : 'Connection interrupted; retrying…')
|
|
104
106
|
: (zh ? 'dsh 正在重启;此标签页会自动恢复。' : 'dsh is restarting; this tab will recover automatically.');
|
|
105
|
-
element.style.cssText = [
|
|
107
|
+
element.style.cssText = kind === 'restarting' ? [
|
|
106
108
|
'position:fixed', 'inset:0', 'z-index:2147483647', 'display:grid', 'place-items:center',
|
|
107
109
|
'padding:24px', 'background:rgba(15,18,24,.92)', 'color:#fff',
|
|
108
110
|
'font:500 16px/1.5 system-ui,sans-serif', 'text-align:center',
|
|
111
|
+
].join(';') : [
|
|
112
|
+
'position:fixed', 'top:calc(env(safe-area-inset-top,0px) + 12px)', 'left:12px', 'right:12px',
|
|
113
|
+
'max-width:520px', 'margin:0 auto', 'box-sizing:border-box', 'z-index:2147483647',
|
|
114
|
+
'display:flex', 'align-items:center', 'justify-content:space-between', 'gap:12px',
|
|
115
|
+
'padding:12px 16px', 'border:1px solid var(--dsw-alias-border-l2,#8885)', 'border-radius:16px',
|
|
116
|
+
'background:var(--dsw-alias-bg-base,#fff)', 'color:var(--dsw-alias-label-primary,#181a20)',
|
|
117
|
+
'box-shadow:0 4px 24px #0002', 'font:400 15px/1.5 system-ui,sans-serif',
|
|
109
118
|
].join(';');
|
|
110
|
-
|
|
119
|
+
if (kind === 'disconnected' && retry) {
|
|
120
|
+
const button = document.createElement('button');
|
|
121
|
+
button.textContent = zh ? '重试' : 'Retry';
|
|
122
|
+
button.style.cssText = 'flex:none;border:0;background:transparent;color:var(--dsw-alias-state-business-primary,#4d6bfe);font:inherit;min-height:36px;cursor:pointer';
|
|
123
|
+
button.onclick = retry;
|
|
124
|
+
element.append(button);
|
|
125
|
+
}
|
|
126
|
+
// Official theme tokens live on body, so the notice must inherit from it.
|
|
127
|
+
if (!existing)
|
|
128
|
+
(document.body ?? document.documentElement).append(element);
|
|
111
129
|
return element;
|
|
112
130
|
}
|
|
113
|
-
async function post(body) {
|
|
131
|
+
async function post(body, signal) {
|
|
114
132
|
return fetch(ROUTE, {
|
|
115
133
|
method: 'POST',
|
|
134
|
+
signal,
|
|
116
135
|
credentials: 'same-origin',
|
|
117
136
|
cache: 'no-store',
|
|
118
137
|
headers: { 'content-type': 'application/json' },
|
|
119
138
|
body: JSON.stringify(body),
|
|
120
139
|
});
|
|
121
140
|
}
|
|
122
|
-
async function acknowledge(pending) {
|
|
141
|
+
async function acknowledge(pending, signal) {
|
|
123
142
|
const response = await post({
|
|
124
143
|
version: 1,
|
|
125
144
|
operation: 'ack',
|
|
@@ -127,7 +146,7 @@ async function acknowledge(pending) {
|
|
|
127
146
|
channel: pending.channel,
|
|
128
147
|
authentication: pending.authentication,
|
|
129
148
|
...(pending.capability !== undefined ? { capability: pending.capability } : {}),
|
|
130
|
-
});
|
|
149
|
+
}, signal);
|
|
131
150
|
return response.status === 204;
|
|
132
151
|
}
|
|
133
152
|
/** Install the polling/reload loop for this tab's lifetime. */
|
|
@@ -138,24 +157,68 @@ export function apply(_ctx) {
|
|
|
138
157
|
let cap;
|
|
139
158
|
let errorRetryMs = ERROR_RETRY_MIN_MS;
|
|
140
159
|
let firstFailureAt;
|
|
141
|
-
|
|
142
|
-
|
|
160
|
+
let request;
|
|
161
|
+
let lastWakeAt = -Infinity;
|
|
162
|
+
const foreground = () => document.visibilityState !== 'hidden' && navigator.onLine !== false;
|
|
163
|
+
const clearConnectionNotice = () => {
|
|
164
|
+
const notice = document.getElementById('ankh-guard-browser-handoff');
|
|
165
|
+
if (notice?.dataset.kind === 'disconnected')
|
|
166
|
+
notice.remove();
|
|
167
|
+
};
|
|
168
|
+
const pause = () => {
|
|
169
|
+
if (timer !== undefined)
|
|
170
|
+
clearTimeout(timer);
|
|
171
|
+
timer = undefined;
|
|
172
|
+
const previous = request;
|
|
173
|
+
request = undefined;
|
|
174
|
+
previous?.abort();
|
|
175
|
+
firstFailureAt = undefined;
|
|
176
|
+
errorRetryMs = ERROR_RETRY_MIN_MS;
|
|
177
|
+
clearConnectionNotice();
|
|
178
|
+
};
|
|
179
|
+
const wake = () => {
|
|
180
|
+
if (disposed || !foreground() || Date.now() - lastWakeAt < 500)
|
|
181
|
+
return;
|
|
182
|
+
lastWakeAt = Date.now();
|
|
183
|
+
pause();
|
|
184
|
+
schedule();
|
|
185
|
+
};
|
|
186
|
+
const visibility = () => {
|
|
187
|
+
if (!foreground()) {
|
|
188
|
+
lastWakeAt = -Infinity;
|
|
189
|
+
pause();
|
|
190
|
+
}
|
|
191
|
+
else
|
|
192
|
+
wake();
|
|
193
|
+
};
|
|
194
|
+
const page = (event) => { if (event.persisted)
|
|
195
|
+
wake(); };
|
|
196
|
+
// Only visible failures count. Ordinary outages never block the page.
|
|
143
197
|
const noteDisconnected = () => {
|
|
144
198
|
firstFailureAt ??= Date.now();
|
|
145
|
-
if (Date.now() - firstFailureAt >=
|
|
146
|
-
waitingOverlay('disconnected');
|
|
199
|
+
if (Date.now() - firstFailureAt >= DISCONNECTED_NOTICE_MS)
|
|
200
|
+
waitingOverlay('disconnected', wake);
|
|
147
201
|
};
|
|
148
202
|
const schedule = (delayMs = 0) => {
|
|
149
|
-
if (
|
|
150
|
-
timer
|
|
203
|
+
if (timer !== undefined)
|
|
204
|
+
clearTimeout(timer);
|
|
205
|
+
if (!disposed && foreground())
|
|
206
|
+
timer = setTimeout(() => { timer = undefined; void tick(); }, delayMs);
|
|
151
207
|
};
|
|
152
208
|
const tick = async () => {
|
|
153
|
-
if (disposed)
|
|
209
|
+
if (disposed || !foreground() || request)
|
|
154
210
|
return;
|
|
211
|
+
const current = new AbortController();
|
|
212
|
+
request = current;
|
|
213
|
+
const stale = () => disposed || request !== current || !foreground();
|
|
214
|
+
const deadline = setTimeout(() => current.abort(), REQUEST_TIMEOUT_MS);
|
|
155
215
|
try {
|
|
156
216
|
if (pending !== null) {
|
|
157
217
|
waitingOverlay();
|
|
158
|
-
|
|
218
|
+
const acknowledged = await acknowledge(pending, current.signal);
|
|
219
|
+
if (stale())
|
|
220
|
+
return;
|
|
221
|
+
if (acknowledged) {
|
|
159
222
|
const returnPath = pending.returnPath;
|
|
160
223
|
const originalTab = pending.channel === 'original-tab';
|
|
161
224
|
sessionStorage.removeItem(PENDING_KEY);
|
|
@@ -178,7 +241,9 @@ export function apply(_ctx) {
|
|
|
178
241
|
const response = await post({
|
|
179
242
|
version: 1, operation: 'poll', capability: tabCapability,
|
|
180
243
|
...(knownBootId === null ? {} : { knownBootId }),
|
|
181
|
-
});
|
|
244
|
+
}, current.signal);
|
|
245
|
+
if (stale())
|
|
246
|
+
return;
|
|
182
247
|
if (!response.ok) {
|
|
183
248
|
noteDisconnected();
|
|
184
249
|
schedule(errorRetryMs);
|
|
@@ -188,6 +253,8 @@ export function apply(_ctx) {
|
|
|
188
253
|
errorRetryMs = ERROR_RETRY_MIN_MS;
|
|
189
254
|
firstFailureAt = undefined;
|
|
190
255
|
const result = await response.json();
|
|
256
|
+
if (stale())
|
|
257
|
+
return;
|
|
191
258
|
// Learn the serving boot id before acting on it — storing the successor's
|
|
192
259
|
// id ahead of the reload is what makes the generation check one-shot.
|
|
193
260
|
if (typeof result.bootId === 'string')
|
|
@@ -247,18 +314,35 @@ export function apply(_ctx) {
|
|
|
247
314
|
}
|
|
248
315
|
}
|
|
249
316
|
catch {
|
|
317
|
+
if (stale())
|
|
318
|
+
return;
|
|
250
319
|
// The expected restart interval rejects fetches; keep the old page and retry.
|
|
251
320
|
noteDisconnected();
|
|
252
321
|
schedule(errorRetryMs);
|
|
253
322
|
errorRetryMs = Math.min(errorRetryMs * 2, ERROR_RETRY_MAX_MS);
|
|
254
323
|
return;
|
|
255
324
|
}
|
|
325
|
+
finally {
|
|
326
|
+
clearTimeout(deadline);
|
|
327
|
+
if (request === current)
|
|
328
|
+
request = undefined;
|
|
329
|
+
}
|
|
256
330
|
schedule(ACTIVE_RETRY_MS);
|
|
257
331
|
};
|
|
332
|
+
document.addEventListener('visibilitychange', visibility);
|
|
333
|
+
window.addEventListener('online', wake);
|
|
334
|
+
window.addEventListener('offline', visibility);
|
|
335
|
+
window.addEventListener('pageshow', page);
|
|
336
|
+
window.addEventListener('dsh-mobile-foreground', wake);
|
|
258
337
|
void tick();
|
|
259
338
|
return () => {
|
|
260
339
|
disposed = true;
|
|
261
|
-
|
|
262
|
-
|
|
340
|
+
pause();
|
|
341
|
+
document.removeEventListener('visibilitychange', visibility);
|
|
342
|
+
window.removeEventListener('online', wake);
|
|
343
|
+
window.removeEventListener('offline', visibility);
|
|
344
|
+
window.removeEventListener('pageshow', page);
|
|
345
|
+
window.removeEventListener('dsh-mobile-foreground', wake);
|
|
346
|
+
document.getElementById('ankh-guard-browser-handoff')?.remove();
|
|
263
347
|
};
|
|
264
348
|
}
|