dsh-tiddlywiki 0.22.8 → 0.22.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +5 -0
- package/cordis.patch.yml +13 -13
- package/lib/index.js +141 -31
- package/lib/index.js.map +1 -1
- package/package.json +2 -2
- package/src/client/markdown-editor.ts +128 -128
- package/src/client/state.ts +39 -39
- package/src/client/toast.ts +22 -22
- package/src/host/config.ts +9 -0
- package/src/host/routes.ts +25 -2
- package/src/host/tools.ts +31 -6
- package/src/host/tw-api.ts +63 -1
- package/src/host/wiki.ts +39 -4
- package/src/host/write-policy.ts +53 -5
- package/src/index.ts +3 -3
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 dsh-tiddlywiki contributors
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dsh-tiddlywiki contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
| 📝 **可配置的注入提示词** | 插件注入每个会话的「TiddlyWiki 持久知识库」提示词可在设置页配置:**默认精简版**(~1.7KB,只保留工具 schema 表达不了的约定——同步纪律 / 标签约定 / 链接格式),可选**完整版**(额外附一份**由工具注册表实时生成**的参数索引,不会再过期);`extra` 追加自定义规范、`override` 整段接管、可整体停用;**保存后无需重启 dsh web**(section 即时重注册,当前会话下一步即生效),设置页可**按表单当前值预览**即将注入的全文(未保存的形态切换也立刻可见,v0.21.0 / v0.22.7) |
|
|
31
31
|
| 🤖 **Agent 工具** | 15 个 `tiddlywiki_*` 工具:检索(**相关度排序 + 命中处片段 + 字段过滤**)、读写、**增量追加**、批量、重命名、**软删除/回收站**、**反向链接**、**附件入库**、**知识库体检**、git 同步与冲突解决(v0.19.0;检索/最近仍在**服务端**排除二进制附件,大 wiki 上从 515MB/17s 降到 ~0.4s) |
|
|
32
32
|
| 🛡️ **不会被覆盖的写入** | 所有写入路径(agent 工具 **与** 快速笔记/编辑器路由)都**先读后写**:不传 tags 就保留原有标签、自定义字段与**内容类型**(`text/css`/wikitext 等不会被重置成 Markdown,v0.20.1);`tiddlywiki_put(..., expectedModified/expectedRevision)` 与 `tiddlywiki_delete(..., expectedModified/expectedRevision)` 乐观并发——读取后若有人(在 TW 编辑器里)改过,写入/删除被拒绝(HTTP 409)而不是静默覆盖或丢进回收站;`tiddlywiki_attach` 的同名标题**默认拒绝**(要覆盖必须 `force: true`);`tiddlywiki_delete` 默认**软删除进回收站**,`tiddlywiki_trash` 可恢复(v0.19.0 / v0.19.1 / v0.19.5) |
|
|
33
|
+
| 🕒 **时间戳不再丢** | 插件写入的每条笔记都会带上 TW 的 `created`/`modified`(17 位紧凑 UTC,与 TW 编辑器逐字节一致):新建 = 两者都取当前时刻,覆盖 = **保留原 `created`、刷新 `modified`**。此前这两个字段被当成「TW 服务端会补」而丢弃,而服务端**从不补**——缺 `modified` 的条目会被 TW 的 `sortTiddlers` 当空串,在 `+[!sort[modified]]` 页面上**直接沉到最后一名**(表现为「新日记没被收录」)。`TiddlyWebClient.put()` 还有一道兜底,任何写路径都不会写出无时间戳的条目(v0.22.5) |
|
|
33
34
|
| 🧼 **渲染片段净化** | 回复流卡片与会话汇总注入的 TW 片段先经 **host 白名单净化**(丢 `iframe`/`script`/`svg`/`on*`/`javascript:`/`data:text/html` 等)——TW 自己的解析器只剥 `on*`,`<iframe src="javascript:…">` 会原样通过并在 DSH 页面里执行(v0.19.1 修复的存储型 XSS) |
|
|
34
35
|
| 🔒 **写路由方法校验** | 每个写路由只接受自己的 HTTP 方法:跨站 `GET /sync`、`GET /restart`、`GET /upload` 一律 405 且无副作用(v0.19.0 修复了「任意网页一张 `<img>` 即可触发 pull/commit/push」的 CSRF 面) |
|
|
35
36
|
| 🔑 **密钥不外泄** | `bridge.token` / `ui.sendToAgent.token` / **`auth.password`**(v0.20.0)在 `/admin/state`、`/admin/config` 的回包里是 `********`(设置页原样保存 ≠ 覆盖,清空即删除),`git.remote` 里的 PAT 打码;**`/tw`、`/api` 与 `POST /render` 都拒绝 `$:/plugins/dsh-tiddlywiki/` 命名空间**——此前 `/render` 能把配置 tiddler 连 token 与 PAT 一起渲染出来(v0.19.3 / v0.20.0) |
|
|
@@ -352,6 +353,10 @@ lib/ # 预构建产物(发布含 lib/**,提交入库;
|
|
|
352
353
|
|
|
353
354
|
> 最近几个主要版本的一句话记录(完整变更见 [Releases](https://github.com/bbqisbbq/dsh-tiddlywiki/releases) / git log)。
|
|
354
355
|
|
|
356
|
+
- **v0.22.10**(2026-09-17):**修「写入丢失 `created`/`modified`,新笔记在按修改时间排序的页面上沉到最后一名」**(用户实测报障:日记在「主题页·日志」排 175/175,看着像没被收录)。根因:插件把这两个字段列进 `CLEAN_SKIP_FIELDS`(假设「TW 服务端会补」),但 TW 服务端的 PUT 路由**从不补**时间戳——`getCreationFields()`/`getModificationFields()` 只在 TW 自己的浏览器 UI 里调用,于是插件写的条目落盘只有 tags/title/type;而 TW 的 `sortTiddlers` 对缺失字段取 `fields[sortField] || ""`,`!sort[modified]` 降序时空串直接沉底。**修复**:`buildWriteTiddler()` 统一负责写这两个字段,语义与 TW 编辑器一致——新建 = 都取当前时刻,覆盖 = 保留原 `created`、只刷新 `modified`(基底无 `created` 的迁移存量则补当前时刻);格式用新增 `formatTiddlerDate()`(TW 的 17 位紧凑 UTC,与 `$tw.utils.stringifyDate()` 逐字节一致,别用 ISO)。两者继续留在 `RESERVED_TIDDLER_FIELDS`(`fields` 不得覆盖),只是不再被 CLEAN_SKIP 丢弃。`TiddlyWebClient.put()` 另有 `ensureTiddlerTimestamps()` 兜底(**只补缺、绝不改写**已有值),覆盖剪藏桥 / seed / 配置 tiddler 等手拼 PUT 的路径。顺带统一:`rename`(含引用改写)与回收站恢复改走共享写策略(原先手拼 body 会把旧 `modified` 原样带过去);回收站快照与恢复保留原时间戳(恢复 = 恢复原状,`trash-at` 单独记删除时刻);`/edit` 生成的 TW 草稿携带原笔记的 `created`——TW 保存草稿时草稿字段会覆盖新生成的时间戳,草稿不带它就会把笔记的创建时刻重置成「刚才」。守门:`verify-write-policy` +7 条纯函数(格式往返 / 三种语义 / `$:/` / `fields` 不可覆盖 / 兜底只补不改写),`verify-tools` +5 条 E2E(含用 TW 真实 `!sort[modified]` 过滤器验证排序),selftest 增配置 `created` 保留断言。**存量数据不自动回填**:线上 wiki 1161 个 `.tid` 中 948 个缺 `modified`(多为历史迁移脚本所致),它们仍会在 `sort[modified]` 页面沉底,可用 wiki 根目录的 `backfill-timestamps.mjs` 一次性补齐(默认 dry-run,按 **git 历史**取首次出现/最后变更时间,`--write` 才落盘)。
|
|
357
|
+
|
|
358
|
+
- **v0.22.9**(2026-09-16):**修「TiddlyWiki 编辑者署名被静默清空」**(用户实测报障:控制面板 →「信息」→「基础」里的「编辑者署名」填完保存不了、刷新就没了)。根因是 DSH 启动 TW 子进程时(匿名 loopback 分支)**没传 `anon-username`**:`get-status.js:21` 回落到 `""`,而 TiddlyWeb adaptor 判定登录用的是 `isLoggedIn = json.username !== "GUEST"`(`tiddlywebadaptor.js:93`)——`"" !== "GUEST"` 为真,于是**匿名读者被当成已登录用户**;`syncer.js:281-283` 随即在**每次页面加载**时把 `$:/status/UserName` 覆写成那个空串。名字在当次会话里看着还在,一刷新就没了;而一旦 `$:/config/SyncFilter` 放行了 `UserName`(本仓库 wiki 的现状,有一篇专门的踩坑笔记记录),这个空串还会**同步落盘并进 git**,从「内存里丢了」升级成**真实、可追溯的数据丢失**。现在匿名分支显式传 `anon-username=GUEST`(`wiki.ts` 的 `ANON_USERNAME` 常量):`isLoggedIn` 变 false → syncer **根本不再写** `UserName` → 用户填的名字稳定保留。⚠️ `GUEST` 是**承重的魔法值**,不是随便取的名字:TW 判的就是 `!== "GUEST"` 这个哨兵,填任何人名(含你自己的名字)都仍然算「已登录」,只会把署名覆盖成人名而不是空串。**为什么拖到现在**:`src/host/wiki.ts` 从 v0.16 起就写着一句注释「Passing anon-username/readers/writers here was verified to 401 every request」——它把**两个独立参数**混为一谈,等于给后来每个维护者发了张「此路不通」的牌子。实测(TW 5.4.1)只有 `readers`/`writers` 会关掉匿名访问(`server.js:63-66` 里显式的 `readers` 会让 `(anon)` 不再是 principal,于是 `isAuthorized()` 为 false,`requestHandler` 用当时为 `undefined` 的 `authenticatedUsername` 拼出 `'undefined' is not authorized` 的 401);`anon-username` **单独**传完全安全(`/status` 200、GET 200、PUT 204)。那句注释已改正并写明真实机制。**副作用(外观级)**:`$:/status/IsLoggedIn` 恒为 `no`,TW 的 SyncerDropdown 会多出一个用不上的 Login 按钮;`wiki.js:1690` 的 `generateDraftTitle` 会从 `Draft/Title` 形态切到 `Draft/Attribution` 形态(内核既有行为,与「快速笔记」的 `/edit` 草稿路由无关——后者按 `draft.of` 字段匹配,不依赖标题字面量)。**影响面**:默认 SyncFilter 的用户**不会**丢盘(`$:/status/` 被默认过滤器整个排除,空串走不到落盘),实际受数据丢失影响的只有**已经放行 `UserName` 的人**——所以这一版主要是消除一颗静默的数据丢失地雷 + 拔掉那句误导注释。守门:新增 `scripts/verify-anon-username.mjs`(进 `verify:e2e`,真实 TW 子进程)——`ANON_USERNAME` 必须字面量 `GUEST`、`/status` 必须回该哨兵、落盘署名不得被覆盖、匿名模式仍可正常写条目、auth 模式不受影响且对匿名请求仍 401,外加源码级断言(旧误导注释必须已删、鉴权分支不得顺手传 `anon-username`);**已反向验证**:把 `anon-username` 那行去掉即红 3 条。
|
|
359
|
+
|
|
355
360
|
- **v0.22.8**(2026-09-15):**第六轮代码审计修复**——host 与 client 各做一份只读审计,逐条核实后修 10 处真缺陷,并清掉一轮重复实现与死代码。① **数据丢失(P0)**:`/edit` 仍会把「调用方文本或笔记已保存正文」PUT 进**刚复用**的那个草稿,而那个草稿里装的是用户在 TW 原生编辑器里敲的**未保存内容**——触发路径极常见:打开快速笔记(标题精确到分钟)→ 写 → 关掉 → 同一分钟内再打开 → 草稿被覆盖。现在只有**我们创建的**草稿才由我们填充,复用的仅在调用方显式给了文本时才写。② **设置页超时(P0)**:`fetchJson` 展开 `init` 后又硬编码 `signal`,于是「知识库切换 / 恢复默认 / 重启 TW」的 120s 预算被 15s 掐断——宿主其实切成功了,用户却看到「切换失败」(大 wiki 冷启动实测 44s+),且重试会再走一遍停/启。③ **净化器崩服务**:`�` 这类越界数字实体会让 `String.fromCodePoint()` 抛 `RangeError`,而净化器没有 try——一条笔记正文就能让 `POST /render` 502,打坏回复流工具卡与会话「知识库」Tab。④ **「幽灵草稿」**:保存/「丢弃」后重置编辑器会触发防抖,把「空正文 + 新时间戳标题」又写成草稿,下次打开误报「已恢复未保存草稿」并跳过默认 tag。⑤ `/recent` 一直丢掉 `since`(工具与卡片都发它),于是模型看到过滤结果、人看到未过滤列表——与 v0.20.0 修过的 `/search` 同类漂移。⑥ 隐藏的 TW 面板会因为一个在途 `/status` 响应重新起一轮 30×1.5s 轮询。⑦ `/tw` 代理补齐显式方法白名单。⑧ `/render` 的 404 改为结构化异常(原先按错误消息文本正则,改词即把「不存在」变 502)。⑨ `/get` 改用共享的 `isBlockedProxyTitle()`(原为硬编码字面量副本)。⑩ 侧边栏显示名改完设置即时生效(原只挂载时读一次)。**去重/死代码**:新增 `render-fetch.ts`(唯一渲染调用)、`describeSyncResult()`(同步回执)、`formatLocalMinute()`,`ui-config.ts` 承担全部 `ui.*` 投影;删除生产零调用的 `ensureTwWebHost()`(与 `tw-web-host` seed 重复)与 `listTags()`,多个仅供模块内部使用的 `export` 与「算了却从不显示」的字段一并清除。守门:`verify-render-sanitizer.mjs` +1、`verify-frame-guards.mjs` +3、`verify-audit-fixes.mjs` +3(草稿覆盖那条**已反向验证**:还原修复即红)。
|
|
356
361
|
|
|
357
362
|
- **v0.22.7**(2026-09-14):**设置页「预览注入文本」改为按表单当前值渲染**(用户实测提问「两种内置文本形态差别是什么?为什么选了不同形态后查看当前注入文本显示的东西一样?」)。两种形态的差别其实只在 intro 一段:`slim`(默认)是一句能力概览(实测 1735 字符),`full` 换成由工具注册表实时生成的 15 行参数索引(2504 字符),三块治理约定(写入与并发 / 同步纪律 / 笔记约定)完全相同。但预览按钮此前只发 `GET /admin/prompt`,读的是 **host 已保存的有效配置**,而下拉框的值只活在浏览器 DOM 里——于是「切换形态 → 不点保存 → 点预览」看到的是逐字节相同的旧文本,用户自然以为两种形态没差别。现在:① `prompt.ts` 新增 `normalizePromptPreview()`(把不可信的 body 白名单化成 4 个字段,未知键/错类型丢弃并回落内置默认)+ `describePrompt()`(`applyPrompt()` 与预览**共用同一份拼装**,预览不可能与保存后的注入不一致);② `POST /admin/prompt` 接收 `{enabled,mode,extra,override}` 草稿并渲染全文,**写入零副作用**(不落盘、不触发 `onConfigChanged`),同源守卫 + 畸形 JSON 400;GET 保留为「已保存/正在注入」的读取;③ 设置页按钮改名「预览注入文本(按表单当前值)」并 POST 草稿,标题栏在表单有未保存改动时标注「含未保存的修改,保存后才真正注入」。守门:`verify-prompt.mjs` 新增 3 条纯函数断言(白名单、`describePrompt` 与 `buildPromptText` 逐字节一致、两种形态在草稿里必须不同),`verify-seeds-admin.mjs` 第 8 段把「POST 必须 405」换成 7 条草稿断言(slim/full 草稿不同、extra 追加、`enabled:false` 为空、GET 仍是旧的已保存状态且 `onConfigChanged` 未触发、垃圾字段回落、畸形 JSON 400、跨站 403)。
|
package/cordis.patch.yml
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
# dsh-tiddlywiki bundle patch: inserts the plugin row into the web profile
|
|
2
|
-
# roster. Applied as a profile bundle layer (the `dsh.bundle.patch` manifest
|
|
3
|
-
# field) over dsh-base; activate with
|
|
4
|
-
# `dsh plugin --profile web add link:<path-to-this-package>` and restart dsh web.
|
|
5
|
-
#
|
|
6
|
-
# The row is a bare plugin by package name: the node half (exports ".") runs
|
|
7
|
-
# in the host process (WikiServer, routes, tiddlywiki_* tools, prompt section,
|
|
8
|
-
# auto-commit), and the `dsh.client` declaration in package.json makes the
|
|
9
|
-
# browser half (exports "./client", served at /plugins/dsh-tiddlywiki/client.js)
|
|
10
|
-
# load in the web GUI.
|
|
11
|
-
- insert:
|
|
12
|
-
- id: dsh-tiddlywiki
|
|
13
|
-
name: dsh-tiddlywiki
|
|
1
|
+
# dsh-tiddlywiki bundle patch: inserts the plugin row into the web profile
|
|
2
|
+
# roster. Applied as a profile bundle layer (the `dsh.bundle.patch` manifest
|
|
3
|
+
# field) over dsh-base; activate with
|
|
4
|
+
# `dsh plugin --profile web add link:<path-to-this-package>` and restart dsh web.
|
|
5
|
+
#
|
|
6
|
+
# The row is a bare plugin by package name: the node half (exports ".") runs
|
|
7
|
+
# in the host process (WikiServer, routes, tiddlywiki_* tools, prompt section,
|
|
8
|
+
# auto-commit), and the `dsh.client` declaration in package.json makes the
|
|
9
|
+
# browser half (exports "./client", served at /plugins/dsh-tiddlywiki/client.js)
|
|
10
|
+
# load in the web GUI.
|
|
11
|
+
- insert:
|
|
12
|
+
- id: dsh-tiddlywiki
|
|
13
|
+
name: dsh-tiddlywiki
|
package/lib/index.js
CHANGED
|
@@ -531,6 +531,54 @@ function toIsoDateString(value) {
|
|
|
531
531
|
const ms = parseTiddlerDate(value);
|
|
532
532
|
return ms === void 0 ? null : new Date(ms).toISOString();
|
|
533
533
|
}
|
|
534
|
+
/**
|
|
535
|
+
* Format an instant in TW's own date-field format: `YYYYMMDDhhmmssSSS` (UTC,
|
|
536
|
+
* 17 digits) — byte-for-byte what `$tw.utils.stringifyDate()` writes, which is
|
|
537
|
+
* the inverse of `parseTiddlerDate()` above.
|
|
538
|
+
*
|
|
539
|
+
* TW's `created`/`modified` tiddlerfield modules parse with `$tw.utils.parseDate`
|
|
540
|
+
* and stringify with `$tw.utils.stringifyDate`, so a Date produced by either
|
|
541
|
+
* round-trips. Sending ISO-8601 instead would still be PARSED correctly (the
|
|
542
|
+
* date parser is lenient) but the on-disk `.tid`/`.meta` would differ from what
|
|
543
|
+
* the TW editor writes — keeping the two write paths byte-identical is the whole
|
|
544
|
+
* point of v0.22.10, so writers must use this function.
|
|
545
|
+
*
|
|
546
|
+
* (Local time is deliberately NOT used: TW stores UTC and renders in the
|
|
547
|
+
* viewer's zone, so a local-time string would shift every displayed date.)
|
|
548
|
+
*/
|
|
549
|
+
function formatTiddlerDate(value) {
|
|
550
|
+
const d = value instanceof Date ? value : new Date(value);
|
|
551
|
+
const pad = (n, width = 2) => String(n).padStart(width, "0");
|
|
552
|
+
return `${d.getUTCFullYear()}${pad(d.getUTCMonth() + 1)}${pad(d.getUTCDate())}${pad(d.getUTCHours())}${pad(d.getUTCMinutes())}${pad(d.getUTCSeconds())}${pad(d.getUTCMilliseconds(), 3)}`;
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Fill missing `created`/`modified` on a tiddler about to be PUT (v0.22.10).
|
|
556
|
+
*
|
|
557
|
+
* SAFETY NET, not the policy: `buildWriteTiddler()` owns the semantics (new
|
|
558
|
+
* tiddler ⇒ both = now; overwrite ⇒ keep the base `created`, refresh `modified`).
|
|
559
|
+
* This only guarantees the INVARIANT that every tiddler this plugin writes
|
|
560
|
+
* carries both fields, including the write paths that deliberately hand-build
|
|
561
|
+
* their PUT (clip bridge, seeds, config, `/upload`, session summary) and any
|
|
562
|
+
* future one.
|
|
563
|
+
*
|
|
564
|
+
* Why it matters: TW's server-side write path never stamps these fields
|
|
565
|
+
* (`put-tiddler.js` just does `addTiddler(new $tw.Tiddler(fields, {title}))`;
|
|
566
|
+
* `getCreationFields()`/`getModificationFields()` are only called by TW's own
|
|
567
|
+
* UI). A stored tiddler without `modified` is read by `sortTiddlers` as
|
|
568
|
+
* `fields[sortField] || ""`, so `!sort[modified]` sinks it to the LAST slot —
|
|
569
|
+
* the note looks like it was never collected.
|
|
570
|
+
*
|
|
571
|
+
* Existing values are NEVER overwritten: explicit timestamps (a trash snapshot
|
|
572
|
+
* copied from the original, a restore, or a caller that set them on purpose)
|
|
573
|
+
* must survive.
|
|
574
|
+
*/
|
|
575
|
+
function ensureTiddlerTimestamps(tiddler, now = /* @__PURE__ */ new Date()) {
|
|
576
|
+
const stamp = formatTiddlerDate(now);
|
|
577
|
+
const hasCreated = typeof tiddler.created === "string" && tiddler.created.trim().length > 0;
|
|
578
|
+
const hasModified = typeof tiddler.modified === "string" && tiddler.modified.trim().length > 0;
|
|
579
|
+
if (!hasCreated) tiddler.created = hasModified ? tiddler.modified : stamp;
|
|
580
|
+
if (!hasModified) tiddler.modified = hasCreated ? tiddler.created : stamp;
|
|
581
|
+
}
|
|
534
582
|
/** Split TW's whitespace-joined tags string into an array. */
|
|
535
583
|
function normalizeTags(tags) {
|
|
536
584
|
if (tags === void 0) return void 0;
|
|
@@ -642,8 +690,18 @@ var TiddlyWebClient = class TiddlyWebClient {
|
|
|
642
690
|
}
|
|
643
691
|
return res.text();
|
|
644
692
|
}
|
|
645
|
-
/**
|
|
693
|
+
/**
|
|
694
|
+
* Write (create or overwrite) one tiddler via PUT (204 on success).
|
|
695
|
+
*
|
|
696
|
+
* Every write goes through here, so this is also where the timestamp INVARIANT
|
|
697
|
+
* is enforced (v0.22.10): a tiddler without `modified` would be sorted as an
|
|
698
|
+
* empty string by TW's `sortTiddlers` and sink to the bottom of every
|
|
699
|
+
* `!sort[modified]` page. `buildWriteTiddler()` decides the real semantics
|
|
700
|
+
* (keep `created` on overwrite, refresh `modified`); this only fills gaps for
|
|
701
|
+
* the paths that build their PUT by hand.
|
|
702
|
+
*/
|
|
646
703
|
async put(tiddler) {
|
|
704
|
+
ensureTiddlerTimestamps(tiddler);
|
|
647
705
|
const title = tiddler.title;
|
|
648
706
|
const res = await this.request(`/recipes/default/tiddlers/${encodeURIComponent(title)}`, {
|
|
649
707
|
method: "PUT",
|
|
@@ -1020,6 +1078,18 @@ const KILL_GRACE_MS = 3e3;
|
|
|
1020
1078
|
const LOG_BUFFER_LIMIT = 200;
|
|
1021
1079
|
/** One-shot scaffold timeout for `--init server`. */
|
|
1022
1080
|
const INIT_TIMEOUT_MS = 3e4;
|
|
1081
|
+
/**
|
|
1082
|
+
* `anon-username` for the anonymous loopback spawn (v0.22.9).
|
|
1083
|
+
*
|
|
1084
|
+
* This MUST stay the literal `"GUEST"`. TW's TiddlyWeb adaptor derives its
|
|
1085
|
+
* login flag as `json.username !== "GUEST"` (tiddlywebadaptor.js:93), so any
|
|
1086
|
+
* other value — including any real person's name — still reports as logged in,
|
|
1087
|
+
* and syncer.js then overwrites `$:/status/UserName` with whatever `/status`
|
|
1088
|
+
* returns. Only this sentinel makes the syncer leave the user's signature
|
|
1089
|
+
* alone. Exported so the regression gate asserts the exact string instead of
|
|
1090
|
+
* re-typing it.
|
|
1091
|
+
*/
|
|
1092
|
+
const ANON_USERNAME = "GUEST";
|
|
1023
1093
|
/** Resolve the absolute entry of the installed `tiddlywiki` package. */
|
|
1024
1094
|
function resolveTwEntry() {
|
|
1025
1095
|
return createRequire(import.meta.url).resolve("tiddlywiki/tiddlywiki.js");
|
|
@@ -1217,7 +1287,7 @@ var WikiServer = class {
|
|
|
1217
1287
|
args.push(`password=${this.options.password ?? ""}`);
|
|
1218
1288
|
args.push(`readers=${this.options.username}`);
|
|
1219
1289
|
args.push(`writers=${this.options.username}`);
|
|
1220
|
-
}
|
|
1290
|
+
} else args.push(`anon-username=${ANON_USERNAME}`);
|
|
1221
1291
|
this.log(`spawn: ${process.execPath} ${args.map((a) => /^password=/.test(a) ? "password=***" : a).join(" ")}`);
|
|
1222
1292
|
const child = spawn(process.execPath, args, {
|
|
1223
1293
|
cwd: this.wikiPath,
|
|
@@ -3639,19 +3709,22 @@ var ConfigStore = class {
|
|
|
3639
3709
|
*/
|
|
3640
3710
|
async set(client, patch) {
|
|
3641
3711
|
let stored = this.overrides;
|
|
3712
|
+
let existingCreated;
|
|
3642
3713
|
try {
|
|
3643
3714
|
const tiddler = await client.get(CONFIG_TIDDLER);
|
|
3644
3715
|
if (tiddler !== void 0 && typeof tiddler.text === "string") {
|
|
3645
3716
|
const parsed = JSON.parse(tiddler.text);
|
|
3646
3717
|
if (isPlainObject(parsed)) stored = parsed;
|
|
3647
3718
|
}
|
|
3719
|
+
if (tiddler !== void 0 && typeof tiddler.created === "string" && tiddler.created.trim().length > 0) existingCreated = tiddler.created;
|
|
3648
3720
|
} catch {}
|
|
3649
3721
|
this.overrides = deepMerge(stored, patch);
|
|
3650
3722
|
await client.put({
|
|
3651
3723
|
title: CONFIG_TIDDLER,
|
|
3652
3724
|
text: JSON.stringify(this.overrides, null, 2),
|
|
3653
3725
|
type: "application/json",
|
|
3654
|
-
tags: []
|
|
3726
|
+
tags: [],
|
|
3727
|
+
...existingCreated !== void 0 ? { created: existingCreated } : {}
|
|
3655
3728
|
});
|
|
3656
3729
|
return this.get();
|
|
3657
3730
|
}
|
|
@@ -4271,7 +4344,7 @@ const HUMAN_EDITED_TAG = "human-edited";
|
|
|
4271
4344
|
/** Agent / 人类笔记的默认内容类型(TW 对无 type 的条目按 wikitext 解析)。 */
|
|
4272
4345
|
const DEFAULT_NOTE_TYPE = "text/markdown";
|
|
4273
4346
|
/**
|
|
4274
|
-
* 构造 PUT body
|
|
4347
|
+
* 构造 PUT body 时跳过的字段(身份/内容)。
|
|
4275
4348
|
*
|
|
4276
4349
|
* ⚠️ `type` **不在**这里(v0.20.1 修复):它曾被误列为跳过字段,于是
|
|
4277
4350
|
* `cleanTiddler(existing)` 把条目的内容类型丢掉,`finalTypeForWrite()` 随后又
|
|
@@ -4281,13 +4354,20 @@ const DEFAULT_NOTE_TYPE = "text/markdown";
|
|
|
4281
4354
|
* - `text/markdown` 笔记被改成 `text/vnd.tiddlywiki` → `##`/`**粗体**`/表格全按
|
|
4282
4355
|
* wikitext 解析(磁盘上 `.md` + `.meta` 也变成 `.tid`)。
|
|
4283
4356
|
* 内容类型是条目的解析方式,必须与 tags/自定义字段一样按「以已有条目为基底」保留。
|
|
4357
|
+
*
|
|
4358
|
+
* ⚠️ `created`/`modified` 也**不再**在这里(v0.22.10 修复):它们曾被当成「TW 自己
|
|
4359
|
+
* 拥有的时间戳,服务端会补」而丢弃——**TW 的服务端写路径从不补这两个字段**
|
|
4360
|
+
* (`core-server` 的 put 路由只是 `addTiddler(new $tw.Tiddler(fields,{title}))`,
|
|
4361
|
+
* `getCreationFields()`/`getModificationFields()` 只在 TW **自己的 UI** 里被调用;
|
|
4362
|
+
* 实测 `tiddlywiki_put` 新建条目落盘只有 tags/title/type)。于是插件写下的条目在
|
|
4363
|
+
* `+[!sort[modified]]` 这类页面上被 `sortTiddlers` 的 `fields[sortField] || ""`
|
|
4364
|
+
* 当空串——降序时**沉到最后一名**,看起来就像「没被收录」。现在这两个字段由
|
|
4365
|
+
* `buildWriteTiddler()` 负责写入(见 `stampTiddlerTimes`),与 TW 编辑器行为一致。
|
|
4284
4366
|
*/
|
|
4285
4367
|
const CLEAN_SKIP_FIELDS = /* @__PURE__ */ new Set([
|
|
4286
4368
|
"title",
|
|
4287
4369
|
"text",
|
|
4288
4370
|
"tags",
|
|
4289
|
-
"created",
|
|
4290
|
-
"modified",
|
|
4291
4371
|
"fields"
|
|
4292
4372
|
]);
|
|
4293
4373
|
/**
|
|
@@ -4415,11 +4495,38 @@ function finalTypeForWrite(title, tiddler, isNew = true) {
|
|
|
4415
4495
|
return { defaulted: true };
|
|
4416
4496
|
}
|
|
4417
4497
|
/**
|
|
4498
|
+
* 给要写出的条目补 `created`/`modified`(v0.22.10),语义与 TW 编辑器一致:
|
|
4499
|
+
*
|
|
4500
|
+
* | 情形 | created | modified |
|
|
4501
|
+
* |---|---|---|
|
|
4502
|
+
* | 新建(含首次写入的 `$:/`) | 当前时刻 | 当前时刻 |
|
|
4503
|
+
* | 覆盖既有条目、基底有 created | **基底原值** | 当前时刻 |
|
|
4504
|
+
* | 覆盖既有条目、基底无 created(迁移存量) | 当前时刻 | 当前时刻 |
|
|
4505
|
+
*
|
|
4506
|
+
* 为什么必须由插件来写:TW 的 `getCreationFields()`/`getModificationFields()`
|
|
4507
|
+
* 只在 **TW 自己的 UI**(`$tw.wiki.setTiddlerData`、navigation/editor widgets…)
|
|
4508
|
+
* 里被调用;服务端的 PUT 路由只做
|
|
4509
|
+
* `state.wiki.addTiddler(new $tw.Tiddler(fields, {title: title}))`,**不会补**
|
|
4510
|
+
* 这两个字段。所以「服务端会补」这个假设是错的,缺 `modified` 的条目会被
|
|
4511
|
+
* `sortTiddlers` 的 `fields[sortField] || ""` 当成空串——`!sort[modified]` 降序时
|
|
4512
|
+
* 直接沉到最后一名(用户看到的现象:新日记在「主题页·日志」里排 175/175)。
|
|
4513
|
+
*
|
|
4514
|
+
* 值是 TW 的紧凑格式 `YYYYMMDDhhmmssSSS`(UTC),与 `$tw.utils.stringifyDate()`
|
|
4515
|
+
* 逐字节一致,TW 的日期字段模块能正常 parse/stringify/显示。
|
|
4516
|
+
*/
|
|
4517
|
+
function stampTiddlerTimes(tiddler, existing, now = /* @__PURE__ */ new Date()) {
|
|
4518
|
+
const stamp = formatTiddlerDate(now);
|
|
4519
|
+
tiddler.created = (typeof existing?.created === "string" && existing.created.trim().length > 0 ? existing.created : void 0) ?? stamp;
|
|
4520
|
+
tiddler.modified = stamp;
|
|
4521
|
+
}
|
|
4522
|
+
/**
|
|
4418
4523
|
* 构造一次写要 PUT 的 tiddler。
|
|
4419
4524
|
*
|
|
4420
|
-
* - 已有条目 →
|
|
4525
|
+
* - 已有条目 → 以其为基底(标签、自定义字段、**内容类型**、**created** 全部保留);
|
|
4421
4526
|
* - 新条目 → `{title, text}` + 默认/显式标签(`$:/` 不带 agent 标签);
|
|
4422
|
-
* - `fields` 逐个覆盖;`type` 未指定时**仅新建条目**补 Markdown(`$:/`
|
|
4527
|
+
* - `fields` 逐个覆盖;`type` 未指定时**仅新建条目**补 Markdown(`$:/` 除外);
|
|
4528
|
+
* - `created`/`modified` 一律补写(v0.22.10,见 `stampTiddlerTimes`)——TW 的服务端
|
|
4529
|
+
* 写路径**不会**补这两个字段,缺它们的条目会在 `!sort[modified]` 里沉底。
|
|
4423
4530
|
*/
|
|
4424
4531
|
function buildWriteTiddler(title, text, options = {}) {
|
|
4425
4532
|
const { existing, fields } = options;
|
|
@@ -4442,6 +4549,7 @@ function buildWriteTiddler(title, text, options = {}) {
|
|
|
4442
4549
|
}
|
|
4443
4550
|
applyCustomFields(tiddler, fields);
|
|
4444
4551
|
const { defaulted } = finalTypeForWrite(title, tiddler, existing === void 0);
|
|
4552
|
+
stampTiddlerTimes(tiddler, existing, options.now);
|
|
4445
4553
|
return {
|
|
4446
4554
|
tiddler,
|
|
4447
4555
|
typeDefaulted: defaulted
|
|
@@ -4616,13 +4724,18 @@ async function openInTwEditor(client, title, text, tags, options = {}) {
|
|
|
4616
4724
|
} catch {}
|
|
4617
4725
|
if (draftTitle === void 0) draftTitle = `${canonical} ${Date.now()}`;
|
|
4618
4726
|
}
|
|
4619
|
-
if (!draftExists || text.trim().length > 0)
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4727
|
+
if (!draftExists || text.trim().length > 0) {
|
|
4728
|
+
const draftCreated = typeof existing?.created === "string" && existing.created.trim().length > 0 ? existing.created : void 0;
|
|
4729
|
+
await client.put({
|
|
4730
|
+
title: draftTitle,
|
|
4731
|
+
text: draftText,
|
|
4732
|
+
"draft.of": title,
|
|
4733
|
+
"draft.title": title,
|
|
4734
|
+
type: draftType,
|
|
4735
|
+
...draftCreated !== void 0 ? { created: draftCreated } : {},
|
|
4736
|
+
modified: formatTiddlerDate(/* @__PURE__ */ new Date())
|
|
4737
|
+
});
|
|
4738
|
+
}
|
|
4626
4739
|
return {
|
|
4627
4740
|
title,
|
|
4628
4741
|
draftTitle
|
|
@@ -8399,19 +8512,15 @@ function registerTiddlywikiTools(ctx, deps) {
|
|
|
8399
8512
|
if (text.length === 0) continue;
|
|
8400
8513
|
const rewritten = rewriteRefs(text, oldTitle, newTitle);
|
|
8401
8514
|
if (rewritten.count > 0) {
|
|
8402
|
-
|
|
8403
|
-
|
|
8404
|
-
text: rewritten.text
|
|
8405
|
-
});
|
|
8515
|
+
const { tiddler } = buildWriteTiddler(t.title, rewritten.text, { existing: t });
|
|
8516
|
+
await wiki.put(tiddler);
|
|
8406
8517
|
refsUpdated += rewritten.count;
|
|
8407
8518
|
refsTiddlers++;
|
|
8408
8519
|
}
|
|
8409
8520
|
}
|
|
8410
8521
|
}
|
|
8411
|
-
|
|
8412
|
-
|
|
8413
|
-
title: newTitle
|
|
8414
|
-
});
|
|
8522
|
+
const { tiddler: renamed } = buildWriteTiddler(newTitle, existing.text ?? "", { existing });
|
|
8523
|
+
await wiki.put(renamed);
|
|
8415
8524
|
let deleteFailed;
|
|
8416
8525
|
try {
|
|
8417
8526
|
await wiki.delete(oldTitle);
|
|
@@ -8483,9 +8592,11 @@ function registerTiddlywikiTools(ctx, deps) {
|
|
|
8483
8592
|
}
|
|
8484
8593
|
const trashTitle = trashTitleFor(args.title);
|
|
8485
8594
|
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
8595
|
+
const { tiddler: trashTiddler } = buildWriteTiddler(trashTitle, existing.text ?? "", { existing });
|
|
8596
|
+
trashTiddler.created = existing.created ?? trashTiddler.created;
|
|
8597
|
+
trashTiddler.modified = existing.modified ?? trashTiddler.modified;
|
|
8486
8598
|
await wiki.put({
|
|
8487
|
-
...
|
|
8488
|
-
title: trashTitle,
|
|
8599
|
+
...trashTiddler,
|
|
8489
8600
|
"trash-of": args.title,
|
|
8490
8601
|
"trash-at": at
|
|
8491
8602
|
});
|
|
@@ -8575,13 +8686,12 @@ function registerTiddlywikiTools(ctx, deps) {
|
|
|
8575
8686
|
throw new Error(`回收站条目「${match.trash}」已不存在(索引已清理)`);
|
|
8576
8687
|
}
|
|
8577
8688
|
if (await wiki.get(match.of) !== void 0) throw new Error(`无法恢复:标题「${match.of}」已被占用,请先处理现有条目`);
|
|
8578
|
-
const restored =
|
|
8689
|
+
const { tiddler: restored } = buildWriteTiddler(match.of, stored.text ?? "", { existing: stored });
|
|
8690
|
+
restored.created = stored.created ?? restored.created;
|
|
8691
|
+
restored.modified = stored.modified ?? restored.modified;
|
|
8579
8692
|
delete restored["trash-of"];
|
|
8580
8693
|
delete restored["trash-at"];
|
|
8581
|
-
await wiki.put(
|
|
8582
|
-
...restored,
|
|
8583
|
-
title: match.of
|
|
8584
|
-
});
|
|
8694
|
+
await wiki.put(restored);
|
|
8585
8695
|
await wiki.delete(match.trash);
|
|
8586
8696
|
await writeTrashIndex(wiki, indexed.filter((entry) => entry.trash !== match.trash));
|
|
8587
8697
|
deps.autoCommit();
|
|
@@ -10036,6 +10146,6 @@ function apply(ctx, rawConfig = {}) {
|
|
|
10036
10146
|
}, "dsh-tiddlywiki: host teardown");
|
|
10037
10147
|
}
|
|
10038
10148
|
//#endregion
|
|
10039
|
-
export { AGENT_WRITTEN_TAG, ALL_ARTICLES_MARKER_TITLE, ALL_ARTICLES_TEXT, ALL_ARTICLES_TITLE, 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, ConfigStore, DEFAULT_NOTE_TYPE, DEFAULT_PROMPT_MODE, DOC_NOTE_TAG, DOC_NOTE_TEXT, DOC_NOTE_TITLE, DSH_DOCS_TAG, 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, SEED_DEFS, SEED_MARKER_VERSION, SEND_TO_AGENT_BUNDLE_TEXT, SEND_TO_AGENT_MARKER_TITLE, SEND_TO_AGENT_PLUGIN_TITLE, SESSION_SUMMARY_PREFIX, 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, WikiServer, WriteConflictError, apply, assertNoConflict, assertPublicImageUrl, awaitReady, buildBinaryTiddler, buildClipTiddler, buildImageNoteTiddler, buildPromptText, buildWriteTiddler, bundledCatalog, checkAllSeeds, cleanTiddler, clearLocationState, deepMerge, defaultLocationStateFile, defineTool, describePrompt, docNoteText, downloadClipImage, dshHomePath, ensureLanguage, ensurePlugin, escapeInline, escapePromptBraces, expandEnvPath, flattenTiddlerFields, flushPendingWrites, hashText, hostAllowed, imageExtensionForMime, inject, isBinaryType, isPrivateAddress, isSafeUrl, listWikiCandidates, locationPath, maskConfigSecrets, name, needsRestartAfterSeeds, normalizeLocation, normalizePromptMode, normalizePromptPreview, normalizeReadyTimeoutMs, normalizeThemes, openInTwEditor, parseClipPayload, parseSeedMarker, pickImageMime, readActiveThemeName, readLocationState, readSeedMarker, readWikiInfo, readyHardTimeoutMs, registerAdminRoutes, registerRoutes, registerTiddlywikiTools, removeSeedById, resolveClipTitle, resolveTwRoot, runAllSeeds, runSeedById, sanitizeTwFragment, seedAllArticles, seedClipBridge, seedDocNote, seedHomeIndex, seedMenubarTheme, seedRenderRoute, seedSendToAgent, seedStarterDocs, seedUiStyles, stripMaskedSecrets, switchWiki, tiddlywikiToolSummary, toolSignatureLines, unseedClipBridge, waitForFileWrite, writeLocationState, writeSeedMarker, writeSessionSummary, writeWikiInfo };
|
|
10149
|
+
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, ConfigStore, DEFAULT_NOTE_TYPE, DEFAULT_PROMPT_MODE, DOC_NOTE_TAG, DOC_NOTE_TEXT, DOC_NOTE_TITLE, DSH_DOCS_TAG, 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, SEED_DEFS, SEED_MARKER_VERSION, SEND_TO_AGENT_BUNDLE_TEXT, SEND_TO_AGENT_MARKER_TITLE, SEND_TO_AGENT_PLUGIN_TITLE, SESSION_SUMMARY_PREFIX, 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, WikiServer, WriteConflictError, apply, assertNoConflict, assertPublicImageUrl, awaitReady, buildBinaryTiddler, buildClipTiddler, buildImageNoteTiddler, buildPromptText, buildWriteTiddler, bundledCatalog, checkAllSeeds, cleanTiddler, clearLocationState, deepMerge, defaultLocationStateFile, defineTool, describePrompt, docNoteText, downloadClipImage, dshHomePath, ensureLanguage, ensurePlugin, ensureTiddlerTimestamps, escapeInline, escapePromptBraces, expandEnvPath, flattenTiddlerFields, flushPendingWrites, formatTiddlerDate, hashText, hostAllowed, imageExtensionForMime, inject, isBinaryType, isPrivateAddress, isSafeUrl, listWikiCandidates, locationPath, maskConfigSecrets, name, needsRestartAfterSeeds, normalizeLocation, normalizePromptMode, normalizePromptPreview, normalizeReadyTimeoutMs, normalizeThemes, openInTwEditor, parseClipPayload, parseSeedMarker, parseTiddlerDate, pickImageMime, readActiveThemeName, readLocationState, readSeedMarker, readWikiInfo, readyHardTimeoutMs, registerAdminRoutes, registerRoutes, registerTiddlywikiTools, removeSeedById, resolveClipTitle, resolveTwRoot, runAllSeeds, runSeedById, sanitizeTwFragment, seedAllArticles, seedClipBridge, seedDocNote, seedHomeIndex, seedMenubarTheme, seedRenderRoute, seedSendToAgent, seedStarterDocs, seedUiStyles, stripMaskedSecrets, switchWiki, tiddlywikiToolSummary, toIsoDateString, toolSignatureLines, unseedClipBridge, waitForFileWrite, writeLocationState, writeSeedMarker, writeSessionSummary, writeWikiInfo };
|
|
10040
10150
|
|
|
10041
10151
|
//# sourceMappingURL=index.js.map
|