dsh-tiddlywiki 0.30.62 → 0.30.65

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/docs/CHANGELOG.md CHANGED
@@ -4,6 +4,10 @@
4
4
 
5
5
  > 最近几个主要版本的一句话记录(完整变更见 [Releases](https://github.com/bbqisbbq/dsh-tiddlywiki/releases) / git log)。
6
6
 
7
+ - **v0.30.65**(2026-10-09):**新增 `tiddlywiki_replace` 工具(笔记正文局部替换)**。Agent 想只改笔记里的几处文字(比如把 Markdown 圆括号内链批量改成双括号 `[[x]]`)此前没有对应工具:要么整篇 `tiddlywiki_put` 重写(易出错、费 token),要么绕过插件手写 HTTP PUT(会踩「`modified` 是 UTC、别写本地时间」这类坑)。新工具 `replace(title, old, new, all?, expectedModified?, expectedRevision?, force?)`:读全文 → 精确替换子串 → 整篇回写;默认只换第一处、`all:true` 换全部;`old` 未命中回执 `replaced:0` 且不报错(别把「没命中」当「改好了」);与 put/append 同一套乐观并发保护与写策略(保留 type/tags/字段、二进制附件拒绝)。工具数 15 → 16:`full` 提示词目录、`verify-prompt`/`verify-tool-views` 的工具数断言、AGENTS.md §2、package.json 描述、docs/development.md 同步;客户端新增「局部替换」工具卡与徽标(`tool-views` / `i18n-card`)。
8
+
9
+ - **v0.30.63**(2026-10-09):**默认提示词整理(「聊天引用」与「笔记内引用」分家)+「整段替换」配置说明勘误**。① **笔记约定的链接规则拆成两条**:旧版只有一条 `[标题](/dsh-tiddlywiki/tw/#标题)`——那是**聊天回复**专用写法,模型会照抄进笔记正文,于是 URL 一旦带未转义空格就被 markdown-it 整条拒绝渲染成纯文本,即便渲染成功也是外链、点开另起标签页(正是「笔记里的链接偶尔是 Markdown 格式、不是 `[[…]]`」的根因)。现在拆成「**笔记内引用**」写 `[[标题]]`(文字不同写 `[[标题|文字]]`,两种内容类型下都能正确渲染成站内链接、无需 URL 编码)与「**聊天里引用笔记**」写带作用域 base 的链接形式(多库时自动带库 id)。slim 正文 866 → 937 字符(仍 < 预算 1000,`verify-prompt.mjs` 预算/链接/多库断言全绿)。② **`config.prompt.override` 设置页说明勘误**(zh+en):原「非空时取代上面的内置文本」有歧义,改为「非空时替换内置提示词,附加说明仍会追加在末尾」。纯文案,无行为变化。
10
+
7
11
  - **v0.30.62**(2026-10-09):**第二轮审查的三类修复一次落地(回收站格式合并 / 会话作用域目录 / 剪藏目标库口径 + 提示词 −14% + 清理)**。全部改动都有行为级守门,且都做了反向验证。① **回收站:两种格式真正合并**。v0.30.59 的注释写着「legacy index is still READ so a wiki that has both keeps working」,代码却是 `trashbin !== undefined ? {entries: []} : readTrashIndex()` —— 于是**装了 trashbin 的库反而看不到内置条目**:work 库当时回收站里明明有 1 条内置索引条目,`trash list` 报「共 0 条」(本次实测复现)。现在两种格式**都读、合并列出**,恢复/清空按「这条来自哪种格式」记账(`fromIndex`)。② **`|` 分隔符会截断标题**:v0.30.60 的注释断言「TW 把 `|` 转义成 `&#124;`」——**实测为假**(`<$text text="a|b"/>` 原样渲染回 `a|b`;TW 的 htmlEncode 只转 `& < > "`),所以标题含 `|` 的副本会被拆成两段、列出**截断的标题**且恢复不了。改为用**自有标记 `<li>`** 分隔(标题里的 `<` 必被转义,不可能伪造标记),并把抽取逻辑拆成纯函数 `parseTrashbinFragment()`;顺手补上**实体还原**(`&lt;`/`&gt;`/`&quot;`/`&amp;`,`&amp;` 放最后才是无损的)⇒ 标题含 `<`/`&`/`"` 的副本现在也能正确列出与恢复。③ **恢复不再把副本的溯源字段留在笔记上**:`TRASHBIN_AT_FIELD` 在 v0.30.60 从 `trash-at` 改名成 `trash-iso`,而恢复路径只删新名字 ⇒ 恢复**任何 v0.30.60 之前的副本**(或内置格式快照)都会让 `trash-at` 留在恢复出来的笔记上(正是改名要躲的那种「TW 会重排成本地日期」的字段)。现在旧名字有具名常量 `TRASHBIN_LEGACY_AT_FIELD`,**列出时读、恢复时删**,内置格式的写入也统一用新常量。④ **`git_resolve` / `git_sync` 的目录改从会话作用域读**(`ToolScope.dir`):两者原来都走 `ToolsDeps.wikiPath()`,而它解析成**默认库** —— 于是「排干哨兵写进会话库、却在默认库目录里等文件」(多库且作用域≠默认时**必然** 12s 超时 + 假 `drainFailed`,而真正要验的库一行没验),`git_resolve` 不传 `wiki` 时会在**错的仓库**里 checkout+commit(回执示例恰好就是不带 `wiki` 的写法)。`ToolsDeps.wikiPath` 已删除(它没有 session 参数、只会误导)。⑤ **剪藏目标库口径统一**:`bridge.wiki` 指向**已登记但没在跑**的库时不再静默回落默认库(那正是「剪到别的库」的失败形态,而书签小工具根本不看回执里的库名),改为 **503 + 点名那个库**(与 `?wiki=` 路由自 v0.29.0 起的规则一致,`targetProblem()` 把句子带进响应体);**未知 id** 仍回落(老书签不能 404);书签的成功提示现在显示回执里的库 id;`bridge.*` 是**插件级**设置(读取它的是默认库那份 config),设置页因此只在「未选中具体库」的作用域里露出这一组,选中某个库时给一句说明 —— 否则在别的库的配置里改它是静默无效的(v0.28.8 起就存在的陷阱);seed 文档的配置表补上 `bridge.wiki` 这一行。⑥ **注入提示词 1011 → 866 字符(−14%,预算 1200 → 1000)**:写入约定压成「被拒后重读、别用 `force`」一句(`expectedModified`/`force` 的语义逐字在 `put` 的 schema 里)、同步那条去掉与 schema 重复的自动 commit 说明、intro 去掉「本机有一个知识库」这个**多库下就是错的**说法;同时**多库时正文会自动换成带库 id 的链接格式** `/tw/<id>/#标题`(裸 `/tw/` 在助手正文里跟随**焦点库**、写进笔记则把中央 TW 的 iframe 导航到**默认库**——两种都不是「本会话那个库」)。⑦ **清理**:删掉死代码 `messageKeys()`(注释自称有消费者,实际全仓 1 处引用=声明自身)、修掉三处过时注释(「尚无 i18n 分支」/「index.ts effectiveBridge」/两条被粘成一行的注释)、`tiddlywiki_status` 这个**不存在的工具名**从错误文案里去掉、删掉两个零引用的一次性迁移脚本(`push-notes-to-wiki.mjs` / `split-agents-to-wiki.mjs`)、`docs/code-review-*.md` 不再随 npm 包发布(`files` 加否定式 + 守门断言,实测 171 → 170 项)、`wechat.endpoint` 补进 `ResolvedConfig`/`STRING_CONFIG_PATHS` 两处形状、AGENTS.md §2 的行数清单去掉会漂移的数字。**守门**:`verify-trashbin-probe.mjs` 11 → **15 条**(新增「两种格式合并列出」「从索引恢复要摘掉索引记录」「恢复不得残留 `trash-at`」「含 `|`/转义标题的解析」)、新增 `scripts/verify-clip-target.mjs`(运行中/已登记未运行/未知 id/清单未就绪 + 端到端 503 文案,**这是 AGENTS 早就声称存在、实际为 0 的守门**)、`verify-git-multi-sync.mjs` 新增两条(哨兵必须落在会话库目录、`git_resolve` 缺省目标必须是会话库)、`verify-prompt.mjs` 新增多库链接用例。**反向验证**:去掉 `delete restored[TRASHBIN_LEGACY_AT_FIELD]` ⇒ 15 条里精确红 1 条;恢复后逐字一致。**环境事实(今天第二次踩到)**:运行时的 `profiles/*/node_modules/dsh-tiddlywiki` 必须显式升级,源码仓库改了不会自动生效。
8
12
  - **v0.30.61**(2026-10-09):**修 `tiddlywiki_trash list` 少报一条 —— TW 给块级片段的首元素套 `<p>`** —— v0.30.60 实跑时 `list` 对**126 条**回收站报「共 125 条」,少的那一条**恰好是排序后的第一条**(实测片段为 `<p>$:/trashbin/__验收测试_trashbin软删除__|…`,而 `startsWith('$:/trashbin/')` 对带 `<p>` 的首条不成立)。**「列表少一条」是最坏的失败形态之一** —— 少的那条可能正是用户最想找的那条,而工具报告成功、不报错。现在改为**先定位前缀再剥标签**(`indexOf` 取到前缀处、再 `replace(/<[^>]*>/g,'')`),与首条是否被段落包裹无关。② 同时兼容旧字段名:`trash-iso` 是 v0.30.60 起的名字,**此前写的副本只带 `trash-at`**,不兼容读它时回执里「删除于」那一栏**整列为空**(实测);现在两个字段都读,`trash-iso` 优先。**守门** `scripts/verify-trashbin-probe.mjs` 增至 **11 条**,新增两条钉住上述两点;**反向验证**:去掉剥标签逻辑 ⇒ 红、改回 `\n` 拆分 ⇒ 红,各自精确点名,恢复后逐字一致。
9
13
  - **v0.30.60**(2026-10-09):**修 v0.30.59 的 `tiddlywiki_trash` 静默谎报「回收站是空的」** —— v0.30.59 发布后第一次实跑,`list` 就报「共 0 条」,而 wiki 里明明有 126 条 `$:/trashbin/` 副本。**两个独立缺陷叠在一起,缺一个都不会这么静默**:① `listTrashbin()` 的探针写成 `<$list … variable="ignore">` —— **`variable="ignore"` 会占用 `currentTiddler` 这个保留变量名**,于是循环体内的 `<$text text=<<currentTiddler>>/>` 全部绑到空上下文,实测返回 **126 个分隔符、零个标题**;② 分隔符原写成 `\n`,而片段是 **HTML**、换行会被折成空格,按 `\n` 拆会把整个列表当成**一行**,那行又过不了 `$:/trashbin/` 前缀检查。两者都产出 `[]`,而「空的回收站」是完全正常的答案 —— 于是工具**报告成功、同时撒谎**。③ 补一道自检:**有分隔符却零标题 = 探针坏了,必须显式报错**(不再回落成空列表)。④ 逐条 `wiki.get()` 改**有界并发(8)**:125 条串行就是 125 次 REST 往返。⑤ 溯源时间字段由 `trash-at` 改名 **`trash-iso`** —— TW 会把任何 `*-at`/`*-date` 字段当日期**重新序列化成本地格式**(实测 PUT 进去的 ISO 读回来是 `10/09/2026 05:11:54`),机器可读的值会丢。⑥ 配套发现并修正一处**流程事实**:`plugins/dsh-tiddlywiki`(源码仓库,0.30.59)与 `profiles/*/node_modules/dsh-tiddlywiki`(运行时实际加载,0.30.57)**是两份不同的副本** —— 改完源码 + 重启 DSH 不会生效,必须走发布流程(tag → npm publish → 升级运行时副本),这是 AGENTS.md §4 末尾第 8 步「同步线上 wiki」的真实含义。**守门**(新增 `scripts/verify-trashbin-probe.mjs`,进 `verify:unit`,8 条):钉住探针不含 `variable="ignore"`、必须输出 `<<currentTiddler>>`、必须按 `|` 拆而非换行、有分隔符零标题必须 throw、有界并发、字段名。**反向验证**:把 `variable="ignore"` 注回去 ⇒ 红;把 `|` 换回 `\n` ⇒ 红;恢复后逐字一致。
@@ -107,7 +107,7 @@ src/
107
107
  │ ├── seeds.ts # 统一 seed 注册表(13 项,三层:核心/起步/可选)
108
108
  │ ├── prompt.ts # 系统提示词(v0.21.0):slim/full 两种形态 + extra/override,full 的目录由工具注册表实时生成;v0.22.7 起草稿预览;v0.29.0 slim 去重到 ~1.0KB
109
109
  │ ├── seed-*.ts # 各 seed 实现(bundle/首页/ui-styles 常量脚本生成;starter-docs/menubar/clip-bridge 手工维护)
110
- │ └── tools.ts # 15 个 tiddlywiki_* 工具的**装配与注册顺序**;实现分 tools-{support,read,notes,attach,git}.ts(v0.28.8)
110
+ │ └── tools.ts # 16 个 tiddlywiki_* 工具的**装配与注册顺序**;实现分 tools-{support,read,notes,attach,git}.ts(v0.28.8)
111
111
  └── client/ # 浏览器半部
112
112
  ├── index.ts # client 入口(inject ['slots'],纯 DOM,永不 throw)
113
113
  ├── endpoints.ts # 客户端同源端点常量 + describeSyncResult(同步回执唯一实现,v0.22.8)
package/docs/features.md CHANGED
@@ -16,7 +16,7 @@
16
16
  | 📍 **知识库位置可切换** | 设置页「知识库位置」可把插件切换到**任意本地文件夹**(不用改 cordis 配置、不用重装):就地停/起 TW 子进程,目标目录没有 `tiddlywiki.info` 时自动 `--init server` 建一个全新知识库;选择记在 `$DSH_HOME/dsh-tiddlywiki/location.json`——一个**在 wiki 之外**的指针文件(所以切到新 wiki 后仍记得「我用的是哪个」),「恢复为配置默认」一键清除;**切换失败自动回滚**到原知识库并如实告知(v0.22.0)。⚠️ 它一次只有一个库在线;要**多库同时在线**见上面的「多知识库」(v0.28.0) |
17
17
  | 🔄 **seed 更新检测** | seed 标记记录**内容哈希**:内置内容在本 wiki 预置之后更新过 → 设置页显示「⬆ 有更新」;被你改过 → 显示「✏️ 本地已修改」,且「重新初始化」前**二次确认**;两者都基于哈希判定,绝不猜(旧格式标记会明确显示「尚未启用更新检测」,不会误报为「你改过」,v0.22.0) |
18
18
  | 🎨 **自定义样式** | 「自定义样式」seed:编辑器美化 / 窄屏侧栏隐藏 / menubar 加高 / 批注弹窗等 5 张通用样式表,新 wiki 也能一键初始化(可选,v0.16.22) |
19
- | 📝 **可配置的注入提示词** | 插件注入每个会话的「TiddlyWiki 持久知识库」提示词可在设置页配置:**默认精简版**(v0.30.62 起 **866 字符 / 预算 1000**,只保留工具 schema **表达不了**的约定——同步时机 / 写入被拒后重读而不用 `force` / 时效标注 / 淘汰权归人 / `human-edited` / 链接格式 / todo 约定),可选**完整版**(额外附一份**由工具注册表实时生成**的参数索引,不会再过期);`extra` 追加自定义规范、`override` 整段接管、可整体停用;**保存后无需重启 dsh web**(section 即时重注册,当前会话下一步即生效),设置页可**按表单当前值预览**即将注入的全文(未保存的形态切换也立刻可见,v0.21.0 / v0.22.7)。**v0.29.0 去重**:与工具 description 逐字重复的句子(工作区标记 / 先窄后宽 / 多词 AND / tags·type 保留规则)从提示词里删掉、**只留 schema 一份**,`verify-prompt.mjs` 改为在 schema 侧逐条断言「从提示词删 ≠ 丢了」。**v0.30.62 再瘦一轮**(1011 → 866 字符):写入约定压成「被拒后重读、别用 `force`」,intro 去掉多库下就不成立的「本机有一个知识库」;**多库时正文自动换成带库 id 的链接格式** `/tw/<id>/#标题` —— 裸 `/tw/` 在助手正文里跟随**焦点库**、写进笔记则把中央 TW 的 iframe 导航到**默认库**,两种都不是「本会话那个库」 |
19
+ | 📝 **可配置的注入提示词** | 插件注入每个会话的「TiddlyWiki 持久知识库」提示词可在设置页配置:**默认精简版**(v0.30.63 起 **937 字符 / 预算 1000**,只保留工具 schema **表达不了**的约定——同步时机 / 写入被拒后重读而不用 `force` / 时效标注 / 淘汰权归人 / `human-edited` / 链接格式 / todo 约定),可选**完整版**(额外附一份**由工具注册表实时生成**的参数索引,不会再过期);`extra` 追加自定义规范、`override` 整段接管、可整体停用;**保存后无需重启 dsh web**(section 即时重注册,当前会话下一步即生效),设置页可**按表单当前值预览**即将注入的全文(未保存的形态切换也立刻可见,v0.21.0 / v0.22.7)。**v0.29.0 去重**:与工具 description 逐字重复的句子(工作区标记 / 先窄后宽 / 多词 AND / tags·type 保留规则)从提示词里删掉、**只留 schema 一份**,`verify-prompt.mjs` 改为在 schema 侧逐条断言「从提示词删 ≠ 丢了」。**v0.30.62 再瘦一轮**(1011 → 866 字符)、**v0.30.63 拆链接两条**(866 → 937 字符):写入约定压成「被拒后重读、别用 `force`」,intro 去掉多库下就不成立的「本机有一个知识库」;**多库时正文自动换成带库 id 的链接格式** `/tw/<id>/#标题` —— 裸 `/tw/` 在助手正文里跟随**焦点库**、写进笔记则把中央 TW 的 iframe 导航到**默认库**,两种都不是「本会话那个库」 |
20
20
  | 🌐 **中英双语界面**(v0.30.6) | 插件自己的界面(FAB / 侧边栏 / 快速笔记 / 工具卡片 / 设置页 / toast)与 TW 子进程**共用同一个 `uiLanguage` 设置**;文案按区域分片成 `i18n-*.ts` 的 `zh`/`en` 目录,守门保证两侧键集合一致、且**客户端代码里不再有硬编码中文文案** |
21
21
  | 🤖 **Agent 工具** | 15 个 `tiddlywiki_*` 工具:检索(**相关度排序 + 命中处片段 + 字段过滤**)、读写、**增量追加**、批量、重命名、**软删除/回收站**、**反向链接**、**附件入库**、**知识库体检**、git 同步与冲突解决(v0.30.5 起 `git_sync` **一次同步所有可同步的库**、按仓库去重、逐仓库独立成败;**v0.30.58 起「有远端」= 配了 `git.remote` **或**该库所在仓库自己的 `origin`**,配置为空即回落、回执写明远端与来源;v0.19.0;检索/最近仍在**服务端**排除二进制附件,大 wiki 上从 515MB/17s 降到 ~0.4s) |
22
22
  | 🛡️ **不会被覆盖的写入** | 所有写入路径(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` 更新引用时若某个引用者在遍历期间被改动过,**跳过它**并在回执里报出数量,而不是覆盖它 |