dsh-tiddlywiki 0.30.56 → 0.30.58

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,8 @@
4
4
 
5
5
  > 最近几个主要版本的一句话记录(完整变更见 [Releases](https://github.com/bbqisbbq/dsh-tiddlywiki/releases) / git log)。
6
6
 
7
+ - **v0.30.58**(2026-10-09):**`tiddlywiki_git_sync` 不再要求显式配 `git.remote` —— 配置为空时回落到该库所在仓库自己的 `origin`**(作者 2026-10-09 裁定,方案 A)。起因是一个**同一个问题两个答案**的真实现场:设置页/FAB 的「同步」按钮一直是在目录里跑 `pull/commit/push`、按仓库 `origin` 推,而 Agent 工具把 `git.remote` 当成"开启同步"的开关 —— 同一个仓库,界面点得动、工具却回「没有任何知识库配置了 git.remote(同步远端)」并**整库跳过**(实测:本机 notes 仓库已推到远端、`git remote -v` 有 origin,工具仍报三个库皆未配置)。现在:① 判定改为「**`git.remote` 或仓库 `origin`**」,`git.remote` 降级为**覆盖**(也仍是 `ensureRemote` 写入的目标);② 回落**只在「没配 **且** 真在 git 仓库里」时发生** —— 不在仓库里就不白跑 `git`(新增 `GitFace.originOf()`,**只读、不建远端**);③ `GitSyncTarget` 新增 `remoteSource: 'config' | 'origin' | 'none'`,**回执逐仓库写出远端与来源**(「自动识别自仓库 origin」/「来自 git.remote 配置」),且 URL 里的**凭据先剥掉**(`git.remote` 可能带 PAT,`/admin/state` 对同一个值打码 —— 回执是新开的一条出口);④ `skipped` 分得清「未配 `git.remote`,仓库也没有 origin」与「不在 git 仓库里」,零目标时的文案同时给出两条出路;⑤ 设置页「git 远端」提示改为「空=自动用该库所在仓库的 origin」,`docs/features.md` / `docs/usage.md` / `AGENTS.md` 同步改写。**守门**:`scripts/verify-git-multi-sync.mjs` 新增「回落只在该回落时发生」(**假 farm + 假 GitFace 直接驱动 `createGitLayer().gitTargets()`**,断言 `originOf` 的**调用点集合**:有配置的不探、不在仓库的不探)、回执来源文案、凭据剥离、两种跳过原因;`scripts/verify-repo-keying.mjs` 用真临时仓库钉 `originOf()` 的三态(有 origin / 没有 / 不在仓库里)与「只读不建远端」。`createGitLayer` 因此进入公共面(`scripts/index-barrel-surface.json` 已更新)。**反向验证**:把回落那一行改回 `''` ⇒ 新守门当场点名 `personal` 的 `remoteSource`;恢复后逐字一致。**顺手修好一处既存红灯**:`AGENTS.md` §5 的开发文档索引还指着 2026-10-04 那次笔记整理里**已被合并删除**的 5 篇旧笔记(常见坑 / 架构事实 / 仓库布局 / 易变清单 / 发布流程),而 `scripts/verify-wiki-pointers.mjs` 正是为此存在 —— 指针已改指**整理后仍在的三篇**(《dsh-tiddlywiki 事实总表》《TiddlyWiki 使用与技巧事实总表》《拆库丢失的系统条目清单》)+ 仓库 `docs/CHANGELOG.md` / `docs/development.md`,§2 §4 §6 §7 里同样的旧名字一并改掉。**不恢复那些笔记**(删除是有意为之),`verify:sync` 因此恢复全绿。
8
+ - **v0.30.57**(2026-09-30):**给 `AGENTS.md` 的注入预算装上守门** —— 这条纪律的**存在理由就是它自己**。本文件顶部写着:宿主给 workspace instructions 的预算是 **65536 字节**,「过去它长到 ~120KB,**截断正好砍掉最后几节(发布流程 / 维护规则 / 常见坑)**」—— 也就是说**这份文件当初被拆成「规则 + 指针」的原因,就是它撞过预算上限**。而那条教训**只写在正文里,没有任何东西在量它**(`verify-doc-links` 管链接、`verify-wiki-pointers` 管指针,都不管篇幅),失效方式依然是沉默的:文件继续长到超限,宿主从末尾截断,被砍掉的正好是**最该被看到的规则**,**没有人会收到报错**。新增 `scripts/verify-agents-budget.mjs`(进 `verify:static`):**两级硬阈值** —— 硬上限 **65536**(宿主预算=事实边界)与软上限 **45000**(自我警戒线),**两级都红**(本仓库的教训是「只打印 warning 的守门等于没有守门」;要抬 SOFT 必须明确改常量=有意识的决定)。⚠️ 顺带守一件更隐蔽的事:**文件第 5 行对自身体积的自述不得漂移** —— 它当时写着「现在拆完约 **12KB**」,而实测已 **40.7KB**(**偏差 70%**,文件在对自己撒谎而无人被冒犯)。**这正是本守门第一次运行就抓到的**:`顶部自述「约 12KB」,实测 40.7KB`。已改为实测值并把守门写进那句旁边。**反向验证**:填充到 46920 字节(> 软上限)⇒ 红;填充到 66920 字节(> 硬上限)⇒ 红并点名「注入会被从末尾截断」;恢复后哈希逐字一致。当前实况:`41890 字节 / 65536 = 63.9%`。
7
9
  - **v0.30.56**(2026-09-30):**`AGENTS.md` 的 wiki 指针现在有守门了** —— §5「开发文档索引」是这套文档体系的**承重墙**(细节搬进 wiki、注入文件只留指针),但指针断掉的后果是**沉默的**:新会话按它去读会读到空气,而 `tsc` / 任何现有守门都不会报错(`verify-doc-links.mjs` 只管仓库内的相对文件链接)。新增 `scripts/verify-wiki-pointers.mjs`(并进 `npm run verify:sync`):**从 `AGENTS.md` 的表格行里**取出 `[[…]]` 指针(**只取表格行** —— 正文里的 `[[标题]]` 是 TW 语法示例、不是指针,这个区分是判据的核心),再从 wiki 数据根解析出**全部条目标题**(meta 的 `title:` 优先、退回文件名),要求每个指针都命中。**多库模式也认**(优先读 `$DSH_HOME/dsh-tiddlywiki/wikis.json` 的 `defaultId`/全部库,退回单库的 `location.json`)—— 实测本机上插件跑在多库模式、`location.json` 根本不存在,只认单库的话这个守门会**永远 SKIP**。⚠️ **找不到 wiki 数据根时 SKIP 而不是 FAIL**(别人的机器 / CI 上 wiki 可能不存在,前提不成立),但 SKIP **必须明说**「本轮未验证」;也正因如此它**不进 `verify:static`**(CI 上必然 SKIP,等于给人「已守」的错觉),而与 `verify:lib-sync` 同挂在 `verify:sync`(本机环境相关、提交/发布前跑)。**反向验证**:把 §5 的一个指针改成不存在的笔记名(=笔记被改名后的真实形状)⇒ **红并点名**;`--self-test` 模式另行证明「不存在的指针名会被判为缺失」。当前实况:**5 个指针全部命中 · 3 个库 · 2041 条条目**。
8
10
  - **v0.30.55**(2026-09-30):**把新守门接进 `prepublishOnly` + 修 `verify-all` 的收尾** —— ① `package.json` 的 `prepublishOnly` 此前只跑 `typecheck + build + verify:static + verify:unit`,**不含 `verify:e2e` / `verify:large` / `selftest` / `smoke:client`**,且**不跑 `verify:sync`** ⇒ 发布时构建产物可能**没被校验过与 `src/` 同步**(正是 v0.30.52 那个缺口的发布形态)。现在改为 **`npm run verify:all`**(= `verify:sync` + 8 步 `verify`),发布前的门与 CI 完全一致。② `verify-lock-sync.mjs`(v0.30.54 新增)此前只在 `verify:static` 里;本版确认它在 `prepublishOnly` 路径上也一定会跑(`verify:all` → `verify` → `verify:static` 的第一步就是它)。③ 顺手把交接卡与三篇 wiki 笔记更新到 v0.30.55。**发布前的门 = CI 的门 = 本地的门**,三处同源。
9
11
  - **v0.30.54**(2026-09-30):**铁律 #12 终于有了执行者** —— `package-lock.json` 与 `package.json` 的同步,此前**只靠「记得手工跑一下 `npm ci --dry-run`」**(既慢又碰网络,于是没人会在提交前跑)。新增 `scripts/verify-lock-sync.mjs`(进 `verify:static` 的**第一步**,因为它是纯读两个 JSON、最快能给出结论的一道网):**零网络、零磁盘副作用**地查六件事 —— ① 版本**三处一致**(`package.json.version` / `package-lock.json.version` / `packages[""].version`,发版漏改一处 `npm ci` 就报 `lock file does not satisfy`);② `lockfileVersion >= 2`;③ 名字一致;④ 四个依赖字段的**集合**双向一致(漏了 / 已删);⑤ 每个声明的依赖在 lock 里有**实体条目**且 **locked 版本满足声明的 range**(用 semver;非 semver 范围如 `git`/`file:` 跳过并明说);⑥ **范围本身一致**(同一个依赖写出两个范围=手改过 lock)。⚠️ 它**不替代** `npm ci`(真正的「全量可解析性」需要网络,交给 CI),它拦的是「**改了 `package.json` 忘了重生成 lock**」这一类事故的完整形状。**反向验证三种注入**:改 range 不重生成 lock ⇒ 红(范围不一致);改 lock 里实体版本到不满足范围 ⇒ 红;只改 lock 顶层 version ⇒ 红。当前实况:`LOCK SYNC OK(版本三处一致 · lockfileVersion 3 · 14 个依赖全部对得上)`。
package/docs/features.md CHANGED
@@ -18,7 +18,7 @@
18
18
  | 🎨 **自定义样式** | 「自定义样式」seed:编辑器美化 / 窄屏侧栏隐藏 / menubar 加高 / 批注弹窗等 5 张通用样式表,新 wiki 也能一键初始化(可选,v0.16.22) |
19
19
  | 📝 **可配置的注入提示词** | 插件注入每个会话的「TiddlyWiki 持久知识库」提示词可在设置页配置:**默认精简版**(v0.29.0 起 ~1.0KB,只保留工具 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 侧逐条断言「从提示词删 ≠ 丢了」 |
20
20
  | 🌐 **中英双语界面**(v0.30.6) | 插件自己的界面(FAB / 侧边栏 / 快速笔记 / 工具卡片 / 设置页 / toast)与 TW 子进程**共用同一个 `uiLanguage` 设置**;文案按区域分片成 `i18n-*.ts` 的 `zh`/`en` 目录,守门保证两侧键集合一致、且**客户端代码里不再有硬编码中文文案** |
21
- | 🤖 **Agent 工具** | 15 个 `tiddlywiki_*` 工具:检索(**相关度排序 + 命中处片段 + 字段过滤**)、读写、**增量追加**、批量、重命名、**软删除/回收站**、**反向链接**、**附件入库**、**知识库体检**、git 同步与冲突解决(v0.30.5 起 `git_sync` **一次同步所有配了 `git.remote` 的库**、按仓库去重、逐仓库独立成败;v0.19.0;检索/最近仍在**服务端**排除二进制附件,大 wiki 上从 515MB/17s 降到 ~0.4s) |
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` 更新引用时若某个引用者在遍历期间被改动过,**跳过它**并在回执里报出数量,而不是覆盖它 |
23
23
  | 🏷 **工作区感知的读写** | **新建**笔记自动带 `ws/<项目名>` 标签 + `workspace` 字段(项目名取自会话工作目录,`note.workspaceMark` 可关);`tiddlywiki_search` 因此默认**先在本项目里找、区内 0 条才自动扩大到全库**并在回执里写明实际范围,`query` 按空白切词、**全部词命中**才算(AND)。工作区只是**附加**标记,绝不改写笔记原有的标签/字段,也**只在新建时**打(v0.24.0) |
24
24
  | 🕰 **时效性内容只提示不删** | 阶段性笔记(版本记录 / 部署步骤 / 排期 / 临时方案)可声明 `valid-until`(硬过期)或 `review-after`(该复查),被取代时写 `superseded-by` + `superseded` 标签并**保留旧笔记**;`tiddlywiki_lint` 的 `stale` 检查**只报告**(含「版本号或 done 标签 + 长期未改动」的**候选**,明确标注非判定)——**淘汰永远由人决定**,插件不会自动删除或归档任何笔记(v0.24.0) |
package/docs/usage.md CHANGED
@@ -19,13 +19,13 @@
19
19
  | `tiddlywiki_backlinks` | 反向链接:谁用 `[[标题]]`/`{{标题}}` 引用了它、谁把它当标签 |
20
20
  | `tiddlywiki_attach` | 把**本机文件或公网 http(s) 地址**存成附件(图片/PDF/… 等二进制存 **`type` + base64 正文**;`text/markdown`·`text/plain`·`application/json` 等**文本**存 `type` + **纯文本正文**,这样 TW 才渲染得出来、也才搜得到 —— v0.30.22),可嵌入某篇笔记;URL 走 SSRF 守卫;**同名 tiddler 已存在时默认拒绝**(避免静默覆盖笔记),确认覆盖要传 `force: true`(tags/自定义字段仍保留) |
21
21
  | `tiddlywiki_lint` | 知识库体检(**只读**):垃圾标签 / 死链 / 空笔记 / 缺内容类型的类 Markdown 笔记 / **时效性内容**(`valid-until`·`review-after` 过期,以及「版本号或 done 标签 + 长期未改动」的**候选**,`staleAfterDays` 可调) |
22
- | `tiddlywiki_git_sync` | `action: pull\|push\|sync` —— **作用于所有配了 `git.remote` 的库**(v0.30.5):多库各自独立仓库时逐仓库同步、**每个仓库只跑一次**(多库共用一个工作树时不会重复 pull/commit/push),一个仓库冲突不影响其余;回执逐仓库标注「哪个库 @ 哪个仓库」,没配 remote 的库被列出来而不是静默略过 |
22
+ | `tiddlywiki_git_sync` | `action: pull\|push\|sync` —— **作用于所有可同步的库**(v0.30.5;判定见 v0.30.58):**「有远端」= 该库配了 `git.remote`,或它所在仓库有自己的 `origin`**(没配就自动回落,与设置页「同步」按钮同口径),两者都没有、或不在 git 仓库里的库被列出来而不是静默略过。多库各自独立仓库时逐仓库同步、**每个仓库只跑一次**(多库共用一个工作树时不会重复 pull/commit/push),一个仓库冲突不影响其余;回执逐仓库标注「哪个库 @ 哪个仓库」与**远端及其来源**(配置 / 自动识别自 origin;URL 里的凭据已剥) |
23
23
  | `tiddlywiki_git_resolve` | pull 冲突后按 tiddler 二选一(`keep-local\|keep-remote`) |
24
24
 
25
25
  **知识库同步纪律**:
26
26
  1. 开工先 `tiddlywiki_git_sync action=pull`(rebase + autostash,真冲突会 abort 并报文件)。
27
27
  2. 冲突后用 `tiddlywiki_git_resolve` 二选一解决,再重新 sync(**绝不自动覆盖**)。
28
- 3. 收工 `tiddlywiki_git_sync action=sync`(pull → commit → push)。**它与「同步」按钮的区别**(v0.30.5):工具作用于**所有**配了 `git.remote` 的库(一次一个仓库,各自独立成败),而设置页/FAB 的「同步」按钮只作用于**当前那个库**(它带着 `?wiki=`)。多库各有仓库时,冲突回执会写清是哪个库,解决时把那个库的 id 传给 `tiddlywiki_git_resolve wiki=<id>`。
28
+ 3. 收工 `tiddlywiki_git_sync action=sync`(pull → commit → push)。**它与「同步」按钮的区别**(v0.30.5;v0.30.58 起口径对齐):工具作用于**所有**可同步的库(一次一个仓库,各自独立成败),而设置页/FAB 的「同步」按钮只作用于**当前那个库**(它带着 `?wiki=`)。**「可同步」两边现在同源** —— 该库配了 `git.remote`,或它所在仓库有自己的 `origin`(v0.30.58 前工具只认前者,于是界面点得动、工具却回「没有任何知识库配置了 git.remote」)。多库各有仓库时,冲突回执会写清是哪个库,解决时把那个库的 id 传给 `tiddlywiki_git_resolve wiki=<id>`。
29
29
 
30
30
  > 🛡 **带着冲突绝不提交**(v0.23.4):`commit` 之前会探测「进行中的 rebase / 未合并路径 / **工作树里残留的冲突标记**」,命中就**拒绝提交**(`/sync` 返回 409 + 文件名、自动提交只上报一次、`/status` 与设置页状态行显示「冲突未解决(N 个文件,已阻止提交)」)。这条守卫来自真实事故:autostash 重新应用冲突后 `rebase --abort` 已无事可 abort,冲突标记留在工作树里被自动提交**永久写进了 git 历史**(连带插件自己解析不了配置)。**删冲突标记的方式**:解决后 `tiddlywiki_git_resolve`(或手动编辑掉 `<<<<<<<`/`=======`/`>>>>>>>` 三行)再 sync。
31
31
  >
package/lib/client.js CHANGED
@@ -20,9 +20,9 @@ Ctrl+Enter to save`,"note.uploadTitle":`Upload a file into the wiki and insert a
20
20
  Current: {root}`,"settings.wikis.moveNamePrompt":`Folder name (current: {name}; enter . to use the root folder above directly)`,"settings.wikis.rootRequired":`The root folder cannot be empty`,"settings.wikis.moving":`Moving (this wiki will be stopped and started)…`,"settings.wikis.needRootAndName":`Please fill in both the root folder and the folder name`,"settings.wikis.adding":`Adding (a new wiki has to be initialised and cold-started, one moment)…`,"settings.wikis.listFailed":`Could not read the knowledge-base list: {message}`,"settings.wikis.locationTitle":`Knowledge base location (switchable)`,"settings.wikis.rootPlaceholderSingle":`Absolute path, e.g. D:\\notes or $DSH_HOME/tiddlywiki`,"settings.wikis.folderPlaceholderSingle":`Folder name (default main; enter . to use the folder above directly)`,"settings.wikis.rootField":`Root folder (wikiRoot)`,"settings.wikis.folderField":`Folder name (wiki)`,"settings.wikis.switchHere":`Switch to this location`,"settings.wikis.resetToConfig":`Reset to the configured default`,"settings.wikis.locationHint":"Switching stops and restarts the TW child process in place (a few seconds); if the new folder has no tiddlywiki.info yet, the plugin runs `--init server` to create a fresh knowledge base there. The chosen location is remembered in a pointer file under $DSH_HOME (outside any wiki) and can be cleared with Reset to the configured default.","settings.wikis.locationUnavailable":`request failed`,"settings.wikis.current":`Current: {path}`,"settings.wikis.unknownPath":`(unknown)`,"settings.wikis.sourceState":`Source: pointer file (switched from this settings page)`,"settings.wikis.sourceConfig":`Source: cordis config (config.wikiRoot / config.wiki)`,"settings.wikis.sourceDefault":`Source: defaults ($DSH_HOME/tiddlywiki + main)`,"settings.wikis.sourceLine":`{source} · Configured default: {path}`,"settings.wikis.stateFile":`Pointer file: {path} (absent = using the configured default)`,"settings.wikis.candidatesLabel":`Wikis available in the same folder:`,"settings.wikis.rootItself":`(the root folder itself)`,"settings.wikis.candidateTitle":`Fill in the folder name "{name}"`,"settings.wikis.locationFailed":`Could not read the location: {message}`,"settings.wikis.needRoot":`Please fill in the root folder (an absolute path) first`,"settings.wikis.switchConfirm":`Switch the knowledge base to:
21
21
  {path}
22
22
 
23
- The TW child process will be stopped and restarted; this conversation and the tools will then read and write the new knowledge base. Continue?`,"settings.wikis.switching":`Switching… (restarting TW)`,"settings.wikis.switched":`Switched to {path}`,"settings.wikis.switchedWarn":`Switched to {path} (note: {warning})`,"settings.wikis.switchFailed":`Switch failed: {message}`,"settings.wikis.resetConfirm":`Reset to the configured default location? The location pointer file will be deleted and (if needed) the knowledge base from the config will be used again.`,"settings.wikis.resetDone":`Reset to the configured default: {path}`,"settings.wikis.resetWarn":`Reset to the default (note: {warning})`,"settings.wikis.resetFailed":`Reset failed: {message}`,"settings.iconPicker.label":`Icon`,"settings.iconPicker.aria":`Choose entry icon`,"settings.iconPicker.titleDefault":`Choose entry icon (default)`,"settings.iconPicker.titleCurrent":`Choose entry icon (current: {icon})`,"settings.iconPicker.default":`Default icon`,"settings.iconPicker.prev":`‹ Previous`,"settings.iconPicker.next":`Next ›`,"settings.iconPicker.page":`Page {page} of {pages} · {total} icons`,"settings.iconPicker.total":`{total} icons`,"settings.icon.archive":`Archive`,"settings.icon.archiveCheck":`Archive (done)`,"settings.icon.archiveOff":`Unarchive`,"settings.icon.unarchive":`Take out of archive`,"settings.icon.database":`Database`,"settings.icon.data":`Data`,"settings.icon.folder":`Folder`,"settings.icon.globe":`Globe / public`,"settings.icon.branch":`Branch`,"settings.icon.list":`List`,"settings.icon.checklist":`Checklist`,"settings.icon.flatList":`Flat list`,"settings.icon.skill":`Skill`,"settings.icon.goal":`Goal`,"settings.icon.compact":`Compact`,"settings.icon.clock":`Clock / history`,"settings.icon.alarm":`Reminder`,"settings.icon.pin":`Pinned`,"settings.icon.shield":`Secure / private`,"settings.icon.api":`API`,"settings.icon.code":`Code`,"settings.icon.users":`Team`,"settings.icon.user":`Personal`,"settings.icon.plugin":`Plugin`,"settings.icon.pinwheel":`Pinwheel`,"settings.icon.gauge":`Gauge / usage`,"settings.icon.tree":`Workspace tree`,"settings.icon.personalization":`Personalisation`,"settings.icon.link":`Link`,"settings.icon.search":`Search`,"settings.icon.settings":`Settings`,"settings.icon.sparkle":`Sparkle`,"settings.icon.think":`Thinking`,"settings.icon.light":`Light`,"settings.icon.dark":`Dark`,"settings.icon.refresh":`Refresh`,"settings.icon.book":`Book`,"settings.icon.briefcase":`Briefcase`,"settings.icon.home":`Home`,"settings.icon.notebook":`Notebook`,"settings.icon.flask":`Lab`,"settings.icon.star":`Star`,"settings.plugin.title":`Plugins (bundled official plugins)`,"settings.plugin.hint":`A tick writes the plugin into the startup install list in tiddlywiki.info (offline, matched to the engine version) and TW restarts on apply. This covers engine-bundled plugins only; plugins installed, disabled or removed in the TW control panel appear below under "Plugins inside the wiki".`,"settings.plugin.searchPlaceholder":`Search plugins…`,"settings.plugin.apply":`Apply plugins (restart TW)`,"settings.plugin.badgeDisabled":`Disabled in TW`,"settings.plugin.badgeDisabledTitle":`This plugin is in tiddlywiki.info but was disabled in the TW control panel ($:/config/Plugins/Disabled) — re-enable it there`,"settings.plugin.badgeInstalled":`Installed in wiki`,"settings.plugin.badgeInstalledTitle":`Not in the startup list, but the wiki holds a plugin tiddler with the same title (installed natively in TW) and the TW runtime is already using it`,"settings.plugin.applied":`Plugins applied, TW restarted`,"settings.wikiPlugins.title":`Plugins inside the wiki (installed natively in TW, read-only)`,"settings.wikiPlugins.hint":`These plugins are stored as tiddlers inside the wiki files (TW control panel → Plugins → install/import) and are not affected by the ticks above. Enable, disable or remove them from the TW control panel.`,"settings.wikiPlugins.disabled":`Disabled`,"settings.wikiPlugins.disabledTitle":`This plugin was disabled in the TW control panel`,"settings.themes.title":`Themes (bundled themes)`,"settings.themes.hint":`"Loaded" = themes available in TW (multi-select; the dependency chain comes along, e.g. heavier pulls in snowwhite+vanilla); "Active" = the theme you see (single choice, added to the loaded set automatically). TW restarts on apply.`,"settings.themes.colLoad":`Loaded`,"settings.themes.colActive":`Active`,"settings.themes.colTheme":`Theme`,"settings.themes.apply":`Apply themes (restart TW)`,"settings.themes.loadTitle":`Load this theme`,"settings.themes.activeTitle":`Make this the active theme`,"settings.themes.applied":`Themes applied, TW restarted`,"settings.langs.title":`Languages (bundled official language packs)`,"settings.langs.hint":`Tick to enable a language plugin and restart TW; for Chinese pick zh-Hans (simplified) or zh-CN.`,"settings.langs.apply":`Apply languages (restart TW)`,"settings.langs.applied":`Languages applied, TW restarted`,"settings.catalog.applyFailed":`Apply failed: {message}`,"settings.seeds.title":`Initialisation (one-time seeds)`,"settings.seeds.hint":`These are the tiddlers and settings the plugin seeds into the wiki so that it can work with dsh. "Send to agent button" and "TW front-end API base URL" are required: they are written automatically at startup (only when missing, never overwriting your edits). The rest are optional — not written by default and never forced. Click Re-initialise when you want them, and De-initialise to take them back out.`,"settings.seeds.fetchFailed":`request failed`,"settings.seeds.present":`Present`,"settings.seeds.missing":`Missing`,"settings.seeds.updateChip":`⬆ Update available`,"settings.seeds.updateTitle":`The built-in content changed after this wiki was seeded — click Re-initialise to take it`,"settings.seeds.modifiedChip":`✏️ Edited locally`,"settings.seeds.modifiedTitle":`This one is your content: re-initialising will overwrite it`,"settings.seeds.updateToBuiltin":`Update to built-in version`,"settings.seeds.reinit":`Re-initialise`,"settings.seeds.rewriteTitle":`Force-rewrite the built-in content of "{title}" (overwrites the current tiddler)`,"settings.seeds.overwriteModified":`"{title}" has local edits and updating will overwrite them.`,"settings.seeds.overwriteUnknown":`Cannot tell whether "{title}" was edited (old-style marker); updating may overwrite your changes.`,"settings.seeds.overwriteConfirm":`{warning}Overwrite with the built-in version?`,"settings.seeds.running":`Working…`,"settings.seeds.reinitFailed":`Re-initialise failed: {message}`,"settings.seeds.reinitDone":`Re-initialised "{title}"`,"settings.seeds.remove":`De-initialise`,"settings.seeds.removeTitle":`Remove the tiddlers and markers written by "{title}" (back to the never-seeded state)`,"settings.seeds.removeConfirm":`De-initialise "{title}"? The tiddlers it wrote (including its one-time marker) will be deleted; you can bring them back later with Re-initialise.`,"settings.seeds.removing":`Removing…`,"settings.seeds.removeFailed":`De-initialise failed: {message}`,"settings.seeds.removeDone":`De-initialised "{title}"`,"settings.seeds.statusLine":`{total} items, {present} ready; {removable} removable`,"settings.seeds.loadStatusFailed":`Could not load the initialisation status: {message}`,"settings.seeds.runAll":`Re-initialise all`,"settings.seeds.runAllTitle":`Force-rewrite every one-time seed (optional seeds included; overwrites the current tiddlers and settings)`,"settings.seeds.allReinitDone":`All seeds re-initialised`,"settings.seeds.removeAll":`De-initialise all`,"settings.seeds.removeAllTitle":`Remove the tiddlers and markers written by every optional seed (required items are kept)`,"settings.seeds.removeAllConfirm":`De-initialise all optional seeds? Every tiddler they wrote (including their one-time markers) will be deleted. Required items (the send-to-agent button and the TW front-end API base URL) are kept.`,"settings.seeds.allRemoved":`All optional seeds de-initialised`}},{zh:{"config.title":`常规配置`,"config.token.showHide":`显示 / 隐藏`,"config.num.empty":`不能为空`,"config.num.nan":`请输入数字`,"config.num.integer":`必须是整数`,"config.num.belowMin":`不能小于 {min}`,"config.num.aboveMax":`不能大于 {max}`,"config.num.invalidHint":`⚠️ {message}(保存会被拒绝)`,"config.note.tag":`快速笔记默认 tag`,"config.note.workspaceMark":`新建笔记自动标工作区(ws/<项目名> 标签 + workspace 字段,取自当前会话工作目录)`,"config.git.autoCommit":`自动 commit(防抖)`,"config.git.debounceMs":`自动 commit 防抖(ms;范围 0–3600000)`,"config.git.remote":`git 远端(空=仅本地;保存后即时生效,但**清空不会删除已配置的 origin**,需要时请用 git 命令处理)`,"config.git.branch":`git 分支(**只在仓库首次初始化时生效**;已有仓库请用 git 命令改分支——上面的状态行显示的是真实分支)`,"config.startup.readyTimeoutMs":`TW 启动就绪等待(ms;默认 60000,范围 5000–600000,超时上限为其 3 倍,对下次启动/重启生效)`,"config.ui.showQuickNote":`显示「知识库」按钮里的「快速笔记」入口`,"config.ui.showQuickNoteDock":`显示聊天输入框上方的「快速笔记」快捷按钮`,"config.ui.quickNoteMode":`点击「快速笔记」的打开方式`,"config.ui.quickNoteModeNative":`原生编辑器:直接弹出 TW 原生编辑页(新建/恢复草稿)`,"config.ui.quickNoteModeCard":`Markdown 卡片:弹出现有快速笔记卡片(CodeMirror 编辑器)`,"config.ui.multiWikiSidebarHint":`侧边栏入口:多库模式下每个知识库各占一行、各用自己的「显示名」(在「知识库列表」里改),不再是统一的一个名称,所以这里没有对应配置项。`,"config.ui.sidebarLabel":`侧边栏 TW 入口显示名称`,"config.ui.showPanelStatus":`显示「知识库」按钮里的 TW 面板/重载入口与状态行`,"config.ui.showSyncButton":`显示「知识库」按钮里的「同步」入口与 git 状态点`,"config.ui.followDshTheme":`嵌入式 TW 跟随 DSH 深浅主题(暗色时自动切深色 palette,不写回 wiki)`,"config.ui.darkPalette":`暗色时 TW palette(tiddler 标题)`,"config.ui.tabLabel":`会话顶部「知识库」Tab 名称`,"config.ui.showSessionTab":`显示会话顶部「知识库」Tab(本会话相关 wiki 笔记汇总)`,"config.ui.showRightbarTab":`在 DSH 右侧边栏提供 TiddlyWiki 入口/Tab(与聊天并排)`,"config.ui.sendToAgent.enabled":`启用「发送给 Agent」(TW 笔记 → DSH 会话注入)`,"config.ui.sendToAgent.endpoint":`TW 端请求基址(空=自动取当前 DSH origin)`,"config.ui.sendToAgent.token":`共享 token(非空时路由校验 x-send-to-agent-token 头;已设置时显示为 ********,原样保存=不改,清空=删除)`,"config.ui.allArticles.pageSize":`「所有文章」每页条数(1–200)`,"config.wechat.title":`可选功能:微信公众号发布`,"config.wechat.hint":`把 wiki 笔记一键发到公众号草稿箱(可选点发表)。**需要额外安装**:opencli 与 Browser Bridge 浏览器扩展,并让浏览器登录 mp.weixin.qq.com;adapter 在本仓库 tools/wechat/,安装与排错见 docs/wechat-publish-setup.md。不安装/不开启它,插件其他功能完全不受影响。开启后:注入提示词会多一条「发布前先读发布元数据规范」的约定,并在启动时把「发布元数据规范」与「微信公众号发布指南」(安装/换机还原步骤,等于仓库 docs/wechat-publish-setup.md)两篇文档写进 wiki(同名不覆盖)。`,"config.wechat.enabled":`启用微信公众号发布(默认关;需先按 docs/wechat-publish-setup.md 安装 opencli + 浏览器扩展)`,"config.wechat.command":`opencli 命令(默认 opencli;用别名或绝对路径时填这里。仅在启用微信发布时生效)`,"config.wechat.adapter":`发布适配器(仅在启用微信发布时生效)`,"config.wechat.adapterDefault":`publish-note(默认):图文排版,封面单图`,"config.wechat.adapterImgs":`publish-note-imgs:正文内嵌图全部上传 CDN(需先重跑 install-wechat-adapters.mjs)`,"config.wechat.token":`发布 token(非空时 /wechat/* 校验 x-wechat-publish-token 头,强烈建议设置,否则任何能打开本页的人都能替你发文;已设置时显示为 ********,原样保存=不改,清空=删除。仅在启用微信发布时生效)`,"config.wechat.dsn":`渲染基址 dsn(adapter 回连 DSH 用,形如 http://127.0.0.1:<端口>/dsh-tiddlywiki;留空=按请求自动推导。仅在启用微信发布时生效)`,"config.prompt.title":`系统提示词(注入每个会话)`,"config.prompt.hint":`插件把自己的约定(同步纪律 / 标签约定 / 链接格式等)注入每个会话的系统提示词。保存后**无需重启 dsh web**:section 会即时重新注册,当前会话从下一步起就使用新文本。`,"config.prompt.enabled":`注入 TiddlyWiki 提示词(关闭后本插件不再注入任何文本)`,"config.prompt.mode":`内置文本形态`,"config.prompt.modeSlim":`精简(默认):只保留工具 schema 表达不了的约定`,"config.prompt.modeFull":`完整:额外附一份由工具注册表实时生成的参数索引`,"config.prompt.extra":`附加说明(永远追加在末尾,可放团队/个人规范)`,"config.prompt.override":`整段替换(非空时取代上面的内置文本,附加说明仍会追加)`,"config.prompt.preview":`预览注入文本(按表单当前值)`,"config.prompt.previewTitle":`按表单当前值渲染注入文本(不用先保存);点「保存配置」后这就是实际注入的内容`,"config.prompt.fetchFailed":`获取失败`,"config.prompt.previewDisabled":`(已关闭:不会注入任何提示词)`,"config.prompt.previewHeader":`# 形态 {mode} · {length} 字符{dirty}
23
+ The TW child process will be stopped and restarted; this conversation and the tools will then read and write the new knowledge base. Continue?`,"settings.wikis.switching":`Switching… (restarting TW)`,"settings.wikis.switched":`Switched to {path}`,"settings.wikis.switchedWarn":`Switched to {path} (note: {warning})`,"settings.wikis.switchFailed":`Switch failed: {message}`,"settings.wikis.resetConfirm":`Reset to the configured default location? The location pointer file will be deleted and (if needed) the knowledge base from the config will be used again.`,"settings.wikis.resetDone":`Reset to the configured default: {path}`,"settings.wikis.resetWarn":`Reset to the default (note: {warning})`,"settings.wikis.resetFailed":`Reset failed: {message}`,"settings.iconPicker.label":`Icon`,"settings.iconPicker.aria":`Choose entry icon`,"settings.iconPicker.titleDefault":`Choose entry icon (default)`,"settings.iconPicker.titleCurrent":`Choose entry icon (current: {icon})`,"settings.iconPicker.default":`Default icon`,"settings.iconPicker.prev":`‹ Previous`,"settings.iconPicker.next":`Next ›`,"settings.iconPicker.page":`Page {page} of {pages} · {total} icons`,"settings.iconPicker.total":`{total} icons`,"settings.icon.archive":`Archive`,"settings.icon.archiveCheck":`Archive (done)`,"settings.icon.archiveOff":`Unarchive`,"settings.icon.unarchive":`Take out of archive`,"settings.icon.database":`Database`,"settings.icon.data":`Data`,"settings.icon.folder":`Folder`,"settings.icon.globe":`Globe / public`,"settings.icon.branch":`Branch`,"settings.icon.list":`List`,"settings.icon.checklist":`Checklist`,"settings.icon.flatList":`Flat list`,"settings.icon.skill":`Skill`,"settings.icon.goal":`Goal`,"settings.icon.compact":`Compact`,"settings.icon.clock":`Clock / history`,"settings.icon.alarm":`Reminder`,"settings.icon.pin":`Pinned`,"settings.icon.shield":`Secure / private`,"settings.icon.api":`API`,"settings.icon.code":`Code`,"settings.icon.users":`Team`,"settings.icon.user":`Personal`,"settings.icon.plugin":`Plugin`,"settings.icon.pinwheel":`Pinwheel`,"settings.icon.gauge":`Gauge / usage`,"settings.icon.tree":`Workspace tree`,"settings.icon.personalization":`Personalisation`,"settings.icon.link":`Link`,"settings.icon.search":`Search`,"settings.icon.settings":`Settings`,"settings.icon.sparkle":`Sparkle`,"settings.icon.think":`Thinking`,"settings.icon.light":`Light`,"settings.icon.dark":`Dark`,"settings.icon.refresh":`Refresh`,"settings.icon.book":`Book`,"settings.icon.briefcase":`Briefcase`,"settings.icon.home":`Home`,"settings.icon.notebook":`Notebook`,"settings.icon.flask":`Lab`,"settings.icon.star":`Star`,"settings.plugin.title":`Plugins (bundled official plugins)`,"settings.plugin.hint":`A tick writes the plugin into the startup install list in tiddlywiki.info (offline, matched to the engine version) and TW restarts on apply. This covers engine-bundled plugins only; plugins installed, disabled or removed in the TW control panel appear below under "Plugins inside the wiki".`,"settings.plugin.searchPlaceholder":`Search plugins…`,"settings.plugin.apply":`Apply plugins (restart TW)`,"settings.plugin.badgeDisabled":`Disabled in TW`,"settings.plugin.badgeDisabledTitle":`This plugin is in tiddlywiki.info but was disabled in the TW control panel ($:/config/Plugins/Disabled) — re-enable it there`,"settings.plugin.badgeInstalled":`Installed in wiki`,"settings.plugin.badgeInstalledTitle":`Not in the startup list, but the wiki holds a plugin tiddler with the same title (installed natively in TW) and the TW runtime is already using it`,"settings.plugin.applied":`Plugins applied, TW restarted`,"settings.wikiPlugins.title":`Plugins inside the wiki (installed natively in TW, read-only)`,"settings.wikiPlugins.hint":`These plugins are stored as tiddlers inside the wiki files (TW control panel → Plugins → install/import) and are not affected by the ticks above. Enable, disable or remove them from the TW control panel.`,"settings.wikiPlugins.disabled":`Disabled`,"settings.wikiPlugins.disabledTitle":`This plugin was disabled in the TW control panel`,"settings.themes.title":`Themes (bundled themes)`,"settings.themes.hint":`"Loaded" = themes available in TW (multi-select; the dependency chain comes along, e.g. heavier pulls in snowwhite+vanilla); "Active" = the theme you see (single choice, added to the loaded set automatically). TW restarts on apply.`,"settings.themes.colLoad":`Loaded`,"settings.themes.colActive":`Active`,"settings.themes.colTheme":`Theme`,"settings.themes.apply":`Apply themes (restart TW)`,"settings.themes.loadTitle":`Load this theme`,"settings.themes.activeTitle":`Make this the active theme`,"settings.themes.applied":`Themes applied, TW restarted`,"settings.langs.title":`Languages (bundled official language packs)`,"settings.langs.hint":`Tick to enable a language plugin and restart TW; for Chinese pick zh-Hans (simplified) or zh-CN.`,"settings.langs.apply":`Apply languages (restart TW)`,"settings.langs.applied":`Languages applied, TW restarted`,"settings.catalog.applyFailed":`Apply failed: {message}`,"settings.seeds.title":`Initialisation (one-time seeds)`,"settings.seeds.hint":`These are the tiddlers and settings the plugin seeds into the wiki so that it can work with dsh. "Send to agent button" and "TW front-end API base URL" are required: they are written automatically at startup (only when missing, never overwriting your edits). The rest are optional — not written by default and never forced. Click Re-initialise when you want them, and De-initialise to take them back out.`,"settings.seeds.fetchFailed":`request failed`,"settings.seeds.present":`Present`,"settings.seeds.missing":`Missing`,"settings.seeds.updateChip":`⬆ Update available`,"settings.seeds.updateTitle":`The built-in content changed after this wiki was seeded — click Re-initialise to take it`,"settings.seeds.modifiedChip":`✏️ Edited locally`,"settings.seeds.modifiedTitle":`This one is your content: re-initialising will overwrite it`,"settings.seeds.updateToBuiltin":`Update to built-in version`,"settings.seeds.reinit":`Re-initialise`,"settings.seeds.rewriteTitle":`Force-rewrite the built-in content of "{title}" (overwrites the current tiddler)`,"settings.seeds.overwriteModified":`"{title}" has local edits and updating will overwrite them.`,"settings.seeds.overwriteUnknown":`Cannot tell whether "{title}" was edited (old-style marker); updating may overwrite your changes.`,"settings.seeds.overwriteConfirm":`{warning}Overwrite with the built-in version?`,"settings.seeds.running":`Working…`,"settings.seeds.reinitFailed":`Re-initialise failed: {message}`,"settings.seeds.reinitDone":`Re-initialised "{title}"`,"settings.seeds.remove":`De-initialise`,"settings.seeds.removeTitle":`Remove the tiddlers and markers written by "{title}" (back to the never-seeded state)`,"settings.seeds.removeConfirm":`De-initialise "{title}"? The tiddlers it wrote (including its one-time marker) will be deleted; you can bring them back later with Re-initialise.`,"settings.seeds.removing":`Removing…`,"settings.seeds.removeFailed":`De-initialise failed: {message}`,"settings.seeds.removeDone":`De-initialised "{title}"`,"settings.seeds.statusLine":`{total} items, {present} ready; {removable} removable`,"settings.seeds.loadStatusFailed":`Could not load the initialisation status: {message}`,"settings.seeds.runAll":`Re-initialise all`,"settings.seeds.runAllTitle":`Force-rewrite every one-time seed (optional seeds included; overwrites the current tiddlers and settings)`,"settings.seeds.allReinitDone":`All seeds re-initialised`,"settings.seeds.removeAll":`De-initialise all`,"settings.seeds.removeAllTitle":`Remove the tiddlers and markers written by every optional seed (required items are kept)`,"settings.seeds.removeAllConfirm":`De-initialise all optional seeds? Every tiddler they wrote (including their one-time markers) will be deleted. Required items (the send-to-agent button and the TW front-end API base URL) are kept.`,"settings.seeds.allRemoved":`All optional seeds de-initialised`}},{zh:{"config.title":`常规配置`,"config.token.showHide":`显示 / 隐藏`,"config.num.empty":`不能为空`,"config.num.nan":`请输入数字`,"config.num.integer":`必须是整数`,"config.num.belowMin":`不能小于 {min}`,"config.num.aboveMax":`不能大于 {max}`,"config.num.invalidHint":`⚠️ {message}(保存会被拒绝)`,"config.note.tag":`快速笔记默认 tag`,"config.note.workspaceMark":`新建笔记自动标工作区(ws/<项目名> 标签 + workspace 字段,取自当前会话工作目录)`,"config.git.autoCommit":`自动 commit(防抖)`,"config.git.debounceMs":`自动 commit 防抖(ms;范围 0–3600000)`,"config.git.remote":"git 远端(**空=自动用该库所在仓库的 `origin`**;填了则以填的为准。保存后即时生效,但**清空不会删除已配置的 origin**,需要时请用 git 命令处理)","config.git.branch":`git 分支(**只在仓库首次初始化时生效**;已有仓库请用 git 命令改分支——上面的状态行显示的是真实分支)`,"config.startup.readyTimeoutMs":`TW 启动就绪等待(ms;默认 60000,范围 5000–600000,超时上限为其 3 倍,对下次启动/重启生效)`,"config.ui.showQuickNote":`显示「知识库」按钮里的「快速笔记」入口`,"config.ui.showQuickNoteDock":`显示聊天输入框上方的「快速笔记」快捷按钮`,"config.ui.quickNoteMode":`点击「快速笔记」的打开方式`,"config.ui.quickNoteModeNative":`原生编辑器:直接弹出 TW 原生编辑页(新建/恢复草稿)`,"config.ui.quickNoteModeCard":`Markdown 卡片:弹出现有快速笔记卡片(CodeMirror 编辑器)`,"config.ui.multiWikiSidebarHint":`侧边栏入口:多库模式下每个知识库各占一行、各用自己的「显示名」(在「知识库列表」里改),不再是统一的一个名称,所以这里没有对应配置项。`,"config.ui.sidebarLabel":`侧边栏 TW 入口显示名称`,"config.ui.showPanelStatus":`显示「知识库」按钮里的 TW 面板/重载入口与状态行`,"config.ui.showSyncButton":`显示「知识库」按钮里的「同步」入口与 git 状态点`,"config.ui.followDshTheme":`嵌入式 TW 跟随 DSH 深浅主题(暗色时自动切深色 palette,不写回 wiki)`,"config.ui.darkPalette":`暗色时 TW palette(tiddler 标题)`,"config.ui.tabLabel":`会话顶部「知识库」Tab 名称`,"config.ui.showSessionTab":`显示会话顶部「知识库」Tab(本会话相关 wiki 笔记汇总)`,"config.ui.showRightbarTab":`在 DSH 右侧边栏提供 TiddlyWiki 入口/Tab(与聊天并排)`,"config.ui.sendToAgent.enabled":`启用「发送给 Agent」(TW 笔记 → DSH 会话注入)`,"config.ui.sendToAgent.endpoint":`TW 端请求基址(空=自动取当前 DSH origin)`,"config.ui.sendToAgent.token":`共享 token(非空时路由校验 x-send-to-agent-token 头;已设置时显示为 ********,原样保存=不改,清空=删除)`,"config.ui.allArticles.pageSize":`「所有文章」每页条数(1–200)`,"config.wechat.title":`可选功能:微信公众号发布`,"config.wechat.hint":`把 wiki 笔记一键发到公众号草稿箱(可选点发表)。**需要额外安装**:opencli 与 Browser Bridge 浏览器扩展,并让浏览器登录 mp.weixin.qq.com;adapter 在本仓库 tools/wechat/,安装与排错见 docs/wechat-publish-setup.md。不安装/不开启它,插件其他功能完全不受影响。开启后:注入提示词会多一条「发布前先读发布元数据规范」的约定,并在启动时把「发布元数据规范」与「微信公众号发布指南」(安装/换机还原步骤,等于仓库 docs/wechat-publish-setup.md)两篇文档写进 wiki(同名不覆盖)。`,"config.wechat.enabled":`启用微信公众号发布(默认关;需先按 docs/wechat-publish-setup.md 安装 opencli + 浏览器扩展)`,"config.wechat.command":`opencli 命令(默认 opencli;用别名或绝对路径时填这里。仅在启用微信发布时生效)`,"config.wechat.adapter":`发布适配器(仅在启用微信发布时生效)`,"config.wechat.adapterDefault":`publish-note(默认):图文排版,封面单图`,"config.wechat.adapterImgs":`publish-note-imgs:正文内嵌图全部上传 CDN(需先重跑 install-wechat-adapters.mjs)`,"config.wechat.token":`发布 token(非空时 /wechat/* 校验 x-wechat-publish-token 头,强烈建议设置,否则任何能打开本页的人都能替你发文;已设置时显示为 ********,原样保存=不改,清空=删除。仅在启用微信发布时生效)`,"config.wechat.dsn":`渲染基址 dsn(adapter 回连 DSH 用,形如 http://127.0.0.1:<端口>/dsh-tiddlywiki;留空=按请求自动推导。仅在启用微信发布时生效)`,"config.prompt.title":`系统提示词(注入每个会话)`,"config.prompt.hint":`插件把自己的约定(同步纪律 / 标签约定 / 链接格式等)注入每个会话的系统提示词。保存后**无需重启 dsh web**:section 会即时重新注册,当前会话从下一步起就使用新文本。`,"config.prompt.enabled":`注入 TiddlyWiki 提示词(关闭后本插件不再注入任何文本)`,"config.prompt.mode":`内置文本形态`,"config.prompt.modeSlim":`精简(默认):只保留工具 schema 表达不了的约定`,"config.prompt.modeFull":`完整:额外附一份由工具注册表实时生成的参数索引`,"config.prompt.extra":`附加说明(永远追加在末尾,可放团队/个人规范)`,"config.prompt.override":`整段替换(非空时取代上面的内置文本,附加说明仍会追加)`,"config.prompt.preview":`预览注入文本(按表单当前值)`,"config.prompt.previewTitle":`按表单当前值渲染注入文本(不用先保存);点「保存配置」后这就是实际注入的内容`,"config.prompt.fetchFailed":`获取失败`,"config.prompt.previewDisabled":`(已关闭:不会注入任何提示词)`,"config.prompt.previewHeader":`# 形态 {mode} · {length} 字符{dirty}
24
24
 
25
- {text}`,"config.prompt.previewDirty":`(含未保存的修改,保存后才真正注入)`,"config.prompt.readFailed":`读取提示词失败:{message}`,"config.bridge.enabled":`启用「本地剪藏桥」(书签小工具后端监听 127.0.0.1 端口,保存后立即生效)`,"config.bridge.port":`剪藏桥端口(整数 1–65535;改端口需重启 dsh web 生效)`,"config.bridge.token":`剪藏桥 token(非空时校验书签的 x-clip-token 头;强烈建议设置。已设置时显示为 ********,原样保存=不改,清空=删除)`,"config.bridge.tag":`剪藏笔记默认 tag`,"config.bridge.wiki":`剪藏写入的知识库(不指定 = 写进知识库列表里带 ★ 的那个库;多库时可指定具体的库)`,"config.clipWiki.followDefault":`(跟随默认的那个库)`,"config.clipWiki.defaultSuffix":`{label}(默认)`,"config.clipWiki.option":`{label}({id}){suffix}`,"config.clipWiki.notRunning":` · 未运行`,"config.clipWiki.notInListWithDefault":`{current}(不在清单里,保存后写进 {label})`,"config.clipWiki.notInList":`{current}(不在清单里)`,"config.save":`保存配置`,"config.invalidFields":`有 {count} 个字段填得不对(见字段旁的红色提示),未保存`,"config.noChanges":`没有改动`,"config.saved":`配置已保存`,"config.saveFailed":`保存失败:{message}`},en:{"config.title":`General settings`,"config.token.showHide":`Show / hide`,"config.num.empty":`Cannot be empty`,"config.num.nan":`Enter a number`,"config.num.integer":`Must be an integer`,"config.num.belowMin":`Cannot be less than {min}`,"config.num.aboveMax":`Cannot be greater than {max}`,"config.num.invalidHint":`⚠️ {message} (save will be refused)`,"config.note.tag":`Default tag for quick notes`,"config.note.workspaceMark":`Tag new notes with their workspace (ws/<project> tag + workspace field, taken from the session working directory)`,"config.git.autoCommit":`Auto-commit (debounced)`,"config.git.debounceMs":`Auto-commit debounce (ms; range 0–3600000)`,"config.git.remote":`Git remote (empty = local only; takes effect right after saving, but **clearing it does NOT delete a configured origin** — use git commands for that)`,"config.git.branch":`Git branch (**only used when the repository is first initialised**; for an existing repository change the branch with git commands — the status row above shows the real branch)`,"config.startup.readyTimeoutMs":`TW startup readiness wait (ms; default 60000, range 5000–600000, hard limit 3× that, applies to the next start/restart)`,"config.ui.showQuickNote":`Show the Quick note entry in the Knowledge base button`,"config.ui.showQuickNoteDock":`Show the Quick note shortcut above the chat input`,"config.ui.quickNoteMode":`What clicking Quick note opens`,"config.ui.quickNoteModeNative":`Native editor: opens the TW edit page directly (new or restored draft)`,"config.ui.quickNoteModeCard":`Markdown card: opens the quick-note card (CodeMirror editor)`,"config.ui.multiWikiSidebarHint":`Sidebar entry: in multi-wiki mode every knowledge base gets its own row and its own display name (edited in the knowledge-base list), so there is no single name to configure here.`,"config.ui.sidebarLabel":`Sidebar TW entry display name`,"config.ui.showPanelStatus":`Show the TW panel / reload entry and status row in the Knowledge base button`,"config.ui.showSyncButton":`Show the Sync entry and git status dot in the Knowledge base button`,"config.ui.followDshTheme":`Make the embedded TW follow the DSH light/dark theme (switch palette in dark mode, never written back to the wiki)`,"config.ui.darkPalette":`TW palette in dark mode (tiddler title)`,"config.ui.tabLabel":`Name of the Knowledge base tab above the conversation`,"config.ui.showSessionTab":`Show the Knowledge base tab above the conversation (wiki notes related to this session)`,"config.ui.showRightbarTab":`Offer a TiddlyWiki entry/tab in the DSH right sidebar (next to the chat)`,"config.ui.sendToAgent.enabled":`Enable Send to agent (TW note → inject into the DSH conversation)`,"config.ui.sendToAgent.endpoint":`TW-side request base URL (empty = use the current DSH origin)`,"config.ui.sendToAgent.token":`Shared token (when non-empty the route checks the x-send-to-agent-token header; shown as ******** once set — saving it unchanged keeps it, clearing it deletes it)`,"config.ui.allArticles.pageSize":`Items per page in All articles (1–200)`,"config.wechat.title":`Optional feature: publishing to WeChat official accounts`,"config.wechat.hint":`Publish wiki notes to the official-account draft box in one click (publishing itself stays optional). **Extra installation required**: opencli and the Browser Bridge browser extension, plus a browser signed in to mp.weixin.qq.com; the adapter lives in tools/wechat/ of this repository, see docs/wechat-publish-setup.md for installation and troubleshooting. Not installing or not enabling it leaves every other feature untouched. Once enabled: the injected prompt gains a "read the publishing metadata spec first" convention, and two documents — the publishing metadata spec and the WeChat publishing guide (install / restore-on-a-new-machine steps, i.e. docs/wechat-publish-setup.md) — are written into the wiki at startup (existing files are never overwritten).`,"config.wechat.enabled":`Enable WeChat official-account publishing (off by default; install opencli + the browser extension as described in docs/wechat-publish-setup.md first)`,"config.wechat.command":`opencli command (default opencli; put an alias or absolute path here. Only used when WeChat publishing is enabled)`,"config.wechat.adapter":`Publishing adapter (only used when WeChat publishing is enabled)`,"config.wechat.adapterDefault":`publish-note (default): laid-out text with a single cover image`,"config.wechat.adapterImgs":`publish-note-imgs: every inline image is uploaded to a CDN (re-run install-wechat-adapters.mjs first)`,"config.wechat.token":`Publishing token (when non-empty /wechat/* checks the x-wechat-publish-token header; strongly recommended, otherwise anyone who can open this page can publish as you. Shown as ******** once set — saving it unchanged keeps it, clearing it deletes it. Only used when WeChat publishing is enabled)`,"config.wechat.dsn":`Render base dsn (for the adapter to call back into DSH, e.g. http://127.0.0.1:<port>/dsh-tiddlywiki; empty = derived from the request. Only used when WeChat publishing is enabled)`,"config.prompt.title":`System prompt (injected into every session)`,"config.prompt.hint":`The plugin injects its own conventions (sync discipline, tag rules, link format and so on) into every session system prompt. Saving needs **no dsh web restart**: the section is re-registered immediately and the current session uses the new text from its next step.`,"config.prompt.enabled":`Inject the TiddlyWiki prompt (when off the plugin injects no text at all)`,"config.prompt.mode":`Built-in text form`,"config.prompt.modeSlim":`Slim (default): keeps only the conventions the tool schemas cannot express`,"config.prompt.modeFull":`Full: adds a parameter index generated live from the tool registry`,"config.prompt.extra":`Extra notes (always appended at the end; good for team or personal rules)`,"config.prompt.override":`Full replacement (when non-empty it replaces the built-in text above; extra notes are still appended)`,"config.prompt.preview":`Preview the injected text (from the current form values)`,"config.prompt.previewTitle":`Render the injected text from the current form values (no need to save first); after Save this is exactly what gets injected`,"config.prompt.fetchFailed":`request failed`,"config.prompt.previewDisabled":`(disabled: no prompt will be injected)`,"config.prompt.previewHeader":`# form {mode} · {length} characters{dirty}
25
+ {text}`,"config.prompt.previewDirty":`(含未保存的修改,保存后才真正注入)`,"config.prompt.readFailed":`读取提示词失败:{message}`,"config.bridge.enabled":`启用「本地剪藏桥」(书签小工具后端监听 127.0.0.1 端口,保存后立即生效)`,"config.bridge.port":`剪藏桥端口(整数 1–65535;改端口需重启 dsh web 生效)`,"config.bridge.token":`剪藏桥 token(非空时校验书签的 x-clip-token 头;强烈建议设置。已设置时显示为 ********,原样保存=不改,清空=删除)`,"config.bridge.tag":`剪藏笔记默认 tag`,"config.bridge.wiki":`剪藏写入的知识库(不指定 = 写进知识库列表里带 ★ 的那个库;多库时可指定具体的库)`,"config.clipWiki.followDefault":`(跟随默认的那个库)`,"config.clipWiki.defaultSuffix":`{label}(默认)`,"config.clipWiki.option":`{label}({id}){suffix}`,"config.clipWiki.notRunning":` · 未运行`,"config.clipWiki.notInListWithDefault":`{current}(不在清单里,保存后写进 {label})`,"config.clipWiki.notInList":`{current}(不在清单里)`,"config.save":`保存配置`,"config.invalidFields":`有 {count} 个字段填得不对(见字段旁的红色提示),未保存`,"config.noChanges":`没有改动`,"config.saved":`配置已保存`,"config.saveFailed":`保存失败:{message}`},en:{"config.title":`General settings`,"config.token.showHide":`Show / hide`,"config.num.empty":`Cannot be empty`,"config.num.nan":`Enter a number`,"config.num.integer":`Must be an integer`,"config.num.belowMin":`Cannot be less than {min}`,"config.num.aboveMax":`Cannot be greater than {max}`,"config.num.invalidHint":`⚠️ {message} (save will be refused)`,"config.note.tag":`Default tag for quick notes`,"config.note.workspaceMark":`Tag new notes with their workspace (ws/<project> tag + workspace field, taken from the session working directory)`,"config.git.autoCommit":`Auto-commit (debounced)`,"config.git.debounceMs":`Auto-commit debounce (ms; range 0–3600000)`,"config.git.remote":"Git remote (**empty = use the `origin` of the repository this wiki lives in**; a value here overrides it. Takes effect right after saving, but **clearing it does NOT delete a configured origin** — use git commands for that)","config.git.branch":`Git branch (**only used when the repository is first initialised**; for an existing repository change the branch with git commands — the status row above shows the real branch)`,"config.startup.readyTimeoutMs":`TW startup readiness wait (ms; default 60000, range 5000–600000, hard limit 3× that, applies to the next start/restart)`,"config.ui.showQuickNote":`Show the Quick note entry in the Knowledge base button`,"config.ui.showQuickNoteDock":`Show the Quick note shortcut above the chat input`,"config.ui.quickNoteMode":`What clicking Quick note opens`,"config.ui.quickNoteModeNative":`Native editor: opens the TW edit page directly (new or restored draft)`,"config.ui.quickNoteModeCard":`Markdown card: opens the quick-note card (CodeMirror editor)`,"config.ui.multiWikiSidebarHint":`Sidebar entry: in multi-wiki mode every knowledge base gets its own row and its own display name (edited in the knowledge-base list), so there is no single name to configure here.`,"config.ui.sidebarLabel":`Sidebar TW entry display name`,"config.ui.showPanelStatus":`Show the TW panel / reload entry and status row in the Knowledge base button`,"config.ui.showSyncButton":`Show the Sync entry and git status dot in the Knowledge base button`,"config.ui.followDshTheme":`Make the embedded TW follow the DSH light/dark theme (switch palette in dark mode, never written back to the wiki)`,"config.ui.darkPalette":`TW palette in dark mode (tiddler title)`,"config.ui.tabLabel":`Name of the Knowledge base tab above the conversation`,"config.ui.showSessionTab":`Show the Knowledge base tab above the conversation (wiki notes related to this session)`,"config.ui.showRightbarTab":`Offer a TiddlyWiki entry/tab in the DSH right sidebar (next to the chat)`,"config.ui.sendToAgent.enabled":`Enable Send to agent (TW note → inject into the DSH conversation)`,"config.ui.sendToAgent.endpoint":`TW-side request base URL (empty = use the current DSH origin)`,"config.ui.sendToAgent.token":`Shared token (when non-empty the route checks the x-send-to-agent-token header; shown as ******** once set — saving it unchanged keeps it, clearing it deletes it)`,"config.ui.allArticles.pageSize":`Items per page in All articles (1–200)`,"config.wechat.title":`Optional feature: publishing to WeChat official accounts`,"config.wechat.hint":`Publish wiki notes to the official-account draft box in one click (publishing itself stays optional). **Extra installation required**: opencli and the Browser Bridge browser extension, plus a browser signed in to mp.weixin.qq.com; the adapter lives in tools/wechat/ of this repository, see docs/wechat-publish-setup.md for installation and troubleshooting. Not installing or not enabling it leaves every other feature untouched. Once enabled: the injected prompt gains a "read the publishing metadata spec first" convention, and two documents — the publishing metadata spec and the WeChat publishing guide (install / restore-on-a-new-machine steps, i.e. docs/wechat-publish-setup.md) — are written into the wiki at startup (existing files are never overwritten).`,"config.wechat.enabled":`Enable WeChat official-account publishing (off by default; install opencli + the browser extension as described in docs/wechat-publish-setup.md first)`,"config.wechat.command":`opencli command (default opencli; put an alias or absolute path here. Only used when WeChat publishing is enabled)`,"config.wechat.adapter":`Publishing adapter (only used when WeChat publishing is enabled)`,"config.wechat.adapterDefault":`publish-note (default): laid-out text with a single cover image`,"config.wechat.adapterImgs":`publish-note-imgs: every inline image is uploaded to a CDN (re-run install-wechat-adapters.mjs first)`,"config.wechat.token":`Publishing token (when non-empty /wechat/* checks the x-wechat-publish-token header; strongly recommended, otherwise anyone who can open this page can publish as you. Shown as ******** once set — saving it unchanged keeps it, clearing it deletes it. Only used when WeChat publishing is enabled)`,"config.wechat.dsn":`Render base dsn (for the adapter to call back into DSH, e.g. http://127.0.0.1:<port>/dsh-tiddlywiki; empty = derived from the request. Only used when WeChat publishing is enabled)`,"config.prompt.title":`System prompt (injected into every session)`,"config.prompt.hint":`The plugin injects its own conventions (sync discipline, tag rules, link format and so on) into every session system prompt. Saving needs **no dsh web restart**: the section is re-registered immediately and the current session uses the new text from its next step.`,"config.prompt.enabled":`Inject the TiddlyWiki prompt (when off the plugin injects no text at all)`,"config.prompt.mode":`Built-in text form`,"config.prompt.modeSlim":`Slim (default): keeps only the conventions the tool schemas cannot express`,"config.prompt.modeFull":`Full: adds a parameter index generated live from the tool registry`,"config.prompt.extra":`Extra notes (always appended at the end; good for team or personal rules)`,"config.prompt.override":`Full replacement (when non-empty it replaces the built-in text above; extra notes are still appended)`,"config.prompt.preview":`Preview the injected text (from the current form values)`,"config.prompt.previewTitle":`Render the injected text from the current form values (no need to save first); after Save this is exactly what gets injected`,"config.prompt.fetchFailed":`request failed`,"config.prompt.previewDisabled":`(disabled: no prompt will be injected)`,"config.prompt.previewHeader":`# form {mode} · {length} characters{dirty}
26
26
 
27
27
  {text}`,"config.prompt.previewDirty":` (includes unsaved edits; only saved values are really injected)`,"config.prompt.readFailed":`Could not read the prompt: {message}`,"config.bridge.enabled":`Enable the local clip bridge (bookmarklet backend listening on a 127.0.0.1 port; effective immediately after saving)`,"config.bridge.port":`Clip bridge port (integer 1–65535; changing it needs a dsh web restart)`,"config.bridge.token":`Clip bridge token (when non-empty the bookmarklet x-clip-token header is checked; strongly recommended. Shown as ******** once set — saving it unchanged keeps it, clearing it deletes it)`,"config.bridge.tag":`Default tag for clipped notes`,"config.bridge.wiki":`Knowledge base that clips are written to (unspecified = the one marked ★ in the knowledge-base list; in multi-wiki mode you can name a specific one)`,"config.clipWiki.followDefault":`(follow whichever wiki is the default)`,"config.clipWiki.defaultSuffix":`{label} (default)`,"config.clipWiki.option":`{label} ({id}){suffix}`,"config.clipWiki.notRunning":` · not running`,"config.clipWiki.notInListWithDefault":`{current} (not in the list; saving writes to {label})`,"config.clipWiki.notInList":`{current} (not in the list)`,"config.save":`Save settings`,"config.invalidFields":`{count} fields are filled in incorrectly (see the red hint next to them); nothing was saved`,"config.noChanges":`No changes`,"config.saved":`Settings saved`,"config.saveFailed":`Save failed: {message}`}},{zh:{},en:{}},{zh:{"frame.syncRestarted":`,TW 已重启`,"frame.syncNotRestarted":`,TW 未自动重启`,"frame.rightbarTitle":`TiddlyWiki 知识库`,"frame.rightbarDescription":`在右侧边栏打开 TiddlyWiki 编辑器(与聊天并排)`,"frame.settingsSection":`TiddlyWiki 知识库`,"frame.syncTooltip":`同步知识库`,"frame.gitUnavailable":`git 仓库不可用`,"frame.branch":`分支 {branch}`,"frame.ahead":`领先 {n}`,"frame.behind":`落后 {n}`,"frame.dirty":`有 {n} 个未提交改动`,"frame.lastSync":`上次同步 {time}`,"frame.syncing":`同步进行中…`,"frame.syncDone":`同步完成:{message}`,"frame.syncFailed":`同步失败:{message}`,"frame.tabLabelDefault":`知识库`,"frame.twUnavailable":`TiddlyWiki 服务不可用`,"frame.twStarting":`TiddlyWiki 服务正在启动…`,"frame.retry":`重试`,"frame.restarting":`重启中…`,"frame.loadingWiki":`正在载入知识库「{label}」…`,"frame.statusUnreachable":`无法访问 /dsh-tiddlywiki/status`,"frame.noEditorUrl":`服务未返回编辑器地址`,"frame.statusError":`服务状态:{status}`},en:{"frame.syncRestarted":`, TiddlyWiki restarted`,"frame.syncNotRestarted":`, TiddlyWiki was not restarted automatically`,"frame.rightbarTitle":`TiddlyWiki knowledge base`,"frame.rightbarDescription":`Open the TiddlyWiki editor in the right sidebar, next to the chat`,"frame.settingsSection":`TiddlyWiki knowledge base`,"frame.syncTooltip":`Sync knowledge base`,"frame.gitUnavailable":`git repository unavailable`,"frame.branch":`branch {branch}`,"frame.ahead":`{n} ahead`,"frame.behind":`{n} behind`,"frame.dirty":`{n} uncommitted change(s)`,"frame.lastSync":`last synced {time}`,"frame.syncing":`Syncing…`,"frame.syncDone":`Sync finished: {message}`,"frame.syncFailed":`Sync failed: {message}`,"frame.tabLabelDefault":`Knowledge base`,"frame.twUnavailable":`TiddlyWiki service unavailable`,"frame.twStarting":`TiddlyWiki service is starting…`,"frame.retry":`Retry`,"frame.restarting":`Restarting…`,"frame.loadingWiki":`Loading knowledge base "{label}"…`,"frame.statusUnreachable":`Cannot reach /dsh-tiddlywiki/status`,"frame.noEditorUrl":`The service returned no editor address`,"frame.statusError":`Service status: {status}`}}],u={zh:Object.assign({},...l.map(e=>e.zh)),en:Object.assign({},...l.map(e=>e.en))},d=`dsh-tiddlywiki:lang`;function f(e){if(typeof e!=`string`)return`zh`;let t=e.trim().toLowerCase();return t.startsWith(`en`)?`en`:(t.startsWith(`zh`),`zh`)}function p(){try{let e=globalThis.localStorage?.getItem(d);return e==null?void 0:f(e)}catch{return}}let m=p()??`zh`;function h(){return m}function g(e){let t=f(e),n=t!==m;m=t;try{globalThis.localStorage?.setItem(d,t)}catch{}return n}function _(e,t){let n=u[m],r=u.zh,i=n?.[e]??r?.[e]??e;return t===void 0?i:i.replace(/\{(\w+)\}/g,(e,n)=>{let r=t[n];return r===void 0?e:String(r)})}const v=`
28
28
  /* ── sidebar entry ───────────────────────────────────────────── */
package/lib/index.js CHANGED
@@ -459,6 +459,27 @@ var GitFace = class {
459
459
  message: (r.stderr.trim() || r.stdout.trim()).slice(0, 500)
460
460
  };
461
461
  }
462
+ /**
463
+ * `origin`'s URL in `dir`, or `''` when that remote does not exist (or `dir`
464
+ * is not in a repository at all). v0.30.58: `gitTargets()` falls back to this
465
+ * when a knowledge base has no `git.remote` — the repo is the source of truth
466
+ * for "where would a push go", and unlike the config cache it is always
467
+ * current on disk (a pulled config tiddler only reaches the host on a full
468
+ * wiki start; see the wiki note 「同步口径」).
469
+ *
470
+ * Read-only: never adds a remote. `ensureRemote` is the writing half.
471
+ */
472
+ async originOf(dir) {
473
+ const r = await this.exec([
474
+ "remote",
475
+ "get-url",
476
+ "origin"
477
+ ], {
478
+ cwd: dir,
479
+ timeout: QUICK_TIMEOUT_MS
480
+ });
481
+ return r.ok ? r.stdout.trim() : "";
482
+ }
462
483
  /** Ensure `origin` points at `url` (add or set-url). */
463
484
  async ensureRemote(dir, url) {
464
485
  const cur = await this.exec([
@@ -8520,6 +8541,13 @@ async function removeSeedById(ctx, id) {
8520
8541
  * install could not sync the others from the agent at all — while the receipt
8521
8542
  * was labelled with the session's wiki.
8522
8543
  *
8544
+ * v0.30.58 (author's decision, 2026-10-09): the SCOPE is still "every wiki in a
8545
+ * repository", but "has a remote" now means **`git.remote` OR the repository's
8546
+ * own `origin`**. The tool used to skip a repo the settings-page 「同步」 button
8547
+ * synced happily, and reported「没有任何知识库配置了 git.remote(同步远端)」
8548
+ * while the repo plainly had an origin — two answers to one question. The
8549
+ * receipt now names the remote and its source.
8550
+ *
8523
8551
  * Split out of `tools.ts` in v0.28.8 — pure code move, bodies unchanged.
8524
8552
  *
8525
8553
  * @module dsh-tiddlywiki/host/tools-git
@@ -8537,12 +8565,29 @@ function gitStatusBits(s) {
8537
8565
  function repoLabel(repo) {
8538
8566
  return repo.wikis.length > 0 ? `库 ${repo.wikis.join("、")}` : "(未登记库)";
8539
8567
  }
8568
+ /**
8569
+ * Strip credentials from a remote URL before it reaches the receipt.
8570
+ *
8571
+ * v0.30.58 started printing remotes here (they used to be invisible), and a
8572
+ * `git.remote` may legitimately carry a PAT — `/admin/state` masks exactly that
8573
+ * value, so printing it raw from a tool would open a new leak path. Only the
8574
+ * `scheme://user:pass@host` form is affected; `git@host:path` has no `//`.
8575
+ */
8576
+ function safeRemote(url) {
8577
+ return url.replace(/\/\/[^/@]*@/, "//");
8578
+ }
8579
+ /** `(远端 <url>,来自 …)` — which remote a repo is about to push to, and why. */
8580
+ function remoteNote(repo) {
8581
+ const remote = repo.remote ?? "";
8582
+ if (remote.length === 0) return "";
8583
+ return `(远端 ${safeRemote(remote)}${repo.remoteSource === "origin" ? ",自动识别自仓库 origin" : ",来自 git.remote 配置"})`;
8584
+ }
8540
8585
  function renderSync(value) {
8541
8586
  const lines = [`git ${value.action}: ${value.ok ? "成功" : "失败"}`];
8542
8587
  lines.push(` ${value.message}`);
8543
8588
  if (value.skipped !== void 0 && value.skipped.length > 0) lines.push(`未同步:${value.skipped.join(";")}`);
8544
8589
  for (const repo of value.repos ?? []) {
8545
- lines.push(`- ${repoLabel(repo)} @ ${repo.root}`);
8590
+ lines.push(`- ${repoLabel(repo)} @ ${repo.root}${remoteNote(repo)}`);
8546
8591
  lines.push(` ${repo.ok ? "成功" : "失败"}: ${repo.message}`);
8547
8592
  if (repo.conflictFiles !== void 0 && repo.conflictFiles.length > 0) {
8548
8593
  lines.push(" 冲突文件(rebase 已 abort,勿自动覆盖):");
@@ -8574,6 +8619,10 @@ async function syncOneRepo(deps, group, action, message, drainFailed) {
8574
8619
  root: group.root,
8575
8620
  wikis: group.targets.map((t) => t.label),
8576
8621
  wikiIds: group.targets.map((t) => t.id),
8622
+ ...group.targets[0] !== void 0 && group.targets[0].remote.length > 0 ? {
8623
+ remote: group.targets[0].remote,
8624
+ remoteSource: group.targets[0].remoteSource === "origin" ? "origin" : "config"
8625
+ } : {},
8577
8626
  ...drainFailed ? { drainFailed: true } : {}
8578
8627
  };
8579
8628
  /**
@@ -8663,7 +8712,7 @@ function gitSyncTool(env) {
8663
8712
  const { deps, requireWiki } = env;
8664
8713
  return defineTool({
8665
8714
  name: "tiddlywiki_git_sync",
8666
- description: "对**所有开启了 git 同步**(配了 `git.remote`)的知识库仓库做同步:pull(拉取远端并 rebase 本地,冲突则 abort 并报文件)、push(推送本地提交到远端)、sync(pull → commit 本地改动 → push)。多个库共用一个仓库时**只同步一次**;每个仓库独立成败(一个仓库冲突不影响其余),回执逐仓库标明「哪个库 @ 哪个仓库」。未配 `git.remote` 或不在 git 仓库里的库会被跳过并在回执里列出。",
8715
+ description: "对**所有可同步**的知识库仓库做同步:pull(拉取远端并 rebase 本地,冲突则 abort 并报文件)、push(推送本地提交到远端)、sync(pull → commit 本地改动 → push)。可同步 = 该库配了 `git.remote`,没配就**自动用它所在仓库的 `origin`**(与设置页「同步」按钮同口径);两者都没有、或不在 git 仓库里的库会被跳过并在回执里列出。多个库共用一个仓库时**只同步一次**;每个仓库独立成败(一个仓库冲突不影响其余),回执逐仓库标明「哪个库 @ 哪个仓库」与远端来源。",
8667
8716
  parameters: {
8668
8717
  action: {
8669
8718
  type: "string",
@@ -8690,11 +8739,11 @@ function gitSyncTool(env) {
8690
8739
  */
8691
8740
  const allTargets = await deps.gitTargets?.() ?? [];
8692
8741
  const targets = allTargets.filter((t) => t.remote.trim().length > 0);
8693
- const skipped = allTargets.filter((t) => t.remote.trim().length === 0).map((t) => `${t.label}(未配 git.remote)`);
8742
+ const skipped = allTargets.filter((t) => t.remote.trim().length === 0).map((t) => `${t.label}(${t.repoRoot === void 0 ? "不在 git 仓库里" : "未配 git.remote,仓库也没有 origin"})`);
8694
8743
  if (targets.length === 0) return {
8695
8744
  action,
8696
8745
  ok: false,
8697
- message: allTargets.length === 0 ? "没有已登记的知识库,未执行任何 git 操作。" : "没有任何知识库配置了 git.remote(同步远端),未执行任何 git 操作;请在设置页给要同步的库填上远端地址。",
8746
+ message: allTargets.length === 0 ? "没有已登记的知识库,未执行任何 git 操作。" : "没有任何知识库可同步(既没配 git.remote,仓库里也没有 origin),未执行任何 git 操作;请在设置页给要同步的库填上远端地址,或给仓库加上 origin。",
8698
8747
  ...skipped.length > 0 ? { skipped } : {},
8699
8748
  repos: []
8700
8749
  };
@@ -8773,7 +8822,7 @@ function gitResolveTool(env) {
8773
8822
  const { deps } = env;
8774
8823
  return defineTool({
8775
8824
  name: "tiddlywiki_git_resolve",
8776
- description: "在 tiddlywiki_git_sync action=pull 冲突(已 abort)后,按 tiddler 二选一解决:keep-local 保留本地版本;keep-remote 用 git fetch 拉取远端并检出远端版本(需已配置 git.remote);list 仅报告当前状态。多库各有独立仓库时用 `wiki` 指明是哪一个(取 sync 回执里的库 id)。解决后建议重新 pull/sync 整合其余改动。",
8825
+ description: "在 tiddlywiki_git_sync action=pull 冲突(已 abort)后,按 tiddler 二选一解决:keep-local 保留本地版本;keep-remote 用 git fetch 拉取远端并检出远端版本(需仓库有可用远端:`git.remote` 或它自己的 `origin`);list 仅报告当前状态。多库各有独立仓库时用 `wiki` 指明是哪一个(取 sync 回执里的库 id)。解决后建议重新 pull/sync 整合其余改动。",
8777
8826
  parameters: {
8778
8827
  strategy: {
8779
8828
  type: "string",
@@ -8846,7 +8895,7 @@ function gitResolveTool(env) {
8846
8895
  if (!fetched.ok) return {
8847
8896
  ok: false,
8848
8897
  action: "keep-remote",
8849
- message: `fetch 失败(可能未配置 git.remote):${fetched.message}`
8898
+ message: `fetch 失败(仓库可能既没有 origin、也没配 git.remote):${fetched.message}`
8850
8899
  };
8851
8900
  const checked = await deps.git.checkoutFetchHead(dir, files);
8852
8901
  if (!checked.ok) return {
@@ -11028,14 +11077,34 @@ function createGitLayer(deps) {
11028
11077
  const runtime = live.runtime(entry.id);
11029
11078
  const dir = entryPath(entry);
11030
11079
  const g = (runtime?.eff() ?? deps.baseShape()).git ?? {};
11031
- const remote = (typeof g.remote === "string" && g.remote.trim().length > 0 ? g.remote : base.remote).trim();
11080
+ const configured = (typeof g.remote === "string" && g.remote.trim().length > 0 ? g.remote : base.remote).trim();
11032
11081
  const repoRoot = await repos.repoRootOf(dir);
11082
+ /**
11083
+ * No `git.remote` no longer means "skip this wiki" (v0.30.58, author
11084
+ * 2026-10-09): it means "use the repository's own `origin`".
11085
+ *
11086
+ * WHY: the settings-page 「同步」 button has always run
11087
+ * `pull/commit/push` in the folder and therefore synced any repo with an
11088
+ * origin, while this tool — the agent's only way to sync — refused the
11089
+ * very same repo and reported "没有任何知识库配置了 git.remote(同步远端)」".
11090
+ * A user who added the remote with plain `git remote add` (or who pulled a
11091
+ * config tiddler that had not reached the host's config cache yet) got two
11092
+ * different answers for one question.
11093
+ *
11094
+ * `git.remote` stays as an explicit OVERRIDE (and as the target
11095
+ * `ensureRemote` writes). Only probed when there is no config value and
11096
+ * the folder actually belongs to a repository — a wiki outside git keeps
11097
+ * costing zero extra `git` calls.
11098
+ */
11099
+ const detected = configured.length > 0 || repoRoot === void 0 ? "" : await git.originOf(dir);
11100
+ const remote = configured.length > 0 ? configured : detected;
11033
11101
  out.push({
11034
11102
  id: entry.id,
11035
11103
  label: entry.label ?? entry.id,
11036
11104
  dir,
11037
11105
  ...repoRoot !== void 0 ? { repoRoot } : {},
11038
11106
  remote,
11107
+ remoteSource: configured.length > 0 ? "config" : detected.length > 0 ? "origin" : "none",
11039
11108
  branch: typeof g.branch === "string" && g.branch.trim().length > 0 ? g.branch.trim() : base.branch,
11040
11109
  running: runtime !== void 0
11041
11110
  });
@@ -16585,6 +16654,6 @@ function apply(ctx, rawConfig = {}) {
16585
16654
  }, "dsh-tiddlywiki: host teardown");
16586
16655
  }
16587
16656
  //#endregion
16588
- export { AGENT_WRITTEN_TAG, ALL_ARTICLES_MARKER_TITLE, ALL_ARTICLES_TEXT, ALL_ARTICLES_TITLE, ANON_USERNAME, AutoCommitter, CLIP_BRIDGE_BOOKMARKLET, CLIP_BRIDGE_DEFAULT_PORT, CLIP_BRIDGE_DOC_TEXT, CLIP_BRIDGE_DOC_TITLE, CLIP_BRIDGE_DRAG_HREF, CLIP_BRIDGE_MARKER_TITLE, ClipBridge, ConfigPatchError, ConfigStore, ConfigUnreadableError, DEFAULT_NOTE_TYPE, DEFAULT_PROMPT_MODE, DEFAULT_WECHAT_ADAPTER, DEFAULT_WECHAT_COMMAND, DEFAULT_WIKI_ID, DEFAULT_WIKI_MODE, DOC_NOTE_TAG, DOC_NOTE_TEXT, DOC_NOTE_TITLE, DSH_DOCS_TAG, GitConflictStateError, GitFace, HOME_DEFAULT_TIDDLERS, HOME_INDEX_ITEMS, HOME_INDEX_MARKER_TITLE, HUMAN_EDITED_TAG, LOCATION_STATE_VERSION, MARKDOWN_PLUGIN, MASKED_SECRET, MENUBAR_THEME_MARKER_TITLE, MENUBAR_THEME_TEXT, MENUBAR_THEME_TIDDLER, MISSING_TYPE_FILTER, PATH_PREFIX, PROMPT_GOVERNANCE_BLOCKS, PROMPT_MODES, PROMPT_SECTION_NAME, PROMPT_SECTION_ORDER, READY_HARD_FACTOR, READY_POLL_MS, READY_SLOW_POLL_MS, READY_TIMEOUT_DEFAULT_MS, READY_TIMEOUT_MAX_MS, READY_TIMEOUT_MIN_MS, RENDER_BUNDLE_TEXT, RENDER_MARKER_TITLE, RENDER_PLUGIN_FILE, RENDER_PLUGIN_TITLE, RESERVED_WIKI_IDS, RepoCommitters, SEED_DEFS, SEED_MARKER_VERSION, SEND_TO_AGENT_BUNDLE_TEXT, SEND_TO_AGENT_MARKER_TITLE, SEND_TO_AGENT_PLUGIN_TITLE, SESSION_SCOPE_MAX_AGE_MS, SESSION_SCOPE_VERSION, SESSION_SUMMARY_PREFIX, SKILL_FILE_NAME, SPLIT_SKILL_DIR, SPLIT_SKILL_MARKER, STARTER_DOCS_ITEMS, STARTER_DOCS_MARKER_TITLE, TEXT_LIST_FILTER, TRASH_INDEX_TITLE, TRASH_PREFIX, TW_PROXY_PATH, TW_PROXY_PREFIX, TW_WEB_HOST_DEFAULT, TW_WEB_HOST_TIDDLER, TiddlyWebClient, TrashIndexUnavailableError, UI_STYLES_MARKER_TITLE, UI_STYLE_ITEMS, WECHAT_ADAPTERS, WECHAT_ADAPTER_FILES, WECHAT_ADAPTER_MARKER, WECHAT_DOCS_MARKER_TITLE, WECHAT_DOCS_TEXT, WECHAT_DOCS_TITLE, WECHAT_PUBLISH_BUNDLE_TEXT, WECHAT_PUBLISH_MARKER_TITLE, WECHAT_PUBLISH_PLUGIN_TITLE, WIKI_ICON_NAMES, WIKI_MODES, WIKI_REGISTRY_VERSION, WORKSPACE_FIELD, WORKSPACE_TAG_PREFIX, WechatPublishRunner, WikiFarm, WikiInstance, WikiServer, WriteConflictError, apply, applyWikiAction, assertNoConflict, assertPublicImageUrl, awaitReady, buildBinaryTiddler, buildClipTiddler, buildImageNoteTiddler, buildPromptText, buildPublishInvocation, buildVersionInvocation, buildWriteTiddler, bundledCatalog, capOutput, checkAllSeeds, checkWechatReady, cleanTiddler, clearLocationState, createMutationLock, deepMerge, defaultAdaptersDir, defaultEntry, defaultLocationStateFile, defaultRegistryFile, defaultSessionScopeFile, defaultSkillRoot, defaultWechatJobDir, defineTool, deriveWikiId, describeConflict, describePrompt, describeUnreadableConfig, docNoteText, downloadClipImage, drainThenStop, dshHomePath, ensureLanguage, ensurePlugin, ensureTiddlerTimestamps, entryLocation, entryPath, escapeInline, escapePromptBraces, expandEnvPath, findEntry, findPathConflicts, flattenTiddlerFields, flushPendingWrites, formatTiddlerDate, hashText, hostAllowed, imageExtensionForMime, inject, insertIntoSection, installSplitSkill, interpretPublishOutcome, isBinaryType, isInsidePath, isJunkTag, isPrivateAddress, isSafeCliValue, isSafeDsn, isSafeSessionId, isSafeUrl, listWikiCandidates, locationPath, maskConfigSecrets, name, needsRestartAfterSeeds, normalizeConfigPatch, normalizeEntry, normalizeFieldsArg, normalizeLocation, normalizePromptMode, normalizePromptPreview, normalizeReadyTimeoutMs, normalizeThemes, normalizeWechatConfig, normalizeWikiIcon, normalizeWikiId, normalizeWorkspaceName, openInTwEditor, parseClipPayload, parseSeedMarker, parseTiddlerDate, pathComparisonKey, pickImageMime, pinLanguageTiddler, proxyBaseFor, pruneScopes, readActiveThemeName, readBundledSkillText, readLocationState, readRegistry, readSeedMarker, readSessionScopes, readWikiInfo, readyHardTimeoutMs, registerAdminRoutes, registerRoutes, registerTiddlywikiTools, removeSeedById, removeWiki, resolveAgentScope, resolveClipTitle, resolveNodeExecutable, resolveTwRoot, resolveWikiRoot, runAllSeeds, runSeedById, sanitizeTwFragment, scanAdapterDir, scopeBanner, seedAllArticles, seedClipBridge, seedDocNote, seedHomeIndex, seedMenubarTheme, seedRenderRoute, seedSendToAgent, seedStarterDocs, seedUiStyles, seedWechatDocs, seedWechatPublish, setSessionScope, singleEntryRegistry, stoppedWikiFromRequest, stoppedWikiMessage, stripMaskedSecrets, switchWiki, tailOf, targetRuntimeFor, tiddlywikiToolSummary, toIsoDateString, toolSignatureLines, unseedClipBridge, unseedWechatDocs, unseedWechatPublish, upsertWiki, validateRegistry, waitForFileWrite, wikiIdFromRequest, withScopeBanner, workspaceMarkFromCwd, workspaceNameFromCwd, workspaceTagName, writeLocationState, writeRegistry, writeSeedMarker, writeSessionScopes, writeSessionSummary, writeWikiInfo };
16657
+ export { AGENT_WRITTEN_TAG, ALL_ARTICLES_MARKER_TITLE, ALL_ARTICLES_TEXT, ALL_ARTICLES_TITLE, ANON_USERNAME, AutoCommitter, CLIP_BRIDGE_BOOKMARKLET, CLIP_BRIDGE_DEFAULT_PORT, CLIP_BRIDGE_DOC_TEXT, CLIP_BRIDGE_DOC_TITLE, CLIP_BRIDGE_DRAG_HREF, CLIP_BRIDGE_MARKER_TITLE, ClipBridge, ConfigPatchError, ConfigStore, ConfigUnreadableError, DEFAULT_NOTE_TYPE, DEFAULT_PROMPT_MODE, DEFAULT_WECHAT_ADAPTER, DEFAULT_WECHAT_COMMAND, DEFAULT_WIKI_ID, DEFAULT_WIKI_MODE, DOC_NOTE_TAG, DOC_NOTE_TEXT, DOC_NOTE_TITLE, DSH_DOCS_TAG, GitConflictStateError, GitFace, HOME_DEFAULT_TIDDLERS, HOME_INDEX_ITEMS, HOME_INDEX_MARKER_TITLE, HUMAN_EDITED_TAG, LOCATION_STATE_VERSION, MARKDOWN_PLUGIN, MASKED_SECRET, MENUBAR_THEME_MARKER_TITLE, MENUBAR_THEME_TEXT, MENUBAR_THEME_TIDDLER, MISSING_TYPE_FILTER, PATH_PREFIX, PROMPT_GOVERNANCE_BLOCKS, PROMPT_MODES, PROMPT_SECTION_NAME, PROMPT_SECTION_ORDER, READY_HARD_FACTOR, READY_POLL_MS, READY_SLOW_POLL_MS, READY_TIMEOUT_DEFAULT_MS, READY_TIMEOUT_MAX_MS, READY_TIMEOUT_MIN_MS, RENDER_BUNDLE_TEXT, RENDER_MARKER_TITLE, RENDER_PLUGIN_FILE, RENDER_PLUGIN_TITLE, RESERVED_WIKI_IDS, RepoCommitters, SEED_DEFS, SEED_MARKER_VERSION, SEND_TO_AGENT_BUNDLE_TEXT, SEND_TO_AGENT_MARKER_TITLE, SEND_TO_AGENT_PLUGIN_TITLE, SESSION_SCOPE_MAX_AGE_MS, SESSION_SCOPE_VERSION, SESSION_SUMMARY_PREFIX, SKILL_FILE_NAME, SPLIT_SKILL_DIR, SPLIT_SKILL_MARKER, STARTER_DOCS_ITEMS, STARTER_DOCS_MARKER_TITLE, TEXT_LIST_FILTER, TRASH_INDEX_TITLE, TRASH_PREFIX, TW_PROXY_PATH, TW_PROXY_PREFIX, TW_WEB_HOST_DEFAULT, TW_WEB_HOST_TIDDLER, TiddlyWebClient, TrashIndexUnavailableError, UI_STYLES_MARKER_TITLE, UI_STYLE_ITEMS, WECHAT_ADAPTERS, WECHAT_ADAPTER_FILES, WECHAT_ADAPTER_MARKER, WECHAT_DOCS_MARKER_TITLE, WECHAT_DOCS_TEXT, WECHAT_DOCS_TITLE, WECHAT_PUBLISH_BUNDLE_TEXT, WECHAT_PUBLISH_MARKER_TITLE, WECHAT_PUBLISH_PLUGIN_TITLE, WIKI_ICON_NAMES, WIKI_MODES, WIKI_REGISTRY_VERSION, WORKSPACE_FIELD, WORKSPACE_TAG_PREFIX, WechatPublishRunner, WikiFarm, WikiInstance, WikiServer, WriteConflictError, apply, applyWikiAction, assertNoConflict, assertPublicImageUrl, awaitReady, buildBinaryTiddler, buildClipTiddler, buildImageNoteTiddler, buildPromptText, buildPublishInvocation, buildVersionInvocation, buildWriteTiddler, bundledCatalog, capOutput, checkAllSeeds, checkWechatReady, cleanTiddler, clearLocationState, createGitLayer, createMutationLock, deepMerge, defaultAdaptersDir, defaultEntry, defaultLocationStateFile, defaultRegistryFile, defaultSessionScopeFile, defaultSkillRoot, defaultWechatJobDir, defineTool, deriveWikiId, describeConflict, describePrompt, describeUnreadableConfig, docNoteText, downloadClipImage, drainThenStop, dshHomePath, ensureLanguage, ensurePlugin, ensureTiddlerTimestamps, entryLocation, entryPath, escapeInline, escapePromptBraces, expandEnvPath, findEntry, findPathConflicts, flattenTiddlerFields, flushPendingWrites, formatTiddlerDate, hashText, hostAllowed, imageExtensionForMime, inject, insertIntoSection, installSplitSkill, interpretPublishOutcome, isBinaryType, isInsidePath, isJunkTag, isPrivateAddress, isSafeCliValue, isSafeDsn, isSafeSessionId, isSafeUrl, listWikiCandidates, locationPath, maskConfigSecrets, name, needsRestartAfterSeeds, normalizeConfigPatch, normalizeEntry, normalizeFieldsArg, normalizeLocation, normalizePromptMode, normalizePromptPreview, normalizeReadyTimeoutMs, normalizeThemes, normalizeWechatConfig, normalizeWikiIcon, normalizeWikiId, normalizeWorkspaceName, openInTwEditor, parseClipPayload, parseSeedMarker, parseTiddlerDate, pathComparisonKey, pickImageMime, pinLanguageTiddler, proxyBaseFor, pruneScopes, readActiveThemeName, readBundledSkillText, readLocationState, readRegistry, readSeedMarker, readSessionScopes, readWikiInfo, readyHardTimeoutMs, registerAdminRoutes, registerRoutes, registerTiddlywikiTools, removeSeedById, removeWiki, resolveAgentScope, resolveClipTitle, resolveNodeExecutable, resolveTwRoot, resolveWikiRoot, runAllSeeds, runSeedById, sanitizeTwFragment, scanAdapterDir, scopeBanner, seedAllArticles, seedClipBridge, seedDocNote, seedHomeIndex, seedMenubarTheme, seedRenderRoute, seedSendToAgent, seedStarterDocs, seedUiStyles, seedWechatDocs, seedWechatPublish, setSessionScope, singleEntryRegistry, stoppedWikiFromRequest, stoppedWikiMessage, stripMaskedSecrets, switchWiki, tailOf, targetRuntimeFor, tiddlywikiToolSummary, toIsoDateString, toolSignatureLines, unseedClipBridge, unseedWechatDocs, unseedWechatPublish, upsertWiki, validateRegistry, waitForFileWrite, wikiIdFromRequest, withScopeBanner, workspaceMarkFromCwd, workspaceNameFromCwd, workspaceTagName, writeLocationState, writeRegistry, writeSeedMarker, writeSessionScopes, writeSessionSummary, writeWikiInfo };
16589
16658
 
16590
16659
  //# sourceMappingURL=index.js.map