dsh-large-proj-perf 1.3.0 → 1.3.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 CHANGED
@@ -9,6 +9,78 @@
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [1.3.3] — 2026-10-24
13
+
14
+ 面向 **dsh 0.2.1-rc.3** 的兼容版本。rc.3 是**纯版本刷新**:17 个关键包
15
+ (`dsh-session`、`dsh-session-persistence(-jsonl)`、`dsh-session-projection(-cache)`、
16
+ `dsh-app-boot`、`dsh-web-app`、`dsh-plugin-manager`、`dsh-agent-loop`、`dsh-base`、
17
+ `dsh-host-webserver`、`dsh-home-paths`、`dsh-session-checkpoint-policy`、
18
+ `dsh-session-query`、`dsh-session-format-catalog`、`dsh-session-format-v3-to-v4`、
19
+ `dsh-working-directory`、`dsh-agent-preset-registry`)rc.2→rc.3 逐字节相同;
20
+ 顶层依赖集 86 项无增删;305 个已装包全部对齐 rc.3。四个补丁点与投影缓存零影响。
21
+
22
+ ### Changed
23
+
24
+ - 已知版本列表加入 `0.2.1-rc.3`;支持范围上限更新为 `0.2.1-rc.3`。
25
+
26
+ ## [1.3.2] — 2026-10-17
27
+
28
+ 面向 **dsh 0.2.1-rc.2** 的兼容版本。rc.2 是**纯版本刷新**:13 个关键包
29
+ (`dsh-session`、`dsh-session-persistence(-jsonl)`、`dsh-session-projection(-cache)`、
30
+ `dsh-app-boot`、`dsh-agent-loop`、`dsh-web-app`、`dsh-plugin-manager`、
31
+ `dsh-host-webserver`、`dsh-home-paths` 等)rc.1→rc.2 逐字节相同,四个补丁点零影响。
32
+
33
+ ### Added
34
+
35
+ - **插件清单展示元数据**(按官方 bundle 规范):`icon.svg`、
36
+ `locale/en.json`、`locale/zh.json`(双语标题与描述),并在 `exports` 中暴露
37
+ `./locale/*.json` 与 `./cordis.patch.yml`、在 `files` 中纳入这些资源。
38
+ 此前插件在插件管理器中只显示裸包名、无图标。
39
+ - `verify_compat` 新增 4 条断言:bundle 声明完整(`dsh.bundle.patch`)、双语
40
+ `locale` 可解析、`exports` 暴露子路径、展示元数据可被宿主 `readPluginMeta` 解析。
41
+
42
+ ### Changed
43
+
44
+ - 已知版本列表加入 `0.2.1-rc.2`;支持范围上限更新为 `0.2.1-rc.2`。
45
+ - **README 重整**:从 315 行精简到 156 行,只保留使用者需要的内容(能力与效果、
46
+ 安装、配置、验证、已知限制)。维护者向内容拆到 `docs/`:
47
+ - `docs/ARCHITECTURE.md` —— 三类同步阻塞、各补丁实现原理、加载侧分工、
48
+ identity 契约、安全性与回退、代码结构;
49
+ - `docs/COMPATIBILITY.md` —— 版本矩阵、0.1.3 破坏面、版本门禁规则、
50
+ 插件清单规范、升级检查清单。
51
+ - `files` 白名单纳入 `docs/`,随 npm 包发布。
52
+
53
+ ## [1.3.1] — 2026-10-17
54
+
55
+ 面向 **dsh 0.2.1-rc.1** 的兼容与修复版本。
56
+
57
+ ### Added
58
+
59
+ - **`engines.dsh` 声明**(`>=0.1.0-rc.6 <0.3.0-0`)。0.2.1 起门禁优先读
60
+ `engines.dsh` 且**独占生效**——声明后 `peerDependencies` 的 dsh 项被完全忽略。
61
+ 两处声明保持一致,同时满足 0.2.0(只认 peer)与 0.2.1+(只认 engines)两代门禁。
62
+ - `verify_compat` 改为直接 `import` 宿主真实的 `evaluatePluginCompatibility` 实算
63
+ 门禁(拿不到时回退 semver 离线复算),并把 `engines.dsh` 纳入断言。
64
+ - e2e 新增 2 组回归断言,锁定本次修复的两个预热缺陷。
65
+
66
+ ### Fixed
67
+
68
+ - **预热用 `def.init()` 无参调用**(严重):上游投影契约是
69
+ `init(header, inheritedEventCount)`,`agentPreset` / `workingDirectory` /
70
+ `agentTeam` 等真实投影从 header 取值,无参调用抛 `TypeError` 导致该 unit
71
+ 预热静默失败。现按上游 `restore()` 语义传 `(session.header, session.inheritedEventCount)`。
72
+ - **预热写入的 cell 缺 `views` 字段**(严重):上游 `buildCell()` 产出
73
+ `{state, observedSeq, views: [undefined, undefined]}`,`drive()` / `advanceCell()`
74
+ 会读写 `views[0]/[1]`。缺该字段时,预热后的会话在**下一条事件到达**时抛
75
+ `TypeError: Cannot read properties of undefined (reading '1')`。现按 `buildCell` 同形写入。
76
+
77
+ ### Changed
78
+
79
+ - **测试适配 0.2.1 的 jsonl 后端形态**:该包不再导出 `default` 类,只导出
80
+ cordis 插件(`name`/`apply`/`Config`)。e2e 改为经真实 `Context.plugin` 装载后
81
+ 从 `ctx.sessionPersistence` 取实例(同时保留 0.2.0 及之前的原型直取路径)。
82
+ - 已知版本列表加入 `0.2.1-rc.1`。
83
+
12
84
  ## [1.3.0] — 2026-10-11
13
85
 
14
86
  面向 **dsh 0.2.0** 的发布准备版本。
package/README.md CHANGED
@@ -1,218 +1,114 @@
1
1
  # dsh-large-proj-perf
2
2
 
3
- [![Version](https://img.shields.io/badge/version-1.3.0-blue)]()
4
- [![dsh](https://img.shields.io/badge/dsh-0.1.0--rc.6..0.2.0--rc.2-green)]()
3
+ [![Version](https://img.shields.io/badge/version-1.3.3-blue)]()
4
+ [![dsh](https://img.shields.io/badge/dsh-0.1.0--rc.6..0.2.1--rc.3-green)]()
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
6
 
7
- DSH(DeepSeek Harness)大会话性能插件:零拷贝 fork、分片投影预热、fork 缓存回填、
8
- 分片 materialize、冷会话内存治理——一次装齐,消除 fork / 历史加载 / 落盘对超大会话
9
- (数十万事件)的事件循环阻塞与 OOM。
7
+ DSH(DeepSeek Harness)大会话性能插件。消除 fork、历史会话加载、超大会话落盘对
8
+ 事件循环的阻塞,并治理冷会话内存——面向数十万事件的会话。
10
9
 
11
- 所有优化带**源码特征校验 + 协议探测 + 三层回退**(能力探测 / try-catch / 配置开关),
12
- 不匹配自动跳过、绝不盲补;上游吸收了某项能力后对应补丁**自动从 `active` 转
13
- `retired`**——插件按项逐项退役,最终全部 `retired` 之日即整体退役之时。
10
+ 所有优化都带**源码特征校验 + 协议探测 + 三层回退**(能力探测 / try-catch / 配置
11
+ 开关):dsh 内部结构变化时补丁自动跳过并告警,绝不盲补。上游原生实现了某项能力后,
12
+ 对应补丁自动从 `active` 转为 `retired`。
14
13
 
15
- ## 特性一览
14
+ ## 能力与效果
16
15
 
17
- | 能力 | 解决什么 | 0.1.3-alpha.2 实测 | 状态 |
16
+ | 能力 | 症状 | 效果 | 当前状态 |
18
17
  |---|---|---|---|
19
- | 零拷贝 fork | fork 时逐事件 `snapshotJsonValue` 深拷贝阻塞 | 49ms → 5.9ms(0.1.7 真实环境) | 活跃(0.1.7 走 `create(eventState:'shared-frozen')` + `buildForkSeed` 通道) |
20
- | 分片投影预热 | 打开历史会话的同步冷折叠冻结事件循环 | 74 万事件 20min → ~200ms | 活跃 |
21
- | fork 缓存回填 | fork 子会话无投影缓存行,重开走全量读 | 分钟级 → 秒级 | 活跃(0.1.3 直读 `session.vN.jsonl.zstd` 多帧) |
22
- | 分片 materialize | fork 落盘单巨字符串:60 万事件 501MB、74 万直接 RangeError | 多帧 zstd,字节兼容 | 活跃(0.1.3 三参签名已适配) |
23
- | 冷会话 LRU 裁剪 | 冷会话事件树 ~700MB/个 ×5 叠加 OOM | 省 ~2.8GB | 0.1.3 上游系统修复(`coldLogMemo=2`),补丁自动跳空 |
24
- | fast initFor | persistence 的 `structuredClone(seed)` 深拷贝 | 135ms → ~0ms | 已退役(rc.8 起上游原生;0.1.3 coordinator 移除) |
25
- | heap 检测 | V8 heap 上限过低告警 | 运维辅助 | 活跃 |
18
+ | 零拷贝 fork | fork 逐事件 `snapshotJsonValue` 深拷贝 | 49ms → 5.9ms | 活跃 |
19
+ | 分片投影预热 | 打开历史会话时同步冷折叠冻结事件循环 | 74 万事件 20 分钟 → ~200ms | 活跃 |
20
+ | fork 缓存回填 | 子会话无投影缓存行,重开走全量读 | 分钟级 → 秒级 | 活跃 |
21
+ | 分片 materialize | 落盘单巨字符串:60 万事件 501MB、74 万 RangeError | 多帧 zstd,字节兼容 | 活跃 |
22
+ | 冷会话 LRU 裁剪 | 冷会话事件树 ~700MB/个 × 5 叠加 OOM | 省 ~2.8GB | 上游已代偿 |
23
+ | fast initFor | persistence 的 `structuredClone(seed)` | 135ms → ~0ms | 已退役 |
24
+ | heap 检测 | V8 heap 上限过低无提示 | 启动告警并给出参数建议 | 活跃 |
26
25
 
27
- 运行时各补丁的实际状态经 `stats.get` 的 `patches` 字段暴露:
28
- `active`(已安装生效)/ `retired`(上游已原生实现)/ `inactive`(环境缺失或特征
29
- 漂移)/ `off`(配置关闭)。
30
-
31
- ## 工作原理
32
-
33
- dsh 0.1.x 在大会话上有三类同步阻塞(源码级定位 + 实测):
34
-
35
- | 问题 | 环节 | 实测 |
36
- |---|---|---|
37
- | A. fork 深拷贝 | `Session` 构造器逐事件 `snapshotJsonValue` + 旧 persistence `initFor` 的 `structuredClone(seed)` | 18.2MB / 20k 事件合计 ~480ms |
38
- | B. projection 冷折叠 | `cellFor()` 冷时同步 `buildCell` 全量折叠 | 74 万事件阻塞 20+ 分钟(100% 单核) |
39
- | C. fork 全量序列化 | `encodeMaterialization` 一次性序列化整个 seed | 60 万事件 501MB 单串;74 万 RangeError |
40
-
41
- 对应方案(全部独立开关、失败自动回退官方实现):
42
-
43
- 1. **零拷贝 fork**:fork 的 seed 是 live 会话的 `snapshotEvents()`(事件在 append 时
44
- 已 deepFreeze)。改走 fromRestore 通道原地冻结复用引用(三协议自动探测):
45
- - **0.1.7+**:复用原生 `_resolveForkSource` + `_forkBoundary` + 模块级
46
- `buildForkSeed`(slice + 注入 `session/end-seed {inherited:true}` marker +
47
- `openTurnClosers` 补 turn 闭合),只把 `create` 的写入通道换成
48
- `eventState:'shared-frozen'`;`buildForkSeed` 新建的事件未冻结,送入前
49
- 原地深冻结(满足「已冻结或独立拥有」前提);
50
- - **0.1.3–0.1.5**:复用 `_forkSeed`,补丁补 `version/id/createdAt` 到 meta
51
- 并在 seed 尾部注入 inherited marker(磁盘校验要求 `cut == marker.seq`,
52
- 原生快照通道自动追加、restore 通道不追加);
53
- - **alpha.5 / rc.1**:`prepare({seedSource:'persistence'})` 旧通道。
54
- 2. **分片投影预热**:会话进入且事件数超阈值时,抢在首次冷折叠前分片重放 cells
55
- (每片间 `setImmediate`/`setTimeout` 让出),直写 `registration.cells`;有投影
56
- 缓存行时取基线跳过已折叠前缀;fork 子会话预热后回填缓存行。
57
- 3. **分片 materialize**:每 `materializeChunkEvents` 事件一个 zstd frame(多帧是
58
- 解码端 `scanZstdFrames` 的原生格式,字节兼容),消除单巨字符串。0.1.3 头帧
59
- 直接复用上游 `encodeCurrentHeader` 编码器(`original(meta, inheritedEventCount,
60
- [])` 只产 header 帧),不重写格式层。
61
- 4. **冷会话 LRU 裁剪**:运行时把 `SessionPreparations.capacity` 降到
62
- `preparedCacheSize`(默认 1)并淘汰最旧 ready 条目;`config.set` 即时生效,
63
- dispose 恢复原容量。0.1.3 起上游以 `COLD_LOG_MEMO_MAX_ENTRIES = 2` + handle
64
- 模型懒 materialize 系统代偿,该补丁在上游 0.1.3 自动跳空(老版本仍活跃)。
65
- 5. **fast initFor**:`structuredClone(seed)` → 冻结引用复用;rc.8 起上游原生实现,
66
- 补丁自动退役(0.1.3 连 `PersistenceCoordinator` 本体也已移除)。
67
- 6. **heap 检测**:heap 上限低于阈值(默认 6GB)时告警并提示
68
- `--max-old-space-size`。
69
-
70
- 安全性:共享冻结引用与深拷贝语义等价(事件进入源会话时已完整校验并深冻结);
71
- 三层回退保证任何不匹配都不破坏上游行为。
72
-
73
- ### 加载侧:上游已优化的部分与插件的作用点
74
-
75
- 长对话「打开」的成本分三段,0.1.3–0.1.7 期间上游已优化前两段:
76
-
77
- | 阶段 | 上游现状(0.1.7) | 插件是否介入 |
78
- |---|---|---|
79
- | 磁盘解码 | 逐帧 zstd 解码 + 增量 `SessionLogScanner`(不拼整串),每 500ms `scheduler.yield()` 让出事件循环 | 否(上游已良好) |
80
- | 老格式迁移 | 格式 < v4 的会话经 v0→v1→v2→v3→v4 链,**在 Worker Thread 内完成**并发布新世代文件(`session.vN.jsonl`) | 否(上游已良好) |
81
- | 投影折叠 | `cellFor` 冷时**同步全量** `buildCell`(仍在主线程) | **是**:分片预热 + 缓存行基线(见下) |
82
- | 落盘编码 | `encodeMaterialization` 单 body 帧(fork 子会话首次落盘可能 501MB 单串) | **是**:分片 zstd 多帧 |
83
-
84
- 插件写/读投影缓存行时必须使用与上游 `identityOf` 一致的键:`createdAt`/`cwd`/
85
- `isSeeded`/`inheritedEventCount` 以及 **0.1.3 起新增的 `formatVersion`**
86
- (= `header.version`)。上传 identity 缺 `formatVersion` 时,上游只把它当作
87
- title 前驱提示,**不能作折叠短路**——补行与基线复用会静默失效(此坑已于 1.2.0
88
- 修复并有回归断言守护)。
89
-
90
- ## 支持版本
91
-
92
- | 版本线 | 形态 |
93
- |---|---|
94
- | 0.1.0-rc.6 / rc.7 / rc.8 | fork `prepare(seedSource)` 通道;initFor `structuredClone` 形态 |
95
- | 0.1.1-rc.1 / rc.2 | 同 alpha.5 形态;rc.2 为 `(meta, events)` 裸 meta 签名 |
96
- | 0.1.2-alpha.5 / 0.1.2-rc.1 | `snapshotEvents()`;fork meta `isSeeded`+`inheritedEventCount`;`(storage, events)` 签名 |
97
- | 0.1.3-alpha.2 | persistence 子系统重写(见下);投影缓存 identity 新增 `formatVersion`(插件已跟进) |
98
- | 0.1.5-alpha.1 | 会话格式 **v3**(`SESSION_FORMAT_VERSION=3`);cut/header 语义与 v2 一致,文件名 `session.v3.jsonl`;补丁点零行级变更 |
99
- | 0.1.5-rc.1 | 仅新增已知事件类型(`deliverables/presented`、`subagent/catalog`);补丁点零变化 |
100
- | 0.1.5-rc.2 | 相对 rc.1 四个关键包源码零差异(仅版本号),无新增变更 |
101
- | 0.1.7-rc.2 | 新增 `toolHistory()` 增量折叠(游标只消费新事件);persistence/format/projection-cache 零差异,补丁点无变化 |
102
- | 0.2.0-rc.2 | 主版本升级。**插件管理引入严格 peer 门禁**(见下);`dsh-session` 重构 tail-repair 并新增 `ToolCallRecovery`(`buildForkSeed`/`SESSION_FORMAT_VERSION` 等导出不变);`llm-deepseek` 拆分为 `-api-key`/`-account`;四个补丁点与 projection-cache 零破坏 |
103
- | 0.1.7-rc.1 | fork 内部重构:`_forkSeed` → `_forkBoundary` + 模块级 `buildForkSeed`;**open-turn 边界从「拒绝」改为「注入 turn 闭合事件」**;会话格式升 **v4**(cut/文件名语义同 v2/v3);事件词汇收紧(`tool/result` role=`tool`、新增 `system/message`)。插件已适配(三协议探测) |
104
-
105
- ### 0.1.3-alpha.2 关键变化(已全部适配)
106
-
107
- - `dsh-session-persistence` 变为 contract 层(`SessionPersistence` 基类 + 错误 +
108
- 校验),`PersistenceCoordinator`/`initFor`/`SessionPreparations` 移除;JSONL
109
- 实现移入 `dsh-session-persistence-jsonl`。
110
- - 格式 v0→v1→v2 迁移链(`dsh-session-format` 系列);物理 header 移除
111
- `seedLength`,seed cut 由事件流尾 `session/end-seed {inherited:true}` marker
112
- 表达(`cut == marker.seq`);文件名变 `session.vN.jsonl[.zstd]`。
113
- - `encodeMaterialization(meta, events)` → `(meta, inheritedEventCount, events)`;
114
- 事件词汇收紧(`assistant/message` 需 `data.stream`、消息 `id/source/role` 校验)。
115
-
116
- 升级 dsh 后:启动日志显示 `dsh version: x.y.z (verified)`(版本探针)或列表外
117
- 版本告警;跑 `node tests/verify_compat.mjs`(对真实安装源码做结构断言 + peer 门禁
118
- 离线复算,当前 30 项);确认无 `signature mismatch` 告警。
26
+ 运行时状态经 `stats.get` 的 `patches` 字段暴露:`active`(已安装生效)、
27
+ `retired`(上游已原生实现)、`inactive`(环境缺失或特征漂移)、`off`(配置关闭)。
119
28
 
120
29
  ## 安装
121
30
 
122
- 要求:Node **≥ 22.15.0**(`node:zlib` zstd 接口);dsh `0.1.0-rc.6` ~ `0.2.0-rc.2`
123
- (`package.json` 已声明 `engines`)。
31
+ 要求 Node **≥ 22.15.0**(`node:zlib` 的 zstd 接口),dsh `0.1.0-rc.6` ~ `0.2.1-rc.3`。
124
32
 
125
33
  ```sh
126
34
  # 从 GitHub 安装(推荐)
127
35
  dsh plugin --profile web add github:orangeofcarl0-sys/dsh-large-proj-perf
128
36
 
37
+ # 从 npm 安装
38
+ dsh plugin --profile web add dsh-large-proj-perf
39
+
129
40
  # 本地开发
130
41
  dsh plugin --profile web add file:<本仓库路径>
131
42
  ```
132
43
 
133
- > 修改仓库代码后需把 `lib/`、`cordis.patch.yml`、`package.json` 同步到
134
- > `<DSH_HOME>/profiles/web/node_modules/dsh-large-proj-perf/`(`file:` 安装
135
- > 不自动跟随),或重新 `dsh plugin add`。重启 `dsh web` 生效,日志出现
136
- > `[dsh-perf] installed (...)` 即成功。
137
-
138
- ### 插件管理:0.2.0 的 peer 门禁(重要)
139
-
140
- dsh 0.2.0 起,profile bundle 加载前会校验插件 `package.json` 的
141
- `peerDependencies` 中**所有 `@deepseek-ai/dsh` / `@deepseek-ai/dsh-*` 项**:
142
- 运行时版本不满足 range 即**跳过整个插件**(启动日志打
143
- `skipping profile bundle "<name>": ... incompatible with dsh <ver>`),可用
144
- `dsh plugin allow-version <pkg>@<ver> --profile web` 做精确版本豁免。
145
-
146
- 本插件的声明(0.2.0 契约的显式兼容声明):
44
+ 重启 `dsh web` 后,启动日志出现 `[dsh-perf] installed (...)` 即装载成功。
147
45
 
148
- ```json
149
- "peerDependencies": {
150
- "@deepseek-ai/cordis": "^4.0.1",
151
- "@deepseek-ai/dsh": ">=0.1.0-rc.6 <0.3.0-0",
152
- "@deepseek-ai/dsh-session": ">=0.1.0-rc.6 <0.3.0-0"
153
- }
154
- ```
155
-
156
- - 只声明我们**真实依赖**的东西:`dsh-session`(`snapshotEvents`/`buildForkSeed`/
157
- `SessionLogOffset`)与 `dsh`(版本探针);
158
- - 上界 `<0.3.0-0` 覆盖整个 0.1.x–0.2.x 线:既让门禁能替我们守住兼容边界,
159
- 又不会在小版本推进时误杀自己。**升级 dsh 大版本(0.3+)前需先更新此范围**;
160
- - `verify_compat.mjs` 会用 dsh 自带的 semver **离线复算**同一条门禁规则
161
- (`plugin contract: declared dsh peers satisfy runtime version`)——离线报警
162
- = 重启后必被跳过。
46
+ > `file:` 安装不跟随源码改动。改完代码需把 `lib/`、`cordis.patch.yml`、
47
+ > `package.json`、`icon.svg`、`locale/` 同步到
48
+ > `<DSH_HOME>/profiles/web/node_modules/dsh-large-proj-perf/`,或重新 `dsh plugin add`。
163
49
 
164
- ### 大会话内存(推荐启动方式)
50
+ ### 建议同时调高 V8 heap 上限
165
51
 
166
- 多个超大会话的 live 事件树每个 ~700MB,默认 V8 heap 上限 ~4GB 会 OOM。插件已自动
167
- 做冷会话治理,但 heap 上限是**启动期参数**,进程内改不了——推荐用仓库脚本启动(内置
168
- `--max-old-space-size=8192`):
52
+ 多个大会话并存时,每个 live 事件树约 700MB,默认 ~4GB 上限容易 OOM。heap 上限是
53
+ **启动期参数**,进程内改不了。用仓库脚本启动(内置 `--max-old-space-size=8192`):
169
54
 
170
55
  ```powershell
171
56
  powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\start-dsh.ps1
172
57
  ```
173
58
 
174
- ### 避免超长对话(主动规避)
175
-
176
- 本插件是「被动优化」,无法削减**正在使用的** live 事件树(每会话几百 MB 是它的
177
- 物理成本)。推荐配合 [dsh-fresh-start](https://github.com/orangeofcarl0-sys/dsh-fresh-start):
178
- `/fresh` 一键「总结 → 开新会话 → 归档老会话」,主动控制会话规模。本插件兜底
179
- 性能,fresh-start 控制规模。
59
+ 插件会在上限低于 6GB 时打告警,但不代为修改。
180
60
 
181
61
  ## 配置
182
62
 
183
- 全部配置可经 Settings 卡片、`config.set` API 或 settings 持久化修改;数值项带下限
184
- 钳制(非法值被拒绝或收敛到下限:`materializeChunkEvents ≥ 1000`、`chunkSize ≥ 1`、
185
- `preparedCacheSize ≥ 1` 等),NaN/Infinity 直接拒绝。
63
+ 可经 Settings 卡片、`config.set` API 或 settings 持久化修改。数值项带下限钳制
64
+ (如 `materializeChunkEvents ≥ 1000`、`chunkSize ≥ 1`),NaN/Infinity 直接拒绝。
186
65
 
187
66
  | 键 | 默认 | 说明 |
188
67
  |---|---|---|
189
- | `zeroCopyFork` | `true` | 零拷贝 fork(三协议自动探测:0.1.7 fork-seed / 0.1.3 event-state / alpha.5 seed-source) |
190
- | `fastInitFor` | `true` | fast initFor(rc.8+ 自动退役;0.1.3 无此形态) |
68
+ | `zeroCopyFork` | `true` | 零拷贝 fork |
69
+ | `fastInitFor` | `true` | fast initFor(新版 dsh 自动退役) |
191
70
  | `slowForkWarnMs` | `100` | fork 耗时告警阈值(ms) |
192
- | `warmupEnabled` | `true` | 大会话投影分片预热总开关 |
193
- | `minEvents` | `20000` | 低于此事件数不预热(同步冷折叠足够快) |
194
- | `chunkSize` | `5000` | 每片折叠事件数(下限 1;片间让出事件循环) |
195
- | `chunkYieldMs` | `0` | 让出方式:0=setImmediate;>0=setTimeout(ms) |
196
- | `warmOnCreated` | `true` | session/created(含 resume 的大会话)即预热 |
71
+ | `warmupEnabled` | `true` | 投影分片预热总开关 |
72
+ | `minEvents` | `20000` | 低于此事件数不预热 |
73
+ | `chunkSize` | `5000` | 每片折叠事件数(片间让出事件循环) |
74
+ | `chunkYieldMs` | `0` | 让出方式:0 = `setImmediate`,>0 = `setTimeout(ms)` |
75
+ | `warmOnCreated` | `true` | `session/created`(含 resume)即预热 |
197
76
  | `backfillOnBoot` | `false` | 磁盘冷会话补投影缓存行(大文件解码同步,默认关) |
198
77
  | `backfillMaxSessions` | `8` | 补行扫描会话数上限 |
199
78
  | `backfillMinBytes` / `backfillMaxBytes` | `1MB` / `32MB` | 补行文件大小范围 |
200
- | `chunkedMaterialize` | `true` | 分片落盘(0.1.3 头帧复用上游编码器) |
79
+ | `chunkedMaterialize` | `true` | 分片落盘 |
201
80
  | `materializeChunkEvents` | `50000` | 每帧事件数(下限 1000) |
202
- | `preparedCacheTrim` | `true` | 冷会话 LRU 裁剪总开关(false 恢复官方容量) |
203
- | `preparedCacheSize` | `1` | 裁剪目标容量(官方默认 5 → 1,省 ~2.8GB) |
204
- | `keepRecent` | `50` | 内存保留的最近记录数(fork/预热统计) |
81
+ | `preparedCacheTrim` | `true` | 冷会话 LRU 裁剪总开关 |
82
+ | `preparedCacheSize` | `1` | 裁剪目标容量(官方默认 5) |
83
+ | `keepRecent` | `50` | 内存保留的最近统计记录数 |
205
84
  | `heapWarnBytes` | `6GB` | heap 上限告警阈值 |
206
85
 
207
- ## API
86
+ ## 验证是否生效
87
+
88
+ 启动日志会打印探测到的 dsh 版本与各补丁安装结果:
208
89
 
209
- `POST http://127.0.0.1:3080/dsh-large-proj-perf/api/<method>`(回环 + 同源校验):
90
+ ```
91
+ [dsh-perf] dsh version: 0.2.1-rc.3 (verified)
92
+ [dsh-perf] zero-copy fork installed (SessionStore.prototype.fork patched, protocol: 0.1.7 forkSeed)
93
+ [dsh-perf] chunked materialize installed (chunk=50000 events/frame, protocol: 0.1.3)
94
+ ```
95
+
96
+ 出现 `signature mismatch` 说明 dsh 内部结构已变,对应补丁跳过(功能回退到官方实现,
97
+ 不会出错)。此时升级插件或运行兼容性检查:
98
+
99
+ ```sh
100
+ node tests/verify_compat.mjs
101
+ ```
102
+
103
+ ### HTTP API
104
+
105
+ `POST http://127.0.0.1:3080/dsh-large-proj-perf/api/<method>`(仅回环 + 同源):
210
106
 
211
107
  | 端点 | 说明 |
212
108
  |---|---|
213
- | `stats.get` | `dshVersion` 版本探针、`patches` 各补丁状态(active/retired/inactive/off)、fork/预热/补行计数与最近记录 |
214
- | `stats.reset` | 清零计数与历史(保留版本探针与补丁状态) |
215
- | `config.get` / `config.set` | 运行时开关;`config.set` 同时写 settings 持久化(`backfillOnBoot` 等生效即补跑) |
109
+ | `stats.get` | 版本探针、各补丁状态、fork/预热/补行计数与最近记录 |
110
+ | `stats.reset` | 清零计数(保留版本探针与补丁状态) |
111
+ | `config.get` / `config.set` | 运行时开关,`set` 同时写 settings 持久化 |
216
112
 
217
113
  ```sh
218
114
  curl -X POST http://127.0.0.1:3080/dsh-large-proj-perf/api/stats.get
@@ -220,48 +116,40 @@ curl -X POST http://127.0.0.1:3080/dsh-large-proj-perf/api/config.set \
220
116
  -H 'Content-Type: application/json' -d '{"key":"backfillOnBoot","value":true}'
221
117
  ```
222
118
 
223
- ## 开发与测试
119
+ ## 已知限制
120
+
121
+ - **这是被动优化**,削减不了正在使用的 live 事件树(每会话几百 MB 是物理成本)。
122
+ 建议配合 [dsh-fresh-start](https://github.com/orangeofcarl0-sys/dsh-fresh-start)
123
+ 主动控制会话规模:`/fresh` 一键「总结 → 开新会话 → 归档老会话」。
124
+ - `backfillOnBoot` 的解码在插件层同步执行(虽按 `chunkSize` 分片让出),超大文件
125
+ 仍可能短暂冻结事件循环,故默认关闭。
126
+ - `preparedCacheTrim` 在新版 dsh 上自动跳空为 `inactive`(上游已系统代偿);仅在
127
+ 老版本 dsh 上真正生效。
128
+ - 与其他插件不冲突,但若某个行管理类插件禁用了 `session-projection-cache`,
129
+ 本插件的缓存与预热能力会被削弱。
130
+
131
+ ## 开发
224
132
 
225
133
  ```sh
226
- # 一次到位:junction 链接全局 dsh 的嵌套依赖(仓库无 node_modules)
134
+ # 链接全局 dsh 的嵌套依赖(仓库本身无 node_modules)
227
135
  powershell -ExecutionPolicy Bypass -File .\scripts\link-deps.ps1
228
136
 
229
- # 全套 8 套件 138 断言
230
- # - verify_compat:对真实安装源码做特征断言 + peer 门禁离线复算
231
- # - e2e_real_components:直接驱动 dsh 真实实现类(真实 JsonlSessionPersistence
232
- # / SessionProjectionRegistry / SessionStore),逐点位验证补丁安装与行为
137
+ # 全套 8 套件 147 断言
233
138
  npm test
234
139
  ```
235
140
 
236
- 仓库布局:
237
-
238
- ```
239
- lib/index.js 编排层(apply:配置/统计/日志/版本探针/补丁安装/定时器)
240
- lib/runtime.js 日志、safeGet、sessionEventsOf、sessionLogOffset、版本探针
241
- lib/config.js 默认值/下限钳制/settings schema 注册
242
- lib/stats.js 统计与补丁生命周期状态(active/retired/inactive/off)
243
- lib/api.js stats/config HTTP 端点
244
- lib/warmup.js 分片投影预热 + fork 缓存回填
245
- lib/backfill.js 磁盘冷会话补行(readRaw / 0.1.3 直读双通道)
246
- lib/patches/*.js 零拷贝 fork / fast initFor / 冷会话 LRU / 分片 materialize
247
- tests/ verify_compat(真实源码特征 + 门禁复算)/ e2e_real_components
248
- (真实实现类端到端)/ smoke_*、test_*(mock 逻辑与边界)
249
- ```
250
-
251
- 补丁安装原则:**特征校验先行**(`String(原型方法)` 含特征字面量,不匹配打
252
- `signature mismatch` 后跳过);**协议探测**(如 fork 的 `eventState` vs
253
- `seedSource` 双分支);**try-catch 兜底**(零拷贝失败自动回退官方实现并计入
254
- `fallbacks`)。所有补丁 dispose 时按原形状还原(原型方法 delete 回落 / 自有属性
255
- 原样写回)。
141
+ 测试分两层:`verify_compat` 对真实安装源码做特征断言与门禁实算;`e2e_real_components`
142
+ 直接驱动 dsh 的真实实现类(`JsonlSessionPersistence` / `SessionProjectionRegistry` /
143
+ `SessionStore`),逐点位验证「补丁装上 → 行为正确」。其余套件覆盖逻辑与边界。
256
144
 
257
- ## 已知限制
145
+ ## 更多文档
258
146
 
259
- - `backfillOnBoot` 的解码在插件层同步执行(虽按 chunkSize 分片让出),超大文件
260
- 仍可能短暂冻结事件循环,故默认关闭,需要时手动开启。
261
- - 0.1.3 上 `preparedCacheTrim` 自动跳空为 `inactive`(上游已系统代偿);若你仍
262
- 运行老版本 dsh,该补丁才真正生效。
263
- - 插件不与 `dsh-perf`(配置订阅类)或 `dsh-pref-kit` 冲突,但行管理类插件禁用
264
- `session-projection-cache` 会削弱本插件的缓存/预热功能。
147
+ | 文档 | 内容 |
148
+ |---|---|
149
+ | [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | 三类同步阻塞的定位、各补丁实现原理、加载侧分工、安全性与回退设计 |
150
+ | [docs/COMPATIBILITY.md](docs/COMPATIBILITY.md) | 版本矩阵、各版本内部形态、dsh 版本门禁规则、插件清单规范 |
151
+ | [CHANGELOG.md](CHANGELOG.md) | 版本变更记录 |
152
+ | [PUBLISHING.md](PUBLISHING.md) | 发布流程与约定 |
265
153
 
266
154
  ## License
267
155
 
@@ -0,0 +1,124 @@
1
+ # 架构与实现
2
+
3
+ 本文面向维护者,说明每项优化针对的具体阻塞点、实现方式,以及安全性与回退设计。
4
+
5
+ ## 三类同步阻塞
6
+
7
+ dsh 0.1.x 在大会话上有三类同步阻塞,均为主线程事件循环占用:
8
+
9
+ | 问题 | 环节 | 实测 |
10
+ |---|---|---|
11
+ | A. fork 深拷贝 | `Session` 构造器逐事件 `snapshotJsonValue`,旧 persistence `initFor` 的 `structuredClone(seed)` | 18.2MB / 20k 事件合计 ~480ms |
12
+ | B. projection 冷折叠 | `cellFor()` 冷时同步 `buildCell` 全量折叠 | 74 万事件阻塞 20+ 分钟(100% 单核) |
13
+ | C. fork 全量序列化 | `encodeMaterialization` 一次性序列化整个 seed | 60 万事件 501MB 单串;74 万 RangeError |
14
+
15
+ ## 各补丁的实现
16
+
17
+ 所有补丁独立开关、失败自动回退官方实现。
18
+
19
+ ### 1. 零拷贝 fork(问题 A)
20
+
21
+ fork 的 seed 是 live 会话的 `snapshotEvents()`,这些事件在 append 时已被
22
+ `deepFreeze`。补丁改走 fromRestore 通道,原地冻结复用引用而非深拷贝。
23
+
24
+ fork 协议经历三代,补丁按能力探测自动选择:
25
+
26
+ | 协议 | 版本 | 实现 |
27
+ |---|---|---|
28
+ | fork-seed | 0.1.7+ | 复用原生 `_resolveForkSource` + `_forkBoundary` + 模块级 `buildForkSeed`(slice + 注入 `session/end-seed {inherited:true}` marker + `openTurnClosers` 补 turn 闭合),只把 `create` 的写入通道换成 `eventState:'shared-frozen'`。`buildForkSeed` 新建的事件未冻结,送入前原地深冻结 |
29
+ | event-state | 0.1.3–0.1.5 | 复用 `_forkSeed`,补丁补 `version/id/createdAt` 到 meta,并在 seed 尾部注入 inherited marker(磁盘校验要求 `cut == marker.seq`;原生快照通道自动追加,restore 通道不追加) |
30
+ | seed-source | alpha.5 / rc.1 | `prepare({seedSource:'persistence'})` 旧通道 |
31
+
32
+ 探测依据是方法存在性与源码字面量,任一不满足即整体跳过,绝不安装会破坏 fork 的补丁。
33
+
34
+ 已知取舍:open-turn 边界 fork 时,fromRestore 模式会补一条非 inherited 的 live
35
+ end-seed 标记(原生快照通道不补)。该标记不参与 seed cut、不影响 v4 校验,代价是
36
+ 子会话事件流比原生多一条标记。
37
+
38
+ ### 2. 分片投影预热(问题 B)
39
+
40
+ 会话进入且事件数超过 `minEvents` 时,抢在首次同步冷折叠前分片重放 cells:每片
41
+ `chunkSize` 个事件,片间用 `setImmediate`/`setTimeout` 让出事件循环,折叠完成后直写
42
+ `registration.cells`(WeakMap)。此后 `snapshot()` / `drive()` 全部命中热 cell。
43
+
44
+ 有投影缓存行时取基线跳过已折叠前缀;fork 子会话预热后回填缓存行,否则被放弃时
45
+ 永远没有缓存行,下次打开会走 `readFrom(0)` 全量读。
46
+
47
+ 写入的 cell 必须与上游 `buildCell()` 同形——`{ state, observedSeq, views: [undefined, undefined] }`,
48
+ 且初始化必须按上游签名 `init(header, inheritedEventCount)` 调用。缺 `views` 会在下一条
49
+ 事件经 `drive()` 时抛 `TypeError`;无参 `init()` 会让所有从 header 取值的投影
50
+ (`agentPreset`、`workingDirectory`、`agentTeam`)抛错。两者均有回归断言守护。
51
+
52
+ ### 3. 分片 materialize(问题 C)
53
+
54
+ 每 `materializeChunkEvents` 个事件一个 zstd frame。多帧是解码端 `scanZstdFrames`
55
+ 的原生格式,字节兼容。
56
+
57
+ 头帧不复刻上游编码器,而是直接调用原方法 `original(meta, inheritedEventCount, [])`
58
+ ——`events.length === 0` 时只产 header 帧,编码逻辑始终归上游所有。
59
+
60
+ ### 4. 冷会话 LRU 裁剪
61
+
62
+ 运行时把 `SessionPreparations.capacity` 降到 `preparedCacheSize`(默认 1)并淘汰最旧
63
+ ready 条目;`config.set` 即时生效,dispose 恢复原容量。
64
+
65
+ 新版 dsh 已用 `COLD_LOG_MEMO_MAX_ENTRIES = 2` + handle 模型懒 materialize 系统代偿,
66
+ 该补丁自动跳空为 `inactive`(老版本仍活跃)。
67
+
68
+ ### 5. fast initFor
69
+
70
+ `structuredClone(seed)` 换成冻结引用复用。rc.8 起上游原生实现,补丁自动退役;
71
+ 0.1.3 连 `PersistenceCoordinator` 本体也已移除。
72
+
73
+ ### 6. heap 检测
74
+
75
+ heap 上限低于 `heapWarnBytes`(默认 6GB)时告警并提示 `--max-old-space-size`。
76
+
77
+ ## 加载侧:上游已优化的部分与插件的作用点
78
+
79
+ 长对话「打开」的成本分四段,其中前两段上游已自行优化:
80
+
81
+ | 阶段 | 上游现状 | 插件是否介入 |
82
+ |---|---|---|
83
+ | 磁盘解码 | 逐帧 zstd 解码 + 增量 `SessionLogScanner`(不拼整串),每 500ms `scheduler.yield()` | 否 |
84
+ | 老格式迁移 | 格式 < v4 的会话经 v0→v1→v2→v3→v4 链,**在 Worker Thread 内完成**并发布新世代文件 | 否 |
85
+ | 投影折叠 | `cellFor` 冷时**同步全量** `buildCell`(仍在主线程) | **是**:分片预热 + 缓存行基线 |
86
+ | 落盘编码 | `encodeMaterialization` 单 body 帧 | **是**:分片 zstd 多帧 |
87
+
88
+ ### 投影缓存行的 identity 契约
89
+
90
+ 插件写/读缓存行时必须使用与上游 `identityOf` 一致的键:`createdAt`、`cwd`、
91
+ `isSeeded`、`inheritedEventCount`,以及 **0.1.3 起新增的 `formatVersion`**
92
+ (= `header.version`)。
93
+
94
+ 缺 `formatVersion` 时上游只把它当作 title 前驱提示,**不能作折叠短路**——补行与
95
+ 基线复用会静默失效。该问题已于 1.2.0 修复,并有回归断言守护。
96
+
97
+ ## 安全性与回退
98
+
99
+ 共享冻结引用与深拷贝语义等价:事件进入源会话时已完整校验并深冻结,fork 的 seed
100
+ 只是复用这些不可变对象。
101
+
102
+ 三层回退保证任何不匹配都不破坏上游行为:
103
+
104
+ 1. **能力探测**——依赖的方法/导出不存在时直接跳过;
105
+ 2. **源码特征校验**——`String(原型方法)` 不含特征字面量时打 `signature mismatch` 并跳过;
106
+ 3. **try-catch 兜底**——零拷贝 fork 失败时自动回退官方实现,并计入 `fallbacks` 计数。
107
+
108
+ 所有补丁在 dispose 时按原形状还原:原型方法用 `delete` 回落,自有属性原样写回,
109
+ 不留 bind 副本遮蔽。
110
+
111
+ ## 代码结构
112
+
113
+ ```
114
+ lib/index.js 编排层(apply:配置/统计/日志/版本探针/补丁安装/定时器)
115
+ lib/runtime.js 日志、safeGet、sessionEventsOf、sessionLogOffset、版本探针
116
+ lib/config.js 默认值/下限钳制/settings schema 注册
117
+ lib/stats.js 统计与补丁生命周期状态
118
+ lib/api.js stats/config HTTP 端点
119
+ lib/warmup.js 分片投影预热 + fork 缓存回填
120
+ lib/backfill.js 磁盘冷会话补行(0.1.3 前 readRaw / 之后直读双通道)
121
+ lib/patches/*.js 零拷贝 fork / fast initFor / 冷会话 LRU / 分片 materialize
122
+ tests/run.mjs 测试运行器:每套件独立子进程(隔离内存,避免累积崩溃)
123
+ tools/ 开发者专用脚本(不随 npm 包发布)
124
+ ```
@@ -0,0 +1,115 @@
1
+ # 兼容性与插件规范
2
+
3
+ 本文面向维护者:记录各 dsh 版本的内部形态差异、版本门禁规则,以及插件清单规范。
4
+
5
+ ## 版本矩阵
6
+
7
+ 按时间顺序。每一行说明该版本对插件作用点的影响。
8
+
9
+ | dsh 版本 | 内部形态与影响 |
10
+ |---|---|
11
+ | 0.1.0-rc.6 / rc.7 / rc.8 | fork 走 `prepare(seedSource)` 通道;initFor 为 `structuredClone` 形态。rc.8 起 initFor 上游原生零拷贝,fast initFor 补丁退役 |
12
+ | 0.1.1-rc.1 / rc.2 | 同 alpha.5 形态;rc.2 的 `encodeMaterialization` 是 `(meta, events)` 裸 meta 签名 |
13
+ | 0.1.2-alpha.5 / 0.1.2-rc.1 | 新增 `snapshotEvents()`;fork meta 带 `isSeeded` + `inheritedEventCount`;签名变 `(storage, events)` |
14
+ | 0.1.3-alpha.2 | **persistence 子系统重写**(见下);投影缓存 identity 新增 `formatVersion` |
15
+ | 0.1.5-alpha.1 | 会话格式 **v3**(`SESSION_FORMAT_VERSION=3`);cut/header 语义同 v2,文件名 `session.v3.jsonl` |
16
+ | 0.1.5-rc.1 | 仅新增已知事件类型(`deliverables/presented`、`subagent/catalog`) |
17
+ | 0.1.5-rc.2 | 相对 rc.1 四个关键包源码零差异(仅版本号) |
18
+ | 0.1.7-rc.1 | fork 内部重构:`_forkSeed` → `_forkBoundary` + 模块级 `buildForkSeed`;**open-turn 边界从「拒绝」改为「注入 turn 闭合事件」**;会话格式升 **v4**;事件词汇收紧(`tool/result` role=`tool`、新增 `system/message`) |
19
+ | 0.1.7-rc.2 | 新增 `toolHistory()` 增量折叠;persistence/format/projection-cache 零差异 |
20
+ | 0.2.0-rc.2 | 主版本升级。**插件管理引入严格 peer 门禁**;`dsh-session` 重构 tail-repair 并新增 `ToolCallRecovery`(`buildForkSeed`/`SESSION_FORMAT_VERSION` 等导出不变);`llm-deepseek` 拆分为 `-api-key` / `-account` |
21
+ | 0.2.1-rc.1 | **门禁新增 `engines.dsh` 且独占生效**;jsonl 后端改为 cordis 插件形态(只导出 `name`/`apply`/`Config`);persistence 改为 `ctx.effect` 资源生命周期 + `listConcurrency` 限流;`dsh-session` 新增 `appendPluginRecord`/`pluginRecordOf`;投影 `register()` 直接返回 effect。**fork 通道与四个补丁点源码零变化** |
22
+ | 0.2.1-rc.2 | **纯版本刷新**:13 个关键包 rc.1→rc.2 逐字节相同。唯一实质变更在 `dsh-web-app/cordis.patch.yml`(新增 `ui-deepseek-model-label-1017` 条目),不触及任何作用点 |
23
+ | 0.2.1-rc.3 | **纯版本刷新**:17 个关键包 rc.2→rc.3 逐字节相同(含 `dsh-session`、三个 persistence/projection 包、`dsh-app-boot`、`dsh-web-app`、`dsh-plugin-manager`、`dsh-session-format-*`、`dsh-working-directory`、`dsh-agent-preset-registry` 等);依赖集 86 项无增删;305 个已装包全部对齐 rc.3。插件侧仅需登记版本 |
24
+
25
+ ## 0.1.3-alpha.2 的关键变化
26
+
27
+ 这次是破坏面最大的一次,四个作用点全部需要重新适配:
28
+
29
+ - `dsh-session-persistence` 变为 contract 层(`SessionPersistence` 基类 + 错误 + 校验),
30
+ `PersistenceCoordinator` / `initFor` / `SessionPreparations` 移除;JSONL 实现移入
31
+ `dsh-session-persistence-jsonl`。
32
+ - 格式 v0→v1→v2 迁移链(`dsh-session-format` 系列);物理 header 移除 `seedLength`,
33
+ seed cut 改由事件流尾 `session/end-seed {inherited:true}` marker 表达(`cut == marker.seq`);
34
+ 文件名变 `session.vN.jsonl[.zstd]`。
35
+ - `encodeMaterialization(meta, events)` → `(meta, inheritedEventCount, events)`。
36
+ - 事件词汇收紧:`assistant/message` 需 `data.stream`,消息需 `id/source/role` 校验。
37
+
38
+ ## dsh 版本门禁
39
+
40
+ dsh 0.2.0 起,profile bundle 加载前会校验插件 `package.json` 的 `peerDependencies`
41
+ 中**所有 `@deepseek-ai/dsh` / `@deepseek-ai/dsh-*` 项**:运行时版本不满足 range 即
42
+ **跳过整个插件**(启动日志打 `skipping profile bundle "<name>": ... incompatible`),
43
+ 可用 `dsh plugin allow-version <pkg>@<ver> --profile web` 做精确版本豁免。
44
+
45
+ **0.2.1 新增 `engines.dsh`,且它独占生效**:只要插件声明了 `engines.dsh`,门禁就
46
+ **只**看它,`peerDependencies` 里的 dsh 项被完全忽略(`unmetRequirements()` 的
47
+ `if (Object.hasOwn(engines, "dsh"))` 分支直接返回)。
48
+
49
+ 本插件的声明:
50
+
51
+ ```json
52
+ "engines": {
53
+ "node": ">=22.15.0",
54
+ "dsh": ">=0.1.0-rc.6 <0.3.0-0"
55
+ },
56
+ "peerDependencies": {
57
+ "@deepseek-ai/cordis": "^4.0.1",
58
+ "@deepseek-ai/dsh": ">=0.1.0-rc.6 <0.3.0-0",
59
+ "@deepseek-ai/dsh-session": ">=0.1.0-rc.6 <0.3.0-0"
60
+ }
61
+ ```
62
+
63
+ - 只声明真实依赖:`dsh-session`(`snapshotEvents` / `buildForkSeed` / `SessionLogOffset`)
64
+ 与 `dsh`(版本探针)。
65
+ - 上界 `<0.3.0-0` 覆盖整个 0.1.x–0.2.x 线:既让门禁守住兼容边界,又不会在小版本推进时
66
+ 误杀自己。**升级 dsh 大版本(0.3+)前必须先更新此范围。**
67
+ - 保留 `peerDependencies` 是为旧版本兜底:0.2.0 及之前不认 `engines.dsh`,只读 peer
68
+ 范围。两处保持一致即可同时满足两代门禁。
69
+ - `verify_compat.mjs` 直接 `import` 宿主真实的 `evaluatePluginCompatibility` 实算一遍,
70
+ 拿不到该导出时回退 semver 离线复算。报警 = 重启后必被跳过。
71
+
72
+ ## 插件清单规范
73
+
74
+ dsh 从插件 `package.json` 读取两类信息,缺一不可:
75
+
76
+ | 用途 | 字段 | 说明 |
77
+ |---|---|---|
78
+ | **加载** | `dsh.bundle.patch` | bundle patch 文件路径(或路径数组),profile 加载时按序应用;缺它插件不会被当作 bundle |
79
+ | **展示** | `icon` + `./locale/*.json` | 插件管理器与设置页的标题、描述、图标;缺了会退化为裸包名 |
80
+
81
+ 本插件的完整声明:
82
+
83
+ ```json
84
+ "icon": "./icon.svg",
85
+ "exports": {
86
+ ".": { "default": "./lib/index.js" },
87
+ "./locale/*.json": "./locale/*.json",
88
+ "./package.json": "./package.json",
89
+ "./cordis.patch.yml": "./cordis.patch.yml"
90
+ },
91
+ "files": ["lib", "scripts", "locale/*.json", "icon.svg", "cordis.patch.yml", "README.md", "CHANGELOG.md", "LICENSE"],
92
+ "dsh": { "bundle": { "patch": "./cordis.patch.yml" } }
93
+ ```
94
+
95
+ `locale/en.json` / `locale/zh.json` 结构与官方 bundle 一致:
96
+
97
+ ```json
98
+ { "meta": { "title": "…", "description": "…" } }
99
+ ```
100
+
101
+ 注意 `exports` 必须显式暴露 `./locale/*.json` 与 `./cordis.patch.yml`——宿主通过
102
+ `optionalResourcePath("<pkg>/locale/en.json")` 走 Node 解析读取,未导出的子路径读不到。
103
+
104
+ 图标走 `icon` 字段(相对路径,须在包目录内,≤256 KiB,支持 SVG/PNG/JPEG/WebP),
105
+ 也可改用 `exports` 暴露的 `./icon` 资源。
106
+
107
+ ## 升级 dsh 后的检查清单
108
+
109
+ 1. 启动日志确认 `dsh version: x.y.z (verified)`,且无 `signature mismatch` 与
110
+ `skipping profile bundle`。
111
+ 2. `node tests/verify_compat.mjs` 全通过。
112
+ 3. `npm test` 全通过(含真实组件 e2e)。
113
+ 4. 若新版本不在 `KNOWN_DSH_VERSIONS`,按需登记(`lib/runtime.js` 与
114
+ `tests/verify_compat.mjs` 各一份)。
115
+ 5. 实机启动 web profile,`stats.get` 确认各点位为预期的 `active` / `inactive`。
package/icon.svg ADDED
@@ -0,0 +1,14 @@
1
+ <svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <g transform="translate(1.5 1.5) scale(0.9166666667)">
3
+ <rect x="4" y="8" width="28" height="5" rx="2.5" fill="url(#a)" fill-opacity="0.9" />
4
+ <rect x="4" y="15.5" width="21" height="5" rx="2.5" fill="url(#a)" fill-opacity="0.65" />
5
+ <rect x="4" y="23" width="14" height="5" rx="2.5" fill="url(#a)" fill-opacity="0.4" />
6
+ <path d="M27 21.5L31 25.5L27 29.5" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
7
+ </g>
8
+ <defs>
9
+ <linearGradient id="a" x1="4" y1="8" x2="4" y2="30" gradientUnits="userSpaceOnUse">
10
+ <stop stop-color="#5EEAD4" />
11
+ <stop offset="1" stop-color="#2B7FFF" />
12
+ </linearGradient>
13
+ </defs>
14
+ </svg>
package/lib/runtime.js CHANGED
@@ -10,7 +10,7 @@ export const TAG = '[dsh-perf]'
10
10
 
11
11
  // 插件开发/验证过的 dsh 版本。运行时探针检测到列表外版本时打告警,并提示跑
12
12
  // tests/verify_compat.mjs 确认补丁特征是否仍匹配(特征校验会自动跳过,但会静默失效)。
13
- export const KNOWN_DSH_VERSIONS = ['0.1.0-rc.6', '0.1.0-rc.7', '0.1.0-rc.8', '0.1.1-rc.1', '0.1.1-rc.2', '0.1.2-alpha.5', '0.1.2-rc.1', '0.1.3-alpha.2', '0.1.5-alpha.1', '0.1.5-rc.1', '0.1.5-rc.2', '0.1.7-rc.1', '0.1.7-rc.2', '0.2.0-rc.2']
13
+ export const KNOWN_DSH_VERSIONS = ['0.1.0-rc.6', '0.1.0-rc.7', '0.1.0-rc.8', '0.1.1-rc.1', '0.1.1-rc.2', '0.1.2-alpha.5', '0.1.2-rc.1', '0.1.3-alpha.2', '0.1.5-alpha.1', '0.1.5-rc.1', '0.1.5-rc.2', '0.1.7-rc.1', '0.1.7-rc.2', '0.2.0-rc.2', '0.2.1-rc.1', '0.2.1-rc.2', '0.2.1-rc.3']
14
14
 
15
15
  // cordis 环境下对缺失服务调用 ctx.get 可能抛错——统一安全取服务
16
16
  export const safeGet = (ctx, name) => {
package/lib/warmup.js CHANGED
@@ -57,7 +57,10 @@ export function createWarmup(pc) {
57
57
  const row = checkpointRows[key]
58
58
  const usable = row !== void 0 && row.ver === def.stateVersion
59
59
  const unitBaseSeq = usable ? row.seq : -1
60
- let state = usable ? row.val : def.init()
60
+ // init 的上游签名是 init(header, inheritedEventCount):多个真实投影
61
+ // (agentPreset / workingDirectory / agentTeam)从 header 取值,无参调用
62
+ // 会直接抛 TypeError。缓存行可用时按上游 restore() 语义直接用 row.val。
63
+ let state = usable ? row.val : def.init(session.header, session.inheritedEventCount)
61
64
 
62
65
  let i = 0
63
66
  if (usable) {
@@ -79,7 +82,10 @@ export function createWarmup(pc) {
79
82
  const liveReg = registry.registrations.get(key)
80
83
  if (liveReg !== registration || liveReg.cells.get(session) !== void 0) return { status: 'skip', baseSeq: unitBaseSeq }
81
84
  const observedSeq = events.at(-1)?.seq ?? -1
82
- liveReg.cells.set(session, { state, observedSeq })
85
+ // cell 必须与上游 buildCell 同形:drive()/advanceCell() 会读写 views[0]/[1],
86
+ // 缺该字段会在下一条事件到达时抛 TypeError(Cannot read properties of
87
+ // undefined (reading '1'))。
88
+ liveReg.cells.set(session, { state, observedSeq, views: [void 0, void 0] })
83
89
  return { status: 'folded', baseSeq: unitBaseSeq }
84
90
  }
85
91
 
package/locale/en.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Large Project Performance",
4
+ "description": "Keeps fork, cold-session loading, and persistence of very large sessions off the event loop: zero-copy fork, chunked projection warmup, fork cache backfill, chunked materialize, and cold-session memory governance."
5
+ }
6
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "大会话性能优化",
4
+ "description": "让 fork、冷会话加载与超大会话落盘不再阻塞事件循环:零拷贝 fork、投影分片预热、fork 缓存回填、分片 materialize 与冷会话内存治理。"
5
+ }
6
+ }
package/package.json CHANGED
@@ -1,17 +1,19 @@
1
1
  {
2
2
  "name": "dsh-large-proj-perf",
3
- "version": "1.3.0",
3
+ "version": "1.3.3",
4
4
  "description": "DSH 大会话性能插件:零拷贝 fork + 投影分片预热 + fork 缓存回填 + 分片 materialize + 冷会话内存治理 + heap 检测(DeepSeek Harness / DSH)",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
+ "icon": "./icon.svg",
7
8
  "engines": {
8
- "node": ">=22.15.0"
9
+ "node": ">=22.15.0",
10
+ "dsh": ">=0.1.0-rc.6 <0.3.0-0"
9
11
  },
10
12
  "publishConfig": {
11
13
  "access": "public"
12
14
  },
13
15
  "scripts": {
14
- "test": "node tests/verify_compat.mjs && node tests/smoke_fork.mjs && node tests/test_fast_initfor.mjs && node tests/test_cache_trim.mjs && node tests/smoke_warmup.mjs && node tests/test_chunked_materialize.mjs && node tests/test_backfill.mjs && node tests/e2e_real_components.mjs",
16
+ "test": "node tests/run.mjs",
15
17
  "prepublishOnly": "npm test",
16
18
  "pack:check": "npm pack --dry-run"
17
19
  },
@@ -19,15 +21,20 @@
19
21
  ".": {
20
22
  "default": "./lib/index.js"
21
23
  },
22
- "./package.json": "./package.json"
24
+ "./locale/*.json": "./locale/*.json",
25
+ "./package.json": "./package.json",
26
+ "./cordis.patch.yml": "./cordis.patch.yml"
23
27
  },
24
28
  "files": [
25
29
  "lib",
26
30
  "scripts",
31
+ "locale/*.json",
32
+ "icon.svg",
27
33
  "cordis.patch.yml",
28
34
  "README.md",
29
35
  "CHANGELOG.md",
30
- "LICENSE"
36
+ "LICENSE",
37
+ "docs"
31
38
  ],
32
39
  "license": "MIT",
33
40
  "repository": {
@@ -1,46 +0,0 @@
1
- #!/usr/bin/env bash
2
- # 安全写入 npm 发布用 token 到 ~/.npmrc(token 不回显、不进 shell 历史)。
3
- #
4
- # 用法:
5
- # bash scripts/set-npm-token.sh
6
- # 然后按提示粘贴 token(输入不可见),回车确认。
7
- #
8
- # 前置:在 https://www.npmjs.com/settings/<用户名>/tokens 生成
9
- # **Granular Access Token** 并勾选 "Bypass 2FA"(或选 Automation 类型),
10
- # 权限至少包含 dsh-large-proj-perf 的 Read and write。
11
- set -euo pipefail
12
-
13
- NPMRC="${HOME}/.npmrc"
14
- REGISTRY_LINE='//registry.npmjs.org/:_authToken='
15
-
16
- printf '粘贴 npm token(输入不可见,回车确认): '
17
- IFS= read -rs TOKEN
18
- printf '\n'
19
-
20
- if [ -z "${TOKEN}" ]; then
21
- echo "错误:token 为空,未做任何修改。" >&2
22
- exit 1
23
- fi
24
- case "${TOKEN}" in
25
- npm_*|*[![:space:]]*) : ;;
26
- *) echo "错误:token 含空白字符,疑似误粘贴。" >&2; exit 1 ;;
27
- esac
28
-
29
- # 备份现有 npmrc(若存在)
30
- if [ -f "${NPMRC}" ]; then
31
- cp "${NPMRC}" "${NPMRC}.bak-$(date +%Y%m%d-%H%M%S)"
32
- # 移除旧的 registry.npmjs.org token 行(保留其他 registry 配置)
33
- grep -v '^//registry.npmjs.org/:_authToken=' "${NPMRC}" > "${NPMRC}.tmp" || true
34
- mv "${NPMRC}.tmp" "${NPMRC}"
35
- fi
36
-
37
- printf '%s%s\n' "${REGISTRY_LINE}" "${TOKEN}" >> "${NPMRC}"
38
- chmod 600 "${NPMRC}" 2>/dev/null || true
39
-
40
- echo "已写入 ${NPMRC}(权限 600)。"
41
- echo "校验身份:"
42
- npm whoami --registry=https://registry.npmjs.org || {
43
- echo "身份校验失败——token 可能无效或权限不足。" >&2
44
- exit 1
45
- }
46
- echo "完成。现在可以执行:npm publish --registry=https://registry.npmjs.org"