dsh-crossterm 0.9.7 → 0.9.9

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 CHANGED
@@ -1,3 +1,21 @@
1
+ ## 0.9.9 — 2026-10-20
2
+
3
+ ### 修复:幽灵链成员退不掉(YOCE_C 报告)
4
+
5
+ - 现象:`term_unlink code="X" all=true` 回「本终端没有接在特征码「X」上」,但链台账里确实还留着本终端成员;只能先 `term_link` 补回 links 再 `term_unlink` 绕开。
6
+ - 根因:`unlinkChain()` 只遍历内存 `this.links`(`lib/index.js`),台账里的成员它看不见;`leaveChain()` 只能按 `nodeId::sessionId` 精确删。
7
+ - 修法:
8
+ - `lib/bus.js` 新增 `leaveChainNode(code, nodeId)`:按 nodeId 摘掉该节点在链上的全部成员,空了删链文件。
9
+ - `lib/index.js` 的 `unlinkChain()`:`all=true` 且 links 无记录时改走 `leaveChainNode`,成功则返回 `{ ok:true, ghosts, removed }`。
10
+ - `chainSweep` 加自愈:`linksLoaded` 之后,本节点在台账里有、links 里没有的成员会被摘掉并记日志。
11
+ - 组件版本:跨会话 `0.5.0` → `0.5.1`。
12
+
13
+ ## 0.9.8 — 2026-10-20
14
+
15
+ - 自动重启组件维护权交回:原负责人会话「自动重启」(`a3e9f80e`)按机主指示即日起停止改动 `components/restart/**`;`MAINTAINERS.md` 的 `modules.restart` 改为插件负责人 `b6aa099b` 暂管,新负责人等后续交接时再任命。
16
+ - 记入其交回时的两条观察(待下次动该组件前定口径):① market `runningAgents` 比心跳"粘",现取并集偏保守,可选「心跳权威、market 兜底」;② 检查点取消后 `rearm-watch.mjs` 用固定 `--delay 15`,与「空闲 5 秒后再重排」是否统一。
17
+ - 只改 `MAINTAINERS.md` / `CHANGELOG.md` / `README.md`;代码与组件版本不动(入口 0.9.3 / 跨会话 0.5.0 / 跨工作区 0.1.0 / 跨设备 0.4.0 / 自动重启 0.3.29),插件总版本 0.9.7 → 0.9.8。
18
+
1
19
  ## 0.9.7 — 2026-10-20
2
20
 
3
21
  - 组件版本按用户要求重新定起点:跨会话 `0.9.2 → 0.5.0`、跨设备 `0.9.2 → 0.4.0`、跨工作区 `0.9.5 → 0.1.0`;入口(ui)仍 `0.9.3`、自动重启仍 `0.3.29`。
package/MAINTAINERS.md CHANGED
@@ -56,13 +56,12 @@
56
56
  "scope": "components/device/**、lib/cloud/**(中转站、备份、layout)"
57
57
  },
58
58
  "restart": {
59
- "session": "session-a3e9f80e-6f28-425c-9807-0b9d8b872b5e",
60
- "short": "a3e9f80e",
61
- "name": "自动重启",
62
- "role": "自动重启模块负责人(原 dsh-restart 插件作者会话)",
59
+ "session": "session-b6aa099b-cfa6-42b7-961c-10f720236219",
60
+ "short": "b6aa099b",
61
+ "role": "自动重启组件负责人(暂由插件负责人直管,等后续交接时任命新的)",
63
62
  "since": "2026-10-20",
64
63
  "scope": "components/restart/ 下的 core.js、runtime.js、scripts/**",
65
- "rule": "不单独发版、不自己发版;改完向主维护会话(b6aa099b)汇报,由主维护并入 dsh-crossterm 发版;只改 components/restart/** 时把它的组件版本号(现 0.3.28)加一"
64
+ "rule": "原负责人 a3e9f80e 已于 2026-10-20 19:33 交回维护权(机主指示:转给插件负责人,等负责人任命新的);暂由 b6aa099b 直管,只改 components/restart/** 时把组件版本号(现 0.3.29)加一,仍由插件负责人统一发版"
66
65
  }
67
66
  },
68
67
  "previous": [
@@ -88,19 +87,29 @@
88
87
  | 组件(插件页顺序) | 当前版本 | 负责人 | 范围 | 规矩 |
89
88
  |---|---|---|---|---|
90
89
  | ① 跨端互通(自更新) | v0.9.3 | `b6aa099b` | `components/ui/**`、`client/**`、`locale/**`、`lib/web.js` | 组件版本号在 `components/ui/manifest.json` |
91
- | ② 跨会话 | v0.9.2 | `b6aa099b` | `components/session/**`、`lib/bus.js` 的链/邮件 | 同上 |
92
- | ③ 跨工作区 | v0.9.5 | `b6aa099b` | `components/terminal/**`、presence / `term_send` | 同上 |
93
- | ④ 跨设备 | v0.9.2 | `b6aa099b` | `components/device/**`、`lib/cloud/**` | 同上 |
94
- | ⑤ 自动重启 | v0.3.29 | `a3e9f80e`(会话名「自动重启」) | `components/restart/**`(`core.js` / `runtime.js` / `scripts/**`) | 不单独发版、不自己发版;改完汇报 `b6aa099b`,由插件负责人并入 `dsh-crossterm` 发版;只动它就把它的版本号加一 |
90
+ | ② 跨会话 | v0.5.0 | `b6aa099b` | `components/session/**`、`lib/bus.js` 的链/邮件 | 同上 |
91
+ | ③ 跨工作区 | v0.1.0 | `b6aa099b` | `components/terminal/**`、presence / `term_send` | 同上 |
92
+ | ④ 跨设备 | v0.4.0 | `b6aa099b` | `components/device/**`、`lib/cloud/**` | 同上 |
93
+ | ⑤ 自动重启 | v0.3.29 | `b6aa099b`(暂管;原 `a3e9f80e` 已于 2026-10-20 19:33 交回) | `components/restart/**`(`core.js` / `runtime.js` / `scripts/**`) | 不单独发版;新负责人等后续交接时任命;只动它就把它的版本号加一 |
95
94
 
96
95
  **组件版本号怎么升**:只在该组件自己的文件改动时升它(在 `components/<part>/manifest.json` 的 `version`),
97
96
  跟着插件一起发版;插件总版本仍按发版技能的三分位规则。插件页与 `GET /crossterm/api/about` 的 `components[].version` 读的就是这里。
97
+ ## 待定事项(自动重启组件交回时留的)
98
+
99
+ 原负责人 `a3e9f80e` 交回维护权时留下两条,下次动 `components/restart/**` 前先定口径:
100
+
101
+ 1. market `/dsh-market/status` 的 `runningAgents` 有时比心跳"粘"(会话已不在回合里,市场还列着)——现在三路取并集偏保守,可选「心跳权威、market 只兜底」。
102
+ 2. 检查点取消后 `rearm-watch.mjs` 用固定 `--delay 15`,与「空闲 5 秒后再重排」的口径是否统一、是否沿用同一份唤醒清单。
103
+
104
+ 现场验证(10 s 检查点取消 → 转后台 → 空闲后重排)仍挂起,等机主安排;不得自行触发真重启。
105
+
98
106
  ## 任期表
99
107
 
100
108
  | 起(本地时间) | 止(本地时间) | 会话 | 终端 / nodeId | 角色 | 交接依据 |
101
109
  | --- | --- | --- | --- | --- | --- |
102
110
  | 2026-10-17 20:30 | 2026-10-18 01:09 | `session-6ef6da30…`(初版负责人) | TS-01 / `ts-01-a52535` | 负责人 | 会话故障,转录停止增长、投递不消费;用户改派 |
103
111
  | 2026-10-18 00:59 | 至今 | `session-b6aa099b…`(互通插件主维护) | TS-01 / `ts-01-a52535` | 负责人 | 用户首条指令:「你现在是互通插件的新负责人」 |
112
+ | 2026-10-20 19:33 | 2026-10-20 19:33 | `session-a3e9f80e…`(自动重启组件) | TS-01 / `ts-01-a52535` | 组件负责人 | 交回:机主指示「将组件权限转给插件负责人,等负责人任命新的」 |
104
113
  | 2026-10-17 21:44 | 至今(按派活) | `session-ccf34a5a…`(同端多会话2) | TS-01 / `ts-01-a52535` | 助手 | 用户:「指定同端多会话2为你的助手,部分修改可让他进行」 |
105
114
 
106
115
  时间口径:接手取该会话转录 `session.v4.jsonl.zstd` 的**创建时间**,交卸取其**最后写入时间**(本机 `C:\Users\Administrator\.dsh\sessions\--F-dsh-001X--\<会话>\`)。
package/README.md CHANGED
@@ -19,7 +19,7 @@ A 终端看不到 B 终端里有哪个会话、在干什么,也没法把一条
19
19
  | 顺序 | 组件名称 | 版本 | 作用 | 工具 |
20
20
  |---|---|---|---|---|
21
21
  | ① | 跨端互通(入口/自更新) | v0.9.3 | 自更新开关:打开后定期检查 npm 新版本并自动安装(默认关;装完要重启 DSH 生效)。关掉它只失去卡片与自动更新,下面四个组件照常工作。裸包名行,承载插件标题/简介与配置卡片,不挂工具 | — |
22
- | ② | 跨会话 | v0.5.0 | 同一终端里的多个会话之间互通:特征码链、内联邮件、收件箱 | `term_link` `term_unlink` `term_broadcast` `term_inbox` `term_config` |
22
+ | ② | 跨会话 | v0.5.1 | 同一终端里的多个会话之间互通:特征码链、内联邮件、收件箱 | `term_link` `term_unlink` `term_broadcast` `term_inbox` `term_config` |
23
23
  | ③ | 跨工作区 | v0.1.0 | 在本机不同工作区、不同会话之间互发邮件与文件(跨终端本轮不做,暂无条件) | `term_whoami` `term_list` `term_send` `term_send_files` |
24
24
  | ④ | 跨设备 | v0.4.0 | 经云端中转站,在多台机器的 DSH 之间互发邮件与文件 | `term_cloud` |
25
25
  | ⑤ | 自动重启 | v0.3.29 | 改完插件/配置后让 DSH 自己重启一次,并自动拉起离线会话(原 `dsh-restart` 插件,0.9.0 起并入) | `dsh_restart` |
@@ -30,22 +30,25 @@ A 终端看不到 B 终端里有哪个会话、在干什么,也没法把一条
30
30
  >
31
31
  > 组件版本号怎么升:**只在该组件自己的文件改动时升它**,跟着插件一起发版(例:只改自动重启 → `components/restart` 的 `0.3.28` → `0.3.29`,其余四个不动)。插件总版本仍按发版技能的三分位规则。
32
32
 
33
- **本版更新(0.9.7)**
33
+ **本版更新(0.9.9)**
34
34
 
35
- - **组件版本按你的要求重新给了起点**:跨会话 0.9.2 → **0.5.0**、跨设备 0.9.2 → **0.4.0**、跨工作区 0.9.5 → **0.1.0**;入口仍 0.9.3、自动重启仍 0.3.29。
36
- - **数字变小是故意的**:每个组件按自己从零开始的时间线走,和插件总版本(0.9.6 → 0.9.7)不再绑定;之后只有哪个组件改了才加哪个。
37
- - **只动了三个 `components/<part>/manifest.json` 的 `version` 与文档**:代码、`cordis.patch.yml`、已装机器都不用改,重启后插件页组件表即显示新号。
35
+ - **修「幽灵链成员」退不掉**(YOCE_C 2026-10-20 报的坑):`term_unlink ... all=true` 以前只遍历内存里的 links,遇到「链台账里还留着本终端成员、但 links 已没有这条记录」就永远退不掉(一直回「本终端没有接在特征码上」)。
36
+ - 现在 `all=true` 在 links 为空时也会按 **nodeId** 把本终端在这串码上的成员从台账里摘掉,返回值带 `ghosts`;成员清空时链文件自动删除。
37
+ - `chainSweep` 每 6 拍自愈一次:本节点在台账里有、links 里却没有的成员会被摘掉,并记一行日志「chain sweep: 摘掉幽灵成员」。
38
+ - 显式传 `sessionId` 的行为不变——只退那一个,不动其它会话(笔误不会误删)。
39
+ - 组件版本:跨会话 0.5.0 → **0.5.1**,其余组件不动。
38
40
 
39
- **上一版(0.9.6)**
41
+ **上一版(0.9.8)**
40
42
 
41
- - **去掉「跨账号」组件**:插件页从 6 个组件变成 5 个,`components/account/` 与那行 insert 一起删掉,源码归档在 `archive/components-removed/account-0.9.2/`。
43
+ - **自动重启组件交回维护权**:原负责人会话「自动重启」按机主指示即日起停止改动,`components/restart/**` 暂由插件负责人 `b6aa099b` 直管;新负责人等后续交接时再任命(`MAINTAINERS.md` 的 `modules.restart` 已改,旧负责人记入任期)。
44
+ - 它交回时留了两条观察,已记进 `MAINTAINERS.md` 的「待定事项」,下次动这个组件前先定口径。
45
+ - 只改 `MAINTAINERS.md` / `CHANGELOG.md` / README,代码与组件版本都不动。
42
46
 
43
- **上一版(0.9.5)**
44
-
45
- - **组件改名:跨终端 → 跨工作区**(跨终端本轮不做,暂无条件);part 标识仍是 `terminal`,改的只是显示名,已装机器不用改配置。
46
-
47
- **更早(0.9.4 / 0.9.3 / 0.9.2 / 0.9.0 / 0.8.14 / 0.8.13 / 0.8.12 / 0.8.11 / 0.8.10 / 0.8.9 / 0.8.8 / 0.8.7 / 0.8.6 / 0.8.5 / 0.8.0)**
47
+ **更早(0.9.7 / 0.9.6 / 0.9.5 / 0.9.4 / 0.9.3 / 0.9.2 / 0.9.0 / 0.8.14 / 0.8.13 / 0.8.12 / 0.8.11 / 0.8.10 / 0.8.9 / 0.8.8 / 0.8.7 / 0.8.6 / 0.8.5 / 0.8.0)**
48
48
 
49
+ - 0.9.7:组件版本按机主要求重新定起点(跨会话 v0.5.0 / 跨设备 v0.4.0 / 跨工作区 v0.1.0)。
50
+ - 0.9.6:去掉「跨账号」组件,插件页变成 5 个组件。
51
+ - 0.9.5:组件「跨终端」改名「跨工作区」(跨终端本轮不做)。
49
52
  - 0.9.3:组件表补全六个组件,入口名从 `ui` 改成「跨端互通(入口)」,组件版本号各升各的第一次落地。
50
53
  - 0.9.0:自动重启并进来当第 6 个组件(`dsh_restart` + 8 个 helper 脚本),由原 `dsh-restart` 会话继续维护、不再单独发版。
51
54
  - 0.8.14:装完未重启时卡片显示「待重启」、更新按钮变灰「已装待重启」,`/check` 回 `needRestart=true`。
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "跨会话",
3
3
  "description": "同一终端里的多个会话之间互通:特征码链、内联邮件、收件箱",
4
- "version": "0.5.0"
4
+ "version": "0.5.1"
5
5
  }
package/lib/bus.js CHANGED
@@ -23,7 +23,7 @@ import path from 'node:path'
23
23
  import crypto from 'node:crypto'
24
24
  import { rotateIfNeeded } from './logs.js'
25
25
 
26
- export const VERSION = '0.9.7'
26
+ export const VERSION = '0.9.9'
27
27
 
28
28
  /** DSH 的家目录:与宿主其它状态(sessions / profiles)同一个根,多终端天然共享 */
29
29
  export function dshHome() {
@@ -524,6 +524,26 @@ export class Bus {
524
524
  return { code, removed }
525
525
  }
526
526
 
527
+ /**
528
+ * 按代码 + 节点退出:把这个节点在链上的**全部**成员一次性摘掉。
529
+ * 用途是清「链台账里还有成员、但本机 links 已经没有记录」的幽灵成员 ——
530
+ * 0.9.9 之前 unlinkChain 只遍历内存 this.links,这种残留永远退不掉(YOCE_C 2026-10-20 报的坑)。
531
+ */
532
+ async leaveChainNode(code, nodeId) {
533
+ const file = this.chainFile(code)
534
+ const chain = await readJson(file)
535
+ if (!chain || !Array.isArray(chain.members)) return { code, removed: 0, found: false }
536
+ const before = chain.members.length
537
+ chain.members = chain.members.filter((m) => m.nodeId !== nodeId)
538
+ const removed = before - chain.members.length
539
+ if (!chain.members.length) {
540
+ await fsp.unlink(file).catch(() => {})
541
+ return { code, removed, empty: true, found: true }
542
+ }
543
+ if (removed) await writeJsonAtomic(file, { ...chain, updatedAt: Date.now() })
544
+ return { code, removed, found: true }
545
+ }
546
+
527
547
  /** 剔掉已经不存在的成员(进程退出 / 长时间没心跳) */
528
548
  async pruneChain(code, nodes) {
529
549
  const chain = await readJson(this.chainFile(code))
package/lib/index.js CHANGED
@@ -1024,6 +1024,16 @@ class CrossTerm {
1024
1024
  for (const chain of await this.bus.listChains()) {
1025
1025
  for (const m of chain.members || []) {
1026
1026
  if (m.nodeId !== this.nodeId || !m.sessionId) continue
1027
+ // 自愈:本节点在台账里有成员,但 links 里已经没有这条记录 → 摘掉(0.9.9)。
1028
+ // 只在 links 已从磁盘载入之后做,避免启动早期把合法成员误删。
1029
+ if (this.linksLoaded) {
1030
+ const stillLinked = [...this.links.values()].some((l) => l.code === chain.code && String(l.sessionId || '') === String(m.sessionId))
1031
+ if (!stillLinked) {
1032
+ await this.bus.leaveChain(chain.code, this.nodeId, m.sessionId).catch(() => {})
1033
+ await this.note(`chain sweep: 摘掉幽灵成员 ${shortId(m.sessionId)} @ ${chain.code}`)
1034
+ continue
1035
+ }
1036
+ }
1027
1037
  const { known } = this.sessionPresence(m.sessionId)
1028
1038
  if (known) this.chainMisses.delete(`${chain.code}::${m.sessionId}`)
1029
1039
  if (Number(m.pid) !== process.pid || m.version !== VERSION) {
@@ -1172,6 +1182,16 @@ class CrossTerm {
1172
1182
  if (!sid && !all && !caller) return { ok: false, detail: '不知道该退哪个会话:请传 sessionId,或显式 all=true 退掉本终端在这串码上的全部会话' }
1173
1183
  const victims = [...this.links.entries()].filter(([, l]) => l.code === norm && (!scope.length || scope.includes(l.sessionId)))
1174
1184
  if (!victims.length) {
1185
+ // 幽灵成员:links 里没有本终端的记录,但链台账里还留着本节点的人(0.9.9 修)。
1186
+ // all=true 视为「把本终端在这串码上的痕迹全清」,连台账一起摘;
1187
+ // 显式指定 sessionId 时不动 —— 那可能是笔误,不能顺手清别的会话。
1188
+ if (all) {
1189
+ const ghost = await this.bus.leaveChainNode(norm, this.nodeId).catch(() => undefined)
1190
+ if (ghost && ghost.removed > 0) {
1191
+ await this.note(`unlinked ${norm} [ghost ${ghost.removed}]`)
1192
+ return { ok: true, code: norm, removed: ghost.removed, ghosts: ghost.removed, empty: !!ghost.empty, sessions: [] }
1193
+ }
1194
+ }
1175
1195
  return { ok: false, detail: `本终端${scope.length ? `的会话 ${this.labelOf(scope[0])}` : ''}没有接在特征码「${norm}」上` }
1176
1196
  }
1177
1197
  let removed = 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-crossterm",
3
- "version": "0.9.7",
3
+ "version": "0.9.9",
4
4
  "description": "跨端互通:同一 DSH 账号下的会话、工作区、设备之间互通 —— 特征码链、内联邮件、云端中转站与自动备份",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",