@xxgxiaoyu/dsh-session-sync 0.0.0-stage → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,74 @@
1
+ # Changelog
2
+
3
+ ## 0.8.3 — 2026-10-22
4
+
5
+ - **Privacy fix: removed every screenshot containing real device names, IPs, paths or session titles.** This covers the README images (`docs/preview*.png`, `docs/queue.png`) and the older `docs/mockups/*.png`. The repository no longer contains them, and the npm package no longer bundles them.
6
+ - Sample data in the design draft now uses placeholders (device names, IPs, paths, session titles); the HTML/CSS draft stays, the preview images do not.
7
+ - Code unchanged; **60** tests passing.
8
+
9
+ ## 0.8.2 — 2026-10-22
10
+
11
+ - **npm package renamed to `@xxgxiaoyu/dsh-session-sync`.** The unscoped `dsh-session-sync` belongs to another project (`perrylink`, 0.2.30), so the current account cannot publish it. The bundle patch mount name is updated too; plugin/slot ids and the client namespace are unchanged.
12
+ - Code identical to 0.8.1; **60** tests passing.
13
+
14
+ ## 0.8.1 — 2026-10-22
15
+
16
+ - **Fixed: row actions overlapped the status chip.** “Pull here / Push a session…” was too long for the action column; rows now use short Pull / Push labels, the action column is 104px, and the push view uses a three-column grid.
17
+ - **Clarified “Concurrent transfers”.** Auto estimates from the CPU count and caps at 5 (4 on this machine); manual 1–16 overrides auto and is grouped separately, so the 16 options no longer read as “5 max”.
18
+ - Tests: **60** passing.
19
+
20
+ ## 0.8.0 — 2026-10-22
21
+
22
+ - **UI rebuilt around a session desk.** Devices live in a left rail that only selects; maintenance moved into the stage header ⋯. Direction is now two explicit tabs, Pull / Push, with a one-line flow hint.
23
+ - **Whole-row selection.** Clicking the title, meta line or status badge selects the row; the checkbox is just the visible state. Shift-range, ⌘/Ctrl toggle, Space/Enter, Esc and row-action isolation are preserved.
24
+ - **Selection bar appears only when needed.** It carries the count, target menu, start and clear. The target menu lists ungrouped, workspaces and custom paths, so the old bottom workspace list is gone.
25
+ - **Transfer dock replaces the drawer.** A persistent bottom summary expands upward into the queue; the session list stays readable.
26
+ - **Settings reorganised** into Identity / Transfer / Advanced / About, two columns in the panel and one in the host settings page; manual addresses and subnet scans are list editors now.
27
+ - **Complete state coverage**: empty, loading, offline, no results, read failure and diagnostics; all icons inline, all colours/radii/motion from DSH tokens.
28
+ - Tests: **60** passing, including whole-row selection, target scoping and the new main panel.
29
+
30
+ ## 0.7.1 — 2026-10-22
31
+
32
+ - **Device column overflow fixed**: `box-sizing` on the shell/column/rows, so the row's ⋯ button no longer spills across the column gap and over the session pane.
33
+ - **Long device lists scroll instead of looking truncated**: the column caps its height and scrolls, and a hint states that every discovered (or previously seen) device is listed — other subnets are scanned from “Network & advanced”.
34
+ - **Test**: renders 30 devices and asserts all 30 rows exist.
35
+
36
+ Tests: 59.
37
+
38
+ ## 0.7.0 — 2026-10-22
39
+
40
+ - Panel redesigned around a master/detail layout: devices live in a left column, the selected device's sessions are always visible on the right — no hidden "click the card to expand" affordance. The first online device is selected automatically, device rows are full-row targets with a session count and chevron, and the right pane has 对端会话 / 本机会话 tabs.
41
+ - Device actions moved into a per-row ⋯ menu (push here, rename, copy address, refresh, remove); inline rename stays in the row.
42
+ - `DeviceItem` replaces the old expandable device card; tests updated (58 passing).
43
+ - Screenshot refreshed (`docs/preview.png`).
44
+
45
+ ## 0.6.1 — 2026-10-22
46
+
47
+ - **Fixed: every session looked "Untitled".** `readTitleSnapshots` returns settled results (`{status, value}`), but the old code read them as bare snapshots, so titles were always null; and most sessions never write a `session/title` event — DSH keeps their names in its projection cache. Both sources are now folded together.
48
+ - **Concurrent transfers.** The queue runs N workers in parallel. Default is derived from the CPU count (capped at 5); pick 1/2/3/4/5/6/8/10/12/16 under Settings → Concurrent transfers.
49
+ - **Live transfer progress.** Pulls stream the response and count bytes (`content-length` gives a real percentage); the host exposes `received/total` per transfer in `state.progress`, the client polls every 700 ms while the queue runs, and both the item badge/bar and the bottom bar show real progress instead of a spinner.
50
+ - 58 tests (new: title regression, concurrency validation).
51
+
52
+ ## 0.6.0 — 2026-10-22
53
+
54
+ - **The transfer queue got a real design.** Every transfer is its own card: status icon (spinner / check / warning), session title, direction and peer, target directory, measured duration and the failure reason. Cards slide in with a short stagger, pop their check when they land, and keep a steady shimmer while running.
55
+ - **The batch is legible at a glance.** Four counters open the drawer (done / running / skipped / failed) and the persistent bar shows the running session plus a progress meter, so a long pull no longer looks frozen.
56
+ - **Both entry points behave the same.** Clicking anywhere on the bar (or pressing Enter/Space) opens the drawer; while it is open the bar stays out of the way.
57
+ - **Accents use DSH's business blue.** `--dsw-alias-brand-primary` is white in the dark theme, which made the running card unreadable; the queue now draws its accents from `--dsw-alias-state-business-primary` and keeps success/error tokens for outcomes.
58
+ - **Motion respects `prefers-reduced-motion`.** Spinner, shimmer, stagger and pop are all disabled for users who ask for less movement.
59
+ - Tests **56** pass, including the drawer contract (one card per transfer, four counters, filled track for settled rows).
60
+
61
+ ## 0.5.5 — 2026-10-21
62
+
63
+ - **Sessions pulled by early builds open again.** Those copies were imported with `isSeeded: false` but still carried the peer's inherited end-seed marker, which DSH rejects with `released v2 unseeded Session contains an inherited end-seed marker`. Imports now strip the inherited prefix, so a copy only contains the conversation you actually own.
64
+ - **Repair what is already on disk.** *Network & advanced → Repair old sessions* scans `<DSH_HOME>/sessions`, rewrites affected logs (keeping a `.bak` beside each one) and reports how many it fixed. The same logic is available offline: `node scripts/repair-sessions.mjs [--dry-run] [--home ~/.dsh]`.
65
+ - **Stored logs are read frame by frame.** DSH writes one zstd frame per batch and Node's `zstdDecompressSync` stops after the first one, so the repair path walks the frame headers itself instead of guessing.
66
+ - Tests **56** pass, including the inherited-prefix regression and the repair round trip.
67
+
68
+ ## 0.5.4 — 2026-10-21
69
+
70
+ - **Dropping a session actually starts the transfer.** `dragend` fires before the 90 ms success animation finished, and it cleared the drag payload; the drop then ran with `null` and returned silently. The payload is now captured at drop time and handed to the queue, so a drop opens the confirmation dialog again.
71
+ - **Drops go through DSH's own workspace model.** Dropping on a sidebar row no longer just copies the folder path: the imported session is attached with `workspaceRegistry.attachSession()` and positioned with `insertSessionBefore()`, which is exactly what the native sidebar does internally. `workspace.json` is never rewritten by hand.
72
+ - **Insert at the row you point at.** Hovering a session row draws a 2 px insertion line above or below it (whichever half the pointer is in) and the pull request carries `workspace` + `before`, so the conversation lands between the rows you chose instead of at the end of the group.
73
+ - **The confirmation names the workspace.** The dialog shows the group title (`拉取到 Alpha`) instead of the raw directory path.
74
+ - Tests **50** pass, including the attach/insert contract and the drop payload captured after `dragend`.
@@ -0,0 +1,59 @@
1
+ # 更新日志
2
+
3
+ ## 0.8.3 — 2026-10-22
4
+
5
+ - **隐私修复:移除所有含真实设备名、IP、路径和会话标题的截图。** 受影响的是 README 里的 `docs/preview*.png`、`docs/queue.png`,以及旧设计稿 `docs/mockups/*.png`。仓库不再包含这些图片,npm 包也不再打包它们。
6
+ - 设计稿里的示例数据同步改成占位符(设备名、IP、路径、会话标题),避免源码继续暴露;设计稿的 HTML/CSS 仍在,预览图不再随仓库发布。
7
+ - 测试 **60 项**通过;代码逻辑未改。
8
+
9
+ ## 0.8.2 — 2026-10-22
10
+
11
+ - **npm 发布名改为 `@xxgxiaoyu/dsh-session-sync`。** npm 上的 `dsh-session-sync` 已被 `perrylink` 的项目占用(0.2.30),无法用当前账号发布原名;scope 包名与 `cordis.patch.yml` 的挂载 `name` 同步更新。插件 id、槽位 id 和客户端命名空间保持不变。
12
+ - 代码与 0.8.1 一致;测试 **60 项**通过。
13
+
14
+ ## 0.8.1 — 2026-10-22
15
+
16
+ - **修复:会话行状态与行内按钮重叠。** 行内「拉取到本机 / 推送会话…」的文案太长,挤出了动作列并盖住状态徽章;现在行内改用短标签「拉取 / 推送」,动作列加宽到 104px,推送视图改用三列网格。
17
+ - **说清「同时传输」的含义。** 自动值按 CPU 核数估算、封顶 5(本机当前 4 个);手动 1–16 会覆盖自动值,单独放在「手动并发」分组里,不再让人误以为最多只能选 5。
18
+ - 测试 **60 项**通过。
19
+
20
+ ## 0.8.0 — 2026-10-22
21
+
22
+ - **界面重构。** 会话台改成「设备栏 + 会话台」:左栏只负责选设备,维护动作收进右栏头部的 ⋯;方向改成「拉取 / 推送」两个标签,标题下写明流向。
23
+ - **整行点击选择。** 点标题、元信息、状态徽章都能选中;勾选框只显示状态。Shift 连选、⌘/Ctrl 追加、Space/Enter 切换、Esc 清除都保留,行内按钮不抢选择。
24
+ - **选择条按需出现。** 选中后才浮出「已选 N、目标目录、开始、清除」;目标菜单列出未分组、工作区和自定义路径,不再在页面底部重复一整段工作区列表。
25
+ - **传输坞替代抽屉。** 底部一条总进度,向上展开逐条队列;列表始终可读、可继续勾选。
26
+ - **设置重做。** 身份 / 传输 / 高级 / 关于四块,面板内双栏、宿主设置页单栏;手动地址和扫描网段改成可增删的列表编辑器。
27
+ - **状态补齐。** 空态、加载、离线、无结果、读取失败、诊断都按新壳重画;图标全部内联 SVG,颜色/圆角/动效继续走 DSH token,深浅主题自动跟随。
28
+ - 测试 **60 项**通过,新增「整行点击选择」「目标菜单限定工作区」「主面板设备列表/会话校验」等覆盖。
29
+
30
+ ## 0.6.1 — 2026-10-22
31
+
32
+ - **修复:会话列表全是「未命名会话」。** 两个原因:`readTitleSnapshots` 返回的是 settled 结果(`{status, value}`),旧代码按裸快照读取,标题永远是 null;且多数会话从不写 `session/title` 事件,名字只存在 DSH 的投影缓存里。现在两条都读,标题正常显示。
33
+ - **多线程同步。** 队列改成 N 个 worker 并行拉取/推送,默认按 CPU 核数自动估(上限 5),也可在「同步设置 → 同时传输」里指定 1/2/3/4/5/6/8/10/12/16。
34
+ - **实时传输进度。** 拉取改为流式读取并统计字节数(`content-length` 已知时给出百分比),宿主在 `state.progress` 暴露每个传输的 `received/total`;队列运行时客户端 700ms 轮询一次,卡片徽章显示百分比、进度条按真实字节走,底条也把当前项的进度算进总进度。
35
+ - 测试 58 项(新增标题回归、并发设置校验)。
36
+
37
+ ## 0.6.0 — 2026-10-22
38
+
39
+ - **传输队列重做。** 每个传输一张卡:状态图标(旋转/对勾/警告)、会话标题、方向与设备、目标目录、实测耗时与失败原因。卡片带短暂的错峰入场、完成时的对勾弹跳,运行中保留持续的流光进度条。
40
+ - **一眼看清这一批。** 抽屉顶部四格计数(已完成 / 传输中 / 已存在,跳过 / 失败),页面底部常驻条显示当前传输项与进度条,长任务不再像卡住。
41
+ - **两个入口一致。** 点条上任意位置(或 Enter/空格)都能打开抽屉;抽屉打开时条自动让位。
42
+ - **强调色改用 DSH 的业务蓝。** 深色主题里 `--dsw-alias-brand-primary` 是白色,会把运行中的卡片刷成白底白字;队列改用 `--dsw-alias-state-business-primary`,成功/失败仍走语义色。
43
+ - **动效尊重 `prefers-reduced-motion`。** 旋转、流光、错峰入场与弹跳都会被关掉。
44
+ - 测试 **56 项**通过,新增抽屉契约(每条一张卡、四格计数、完成行带满格轨道)。
45
+
46
+ ## 0.5.5 — 2026-10-21
47
+
48
+ - **早期版本拉取过的会话能重新打开了。** 那些副本写入时标成 `isSeeded: false`,却还带着对端的继承标记,DSH 会以 `released v2 unseeded Session contains an inherited end-seed marker` 拒绝加载。现在导入时会剥掉继承前缀,副本里只保留真正属于你的对话。
49
+ - **已经坏在磁盘上的也能修。** 设置里的「网络与高级 → 修复历史会话」会扫描 `<DSH_HOME>/sessions`,就地修复受影响的日志(每个旁边留一份 `.bak`),并告诉你修了几个。离线可用同款逻辑:`node scripts/repair-sessions.mjs [--dry-run] [--home ~/.dsh]`。
50
+ - **按帧读取存储日志。** DSH 每批写入一个 zstd 帧,而 Node 的 `zstdDecompressSync` 只解第一帧,修复路径因此自己解析帧头,不靠猜。
51
+ - 测试 **56 项**通过,包含继承前缀回归与修复往返回环。
52
+
53
+ ## 0.5.4 — 2026-10-21
54
+
55
+ - **拖拽真的会触发传输了。** `dragend` 在 90ms 成功态播完之前就触发,并清空了拖拽载荷;等延时回调执行 drop 时载荷已是 `null`,于是静默返回。现在在松手瞬间捕获载荷再交给队列,拖放会正常弹出确认框。
56
+ - **放下时走 DSH 自己的工作区模型。** 拖到侧栏行不再只是把目录路径当 cwd:导入的会话会用 `workspaceRegistry.attachSession()` 挂进工作区,并用 `insertSessionBefore()` 排到指定位置——这正是原生侧栏内部用的同一套接口,插件不再手改 `workspace.json`。
57
+ - **按指针位置插入。** 悬停会话行时会在该行的上半/下半画一条 2px 插入线,拉取请求带上 `workspace` 与 `before`,会话会落在你指的那两行之间,而不是分组末尾。
58
+ - **确认框写明工作区。** 标题显示分组名(`拉取到 Alpha`),不再显示一长串目录路径。
59
+ - 测试 **50 项**通过,新增「挂载 + 插入位置」契约与「dragend 之后仍能拿到拖拽载荷」的回归用例。
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 XXGxiaoyu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,184 @@
1
- # Temporary Holding Version
1
+ # dsh-session-sync
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+
4
+ **LAN session sync for DSH.** Every DSH that has this plugin discovers the others on the local network, lists their sessions, and can pull or push conversations over plain HTTP. The nickname you give a device lives **only on your machine** — the peer can go offline, reboot, or change IP and come back, and you still see the name you gave it.
5
+
6
+ <div align="center">
7
+
8
+ [![CI](https://github.com/XXGxiaoyu/dsh-session-sync/actions/workflows/ci.yml/badge.svg)](https://github.com/XXGxiaoyu/dsh-session-sync/actions/workflows/ci.yml)
9
+ [![GitHub stars](https://img.shields.io/github/stars/XXGxiaoyu/dsh-session-sync)](https://github.com/XXGxiaoyu/dsh-session-sync/stargazers)
10
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
11
+ [![DSH](https://img.shields.io/badge/DSH-0.2.1--rc.2-4d6bfe)](https://www.npmjs.com/package/@deepseek-ai/dsh)
12
+
13
+ 🌏 [简体中文](./README_ZH.md) · **English**
14
+
15
+ </div>
16
+
17
+ > **0.8.0 UI rebuild**: session desk, whole-row selection, selection bar, transfer dock and settings were redrawn. The full draft (19 scenes, dark/light, zh/en) is in [`docs/redesign/index.html`](https://github.com/XXGxiaoyu/dsh-session-sync/tree/main/docs/redesign); implementation mapping and acceptance are in [`DESIGN.md`](https://github.com/XXGxiaoyu/dsh-session-sync/blob/main/docs/redesign/DESIGN.md). The whole-session file transfer design (chunks / resume) is in [`session-bundle-transfer.md`](https://github.com/XXGxiaoyu/dsh-session-sync/blob/main/docs/architecture/session-bundle-transfer.md) (next phase).
18
+
19
+
20
+
21
+ ## ✨ Features
22
+
23
+ | | |
24
+ |---|---|
25
+ | **📡 Zero-config discovery**<br/>UDP broadcast on every IPv4 interface with a 4s beacon. A peer that just appeared gets an immediate unicast reply, so new devices show up without waiting for the next round. | **🏷️ Local-only nicknames**<br/>Stored in `<home>/session-sync/state.json` and keyed by a stable **device id** (uuid) — not IP, not hostname. The peer renaming itself never overwrites your label. |
26
+ | **🗂️ Browse · pull · push**<br/>See each device's sessions with title, cwd and time; pull one into this machine or push one of yours out. Imported rows are marked as pulled. | **🔐 SHA-256 verification**<br/>Exports carry `sha256 = SHA-256(JSON.stringify({format, session, events}))`; imports compare it **before writing a byte** and refuse with HTTP 409 `checksum-mismatch`. Each row has a *Verify* action. |
27
+ | **🔁 Optional auto-pull**<br/>Every 20s, pull sessions you have not pulled yet. Content fingerprints (not ids) stop two machines with auto-pull on from bouncing one conversation back and forth forever. | **🎨 Native look, i18n**<br/>Built from DSH design tokens (colors, radii, type scale, 0.2s transitions), so light/dark follow the host. Language follows DSH by default, with explicit 中文 / English overrides. |
28
+ | **🔎 Find sessions fast**<br/>Search (press `/`), 全部 / 未拉取 / 已拉取 filters, sort by time or title, and ↑↓ / Space / Enter navigation. Rows stay quiet: one status chip, with 校验 and 拉取 on hover or focus. | **🧰 Tidy devices & queue**<br/>The rail only selects; rename, copy address, refresh, push and forget live in the stage header ⋯. Transfers sit in a bottom dock that expands into the queue; network/advanced options stay folded. |
29
+ | **🪟 Master / detail**<br/>Devices sit in a left column (session count + chevron, the whole row is the target) while the selected device's sessions stay visible on the right — no hidden "click a card to expand". The first online device is selected automatically. | **🧭 Device actions**<br/>Push here, rename, copy address, refresh and remove live in the stage header ⋯; rename happens inline in the header. |
30
+ | **🖱️ Drag, select, drop**<br/>Select one or many sessions and drag them onto any known folder **or straight onto a workspace row in DSH's own sidebar**: the header takes the whole group, a session row takes the pointer's position and shows an insertion line between rows. Every drop asks for confirmation and names the workspace it will land in. | **🚦 Confirmed transfer queue**<br/>Repeated pushes/pulls are detected up front; a dialog lets you keep the local copy, overwrite it, or drop that single item. The rest of the queue keeps running in order. |
31
+ | **⚡ One-click sync**<br/>**Sync all** / **Push all** plan the whole device in one go, skip sessions already present by content fingerprint, and report each row as done, skipped or failed. | **🌐 Cross-subnet scanning**<br/>Broadcast stays on the local segment; add CIDRs, IPs or `ip:port` under *Cross-subnet scan*. CIDRs get silent UDP discovery packets, single IPs are also probed over HTTP on 47802–47817 — a firewall that drops UDP no longer hides the peer. |
32
+ | **🚦 Queue that shows its work**<br/>Each transfer is a card with spinner, measured duration, failure reason and a progress track; pulls stream their bytes so the card and the sticky bar show **live percentages**. The queue runs several workers sized from the CPU count (capped at 5), or a fixed 1–16 concurrency you choose. |
33
+
34
+ ### Three UI promises
35
+
36
+ - **“This device” is always the first row, and one IP is never two devices.** The card shows this machine's name, `LAN address:sync port` and running version. Any peer whose address belongs to this host is grouped underneath it as “other DSH instances on this machine” (browsable and pushable, so cross-profile sync still works) instead of counting as a device — the list and its counters only ever mean **other machines**.
37
+ - **Untitled sessions carry `#first-8-chars-of-session-id`.** A pile of “Untitled” rows looks identical; the id is what tells them apart. The pull plan's “already present” flag is keyed by the same peer + session id, so renaming a session never turns a duplicate into a fresh copy.
38
+ - **A crashing UI is never blank.** Render errors hit an error boundary that prints platform, Node version, host version, ports, UA and a stack summary, with *Reload* / *Copy diagnostics*. If Windows still shows nothing, paste that block into an issue.
39
+
40
+ ## 🚀 Installation
41
+
42
+ ```sh
43
+ # From GitHub (recommended, tracks the latest commit)
44
+ dsh plugin --profile web add github:XXGxiaoyu/dsh-session-sync
45
+
46
+ # Or from npm (scoped name; the unscoped dsh-session-sync belongs to another project)
47
+ dsh plugin --profile web add @xxgxiaoyu/dsh-session-sync
48
+ ```
49
+
50
+ Requires DSH **0.2.1-rc.2** (the version bundled with the current desktop app) and Node 22+.
51
+
52
+ Then restart the DSH desktop app (host-side changes load at boot; open pages pick up the new UI on refresh). The page footer shows the build that is actually running — if it reads an older number than the latest release, the process has not been restarted yet. The install is auto-activated: the package declares `dsh.bundle`, so `dsh plugin add` adds it to `dsh.profile.bundles` on its own.
53
+
54
+ Prebuilt tarball (no build step): <https://github.com/XXGxiaoyu/dsh-session-sync/releases/latest/download/dsh-session-sync.tgz>
55
+
56
+ <details>
57
+ <summary>Manual install from a local checkout</summary>
58
+
59
+ ```jsonc
60
+ // ~/.dsh/profiles/<profile>/package.json
61
+ {
62
+ "dependencies": {
63
+ "dsh-session-sync": "link:/absolute/path/to/dsh-session-sync"
64
+ },
65
+ "dsh": {
66
+ "profile": {
67
+ "bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app", "dsh-session-sync"]
68
+ }
69
+ }
70
+ }
71
+ ```
72
+
73
+ ```sh
74
+ cd ~/.dsh/profiles/<profile> && pnpm install
75
+ ```
76
+
77
+ </details>
78
+
79
+ Confirm it loaded — the log shows:
80
+
81
+ ```text
82
+ [dsh-session-sync] ready: device <hostname>, sync port 47802, discovery port 47801
83
+ [dsh-session-sync] discovered <peer name> (192.168.1.7:47802)
84
+ ```
85
+
86
+ ## 🧭 Where it lives
87
+
88
+ A **Session Sync** row appears under **Plugins** in the left sidebar (`sidebar.panellist` + keyed `main` panel, id `session-sync`). The same component is also registered as **Settings → Session Sync** (`settings.section`).
89
+
90
+ - **This machine** — the name other devices see; changing it broadcasts immediately.
91
+ - **Devices** — online/offline, last heartbeat, session count, platform and version.
92
+ - **Rename / Forget** — a per-device nickname for this machine only. Offline devices keep their row (and their nickname), greyed out.
93
+ - **Sessions / Pull / Push** — list a peer's sessions, copy one here, or send one over.
94
+ - **Verify** — re-fetch the peer's payload, recompute SHA-256 and compare it with the local copy (`sha256 2b12f7854d80… verified`).
95
+ - **Integrity, not identity.** A digest proves the bytes match; it does not prove who sent them — anyone can recompute it. So verification stays automatic and fail-closed, and there is no per-transfer “confirm the hash” prompt (that only trains people to click through). When you genuinely need authenticity, copy the full digest and compare it out-of-band (phone, terminal); the copy action sits next to the verified row.
96
+ - **Sync settings** — UI language, auto-pull, and manual peer addresses for networks where broadcast is blocked (`ip` or `ip:udpPort`, one per line).
97
+
98
+ ## 🌏 UI language
99
+
100
+ | Option | Behavior |
101
+ | --- | --- |
102
+ | Follow DSH (default) | Use DSH's current language. DSH already resolves the system language, so non-Chinese systems get English, Chinese systems get Chinese |
103
+ | 中文 | Always Chinese |
104
+ | English | Always English |
105
+
106
+ ```text
107
+ settings.locale = 'auto' | 'zh' | 'en' // persisted in <home>/session-sync/state.json
108
+ ```
109
+
110
+ The plugin reads `ctx.locale.getSnapshot().active` (`zh*` → Chinese, everything else → English) and only falls back to `navigator.languages` when no locale service exists. A language change is written to disk **before** the UI switches, so it never reverts on the next open. Sidebar/settings labels are resolved by the host, so the plugin re-registers its slots when the language changes — the page and the sidebar row never disagree.
111
+
112
+
113
+ ## 🎨 Look and motion
114
+
115
+ The UI uses DSH's own CSS variables rather than copied hex values: `--dsw-alias-*` for colors, `--dsw-radius-*`, the `--dsw-font-*` scale, and `var(--ds-transition-duration) var(--ds-ease-in-out)` for every transition. Layout follows the built-in settings pages — title + muted note on the left, control on the right, `.5px` dividers, 32px inputs/selects, 36×22 pill switches — so it stays consistent in both themes and in narrow windows.
116
+
117
+ ## ⚙️ Configuration (loader row `config`)
118
+
119
+ | Key | Default | Meaning |
120
+ | --- | --- | --- |
121
+ | `name` | hostname | Initial display name (the GUI value wins afterwards) |
122
+ | `lanPort` | `47802` | Sync HTTP port; tries the next 16 ports when busy |
123
+ | `discoveryPort` | `47801` | UDP discovery port |
124
+ | `token` | none | If set, LAN HTTP calls must send `x-dsh-session-sync-token` |
125
+ | `home` | `$DSH_HOME` or `~/.dsh` | Root for state and sessions |
126
+ | `autoPullIntervalMs` | `20000` | Auto-pull interval (lowered in tests) |
127
+ | `verbose` | `true` | Turn plugin logging off |
128
+ | `subnets` | `[]` | Extra CIDRs/IPs/`ip:port` for devices that UDP broadcast cannot reach (e.g. `192.168.5.0/24`, `10.0.0.7`, `10.0.0.7:47802`). CIDRs use UDP discovery packets; bare IPs are also probed over HTTP on 47802–47817 |
129
+
130
+ ## 🔌 LAN protocol (`/v1/*`)
131
+
132
+ Binds `0.0.0.0`; every route except `/v1/health` requires `x-dsh-session-sync: 1`.
133
+
134
+ | Method | Path | Purpose |
135
+ | --- | --- | --- |
136
+ | GET | `/v1/health` | `{ok:true}` liveness |
137
+ | GET | `/v1/info` | Device id, name, session count, version |
138
+ | GET | `/v1/sessions?limit=N` | Session summaries |
139
+ | GET | `/v1/sessions/:id` | Export `{format:'dsh-session-sync/1', session, events, sha256}` |
140
+ | POST | `/v1/sessions` | Import the same payload (verified when `sha256` is present), returns the new id; `?force=1` skips the receiver-side fingerprint check |
141
+
142
+ Discovery is a UDP broadcast: every 4s a `dsh-session-sync/hello` datagram (`{id, name, port, platform, version, sessions}`) goes to each IPv4 interface's broadcast address, `127.0.0.1`, and any configured seeds; unknown ids are answered by unicast right away.
143
+
144
+ The GUI side (`/session-sync/api`) runs on DSH's own loopback web server and is fenced by the host connection policy; `/v1/*` is the only surface exposed to the LAN.
145
+
146
+ ## 🧵 Design notes
147
+
148
+ - **Nicknames are keyed by device id, not IP.** `deviceId` is a uuid generated on first start; `peers[<deviceId>].customName` is yours alone.
149
+ - **Imports use the official persistence service** — `ctx.sessionPersistence.create() → append() → flush()`, with a v4 header that keeps only the allowed keys (`version/id/createdAt/cwd/isSeeded/delegationDepth/parentSession/agentPreset`). One extra `type` is rejected with `format v4 header has unexpected field type`.
150
+ - **Imports keep the source `createdAt`, re-index ids and seq.** The id must be unique per machine; seq starts at 0 and stays contiguous; `createdAt` is preserved so content fingerprints agree.
151
+ - **Cross-machine `cwd`.** The source cwd is kept when it exists here, otherwise it falls back to `process.cwd()` — a session pointing at a path that does not exist would vanish from the workspace list.
152
+ - **SHA-256 is byte-exact.** Exports and imports serialize the same object, so key order is identical and digests are comparable. Mismatch aborts before persistence.
153
+ - **Your own machine is not a device.** The device list only ever holds other machines: peers on this host's address are dropped on arrival (and pruned from history on boot), and several peers on one IP collapse into the freshest row. Rename this machine in the 本机 section; the counter only means *other machines*.
154
+ - **Fingerprints, not ids, dedupe auto-pull.** `state.signatures[sha1(createdAt, event shape, first preview)]` is recorded on export and import; auto-pull skips anything already known. Without it, two auto-pulling machines would keep re-importing the same conversation under new ids.
155
+ - **Everything local stays local.** Nicknames, import records and settings never leave `<home>/session-sync/state.json`.
156
+
157
+ ## 🩺 Troubleshooting
158
+
159
+ A build before 0.5.5 imported sessions with `isSeeded: false` but kept the peer's inherited end-seed marker, so DSH refused to open them (`released v2 unseeded Session contains an inherited end-seed marker`). Open *Network & advanced → Repair old sessions* to fix everything under `<DSH_HOME>/sessions`, or run it offline:
160
+
161
+ ```bash
162
+ node scripts/repair-sessions.mjs # rewrite affected logs, keep *.bak
163
+ node scripts/repair-sessions.mjs --dry-run # only report what would change
164
+ ```
165
+
166
+ ## 🧪 Development
167
+
168
+ ```bash
169
+ node --test --test-force-exit --test-timeout=120000 'test/*.test.mjs'
170
+ ```
171
+
172
+ 56 cases: client bundle contract, dictionary parity, footer, stale-host hint and version pinning (13), jsdom mount and interactions (5), two-instance end-to-end — discovery, rename persistence, restart, pull/push, auto-pull dedupe, receiver dedupe, SHA-256 tamper rejection and HTTP-only discovery (8), and the probe fallback contract (7).
173
+
174
+ Run a headless instance without the desktop shell:
175
+
176
+ ```bash
177
+ DSH_HOME=/tmp/dsh-ss-home node <dsh>/lib/bin.js --profile ssync --no-open --host 127.0.0.1 --port 4399
178
+ ```
179
+
180
+ `--profile` must come **before** launcher flags such as `--no-open`; `web --profile x` fails with `select a profile only once`.
181
+
182
+ ## 📄 License
183
+
184
+ [MIT](./LICENSE)
package/README_ZH.md ADDED
@@ -0,0 +1,185 @@
1
+ # dsh-session-sync
2
+
3
+
4
+ **DSH 的局域网会话同步插件。** 装了它的 DSH 会在局域网里互相发现,列出对方设备,可以通过普通 HTTP 浏览、拉取、推送会话。**你给某台设备起的外号只存在本机**——对方离线、重启、换 IP 再上线,你看到的还是自己给它起的名字。
5
+
6
+ <div align="center">
7
+
8
+ [![CI](https://github.com/XXGxiaoyu/dsh-session-sync/actions/workflows/ci.yml/badge.svg)](https://github.com/XXGxiaoyu/dsh-session-sync/actions/workflows/ci.yml)
9
+ [![GitHub stars](https://img.shields.io/github/stars/XXGxiaoyu/dsh-session-sync)](https://github.com/XXGxiaoyu/dsh-session-sync/stargazers)
10
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
11
+ [![DSH](https://img.shields.io/badge/DSH-0.2.1--rc.2-4d6bfe)](https://www.npmjs.com/package/@deepseek-ai/dsh)
12
+
13
+ 🌏 **简体中文** · [English](./README.md)
14
+
15
+ </div>
16
+
17
+ > **0.8.0 界面重构**:会话台、整行选择、选择条、传输坞与设置页全部重画。完整稿(19 个场景,可切深浅主题 / 中英文)见 [`docs/redesign/index.html`](https://github.com/XXGxiaoyu/dsh-session-sync/tree/main/docs/redesign),实现映射与验收见 [`DESIGN.md`](https://github.com/XXGxiaoyu/dsh-session-sync/blob/main/docs/redesign/DESIGN.md)。整包文件迁移的分片 / 断点续传设计见 [`session-bundle-transfer.md`](https://github.com/XXGxiaoyu/dsh-session-sync/blob/main/docs/architecture/session-bundle-transfer.md)(下一阶段)。
18
+
19
+
20
+
21
+ ## ✨ 功能
22
+
23
+ | | |
24
+ |---|---|
25
+ | **📡 零配置发现**<br/>每 4 秒向每个 IPv4 网段的广播地址发一次 UDP 心跳;收到陌生设备的广播会立刻单播回礼,所以新上线的机器不用等下一轮。 | **🏷️ 仅存本机的外号**<br/>写在 `<home>/session-sync/state.json`,按**稳定设备 id**(uuid)索引,不看 IP、不看主机名。对端改自己的显示名,不会覆盖你起的外号。 |
26
+ | **🗂️ 浏览 · 拉取 · 推送**<br/>看到对方每个会话的标题、cwd 和时间;一键把某个会话拉到本机,或把本机的推过去。拉回来的会话在列表里有标记。 | **🔐 SHA-256 校验**<br/>导出时附带 `sha256 = SHA-256(JSON.stringify({format, session, events}))`;导入**先比摘要再写库**,不一致直接拒收(HTTP 409 `checksum-mismatch`)。每个会话行都有「校验」。 |
27
+ | **🔁 可选自动拉取**<br/>每 20 秒扫一次在线设备,把还没拉过的会话同步过来。去重靠内容指纹(不是 id),两台都开自动拉取也不会把同一段对话反复互导。 | **🎨 原生观感与 i18n**<br/>全部用 DSH 的设计变量(颜色、圆角、字号阶梯、0.2s 动效),深浅主题跟随宿主;语言默认跟随 DSH,也可固定中文 / English。 |
28
+ | **🖱️ 选择与拖拽**<br/>**点整行(标题、元信息、状态徽章)即可选中**,勾选框只显示状态;Shift 连选、⌘/Ctrl 追加、空格/回车切换。会话可多选,拖到设备卡片即推送;拖到侧栏工作区的**标题行、或其展开后的任意一行**(含空白)都能命中;拖到会话行时按指针位置决定插入点,行间会出现一条插入线,松手先给 90ms 成功态再弹确认框,并写明这条对话会导入到哪个工作区。 | **🚦 可确认的传输队列**<br/>推送/拉取前先查重:已存在时弹框让你选「保留本机」「覆盖本机」或单独取消这一条,其余条目照常排队执行。 |
29
+ | **🔎 找会话更快**<br/>搜索(按 `/` 聚焦)、全部 / 未拉取 / 已拉取筛选、按时间或标题排序,↑↓ / 空格 / 回车操作。每行只留状态徽章,校验与拉取在悬停或聚焦时出现。 | **🧰 设备与队列归位**<br/>设备栏只负责选择;命名、复制地址、刷新、推送、移除收进右栏头部的 ⋯。传输进度收到底部传输坞,向上展开队列;网络与高级选项默认折叠。 |
30
+ | **📁 自动同步目录**<br/>`syncDir` 决定自动拉取落到哪:插件自己的「未分组」目录、某个工作区,或任意绝对路径(保存时创建)。 | **🪟 主从两栏**<br/>左侧是设备列表(每行显示会话数与箭头,整行可点),右侧常驻所选设备的会话列表——不需要先发现「点设备才能展开」。默认自动选中第一台在线设备。 | **🧭 设备操作**<br/>右栏头部的 ⋯ 放推送、重命名、复制地址、刷新、移除;重命名在头部就地改。 |
31
+ | **🪄 拖到侧栏**<br/>拖动时高亮 DSH 自己的工作区行(读 `data-row-key`),不改侧栏 DOM、不抢插槽;松手仍走二次确认。 |
32
+ | **⚡ 一键同步**<br/>「一键同步」/「一键推送」一次规划整台设备,按内容指纹跳过已有会话,逐条显示完成、跳过或失败。 | **🌐 跨网段扫描**<br/>广播只能留在本网段;在「跨网段扫描」里填 CIDR、IP 或 `IP:端口`。网段走静默 UDP 发现包,单个 IP 额外用 HTTP 探测 47802–47817 端口——防火墙丢弃 UDP 也藏不住对端。 |
33
+ | **🚦 看得见过程的队列**<br/>每条传输是一张卡:旋转图标、实测耗时、失败原因与进度轨道;拉取走流式字节计数,卡片和底部条显示**实时百分比**。队列按 CPU 核数自动开多个 worker(上限 5),也可在设置里指定 1–16 个并发。 |
34
+
35
+ ### 界面上的三条约定
36
+
37
+ - **「本机」永远在第一行,同 IP 不算第二台。** 设备区顶部固定一张本机卡片:名字、局域网地址:同步端口、运行版本。地址属于本机的 peer 是同一台机器(别的 profile、别的端口),**不会出现在设备列表里**;列表数量永远只表示**其他机器**。
38
+ - **未命名会话显示 `#会话 id 前 8 位`。** 一堆“未命名会话”长得一样,靠 id 才分得清;拉取计划里的「已存在」也是按同一台设备的同一个会话 id 判定的,改标题不会把重复判成新会话。
39
+ - **界面出错不再是一片空白。** 渲染异常会被错误边界接住,显示平台、Node 版本、宿主版本、端口、UA 和堆栈摘要,并带「重新加载 / 复制诊断」。Windows 上如果仍然空白,把这块诊断内容发到 issue 即可。
40
+
41
+ ## 🚀 安装
42
+
43
+ ```sh
44
+ # 从 GitHub 安装(推荐,包含最新提交)
45
+ dsh plugin --profile web add github:XXGxiaoyu/dsh-session-sync
46
+
47
+ # 或从 npm(scope 包名;npm 上的 dsh-session-sync 属于另一个项目)
48
+ dsh plugin --profile web add @xxgxiaoyu/dsh-session-sync
49
+ ```
50
+
51
+ 需要 DSH **0.2.1-rc.2**(当前桌面版内置的版本)与 Node 22+。
52
+
53
+ 然后重启 DSH 桌面应用(宿主半边的改动在启动时加载,页面刷新即可拿到新 UI)。装完会自动启用:包里声明了 `dsh.bundle`,`dsh plugin add` 会自己把它加进 `dsh.profile.bundles`。页脚显示的是**真正在运行**的那份构建;如果比 Latest 版本旧,说明进程还没重启。
54
+
55
+ 预构建 tarball(免构建):<https://github.com/XXGxiaoyu/dsh-session-sync/releases/latest/download/dsh-session-sync.tgz>
56
+
57
+ <details>
58
+ <summary>从本地目录手动安装</summary>
59
+
60
+ ```jsonc
61
+ // ~/.dsh/profiles/<profile>/package.json
62
+ {
63
+ "dependencies": {
64
+ "dsh-session-sync": "link:/absolute/path/to/dsh-session-sync"
65
+ },
66
+ "dsh": {
67
+ "profile": {
68
+ "bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app", "dsh-session-sync"]
69
+ }
70
+ }
71
+ }
72
+ ```
73
+
74
+ ```sh
75
+ cd ~/.dsh/profiles/<profile> && pnpm install
76
+ ```
77
+
78
+ </details>
79
+
80
+ 加载成功的话,日志里会出现:
81
+
82
+ ```text
83
+ [dsh-session-sync] 已就绪:设备 <hostname>,同步端口 47802,发现端口 47801
84
+ [dsh-session-sync] 发现设备 <另一台的名字> (192.168.1.7:47802)
85
+ ```
86
+
87
+ ## 🧭 在哪用
88
+
89
+ 侧边栏左侧「插件」下面多了一行**会话同步**(`sidebar.panellist` + keyed `main` 面板,id 都是 `session-sync`);同一个组件也注册成 **设置 → 会话同步**(`settings.section`)。
90
+
91
+ - **本机**——别的设备看到的你的名字,改完立刻广播。
92
+ - **设备**——在线/离线、最近心跳、会话数、平台与版本。
93
+ - **命名 / 移除**——只影响本机看到的名字;离线设备保留这一行(和你的外号),只是变灰。
94
+ - **查看会话 / 拉取 / 推送**——列对方会话、拉一个到本机、把本机的送出去。
95
+ - **校验**——重新拉一遍对方的整包,重算 SHA-256 与本机副本比对(`sha256 2b12f7854d80… 校验通过`)。
96
+ - **摘要证明完整性,不证明身份。** 任何拿到内容的人都能重算摘要,所以校验保持自动、失败即拒收,不做“每次传输都要手动确认摘要”的打扰(那只会让人无脑点通过)。真需要带外核对时,点行内的「复制完整摘要」,用另一条通道(手机、终端)对比即可。
97
+ - **同步设置**——界面语言、自动拉取开关、手动设备地址(广播被挡的网络用,填 `ip` 或 `ip:udpPort`,一行一个)。
98
+
99
+ ## 🌏 界面语言
100
+
101
+ | 选项 | 行为 |
102
+ | --- | --- |
103
+ | 跟随 DSH(默认) | 用 DSH 当前语言。DSH 本身就会按系统语言回退,所以非中文系统一律 English,中文系统中文 |
104
+ | 中文 | 固定中文 |
105
+ | English | 固定 English |
106
+
107
+ ```text
108
+ settings.locale = 'auto' | 'zh' | 'en' // 持久化在 <home>/session-sync/state.json
109
+ ```
110
+
111
+ 插件读 `ctx.locale.getSnapshot().active`(`zh*` → 中文,其余 → English);只有拿不到 locale 服务时才退回 `navigator.languages`。切换语言先落盘再换 UI,不会出现「下次打开又变回去」。侧栏行和设置项的文字由宿主解析,所以语言变化时插件会重新登记槽位——页面和侧栏永远不会一个中文一个英文。
112
+
113
+
114
+ ## 🎨 界面风格
115
+
116
+ 界面不写死任何色值,直接用 DSH 的变量:颜色 `--dsw-alias-*`,圆角 `--dsw-radius-*`,字号用 `--dsw-font-*` 阶梯,动效统一 `var(--ds-transition-duration) var(--ds-ease-in-out)`。排版照搬内置设置页——左边「标题 + 灰色说明」、右边控件、行间 `.5px` 分隔线、32 高输入/下拉、36×22 的 pill 开关,所以两套主题、窄窗口下都与宿主一致。
117
+
118
+ ## ⚙️ 配置(loader row 的 `config`)
119
+
120
+ | 键 | 默认 | 说明 |
121
+ | --- | --- | --- |
122
+ | `name` | 主机名 | 首次启动的本机显示名(之后以 GUI 为准) |
123
+ | `lanPort` | `47802` | 同步 HTTP 端口,占用时往后试 16 个 |
124
+ | `discoveryPort` | `47801` | UDP 发现端口 |
125
+ | `token` | 无 | 设置后局域网请求需带 `x-dsh-session-sync-token` |
126
+ | `home` | `$DSH_HOME` 或 `~/.dsh` | 状态与会话根目录 |
127
+ | `autoPullIntervalMs` | `20000` | 自动拉取间隔(测试里会调小) |
128
+ | `verbose` | `true` | 关掉插件日志 |
129
+ | `subnets` | `[]` | 广播到不了时额外扫描的 CIDR/IP/`IP:端口`(如 `192.168.5.0/24`、`10.0.0.7`、`10.0.0.7:47802`)。网段用 UDP 发现包;单个 IP 还会用 HTTP 探测 47802–47817 |
130
+
131
+ ## 🔌 局域网协议(`/v1/*`)
132
+
133
+ 绑定 `0.0.0.0`;除 `/v1/health` 外都要 `x-dsh-session-sync: 1`。
134
+
135
+ | 方法 | 路径 | 作用 |
136
+ | --- | --- | --- |
137
+ | GET | `/v1/health` | `{ok:true}` 探活 |
138
+ | GET | `/v1/info` | 设备 id、名字、会话数、版本 |
139
+ | GET | `/v1/sessions?limit=N` | 会话摘要列表 |
140
+ | GET | `/v1/sessions/:id` | 导出 `{format:'dsh-session-sync/1', session, events, sha256}` |
141
+ | POST | `/v1/sessions` | 导入同一格式(带 `sha256` 会先校验),返回新 id |
142
+
143
+ 发现走 UDP 广播:每 4 秒向各 IPv4 网段广播地址、`127.0.0.1` 和配置的 seeds 发 `dsh-session-sync/hello` 报文(`{id, name, port, platform, version, sessions}`),陌生 id 立刻单播回礼,新设备不用等下一轮。
144
+
145
+ GUI 的 `/session-sync/api` 跑在 DSH 自己的回环 web 服务上,受宿主连接策略保护;真正暴露到局域网的只有 `/v1/*`。
146
+
147
+ ## 🧵 设计决定
148
+
149
+ - **命名按设备 id,不按 IP。** `deviceId` 是首次启动生成的 uuid,`peers[<deviceId>].customName` 只属于本机。
150
+ - **导入走官方持久化服务**:`ctx.sessionPersistence.create() → append() → flush()`,v4 header 只留允许字段(`version/id/createdAt/cwd/isSeeded/delegationDepth/parentSession/agentPreset`)。多一个 `type` 就会被 `format v4 header has unexpected field type` 拒掉。
151
+ - **导入保留源 `createdAt`,重编 id 和 seq。** id 必须全局唯一,seq 从 0 连续;`createdAt` 保留是为了让内容指纹成立。
152
+ - **跨机 cwd**:源 cwd 在本机存在就保留,不存在落到 `process.cwd()`,否则会话会在工作区列表里「消失」。
153
+ - **SHA-256 认字节**:两端序列化同一份对象,键序一致所以摘要可比;不一致在落库前就中止。
154
+ - **去重靠指纹不靠 id**:`state.signatures[sha1(createdAt, 事件形状, 首条预览)]` 在导出/导入时登记,自动拉取遇到就跳过。没有它,两台都开自动拉取的机器会把同一段对话换 id 反复互导。
155
+ - **本机可见**:外号、导入记录、设置全在 `<home>/session-sync/state.json`,不上传、不同步。
156
+
157
+ ## 🩺 常见问题
158
+
159
+ 0.5.5 之前的版本导入会话时写的是 `isSeeded: false`,却保留了对方的继承标记,DSH 会拒绝打开(`released v2 unseeded Session contains an inherited end-seed marker`)。在「网络与高级 → 修复历史会话」里点一下即可修复 `<DSH_HOME>/sessions` 下的全部记录,也可以离线跑:
160
+
161
+ ```bash
162
+ node scripts/repair-sessions.mjs # 就地修复,保留 *.bak
163
+ node scripts/repair-sessions.mjs --dry-run # 只报告,不写盘
164
+ ```
165
+
166
+ ## 🧪 开发
167
+
168
+ ```bash
169
+ node --test --test-force-exit --test-timeout=120000 'test/*.test.mjs'
170
+ ```
171
+
172
+ 35 个用例:客户端 bundle 契约、词典一致性、页脚与版本一致性(13)、jsdom 挂载与交互(5)、双实例端到端——发现/改名持久化/重启保留/拉取推送/自动拉取不反弹/接收端去重/SHA-256 篡改拒收/仅 HTTP 可发现的设备(8)、HTTP 兜底探测契约(7)。
173
+
174
+ 单独起一个不带桌面壳的实例:
175
+
176
+ ```bash
177
+ DSH_HOME=/tmp/dsh-ss-home node <dsh>/lib/bin.js --profile ssync --no-open --host 127.0.0.1 --port 4399
178
+ ```
179
+
180
+ `--profile` 要放在 `--no-open` 这类 launcher flag **前面**;`web --profile x` 会报 `select a profile only once`。
181
+
182
+ ## 📄 许可证
183
+
184
+ [MIT](./LICENSE)
185
+
@@ -0,0 +1,15 @@
1
+ # dsh-session-sync bundle patch
2
+ #
3
+ # 只有一条挂载行:本包装进 profile(`dsh.profile.bundles` 或 profile 的
4
+ # cordis.patch.yml)后,loader 会按这里的 id/name 挂载插件实例。
5
+ # 想改配置就再写一条 override,例如:
6
+ #
7
+ # - override:
8
+ # - id: session-sync
9
+ # config:
10
+ # lanPort: 47802
11
+ # discoveryPort: 47801
12
+ # verbose: false
13
+ - insert:
14
+ - id: session-sync
15
+ name: @xxgxiaoyu/dsh-session-sync
package/icon.svg ADDED
@@ -0,0 +1,9 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64" fill="none">
2
+ <rect width="64" height="64" rx="16" fill="#4D6BFE"/>
3
+ <g stroke="#FFFFFF" stroke-width="3.6" stroke-linecap="round" stroke-linejoin="round" fill="none">
4
+ <path d="M18 26a14 14 0 0 1 22.5-6.6"/>
5
+ <path d="M42.5 13.5v6.2h-6.2"/>
6
+ <path d="M46 38a14 14 0 0 1-22.5 6.6"/>
7
+ <path d="M21.5 50.5v-6.2h6.2"/>
8
+ </g>
9
+ </svg>