dsh-world 0.6.4 → 0.6.5

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/CONTRIBUTING.md +161 -161
  3. package/INSTALL.md +314 -311
  4. package/README.md +18 -13
  5. package/client/index.js +20 -1
  6. package/lib/compactionSwap.d.ts +29 -0
  7. package/lib/compactionSwap.js +56 -0
  8. package/lib/console/routes.d.ts +2 -0
  9. package/lib/console/routes.js +6 -0
  10. package/lib/console/snapshot.d.ts +1 -0
  11. package/lib/console/snapshot.js +1 -0
  12. package/lib/ecosystem/qq-native/home.js +2 -2
  13. package/lib/ecosystem/shared/context.d.ts +2 -1
  14. package/lib/ecosystem/shared/context.js +8 -1
  15. package/lib/ecosystem/wechat/home.js +4 -4
  16. package/lib/ecosystem/wechat/index.js +13 -4
  17. package/lib/ecosystem/wechat/messages.js +1 -1
  18. package/lib/ecosystem/wechat/reading.d.ts +9 -0
  19. package/lib/ecosystem/wechat/reading.js +61 -12
  20. package/lib/ecosystem/wechat/sending.d.ts +1 -0
  21. package/lib/ecosystem/wechat/sending.js +3 -0
  22. package/lib/ecosystem/wechat/stickerLibrary.d.ts +50 -0
  23. package/lib/ecosystem/wechat/stickerLibrary.js +167 -0
  24. package/lib/ecosystem/wechat/stickerTools.d.ts +18 -0
  25. package/lib/ecosystem/wechat/stickerTools.js +69 -0
  26. package/lib/index.d.ts +3 -1
  27. package/lib/index.js +9 -2
  28. package/lib/life/activity.js +5 -0
  29. package/lib/life/runtime.d.ts +2 -0
  30. package/lib/life/runtime.js +4 -2
  31. package/lib/runtime.js +36 -37
  32. package/lib/types.d.ts +2 -0
  33. package/lib/worldSwitch.d.ts +7 -1
  34. package/lib/worldSwitch.js +12 -1
  35. package/package.json +115 -114
  36. package/scripts/import-stickers.mjs +17 -0
  37. package/src/ecosystem/wechat/windows/README.md +22 -5
  38. package/src/ecosystem/wechat/windows/adapter.py +24 -5
  39. package/src/ecosystem/wechat/windows/groups.py +2 -2
  40. package/src/ecosystem/wechat/windows/native.py +4 -3
  41. package/src/ecosystem/wechat/windows/outgoing.py +14 -7
  42. package/src/ecosystem/wechat/windows/reading.py +58 -1
  43. package/src/ecosystem/wechat/windows/snapshot.py +8 -0
  44. package/src/ecosystem/wechat/windows/stickers.py +69 -60
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # 更新日志
2
2
 
3
+ ## 0.6.5 — 2026-10-30
4
+
5
+ 本版修复记忆块刷新后埋在长历史中间、attach 与每步退役写出坏日志和微信发送前快照超时卡进人工核对三个问题,减少微信群上下文的 token 开销,新增按需看群图片和插件自带的表情库,并让每日回顾后自动压缩、装上插件默认换用只留对话的压缩。
6
+
7
+ - 修复记忆块刷新后模型“看不见”的问题(#21):旧块原地替换会让新块一直留在历史中间,长会话里被淹没;现在退役旧块,由正常的追加路径把新块放在末尾。回放用户提供的真实日志验证:刷新后只有一个非空记忆块,位于请求末尾。
8
+ - 修复退役记忆块写出无法重载的日志(#22):宿主只在 turn 和 step 都打开时接受空的 `system/message`,而 attach 发生在两轮之间、pre-step 发生在 `step/start` 之前。现在 attach 不再写事件,退役改在 `agent/request` 钩子里写,仍早于请求组装。新增带磁盘重载的回归测试。已经写坏的旧日志不会被自动修复。
9
+ - 微信发送前读不到稳定快照时自动重试(#23):只在一个回复的第一条、真正发送之前超时才识别为“确定未发送”,撤销本次尝试写下的状态并由家重新派发,最多 3 次,之后回到原来的“需要人工核对”。发送之后的确认阶段和第 2 条起的超时保持原有保守处理。
10
+ - 微信群上下文省 token:默认条数由 8 改为 5(控制台仍可调 1–20,没设置过的用户升级后生效);上下文渲染把渠道、账号和会话信息只写一次,每条只留发言人来源,同样输入约少 45%。QQ 共用同一渲染。
11
+ - 新增 `wechat_image`:`wechat_history` 里的群图片先显示为未读取,想看哪张用该条的 `id` 调用;适配器只在已配置群聊按本机缓存读取这一张,作为下一条消息附给她,不推进游标也不清未读。
12
+ - 新增插件自带的表情库(世界目录 `stickers/`),不再依赖 DeepSeek-meme 插件和 `stickerDir`:`sticker_search`(按名称、标签查,每次只返回几条,不回传图片)、`sticker_save`(私聊或群里别人发来的表情包,按消息 ID 取原始动图并核对 md5,同图去重)、`sticker_edit`、`sticker_remove`。每张只有名称和标签,没有分组。发送仍用 `wechat_send`,表情不在库里时排队就被拒绝。旧库用 `scripts/import-stickers.mjs` 导入一次,QQ 仍用自己的收藏表情。
13
+ - 文档说明微信里 `@昵称` 只是普通文字,不会真的 @ 到人:原生发送没有 @ 列表,靠操作窗口输入会抢焦点,所以不做。
14
+ - 每日回顾后自动压缩:她在晚上 23:00 的回顾里会改写「我是谁」,而系统提示只在压缩之后才重新读取,新版要等到下一次压缩才生效。现在回顾那一轮递给她、家空闲后,自动执行一次「应用并压缩家会话」,让新版随压缩一起生效(压缩本来就要重建缓存,不多破坏一次),压缩后最近的聊天作为记忆块重新放回她眼前。默认开,控制台概览的「回顾后自动压缩」可以关;闹钟、问候和递送失败都不会触发。每晚会多一次摘要调用。
15
+ - 装上插件默认换用只留对话的压缩:插件在某个 profile 里第一次启动时,把 `cordis.patch.yml` 里原版压缩那一行换成 `dsh-world/compaction`(同目录留 `.bak` 备份,原子替换,重启 DSH 后生效,世界面板健康状态提示)。只在恰好找到一条原版配置时才改,每个世界只试一次,之后手动改回去不会再被换;配置里写 `replaceCompaction: false` 可以关掉。这会让整个 profile 的压缩都去掉工具调用过程,写代码等其他工作区会话也一样;原来的手动脚本保留。
16
+ - 验证:Node 444 项、Python 122 项测试通过。在 Windows 微信 4.1.15.56 上只读验证了群图片按 ID 读取、群与私聊表情取回和 38 张导入表情转发送格式,并向私聊发送一个库里的表情,服务器确认为 sent。发送前快照超时重试只做了离线测试,`wechat_image`、`sticker_save` 的模型端到端尚待实机。
17
+
3
18
  ## 0.6.4 — 2026-10-29
4
19
 
5
20
  本版恢复相册,新增家会话提示词应用入口和 QQ SDK 按需安装,并修复微信独立发送被待核对记录阻塞的问题。
package/CONTRIBUTING.md CHANGED
@@ -1,161 +1,161 @@
1
- # 贡献协议
2
-
3
- 欢迎为 dsh-world 接入新的聊天平台、桌宠或其他外部程序,也欢迎改进内核。生态模块负责平台收发,复用现有身份、会话和归档能力,依赖一律可选;涉及内核的修改必须单独说明,改动生活线核心之前先开 Issue。
4
-
5
- ## 分支与 PR
6
-
7
- **所有 PR 统一提到 `dev` 分支**,不要直接提到 `main`。
8
-
9
- - `dev`:集成分支。从最新的 `dev` 切出你的分支,改完后向 `dev` 提 PR;维护者在这里合并、验证。
10
- - `main`:稳定分支,由维护者把 `dev` 的内容并入后发布,不直接接收 PR。
11
- - `test`:维护者本机试用的分支,不接收 PR,也不要基于它开发。
12
- - 已经把 PR 提到 `main` 的,请在 PR 页面右上角 **Edit** 里把 base 改成 `dev`;GitHub 默认会选 `main`,提交前请再看一眼目标分支。
13
- - 提 PR 前至少运行 `npm run typecheck` 和 `npm test`,PR 描述按下文「提交说明」填写,并写明是否涉及内核。
14
- - **一个 PR 只做一件事。** PR 开着期间只修这件事的问题,新功能另开 PR;不要在已开的 PR 上持续追加功能,也不要把已关闭 PR 的内容带进别的 PR。基于过旧分支、与当前版本大量冲突的 PR,维护者可能只采用其中的思路自行重写。
15
-
16
- ## 新模块放在哪里
17
-
18
- | 位置 | 职责 |
19
- | --- | --- |
20
- | `src/ecosystem/<channel>/` | 平台自己的接口、消息解析、收发编排、配置和说明文档。例如 QQ 放 `qq/`,B 站放 `bilibili/` |
21
- | `src/ecosystem/<channel>/windows/` 等平台子目录 | 确实需要的本机适配器、启动脚本或原生桥;目录按实际运行环境新增 |
22
- | `src/ecosystem/shared/` | 已有的通用身份、群上下文、会话路由和消息出口;新增公用函数应有实际复用方 |
23
- | `src/ecosystem/bridge/` | 桌宠等本地程序共用的桥;能使用现有协议时直接接入 |
24
- | `tests/` | TypeScript 回归测试;本机适配器测试可与适配器放在一起,按微信模块现有方式组织 |
25
- | `.host/<channel>/` | 本机配置、凭据、依赖环境、运行状态和构建产物,不提交、不打入发布包 |
26
-
27
- 每个平台在自己的目录提供 `README.md`,写清依赖、支持的消息类型、启停和配置方法、身份参数映射、验证范围及平台风险。同步更新 [生态目录说明](src/ecosystem/README.md) 和根 [介绍文档](README.md) 的入口。QQ、B 站目录是新增位置示例,不表示当前已接入。
28
-
29
- 需要新增配置、注册模块或清理资源时,在 `src/index.ts`、`src/types.ts` 等现有位置做必要的接线;平台解析和收发逻辑仍留在自身模块中。模块应能独立启停,关闭后释放监听、路由和连接,不影响其他生态。
30
-
31
- ## 统一身份、会话与记忆
32
-
33
- - 复用 `shared/identity.ts` 的 `SpeakerIdentity` 和校验函数,明确映射 `channel`、`accountId`、`conversationId`、`speakerId`;按平台能力提供聊天类型、消息 ID、显示名和真实 @ 列表。
34
- - ID 使用字符串,`speakerId`、`mentionIds` 使用 `<channel>:<平台用户 ID>`。身份来自经过校验的平台事件或账号数据,不从消息正文、昵称或模型输出推断,不因昵称相同合并用户。
35
- - 外部发言人保持 `role: user`,用结构化身份区分来源。群触发使用平台真实 @ 元数据,手打 `@昵称` 不等同于真实 @。
36
- - 会话按平台、登录账号、聊天类型和会话 ID 区分;群会话复用 `shared/sessions.ts`,放在家所在的工作区。平台模块不另建一份人设或共享记忆。
37
- - 正文通过现有归档服务保存,遵守 `home` / `workspace` 范围、配置轮数、消息实际时间排序和重启生效规则。普通工具参数、查询回执、群图片上下文不直接写入共享记忆;群图片保留在对应会话日志中。
38
- - 接收、投递和归档状态应明确区分;排队成功不能当作发送成功。重连保留去重游标和确认状态,结果不明的发送不能直接重试。
39
-
40
- 以上约定如需改变,应按下节说明内核影响,不能在某个平台内悄悄绕过。
41
-
42
- ## 世界的作用范围
43
-
44
- 她的世界只在房间工作区内生效。记忆块、世界提示词、世界线工具、各平台通道和归档,只进入工作区内匹配预设的普通会话;工作区外只允许两项影响:人设(映射到系统提示词)和启用后的只留对话压缩。新增的注入、工具和事件处理必须遵守这条边界:
45
-
46
- - 判断会话是否属于她的世界,用 `sourceArchive.ts` 的 `inWorld`;拦截、守卫和重新组装分别用 `src/worldScope.ts` 的 `admit`、`scopedTool`、`reassembleOnChange`。每次等待之后、注入之前都要重新判断,不能只在开始时查一次。
47
- - 监听 `session/event` 等全局事件时,先筛出她的会话(家会话或工作区内的会话),不能对所有会话生效。宿主里同时还有用户的其他工作,比如写代码的会话,插件不能读取、推送或改动它们。
48
-
49
- ## 生活线与她的主动性
50
-
51
- dsh-world 养的是一个伙伴,不是一个按日程执行的助手。**她的主动性来自她自己的安排,而不是插件推送的提醒。**
52
-
53
- - 生活线只有一个闹钟:她用 `plan_next` 说好做什么、做到几点,到点由她决定接着做还是换下一件。插件自己唤醒她的,只有新家的第一句话、重新开始的那句话、每日回顾和早上的兜底闹钟。不要另建第二套日程、习惯或提醒系统。
54
- - 插件不往对话里推提醒、清单或定时消息,尤其不能插在伙伴说话的那一轮前面。想让她留意某件事,应该给她能感知的事实(比如在她自己的闹钟消息里附上状态),去不去看、做不做,由她决定。
55
- - 工具给她能力,不替她做决定,也不替她读写自己的东西:日记、我是谁和房间里的文件,由她用宿主自带的文件工具读写(`read` 支持 `offset` / `limit`,`grep` 返回行号)。日记、我是谁和聊天记录的检索与索引,计划和记忆总归档一起设计,不接受只管单一来源的检索工具。
56
- - 已经移除的生活线功能(例如 `read_diary`、QQ 的“主动开口”引擎),不能在 PR 里直接加回来,要先开 Issue 说明为什么现有方式不够。
57
-
58
- 改动闹钟、家会话、每日回顾、世界提示词、记忆块注入,或者给她新增工具,都属于生活线核心改动:**先开 Issue 讨论,再写代码**,并在 PR 里按下节说明内核影响。
59
-
60
- ## 涉及内核时必须说明
61
-
62
- 这里的“内核”指 dsh-world 的公共行为,不仅指某个目录:
63
-
64
- | 影响区域 | 现有位置举例 |
65
- | --- | --- |
66
- | 记忆生成、归档范围、历史回填、存储和压缩 | `src/runtime.ts`、`archive*.ts`、`sourceArchive.ts`、`store.ts`、`history.ts`、`compaction*.ts` |
67
- | 世界的作用范围(工作区) | `src/worldScope.ts`、`archiveSelection.ts`、`workspacePermissions.ts` |
68
- | 人设、世界提示、时间上下文和世界开关 | `src/persona.ts`、`worldPrompt.ts`、`timeContext.ts`、`worldSwitch.ts` 等 |
69
- | 家会话、生活线、每日回顾和宿主适配 | `src/life/`、`src/hostContract.ts` |
70
- | 公共协议、配置、模块生命周期和控制台 | `src/ecosystem/shared/`、`src/index.ts`、`src/types.ts`、`src/console/`、`client/` |
71
-
72
- 提交说明中必须写明“是否涉及内核”。新增平台的配置与注册接线也要列出,说明是否改变已有行为。涉及内核时补充:
73
-
74
- 1. 修改了哪些文件、接口或数据格式,为什么现有扩展点不足以完成接入。
75
- 2. 修改前后的行为,以及对家、其他平台、归档范围、人设、生活线和权限的影响;不受影响的部分可合并说明。
76
- 3. 旧配置、旧会话、归档和待发送队列如何兼容;是否需要迁移或重启,升级失败如何保留原数据。
77
- 4. 对应回归测试、验证结果,以及未验证的宿主版本、平台或真实账号条件。
78
-
79
- 本项目内核与 DSH 宿主需要区分。若方案还依赖修改 DSH 本身,应另外列出宿主版本、补丁或依赖、安装方式和兼容范围,不能把它描述为插件即可独立使用。
80
-
81
- ## 代码设计
82
-
83
- - **先找已有的,再写新的。** 各平台“送一句话、等她这一轮的回复”用 `ecosystem/shared/turn.ts` 的 `askTurn` 和 `createChannelQueue`(排队、超时、取消);群上下文用 `shared/context.ts`;身份用 `shared/identity.ts`;作用范围见上文。不要在模块里再抄一份串行队列、回复判断或超时逻辑;已有实现不够用时,改它并说明原因。
84
- - **按职责拆文件。** 一个文件只负责一件事,解析、排队、重试、编排分开放。模块入口只做接线,不要把所有功能堆进一个几百行的 `index.ts`。
85
- - **不留历史痕迹。** 删除或替换功能时,连同配置、文档、测试、注释和面板控件一起清理;不留孤立注释、只写不读的变量和不再生效的配置项。
86
- - **用结构判断,不靠巧合。** 判断“这一轮是谁开的、回给谁”,看事件来源和会话归属,不要用“现在有没有正在处理的请求”“几秒内发过同样的文字”这类旁证去重或补救。
87
- - **配置和面板一致。** 面板上能改的设置要写进同一份覆盖文件,一次请求里给的几项一起生效;新增配置项同步更新 `src/types.ts`、配置 schema、README 的配置示例和对应文档。
88
-
89
- ## 外部依赖
90
-
91
- 插件主体必须在只装必要依赖的情况下装得上、跑得起来。生态模块一律按“可选”设计:
92
-
93
- - **默认不加新依赖。** 能用宿主服务、Node 内置模块或已有依赖完成的,不引入新包。确实需要时,先开 Issue 说明用途、体积、许可证和替代方案,得到同意再提交。
94
- - **平台 SDK 只能是可选依赖。** 由使用者另行安装的 SDK 声明为可选 peer(`peerDependenciesMeta.optional: true`),开发测试所需版本放在 `devDependencies`。只在模块被拨开时动态 `import`;模块默认关闭,没装 SDK 时只显示该模块不可用,不能影响插件本身和其他模块。不要通过安装脚本修补 SDK 文件。
95
- - **不自动安装或部署第三方程序。** 涉及启动外部程序(例如 SnowLuma)的功能,在模块文档和 PR 中写明其许可条款允许的方式,以及是否默认开启。
96
- - **SDK 按需安装必须由使用者明确开启模块触发。** 复用宿主包管理操作与 profile 写锁,禁用生命周期脚本,不修改 SDK 文件或自动升级 Node;安装失败保留原 profile 配置,安装后需要重启时明确提示。客户端程序的安装与登录仍由使用者处理。
97
-
98
- ## 实现与验证
99
-
100
- - 优先复用已有函数、宿主服务和依赖,通过参数或现有注入机制传入依赖。平台细节留在模块内,不为单个平台新增通用框架;替换旧实现时清理失效代码。
101
- - 查询接口搭配分页设计;配置、ID、路径和外部消息在边界校验,不硬编码真实账号或本机路径。错误应明确返回并保留必要状态,不吞错或伪报成功;成组的数据变更保持事务一致性。
102
- - 确认 `package.json` 的 `files`、导出和构建配置覆盖必要产物,同时排除凭据、数据库、聊天记录和个人媒体。
103
- - 代码修改按影响运行 `npm run typecheck`、`npm test`、`npm run build`,补充相关适配器测试。**每个新功能都要有回归测试**,非平凡逻辑至少一个有意义的用例;只改断言让旧测试通过不算覆盖新行为。测试不得启动或连接本机真实的第三方程序。仅文档修改检查格式与链接即可。
104
- - 真实账号测试与单元测试分开说明:使用什么环境、实际确认了什么、哪些条件尚未验证。涉及第三方协议或客户端内部调用时,在模块文档中说明版本限制、账号及数据风险,不能宣称保证不触发风控。
105
-
106
- ## CI 与安装包验证
107
-
108
- [CI](.github/workflows/ci.yml) 在 PR、`main` 推送和手动触发时运行 Windows / Linux 两条完整检查任务,使用满足开发测试 SDK 要求的 Node 24.22.1。两端执行禁用安装脚本的干净安装、类型检查、全部 Node 回归测试、编译和安装包验证;Windows 另用 Python 3.11 安装固定的微信适配器依赖并跑离线测试。另有 Node 22.16.0 核心入口检查,确认 SDK 不可用时核心仍能加载。相同分支的新运行会取消旧运行,各项独立报告结果。
109
-
110
- 本机可按以下 PowerShell 命令复现 Node 与安装包检查:
111
-
112
- ```powershell
113
- npm ci --ignore-scripts
114
- npm run typecheck
115
- npm test
116
- npm run build
117
- New-Item -ItemType Directory -Force artifacts | Out-Null
118
- npm pack --ignore-scripts --pack-destination artifacts
119
- npm run test:package
120
- ```
121
-
122
- 安装与打包均禁用生命周期脚本,类型检查和编译由前面的命令显式执行。`test:package` 在本仓库 `.host/` 下创建临时消费项目,安装真实 `.tgz` 及其生产依赖,复用父目录提供的 DSH 宿主依赖;先确认未安装 SDK 时核心入口可加载,再另行安装 SDK,检查编译后 QQ 驱动的本地 WebSocket 连接,全程不修补 SDK 文件,结束后清理临时项目。检查不需要 DSH profile、微信进程或真实账号。
123
-
124
- ### npm 发布(维护者)
125
-
126
- 使用上述检查生成的同一个 `.tgz` 发布,避免发布时重新构建出不同内容。包已通过 `publishConfig` 指定 npm 官方源和公开访问;先完成发布预检:
127
-
128
- ```powershell
129
- npm publish .\artifacts\dsh-world-0.6.4.tgz --dry-run --ignore-scripts --registry=https://registry.npmjs.org/
130
- ```
131
-
132
- 确定发布后,用有包发布权限的 npm 账号登录,按 npm 提示完成浏览器和二次验证,再执行:
133
-
134
- ```powershell
135
- npm login --registry=https://registry.npmjs.org/
136
- npm publish .\artifacts\dsh-world-0.6.4.tgz --ignore-scripts --access public --registry=https://registry.npmjs.org/
137
- ```
138
-
139
- 首次发布会创建该包;之后每次发布都要提升版本号,同一个包名和版本不能覆盖。发布成功后使用者可按 [安装说明](INSTALL.md#第一步把-dsh-world-装进-profile) 从 npm 安装或更新。后续版本替换示例中的版本号。流程依据 [npm 发布文档](https://docs.npmjs.com/cli/v11/commands/npm-publish/);npm 登录信息和令牌不要写进仓库。
140
-
141
- 每个平台检查通过后,CI 上传 `artifacts/` 中的 `.tgz` 供下载。CI 不替代桌面端和真实账号验收。
142
-
143
- ## 提交说明
144
-
145
- 可按以下结构提交 Issue 或 PR;未知内容写“未验证”,不要填成已完成:
146
-
147
- ```text
148
- 目标分支:dev(PR 必填)
149
- 新增或修改的生态:
150
- 模块目录与接入方式:
151
- 支持的消息、身份映射与会话路由:
152
- 是否涉及内核:否 / 是(文件、原因、行为和影响)
153
- 是否改动生活线核心或给她新增工具:否 / 是(对应的 Issue)
154
- 新增依赖:无 / 有(为什么必须、是否可选、默认是否加载、许可证、对应的 Issue)
155
- 依赖与许可:
156
- 兼容、迁移与重启要求:
157
- 验证命令与结果:
158
- 已知限制与未验证项:
159
- ```
160
-
161
- 贡献遵循对应文件现有许可。仓库主体采用 [MIT](LICENSE),微信原生发送引擎另有 [独立许可](src/ecosystem/wechat/windows/engine/LICENSE) 与 [来源说明](src/ecosystem/wechat/windows/engine/NOTICE.md),不可按根目录 MIT 许可重新分发该部分。提交第三方代码时保留原许可与来源,不提交无权公开的代码或数据。
1
+ # 贡献协议
2
+
3
+ 欢迎为 dsh-world 接入新的聊天平台、桌宠或其他外部程序,也欢迎改进内核。生态模块负责平台收发,复用现有身份、会话和归档能力,依赖一律可选;涉及内核的修改必须单独说明,改动生活线核心之前先开 Issue。
4
+
5
+ ## 分支与 PR
6
+
7
+ **所有 PR 统一提到 `dev` 分支**,不要直接提到 `main`。
8
+
9
+ - `dev`:集成分支。从最新的 `dev` 切出你的分支,改完后向 `dev` 提 PR;维护者在这里合并、验证。
10
+ - `main`:稳定分支,由维护者把 `dev` 的内容并入后发布,不直接接收 PR。
11
+ - `test`:维护者本机试用的分支,不接收 PR,也不要基于它开发。
12
+ - 已经把 PR 提到 `main` 的,请在 PR 页面右上角 **Edit** 里把 base 改成 `dev`;GitHub 默认会选 `main`,提交前请再看一眼目标分支。
13
+ - 提 PR 前至少运行 `npm run typecheck` 和 `npm test`,PR 描述按下文「提交说明」填写,并写明是否涉及内核。
14
+ - **一个 PR 只做一件事。** PR 开着期间只修这件事的问题,新功能另开 PR;不要在已开的 PR 上持续追加功能,也不要把已关闭 PR 的内容带进别的 PR。基于过旧分支、与当前版本大量冲突的 PR,维护者可能只采用其中的思路自行重写。
15
+
16
+ ## 新模块放在哪里
17
+
18
+ | 位置 | 职责 |
19
+ | --- | --- |
20
+ | `src/ecosystem/<channel>/` | 平台自己的接口、消息解析、收发编排、配置和说明文档。例如 QQ 放 `qq/`,B 站放 `bilibili/` |
21
+ | `src/ecosystem/<channel>/windows/` 等平台子目录 | 确实需要的本机适配器、启动脚本或原生桥;目录按实际运行环境新增 |
22
+ | `src/ecosystem/shared/` | 已有的通用身份、群上下文、会话路由和消息出口;新增公用函数应有实际复用方 |
23
+ | `src/ecosystem/bridge/` | 桌宠等本地程序共用的桥;能使用现有协议时直接接入 |
24
+ | `tests/` | TypeScript 回归测试;本机适配器测试可与适配器放在一起,按微信模块现有方式组织 |
25
+ | `.host/<channel>/` | 本机配置、凭据、依赖环境、运行状态和构建产物,不提交、不打入发布包 |
26
+
27
+ 每个平台在自己的目录提供 `README.md`,写清依赖、支持的消息类型、启停和配置方法、身份参数映射、验证范围及平台风险。同步更新 [生态目录说明](src/ecosystem/README.md) 和根 [介绍文档](README.md) 的入口。QQ、B 站目录是新增位置示例,不表示当前已接入。
28
+
29
+ 需要新增配置、注册模块或清理资源时,在 `src/index.ts`、`src/types.ts` 等现有位置做必要的接线;平台解析和收发逻辑仍留在自身模块中。模块应能独立启停,关闭后释放监听、路由和连接,不影响其他生态。
30
+
31
+ ## 统一身份、会话与记忆
32
+
33
+ - 复用 `shared/identity.ts` 的 `SpeakerIdentity` 和校验函数,明确映射 `channel`、`accountId`、`conversationId`、`speakerId`;按平台能力提供聊天类型、消息 ID、显示名和真实 @ 列表。
34
+ - ID 使用字符串,`speakerId`、`mentionIds` 使用 `<channel>:<平台用户 ID>`。身份来自经过校验的平台事件或账号数据,不从消息正文、昵称或模型输出推断,不因昵称相同合并用户。
35
+ - 外部发言人保持 `role: user`,用结构化身份区分来源。群触发使用平台真实 @ 元数据,手打 `@昵称` 不等同于真实 @。
36
+ - 会话按平台、登录账号、聊天类型和会话 ID 区分;群会话复用 `shared/sessions.ts`,放在家所在的工作区。平台模块不另建一份人设或共享记忆。
37
+ - 正文通过现有归档服务保存,遵守 `home` / `workspace` 范围、配置轮数、消息实际时间排序和重启生效规则。普通工具参数、查询回执、群图片上下文不直接写入共享记忆;群图片保留在对应会话日志中。
38
+ - 接收、投递和归档状态应明确区分;排队成功不能当作发送成功。重连保留去重游标和确认状态,结果不明的发送不能直接重试。
39
+
40
+ 以上约定如需改变,应按下节说明内核影响,不能在某个平台内悄悄绕过。
41
+
42
+ ## 世界的作用范围
43
+
44
+ 她的世界只在房间工作区内生效。记忆块、世界提示词、世界线工具、各平台通道和归档,只进入工作区内匹配预设的普通会话;工作区外只允许两项影响:人设(映射到系统提示词)和启用后的只留对话压缩。新增的注入、工具和事件处理必须遵守这条边界:
45
+
46
+ - 判断会话是否属于她的世界,用 `sourceArchive.ts` 的 `inWorld`;拦截、守卫和重新组装分别用 `src/worldScope.ts` 的 `admit`、`scopedTool`、`reassembleOnChange`。每次等待之后、注入之前都要重新判断,不能只在开始时查一次。
47
+ - 监听 `session/event` 等全局事件时,先筛出她的会话(家会话或工作区内的会话),不能对所有会话生效。宿主里同时还有用户的其他工作,比如写代码的会话,插件不能读取、推送或改动它们。
48
+
49
+ ## 生活线与她的主动性
50
+
51
+ dsh-world 养的是一个伙伴,不是一个按日程执行的助手。**她的主动性来自她自己的安排,而不是插件推送的提醒。**
52
+
53
+ - 生活线只有一个闹钟:她用 `plan_next` 说好做什么、做到几点,到点由她决定接着做还是换下一件。插件自己唤醒她的,只有新家的第一句话、重新开始的那句话、每日回顾和早上的兜底闹钟。不要另建第二套日程、习惯或提醒系统。
54
+ - 插件不往对话里推提醒、清单或定时消息,尤其不能插在伙伴说话的那一轮前面。想让她留意某件事,应该给她能感知的事实(比如在她自己的闹钟消息里附上状态),去不去看、做不做,由她决定。
55
+ - 工具给她能力,不替她做决定,也不替她读写自己的东西:日记、我是谁和房间里的文件,由她用宿主自带的文件工具读写(`read` 支持 `offset` / `limit`,`grep` 返回行号)。日记、我是谁和聊天记录的检索与索引,计划和记忆总归档一起设计,不接受只管单一来源的检索工具。
56
+ - 已经移除的生活线功能(例如 `read_diary`、QQ 的“主动开口”引擎),不能在 PR 里直接加回来,要先开 Issue 说明为什么现有方式不够。
57
+
58
+ 改动闹钟、家会话、每日回顾、世界提示词、记忆块注入,或者给她新增工具,都属于生活线核心改动:**先开 Issue 讨论,再写代码**,并在 PR 里按下节说明内核影响。
59
+
60
+ ## 涉及内核时必须说明
61
+
62
+ 这里的“内核”指 dsh-world 的公共行为,不仅指某个目录:
63
+
64
+ | 影响区域 | 现有位置举例 |
65
+ | --- | --- |
66
+ | 记忆生成、归档范围、历史回填、存储和压缩 | `src/runtime.ts`、`archive*.ts`、`sourceArchive.ts`、`store.ts`、`history.ts`、`compaction*.ts` |
67
+ | 世界的作用范围(工作区) | `src/worldScope.ts`、`archiveSelection.ts`、`workspacePermissions.ts` |
68
+ | 人设、世界提示、时间上下文和世界开关 | `src/persona.ts`、`worldPrompt.ts`、`timeContext.ts`、`worldSwitch.ts` 等 |
69
+ | 家会话、生活线、每日回顾和宿主适配 | `src/life/`、`src/hostContract.ts` |
70
+ | 公共协议、配置、模块生命周期和控制台 | `src/ecosystem/shared/`、`src/index.ts`、`src/types.ts`、`src/console/`、`client/` |
71
+
72
+ 提交说明中必须写明“是否涉及内核”。新增平台的配置与注册接线也要列出,说明是否改变已有行为。涉及内核时补充:
73
+
74
+ 1. 修改了哪些文件、接口或数据格式,为什么现有扩展点不足以完成接入。
75
+ 2. 修改前后的行为,以及对家、其他平台、归档范围、人设、生活线和权限的影响;不受影响的部分可合并说明。
76
+ 3. 旧配置、旧会话、归档和待发送队列如何兼容;是否需要迁移或重启,升级失败如何保留原数据。
77
+ 4. 对应回归测试、验证结果,以及未验证的宿主版本、平台或真实账号条件。
78
+
79
+ 本项目内核与 DSH 宿主需要区分。若方案还依赖修改 DSH 本身,应另外列出宿主版本、补丁或依赖、安装方式和兼容范围,不能把它描述为插件即可独立使用。
80
+
81
+ ## 代码设计
82
+
83
+ - **先找已有的,再写新的。** 各平台“送一句话、等她这一轮的回复”用 `ecosystem/shared/turn.ts` 的 `askTurn` 和 `createChannelQueue`(排队、超时、取消);群上下文用 `shared/context.ts`;身份用 `shared/identity.ts`;作用范围见上文。不要在模块里再抄一份串行队列、回复判断或超时逻辑;已有实现不够用时,改它并说明原因。
84
+ - **按职责拆文件。** 一个文件只负责一件事,解析、排队、重试、编排分开放。模块入口只做接线,不要把所有功能堆进一个几百行的 `index.ts`。
85
+ - **不留历史痕迹。** 删除或替换功能时,连同配置、文档、测试、注释和面板控件一起清理;不留孤立注释、只写不读的变量和不再生效的配置项。
86
+ - **用结构判断,不靠巧合。** 判断“这一轮是谁开的、回给谁”,看事件来源和会话归属,不要用“现在有没有正在处理的请求”“几秒内发过同样的文字”这类旁证去重或补救。
87
+ - **配置和面板一致。** 面板上能改的设置要写进同一份覆盖文件,一次请求里给的几项一起生效;新增配置项同步更新 `src/types.ts`、配置 schema、README 的配置示例和对应文档。
88
+
89
+ ## 外部依赖
90
+
91
+ 插件主体必须在只装必要依赖的情况下装得上、跑得起来。生态模块一律按“可选”设计:
92
+
93
+ - **默认不加新依赖。** 能用宿主服务、Node 内置模块或已有依赖完成的,不引入新包。确实需要时,先开 Issue 说明用途、体积、许可证和替代方案,得到同意再提交。
94
+ - **平台 SDK 只能是可选依赖。** 由使用者另行安装的 SDK 声明为可选 peer(`peerDependenciesMeta.optional: true`),开发测试所需版本放在 `devDependencies`。只在模块被拨开时动态 `import`;模块默认关闭,没装 SDK 时只显示该模块不可用,不能影响插件本身和其他模块。不要通过安装脚本修补 SDK 文件。
95
+ - **不自动安装或部署第三方程序。** 涉及启动外部程序(例如 SnowLuma)的功能,在模块文档和 PR 中写明其许可条款允许的方式,以及是否默认开启。
96
+ - **SDK 按需安装必须由使用者明确开启模块触发。** 复用宿主包管理操作与 profile 写锁,禁用生命周期脚本,不修改 SDK 文件或自动升级 Node;安装失败保留原 profile 配置,安装后需要重启时明确提示。客户端程序的安装与登录仍由使用者处理。
97
+
98
+ ## 实现与验证
99
+
100
+ - 优先复用已有函数、宿主服务和依赖,通过参数或现有注入机制传入依赖。平台细节留在模块内,不为单个平台新增通用框架;替换旧实现时清理失效代码。
101
+ - 查询接口搭配分页设计;配置、ID、路径和外部消息在边界校验,不硬编码真实账号或本机路径。错误应明确返回并保留必要状态,不吞错或伪报成功;成组的数据变更保持事务一致性。
102
+ - 确认 `package.json` 的 `files`、导出和构建配置覆盖必要产物,同时排除凭据、数据库、聊天记录和个人媒体。
103
+ - 代码修改按影响运行 `npm run typecheck`、`npm test`、`npm run build`,补充相关适配器测试。**每个新功能都要有回归测试**,非平凡逻辑至少一个有意义的用例;只改断言让旧测试通过不算覆盖新行为。测试不得启动或连接本机真实的第三方程序。仅文档修改检查格式与链接即可。
104
+ - 真实账号测试与单元测试分开说明:使用什么环境、实际确认了什么、哪些条件尚未验证。涉及第三方协议或客户端内部调用时,在模块文档中说明版本限制、账号及数据风险,不能宣称保证不触发风控。
105
+
106
+ ## CI 与安装包验证
107
+
108
+ [CI](.github/workflows/ci.yml) 在 PR、`main` 推送和手动触发时运行 Windows / Linux 两条完整检查任务,使用满足开发测试 SDK 要求的 Node 24.22.1。两端执行禁用安装脚本的干净安装、类型检查、全部 Node 回归测试、编译和安装包验证;Windows 另用 Python 3.11 安装固定的微信适配器依赖并跑离线测试。另有 Node 22.16.0 核心入口检查,确认 SDK 不可用时核心仍能加载。相同分支的新运行会取消旧运行,各项独立报告结果。
109
+
110
+ 本机可按以下 PowerShell 命令复现 Node 与安装包检查:
111
+
112
+ ```powershell
113
+ npm ci --ignore-scripts
114
+ npm run typecheck
115
+ npm test
116
+ npm run build
117
+ New-Item -ItemType Directory -Force artifacts | Out-Null
118
+ npm pack --ignore-scripts --pack-destination artifacts
119
+ npm run test:package
120
+ ```
121
+
122
+ 安装与打包均禁用生命周期脚本,类型检查和编译由前面的命令显式执行。`test:package` 在本仓库 `.host/` 下创建临时消费项目,安装真实 `.tgz` 及其生产依赖,复用父目录提供的 DSH 宿主依赖;先确认未安装 SDK 时核心入口可加载,再另行安装 SDK,检查编译后 QQ 驱动的本地 WebSocket 连接,全程不修补 SDK 文件,结束后清理临时项目。检查不需要 DSH profile、微信进程或真实账号。
123
+
124
+ ### npm 发布(维护者)
125
+
126
+ 使用上述检查生成的同一个 `.tgz` 发布,避免发布时重新构建出不同内容。包已通过 `publishConfig` 指定 npm 官方源和公开访问;先完成发布预检:
127
+
128
+ ```powershell
129
+ npm publish .\artifacts\dsh-world-0.6.5.tgz --dry-run --ignore-scripts --registry=https://registry.npmjs.org/
130
+ ```
131
+
132
+ 确定发布后,用有包发布权限的 npm 账号登录,按 npm 提示完成浏览器和二次验证,再执行:
133
+
134
+ ```powershell
135
+ npm login --registry=https://registry.npmjs.org/
136
+ npm publish .\artifacts\dsh-world-0.6.5.tgz --ignore-scripts --access public --registry=https://registry.npmjs.org/
137
+ ```
138
+
139
+ 首次发布会创建该包;之后每次发布都要提升版本号,同一个包名和版本不能覆盖。发布成功后使用者可按 [安装说明](INSTALL.md#第一步把-dsh-world-装进-profile) 从 npm 安装或更新。后续版本替换示例中的版本号。流程依据 [npm 发布文档](https://docs.npmjs.com/cli/v11/commands/npm-publish/);npm 登录信息和令牌不要写进仓库。
140
+
141
+ 每个平台检查通过后,CI 上传 `artifacts/` 中的 `.tgz` 供下载。CI 不替代桌面端和真实账号验收。
142
+
143
+ ## 提交说明
144
+
145
+ 可按以下结构提交 Issue 或 PR;未知内容写“未验证”,不要填成已完成:
146
+
147
+ ```text
148
+ 目标分支:dev(PR 必填)
149
+ 新增或修改的生态:
150
+ 模块目录与接入方式:
151
+ 支持的消息、身份映射与会话路由:
152
+ 是否涉及内核:否 / 是(文件、原因、行为和影响)
153
+ 是否改动生活线核心或给她新增工具:否 / 是(对应的 Issue)
154
+ 新增依赖:无 / 有(为什么必须、是否可选、默认是否加载、许可证、对应的 Issue)
155
+ 依赖与许可:
156
+ 兼容、迁移与重启要求:
157
+ 验证命令与结果:
158
+ 已知限制与未验证项:
159
+ ```
160
+
161
+ 贡献遵循对应文件现有许可。仓库主体采用 [MIT](LICENSE),微信原生发送引擎另有 [独立许可](src/ecosystem/wechat/windows/engine/LICENSE) 与 [来源说明](src/ecosystem/wechat/windows/engine/NOTICE.md),不可按根目录 MIT 许可重新分发该部分。提交第三方代码时保留原许可与来源,不提交无权公开的代码或数据。