Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
cd2b746
test(electron): add checkpoint/restore phase-0 prerequisite probes
cherrchen Oct 18, 2026
fe65d2a
feat(electron): record host shutdown report for checkpoint promotion
cherrchen Oct 18, 2026
c3e44c4
feat(electron): capture verified session checkpoints on clean exit
cherrchen Oct 18, 2026
035cf54
feat(electron): journaled session restore with startup convergence
cherrchen Oct 18, 2026
2c64c27
feat(electron): detect foreign session writes and offer explicit restore
cherrchen Oct 18, 2026
a9993e5
feat(electron): phase-5 acceptance and fault-injection coverage
cherrchen Oct 18, 2026
001a712
fix(electron): address review findings on checkpoint/restore
cherrchen Oct 18, 2026
d439a9d
fix(electron): close checkpoint/restore durability and startup gaps
cherrchen Oct 18, 2026
0b4b635
fix(electron): match real SessionFormatUnsupportedError text in refus…
cherrchen Oct 18, 2026
104ea72
fix(electron): stop interpolating raw session ids into restore paths
cherrchen Oct 18, 2026
70de248
fix(electron): require an idle write lease for Host-up unreadable ver…
cherrchen Oct 18, 2026
bc81e2e
fix(electron): show a scan-failure body when manual detection fails
cherrchen Oct 18, 2026
5878784
fix(electron): re-verify staged restore temps on resume
cherrchen Oct 18, 2026
35cf2f1
fix(electron): split unresolvable journals out of the deferred gate
cherrchen Oct 18, 2026
7a59378
fix(electron): invalidate member rows in the legacy projcache file
cherrchen Oct 18, 2026
878cd70
fix(electron): validate manifest and journal shapes at the durable bo…
cherrchen Oct 18, 2026
debb6e7
fix(electron): pin the flat-session-dir invariant at journal build
cherrchen Oct 18, 2026
b81c4e9
fix(electron): harden capture teardown, deadline reach, and copy accu…
cherrchen Oct 18, 2026
e5eeef7
fix(electron): validate durable records through unknown narrowing
cherrchen Oct 18, 2026
ad86662
fix(electron): enforce cooperative shutdown, startup gating, and pinn…
cherrchen Oct 19, 2026
6a9f120
fix(electron): use writable handles for checkpoint file flushes
cherrchen Oct 19, 2026
e6ecdc1
fix(electron): close checkpoint crash-recovery and detection gaps
cherrchen Oct 20, 2026
fec43db
fix(electron): preserve pending restores and cancel checkpoint copies
cherrchen Oct 20, 2026
ec2dc75
fix(electron): handle checkpoint recovery errors and decisions
cherrchen Oct 20, 2026
8412554
fix(electron): scope restore to session history and abort capture on …
cherrchen Oct 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Bilingual-pair consistency record for 2026-10-18-session-checkpoint-restore.md (docs/i18n/README.md): per heading
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
# After editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-10-18-session-checkpoint-restore.md
/agent-note-desktop-session-checkpoint-and-journaled-restore:
en: 410d03dbfea4b1eb
zh: b3ab772826333cd4
/agent-note-desktop-session-checkpoint-and-journaled-restore/problem:
en: ddf3c6e9b82c03f2
zh: fdef021c250dd844
/agent-note-desktop-session-checkpoint-and-journaled-restore/decision:
en: 344cdb2ff6e00b1f
zh: 15ff274bd1c2e116
/agent-note-desktop-session-checkpoint-and-journaled-restore/alternatives-considered:
en: 12a242a3df0b8e19
zh: 974ba1c720bd12b5
/agent-note-desktop-session-checkpoint-and-journaled-restore/consequences:
en: e0f6446e35a830dd
zh: 7021477edf4cd6a6
/agent-note-desktop-session-checkpoint-and-journaled-restore/testing:
en: b9b361e73236488b
zh: ced6c3d80fdb04bd
/agent-note-desktop-session-checkpoint-and-journaled-restore/deferred:
en: 63dc21569fc037b7
zh: bbe023e4148fc0e6
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Agent Note: Desktop session checkpoint and journaled restore

Status: implemented

English | [中文](2026-10-18-session-checkpoint-restore.zh.md)

## Problem

Rivetdeck and CLI `dsh --profile web` share one `$DSH_HOME`, so a newer CLI can write session data the bundled Harness cannot read. One failure mode publishes a higher-generation file and leaves the predecessor intact; the other — the reported incident, dsh-plugin-git PR #20's `job/registered` at seq 24 inside `session.v4.jsonl.zstd` — pollutes the shared current-generation file in place and leaves no untouched copy to expose. For the second mode the only remedy was opening the data on a newer host.

## Decision

Desktop stages a checkpoint candidate after each reported clean Host drain and commits it only for sessions that pass the bundled Harness's real read path inside a read-only worker child. A session whose new copy fails validation keeps its previous verified copy carried into the candidate; a candidate mixing capture times re-validates the combined related-source closure before commit. Per-session restore is a journaled transaction under the session's own `session.lock` write lease: live files are fingerprinted and moved to quarantine, checkpoint bytes publish under canonical names, and a leftover journal converges before the next Host spawn — deferring with a startup dialog while a foreign writer holds a member lease, and stopping as conflicted when live files drifted during the interruption. Three entries reach restore: startup drift detection confirmed through the same read-only validation, an open-time `SessionFormatUnsupportedError` prompt, and a manual menu item.

The mechanism contract lives in [docs/electron/session-checkpoint-restore.md](../../../../docs/electron/session-checkpoint-restore.md). The covered set is sessions plus the managed profile file set, the home-root patch, `session_projcache` storages, and `.env`; credentials, identity, attachments, `node_modules`, and transient lock files are excluded. Restore granularity is per session with its related-source closure; profile rollback is a separate transaction and stays out of scope. Retention bounds only total space — every session's last verified copy is protected. Files from a killed or unverifiable run never promote.

## Alternatives considered

**Desktop-private sessions root.** One overlay line makes Desktop immune to skew but Desktop and CLI stop sharing session history, which defeats the shared-home premise.

**Startup sweep hiding future-generation files.** Covers only the newer-file mode; in-place pollution lives inside the shared current-generation file and a sweep would silently truncate visible history.

**Sanitize-fork: decode, drop unknown events, write a new session copy.** Dropped events can leave dangling references and need seq renumbering, so it offers no safety guarantee. It remains a possible recovery tool, not the main path.

**Keep the bundled Harness version-aligned with the CLI.** An operational practice rather than a mechanism; skew recurs whenever the CLI moves first.

**Do nothing.** Matches the upstream conclusion but leaves the incident's only remedy as "open on a newer host" for the in-place-pollution mode, where no pre-image exists.

## Consequences

Restore rolls back; it does not merge. A checkpoint must exist before the pollution — already-affected sessions have no pre-image. Per-session validation is the load-bearing gate; stat-diff is only a scan hint and flags compatible foreign writes too. A crash or force-quit promotes nothing, so a restore can drop a whole run's session growth. The write lease protects only writers that honor it, so restore additionally requires the confirmed no-other-Harness precondition across switch and finalize; an undetectable foreign reader's in-flight `coldSnapshot` write-back can still re-poison `session_projcache`, a documented non-guarantee. CLI-only users hitting the same skew have no recourse here.

## Testing

`apps/electron/tests/checkpoint-acceptance.spec.ts` pins the fault-injection and real-backend cases: carry-forward across two polluted exits, real `session.lock` competition, read-open at every switch stage, interruption resume at each phase, unknown-event and newer-generation restores, and branch preservation through a quarantine swap-back. `checkpoint-restore.spec.ts` covers the journal state machine and refusals; `checkpoint.spec.ts` covers capture, promotion gating, and the validation worker's result framing; `checkpoint-detect.spec.ts` covers drift detection and the honest no-checkpoint refusal.

## Deferred

- Whether a separately confirmed quarantine feature is wanted for sessions with no verified copy; v1 refuses and leaves live data in place.
- Whether the first launch after a restore may immediately run scheduled or background tasks against restored state.
- Cache coherence beyond the precondition: a `CheckpointIdentity` epoch or equivalent upstream change would let stale `session_projcache` write-backs be rejected instead of relying on process absence.
- Profile and dependency restore, including rebuild honoring `pnpm-lock.yaml` and the install-failure fallback; v1 backs up the managed file set without claiming coverage.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Agent Note: Desktop session checkpoint 与 journaled restore

Status: implemented

[English](2026-10-18-session-checkpoint-restore.md) | 中文

## Problem

Rivetdeck 与 CLI `dsh --profile web` 共享同一个 `$DSH_HOME`,因此新版 CLI 写入的 session 数据可能让内置 Harness 无法读取。一种失败模式是发布更高世代的文件,旧文件保持完整;另一种——已报告的事故,即 dsh-plugin-git PR #20 中 `session.v4.jsonl.zstd` seq 24 处的 `job/registered`——就地污染共享的当前世代文件,没有未受触碰的副本可以暴露。对第二种模式,此前唯一的补救是在更新的主机上打开数据。

## Decision

Desktop 在每次经报告的干净 Host drain 之后暂存一个 checkpoint 候选,且只晋升在只读 worker 子进程中通过内置 Harness 真实读取路径校验的 session。新副本校验失败的 session 保留其先前已验证副本并带入候选;混合了不同捕获时间的候选在提交前对合并后的关联源闭包再次校验。按 session 的恢复是在该 session 自有 `session.lock` 写租约下执行的 journaled 事务:live 文件先取指纹再移入隔离区,checkpoint 字节以规范文件名发布,遗留 journal 在下一次 Host 启动前收敛——当外部写入者持有成员租约时推迟并以启动弹窗呈现,当中断期间 live 文件漂移时以 conflicted 停止。进入恢复有三个入口:经同一只读校验确认的启动漂移检测、打开时的 `SessionFormatUnsupportedError` 提示,以及手动菜单项。

机制契约见 [docs/electron/session-checkpoint-restore.zh.md](../../../../docs/electron/session-checkpoint-restore.zh.md)。覆盖集为 sessions、受管理的 profile 文件集、home 根 patch、`session_projcache` storages 与 `.env`;credentials、identity、attachments、`node_modules` 与临时锁文件排除在外。恢复粒度为单个 session 及其关联源闭包;profile 回滚是独立事务,不在本范围。保留策略只约束总空间——每个 session 的最后一份已验证副本受保护。被杀死或不可验证的运行产生的文件一律不晋升。

## Alternatives considered

**Desktop 私有 sessions root。** 一行 overlay 即可让 Desktop 免于版本错位,但 Desktop 与 CLI 不再共享 session 历史,违背共享 home 的前提。

**启动时扫过并隐藏高世代文件。** 只覆盖新文件模式;就地污染位于共享的当前世代文件内部,清扫会静默截断可见历史。

**sanitize-fork:解码、丢弃未知事件、写出新 session 副本。** 被丢弃的事件可能留下悬空引用且需要重编 seq,无法提供安全保证。它仍是一种可能的恢复工具,而非主路径。

**让内置 Harness 与 CLI 保持版本一致。** 这是运营手段而非机制;只要 CLI 先行,错位就会复发。

**不做任何处理。** 与上游结论一致,但对就地污染模式而言——其不存在前像——事故的唯一补救仍停留在"在更新的主机上打开"。

## Consequences

恢复是回滚,不是合并。checkpoint 必须先于污染存在——已受影响的 session 没有前像。按 session 的校验是承重的闸门;stat-diff 只是扫描提示,也会标记完全兼容的外部写入。崩溃或强退不晋升任何内容,因此一次恢复可能丢弃整轮运行的 session 增量。写租约只保护遵守它的写入者,因此恢复还要求经确认的无其它 Harness 前提贯穿 switch 与 finalize;一个无法检测的外部 reader 的在途 `coldSnapshot` 回写仍可重新污染 `session_projcache`,这是记录在案的非保证项。遭遇同样版本错位的纯 CLI 用户在此没有补救途径。

## Testing

`apps/electron/tests/checkpoint-acceptance.spec.ts` 固定了故障注入与真实后端用例:两次污染退出间的 carry-forward、真实 `session.lock` 竞争、各 switch 阶段的 read-open、各阶段中断后的 resume、未知事件与更高世代的恢复,以及 quarantine 换回后的分支保全。`checkpoint-restore.spec.ts` 覆盖 journal 状态机与各类拒绝;`checkpoint.spec.ts` 覆盖捕获、晋升闸门与校验 worker 的结果帧;`checkpoint-detect.spec.ts` 覆盖漂移检测与无检查点时的如实拒绝。

## Deferred

- 是否需要为没有已验证副本的 session 提供单独确认的隔离区功能;v1 拒绝并保持 live 数据原样。
- 恢复后的首次启动能否立即对已恢复状态运行定时或后台任务。
- 前提之外的缓存一致性:`CheckpointIdentity` epoch 或等价的上游改动可以让陈旧的 `session_projcache` 回写被拒绝,而不必依赖进程缺席。
- Profile 与依赖恢复,包括按 `pnpm-lock.yaml` 重建与安装失败回退;v1 备份受管理文件集但不宣称覆盖。
9 changes: 9 additions & 0 deletions .github/workflows/desktop-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,15 @@ jobs:
DSH_ELECTRON_CORE_SMOKE: '1'
run: pnpm exec vitest run apps/electron/tests/runtime.spec.ts

- name: Build Host for checkpoint shutdown
run: |
pnpm run build
pnpm --filter @dsh-electron/dsh-electron build:runtime-plugins
pnpm --filter @dsh-electron/dsh-electron build:main

- name: Test Windows checkpoint shutdown
run: pnpm --filter @dsh-electron/dsh-electron exec vitest run --config vitest.ownership.config.ts tests/checkpoint-host.live.e2e.ts

- name: Test Windows Network Runtime
run: pnpm --filter @dsh-electron/dsh-electron test:network-runtime

Expand Down
8 changes: 4 additions & 4 deletions apps/electron/README.i18n.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
en: fc4c57f046bb0f70
zh: be135762735d97a6
/rivetdeck/development:
en: ea6572d211dbb239
zh: 9a8be42f53e04a76
en: e630a97f545fae5d
zh: fb88cc959d53e210
/rivetdeck/desktop-integration:
en: 953c3bc2680c4391
zh: 85bcbdb54323009a
en: a087abc7c066c636
zh: e946ae81afa211ed
/rivetdeck/updates:
en: 033fb650e58c1882
zh: 9c39e282313d6346
Expand Down
4 changes: 3 additions & 1 deletion apps/electron/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ pnpm --filter @dsh-electron/dsh-electron test:ownership

Git artifact acceptance compares native filesystem paths so Windows short names and Git path separators identify the same directory. Runtime verification fixtures link the installed Node executable on POSIX and copy it on Windows; the verification case and cleanup each allow 30 seconds to cover the verifier's 15-second child deadline. Managed runtime checks execute at the final generation path before activation ([lifecycle](../../docs/electron/runtime-environments.md)).

Checkpoint capture and restore flush staged files through writable handles before publishing them; Windows requires write access for file flushes. POSIX publication also flushes the staged directory hierarchy and both parents of quarantine moves. Regression tests cover writable flushes, directory-sync failure, cross-volume copy interruption, and pending-request acknowledgment before journal deletion.

The Electron application is a private installer, not an npm release member. Its dependency sync copies upstream CLI workspace dependencies using `workspace:^`, excludes experimental peers, and retains stable peers required by optional bundles; the repository's `constraints` check applies that range to this application and keeps the upstream publication rules on upstream packages.

Repository Python integration tests require CPython 3.10+ selected by `python3` on PATH. If mise reports an inactive shim, activate an installed version for the command, for example `mise exec python@3.13.12 -- pnpm test packages/experimental/code-runtime-python/tests/runtime.spec.ts packages/experimental/code-runtime-python/tests/boot-write-failure.spec.ts`. Use a version installed on your machine; `python3 --version` must succeed before running the tests.
Expand All @@ -69,7 +71,7 @@ OS desktop capabilities are owned by Electron Main and exposed through the typed

The bundled Desktop network subprocess plugin imports the installed `dsh-subprocess-local` and `dsh-subprocess` packages at runtime. They must share the Host's executable-not-found error class so terminal shell discovery can skip unavailable candidates.

The native page context menu exposes cut, copy, paste, select all, and reload according to Chromium's current editing capabilities; development builds also expose DevTools. The application menu and tray menu provide the desktop-owned About window, update channel selection, and manual update check.
The native page context menu exposes cut, copy, paste, select all, and reload according to Chromium's current editing capabilities; development builds also expose DevTools. The application menu and tray menu provide the desktop-owned About window, update channel selection, manual update check, and the **Restore a Session…** entry, which scans session checkpoints and offers to roll a session the bundled Harness can no longer read back to the verified copy shown in the confirmation. Pending requests and intent journals keep that checkpoint through relaunch and retention; unfinished or unreadable restore journals require a startup decision before the Host opens sessions. Within one launch, unchanged journal decisions appear once; confirmation warns from the oldest capture time across all restored members. Detection holds related-session write leases throughout validation, and carried copies remain detectable until live data is readable or restored. Restore replaces only canonical session-generation files and leaves extension artifacts untouched; intents containing other files remain deferred. Session-tree enumeration errors abort checkpoint capture, preserving the previous committed copy; a missing sessions root is allowed for a fresh home. [Session checkpoint and restore](../../docs/electron/session-checkpoint-restore.md) describes shutdown, cancellation, and recovery.

The tray uses a monochrome DeepSeek glyph rasterized from the tracked `assets/tray/deepseek.svg` (LobeHub lobe-icons, MIT). `pnpm run build:tray` emits per-DPI PNGs under `build/tray/`; Windows and Linux select a black glyph for a light native theme and a white glyph for a dark native theme at the nearest packaged pixel size for the primary display scale factor, refreshing when Electron reports a theme or display-metrics change. macOS uses pre-rasterized template PNGs so the operating system controls menu-bar contrast.

Expand Down
Loading
Loading