dsh-tiddlywiki 0.27.3 → 0.28.1
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/LICENSE +21 -21
- package/README.md +3 -0
- package/cordis.patch.yml +13 -13
- package/docs/CHANGELOG.md +2 -0
- package/docs/code-review-2026-09-17.md +151 -0
- package/docs/features.md +4 -1
- package/docs/usage.md +32 -0
- package/docs/wiki-split.md +97 -0
- package/lib/client.js +63 -27
- package/lib/index.js +2816 -516
- package/lib/index.js.map +1 -1
- package/package.json +4 -3
- package/skills/wiki-split/SKILL.md +106 -0
- package/src/client/endpoints.ts +52 -0
- package/src/client/index.ts +14 -0
- package/src/client/knowledge-fab.ts +62 -3
- package/src/client/markdown-editor.ts +128 -128
- package/src/client/note-widget.ts +133 -17
- package/src/client/panel.ts +2 -1
- package/src/client/settings-page.ts +217 -3
- package/src/client/state.ts +39 -39
- package/src/client/status-cache.ts +17 -0
- package/src/client/styles.ts +36 -0
- package/src/client/toast.ts +22 -22
- package/src/client/tw-frame.ts +28 -4
- package/src/client/wiki-focus.ts +70 -0
- package/src/client/wiki-scope-dock.ts +116 -0
- package/src/host/admin.ts +175 -35
- package/src/host/git.ts +50 -5
- package/src/host/path-key.ts +35 -0
- package/src/host/prompt.ts +24 -0
- package/src/host/repo-committers.ts +149 -0
- package/src/host/routes.ts +254 -81
- package/src/host/seeds.ts +51 -10
- package/src/host/session-scope.ts +154 -0
- package/src/host/skill-install.ts +121 -0
- package/src/host/tools.ts +141 -49
- package/src/host/wiki-farm.ts +417 -0
- package/src/host/wiki-instance.ts +596 -0
- package/src/host/wiki-registry.ts +601 -0
- package/src/host/wiki.ts +19 -0
- package/src/index.ts +693 -458
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 dsh-tiddlywiki contributors
|
|
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.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dsh-tiddlywiki contributors
|
|
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
|
@@ -61,11 +61,14 @@ dsh plugin --profile web add dsh-tiddlywiki
|
|
|
61
61
|
| 完整能力清单(每条的**为什么**) | [docs/features.md](docs/features.md) |
|
|
62
62
|
| 使用指南 / 工具参考 / 配置 / 远程访问 | [docs/usage.md](docs/usage.md) |
|
|
63
63
|
| 发布到微信公众号(可选,需额外安装) | [docs/wechat-publish-setup.md](docs/wechat-publish-setup.md) |
|
|
64
|
+
| 把知识库拆成几个独立的小库(含可复制给 Agent 的提示词) | [docs/wiki-split.md](docs/wiki-split.md) |
|
|
64
65
|
| 开发与发布流程(给维护者) | [docs/development.md](docs/development.md) |
|
|
65
66
|
| 完整版本变更历史 | [docs/CHANGELOG.md](docs/CHANGELOG.md) |
|
|
66
67
|
|
|
67
68
|
## 🕘 版本记录
|
|
68
69
|
|
|
70
|
+
- **v0.28.1**(2026-09-28):**修「拆库 skill 在 wiki 启动慢 / 起不来时永远装不上」+ 快速笔记的目标库可切换(R7)**。① **真实事故**(作者升级 0.28.0 后重启宿主时发现,测试没拦住):技能安装写在了 `farm.startAll()` **之后**,而那个 wiki 很大、启动要几十秒 —— 于是"技能装没装"取决于"wiki 起得快不快";更糟的是 wiki 若因配置问题**永远起不来**,技能就永远不装,而它跟 wiki 毫无关系(只往 `$DSH_HOME/skills/` 写一个文件)。已挪到启动任务**最前面**并新增**顺序守门**(`verify-skill-install` 断言安装早于 `startAll()`,已反向验证)。② 快速笔记卡片新增「写入」目标库:**默认跟随你在 GUI 里看的那个库**(焦点库),卡片内可临时改、改过后不再被焦点带走。要点是**整张卡片一起换库**:标签建议 / 最近 / 草稿读取 / 附件上传 / 保存 / 随后弹出的 TW 编辑器必须落在同一个库 —— 只改保存那一处会产出"标签列表来自 A、笔记写进 B",比不做还糟(弹窗那条另有一条守门:写入 A、编辑器打开 B(空白)是典型的"看起来成功了"的失败)。目标库没在运行时卡片**先讲清楚**,而不是让你写完一段再失败。单库安装不显示选择器、请求 URL 逐字不变。
|
|
71
|
+
- **v0.28.0**(2026-09-28):**多知识库**——同一进程内可同时运行多个 TiddlyWiki,每个库独立配置、独立 git、按会话选择作用域,且可对 Agent **彻底隐身**。起因是作者自己的库:`书籍` 语料 2468 条与日常笔记挤在一个 800MB 仓库里,Agent 检索/lint/backlink 每天在约 30 倍于工作集的内容上跑。拆库需要**同时在线**——只切换位置的话,你切到语料库翻资料时 Agent 也跟着切过去了。要点:① 三个模块各司其职(`wiki-registry` 清单/模式/动作、`wiki-instance` 单库运行时、`wiki-farm` 谁该在跑 + 会话作用域);② 模式开关 `wikis.json` 的 `mode`(缺省 `single`,升级用户逐字不变);③ 路由按库定向 `/tw/<id>/…`(每个库 seed 自己的同源基址——不区分 id 就会「页面显示 A、读写落在 B」),裸 `/tw/` 仍是默认库别名,为此引入**保留 id 名单**;④ **git 按仓库重构**:多库可共用一个仓库,`git add -A` 只允许一个 committer,pull 后只重启受影响的库;⑤ Agent 侧:会话单选作用域 + `agentVisible`,工具回执在可见库多于一个时**标明库名**,注入提示词**按会话求值**并读作用域库的 `prompt.*`;两条刻意拒绝(隐身库→忽略;没在跑→报错而非静默换库);⑥ GUI:输入框上方的会话选择器、FAB 的知识库分组、设置页「知识库列表」与**显式配置作用域**;⑦ **拆库引导** `docs/wiki-split.md` + 随包 skill(安装时只覆盖带标记的文件)。新增 6 个守门脚本(共约 96 条断言,含**真起两个 TW 子进程**与**真 git 仓库**两类集成验证),全部反向验证;4 个既有守门跟着间接层更新。**未做**:per-wiki 认证(`auth.*` 仍全局)、快速笔记目标切换策略。
|
|
69
72
|
- **v0.27.3**(2026-09-28):**修 v0.27.1 拆文档时打断的 6 个相对链接**(`docs/` 内的链接仍按仓库根写成 `docs/xxx.md`,从 `docs/` 内部点击打不开);新增守门 `scripts/verify-doc-links.mjs`(校验 README + `docs/**` 的全部相对链接,已挂进 `verify:static`,让这类错误下次发不出来);**修 CI 偶发假红**:`verify-wiki-switch` 的回滚断言会抢在 TW 载入 store 之前读取,改为 15s 有界轮询后再断言。**无运行时行为改动。**
|
|
70
73
|
- **v0.27.2**(2026-09-28):修本页一处**指向不存在内容的死链**("不适合"那段曾让人去看 `docs/features.md` 末尾的说明,那里并没有)。纯文案,无代码改动。
|
|
71
74
|
- **v0.27.1**(2026-09-28):**README 从 175KB 精简成本页**——原来 68% 的篇幅是变更历史。能力清单 / 使用指南 / 开发发布 / 完整变更历史拆到 `docs/`(内容未删),首页只留"是什么、怎么装、好在哪、适不适合你"。**无代码改动。**
|
package/cordis.patch.yml
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
# dsh-tiddlywiki bundle patch: inserts the plugin row into the web profile
|
|
2
|
-
# roster. Applied as a profile bundle layer (the `dsh.bundle.patch` manifest
|
|
3
|
-
# field) over dsh-base; activate with
|
|
4
|
-
# `dsh plugin --profile web add link:<path-to-this-package>` and restart dsh web.
|
|
5
|
-
#
|
|
6
|
-
# The row is a bare plugin by package name: the node half (exports ".") runs
|
|
7
|
-
# in the host process (WikiServer, routes, tiddlywiki_* tools, prompt section,
|
|
8
|
-
# auto-commit), and the `dsh.client` declaration in package.json makes the
|
|
9
|
-
# browser half (exports "./client", served at /plugins/dsh-tiddlywiki/client.js)
|
|
10
|
-
# load in the web GUI.
|
|
11
|
-
- insert:
|
|
12
|
-
- id: dsh-tiddlywiki
|
|
13
|
-
name: dsh-tiddlywiki
|
|
1
|
+
# dsh-tiddlywiki bundle patch: inserts the plugin row into the web profile
|
|
2
|
+
# roster. Applied as a profile bundle layer (the `dsh.bundle.patch` manifest
|
|
3
|
+
# field) over dsh-base; activate with
|
|
4
|
+
# `dsh plugin --profile web add link:<path-to-this-package>` and restart dsh web.
|
|
5
|
+
#
|
|
6
|
+
# The row is a bare plugin by package name: the node half (exports ".") runs
|
|
7
|
+
# in the host process (WikiServer, routes, tiddlywiki_* tools, prompt section,
|
|
8
|
+
# auto-commit), and the `dsh.client` declaration in package.json makes the
|
|
9
|
+
# browser half (exports "./client", served at /plugins/dsh-tiddlywiki/client.js)
|
|
10
|
+
# load in the web GUI.
|
|
11
|
+
- insert:
|
|
12
|
+
- id: dsh-tiddlywiki
|
|
13
|
+
name: dsh-tiddlywiki
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
> 最近几个主要版本的一句话记录(完整变更见 [Releases](https://github.com/bbqisbbq/dsh-tiddlywiki/releases) / git log)。
|
|
6
6
|
|
|
7
|
+
- **v0.28.1**(2026-09-28):**修「拆库 skill 在 wiki 启动慢 / 起不来时永远装不上」+ 快速笔记的目标库可切换(R7)**。① **真实事故**(作者升级 0.28.0 后重启宿主时发现,测试没拦住):技能安装写在了 `farm.startAll()` **之后**,而作者的知识库很大、TW 冷启动要几十秒 —— 于是"技能装没装"取决于"wiki 起得快不快";更糟的是 wiki 若因配置问题**永远起不来**,技能就永远不装,而它跟 wiki 毫无关系(只往 `$DSH_HOME/skills/` 写一个文件)。已挪到启动任务**最前面**,并新增**顺序守门**:`verify-skill-install` 断言 `installSplitSkill()` 出现在 `farm.startAll()` 之前(已反向验证:在安装前插一次 startAll → 该条红)。② **R7 快速笔记目标**:卡片新增「写入」目标库,**默认跟随 GUI 的焦点库**(你在看哪个库,快速笔记就进哪个库),卡片内可临时改且改过之后不再被焦点带走。设计的核心是**整张卡片一起换库** —— 标签建议(memo 以 URL 为键,换库自动重读)/ 最近列表 / 草稿读取 / 附件上传 / 保存 / 随后弹出的 TW 编辑器,六处全部经 `withWikiQuery()`(`?wiki=<id>`)与 `twProxyFor()` 定向;只改保存那一处会产出"标签列表来自 A、笔记写进 B",比不做还糟。目标库没在运行时卡片会**先讲清楚**("先在右下角「知识库」菜单里打开它"),而不是让用户写完一段再失败;这里刻意**不自动启动** —— 启动是「知识库」菜单的职责,保存路由顺手 spawn 进程不该由一次误点触发。单库安装不显示选择器、请求 URL 逐字不变。守门 `verify-wiki-focus` 扩到 16 条(含 `withWikiQuery` 的四条纯逻辑断言与"整张卡片同库"的源码级断言,已反向验证);`verify-tw-origin` 的弹窗接线断言跟着间接层更新(保留强度)。
|
|
8
|
+
- **v0.28.0**(2026-09-28):**多知识库**——同一进程内可同时运行多个 TiddlyWiki,每个库独立配置、独立 git,Agent 侧按**会话**选择作用域,且每个库可对 Agent **彻底隐身**。起因是作者自己的库:`书籍` 语料 2468 条与日常笔记挤在一个 800MB 仓库里,Agent 的检索 / lint / backlink 每天在约 **30 倍于工作集**的内容上跑(「先窄后宽」是缓解不是解法)。而拆库必须**同时在线**——只切换位置的话,你切到语料库翻资料时 Agent 也跟着切过去了。**① 三个模块各司其职**:`host/wiki-registry.ts`(清单 / 模式 / 动作规则,纯函数)、`host/wiki-instance.ts`(**一个**库的运行时:TW 子进程 + REST 客户端 + 配置覆盖层 + fs 监听 + 自身自举)、`host/wiki-farm.ts`(**谁该在跑**:reconcile 规则 + 会话作用域解析);`apply()` 从"抱着一个实例"变成"驱动一个农场"。**② 模式开关**:`wikis.json` 的 `mode: 'single' | 'multi'`,**缺省 `single`** —— 升级用户的行为逐字不变(位置仍由旧指针文件 > cordis 默认决定,registry 此时只记住模式与候选列表)。**③ 路由按库定向**:`/tw/<id>/…`,且每个库把自己那份同源代理基址 seed 进**各自的** `$:/config/tiddlyweb/host`(TW 用它拼**所有** API URL;不区分 id 就会出现"页面显示 A、每次读写落在 B");裸 `/tw/` 保留为默认库别名(旧链接 / 书签 / 已 seed 的值都不失效)。为此引入**保留 id 名单**(`status`/`files`/`recipes`/`render`… 与 TW 自己的根路径撞名时无法区分,一律拒绝注册)。非代理路由统一吃 `?wiki=<id>`。**④ git 按仓库重构**:多个库可以共用**一个**仓库(按文件夹物理划分)。`git add -A` 在子目录执行也作用于整个工作树,所以自动提交必须**按仓库**去重(`host/repo-committers.ts`,提交在仓库根执行;`dir → repo` 只缓存**肯定**结果——否定结果缓存会让全新安装的自动提交永久静默失效);`pull()` 回传改动路径,**只重启内容真的变了的库**("这次 pull 改了东西"与"这个库快照过期了"是两个问题)。**⑤ Agent 侧**:会话级作用域(`$DSH_HOME/dsh-tiddlywiki/sessions.json`,单选,软失败——读不到就只是"没选过")+ `agentVisible`(**彻底隐身**:不进选择器、不进提示词、工具不可达)。工具按会话路由,且**可见库多于一个时每条回执都标明库名**(一处包装实现,不是改 15 个 render);注入提示词改为**按会话求值**(`PromptSection.text` 支持函数,DSH 每次装配传入该会话的 agent)并读**作用域那个库**的 `prompt.*`——单库安装的 slim 仍是 1918 字符,逐字节不变。**两条刻意的拒绝**:作用域指向隐身库 → 忽略(隐身是硬边界,过期选择不能绕过它);指向可见但**没在跑**的库 → **报错并带上库名**,绝不静默落到别的库(静默换库 = 笔记进了用户没指定的知识库而且看起来一切正常,这是本功能唯一真正危险的失败方式)。**⑥ GUI**:`/status` 回传知识库名册;输入框上方的**会话选择器**(只在可见库多于一个时存在);右下角「知识库」菜单的知识库分组(点开一个没在跑的库会**显式 POST** 启动它——代理路由绝不在 GET 里 spawn 子进程);设置页新增「知识库列表」(模式切换、增删改、启停、设为默认、可见性、开局自启、新增;「移出列表」明确写着**不删目录**)与**显式配置作用域**(每个库的配置在它自己的 tiddler 里,不说清就会出现"改了但不生效")。**⑦ 拆库引导**:`docs/wiki-split.md`(人读 + 顶部一整段**可直接复制给 Agent** 的话)+ 随包 skill `skills/wiki-split/SKILL.md`(启动时装到 `$DSH_HOME/skills/`;**只覆盖带标记的文件**,用户自己写的 skill 一个字节都不碰)。**守门**:新增 6 个脚本——`verify-wiki-registry`(18 条)、`verify-wiki-farm`(12 条,注入假实例钉死 reconcile)、`verify-wiki-farm-boot`(**22 条,真起两个 TW 子进程**)、`verify-session-scope`(13 条)、`verify-repo-keying`(11 条,**真 git 仓库**)、`verify-skill-install`(7 条)、`verify-wiki-focus`(13 条,真读 client 源码),**全部反向验证过**;另有 5 个既有守门(drain / language-pin / frame-guards / ready-policy / tw-origin / conflict-config)跟着间接层更新,同样保留强度并反向验证。**未做**:per-wiki `auth.*`(仍是全局)、快速笔记目标切换(目前写默认库也显示默认库,一致但不是最终形态)。
|
|
7
9
|
- **v0.27.0**(2026-09-28):**重新定位:从「AI 记忆工具」改写成「你和 Agent 共用的本地文档中心」——纯元数据 + 文档发布,运行时行为零变化。** 触发原因:生态里「记忆 / 知识库」这一格已挤进 30+ 个插件(最大的 `MemOS` 已 11,603★),而本插件真正的差异从来不是「更自动的记忆」,而是**人机共用同一份纯文本文件**:人能读、能改、能审计、能随时带走,Agent 用同一套工具读写它。本次把这件事搬上门口那块招牌:① `package.json` 的 `description` 由英文功能清单改为双语定位,并明确写出**不是自动记忆插件、不做后台抓取**,`keywords` 补 `knowledge-management` / `local-first` / `journal` / `publishing` / `wechat`;② GitHub topics 同步补位,便于目录站归类;③ 修正 README 一处过时数字(「Agent 用 10 个工具」→ 15 个)。**无代码改动**——「装完空手能用」(自动建库 + 起步 seed + 快速开始 7 步)自 v0.22.0 起就已具备,本次只是把它讲清楚。
|
|
8
10
|
- **v0.26.7**(2026-09-26):**修「DSH 桌面版里 TW 面板没有『添加条目』按钮」——嵌入式 TW 被渲染在自定义 scheme 上,于是它根本没有同步器**。现象(用户报障,桌面版):TW 面板能看能搜,但页面控件里 **添加条目 / 添加日志 / 添加图片 / 导入 / 条目管理器** 整排不见,单条笔记的 **编辑 / 克隆 / 删除** 也不见,像装了个只读版 TW。**根因不在本插件,而在 TW 内核的一处协议判定 + 桌面版新增的渲染 scheme**:TiddlyWiki 自己的 TiddlyWeb sync adaptor 只在**文档协议以 `http` 开头**时才导出自己(`plugins/tiddlywiki/tiddlyweb/tiddlywebadaptor.js`:`if($tw.browser && document.location.protocol.substr(0,4) === "http")`);而桌面版用自定义 scheme `dsh-app:` 渲染界面(已在本机运行中的 Electron 渲染进程命令行里核实 `--standard-schemes=dsh-app --secure-schemes=dsh-app …`),于是 iframe 里的 TW **没有 syncadaptor → 没有 `$tw.syncer`**:`$:/status/IsReadOnly` 永远没人写,TW 自己的只读样式表 `$:/plugins/tiddlywiki/tiddlyweb/readonly`(`<$reveal … default="yes">`,**状态未知即当只读**)就把那批按钮 `display:none` 掉;同时 saver 退化成本地 `dirtyTracking`,**在那儿做的编辑根本存不回 wiki**。实测复现(不依赖桌面版):把同一份 wiki 页面用 `file://` 打开(同样非 http),+ 同样消失——所以这不是「按钮坏了」,而是「这个文档里 TW 没有同步器」。**修复思路:让嵌入式 TW 跑在真正的 http origin 上**。① 宿主 `/status` / `/edit` / `/session/summary` 在原有相对字段旁,新增**绝对孪生字段** `twProxyAbsolute` / `twUrlAbsolute`(`absoluteHostBase()`,由请求 `Host` 头推导;脏头一律拒绝 `/^[A-Za-z0-9.\-[\]:]+$/`,避免把路径/凭据拼进回给浏览器的 URL);② 客户端新增 `resolveTwUrl()`(`endpoints.ts`)——**页面本身是 http(s) 时继续走相对路径**(loopback / 局域网 / Tailscale / 域名 / HTTPS 行为一字不变,绝不把 https 页面改源),**只有页面不是 http(s) 时**才改用宿主回环 http 基址,于是桌面版 iframe 里 TW 的协议变成 `http:`,sync adaptor 正常加载、同步器回来、按钮与保存一起复原;③ 随之而来的跨源:桌面版下 TW frame 与宿主页面不同源,`tw-frame.ts` 的 hash 导航把「读跨源 `$tw` 抛 SecurityError」包进 try/catch 并直接走整页兜底(不再冒泡、链接不丢),`theme-sync` 本就是 best-effort 跨源静默。守门:新增 `scripts/verify-tw-origin.mjs`(7 条:Host 头推导 / 脏头拒绝 / http(s) 忽略绝对基址 / `dsh-app:`+`file:` 采用绝对基址 / 老服务端不回归 / 两处接线断言 / 跨源兜底源码断言),`verify-frame-surface` 再增 2 条行为单测(`dsh-app:` 页面必须把 frame 指到宿主 http 基址、且 http 页面仍用同源相对地址;跨源 frame 读 `$tw` 抛错时必须整页兜底)。**注意**:这修的是「TW 被非 http(s) 文档嵌入」;「在浏览器里直连 TW 子端口(`http://127.0.0.1:<port>/`)」是另一回事——那样 TW 的 API 基址会被指到 DSH 代理前缀而 404,同样显示成只读,请一律用 DSH 同源入口(面板 / 右侧栏知识库 tab / `<DSH origin>/dsh-tiddlywiki/tw/`)。
|
|
9
11
|
- **v0.26.6**(2026-09-26):**修 `tiddlywiki_append` 的 `heading` 在 CRLF 笔记上完全失效、且静默回退到文末**(作者本机知识库的一篇 25KB / 454 行长笔记上连续踩两次后定位到源码)。现象:对这篇笔记连做两次带 `heading` 的 append,内容**全部**落到了文末,而回执照样打印「段落「X」」——看起来完全成功。**根因**:`insertIntoSection` 用 `base.split('\n')`,而 CRLF 正文的**每一行结尾都留着 `\r`**;JS 里 `(.*)` **不匹配 `\r`**、`$` 也**不匹配 `\r` 之前的位置**,于是 `/^#{1,6}\s+(.*)$/` 对**每一个**标题都匹配失败 ⇒ `start < 0` ⇒ 走「追加到文末」的回退分支。**只在 CRLF 笔记上复现**(纯 LF 笔记一切正常),所以它看起来像偶发;落盘文件的「**376 个 CRLF + 78 个裸 LF**」混合行尾正是走过该分支的签名(`lines.join('\n')` 保留每行原有 `\r`、只把新块用 `\n` 接上)。**修复三处**:① 标题匹配前**只**剥掉行尾 `\r`(原文行尾一个字节都不动;**没有**改成 `split(/\r?\n/)`——那会让每次 append 都把整篇重写成 LF,炸出全文件 diff);② 新块按**文档自己的行尾**写入(`base.includes('\r\n')` 判定),CRLF 笔记不再越写越乱;③ `insertIntoSection` 改为返回 `{ text, matched }`,`tiddlywiki_append` 把它作为 **`headingMatched`** 一并返回,未命中时回执改为「⚠️ 未找到标题「X」,已改为追加到文末」——**不再把「定位失败」伪装成「写进了那一节」**(Agent 因此完全无法自检,这是本 bug 最坑的一点)。顺带把 `heading` 的真实落点语义写进 schema:实为「该标题之后、**下一个任意级别标题之前**」,标题后紧跟子标题时落在两者之间(=章节开头),并非 schema 原先暗示的「整节末尾」。守门:`verify-tools` +4 条 E2E(CRLF 正文的笔记必须 `headingMatched=true` 且标记落在目标段内、段内不得混入裸 LF;未命中必须 `headingMatched=false` 且回执含「未找到标题」、不得再宣称写进该段落;命中时的反向保护;wikitext `!` 标题同样可定位),**已反向验证**(撤掉 lib 里的行尾 CR 剥离即红 1 条)。
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# dsh-tiddlywiki 全面代码审查(v0.22.10,2026-09-17)
|
|
2
|
+
|
|
3
|
+
审查对象:`1d67543`(v0.22.10)工作区,覆盖 host 数据写路径、HTTP/路由/安全层、进程/生命周期/存储/seed、以及全部 client 代码。
|
|
4
|
+
|
|
5
|
+
**结论**:本地四套校验(`verify:static` / `verify:unit` / `verify:e2e` / `verify:large`)全绿,`typecheck` 通过,`npm run build` 重建 `lib/` 与提交版**逐字节一致**(无脏构建产物)。但审查发现 **3 处 P0/P1 安全问题**——v0.19.3 / v0.20.0 建立的「插件密钥命名空间不可经代理读出」这条防线**实测已被绕过**。
|
|
6
|
+
|
|
7
|
+
标注约定:✅ = 我在本机实测/读码亲自复现;○ = 读码确认(未运行时复现)。
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## P0 — 安全
|
|
12
|
+
|
|
13
|
+
### 1. ✅ 代理的「密钥命名空间」封锁可用**百分号编码的单段路径**绕过
|
|
14
|
+
|
|
15
|
+
`src/host/routes.ts:565-575`(`isBlockedProxyPath`,用于 `/api` 与 `/tw`)
|
|
16
|
+
|
|
17
|
+
守卫只在路径里含**字面量 `/tiddlers/`** 时才生效。而 TW core 的 `get-tiddler-html.js` 路由是 `path = /^\/([^\/]+)$/`(**单段**,用 `decodeURIComponentSafe` 解码),于是把 `/` 编码成 `%2F` 就能绕开那个字面量匹配。
|
|
18
|
+
|
|
19
|
+
本机实测(只读 GET):
|
|
20
|
+
|
|
21
|
+
| 请求 | 结果 |
|
|
22
|
+
|---|---|
|
|
23
|
+
| `GET /dsh-tiddlywiki/tw/tiddlers/$:/plugins/dsh-tiddlywiki/config` | **403**(守卫生效) |
|
|
24
|
+
| `GET /dsh-tiddlywiki/tw/%24%3A%2Fplugins%2Fdsh-tiddlywiki%2Fconfig` | **200**,676 字节插件配置 JSON |
|
|
25
|
+
| `GET /dsh-tiddlywiki/api/%24%3A%2F...%2Fconfig` | **200**(同上) |
|
|
26
|
+
| `GET /dsh-tiddlywiki/get?title=...` | 403(这条路由没问题) |
|
|
27
|
+
|
|
28
|
+
同一编码手法对 `/api` 同样有效。**这次没有泄密**:本机 `$:/plugins/dsh-tiddlywiki/config` 里没有 `bridge.token` / `auth.password`,`git.remote` 也是不含凭据的纯 https 地址——但 README 恰恰叫用户去设 `bridge.token`,一旦设了,任何**同源**读取方(页面内 XSS、被注入的 TW 内容、其它插件)即可拿到明文。
|
|
29
|
+
|
|
30
|
+
**修复**:别再用 `/tiddlers/` 这个标记做判定。应把整条被代理路径解码、归一后,判断解码结果是否落在受保护标题上(覆盖 TW 各条路由形态),或在转发前对每个路径段解码后比对。
|
|
31
|
+
|
|
32
|
+
### 2. ✅ `/tw/` 索引页把整个 store(含插件配置)原样吐出
|
|
33
|
+
|
|
34
|
+
`src/host/routes.ts:1474-1553`(`handleTwProxy`)
|
|
35
|
+
|
|
36
|
+
`$:/core/save/all`(`templates/save-all.tid`)没有排除插件命名空间,所以 `GET /dsh-tiddlywiki/tw/` 会内嵌每一条 tiddler。本机实测:**33,643,875 字节**,且正文中确实包含 `$:/plugins/dsh-tiddlywiki/config`。
|
|
37
|
+
|
|
38
|
+
这条路径完全绕过了 `/admin/state` 的密钥打码(`admin.ts:385-414`)。
|
|
39
|
+
|
|
40
|
+
**修复**:在 store 的 publish 过滤里排除 `$:/plugins/dsh-tiddlywiki/`;更彻底的做法是共享 token 根本不进 wiki tiddler。
|
|
41
|
+
|
|
42
|
+
### 3. ✅ `POST /render` 只挡了 `title` 分支,`text` 分支的**转写**可读出被保护条目
|
|
43
|
+
|
|
44
|
+
`src/host/routes.ts:1394-1405`
|
|
45
|
+
|
|
46
|
+
v0.20.0 的加固只覆盖 `title`;`text` 分支未校验,而 TW 的 `/render` 会解析 `{{…}}` 转写,`contextTitle` 同样未校验。本机实测:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
POST /render {"text":"{{$:/plugins/dsh-tiddlywiki/config}}","type":"text/vnd.tiddlywiki"}
|
|
50
|
+
→ 200,2744 字节,净化后的片段里是配置 JSON 的完整内容
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
即:加固过的这条路由,本身仍可把密钥渲染出来。
|
|
54
|
+
|
|
55
|
+
**修复**:`text` 分支拒绝含受保护标题的正文,并校验 `contextTitle`;或把转写放进受限上下文。
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## P1/P2 — 数据安全与生命周期
|
|
60
|
+
|
|
61
|
+
### 4. ○ `tiddlywiki_delete`:回收站索引的守卫发生在**破坏性步骤之后**
|
|
62
|
+
|
|
63
|
+
`src/host/tools.ts:761`(`wiki.delete`)先于 `:768-769`(`readTrashIndex` + 中止判定)。
|
|
64
|
+
|
|
65
|
+
于是「快照 PUT 成功 → 原条目已删除 → 索引读失败」时,工具报的是「本次操作已中止」,但实际上原标题**已经没了**,而回收站副本**没进索引**:`trash list` 看不到、`restore` 找不到、`empty` 清不掉——成为永久孤儿(只能从 git 里捞)。
|
|
66
|
+
|
|
67
|
+
代码注释(`tools.ts:762-767`)与回归测试(`verify-audit-fixes.mjs:335`)都声称「先读索引再删除」,与实际执行顺序相反;这也是测试为何没抓到它的原因。
|
|
68
|
+
|
|
69
|
+
**修复**:把 `readTrashIndex` 与该守卫上移到快照 PUT 之前;若保留删除后失败的可能,错误信息里必须带上 `trashTitle` 以便恢复。
|
|
70
|
+
|
|
71
|
+
### 5. ○ `assertNoConflict` 把两个令牌当**或**处理,且 `revision` 不跨重启存活
|
|
72
|
+
|
|
73
|
+
`src/host/write-policy.ts:276-279`
|
|
74
|
+
|
|
75
|
+
先判 `modified` 命中即 return;再判 `revision` 命中即 return。TW 的 `revision` 是 `core/modules/wiki.js` 里的内存 hashmap(`changeCount`,**不持久化**,重启后每条回到 1)——我已在 `node_modules/tiddlywiki/core/modules/wiki.js:151-155` 确认。
|
|
76
|
+
|
|
77
|
+
后果:agent 读到 `revision: 1` → 人类在 TW 里改动 → 一次 pull/重启让 revision 复位 → agent 带 `expectedRevision: 1` 的写入被**接受**,人类的改动被静默覆盖。更糟的是**同时传两个令牌并不更安全**:`modified` 不匹配时会落到 `revision` 分支继续写。
|
|
78
|
+
|
|
79
|
+
**修复**:调用方给了哪个令牌就必须匹配哪个,任一不匹配即拒;并修正三处工具描述(`tools.ts:503`、`719`、`973`)不要暗示 revision 跨重启有效。
|
|
80
|
+
|
|
81
|
+
### 6. ✅ `fs.watch` 没有 `'error'` 监听器,可让整个 dsh web 进程退出
|
|
82
|
+
|
|
83
|
+
`src/index.ts:279-294`(`watchWiki`)
|
|
84
|
+
|
|
85
|
+
只挂了 `'change'`。Node 的 EventEmitter 在**没有 `'error'` 监听器**时会把 `error` 事件抛成未捕获异常(本机已用最小复现确认),而宿主没有 `uncaughtException` 处理器。`fs.watch` 的 `error` 是**异步**事件(Linux `ENOSPC` 用尽 inotify watch、Windows `EPERM`),外面那个 try/catch 只能接住同步创建失败。
|
|
86
|
+
|
|
87
|
+
**修复**:`watcher.on('error', () => {})`(我们自己的写入仍会 `touch()` 自动提交,退化是安全的)。
|
|
88
|
+
|
|
89
|
+
### 7. ○ `stop()` 不能取消在途的 `start()`,可泄漏孤儿 TW 子进程
|
|
90
|
+
|
|
91
|
+
`src/host/wiki.ts:550-587` vs `303`/`307`/`351`
|
|
92
|
+
|
|
93
|
+
`startOnce()` 在 `await this.ensureWiki()`(302)与 `await this.findFreePort()`(307)处让出执行权;`stop()` 在此期间把 `this.child` 取走并置空(559-560)。等 `startOnce()` 继续走到 `spawn()`(351)并赋值 `this.child` 时,**已经没人会去杀它**了。
|
|
94
|
+
|
|
95
|
+
可达路径:`scheduleRestart()` 的定时器在调用 `start()` **之前**就清掉了 `this.restartTimer`(517),所以随后的 `stop()` 既没定时器可取消、也看不到子进程;以及 `index.ts:884-888` 那个 3 秒 teardown race 超时。
|
|
96
|
+
|
|
97
|
+
**修复**:在 `spawn()` 前立刻复查 `if (this.stopping) return this.status()`(`stopping` 已有,无需新状态)。
|
|
98
|
+
|
|
99
|
+
### 8. ○ 回滚失败会让自动提交**永久停摆**
|
|
100
|
+
|
|
101
|
+
`src/host/wiki-switch.ts:86-99` + `src/index.ts:753-754`
|
|
102
|
+
|
|
103
|
+
外层失败路径已经先跑过 `teardownExtras()`(753,会 dispose AutoCommitter 与 fs watcher)再 `applyLocation`。而 `restore()` 里 `startServer()`(90)排在 `setupExtras()`(92)**之前**——若回滚时 `startServer()` 抛错(例如旧 wiki 撞上硬就绪上限),`catch` 直接返回,`setupExtras()` 永不执行,且没有任何其它路径会重新装配。此后所有 wiki 写入都不会再被提交,而旧子进程可能因「迟到就绪」复探仍在服务,`/status` 看起来一切正常。
|
|
104
|
+
|
|
105
|
+
**修复**:在 `restore()` 里用 `finally` 重新装配 extras。
|
|
106
|
+
|
|
107
|
+
### 9. ○ `runSwitch` 缺 `disposed` 守卫
|
|
108
|
+
|
|
109
|
+
`src/index.ts:736-770`。`disposed` 在作用域内(571 声明)但 `runSwitch` 从不检查:热重载/关闭期间的在途切换会重新挂上 committer+watcher,并可能在 teardown 已 `stop()` 之后 spawn 出新的 TW 子进程。
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## P3 — 低危 / 行为瑕疵
|
|
114
|
+
|
|
115
|
+
10. ✅ **`escapePromptBraces` 对连续花括号失效**(`src/host/prompt.ts:142-144`)。`replace(/\{\{/g, …)` 只替换**不重叠**匹配,本机实测:3 个及以上连续 `{` 会残留 `{{`(`{{{name}}}` → `{<ZWSP>{{name}}}`),而 DSH 对未知变量**直接抛错**,会炸掉整个系统提示词装配——正是该函数存在的理由。现有断言(`verify-prompt.mjs:141`)只覆盖最简单的单个 `{{cwd}}`。**修复**:循环替换至无 `{{`,或在任意相邻两括号间插入 ZWSP。
|
|
116
|
+
11. ✅ **`readLimit` 把空参数当 0**(`src/host/routes.ts:315-318`):`?limit=` → `Number('' ?? 15)` = 0 → 夹到 1。本机实测 `/recent?limit=` 返回 `limit=1`,而 `/recent` 是 15。**修复**:空值按缺省处理(`readOptionalLimit` 已是对的)。
|
|
117
|
+
12. ✅ **`git pull` 失败后无条件 `rebase --abort`**(`src/host/git.ts:159-160`):只要 pull 失败就 abort——包括用户在 wiki 仓库里**自己正在做的 rebase**。本机真实仓库复现:`rebase in progress: True` → `pull --rebase` 报 `Pulling is not possible because you have unmerged files` → 插件执行 `rebase --abort` → 用户的 rebase 与冲突解决被清掉(提交仍在 reflog/ORIG_HEAD,可恢复)。**修复**:仅在确有 rebase 进行中且失败原因是冲突时才 abort。
|
|
118
|
+
13. ○ **clip-bridge 的 OPTIONS 预检排在 Host 白名单之前**(`src/host/clip-bridge.ts:688` 早于 `:694`):DNS-rebinding 页面能拿到 204 + `Access-Control-Allow-Origin: *` + `Allow-Private-Network: true`(仅信息泄露:确认回环端口有服务;后续 POST 仍 403)。
|
|
119
|
+
14. ○ **`ensureTiddlerTimestamps` 在只给了 `created` 时把 `modified` 钉死成 `created`**(`src/host/tw-api.ts:189`)。真实受害者就是配置 tiddler:`config.ts:215-221` 每次保存都只带 `created`,于是 `modified` 永远停在首次写入的时刻——与该文件 `198-200` 行自述的意图(「modified tracks edits」)相反。**本机磁盘实测**:`$__plugins_dsh-tiddlywiki_config.json.meta` 里 `created` 与 `modified` **完全相同**(`20260917033457660`)。`routes.ts:447-457` 因为显式传了 `modified` 才躲过。**修复**:缺的那一半用 `now`(仍保留「绝不改写已有值」的保证)。
|
|
120
|
+
15. ○ **`append` 在 `mode=prepend` 时回执里谎报段落**(`src/host/tools.ts:884` vs `:908`,render 在 `:866`):`heading` 只在 append 分支生效,却无条件回显,模型会以为写进了「日志」段。
|
|
121
|
+
16. ○ **`$:/` 系统条目其实会被软删除**(`src/host/tools.ts:743-744`):注释写「system tiddlers are never nested」,但条件只排除了 `TRASH_PREFIX`。`delete('$:/dsh-tiddlywiki/trash-index')` 会把索引本身移入回收站,随后读到 404 → 当成「回收站为空」→ 索引被重写成 1 条,**丢掉全部回收站记录**(正是 v0.19.5 想防的那类损失,只是换了扇门)。
|
|
122
|
+
17. ○ **`/render` 内层 catch 把超限/中断报成 400**(`src/host/routes.ts:1377-1382`):`body too large` 本应 413。
|
|
123
|
+
18. ○ **`put` 能把二进制附件改成损坏的文本**(`src/host/tools.ts:534`):按设计保留基底 `type`,于是对 `type: image/png` 的既有附件写入 markdown,type 仍是 `image/png` → 图裂,且回执里 `typeChanged` 为空(类型确实没变)。
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 已复核为正确的部分(尽力想证伪但没成功)
|
|
128
|
+
|
|
129
|
+
- **写策略的字段保留**:`cleanTiddler` 确实摊平了 `get-tiddler.js` 塞进 `fields` 的未知字段;`applyCustomFields` 拒绝 `title/text/tags/created/modified` 而放行 `type`;「覆盖保留 type/tags/自定义字段」的契约成立。
|
|
130
|
+
- **v0.22.10 的时间戳语义**:`formatTiddlerDate` ⇄ `parseTiddlerDate` 对 **1970–9999 年间所有可达时刻**往返一致(含 `.000`、`+1ms`、`+999ms`、1969 年末);`stampTiddlerTimes` 始终从基底取 `created`、从 `now` 取 `modified`,不会让 `modified` 早于 `created`;覆盖不会丢原 `created`;`ensureTiddlerTimestamps` 只补缺、绝不改写(回收站快照、恢复、`/edit` 草稿的显式值都存活)。仅当 `new Date()` 落到公元 1000 年前/10000 年后才不往返——生产不可达。
|
|
131
|
+
- **回收站快照/恢复的时间戳**:删除时保留原 `created`+`modified`,恢复同理,所以恢复的笔记不会跳到「最近修改」顶部;索引的 404 与读失败区分正确。
|
|
132
|
+
- **`batch_put` 有界并发**:结果按下标回填、逐条容错、计数器只在同步段内改、`autoCommit()` 在循环外只调一次。
|
|
133
|
+
- **`attach` 覆盖保护**:同名默认拒绝、需 `force`;附件本身与 `noteTitle` 嵌入都走共享写策略。
|
|
134
|
+
- **HTTP 层的方法校验**:routes.ts 与 admin.ts 的每一个写 handler 都声明了方法、每一个读 handler 都用 `rejectNonRead`,29 处注册全被 `guardHandler` 包裹;`readBodyStream` 在 `aborted`/`close` 上会 settle,不会挂死请求。
|
|
135
|
+
- **`/admin/restart` 即便没走 `beginMutation` 也不可能双重重启**:`WikiServer.start()/restart()` 是单飞的。
|
|
136
|
+
- **密钥打码**:`maskConfigSecrets`/`stripMaskedSecrets` 对 `bridge.token`、`auth.password`、`ui.sendToAgent.token`、`git.remote` 的往返正确;本机 `/status` 返回的是不含凭据的 remote。
|
|
137
|
+
- **SSRF 守卫**:`isPrivateAddress`/`ipv6ToBytes` 是逐字节且完备的(`::1`、`::ffff:7f00:1`、NAT64、6to4、IPv4-compatible 都判私网),解析不可判时 fail-closed;`resolvePublicTarget` 用自定义 `lookup` **钉住**已校验 IP 防 rebinding,每一跳重定向都重新校验;`hostAllowed` 字母精确;`safeTokenEqual` 两侧哈希。
|
|
138
|
+
- **seed 的 ONE-SHOT 语义**:只有 404 才算缺失(`readSeedTiddler`/`presentOf`/`inspectSeedContent`),非 force 绝不覆盖已存在条目;`refreshSeedMarker` 跳过无变化的 PUT。
|
|
139
|
+
- **子进程退出处理**:身份检查防止被替换的旧子进程清掉新子进程的状态;`stop()` 会先清「迟到就绪」复探。
|
|
140
|
+
- **净化器**:`codePointToChar` 已把数字实体夹在 `0x10FFFF`,`�` 不再抛 `RangeError`。
|
|
141
|
+
- **client 半部**:我逐文件读了 `tw-frame.ts`、`panel.ts`、`index.ts`、`sidebar-entry.ts`、`theme-sync.ts`、`quick-note-dock.ts`、`session-summary.ts`、`status-cache.ts`、`sync-button.ts`、`editor-popup.ts`、`knowledge-fab.ts`、`tool-views.ts`、`note-widget.ts` 的关键段:定时器/监听/observer/ResizeObserver 在 dispose 路径上都被回收、可见性都在 `await` 之后复查、`iframe.src` 从不作为加载判据(只用 `dataset.loaded`)、`contentDocument` 读取都有守卫、`dangerouslySetInnerHTML` 只吃宿主净化过的 `/render` 输出。**未发现新缺陷。**
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## 建议的修复顺序
|
|
146
|
+
|
|
147
|
+
1. **P0 三连**(安全,同一个根因族):代理路径判定重写(#1)+ store 排除插件命名空间(#2)+ `/render` 的 `text`/`contextTitle` 校验(#3)。
|
|
148
|
+
2. **P1/P2 数据安全**:#4(删除前先读索引)、#5(令牌 AND 语义 + revision 不可持久化的措辞)、#6(`fs.watch` error 监听)、#7(`spawn` 前复查 `stopping`)、#8(`restore()` 用 finally 重装 extras)、#9(`runSwitch` 的 disposed 守卫)。
|
|
149
|
+
3. **P3**:#10–#18,其中 #10(提示词转义)、#11(空 limit)、#14(配置 `modified` 钉死)最值得顺手带上。
|
|
150
|
+
|
|
151
|
+
守门建议:#1/#2/#3 各加一条 E2E(用编码路径与转写正文断言 403),#4 加一条「索引读失败时原条目必须仍在」的顺序断言,#5 加一条「modified 不匹配即便 revision 相同也必须拒」的纯函数断言,#6/#7 各加一条源码级或注入式断言,#10 扩展现有 `verify-prompt.mjs` 断言(连续花括号),#11 加一条空 `?limit=` 的路由断言。
|
package/docs/features.md
CHANGED
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
| 能力 | 说明 |
|
|
6
6
|
|---|---|
|
|
7
7
|
| 🏠 **文档中心起步包** | 首次安装自动 seed:插件说明 + 「示例与文档」(主题汇总模板 / 教程 / 三个示例主题页),首页「📚 插件文档」栏一键查阅;**同名 tiddler 已存在一律安全跳过,绝不覆盖你的数据**(v0.16.22);插件说明里的**工具清单由工具注册表实时生成**,不会再写着「10 个工具」却已经 15 个(v0.22.0) |
|
|
8
|
-
|
|
|
8
|
+
| 🗂 **多知识库(v0.28.0)** | **多个 TiddlyWiki 同时在线**,每个库独立配置、独立 git 仓库(或共用仓库按文件夹物理划分 —— 「工作 + 个人」同仓、「书籍」独立仓,两种拓扑都支持)。开关是 `wikis.json` 的 `mode`,**缺省 `single`**:升级用户的行为逐字不变(位置仍由旧指针文件 > cordis 默认决定)。每个库有自己的编辑器与入口(右下角「知识库」菜单的分组、每个库自己的 `/tw/<id>/` 同源基址),设置页「知识库列表」负责增删改 / 启停 / 设为默认 / 开局自启 / 对 Agent 可见性;「移出列表」明确**不会删除目录**。快速笔记卡片也能选目标库(默认跟随你在 GUI 里看的那个库;**整张卡片一起换库**:标签建议 / 最近 / 附件 / 保存 / 弹窗编辑器同库)。 |
|
|
9
|
+
| 🔒 **对 Agent 的可见性(v0.28.0)** | 每个库可设「对 Agent 隐身」= **彻底不可达**:不进会话选择器、不进注入提示词、`tiddlywiki_*` 工具永不触及(人在 GUI 里照常用它)。会话级作用域**单选**(输入框上方的选择器,选择存在 `$DSH_HOME/dsh-tiddlywiki/sessions.json` 并跨重启存活),工具按会话路由,**可见库多于一个时每条回执都标明库名**,注入提示词按会话求值并读**该库自己**的 `prompt.*`。两条刻意的拒绝:作用域指向隐身库 → 忽略(隐身是硬边界);指向可见但**没在跑**的库 → **报错并带上库名**,绝不静默落到别的库 —— 静默换库会让笔记落进你没指定过的库,而且看起来一切正常。 |
|
|
10
|
+
| 🧩 **拆库引导(v0.28.0)** | 想把现有库按用途拆开(语料/归档 vs 工作集)时有一份现成流程:[docs/wiki-split.md](wiki-split.md) 顶部是**一整段可直接复制给 Agent 的话**;Agent 会先只读体检、再问五个问题(每个都给默认建议)、出方案表格等你确认,然后备份 → `git mv` 保历史 → 登记进插件 → 校验,**旧库一个都不删**。随包 skill `dsh-tiddlywiki-wiki-split` 会在启动时装进 `$DSH_HOME/skills/`(**只覆盖带标记的文件**,你自己写的 skill 不被碰),也可以直接说「用拆库 skill 帮我拆」。 |
|
|
11
|
+
| 📍 **知识库位置可切换** | 设置页「知识库位置」可把插件切换到**任意本地文件夹**(不用改 cordis 配置、不用重装):就地停/起 TW 子进程,目标目录没有 `tiddlywiki.info` 时自动 `--init server` 建一个全新知识库;选择记在 `$DSH_HOME/dsh-tiddlywiki/location.json`——一个**在 wiki 之外**的指针文件(所以切到新 wiki 后仍记得「我用的是哪个」),「恢复为配置默认」一键清除;**切换失败自动回滚**到原知识库并如实告知(v0.22.0)。⚠️ 它一次只有一个库在线;要**多库同时在线**见上面的「多知识库」(v0.28.0) |
|
|
9
12
|
| 🔄 **seed 更新检测** | seed 标记记录**内容哈希**:内置内容在本 wiki 预置之后更新过 → 设置页显示「⬆ 有更新」;被你改过 → 显示「✏️ 本地已修改」,且「重新初始化」前**二次确认**;两者都基于哈希判定,绝不猜(旧格式标记会明确显示「尚未启用更新检测」,不会误报为「你改过」,v0.22.0) |
|
|
10
13
|
| 🎨 **自定义样式** | 「自定义样式」seed:编辑器美化 / 窄屏侧栏隐藏 / menubar 加高 / 批注弹窗等 5 张通用样式表,新 wiki 也能一键初始化(可选,v0.16.22) |
|
|
11
14
|
| 📝 **可配置的注入提示词** | 插件注入每个会话的「TiddlyWiki 持久知识库」提示词可在设置页配置:**默认精简版**(v0.24.0 起 ~1.9KB,只保留工具 schema 表达不了的约定——同步纪律 / 工作区标记 / 先窄后宽检索 / 时效标注 / 链接格式),可选**完整版**(额外附一份**由工具注册表实时生成**的参数索引,不会再过期);`extra` 追加自定义规范、`override` 整段接管、可整体停用;**保存后无需重启 dsh web**(section 即时重注册,当前会话下一步即生效),设置页可**按表单当前值预览**即将注入的全文(未保存的形态切换也立刻可见,v0.21.0 / v0.22.7) |
|
package/docs/usage.md
CHANGED
|
@@ -70,6 +70,38 @@
|
|
|
70
70
|
- **🔧 知识库 FAB**:统一入口(TW 面板开关/重载、快速笔记、同步、TW 服务状态悬停 tip)。同步拉取到新内容会自动重启 TW(同端口)。
|
|
71
71
|
- **⚙️ 设置页**:DSH 设置 →「TiddlyWiki 知识库」:状态/重启、**知识库位置(可切换)**、常规配置、插件/主题/语言管理(**自带官方插件**的离线启停;经 TW 面板装的第三方插件以只读小节列出,见 v0.26.0)、**初始化**(seed 状态与重新初始化)。配置写入 `$:/plugins/dsh-tiddlywiki/config` tiddler,覆盖 cordis `config:` 块(tiddler 优先)。
|
|
72
72
|
|
|
73
|
+
### 🗂 多知识库(同时在线,v0.28.0)
|
|
74
|
+
|
|
75
|
+
默认是**单库模式**:一个知识库、一个 TW 子进程,一切与以前完全一样。要同时用多个库:
|
|
76
|
+
|
|
77
|
+
1. **加第二个库**:设置页 →「TiddlyWiki 知识库」→ **知识库列表** → 填根目录 + 文件夹名(+ 显示名)→
|
|
78
|
+
「添加知识库」。目录不存在会自动 `--init server` 建一个全新知识库;已存在的目录直接接管。
|
|
79
|
+
2. **切到多库模式**:同一区块的「运行模式」选 **多库**。此后清单里的库都可用;标了「开局自启」的
|
|
80
|
+
随 DSH 一起启动,其余在你打开或选中它时启动(**每个库一个 TW 子进程,约 150 MB**)。
|
|
81
|
+
3. **在界面里用它**:右下角「知识库」按钮的菜单会列出所有库(● 当前),点一个就切过去并打开它的
|
|
82
|
+
编辑器;每个库的编辑器走自己的同源地址 `/dsh-tiddlywiki/tw/<id>/`,**不会串台**。
|
|
83
|
+
4. **告诉 Agent 用哪个库**:输入框上方会出现「知识库」下拉(**只在可见库多于一个时出现**),选择是
|
|
84
|
+
**按会话**的,存在 `$DSH_HOME/dsh-tiddlywiki/sessions.json`,跨 `dsh web` 重启仍在。选一个没在跑的
|
|
85
|
+
库会自动把它启动(工具是同步解析作用域的,所以"选中"这一步必须顺手起它)。
|
|
86
|
+
5. **让某个库对 Agent 隐身**:知识库列表里点该行的「**对 Agent 隐身**」。隐身 = Agent 永远碰不到它:
|
|
87
|
+
不进选择器、不进注入提示词、检索 / lint / 反向链接都不含它。人仍然照常用 —— 适合书籍语料、导入归档。
|
|
88
|
+
6. **想拆分现有的库**:见 [拆分知识库](./wiki-split.md),那里有一段**可以直接复制给 Agent 的话**。
|
|
89
|
+
|
|
90
|
+
细节:
|
|
91
|
+
|
|
92
|
+
- **每个库有自己的一套设置**:笔记标签、语言、主题、`prompt.*` 都在该库自己的
|
|
93
|
+
`$:/plugins/dsh-tiddlywiki/config` 里。设置页顶部会显示「**配置作用域**:…」,点某个库那一行的
|
|
94
|
+
「配置」即可改它的设置 —— 作用域不说清就会出现"改了但不生效"。
|
|
95
|
+
- **git 按仓库生效**:多个库可以共用**一个**仓库(例如「工作」+「个人」放在 `D:/notes/` 下的两个子
|
|
96
|
+
目录),也可以各自独立仓库。共用仓库时**只有一个自动提交者**(`git add -A` 会作用于整个工作树),
|
|
97
|
+
`git.*` 以该仓库下**列表里第一个库**为准、配置不一致时打印警告;pull 之后**只重启内容真的变了的库**。
|
|
98
|
+
- **快速笔记的目标库**:卡片里有一个「写入」下拉,**默认跟随你在 GUI 里看的那个库**(右下角菜单切换焦点库,它就跟着变);在里面手动选过之后,这张卡片就不再被焦点带走。改目标库是**整张卡片一起换**——标签建议、最近列表、附件上传、保存、以及随后弹出的 TW 编辑器都会落到同一个库。目标库没在运行时卡片会先提示你,而不是等你写完再失败。
|
|
99
|
+
- **「移出列表」不删目录**:只是让插件不再管理它,内容原样保留。
|
|
100
|
+
- **清单文件**:`$DSH_HOME/dsh-tiddlywiki/wikis.json`(在每本 wiki 之外,所以它不会随某个库被搬走)。
|
|
101
|
+
手写时注意:`id` 只能用小写字母/数字加 `._-`,且不能是 `status`/`files`/`recipes`/`render` 这类与 TW
|
|
102
|
+
根路径撞名的保留字;两个库不能指向同一目录,**也不能互相嵌套**(外层库的 `git add -A` 会把内层内容
|
|
103
|
+
一起提交,插件会直接拒绝这种登记)。
|
|
104
|
+
|
|
73
105
|
### 📍 知识库位置(可切换,v0.22.0)
|
|
74
106
|
|
|
75
107
|
设置页顶部「**知识库位置(可切换)**」区块显示当前实际服务的文件夹、这个位置**是怎么决定的**(指针文件 / cordis 配置 / 默认值)、指针文件路径,以及**同目录下其它看起来像 wiki 的文件夹**(含 `tiddlywiki.info`)的快捷填入按钮。
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# 把知识库拆成几个独立的小库
|
|
2
|
+
|
|
3
|
+
> **这一页是给人看的。** 下面的「发给你的 Agent」整段复制,粘到你自己的 agent 会话里就行 ——
|
|
4
|
+
> 它会先做只读体检,再问你几个问题,最后给你一张方案表格等你确认。
|
|
5
|
+
>
|
|
6
|
+
> Agent 侧还有一份等价的操作手册(skill):`dsh-tiddlywiki-wiki-split`。装好插件(≥ 0.28.0)
|
|
7
|
+
> 后它就在你的 skill 目录里,你也可以直接说「用拆库 skill 帮我拆」。
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 发给你的 Agent(整段复制)
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
我的 dsh-tiddlywiki 知识库该拆了:日常笔记和一大坨只读语料放在同一个库、同一个 git 仓库里,
|
|
15
|
+
Agent 检索每天在几十倍于工作集的内容上跑。
|
|
16
|
+
|
|
17
|
+
请按「拆分知识库」的流程帮我做,规则:
|
|
18
|
+
1) 先只读体检(统计 tiddler 数、tag 分布、files/ 附件体积、git 拓扑与仓库体积),不要改任何文件;
|
|
19
|
+
2) 一次问完你需要知道的几个问题,每个问题都给出你建议的默认答案;
|
|
20
|
+
3) 给我一张方案表格(每个库:显示名 / 收什么 / 目录 / git 仓库 / 是否需要被 Agent 看见)等我确认;
|
|
21
|
+
4) 我确认之前,不要创建、移动、删除任何文件;
|
|
22
|
+
5) 执行时先做备份,用 git mv 保留历史,旧库一个都不许删。
|
|
23
|
+
|
|
24
|
+
如果你有 dsh-tiddlywiki 的 wiki-split skill,直接用它。
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 什么时候值得拆
|
|
30
|
+
|
|
31
|
+
出现下面任意一条,拆分就不是洁癖而是止损:
|
|
32
|
+
|
|
33
|
+
- **检索被淹没**:Agent 搜一个词,前十条全是语料/导入的旧归档,工作笔记排在后面。
|
|
34
|
+
- **种类不同、生命周期不同**:写作稿需要备份与双机同步;书籍语料基本只读;导入的
|
|
35
|
+
Obsidian/Logseq 归档迁完就不动;Agent 工作记忆天天写。它们混在一个库里,谁的行为都照顾不到。
|
|
36
|
+
- **仓库太重**:图片附件让 `.git` 涨到几百 MB,而 60 秒一次的自动提交还要背着它。
|
|
37
|
+
- **想分别备份到不同的远端**:语料不需要和私人笔记推到同一个仓库。
|
|
38
|
+
|
|
39
|
+
**不需要拆的情况**:只有一类内容,或者你只是"觉得库有点大"。拆分本身有成本(每个库是一个
|
|
40
|
+
独立的 TiddlyWiki 进程)。
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Agent 会问你什么(提前想好,回答会快很多)
|
|
45
|
+
|
|
46
|
+
| 问题 | 它要的答案 | 你大概会说什么 |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| 拆成几个库,各叫什么? | 显示名(会出现在界面入口/选择器里) | 「书籍」「工作」「个人」 |
|
|
49
|
+
| 每个库收什么? | 按 tag / 关键词 / 目录 / 时间段说清归属 | 「`书籍` 标签的都归书籍库」 |
|
|
50
|
+
| git 怎么分? | 共用现有仓库(按文件夹物理划分)/ 各自独立仓库 | 「书籍独立仓库;工作和个人共用现在的仓库」 |
|
|
51
|
+
| 哪些库对 Agent 可见? | 可见 = 能被检索/写入;隐身 = Agent 永远碰不到 | 「书籍对 Agent 隐身」 |
|
|
52
|
+
| 现在就搬,还是先出报告? | 先出报告 = 只读 dry-run | 「先给我看一眼方案」 |
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Agent 会怎么做(可以随时叫停)
|
|
57
|
+
|
|
58
|
+
1. **只读体检**:tiddler 总数、tag 分布 Top-N、`files/` 体积、`git rev-parse --show-toplevel`
|
|
59
|
+
与仓库体积。这一步不改任何东西。
|
|
60
|
+
2. **出方案**:一张表(库名 → 收纳规则 → 目录 → git 仓库 → 对 Agent 是否可见 + 是否开局自启),
|
|
61
|
+
附上"预计搬多少条"和**不可逆点提示**。
|
|
62
|
+
3. **等你确认**。确认后才动手。
|
|
63
|
+
4. **备份**:给现有仓库打一个 tag 或clone 一份(能整体回滚)。
|
|
64
|
+
5. **搬迁**:建目录 → `git mv` 尽量保住历史 → 每个库补上 `tiddlywiki.info`。
|
|
65
|
+
6. **登记**:把库写进插件的知识库清单(设置页「知识库列表」或
|
|
66
|
+
`$DSH_HOME/dsh-tiddlywiki/wikis.json`),按你的答案设 `agentVisible` / `autostart`。
|
|
67
|
+
7. **校验**:各库 tiddler 计数与预期一致、工作库检索不再命中语料、每个库能独立打开并提交、
|
|
68
|
+
Agent 作用域隔离生效。
|
|
69
|
+
|
|
70
|
+
**回滚**:旧库目录不会被删;`git reset --hard <备份 tag>` 可以退回去。
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 拆分之后
|
|
75
|
+
|
|
76
|
+
- **每个库有自己的一套设置**(各自的配置 tiddler):笔记标签、语言、主题、是否对 Agent 可见。
|
|
77
|
+
设置页顶部会显示「配置作用域:…」,点某个库那一行的「配置」就能改它的设置。
|
|
78
|
+
- **每个库的编辑器和入口**:右下角「知识库」按钮的菜单里会列出所有库;侧边栏/右栏入口按你的
|
|
79
|
+
开关生成。每个库在界面上有自己的一份 TW 编辑器。
|
|
80
|
+
- **每个库一个 TW 进程**(约 150 MB):标了「开局自启」的随 DSH 启动,其余的在你打开或选中它时
|
|
81
|
+
启动 —— 打开一个没在跑的库会自动把它拉起来。
|
|
82
|
+
- **对 Agent 隐身的库**不会出现在会话选择器里,检索/lint/反向链接也永远不碰它。
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 前置条件与旧版本
|
|
87
|
+
|
|
88
|
+
- 需要插件 **≥ 0.28.0**(多知识库模式)。
|
|
89
|
+
- 更早的版本只有「切换知识库位置」(同一时刻只有一个库是活的)。用它也能缓解检索被淹没,但
|
|
90
|
+
**做不到同时在线**:你切到语料库去翻资料时,Agent 也跟着切过去了。旧版本的降级做法是:
|
|
91
|
+
建好目录 → 手工搬迁 → 在设置页「知识库位置」里切换,并在自己的笔记里写清"语料在另一个库"。
|
|
92
|
+
|
|
93
|
+
## 相关
|
|
94
|
+
|
|
95
|
+
- [`usage.md`](./usage.md) —— 安装、配置与日常使用
|
|
96
|
+
- [`features.md`](./features.md) —— 能力清单
|
|
97
|
+
- [`CHANGELOG.md`](./CHANGELOG.md) —— 版本记录
|