dsh-tiddlywiki 0.8.0 → 0.11.0

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/README.md CHANGED
@@ -7,6 +7,8 @@
7
7
  [![GitHub](https://img.shields.io/github/stars/bbqisbbq/dsh-tiddlywiki)](https://github.com/bbqisbbq/dsh-tiddlywiki)
8
8
  [![Awesome DSH Plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
9
9
 
10
+ > 📚 **文档索引**:[🧩 初始化(一次性预置)](#🧩-初始化一次性预置) · [docs/seed-initialization.md](docs/seed-initialization.md)(统一 seed 注册表 / ONE-SHOT 与 force 语义 / 后台 API / 重新生成内置常量)
11
+
10
12
  等 Agent 干活的时候,人常是干坐着的——想随手写点什么,又不想切来切去。TiddlyWiki 单文件、纯文本、wiki 语法、自带 git 同步,天生适合**随手写点小东西**。于是把它做成 DSH 原生插件:不用离开当前界面,聊天区右下角就有快速笔记;要正经编辑,弹出 TW 原生编辑器;写下的内容自动进 git,既是知识库也是备份。
11
13
 
12
14
  ---
@@ -16,11 +18,12 @@
16
18
  | 能力 | 说明 |
17
19
  |---|---|
18
20
  | 🤖 **Agent 工具** | `tiddlywiki_search` / `get` / `put` / `batch_put` / `rename` / `delete` / `recent` / `list_tags` / `git_sync` / `git_resolve` 十个工具:检索、读写、批量、重命名、删除、git 同步与冲突解决 |
21
+ | 📤 **一键发送给 Agent** | TW 笔记工具栏「**发送给 Agent**」按钮(首次启动自动写入 wiki):把当前笔记作为消息注入所选 dsh 会话(按工作区分组选择,可新建工作区/会话,**可选「工作模式」= Agent 预设**,如 默认/cordis/blade,已有会话显示其当前模式);**消息自动附加待办说明**——告知 Agent 这是用户提前编辑在 wiki 中的待办事项,不清楚应主动提问 |
19
22
  | 🧭 **内嵌编辑器** | 侧边栏「TiddlyWiki」入口 → 中央列内嵌完整 TW 5 编辑器(**同源代理**,经 DSH origin 访问,Tailscale/内网/域名/HTTPS 均可用) |
20
23
  | 🌗 **跟随 DSH 主题** | 嵌入式 TW(中央面板 +「在 TW 中编辑」弹窗)**自适应 DSH 深浅主题**:暗色自动切深色 palette、浅色恢复原 palette;**纯内存切换,不写回 wiki、不进 git**;设置页可关可换深色 palette |
21
24
  | 📝 **快速笔记** | 右下角「知识库」悬浮按钮 → 快速笔记卡片:**CodeMirror 6** Markdown 编辑器(语法高亮 + 撤销/重做)、文件上传、多选/自动补全 tag、**草稿自动保存(刷新不丢)**、**「🕘 最近」一键载入旧笔记**,Ctrl+Enter 保存;可整体隐藏 |
22
25
  | 🔄 **一键同步** | 「知识库」按钮 →「🔁 同步」:pull → commit → push;FAB 上的状态点实时反映 git 状态(已同步/待提交/可更新/离线) |
23
- | ⚙️ **设置页** | DSH 设置 →「TiddlyWiki 知识库」:插件/主题/语言管理与运行配置,应用后自动重启 TW |
26
+ | ⚙️ **设置页** | DSH 设置 →「TiddlyWiki 知识库」:插件/主题/语言管理与运行配置,应用后自动重启 TW;**「初始化」区块**列出所有一次性预置项(说明笔记/发送按钮/首页/同源代理基址)的实时状态,可随时手动**重新初始化** |
24
27
  | 🛡 **零摩擦生命周期** | 随 dsh 自动启停;TW 子进程崩溃自动重启(退避);端口/目录/首次 git init 全自动 |
25
28
  | 💾 **数据即备份** | wiki 文件夹本身就是一个 git 仓库;自动 commit(60s 防抖,可关),配置随 dsh-market 迁移 |
26
29
 
@@ -30,6 +33,9 @@
30
33
 
31
34
  > 最近几个主要版本的一句话更新记录(完整变更见 git log / Releases)。
32
35
 
36
+ - **v0.11.0**(2026-09-04):TW「**发送给 Agent**」选择器新增**「工作模式」(Agent 预设)**——弹层顶部可选择会话挂载的 Agent 预设(如 默认/cordis/blade,来自 DSH `agentPresets`),**新建工作区/会话并发送**时以所选模式创建(`/agent/create` 新增 `mode` 参数 → `sessionController.create(agentPreset)`);已有会话显示其当前模式(`🧭` 徽标)。新增 `GET /agent/modes` 返回可用模式清单 + 部署默认。对旧宿主**向后兼容**:模式接口缺失时选择器降级为默认模式,功能不受影响。
37
+ - **v0.10.0**(2026-09-04):**统一 seed 注册表**——把「与 dsh 联动、需要 wiki 预置」的全部一次性项收进 `SEED_DEFS`(说明笔记、发送给 Agent 按钮、**首页「所有标签/标签笔记」**、**TW 前端同源代理基址**),首次启动只写缺失、不覆盖用户内容;设置页新增「**初始化**」区块:列出每项实时状态 + 手动「**重新初始化**」(force 重写内置内容,含缺失项补齐与 TW 前端基址修复)。新装用户开箱即有**首页**(四象限待办 + 标签统计 + Agent 区块),不再手工放 tiddler。配套文档:[🧩 初始化小节](#🧩-初始化一次性预置) + [docs/seed-initialization.md](docs/seed-initialization.md)。
38
+ - **v0.9.0**(2026-09-04):TW「**发送给 Agent**」按钮随插件**首次启动自动写入 wiki**(ONE-SHOT:只写一次、用户可删可改不复活)——新装用户开箱即用,无需手工往 wiki 塞插件 bundle;消息格式更新:**去掉 `【TiddlyWiki 笔记一键发送】` 前缀**,并**附加待办说明**告知 Agent 这是用户提前编辑在 wiki 中的待办事项、不清楚应主动提问。
33
39
  - **v0.8.0**(2026-09-04):嵌入式 TW **跟随 DSH 深浅主题**——暗色自动切深色 palette(默认 CupertinoDark)、浅色恢复原 palette,纯内存切换**不写回 wiki、不进 git**,设置页可开关/换深色 palette。
34
40
  - **v0.7.2**(2026-09-03):「一键发送到 dsh」改为**工作区优先**——新增 POST `/agent/create` 按 cwd 落入真实 Workspace,新会话不再丢到「未分组」;自动给新笔记补打 `agent-written` 标签并在首页分区展示。
35
41
  - **v0.7.0**(2026-09-03):TW 里**一键把当前笔记发送到 dsh 会话**(`/agent/sessions` + `/agent/send` + TW 按钮,可配置开关)。
@@ -53,7 +59,7 @@ dsh plugin --profile web add github:bbqisbbq/dsh-tiddlywiki
53
59
  dsh plugin --profile web add link:/path/to/your/dsh-tiddlywiki
54
60
  ```
55
61
 
56
- 首次启动会自动完成三件事:**初始化 wiki 目录**、**`git init` 并提交基线**、向 wiki **写入一次**「dsh-tiddlywiki 插件说明」笔记(tag `docs`)。
62
+ 首次启动会自动完成:**初始化 wiki 目录**、**`git init` 并提交基线**、把「与 dsh 联动、需要 wiki 预置」的一次性项写入 wiki(**统一 seed 注册表**,见「初始化」):向 wiki **写入一次**「dsh-tiddlywiki 插件说明」笔记(tag `docs`)、**写入一次**「发送给 Agent」TW 按钮插件(`$:/plugins/dsh/send-to-agent`)、**写入首页**「所有标签 / 标签笔记」(四象限待办 + 标签统计 + Agent 区块)、把 `$:/config/tiddlyweb/host` 指向同源代理。它们都是 ONE-SHOT:只写缺失、不覆盖你的改动、删掉重启也不会自动恢复——需要时可在设置页「**初始化**」区块手动「重新初始化」(force 重写内置内容)。
57
63
 
58
64
  ---
59
65
 
@@ -65,6 +71,7 @@ dsh plugin --profile web add link:/path/to/your/dsh-tiddlywiki
65
71
  4. **正经排版**:在笔记里点「✏️ 在 TW 中编辑」,弹出 TW 原生编辑器小窗继续写;想接着改旧笔记,点「🕘 最近」一键载入。
66
72
  5. **收工同步**:点「知识库」→「🔁 同步」,一键 pull → commit → push,把今天的记录推到远端备份。
67
73
  6. **让 Agent 参与**:直接在聊天里说「把刚才的会议纪要存进知识库」——Agent 会用 `tiddlywiki_*` 工具读写。
74
+ 7. **一键把笔记发给 Agent**:在 TW 里打开任意笔记,点工具栏「**发送给 Agent**」→ 选择目标会话(按工作区分组)→ 笔记作为消息注入该会话;消息自带待办说明,Agent 不清楚会主动提问。
68
75
 
69
76
  > 想直接看 Agent 侧完整能力?跳到 [📖 使用指南](#-使用指南)。
70
77
 
@@ -100,6 +107,13 @@ dsh plugin --profile web add link:/path/to/your/dsh-tiddlywiki
100
107
 
101
108
  ### 🧑‍💻 给人:界面操作
102
109
 
110
+ **📤 一键发送给 Agent** — 插件首次启动会把「发送给 Agent」按钮插件写入 wiki(`$:/plugins/dsh/send-to-agent`,ONE-SHOT)。在 TW 里打开任意笔记,工具栏点「**发送给 Agent**」:
111
+ - 弹层顶部有**「工作模式」(Agent 预设)**选择器(v0.11.0):列出 DSH 全部可用预设(`/agent/modes`,标记部署默认),**新建工作区/会话并发送**时按所选模式创建(`/agent/create` 传 `mode` → `sessionController.create(agentPreset)`);已有会话旁显示其当前模式徽标(🧭);
112
+ - 弹层**按工作区(cwd)分组**列出可见会话,点选即把当前笔记作为消息注入(`sessionController.prompt`,与聊天输入同 API);
113
+ - 也可以**新建工作区/会话**再发送(`/agent/create` 按 cwd 落入真实 Workspace,会话不落「未分组」);
114
+ - 消息格式为 `《标题》` + 标签/类型 + **待办说明**(告知 Agent 这是用户提前编辑在 wiki 中的待办事项、不清楚应主动提问)+ 正文;
115
+ - 开关与 token 见设置页「常规配置」/配置项 `ui.sendToAgent`(默认开)。
116
+
103
117
  **🧭 中央列编辑器** — 侧边栏「TiddlyWiki」按钮开关中央编辑器面板(**同源代理**:iframe 指向 `<DSH origin>/dsh-tiddlywiki/tw/`,由 DSH 转发到回环上的 TW 服务),完整 TW 5 编辑器。
104
118
 
105
119
  **🌗 跟随 DSH 主题** — 中央面板与「✏️ 在 TW 中编辑」弹窗里的 TW **自动跟随 DSH 的深浅主题**(v0.8.0 起,`ui.followDshTheme`,默认开):
@@ -134,6 +148,7 @@ dsh plugin --profile web add link:/path/to/your/dsh-tiddlywiki
134
148
  | 插件管理 | 自带官方插件勾选(可搜索)→ 应用并自动重启 TW |
135
149
  | 主题管理 | 自带主题**多选加载 + 单选活动** → 应用并自动重启 TW |
136
150
  | 语言管理 | 自带官方语言包勾选(含 zh-Hans 简体)→ 应用并自动重启 TW |
151
+ | **初始化** | 一次性预置项实时状态(说明笔记/发送按钮/首页/同源代理基址)→ 每项「重新初始化」+ 「全部重新初始化」 |
137
152
 
138
153
  - 配置写入 wiki 内的 `$:/plugins/dsh-tiddlywiki/config` tiddler(JSON),随 git 同步,作为 **cordis `config:` 块之上的覆盖层**(tiddler 优先)。
139
154
  - git 类配置修改后**重启 dsh web 生效**(bootstrap 时读取)。
@@ -146,6 +161,25 @@ dsh plugin --profile web add link:/path/to/your/dsh-tiddlywiki
146
161
 
147
162
  样式为空壳的主题(如 tight-heavier)会自动从清单里隐藏,避免选了没效果。
148
163
 
164
+ ### 🧩 初始化(一次性预置)
165
+
166
+ 插件与 dsh 联动、需要在 wiki 里预置 tiddler/配置的项,全部收在**统一 seed 注册表**中(`SEED_DEFS`)。首次启动自动执行(**非 force**:只写缺失、绝不覆盖你的改动);设置页「**初始化**」区块可随时查看每项实时状态并手动「**重新初始化**」(force:重写内置内容)。
167
+
168
+ | seed id | 预置内容 | 与 dsh 的联动 | ONE-SHOT 语义 |
169
+ |---|---|---|---|
170
+ | `doc-note` | 「dsh-tiddlywiki 插件说明」笔记(tag `docs`) | 新手引导 | marker 门控:只写一次,删掉重启不复活 |
171
+ | `send-to-agent` | 「发送给 Agent」TW 按钮插件 `$:/plugins/dsh/send-to-agent` | 一键把笔记注入 dsh 会话的入口 | 同上 |
172
+ | `home-index` | 首页「所有标签 / 标签笔记」 | **系统提示承诺的首页**:四象限待办 + 标签统计 + **Agent 区块**(纯 Agent / Agent+人工 分区,主标签列表排除 `agent-written`) | 同上 |
173
+ | `tw-web-host` | `$:/config/tiddlyweb/host` → `/dsh-tiddlywiki/tw/` | 嵌入式 TW 编辑器的同源代理 API 基址(远程访问模式前提) | 非 force = 缺失/旧默认才写、用户自定义保留;force = 强制写回代理基址 |
174
+
175
+ **用法**:
176
+ - **查看状态**:设置页 →「初始化」→ 每项一个状态点(✓ 已就绪 / ✗ 缺失)+ 说明。
177
+ - **重新初始化单项**:点该项「重新初始化」——force 重写内置内容(覆盖你对该 tiddler 的改动)并重记 marker,用于「我把首页改坏了想恢复模板」「按钮被我删了想补回」等场景。
178
+ - **全部重新初始化**:底部「全部重新初始化」一键重跑全部四项。
179
+ - **为什么删掉重启不会自动恢复**:ONE-SHOT marker(`$:/plugins/dsh-tiddlywiki/seed-*`)记录「已提供过」,之后归用户所有——想恢复请手动「重新初始化」,而非依赖重启。
180
+
181
+ 详细设计(后台 API、force 语义、如何重新生成内置常量)见 [docs/seed-initialization.md](docs/seed-initialization.md)。
182
+
149
183
  ### 🌐 界面语言(中文)
150
184
 
151
185
  TW 的界面语言由**语言插件**决定,不是某个配置字符串。tiddlywiki 包自带全部官方语言包(`node_modules/tiddlywiki/languages/`,含 zh-Hans 简体、zh-CN、zh-Hant、en-GB、ja-JP…),本插件离线启用即可:
@@ -225,6 +259,14 @@ npm run typecheck # tsc --noEmit
225
259
  npm run build # clean + host tsdown + client tsdown + wrap
226
260
  npm run selftest # headless:spawn TW → REST 读写 → git → 退出回收
227
261
  node scripts/verify-theme-browser.mjs # 可选:真实浏览器验证「跟随 DSH 主题」的 palette 切换与不持久化(需 puppeteer-core + Chrome,缺则 SKIP)
262
+ node scripts/verify-seed-send-to-agent.mjs # 可选:全新 wiki 上 E2E 验证「发送给 Agent」按钮 seed(bundle 写入/幂等/marker)
263
+ node scripts/verify-seeds-admin.mjs # 可选:全新 wiki + 真实 HTTP 验证 /admin/seeds 状态与 /admin/seeds/run(单跑/全跑/force/unknown id)
264
+
265
+ # 改了 wiki 里的 $:/plugins/dsh/send-to-agent bundle 后,重新生成内置常量:
266
+ node scripts/gen-seed-send-to-agent.mjs '<wiki>/tiddlers/$__plugins_dsh_send-to-agent.json' src/host/seed-send-to-agent.ts && npm run build
267
+
268
+ # 改了 wiki 首页「所有标签 / 标签笔记」后,重新生成内置 seed 常量:
269
+ node scripts/gen-seed-home.mjs '<wiki>/tiddlers/所有标签.tid' '<wiki>/tiddlers/标签笔记.tid' src/host/seed-home.ts && npm run build
228
270
  ```
229
271
 
230
272
  **产物约定**(发布必守):`lib/` 内**零** `@deepseek-ai` 运行时 import(`src/sdk.ts` 自实现 `defineTool` / `dshHomePath`,类型用结构接口)。发布前用 `grep -r "@deepseek-ai" lib/` 验证。
@@ -246,6 +288,10 @@ node scripts/verify-theme-browser.mjs # 可选:真实浏览器验证「跟
246
288
  | `/dsh-tiddlywiki/sync` | POST | 一键 pull → commit → push |
247
289
  | `/dsh-tiddlywiki/upload` | POST | 文件上传到 `files/`(原始 body + `X-Filename`) |
248
290
  | `/dsh-tiddlywiki/restart` | POST | 重启 TW 子进程 |
291
+ | `/dsh-tiddlywiki/agent/sessions` | GET | 可见会话列表(TW「发送给 Agent」选择器;含每会话 `agentPreset` 模式徽标) |
292
+ | `/dsh-tiddlywiki/agent/modes` | GET | 可用「工作模式」(Agent 预设)清单 + 部署默认(v0.11.0) |
293
+ | `/dsh-tiddlywiki/agent/send` | POST | 把笔记作为消息注入一个会话(`sessionController.prompt`) |
294
+ | `/dsh-tiddlywiki/agent/create` | POST | 按 cwd 新建/复用 Workspace + 会话(工作区优先);可选 `mode`(Agent 预设) |
249
295
  | `/dsh-tiddlywiki/api/*` | any | 透传到 TW 服务(JSON) |
250
296
  | `/dsh-tiddlywiki/tw/*` | any | **同源 TW 代理**:整个 TW 前端(index + `/files/*` + TiddlyWeb API)→ 回环 TW(v0.6.0,远程访问核心) |
251
297
 
@@ -265,6 +311,8 @@ npm publish
265
311
  ## 🗂 项目结构
266
312
 
267
313
  ```
314
+ docs/
315
+ └── seed-initialization.md # 统一 seed 注册表详解:ONE-SHOT/force 语义、后台 API、设置页操作、重新生成内置常量
268
316
  src/
269
317
  ├── index.ts # host 入口:装配 WikiServer/路由/工具/prompt/自动 commit
270
318
  ├── sdk.ts # 自包含 defineTool + dshHomePath(零 @deepseek-ai 运行时依赖)
@@ -275,7 +323,10 @@ src/
275
323
  │ ├── routes.ts # /status /note /edit /tags /sync /upload /restart /api/* /tw/* 路由
276
324
  │ ├── admin.ts # 设置页后台:tiddlywiki.info 读写 + 目录枚举 + /admin/* 路由
277
325
  │ ├── config.ts # ConfigStore:cordis config 基底 + 配置 tiddler 覆盖层
278
- │ ├── seed-notes.ts # 首次启动一次性写入「插件说明」笔记
326
+ │ ├── seed-notes.ts # seed: 首次一次性写入「插件说明」笔记(支持 force)
327
+ │ ├── seed-send-to-agent.ts # seed: 首次一次性写入「发送给 Agent」TW 按钮插件(bundle 内嵌常量,支持 force)
328
+ │ ├── seed-home.ts # seed: 首页「所有标签/标签笔记」(生成自 wiki .tid,支持 force)
329
+ │ ├── seeds.ts # 统一 seed 注册表:doc-note / send-to-agent / home-index / tw-web-host,check + run(force)
279
330
  │ └── tools.ts # 5 个工具(列表式注册,可扩展)
280
331
  └── client/
281
332
  ├── index.ts # client 入口(纯 DOM + settings.section 注册,永不 throw)
@@ -296,7 +347,7 @@ src/
296
347
 
297
348
  - **GitHub(公开)**:https://github.com/bbqisbbq/dsh-tiddlywiki
298
349
  - **npm**:`dsh-tiddlywiki`(https://www.npmjs.com/package/dsh-tiddlywiki)
299
- - **说明笔记**:插件在**首次启动**时向 wiki **写入一次**「`dsh-tiddlywiki 插件说明`」笔记(tag `docs`)——只写一次、手动编辑不被覆盖、**删除后重启不会自动恢复**(一次性标记;清空 wiki 重装会再写入)。
350
+ - **说明笔记**:插件在**首次启动**时把「与 dsh 联动、需要 wiki 预置」的一次性项写入 wiki(**统一 seed 注册表**,见 [🧩 初始化](#🧩-初始化一次性预置)):`dsh-tiddlywiki 插件说明` 笔记(tag `docs`)、「发送给 Agent」按钮插件(`$:/plugins/dsh/send-to-agent`)、首页「所有标签/标签笔记」、`$:/config/tiddlyweb/host` 同源代理基址——都只写一次、手动编辑不被覆盖、**删除后重启不会自动恢复**(一次性标记;清空 wiki 重装会再写入;设置页「初始化」可手动重新初始化)。
300
351
 
301
352
  **可被检索的标准字段**(为 GitHub / npm / 搜索引擎发现):
302
353
 
@@ -0,0 +1,184 @@
1
+ # 统一 seed 注册表 & 后台「重新初始化」
2
+
3
+ > 本文档说明 dsh-tiddlywiki **v0.10.0** 引入的「一次性预置」机制:哪些东西需要随插件初始化写入 wiki、它们与 dsh 的联动关系、ONE-SHOT / force 语义、后台 API 与设置页操作,以及开发时如何重新生成内置常量。
4
+
5
+ ---
6
+
7
+ ## 1. 背景:为什么需要 seed
8
+
9
+ 插件与 dsh 有多个联动功能,**前提是 wiki 里存在特定 tiddler / 配置**。这些项历史上靠手工往 wiki 里塞(只存在于个别 wiki 的 git 历史中),**初次安装插件的新用户拿不到**,导致联动功能残缺:
10
+
11
+ | 联动功能 | 依赖的 wiki 预置 | 缺失时的表现 |
12
+ |---|---|---|
13
+ | 一键发送给 Agent | `$:/plugins/dsh/send-to-agent` 按钮插件 | TW 工具栏没有「发送给 Agent」按钮,后端路由在但无入口 |
14
+ | 首页(Agent 区块 / 标签统计) | 「所有标签」「标签笔记」两个 tiddler | 系统提示承诺的「首页把 Agent 笔记单独列在 Agent 区块、主标签列表只统计人类笔记」完全不存在 |
15
+ | 嵌入式 TW 编辑器 | `$:/config/tiddlyweb/host` 指向同源代理 | iframe 里的 TW 前端 API 基址指向错误的 origin,编辑/保存失效 |
16
+ | 新手引导 | 「dsh-tiddlywiki 插件说明」笔记 | 新用户没有入门说明 |
17
+
18
+ v0.10.0 把**全部**这类项收进一个**统一 seed 注册表**,随插件首次启动自动预置;并给后台加「重新初始化」能力,让缺失/被改坏的项可以随时由用户手动恢复。
19
+
20
+ ---
21
+
22
+ ## 2. 统一 seed 注册表(`SEED_DEFS`)
23
+
24
+ 源码:`src/host/seeds.ts`。每一项是一个 `SeedDef`:
25
+
26
+ ```ts
27
+ interface SeedDef {
28
+ id: string // doc-note / send-to-agent / home-index / tw-web-host
29
+ title: string // 设置页显示名
30
+ description: string // 说明
31
+ check(ctx): Promise<SeedStatus> // 当前状态(present / detail),供 UI 展示
32
+ run(ctx, force): Promise<SeedRunResult> // 执行;force=false 保持 ONE-SHOT,force=true 重写内置内容
33
+ }
34
+ ```
35
+
36
+ | id | 实现 | 写什么 | marker |
37
+ |---|---|---|---|
38
+ | `doc-note` | `seed-notes.ts` → `seedDocNote` | 「dsh-tiddlywiki 插件说明」(tag `docs`) | `$:/plugins/dsh-tiddlywiki/seed-doc-note` |
39
+ | `send-to-agent` | `seed-send-to-agent.ts` → `seedSendToAgent` | `$:/plugins/dsh/send-to-agent` 按钮 bundle(`application/json`) | `$:/plugins/dsh-tiddlywiki/seed-send-to-agent` |
40
+ | `home-index` | `seed-home.ts` → `seedHomeIndex` | 「所有标签」+「标签笔记」(tag `索引`) | `$:/plugins/dsh-tiddlywiki/seed-home-index` |
41
+ | `tw-web-host` | `seeds.ts` 内联 | `$:/config/tiddlyweb/host` → `/dsh-tiddlywiki/tw/` | 无 marker(ensure 型,见 §4) |
42
+
43
+ ### 统一入口(`src/index.ts` 导出)
44
+
45
+ - `runAllSeeds(ctx)` —— **启动路径**:四项全部非 force 执行(只写缺失)。启动时序在 `configStore.load()` 之后、`bootstrapGit()` 之前,保证 seed 写入的内容进入首次 git 提交。
46
+ - `checkAllSeeds(ctx)` —— 返回每项当前状态数组(设置页「初始化」区块数据源)。
47
+ - `runSeedById(ctx, id?, force)` —— 单跑(`id` 指定)或全跑(`id` 为 `undefined`);`force` 为手动「重新初始化」;未知 `id` 返回显式错误结果而非抛异常。
48
+
49
+ ---
50
+
51
+ ## 3. ONE-SHOT 语义(非 force)
52
+
53
+ **核心原则:seed 只提供一次,之后内容归用户所有。**
54
+
55
+ - **marker 门控**(doc-note / send-to-agent / home-index):首次执行写入内容 + 写 marker;此后只要 marker 在,seed 就**不再写**(无论目标 tiddler 是否存在)。
56
+ - **用户删除 tiddler 后,重启不会复活**(marker 仍在)——这是刻意的:用户删掉 = 不想要。
57
+ - **用户编辑过 tiddler,永远不会被启动 seed 覆盖**(marker 在,seed 根本不触碰)。
58
+ - **升级兼容**:老 wiki 已有这些 tiddler(旧版本手工放的)时,首次执行只补写 marker、不覆盖现有内容,从这一刻起同样归用户所有。
59
+
60
+ **tw-web-host 例外**:它不是 marker 型,而是 **ensure 型**——非 force 时仅在 **tiddler 缺失**或**仍是旧默认值**(`$protocol$//$host$/`)时写入代理路径;**用户自定义的其它基址会被保留**(例如确实在专属域名上暴露 TW 的场景)。
61
+
62
+ ---
63
+
64
+ ## 4. force 语义(手动「重新初始化」)
65
+
66
+ 设置页「初始化」区块的「重新初始化」按钮(以及后台 `POST /admin/seeds/run` 带 `force: true`)触发:
67
+
68
+ - **无视 marker**:即使已提供过,也会重新执行;
69
+ - **覆盖现有内容**:把目标 tiddler 重写为内置内容(会覆盖你对该 tiddler 的改动——按钮点击前有提示文案);
70
+ - **重记 marker**:执行后 marker 重新记录,回到「已提供」状态。
71
+
72
+ 典型使用场景:
73
+
74
+ - 「我把首页改坏了,想恢复成模板」→ `home-index` 重新初始化;
75
+ - 「发送给 Agent 按钮被我删了 / 改坏了」→ `send-to-agent` 重新初始化;
76
+ - 「TW 编辑器打不开,疑似 `$:/config/tiddlyweb/host` 被改错」→ `tw-web-host` 重新初始化(force 强制写回代理基址);
77
+ - 「刚装完发现 wiki 里缺了说明/首页」→ 对应项重新初始化或「全部重新初始化」。
78
+
79
+ > ⚠️ force 会**覆盖你对目标 tiddler 的编辑**,仅用于明确想要恢复内置内容时。日常「补缺失」不需要 force——启动流程与「全部重新初始化」之外的场景由 ONE-SHOT 语义兜底。
80
+
81
+ ---
82
+
83
+ ## 5. 后台 API
84
+
85
+ 挂载在插件管理路由下(`ROUTE_PREFIX = /dsh-tiddlywiki`),与设置页同源。
86
+
87
+ ### `GET /dsh-tiddlywiki/admin/seeds`
88
+
89
+ 返回每项实时状态(`checkAllSeeds`):
90
+
91
+ ```jsonc
92
+ {
93
+ "ok": true,
94
+ "items": [
95
+ { "id": "doc-note", "title": "插件说明笔记", "description": "…", "present": true, "detail": "已存在" },
96
+ { "id": "send-to-agent", "title": "「发送给 Agent」按钮", "description": "…", "present": true, "detail": "已存在" },
97
+ { "id": "home-index", "title": "首页(所有标签 / 标签笔记)", "description": "…", "present": false, "detail": "缺失:所有标签" },
98
+ { "id": "tw-web-host", "title": "TW 前端 API 基址(同源代理)", "description": "…", "present": true, "detail": "已指向 /dsh-tiddlywiki/tw/" }
99
+ ]
100
+ }
101
+ ```
102
+
103
+ wiki 服务未运行时返回 `503 { ok: false, error: "wiki service is not running" }`。
104
+
105
+ ### `POST /dsh-tiddlywiki/admin/seeds/run`
106
+
107
+ 请求体:
108
+
109
+ | 字段 | 类型 | 说明 |
110
+ |---|---|---|
111
+ | `id` | string, 可选 | 指定运行单项;缺省 = 全部 |
112
+ | `force` | boolean, 默认 false | true = 手动「重新初始化」(重写内置内容);false = ONE-SHOT(只写缺失) |
113
+
114
+ 响应:
115
+
116
+ ```jsonc
117
+ {
118
+ "ok": true,
119
+ "results": [
120
+ { "id": "home-index", "ok": true, "wrote": true, "detail": "已重新初始化" },
121
+ { "id": "tw-web-host", "ok": true, "wrote": false, "detail": "已指向自定义基址,未覆盖" }
122
+ ]
123
+ }
124
+ ```
125
+
126
+ - `results` 顺序 = 注册表顺序;
127
+ - 任一失败 → HTTP `400`,`ok: false`,失败项在 `results[].error` 里有原因;
128
+ - 未知 `id` → `400 { ok: false, results: [{ id, ok: false, error: "unknown seed: <id>" }] }`;
129
+ - 请求体过大(> 1MB)或非法 JSON → `400`。
130
+
131
+ ---
132
+
133
+ ## 6. 设置页操作
134
+
135
+ DSH 设置 →「TiddlyWiki 知识库」→ 最底部「**初始化(一次性预置)**」区块:
136
+
137
+ - 顶部状态行:`共 N 项,M 项已就绪`;
138
+ - 每项一行:状态点(✓ 已就绪 / ✗ 缺失)+ 名称 + 实时详情(如缺失了哪个 tiddler)+ 「重新初始化」按钮;
139
+ - 底部:「**全部重新初始化**」按钮(force 全部四项)。
140
+
141
+ 前端源码:`src/client/settings-page.ts` 的 `renderSeedsSection`;样式 `src/client/styles.ts`(状态点 `ok` / `missing`)。
142
+
143
+ ---
144
+
145
+ ## 7. 开发:重新生成内置常量
146
+
147
+ 两个 seed 的内容以**内嵌常量**形式随插件发布,修改来源后需重新生成:
148
+
149
+ ```bash
150
+ # 改了 wiki 里的「发送给 Agent」按钮 bundle($:/plugins/dsh/send-to-agent):
151
+ node scripts/gen-seed-send-to-agent.mjs '<wiki>/tiddlers/$__plugins_dsh_send-to-agent.json' src/host/seed-send-to-agent.ts
152
+
153
+ # 改了 wiki 首页(所有标签 / 标签笔记):
154
+ node scripts/gen-seed-home.mjs '<wiki>/tiddlers/所有标签.tid' '<wiki>/tiddlers/标签笔记.tid' src/host/seed-home.ts
155
+
156
+ # 重新生成后务必:
157
+ npm run typecheck && npm run build && npm run selftest
158
+ ```
159
+
160
+ > PowerShell 注意:含 `$` 的路径(如 `$__plugins_dsh_send-to-agent.json`)要用**单引号**包裹,否则 `$` 会被当作变量展开。
161
+
162
+ 新增一个 seed 的步骤:
163
+
164
+ 1. 写实现(或复用现有 `seedXxx(client, { force? })` 模式);
165
+ 2. 在 `src/host/seeds.ts` 的 `SEED_DEFS` 里登记(`check` + `run`);
166
+ 3. (若需要展示/强制恢复)确认启动路径 `runAllSeeds` 自动覆盖它;
167
+ 4. selftest 增加对应断言段,跑 `verify-seeds-admin.mjs` 验证后台 API;
168
+ 5. bump 版本、更新本文档与 README。
169
+
170
+ ---
171
+
172
+ ## 8. 验证
173
+
174
+ - `npm run typecheck` / `npm run build`:编译与打包;
175
+ - `npm run selftest`:5d 段覆盖注册表清单、runAllSeeds、单跑幂等、force 重写、unknown id、tw-web-host 三分支(custom 保留 / force 写回 / legacy 修复);
176
+ - `node scripts/verify-seeds-admin.mjs`:全新 wiki + 真实 HTTP,端到端验证 `GET /admin/seeds` 状态流转(全缺失 → 启动后全就绪)与 `POST /admin/seeds/run`(force 单跑恢复被改坏的首页、非 force 不覆盖用户内容、force-all 恢复代理基址、unknown id 400)。
177
+
178
+ ---
179
+
180
+ ## 9. 生效方式
181
+
182
+ - seed 注册表属插件源码逻辑:**重启 dsh web** 后生效(会中断当前会话,注意时机)。
183
+ - 本 wiki(已存在上述 tiddler):重启只补写 marker,**不覆盖现有内容**;要强制恢复内置内容,用设置页「初始化」。
184
+ - 新装 wiki:首次启动自动获得全部预置(说明笔记 / 发送按钮 / 首页 / 代理基址),无需手工放 tiddler。
@@ -338,6 +338,8 @@ html[data-dsh-tw-active] .dshDesktopConversationSurface > :not([data-dsh-tw-view
338
338
  .dsh-tw-settings-chip[data-state="running"] { color: var(--dsw-alias-state-success-primary, #3eaa5f); }
339
339
  .dsh-tw-settings-chip[data-state="starting"] { color: var(--dsw-alias-state-warning-primary, #d9822b); }
340
340
  .dsh-tw-settings-chip[data-state="failed"], .dsh-tw-settings-chip[data-state="stopped"] { color: var(--dsw-alias-state-error-primary, #d13b3b); }
341
+ .dsh-tw-settings-chip[data-state="ok"] { color: var(--dsw-alias-state-success-primary, #3eaa5f); }
342
+ .dsh-tw-settings-chip[data-state="missing"] { color: var(--dsw-alias-state-error-primary, #d13b3b); }
341
343
  .dsh-tw-settings-section { display: flex; flex-direction: column; gap: 8px; padding: 10px 12px; border-radius: 10px; border: 1px solid var(--dsw-alias-border-l2, rgba(128,128,128,.18)); }
342
344
  .dsh-tw-settings-h { margin: 0; font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-primary, #222); }
343
345
  .dsh-tw-settings-field { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--dsw-alias-label-secondary, inherit); }
@@ -27808,12 +27810,16 @@ function mountKnowledgeFab(state, note, sync) {
27808
27810
  * POST /dsh-tiddlywiki/admin/info { plugins?, themes? } → restart TW
27809
27811
  * POST /dsh-tiddlywiki/admin/config { ...patch } → persist
27810
27812
  * POST /dsh-tiddlywiki/admin/restart restart the TW child
27813
+ * GET /dsh-tiddlywiki/admin/seeds one-time seed statuses
27814
+ * POST /dsh-tiddlywiki/admin/seeds/run { id?, force? } → run one/all seeds
27811
27815
  *
27812
27816
  * Sections:
27813
27817
  * 1. 状态/重启 TW 运行状态 + git 概览 + 重启按钮
27814
27818
  * 2. 常规配置 note.tag / git.* / uiLanguage(改了什么保存什么)
27815
27819
  * 3. 插件管理 自带官方插件勾选(可搜索)→ 应用并重启 TW
27816
27820
  * 4. 主题管理 自带主题单选 → 应用并重启 TW
27821
+ * 5. 初始化 一次性预置(doc-note/send-to-agent/home-index/tw-web-host)
27822
+ * 状态 + 手动「重新初始化」(force)
27817
27823
  *
27818
27824
  * @module dsh-tiddlywiki/client/settings-page
27819
27825
  */
@@ -27821,6 +27827,8 @@ const STATE_ENDPOINT = "/dsh-tiddlywiki/admin/state";
27821
27827
  const INFO_ENDPOINT = "/dsh-tiddlywiki/admin/info";
27822
27828
  const CONFIG_ENDPOINT = "/dsh-tiddlywiki/admin/config";
27823
27829
  const RESTART_ENDPOINT = "/dsh-tiddlywiki/admin/restart";
27830
+ const SEEDS_ENDPOINT = "/dsh-tiddlywiki/admin/seeds";
27831
+ const SEEDS_RUN_ENDPOINT = "/dsh-tiddlywiki/admin/seeds/run";
27824
27832
  const SYNC_ENDPOINT = "/dsh-tiddlywiki/sync";
27825
27833
  function make(tag, className, text) {
27826
27834
  const node = document.createElement(tag);
@@ -28191,10 +28199,104 @@ function renderCatalogSection(body, info, catalog, refresh) {
28191
28199
  langSection.append(langHint, langWrap, applyLangs);
28192
28200
  body.append(langSection);
28193
28201
  }
28202
+ /**
28203
+ * 初始化 section: lists every one-time seed with its live status and offers a
28204
+ * manual "重新初始化" per item (and for all). Non-forced re-runs keep the
28205
+ * write-if-missing semantics; the button forces a rewrite to built-in content.
28206
+ */
28207
+ function renderSeedsSection(body) {
28208
+ const section = make("section", "dsh-tw-settings-section");
28209
+ section.append(make("h3", "dsh-tw-settings-h", "初始化(一次性预置)"));
28210
+ const hint = make("div", "dsh-tw-settings-muted", "这些是插件与 dsh 联动、需要在 wiki 里预置的 tiddler/配置,首次启动会自动写入(只写缺失,不覆盖你的改动)。下面可随时手动「重新初始化」强制恢复内置内容。");
28211
+ const wrap = make("div", "dsh-tw-settings-list");
28212
+ const statusLine = make("div", "dsh-tw-settings-muted");
28213
+ const load = async () => {
28214
+ try {
28215
+ const data = await fetchJson(SEEDS_ENDPOINT);
28216
+ if (data.ok !== true || !Array.isArray(data.items)) throw new Error(data.error ?? "获取失败");
28217
+ wrap.replaceChildren();
28218
+ let presentCount = 0;
28219
+ for (const item of data.items) {
28220
+ if (item.present) presentCount += 1;
28221
+ const dot = make("span", "dsh-tw-settings-chip", item.present ? "✓" : "✗");
28222
+ dot.dataset.state = item.present ? "ok" : "missing";
28223
+ dot.title = item.present ? "已存在" : "缺失";
28224
+ const title = make("span", "dsh-tw-settings-name", item.title);
28225
+ title.title = item.id;
28226
+ const desc = make("span", "dsh-tw-settings-muted", item.detail ?? item.description);
28227
+ const btn = make("button", "dsh-tw-settings-btn", "重新初始化");
28228
+ btn.type = "button";
28229
+ btn.title = `强制重写「${item.title}」的内置内容(会覆盖当前 tiddler)`;
28230
+ btn.addEventListener("click", () => {
28231
+ btn.disabled = true;
28232
+ btn.textContent = "执行中…";
28233
+ (async () => {
28234
+ try {
28235
+ const res = await fetch(SEEDS_RUN_ENDPOINT, {
28236
+ method: "POST",
28237
+ headers: { "content-type": "application/json" },
28238
+ body: JSON.stringify({
28239
+ id: item.id,
28240
+ force: true
28241
+ }),
28242
+ signal: AbortSignal.timeout(3e4)
28243
+ });
28244
+ const payload = await res.json().catch(() => null);
28245
+ if (!res.ok || payload?.ok !== true) toast(`重新初始化失败:${payload?.error ?? payload?.results?.[0]?.error ?? `HTTP ${res.status}`}`);
28246
+ else toast(`已重新初始化「${item.title}」`);
28247
+ load();
28248
+ } catch (err) {
28249
+ toast(`重新初始化失败:${err instanceof Error ? err.message : String(err)}`);
28250
+ } finally {
28251
+ btn.disabled = false;
28252
+ btn.textContent = "重新初始化";
28253
+ }
28254
+ })();
28255
+ });
28256
+ const row = make("div", "dsh-tw-settings-row dsh-tw-settings-plugin");
28257
+ row.append(dot, title, desc, btn);
28258
+ wrap.append(row);
28259
+ }
28260
+ statusLine.textContent = `共 ${data.items.length} 项,${presentCount} 项已就绪`;
28261
+ } catch (err) {
28262
+ statusLine.textContent = `加载初始化状态失败:${err instanceof Error ? err.message : String(err)}`;
28263
+ }
28264
+ };
28265
+ const runAll = make("button", "dsh-tw-settings-btn dsh-tw-settings-primary", "全部重新初始化");
28266
+ runAll.type = "button";
28267
+ runAll.title = "强制重写所有一次性预置内容(会覆盖当前 tiddler/配置)";
28268
+ runAll.addEventListener("click", () => {
28269
+ runAll.disabled = true;
28270
+ runAll.textContent = "执行中…";
28271
+ (async () => {
28272
+ try {
28273
+ const res = await fetch(SEEDS_RUN_ENDPOINT, {
28274
+ method: "POST",
28275
+ headers: { "content-type": "application/json" },
28276
+ body: JSON.stringify({ force: true }),
28277
+ signal: AbortSignal.timeout(6e4)
28278
+ });
28279
+ const payload = await res.json().catch(() => null);
28280
+ if (!res.ok || payload?.ok !== true) toast(`重新初始化失败:${payload?.error ?? `HTTP ${res.status}`}`);
28281
+ else toast("全部已重新初始化");
28282
+ load();
28283
+ } catch (err) {
28284
+ toast(`重新初始化失败:${err instanceof Error ? err.message : String(err)}`);
28285
+ } finally {
28286
+ runAll.disabled = false;
28287
+ runAll.textContent = "全部重新初始化";
28288
+ }
28289
+ })();
28290
+ });
28291
+ section.append(hint, statusLine, wrap, runAll);
28292
+ body.append(section);
28293
+ load();
28294
+ }
28194
28295
  function renderMain(body, state, refresh) {
28195
28296
  body.replaceChildren();
28196
28297
  renderConfigSection(body, state.config ?? {}, refresh);
28197
28298
  renderCatalogSection(body, state.info, state.catalog, refresh);
28299
+ renderSeedsSection(body);
28198
28300
  }
28199
28301
  /** React wrapper consumed by the shell's settings.section slot. */
28200
28302
  function SettingsSection() {
package/lib/client.js CHANGED
@@ -344,6 +344,8 @@ html[data-dsh-tw-active] .dshDesktopConversationSurface > :not([data-dsh-tw-view
344
344
  .dsh-tw-settings-chip[data-state="running"] { color: var(--dsw-alias-state-success-primary, #3eaa5f); }
345
345
  .dsh-tw-settings-chip[data-state="starting"] { color: var(--dsw-alias-state-warning-primary, #d9822b); }
346
346
  .dsh-tw-settings-chip[data-state="failed"], .dsh-tw-settings-chip[data-state="stopped"] { color: var(--dsw-alias-state-error-primary, #d13b3b); }
347
+ .dsh-tw-settings-chip[data-state="ok"] { color: var(--dsw-alias-state-success-primary, #3eaa5f); }
348
+ .dsh-tw-settings-chip[data-state="missing"] { color: var(--dsw-alias-state-error-primary, #d13b3b); }
347
349
  .dsh-tw-settings-section { display: flex; flex-direction: column; gap: 8px; padding: 10px 12px; border-radius: 10px; border: 1px solid var(--dsw-alias-border-l2, rgba(128,128,128,.18)); }
348
350
  .dsh-tw-settings-h { margin: 0; font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-primary, #222); }
349
351
  .dsh-tw-settings-field { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--dsw-alias-label-secondary, inherit); }
@@ -27814,12 +27816,16 @@ function mountKnowledgeFab(state, note, sync) {
27814
27816
  * POST /dsh-tiddlywiki/admin/info { plugins?, themes? } → restart TW
27815
27817
  * POST /dsh-tiddlywiki/admin/config { ...patch } → persist
27816
27818
  * POST /dsh-tiddlywiki/admin/restart restart the TW child
27819
+ * GET /dsh-tiddlywiki/admin/seeds one-time seed statuses
27820
+ * POST /dsh-tiddlywiki/admin/seeds/run { id?, force? } → run one/all seeds
27817
27821
  *
27818
27822
  * Sections:
27819
27823
  * 1. 状态/重启 TW 运行状态 + git 概览 + 重启按钮
27820
27824
  * 2. 常规配置 note.tag / git.* / uiLanguage(改了什么保存什么)
27821
27825
  * 3. 插件管理 自带官方插件勾选(可搜索)→ 应用并重启 TW
27822
27826
  * 4. 主题管理 自带主题单选 → 应用并重启 TW
27827
+ * 5. 初始化 一次性预置(doc-note/send-to-agent/home-index/tw-web-host)
27828
+ * 状态 + 手动「重新初始化」(force)
27823
27829
  *
27824
27830
  * @module dsh-tiddlywiki/client/settings-page
27825
27831
  */
@@ -27827,6 +27833,8 @@ const STATE_ENDPOINT = "/dsh-tiddlywiki/admin/state";
27827
27833
  const INFO_ENDPOINT = "/dsh-tiddlywiki/admin/info";
27828
27834
  const CONFIG_ENDPOINT = "/dsh-tiddlywiki/admin/config";
27829
27835
  const RESTART_ENDPOINT = "/dsh-tiddlywiki/admin/restart";
27836
+ const SEEDS_ENDPOINT = "/dsh-tiddlywiki/admin/seeds";
27837
+ const SEEDS_RUN_ENDPOINT = "/dsh-tiddlywiki/admin/seeds/run";
27830
27838
  const SYNC_ENDPOINT = "/dsh-tiddlywiki/sync";
27831
27839
  function make(tag, className, text) {
27832
27840
  const node = document.createElement(tag);
@@ -28197,10 +28205,104 @@ function renderCatalogSection(body, info, catalog, refresh) {
28197
28205
  langSection.append(langHint, langWrap, applyLangs);
28198
28206
  body.append(langSection);
28199
28207
  }
28208
+ /**
28209
+ * 初始化 section: lists every one-time seed with its live status and offers a
28210
+ * manual "重新初始化" per item (and for all). Non-forced re-runs keep the
28211
+ * write-if-missing semantics; the button forces a rewrite to built-in content.
28212
+ */
28213
+ function renderSeedsSection(body) {
28214
+ const section = make("section", "dsh-tw-settings-section");
28215
+ section.append(make("h3", "dsh-tw-settings-h", "初始化(一次性预置)"));
28216
+ const hint = make("div", "dsh-tw-settings-muted", "这些是插件与 dsh 联动、需要在 wiki 里预置的 tiddler/配置,首次启动会自动写入(只写缺失,不覆盖你的改动)。下面可随时手动「重新初始化」强制恢复内置内容。");
28217
+ const wrap = make("div", "dsh-tw-settings-list");
28218
+ const statusLine = make("div", "dsh-tw-settings-muted");
28219
+ const load = async () => {
28220
+ try {
28221
+ const data = await fetchJson(SEEDS_ENDPOINT);
28222
+ if (data.ok !== true || !Array.isArray(data.items)) throw new Error(data.error ?? "获取失败");
28223
+ wrap.replaceChildren();
28224
+ let presentCount = 0;
28225
+ for (const item of data.items) {
28226
+ if (item.present) presentCount += 1;
28227
+ const dot = make("span", "dsh-tw-settings-chip", item.present ? "✓" : "✗");
28228
+ dot.dataset.state = item.present ? "ok" : "missing";
28229
+ dot.title = item.present ? "已存在" : "缺失";
28230
+ const title = make("span", "dsh-tw-settings-name", item.title);
28231
+ title.title = item.id;
28232
+ const desc = make("span", "dsh-tw-settings-muted", item.detail ?? item.description);
28233
+ const btn = make("button", "dsh-tw-settings-btn", "重新初始化");
28234
+ btn.type = "button";
28235
+ btn.title = `强制重写「${item.title}」的内置内容(会覆盖当前 tiddler)`;
28236
+ btn.addEventListener("click", () => {
28237
+ btn.disabled = true;
28238
+ btn.textContent = "执行中…";
28239
+ (async () => {
28240
+ try {
28241
+ const res = await fetch(SEEDS_RUN_ENDPOINT, {
28242
+ method: "POST",
28243
+ headers: { "content-type": "application/json" },
28244
+ body: JSON.stringify({
28245
+ id: item.id,
28246
+ force: true
28247
+ }),
28248
+ signal: AbortSignal.timeout(3e4)
28249
+ });
28250
+ const payload = await res.json().catch(() => null);
28251
+ if (!res.ok || payload?.ok !== true) toast(`重新初始化失败:${payload?.error ?? payload?.results?.[0]?.error ?? `HTTP ${res.status}`}`);
28252
+ else toast(`已重新初始化「${item.title}」`);
28253
+ load();
28254
+ } catch (err) {
28255
+ toast(`重新初始化失败:${err instanceof Error ? err.message : String(err)}`);
28256
+ } finally {
28257
+ btn.disabled = false;
28258
+ btn.textContent = "重新初始化";
28259
+ }
28260
+ })();
28261
+ });
28262
+ const row = make("div", "dsh-tw-settings-row dsh-tw-settings-plugin");
28263
+ row.append(dot, title, desc, btn);
28264
+ wrap.append(row);
28265
+ }
28266
+ statusLine.textContent = `共 ${data.items.length} 项,${presentCount} 项已就绪`;
28267
+ } catch (err) {
28268
+ statusLine.textContent = `加载初始化状态失败:${err instanceof Error ? err.message : String(err)}`;
28269
+ }
28270
+ };
28271
+ const runAll = make("button", "dsh-tw-settings-btn dsh-tw-settings-primary", "全部重新初始化");
28272
+ runAll.type = "button";
28273
+ runAll.title = "强制重写所有一次性预置内容(会覆盖当前 tiddler/配置)";
28274
+ runAll.addEventListener("click", () => {
28275
+ runAll.disabled = true;
28276
+ runAll.textContent = "执行中…";
28277
+ (async () => {
28278
+ try {
28279
+ const res = await fetch(SEEDS_RUN_ENDPOINT, {
28280
+ method: "POST",
28281
+ headers: { "content-type": "application/json" },
28282
+ body: JSON.stringify({ force: true }),
28283
+ signal: AbortSignal.timeout(6e4)
28284
+ });
28285
+ const payload = await res.json().catch(() => null);
28286
+ if (!res.ok || payload?.ok !== true) toast(`重新初始化失败:${payload?.error ?? `HTTP ${res.status}`}`);
28287
+ else toast("全部已重新初始化");
28288
+ load();
28289
+ } catch (err) {
28290
+ toast(`重新初始化失败:${err instanceof Error ? err.message : String(err)}`);
28291
+ } finally {
28292
+ runAll.disabled = false;
28293
+ runAll.textContent = "全部重新初始化";
28294
+ }
28295
+ })();
28296
+ });
28297
+ section.append(hint, statusLine, wrap, runAll);
28298
+ body.append(section);
28299
+ load();
28300
+ }
28200
28301
  function renderMain(body, state, refresh) {
28201
28302
  body.replaceChildren();
28202
28303
  renderConfigSection(body, state.config ?? {}, refresh);
28203
28304
  renderCatalogSection(body, state.info, state.catalog, refresh);
28305
+ renderSeedsSection(body);
28204
28306
  }
28205
28307
  /** React wrapper consumed by the shell's settings.section slot. */
28206
28308
  function SettingsSection() {