dsh-tiddlywiki 0.33.4 → 0.34.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.
Files changed (49) hide show
  1. package/README.md +2 -4
  2. package/docs/CHANGELOG.md +15 -3
  3. package/docs/development.md +0 -1
  4. package/docs/features.md +1 -2
  5. package/docs/seed-initialization.md +3 -7
  6. package/docs/usage.md +1 -48
  7. package/lib/client.js +3 -3
  8. package/lib/index.js +238 -1480
  9. package/lib/index.js.map +1 -1
  10. package/package.json +4 -7
  11. package/src/client/i18n-config.ts +1 -20
  12. package/src/client/settings-page-config.ts +3 -25
  13. package/src/client/settings-page.ts +1 -1
  14. package/src/host/admin-secrets.ts +0 -10
  15. package/src/host/clip-bridge-payload.ts +1 -1
  16. package/src/host/config.ts +1 -30
  17. package/src/host/git.ts +1 -1
  18. package/src/host/prompt.ts +5 -32
  19. package/src/host/routes-helpers.ts +1 -1
  20. package/src/host/routes-tw-proxy.ts +1 -1
  21. package/src/host/routes.ts +1 -46
  22. package/src/host/seeds-registry.ts +0 -38
  23. package/src/host/seeds.ts +1 -20
  24. package/src/host/tools-notes-write.ts +1 -1
  25. package/src/host/tools-read.ts +17 -8
  26. package/src/host/tools-support.ts +3 -3
  27. package/src/host/tw-api.ts +25 -6
  28. package/src/host/wiki-instance.ts +1 -17
  29. package/src/index-config.ts +0 -19
  30. package/src/index-git.ts +2 -14
  31. package/src/index-prompt.ts +1 -5
  32. package/src/index-routes.ts +11 -25
  33. package/src/index.ts +2 -44
  34. package/docs/plans/2026-09-17-wechat-publish-design.md +0 -208
  35. package/docs/wechat-publish-setup.md +0 -459
  36. package/src/host/routes-wechat.ts +0 -230
  37. package/src/host/seed-publish-spec.ts +0 -146
  38. package/src/host/seed-wechat-docs.ts +0 -70
  39. package/src/host/seed-wechat-publish.ts +0 -81
  40. package/src/host/wechat-publish-contract.ts +0 -275
  41. package/src/host/wechat-publish.ts +0 -454
  42. package/tools/wechat/backfill-publish-state.mjs +0 -165
  43. package/tools/wechat/create-article.js +0 -114
  44. package/tools/wechat/install-wechat-adapters.mjs +0 -137
  45. package/tools/wechat/publish-note-imgs.js +0 -379
  46. package/tools/wechat/publish-note.js +0 -280
  47. package/tools/wechat/seed-publish-spec-now.mts +0 -77
  48. package/tools/wechat/wechat-html.js +0 -190
  49. package/tools/wechat/weixin-flow.js +0 -461
package/README.md CHANGED
@@ -40,7 +40,6 @@ dsh plugin --profile web add dsh-tiddlywiki
40
40
  | 🚰 **重启不丢写入** | 停/重启 TW 只有一条路:先排干 syncer 队列再停,排不干净会如实报 `drained:false` |
41
41
  | 🕰 **可审计、可回溯** | 库本身就是 git 仓库(自动 commit + 一键同步);反向链接、标签、体检(`lint`);阶段性内容可标 `valid-until` / `superseded-by`,**淘汰永远由人决定,插件不自动删** |
42
42
  | 📦 **数据可带走** | 磁盘上就是 `.md` + `.meta` + `.tid` + `files/`,不需要这个插件也能读 |
43
- | 📮 **可选发布** | 打开后能把笔记一键发到微信公众号草稿箱(默认关闭,不打扰) |
44
43
 
45
44
  ## 🤖 Agent 工具(15 个)
46
45
 
@@ -50,7 +49,7 @@ dsh plugin --profile web add dsh-tiddlywiki
50
49
 
51
50
  ## 🔧 配置
52
51
 
53
- 插件向每个会话注入一段可配置的提示词(默认精简版 ~1.9KB,可整体关掉或替换),其余配置在设置页:知识库位置、git(自动提交/远端/分支)、笔记标签与工作区标记、快速笔记、剪藏桥、公众号发布、界面语言、访问口令。
52
+ 插件向每个会话注入一段可配置的提示词(默认精简版 ~1.9KB,可整体关掉或替换),其余配置在设置页:知识库位置、git(自动提交/远端/分支)、笔记标签与工作区标记、快速笔记、剪藏桥、界面语言、访问口令。
54
53
 
55
54
  完整配置项与远程访问(Tailscale / 内网 / 域名)见 [docs/usage.md](docs/usage.md)。
56
55
 
@@ -60,7 +59,6 @@ dsh plugin --profile web add dsh-tiddlywiki
60
59
  |---|---|
61
60
  | 完整能力清单(每条的**为什么**) | [docs/features.md](docs/features.md) |
62
61
  | 使用指南 / 工具参考 / 配置 / 远程访问 | [docs/usage.md](docs/usage.md) |
63
- | 发布到微信公众号(可选,需额外安装) | [docs/wechat-publish-setup.md](docs/wechat-publish-setup.md) |
64
62
  | 把知识库拆成几个独立的小库(含可复制给 Agent 的提示词) | [docs/wiki-split.md](docs/wiki-split.md) |
65
63
  | 开发与发布流程(给维护者) | [docs/development.md](docs/development.md) |
66
64
  | 版本变更历史(逐版本、含"为什么";**README 不再重复它**) | [docs/CHANGELOG.md](docs/CHANGELOG.md) |
@@ -69,4 +67,4 @@ dsh plugin --profile web add dsh-tiddlywiki
69
67
 
70
68
  - **GitHub**:https://github.com/bbqisbbq/dsh-tiddlywiki | **npm**:`dsh-tiddlywiki`
71
69
  - **用得不顺请来提 issue**:https://github.com/bbqisbbq/dsh-tiddlywiki/issues —— 这插件原本是作者给自己做的,现在有同道人用;**你不报,我就不知道哪里不对**。
72
- - MIT;Node ≥ 22;GitHub topics:`dsh` `dsh-plugin` `tiddlywiki` `knowledge-base` `knowledge-management` `note-taking` `journal` `git-sync` `local-first` `agent-tools` `publishing` `wechat` 等
70
+ - MIT;Node ≥ 22;GitHub topics:`dsh` `dsh-plugin` `tiddlywiki` `knowledge-base` `knowledge-management` `note-taking` `journal` `git-sync` `local-first` `agent-tools` 等
package/docs/CHANGELOG.md CHANGED
@@ -6,6 +6,18 @@
6
6
 
7
7
  ## 最近版本(详细)
8
8
 
9
+ - **v0.34.1**(2026-11-06):**修 `tiddlywiki_search` 的「纯标签筛选」**(issue #1,用户实测报告)。
10
+ - **症状**:只传 `tags` / `tag`、不传 `query` 时**三层坏**,一层修完才露出下一层 —— ① `Cannot read properties of undefined (reading 'split')`;② 崩溃修掉后**恒返回 0 条**(标签过滤等于没生效);③ 再修报 `tool "tiddlywiki_search" returned invalid output: value is not lossless JSON`。带 `query` 时一切正常,所以「按标签列出」这条日常用法一直没被任何用例走过。
11
+ - **三处根因**:① `TiddlyWikiClient.search()` 签名写死 `query: string` 且首行裸调 `query.split(…)`,而工具 schema 里 `query` 本就是可选的(`snippetAround` 同源,正文 > 160 字时同样炸);② 空查询的短路 `if (terms.length === 0) return { items: [], total: 0 }` 写在 `wantedTags` 过滤**之前** ⇒ 标签判断永远执行不到;③ 回执里 `query: args.query` 把 `undefined` 回给宿主,DSH 的 lossless-JSON 校验因此判整次调用 invalid output。
12
+ - **修法**(原则:空 query 仍不匹配任何东西,但**有其它过滤条件时不短路**):`search(query: string | undefined)` + `(query ?? '')`;短路条件收窄成「terms 为空**且** tags/since/type/field 都没有」;工具层先把 `args.query ?? ''` 归一,回执与 `snippetAround` 都用归一后的值;回执渲染在无关键词时改写成「按条件列出(按修改时间倒序)」,不再出现空的「搜索「」」。
13
+ - **守门**:`scripts/verify-tools.mjs` 新增一组 E2E(`tags` / 单值 `tag` / 多标签 AND / `tags`+`field:value` / `type`+`tag` / 长正文摘要 / 回执可 JSON 无损往返 / 渲染不含「搜索「」」),并把旧的「空白 query + tag 必须 0 条」断言改判成「**光有空查询**才 0 条」。反向验证:把归一去掉 ⇒ 无损往返断言 + schema 反查(`query 应为 string,实际 undefined`)双双变红;把短路条件还原 ⇒ 新用例与旧的多词 AND 用例变红。
14
+
15
+ - **v0.34.0**(2026-10-27):**移除「发布到微信公众号」功能**(作者裁定:没必要)。
16
+ - **删了什么**:三条路由 `/wechat/{ready,publish,publish/status}`;配置 `wechat{enabled,command,adapter,token,dsn,endpoint}` 与设置页「可选功能:微信公众号发布」整块(含提示词预览里随开关变化的草稿字段);注入提示词里的发布前约定(`PUBLISH_RULE`);三个 gated seed `publish-spec` / `wechat-setup` / `wechat-publish`;`WechatPublishRunner` + 契约层(`wechat-publish.ts` / `wechat-publish-contract.ts`,约 900 行);TW 工具栏「发布到公众号」按钮 bundle 及其 `build-*` / `gen-*` / 三个 `verify-wechat-*` 守门;`tools/wechat/` 的 opencli adapter 全家(`wechat-html.js` / `weixin-flow.js` / `publish-note*` / 安装与回填脚本);文档 `docs/wechat-publish-setup.md` 与设计稿。
17
+ - **顺手拆掉的机制**:seed 的 `gate` 机制随最后一个使用者一起删除 —— `runAllSeeds` 不再有 gate 分支,`SeedContext.wechat` 也没了;seed 注册表 **13 → 10 项**(核心 3 + 起步 2 + 可选 5)。`admin-secrets` 不再打码 `wechat.token`,`STRING_CONFIG_PATHS` / `BOOLEAN_CONFIG_PATHS` 去掉五条 `wechat.*`。客户端 i18n 475 → 467 键,`index.ts` 公共面 −33 个名字(289 个)。
18
+ - **旧 wiki 怎么办**:插件**不会**替你删已写进 wiki 的那几篇内容(`发布元数据规范` / `微信公众号发布指南` / `$:/plugins/dsh/wechat-publish`)——自行在 TW 里删除即可。旧配置 tiddler 里残留的 `wechat` 块按未知键透传保留、不再被读取。
19
+ - **为什么一起删而不是留着**:它是本插件里唯一会**按配置去 spawn 外部 CLI** 的能力,牵着一整条 opencli + 浏览器扩展的安装链(插件替你装不了),维护面却是四个守门脚本加两份生成脚本;收益只在「一键存草稿箱」,而发表仍要人手扫码。
20
+
9
21
  - **v0.33.4**(2026-10-26):**四路只读审计(host / 工具与数据路径 / 客户端 / 死代码)后的修复版**。
10
22
  - **数据安全**:trashbin 软删除把副本标签整体替换成 `$:/tags/trashbin`,恢复时又从 `fields.tags` 找原文——而 TW 的 `tags` 是已知字段、永远不进 `fields`,于是装了 trashbin 的库删完再恢复**标签全丢**且静默(现在副本写 `[标记, ...原标签]`,恢复只剥标记);写工具(put / batch_put / delete / rename / append / replace / attach)**没有** `isBlockedProxyTitle()` 守卫,模型可直接改写 `$:/plugins/dsh-tiddlywiki/config`(`wechat.command` 会被 spawn、`git.remote` 下次同步生效)——读侧早就拦了,写侧一直敞着。
11
23
  - **互斥与假成功**:`MutationLock.end()` 没有所有权校验,而 `/admin/seeds/run` 对**每条**响应都调它(最常见的「重新初始化但什么都没改」压根没 begin)⇒ 会把此刻正持锁的 `/sync` 释放掉,`/admin/restart` 随即拿锁、两条路径同时排干 + 停子进程;现在 `end(label)` 校验持有者。`/admin/info`(改插件/主题/语言也会 drain + 重启)一直没接这把锁;`/restart` 与 `/admin/restart` 在目标库没在跑时回 `{ok:true,drained:true}`(什么都没重启)。
@@ -39,14 +51,14 @@ v0.33.1 修好了 `key` 配对,但用户实测仍是纯文本,且 devtools
39
51
 
40
52
  - **v0.32.0**(2026-10-26):**新增 `.tid` / `.tiddler` 文件的侧栏预览**(⑦ 的第三项)。wiki 文件夹本身就是一堆 `.tid`,而 DSH 的文件预览把未知扩展名当纯文本 —— 打开一个 `.tid` 看到的是 `title: …` 字段行加原始 wikitext,`[[链接]]`/`<<宏>>` 几乎不可读。现在注册一个预览实现:元数据用 `ctx.documentPreviews.register({ id, extensions: ['tid','tiddler'], priority, title, loading: 'text-pages' })`,正文以**同一个 id** 注入 keyed、Session 作用域的 `sidebar.right.tab.document`;正文把 wikitext POST 到本插件自己的 `/dsh-tiddlywiki/render`(铁律 #7 的净化管道)换成真 TW 渲染结果,字段行单独排一行摘要。**绝不白屏**是这个功能的硬约束:拿不到内容 → "等待文件内容…";只有字段没有正文 → 明确说明;渲染失败 → `<pre>` 原文 + 一句"渲染失败,下面是原文"。宿主没有 `documentPreviews` 服务时**什么都不做**(老版本 DSH 不受影响),且元数据与正文必须**成对**(拿不到 slots 就把已注册的元数据撤回,单边注册会让面板空白)。契约是从宿主 `app.asar` 的 `dsh-client-ui-sidebar-documentpreview/lib/client.js` 逐行核实后写的(含 `TextPreview` 的完整 props 签名与 `content` 三态),全部记在 `docs/development.md`。**守门**:新增 `scripts/verify-tid-preview.mjs`(进 `verify:unit`,10 条)——纯解析用例(字段/CRLF/没有字段必须整份当正文/只有字段/空文本)+ **成对注册**(两处必须用同一个 `TID_PREVIEW_ID` 常量、正文必须先 `slots.inject` 再 `register`)+ **绝不白屏**(`<pre>` 兜底、三种空态各自的 `t()` 文案)+ 渲染必须走 `RENDER_ENDPOINT` 且失败回 `null` 不抛 + 可选依赖的静默跳过与回滚。纯逻辑拆在 `client/tid-parse.ts`(宿主 barrel 也要导它,而宿主包里不能出现 `react`),`client/tid-preview.ts` 只放 UI。客户端 i18n 473 → 477 键。
41
53
 
42
- - **v0.31.1**(2026-10-26):**新增 `tiddlywiki_export`(笔记落盘,配合 `present` 交付)**。「把这条笔记给我」此前只有两条路:正文贴进回复(长笔记会撞回执预算,`get` 还得分页),或者让人类自己去 TW 里复制;DSH 的 `present` 能交付**文件**,但一直缺「把笔记变成文件」这一步。新工具 `export(title, format?, dir?)`:读一篇笔记 → 按格式落盘 → 回 `{path, bytes, format}`,助手紧接着 `present([{path, description}])` 即可。三种格式:`md`(YAML frontmatter + 正文,默认)、`tid`(TiddlyWiki 原生字段行 + 空行 + 正文)、`json`(完整 tiddler)。三条纪律都不是可选项:① **绝不覆盖同名文件**(导出是旁路产物,静默覆盖等于毁掉上次的交付物);② **`$:/plugins/dsh-tiddlywiki/` 系统条目拒绝导出**(可能是密钥,走铁律 #6 的共享原语);③ 读失败 ≠ 不存在(铁律 #3)。自定义字段两处都收(顶层键 + `fields`)—— 只收一处会让「导出丢字段」。守门 `scripts/verify-tools.mjs` 新增一条 7 组断言的 E2E(三格式内容逐项核对、同名拒绝、不存在回 `notFound` 且不写空文件、系统条目拒绝、相对 `dir` 拒绝),并与既有的「真实返回值必须通过 `output.schema`」自动联动。工具数 16 → 17(AGENTS §2、`verify-tool-views` / `verify-prompt` 的计数断言、`TOOL_VIEW_KEYS` + `TOOL_LABELS` + `i18n-card` 的 `card.toolExport` 同步)。
43
54
 
44
- - **v0.31.0**(2026-10-26):**对齐 DSH 0.2.1 的一轮改造 + 两条真实回归修复**。① **选库入口搬到官方座位**:`conversation.input.selector.context` 在 0.2.1 里**不存在**(v0.28.8 起的 `scope-seat.ts` 整个是死代码),选库改挂官方内联座位 `conversation.input.left`(`id: 'wiki-scope'`),`scope-seat.ts` 删除、dock 里那套 `scope` 内联渲染与自测量一并清掉;右侧栏 guide 条目补 `id` + `keepMounted: true`(0.2.1 的分页约定)。② **新增 `scripts/verify-dsh-contract.mjs`**:解析宿主 `app.asar` 里的 `dsh-cordis-client-runner`,把 103 条官方座位目录与我们源码里注册的 6 个座位逐一比对(存在性 / kind / `tool.call.toolview` 的 54 个已占 key 是否撞车),并断言 `engines.dsh` 已声明 —— 此前"座位名写错"只会静默不渲染。③ **`tiddlywiki_get` 支持分页**(`offset`/`limit`,默认 12000 字符):超长笔记一次读全会撞宿主的 12500 token 回执上限;回执明确写"正文 a–b / 共 N 字符"并给出下一步 `offset`。`search`/`lint` 的回执也加了同量级的体量预算(命中条数与计数结论**永不截断**,只截示例并说明省略了多少)。④ **16 个工具补 `output.schema`**:DSH 0.2.1 在每次调用后用 `validateJsonSchemaValue` 校验返回值,不过即抛 `ToolOutputError` —— 而 `sdk.ts` 的 `output.schema` 自 v0.20.0 起写死成 `{}`(当时注释断言"没人读它",**实测为假**)。现在按"只声明必定存在且类型稳定"的规则为 16 个工具各写一份,并加两条守门:每个工具必须声明非空 object schema;**用 E2E 里每一次真实返回值反查声明**(抓"schema 比现实更严"这种线上致命错误)。⑤ **skill 改用 `ctx.skills.register`**:拆库 skill 过去只能往 `$DSH_HOME/skills` 写文件,现在优先注册到宿主 skill 注册表(内存、随插件卸载消失、不碰用户目录),只有宿主没有该服务时才回落写文件;新增 `scripts/verify-embedded-skill.mjs`(解析/坏输入/disposer/注册表抛错/**启动顺序必须"先注册后回落"**)。⑥ **新增 `/note` 斜杠命令**:一句话记笔记,直接写进当前会话的知识库、不产生模型消息;同名笔记**已存在就拒绝**并指路("同一件事写多份互相矛盾"是最难收拾的失败形态),且写成 `inbox` + 工作站标记、**不打 `agent-written`**(那是人写的);守门 `scripts/verify-note-command.mjs`。⑦ **提示词两处修订**:新增「### 先查再答」(模型可见工具从 57 涨到 243 之后,新会话不再主动检索 —— 注入本身没坏,`system/message` 里逐字可查,坏在提示词只讲"怎么写"、从不讲"什么时候该来查")与「### 时效与去重」(作者原话:agent 爱写短命内容、同一件事写多份有差异的、正文里堆变更流水)—— 钉子:一件事只留一处权威表述(先搜、命中就**原地改那一节**)、每条笔记必须看得出"还有效到什么时候"(`valid-until`/`review-after`/"长期有效",说不清时效的别写)、**不写流水账**、**读到就先判时效**。slim 正文 1079 → **1390 字符**(预算 1000 → 1470,与逐条断言绑定),并把提示词正文的拼装从"硬编码数组 + 导出的块清单"两份实现收成 `governanceBlocks()` 单一来源 —— 加规则时 `verify-prompt` 当场抓到这个双份实现。
45
55
 
46
- ## 历史版本(一句话,150 条,新的在上)
56
+ ## 历史版本(一句话,152 条,新的在上)
47
57
 
48
58
  | 版本 | 日期 | 变更 |
49
59
  |---|---|---|
60
+ | v0.31.1 | 2026-10-26 | 新增 `tiddlywiki_export`(笔记落盘:md/tid/json,同名拒绝覆盖、系统条目拒绝)+ 工具数 16→17 |
61
+ | v0.31.0 | 2026-10-26 | 对齐 DSH 0.2.1 的改造(选库入口搬到 `conversation.input.left`、`verify-dsh-contract`、`get` 分页、16 个工具补 `output.schema`、skill 走 `ctx.skills.register`、新增 `/note` 命令、提示词加「先查再答」「时效与去重」) |
50
62
  | v0.30.65 | 2026-10-09 | 新增 tiddlywiki_replace 工具(笔记正文局部替换) |
51
63
  | v0.30.63 | 2026-10-09 | 默认提示词把「笔记内引用」与「聊天引用」拆成两条链接规则;`config.prompt.override` 设置页说明勘误 |
52
64
  | v0.30.62 | 2026-10-09 | 第二轮审查三类修复(回收站两种格式合并 / `git_sync`·`git_resolve` 改用会话作用域目录 / 剪藏目标库没在跑改 503 点名)+ 提示词 −14% |
@@ -65,7 +65,6 @@ node scripts/gen-seed-ui-styles.mjs '<wiki>/tiddlers/<样式.css>' … src/host/
65
65
  | `/dsh-tiddlywiki/session/summary` | POST | 会话「知识库」Tab 汇总 |
66
66
  | `/dsh-tiddlywiki/session/wiki` | GET/POST | 会话级知识库作用域:读 = 当前选择 + 解析结果(`scope`/`resolved`/`reason`/`mode`,v0.29.0 起带 `mode`);写 = 选择或清空(v0.28.0) |
67
67
  | `/dsh-tiddlywiki/agent/sessions` `/modes` `/send` `/create` | GET/POST | TW「发送给 Agent」:会话/模式/发送/新建 |
68
- | `/dsh-tiddlywiki/wechat/ready` `/publish` `/publish/status` | GET/POST | 公众号发布(可选功能,`wechat.enabled` 关闭时一律 403,v0.23.3) |
69
68
  | `/dsh-tiddlywiki/api/*` | any | 透传 TW 服务(JSON) |
70
69
  | `/dsh-tiddlywiki/tw/*` | any | 同源 TW 代理(远程访问核心);`/tw/<id>/…` 按库定向,裸 `/tw/` = 默认库别名(v0.28.0) |
71
70
  | `/dsh-tiddlywiki/admin/state` `/info` `/config` `/restart` | GET/POST | 设置页:状态快照 / 插件·主题·语言 / 配置读写 / 重启(v0.28.0 起全部按 `?wiki=` 定向) |
package/docs/features.md CHANGED
@@ -24,7 +24,7 @@
24
24
  | 🌐 **中英双语界面**(v0.30.6) | 插件自己的界面(FAB / 侧边栏 / 快速笔记 / 工具卡片 / 设置页 / toast)与 TW 子进程**共用同一个 `uiLanguage` 设置**;文案按区域分片成 `i18n-*.ts` 的 `zh`/`en` 目录,守门保证两侧键集合一致、且**客户端代码里不再有硬编码中文文案** |
25
25
  | 🤖 **Agent 工具** | 15 个 `tiddlywiki_*` 工具:检索(**相关度排序 + 命中处片段 + 字段过滤**)、读写、**增量追加**、批量、重命名、**软删除/回收站**、**反向链接**、**附件入库**、**知识库体检**、git 同步与冲突解决(v0.30.5 起 `git_sync` **一次同步所有可同步的库**、按仓库去重、逐仓库独立成败;**v0.30.58 起「有远端」= 配了 `git.remote` **或**该库所在仓库自己的 `origin`**,配置为空即回落、回执写明远端与来源;v0.19.0;检索/最近仍在**服务端**排除二进制附件,大 wiki 上从 515MB/17s 降到 ~0.4s) |
26
26
  | 🛡️ **不会被覆盖的写入** | 所有写入路径(agent 工具 **与** 快速笔记/编辑器路由)都**先读后写**:不传 tags 就保留原有标签、自定义字段与**内容类型**(`text/css`/wikitext 等不会被重置成 Markdown,v0.20.1);`tiddlywiki_put(..., expectedModified/expectedRevision)` 与 `tiddlywiki_delete(..., expectedModified/expectedRevision)` 乐观并发——读取后若有人(在 TW 编辑器里)改过,写入/删除被拒绝(HTTP 409)而不是静默覆盖或丢进回收站;**给了哪个令牌就必须匹配哪个**(v0.23.5:两个令牌是 AND 不是 OR——`revision` 是 TW 的内存计数器、重启会复位,旧逻辑会因此静默放行跨重启的覆盖);`tiddlywiki_attach` 的同名标题**默认拒绝**(要覆盖必须 `force: true`);`tiddlywiki_delete` 默认**软删除进回收站**,`tiddlywiki_trash` 可恢复(v0.19.0 / v0.19.1 / v0.19.5 / v0.23.5)。**v0.25.0**:`tiddlywiki_append` / `tiddlywiki_rename` / `tiddlywiki_batch_put`(逐条目)也支持并发令牌——它们同样是「读全文 → 整篇回写」,此前**没有令牌可传**,而注入的提示词恰恰让模型回传 `expectedModified`;`tags: []` 现在表示**清空全部标签**(此前被当成「未传」而静默保留,模型按 lint 的 junk-tags 建议去清标签时什么都不会发生);`tiddlywiki_rename` 更新引用时若某个引用者在遍历期间被改动过,**跳过它**并在回执里报出数量,而不是覆盖它 |
27
- | 🏷 **工作区感知的读写** | **新建**笔记自动带 `ws/<项目名>` 标签 + `workspace` 字段(项目名取自会话工作目录,`note.workspaceMark` 可关);`tiddlywiki_search` 因此默认**先在本项目里找、区内 0 条才自动扩大到全库**并在回执里写明实际范围,`query` 按空白切词、**全部词命中**才算(AND)。工作区只是**附加**标记,绝不改写笔记原有的标签/字段,也**只在新建时**打(v0.24.0) |
27
+ | 🏷 **工作区感知的读写** | **新建**笔记自动带 `ws/<项目名>` 标签 + `workspace` 字段(项目名取自会话工作目录,`note.workspaceMark` 可关);`tiddlywiki_search` 因此默认**先在本项目里找、区内 0 条才自动扩大到全库**并在回执里写明实际范围,`query` 按空白切词、**全部词命中**才算(AND)。工作区只是**附加**标记,绝不改写笔记原有的标签/字段,也**只在新建时**打(v0.24.0)。**只按标签列笔记**也直接可用(v0.34.1):`query` 可省略,只传 `tags`/`tag`/`field` 就是按条件列出(此前这种调用会崩、修掉崩溃后恒返 0 条) |
28
28
  | 🕰 **时效性内容只提示不删** | 阶段性笔记(版本记录 / 部署步骤 / 排期 / 临时方案)可声明 `valid-until`(硬过期)或 `review-after`(该复查),被取代时写 `superseded-by` + `superseded` 标签并**保留旧笔记**;`tiddlywiki_lint` 的 `stale` 检查**只报告**(含「版本号或 done 标签 + 长期未改动」的**候选**,明确标注非判定)——**淘汰永远由人决定**,插件不会自动删除或归档任何笔记(v0.24.0) |
29
29
  | 🚰 **重启不丢写入** | 停/重启 TW 只有一条路:`drainThenStop()`——**排干 syncer 队列之后才停子进程**,`/restart`、`/admin/restart`、插件/主题变更后的重启、知识库切换、`/sync`、seed、**以及启动自举路径**(v0.29.0:`pluginAdded` 与 `uiLanguage` 两处曾是裸 `server.restart()`,现在全部走 `instance.restart()`)都经它;排干不完整会在回包里如实报 `drained:false`,而不是假装没事(v0.24.1)。唯一的例外是 `dispose()`(卸载路径按设计不排干,且它只从会先排干的 `stopEntry` 到达)|
30
30
  | 🕒 **时间戳不再丢** | 插件写入的每条笔记都会带上 TW 的 `created`/`modified`(17 位紧凑 UTC,与 TW 编辑器逐字节一致):新建 = 两者都取当前时刻,覆盖 = **保留原 `created`、刷新 `modified`**。此前这两个字段被当成「TW 服务端会补」而丢弃,而服务端**从不补**——缺 `modified` 的条目会被 TW 的 `sortTiddlers` 当空串,在 `+[!sort[modified]]` 页面上**直接沉到最后一名**(表现为「新日记没被收录」)。`TiddlyWebClient.put()` 还有一道兜底,任何写路径都不会写出无时间戳的条目(v0.22.5) |
@@ -34,7 +34,6 @@
34
34
  | 🧯 **路由不会拖垮进程** | 所有路由经 `guardHandler` 包装:任何 rejection(含代理里 try 之外的 `new URL()`)都变成 413/500 响应,而不是宿主未处理的 promise rejection(那会**直接结束 dsh web 进程**并挂死请求);剪藏桥 listen 后保留常驻 `error` 监听(v0.19.3) |
35
35
  | 📊 **回复流卡片** | 工具结果显示原生 TW 卡片(**按笔记自己的内容类型渲染**:Markdown 笔记就是 Markdown,v0.18.0),检索/最近列表带**命中处摘要**(v0.22.8);`[标题](/dsh-tiddlywiki/tw/#标题)` 点击直达 TW 面板 |
36
36
  | 📤 **发送给 Agent** | TW 笔记工具栏一键把当前笔记注入所选 dsh 会话(可选工作模式/权限/附加说明);成功/失败会弹出提示(v0.20.0 修复:此前提示把自由文本当 tiddler 标题传给 TW notifier,全部静默) |
37
- | 📮 **发布到微信公众号**(**可选,默认关**) | 把笔记一键发到公众号**草稿箱**(可选点发表):TW 渲染 → 补内联样式 → 浏览器自动化复用你已登录的后台会话。**绕开官方 API 权限封锁**(2025-07 起个人主体账号的发布接口被回收),个人号可用;发表需管理员扫一次码。**需额外安装**(opencli + 浏览器扩展),插件不替你装;**关闭时完全不打扰**(不注入提示词、不写文档)。**带发布元数据**(`pub-state`/`pub-platform`/`pub-wechat-*` + `no-publish` 标签)避免重发或误发。**v0.23.3 起 TW 工具栏有「发布到公众号」按钮**(预检 + 进度轮询,只到草稿箱),也可继续用 CLI。见 [docs/wechat-publish-setup.md](wechat-publish-setup.md) |
38
37
  | 🧭 **内嵌编辑器** | 中央列内嵌完整 TW 5 编辑器(同源代理,Tailscale/内网/域名/HTTPS 均可) |
39
38
  | 🗂️ **右侧边栏 Tab** | DSH 新右侧栏(rightbar):首页「TiddlyWiki 知识库」入口一键打开,与聊天并排;链接点击可直达(v0.16.21) |
40
39
  | 🧪 **审计守门** | 第四轮审计(v0.20.0)把 CI 与 `npm run verify:*` 合成一份清单,并补上 `verify-constants`(filter 长度预算)、`/render` 403、auth 打码、notify 与草稿避让回归。**v0.25.0 再加两道**:`verify-config-patch`(设置页补丁必须过 `normalizeConfigPatch`:范围夹取 / 类型错误 400 / `__proto__` 等危险键拒绝 / 未知键透传)与 `verify-tool-views`(源码级断言**客户端 `TOOL_VIEW_KEYS` 与 host 注册的工具名完全一致**、`TOOL_LABELS` 全覆盖、工作区前缀与 host 常量相同——此前只靠 AGENTS.md 一句「记得同步」,新增工具时卡片会静默退化成纯文本) |
@@ -58,18 +58,14 @@ interface SeedDef {
58
58
  | `ui-styles` | `seed-ui-styles.ts` → `seedUiStyles` / `unseedUiStyles` | 5 张通用样式表(只带功能 tag `$:/tags/Stylesheet`,剥离 wiki 本地标签与个人数据):编辑器美化、标题与按钮区分开、侧边栏窄屏自动隐藏、批注弹窗、menubar 顶栏加高 | `$:/plugins/dsh-tiddlywiki/seed-ui-styles` | 可选 |
59
59
  | `menubar-theme` | `seed-menubar-theme.ts` → `seedMenubarTheme` / `unseedMenubarTheme` | `$:/plugins/dsh-tiddlywiki/menubar-theme`(tag `$:/tags/Stylesheet`)——覆盖 tiddlywiki/menubar 顶栏:把 `<<colour menubar-background>>` 的「默认色映射蓝色」改为跟随活动 palette 的 `background`/`foreground`,随 DSH 主题切换(`$:/palette` 翻转)自动换色 | `$:/plugins/dsh-tiddlywiki/seed-menubar-theme` | 可选 |
60
60
  | `clip-bridge` | `seed-clip-bridge.ts` → `seedClipBridge` / `unseedClipBridge` | 「本地剪藏桥 + 书签小工具」使用说明(Markdown 文档,带书签代码 / 启用步骤 / 安全说明,tag `dsh-docs`)——真功能在 `clip-bridge.ts` 运行时代码里 | `$:/plugins/dsh-tiddlywiki/seed-clip-bridge` | 可选 |
61
- | `publish-spec` | `seed-publish-spec.ts` → `seedPublishSpec` / `unseedPublishSpec` | 「发布元数据规范」(Markdown,tag `dsh-docs`)——pub-state / pub-* 字段与 no-publish 标签约定,供微信发布流程判断「发过没有 / 能不能发」 | `$:/dsh-tiddlywiki/publish-spec-seeded` | **起步(gated)** |
62
- | `wechat-setup` | `seed-wechat-docs.ts` → `seedWechatDocs` / `unseedWechatDocs` | 「微信公众号发布指南」(Markdown,tag `dsh-docs`)——**逐字节等于 `docs/wechat-publish-setup.md`**(由 `scripts/gen-seed-wechat-docs.mjs` 生成,勿手改常量):opencli / 扩展 / 登录 / adapter 安装与换机还原、三个坑、排错表 | `$:/plugins/dsh-tiddlywiki/seed-wechat-docs` | **起步(gated)** |
63
- | `wechat-publish` | `seed-wechat-publish.ts` → `seedWechatPublish` / `unseedWechatPublish` | `$:/plugins/dsh/wechat-publish` 工具栏按钮 bundle(`application/json`)——TW 笔记工具栏的「发布到公众号」按钮 | `$:/plugins/dsh-tiddlywiki/seed-wechat-publish` | **起步(gated)** |
64
61
  | `tw-web-host` | `seeds.ts` 内联 | `$:/config/tiddlyweb/host` → `/dsh-tiddlywiki/tw/` | 无 marker(ensure 型,见 §4) | **核心** |
65
62
 
66
63
  > ℹ️ `home-index` 的 seed 版首页是**通用版**:生成脚本**默认**剥离作者 wiki 里的个人元素(主题页 tabs、书籍书架入口等,仅 `--keep-private` 才原样嵌入),并内置「📚 插件文档」tabs 栏(`[tag[dsh-docs]!is[system]]`,默认展开插件说明)。作者自己的 wiki 首页不受影响(seed 是 ONE-SHOT,不会覆盖)。
67
64
  >
68
65
  > ℹ️ **v0.22.0 起 marker 记内容哈希**:marker tiddler(`$:/plugins/dsh-tiddlywiki/seed-*`)的正文从一行 `seeded-once` 升级为 JSON `{ version, hashes: { <标题>: <sha256 前 16 位> }, at }`——哈希记录的是**我们写下的内置正文**,据此可区分「内置内容更新了」与「用户自己改过」(见 §3.1)。旧 marker 仍可读,按文本比对,并在下一次重新初始化时升级。
69
66
  >
70
- > ℹ️ **v0.23.0 起 gated seed**:`publish-spec` / `wechat-setup`(v0.23.1)/ `wechat-publish`(v0.23.3)虽是起步层(`startup: true`),但带 `gate: (ctx) => ctx.wechat === true`——**只在设置页开启「微信公众号发布」(`wechat.enabled`)时**才参与启动写入;不开该功能的用户 wiki 里不会出现发布相关文档(提示词里也没有指针,死链风险只存在于开启侧)。`gate` 只作用于启动路径 `runAllSeeds`;设置页手动「初始化」走 `runSeedById` 不受它约束(显式请求)。`checkAllSeeds` 照常报告三项「缺失」,属预期。
71
67
  >
72
- > ℹ️ **v0.22.0 起 `doc-note` 正文是生成的**:工具清单来自 `tiddlywikiToolSummary()`(`docNoteText(tools)`),不再手抄「N 个 agent 工具」。无注册表的 headless 调用会退化成一句指针,绝不写出过期数量。`wechat-setup` 的正文同理由 `docs/wechat-publish-setup.md` 生成(`scripts/gen-seed-wechat-docs.mjs`),`scripts/verify-wechat-docs-seed.mjs` 守逐字节一致。
68
+ > ℹ️ **v0.22.0 起 `doc-note` 正文是生成的**:工具清单来自 `tiddlywikiToolSummary()`(`docNoteText(tools)`),不再手抄「N 个 agent 工具」。无注册表的 headless 调用会退化成一句指针,绝不写出过期数量。
73
69
 
74
70
  ### 统一入口(`src/index.ts` 导出)
75
71
 
@@ -290,8 +286,8 @@ npm run typecheck && npm run build && npm run selftest
290
286
  ## 8. 验证
291
287
 
292
288
  - `npm run typecheck` / `npm run build`:编译与打包;
293
- - `npm run selftest`:5d 段覆盖注册表清单(**13 项**,含 `core` / `removable` / `startup`)、**启动只 seed 核心 + 起步项(wechat 关时 5 项)且不碰可选项**、手动全跑只写缺失、单跑幂等、force 重写、unknown id、tw-web-host 三分支(custom 保留 / force 写回 / legacy 修复)、**反初始化**(单移除 / 核心拒绝 / unknown / 全部移除保留核心)——另含 starter-docs 安全跳过、ui-styles 仅功能 tag、seed 版首页剥离私有人口 + 文档栏等断言;
294
- - `node scripts/verify-seeds-admin.mjs`:全新 wiki + 真实 HTTP,端到端验证 `GET /admin/seeds` 状态流转(**13 项全缺失** → 启动后核心+起步就绪、可选项仍缺失)与 `POST /admin/seeds/run`(force 单跑恢复被改坏的首页、非 force 不覆盖用户内容、force-all 恢复代理基址、unknown id 400)+ `POST /admin/seeds/remove`(移除非核心、核心 400、remove-all 仅剩核心)。
289
+ - `npm run selftest`:5d 段覆盖注册表清单(**10 项**,含 `core` / `removable` / `startup`)、**启动只 seed 核心 + 起步项(5 项)且不碰可选项**、手动全跑只写缺失、单跑幂等、force 重写、unknown id、tw-web-host 三分支(custom 保留 / force 写回 / legacy 修复)、**反初始化**(单移除 / 核心拒绝 / unknown / 全部移除保留核心)——另含 starter-docs 安全跳过、ui-styles 仅功能 tag、seed 版首页剥离私有人口 + 文档栏等断言;
290
+ - `node scripts/verify-seeds-admin.mjs`:全新 wiki + 真实 HTTP,端到端验证 `GET /admin/seeds` 状态流转(**10 项全缺失** → 启动后核心+起步就绪、可选项仍缺失)与 `POST /admin/seeds/run`(force 单跑恢复被改坏的首页、非 force 不覆盖用户内容、force-all 恢复代理基址、unknown id 400)+ `POST /admin/seeds/remove`(移除非核心、核心 400、remove-all 仅剩核心)。
295
291
 
296
292
  ---
297
293
 
package/docs/usage.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  | 工具 | 说明 |
8
8
  |---|---|
9
- | `tiddlywiki_search` | 检索(`query` + 可选 `tags[]/tag/since/type/field/value/limit`),**按相关度排序**(标题命中 > 标签 > 正文命中次数),摘要取自**命中处上下文**而不是正文开头。v0.24.0:`query` 按空白切词、**全部词命中**才算(AND);默认**先在工作区内查、区内 0 条才自动扩大到全库**,回执里写明实际范围 |
9
+ | `tiddlywiki_search` | 检索(`query` + 可选 `tags[]/tag/since/type/field/value/limit`),**按相关度排序**(标题命中 > 标签 > 正文命中次数),摘要取自**命中处上下文**而不是正文开头。v0.24.0:`query` 按空白切词、**全部词命中**才算(AND);默认**先在工作区内查、区内 0 条才自动扩大到全库**,回执里写明实际范围。**v0.34.1:`query` 可以省略** —— 只传 `tags` / `tag` / `since` / `type` / `field(+value)` 时就是「按条件列出」(按修改时间倒序),不再报错、也不再恒返 0 条 |
10
10
  | `tiddlywiki_recent` | 最近修改的笔记(倒序,支持 `limit`/`since`),开工快速了解动态 |
11
11
  | `tiddlywiki_list_tags` | 现有非系统 tag 及计数(已排除只挂在二进制附件上的 tag);默认列使用最多的 200 个(`limit` 可调、上限 1000),截断时返回 `total`/`truncated` |
12
12
  | `tiddlywiki_get` | 读单个 tiddler 全文(`modified` 以 ISO 返回,可直接用作 `expectedModified`) |
@@ -126,46 +126,6 @@
126
126
 
127
127
  `wikiRoot` / `wiki` 现在只是**默认值**——运行中的实际位置以指针文件优先。这也是为什么它能不改 cordis、不重装就切。
128
128
 
129
- ### 📮 发布到微信公众号(**可选功能,默认关闭**,2026-09-17)
130
-
131
- > **这是可选功能**:需要额外安装 opencli + Browser Bridge 浏览器扩展,**插件不替你装**。
132
- > 开关 `wechat.enabled` **默认 `false`**——关闭时不注入任何发布相关提示词、也不往 wiki 写
133
- > 「发布元数据规范」。开启:DSH 设置 →「TiddlyWiki 知识库」→「可选功能:微信公众号发布」。
134
- > 完整安装步骤与排错见 [docs/wechat-publish-setup.md](wechat-publish-setup.md)。
135
-
136
- 把 wiki 里的任意笔记**一键发到微信公众号草稿箱**(可选直接发表)。整套能力放在 `tools/wechat/`,**不依赖公众号服务端 API**——因为 2025-07 起官方已回收个人主体账号的「发布能力」接口权限;本方案改用**浏览器自动化复用你已登录的后台会话**,所以个人号也能用。
137
-
138
- **两条路,随便挑一条**:
139
-
140
- - **点按钮(v0.23.3,日常推荐)**:开启可选功能后,笔记工具栏出现「**发布到公众号**」。点它 → 先**预检**(opencli 在不在、adapter 缺不缺)→ 弹确认框(自动列出 `no-publish` / `pub-state` 警告)→「开始存草稿」→ 覆盖层每 2 秒显示进度与日志。**只到草稿箱为止**,发表请到后台点(需扫码)。背后是宿主进程起一个单并发的后台任务(`POST /dsh-tiddlywiki/wechat/publish` + 轮询 `…/status`),标题经 **UTF-8 文件**(`--title-file`)传给 adapter——不进 argv,避免 Windows `cmd.exe` shim 把 `&` 当命令分隔符、把中文解成乱码。
141
- - **敲命令**(等价,适合批量 / 脚本化):
142
-
143
- ```bash
144
- # ① 先按 docs/wechat-publish-setup.md 装好 opencli + 浏览器扩展,并登录公众号
145
- # ② 一次性:装 adapter 到本机 opencli(幂等,会自检扩展/登录状态)
146
- node tools/wechat/install-wechat-adapters.mjs
147
-
148
- # ③ 发布(注意必须带 --trace retain-on-failure,原因见下)
149
- opencli weixin publish-note "笔记标题" --trace retain-on-failure -f json
150
- opencli weixin publish-note "笔记标题" --cover ./cover.png -f json # 带封面
151
- opencli weixin publish-note "笔记标题" --preview ./out -f json # 先导出排版预览
152
- opencli weixin publish-note "笔记标题" --publish -f json # 直接发表(需管理员扫码)
153
-
154
- # ④ 可选:回填存量「已发布」状态(默认 dry-run)
155
- node tools/wechat/backfill-publish-state.mjs # 看
156
- node tools/wechat/backfill-publish-state.mjs --write # 写
157
- ```
158
-
159
- **流程**:笔记标题 → DSH 的 `/render`(TW 自己渲染成语义 HTML,含代码高亮)→ `wechat-html.js` 补**内联样式**(微信会剥 `<style>` 和 class,只认内联)→ opencli 驱动后台填表/写正文/传图/设封面/存草稿 →(可选)点发表。
160
-
161
- **三个要点**:
162
-
163
- 1. **必须带 `--trace retain-on-failure`**——不带会对 `mp.weixin.qq.com` 稳定报 `Navigation rejected`(实测 trace 开 5/5 成功、关 8/8 失败;这是 opencli 1.8.7 的 bug,`--site-session ephemeral` 等绕法均无效)。
164
- 2. **发表必须管理员扫码**——后台点「发表」后微信要求管理员微信扫码确认,无法自动化。「一键」的真实含义是「脚本做到填表/排版/上传,你只需扫一次码」。默认走**发表**(不推送粉丝、不占群发额度),群发请自行在后台操作。
165
- 3. **图片上传用 DataTransfer 注入**,不用 `page.setFileInput`——后者依赖 CDP `Page.fileChooserOpened`,本机扩展版本组合下稳定失败;改用页面上下文直接塞 `input.files`,实测图片真进 `mmbiz.qpic.cn`。代价是单图 **8MB** 上限(字节要以 base64 穿过 evaluate)。
166
-
167
- **换机器还原**见 [docs/wechat-publish-setup.md](wechat-publish-setup.md)(含 opencli / Browser Bridge 扩展安装、扫码登录、排错表)。设计依据与全部实测细节见 [docs/plans/2026-09-17-wechat-publish-design.md](plans/2026-09-17-wechat-publish-design.md)。
168
-
169
129
  ### 🧩 初始化(一次性预置 seed):哪些「必备」,哪些「可有可无」
170
130
 
171
131
  seed 是把「wiki 里预置内容」随插件分发的机制:**ONE-SHOT(只写缺失)+ 安全跳过(同名 tiddler 已存在绝不覆盖你的数据)**,需要时可「重新初始化」恢复、可「反初始化」移除。详细见 [docs/seed-initialization.md](seed-initialization.md)。
@@ -177,7 +137,6 @@ seed 是把「wiki 里预置内容」随插件分发的机制:**ONE-SHOT(只
177
137
  | | `tw-web-host` | TW 前端 API 基址 → 同源代理(内嵌编辑器前提) | 自动写 |
178
138
  | 📖 **起步**(默认写、可移除,**想要完整体验建档案**) | `doc-note` | 「dsh-tiddlywiki 插件说明」笔记 | 自动写 |
179
139
  | | `starter-docs` | 「示例与文档」:主题汇总页·模板 + 教程 + 三个示例主题页(日志/决策记录/排障) | 自动写 |
180
- | | `publish-spec` / `wechat-setup` / `wechat-publish`(**带 gate**) | 只有开启「可选功能:微信公众号发布」才写:发布元数据规范 + 换机还原指南 + 「**发布到公众号**」工具栏按钮插件(v0.23.3) | 开启后自动写 |
181
140
  | 🎀 **可选**(默认不写、设置页手动、可移除,**可有可无**) | `home-index` | 首页(四象限待办 + 快速记笔记 + 所有标签/所有文章 + 📚 文档栏)——文档中心的「门面」 | 手动 |
182
141
  | | `all-articles` | 「所有文章」两列分页总览(🤖 Agent / 👤 人工) | 手动 |
183
142
  | | `ui-styles` | 自定义样式 5 张(编辑器美化 / 窄屏侧栏 / menubar 加高 / 批注弹窗等) | 手动 |
@@ -234,12 +193,6 @@ seed 是把「wiki 里预置内容」随插件分发的机制:**ONE-SHOT(只
234
193
  showSessionTab: true
235
194
  showRightbarTab: true # DSH 右侧边栏提供 TiddlyWiki 入口/Tab
236
195
  sendToAgent: { enabled: true }
237
- wechat: # 可选功能:微信公众号发布(默认关;三条 /wechat/* 路由在关时一律 403)
238
- enabled: false # 开:注入发布约定 + 启动写 3 个 gated seed(元数据规范/换机指南/工具栏按钮)
239
- adapter: "publish-note" # publish-note-imgs = 正文内嵌多图版(需该 adapter 已装)
240
- command: "opencli" # CLI 路径(不在 PATH / 用了别名时填绝对路径)
241
- token: "" # 非空时 /wechat/* 要求请求头 x-wechat-publish-token(按钮自动带)
242
- dsn: "" # adapter 回连 DSH 的基址;空 = 按请求端口推导 loopback
243
196
  uiLanguage: "" # 留空不干预;"zh-Hans" 自动启用简体
244
197
  auth:
245
198
  username: "" # 默认 loopback 匿名;暴露到非回环才需要