dsh-tiddlywiki 0.22.0 → 0.22.2

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/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["httpsGet","httpGet","createServer"],"sources":["../src/host/git.ts","../src/host/tw-api.ts","../src/host/wiki.ts","../src/host/session-summary.ts","../src/host/http.ts","../src/host/sanitize.ts","../src/host/seed-util.ts","../src/host/prompt.ts","../src/host/seed-notes.ts","../src/host/seed-starter-docs.ts","../src/host/seed-send-to-agent.ts","../src/host/seed-home.ts","../src/host/seed-all-articles.ts","../src/host/seed-ui-styles.ts","../src/host/seed-menubar-theme.ts","../src/host/seed-clip-bridge.ts","../src/host/seed-render.ts","../src/host/config.ts","../src/host/seeds.ts","../src/host/text-util.ts","../src/host/write-policy.ts","../src/host/routes.ts","../src/host/admin.ts","../src/host/clip-bridge.ts","../src/sdk.ts","../src/host/tools.ts","../src/host/wiki-location.ts","../src/host/wiki-switch.ts","../src/index.ts"],"sourcesContent":["/**\n * Git face (design doc §7, D11) — the ONLY place dsh-tiddlywiki shells out to\n * git. The wiki folder itself is the repository; the folder is pure text\n * (FileSystemAdaptor writes one file per tiddler), so git is a natural sync /\n * backup channel.\n *\n * Sync model is the single-thread alternating one:\n * 1. start of work: `git pull --rebase --autostash`\n * 2. end of work: `git add -A && git commit && git push`\n * 3. auto-commit: debounced 60s commit after wiki writes (AutoCommitter)\n *\n * Conflict policy (user-confirmed, no complex handling): a rebase conflict\n * (only reachable by \"forgot to pull before writing\") → `git rebase --abort`\n * + report the unmerged files. Never auto-merge data.\n *\n * @module dsh-tiddlywiki/host/git\n */\nimport { execFile } from 'node:child_process'\nimport { promisify } from 'node:util'\n\nconst execFileP = promisify(execFile)\n\n/** Timeout for quick read-only queries. */\nconst QUICK_TIMEOUT_MS = 5_000\n\n/** Timeout for structural/network operations. */\nconst HEAVY_TIMEOUT_MS = 60_000\n\nexport interface ExecResult { ok: boolean; stdout: string; stderr: string }\nexport type ExecFn = (args: string[], options: { cwd?: string; timeout?: number }) => Promise<ExecResult>\n\n/** Default exec layer: run `git <args>` under a cwd with a timeout. */\nconst defaultExec: ExecFn = async (args, options) => {\n try {\n const { stdout, stderr } = await execFileP('git', args, {\n cwd: options.cwd,\n timeout: options.timeout ?? QUICK_TIMEOUT_MS,\n windowsHide: true,\n encoding: 'utf8',\n maxBuffer: 32 * 1024 * 1024,\n })\n return { ok: true, stdout, stderr }\n } catch (err) {\n const e = err as { stdout?: string; stderr?: string; message?: string }\n return { ok: false, stdout: e.stdout ?? '', stderr: e.stderr ?? String(e.message ?? err) }\n }\n}\n\nexport interface GitStatusView {\n exists: boolean\n branch: string\n dirty: boolean\n dirtyFiles: string[]\n remote: string\n lastCommit?: string\n ahead?: number\n behind?: number\n}\n\nexport interface GitActionResult { ok: boolean; message: string; conflictFiles?: string[] }\n\nfunction parseCount(line: string, re: RegExp): number | undefined {\n const m = line.match(re)\n return m === null ? undefined : Number(m[1])\n}\n\nexport class GitFace {\n /**\n * FIFO mutex for the index-touching operations (`commit` and `pull\n * --rebase`). The debounced AutoCommitter can fire while an explicit\n * `tiddlywiki_git_sync` is rebasing; both would fight over\n * `.git/index.lock` and intermittently fail. Per-instance, so a second\n * GitFace (e.g. the settings page's status probe) is unaffected.\n */\n private lock: Promise<unknown> = Promise.resolve()\n\n private withLock<T>(fn: () => Promise<T>): Promise<T> {\n const run = this.lock.then(fn, fn)\n this.lock = run.then(() => undefined, () => undefined)\n return run\n }\n\n constructor(private readonly exec: ExecFn = defaultExec) {}\n\n async isRepo(dir: string): Promise<boolean> {\n const r = await this.exec(['rev-parse', '--is-inside-work-tree'], { cwd: dir, timeout: 2_000 })\n return r.ok && r.stdout.trim() === 'true'\n }\n\n async init(dir: string, branch = 'main'): Promise<boolean> {\n const r = await this.exec(['init', '-b', branch], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n return r.ok\n }\n\n /** Initial commit for a fresh repo (tolerates an empty index). */\n async initialCommit(dir: string): Promise<boolean> {\n await this.exec(['add', '-A'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n const r = await this.exec([...identity(), 'commit', '-m', 'chore(dsh-tiddlywiki): initial commit'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n return r.ok || /nothing to commit/.test(r.stderr + r.stdout)\n }\n\n /**\n * Stage everything and commit; a local identity is always provided so the\n * plugin never depends on the machine's global git config. Returns whether\n * a commit actually happened. Serialized against `pull()` (index.lock).\n */\n commit(dir: string, message: string): Promise<{ committed: boolean; message: string }> {\n return this.withLock(() => this.commitUnlocked(dir, message))\n }\n\n private async commitUnlocked(dir: string, message: string): Promise<{ committed: boolean; message: string }> {\n await this.exec(['add', '-A'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n const staged = await this.exec(['diff', '--cached', '--quiet'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\n // `diff --cached --quiet` exits 0 when nothing is staged → nothing to commit.\n if (staged.ok) return { committed: false, message: 'nothing to commit' }\n const r = await this.exec([...identity(), 'commit', '-m', message], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n return r.ok\n ? { committed: true, message }\n : { committed: false, message: `commit failed: ${(r.stderr.trim() || r.stdout.trim()).slice(0, 500)}` }\n }\n\n async status(dir: string): Promise<GitStatusView> {\n const empty: GitStatusView = { exists: false, branch: '', dirty: false, dirtyFiles: [], remote: '' }\n const r = await this.exec(['status', '--porcelain', '-b'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\n if (!r.ok) return empty\n const lines = r.stdout.split('\\n').filter((l) => l.length > 0)\n const branchLine = lines.find((l) => l.startsWith('## '))\n const branch = branchLine === undefined ? '' : branchLine.slice(3).split('...')[0] ?? ''\n const ahead = branchLine === undefined ? undefined : parseCount(branchLine, /ahead (\\d+)/)\n const behind = branchLine === undefined ? undefined : parseCount(branchLine, /behind (\\d+)/)\n const dirty = lines.some((l) => !l.startsWith('## '))\n const dirtyFiles = lines.filter((l) => !l.startsWith('## ')).map((l) => l.slice(3).trim()).filter(Boolean)\n const remoteR = await this.exec(['remote', '-v'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\n const remote = remoteR.ok ? remoteR.stdout.split('\\n').map((l) => l.trim()).find(Boolean) ?? '' : ''\n const lastR = await this.exec(['log', '-1', '--format=%h %s'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\n const lastCommit = lastR.ok && lastR.stdout.trim().length > 0 ? lastR.stdout.trim() : undefined\n return { exists: true, branch, dirty, dirtyFiles, remote, ...(lastCommit !== undefined ? { lastCommit } : {}), ...(ahead !== undefined ? { ahead } : {}), ...(behind !== undefined ? { behind } : {}) }\n }\n\n /** `git pull --rebase --autostash`; on conflict: abort + report files.\n * On success, `changed: true` means HEAD actually moved (files came in /\n * commits were replayed) — callers use it to decide whether a running TW\n * child needs a restart to drop its stale in-memory snapshot.\n * Serialized against `commit()` (index.lock). */\n pull(dir: string): Promise<GitActionResult & { changed?: boolean }> {\n return this.withLock(() => this.pullUnlocked(dir))\n }\n\n private async pullUnlocked(dir: string): Promise<GitActionResult & { changed?: boolean }> {\n const before = await this.exec(['rev-parse', 'HEAD'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\n const beforeHead = before.ok ? before.stdout.trim() : ''\n const r = await this.exec(['pull', '--rebase', '--autostash'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n if (r.ok) {\n const after = await this.exec(['rev-parse', 'HEAD'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\n const afterHead = after.ok ? after.stdout.trim() : ''\n const changed = beforeHead.length > 0 && beforeHead !== afterHead\n return { ok: true, message: r.stdout.trim() || 'pull ok', ...(changed ? { changed: true } : {}) }\n }\n const conflictFiles = await this.unmergedFiles(dir)\n await this.exec(['rebase', '--abort'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n const reason = (r.stderr.trim() || r.stdout.trim()).slice(0, 500)\n return { ok: false, message: conflictFiles.length > 0 ? `conflict in ${conflictFiles.join(', ')} (rebase aborted): ${reason}` : `pull failed: ${reason}`, ...(conflictFiles.length > 0 ? { conflictFiles } : {}) }\n }\n\n async push(dir: string): Promise<GitActionResult> {\n const r = await this.exec(['push'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n return r.ok\n ? { ok: true, message: r.stdout.trim() || 'push ok' }\n : { ok: false, message: (r.stderr.trim() || r.stdout.trim()).slice(0, 500) }\n }\n\n /** First push with upstream tracking (called once after a remote is set). */\n async firstPush(dir: string): Promise<GitActionResult> {\n const branch = (await this.status(dir)).branch || 'main'\n const r = await this.exec(['push', '-u', 'origin', branch], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n return r.ok\n ? { ok: true, message: `pushed ${branch} to origin` }\n : { ok: false, message: (r.stderr.trim() || r.stdout.trim()).slice(0, 500) }\n }\n\n /** `git fetch` (no remote configured → failure reported by the caller). */\n async fetch(dir: string): Promise<GitActionResult> {\n const r = await this.exec(['fetch'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n return r.ok\n ? { ok: true, message: r.stdout.trim() || 'fetch ok' }\n : { ok: false, message: (r.stderr.trim() || r.stdout.trim()).slice(0, 500) }\n }\n\n /**\n * Restore the given files from the freshly fetched remote HEAD (FETCH_HEAD)\n * into the working tree + index — the \"keep remote version\" half of\n * tiddler-granular conflict resolution. Callers must `git fetch` first.\n */\n async checkoutFetchHead(dir: string, files: string[]): Promise<GitActionResult> {\n if (files.length === 0) return { ok: true, message: 'no files given' }\n const r = await this.exec(['checkout', 'FETCH_HEAD', '--', ...files], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n return r.ok\n ? { ok: true, message: `已从远端检出 ${files.length} 个文件` }\n : { ok: false, message: (r.stderr.trim() || r.stdout.trim()).slice(0, 500) }\n }\n\n /** Ensure `origin` points at `url` (add or set-url). */\n async ensureRemote(dir: string, url: string): Promise<GitActionResult> {\n const cur = await this.exec(['remote', 'get-url', 'origin'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\n if (cur.ok) {\n if (cur.stdout.trim() === url) return { ok: true, message: 'remote origin already set' }\n const set = await this.exec(['remote', 'set-url', 'origin', url], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n return set.ok ? { ok: true, message: `remote origin → ${url}` } : { ok: false, message: set.stderr.trim() || 'remote set-url failed' }\n }\n const add = await this.exec(['remote', 'add', 'origin', url], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n return add.ok ? { ok: true, message: `remote origin → ${url}` } : { ok: false, message: add.stderr.trim() || 'remote add failed' }\n }\n\n private async unmergedFiles(dir: string): Promise<string[]> {\n const r = await this.exec(['diff', '--name-only', '--diff-filter=U'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\n return r.ok ? r.stdout.split('\\n').map((l) => l.trim()).filter(Boolean) : []\n }\n}\n\n/** Always-on local identity so commits never depend on global git config. */\nfunction identity(): string[] {\n return ['-c', 'user.name=dsh-tiddlywiki', '-c', 'user.email=dsh-tiddlywiki@local']\n}\n\nexport interface AutoCommitterOptions {\n git: GitFace\n dir: string\n enabled: boolean\n debounceMs: number\n message: () => string\n onError?: (err: unknown) => void\n /** Observability hook: fired after each flush attempt (used by selftest to\n * assert the debounce really committed). */\n onCommit?: (info: { committed: boolean; message: string }) => void\n}\n\n/**\n * Debounced auto-committer: every wiki write calls `touch()`; the commit\n * fires once writes settle for `debounceMs`. Disable with git.autoCommit.\n */\nexport class AutoCommitter {\n private timer: NodeJS.Timeout | undefined\n private disposed = false\n\n constructor(private readonly options: AutoCommitterOptions) {}\n\n touch(): void {\n if (!this.options.enabled || this.disposed) return\n if (this.timer !== undefined) clearTimeout(this.timer)\n this.timer = setTimeout(() => { void this.flush() }, this.options.debounceMs)\n }\n\n /** Run a commit now (also cancels the pending debounce). */\n async flush(): Promise<void> {\n if (this.timer !== undefined) {\n clearTimeout(this.timer)\n this.timer = undefined\n }\n if (!this.options.enabled || this.disposed) return\n try {\n const result = await this.options.git.commit(this.options.dir, this.options.message())\n this.options.onCommit?.(result)\n } catch (err) {\n this.options.onError?.(err)\n }\n }\n\n dispose(): void {\n this.disposed = true\n if (this.timer !== undefined) {\n clearTimeout(this.timer)\n this.timer = undefined\n }\n }\n}\n","/**\n * TiddlyWeb REST client (design doc §5) — the ONLY way every writer reaches\n * the wiki (quick notes, agent tools, editor saves all go through the TW\n * service, D1), so there is never a second write path.\n *\n * ROUTES ARE EMPIRICALLY VERIFIED against tiddlywiki 5.4.1's core-server\n * (`core-server/server/routes/`):\n * GET /recipes/default/tiddlers.json[?exclude=...] list (skinny)\n * GET /recipes/default/tiddlers/<title> read one (404 absent)\n * PUT /recipes/default/tiddlers/<title> write one (204)\n * DELETE /bags/default/tiddlers/<title> delete one (204)\n * Writes require the `X-Requested-With: TiddlyWiki` header (TW CSRF), which\n * this client always sends. Tags arrive as a whitespace-joined STRING and are\n * normalized to arrays here.\n *\n * SEARCH (R2): the server blocks arbitrary `filter=` queries with 403 unless\n * the exact filter is whitelisted in $:/config/Server/ExternalFilters. So\n * `search()` fetches the TEXT-BEARING listing WITH text (TEXT_LIST_FILTER +\n * `?exclude=` a sentinel, whitelist self-healed on first 403) and matches\n * locally — one request, no 403, no per-tiddler round-trips, and binary\n * tiddlers (images etc.) never ship their base64 payload (v0.16.20).\n *\n * @module dsh-tiddlywiki/host/tw-api\n */\n\n/** A tiddler's readable fields (loose on purpose). */\nexport interface Tiddler {\n title: string\n text?: string\n tags?: string[]\n type?: string\n created?: string\n modified?: string\n /** Extra custom fields returned by the server are folded under `fields`. */\n fields?: Record<string, unknown>\n [key: string]: unknown\n}\n\nconst REQUEST_TIMEOUT_MS = 10_000\n\n/** TW's CSRF gate: writes must carry this header (TW's own UI always does). */\nconst CSRF_HEADER = { 'x-requested-with': 'TiddlyWiki' }\n\n/** Sentinel `exclude` value: excludes nothing, so `text` stays in the list. */\nconst LIST_WITH_TEXT_EXCLUDE = '__dsh_tw_none__'\n\n/**\n * Recipe-list filter that keeps ONLY text-bearing tiddlers (notes): no `type`\n * field at all, or a `text/*` type. Binary tiddlers (images/audio/video/fonts/\n * PDF/zip…) carry their payload as a base64 `text` field — on a big wiki (e.g.\n * thousands of scanned book pages) the full listing serializes 500+MB of base64\n * and times every agent read out. This filter drops them SERVER-SIDE so the\n * listing stays ~6MB (v0.16.20, verified on a 2418-tiddler wiki:\n * 515MB/17s → 6MB/0.3s).\n *\n * TW 5.4.1 filter facts that matter here (all verified empirically):\n * - `prefix`/`match` operators match the TITLE only — field matching needs\n * `regexp:<field>` / `field:<field>`.\n * - `[has[type]]` = \"has a non-empty type field\"; `[has:type[]]` is a DIFFERENT\n * call (suffix \"type\" + empty operand) that matches everything.\n * - space-separated operations UNION (no prefix = \"or\"); `+` means\n * intersection.\n * - negated `regexp:type`/`field:type` DROP type-less tiddlers (their field\n * string is null), so \"exclude binary\" must be written as the positive\n * \"no type OR text-ish\" union below.\n *\n * FILENAME BUDGET (Windows, verified): the whitelist tiddler below is stored\n * as a .tid file whose name contains the ENTIRE filter string, so the filter\n * must stay short — a ~180-char filter produced a ~217-char filename that\n * broke `git add` on Windows (\"Filename too long\", MAX_PATH) and killed the\n * auto-committer. 86 chars keeps the file at ~123 chars even on a deep wiki\n * path. (Consequence: application/json tiddlers are not searchable — on this\n * wiki there are none, and they are config/data tiddlers, not notes.)\n */\nexport const TEXT_LIST_FILTER = [\n '[all[tiddlers]!is[system]!has[type]]',\n '[all[tiddlers]!is[system]regexp:type[(?i)^text/]]',\n].join(' ')\n\n/**\n * 真正「没有 type 字段」的条目(v0.19.1)。\n *\n * 必须靠过滤器判定:TW 服务端在 listing 与单条 GET 里都会给无 type 的条目\n * **补上** `text/vnd.tiddlywiki`(`tiddlerFields.type = tiddlerFields.type ||\n * \"text/vnd.tiddlywiki\"`),所以响应里的 `type` 永远非空,客户端无法区分\n * 「本来是 wikitext」与「压根没有 type」。过滤器在服务端按真实字段求值,返回\n * 的标题就是真的没有 type 的那些。串长 36 字符(白名单 tiddler 文件名 = 整个\n * filter,必须短)。\n */\nexport const MISSING_TYPE_FILTER = '[all[tiddlers]!is[system]!has[type]]'\n\n/** TiddlyWeb blocks every non-default filter with 403 unless the EXACT filter\n * string is whitelisted at `$:/config/Server/ExternalFilters/<filter>` = \"yes\".\n * This client writes that tiddler once on 403 (self-heal, idempotent) and\n * retries; the tiddler lives in the wiki and travels with its git history. */\nfunction externalFilterWhitelistTitle(filter: string): string {\n return `$:/config/Server/ExternalFilters/${filter}`\n}\n\n/** Binary MIME type prefixes — their `text` field is base64 payload, not content. */\nconst BINARY_TYPE_PREFIXES = ['image/', 'audio/', 'video/', 'font/']\nconst BINARY_TYPE_EXACT = new Set([\n 'application/octet-stream', 'application/pdf', 'application/zip', 'application/gzip',\n 'application/x-gzip', 'application/x-7z-compressed', 'application/x-rar-compressed',\n 'application/epub+zip', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n])\n\n/** True when a tiddler's `type` marks it as binary (base64 in `text`). */\nexport function isBinaryType(type: string | undefined): boolean {\n if (typeof type !== 'string' || type.length === 0) return false\n return BINARY_TYPE_PREFIXES.some((p) => type.startsWith(p)) || BINARY_TYPE_EXACT.has(type)\n}\n\n/**\n * Parse a tiddler date field. TW's REST listing returns dates in the COMPACT\n * form (`20260101000000000` = YYYYMMDDhhmmssSSS, UTC), NOT ISO — the old code\n * fed that to `new Date()`, got Invalid Date, and therefore made every\n * `since`-filtered search/recent return nothing (v0.19.0). Both forms are\n * accepted here; undefined = absent/unparseable.\n */\nexport function parseTiddlerDate(value: unknown): number | undefined {\n if (typeof value !== 'string' || value.length === 0) return undefined\n const compact = /^(\\d{4})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{3})$/.exec(value.trim())\n if (compact !== null) {\n return Date.UTC(\n Number(compact[1]), Number(compact[2]) - 1, Number(compact[3]),\n Number(compact[4]), Number(compact[5]), Number(compact[6]), Number(compact[7]),\n )\n }\n const ms = new Date(value.trim()).getTime()\n return Number.isNaN(ms) ? undefined : ms\n}\n\n/** Normalize a tiddler date field to ISO-8601 (for display), or null. */\nexport function toIsoDateString(value: unknown): string | null {\n const ms = parseTiddlerDate(value)\n return ms === undefined ? null : new Date(ms).toISOString()\n}\n\n/** Split TW's whitespace-joined tags string into an array. */\nfunction normalizeTags(tags: unknown): string[] | undefined {\n if (tags === undefined) return undefined\n if (Array.isArray(tags)) return tags.map(String)\n if (typeof tags === 'string') {\n const parts = tags.trim().split(/\\s+/).filter(Boolean)\n return parts.length > 0 ? parts : []\n }\n return []\n}\n\n/** Normalize a raw server tiddler (tags string → array, unknown fields nested). */\nfunction normalizeTiddler(raw: Record<string, unknown>): Tiddler {\n const out = { ...raw } as Tiddler\n const tags = normalizeTags(raw.tags)\n if (tags !== undefined) out.tags = tags\n return out\n}\n\nexport class TiddlyWebClient {\n /** Preemptive Basic credentials, when the wiki runs in locked-down mode. */\n private readonly authHeader: string | undefined\n\n /**\n * Short-TTL cache for the TEXT-bearing listing. `search` / `recent` /\n * `listTags`-style calls each pull the whole listing (≈6MB on a 5k-tiddler\n * wiki); a burst of tool calls re-downloaded and re-parsed it every time.\n * Cached as a PROMISE so concurrent callers share one request, invalidated by\n * every write this client performs (v0.19.0).\n */\n private textListing: { at: number; value: Promise<Tiddler[]> } | undefined\n /**\n * Same short-TTL + in-flight-merge cache for the SKINNY listings (v0.19.1):\n * `/tags` (`list(TEXT_LIST_FILTER, false)`) and the lint title/dedup listings\n * each pull the whole title list; without a cache every `/status`-adjacent\n * poll and every lint re-downloaded + re-parsed it. Keyed by filter string\n * ('' = server default listing).\n */\n private readonly skinnyListings = new Map<string, { at: number; value: Promise<Tiddler[]> }>()\n private static readonly TEXT_LISTING_TTL_MS = 2_000\n\n /** Drop the listing caches — called after any write so reads never go stale. */\n private invalidateListing(): void {\n this.textListing = undefined\n this.skinnyListings.clear()\n }\n\n constructor(private readonly baseUrl: string, auth?: { username?: string; password?: string }) {\n const username = typeof auth?.username === 'string' ? auth.username : ''\n if (username.length > 0) {\n const password = typeof auth?.password === 'string' ? auth.password : ''\n this.authHeader = `Basic ${Buffer.from(`${username}:${password}`, 'utf8').toString('base64')}`\n }\n }\n\n private async request(path: string, init?: RequestInit): Promise<Response> {\n const headers: Record<string, string> = { ...((init?.headers as Record<string, string> | undefined) ?? {}) }\n // The plugin spawns TW with `readers`/`writers` whenever auth.username is\n // configured, so EVERY request (reads included) needs the credentials —\n // otherwise the whole client 401s. TW's BasicAuthenticator accepts a\n // preemptive Authorization header (no challenge round-trip needed).\n if (this.authHeader !== undefined) headers.authorization = this.authHeader\n return fetch(`${this.baseUrl}${path}`, {\n ...init,\n headers,\n signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),\n })\n }\n\n /** GET /status → { username, anonymous, space, tiddlywiki_version, ... }. */\n async status(): Promise<Record<string, unknown>> {\n const res = await this.request('/status')\n if (!res.ok) throw new Error(`TiddlyWeb /status HTTP ${res.status}`)\n return res.json() as Promise<Record<string, unknown>>\n }\n\n /** Read one tiddler; undefined when it does not exist (404). */\n async get(title: string): Promise<Tiddler | undefined> {\n const res = await this.request(`/recipes/default/tiddlers/${encodeURIComponent(title)}`)\n if (res.status === 404) return undefined\n if (!res.ok) throw new Error(`TiddlyWeb GET /recipes/default/tiddlers/${title} HTTP ${res.status}`)\n return normalizeTiddler((await res.json()) as Record<string, unknown>)\n }\n\n /**\n * Render a tiddler (or raw wiki text) to an HTML fragment through TW's own\n * `/render` server route — the bundle the `render-route` seed installs.\n *\n * READ-ONLY, but the route is a POST (handler contract) and TW's server gates\n * POST behind the writer CSRF header, so the CSRF header is sent like a write.\n * Throws on 404 (`notFound`) so callers can distinguish \"no such tiddler\".\n */\n async render(body: { title?: string; text?: string; type?: string; contextTitle?: string; parseAsInline?: boolean }): Promise<string> {\n const res = await this.request('/render', {\n method: 'POST',\n headers: { 'content-type': 'application/json', ...CSRF_HEADER },\n body: JSON.stringify(body),\n })\n if (res.status === 404) throw new Error('TiddlyWeb POST /render HTTP 404: tiddler not found')\n if (!res.ok) {\n const detail = await res.text().catch(() => '')\n throw new Error(`TiddlyWeb POST /render HTTP ${res.status}: ${detail.slice(0, 200)}`)\n }\n return res.text()\n }\n\n /** Write (create or overwrite) one tiddler via PUT (204 on success). */\n async put(tiddler: Tiddler): Promise<Tiddler> {\n const title = tiddler.title\n const res = await this.request(`/recipes/default/tiddlers/${encodeURIComponent(title)}`, {\n method: 'PUT',\n headers: { 'content-type': 'application/json', ...CSRF_HEADER },\n body: JSON.stringify(tiddler),\n })\n this.invalidateListing()\n if (!res.ok) {\n const detail = await res.text().catch(() => '')\n throw new Error(`TiddlyWeb PUT /recipes/default/tiddlers/${title} HTTP ${res.status}: ${detail.slice(0, 300)}`)\n }\n return tiddler\n }\n\n /** Delete one tiddler via the bags route (204); a missing one is a no-op. */\n async delete(title: string): Promise<void> {\n const res = await this.request(`/bags/default/tiddlers/${encodeURIComponent(title)}`, {\n method: 'DELETE',\n headers: CSRF_HEADER,\n })\n this.invalidateListing()\n if (res.status === 404) return\n if (!res.ok) throw new Error(`TiddlyWeb DELETE /bags/default/tiddlers/${title} HTTP ${res.status}`)\n }\n\n /**\n * List tiddlers via the default server filter. Arbitrary `filter=` queries\n * are blocked by the server (403) unless whitelisted, so callers needing a\n * subset should use search(); a supplied filter that is 403-blocked falls\n * back to the text-bearing filter.\n *\n * `includeText=true` returns only TEXT-BEARING tiddlers WITH their `text`\n * (never the base64 payloads of binary tiddlers): on 403 the whitelist\n * tiddler for TEXT_LIST_FILTER is written once and the request retried\n * (self-heal); if that write fails (read-only wiki) it degrades to the\n * skinny listing (title/tags matchable, empty snippets).\n */\n async list(filter?: string, includeText = false): Promise<Tiddler[]> {\n if (!includeText) {\n // Skinny listings share the short-TTL promise cache (v0.19.1): `/tags` and\n // the lint title listings are re-requested by every poll/lint, and each\n // miss transferred + parsed the whole title list.\n const key = filter ?? ''\n const cached = this.skinnyListings.get(key)\n if (cached !== undefined && Date.now() - cached.at < TiddlyWebClient.TEXT_LISTING_TTL_MS) return cached.value\n const pending = this.fetchListing(filter)\n this.skinnyListings.set(key, { at: Date.now(), value: pending })\n pending.catch(() => { if (this.skinnyListings.get(key)?.value === pending) this.skinnyListings.delete(key) })\n return pending\n }\n const explicit = filter !== undefined && filter.length > 0\n // Explicit filter: the caller asked for a SPECIFIC set — never silently\n // answer with a different one (the old code fell back to TEXT_LIST_FILTER,\n // i.e. it returned set B for a request of set A).\n if (explicit) return this.fetchTextListing(filter as string, false)\n const cached = this.textListing\n if (cached !== undefined && Date.now() - cached.at < TiddlyWebClient.TEXT_LISTING_TTL_MS) return cached.value\n const pending = this.fetchTextListing(TEXT_LIST_FILTER, true)\n this.textListing = { at: Date.now(), value: pending }\n // Never cache a rejection: the next call must retry the network.\n pending.catch(() => { if (this.textListing?.value === pending) this.textListing = undefined })\n return pending\n }\n\n /**\n * Fetch a text-bearing listing for `filter`, self-healing the external-filter\n * whitelist on 403. `allowSkinnyFallback` is true only for the plugin's OWN\n * default filter (TEXT_LIST_FILTER), where degrading to the skinny listing is\n * a documented last resort on a read-only wiki; an explicit caller filter\n * propagates the 403 instead.\n */\n private async fetchTextListing(filter: string, allowSkinnyFallback: boolean): Promise<Tiddler[]> {\n let res = await this.requestListWithText(filter)\n if (res.status === 403) {\n await this.ensureExternalFilterWhitelist(filter).catch(() => undefined)\n res = await this.requestListWithText(filter)\n }\n if (res.status === 403) {\n if (allowSkinnyFallback) return this.fetchListing(undefined)\n throw new Error(`TiddlyWeb 拒绝该 filter(未在 $:/config/Server/ExternalFilters 白名单中):${filter.slice(0, 100)}`)\n }\n if (!res.ok) throw new Error(`TiddlyWeb recipe list HTTP ${res.status}`)\n return this.parseList(res)\n }\n\n /** GET the recipe listing WITHOUT text payloads (server default excludes `text`). */\n private async fetchListing(filter?: string): Promise<Tiddler[]> {\n const params = new URLSearchParams()\n if (filter !== undefined && filter.length > 0) params.set('filter', filter)\n const query = params.toString()\n let res = await this.request(`/recipes/default/tiddlers.json${query.length > 0 ? `?${query}` : ''}`)\n if (!res.ok && res.status === 403 && filter !== undefined && filter.length > 0) {\n // Not whitelisted: write the whitelist tiddler once and retry the SAME\n // filter. (The old code silently refetched the DEFAULT listing — the\n // caller asked for set A and got set B.)\n await this.ensureExternalFilterWhitelist(filter).catch(() => undefined)\n res = await this.request(`/recipes/default/tiddlers.json?${query}`)\n if (!res.ok && res.status === 403) {\n throw new Error(`TiddlyWeb 拒绝该 filter(未在 $:/config/Server/ExternalFilters 白名单中):${filter.slice(0, 100)}`)\n }\n }\n if (!res.ok) throw new Error(`TiddlyWeb recipe list HTTP ${res.status}`)\n return this.parseList(res)\n }\n\n /** GET the recipe listing WITH text for a specific (whitelisted) filter. */\n private async requestListWithText(filter: string): Promise<Response> {\n return this.request(`/recipes/default/tiddlers.json?filter=${encodeURIComponent(filter)}&exclude=${LIST_WITH_TEXT_EXCLUDE}`)\n }\n\n /** Write `$:/config/Server/ExternalFilters/<filter>` = \"yes\" (idempotent). */\n private async ensureExternalFilterWhitelist(filter: string): Promise<void> {\n await this.put({ title: externalFilterWhitelistTitle(filter), text: 'yes' })\n }\n\n private async parseList(res: Response): Promise<Tiddler[]> {\n const data = (await res.json()) as Array<Record<string, unknown>> | { tiddlers?: Array<Record<string, unknown>> }\n const items = Array.isArray(data) ? data : (data.tiddlers ?? [])\n return items.map(normalizeTiddler)\n }\n\n /**\n * Search text-bearing tiddlers: one request (text-bearing listing with text)\n * plus local case-insensitive substring matching on title + text, optional\n * exact tags (AND), a `since` modified-time floor, an exact `type`, capped at\n * `limit`. Robust against the server's external-filter 403 (whitelist is\n * self-healed). Binary tiddlers (images/audio/…) are NOT in the listing, so\n * they can never match — a deliberate flood guard on big wikis.\n */\n /**\n * Search text-bearing tiddlers: one request (text-bearing listing with text,\n * short-TTL cached) plus local case-insensitive substring matching on title,\n * tags and text, optional exact tags (AND), a `since` modified-time floor, an\n * exact `type`, an exact custom `field`+`value` pair, and a `limit`.\n *\n * Results are RANKED (title hit > tag hit > body hit count, then newest\n * first) rather than returned in listing order, and `limit` is clamped to\n * 1…200 like the HTTP route always did (v0.19.0). Binary tiddlers are not in\n * the listing at all, so they can never flood the results.\n */\n async search(query: string, options: SearchOptions = {}): Promise<{ items: Tiddler[]; total: number }> {\n const items = await this.list(undefined, true)\n const needle = query.toLowerCase()\n const sinceTime = parseSince(options.since)\n const wantedTags = [...(options.tags ?? []), options.tag].filter((t): t is string => typeof t === 'string' && t.trim().length > 0)\n const limit = clampLimit(options.limit, 30)\n const fieldName = typeof options.field === 'string' && options.field.length > 0 ? options.field : undefined\n const fieldValue = typeof options.value === 'string' ? options.value : undefined\n const matched: Array<{ t: Tiddler; score: number }> = []\n for (const t of items) {\n if (t.title.startsWith('$:/')) continue\n const titleHit = needle.length > 0 && t.title.toLowerCase().includes(needle)\n const text = t.text ?? ''\n const textHit = needle.length > 0 && text.toLowerCase().includes(needle)\n const tagHit = needle.length > 0 && (t.tags ?? []).some((tag) => tag.toLowerCase().includes(needle))\n if (!titleHit && !textHit && !tagHit) continue\n if (sinceTime !== undefined) {\n const modified = parseTiddlerDate(t.modified)\n if (modified === undefined || modified < sinceTime) continue\n }\n if (options.type !== undefined && options.type.length > 0 && (t.type ?? 'text/vnd.tiddlywiki') !== options.type) continue\n if (wantedTags.length > 0) {\n const tags = (t.tags ?? []).map((tag) => tag.toLowerCase())\n if (!wantedTags.every((w) => tags.includes(w.toLowerCase()))) continue\n }\n // Custom-field match: exact value comparison on any field (note metadata\n // such as `q`, `due`, `clip-url` is not part of title/text, so it used to\n // be unsearchable).\n if (fieldName !== undefined) {\n const actual = t[fieldName]\n if (typeof actual !== 'string') continue\n if (fieldValue !== undefined && actual !== fieldValue) continue\n }\n let score = 0\n if (titleHit) score += 6\n if (tagHit) score += 3\n if (needle.length > 0) score += Math.min(countOccurrences(text.toLowerCase(), needle), 5)\n matched.push({ t, score })\n }\n matched.sort((a, b) => {\n if (b.score !== a.score) return b.score - a.score\n return modifiedAt(b.t) - modifiedAt(a.t)\n })\n return { items: matched.slice(0, limit).map((entry) => entry.t), total: matched.length }\n }\n\n /**\n * List the most recently modified TEXT-BEARING (non-system, non-binary)\n * tiddlers, newest first (missing/modified-less tiddlers sort to the tail).\n * `since` keeps only tiddlers modified at/after that instant. Binary\n * attachments never appear — a book import touching hundreds of images must\n * not crowd the \"最近修改\" list.\n */\n async recent(limit = 15, since?: string): Promise<Tiddler[]> {\n const items = await this.list(undefined, true)\n const sinceTime = parseSince(since)\n const filtered = items.filter((t) => {\n if (t.title.startsWith('$:/')) return false\n if (sinceTime !== undefined) {\n const modified = parseTiddlerDate(t.modified)\n if (modified === undefined || modified < sinceTime) return false\n }\n return true\n })\n filtered.sort((a, b) => modifiedAt(b) - modifiedAt(a))\n return filtered.slice(0, Math.max(1, Math.min(limit, 200)))\n }\n\n /**\n * Distinct non-system tags with their tiddler counts, most-used first then\n * zh-locale. One skinny listing request (no text payloads).\n *\n * `limit` (v0.19.4) caps the returned array after sorting; the caller loses\n * the tail count unless it also asks `tagStats()`.\n */\n async listTags(limit?: number): Promise<Array<{ tag: string; count: number }>> {\n const tags = (await this.tagStats()).tags\n if (limit === undefined) return tags\n return tags.slice(0, Math.max(1, Math.min(Math.floor(limit), 1000)))\n }\n\n /** Full tag vocabulary + its size, so callers that cap can still report the\n * total (\"showing 200 of N\") — one listing request either way. */\n async tagStats(): Promise<{ total: number; tags: Array<{ tag: string; count: number }> }> {\n // TEXT_LIST_FILTER (server-side, whitelist self-healed) so a tag that only\n // hangs off binary attachments is not counted as a knowledge-base tag\n // (v0.19.0 — the unfiltered skinny listing included image/book-page tags).\n const items = await this.list(TEXT_LIST_FILTER, false)\n const map = new Map<string, number>()\n for (const t of items) {\n if (t.title.startsWith('$:/')) continue\n for (const tag of t.tags ?? []) {\n if (tag.startsWith('$:/')) continue\n map.set(tag, (map.get(tag) ?? 0) + 1)\n }\n }\n const tags = [...map.entries()]\n .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0], 'zh'))\n .map(([tag, count]) => ({ tag, count }))\n return { total: tags.length, tags }\n }\n}\n\n/** Optional search / recent filters. */\nexport interface SearchOptions {\n /** Every tag in this list must be present (AND semantics). */\n tags?: string[]\n /** Legacy single exact tag (AND with `tags`). */\n tag?: string\n /** ISO 8601 instant; keep only tiddlers modified at/after it. */\n since?: string\n /** Exact tiddler type (default type is \"text/vnd.tiddlywiki\"). */\n type?: string\n /** Custom field name to filter on (exact match on the field's string value). */\n field?: string\n /** Required value for `field` (omit to require only that the field exists). */\n value?: string\n /** Max results (default 30, capped 200). */\n limit?: number\n}\n\n/** Clamp a caller-supplied limit into 1…200 (default when absent/invalid). */\nexport function clampLimit(limit: unknown, fallback: number): number {\n const value = typeof limit === 'number' && Number.isFinite(limit) ? Math.floor(limit) : fallback\n return Math.max(1, Math.min(value, 200))\n}\n\n/** Count non-overlapping occurrences of `needle` in `haystack`. */\nfunction countOccurrences(haystack: string, needle: string): number {\n if (needle.length === 0) return 0\n let count = 0\n let index = haystack.indexOf(needle)\n while (index !== -1) {\n count++\n index = haystack.indexOf(needle, index + needle.length)\n }\n return count\n}\n\n/** `modified` as epoch ms (0 when absent/unparseable) — for result ranking. */\nfunction modifiedAt(tiddler: Tiddler): number {\n return parseTiddlerDate(tiddler.modified) ?? 0\n}\n\n/** Parse a `since` value into an epoch ms, or undefined when absent/invalid. */\nfunction parseSince(since: string | undefined): number | undefined {\n if (typeof since !== 'string' || since.trim().length === 0) return undefined\n const ms = new Date(since.trim()).getTime()\n return Number.isNaN(ms) ? undefined : ms\n}\n","/**\n * WikiServer — the TiddlyWiki 5 child-process lifecycle (design doc §9, D3).\n *\n * Zero-friction rules:\n * - ensure the wiki folder exists (scaffold with `--init server` once)\n * - git bootstrap is NOT this class's job (index.ts owns the GitFace)\n * - auto-detect a free loopback port unless one is pinned in config\n * - spawn `node <tw>/tiddlywiki.js <wiki> --listen host=127.0.0.1 ...`\n * and poll /status until it answers 200\n * - the TW child serves at the ROOT of its own dedicated loopback port (no\n * `path-prefix`): TW's browser frontend builds its API URLs from\n * `$protocol$//$host$/` only, so any path-prefix makes every frontend call\n * ../../status → 404 (verified against tiddlywiki 5.4.1). Namespacing lives\n * on the DSH webserver side (/dsh-tiddlywiki/* routes), never in TW itself.\n * - crash → restart with exponential backoff (1s,2s,4s… cap 30s), reset on\n * a successful readiness\n * - stop() is deterministic: SIGTERM, escalate to SIGKILL after a grace\n * period, and never leave a timer that would respawn during teardown\n *\n * @module dsh-tiddlywiki/host/wiki\n */\nimport { spawn, execFile, type ChildProcessByStdio } from 'node:child_process'\nimport { existsSync } from 'node:fs'\nimport { mkdir } from 'node:fs/promises'\nimport { createRequire } from 'node:module'\nimport { createServer } from 'node:net'\nimport { join, resolve } from 'node:path'\nimport type { Readable } from 'node:stream'\n\n/** The DSH webserver route prefix (NOT a TW path-prefix; see module header). */\nexport const PATH_PREFIX = '/dsh-tiddlywiki'\n\n/**\n * Same-origin TW proxy route on the DSH webserver (remote-access mode, R1).\n * The browser only ever talks to the DSH origin — which it already reaches\n * over loopback, LAN, Tailscale, a domain or HTTPS — and DSH proxies to the\n * loopback TW child. TW's frontend is pointed at this prefix via the\n * `$:/config/tiddlyweb/host` tiddler so every API call stays same-origin.\n */\nexport const TW_PROXY_PREFIX = `${PATH_PREFIX}/tw`\n\n/** The proxy base path (trailing slash) handed to browsers / TW's frontend. */\nexport const TW_PROXY_PATH = `${TW_PROXY_PREFIX}/`\n\n/** How long to wait for the wiki to answer /status. */\nconst READY_TIMEOUT_MS = 20_000\n\n/** Poll cadence while waiting for readiness. */\nconst READY_POLL_MS = 500\n\n/** Backoff ceiling for crash restarts. */\nconst MAX_RESTART_BACKOFF_MS = 30_000\n\n/** SIGTERM → SIGKILL escalation grace. */\nconst KILL_GRACE_MS = 3_000\n\n/** Ring-buffer cap for the stdout/stderr log. */\nconst LOG_BUFFER_LIMIT = 200\n\n/** One-shot scaffold timeout for `--init server`. */\nconst INIT_TIMEOUT_MS = 30_000\n\nexport interface WikiServerOptions {\n /** Root that holds one folder per wiki (default $DSH_HOME/tiddlywiki). */\n wikiRoot: string\n /** Wiki folder name under wikiRoot (default \"main\"). */\n wiki: string\n /** Port; 0 = auto-detect a free loopback port. */\n port: number\n /** Optional Basic Auth (loopback anonymous by default). */\n username?: string\n password?: string\n logBufferLimit?: number\n}\n\nexport type WikiHealth = 'starting' | 'running' | 'stopped' | 'failed'\n\nexport interface WikiStatusView {\n status: WikiHealth\n url?: string\n port?: number\n wikiPath: string\n pid?: number\n lastStartedAt?: number\n error?: string\n logs: string[]\n}\n\n/** Resolve the absolute entry of the installed `tiddlywiki` package. */\nfunction resolveTwEntry(): string {\n const require = createRequire(import.meta.url)\n return require.resolve('tiddlywiki/tiddlywiki.js')\n}\n\nexport class WikiServer {\n private child: ChildProcessByStdio<null, Readable, Readable> | undefined\n /**\n * The wiki folder this server currently serves. MUTABLE since v0.22.0: a\n * runtime switch calls {@link setLocation} while the child is stopped.\n */\n private wikiPath: string\n private readonly logs: string[] = []\n private readonly logLimit: number\n private health: WikiHealth = 'stopped'\n private port: number | undefined\n private stopping = false\n private restartTimer: NodeJS.Timeout | undefined\n private restartDelay = 1_000\n private lastStartedAt: number | undefined\n private error: string | undefined\n /** In-flight start() promise: single-flight guard (v0.19.0). */\n private startPromise: Promise<WikiStatusView> | undefined\n /** In-flight restart(): two concurrent restarts must not spawn two children (v0.19.1). */\n private restartPromise: Promise<WikiStatusView> | undefined\n /** Set by a successful readiness probe; a crash BEFORE readiness means the\n * auto-chosen port may have been taken, so it is re-probed on restart. */\n private wasReady = false\n\n constructor(private readonly options: WikiServerOptions) {\n this.wikiPath = resolve(options.wikiRoot, options.wiki)\n this.location = { root: options.wikiRoot, name: options.wiki }\n this.logLimit = options.logBufferLimit ?? LOG_BUFFER_LIMIT\n }\n\n /**\n * The wiki folder this server currently serves (v0.22.0: mutable — a runtime\n * switch calls {@link setLocation} while the child is stopped).\n */\n\n /** Last location applied through {@link setLocation} / the constructor. */\n private location: { root: string; name: string }\n\n /** Current location (root + folder name), for the settings page. */\n get currentLocation(): { root: string; name: string } {\n return { ...this.location }\n }\n\n /**\n * Point this server at a DIFFERENT wiki folder (runtime switch, v0.22.0).\n *\n * Refuses while a child is running: a switch that forgot the folder while the\n * old process still served it would leave `/status`, the REST client and the\n * git face describing two different wikis. Callers stop() first (see\n * `switchWiki()` in host/wiki-switch.ts), then start() again.\n *\n * The bound port is INTENTIONALLY kept: reusing it across a switch keeps the\n * iframe src and the cached REST client valid (same reasoning as restart()).\n */\n setLocation(location: { root: string; name: string }): void {\n if (this.child !== undefined) throw new Error('wiki 必须先在停止状态下才能切换位置')\n const next = resolve(location.root, location.name)\n if (next === this.wikiPath) {\n this.location = { root: location.root, name: location.name }\n return\n }\n this.location = { root: location.root, name: location.name }\n this.wikiPath = next\n // A different folder is a different wiki: clear the stale failure message\n // and the readiness flag (a crash before readiness re-probes the port).\n this.wasReady = false\n this.error = undefined\n if (this.child === undefined) this.health = 'stopped'\n this.log(`location → ${next}`)\n }\n\n /** Base URL of the TW service, once a port is bound (root, no path prefix). */\n get url(): string | undefined {\n return this.port === undefined ? undefined : `http://127.0.0.1:${this.port}`\n }\n\n /** The currently bound port (undefined until first spawn). */\n get currentPort(): number | undefined {\n return this.port\n }\n\n private log(line: string): void {\n const ts = new Date().toISOString()\n this.logs.push(`[${ts}] ${line}`)\n if (this.logs.length > this.logLimit) this.logs.splice(0, this.logs.length - this.logLimit)\n }\n\n /** Scaffold the wiki folder with `--init server` when it is absent. */\n async ensureWiki(): Promise<void> {\n await mkdir(this.wikiPath, { recursive: true })\n if (existsSync(join(this.wikiPath, 'tiddlywiki.info'))) return\n const tw = resolveTwEntry()\n this.log(`init: ${process.execPath} ${tw} ${this.wikiPath} --init server`)\n await new Promise<void>((resolveP, rejectP) => {\n execFile(process.execPath, [tw, this.wikiPath, '--init', 'server'], { timeout: INIT_TIMEOUT_MS, windowsHide: true }, (err) => {\n if (err) rejectP(err as Error)\n else resolveP()\n })\n })\n }\n\n /** Probe a free loopback port. */\n private async findFreePort(): Promise<number> {\n return new Promise<number>((resolveP, rejectP) => {\n const server = createServer()\n server.unref()\n server.once('error', rejectP)\n server.listen(0, '127.0.0.1', () => {\n const address = server.address()\n if (address === null || typeof address === 'string') {\n server.close()\n rejectP(new Error('cannot resolve a free port'))\n return\n }\n const port = address.port\n server.close(() => resolveP(port))\n })\n })\n }\n\n /**\n * Start (or restart) the TW child. Resolves once `/status` answers 200 or\n * the readiness deadline passes. Never throws on a crash — the exit handler\n * schedules a self-healing restart unless we are stopping.\n *\n * SINGLE-FLIGHT (v0.19.0): two concurrent callers (double restart click, a\n * restart racing the self-heal timer) both used to pass the\n * `child !== undefined` check, probe their own port and spawn a child each —\n * the loser became an orphan process holding a port until dsh web exited.\n */\n async start(): Promise<WikiStatusView> {\n if (this.startPromise !== undefined) return this.startPromise\n const run = this.startOnce()\n this.startPromise = run\n try {\n return await run\n } finally {\n this.startPromise = undefined\n }\n }\n\n private async startOnce(): Promise<WikiStatusView> {\n this.stopping = false\n this.restartDelay = 1_000\n // Clear a previous failure's message (v0.19.5): a self-healed restart left\n // `this.error` set forever, so `/status` (and the panel/FAB tooltip) kept\n // reporting a stale fault even though the wiki was healthy. A fresh attempt\n // is not a failure until it proves to be one.\n this.error = undefined\n await this.ensureWiki()\n if (this.child !== undefined) return this.status()\n this.health = 'starting'\n // Reuse an existing auto port across restarts (restart() → stop() → start())\n // so a fixed-baseUrl TiddlyWebClient stays valid and iframe src is stable.\n const port = this.options.port > 0 ? this.options.port : ((this.port ?? 0) > 0 ? this.port as number : await this.findFreePort())\n this.port = port\n const tw = resolveTwEntry()\n const args = [tw, this.wikiPath, '--listen', 'host=127.0.0.1', `port=${port}`]\n if (this.options.username) {\n // Locked-down mode for non-loopback exposure: Basic Auth + access lists.\n args.push(`username=${this.options.username}`)\n args.push(`password=${this.options.password ?? ''}`)\n args.push(`readers=${this.options.username}`)\n args.push(`writers=${this.options.username}`)\n }\n // Anonymous loopback mode carries NO auth args: TW's defaults open the\n // wiki to anonymous read/write on the bound (loopback) address. Passing\n // anon-username/readers/writers here was verified to 401 every request\n // ('undefined' is not authorized), so the anonymous branch stays bare.\n //\n // NEVER log the raw argv: it carries `password=…`, the ring buffer is\n // returned by the UNAUTHENTICATED GET /status, and the same string could\n // end up in a crash dump. (v0.19.0 — the spawn line used to be logged\n // verbatim; the password still lives in the OS process list, which is why\n // the wiki binds loopback by default.)\n this.log(`spawn: ${process.execPath} ${args.map((a) => (/^password=/.test(a) ? 'password=***' : a)).join(' ')}`)\n const child = spawn(process.execPath, args, { cwd: this.wikiPath, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true })\n this.child = child\n child.stdout.on('data', (chunk: Buffer) => this.log(`[out] ${String(chunk).trimEnd()}`))\n child.stderr.on('data', (chunk: Buffer) => this.log(`[err] ${String(chunk).trimEnd()}`))\n child.once('exit', (code, signal) => {\n // IDENTITY CHECK (v0.19.0): restart() SIGKILLs the old child and start()\n // immediately flips `stopping` back to false, so the old child's exit\n // event can arrive AFTER the new one is running. Without this check it\n // cleared `this.child`/`this.port` and marked the healthy new child\n // 'stopped' — waitReady() then polled `undefined/status` and every route\n // 503'd while TW was actually up.\n if (this.child !== child) {\n this.log(`exit code=${code} signal=${signal ?? ''} (superseded child — ignored)`)\n return\n }\n this.log(`exit code=${code} signal=${signal ?? ''} stopping=${this.stopping}`)\n this.child = undefined\n this.health = 'stopped'\n if (!this.stopping) {\n // Never became ready → most likely `EADDRINUSE` on the remembered auto\n // port. Forget it so the restart probes a fresh one instead of looping\n // on a port this process no longer owns. An explicitly configured port\n // is always honored as-is.\n if (this.options.port === 0 && !this.wasReady) this.port = undefined\n this.scheduleRestart()\n }\n })\n child.once('error', (err) => {\n this.log(`spawn error: ${err.message}`)\n this.error = err.message\n if (this.child !== child) return\n this.child = undefined\n this.health = 'failed'\n if (!this.stopping) this.scheduleRestart()\n })\n this.lastStartedAt = Date.now()\n this.wasReady = false\n await this.waitReady()\n return this.status()\n }\n\n /** Poll /status until 200 or the deadline; throws only on deadline/crash. */\n private async waitReady(): Promise<void> {\n const deadline = Date.now() + READY_TIMEOUT_MS\n // Locked-down mode (`username` configured) puts /status behind TW's\n // `readers` list: an anonymous probe gets 401 forever and the wiki would\n // never be reported ready. Authenticate preemptively, exactly like the\n // REST client does.\n const headers = this.options.username !== undefined && this.options.username.length > 0\n ? { authorization: `Basic ${Buffer.from(`${this.options.username}:${this.options.password ?? ''}`, 'utf8').toString('base64')}` }\n : undefined\n for (;;) {\n if (this.child === undefined) throw new Error('wiki process exited before ready')\n try {\n const res = await fetch(`${this.url}/status`, {\n signal: AbortSignal.timeout(2_000),\n ...(headers !== undefined ? { headers } : {}),\n })\n if (res.ok) {\n this.health = 'running'\n this.wasReady = true\n this.error = undefined\n this.log('ready: /status 200')\n return\n }\n } catch {\n /* not ready yet */\n }\n if (Date.now() > deadline) {\n this.health = 'failed'\n this.error = 'wiki server did not become ready in time'\n this.log(this.error)\n throw new Error(this.error)\n }\n await new Promise<void>((r) => setTimeout(r, READY_POLL_MS))\n }\n }\n\n private scheduleRestart(): void {\n if (this.stopping || this.restartTimer !== undefined) return\n const delay = this.restartDelay\n this.restartDelay = Math.min(this.restartDelay * 2, MAX_RESTART_BACKOFF_MS)\n this.log(`restart scheduled in ${delay}ms`)\n this.health = 'starting'\n this.restartTimer = setTimeout(() => {\n this.restartTimer = undefined\n void this.start().catch((err) => {\n this.health = 'failed'\n this.error = err instanceof Error ? err.message : String(err)\n this.log(`restart failed: ${this.error}`)\n })\n }, delay)\n }\n\n /**\n * One-click restart (routes `/restart`, `/sync`, every `/admin/*` restart).\n *\n * SINGLE-FLIGHT (v0.19.1): the route-level mutex in routes.ts could not cover\n * the admin routes (separate closure), so two concurrent restarts — a double\n * click, or `/restart` racing `/admin/seeds/run` — used to run `stop()` twice\n * and spawn two children; the loser became an orphan process holding a port\n * until dsh web exited. Serializing here fixes every caller at once.\n */\n async restart(): Promise<WikiStatusView> {\n if (this.restartPromise !== undefined) return this.restartPromise\n const run = (async (): Promise<WikiStatusView> => {\n await this.stop()\n return this.start()\n })()\n this.restartPromise = run\n try {\n return await run\n } finally {\n this.restartPromise = undefined\n }\n }\n\n /** Deterministic teardown: cancel timers, SIGTERM, escalate to SIGKILL. */\n async stop(): Promise<void> {\n this.stopping = true\n if (this.restartTimer !== undefined) {\n clearTimeout(this.restartTimer)\n this.restartTimer = undefined\n }\n const child = this.child\n this.child = undefined\n if (child !== undefined && child.exitCode === null && child.signalCode === null) {\n try {\n child.kill('SIGTERM')\n } catch { /* already gone */ }\n await Promise.race([\n new Promise<void>((r) => child.once('exit', () => r())),\n new Promise<void>((r) => {\n setTimeout(() => {\n try { child.kill('SIGKILL') } catch { /* already gone */ }\n r()\n }, KILL_GRACE_MS).unref?.()\n }),\n ])\n // After the SIGKILL escalation, give the OS a moment to actually reap the\n // process: an immediate start() that rebinds the same port would\n // otherwise hit EADDRINUSE and enter the backoff loop (v0.19.0).\n if (child.exitCode === null && child.signalCode === null) {\n await Promise.race([\n new Promise<void>((r) => child.once('exit', () => r())),\n new Promise<void>((r) => { setTimeout(r, 1_000).unref?.() }),\n ])\n }\n }\n // Only report 'stopped' when no replacement child has been spawned in the\n // meantime (restart() runs stop() → start()).\n if (this.child === undefined) this.health = 'stopped'\n }\n\n /** Live status view (health, url, git-independent, recent logs). */\n status(): WikiStatusView {\n return {\n status: this.health,\n url: this.url,\n port: this.port,\n wikiPath: this.wikiPath,\n pid: this.child?.pid,\n lastStartedAt: this.lastStartedAt,\n ...(this.error !== undefined ? { error: this.error } : {}),\n logs: [...this.logs],\n }\n }\n}\n","/**\n * 会话相关 wiki 汇总(会话顶部「知识库」Tab 的后端)——把一次 DSH 会话(含其后代\n * subagent)在本会话中产生 / 读取 / 检索过的 wiki 笔记汇总成一篇 TW wikitext,写入\n * TW 的 volatile 命名空间 `$:/temp/dsh/session-summary/<会话ID>`。\n *\n * 判定来源(设计定稿,探查验证):\n * - Host 服务 `sessionQuery`(可选注入):`readSession(sessionId)` 返回完整事件日志\n * (events 升序),`traceSession` 返回祖先/后代树。绝不直接解析\n * `~/.dsh/sessions` 目录下的 session 文件——那是 zstd 压缩的。\n * - `tool/call` 事件精确记录每次 `tiddlywiki_*` 调用的 `name` 与\n * `arguments`(JSON 字符串)。\n * - `assistant/message` 事件 `message.content` 的 text 块可扫\n * `/dsh-tiddlywiki/tw/#...` 引用链接(视为「读取」)。\n *\n * 归类规则(§三 定稿):\n * 产生 📝 tiddlywiki_put / batch_put / rename(arguments 取 title)\n * 读取 👀 tiddlywiki_get(title)+ 助手回复里的 wiki 引用链接\n * 检索 🔍 search / recent(记关键词,不算单篇)\n * 范围 本会话 + 后代 subagent(traceSession 递归,仅子代理触达的笔记标注)\n *\n * 渲染(§四):写入 `$:/temp/...`——TW 5.4.1 默认 `$:/config/SyncFilter` 显式\n * `-[prefix[$:/temp/]]`(core/wiki/config/SyncFilter.tid),服务端 syncer 从不把\n * $:/temp 落盘:内存态、不进 git、TW 重启即消失(已实测:PUT 204、tiddlers/ 无文件、\n * git clean)。TW 用自己主题/链接/导航原生渲染,不另造 UI。\n *\n * @module dsh-tiddlywiki/host/session-summary\n */\nimport { parseTiddlerDate, type TiddlyWebClient } from './tw-api.ts'\n\n/** 会话汇总 tiddler 的 $:/temp 命名空间前缀。 */\nexport const SESSION_SUMMARY_PREFIX = '$:/temp/dsh/session-summary/'\n\n/** 会话内能触达 wiki 的 tiddlywiki_* 工具名(search/recent 只记检索记录,不算单篇)。 */\nconst NOTE_TOOL_NAMES = new Set(['tiddlywiki_put', 'tiddlywiki_batch_put', 'tiddlywiki_rename', 'tiddlywiki_get', 'tiddlywiki_search', 'tiddlywiki_recent'])\n\n/** 每篇笔记的触达摘要(按 title 去重,时间取最近一次)。 */\nexport interface NoteEntry {\n title: string\n action: 'produced' | 'read'\n /** 最近一次触达的 epoch ms。 */\n time: number\n /** 附加说明(如 rename 的旧标题)。 */\n detail?: string\n /** true = 仅由后代 subagent 触达过(本会话自己没碰过)。 */\n subagent: boolean\n}\n\n/** 一次检索类调用(search / recent),不算单篇笔记。 */\nexport interface SearchRecord {\n kind: 'search' | 'recent'\n query: string\n tags: string[]\n time: number\n subagent: boolean\n}\n\n/** 一次会话扫描的收集结果。 */\nexport interface Collected {\n produced: Map<string, NoteEntry>\n read: Map<string, NoteEntry>\n searches: SearchRecord[]\n}\n\n/**\n * 结构化 `sessionQuery` 服务脸(dsh-api-session-query 的极小子集,只声明本模块用到\n * 的方法;运行时实例是真实 Service,绝不是 Inspect 数据)。\n */\nexport interface SessionQueryFace {\n /** Read and replay-validate one complete logical session log (events ascending). */\n readSession(sessionId: string): Promise<SessionLogSnapshot>\n /** Trace known ancestry and descendants from one corpus observation. */\n traceSession(sessionId: string, signal?: AbortSignal): Promise<SessionLineageTrace>\n}\n\nexport interface SessionLogSnapshot {\n session: { id: string }\n events: SessionEvent[]\n}\n\nexport interface SessionEvent {\n type: string\n seq: number\n time: number\n data: Record<string, unknown>\n}\n\nexport interface SessionLineageTrace {\n descendants: SessionLineageNode[]\n}\n\nexport interface SessionLineageNode {\n session: { header: { id: string } }\n descendants: SessionLineageNode[]\n}\n\n/**\n * Bounds so one enormous session tree cannot make the「知识库」Tab unresponsive:\n * the summary reads every listed session's FULL event log and then probes each\n * touched tiddler over REST.\n */\nconst MAX_SESSIONS = 40\nconst MAX_SEARCH_RECORDS = 200\nconst MAX_ENRICH_TITLES = 300\n\n/** 递归收集后代树里所有 session id(不含根自身,超过上限即停)。 */\nfunction collectDescendantIds(nodes: SessionLineageNode[] | undefined, out: string[]): void {\n if (!Array.isArray(nodes)) return\n for (const node of nodes) {\n if (out.length >= MAX_SESSIONS) return\n const id = node?.session?.header?.id\n if (typeof id === 'string' && id.length > 0) {\n out.push(id)\n collectDescendantIds(node?.descendants, out)\n }\n }\n}\n\n/**\n * 汇总条目会直接写进 `[[标题]]` wikitext——标题既要能安全渲染、又不能制造死链。\n * 会话日志里常混入模型的示例/残缺写法(`#…`、`#标题`、带反引号的截断链接、\n * 控制字符等),这类收进来会被 TW 渲染成「佚失条目」;一律过滤。\n */\nfunction isPlausibleTitle(title: string): boolean {\n if (title.length === 0 || title.length > 300) return false\n if (title.trim().length === 0) return false\n if (/[\\u0000-\\u001f\\u007f]/.test(title)) return false // 控制字符 / 换行\n if (/[\\]|`#]/.test(title)) return false // 会破坏 [[…]] 链接语法\n if (title.startsWith('…') || title === '标题') return false // 占位 / 示例写法\n return true\n}\n\n/** 记录一篇笔记的触达;同标题合并(时间取最新、detail 取最新、subagent 取 AND)。 */\nfunction record(map: Map<string, NoteEntry>, entry: NoteEntry): void {\n if (entry.title.startsWith('$:/')) return\n if (!isPlausibleTitle(entry.title)) return\n const prev = map.get(entry.title)\n if (prev !== undefined) {\n prev.time = Math.max(prev.time, entry.time)\n if (entry.detail !== undefined) prev.detail = entry.detail\n prev.subagent = prev.subagent && entry.subagent\n return\n }\n map.set(entry.title, { ...entry })\n}\n\n/** 扫助手回复文本里的 `/dsh-tiddlywiki/tw/#标题` 引用链接 → 读取。 */\nfunction scanRefs(text: string, time: number, subagent: boolean, collected: Collected): void {\n // 只认到下一个 `)` / 空白 / `]` / `#` 为止——`#` 会截断后续碎片(模型示例常写成\n // `#…` 或 `#标题`,这些由 isPlausibleTitle 在 record 时过滤)。\n const re = /\\/dsh-tiddlywiki\\/tw\\/#([^#)\\s\\]]+)/g\n let m: RegExpExecArray | null\n while ((m = re.exec(text)) !== null) {\n const raw = m[1] ?? ''\n if (raw.length === 0) continue\n let title = raw\n try {\n title = decodeURIComponent(raw)\n } catch {\n /* keep raw on malformed percent-encoding */\n }\n if (title.length === 0) continue\n record(collected.read, { title, action: 'read', time, subagent })\n }\n}\n\n/** 扫描一次会话的事件日志,按 §三 规则收集笔记触达与检索记录。 */\nfunction scanSnapshot(snap: SessionLogSnapshot | undefined, subagent: boolean, collected: Collected): void {\n const events = snap?.events\n if (!Array.isArray(events)) return\n for (const ev of events) {\n const type = ev?.type\n const t = typeof ev?.time === 'number' ? ev.time : Date.now()\n if (type === 'tool/call') {\n const name = ev.data?.name\n if (typeof name !== 'string' || !NOTE_TOOL_NAMES.has(name)) continue\n let args: Record<string, unknown> = {}\n if (typeof ev.data?.arguments === 'string') {\n try {\n args = JSON.parse(ev.data.arguments) as Record<string, unknown>\n } catch {\n args = {}\n }\n }\n switch (name) {\n case 'tiddlywiki_put':\n if (typeof args.title === 'string' && args.title.length > 0) {\n record(collected.produced, { title: args.title, action: 'produced', time: t, subagent })\n }\n break\n case 'tiddlywiki_batch_put':\n if (Array.isArray(args.items)) {\n for (const item of args.items) {\n if (item !== null && typeof item === 'object' && typeof (item as { title?: unknown }).title === 'string') {\n const title = (item as { title: string }).title\n if (title.length > 0) record(collected.produced, { title, action: 'produced', time: t, subagent })\n }\n }\n }\n break\n case 'tiddlywiki_rename':\n if (typeof args.newTitle === 'string' && args.newTitle.length > 0) {\n const old = typeof args.oldTitle === 'string' && args.oldTitle.length > 0 ? args.oldTitle : undefined\n record(collected.produced, {\n title: args.newTitle,\n action: 'produced',\n time: t,\n detail: old !== undefined && isPlausibleTitle(old) ? `重命名自「${old}」` : undefined,\n subagent,\n })\n }\n break\n case 'tiddlywiki_get':\n if (typeof args.title === 'string' && args.title.length > 0) {\n record(collected.read, { title: args.title, action: 'read', time: t, subagent })\n }\n break\n case 'tiddlywiki_search': {\n if (collected.searches.length >= MAX_SEARCH_RECORDS) break\n const query = typeof args.query === 'string' ? args.query : ''\n const tags = Array.isArray(args.tags) ? args.tags.filter((x): x is string => typeof x === 'string') : []\n collected.searches.push({ kind: 'search', query, tags, time: t, subagent })\n break\n }\n case 'tiddlywiki_recent':\n if (collected.searches.length < MAX_SEARCH_RECORDS) {\n collected.searches.push({ kind: 'recent', query: '', tags: [], time: t, subagent })\n }\n break\n }\n } else if (type === 'assistant/message') {\n const message = ev.data?.message\n const content = message !== null && typeof message === 'object' ? (message as { content?: unknown }).content : undefined\n if (Array.isArray(content)) {\n for (const block of content) {\n if (block === null || typeof block !== 'object') continue\n const b = block as { type?: unknown; text?: unknown }\n if (b.type === 'text' && typeof b.text === 'string' && b.text.length > 0) {\n scanRefs(b.text, t, subagent, collected)\n }\n }\n }\n }\n }\n}\n\n/**\n * 内联文本净化(v0.19.3):汇总 wikitext 会被 TW 渲染成 HTML 片段再注入 DSH 页面,\n * 而 TW 的解析器**原样透传 HTML**。凡是来自会话日志/请求体的字符串(sessionId、\n * rename 的旧标题、检索词)都要先转义 HTML 元字符并去掉控制字符,别指望上层净化。\n */\nexport function escapeInline(text: string, max = 200): string {\n return text\n .replace(/[\\u0000-\\u001f\\u007f]/g, ' ')\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/[[\\]|]/g, ' ')\n .slice(0, max)\n}\n\n/** 当前状态(存在?标签?修改时间?摘要)。 */\ninterface TitleState {\n exists: boolean\n tags: string[]\n modified?: string\n snippet?: string\n /** 查询失败(非 404):状态未知,不能报「已删除/不存在」。 */\n unknown?: boolean\n}\n\n/** 并发(8 路)查询每篇笔记的当前状态(404 → 不存在/已删除)。 */\nasync function enrichTitles(client: TiddlyWebClient, titles: string[]): Promise<Map<string, TitleState>> {\n const out = new Map<string, TitleState>()\n const enquire = async (title: string): Promise<[string, TitleState]> => {\n try {\n const t = await client.get(title)\n if (t === undefined) return [title, { exists: false, tags: [] }]\n const flat = typeof t.text === 'string' ? t.text.replace(/\\s+/g, ' ').trim() : ''\n return [title, {\n exists: true,\n tags: t.tags ?? [],\n modified: typeof t.modified === 'string' ? t.modified : undefined,\n snippet: flat.length > 48 ? `${flat.slice(0, 48)}…` : flat,\n }]\n } catch {\n // 只有 404 才算「不存在」(tw-api 的 get 只在 404 返回 undefined):\n // 超时/5xx/服务重启中的查询失败必须报「状态未知」,否则一篇还在的笔记\n // 会被标成「已删除」——正是项目里反复强调的「读取失败 ≠ 条目不存在」。\n return [title, { exists: false, tags: [], unknown: true }]\n }\n }\n // Bounded batches: parallel within a batch, insertion order preserved across\n // batches (Map order = the caller's title order, same as the old sequential).\n for (let i = 0; i < titles.length; i += 8) {\n const batch = titles.slice(i, i + 8)\n for (const [title, state] of await Promise.all(batch.map(enquire))) out.set(title, state)\n }\n return out\n}\n\n/** epoch ms → 本地 `YYYY-MM-DD HH:mm`。 */\nfunction fmtTime(ms: number): string {\n const d = new Date(ms)\n if (Number.isNaN(d.getTime())) return ''\n const p = (n: number): string => String(n).padStart(2, '0')\n return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`\n}\n\n/**\n * TW `modified` → 本地时间(解析失败则原样返回)。\n *\n * v0.20.0: the REST layer returns the COMPACT form (`20260101000000000`,\n * YYYYMMDDhhmmssSSS UTC), which `Date.parse()` rejects with NaN — the summary\n * therefore printed the raw 17-digit string. `parseTiddlerDate` handles both\n * the compact and the ISO form (same rule as the rest of the host).\n */\nfunction fmtModified(value: string | undefined): string {\n if (value === undefined || value.length === 0) return ''\n const ms = parseTiddlerDate(value)\n return ms === undefined ? value : fmtTime(ms)\n}\n\n/** 组装分组 wikitext:产生 / 读取(未产生过的)/ 检索记录。 */\nfunction buildWikitext(\n sessionId: string,\n collected: Collected,\n producedTitles: string[],\n readTitles: string[],\n stateByTitle: Map<string, TitleState>,\n): string {\n const lines: string[] = []\n lines.push('! 会话相关 wiki 汇总')\n lines.push('')\n lines.push(\n `本页列出会话 \\`${escapeInline(sessionId, 120)}\\`(含其后代子代理)在本会话中产生 / 读取 / 检索过的知识库笔记。`,\n )\n lines.push('')\n const producedCount = producedTitles.length\n const readCount = readTitles.length\n const searchCount = collected.searches.length\n // Was anything touched ONLY through a descendant subagent? (The old column\n // said「本会话 + 子代理」whenever anything existed at all.)\n const anySubagent =\n [...collected.produced.values()].some((e) => e.subagent) ||\n [...collected.read.values()].some((e) => e.subagent) ||\n collected.searches.some((s) => s.subagent)\n if (producedCount + readCount + searchCount === 0) {\n lines.push('> 本会话暂时没有产生、读取或检索过任何知识库笔记。')\n lines.push('>')\n lines.push('> 本页为会话自动生成的临时汇总(`$:/temp`,不落盘、不进 git,重启 TW 即消失);需要时点 Tab 顶栏的「🔄 刷新」重新生成。')\n return lines.join('\\n')\n }\n lines.push(\n '| 产生 📝 | 读取 👀 | 检索 🔍 | 涉及会话 |',\n '| --- | --- | --- | --- |',\n `| ${producedCount} | ${readCount} | ${searchCount} | ${anySubagent ? '本会话 + 子代理' : '本会话'} |`,\n )\n lines.push('')\n\n const entryLine = (title: string, state: TitleState | undefined, entry: NoteEntry): string => {\n const bits: string[] = [`[[${title}]]`]\n if (state === undefined) {\n // Not probed at all: `enrichTitles` only queries the first\n // MAX_ENRICH_TITLES titles, so a long session's tail must NOT be reported\n // as「已删除/不存在」(v0.19.5) — that is a false accusation, not a status.\n bits.push('(未探测,超出单次查询上限)')\n } else if (state.unknown === true) {\n bits.push('⚠️ 状态未知(查询失败)')\n } else if (!state.exists) {\n bits.push('⚠️ 已删除/不存在')\n } else {\n if (state.tags.length > 0) bits.push(`标签 ${state.tags.slice(0, 6).map((tag) => escapeInline(tag, 40)).join('、')}${state.tags.length > 6 ? '…' : ''}`)\n const mod = fmtModified(state.modified)\n if (mod.length > 0) bits.push(`修改 ${mod}`)\n }\n const t = fmtTime(entry.time)\n if (t.length > 0) bits.push(`会话内 ${t}`)\n if (entry.detail !== undefined) bits.push(escapeInline(entry.detail, 200))\n if (entry.subagent) bits.push('(子代理)')\n return `* ${bits.join(' · ')}`\n }\n\n if (producedCount > 0) {\n lines.push('!!! 📝 产生')\n for (const title of producedTitles) {\n const entry = collected.produced.get(title)\n if (entry !== undefined) lines.push(entryLine(title, stateByTitle.get(title), entry))\n }\n lines.push('')\n }\n if (readCount > 0) {\n lines.push('!!! 👀 读取')\n for (const title of readTitles) {\n const entry = collected.read.get(title)\n if (entry !== undefined) lines.push(entryLine(title, stateByTitle.get(title), entry))\n }\n lines.push('')\n }\n if (searchCount > 0) {\n lines.push('!!! 🔍 检索记录')\n for (const r of collected.searches) {\n const bits: string[] = []\n if (r.kind === 'search') {\n bits.push(`\\`tiddlywiki_search\\` query=「${r.query.length > 0 ? escapeInline(r.query, 120) : '(空)'}」`)\n if (r.tags.length > 0) bits.push(`tags ${r.tags.map((tag) => escapeInline(tag, 40)).join('、')}`)\n } else {\n bits.push('`tiddlywiki_recent`(最近修改)')\n }\n const t = fmtTime(r.time)\n if (t.length > 0) bits.push(t)\n if (r.subagent) bits.push('(子代理)')\n lines.push(`* ${bits.join(' · ')}`)\n }\n lines.push('')\n }\n lines.push('---')\n lines.push('> 本页为会话自动生成的临时汇总(`$:/temp`,不落盘、不进 git,重启 TW 即消失);需要时点 Tab 顶栏的「🔄 刷新」重新生成。')\n return lines.join('\\n')\n}\n\nexport interface SessionSummaryResult {\n title: string\n counts: { produced: number; read: number; searches: number; sessions: number }\n empty: boolean\n generatedAt: string\n}\n\n/**\n * 生成并写入一篇会话汇总 tiddler($:/temp,volatile)。\n * @param client 可用 TW 客户端(调用方已保证 wiki 服务在线)。\n * @param sq sessionQuery 服务(调用方已保证可用)。\n * @param sessionId 目标会话 id(本会话 + 后代 subagent)。\n */\nexport async function writeSessionSummary(client: TiddlyWebClient, sq: SessionQueryFace, sessionId: string): Promise<SessionSummaryResult> {\n // 1) 会话范围:自身 + traceSession 后代树(递归)。trace 失败(会话已归档/不存在)\n // 就只汇总自身,绝不让单次失败拖垮整页。\n const ids: string[] = [sessionId]\n try {\n const trace = await sq.traceSession(sessionId, AbortSignal.timeout(10_000))\n collectDescendantIds(trace?.descendants, ids)\n } catch {\n /* trace unavailable → self only */\n }\n\n // 2) 逐个会话读完整事件日志并收集(任一失败跳过,其余照常)。\n const collected: Collected = { produced: new Map(), read: new Map(), searches: [] }\n for (const id of ids) {\n try {\n const snap = await sq.readSession(id)\n scanSnapshot(snap, id !== sessionId, collected)\n } catch {\n /* one bad session must not fail the whole summary */\n }\n }\n\n // 3) 产生优先:同时被产生+读取的笔记只列在「产生」,读取区只放未产生过的。\n const producedTitles = [...collected.produced.keys()].sort((a, b) => (collected.produced.get(b)?.time ?? 0) - (collected.produced.get(a)?.time ?? 0))\n const readTitles = [...collected.read.keys()]\n .filter((t) => !collected.produced.has(t))\n .sort((a, b) => (collected.read.get(b)?.time ?? 0) - (collected.read.get(a)?.time ?? 0))\n collected.searches.sort((a, b) => b.time - a.time)\n\n // 4) 查询当前状态(存在?标签?时间?)→ 组装 wikitext → PUT volatile tiddler。\n // 探测量有上限:一篇超长会话可能触碰成百上千篇笔记。\n const allTitles = [...new Set([...producedTitles, ...readTitles])]\n const probedTitles = allTitles.slice(0, MAX_ENRICH_TITLES)\n const stateByTitle = await enrichTitles(client, probedTitles)\n const text = buildWikitext(sessionId, collected, producedTitles, readTitles, stateByTitle)\n const title = `${SESSION_SUMMARY_PREFIX}${sessionId}`\n await client.put({ title, text, type: 'text/vnd.tiddlywiki', tags: [] })\n\n return {\n title,\n counts: { produced: producedTitles.length, read: readTitles.length, searches: collected.searches.length, sessions: ids.length },\n empty: producedTitles.length + readTitles.length + collected.searches.length === 0,\n generatedAt: new Date().toISOString(),\n }\n}\n","/**\n * Shared node:http helpers for the DSH webserver routes (routes.ts, admin.ts).\n *\n * One implementation of body reading + JSON response writing so the route\n * layers cannot drift (they previously carried two/three copies).\n *\n * @module dsh-tiddlywiki/host/http\n */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { createHash, timingSafeEqual } from 'node:crypto'\n\n/**\n * Constant-time string comparison for shared tokens: both sides are hashed\n * first, so neither the content nor the LENGTH of the expected token leaks\n * through timing (v0.19.0). One implementation for every caller — routes.ts\n * and clip-bridge.ts each carried a private copy (v0.20.0).\n */\nexport function safeTokenEqual(a: string, b: string): boolean {\n const ha = createHash('sha256').update(a, 'utf8').digest()\n const hb = createHash('sha256').update(b, 'utf8').digest()\n return timingSafeEqual(ha, hb)\n}\n\n/** Default cap for small JSON bodies (note/restart/config…). */\nexport const MAX_JSON_BODY_BYTES = 2 * 1024 * 1024\n\n/** Cap for the /api passthrough body (tiddler content can be large). */\nexport const MAX_PROXY_BODY_BYTES = 16 * 1024 * 1024\n\n/** Cap for uploaded file bodies (/tw proxy, /upload). */\nexport const MAX_UPLOAD_BYTES = 64 * 1024 * 1024\n\nfunction readBodyStream(req: IncomingMessage, limit: number, encoding: true): Promise<string>\nfunction readBodyStream(req: IncomingMessage, limit: number, encoding: false): Promise<Buffer>\nfunction readBodyStream(req: IncomingMessage, limit: number, encoding: boolean): Promise<string | Buffer> {\n return new Promise((resolveP, rejectP) => {\n let size = 0\n let settled = false\n const chunks: Buffer[] = []\n const fail = (err: Error): void => {\n if (settled) return\n settled = true\n rejectP(err)\n }\n const succeed = (value: string | Buffer): void => {\n if (settled) return\n settled = true\n resolveP(value)\n }\n req.on('data', (chunk: Buffer) => {\n size += chunk.length\n if (size > limit) {\n fail(new Error('body too large'))\n req.destroy()\n return\n }\n chunks.push(chunk)\n })\n req.on('end', () => {\n const buf = Buffer.concat(chunks)\n succeed(encoding ? buf.toString('utf8') : buf)\n })\n req.on('error', (err) => fail(err instanceof Error ? err : new Error(String(err))))\n // A caller that hangs up mid-body used to leave this promise pending\n // forever (the route handler never settled, and the socket was already\n // gone). `close` also fires after a normal `end`, hence the readableEnded\n // guard.\n req.on('aborted', () => fail(new Error('request aborted before the body was fully received')))\n req.on('close', () => {\n if (!req.readableEnded) fail(new Error('request closed before the body was read'))\n })\n })\n}\n\n/** Read a JSON (utf8) request body up to `limit` bytes (default MAX_JSON_BODY_BYTES). */\nexport function readBody(req: IncomingMessage, limit = MAX_JSON_BODY_BYTES): Promise<string> {\n return readBodyStream(req, limit, true)\n}\n\n/** Read a raw (binary-safe) request body up to `limit` bytes (default MAX_UPLOAD_BYTES). */\nexport function readBodyBuffer(req: IncomingMessage, limit = MAX_UPLOAD_BYTES): Promise<Buffer> {\n return readBodyStream(req, limit, false)\n}\n\n/** Write a JSON response (no-store, utf-8). */\nexport function json(res: ServerResponse, payload: unknown, status = 200): void {\n res.writeHead(status, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' })\n res.end(JSON.stringify(payload))\n}\n\n/**\n * Map a thrown error to an HTTP status. Oversized bodies are a client problem\n * (413), everything else is ours (500) — the routes used to answer 500 for\n * `/note` and `/admin/*` but 413 for `/upload` for the identical condition.\n */\nexport function errorStatus(err: unknown): number {\n const message = err instanceof Error ? err.message : String(err)\n return /body too large/i.test(message) ? 413 : 500\n}\n\n/** The route-handler shape the registries take. */\nexport type RouteHandler = (req: IncomingMessage, res: ServerResponse) => Promise<void>\n\n/**\n * Wrap an async route handler so a rejected promise can never become an\n * unhandled rejection (v0.19.3).\n *\n * WHY: every registration used to be `void handleX(req, res)`, and the host\n * installs no `unhandledRejection` handler — Node's default policy then EXITS\n * the whole dsh web process, while the client request hangs with no response.\n * A handler that throws (including a sync throw before its own try block, e.g.\n * `new URL(req.url)` outside the try in the proxies) now always ends in a\n * response: 413/500 JSON, or a bare `end()` when headers are already sent.\n */\nexport function guardHandler(handler: RouteHandler): (req: IncomingMessage, res: ServerResponse) => void {\n return (req, res) => {\n void handler(req, res).catch((err) => {\n try {\n if (!res.headersSent) json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n else res.end()\n } catch { /* response already gone */ }\n })\n }\n}\n\n/**\n * True when a NON-GET/HEAD/OPTIONS request is a cross-site (CSRF) request.\n *\n * Every legitimate caller of these routes is same-origin: the DSH GUI, the\n * embedded TW (served through the `/tw` proxy), and the TW-side \"发送给 Agent\"\n * button all run on the DSH origin. A page from another site can still fire a\n * simple request at a loopback/LAN-reachable DSH, and the browser tags it with\n * `Origin` / `Sec-Fetch-Site` — which is what we reject here.\n *\n * Only WRITES are checked: a cross-site navigation (Sec-Fetch-Site\n * `cross-site`) must still be able to open `/tw/` in a new tab, and cross-site\n * GETs cannot read the response (no CORS headers) so they cannot exfiltrate.\n * Requests that carry neither header (curl, server-to-server, old browsers)\n * are allowed on purpose: this is CSRF hardening, NOT an auth boundary — the\n * host's own auth is what protects a network-exposed DSH.\n */\nexport function isCrossSiteWrite(req: IncomingMessage): boolean {\n const method = (req.method ?? 'GET').toUpperCase()\n if (method === 'GET' || method === 'HEAD' || method === 'OPTIONS') return false\n // Widen the header types explicitly: `IncomingHttpHeaders` types both as\n // `string | undefined`, so an Array.isArray() branch would otherwise be\n // narrowed to `never` (repeat headers are still possible at runtime).\n const siteHeader: string | string[] | undefined = req.headers['sec-fetch-site']\n const site = typeof siteHeader === 'string' ? siteHeader.toLowerCase() : Array.isArray(siteHeader) ? String(siteHeader[0] ?? '').toLowerCase() : ''\n if (site === 'cross-site') return true\n const originHeader: string | string[] | undefined = req.headers.origin\n const origin = typeof originHeader === 'string' ? originHeader : Array.isArray(originHeader) ? String(originHeader[0] ?? '') : ''\n if (origin.length === 0) return false\n if (origin === 'null') return true\n const host = req.headers.host\n if (typeof host !== 'string' || host.length === 0) return false\n try {\n return new URL(origin).host.toLowerCase() !== host.toLowerCase()\n } catch {\n return true\n }\n}\n\n/**\n * Guard for a route handler: enforces the expected HTTP method(s), then the\n * CSRF check for writes. Returns true after writing the error response — the\n * caller just does `if (rejectCrossSiteWrite(req, res, ['POST'])) return`.\n *\n * WHY the method check is mandatory (v0.19.0): the host webserver dispatches\n * routes by PATHNAME ONLY (dsh-host-webserver matches `rawPath` and calls the\n * handler, no method filter), and `isCrossSiteWrite` deliberately IGNORES\n * read methods. Together that meant `GET /dsh-tiddlywiki/sync` ran a\n * pull+commit+push, `GET /restart` restarted the TW child and `GET /upload`\n * wrote a file — all reachable from any web page with a bare\n * `<img src=\"http://127.0.0.1:3080/dsh-tiddlywiki/sync\">`, since browsers send\n * no Origin/Sec-Fetch-Site that we would reject on a GET. Declaring the method\n * per route closes that class: a cross-site GET now gets 405 and no effect.\n *\n * @param allowedMethods when given, any other method → 405 (no side effect).\n */\nexport function rejectCrossSiteWrite(\n req: IncomingMessage,\n res: ServerResponse,\n allowedMethods?: readonly string[],\n): boolean {\n const method = (req.method ?? 'GET').toUpperCase()\n if (allowedMethods !== undefined && !allowedMethods.includes(method)) {\n res.writeHead(405, {\n 'content-type': 'application/json; charset=utf-8',\n 'cache-control': 'no-store',\n allow: [...allowedMethods, 'OPTIONS'].join(', '),\n })\n res.end(JSON.stringify({ ok: false, error: `method not allowed: ${method}` }))\n return true\n }\n if (!isCrossSiteWrite(req)) return false\n json(res, { ok: false, error: 'cross-site request rejected' }, 403)\n return true\n}\n\n/** Read-only route guard: GET/HEAD only, no side effects on any other method. */\nexport function rejectNonRead(req: IncomingMessage, res: ServerResponse): boolean {\n return rejectCrossSiteWrite(req, res, ['GET', 'HEAD'])\n}\n","/**\n * TiddlyWiki 渲染片段的白名单净化器(v0.19.1,安全)。\n *\n * 背景:`/render` 把 tiddler 正文渲染成 HTML 片段,客户端(回复流工具卡 +\n * 会话「知识库」Tab)用 `dangerouslySetInnerHTML` 注入 **DSH 同源页面**。\n * TW 的 wikitext/markdown 解析器只剥 `on*` 事件属性(`<script>` 会变成\n * `<safe-script>`),但 `<iframe src=\"javascript:…\">`、`<a href=\"javascript:…\">`、\n * `<form action=\"javascript:…\">` 之类会**原样通过**(headless 实测)——一段\n * 写进 wiki 的正文就能在 DSH 页面里执行脚本(可调 `/dsh-tiddlywiki/*` 管理路由)。\n *\n * 因此所有要注入 DSH 页面的 TW 片段都必须先过这里。策略是**白名单 + 丢弃**,\n * 不是黑名单补丁:\n * - 丢掉整棵子树:script/style/iframe/frame/frameset/object/applet/form/\n * base/meta/link/noscript/template/svg/math/xmp/plaintext/listing/\n * basefont/bgsound/title;\n * - 丢掉任何 `on*` 属性、`srcdoc`、`srcset`;\n * - URL 属性(href/src/xlink:href/action/poster/data/…)只允许\n * http/https/mailto/tel、相对路径/锚点,以及栅格 `data:image/*`\n * (`data:image/svg+xml` 与 `data:text/html` 一律拒绝);\n * - 属性值先做实体解码 + 去控制符再判 scheme,挡住 `&#106;avascript:`、\n * `java\\tscript:` 这类混淆;\n * - 其它未知标签/属性原样保留(TW 片段大量使用 `tc-*`/`$`/`data-*`)。\n *\n * 纯字符串实现(不依赖 DOM):host 侧在返回给浏览器之前净化,且能被\n * `scripts/verify-render-sanitizer.mjs` 在 Node 里逐条断言。\n *\n * @module dsh-tiddlywiki/host/sanitize\n */\n\n/** 连内容一起丢掉的元素(其 innerText 也不可信,直接跳过到闭合标签之后)。 */\nconst DROP_WITH_CONTENT = new Set([\n 'script', 'style', 'iframe', 'frame', 'frameset', 'object', 'embed', 'applet', 'form',\n 'base', 'meta', 'link', 'noscript', 'template', 'svg', 'math',\n 'xmp', 'plaintext', 'listing', 'basefont', 'bgsound', 'title',\n])\n\n/** 自闭合(void)元素——不需要闭合标签。 */\nconst VOID_ELEMENTS = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta',\n 'param', 'source', 'track', 'wbr',\n])\n\n/** 需要做 scheme 检查的 URL 属性。 */\nconst URL_ATTRIBUTES = new Set([\n 'href', 'src', 'xlink:href', 'action', 'formaction', 'poster', 'data',\n 'dynsrc', 'lowsrc', 'background', 'cite', 'longdesc', 'usemap', 'manifest',\n])\n\n/** 直接丢弃的属性(不参与 URL 检查)。 */\nconst DROP_ATTRIBUTES = new Set(['srcdoc', 'srcset', 'formaction', 'action', 'ping'])\n\n/** 允许的 URL scheme(其余带 scheme 的值一律拒绝)。 */\nconst ALLOWED_SCHEMES = new Set(['http', 'https', 'mailto', 'tel'])\n\n/** 允许的 data: 图片 MIME(栅格;SVG 可携带脚本,拒绝)。 */\nconst ALLOWED_DATA_IMAGE = /^data:image\\/(png|jpe?g|gif|webp|avif|bmp|x-icon|vnd\\.microsoft\\.icon)[;,]/i\n\n/** HTML 实体解码(只覆盖判定 scheme 需要的那些;数字实体 + 关键命名实体)。 */\nfunction decodeEntities(value: string): string {\n return value\n .replace(/&#x([0-9a-f]+);?/gi, (_m, hex: string) => {\n const code = Number.parseInt(hex, 16)\n return Number.isFinite(code) && code > 0 ? String.fromCodePoint(code) : ''\n })\n .replace(/&#(\\d+);?/g, (_m, dec: string) => {\n const code = Number.parseInt(dec, 10)\n return Number.isFinite(code) && code > 0 ? String.fromCodePoint(code) : ''\n })\n .replace(/&(colon|tab|newline|sol|period);/gi, (m) => {\n const key = m.slice(1, -1).toLowerCase()\n return key === 'colon' ? ':' : key === 'tab' ? '\\t' : key === 'newline' ? '\\n' : key === 'sol' ? '/' : '.'\n })\n .replace(/&amp;/gi, '&')\n}\n\n/** 该属性值是否是安全的 URL(相对路径/锚点/白名单 scheme/栅格 data 图片)。 */\nexport function isSafeUrl(rawValue: string): boolean {\n // 去实体 + 去控制符/空白(浏览器解析 URL 时会丢弃这些,故先归一)。\n const value = decodeEntities(rawValue).replace(/[\\u0000-\\u0020\\u007f\\u00a0]+/g, '').trim()\n if (value.length === 0) return true\n if (value.startsWith('#') || value.startsWith('/') || value.startsWith('?') || value.startsWith('.')) return true\n const schemeMatch = /^([a-z][a-z0-9+.-]*):/i.exec(value)\n if (schemeMatch === null) return true // 相对 URL\n const scheme = (schemeMatch[1] ?? '').toLowerCase()\n if (scheme === 'data') return ALLOWED_DATA_IMAGE.test(value)\n return ALLOWED_SCHEMES.has(scheme)\n}\n\n/** 属性名是否允许保留。 */\nfunction isSafeAttributeName(name: string): boolean {\n const lower = name.toLowerCase()\n if (lower.startsWith('on')) return false // onclick / onerror / onload …\n if (DROP_ATTRIBUTES.has(lower)) return false\n return /^[a-z_:][a-z0-9_:.-]*$/i.test(name)\n}\n\n/**\n * 重新输出属性值时的转义:只去控制符、只转义双引号。\n *\n * ⚠️ 不要转义 `&`:源值里的 `&amp;` 是**已经实体化**的文本(TW 渲染输出),\n * 再转一次会变成 `&amp;amp;`,浏览器解码后 URL 里就多了一个字面 `&amp;`\n * (实测踩过:`?b=1&amp;c=2` 被改成 `?b=1&amp;amp;c=2`)。`\"` 必须转义,\n * 否则单引号形式的属性值里的引号会逃出双引号上下文。\n */\nfunction escapeAttr(value: string): string {\n return value.replace(/[\\u0000-\\u001f\\u007f]/g, '').replace(/\"/g, '&quot;')\n}\n\ninterface ParsedTag {\n /** 标签结束位置(`>` 之后)。 */\n end: number\n name: string\n closing: boolean\n selfClosing: boolean\n attrs: Array<{ name: string; value: string }>\n}\n\n/**\n * 解析 `<` 起始处的一个标签。返回 null 表示不是标签(当普通文本处理)。\n * 严格性重要:属性值支持双引号/单引号/无引号三种写法,未闭合的属性值\n * (例如 `<img src=x onerror=\"…>` 里带引号却没闭合)会被吞到 `>` 或字符串末尾,\n * 后续照样被丢弃,不会漏出可执行内容。\n */\nfunction parseTag(html: string, start: number): ParsedTag | null {\n let i = start + 1\n if (i >= html.length) return null\n let closing = false\n if (html[i] === '/') {\n closing = true\n i++\n }\n const nameStart = i\n while (i < html.length && /[a-zA-Z0-9:_-]/.test(html[i] as string)) i++\n const name = html.slice(nameStart, i)\n if (name.length === 0) return null\n\n const attrs: Array<{ name: string; value: string }> = []\n for (;;) {\n while (i < html.length && /\\s/.test(html[i] as string)) i++\n if (i >= html.length) return null\n const ch = html[i]\n if (ch === '>') return { end: i + 1, name, closing, selfClosing: false, attrs }\n if (ch === '/') {\n // `/` 只可能是自闭合标记\n i++\n while (i < html.length && /\\s/.test(html[i] as string)) i++\n if (i < html.length && html[i] === '>') return { end: i + 1, name, closing, selfClosing: true, attrs }\n continue\n }\n const nameStart2 = i\n while (i < html.length && !/[\\s=/>]/.test(html[i] as string)) i++\n const attrName = html.slice(nameStart2, i)\n if (attrName.length === 0) return null\n while (i < html.length && /\\s/.test(html[i] as string)) i++\n let value = ''\n if (i < html.length && html[i] === '=') {\n i++\n while (i < html.length && /\\s/.test(html[i] as string)) i++\n const quote = html[i]\n if (quote === '\"' || quote === \"'\") {\n i++\n const valueStart = i\n while (i < html.length && html[i] !== quote) i++\n value = html.slice(valueStart, i)\n if (i < html.length) i++ // 吃掉收尾引号\n } else {\n const valueStart = i\n while (i < html.length && !/[\\s>]/.test(html[i] as string)) i++\n value = html.slice(valueStart, i)\n }\n }\n attrs.push({ name: attrName, value })\n }\n}\n\n/** 从 `from` 起找到 `</name` 之后的位置(找不到 → 字符串末尾)。 */\nfunction skipElement(html: string, from: number, name: string): number {\n const lower = html.toLowerCase()\n const needle = `</${name.toLowerCase()}`\n const close = lower.indexOf(needle, from)\n if (close < 0) return html.length\n const gt = html.indexOf('>', close + needle.length)\n return gt < 0 ? html.length : gt + 1\n}\n\n/**\n * 净化一个 TW 渲染片段:返回可以安全 `innerHTML` 的 HTML 字符串。\n * 未知标签/属性保留;危险元素整棵丢掉;危险属性丢掉;危险 URL 丢掉。\n */\nexport function sanitizeTwFragment(html: string): string {\n if (typeof html !== 'string' || html.length === 0) return ''\n const out: string[] = []\n let i = 0\n while (i < html.length) {\n const lt = html.indexOf('<', i)\n if (lt < 0) {\n out.push(html.slice(i))\n break\n }\n out.push(html.slice(i, lt))\n // 注释 / doctype / 处理指令:整体丢弃。\n if (html.startsWith('<!--', lt)) {\n const end = html.indexOf('-->', lt + 4)\n i = end < 0 ? html.length : end + 3\n continue\n }\n if (html.startsWith('<!', lt) || html.startsWith('<?', lt)) {\n const end = html.indexOf('>', lt)\n i = end < 0 ? html.length : end + 1\n continue\n }\n const tag = parseTag(html, lt)\n if (tag === null) {\n // 不是标签(例如 `a < b`):按文本输出 `<`,继续扫描。\n out.push('&lt;')\n i = lt + 1\n continue\n }\n const name = tag.name.toLowerCase()\n if (DROP_WITH_CONTENT.has(name)) {\n i = tag.closing || tag.selfClosing || VOID_ELEMENTS.has(name)\n ? tag.end\n : skipElement(html, tag.end, name)\n continue\n }\n if (tag.closing) {\n if (!VOID_ELEMENTS.has(name)) out.push(`</${name}>`)\n i = tag.end\n continue\n }\n const safeAttrs: string[] = []\n for (const attr of tag.attrs) {\n if (!isSafeAttributeName(attr.name)) continue\n const lower = attr.name.toLowerCase()\n if (URL_ATTRIBUTES.has(lower) && !isSafeUrl(attr.value)) continue\n safeAttrs.push(`${attr.name}=\"${escapeAttr(attr.value)}\"`)\n }\n const attrText = safeAttrs.length > 0 ? ` ${safeAttrs.join(' ')}` : ''\n out.push(VOID_ELEMENTS.has(name) || tag.selfClosing ? `<${name}${attrText}>` : `<${name}${attrText}>`)\n i = tag.end\n }\n return out.join('')\n}\n","/**\n * Shared helpers for the ONE-SHOT seeds (`src/host/seed-*.ts`).\n *\n * ERROR POLICY (v0.18.0). A seed may only treat a tiddler as \"missing\" when the\n * server actually answered 404 — `TiddlyWebClient.get()` already does exactly\n * that (404 → `undefined`, everything else throws). Seeds must therefore NOT\n * wrap reads in `.catch(() => undefined)`: that turned a transient failure (TW\n * restarting, timeout, 5xx) into \"the user has no such tiddler\", after which the\n * NON-force startup seeds happily `put()` the built-in content and OVERWROTE\n * user-owned tiddlers (`$:/DefaultTiddlers`, the send-to-agent / render plugin\n * bundles, the doc note…). Letting the error propagate makes the seed report\n * `ok:false` and skip the write, which is the safe outcome.\n *\n * MARKER CONTENT HASHES (v0.22.0). A one-shot marker used to be the literal\n * text `seeded-once` — enough to mean \"offered once\", useless for telling\n * whether the BUILT-IN content changed since (which the settings page needs to\n * say 「有更新」) or whether the USER edited their copy (which must never be\n * overwritten silently). The marker is now JSON:\n *\n * { \"version\": 1, \"hashes\": { \"<title>\": \"<sha256/16>\" }, \"at\": \"<ISO>\" }\n *\n * Old markers (`seeded-once`) are read as `legacy` and compared by TEXT\n * instead: identical to the built-in → ownership is proven and the marker is\n * upgraded in place; different → reported as \"maybe updated / maybe edited\",\n * never silently rewritten.\n *\n * The marker write stays best-effort (it is bookkeeping, not user content) but\n * is logged instead of silently swallowed, so a broken one-shot guarantee is at\n * least visible.\n *\n * @module dsh-tiddlywiki/host/seed-util\n */\nimport { createHash } from 'node:crypto'\nimport type { Tiddler, TiddlyWebClient } from './tw-api.ts'\n\n/** Marker schema version (bump only on an incompatible change). */\nexport const SEED_MARKER_VERSION = 1\n\n/** Parsed marker tiddler. */\nexport interface SeedMarker {\n version: number\n /** title → hash of the text this seed wrote there. */\n hashes: Record<string, string>\n at?: string\n}\n\n/** What `readSeedMarker()` found. */\nexport interface SeedMarkerState {\n /** Present when the marker is our JSON (any version we can read). */\n marker?: SeedMarker\n /**\n * The marker tiddler exists but is not our JSON — written before v0.22.0\n * (the literal `seeded-once`) or hand-edited. Hashes are unavailable; callers\n * fall back to comparing text.\n */\n legacy?: boolean\n}\n\n/** Short content hash used for markers (16 hex chars of sha256). */\nexport function hashText(text: string): string {\n return createHash('sha256').update(text, 'utf8').digest('hex').slice(0, 16)\n}\n\n/**\n * Read one tiddler for a seed. 404 → `undefined`; every other failure (network,\n * timeout, 5xx, auth) propagates to the caller, which turns it into a failed\n * seed result instead of an overwrite.\n */\nexport function readSeedTiddler(client: TiddlyWebClient, title: string): Promise<Tiddler | undefined> {\n return client.get(title)\n}\n\n/** Parse a marker tiddler's text (never throws). */\nexport function parseSeedMarker(text: string | undefined): SeedMarkerState {\n if (typeof text !== 'string' || text.trim().length === 0) return {}\n try {\n const parsed = JSON.parse(text) as { version?: unknown; hashes?: unknown }\n if (typeof parsed !== 'object' || parsed === null) return { legacy: true }\n const hashes = parsed.hashes\n if (typeof hashes !== 'object' || hashes === null) return { legacy: true }\n const clean: Record<string, string> = {}\n for (const [title, value] of Object.entries(hashes as Record<string, unknown>)) {\n if (typeof value === 'string' && value.length > 0) clean[title] = value\n }\n const version = typeof parsed.version === 'number' ? parsed.version : 1\n return { marker: { version, hashes: clean } }\n } catch {\n return { legacy: true }\n }\n}\n\n/**\n * Read a seed marker. 404 → `{}` (never seeded); a read FAILURE propagates like\n * every other seed read (v0.18.0 error policy).\n */\nexport async function readSeedMarker(client: TiddlyWebClient, title: string): Promise<SeedMarkerState> {\n const tiddler = await readSeedTiddler(client, title)\n if (tiddler === undefined) return {}\n return parseSeedMarker(typeof tiddler.text === 'string' ? tiddler.text : '')\n}\n\n/**\n * Write a one-shot marker. Best-effort: a failure is logged (never silent) and\n * does not fail the seed — the content write already succeeded and the marker\n * only records \"offered once\" + which built-in content was written.\n */\nexport async function writeSeedMarker(client: TiddlyWebClient, title: string, hashes?: Record<string, string>): Promise<void> {\n const payload: SeedMarker = { version: SEED_MARKER_VERSION, hashes: hashes ?? {}, at: new Date().toISOString() }\n try {\n await client.put({ title, text: JSON.stringify(payload), type: 'application/json', tags: [] })\n } catch (err) {\n console.warn(`[dsh-tiddlywiki] seed marker write failed (${title}):`, err instanceof Error ? err.message : err)\n }\n}\n","/**\n * System-prompt section for the plugin (v0.21.0).\n *\n * WHY THIS MODULE EXISTS\n * ----------------------\n * Until v0.20.1 the section text was a hand-written template literal inside\n * `src/index.ts` that **duplicated the tool schemas in prose**. It drifted\n * exactly as expected: the signature catalogue was last touched in v0.19.0,\n * while v0.19.4 (list_tags limit), v0.19.5 (delete/attach/batch_put\n * concurrency tokens + attach's refuse-to-overwrite) and v0.20.1 (append\n * `fields`) all changed the tools — so the injected prompt advertised 6 stale\n * signatures for four releases.\n *\n * The fix has two halves:\n * 1. `slim` (default) drops the parameter catalogue entirely — the model\n * already receives every tool's schema, so the section only carries the\n * conventions the schemas CANNOT express (sync discipline, workspace\n * tags, agent-written/human-edited, clickable wiki links). Nothing left\n * to drift.\n * 2. `full` still offers a signature index, but it is **generated from the\n * live tool registry** (`tiddlywikiToolSummary()`), so it can never go\n * stale again.\n *\n * Both modes are composed from the same governance blocks; the verify script\n * asserts every block survives in both (a re-write may not silently drop a\n * rule the user relies on).\n *\n * @module dsh-tiddlywiki/host/prompt\n */\n\n/** Prompt section name (stable id; a re-registration replaces the old one). */\nexport const PROMPT_SECTION_NAME = 'dsh-tiddlywiki'\n\n/** Prompt section order (the plugin's section sits before plan/team policy). */\nexport const PROMPT_SECTION_ORDER = 100\n\n/** Selectable prompt shapes. `slim` is the default since v0.21.0. */\nexport const PROMPT_MODES = ['slim', 'full'] as const\n\n/** One selectable prompt shape. */\nexport type PromptMode = (typeof PROMPT_MODES)[number]\n\n/** Default prompt shape (config `prompt.mode`; v0.21.0 switched to `slim`). */\nexport const DEFAULT_PROMPT_MODE: PromptMode = 'slim'\n\n/**\n * System-prompt configuration (config tiddler overlay `prompt.*`, editable on\n * the settings page; re-applied live — see `applyPrompt()` in src/index.ts).\n */\nexport interface PromptConfig {\n /** false = register no section at all (no prompt text is injected). */\n enabled?: boolean\n /** Shape of the built-in text. `override` (when set) wins over this. */\n mode?: PromptMode\n /** Free-form text always APPENDED after the built-in/override body. */\n extra?: string\n /** Replaces the built-in body entirely (`extra` is still appended). */\n override?: string\n}\n\n/** One registered tool, as summarised for the `full` catalogue. */\nexport interface PromptToolSummary {\n name: string\n /** Parameter names in declaration order, with their required flag. */\n params: Array<{ name: string; required: boolean }>\n}\n\n/** Normalise a config value to a known mode (unknown values fall back to slim). */\nexport function normalizePromptMode(value: unknown): PromptMode {\n return value === 'full' ? 'full' : DEFAULT_PROMPT_MODE\n}\n\n/**\n * Neutralise `{{…}}` groups in USER-provided prompt text.\n *\n * DSH interpolates `{{name}}` in every section before delivery and **throws**\n * on an unknown or malformed name — one `{{cwd}}` typed into `prompt.extra`\n * would break system-prompt assembly for every session. A zero-width space\n * between the braces keeps the text readable while making it literal prose\n * (the interpolation scanner no longer sees a `{{` group).\n */\nexport function escapePromptBraces(text: string): string {\n return text.replace(/\\{\\{/g, '{\\u200B{')\n}\n\n/** Governance block: how to write without clobbering human edits. */\nconst WRITE_RULES = `### 写入与并发\n- 覆盖或删除已有笔记前先 \\`tiddlywiki_get\\` 读一次,把读到的 \\`modified\\`(或 \\`revision\\`)作为 \\`expectedModified\\`(或 \\`expectedRevision\\`)传回去;若期间有人(在 TW 编辑器里)改过,写入会被拒绝并告诉你当前值——此时重新读一遍再决定,**不要用 \\`force\\` 硬覆盖**。\\`tiddlywiki_attach\\` 对同名已有条目默认拒绝写入,理由相同(要覆盖得显式 \\`force: true\\`)。\n- 纯增量内容(日志、批注、清单)优先用 \\`tiddlywiki_append\\`,不必读全文。\n- 覆盖既有条目时**不传 \\`tags\\` 就保留原有标签、自定义字段与内容类型**(只改正文);显式传 \\`tags\\` 才整体替换标签,要改内容类型用 \\`fields.type\\`。\n- 内容类型:新笔记默认写成 Markdown(工具自动补 \\`text/markdown\\`,\\`$:/\\` 系统条目除外);覆盖或追加既有条目时保留它原有的 \\`type\\`。要写 TW 原生 wikitext 才显式传 \\`fields: {\"type\":\"text/vnd.tiddlywiki\"}\\`。⚠️ \\`fields.type\\` 是 TW 的**内容类型**保留字段,业务分类请放 \\`tags\\`。`\n\n/** Governance block: the three-step git discipline + conflict recovery. */\nconst SYNC_RULES = `### 同步纪律(三条)\n1. 开工先 pull:\\`tiddlywiki_git_sync action=pull\\`(rebase + autostash;真冲突会自动 abort 并报冲突文件)。\n2. 收工 commit + push:\\`tiddlywiki_git_sync action=sync\\`(pull → commit → push)。\n3. 插件会自动防抖 commit(默认 60s),需要立刻同步时用上面的工具。\npull 冲突后:先 \\`tiddlywiki_git_resolve files=[冲突文件] strategy=keep-local\\`(保留本地)或 \\`strategy=keep-remote\\`(改用远端版本),再重新 pull/sync 整合其余改动。`\n\n/** Governance block: knowledge-base conventions (tags, memory, links). */\nconst NOTE_RULES = `### 笔记约定\n- 把 wiki 当作长期记忆与知识沉淀的地方:会议纪要、决策记录、调研笔记、随手的想法都可存成独立 tiddler(tag 建议用 inbox/meeting/decision 等便于检索)。\n- **把有价值但不在当前执行范围内的想法沉淀进 wiki**:遇到「未来可能有用 / 值得做」的想法,用 \\`tiddlywiki_put\\` 写成独立 tiddler,打上 \\`todo\\` + \\`agent-written\\` 标签(并附当前工作区名),正文简要说明来源(会话 / 工作区 / 项目背景),由用户决定是否继续。\n- 自动创建笔记时,除了业务性 tag 外,请把**当前工作区(项目)的名字**也作为标签之一,方便按项目归集与检索。\n- \\`agent-written\\`:\\`put\\`/\\`batch_put\\` 新建条目时工具自动补打,无需手动添加、也不要手动移除(除非用户明确要求);若某篇 Agent 笔记之后被人类编辑过,请补打 \\`human-edited\\`。\n- **引用 wiki 笔记用可点击链接**:在回复流中引用笔记时用 \\`[标题](/dsh-tiddlywiki/tw/#标题)\\`(标题含空格/特殊字符时做 URL 编码,如 \\`A%20B\\`;中文可直接写)。点击会打开中央 TW 面板并跳转到该笔记,请优先用它代替纯文本标题。`\n\n/**\n * Governance blocks that MUST survive in every mode — exported so\n * `scripts/verify-prompt.mjs` can assert a re-write never silently drops one.\n */\nexport const PROMPT_GOVERNANCE_BLOCKS: readonly string[] = [WRITE_RULES, SYNC_RULES, NOTE_RULES]\n\n/**\n * One line per tool: `bullet \\`name\\`(p1, p2?, …)`; `?` marks an optional\n * parameter. Shared by the `full` prompt catalogue and by the TW-side doc seed\n * (which reuses it as wikitext bullets), so both stay generated (v0.22.0).\n */\nexport function toolSignatureLines(tools: readonly PromptToolSummary[], bullet = '-'): string[] {\n return tools.map((tool) => {\n if (tool.params.length === 0) return `${bullet} \\`${tool.name}\\``\n const params = tool.params.map((p) => (p.required ? p.name : `${p.name}?`)).join(', ')\n return `${bullet} \\`${tool.name}\\`(${params})`\n })\n}\n\n/** Heading + a one-line capability pointer (slim intro). */\nfunction slimIntro(count: number): string {\n return `## TiddlyWiki 持久知识库\n\n本机有一个 TiddlyWiki 5 持久知识库(wiki 文件夹即 git 仓库)。插件提供 ${count} 个 \\`tiddlywiki_*\\` 工具:检索与读取、写入与批量写入、增量追加、重命名、删除与回收站、反向链接、二进制附件、知识库体检、git 同步与冲突解决。**每个工具的参数与返回契约以工具 schema 的 description 为准**,本段只补充 schema 之外仍需知道的规则。`\n}\n\n/** Heading + the generated signature catalogue (full intro). */\nfunction fullIntro(tools: readonly PromptToolSummary[]): string {\n const lines = toolSignatureLines(tools).join('\\n')\n return `## TiddlyWiki 持久知识库\n\n本机有一个 TiddlyWiki 5 持久知识库(wiki 文件夹即 git 仓库)。可用工具(${tools.length} 个,\\`?\\` 表示可选参数;详细契约以各工具的 description 为准):\n\n${lines}`\n}\n\n/**\n * Build the section text.\n *\n * Composition order: body → `extra`. The body is `override` when non-empty,\n * otherwise the built-in text for `mode`. User text is brace-escaped (see\n * `escapePromptBraces`); the built-in text is not (it never contains `{{`).\n * Returns `''` when `enabled === false` — callers must then register no\n * section at all rather than an empty one.\n */\nexport function buildPromptText(options: {\n mode?: PromptMode\n extra?: string\n override?: string\n tools?: readonly PromptToolSummary[]\n enabled?: boolean\n}): string {\n if (options.enabled === false) return ''\n const tools = options.tools ?? []\n const override = typeof options.override === 'string' ? options.override.trim() : ''\n const body = override.length > 0\n ? escapePromptBraces(override)\n : [normalizePromptMode(options.mode) === 'full' ? fullIntro(tools) : slimIntro(tools.length), WRITE_RULES, SYNC_RULES, NOTE_RULES].join('\\n\\n')\n const extra = typeof options.extra === 'string' ? escapePromptBraces(options.extra.trim()) : ''\n return extra.length > 0 ? `${body}\\n\\n${extra}` : body\n}\n","/**\n * Built-in doc note for the plugin (design doc §14): a short user-facing\n * \"how to use dsh-tiddlywiki\" note seeded into the wiki the first time the\n * plugin runs on a wiki.\n *\n * ONE-SHOT seed: a marker tiddler (`seed-doc-note`) records that the note has\n * been offered once; after that the note is the user's own content — deleting\n * it and restarting dsh web does NOT recreate it, and edits are never\n * overwritten.\n *\n * @module dsh-tiddlywiki/host/seed-notes\n */\nimport type { TiddlyWebClient } from './tw-api.ts'\nimport { readSeedTiddler, writeSeedMarker } from './seed-util.ts'\nimport { toolSignatureLines, type PromptToolSummary } from './prompt.ts'\n\n/** Note tiddler title (a normal, searchable note — not a system tiddler). */\nexport const DOC_NOTE_TITLE = 'dsh-tiddlywiki 插件说明'\n\n/** Tag that makes the note easy to find via `tiddlywiki_search tag=docs`. */\nexport const DOC_NOTE_TAG = 'docs'\n\n/**\n * Shared tag that collects every plugin-seeded doc into the seeded home's\n *「📚 插件文档」tabs strip (see seed-starter-docs.ts DSH_DOCS_TAG).\n */\nexport const DOC_NOTE_DSH_DOCS_TAG = 'dsh-docs'\n\n/** One-time marker: its presence means \"the note was offered once — hands off\". */\nexport const SEED_MARKER_TITLE = '$:/plugins/dsh-tiddlywiki/seed-doc-note'\n\n/** The note body, TiddlyWiki wiki-text. */\nconst DOC_NOTE_HEAD = `! dsh-tiddlywiki 插件说明\n\n本插件把 **TiddlyWiki 5** 作为 DSH 的持久知识库(wiki 文件夹本身就是一个 git 仓库,随内容自动提交/同步)。\n\n!! 它能做什么\n`\n\n/**\n * Tool-list bullet for the doc note, GENERATED from the live tool registry\n * (v0.22.0). The note used to hard-code 「10 个 agent 工具」 and was 5 tools\n * behind by the time anyone noticed — the same class of drift the prompt\n * catalogue had. With no registry available (headless callers) it degrades to\n * a pointer instead of an outdated list.\n */\nfunction docToolBullet(tools: readonly PromptToolSummary[]): string {\n if (tools.length === 0) {\n return \"* **agent 工具**:`tiddlywiki_*` 系列(检索 / 读写 / 批量写 / 增量追加 / 重命名 / 删除与回收站 / 反向链接 / 附件 / 体检 / git 同步与冲突解决)——完整清单与参数见 DSH 设置页与各工具的 schema。\"\n }\n return ['* **' + String(tools.length) + ' 个 agent 工具**(参数以工具 schema 为准,\\u0060?\\u0060 表示可选):', ...toolSignatureLines(tools, '* ')].join('\\n')\n}\n\nconst DOC_NOTE_TAIL = `* **TW 编辑器面板**:侧边栏「TiddlyWiki」按钮 → 在界面中央打开完整版 TW 编辑器。\n* **快速笔记**:点击聊天输入框上方或右下角「知识库」菜单里的「📝 快速笔记」——默认**直达 TW 原生编辑页**(独立小窗,草稿自动续写);也可在设置页切回 Markdown 卡片(语法高亮、文件上传、多选 tag、草稿自动保存、Ctrl+Enter 保存)。「✏️ 在 TW 中编辑」会弹出独立小窗用 TW 原生编辑器编辑。\n* **一键同步**:「知识库」按钮 → 「🔁 同步」一键 pull → commit → push,按钮上的状态点实时反映 git 状态。\n* **git 同步**:写入自动防抖 commit(默认 60 秒);手动 \\`tiddlywiki_git_sync action=sync\\` 做 pull → commit → push。\n* **设置页**:DSH 设置 → 「TiddlyWiki 知识库」管理插件/主题/语言与运行配置(含「知识库」按钮相关显示开关)。\n* **注入给 Agent 的提示词可配置**(设置页「系统提示词」区块):默认**精简版**只约定同步纪律/标签/链接格式(工具参数由工具 schema 提供,不重复);可切完整版(附参数索引)、追加自定义规范(\\`extra\\`)、整段替换(\\`override\\`)或整体停用。保存即生效,无需重启 dsh web;点「查看当前注入文本」可预览下一步实际注入的全文。\n\n!! 知识库纪律(四条)\n\n1. 开工先 \\`tiddlywiki_git_sync action=pull\\`(rebase + autostash,真冲突会自动 abort 并报文件)。\n2. 冲突后:\\`tiddlywiki_git_resolve files=[冲突文件] strategy=keep-local|keep-remote\\` 按 tiddler 二选一解决,再重新 sync。\n3. 收工 \\`tiddlywiki_git_sync action=sync\\`。\n4. 插件自动 commit 兜底,手动 sync 用于需要主动推送的场合。\n\n!! 主题与语言\n\n* **主题**分两层:每行一个「☑ 加载」(多选 = TW 里可用的主题,依赖链自动带上)和「◉ 活动」(单选 = 当前视觉主题)。应用后自动重启 TW。\n* **语言**:设置页勾选 \\`zh-Hans\\`(简体)并应用,TW 界面即切换为中文。\n\n!! 说明\n\n* 本笔记由插件在**首次启动**时自动写入(一次性:只写一次)。删除后重启 dsh web **不会自动恢复**——它从此归你所有。\n* 本笔记带 \\`dsh-docs\\` 标签,会出现在首页「📚 插件文档」栏(与「示例与文档」seed 的教程/模板一起),不需要可自由删除。\n* 更多细节见插件仓库 README。`\n\n/**\n * The doc note text. `tools` should be the live registry summary\n * (`tiddlywikiToolSummary()`), so the tool list can never go stale.\n */\nexport function docNoteText(tools: readonly PromptToolSummary[] = []): string {\n return `${DOC_NOTE_HEAD}${docToolBullet(tools)}${DOC_NOTE_TAIL}`\n}\n\n/** Back-compat constant: the note as built without a live registry. */\nexport const DOC_NOTE_TEXT = docNoteText()\n\n/**\n * Seed the doc note once per wiki (mirrors the one-shot policy). A marker\n * tiddler records that the note has been offered; from then on the note is\n * user-owned and is never re-created (deleting it survives restarts).\n *\n * With `opts.force` the note is (re)written even when it already exists and\n * the marker is (re)written — the settings page uses this for\n * \"重新初始化\". Returns whether a note was written this call. Throws when a read fails (the seed registry reports it as `ok:false`).\n */\nexport async function seedDocNote(client: TiddlyWebClient, opts?: { force?: boolean; tools?: readonly PromptToolSummary[] }): Promise<boolean> {\n const force = opts?.force === true\n if (!force) {\n const marker = await readSeedTiddler(client, SEED_MARKER_TITLE)\n if (marker !== undefined) return false\n }\n const existing = await readSeedTiddler(client, DOC_NOTE_TITLE)\n let wrote = false\n if (force || existing === undefined) {\n await client.put({\n title: DOC_NOTE_TITLE,\n text: docNoteText(opts?.tools ?? []),\n type: 'text/vnd.tiddlywiki',\n tags: [DOC_NOTE_TAG, DOC_NOTE_DSH_DOCS_TAG],\n })\n wrote = true\n }\n // Record the offer regardless, so an existing note (upgrade from an older\n // create-if-missing version) also becomes user-owned from here on.\n await writeSeedMarker(client, SEED_MARKER_TITLE)\n return wrote\n}\n\n/**\n * Un-seed (反初始化): remove the doc note and its one-shot marker, returning\n * the wiki to the \"never offered\" state. Deletion is idempotent — a tiddler\n * that was already gone is simply not listed. Throws when a read fails (the seed registry reports it as `ok:false`).\n */\nexport async function unseedDocNote(client: TiddlyWebClient): Promise<{ removed: string[] }> {\n const removed: string[] = []\n for (const title of [DOC_NOTE_TITLE, SEED_MARKER_TITLE]) {\n const t = await readSeedTiddler(client, title)\n if (t !== undefined) {\n await client.delete(title)\n removed.push(title)\n }\n }\n return { removed }\n}\n","/**\n * The \"示例与文档\" starter seed (v0.16.22): the generic docs / templates /\n * example pages that give a fresh wiki a working \"文档中心\" out of the box —\n * 主题汇总页·模板 (blank template) + 教程:按主题/标签做汇总页 (how-to) + three\n * runnable 主题页 examples (日志 / 决策记录 / 排障).\n *\n * Sanitized: every tiddler carries NO personal data — the tutorial's\n * 配套产物清单 points only at the tiddlers this seed actually provides, and\n * the example pages auto-collect by tag (text verbatim from the author's wiki,\n * they were already generic). Every item gets the shared `dsh-docs` tag so the\n * seeded home's「📚 插件文档」tabs strip collects them automatically.\n *\n * Tier: STARTER — seeded automatically on first install (safe-skip: any\n * same-named tiddler already present is never overwritten) and removable via\n *「反初始化」from the settings page (ONE-SHOT marker-gated, like doc-note).\n *\n * @module dsh-tiddlywiki/host/seed-starter-docs\n */\nimport type { TiddlyWebClient } from './tw-api.ts'\nimport { readSeedTiddler, writeSeedMarker } from './seed-util.ts'\n\n/** One-time marker: presence means \"the docs were offered once — hands off\". */\nexport const STARTER_DOCS_MARKER_TITLE = '$:/plugins/dsh-tiddlywiki/seed-starter-docs'\n\n/** Shared tag that collects every plugin-seeded doc into the home docs tab. */\nexport const DSH_DOCS_TAG = 'dsh-docs'\n\nexport interface StarterDocItem {\n title: string\n tags: string[]\n type: string\n text: string\n}\n\n/** The docs, exactly as seeded (user-owned afterwards, freely deletable). */\nexport const STARTER_DOCS_ITEMS: StarterDocItem[] = [\n {\n title: '主题汇总页·模板',\n tags: ['索引', 'dsh-docs'],\n type: 'text/vnd.tiddlywiki',\n text: `\\\\whitespace trim\n\n!! 📌 主题汇总页\n\n<div style=\"color:#888; font-size:0.85em; border:1px dashed rgba(128,128,128,0.35); border-radius:8px; padding:6px 10px; margin-bottom:8px;\">【模板】复制本页 → 改名(如「XX主题汇总」)→ 把下面两处 <code>主题A</code> 替换成你的标签名 → 保存。给笔记打上该标签即自动收录,本页无需维护。</div>\n\n<div class=\"tc-message-box\">自动收集带 <code>主题A</code> 标签的笔记,按最近修改排序,共 <strong>{{{[tag[主题A]!is[system]!has[draft.of]count[]]}}}</strong> 篇。</div>\n\n<<list-links \"[tag[主题A]!is[system]!has[draft.of]] +[!sort[modified]]\">>\n\n---\n\n### 可选:自定义样式的 <$list> 版(想更花哨时用它替换上面的 list-links)\n\n<ul>\n<$list filter=\"[tag[主题A]!is[system]!has[draft.of]] +[!sort[modified]]\">\n<li><$link to=<<currentTiddler>>><$view field=\"title\"/></$link><span style=\"color:#aaa; font-size:0.85em;\"> · <$view field=\"modified\" format=\"relativedate\"/></span></li>\n</$list>\n</ul>\n\n### 可选:把本页收进「一页多主题」tabs\n\n给本页打上 <code>主题页</code> 标签、并加一个 <code>caption</code> 字段(按钮文字),然后在总览页写:<code>&lt;&lt;tabs \"[tag[主题页]!is[system]]\"&gt;&gt;</code>。详见 [[教程:按主题/标签做汇总页]]。\n`,\n },\n {\n title: '教程:按主题/标签做汇总页',\n tags: ['教程', 'dsh-docs'],\n type: 'text/markdown',\n text: `# 教程:按主题/标签做汇总页\n\n> 适用:本 wiki 的 TiddlyWiki 5.3.x 内核。⚠️ 网上大量教程是 5.1/5.2 时代写的,其中 \\`<<tabs \"筛选器\" \"标签\">>\\` 和 \\`<<count \"筛选器\">>\\` 两种写法在本版本已**改版/移除**,照抄会报错,注意甄别。\n\n## 核心思路(一句话)\n\n「汇总页」不是 TW 的特殊功能,而是:**一个普通笔记 + 正文里一段筛选器(filter)**。渲染时 TW 实时从整个 wiki 挑出符合条件的笔记列出来。**新笔记只要打上对应标签,汇总页自动出现,零维护**——不用像手工目录那样每次手动更新。\n\n## 方法一:零代码,直接用内置标签页\n\n- 打开任意笔记,点正文底部的**标签链接**;\n- 或侧边栏「标签」(标签云)里点某个标签。\n- TW 会自动生成该标签的页面,列出所有带此标签的笔记。\n- 优点:一行代码都不用写;缺点:不能加说明文字、不能自定义排序/样式,也做不了「多主题合一」。\n\n## 方法二:一页一主题(最常用)\n\n新建一个笔记(wikitext),正文写:\n\n\\`\\`\\`wikitext\n!! 📔 日志主题汇总\n<div class=\"tc-message-box\">自动收集带 <code>日志</code> 标签的笔记,共 <strong>{{{[tag[日志]!is[system]!has[draft.of]count[]]}}}</strong> 篇,按最近修改排序。</div>\n\n<<list-links \"[tag[日志]!is[system]!has[draft.of]] +[!sort[modified]]\">>\n\\`\\`\\`\n\n各片段含义:\n\n- \\`[tag[日志]]\\`:挑出所有带「日志」标签的笔记;\n- \\`!is[system]\\`:排除 \\`$:/\\` 系统页;\n- \\`!has[draft.of]\\`:排除未保存的草稿副本;\n- \\`+[!sort[modified]]\\`:按修改时间**倒序**(\\`sort[modified]\\` 为正序);\n- \\`{{{[...count[]]}}}\\`:筛选器计数(旧版 \\`<<count>>\\` 宏已移除,这是 5.3 新写法)。\n\n想自定义每行样式(带相对时间、复选框等),用你更熟悉的 \\`<$list>\\` 写法:\n\n\\`\\`\\`wikitext\n<ul>\n<$list filter=\"[tag[日志]!is[system]!has[draft.of]] +[!sort[modified]]\">\n<li><$link to=<<currentTiddler>>><$view field=\"title\"/></$link><span style=\"color:#aaa; font-size:0.85em;\"> · <$view field=\"modified\" format=\"relativedate\"/></span></li>\n</$list>\n</ul>\n\\`\\`\\`\n\n## 方法三:一页多主题(Dashboard,5.3 新版 tabs)\n\n5.3 的 \\`tabs\\` 宏签名:\\`<<tabs \"选中标签页的筛选器\" \"默认选中项\">>\\`。思路分两步:\n\n1. **每个主题各建一个汇总页**(就是方法二那种),全部打上同一个标签(本插件 seed 的示例用 \\`主题页\\`);要自定义按钮文字,就给该页加一个 \\`caption\\` 字段(如 \\`📔 日志\\`)。\n2. **建一个总览页**,正文只写一行:\n\n\\`\\`\\`wikitext\n<<tabs \"[tag[主题页]!is[system]]\" \"主题页·日志\">>\n\\`\\`\\`\n\n效果:页面上方一排按钮,点哪个就内嵌显示哪个主题的汇总。第二个参数是默认展开的页(可省略,省略则初始不展开)。\n\n> 老教程的 \\`<<tabs \"筛选器A\" \"标签A\" \"筛选器B\" \"标签B\">>\\` 在 5.3 已失效,别用。\n\n本插件已 seed 了可直接运行的示例:[主题页·日志]、[主题页·决策记录]、[主题页·排障](都带 \\`主题页\\` 标签),复制改造即可。\n\n## 筛选器速查表(背熟这几行就够用)\n\n| 想要 | 写法 |\n| --- | --- |\n| 某标签下的笔记 | \\`[tag[主题]]\\` |\n| 排除系统页 / 草稿 | \\`[tag[主题]!is[system]!has[draft.of]]\\` |\n| 修改时间倒序 / 正序 | \\`+[!sort[modified]]\\` / \\`+[sort[modified]]\\` |\n| 按标题排序 | \\`+[sort[title]]\\` |\n| 交集(同时有两个标签) | \\`[tag[A]+tag[B]]\\` |\n| 并集(有任一标签) | \\`[tag[A]] [tag[B]]\\` |\n| 差集(有 A 无 B) | \\`[tag[A]-tag[B]]\\` |\n| 排除某标签 | \\`-[tag[排除项]]\\` |\n| 限定时间段 | \\`[tag[主题]modified[2026-09]]\\` |\n| 计数 | \\`{{{[tag[主题]!is[system]count[]]}}}\\`(或 \\`<$count filter=\"[tag[主题]]\"/>\\`) |\n| 整个 wiki 的非系统非草稿 | \\`all[tiddlers]!is[system]!has[draft.of]\\` |\n\n## 进阶玩法\n\n1. **按字段筛选**:\\`[tag[todo]get[due]compare:date:lt<today>]\\` 可列出「到期日早于今天」的笔记(主页的逾期看板就是这么写的)。\n2. **树状目录**:\\`<<toc tag:\"主题\">>\\` 显示层级目录,适合一个主题下还有子主题的情况。\n3. **模板复用**:复制 [主题汇总页·模板] 改标题和标签即可,不用每次重写。\n4. **打开直达**:把 \\`$:/DefaultTiddlers\\` 改成 \\`[[主题页·日志]]\\`,启动即打开汇总页(当前默认是 [🏠 主页])。\n5. **导航收录**:给汇总页打 \\`索引\\` 标签,它就会从「全部笔记 / 所有文章」里消失、只作为导航页存在;再在 [🏠 主页] 的按钮区加一个入口按钮即可。\n6. **标签整理**:控制台 → 工具 → 标签管理器,可重命名/合并标签,改名后所有笔记自动跟随,汇总页筛选器无需改动(筛选器按标签名匹配,自动跟随改名)。\n\n## 配套产物清单(插件 seed 提供了什么)\n\n- [主题页·日志]、[主题页·决策记录]、[主题页·排障] —— 单主题汇总页实例(各自可独立打开,也可作为「一页多主题」tabs 的标签页)\n- [主题汇总页·模板] —— 空白模板,复制即用\n- 本教程 —— 就是这一页\n\n以上都来自「示例与文档」seed(tag \\`dsh-docs\\`),不需要可自由删除,删除后不会自动恢复。\n`,\n },\n {\n title: '主题页·日志',\n tags: ['主题页', '索引', 'dsh-docs'],\n type: 'text/vnd.tiddlywiki',\n text: `\\\\whitespace trim\n\n!! 📔 日志主题汇总\n\n<div class=\"tc-message-box\">自动收集带 <code>日志</code> 标签的笔记,按最近修改排序,共 <strong>{{{[tag[日志]!is[system]!has[draft.of]count[]]}}}</strong> 篇。给笔记打上「日志」标签即自动收录,本页无需维护(做法见 [[教程:按主题/标签做汇总页]])。</div>\n\n<<list-links \"[tag[日志]!is[system]!has[draft.of]] +[!sort[modified]]\">>\n`,\n },\n {\n title: '主题页·决策记录',\n tags: ['主题页', '索引', 'dsh-docs'],\n type: 'text/vnd.tiddlywiki',\n text: `\\\\whitespace trim\n\n!! 📌 决策记录主题汇总\n\n<div class=\"tc-message-box\">自动收集带 <code>决策记录</code> 标签的笔记,按最近修改排序,共 <strong>{{{[tag[决策记录]!is[system]!has[draft.of]count[]]}}}</strong> 篇。给笔记打上「决策记录」标签即自动收录,本页无需维护(做法见 [[教程:按主题/标签做汇总页]])。</div>\n\n<<list-links \"[tag[决策记录]!is[system]!has[draft.of]] +[!sort[modified]]\">>\n`,\n },\n {\n title: '主题页·排障',\n tags: ['主题页', '索引', 'dsh-docs'],\n type: 'text/vnd.tiddlywiki',\n text: `\\\\whitespace trim\n\n!! 🔧 排障主题汇总\n\n<div class=\"tc-message-box\">自动收集带 <code>troubleshooting</code> 标签的笔记,按最近修改排序,共 <strong>{{{[tag[troubleshooting]!is[system]!has[draft.of]count[]]}}}</strong> 篇。给笔记打上「troubleshooting」标签即自动收录,本页无需维护(做法见 [[教程:按主题/标签做汇总页]])。</div>\n\n<<list-links \"[tag[troubleshooting]!is[system]!has[draft.of]] +[!sort[modified]]\">>\n`,\n },\n]\n\n/**\n * Seed the starter docs once per wiki (safe-skip: same-named tiddlers already\n * present are NEVER overwritten — user data stays user data). Marker-gated\n * ONE-SHOT; with `opts.force` the tiddlers are (re)written and the marker\n * (re)recorded — the settings page uses this for \"重新初始化\".\n * Returns whether anything was written this call. Throws when a read fails (the seed registry reports it as `ok:false`).\n */\nexport async function seedStarterDocs(client: TiddlyWebClient, opts?: { force?: boolean }): Promise<boolean> {\n const force = opts?.force === true\n if (!force) {\n const marker = await readSeedTiddler(client, STARTER_DOCS_MARKER_TITLE)\n if (marker !== undefined) return false\n }\n let wrote = false\n for (const item of STARTER_DOCS_ITEMS) {\n const existing = await readSeedTiddler(client, item.title)\n if (force || existing === undefined) {\n await client.put({ title: item.title, text: item.text, type: item.type, tags: item.tags })\n wrote = true\n }\n }\n await writeSeedMarker(client, STARTER_DOCS_MARKER_TITLE)\n return wrote\n}\n\n/**\n * Un-seed (反初始化): remove the starter docs and their marker. Deletion is\n * idempotent — a tiddler already gone is not listed. Throws when a read fails (the seed registry reports it as `ok:false`).\n */\nexport async function unseedStarterDocs(client: TiddlyWebClient): Promise<{ removed: string[] }> {\n const removed: string[] = []\n for (const item of STARTER_DOCS_ITEMS) {\n const t = await readSeedTiddler(client, item.title)\n if (t !== undefined) {\n await client.delete(item.title)\n removed.push(item.title)\n }\n }\n const marker = await readSeedTiddler(client, STARTER_DOCS_MARKER_TITLE)\n if (marker !== undefined) {\n await client.delete(STARTER_DOCS_MARKER_TITLE)\n removed.push(STARTER_DOCS_MARKER_TITLE)\n }\n return { removed }\n}","/**\n * Generated from the wiki bundle (do not hand-edit the constant).\n * Source: send-to-agent.bundle.json\n *\n * The \"发送给 Agent\" TW button, packaged as a TiddlyWiki plugin bundle\n * (`{\"tiddlers\": {...}}`), seeded into fresh wikis by seedSendToAgent.\n *\n * @module dsh-tiddlywiki/host/seed-send-to-agent\n */\nimport type { TiddlyWebClient } from './tw-api.ts'\nimport { readSeedTiddler, writeSeedMarker } from './seed-util.ts'\n\n/** The packaged plugin tiddler title (a TW system tiddler, type application/json). */\nexport const SEND_TO_AGENT_PLUGIN_TITLE = '$:/plugins/dsh/send-to-agent'\n\n/** One-time marker: presence means \"the button was offered once — hands off\". */\nexport const SEND_TO_AGENT_MARKER_TITLE = '$:/plugins/dsh-tiddlywiki/seed-send-to-agent'\n\n/** The bundle's JSON text (`{\"tiddlers\": {...}}`), exactly as TW stores it. */\nexport const SEND_TO_AGENT_BUNDLE_TEXT = \"{\\n \\\"tiddlers\\\": {\\n \\\"$:/plugins/dsh/send-to-agent/plugin.info\\\": {\\n \\\"title\\\": \\\"$:/plugins/dsh/send-to-agent/plugin.info\\\",\\n \\\"type\\\": \\\"application/json\\\",\\n \\\"text\\\": \\\"{\\\\\\\"title\\\\\\\":\\\\\\\"$:/plugins/dsh/send-to-agent\\\\\\\",\\\\\\\"name\\\\\\\":\\\\\\\"Send to Agent\\\\\\\",\\\\\\\"description\\\\\\\":\\\\\\\"把当前笔记一键发送给 DSH Agent(TiddlyWiki → DSH 会话注入)\\\\\\\",\\\\\\\"author\\\\\\\":\\\\\\\"dsh-tiddlywiki\\\\\\\",\\\\\\\"version\\\\\\\":\\\\\\\"0.3.5\\\\\\\",\\\\\\\"plugin-type\\\\\\\":\\\\\\\"plugin\\\\\\\"}\\\"\\n },\\n \\\"$:/plugins/dsh/send-to-agent/startup.js\\\": {\\n \\\"title\\\": \\\"$:/plugins/dsh/send-to-agent/startup.js\\\",\\n \\\"type\\\": \\\"application/javascript\\\",\\n \\\"module-type\\\": \\\"startup\\\",\\n \\\"text\\\": \\\"/*\\\\\\\\\\\\ntitle: $:/plugins/dsh/send-to-agent/startup.js\\\\ntype: application/javascript\\\\nmodule-type: startup\\\\n\\\\n\\\\\\\\*/\\\\n(function(){\\\\n\\\\n/*jslint node: true, browser: true */\\\\n/*global $tw: false */\\\\n\\\\\\\"use strict\\\\\\\";\\\\n\\\\nexports.name = \\\\\\\"dsh-send-to-agent\\\\\\\";\\\\nexports.after = [\\\\\\\"story\\\\\\\"];\\\\nexports.platforms = [\\\\\\\"browser\\\\\\\"];\\\\n\\\\nfunction readConfig() {\\\\n\\\\tvar config = { enabled: true, endpoint: \\\\\\\"\\\\\\\", token: \\\\\\\"\\\\\\\" };\\\\n\\\\ttry {\\\\n\\\\t\\\\tvar t = $tw.wiki.getTiddler(\\\\\\\"$:/plugins/dsh-tiddlywiki/config\\\\\\\");\\\\n\\\\t\\\\tif (t && t.fields && typeof t.fields.text === \\\\\\\"string\\\\\\\") {\\\\n\\\\t\\\\t\\\\tvar parsed = JSON.parse(t.fields.text);\\\\n\\\\t\\\\t\\\\tvar s2a = parsed && parsed.ui && parsed.ui.sendToAgent;\\\\n\\\\t\\\\t\\\\tif (s2a) {\\\\n\\\\t\\\\t\\\\t\\\\tif (typeof s2a.enabled === \\\\\\\"boolean\\\\\\\") { config.enabled = s2a.enabled; }\\\\n\\\\t\\\\t\\\\t\\\\tif (typeof s2a.endpoint === \\\\\\\"string\\\\\\\" && s2a.endpoint.length > 0) { config.endpoint = s2a.endpoint; }\\\\n\\\\t\\\\t\\\\t\\\\tif (typeof s2a.token === \\\\\\\"string\\\\\\\" && s2a.token.length > 0) { config.token = s2a.token; }\\\\n\\\\t\\\\t\\\\t}\\\\n\\\\t\\\\t}\\\\n\\\\t} catch (e) {}\\\\n\\\\treturn config;\\\\n}\\\\n\\\\nfunction baseEndpoint() {\\\\n\\\\tvar config = readConfig();\\\\n\\\\tif (config.endpoint.length > 0) { return config.endpoint.replace(/\\\\\\\\/+$/, \\\\\\\"\\\\\\\"); }\\\\n\\\\tif (typeof location !== \\\\\\\"undefined\\\\\\\" && location.origin) { return location.origin + \\\\\\\"/dsh-tiddlywiki\\\\\\\"; }\\\\n\\\\treturn \\\\\\\"/dsh-tiddlywiki\\\\\\\";\\\\n}\\\\n\\\\n/*\\\\nShow a transient message.\\\\n\\\\nTW's notifier renders a TIDDLER by title and does **nothing at all** when that\\\\ntiddler does not exist (core/modules/utils/dom/notifier.js: \\\\\\\"Don't do anything\\\\nif the tiddler doesn't exist\\\\\\\"). Passing free text as the title — which is what\\\\nthis function used to do — therefore made every success/failure notice of the\\\\n「发送给 Agent」 button invisible: the user clicked, chose a session, and saw\\\\nnothing. Store the message in a $:/temp tiddler first (volatile: never synced,\\\\nnever written to disk) and display THAT title.\\\\n*/\\\\nvar NOTICE_TITLE = \\\\\\\"$:/temp/dsh/send-to-agent/notice\\\\\\\";\\\\nfunction notify(msg) {\\\\n\\\\ttry {\\\\n\\\\t\\\\tif ($tw.wiki && $tw.notifier && typeof $tw.notifier.display === \\\\\\\"function\\\\\\\" && typeof $tw.wiki.addTiddler === \\\\\\\"function\\\\\\\") {\\\\n\\\\t\\\\t\\\\t$tw.wiki.addTiddler({ title: NOTICE_TITLE, text: String(msg), type: \\\\\\\"text/vnd.tiddlywiki\\\\\\\" });\\\\n\\\\t\\\\t\\\\t$tw.notifier.display(NOTICE_TITLE);\\\\n\\\\t\\\\t\\\\treturn;\\\\n\\\\t\\\\t}\\\\n\\\\t} catch (e) {}\\\\n\\\\tif (typeof alert === \\\\\\\"function\\\\\\\") { alert(msg); }\\\\n}\\\\n\\\\nfunction doSend(payload, sessionId, note) {\\\\n\\\\tvar config = readConfig();\\\\n\\\\tvar lines = [];\\\\n\\\\tlines.push(\\\\\\\"《\\\\\\\" + payload.title + \\\\\\\"》\\\\\\\");\\\\n\\\\tlines.push(\\\\\\\"标签: \\\\\\\" + (payload.tags || []).join(\\\\\\\", \\\\\\\"));\\\\n\\\\tlines.push(\\\\\\\"类型: \\\\\\\" + (payload.type || \\\\\\\"无\\\\\\\"));\\\\n\\\\tlines.push(\\\\\\\"\\\\\\\");\\\\n\\\\tlines.push(\\\\\\\"【待办说明】以下内容是我(用户)提前编辑在 TiddlyWiki 知识库中的待办事项,通过「发送给 Agent」一键发送给你处理。请按内容执行;如有任何不清楚的地方,请主动向我提问,不要臆测或擅自发挥。\\\\\\\");\\\\n\\\\tlines.push(\\\\\\\"\\\\\\\");\\\\n\\\\tlines.push(payload.text || \\\\\\\"\\\\\\\");\\\\n\\\\t// 附加说明放在消息最后:正文之后、作为我(用户)的最终补充要求,优先遵循。\\\\n\\\\tif (note && String(note).trim().length > 0) {\\\\n\\\\t\\\\tlines.push(\\\\\\\"\\\\\\\");\\\\n\\\\t\\\\tlines.push(\\\\\\\"【附加说明】\\\\\\\" + String(note).trim());\\\\n\\\\t}\\\\n\\\\tvar text = lines.join(\\\\\\\"\\\\\\\\n\\\\\\\");\\\\n\\\\tvar headers = { \\\\\\\"Content-Type\\\\\\\": \\\\\\\"application/json\\\\\\\" };\\\\n\\\\tif (config.token.length > 0) { headers[\\\\\\\"x-send-to-agent-token\\\\\\\"] = config.token; }\\\\n\\\\t$tw.utils.httpRequest({\\\\n\\\\t\\\\turl: baseEndpoint() + \\\\\\\"/agent/send\\\\\\\",\\\\n\\\\t\\\\ttype: \\\\\\\"POST\\\\\\\",\\\\n\\\\t\\\\theaders: headers,\\\\n\\\\t\\\\tdata: JSON.stringify({ sessionId: sessionId, text: text }),\\\\n\\\\t\\\\tcallback: function(err, data) {\\\\n\\\\t\\\\t\\\\tvar parsed = null;\\\\n\\\\t\\\\t\\\\ttry { parsed = JSON.parse(data || \\\\\\\"\\\\\\\"); } catch (e) {}\\\\n\\\\t\\\\t\\\\tif (err) { notify(\\\\\\\"发送失败:\\\\\\\" + err); return; }\\\\n\\\\t\\\\t\\\\tif (parsed && parsed.ok) {\\\\n\\\\t\\\\t\\\\t\\\\tnotify(\\\\\\\"已发送 ✓ 会话 \\\\\\\" + sessionId.slice(0, 8));\\\\n\\\\t\\\\t\\\\t} else {\\\\n\\\\t\\\\t\\\\t\\\\tnotify(\\\\\\\"发送失败:\\\\\\\" + ((parsed && parsed.error) || data || \\\\\\\"未知错误\\\\\\\"));\\\\n\\\\t\\\\t\\\\t}\\\\n\\\\t\\\\t}\\\\n\\\\t});\\\\n}\\\\n\\\\nfunction closeOverlay(overlay, escHandler) {\\\\n\\\\tif (escHandler) { document.removeEventListener(\\\\\\\"keydown\\\\\\\", escHandler); }\\\\n\\\\tif (overlay && overlay.parentNode) { overlay.parentNode.removeChild(overlay); }\\\\n}\\\\n\\\\nfunction showPicker(payload, items, modes, defaultId, permissions) {\\\\n\\\\tif (typeof document === \\\\\\\"undefined\\\\\\\") { return; }\\\\n\\\\t// remove any previously-open picker\\\\n\\\\tvar old = document.getElementById(\\\\\\\"dsh-send-picker\\\\\\\");\\\\n\\\\tif (old && old.parentNode) { old.parentNode.removeChild(old); }\\\\n\\\\n\\\\tvar state = { workspace: null, mode: \\\\\\\"\\\\\\\", note: \\\\\\\"\\\\\\\", permission: \\\\\\\"\\\\\\\" };\\\\n\\\\n\\\\tvar overlay = document.createElement(\\\\\\\"div\\\\\\\");\\\\n\\\\toverlay.id = \\\\\\\"dsh-send-picker\\\\\\\";\\\\n\\\\toverlay.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.35);z-index:99999;display:flex;align-items:center;justify-content:center;\\\\\\\");\\\\n\\\\tvar box = document.createElement(\\\\\\\"div\\\\\\\");\\\\n\\\\tbox.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"background:#fff;color:#333;border-radius:8px;padding:14px;min-width:340px;max-width:560px;max-height:82vh;display:flex;flex-direction:column;box-shadow:0 8px 30px rgba(0,0,0,.3);font-family:system-ui,-apple-system,sans-serif;\\\\\\\");\\\\n\\\\n\\\\t// header\\\\n\\\\tvar header = document.createElement(\\\\\\\"div\\\\\\\");\\\\n\\\\theader.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"display:flex;align-items:center;justify-content:space-between;margin-bottom:6px;\\\\\\\");\\\\n\\\\tvar h = document.createElement(\\\\\\\"h3\\\\\\\");\\\\n\\\\th.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"margin:0;font-size:15px;\\\\\\\");\\\\n\\\\th.textContent = \\\\\\\"发送给 Agent · 选择目标\\\\\\\";\\\\n\\\\tvar closeX = document.createElement(\\\\\\\"button\\\\\\\");\\\\n\\\\tcloseX.type = \\\\\\\"button\\\\\\\";\\\\n\\\\tcloseX.textContent = \\\\\\\"✕\\\\\\\";\\\\n\\\\tcloseX.title = \\\\\\\"关闭\\\\\\\";\\\\n\\\\tcloseX.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"border:none;background:transparent;font-size:15px;cursor:pointer;color:#888;padding:2px 8px;border-radius:4px;\\\\\\\");\\\\n\\\\tcloseX.addEventListener(\\\\\\\"click\\\\\\\", function() { closeOverlay(overlay, escHandler); });\\\\n\\\\theader.appendChild(h);\\\\n\\\\theader.appendChild(closeX);\\\\n\\\\tbox.appendChild(header);\\\\n\\\\n\\\\t// description\\\\n\\\\tvar desc = document.createElement(\\\\\\\"p\\\\\\\");\\\\n\\\\tdesc.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"margin:0 0 8px;font-size:12px;color:#888;word-break:break-all;\\\\\\\");\\\\n\\\\tdesc.textContent = \\\\\\\"《\\\\\\\" + payload.title + \\\\\\\"》将作为消息注入所选会话\\\\\\\";\\\\n\\\\tbox.appendChild(desc);\\\\n\\\\n\\\\t// 附加说明(可选)— any extra context the user wants to attach to the\\\\n\\\\t// message, e.g. what to focus on or how to handle it.\\\\n\\\\tvar noteRow = document.createElement(\\\\\\\"div\\\\\\\");\\\\n\\\\tnoteRow.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"margin:0 0 8px;\\\\\\\");\\\\n\\\\tvar noteLbl = document.createElement(\\\\\\\"label\\\\\\\");\\\\n\\\\tnoteLbl.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"display:block;font-size:12px;color:#666;margin-bottom:4px;\\\\\\\");\\\\n\\\\tnoteLbl.textContent = \\\\\\\"附加说明(可选,随笔记一起发给 Agent)\\\\\\\";\\\\n\\\\tnoteRow.appendChild(noteLbl);\\\\n\\\\tvar noteTa = document.createElement(\\\\\\\"textarea\\\\\\\");\\\\n\\\\tnoteTa.placeholder = \\\\\\\"例如:请重点看第 3 条;我希望你按 XX 方式处理…\\\\\\\";\\\\n\\\\tnoteTa.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"width:100%;box-sizing:border-box;padding:6px 8px;border:1px solid #ccc;border-radius:6px;font-size:13px;min-height:44px;resize:vertical;font-family:inherit;\\\\\\\");\\\\n\\\\tnoteTa.addEventListener(\\\\\\\"input\\\\\\\", function() { state.note = noteTa.value; });\\\\n\\\\tnoteRow.appendChild(noteTa);\\\\n\\\\tbox.appendChild(noteRow);\\\\n\\\\n\\\\t// 工作模式(Agent 预设)selector — applies to newly created sessions; the\\\\n\\\\t// modes come from GET /agent/modes (id/name/description + deployment\\\\n\\\\t// default). When the modes endpoint is unreachable (e.g. an older host) the\\\\n\\\\t// row degrades to a hint and no mode is sent — DSH uses its default.\\\\n\\\\tvar modeRow = document.createElement(\\\\\\\"div\\\\\\\");\\\\n\\\\tmodeRow.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"margin:0 0 8px;\\\\\\\");\\\\n\\\\tif (modes && modes.length > 0) {\\\\n\\\\t\\\\tvar modeLbl = document.createElement(\\\\\\\"label\\\\\\\");\\\\n\\\\t\\\\tmodeLbl.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"display:block;font-size:12px;color:#666;margin-bottom:4px;\\\\\\\");\\\\n\\\\t\\\\tmodeLbl.textContent = \\\\\\\"工作模式(Agent 预设)— 用于新建会话\\\\\\\";\\\\n\\\\t\\\\tmodeRow.appendChild(modeLbl);\\\\n\\\\t\\\\tvar sel = document.createElement(\\\\\\\"select\\\\\\\");\\\\n\\\\t\\\\tsel.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"width:100%;box-sizing:border-box;padding:6px 8px;border:1px solid #ccc;border-radius:6px;font-size:13px;background:#fff;\\\\\\\");\\\\n\\\\t\\\\tvar optDefault = document.createElement(\\\\\\\"option\\\\\\\");\\\\n\\\\t\\\\toptDefault.value = \\\\\\\"\\\\\\\";\\\\n\\\\t\\\\toptDefault.textContent = \\\\\\\"(默认模式)\\\\\\\";\\\\n\\\\t\\\\tsel.appendChild(optDefault);\\\\n\\\\t\\\\tmodes.forEach(function(m) {\\\\n\\\\t\\\\t\\\\tvar o = document.createElement(\\\\\\\"option\\\\\\\");\\\\n\\\\t\\\\t\\\\to.value = m.id || \\\\\\\"\\\\\\\";\\\\n\\\\t\\\\t\\\\to.textContent = (m.name || m.id) + (m.isDefault ? \\\\\\\"(默认)\\\\\\\" : \\\\\\\"\\\\\\\");\\\\n\\\\t\\\\t\\\\tsel.appendChild(o);\\\\n\\\\t\\\\t});\\\\n\\\\t\\\\t// preselect the deployment default when listed\\\\n\\\\t\\\\tif (defaultId) {\\\\n\\\\t\\\\t\\\\tfor (var i = 0; i < sel.options.length; i++) {\\\\n\\\\t\\\\t\\\\t\\\\tif (sel.options[i].value === defaultId) { sel.selectedIndex = i; break; }\\\\n\\\\t\\\\t\\\\t}\\\\n\\\\t\\\\t}\\\\n\\\\t\\\\tstate.mode = sel.options[sel.selectedIndex] ? sel.options[sel.selectedIndex].value : \\\\\\\"\\\\\\\";\\\\n\\\\t\\\\tsel.addEventListener(\\\\\\\"change\\\\\\\", function() { state.mode = sel.value; });\\\\n\\\\t\\\\tmodeRow.appendChild(sel);\\\\n\\\\t} else {\\\\n\\\\t\\\\tvar modeHint = document.createElement(\\\\\\\"p\\\\\\\");\\\\n\\\\t\\\\tmodeHint.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"margin:0;font-size:12px;color:#999;\\\\\\\");\\\\n\\\\t\\\\tmodeHint.textContent = \\\\\\\"(未获取到可用工作模式,将使用 DSH 默认模式)\\\\\\\";\\\\n\\\\t\\\\tmodeRow.appendChild(modeHint);\\\\n\\\\t}\\\\n\\\\tbox.appendChild(modeRow);\\\\n\\\\n\\\\t// 权限(权限预设)selector — applies to newly created sessions only; the\\\\n\\\\t// options come from GET /agent/modes' `permissions` roster (each bundles a\\\\n\\\\t// sandbox mode + approval policy). Existing sessions keep their own\\\\n\\\\t// permission, so this only affects \\\\\\\"新建会话并发送\\\\\\\".\\\\n\\\\tvar permRow = document.createElement(\\\\\\\"div\\\\\\\");\\\\n\\\\tpermRow.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"margin:0 0 8px;\\\\\\\");\\\\n\\\\tif (permissions && Array.isArray(permissions.items) && permissions.items.length > 0) {\\\\n\\\\t\\\\tvar permLbl = document.createElement(\\\\\\\"label\\\\\\\");\\\\n\\\\t\\\\tpermLbl.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"display:block;font-size:12px;color:#666;margin-bottom:4px;\\\\\\\");\\\\n\\\\t\\\\tpermLbl.textContent = \\\\\\\"权限(权限预设)— 用于新建会话\\\\\\\";\\\\n\\\\t\\\\tpermRow.appendChild(permLbl);\\\\n\\\\t\\\\tvar psel = document.createElement(\\\\\\\"select\\\\\\\");\\\\n\\\\t\\\\tpsel.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"width:100%;box-sizing:border-box;padding:6px 8px;border:1px solid #ccc;border-radius:6px;font-size:13px;background:#fff;\\\\\\\");\\\\n\\\\t\\\\tpermissions.items.forEach(function(p) {\\\\n\\\\t\\\\t\\\\tvar o = document.createElement(\\\\\\\"option\\\\\\\");\\\\n\\\\t\\\\t\\\\to.value = p.value || \\\\\\\"\\\\\\\";\\\\n\\\\t\\\\t\\\\to.textContent = p.name || p.value;\\\\n\\\\t\\\\t\\\\tif (p.description) { o.title = p.description; }\\\\n\\\\t\\\\t\\\\tpsel.appendChild(o);\\\\n\\\\t\\\\t});\\\\n\\\\t\\\\t// preselect the deployment default when listed\\\\n\\\\t\\\\tif (permissions.defaultId) {\\\\n\\\\t\\\\t\\\\tfor (var j = 0; j < psel.options.length; j++) {\\\\n\\\\t\\\\t\\\\t\\\\tif (psel.options[j].value === permissions.defaultId) { psel.selectedIndex = j; break; }\\\\n\\\\t\\\\t\\\\t}\\\\n\\\\t\\\\t}\\\\n\\\\t\\\\tstate.permission = psel.options[psel.selectedIndex] ? psel.options[psel.selectedIndex].value : \\\\\\\"\\\\\\\";\\\\n\\\\t\\\\tpsel.addEventListener(\\\\\\\"change\\\\\\\", function() { state.permission = psel.value; });\\\\n\\\\t\\\\tpermRow.appendChild(psel);\\\\n\\\\t} else {\\\\n\\\\t\\\\tvar permHint = document.createElement(\\\\\\\"p\\\\\\\");\\\\n\\\\t\\\\tpermHint.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"margin:0;font-size:12px;color:#999;\\\\\\\");\\\\n\\\\t\\\\tpermHint.textContent = \\\\\\\"(未获取到权限预设,新建会话将使用 DSH 默认权限)\\\\\\\";\\\\n\\\\t\\\\tpermRow.appendChild(permHint);\\\\n\\\\t}\\\\n\\\\tbox.appendChild(permRow);\\\\n\\\\n\\\\t// scrollable body\\\\n\\\\tvar body = document.createElement(\\\\\\\"div\\\\\\\");\\\\n\\\\tbody.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"overflow:auto;flex:1;min-height:0;\\\\\\\");\\\\n\\\\tbox.appendChild(body);\\\\n\\\\n\\\\t// footer with cancel\\\\n\\\\tvar footer = document.createElement(\\\\\\\"div\\\\\\\");\\\\n\\\\tfooter.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"display:flex;justify-content:flex-end;gap:8px;margin-top:10px;\\\\\\\");\\\\n\\\\tvar cancel = document.createElement(\\\\\\\"button\\\\\\\");\\\\n\\\\tcancel.type = \\\\\\\"button\\\\\\\";\\\\n\\\\tcancel.textContent = \\\\\\\"取消\\\\\\\";\\\\n\\\\tcancel.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"padding:6px 14px;border:1px solid #ccc;border-radius:6px;background:#fff;cursor:pointer;font-size:13px;\\\\\\\");\\\\n\\\\tcancel.addEventListener(\\\\\\\"click\\\\\\\", function() { closeOverlay(overlay, escHandler); });\\\\n\\\\tfooter.appendChild(cancel);\\\\n\\\\tbox.appendChild(footer);\\\\n\\\\n\\\\toverlay.appendChild(box);\\\\n\\\\tdocument.body.appendChild(overlay);\\\\n\\\\n\\\\t// backdrop click + Esc close\\\\n\\\\toverlay.addEventListener(\\\\\\\"click\\\\\\\", function(e) {\\\\n\\\\t\\\\tif (e.target === overlay) { closeOverlay(overlay, escHandler); }\\\\n\\\\t});\\\\n\\\\tvar escHandler = function(e) { if (e.key === \\\\\\\"Escape\\\\\\\") { closeOverlay(overlay, escHandler); } };\\\\n\\\\tdocument.addEventListener(\\\\\\\"keydown\\\\\\\", escHandler);\\\\n\\\\n\\\\t// group sessions by workspace (cwd)\\\\n\\\\tvar groups = {};\\\\n\\\\tvar order = [];\\\\n\\\\t(items || []).forEach(function(s) {\\\\n\\\\t\\\\tvar key = (s.cwd && String(s.cwd).length > 0) ? s.cwd : \\\\\\\"__default__\\\\\\\";\\\\n\\\\t\\\\tif (!groups[key]) { groups[key] = { cwd: s.cwd, sessions: [], max: 0 }; order.push(key); }\\\\n\\\\t\\\\tgroups[key].sessions.push(s);\\\\n\\\\t\\\\tif ((s.updatedAt || 0) > groups[key].max) { groups[key].max = s.updatedAt || 0; }\\\\n\\\\t});\\\\n\\\\torder.sort(function(a, b) { return groups[b].max - groups[a].max; });\\\\n\\\\n\\\\tfunction btnStyle() {\\\\n\\\\t\\\\treturn \\\\\\\"display:block;width:100%;text-align:left;padding:8px 10px;margin:4px 0;border:1px solid #ddd;border-radius:6px;background:#f7f7f7;cursor:pointer;font-size:13px;\\\\\\\";\\\\n\\\\t}\\\\n\\\\tfunction primaryBtnStyle() {\\\\n\\\\t\\\\treturn \\\\\\\"display:block;width:100%;text-align:center;padding:7px 10px;margin:6px 0 0;border:1px solid #4a90d9;border-radius:6px;background:#4a90d9;color:#fff;cursor:pointer;font-size:13px;\\\\\\\";\\\\n\\\\t}\\\\n\\\\tfunction smallBtnStyle() {\\\\n\\\\t\\\\treturn \\\\\\\"margin:0 0 6px;padding:4px 10px;border:1px solid #ccc;border-radius:6px;background:#fff;cursor:pointer;font-size:12px;color:#555;\\\\\\\";\\\\n\\\\t}\\\\n\\\\n\\\\tfunction shortSessionLabel(s) {\\\\n\\\\t\\\\tvar id = s.sessionId || \\\\\\\"\\\\\\\";\\\\n\\\\t\\\\tvar parts = id.split(\\\\\\\"-\\\\\\\");\\\\n\\\\t\\\\tvar short = (parts.length > 1 ? parts[parts.length - 1] : id).slice(-10);\\\\n\\\\t\\\\tvar when = \\\\\\\"\\\\\\\";\\\\n\\\\t\\\\tif (s.updatedAt) {\\\\n\\\\t\\\\t\\\\tvar diff = Date.now() - s.updatedAt;\\\\n\\\\t\\\\t\\\\tif (diff < 60000) { when = \\\\\\\"刚刚\\\\\\\"; }\\\\n\\\\t\\\\t\\\\telse if (diff < 3600000) { when = Math.floor(diff / 60000) + \\\\\\\" 分钟前\\\\\\\"; }\\\\n\\\\t\\\\t\\\\telse if (diff < 86400000) { when = Math.floor(diff / 3600000) + \\\\\\\" 小时前\\\\\\\"; }\\\\n\\\\t\\\\t\\\\telse {\\\\n\\\\t\\\\t\\\\t\\\\tvar d = new Date(s.updatedAt);\\\\n\\\\t\\\\t\\\\t\\\\twhen = (d.getMonth() + 1) + \\\\\\\"-\\\\\\\" + d.getDate() + \\\\\\\" \\\\\\\" + (\\\\\\\"0\\\\\\\" + d.getHours()).slice(-2) + \\\\\\\":\\\\\\\" + (\\\\\\\"0\\\\\\\" + d.getMinutes()).slice(-2);\\\\n\\\\t\\\\t\\\\t}\\\\n\\\\t\\\\t}\\\\n\\\\t\\\\treturn \\\\\\\"#\\\\\\\" + short + (when ? \\\\\\\" · \\\\\\\" + when : \\\\\\\"\\\\\\\") + (s.running ? \\\\\\\" · ●运行中\\\\\\\" : \\\\\\\"\\\\\\\") + (s.blank ? \\\\\\\" · (空)\\\\\\\" : \\\\\\\"\\\\\\\");\\\\n\\\\t}\\\\n\\\\n\\\\tfunction modeName(id) {\\\\n\\\\t\\\\tif (!id) { return \\\\\\\"\\\\\\\"; }\\\\n\\\\t\\\\tfor (var i = 0; i < (modes || []).length; i++) {\\\\n\\\\t\\\\t\\\\tif (modes[i].id === id) { return modes[i].name || modes[i].id; }\\\\n\\\\t\\\\t}\\\\n\\\\t\\\\treturn id;\\\\n\\\\t}\\\\n\\\\n\\\\tfunction render() {\\\\n\\\\t\\\\tbody.innerHTML = \\\\\\\"\\\\\\\";\\\\n\\\\t\\\\tif (state.workspace === null) { renderWorkspaces(); } else { renderSessions(state.workspace); }\\\\n\\\\t}\\\\n\\\\n\\\\tfunction renderWorkspaces() {\\\\n\\\\t\\\\tif (order.length === 0) {\\\\n\\\\t\\\\t\\\\tvar none = document.createElement(\\\\\\\"p\\\\\\\");\\\\n\\\\t\\\\t\\\\tnone.textContent = \\\\\\\"还没有任何会话——在下方新建一个吧\\\\\\\";\\\\n\\\\t\\\\t\\\\tnone.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"color:#999;font-size:13px;margin:0 0 8px;\\\\\\\");\\\\n\\\\t\\\\t\\\\tbody.appendChild(none);\\\\n\\\\t\\\\t} else {\\\\n\\\\t\\\\t\\\\torder.forEach(function(key) {\\\\n\\\\t\\\\t\\\\t\\\\tvar g = groups[key];\\\\n\\\\t\\\\t\\\\t\\\\tvar label = g.cwd || \\\\\\\"(默认工作区)\\\\\\\";\\\\n\\\\t\\\\t\\\\t\\\\tvar b = document.createElement(\\\\\\\"button\\\\\\\");\\\\n\\\\t\\\\t\\\\t\\\\tb.type = \\\\\\\"button\\\\\\\";\\\\n\\\\t\\\\t\\\\t\\\\tb.setAttribute(\\\\\\\"style\\\\\\\", btnStyle());\\\\n\\\\t\\\\t\\\\t\\\\tb.textContent = \\\\\\\"📁 \\\\\\\" + label + \\\\\\\" · \\\\\\\" + g.sessions.length + \\\\\\\" 个会话\\\\\\\";\\\\n\\\\t\\\\t\\\\t\\\\tb.title = g.cwd || \\\\\\\"默认工作区\\\\\\\";\\\\n\\\\t\\\\t\\\\t\\\\tb.addEventListener(\\\\\\\"click\\\\\\\", function() { state.workspace = key; render(); });\\\\n\\\\t\\\\t\\\\t\\\\tbody.appendChild(b);\\\\n\\\\t\\\\t\\\\t});\\\\n\\\\t\\\\t}\\\\n\\\\t\\\\t// new workspace / session\\\\n\\\\t\\\\tvar newRow = document.createElement(\\\\\\\"div\\\\\\\");\\\\n\\\\t\\\\tnewRow.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"margin-top:10px;border-top:1px solid #eee;padding-top:8px;\\\\\\\");\\\\n\\\\t\\\\tvar lbl = document.createElement(\\\\\\\"p\\\\\\\");\\\\n\\\\t\\\\tlbl.textContent = \\\\\\\"新建工作区 / 会话\\\\\\\";\\\\n\\\\t\\\\tlbl.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"margin:0 0 6px;font-size:12px;color:#666;\\\\\\\");\\\\n\\\\t\\\\tnewRow.appendChild(lbl);\\\\n\\\\t\\\\tvar input = document.createElement(\\\\\\\"input\\\\\\\");\\\\n\\\\t\\\\tinput.type = \\\\\\\"text\\\\\\\";\\\\n\\\\t\\\\tinput.placeholder = \\\\\\\"输入工作区路径,留空为默认工作区\\\\\\\";\\\\n\\\\t\\\\tinput.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"width:100%;box-sizing:border-box;padding:6px 8px;border:1px solid #ccc;border-radius:6px;font-size:13px;\\\\\\\");\\\\n\\\\t\\\\tnewRow.appendChild(input);\\\\n\\\\t\\\\tvar go = document.createElement(\\\\\\\"button\\\\\\\");\\\\n\\\\t\\\\tgo.type = \\\\\\\"button\\\\\\\";\\\\n\\\\t\\\\tgo.textContent = \\\\\\\"创建并发送\\\\\\\";\\\\n\\\\t\\\\tgo.setAttribute(\\\\\\\"style\\\\\\\", primaryBtnStyle());\\\\n\\\\t\\\\tgo.addEventListener(\\\\\\\"click\\\\\\\", function() { createAndSend(payload, input.value.trim()); });\\\\n\\\\t\\\\tnewRow.appendChild(go);\\\\n\\\\t\\\\tinput.addEventListener(\\\\\\\"keydown\\\\\\\", function(e) { if (e.key === \\\\\\\"Enter\\\\\\\") { createAndSend(payload, input.value.trim()); } });\\\\n\\\\t\\\\tbody.appendChild(newRow);\\\\n\\\\t}\\\\n\\\\n\\\\tfunction renderSessions(key) {\\\\n\\\\t\\\\tvar g = groups[key];\\\\n\\\\t\\\\tvar back = document.createElement(\\\\\\\"button\\\\\\\");\\\\n\\\\t\\\\tback.type = \\\\\\\"button\\\\\\\";\\\\n\\\\t\\\\tback.textContent = \\\\\\\"← 返回工作区列表\\\\\\\";\\\\n\\\\t\\\\tback.setAttribute(\\\\\\\"style\\\\\\\", smallBtnStyle());\\\\n\\\\t\\\\tback.addEventListener(\\\\\\\"click\\\\\\\", function() { state.workspace = null; render(); });\\\\n\\\\t\\\\tbody.appendChild(back);\\\\n\\\\n\\\\t\\\\tvar wsName = document.createElement(\\\\\\\"p\\\\\\\");\\\\n\\\\t\\\\twsName.textContent = g.cwd ? \\\\\\\"📁 \\\\\\\" + g.cwd : \\\\\\\"(默认工作区)\\\\\\\";\\\\n\\\\t\\\\twsName.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"margin:0 0 6px;font-size:13px;font-weight:600;word-break:break-all;\\\\\\\");\\\\n\\\\t\\\\tbody.appendChild(wsName);\\\\n\\\\n\\\\t\\\\t(g.sessions || []).forEach(function(s) {\\\\n\\\\t\\\\t\\\\tvar b = document.createElement(\\\\\\\"button\\\\\\\");\\\\n\\\\t\\\\t\\\\tb.type = \\\\\\\"button\\\\\\\";\\\\n\\\\t\\\\t\\\\tb.setAttribute(\\\\\\\"style\\\\\\\", btnStyle());\\\\n\\\\t\\\\t\\\\tvar label = shortSessionLabel(s);\\\\n\\\\t\\\\t\\\\tif (s.agentPreset) { label += \\\\\\\" · 🧭 \\\\\\\" + modeName(s.agentPreset); }\\\\n\\\\t\\\\t\\\\tb.textContent = label;\\\\n\\\\t\\\\t\\\\tb.title = \\\\\\\"会话 \\\\\\\" + s.sessionId + (s.agentPreset ? \\\\\\\" · 工作模式 \\\\\\\" + modeName(s.agentPreset) : \\\\\\\"\\\\\\\");\\\\n\\\\t\\\\t\\\\tb.addEventListener(\\\\\\\"click\\\\\\\", function() { closeOverlay(overlay, escHandler); doSend(payload, s.sessionId, state.note); });\\\\n\\\\t\\\\t\\\\tbody.appendChild(b);\\\\n\\\\t\\\\t});\\\\n\\\\n\\\\t\\\\tvar newBtn = document.createElement(\\\\\\\"button\\\\\\\");\\\\n\\\\t\\\\tnewBtn.type = \\\\\\\"button\\\\\\\";\\\\n\\\\t\\\\tnewBtn.textContent = \\\\\\\"➕ 在此工作区新建会话并发送\\\\\\\";\\\\n\\\\t\\\\tnewBtn.setAttribute(\\\\\\\"style\\\\\\\", primaryBtnStyle());\\\\n\\\\t\\\\tnewBtn.addEventListener(\\\\\\\"click\\\\\\\", function() { createAndSend(payload, g.cwd); });\\\\n\\\\t\\\\tbody.appendChild(newBtn);\\\\n\\\\t}\\\\n\\\\n\\\\tfunction createAndSend(payload2, cwd) {\\\\n\\\\t\\\\tvar config = readConfig();\\\\n\\\\t\\\\tvar headers = { \\\\\\\"Content-Type\\\\\\\": \\\\\\\"application/json\\\\\\\" };\\\\n\\\\t\\\\tif (config.token.length > 0) { headers[\\\\\\\"x-send-to-agent-token\\\\\\\"] = config.token; }\\\\n\\\\t\\\\tvar body = { cwd: cwd || \\\\\\\"\\\\\\\" };\\\\n\\\\t\\\\tif (state.mode && String(state.mode).length > 0) { body.mode = state.mode; }\\\\n\\\\t\\\\tif (state.permission && String(state.permission).length > 0) { body.permission = state.permission; }\\\\n\\\\t\\\\t$tw.utils.httpRequest({\\\\n\\\\t\\\\t\\\\turl: baseEndpoint() + \\\\\\\"/agent/create\\\\\\\",\\\\n\\\\t\\\\t\\\\ttype: \\\\\\\"POST\\\\\\\",\\\\n\\\\t\\\\t\\\\theaders: headers,\\\\n\\\\t\\\\t\\\\tdata: JSON.stringify(body),\\\\n\\\\t\\\\t\\\\tcallback: function(err, data) {\\\\n\\\\t\\\\t\\\\t\\\\tvar parsed = null;\\\\n\\\\t\\\\t\\\\t\\\\ttry { parsed = JSON.parse(data || \\\\\\\"\\\\\\\"); } catch (e) {}\\\\n\\\\t\\\\t\\\\t\\\\tif (err) { notify(\\\\\\\"新建会话失败:\\\\\\\" + err); return; }\\\\n\\\\t\\\\t\\\\t\\\\tif (parsed && parsed.ok && parsed.sessionId) {\\\\n\\\\t\\\\t\\\\t\\\\t\\\\tcloseOverlay(overlay, escHandler);\\\\n\\\\t\\\\t\\\\t\\\\t\\\\tvar modeNote = parsed.mode ? \\\\\\\"(模式 \\\\\\\" + modeName(parsed.mode) + \\\\\\\")\\\\\\\" : \\\\\\\"\\\\\\\";\\\\n\\\\t\\\\t\\\\t\\\\t\\\\tvar permNote = parsed.permissionApplied ? \\\\\\\"(权限 \\\\\\\" + (parsed.permission || \\\\\\\"\\\\\\\") + \\\\\\\")\\\\\\\" : \\\\\\\"\\\\\\\";\\\\n\\\\t\\\\t\\\\t\\\\t\\\\tnotify(\\\\\\\"已创建会话并发送 ✓ \\\\\\\" + parsed.sessionId.slice(0, 8) + modeNote + permNote);\\\\n\\\\t\\\\t\\\\t\\\\t\\\\tdoSend(payload2, parsed.sessionId, state.note);\\\\n\\\\t\\\\t\\\\t\\\\t} else {\\\\n\\\\t\\\\t\\\\t\\\\t\\\\tnotify(\\\\\\\"新建会话失败:\\\\\\\" + ((parsed && parsed.error) || data || \\\\\\\"未知错误\\\\\\\"));\\\\n\\\\t\\\\t\\\\t\\\\t}\\\\n\\\\t\\\\t\\\\t}\\\\n\\\\t\\\\t});\\\\n\\\\t}\\\\n\\\\n\\\\trender();\\\\n}\\\\n\\\\nfunction handleSend(title) {\\\\n\\\\tif (!title) { notify(\\\\\\\"无法确定当前笔记标题\\\\\\\"); return; }\\\\n\\\\tvar tiddler = $tw.wiki.getTiddler(title);\\\\n\\\\tif (!tiddler) { notify(\\\\\\\"找不到笔记:\\\\\\\" + title); return; }\\\\n\\\\tvar config = readConfig();\\\\n\\\\tif (!config.enabled) { notify(\\\\\\\"「发送给 Agent」特性未启用(可在 TW 配置中打开)\\\\\\\"); return; }\\\\n\\\\tvar payload = {\\\\n\\\\t\\\\ttitle: title,\\\\n\\\\t\\\\ttext: tiddler.fields.text || \\\\\\\"\\\\\\\",\\\\n\\\\t\\\\ttags: tiddler.fields.tags || [],\\\\n\\\\t\\\\ttype: tiddler.fields.type || \\\\\\\"\\\\\\\"\\\\n\\\\t};\\\\n\\\\tvar headers = {};\\\\n\\\\tif (config.token.length > 0) { headers[\\\\\\\"x-send-to-agent-token\\\\\\\"] = config.token; }\\\\n\\\\t$tw.utils.httpRequest({\\\\n\\\\t\\\\turl: baseEndpoint() + \\\\\\\"/agent/sessions\\\\\\\",\\\\n\\\\t\\\\ttype: \\\\\\\"GET\\\\\\\",\\\\n\\\\t\\\\theaders: headers,\\\\n\\\\t\\\\tcallback: function(err, data) {\\\\n\\\\t\\\\t\\\\tvar parsed = null;\\\\n\\\\t\\\\t\\\\ttry { parsed = JSON.parse(data || \\\\\\\"\\\\\\\"); } catch (e) {}\\\\n\\\\t\\\\t\\\\tif (err) { notify(\\\\\\\"获取会话列表失败:\\\\\\\" + err); return; }\\\\n\\\\t\\\\t\\\\tif (!parsed || !parsed.ok || !parsed.items) { notify(\\\\\\\"获取会话列表失败:\\\\\\\" + ((parsed && parsed.error) || \\\\\\\"未知错误\\\\\\\")); return; }\\\\n\\\\t\\\\t\\\\t// fetch the available 工作模式 (Agent presets) alongside; degrade to\\\\n\\\\t\\\\t\\\\t// an empty roster (default mode) when the endpoint is missing.\\\\n\\\\t\\\\t\\\\t$tw.utils.httpRequest({\\\\n\\\\t\\\\t\\\\t\\\\turl: baseEndpoint() + \\\\\\\"/agent/modes\\\\\\\",\\\\n\\\\t\\\\t\\\\t\\\\ttype: \\\\\\\"GET\\\\\\\",\\\\n\\\\t\\\\t\\\\t\\\\theaders: headers,\\\\n\\\\t\\\\t\\\\t\\\\tcallback: function(err2, data2) {\\\\n\\\\t\\\\t\\\\t\\\\t\\\\tvar modes = [];\\\\n\\\\t\\\\t\\\\t\\\\t\\\\tvar defaultId = \\\\\\\"\\\\\\\";\\\\n\\\\t\\\\t\\\\t\\\\t\\\\tvar permissions = null;\\\\n\\\\t\\\\t\\\\t\\\\t\\\\tvar parsed2 = null;\\\\n\\\\t\\\\t\\\\t\\\\t\\\\ttry { parsed2 = JSON.parse(data2 || \\\\\\\"\\\\\\\"); } catch (e) {}\\\\n\\\\t\\\\t\\\\t\\\\t\\\\tif (!err2 && parsed2 && parsed2.ok) {\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\tif (Array.isArray(parsed2.items)) {\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\tmodes = parsed2.items;\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\tdefaultId = parsed2.defaultId || \\\\\\\"\\\\\\\";\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t}\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\tif (parsed2.permissions && Array.isArray(parsed2.permissions.items)) {\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\tpermissions = parsed2.permissions;\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t}\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t}\\\\n\\\\t\\\\t\\\\t\\\\t\\\\tshowPicker(payload, parsed.items, modes, defaultId, permissions);\\\\n\\\\t\\\\t\\\\t\\\\t}\\\\n\\\\t\\\\t\\\\t});\\\\n\\\\t\\\\t}\\\\n\\\\t});\\\\n}\\\\n\\\\nexports.startup = function() {\\\\n\\\\tconsole.log(\\\\\\\"[dsh-send-to-agent] startup ran\\\\\\\");\\\\n\\\\tif (!$tw.rootWidget || typeof $tw.rootWidget.addEventListener !== \\\\\\\"function\\\\\\\") { return; }\\\\n\\\\t$tw.rootWidget.addEventListener(\\\\\\\"dsh-send-to-agent\\\\\\\", function(event) {\\\\n\\\\t\\\\thandleSend(event.param);\\\\n\\\\t});\\\\n};\\\\n\\\\n})();\\\\n\\\"\\n },\\n \\\"$:/plugins/dsh/send-to-agent/ui/icon\\\": {\\n \\\"title\\\": \\\"$:/plugins/dsh/send-to-agent/ui/icon\\\",\\n \\\"tags\\\": [\\n \\\"$:/tags/Image\\\"\\n ],\\n \\\"text\\\": \\\"\\\\\\\\parameters (size:\\\\\\\"22pt\\\\\\\")\\\\n<svg width=<<size>> height=<<size>> class=\\\\\\\"tc-image-send-to-agent tc-image-button\\\\\\\" viewBox=\\\\\\\"0 0 24 24\\\\\\\"><path d=\\\\\\\"M3.478 2.404a.75.75 0 0 0-.926.941l2.432 7.905H13.5a.75.75 0 0 1 0 1.5H4.984l-2.432 7.905a.75.75 0 0 0 .926.94 60.519 60.519 0 0 0 18.445-8.986.75.75 0 0 0 0-1.218A60.517 60.517 0 0 0 3.478 2.404Z\\\\\\\"/></svg>\\\\n\\\"\\n },\\n \\\"$:/plugins/dsh/send-to-agent/ui/ViewToolbar/SendToAgent\\\": {\\n \\\"title\\\": \\\"$:/plugins/dsh/send-to-agent/ui/ViewToolbar/SendToAgent\\\",\\n \\\"type\\\": \\\"text/vnd.tiddlywiki\\\",\\n \\\"tags\\\": [\\n \\\"$:/tags/ViewToolbar\\\"\\n ],\\n \\\"icon\\\": \\\"$:/plugins/dsh/send-to-agent/ui/icon\\\",\\n \\\"caption\\\": \\\"发送给 Agent\\\",\\n \\\"description\\\": \\\"把当前笔记一键发送给 DSH Agent(TiddlyWiki → DSH 会话注入)\\\",\\n \\\"text\\\": \\\"\\\\\\\\whitespace trim\\\\n<$button message=\\\\\\\"dsh-send-to-agent\\\\\\\"\\\\n\\\\tparam=<<currentTiddler>>\\\\n\\\\ttooltip=\\\\\\\"把当前笔记一键发送给 DSH Agent\\\\\\\"\\\\n\\\\taria-label=\\\\\\\"把当前笔记一键发送给 DSH Agent\\\\\\\"\\\\n\\\\tclass=<<tv-config-toolbar-class>>\\\\n>\\\\n\\\\t<%if [<tv-config-toolbar-icons>match[yes]] %>\\\\n\\\\t\\\\t{{$:/plugins/dsh/send-to-agent/ui/icon}}\\\\n\\\\t<%endif%>\\\\n\\\\t<%if [<tv-config-toolbar-text>match[yes]] %>\\\\n\\\\t\\\\t<span class=\\\\\\\"tc-btn-text\\\\\\\">\\\\n\\\\t\\\\t<$text text=\\\\\\\"发送给 Agent\\\\\\\"/>\\\\n\\\\t</span>\\\\n\\\\t<%endif%>\\\\n</$button>\\\\n\\\"\\n },\\n \\\"$:/core/ui/ControlPanel/Toolbars/ItemTemplate\\\": {\\n \\\"title\\\": \\\"$:/core/ui/ControlPanel/Toolbars/ItemTemplate\\\",\\n \\\"type\\\": \\\"text/vnd.tiddlywiki\\\",\\n \\\"text\\\": \\\"\\\\\\\\define config-title()\\\\n$(config-base)$$(currentTiddler)$\\\\n\\\\\\\\end\\\\n\\\\\\\\whitespace trim\\\\n\\\\n<$draggable tiddler=<<currentTiddler>>>\\\\n<$checkbox tiddler=<<config-title>> field=\\\\\\\"text\\\\\\\" checked=\\\\\\\"show\\\\\\\" unchecked=\\\\\\\"hide\\\\\\\" default=\\\\\\\"show\\\\\\\"/>\\\\n&#32;\\\\n<span class=\\\\\\\"tc-icon-wrapper\\\\\\\"><$transclude tiddler={{!!icon}}/></span>\\\\n&#32;\\\\n<$transclude field=\\\\\\\"caption\\\\\\\"/>\\\\n&#32;--&#32;\\\\n<i class=\\\\\\\"tc-muted\\\\\\\"><$transclude field=\\\\\\\"description\\\\\\\"/></i>\\\\n</$draggable>\\\\n\\\"\\n }\\n }\\n}\"\n\n/**\n * Seed the \"发送给 Agent\" TW button exactly once per wiki (mirrors the doc-note\n * one-shot policy). The marker records the offer; afterwards the bundle is\n * user-owned — deleting it and restarting dsh web does NOT recreate it, and\n * edits are never overwritten. With `opts.force` the bundle is (re)written even\n * when it already exists and the marker is (re)written — the settings page uses\n * this for \"重新初始化\". Returns whether a bundle was written this call.\n * Throws when a read fails (the seed registry reports it as `ok:false`).\n */\nexport async function seedSendToAgent(client: TiddlyWebClient, opts?: { force?: boolean }): Promise<boolean> {\n const force = opts?.force === true\n if (!force) {\n const marker = await readSeedTiddler(client, SEND_TO_AGENT_MARKER_TITLE)\n if (marker !== undefined) return false\n }\n const existing = await readSeedTiddler(client, SEND_TO_AGENT_PLUGIN_TITLE)\n let wrote = false\n if (force || existing === undefined) {\n await client.put({\n title: SEND_TO_AGENT_PLUGIN_TITLE,\n text: SEND_TO_AGENT_BUNDLE_TEXT,\n type: 'application/json',\n tags: [],\n // TW only REGISTERS a wiki tiddler as a plugin (boot.js\n // registerPluginTiddlers) when the OUTER tiddler carries a\n // `plugin-type` field — without it the bundle is never unpacked and its\n // startup module never runs in the embedded TW. Mirrors what the live\n // wiki's send-to-agent bundle tiddler carries.\n 'plugin-type': 'plugin',\n name: 'Send to Agent',\n author: 'dsh-tiddlywiki',\n version: '0.3.5',\n description: '把当前笔记一键发送给 DSH Agent(TiddlyWiki → DSH 会话注入)',\n })\n wrote = true\n }\n // Record the offer regardless, so an existing bundle (upgrade from a\n // pre-seed wiki) also becomes user-owned from here on.\n await writeSeedMarker(client, SEND_TO_AGENT_MARKER_TITLE)\n return wrote\n}\n","/**\n * Generated from the wiki home tiddlers (do not hand-edit the constants).\n * Source: 🏠 主页.tid, 所有标签.tid, 标签笔记.tid\n *\n * Sanitized with --strip-private: the seed home is the GENERIC home (without\n * the wiki owner's personal 主题页 tabs / private entry buttons), plus the\n *「📚 插件文档」tabs strip (tag dsh-docs) so seeded docs are one click away.\n *\n * The \"首页\" tiddlers that the plugin's system prompt promises (待办四象限 +\n * 所有标签统计 + 标签笔记): seeded into fresh wikis by seedHomeIndex, so new\n * users get the same home page instead of an empty wiki. 🏠 主页 is the\n * default home (the seed also ensures $:/DefaultTiddlers → [[🏠 主页]]).\n *\n * @module dsh-tiddlywiki/host/seed-home\n */\nimport type { TiddlyWebClient } from './tw-api.ts'\nimport { readSeedTiddler, writeSeedMarker } from './seed-util.ts'\n\n/** One-time marker: presence means \"the home was offered once — hands off\". */\nexport const HOME_INDEX_MARKER_TITLE = '$:/plugins/dsh-tiddlywiki/seed-home-index'\n\n/** The $:/DefaultTiddlers body so 🏠 主页 opens by default in fresh wikis. */\nexport const HOME_DEFAULT_TIDDLERS = \"[[🏠 主页]]\"\n\nexport interface HomeIndexItem {\n title: string\n tags: string[]\n type: string\n text: string\n}\n\n/** The home tiddlers, exactly as seeded (user-owned afterwards). */\nexport const HOME_INDEX_ITEMS: HomeIndexItem[] = [{\"title\":\"🏠 主页\",\"tags\":[\"索引\",\"TableOfContents\"],\"type\":\"text/vnd.tiddlywiki\",\"text\":\"\\\\whitespace trim\\n\\n\\\\define quadrant-board()\\n<div style=\\\"display:grid; grid-template-columns:1fr 1fr; gap:10px; margin-top:8px;\\\">\\n<$droppable class=\\\"tc-quadrant tc-quadrant-q1\\\" effect=\\\"move\\\"\\nstyle.border=\\\"1px solid rgba(128,128,128,0.22)\\\"\\nstyle.border-radius=\\\"10px\\\"\\nstyle.padding=\\\"8px 12px\\\"\\nstyle.background=\\\"linear-gradient(135deg,#e74c3c1a,transparent)\\\"\\nactions=\\\"\\\"\\\"<$action-setfield $tiddler=<<actionTiddler>> $field=\\\"q\\\" $value=\\\"q1\\\"/>\\\"\\\"\\\">\\n<div style=\\\"font-weight:700; margin-bottom:6px;\\\">🔴 重要 · 紧急 <span style=\\\"color:#888; font-weight:400; font-size:0.82em;\\\">{{{[tag[todo]!tag[done]field:q[q1]count[]]}}} 件</span></div>\\n<$list filter=\\\"[tag[todo]!tag[done]field:q[q1]sort[modified]]\\\">\\n<div style=\\\"display:flex; gap:6px; align-items:baseline; padding:3px 0; border-bottom:1px dashed rgba(128,128,128,0.15);\\\">\\n<$checkbox tiddler=<<currentTiddler>> tag=\\\"done\\\"/>\\n<$link to=<<currentTiddler>> style=\\\"flex:1; cursor:grab;\\\"><$view field=\\\"title\\\"/></$link>\\n<$list filter=\\\"[<currentTiddler>get[due]compare:date:lt<today>]\\\"><span style=\\\"color:#c0392b; font-size:0.78em;\\\">逾期</span></$list>\\n<$list filter=\\\"[<currentTiddler>get[due]!is[blank]]\\\"><span style=\\\"color:#888; font-size:0.8em;\\\"><$view field=\\\"due\\\" format=\\\"date\\\" template=\\\"MM-DD\\\"/></span></$list>\\n</div>\\n</$list>\\n<$list filter=\\\"[tag[todo]!tag[done]field:q[q1]count[]match[0]]\\\"><div style=\\\"color:#aaa; font-size:0.85em;\\\">(暂无)</div></$list>\\n</$droppable>\\n<$droppable class=\\\"tc-quadrant tc-quadrant-q2\\\" effect=\\\"move\\\"\\nstyle.border=\\\"1px solid rgba(128,128,128,0.22)\\\"\\nstyle.border-radius=\\\"10px\\\"\\nstyle.padding=\\\"8px 12px\\\"\\nstyle.background=\\\"linear-gradient(135deg,#3498db1a,transparent)\\\"\\nactions=\\\"\\\"\\\"<$action-setfield $tiddler=<<actionTiddler>> $field=\\\"q\\\" $value=\\\"q2\\\"/>\\\"\\\"\\\">\\n<div style=\\\"font-weight:700; margin-bottom:6px;\\\">🔵 重要 · 不紧急 <span style=\\\"color:#888; font-weight:400; font-size:0.82em;\\\">{{{[tag[todo]!tag[done]field:q[q2]count[]]}}} 件</span></div>\\n<$list filter=\\\"[tag[todo]!tag[done]field:q[q2]sort[modified]]\\\">\\n<div style=\\\"display:flex; gap:6px; align-items:baseline; padding:3px 0; border-bottom:1px dashed rgba(128,128,128,0.15);\\\">\\n<$checkbox tiddler=<<currentTiddler>> tag=\\\"done\\\"/>\\n<$link to=<<currentTiddler>> style=\\\"flex:1; cursor:grab;\\\"><$view field=\\\"title\\\"/></$link>\\n<$list filter=\\\"[<currentTiddler>get[due]compare:date:lt<today>]\\\"><span style=\\\"color:#c0392b; font-size:0.78em;\\\">逾期</span></$list>\\n<$list filter=\\\"[<currentTiddler>get[due]!is[blank]]\\\"><span style=\\\"color:#888; font-size:0.8em;\\\"><$view field=\\\"due\\\" format=\\\"date\\\" template=\\\"MM-DD\\\"/></span></$list>\\n</div>\\n</$list>\\n<$list filter=\\\"[tag[todo]!tag[done]field:q[q2]count[]match[0]]\\\"><div style=\\\"color:#aaa; font-size:0.85em;\\\">(暂无)</div></$list>\\n</$droppable>\\n<$droppable class=\\\"tc-quadrant tc-quadrant-q3\\\" effect=\\\"move\\\"\\nstyle.border=\\\"1px solid rgba(128,128,128,0.22)\\\"\\nstyle.border-radius=\\\"10px\\\"\\nstyle.padding=\\\"8px 12px\\\"\\nstyle.background=\\\"linear-gradient(135deg,#f39c121a,transparent)\\\"\\nactions=\\\"\\\"\\\"<$action-setfield $tiddler=<<actionTiddler>> $field=\\\"q\\\" $value=\\\"q3\\\"/>\\\"\\\"\\\">\\n<div style=\\\"font-weight:700; margin-bottom:6px;\\\">🟠 紧急 · 不重要 <span style=\\\"color:#888; font-weight:400; font-size:0.82em;\\\">{{{[tag[todo]!tag[done]field:q[q3]count[]]}}} 件</span></div>\\n<$list filter=\\\"[tag[todo]!tag[done]field:q[q3]sort[modified]]\\\">\\n<div style=\\\"display:flex; gap:6px; align-items:baseline; padding:3px 0; border-bottom:1px dashed rgba(128,128,128,0.15);\\\">\\n<$checkbox tiddler=<<currentTiddler>> tag=\\\"done\\\"/>\\n<$link to=<<currentTiddler>> style=\\\"flex:1; cursor:grab;\\\"><$view field=\\\"title\\\"/></$link>\\n<$list filter=\\\"[<currentTiddler>get[due]compare:date:lt<today>]\\\"><span style=\\\"color:#c0392b; font-size:0.78em;\\\">逾期</span></$list>\\n<$list filter=\\\"[<currentTiddler>get[due]!is[blank]]\\\"><span style=\\\"color:#888; font-size:0.8em;\\\"><$view field=\\\"due\\\" format=\\\"date\\\" template=\\\"MM-DD\\\"/></span></$list>\\n</div>\\n</$list>\\n<$list filter=\\\"[tag[todo]!tag[done]field:q[q3]count[]match[0]]\\\"><div style=\\\"color:#aaa; font-size:0.85em;\\\">(暂无)</div></$list>\\n</$droppable>\\n<$droppable class=\\\"tc-quadrant tc-quadrant-q4\\\" effect=\\\"move\\\"\\nstyle.border=\\\"1px solid rgba(128,128,128,0.22)\\\"\\nstyle.border-radius=\\\"10px\\\"\\nstyle.padding=\\\"8px 12px\\\"\\nstyle.background=\\\"linear-gradient(135deg,#95a5a61a,transparent)\\\"\\nactions=\\\"\\\"\\\"<$action-setfield $tiddler=<<actionTiddler>> $field=\\\"q\\\" $value=\\\"q4\\\"/>\\\"\\\"\\\">\\n<div style=\\\"font-weight:700; margin-bottom:6px;\\\">⚪ 不重要 · 不紧急 <span style=\\\"color:#888; font-weight:400; font-size:0.82em;\\\">{{{[tag[todo]!tag[done]field:q[q4]count[]]}}} 件</span></div>\\n<$list filter=\\\"[tag[todo]!tag[done]field:q[q4]sort[modified]]\\\">\\n<div style=\\\"display:flex; gap:6px; align-items:baseline; padding:3px 0; border-bottom:1px dashed rgba(128,128,128,0.15);\\\">\\n<$checkbox tiddler=<<currentTiddler>> tag=\\\"done\\\"/>\\n<$link to=<<currentTiddler>> style=\\\"flex:1; cursor:grab;\\\"><$view field=\\\"title\\\"/></$link>\\n<$list filter=\\\"[<currentTiddler>get[due]compare:date:lt<today>]\\\"><span style=\\\"color:#c0392b; font-size:0.78em;\\\">逾期</span></$list>\\n<$list filter=\\\"[<currentTiddler>get[due]!is[blank]]\\\"><span style=\\\"color:#888; font-size:0.8em;\\\"><$view field=\\\"due\\\" format=\\\"date\\\" template=\\\"MM-DD\\\"/></span></$list>\\n</div>\\n</$list>\\n<$list filter=\\\"[tag[todo]!tag[done]field:q[q4]count[]match[0]]\\\"><div style=\\\"color:#aaa; font-size:0.85em;\\\">(暂无)</div></$list>\\n</$droppable>\\n</div>\\n\\\\end\\n\\n<$set name=\\\"today\\\" value=<<now \\\"YYYY0MM0DD\\\">>>\\n\\n!! 🏠 主页\\n\\n<div class=\\\"tc-message-box\\\">待办看板与知识库入口。</div>\\n\\n<div style=\\\"display:flex; gap:8px; flex-wrap:wrap; margin:8px 0;\\\">\\n<$button class=\\\"tc-btn-invisible tc-tiddlylink\\\" style=\\\"padding:6px 16px; border:1px solid rgba(128,128,128,0.35); border-radius:8px; font-weight:600;\\\">\\n<$action-navigate $to=\\\"所有标签\\\"/>\\n🏷 所有标签\\n</$button>\\n<$button class=\\\"tc-btn-invisible tc-tiddlylink\\\" style=\\\"padding:6px 16px; border:1px solid rgba(128,128,128,0.35); border-radius:8px; font-weight:600;\\\">\\n<$action-navigate $to=\\\"所有文章\\\"/>\\n📚 所有文章\\n</$button>\\n</div>\\n\\n!! 📚 插件文档\\n\\n<div class=\\\"tc-message-box\\\">这里收纳插件初始化时 seed 进来的说明 / 教程 / 模板。给新文档打上 <code>dsh-docs</code> 标签即自动出现在本栏;不需要可自由删除(删除后不会自动恢复)。</div>\\n\\n<<tabs \\\"[tag[dsh-docs]!is[system]]\\\" \\\"dsh-tiddlywiki 插件说明\\\">>\\n\\n!! ✍️ 快速记笔记(完整编辑器)\\n\\n<div class=\\\"tc-message-box\\\">直接在首页写笔记:填标题、用工具栏写正文(支持 Markdown:**加粗**、*斜体*、# 标题、- 列表、> 引用 等)、加标签,点「💾 保存」即创建新笔记(默认 Markdown,标题留空用当前时间)。勾选「同时加入待办」并选象限,可一并进入下方四象限看板。</div>\\n\\n<div style=\\\"border:1px solid rgba(128,128,128,0.22); border-radius:10px; padding:10px 12px; margin:10px 0;\\\">\\n\\n<div style=\\\"display:flex; gap:6px; align-items:center; margin-bottom:6px;\\\">\\n<$edit-text tiddler=\\\"$:/state/home-note/title\\\" tag=\\\"input\\\" placeholder=\\\"标题(留空用当前时间)\\\" style=\\\"flex:1; min-width:200px; padding:6px 10px; border-radius:6px; border:1px solid rgba(128,128,128,0.3);\\\"/>\\n</div>\\n\\n<$edit-text tiddler=\\\"$:/state/home-note/text\\\" tag=\\\"textarea\\\" class=\\\"tc-edit-texteditor\\\" autoHeight=\\\"yes\\\" minHeight=\\\"150px\\\" placeholder=\\\"正文:支持 **加粗**、*斜体*、# 标题、- 列表、> 引用 等 Markdown 语法\\\" style=\\\"width:100%; box-sizing:border-box; padding:6px 10px; border-radius:6px; border:1px solid rgba(128,128,128,0.3);\\\">\\n<$button class=\\\"tc-btn-invisible\\\" tooltip=\\\"加粗\\\" style=\\\"padding:2px 8px; font-weight:700;\\\"><$action-sendmessage $message=\\\"tm-edit-text-operation\\\" $param=\\\"wrap-selection\\\" prefix=\\\"**\\\" suffix=\\\"**\\\" trimSelection=\\\"yes\\\"/>B</$button>\\n<$button class=\\\"tc-btn-invisible\\\" tooltip=\\\"斜体\\\" style=\\\"padding:2px 8px; font-style:italic;\\\"><$action-sendmessage $message=\\\"tm-edit-text-operation\\\" $param=\\\"wrap-selection\\\" prefix=\\\"*\\\" suffix=\\\"*\\\" trimSelection=\\\"yes\\\"/>I</$button>\\n<$button class=\\\"tc-btn-invisible\\\" tooltip=\\\"删除线\\\" style=\\\"padding:2px 8px;\\\"><$action-sendmessage $message=\\\"tm-edit-text-operation\\\" $param=\\\"wrap-selection\\\" prefix=\\\"~~\\\" suffix=\\\"~~\\\" trimSelection=\\\"yes\\\"/>S</$button>\\n<$button class=\\\"tc-btn-invisible\\\" tooltip=\\\"标题\\\" style=\\\"padding:2px 8px;\\\"><$action-sendmessage $message=\\\"tm-edit-text-operation\\\" $param=\\\"prefix-lines\\\" character=\\\"#\\\" count=\\\"1\\\"/>H</$button>\\n<$button class=\\\"tc-btn-invisible\\\" tooltip=\\\"无序列表\\\" style=\\\"padding:2px 8px;\\\"><$action-sendmessage $message=\\\"tm-edit-text-operation\\\" $param=\\\"prefix-lines\\\" character=\\\"-\\\" count=\\\"1\\\"/>•</$button>\\n<$button class=\\\"tc-btn-invisible\\\" tooltip=\\\"有序列表\\\" style=\\\"padding:2px 8px;\\\"><$action-sendmessage $message=\\\"tm-edit-text-operation\\\" $param=\\\"prefix-lines\\\" character=\\\"1.\\\" count=\\\"1\\\"/>1.</$button>\\n<$button class=\\\"tc-btn-invisible\\\" tooltip=\\\"引用\\\" style=\\\"padding:2px 8px;\\\"><$action-sendmessage $message=\\\"tm-edit-text-operation\\\" $param=\\\"prefix-lines\\\" character=\\\">\\\" count=\\\"1\\\"/>❝</$button>\\n<$button class=\\\"tc-btn-invisible\\\" tooltip=\\\"行内代码\\\" style=\\\"padding:2px 8px;\\\"><$action-sendmessage $message=\\\"tm-edit-text-operation\\\" $param=\\\"wrap-selection\\\" prefix=\\\"`\\\" suffix=\\\"`\\\" trimSelection=\\\"yes\\\"/>&lt;/&gt;</$button>\\n</$edit-text>\\n\\n<div style=\\\"display:flex; gap:6px; flex-wrap:wrap; align-items:center; margin-top:6px;\\\">\\n<$edit-text tiddler=\\\"$:/state/home-note/tags\\\" tag=\\\"input\\\" placeholder=\\\"标签(逗号分隔,可选)\\\" style=\\\"flex:1; min-width:160px; padding:6px 10px; border-radius:6px; border:1px solid rgba(128,128,128,0.3);\\\"/>\\n<$checkbox tiddler=\\\"$:/state/home-note/todo\\\" field=\\\"text\\\" checked=\\\"yes\\\" unchecked=\\\"no\\\"> 同时加入待办</$checkbox>\\n<$select tiddler=\\\"$:/state/home-note/quadrant\\\" default=\\\"q2\\\" style=\\\"padding:6px 10px; border-radius:6px; border:1px solid rgba(128,128,128,0.3);\\\">\\n<option value=\\\"q1\\\">Q1 重要·紧急</option>\\n<option value=\\\"q2\\\">Q2 重要·不紧急</option>\\n<option value=\\\"q3\\\">Q3 紧急·不重要</option>\\n<option value=\\\"q4\\\">Q4 不重要·不紧急</option>\\n</$select>\\n</div>\\n\\n<div style=\\\"display:flex; gap:6px; align-items:center; margin-top:8px; flex-wrap:wrap;\\\">\\n<$button class=\\\"tc-btn-invisible\\\" style=\\\"padding:6px 16px; border-radius:6px; border:1px solid rgba(128,128,128,0.35); font-weight:600; background:rgba(128,128,128,0.06);\\\">\\n<$list filter=\\\"[{$:/state/home-note/title}!is[blank]] [{$:/state/home-note/text}!is[blank]]\\\">\\n<$action-createtiddler\\n$basetitle={{{ [{$:/state/home-note/title}!is[blank]then{$:/state/home-note/title}] ~[<now \\\"YYYY-MM-DD-HHmm\\\">] }}}\\ntext={{{ [{$:/state/home-note/text}] }}}\\ntags={{{ [{$:/state/home-note/tags}split[,]split[,]trim[]] [{$:/state/home-note/todo}match[yes]then[todo]] +[join[ ]] }}}\\nq={{{ [{$:/state/home-note/todo}match[yes]then{$:/state/home-note/quadrant}!is[blank]] ~[{$:/state/home-note/todo}match[yes]then[q2]] }}}\\ntype=\\\"text/markdown\\\"\\n$savetitle=\\\"$:/state/home-note/last-created\\\"/>\\n<$action-setfield $tiddler=\\\"$:/state/home-note/title\\\" text=\\\"\\\"/>\\n<$action-setfield $tiddler=\\\"$:/state/home-note/text\\\" text=\\\"\\\"/>\\n<$action-setfield $tiddler=\\\"$:/state/home-note/tags\\\" text=\\\"\\\"/>\\n</$list>\\n💾 保存\\n</$button>\\n<$button class=\\\"tc-btn-invisible\\\" tooltip=\\\"清空草稿\\\" style=\\\"padding:6px 12px; border-radius:6px; border:1px solid rgba(128,128,128,0.3);\\\">\\n<$action-setfield $tiddler=\\\"$:/state/home-note/title\\\" text=\\\"\\\"/>\\n<$action-setfield $tiddler=\\\"$:/state/home-note/text\\\" text=\\\"\\\"/>\\n<$action-setfield $tiddler=\\\"$:/state/home-note/tags\\\" text=\\\"\\\"/>\\n🗑 清空\\n</$button>\\n<$list filter=\\\"[{$:/state/home-note/last-created}!is[blank]]\\\">\\n<span style=\\\"color:#2e7d32; font-size:0.9em;\\\">✅ 已保存:<$link to={{{ [{$:/state/home-note/last-created}] }}}>{{{$:/state/home-note/last-created}}}</$link></span>\\n</$list>\\n</div>\\n\\n</div>\\n\\n!! ✅ 待办 · 四象限\\n\\n<div class=\\\"tc-message-box\\\">在上方「快速记笔记」勾选「同时加入待办」即建任务(默认 Q2 重要·不紧急);也可以给任意笔记打上 <code>todo</code> 标签收集到下方。点任务左侧方框即完成(自动加 <code>done</code> 标签并从看板消失)。下方「未分类待办」可直接拖动到上方任一象限,拖入即自动写入 <code>q</code> 字段完成分类(象限之间也可互相拖动调整);把象限中的待办拖回「未分类」可取消分类。</div>\\n\\n<div style=\\\"margin:6px 0; color:#555;\\\">📅 今日到期 <b>{{{[tag[todo]!tag[done]field:due<today>count[]]}}}</b> 件 · ⏰ 已逾期 <b>{{{[tag[todo]!tag[done]get[due]compare:date:lt<today>count[]]}}}</b> 件</div>\\n\\n<<quadrant-board>>\\n\\n<div style=\\\"margin-top:10px; border:1px dashed rgba(128,128,128,0.35); border-radius:10px; padding:8px 12px;\\\">\\n<div style=\\\"font-weight:700; margin-bottom:6px;\\\">📥 未分类待办 <span style=\\\"color:#888; font-weight:400; font-size:0.82em;\\\">{{{[tag[todo]!tag[done]!has[q]count[]]}}} 件</span> <span style=\\\"color:#aaa; font-weight:400; font-size:0.78em;\\\">· 拖到上方象限自动分类</span></div>\\n<$droppable class=\\\"tc-todo-inbox\\\" effect=\\\"move\\\" actions=\\\"\\\"\\\"<$action-deletefield $tiddler=<<actionTiddler>> $field=\\\"q\\\"/>\\\"\\\"\\\">\\n<$list filter=\\\"[tag[todo]!tag[done]!has[q]sort[modified]]\\\">\\n<div style=\\\"display:flex; gap:6px; align-items:baseline; padding:3px 0; border-bottom:1px dashed rgba(128,128,128,0.15);\\\">\\n<$checkbox tiddler=<<currentTiddler>> tag=\\\"done\\\"/>\\n<$link to=<<currentTiddler>> style=\\\"flex:1; cursor:grab;\\\"><$view field=\\\"title\\\"/></$link>\\n<$list filter=\\\"[<currentTiddler>get[due]compare:date:lt<today>]\\\"><span style=\\\"color:#c0392b; font-size:0.78em;\\\">逾期</span></$list>\\n</div>\\n</$list>\\n<$list filter=\\\"[tag[todo]!tag[done]!has[q]count[]match[0]]\\\"><div style=\\\"color:#aaa; font-size:0.85em;\\\">(暂无 · 把象限中的待办拖回这里可取消分类)</div></$list>\\n</$droppable>\\n</div>\\n\\n</$set>\\n\"},{\"title\":\"所有标签\",\"tags\":[\"索引\",\"TableOfContents\"],\"type\":\"text/vnd.tiddlywiki\",\"text\":\"\\\\whitespace trim\\n\\n\\\\define tag-count() [all[tiddlers]!is[system]!tag[agent-written]tag<currentTiddler>count[]]\\n\\\\define tag-list() [all[tiddlers]!is[system]!tag[agent-written]tags[]!prefix[$:/tags/]!match[索引]]\\n\\n\\\\define agent-notes-pure() [all[tiddlers]!is[system]tag[agent-written]!tag[human-edited]count[]]\\n\\\\define agent-notes-mixed() [all[tiddlers]!is[system]tag[agent-written]tag[human-edited]count[]]\\n\\\\define agent-tags-pure() [all[tiddlers]!is[system]tag[agent-written]!tag[human-edited]tags[]!prefix[$:/tags/]!match[索引]!match[agent-written]!match[human-edited]]\\n\\\\define agent-tags-mixed() [all[tiddlers]!is[system]tag[agent-written]tag[human-edited]tags[]!prefix[$:/tags/]!match[索引]!match[agent-written]!match[human-edited]]\\n\\\\define agent-count-pure() [all[tiddlers]!is[system]tag[agent-written]!tag[human-edited]tag<currentTiddler>count[]]\\n\\\\define agent-count-mixed() [all[tiddlers]!is[system]tag[agent-written]tag[human-edited]tag<currentTiddler>count[]]\\n\\n<$button class=\\\"tc-btn-invisible tc-tiddlylink\\\" style=\\\"margin:6px 0; padding:4px 10px; border:1px solid rgba(128,128,128,0.3); border-radius:6px; font-size:0.9em;\\\">\\n<$action-navigate $to=\\\"🏠 主页\\\"/>\\n← 回主页\\n</$button>\\n\\n!! 🏷 所有标签\\n\\n<div class=\\\"tc-message-box\\\">按笔记数量从多到少排序,仅统计人类笔记(Agent 撰写的笔记已排除,见下方「🤖 Agent 撰写的标签」)。点击标签,查看包含该标签的所有笔记。</div>\\n\\n<div style=\\\"margin-top:12px;\\\">\\n<$list filter=\\\"[subfilter<tag-list>] +[!sortsub:number<tag-count>]\\\">\\n<$set name=\\\"count\\\" value={{{ [subfilter<tag-count>] }}}>\\n<div style=\\\"padding:7px 2px; border-bottom:1px solid rgba(128,128,128,0.15);\\\">\\n<$button set=\\\"$:/state/tag\\\" setTo=<<currentTiddler>> class=\\\"tc-btn-invisible tc-tiddlylink\\\" style=\\\"width:100%; text-align:left;\\\">\\n<$action-navigate $to=\\\"标签笔记\\\"/>\\n<span style=\\\"font-weight:600; font-size:1.05em;\\\"><<currentTiddler>></span>\\n<span style=\\\"color:#888; font-size:0.85em; margin-left:8px;\\\"><<count>> 篇</span>\\n</$button>\\n</div>\\n</$set>\\n</$list>\\n</div>\\n\\n!! 🤖 Agent 撰写的标签\\n\\n<div class=\\\"tc-message-box\\\">以下标签来自带有 <code>agent-written</code> 标签(由 Agent 撰写)的笔记,与上方主列表分开统计。若某篇 Agent 笔记又被人类编辑过,请给它补打 <code>human-edited</code> 标签,即归入下方「Agent + 人工」档。</div>\\n\\n<div style=\\\"font-weight:700; margin:8px 0 4px;\\\">🦾 纯 Agent <span style=\\\"color:#888; font-weight:400; font-size:0.82em;\\\">{{{[subfilter<agent-notes-pure>]}}} 篇</span></div>\\n<div style=\\\"margin-left:10px;\\\">\\n<$list filter=\\\"[subfilter<agent-tags-pure>] +[!sortsub:number<agent-count-pure>]\\\">\\n<$set name=\\\"count\\\" value={{{ [subfilter<agent-count-pure>] }}}>\\n<div style=\\\"padding:7px 2px; border-bottom:1px solid rgba(128,128,128,0.15);\\\">\\n<$button set=\\\"$:/state/tag\\\" setTo=<<currentTiddler>> class=\\\"tc-btn-invisible tc-tiddlylink\\\" style=\\\"width:100%; text-align:left;\\\">\\n<$action-navigate $to=\\\"标签笔记\\\"/>\\n<span style=\\\"font-weight:600; font-size:1.05em;\\\"><<currentTiddler>></span>\\n<span style=\\\"color:#888; font-size:0.85em; margin-left:8px;\\\"><<count>> 篇</span>\\n</$button>\\n</div>\\n</$set>\\n</$list>\\n<$list filter=\\\"[subfilter<agent-tags-pure>count[]match[0]]\\\"><div style=\\\"color:#aaa; font-size:0.85em;\\\">(暂无纯 Agent 笔记)</div></$list>\\n</div>\\n\\n<div style=\\\"font-weight:700; margin:10px 0 4px;\\\">🤝 Agent + 人工 <span style=\\\"color:#888; font-weight:400; font-size:0.82em;\\\">{{{[subfilter<agent-notes-mixed>]}}} 篇</span></div>\\n<div style=\\\"margin-left:10px;\\\">\\n<$list filter=\\\"[subfilter<agent-tags-mixed>] +[!sortsub:number<agent-count-mixed>]\\\">\\n<$set name=\\\"count\\\" value={{{ [subfilter<agent-count-mixed>] }}}>\\n<div style=\\\"padding:7px 2px; border-bottom:1px solid rgba(128,128,128,0.15);\\\">\\n<$button set=\\\"$:/state/tag\\\" setTo=<<currentTiddler>> class=\\\"tc-btn-invisible tc-tiddlylink\\\" style=\\\"width:100%; text-align:left;\\\">\\n<$action-navigate $to=\\\"标签笔记\\\"/>\\n<span style=\\\"font-weight:600; font-size:1.05em;\\\"><<currentTiddler>></span>\\n<span style=\\\"color:#888; font-size:0.85em; margin-left:8px;\\\"><<count>> 篇</span>\\n</$button>\\n</div>\\n</$set>\\n</$list>\\n<$list filter=\\\"[subfilter<agent-tags-mixed>count[]match[0]]\\\"><div style=\\\"color:#aaa; font-size:0.85em;\\\">(暂无「Agent + 人工」笔记)</div></$list>\\n</div>\\n\"},{\"title\":\"标签笔记\",\"tags\":[\"索引\"],\"type\":\"text/vnd.tiddlywiki\",\"text\":\"\\\\whitespace trim\\n\\n<$set name=\\\"sel\\\" value={{{ [{$:/state/tag}] }}}>\\n\\n!! 标签:<<sel>>\\n\\n<div class=\\\"tc-message-box\\\">包含标签 <strong><<sel>></strong> 的所有笔记(自动收集,按最近修改排序)。</div>\\n\\n<ul>\\n<$list filter=\\\"[all[tiddlers]!is[system]!has[draft.of]tag<sel>] +[!sort[modified]]\\\">\\n<li><$link to=<<currentTiddler>>><$view field=\\\"title\\\"/></$link><span style=\\\"color:#aaa; font-size:0.85em;\\\"> · <$view field=\\\"modified\\\" format=\\\"relativedate\\\"/></span></li>\\n</$list>\\n</ul>\\n\\n</$set>\\n\"}]\n\n/**\n * Seed the home/index tiddlers exactly once per wiki (mirrors the doc-note\n * one-shot policy). Also writes $:/DefaultTiddlers → [[🏠 主页]] so the\n * new home opens by default. With `force` the tiddlers are overwritten with the\n * built-in content and the marker is (re)written — the settings page uses this\n * for \"重新初始化\". Returns whether anything was written this call. Throws when a read fails (the seed registry reports it as `ok:false`).\n */\nexport async function seedHomeIndex(client: TiddlyWebClient, opts?: { force?: boolean }): Promise<boolean> {\n const force = opts?.force === true\n if (!force) {\n const marker = await readSeedTiddler(client, HOME_INDEX_MARKER_TITLE)\n if (marker !== undefined) return false\n }\n let wrote = false\n for (const item of HOME_INDEX_ITEMS) {\n const existing = await readSeedTiddler(client, item.title)\n if (force || existing === undefined) {\n await client.put({ title: item.title, text: item.text, type: item.type, tags: item.tags })\n wrote = true\n }\n }\n // Ensure the default home page is 🏠 主页. A fresh wiki's $:/DefaultTiddlers is\n // the core shadow \"GettingStarted\" (or absent), so a first seed writes it;\n // a user-customised DefaultTiddlers is left alone unless force.\n const dt = await readSeedTiddler(client, '$:/DefaultTiddlers')\n const dtText = typeof dt?.text === 'string' ? dt.text.trim() : ''\n if (force || dt === undefined || dtText === 'GettingStarted' || dtText === '[[GettingStarted]]') {\n await client.put({ title: '$:/DefaultTiddlers', text: HOME_DEFAULT_TIDDLERS, type: 'text/vnd.tiddlywiki', tags: [] })\n wrote = true\n }\n await writeSeedMarker(client, HOME_INDEX_MARKER_TITLE)\n return wrote\n}\n\n/**\n * Un-seed (反初始化): remove the home tiddlers and their marker, restoring the\n * wiki's default home only when it still points at the seeded 🏠 主页\n * (a user-customised $:/DefaultTiddlers is left alone). Deletion is idempotent —\n * a tiddler already gone is not listed. Throws when a read fails (the seed registry reports it as `ok:false`).\n */\nexport async function unseedHomeIndex(client: TiddlyWebClient): Promise<{ removed: string[] }> {\n const removed: string[] = []\n for (const item of HOME_INDEX_ITEMS) {\n const t = await readSeedTiddler(client, item.title)\n if (t !== undefined) {\n await client.delete(item.title)\n removed.push(item.title)\n }\n }\n const marker = await readSeedTiddler(client, HOME_INDEX_MARKER_TITLE)\n if (marker !== undefined) {\n await client.delete(HOME_INDEX_MARKER_TITLE)\n removed.push(HOME_INDEX_MARKER_TITLE)\n }\n const dt = await readSeedTiddler(client, '$:/DefaultTiddlers')\n if (dt !== undefined && typeof dt.text === 'string' && dt.text.trim() === \"[[🏠 主页]]\") {\n await client.put({ title: '$:/DefaultTiddlers', text: '[[GettingStarted]]', type: 'text/vnd.tiddlywiki', tags: [] })\n removed.push('$:/DefaultTiddlers')\n }\n return { removed }\n}\n","/**\n * The \"所有文章\" two-column paginated index page, seeded into wikis by\n * seedAllArticles. Source: 所有文章.tid (the wiki's live tiddler).\n *\n * The page lists every non-system, non-draft tiddler in two independently\n * paginated columns:\n * - 🤖 Agent 撰写: agent-written and not human-edited\n * - 👤 人工 / 人类: not agent-written, OR agent-written + human-edited\n * Page size comes live from the plugin config tiddler\n * ($:/plugins/dsh-tiddlywiki/config → ui.allArticles.pageSize, default 10).\n *\n * @module dsh-tiddlywiki/host/seed-all-articles\n */\nimport type { TiddlyWebClient } from './tw-api.ts'\nimport { readSeedTiddler, writeSeedMarker } from './seed-util.ts'\n\n/** One-time marker: presence means \"the page was offered once — hands off\". */\nexport const ALL_ARTICLES_MARKER_TITLE = '$:/plugins/dsh-tiddlywiki/seed-all-articles'\n\n/** The 所有文章 page title. */\nexport const ALL_ARTICLES_TITLE = '所有文章'\n\n/** The page body, exactly as seeded (user-owned afterwards). */\nexport const ALL_ARTICLES_TEXT = `\\\\whitespace trim\n\n\\\\define page-size() [{$:/plugins/dsh-tiddlywiki/config}jsonget[ui],[allArticles],[pageSize]else[10]]\n\\\\define agent-list() [all[tiddlers]!is[system]!has[draft.of]!tag[索引]tag[agent-written]!tag[human-edited]!sort[modified]]\n\\\\define human-list() [all[tiddlers]!is[system]!has[draft.of]!tag[索引]!tag[agent-written]] [all[tiddlers]!is[system]!has[draft.of]!tag[索引]tag[agent-written]tag[human-edited]] +[!sort[modified]]\n\n<$button class=\"tc-btn-invisible tc-tiddlylink\" style=\"margin:6px 0; padding:4px 10px; border:1px solid rgba(128,128,128,0.3); border-radius:6px; font-size:0.9em;\">\n<$action-navigate $to=\"🏠 主页\"/>\n← 回主页\n</$button>\n\n!! 📚 所有文章\n\n<div class=\"tc-message-box\">分两列汇总全部 wiki 条目(不含系统页、草稿与带 <code>索引</code> 标签的导航页):左列 🤖 Agent 撰写(带 <code>agent-written</code> 且未被人工编辑);右列 👤 人工 / 人类(不含 <code>agent-written</code> 的人类笔记 + 被人工编辑过的 <code>human-edited</code> 条目,即使带 <code>agent-written</code>)。两列各自分页,每页条数可在插件设置里调整。</div>\n\n<div style=\"display:grid; grid-template-columns:1fr 1fr; gap:12px; margin-top:8px;\">\n<div style=\"border:1px solid rgba(128,128,128,0.22); border-radius:10px; padding:10px 12px;\">\n<div style=\"font-weight:700; margin-bottom:6px;\">🤖 Agent 撰写 <span style=\"color:#888; font-weight:400; font-size:0.82em;\">{{{[subfilter<agent-list>count[]]}}} 篇</span></div>\n<$set name=\"page\" tiddler=\"$:/state/dsh/all-articles/page/agent\" emptyValue=\"0\">\n<$set name=\"ps\" value={{{ [subfilter<page-size>] }}}>\n<$set name=\"total\" value={{{ [subfilter<agent-list>count[]] }}}>\n<$set name=\"last\" value={{{ [<total>] +[divide<ps>] +[ceil[0]] +[subtract[1]] +[max[0]] }}}>\n<$set name=\"pagec\" value={{{ [<page>] +[min<last>] +[max[0]] }}}>\n<$set name=\"offset\" value={{{ [<pagec>] +[multiply<ps>] }}}>\n<div>\n<$list filter=\"[subfilter<agent-list>] +[rest<offset>] +[first<ps>]\">\n<div style=\"display:flex; gap:6px; align-items:baseline; padding:3px 0; border-bottom:1px dashed rgba(128,128,128,0.15);\">\n<$link to=<<currentTiddler>> style=\"flex:1;\"><$view field=\"title\"/></$link>\n<span style=\"color:#aaa; font-size:0.82em;\"><$view field=\"modified\" format=\"relativedate\"/></span>\n</div>\n</$list>\n<$list filter=\"[subfilter<agent-list>count[]] +[match[0]]\"><div style=\"color:#aaa; font-size:0.85em;\">(暂无)</div></$list>\n</div>\n<$list filter=\"[<total>!match[0]]\">\n<div style=\"display:flex; gap:8px; align-items:center; margin-top:8px;\">\n<$list filter=\"[<pagec>compare:number:gt[0]]\">\n<$button class=\"tc-btn-invisible\" style=\"padding:2px 10px; border:1px solid rgba(128,128,128,0.35); border-radius:6px;\">\n<$action-setfield $tiddler=\"$:/state/dsh/all-articles/page/agent\" $field=\"text\" $value={{{ [<pagec>] +[subtract[1]] +[max[0]] }}}/>\n◀ 上一页\n</$button>\n</$list>\n<span style=\"color:#888; font-size:0.85em;\">第 {{{ [<pagec>] +[add[1]] }}} / {{{ [<total>] +[divide<ps>] +[ceil[0]] }}} 页</span>\n<$list filter=\"[<pagec>compare:number:lt<last>]\">\n<$button class=\"tc-btn-invisible\" style=\"padding:2px 10px; border:1px solid rgba(128,128,128,0.35); border-radius:6px;\">\n<$action-setfield $tiddler=\"$:/state/dsh/all-articles/page/agent\" $field=\"text\" $value={{{ [<pagec>] +[add[1]] +[min<last>] }}}/>\n下一页 ▶\n</$button>\n</$list>\n</div>\n</$list>\n</$set>\n</$set>\n</$set>\n</$set>\n</$set>\n</$set>\n</div>\n<div style=\"border:1px solid rgba(128,128,128,0.22); border-radius:10px; padding:10px 12px;\">\n<div style=\"font-weight:700; margin-bottom:6px;\">👤 人工 / 人类 <span style=\"color:#888; font-weight:400; font-size:0.82em;\">{{{[subfilter<human-list>count[]]}}} 篇</span></div>\n<$set name=\"page\" tiddler=\"$:/state/dsh/all-articles/page/human\" emptyValue=\"0\">\n<$set name=\"ps\" value={{{ [subfilter<page-size>] }}}>\n<$set name=\"total\" value={{{ [subfilter<human-list>count[]] }}}>\n<$set name=\"last\" value={{{ [<total>] +[divide<ps>] +[ceil[0]] +[subtract[1]] +[max[0]] }}}>\n<$set name=\"pagec\" value={{{ [<page>] +[min<last>] +[max[0]] }}}>\n<$set name=\"offset\" value={{{ [<pagec>] +[multiply<ps>] }}}>\n<div>\n<$list filter=\"[subfilter<human-list>] +[rest<offset>] +[first<ps>]\">\n<div style=\"display:flex; gap:6px; align-items:baseline; padding:3px 0; border-bottom:1px dashed rgba(128,128,128,0.15);\">\n<$link to=<<currentTiddler>> style=\"flex:1;\"><$view field=\"title\"/></$link>\n<span style=\"color:#aaa; font-size:0.82em;\"><$view field=\"modified\" format=\"relativedate\"/></span>\n</div>\n</$list>\n<$list filter=\"[subfilter<human-list>count[]] +[match[0]]\"><div style=\"color:#aaa; font-size:0.85em;\">(暂无)</div></$list>\n</div>\n<$list filter=\"[<total>!match[0]]\">\n<div style=\"display:flex; gap:8px; align-items:center; margin-top:8px;\">\n<$list filter=\"[<pagec>compare:number:gt[0]]\">\n<$button class=\"tc-btn-invisible\" style=\"padding:2px 10px; border:1px solid rgba(128,128,128,0.35); border-radius:6px;\">\n<$action-setfield $tiddler=\"$:/state/dsh/all-articles/page/human\" $field=\"text\" $value={{{ [<pagec>] +[subtract[1]] +[max[0]] }}}/>\n◀ 上一页\n</$button>\n</$list>\n<span style=\"color:#888; font-size:0.85em;\">第 {{{ [<pagec>] +[add[1]] }}} / {{{ [<total>] +[divide<ps>] +[ceil[0]] }}} 页</span>\n<$list filter=\"[<pagec>compare:number:lt<last>]\">\n<$button class=\"tc-btn-invisible\" style=\"padding:2px 10px; border:1px solid rgba(128,128,128,0.35); border-radius:6px;\">\n<$action-setfield $tiddler=\"$:/state/dsh/all-articles/page/human\" $field=\"text\" $value={{{ [<pagec>] +[add[1]] +[min<last>] }}}/>\n下一页 ▶\n</$button>\n</$list>\n</div>\n</$list>\n</$set>\n</$set>\n</$set>\n</$set>\n</$set>\n</$set>\n</div>\n</div>\n`\n\n/**\n * Seed the 所有文章 page exactly once per wiki (mirrors the doc-note one-shot\n * policy). With `opts.force` the page is overwritten with the built-in content\n * and the marker is (re)written — the settings page uses this for\n * \"重新初始化\". Returns whether the page was written this call. Throws when a read fails (the seed registry reports it as `ok:false`).\n */\nexport async function seedAllArticles(client: TiddlyWebClient, opts?: { force?: boolean }): Promise<boolean> {\n const force = opts?.force === true\n if (!force) {\n const marker = await readSeedTiddler(client, ALL_ARTICLES_MARKER_TITLE)\n if (marker !== undefined) return false\n }\n const existing = await readSeedTiddler(client, ALL_ARTICLES_TITLE)\n let wrote = false\n if (force || existing === undefined) {\n await client.put({ title: ALL_ARTICLES_TITLE, text: ALL_ARTICLES_TEXT, type: 'text/vnd.tiddlywiki', tags: ['索引'] })\n wrote = true\n }\n await writeSeedMarker(client, ALL_ARTICLES_MARKER_TITLE)\n return wrote\n}\n\n/**\n * Un-seed (反初始化): remove the 所有文章 page and its one-shot marker.\n * Deletion is idempotent — a tiddler that was already gone is not listed.\n * Throws when a read fails (the seed registry reports it as `ok:false`).\n */\nexport async function unseedAllArticles(client: TiddlyWebClient): Promise<{ removed: string[] }> {\n const removed: string[] = []\n for (const title of [ALL_ARTICLES_TITLE, ALL_ARTICLES_MARKER_TITLE]) {\n const t = await readSeedTiddler(client, title)\n if (t !== undefined) {\n await client.delete(title)\n removed.push(title)\n }\n }\n return { removed }\n}\n","/**\n * Generated from the wiki's custom stylesheet tiddlers (do not hand-edit the\n * constants). Source: 编辑器美化 CSS.css, 标题与按钮区分开.css, 侧边栏窄屏自动隐藏.css, 批注弹窗样式.css, menubar 顶栏加高样式.css\n *\n * The「自定义样式」optional seed: the generic UI stylesheets the wiki owner\n * curated (编辑器美化 / 标题与按钮区分开 / 侧边栏窄屏自动隐藏 / 批注弹窗 /\n * menubar 顶栏加高). Only the functional tag $:/tags/Stylesheet is kept —\n * wiki-local tags (自定义 / agent-written …) are NOT seeded, and none of the\n * sheets carry personal data. Seeded ONE-SHOT (marker-gated, never overwrites\n * user edits) + force 重新初始化 + remove 反初始化, like the other optional seeds.\n *\n * @module dsh-tiddlywiki/host/seed-ui-styles\n */\nimport type { TiddlyWebClient } from './tw-api.ts'\nimport { readSeedTiddler, writeSeedMarker } from './seed-util.ts'\n\n/** One-time marker: presence means \"the styles were offered once — hands off\". */\nexport const UI_STYLES_MARKER_TITLE = '$:/plugins/dsh-tiddlywiki/seed-ui-styles'\n\nexport interface UiStyleItem {\n title: string\n tags: string[]\n type: string\n text: string\n}\n\n/** The stylesheets, exactly as seeded (user-owned afterwards). */\nexport const UI_STYLE_ITEMS: UiStyleItem[] = [{\"title\":\"编辑器美化 CSS\",\"tags\":[\"$:/tags/Stylesheet\"],\"type\":\"text/css\",\"text\":\"/* ===== 编辑器美化(TiddlyWiki 5.4 + CodeMirror)===== */\\n\\n/* 字体 / 字号 / 行高 */\\n.tc-editor .CodeMirror {\\n font-family: \\\"Fira Code VF\\\", Consolas, \\\"Courier New\\\", monospace;\\n font-size: 14px;\\n line-height: 1.7;\\n}\\n\\n/* 编辑区内边距(呼吸感) */\\n.tc-editor .CodeMirror-lines {\\n padding: 12px 14px;\\n}\\n\\n/* 光标颜色 */\\n.tc-editor .CodeMirror-cursor {\\n border-left: 2px solid #2e80ff !important;\\n}\\n\\n/* 当前行高亮 */\\n.tc-editor .CodeMirror-activeline-background {\\n background: rgba(46, 128, 255, 0.08);\\n}\\n\\n/* 行号 */\\n.tc-editor .CodeMirror-linenumber {\\n color: #9aa0a6;\\n padding-right: 10px;\\n}\\n\\n/* 选区配色 */\\n.tc-editor .CodeMirror ::selection {\\n background: rgba(46, 128, 255, 0.22);\\n}\\n\\n/* 工具栏按钮:圆角 + 间距 */\\n.tc-editor-toolbar button {\\n border-radius: 6px;\\n margin: 0 2px 2px 0;\\n}\\n\\n/* 编辑页标题输入框:vanilla 默认 2.35em 过大,缩小字号与内边距 */\\n.tc-tiddler-frame input.tc-edit-texteditor.tc-titlebar,\\n.tc-tiddler-frame .tc-titlebar.tc-edit-texteditor {\\n font-size: 1.2em !important;\\n line-height: 1.35em !important;\\n padding: 4px 8px !important;\\n}\\n\\n/* 查看模式标题:vanilla 默认 2.35em 过大,调小一些 */\\n.tc-tiddler-frame .tc-tiddler-title .tc-titlebar,\\n.tc-tiddler-frame .tc-tiddler-title h2.tc-title {\\n font-size: 1.3em !important;\\n line-height: 1.1em !important;\\n}\\n\\n/* 侧边栏站点标题(sidebar 顶部的 wiki 站名):vanilla 默认 2.35em 过大,调小 */\\n.tc-sidebar .tc-site-title {\\n font-size: 1.5em !important;\\n line-height: 1.3em !important;\\n}\\n\"},{\"title\":\"标题与按钮区分开\",\"tags\":[\"$:/tags/Stylesheet\"],\"type\":\"text/css\",\"text\":\".tc-titlebar h2 {\\n\\tdisplay: table-header-group;\\n\\tword-wrap:break-word;\\n\\tword-break:break-all;\\n}\\n\"},{\"title\":\"侧边栏窄屏自动隐藏.css\",\"tags\":[\"$:/tags/Stylesheet\"],\"type\":\"text/css\",\"text\":\"/* ===== 侧边栏窄屏自动隐藏 =====\\n 视口(TW 面板/iframe 宽度)< 960px 时自动完全隐藏右侧 TiddlyWiki 侧边栏,\\n 正文占满整行;窗口变宽 >= 960px 后自动恢复。\\n 断点 959px 对齐主题默认 sidebarbreakpoint=960px(vanilla/snowwhite/starlight)。\\n 想改断点:改下面媒体查询里的数值即可。\\n 想恢复默认:删除本 tiddler。 */\\n\\n@media (max-width: 959px) {\\n\\n\\t/* 侧边栏整栏隐藏(含站点标题、各 sidebar 板块) */\\n\\t.tc-sidebar-scrollable {\\n\\t\\tdisplay: none !important;\\n\\t}\\n\\n\\t/* 右上角「显示/隐藏侧边栏」chevron 按钮一并隐藏:\\n\\t 窄屏下侧边栏已被 CSS 隐藏,此时按钮点击只会改 $:/state/sidebar 状态、\\n\\t 界面却看不到变化,反而误导,故隐藏 */\\n\\t.tc-hide-sidebar-btn,\\n\\t.tc-show-sidebar-btn {\\n\\t\\tdisplay: none !important;\\n\\t}\\n\\n}\\n\"},{\"title\":\"批注弹窗样式\",\"tags\":[\"$:/tags/Stylesheet\"],\"type\":\"text/css\",\"text\":\"/* 批注功能弹窗美化(dsh-ann-*) */\\n.dsh-ann-popup {\\n\\tborder-radius: 12px;\\n\\tpadding: 14px 16px;\\n\\tmax-width: 520px;\\n\\twidth: max-content;\\n\\tmin-width: 300px;\\n\\tbox-shadow: 0 10px 30px rgba(0,0,0,.22);\\n\\tborder: 1px solid rgba(127,127,127,.25);\\n}\\n.dsh-ann-popup .dsh-ann-head {\\n\\tdisplay: flex;\\n\\talign-items: center;\\n\\tjustify-content: space-between;\\n\\tgap: 10px;\\n\\tfont-weight: 700;\\n\\tfont-size: 1.02em;\\n\\tmargin-bottom: 8px;\\n}\\n.dsh-ann-icon-btn {\\n\\tborder: none;\\n\\tbackground: transparent;\\n\\tcursor: pointer;\\n\\tfont-size: 1em;\\n\\tline-height: 1;\\n\\tpadding: 2px 8px;\\n\\tborder-radius: 6px;\\n\\topacity: .55;\\n}\\n.dsh-ann-icon-btn:hover { opacity: 1; background: rgba(127,127,127,.15); }\\n.dsh-ann-sub { font-size: .88em; margin: 4px 0; }\\n.dsh-ann-quote {\\n\\tbackground: rgba(127,127,127,.14);\\n\\tpadding: 2px 8px;\\n\\tborder-radius: 6px;\\n\\tfont-style: italic;\\n}\\n.dsh-ann-chip {\\n\\tdisplay: inline-block;\\n\\twidth: 12px;\\n\\theight: 12px;\\n\\tborder-radius: 50%;\\n\\tmargin: 0 4px 0 8px;\\n\\tborder: 1px solid rgba(0,0,0,.18);\\n\\tvertical-align: -1px;\\n}\\n.dsh-ann-label { font-size: .9em; margin: 10px 0 4px; }\\n.dsh-ann-input {\\n\\twidth: 100%;\\n\\tborder-radius: 8px;\\n\\tborder: 1px solid rgba(127,127,127,.35);\\n\\tpadding: 6px 9px;\\n\\tfont-size: .95em;\\n\\tresize: vertical;\\n}\\n.dsh-ann-input:focus {\\n\\toutline: none;\\n\\tborder-color: #4a90d9;\\n\\tbox-shadow: 0 0 0 2px rgba(74,144,217,.22);\\n}\\n.dsh-ann-actions {\\n\\tdisplay: flex;\\n\\tjustify-content: flex-end;\\n\\talign-items: center;\\n\\tgap: 8px;\\n\\tmargin-top: 12px;\\n}\\n.dsh-ann-btn {\\n\\tdisplay: inline-block;\\n\\tborder-radius: 8px;\\n\\tpadding: 5px 14px;\\n\\tcursor: pointer;\\n\\tborder: 1px solid rgba(127,127,127,.35);\\n\\tbackground: rgba(127,127,127,.08);\\n\\tfont-size: .9em;\\n\\ttext-decoration: none !important;\\n}\\n.dsh-ann-btn:hover { background: rgba(127,127,127,.18); }\\n.dsh-ann-btn-primary {\\n\\tbackground: #4a90d9;\\n\\tborder-color: #4a90d9;\\n\\tcolor: #fff !important;\\n}\\n.dsh-ann-btn-primary:hover { background: #3a80c9; }\\n.dsh-ann-colors {\\n\\tdisplay: flex;\\n\\tgap: 10px;\\n\\tmargin: 10px 0 12px;\\n}\\n.dsh-ann-swatch {\\n\\twidth: 34px;\\n\\theight: 34px;\\n\\tborder-radius: 50%;\\n\\tborder: 2px solid rgba(0,0,0,.14);\\n\\tcursor: pointer;\\n\\tpadding: 0;\\n\\ttransition: transform .12s ease, box-shadow .12s ease;\\n}\\n.dsh-ann-swatch:hover {\\n\\ttransform: scale(1.18);\\n\\tbox-shadow: 0 2px 8px rgba(0,0,0,.28);\\n}\\n.dsh-ann-sel { font-size: .88em; margin-top: 4px; }\\n\"},{\"title\":\"menubar 顶栏加高样式\",\"tags\":[\"$:/tags/Stylesheet\"],\"type\":\"text/css\",\"text\":\"/* ==== menubar 顶栏加高 ====\\n 默认顶栏约 28px(菜单项 padding 0.5em + line-height 1)。\\n 想调高度:改 min-height 数值即可(如 40 / 48 / 56px)。\\n 想恢复默认:删除本 tiddler。\\n flex 布局让菜单项在加高的栏内垂直居中;窄屏汉堡展开时保持纵向堆叠。 */\\n\\nnav.tc-menubar ul.tc-menubar-list {\\n\\tdisplay: flex;\\n\\talign-items: center;\\n\\tbox-sizing: border-box;\\n\\tmin-height: 44px;\\n}\\n\\nnav.tc-menubar .tc-menubar-narrow ul.tc-menubar-list {\\n\\tflex-direction: column;\\n\\talign-items: stretch;\\n}\\n\"}]\n\n/**\n * Seed the custom stylesheet tiddlers once per wiki (mirrors the one-shot\n * policy). With opts.force the tiddlers are overwritten with the built-in\n * content and the marker (re)written — the settings page uses this for\n * \"重新初始化\". Returns whether anything was written this call. Throws when a read fails (the seed registry reports it as `ok:false`).\n */\nexport async function seedUiStyles(client: TiddlyWebClient, opts?: { force?: boolean }): Promise<boolean> {\n const force = opts?.force === true\n if (!force) {\n const marker = await readSeedTiddler(client, UI_STYLES_MARKER_TITLE)\n if (marker !== undefined) return false\n }\n let wrote = false\n for (const item of UI_STYLE_ITEMS) {\n const existing = await readSeedTiddler(client, item.title)\n if (force || existing === undefined) {\n await client.put({ title: item.title, text: item.text, type: item.type, tags: item.tags })\n wrote = true\n }\n }\n await writeSeedMarker(client, UI_STYLES_MARKER_TITLE)\n return wrote\n}\n\n/**\n * Un-seed (反初始化): remove the stylesheet tiddlers and their marker.\n * Deletion is idempotent — a tiddler already gone is not listed. Throws when a read fails (the seed registry reports it as `ok:false`).\n */\nexport async function unseedUiStyles(client: TiddlyWebClient): Promise<{ removed: string[] }> {\n const removed: string[] = []\n for (const item of UI_STYLE_ITEMS) {\n const t = await readSeedTiddler(client, item.title)\n if (t !== undefined) {\n await client.delete(item.title)\n removed.push(item.title)\n }\n }\n const marker = await readSeedTiddler(client, UI_STYLES_MARKER_TITLE)\n if (marker !== undefined) {\n await client.delete(UI_STYLES_MARKER_TITLE)\n removed.push(UI_STYLES_MARKER_TITLE)\n }\n return { removed }\n}\n","/**\n * The menubar theme override seed.\n *\n * The `tiddlywiki/menubar` plugin colours its top bar with\n * `<<colour menubar-background>>`. Most light palettes (Vanilla, Blanca, …) do\n * NOT define `menubar-background`, so the `colour` macro falls through to the\n * plugin's hardcoded `$:/config/DefaultColourMappings/` → `#5778d8` (blue) —\n * the menubar stays blue in every light theme no matter which palette is\n * active. Dark palettes that DO define it (e.g. CupertinoDark → #464646) look\n * fine, but light ones never adapt.\n *\n * The fix lives entirely on the TW side: a small stylesheet tiddler (tagged\n * `$:/tags/Stylesheet`) that pins the menubar's bar/items to the ACTIVE\n * palette's `background` / `foreground` with `!important`. TW re-renders all\n * stylesheets live whenever `$:/palette` changes, and the DSH theme-sync\n * (src/client/theme-sync.ts) flips `$:/palette` in-memory to follow the DSH\n * light/dark theme — so the menubar follows DSH automatically.\n *\n * Registered as the `menubar-theme` seed: ONE-SHOT (marker-gated, never\n * overwrites user edits) + force \"重新初始化\" from the settings page, exactly\n * like the other seeds.\n *\n * @module dsh-tiddlywiki/host/seed-menubar-theme\n */\nimport type { TiddlyWebClient } from './tw-api.ts'\nimport { readSeedTiddler, writeSeedMarker } from './seed-util.ts'\n\n/** One-time marker: presence means \"the override was offered once — hands off\". */\nexport const MENUBAR_THEME_MARKER_TITLE = '$:/plugins/dsh-tiddlywiki/seed-menubar-theme'\n\n/** The stylesheet tiddler that adapts the menubar to the active palette. */\nexport const MENUBAR_THEME_TIDDLER = '$:/plugins/dsh-tiddlywiki/menubar-theme'\n\n/**\n * The override stylesheet body, exactly as seeded (user-owned afterwards).\n * `<<colour background>>` / `<<colour foreground>>` resolve against the ACTIVE\n * palette at render time; `!important` beats the menubar plugin's own rules\n * regardless of stylesheet ordering. `\\rules` mirrors the menubar plugin's own\n * styles.tid (macrocallinline needed for `<<colour>>`).\n */\nexport const MENUBAR_THEME_TEXT = `\\\\rules only filteredtranscludeinline transcludeinline macrodef macrocallinline\n\nnav.tc-menubar ul.tc-menubar-list {\n\tbackground: <<colour background>> !important;\n}\n\nnav.tc-menubar li.tc-menubar-item > a,\nnav.tc-menubar li.tc-menubar-item > button {\n\tcolor: <<colour foreground>> !important;\n\tfill: <<colour foreground>> !important;\n\tborder-radius: 6px !important;\n\ttransition: background-color 120ms ease, color 120ms ease;\n}\n\nnav.tc-menubar li.tc-menubar-item svg {\n\tfill: <<colour foreground>> !important;\n}\n\nnav.tc-menubar li.tc-menubar-item > a:hover,\nnav.tc-menubar li.tc-menubar-item > button:hover {\n\tbackground: color-mix(in srgb, <<colour foreground>> 12%, transparent) !important;\n\tcolor: <<colour foreground>> !important;\n\tfill: <<colour foreground>> !important;\n}\n\nnav.tc-menubar li.tc-menubar-item > a:active,\nnav.tc-menubar li.tc-menubar-item > button:active {\n\tbackground: color-mix(in srgb, <<colour foreground>> 20%, transparent) !important;\n\tcolor: <<colour foreground>> !important;\n\tfill: <<colour foreground>> !important;\n}\n\nnav.tc-menubar li.tc-menubar-item > a.tc-selected,\nnav.tc-menubar li.tc-menubar-item > button.tc-selected {\n\tbackground: color-mix(in srgb, <<colour foreground>> 16%, transparent) !important;\n\tcolor: <<colour foreground>> !important;\n\tfill: <<colour foreground>> !important;\n}\n\nnav.tc-menubar li.tc-menubar-item > a:focus-visible,\nnav.tc-menubar li.tc-menubar-item > button:focus-visible {\n\toutline: none !important;\n\tbox-shadow: none !important;\n}\n`\n\n/**\n * Seed the menubar theme override exactly once per wiki (mirrors the other\n * one-shot seeds). With `opts.force` the tiddler is overwritten with the\n * built-in content and the marker (re)written — the settings page uses this\n * for \"重新初始化\". Returns whether anything was written this call. Throws when a read fails (the seed registry reports it as `ok:false`).\n */\nexport async function seedMenubarTheme(client: TiddlyWebClient, opts?: { force?: boolean }): Promise<boolean> {\n const force = opts?.force === true\n if (!force) {\n const marker = await readSeedTiddler(client, MENUBAR_THEME_MARKER_TITLE)\n if (marker !== undefined) return false\n }\n const existing = await readSeedTiddler(client, MENUBAR_THEME_TIDDLER)\n let wrote = false\n if (force || existing === undefined) {\n await client.put({\n title: MENUBAR_THEME_TIDDLER,\n text: MENUBAR_THEME_TEXT,\n type: 'text/vnd.tiddlywiki',\n tags: ['$:/tags/Stylesheet'],\n })\n wrote = true\n }\n await writeSeedMarker(client, MENUBAR_THEME_MARKER_TITLE)\n return wrote\n}\n\n/**\n * Un-seed (反初始化): remove the menubar override stylesheet and its marker,\n * returning the wiki to the pre-seed state (the tiddlywiki/menubar top bar\n * falls back to its original colour-mapping behaviour). Deletion is\n * idempotent — a tiddler already gone is not listed. Throws when a read fails (the seed registry reports it as `ok:false`).\n */\nexport async function unseedMenubarTheme(client: TiddlyWebClient): Promise<{ removed: string[] }> {\n const removed: string[] = []\n for (const title of [MENUBAR_THEME_TIDDLER, MENUBAR_THEME_MARKER_TITLE]) {\n const t = await readSeedTiddler(client, title)\n if (t !== undefined) {\n await client.delete(title)\n removed.push(title)\n }\n }\n return { removed }\n}\n","/**\n * Optional seed: 「本地剪藏桥(书签小工具)」使用说明 tiddler (hand-maintained\n * doc constant, like seed-notes / seed-starter-docs — no generator script).\n *\n * The feature itself lives in src/host/clip-bridge.ts (loopback HTTP bridge\n * inside the DSH host process, config group `bridge.*`). This seed only ships\n * the user-facing instruction tiddler: what it is, how to enable it, the\n * bookmarklet JS to paste into the browser's bookmarks bar, security notes and\n * troubleshooting. Marked `dsh-docs` so the seeded home「📚 插件文档」tabs strip\n * picks it up automatically (v0.16.22 convention).\n *\n * v0.16.25: the bookmarklet opens a PICKER overlay (title / selection text +\n * a thumbnailed image checklist, og:image auto-marked 封面); chosen images are\n * downloaded by the bridge and stored as binary attachment tiddlers.\n * v0.16.26: fixed a real-page crash (overlay fields were populated via\n * document.getElementById while p was still detached) — now resolved via\n * p.querySelector scoped to the overlay, values set after appendChild.\n * v0.16.27: the doc additionally ships a DRAGGABLE anchor (raw HTML inside the\n * markdown — verified: TW 5.4.1 markdown keeps a javascript: href + draggable\n * intact, real-TW + headless-Chrome spike) so users can drag it straight to\n * the bookmarks bar; the copy-paste code fence stays as the fallback. The\n * anchor href and the fence share the SAME CLIP_BRIDGE_BOOKMARKLET constant,\n * with a sync guard in verify-clip-bridge.mjs + a browser E2E in\n * verify-clip-bridge-browser.mjs.\n * v0.16.28: FIXES the drag-install — the href is now percent-ENCODED\n * (CLIP_BRIDGE_DRAG_HREF). v0.16.27 used HTML entity escaping, which real\n * Chrome does NOT decode for javascript: URLs (a.href keeps the raw attribute,\n * so the dragged bookmark executed entitized text → 'SyntaxError: Unexpected\n * token';' on every page). Chrome DOES percent-decode before executing.\n *\n * The bookmarklet is ONE long line by design (bookmarks-bar URL fields; no\n * backticks / ${} inside, which also lets it live in this backtick literal).\n *\n * ONE-SHOT + marker semantics identical to seed-notes: write only when the\n * marker is missing (non-force), force re-writes, remove deletes doc + marker.\n *\n * @module dsh-tiddlywiki/host/seed-clip-bridge\n */\nimport type { TiddlyWebClient } from './tw-api.ts'\nimport { readSeedTiddler, writeSeedMarker } from './seed-util.ts'\n\n/** The instruction tiddler's title (a normal, searchable note). */\nexport const CLIP_BRIDGE_DOC_TITLE = '本地剪藏桥(书签小工具)'\n\n/** One-time marker: presence = \"the doc was offered once — hands off\". */\nexport const CLIP_BRIDGE_MARKER_TITLE = '$:/plugins/dsh-tiddlywiki/seed-clip-bridge'\n\n/**\n * The bookmarklet itself (ONE line, no backticks / ${}). Mirrors the bridge's\n * defaults (port 8618, empty token); users adjust the two placeholders. Used\n * BOTH as the copy-paste code fence AND (HTML-attribute-escaped) as the\n * draggable anchor href — keep them in sync (verify-clip-bridge.mjs asserts it).\n */\nexport const CLIP_BRIDGE_BOOKMARKLET = `javascript:(function(){var t=(document.title||location.hostname).trim();var sel=(window.getSelection()?window.getSelection().toString():'').trim();var imgs=[],seen={};function add(u,a,c){u=String(u||'').trim();if(!u||u.indexOf('data:')===0||u.indexOf('blob:')===0||seen[u])return;seen[u]=1;imgs.push({u:u,a:(a||'').slice(0,60),c:!!c})}var mm=document.querySelector('meta[property=\"og:image\"],meta[name=\"twitter:image\"],link[rel=\"image_src\"]');if(mm){var mu=mm.content||mm.href;if(mu)add(mu,'封面',true)}for(var ii=0;ii<document.images.length;ii++){var im=document.images[ii],iu=im.currentSrc||im.src;if(!iu)continue;if(im.naturalWidth&&im.naturalHeight&&(im.naturalWidth<80||im.naturalHeight<80))continue;add(iu,im.alt||im.title,false)}if(imgs.length>60)imgs=imgs.slice(0,60);var ov=document.createElement('div');ov.style.cssText='position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(10,14,20,.62);z-index:2147483647;display:flex;align-items:flex-start;justify-content:center;padding:4vh 12px';var p=document.createElement('div');p.id='cb_p';p.style.cssText='background:#1c2128;border:1px solid #3a414c;border-radius:12px;max-width:680px;width:100%;max-height:86vh;overflow:auto;padding:16px 18px;color:#e6e6e6;font-family:system-ui,-apple-system,\"Segoe UI\",sans-serif;box-sizing:border-box';p.innerHTML='<div style=\"font-size:16px;font-weight:700\">剪藏到 TiddlyWiki</div><label style=\"display:block;font-size:12px;opacity:.7;margin:10px 0 2px\">标题</label><input id=\"cb_t\" style=\"width:100%;box-sizing:border-box;background:#0f131a;border:1px solid #3a414c;border-radius:6px;color:#eee;padding:7px 9px;font-size:13px\" value=\"\"><label style=\"display:block;font-size:12px;opacity:.7;margin:10px 0 2px\">正文 / 选中文字(可选)</label><textarea id=\"cb_s\" rows=\"5\" style=\"width:100%;box-sizing:border-box;background:#0f131a;border:1px solid #3a414c;border-radius:6px;color:#eee;padding:7px 9px;font-size:13px;resize:vertical\"></textarea>';var Q=function(id){return p.querySelector('#'+id)};var grid=document.createElement('div');grid.style.cssText='display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:8px;margin:10px 0';for(var j=0;j<imgs.length;j++){(function(idx){var it=imgs[idx],lab=document.createElement('label');lab.style.cssText='display:flex;flex-direction:column;gap:4px;cursor:pointer;background:#0f131a;border:1px solid #3a414c;border-radius:8px;padding:6px;overflow:hidden';var top=document.createElement('div');top.style.cssText='position:relative';var imgE=document.createElement('img');imgE.style.cssText='display:block;width:100%;height:76px;object-fit:cover;border-radius:4px;background:#000';imgE.src=it.u;imgE.loading='lazy';imgE.referrerPolicy='no-referrer';top.appendChild(imgE);if(it.c){var bd=document.createElement('span');bd.style.cssText='position:absolute;top:2px;left:2px;background:#f5a623;color:#000;font-size:10px;padding:0 4px;border-radius:3px';bd.textContent='封面';top.appendChild(bd)}var row=document.createElement('div');row.style.cssText='display:flex;align-items:center;gap:6px';var ch=document.createElement('input');ch.type='checkbox';ch.id='cb_i_'+idx;ch.checked=it.c;var nm=document.createElement('span');nm.style.cssText='font-size:11px;opacity:.85;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1';nm.textContent=it.a||(it.u.split('/').pop()||'');nm.title=it.u;row.appendChild(ch);row.appendChild(nm);lab.appendChild(top);lab.appendChild(row);grid.appendChild(lab)})(j)};var foot=document.createElement('div');foot.style.cssText='display:flex;align-items:center;justify-content:space-between;margin-top:12px';var cnt=document.createElement('span');cnt.id='cb_cnt';cnt.style.cssText='font-size:12px;opacity:.75';foot.appendChild(cnt);var btns=document.createElement('div');btns.style.cssText='display:flex;gap:8px';var cancel=document.createElement('button');cancel.textContent='取消';cancel.style.cssText='cursor:pointer;background:#333a45;border:0;color:#eee;border-radius:6px;padding:7px 14px;font-size:13px';var go=document.createElement('button');go.id='cb_btn';go.textContent='剪藏';go.style.cssText='cursor:pointer;background:#2f6feb;border:0;color:#fff;border-radius:6px;padding:7px 14px;font-size:13px';btns.appendChild(cancel);btns.appendChild(go);foot.appendChild(btns);p.appendChild(grid);p.appendChild(foot);ov.appendChild(p);document.body.appendChild(ov);Q('cb_t').value=t;Q('cb_s').value=sel;function upd(){var n=0;for(var q=0;q<imgs.length;q++){var c=Q('cb_i_'+q);if(c&&c.checked)n++}cnt.textContent=(imgs.length?('共 '+imgs.length+' 张,已选 '+n+' 张'):'页面没有可选图片(仍可剪藏文字)')}upd();grid.onchange=function(){upd()};function close(){document.removeEventListener('keydown',onKey,true);if(ov.parentNode){document.body.removeChild(ov)}}var onKey=function(ev){if(ev.key==='Escape')close()};cancel.onclick=close;ov.onclick=function(ev){if(ev.target===ov)close()};p.onclick=function(ev){ev.stopPropagation()};document.addEventListener('keydown',onKey,true);function doClip(){var tt=(Q('cb_t').value||'').trim()||t;var urls=[];for(var k=0;k<imgs.length;k++){var ck=Q('cb_i_'+k);if(ck&&ck.checked)urls.push(imgs[k].u)}var btn=Q('cb_btn');btn.disabled=true;btn.textContent='剪藏中…';fetch('http://127.0.0.1:8618/clip',{method:'POST',headers:{'content-type':'application/json','x-clip-token':''},body:JSON.stringify({title:tt,url:location.href,text:(Q('cb_s').value||''),images:urls})}).then(function(r){return r.json().then(function(j){return {ok:r.ok&&!!j.ok,error:(j.error||('HTTP '+r.status)),title:j.title,images:j.images||[]}})}).then(function(res){var okN=0;for(var v=0;v<res.images.length;v++)if(res.images[v].ok)okN++;var line=res.ok?('已剪藏:'+(res.title||tt)+(res.images.length?(' 图片 '+okN+'/'+res.images.length+' 成功'):'')):('剪藏失败:'+res.error);p.innerHTML='<div style=\"font-size:15px;font-weight:700;margin:8px 0\">'+line+'</div><div style=\"font-size:12px;opacity:.8;margin-bottom:12px\">图片以附件形式存进知识库(失败项已降级为链接)。</div><button id=\"cb_d\" style=\"cursor:pointer;background:#2f6feb;border:0;color:#fff;border-radius:6px;padding:7px 14px;font-size:13px\">完成</button>';Q('cb_d').onclick=close}).catch(function(e){p.innerHTML='<div style=\"font-size:14px;color:#ff8080;font-weight:700;margin:8px 0\">剪藏桥不可达</div><div style=\"font-size:12px;opacity:.85;margin-bottom:12px\">'+(e&&e.message?e.message:'')+' 请确认:① dsh web 在运行;② 设置页已启用「本地剪藏桥」;③ 书签里的端口与设置一致;④ 设置了 token 的话书签已带上。</div><button id=\"cb_d\" style=\"cursor:pointer;background:#333a45;border:0;color:#fff;border-radius:6px;padding:7px 14px;font-size:13px\">关闭</button>';Q('cb_d').onclick=close})}go.onclick=doClip})()`\n\n/**\n * The drag-install href: `javascript:` + percent-ENCODED code. v0.16.27 used\n * HTML-entity escaping (&quot;/&amp;/&lt;) which BREAKS dragged bookmarks —\n * real-Chrome experiment: `a.href` keeps the raw attribute (no entity decode),\n * so the stored bookmark executes the entitized text → SyntaxError. Chrome\n * DOES percent-decode javascript: URLs before executing (verified: encoded\n * alert sets a flag), and %XX needs no attribute escaping — so encoding is the\n * correct distribution form. decodeURIComponent(encoded) === the clean code\n * (guarded in verify-clip-bridge.mjs).\n */\nexport const CLIP_BRIDGE_DRAG_HREF = `javascript:${encodeURIComponent(CLIP_BRIDGE_BOOKMARKLET.slice('javascript:'.length))}`\n\n/**\n * The instruction body, Markdown. Ships BOTH a draggable anchor (raw HTML;\n * verified to survive TW 5.4.1 markdown rendering with its javascript: href\n * intact) and the copy-paste code fence — same CLIP_BRIDGE_BOOKMARKLET.\n */\nexport const CLIP_BRIDGE_DOC_TEXT = `# 本地剪藏桥(书签小工具)\n\n把任意网页**一键剪藏进 TiddlyWiki**:浏览器里点一下书签,会弹出一个小浮层——确认(或修改)标题与选中文字、**勾选想要的图片**,点「剪藏」即写入本插件的知识库(默认打 \\`clip\\` 标签,随 wiki 自动 git commit),之后随时可在 TW 里整理、让 Agent 检索。\n\n## 原理\n\nDSH 进程里运行着一个只监听 **127.0.0.1**(本机回环)的 HTTP 小桥——端口默认 \\`8618\\`,可在设置页改。书签浮层把 {标题, URL, 选中文字, 图片 URL 列表} POST 给它,小桥经插件唯一的写入通道写进 TiddlyWiki:\n\n- **文字** → 一篇笔记(Markdown;选了图则自动改用 wikitext,图片用 \\`[img[标题]]\\` 内嵌展示);\n- **图片** → 由小桥(DSH 进程侧,无浏览器跨域限制)**下载字节**,存成**二进制附件 tiddler**(\\`type: image/…\\` + base64 正文,即 TW 原生二进制附件形态),同一篇笔记里内嵌显示并保留 \\`clip-url\\` / \\`clip-note\\` 溯源字段;\n- 某张图下载失败(防盗链/断链)→ **自动降级为链接**,不阻塞整次剪藏。\n\n## 启用(三步)\n\n1. **开启**:DSH 设置 →「TiddlyWiki 知识库」→ 常规配置 → 勾选 **「启用本地剪藏桥」** → 保存。**立即生效,无需重启 dsh web**(未勾选时桥不会放行任何写入)。\n2. **(可选)设置 token**:强烈建议在同一处填一个 **\\`bridge.token\\`** 共享口令——非空后,书签请求必须带上 \\`x-clip-token\\` 头才能写入,防止你在浏览器里访问的任意网页偷偷往 wiki 里塞内容。**改端口(\\`bridge.port\\`)则需要重启 dsh web 生效**。\n3. **装书签**(二选一):\n - **拖拽安装**:按住下面这个按钮,**拖到浏览器书签栏**(或地址栏)松手即装好,自动命名为「剪藏」:\n\n <a href=\"${CLIP_BRIDGE_DRAG_HREF}\" draggable=\"true\" title=\"按住我,拖到浏览器书签栏即可安装\" style=\"display:inline-block;padding:8px 16px;border-radius:8px;background:#2f6feb;color:#fff;text-decoration:none;font-size:14px;cursor:grab;margin:4px 0\">📌 剪藏 — 拖到书签栏</a>\n\n - **复制粘贴**(部分浏览器/环境禁止从页面拖动书签时用):浏览器书签栏新建书签,名称随意(如「剪藏」),**地址(URL)粘贴下面整段代码**。若设置了 token,把代码里 \\`'x-clip-token': ''\\` 的空字符串换成你的口令;若改了端口,把 \\`127.0.0.1:8618\\` 一起改掉:\n\n\\`\\`\\`javascript\n${CLIP_BRIDGE_BOOKMARKLET}\n\\`\\`\\`\n\n## 使用\n\n打开任意网页 → 选中想保留的文字(可选)→ 点书签 → 浮层里:\n\n- **标题**可改;**正文**自动填入选中的文字(可编辑);\n- **图片区**列出页面图片的缩略图(自动跳过 <80px 的小图标与 data:/blob: 占位,去重;页面声明了 \\`og:image\\` 的会标「封面」并默认勾选)——勾选想剪的(最多 10 张);\n- 点「**剪藏**」→ 提示「已剪藏:〈标题〉(图片 n/m 成功)」。\n\n笔记以**页面标题**命名(重名自动追加 \\`(2)\\`、\\`(3)\\`…,绝不覆盖已有笔记)。**图片是二进制附件**(打开笔记直接看到图;每张带 \\`clip-url\\`、\\`clip-note\\` 字段溯源);某张图没存成(网站防盗链等)会在笔记里留一行链接。可用「发送给 Agent」一键把整篇丢给会话整理。\n\n## 安全说明\n\n- 桥**只监听 127.0.0.1**,并校验请求的 \\`Host\\` 头(只放行 127.0.0.1 / localhost / ::1)——恶意网页即使把自己的域名解析到本机回环(DNS rebinding)也调不动它。\n- **强烈建议设置 \\`bridge.token\\`**:不设 token 时,你在浏览器里访问的**任何网页**都能向本机桥提交内容(它们读不到回应,但能往 wiki 里写——只是污染笔记,不是执行代码)。token 只有你知道,网页拿不到。\n- 图片**只下载你勾选的那几个 URL**,由 DSH 本机发起(无跨域限制),存进的是字节副本;不做任何代码执行。\n- 图片附件**不参与 \\`search\\` / \\`recent\\`**(按设计排除二进制,避免刷屏);\\`get\\` 对附件只返回元数据。想删某张图,直接在 TW 里删除对应 tiddler 即可。\n\n## 配置项\n\n| 项 | 默认 | 说明 |\n|---|---|---|\n| \\`bridge.enabled\\` | \\`false\\` | 是否启用剪藏桥(设置页保存后立即生效) |\n| \\`bridge.port\\` | \\`8618\\` | 监听端口(改动需**重启 dsh web** 生效) |\n| \\`bridge.token\\` | 空 | 共享口令;非空时校验 \\`x-clip-token\\` 请求头 |\n| \\`bridge.tag\\` | \\`clip\\` | 剪藏笔记默认 tag |\n\n## 命令行/脚本方式(可选)\n\n不想用书签时,直接 POST 也行(适合脚本、API 调用;\\`images\\` 字段可选,传了就存附件):\n\n\\`\\`\\`bash\ncurl -X POST http://127.0.0.1:8618/clip ^\n -H \"content-type: application/json\" ^\n -H \"x-clip-token: 你的token(没设置就删掉这行)\" ^\n -d \"{\\\\\"title\\\\\":\\\\\"测试\\\\\",\\\\\"url\\\\\":\\\\\"https://example.com\\\\\",\\\\\"text\\\\\":\\\\\"测试选中文字\\\\\",\\\\\"images\\\\\":[\\\\\"https://example.com/a.png\\\\\"]}\"\n\\`\\`\\`\n\n成功返回 \\`{\"ok\":true,\"title\":\"测试\",...,\"images\":[{\"url\":\"...\",\"ok\":true,\"title\":\"测试 图片 1.png\"}]}\\`;未启用时返回 503。\n\n## 常见问题\n\n- 点书签提示「剪藏桥不可达」:① dsh web 没在运行;② 设置页没勾选启用;③ 书签里的端口和设置不一致;④ 设置了 token 但书签没带上。\n- 图片显示「× 失败」:多为网站防盗链/禁止外链下载,笔记里会留链接兜底;也可先在浏览器里把图片另存,再手动拖进 TW。\n- 端口被占用:换一个 \\`bridge.port\\` 后**重启 dsh web**。\n- 想彻底关掉:设置页取消勾选保存即可(端口虽在监听但拒绝任何写入),或直接改插件行 config 的 \\`bridge.enabled: false\\`。\n`\n\n/**\n * Seed the instruction tiddler once per wiki (mirrors seed-notes one-shot\n * policy: marker = offered; from then on the doc is user-owned and never\n * re-created). force re-writes doc + marker (settings page「重新初始化」).\n * Throws when a read fails (the seed registry reports it as `ok:false`).\n */\nexport async function seedClipBridge(client: TiddlyWebClient, opts?: { force?: boolean }): Promise<boolean> {\n const force = opts?.force === true\n if (!force) {\n const marker = await readSeedTiddler(client, CLIP_BRIDGE_MARKER_TITLE)\n if (marker !== undefined) return false\n }\n const existing = await readSeedTiddler(client, CLIP_BRIDGE_DOC_TITLE)\n let wrote = false\n if (force || existing === undefined) {\n await client.put({\n title: CLIP_BRIDGE_DOC_TITLE,\n text: CLIP_BRIDGE_DOC_TEXT,\n type: 'text/markdown',\n tags: ['docs', 'dsh-docs'],\n })\n wrote = true\n }\n await writeSeedMarker(client, CLIP_BRIDGE_MARKER_TITLE)\n return wrote\n}\n\n/** 反初始化: remove the doc tiddler + its one-shot marker (idempotent). */\nexport async function unseedClipBridge(client: TiddlyWebClient): Promise<{ removed: string[] }> {\n const removed: string[] = []\n for (const title of [CLIP_BRIDGE_DOC_TITLE, CLIP_BRIDGE_MARKER_TITLE]) {\n const t = await readSeedTiddler(client, title)\n if (t !== undefined) {\n await client.delete(title)\n removed.push(title)\n }\n }\n return { removed }\n}","/**\n * Generated from the wiki bundle (do not hand-edit the constant).\n * Source: render.bundle.json\n *\n * The \"原生渲染路由\" TW plugin, packaged as a TiddlyWiki plugin bundle\n * (`{\"tiddlers\": {...}}`), seeded into fresh wikis by seedRenderRoute. It\n * registers a server-side route module (`server-routes/render.js`,\n * module-type: route) that renders wiki text into a pure HTML fragment with\n * internal links rewritten to the same-origin DSH proxy hash.\n *\n * @module dsh-tiddlywiki/host/seed-render\n */\nimport type { TiddlyWebClient } from './tw-api.ts'\nimport { readSeedTiddler, writeSeedMarker } from './seed-util.ts'\n\n/** The packaged plugin tiddler title (a TW system tiddler, type application/json). */\nexport const RENDER_PLUGIN_TITLE = '$:/plugins/dsh/render'\n\n/** One-time marker: presence means \"the route was offered once — hands off\". */\nexport const RENDER_MARKER_TITLE = '$:/plugins/dsh-tiddlywiki/seed-render'\n\n/**\n * On-disk tiddler filename for RENDER_PLUGIN_TITLE (TW's FileSystemAdaptor\n * encoding: `$:` → `$__`, `/` → `_`). Callers that must restart TW so the\n * seeded server route loads poll for this file after a seed run.\n */\nexport const RENDER_PLUGIN_FILE = '$__plugins_dsh_render.json'\n\n/** The bundle's JSON text (`{\"tiddlers\": {...}}`), exactly as TW stores it. */\nexport const RENDER_BUNDLE_TEXT = \"{\\n \\\"tiddlers\\\": {\\n \\\"$:/plugins/dsh/render/plugin.info\\\": {\\n \\\"title\\\": \\\"$:/plugins/dsh/render/plugin.info\\\",\\n \\\"type\\\": \\\"application/json\\\",\\n \\\"text\\\": \\\"{\\\\\\\"title\\\\\\\":\\\\\\\"$:/plugins/dsh/render\\\\\\\",\\\\\\\"name\\\\\\\":\\\\\\\"DSH Wiki Render\\\\\\\",\\\\\\\"description\\\\\\\":\\\\\\\"把 wiki 文本原生渲染成 HTML 片段(POST /render 服务端路由),供 DSH 回复流工具卡与 wiki 链接跳转使用\\\\\\\",\\\\\\\"author\\\\\\\":\\\\\\\"dsh-tiddlywiki\\\\\\\",\\\\\\\"version\\\\\\\":\\\\\\\"0.2.0\\\\\\\",\\\\\\\"plugin-type\\\\\\\":\\\\\\\"plugin\\\\\\\"}\\\"\\n },\\n \\\"$:/plugins/dsh/render/server-routes/render.js\\\": {\\n \\\"title\\\": \\\"$:/plugins/dsh/render/server-routes/render.js\\\",\\n \\\"type\\\": \\\"application/javascript\\\",\\n \\\"module-type\\\": \\\"route\\\",\\n \\\"text\\\": \\\"/*\\\\\\\\\\\\ntitle: $:/plugins/dsh/render/server-routes/render.js\\\\ntype: application/javascript\\\\nmodule-type: route\\\\n\\\\nPOST /render — native TiddlyWiki → HTML fragment for the DSH reply stream\\\\n\\\\nBody (JSON in state.data):\\\\n { title } render an existing tiddler's body with ITS type\\\\n { text, type?, contextTitle?, parseAsInline? }\\\\n render arbitrary content as a fragment\\\\n\\\\\\\\*/\\\\n\\\\\\\"use strict\\\\\\\";\\\\n\\\\n/*\\\\nThe fragment's internal wiki links are rewritten to the SAME-ORIGIN DSH proxy\\\\nhash (`/dsh-tiddlywiki/tw/#<title>`) by overriding the `tv-wikilink-template`\\\\nvariable while rendering. Verified against tiddlywiki 5.4.1's link widget:\\\\n`link.js` expands `$uri_encoded$` (encodeURIComponentExtended) into the href,\\\\nso the default `#Title` becomes `/dsh-tiddlywiki/tw/#Title`. The DSH page's\\\\ndocument-level click interceptor + the embedded TW's hash navigation\\\\n(story.js reads the hash via decodeURIComponentSafe) both handle that href —\\\\nno post-hoc string rewriting needed.\\\\n*/\\\\nvar WIKILINK_TEMPLATE = \\\\\\\"/dsh-tiddlywiki/tw/#$uri_encoded$\\\\\\\";\\\\n\\\\nexports.methods = [\\\\\\\"POST\\\\\\\"];\\\\n\\\\nexports.path = /^\\\\\\\\/render$/;\\\\n\\\\nexports.info = {\\\\n\\\\tpriority: 100\\\\n};\\\\n\\\\nfunction sendJson(response,status,payload) {\\\\n\\\\tresponse.writeHead(status,{\\\\\\\"Content-Type\\\\\\\":\\\\\\\"application/json; charset=utf-8\\\\\\\",\\\\\\\"Cache-Control\\\\\\\":\\\\\\\"no-store\\\\\\\"});\\\\n\\\\tresponse.end(JSON.stringify(payload));\\\\n}\\\\n\\\\nexports.handler = function(request,response,state) {\\\\n\\\\tvar body;\\\\n\\\\ttry {\\\\n\\\\t\\\\tbody = JSON.parse(state.data || \\\\\\\"{}\\\\\\\");\\\\n\\\\t} catch(e) {\\\\n\\\\t\\\\tsendJson(response,400,{ok:false,error:\\\\\\\"invalid JSON body\\\\\\\"});\\\\n\\\\t\\\\treturn;\\\\n\\\\t}\\\\n\\\\ttry {\\\\n\\\\t\\\\tvar variables = {\\\\n\\\\t\\\\t\\\\t\\\\\\\"tv-wikilink-template\\\\\\\": WIKILINK_TEMPLATE\\\\n\\\\t\\\\t};\\\\n\\\\t\\\\tif(typeof body.title === \\\\\\\"string\\\\\\\" && body.title.length > 0) {\\\\n\\\\t\\\\t\\\\t// Render an existing tiddler's body with ITS OWN content type (block\\\\n\\\\t\\\\t\\\\t// parse, so headings / tables / lists render natively). The type must\\\\n\\\\t\\\\t\\\\t// be honored: dsh-tiddlywiki writes agent notes as `text/markdown`\\\\n\\\\t\\\\t\\\\t// (tiddlywiki/markdown is enabled), and hardcoding wikitext rendered\\\\n\\\\t\\\\t\\\\t// every markdown note as source (`## x` became a wikitext list).\\\\n\\\\t\\\\t\\\\t// $tw.utils.getParser() falls back to text/vnd.tiddlywiki when no\\\\n\\\\t\\\\t\\\\t// parser is registered for the type, so passing the raw type is safe.\\\\n\\\\t\\\\t\\\\tvar tiddler = state.wiki.getTiddler(body.title);\\\\n\\\\t\\\\t\\\\tif(!tiddler) {\\\\n\\\\t\\\\t\\\\t\\\\tsendJson(response,404,{ok:false,notFound:true,title:body.title});\\\\n\\\\t\\\\t\\\\t\\\\treturn;\\\\n\\\\t\\\\t\\\\t}\\\\n\\\\t\\\\t\\\\tvariables.currentTiddler = body.title;\\\\n\\\\t\\\\t\\\\tvar tiddlerType = typeof tiddler.fields.type === \\\\\\\"string\\\\\\\" && tiddler.fields.type.length > 0\\\\n\\\\t\\\\t\\\\t\\\\t? tiddler.fields.type\\\\n\\\\t\\\\t\\\\t\\\\t: \\\\\\\"text/vnd.tiddlywiki\\\\\\\";\\\\n\\\\t\\\\t\\\\tvar html = state.wiki.renderText(\\\\\\\"text/html\\\\\\\",tiddlerType,tiddler.fields.text,{\\\\n\\\\t\\\\t\\\\t\\\\tparseAsInline: false,\\\\n\\\\t\\\\t\\\\t\\\\tvariables: variables\\\\n\\\\t\\\\t\\\\t});\\\\n\\\\t\\\\t\\\\tresponse.writeHead(200,{\\\\\\\"Content-Type\\\\\\\":\\\\\\\"text/html; charset=utf-8\\\\\\\",\\\\\\\"Cache-Control\\\\\\\":\\\\\\\"no-store\\\\\\\"});\\\\n\\\\t\\\\t\\\\tresponse.end(html);\\\\n\\\\t\\\\t\\\\treturn;\\\\n\\\\t\\\\t}\\\\n\\\\t\\\\tif(typeof body.text === \\\\\\\"string\\\\\\\") {\\\\n\\\\t\\\\t\\\\t// Render arbitrary wiki text (contextTitle gives currentTiddler so\\\\n\\\\t\\\\t\\\\t// links/transclusions resolve in that tiddler's context). `type`\\\\n\\\\t\\\\t\\\\t// optionally selects the parser (e.g. text/markdown); the default is\\\\n\\\\t\\\\t\\\\t// wikitext, and unknown types fall back to it inside TW.\\\\n\\\\t\\\\t\\\\tif(typeof body.contextTitle === \\\\\\\"string\\\\\\\" && body.contextTitle.length > 0) {\\\\n\\\\t\\\\t\\\\t\\\\tvariables.currentTiddler = body.contextTitle;\\\\n\\\\t\\\\t\\\\t}\\\\n\\\\t\\\\t\\\\tvar inline = body.parseAsInline === true;\\\\n\\\\t\\\\t\\\\tvar bodyType = typeof body.type === \\\\\\\"string\\\\\\\" && body.type.length > 0 ? body.type : \\\\\\\"text/vnd.tiddlywiki\\\\\\\";\\\\n\\\\t\\\\t\\\\tvar textHtml = state.wiki.renderText(\\\\\\\"text/html\\\\\\\",bodyType,body.text,{\\\\n\\\\t\\\\t\\\\t\\\\tparseAsInline: inline,\\\\n\\\\t\\\\t\\\\t\\\\tvariables: variables\\\\n\\\\t\\\\t\\\\t});\\\\n\\\\t\\\\t\\\\tresponse.writeHead(200,{\\\\\\\"Content-Type\\\\\\\":\\\\\\\"text/html; charset=utf-8\\\\\\\",\\\\\\\"Cache-Control\\\\\\\":\\\\\\\"no-store\\\\\\\"});\\\\n\\\\t\\\\t\\\\tresponse.end(textHtml);\\\\n\\\\t\\\\t\\\\treturn;\\\\n\\\\t\\\\t}\\\\n\\\\t\\\\tsendJson(response,400,{ok:false,error:\\\\\\\"body must provide \\\\\\\\\\\\\\\"title\\\\\\\\\\\\\\\" or \\\\\\\\\\\\\\\"text\\\\\\\\\\\\\\\"\\\\\\\"});\\\\n\\\\t} catch(e) {\\\\n\\\\t\\\\tsendJson(response,500,{ok:false,error:String((e && e.message) || e)});\\\\n\\\\t}\\\\n};\\\\n\\\"\\n }\\n }\\n}\"\n\n/**\n * Seed the \"原生渲染路由\" TW plugin exactly once per wiki (mirrors the\n * send-to-agent one-shot policy). The marker records the offer; afterwards the\n * bundle is user-owned — deleting it and restarting dsh web does NOT recreate\n * it, and edits are never overwritten. With `opts.force` the bundle is\n * (re)written even when it already exists and the marker is (re)written — the\n * settings page uses this for \"重新初始化\". Returns whether a bundle was\n * written this call. Throws when a read fails (the seed registry reports it as `ok:false`).\n */\nexport async function seedRenderRoute(client: TiddlyWebClient, opts?: { force?: boolean }): Promise<boolean> {\n const force = opts?.force === true\n if (!force) {\n const marker = await readSeedTiddler(client, RENDER_MARKER_TITLE)\n if (marker !== undefined) return false\n }\n const existing = await readSeedTiddler(client, RENDER_PLUGIN_TITLE)\n let wrote = false\n if (force || existing === undefined) {\n await client.put({\n title: RENDER_PLUGIN_TITLE,\n text: RENDER_BUNDLE_TEXT,\n type: 'application/json',\n tags: [],\n // TW only REGISTERS a wiki tiddler as a plugin (boot.js\n // registerPluginTiddlers) when the OUTER tiddler carries a\n // `plugin-type` field — without it the bundle is never unpacked and its\n // server-routes module is never defined, so /render 404s. The metadata\n // mirrors what the live send-to-agent bundle's tiddler carries.\n 'plugin-type': 'plugin',\n name: 'DSH Wiki Render',\n author: 'dsh-tiddlywiki',\n version: '0.2.0',\n description: '把 wiki 文本原生渲染成 HTML 片段(POST /render 服务端路由),供 DSH 回复流工具卡与 wiki 链接跳转使用',\n })\n wrote = true\n }\n // Record the offer regardless, so an existing bundle (upgrade from a\n // pre-seed wiki) also becomes user-owned from here on.\n await writeSeedMarker(client, RENDER_MARKER_TITLE)\n return wrote\n}\n","/**\n * Extensible plugin config (design doc §13, config panel).\n *\n * Two layers:\n * base — the cordis `config:` block (profile composition, defaults);\n * overrides — a user-editable config tiddler ($:/plugins/dsh-tiddlywiki/config,\n * a JSON string) written by the settings page.\n * The tiddler overlays the base (tiddler wins), so future config fields just\n * extend the shape — no schema, no @deepseek-ai dependency, and the config\n * travels with the wiki's git history.\n *\n * @module dsh-tiddlywiki/host/config\n */\nimport type { TiddlyWebClient } from './tw-api.ts'\n\n/** Config tiddler (JSON string) where the settings page stores overrides. */\nexport const CONFIG_TIDDLER = '$:/plugins/dsh-tiddlywiki/config'\n\n/** TW frontend API base tiddler, pointed at the same-origin DSH proxy. */\nexport const TW_WEB_HOST_TIDDLER = '$:/config/tiddlyweb/host'\n\n/** The legacy default TW host value this plugin replaces with the proxy. */\nexport const TW_WEB_HOST_DEFAULT = '$protocol$//$host$/'\n\n/**\n * Default dark palette the embedded TW switches to when DSH is dark\n * (mirrored in src/client/theme-sync.ts — the two bundles cannot share code).\n */\nexport const DARK_PALETTE_DEFAULT = '$:/palettes/CupertinoDark'\n\n/** Extensible, loose plugin config shape (future fields just appear here). */\nexport interface PluginConfigShape {\n /**\n * 本地剪藏桥(书签小工具):DSH 进程内监听 127.0.0.1 的 HTTP 桥,接收\n * 书签 POST 的 {title,url,text} 并写入 wiki。`enabled` 保存后立即生效\n * (每个请求实时判定);`token` 非空时校验 `x-clip-token` 头;`tag` 为\n * 剪藏笔记默认 tag;`port` 改动需重启 dsh web(监听只在启动时绑定一次)。\n */\n bridge?: { enabled?: boolean; port?: number; token?: string; tag?: string }\n note?: { tag?: string }\n /**\n * 注入给每个会话的系统提示词(v0.21.0)。默认 `slim`:只保留工具 schema\n * 表达不了的治理约定(同步纪律 / 标签 / 链接格式),不再手抄工具参数;\n * `full` 额外附一份**由工具注册表实时生成**的参数索引(永不过期)。\n * `override` 整段替换内置正文,`extra` 永远追加在最后。\n * 设置页保存后无需重启 dsh web:section 会重新注册,当前会话下一步即生效。\n */\n prompt?: { enabled?: boolean; mode?: 'slim' | 'full'; extra?: string; override?: string }\n git?: { autoCommit?: boolean; debounceMs?: number; remote?: string; branch?: string }\n ui?: {\n /** 是否在界面右下角显示「快速笔记」悬浮按钮(默认 true)。 */\n showQuickNote?: boolean\n /**\n * 是否在聊天输入框上方(conversation.input.dock 槽位)显示「快速笔记」\n * 快捷按钮(默认 true)。与其它插件(todo/cost-meter/goal/queue/git-graph\n * 等)注入的内容同处一个纵向排列的 dock 区,天然不重叠。\n */\n showQuickNoteDock?: boolean\n /**\n * 点击「快速笔记」后的打开方式(默认 `native`):\n * `native` = 直接弹出 TW 原生编辑器(新建/恢复草稿);`card` = 弹出现有\n * 的 Markdown 快速笔记卡片。设置页可切换。\n */\n quickNoteMode?: 'native' | 'card'\n /** 左侧侧边栏 TW 入口的显示名称(默认「TiddlyWiki」,可自定义)。 */\n sidebarLabel?: string\n /** 是否显示 TW 面板右下角的「状态/重载」悬浮按钮(默认 true)。 */\n showPanelStatus?: boolean\n /** 是否在界面右下角显示「同步」悬浮按钮(默认 true)。 */\n showSyncButton?: boolean\n /**\n * 嵌入式 TW 是否跟随 DSH 深浅主题(默认 true):DSH 暗色时把 TW 活动\n * palette 临时切到 `darkPalette`,浅色时恢复用户原 palette;仅内存生效,\n * 不会写回 wiki。\n */\n followDshTheme?: boolean\n /** DSH 暗色时 TW 使用的 palette tiddler 标题(默认 CupertinoDark)。 */\n darkPalette?: string\n /**\n * 一键发送给 Agent(TW 笔记 → DSH 会话注入)。\n * `enabled: false` 时路由 403、TW 端按钮点击提示未启用;`token` 非空时\n * 路由要求请求头 `x-send-to-agent-token` 与之匹配;`endpoint` 可覆盖\n * TW 端默认的 `location.origin + /dsh-tiddlywiki`。\n */\n sendToAgent?: { enabled?: boolean; endpoint?: string; token?: string }\n /**\n * 「所有文章」两列分页页面的每页条数(默认 10)。页面在渲染时实时读取\n * 该值(见 seed-all-articles),改后无需重新初始化。\n */\n allArticles?: { pageSize?: number }\n /**\n * 会话顶部「知识库」Tab 的显示名称(默认「知识库」)。该 Tab 显示本会话\n * 产生/读取/检索过的 wiki 笔记汇总(conversation.view 槽位,见\n * src/client/session-summary.ts)。\n */\n tabLabel?: string\n /** 是否在会话顶部显示「知识库」Tab(会话相关 wiki 汇总,默认 true)。 */\n showSessionTab?: boolean\n /**\n * 是否在 DSH 右侧边栏(new rightbar)提供 TiddlyWiki 入口/Tab(默认\n * true):在 rightbar 首页(guide 页)注册「TiddlyWiki 知识库」入口盒,\n * 点击即在右侧栏以 tab 形式打开完整 TW 编辑器(与聊天并排)。老版本\n * DSH 无 rightbar 时该配置不生效(客户端自动跳过)。\n */\n showRightbarTab?: boolean\n }\n uiLanguage?: string\n [key: string]: unknown\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** Deep-merge: `over` wins; nested plain objects merge recursively. */\nexport function deepMerge(base: Record<string, unknown>, over: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = { ...base }\n for (const [key, value] of Object.entries(over)) {\n if (value === undefined) continue\n if (isPlainObject(value) && isPlainObject(out[key])) {\n out[key] = deepMerge(out[key] as Record<string, unknown>, value)\n } else {\n out[key] = value\n }\n }\n return out\n}\n\n/**\n * Runtime config store: caches the override tiddler and exposes the effective\n * (merged) config. `load` runs at startup and after every write/restart.\n */\nexport class ConfigStore {\n private overrides: PluginConfigShape = {}\n\n constructor(private readonly base: PluginConfigShape) {}\n\n /** Effective config = cordis base overlaid with the user override tiddler. */\n get(): PluginConfigShape {\n return deepMerge(this.base, this.overrides) as PluginConfigShape\n }\n\n /**\n * Reload the override tiddler (no-op when the wiki is unavailable).\n *\n * A MISSING tiddler (404) legitimately means \"no overrides\" and clears the\n * cache. A transient FAILURE (wiki restarting, timeout, 5xx) must NOT: wiping\n * the cache on error silently reverted every user setting for the rest of the\n * session, and the next `set()` then persisted a config tiddler without the\n * user's other overrides.\n */\n async load(client: TiddlyWebClient | undefined): Promise<void> {\n if (client === undefined) {\n this.overrides = {}\n return\n }\n let tiddler\n try {\n tiddler = await client.get(CONFIG_TIDDLER)\n } catch (err) {\n // Keep whatever we already have (usually the previous overrides).\n console.warn('[dsh-tiddlywiki] config tiddler unreadable, keeping cached overrides:', err instanceof Error ? err.message : err)\n return\n }\n if (tiddler === undefined) {\n this.overrides = {}\n return\n }\n try {\n const parsed = JSON.parse(typeof tiddler.text === 'string' ? tiddler.text : '') as unknown\n this.overrides = isPlainObject(parsed) ? (parsed as PluginConfigShape) : {}\n } catch {\n // Malformed JSON in the config tiddler: keep the cache rather than\n // silently reverting to the cordis base.\n console.warn('[dsh-tiddlywiki] config tiddler is not valid JSON, keeping cached overrides')\n }\n }\n\n /**\n * Merge a patch into the overrides and persist the tiddler.\n *\n * The patch is merged onto the STORED overrides (re-read here), not just the\n * in-memory cache: if the startup `load()` failed transiently, saving one\n * setting must not drop every other override the user had stored.\n */\n async set(client: TiddlyWebClient, patch: PluginConfigShape): Promise<PluginConfigShape> {\n let stored: PluginConfigShape = this.overrides\n try {\n const tiddler = await client.get(CONFIG_TIDDLER)\n if (tiddler !== undefined && typeof tiddler.text === 'string') {\n const parsed = JSON.parse(tiddler.text) as unknown\n if (isPlainObject(parsed)) stored = parsed as PluginConfigShape\n }\n } catch {\n // Unreadable config tiddler → merge onto the in-memory cache.\n }\n this.overrides = deepMerge(stored, patch) as PluginConfigShape\n await client.put({\n title: CONFIG_TIDDLER,\n text: JSON.stringify(this.overrides, null, 2),\n type: 'application/json',\n tags: [],\n })\n return this.get()\n }\n}\n","/**\n * Unified seed registry (design: every one-time \"与 dsh 联动需要 wiki 预置\"\n * item is a SeedDef here).\n *\n * Three tiers (v0.16.22):\n * - CORE seeds (`core: true`) are functionally required by the plugin's\n * own features — the startup path seeds exactly these (non-force) and\n * they can never be 反初始化'd.\n * - STARTER seeds (`startup: true`, removable) are docs / examples that\n * give a fresh wiki a working「文档中心」out of the box — the startup path\n * ALSO seeds them on first install (safe-skip: same-named tiddlers are\n * never overwritten), and the user can 反初始化 them anytime.\n * - OPTIONAL seeds (`core: false`, `startup: false`) are nice-to-have\n * content (首页 / 所有文章 / 自定义样式 / menubar 顶栏主题自适应 / 剪藏桥\n * 说明文档) — they are NEVER auto-seeded; the user opts in from the\n * settings page「初始化」section (重新初始化) and can opt out again with\n * 反初始化 (remove).\n *\n * Each seed owns:\n * - `check` — current state (present / missing / needs-update) for the UI;\n * - `run(force)` — non-force keeps the ONE-SHOT / user-owned semantics\n * (write only when missing, never overwrite), force (re)writes the\n * built-in content and (re)records the marker;\n * - `remove` — optional + starter seeds only: delete the seeded tiddlers +\n * markers, returning the wiki to the \"never seeded\" state.\n *\n * Registry: doc-note / starter-docs / send-to-agent / render-route /\n * home-index / all-articles / ui-styles / menubar-theme / clip-bridge /\n * tw-web-host.\n *\n * @module dsh-tiddlywiki/host/seeds\n */\nimport type { TiddlyWebClient } from './tw-api.ts'\nimport type { PromptToolSummary } from './prompt.ts'\nimport { hashText, readSeedMarker, readSeedTiddler, writeSeedMarker } from './seed-util.ts'\nimport { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { seedDocNote, unseedDocNote, docNoteText, DOC_NOTE_TITLE, SEED_MARKER_TITLE } from './seed-notes.ts'\nimport { seedStarterDocs, unseedStarterDocs, STARTER_DOCS_ITEMS, STARTER_DOCS_MARKER_TITLE } from './seed-starter-docs.ts'\nimport { seedSendToAgent, SEND_TO_AGENT_PLUGIN_TITLE, SEND_TO_AGENT_MARKER_TITLE, SEND_TO_AGENT_BUNDLE_TEXT } from './seed-send-to-agent.ts'\nimport { seedHomeIndex, unseedHomeIndex, HOME_INDEX_ITEMS, HOME_INDEX_MARKER_TITLE } from './seed-home.ts'\nimport { seedAllArticles, unseedAllArticles, ALL_ARTICLES_TITLE, ALL_ARTICLES_MARKER_TITLE, ALL_ARTICLES_TEXT } from './seed-all-articles.ts'\nimport { seedUiStyles, unseedUiStyles, UI_STYLE_ITEMS, UI_STYLES_MARKER_TITLE } from './seed-ui-styles.ts'\nimport { seedMenubarTheme, unseedMenubarTheme, MENUBAR_THEME_TIDDLER, MENUBAR_THEME_MARKER_TITLE, MENUBAR_THEME_TEXT } from './seed-menubar-theme.ts'\nimport { seedClipBridge, unseedClipBridge, CLIP_BRIDGE_DOC_TITLE, CLIP_BRIDGE_MARKER_TITLE, CLIP_BRIDGE_DOC_TEXT } from './seed-clip-bridge.ts'\nimport { seedRenderRoute, RENDER_PLUGIN_TITLE, RENDER_MARKER_TITLE, RENDER_BUNDLE_TEXT } from './seed-render.ts'\nimport { TW_WEB_HOST_TIDDLER, TW_WEB_HOST_DEFAULT } from './config.ts'\nimport { TW_PROXY_PATH } from './wiki.ts'\n\n/** One seed's status as reported to the settings page. */\nexport interface SeedStatus {\n id: string\n title: string\n description: string\n /** true = target tiddler(s) present (or host value correct). */\n present: boolean\n /**\n * true = the seed can be 反初始化 (removed). Core seeds that the plugin's\n * own features depend on are never removable from the settings page.\n */\n removable: boolean\n /** Human detail, e.g. which tiddlers are missing. */\n detail?: string\n /**\n * true = the BUILT-IN content moved on since this wiki was seeded, so\n * 「重新初始化」would bring something new (v0.22.0 content hashes). Only set\n * for seeds that declare `content`.\n */\n updateAvailable?: boolean\n /**\n * true = the stored tiddler no longer matches what we wrote (a human edited\n * it); `undefined` = cannot tell (marker written before hashes existed);\n * `false` = untouched. The settings page warns before overwriting.\n */\n userModified?: boolean\n /** true = the marker tiddler predates v0.22.0 (no hashes recorded yet). */\n legacyMarker?: boolean\n}\n\n/** Result of running one seed. */\nexport interface SeedRunResult {\n id: string\n ok: boolean\n /** true = something was written this call. */\n wrote: boolean\n detail?: string\n error?: string\n}\n\n/** Context a seed needs (client to the live TW server). */\nexport interface SeedContext {\n client: TiddlyWebClient\n /**\n * Live tool registry summary, so generated seed content (the doc note's tool\n * list) can never go stale (v0.22.0). Absent in headless callers — generated\n * content then degrades to a pointer instead of an outdated list.\n */\n tools?: readonly PromptToolSummary[]\n}\n\n/**\n * Wait until a file exists on disk (polling), up to `timeoutMs`.\n *\n * TW's syncer flushes REST writes to the filesystem on a ~250ms task timer, so\n * a freshly seeded tiddler is not on disk the moment PUT resolves. A caller\n * that must restart TW right after (so a seeded SERVER-route plugin loads) has\n * to wait for the flush first — otherwise the restarted TW boots from a stale\n * snapshot, loses every in-memory write that had not been flushed yet, and the\n * seeded route is missing.\n *\n * `newerThanMs` makes the wait meaningful when the file ALREADY exists: it must\n * also have been (re)written after that timestamp, which is what a re-seed\n * produces. Without it an existing file would satisfy the wait immediately and\n * the caller would restart TW over in-flight writes (the v0.18.0 regression the\n * seeds-admin verify test caught).\n */\nexport async function waitForFileWrite(filePath: string, timeoutMs = 8_000, pollMs = 150, newerThanMs?: number): Promise<boolean> {\n const deadline = Date.now() + timeoutMs\n for (;;) {\n try {\n if (existsSync(filePath)) {\n if (newerThanMs === undefined) return true\n if (statSync(filePath).mtimeMs >= newerThanMs) return true\n }\n } catch { /* transient */ }\n if (Date.now() >= deadline) return false\n await new Promise<void>((r) => setTimeout(r, pollMs))\n }\n}\n\n/** The only seed that carries a TW SERVER route — it needs a TW restart to load. */\nexport const RESTART_REQUIRED_SEED_IDS = ['render-route'] as const\n\n/**\n * Flush sentinel. TW's REST layer answers 204 as soon as the tiddler is in the\n * in-memory store; the filesystem syncer writes it on a ~250ms timer. A restart\n * in that window boots from the OLD snapshot and silently loses every write\n * still queued — the v0.18.0 review caught it for the render plugin file, but\n * any write could be lost (force-all repeatedly lost `tw-web-host`).\n *\n * A sentinel alone is NOT enough (v0.22.0). TW's syncer defers a title whose\n * last save is younger than `throttleInterval` (`$:/config/SyncThrottleInterval`,\n * default 1s): `chooseNextTask()` picks the first title that `hasChanged` AND is\n * ready, and SKIPS throttled ones — so the sentinel (a different, never-saved\n * title) can land while another dirty title is still deferred. v0.19.0's\n * \"probe landed ⇒ queue drained\" assumption is therefore false, and the extra\n * marker writes of v0.22.0 were enough to make `tw-web-host` the deferred title\n * every time. See `flushPendingWrites()` for the two-phase fix.\n */\nexport const FLUSH_PROBE_TITLE = '$:/plugins/dsh-tiddlywiki/flush-probe'\n/**\n * 哨兵 tiddler 落盘后的**文件名片段**(用 `includes` 匹配,不写死扩展名)。\n *\n * 为什么不写死 `.tid`:TW 的文件系统适配器按内容类型决定扩展名——`text/plain`\n * 写成 `.txt` + `.txt.meta`,`application/json` 写成 `.json`,只有\n * `text/vnd.tiddlywiki` 才是 `.tid`。v0.19.0 的实现传了 `type: 'text/plain'`\n * 却去等 `.tid`,于是**永远等不到**,而所有调用方都只把返回值当 warning ——\n * 「排干 syncer」其实一直没生效(v0.19.1 的 selftest 断言把它抓了出来)。\n * 现在哨兵用默认 wikitext(落 `.tid`),判定再按「文件名含本片段 + 内容含本次\n * 随机戳」来做,扩展名怎么变都不会静默失效。\n */\nexport const FLUSH_PROBE_FILE_HINT = 'dsh-tiddlywiki_flush-probe'\n\n/** TW default `$:/config/SyncThrottleInterval` (ms) when the tiddler is absent. */\nconst DEFAULT_SYNC_THROTTLE_MS = 1_000\n\n/** Read TW's sync throttle interval (ms), clamped; a read failure → the default. */\nasync function readSyncThrottleMs(client: TiddlyWebClient): Promise<number> {\n try {\n const raw = (await client.get('$:/config/SyncThrottleInterval'))?.text?.trim()\n const parsed = raw === undefined || raw.length === 0 ? Number.NaN : Number.parseInt(raw, 10)\n if (!Number.isFinite(parsed) || parsed < 0) return DEFAULT_SYNC_THROTTLE_MS\n return Math.min(parsed, 10_000)\n } catch {\n return DEFAULT_SYNC_THROTTLE_MS\n }\n}\n\n/**\n * Flush the syncer queue before a restart.\n *\n * Returns true only when the store is provably QUIESCENT. Two phases, because a\n * single probe can overtake a throttled title (see the doc block above):\n *\n * 1. write probe A and wait for A on disk — this drains everything the syncer\n * is willing to write now;\n * 2. wait out one throttle window (no writes of ours in between), then write\n * probe B and wait for IT. Any title still dirty after phase 1 becomes\n * ready inside that window and is written, so only the probe remains.\n *\n * Returns false on timeout / a failed probe PUT; callers treat that as a warning\n * (the operation still proceeds — this is best-effort hardening, not a gate).\n */\nexport async function flushPendingWrites(client: TiddlyWebClient, tiddlersDir: string, timeoutMs = 12_000): Promise<boolean> {\n // ⚠️ 判定不能靠 mtime(v0.19.1 教训之二):哨兵是在等待**紧接**它之前写的,\n // Windows 上新文件的 mtime 可能落在 `Date.now()` 同一/前一个时钟刻,\n // `mtime >= startedAt` 于是永远不成立、轮询到超时返回 false。改成写一段唯一\n // 内容、轮询文件里是否出现它:内容一致才算真的落盘,与时钟精度无关。\n const deadline = Date.now() + timeoutMs\n const probeAndWait = async (phase: string): Promise<boolean> => {\n const stamp = `flush ${phase} ${Date.now()} ${Math.random().toString(36).slice(2)}`\n try {\n // 不传 type → TW 按默认 wikitext 存成 .tid。\n await client.put({ title: FLUSH_PROBE_TITLE, text: stamp })\n } catch {\n return false\n }\n for (;;) {\n try {\n for (const name of readdirSync(tiddlersDir)) {\n if (!name.includes(FLUSH_PROBE_FILE_HINT)) continue\n try {\n if (readFileSync(join(tiddlersDir, name), 'utf8').includes(stamp)) return true\n } catch { /* transient read failure */ }\n }\n } catch { /* tiddlers dir not readable yet */ }\n if (Date.now() >= deadline) return false\n await new Promise<void>((r) => setTimeout(r, 150))\n }\n }\n if (!(await probeAndWait('a'))) return false\n // Phase 2: let every throttled title become ready and be written. The probe\n // itself was just saved, so it is throttled too — which is exactly why the\n // second send must wait out the window (otherwise probe B could land while\n // probe A's contemporaries are still deferred).\n const waitMs = Math.min((await readSyncThrottleMs(client)) + 400, 4_000)\n await new Promise<void>((r) => setTimeout(r, waitMs))\n return probeAndWait('b')\n}\n\n/** Did one of the RESTART_REQUIRED seeds actually write something? */\nexport function needsRestartAfterSeeds(results: Array<{ id: string; ok: boolean; wrote: boolean }>): boolean {\n return results.some((r) => r.ok && r.wrote && (RESTART_REQUIRED_SEED_IDS as readonly string[]).includes(r.id))\n}\n\n/** A registered seed: check current state + run (optionally force) + remove. */\nexport interface SeedDef {\n id: string\n title: string\n description: string\n /**\n * Core seeds are seeded automatically on startup (功能必需) and can never\n * be 反初始化'd — removing them would break a plugin feature.\n */\n core: boolean\n /**\n * STARTER tier (v0.16.22): docs / examples that the startup path ALSO seeds\n * on first install (safe-skip: same-named tiddlers never overwritten), but\n * which stay removable via「反初始化」. Meaningful only when `core` is false.\n */\n startup?: boolean\n /**\n * One-shot marker tiddler (v0.22.0): its `hashes` record what the built-in\n * content looked like when this wiki was seeded, which is what lets the\n * settings page tell 「内置内容有更新」 from 「用户自己改过」.\n */\n markerTitle?: string\n /**\n * The tiddlers this seed writes, in a stable order (title → text). Declaring\n * it opts the seed into update detection + marker hashes; the seed modules\n * export the very constants their writers use, so the two cannot drift.\n */\n content?: (ctx: SeedContext) => Promise<Array<{ title: string; text: string }>>\n check(ctx: SeedContext): Promise<SeedStatus>\n run(ctx: SeedContext, force: boolean): Promise<SeedRunResult>\n /** Non-core seeds only: delete the seeded tiddlers + markers (反初始化). */\n remove?(ctx: SeedContext): Promise<SeedRunResult>\n}\n\n/**\n * Presence probe for a seed's `check`. A 404 means \"missing\"; every other\n * failure PROPAGATES (the caller `checkAllSeeds` turns it into a failed check)\n * — swallowing it here would report \"缺失\" for a service that is merely\n * unavailable and invite an overwrite.\n */\nconst presentOf = (ctx: SeedContext, title: string): Promise<boolean> =>\n ctx.client.get(title).then((t) => t !== undefined)\n\n/** Build the per-item detail from an unseed result. */\nconst removedDetail = (id: string, removed: string[]): SeedRunResult => ({\n id,\n ok: true,\n wrote: false,\n detail: removed.length > 0\n ? `已移除:${removed.join('、')}`\n : '本就不存在,无需移除',\n})\n\n/** Metadata shared by every seed and echoed in its status/result objects. */\ninterface SeedMeta {\n id: string\n title: string\n description: string\n core: boolean\n /** STARTER tier: also auto-seeded on first install (removable). */\n startup?: boolean\n /** One-shot marker tiddler (enables content-hash bookkeeping). */\n markerTitle?: string\n}\n\ntype SeedWriter = (client: TiddlyWebClient, opts?: { force?: boolean; tools?: readonly PromptToolSummary[] }) => Promise<boolean>\ntype SeedUnseeder = (client: TiddlyWebClient) => Promise<{ removed: string[] }>\n/** The tiddlers a seed writes, in a stable order (title → built-in text). */\ntype SeedContent = (ctx: SeedContext) => Promise<Array<{ title: string; text: string }>>\n\n/**\n * Compare a seed's built-in content with what is in the wiki + with the hashes\n * recorded when it was seeded (v0.22.0).\n *\n * Decision table (per tiddler):\n * - recorded hash === current built-in → nothing changed;\n * - recorded hash !== current built-in → the built-in MOVED ON (`updateAvailable`);\n * - stored text !== recorded hash → a human edited it (`userModified: true`);\n * - no recorded hash + text === built-in → LEGACY marker (pre-hash); ownership\n * is proven, so the caller may upgrade the marker;\n * - no recorded hash + text !== built-in → either the user edited it or the\n * built-in changed: `updateAvailable` with `userModified: undefined`\n * (unknown) — never silently overwrite on that basis.\n */\nasync function inspectSeedContent(\n ctx: SeedContext,\n content: Array<{ title: string; text: string }>,\n markerTitle: string | undefined,\n): Promise<{ missing: string[]; updateAvailable: boolean; userModified: boolean | undefined; legacyMarker: boolean }> {\n const markerState = markerTitle === undefined ? {} : await readSeedMarker(ctx.client, markerTitle)\n const hashes = markerState.marker?.hashes ?? {}\n const missing: string[] = []\n let updateAvailable = false\n let userModified: boolean | undefined = false\n let legacyMarker = markerState.legacy === true\n for (const item of content) {\n const current = await readSeedTiddler(ctx.client, item.title)\n if (current === undefined) {\n missing.push(item.title)\n continue\n }\n const stored = typeof current.text === 'string' ? current.text : ''\n const builtinHash = hashText(item.text)\n const recorded = hashes[item.title]\n if (recorded !== undefined) {\n if (recorded !== builtinHash) updateAvailable = true\n if (hashText(stored) !== recorded) userModified = true\n } else if (stored === item.text) {\n legacyMarker = true\n } else {\n updateAvailable = true\n if (userModified !== true) userModified = undefined\n }\n }\n return { missing, updateAvailable, userModified, legacyMarker }\n}\n\n/**\n * Record/refresh the marker's content hashes after a run (v0.22.0). Only\n * tiddlers whose stored text equals the built-in text are hashed — that is what\n * \"we own this content\" means, and it keeps user-edited copies out of the\n * bookkeeping. Existing hashes are merged (never dropped), so a run that\n * skipped an edited tiddler does not forget the others.\n */\nasync function refreshSeedMarker(\n content: SeedContent,\n markerTitle: string,\n ctx: SeedContext,\n previousHashes: Record<string, string>,\n): Promise<void> {\n try {\n const hashes: Record<string, string> = { ...previousHashes }\n for (const item of await content(ctx)) {\n const current = await readSeedTiddler(ctx.client, item.title)\n const stored = current === undefined ? undefined : (typeof current.text === 'string' ? current.text : '')\n if (stored === item.text) hashes[item.title] = hashText(item.text)\n }\n // Skip the PUT when nothing changed: the startup path runs this for every\n // typed seed on EVERY boot, and a no-op marker write per boot is noise (and\n // one more REST write before the syncer flush that follows).\n const unchanged = Object.keys(hashes).length === Object.keys(previousHashes).length\n && Object.keys(hashes).every((key) => hashes[key] === previousHashes[key])\n if (!unchanged) await writeSeedMarker(ctx.client, markerTitle, hashes)\n } catch (err) {\n // Bookkeeping only — the content write already succeeded.\n console.warn(`[dsh-tiddlywiki] seed marker hash refresh failed (${markerTitle}):`, err instanceof Error ? err.message : err)\n }\n}\n\n/**\n * Define a seed from a small spec, killing the id/title/description\n * triplication and the try/catch boilerplate every registry entry used to\n * repeat by hand:\n * - `presentTitle` → default check: is that tiddler present?\n * - `check` → custom presence check (overrides presentTitle).\n * - `write` → built-in content writer; true = (re)written this call.\n * - `run` → custom runner (overrides the standard write wrapper;\n * e.g. tw-web-host honors a user-chosen host value).\n * - `unseed` → remove the seeded tiddlers + markers (removable seeds).\n */\nfunction defineSeed(meta: SeedMeta, impl: {\n presentTitle?: string\n check?: (ctx: SeedContext) => Promise<SeedStatus>\n write?: SeedWriter\n run?: (ctx: SeedContext, force: boolean) => Promise<SeedRunResult>\n unseed?: SeedUnseeder\n content?: SeedContent\n}): SeedDef {\n const { id, title, description, core, startup, markerTitle } = meta\n const removable = !core\n if (impl.check === undefined && (impl.presentTitle === undefined || impl.presentTitle.length === 0)) {\n // Fail fast at module load: a seed without a presence probe would query an\n // EMPTY title, always report \"missing\", and be re-run on every boot.\n throw new Error(`seed \"${id}\" needs either presentTitle or check`)\n }\n if (impl.run === undefined && impl.write === undefined) {\n throw new Error(`seed \"${id}\" needs either write or run`)\n }\n // Presence only — every seed gets one. `content`-aware seeds WRAP this below\n // (v0.22.0) instead of replacing it, so a seed's own present/detail semantics\n // survive and the hash fields are added on top.\n const presenceCheck: SeedDef['check'] = impl.check ?? (async (ctx) => {\n const present = await presentOf(ctx, impl.presentTitle ?? '')\n return { id, title, description, present, removable, detail: present ? '已存在' : '缺失' }\n })\n /**\n * A `content`-declaring seed reports update/modification state ALONGSIDE its\n * own presence check. ⚠️ This must wrap the custom check, not be skipped when\n * one exists: `starter-docs` / `home-index` / `ui-styles` all have custom\n * checks AND custom runners, and an earlier version of this wiring silently\n * left them without update detection (the gap was invisible because the E2E\n * only covered `doc-note`).\n */\n const check: SeedDef['check'] = impl.content === undefined ? presenceCheck : async (ctx) => {\n const base = await presenceCheck(ctx)\n const state = await inspectSeedContent(ctx, await impl.content!(ctx), markerTitle)\n const missingDetail = state.missing.length > 0 ? `缺失:${state.missing.join('、')}` : (base.detail ?? '缺失')\n const bits: string[] = [base.present ? (base.detail ?? '已存在') : missingDetail]\n if (state.updateAvailable) bits.push('内置内容有更新')\n if (state.userModified === true) bits.push('本地已修改')\n else if (state.userModified === undefined && state.updateAvailable) bits.push('无法确认是否本地已修改')\n else if (state.legacyMarker) bits.push('更新检测尚未启用(重新初始化一次即可)')\n return {\n ...base,\n updateAvailable: state.updateAvailable,\n userModified: state.userModified,\n legacyMarker: state.legacyMarker,\n detail: bits.join(' · '),\n }\n }\n /**\n * Run wrapper. It ALWAYS wraps (v0.22.0): a custom `run` still needs the\n * marker-hash refresh afterwards, which is what makes「内置内容有更新」work\n * for the seeds that write through their own runner.\n */\n const run: SeedDef['run'] = async (ctx, force) => {\n try {\n // Capture the marker BEFORE the writer rewrites it, so refreshed hashes\n // merge onto what we already knew instead of erasing it.\n const previous = markerTitle === undefined ? {} : (await readSeedMarker(ctx.client, markerTitle)).marker?.hashes ?? {}\n const result = impl.run !== undefined\n ? await impl.run(ctx, force)\n : await (async (): Promise<SeedRunResult> => {\n const wrote = await impl.write!(ctx.client, { force, tools: ctx.tools })\n return { id, ok: true, wrote, detail: wrote ? (force ? '已重新初始化' : '已写入') : (force ? '内容已是最新(未重写)' : '已存在,跳过') }\n })()\n if (impl.content !== undefined && markerTitle !== undefined && result.ok) {\n await refreshSeedMarker(impl.content, markerTitle, ctx, previous)\n }\n return result\n } catch (err) {\n return { id, ok: false, wrote: false, error: err instanceof Error ? err.message : String(err) }\n }\n }\n const remove: SeedDef['remove'] = impl.unseed === undefined ? undefined : async (ctx) => {\n try {\n return removedDetail(id, (await impl.unseed!(ctx.client)).removed)\n } catch (err) {\n return { id, ok: false, wrote: false, error: err instanceof Error ? err.message : String(err) }\n }\n }\n return {\n id, title, description, core,\n ...(startup === undefined ? {} : { startup }),\n ...(markerTitle === undefined ? {} : { markerTitle }),\n ...(impl.content === undefined ? {} : { content: impl.content }),\n check, run,\n ...(remove === undefined ? {} : { remove }),\n }\n}\n\n/** The full registry, in display order. */\nexport const SEED_DEFS: SeedDef[] = [\n defineSeed(\n { id: 'doc-note', title: '插件说明笔记', description: '「dsh-tiddlywiki 插件说明」——入门说明笔记(首次安装默认写入;ONE-SHOT,用户可改可删,标记 dsh-docs 自动进首页「📚 插件文档」栏)。', markerTitle: SEED_MARKER_TITLE, core: false, startup: true },\n { content: async (ctx) => [{ title: DOC_NOTE_TITLE, text: docNoteText(ctx.tools ?? []) }], presentTitle: DOC_NOTE_TITLE, write: seedDocNote, unseed: unseedDocNote },\n ),\n defineSeed(\n { id: 'starter-docs', title: '示例与文档(汇总模板 / 教程 / 主题页示例)', description: '新手文档中心起步包:主题汇总页·模板、教程(按主题/标签做汇总页)、三个可直接运行的示例主题页(日志 / 决策记录 / 排障)。全部打 dsh-docs 标签,自动出现在首页「📚 插件文档」栏;纯示例无个人数据,同名 tiddler 已存在则安全跳过,不会覆盖。首次安装默认写入,可反初始化。', markerTitle: STARTER_DOCS_MARKER_TITLE, core: false, startup: true },\n {\n content: async () => STARTER_DOCS_ITEMS.map((i) => ({ title: i.title, text: i.text })),\n check: async (ctx) => {\n const missing: string[] = []\n for (const item of STARTER_DOCS_ITEMS) {\n if (!(await presentOf(ctx, item.title))) missing.push(item.title)\n }\n return { id: 'starter-docs', title: '示例与文档(汇总模板 / 教程 / 主题页示例)', description: '新手文档中心起步包:主题汇总页·模板、教程(按主题/标签做汇总页)、三个可直接运行的示例主题页(日志 / 决策记录 / 排障)。全部打 dsh-docs 标签,自动出现在首页「📚 插件文档」栏;纯示例无个人数据,同名 tiddler 已存在则安全跳过,不会覆盖。首次安装默认写入,可反初始化。', present: missing.length === 0, removable: true, detail: missing.length === 0 ? '已存在' : `缺失:${missing.join('、')}` }\n },\n write: seedStarterDocs,\n unseed: unseedStarterDocs,\n },\n ),\n defineSeed(\n { id: 'send-to-agent', title: '「发送给 Agent」按钮', description: 'TW 笔记工具栏「发送给 Agent」按钮插件($:/plugins/dsh/send-to-agent)——把笔记一键注入 DSH 会话。', markerTitle: SEND_TO_AGENT_MARKER_TITLE, core: true },\n { content: async () => [{ title: SEND_TO_AGENT_PLUGIN_TITLE, text: SEND_TO_AGENT_BUNDLE_TEXT }], presentTitle: SEND_TO_AGENT_PLUGIN_TITLE, write: seedSendToAgent },\n ),\n defineSeed(\n { id: 'render-route', title: '原生渲染路由(/render)', description: 'TW 服务端路由插件($:/plugins/dsh/render,server-routes/render.js)——把 wiki 文本在运行中的 TW 里原生渲染成 HTML 片段,回复流工具卡与 wiki 链接跳转依赖它。seed 写入后需重启 TW 使路由生效。', markerTitle: RENDER_MARKER_TITLE, core: true },\n { content: async () => [{ title: RENDER_PLUGIN_TITLE, text: RENDER_BUNDLE_TEXT }], presentTitle: RENDER_PLUGIN_TITLE, write: seedRenderRoute },\n ),\n defineSeed(\n { id: 'home-index', title: '首页(主页 / 所有标签 / 标签笔记)', description: '默认主页:四象限待办 + 「所有标签」「所有文章」入口;所有标签:标签统计 + Agent 区块(纯 Agent / Agent+人工);标签笔记:按标签浏览。系统提示承诺的首页由这里 seed,主页(🏠 主页)同时写入 $:/DefaultTiddlers。', markerTitle: HOME_INDEX_MARKER_TITLE, core: false },\n {\n content: async () => HOME_INDEX_ITEMS.map((i) => ({ title: i.title, text: i.text })),\n check: async (ctx) => {\n const missing: string[] = []\n for (const item of HOME_INDEX_ITEMS) {\n if (!(await presentOf(ctx, item.title))) missing.push(item.title)\n }\n return { id: 'home-index', title: '首页(主页 / 所有标签 / 标签笔记)', description: '默认主页:四象限待办 + 「所有标签」「所有文章」入口;所有标签:标签统计 + Agent 区块(纯 Agent / Agent+人工);标签笔记:按标签浏览。系统提示承诺的首页由这里 seed,主页(🏠 主页)同时写入 $:/DefaultTiddlers。', present: missing.length === 0, removable: true, detail: missing.length === 0 ? '已存在' : `缺失:${missing.join('、')}` }\n },\n write: seedHomeIndex,\n unseed: unseedHomeIndex,\n },\n ),\n defineSeed(\n { id: 'all-articles', title: '所有文章(两列分页总览)', description: '「所有文章」——全部条目分两列(🤖 Agent 撰写 / 👤 人工·人类)各自分页展示。每页条数取插件设置 ui.allArticles.pageSize(默认 10)。', markerTitle: ALL_ARTICLES_MARKER_TITLE, core: false },\n { content: async () => [{ title: ALL_ARTICLES_TITLE, text: ALL_ARTICLES_TEXT }], presentTitle: ALL_ARTICLES_TITLE, write: seedAllArticles, unseed: unseedAllArticles },\n ),\n defineSeed(\n { id: 'ui-styles', title: '自定义样式(编辑器美化 / 窄屏侧栏 / menubar 加高 / 批注弹窗)', description: '5 张通用样式表(tag $:/tags/Stylesheet):编辑器美化(CodeMirror 字体/光标/行号)、标题与按钮区分开、侧边栏窄屏自动隐藏(<960px)、menubar 顶栏加高、批注弹窗美化。纯样式无个人数据。', markerTitle: UI_STYLES_MARKER_TITLE, core: false },\n {\n content: async () => UI_STYLE_ITEMS.map((i) => ({ title: i.title, text: i.text })),\n check: async (ctx) => {\n const missing: string[] = []\n for (const item of UI_STYLE_ITEMS) {\n if (!(await presentOf(ctx, item.title))) missing.push(item.title)\n }\n return { id: 'ui-styles', title: '自定义样式(编辑器美化 / 窄屏侧栏 / menubar 加高 / 批注弹窗)', description: '5 张通用样式表(tag $:/tags/Stylesheet):编辑器美化(CodeMirror 字体/光标/行号)、标题与按钮区分开、侧边栏窄屏自动隐藏(<960px)、menubar 顶栏加高、批注弹窗美化。纯样式无个人数据。', present: missing.length === 0, removable: true, detail: missing.length === 0 ? '已存在' : `缺失:${missing.join('、')}` }\n },\n write: seedUiStyles,\n unseed: unseedUiStyles,\n },\n ),\n defineSeed(\n { id: 'menubar-theme', title: 'menubar 顶栏主题自适应', description: '样式表覆盖($:/plugins/dsh-tiddlywiki/menubar-theme,tag $:/tags/Stylesheet)——把 tiddlywiki/menubar 顶栏从「默认色映射的蓝色」改为跟随当前 palette 的 background/foreground,随 DSH 主题切换($:/palette 翻转)自动换色。', markerTitle: MENUBAR_THEME_MARKER_TITLE, core: false },\n { content: async () => [{ title: MENUBAR_THEME_TIDDLER, text: MENUBAR_THEME_TEXT }], presentTitle: MENUBAR_THEME_TIDDLER, write: seedMenubarTheme, unseed: unseedMenubarTheme },\n ),\n defineSeed(\n { id: 'clip-bridge', title: '本地剪藏桥(书签小工具)', description: '「本地剪藏桥 + 书签小工具」使用说明(Markdown 文档,带书签代码/启用步骤/安全说明):DSH 监听 127.0.0.1 端口接收剪藏请求,浏览器书签一键把当前页标题/URL/选中文字写进知识库(配置 bridge.*,默认 clip 标签)。真功能在插件运行时代码里,此 seed 只预置说明文档。', markerTitle: CLIP_BRIDGE_MARKER_TITLE, core: false },\n { content: async () => [{ title: CLIP_BRIDGE_DOC_TITLE, text: CLIP_BRIDGE_DOC_TEXT }], presentTitle: CLIP_BRIDGE_DOC_TITLE, write: seedClipBridge, unseed: unseedClipBridge },\n ),\n defineSeed(\n { id: 'tw-web-host', title: 'TW 前端 API 基址(同源代理)', description: '把 $:/config/tiddlyweb/host 指向 DSH 同源代理,嵌入式 TW 才能经 DSH origin 访问(远程访问模式的前提)。', core: true },\n {\n check: async (ctx) => {\n // `run` (below) honors a USER-CHOSEN base: it only writes when the\n // value is missing or still the legacy default. The check must agree,\n // otherwise a deliberate custom host is reported as「缺失」.\n //\n // Read WITHOUT swallowing (v0.19.0): the old bare `catch` turned any\n // transient failure into `present: false` — i.e. it reported the user's\n // deliberately customised base as「缺失」and invited the settings page's\n // 「重新初始化」to overwrite it. A failure propagates to checkAllSeeds,\n // which reports「检查失败」instead of a bogus missing state.\n const current = (await ctx.client.get(TW_WEB_HOST_TIDDLER))?.text?.trim()\n const present = typeof current === 'string' && current.length > 0 && current !== TW_WEB_HOST_DEFAULT\n const detail = present\n ? (current === TW_PROXY_PATH ? `已指向 ${TW_PROXY_PATH}` : `已指向自定义基址 ${current}(保留,不会覆盖)`)\n : `当前:${current ?? '(缺失)'},应为 ${TW_PROXY_PATH}`\n return { id: 'tw-web-host', title: 'TW 前端 API 基址(同源代理)', description: '把 $:/config/tiddlyweb/host 指向 DSH 同源代理,嵌入式 TW 才能经 DSH origin 访问(远程访问模式的前提)。', present, removable: false, detail }\n },\n run: async (ctx, force) => {\n try {\n // Read without swallowing: a transient failure must NOT look like\n // \"missing\" (that would overwrite a user's custom base).\n const tiddler = await ctx.client.get(TW_WEB_HOST_TIDDLER)\n const current = tiddler?.text?.trim()\n // Non-force keeps the ensure semantics: write only when missing or still\n // the legacy default (a user override pointing elsewhere is honored).\n if (!force && current !== undefined && current !== TW_WEB_HOST_DEFAULT) {\n return { id: 'tw-web-host', ok: true, wrote: false, detail: '已指向自定义基址,未覆盖' }\n }\n await ctx.client.put({ title: TW_WEB_HOST_TIDDLER, text: TW_PROXY_PATH, type: 'text/plain', tags: [] })\n return { id: 'tw-web-host', ok: true, wrote: true, detail: force ? '已重新初始化(强制写回代理基址)' : '已写入代理基址' }\n } catch (err) {\n return { id: 'tw-web-host', ok: false, wrote: false, error: err instanceof Error ? err.message : String(err) }\n }\n },\n },\n ),\n]\n\n/** Check every seed, returning statuses in registry order. */\nexport async function checkAllSeeds(ctx: SeedContext): Promise<SeedStatus[]> {\n const out: SeedStatus[] = []\n for (const def of SEED_DEFS) {\n try {\n out.push(await def.check(ctx))\n } catch (err) {\n out.push({\n id: def.id,\n title: def.title,\n description: def.description,\n present: false,\n removable: !def.core,\n detail: `检查失败:${err instanceof Error ? err.message : String(err)}`,\n })\n }\n }\n return out\n}\n\n/**\n * Run one seed (or all, when id is undefined). Non-force = one-shot semantics;\n * force = manual \"重新初始化\" from the settings page. A manual run with no id\n * (重新初始化 all) covers every registry item, core and optional alike.\n */\nexport async function runSeedById(ctx: SeedContext, id: string | undefined, force: boolean): Promise<SeedRunResult[]> {\n const targets = id === undefined ? SEED_DEFS : SEED_DEFS.filter((d) => d.id === id)\n if (targets.length === 0) {\n return [{ id: id ?? '', ok: false, wrote: false, error: `unknown seed: ${id}` }]\n }\n const out: SeedRunResult[] = []\n for (const def of targets) {\n out.push(await def.run(ctx, force))\n }\n return out\n}\n\n/**\n * Startup path: seed the CORE items (功能必需:发送给 Agent 按钮 + 原生渲染\n * 路由 + TW 前端 API 基址) AND the STARTER items (首次安装默认:插件说明 +\n * 示例与文档), all non-force (write only what is missing — a same-named\n * tiddler already present is NEVER overwritten, so user data stays user data).\n * Remaining optional seeds (首页 / 所有文章 / 自定义样式 / menubar 顶栏主题自\n * 适应) are never forced on users — they opt in from the settings page\n * 「初始化」section.\n */\nexport async function runAllSeeds(ctx: SeedContext): Promise<SeedRunResult[]> {\n const out: SeedRunResult[] = []\n for (const def of SEED_DEFS) {\n if (!def.core && def.startup !== true) continue\n out.push(await def.run(ctx, false))\n }\n return out\n}\n\n/**\n * 反初始化 (remove): delete one non-core seed's (starter 或 optional) seeded\n * tiddlers + markers, or all non-core seeds when `id` is undefined. Core\n * seeds are functionally required and cannot be removed — a direct request\n * for one returns an error result (and is skipped when removing all).\n */\nexport async function removeSeedById(ctx: SeedContext, id: string | undefined): Promise<SeedRunResult[]> {\n if (id === undefined) {\n const out: SeedRunResult[] = []\n for (const def of SEED_DEFS) {\n if (def.core || def.remove === undefined) continue\n out.push(await def.remove(ctx))\n }\n return out\n }\n const def = SEED_DEFS.find((d) => d.id === id)\n if (def === undefined) {\n return [{ id, ok: false, wrote: false, error: `unknown seed: ${id}` }]\n }\n if (def.core) {\n return [{ id, ok: false, wrote: false, error: '该 seed 为核心项(功能必需),不可反初始化' }]\n }\n if (def.remove === undefined) {\n return [{ id, ok: false, wrote: false, error: '该 seed 不支持移除' }]\n }\n return [await def.remove(ctx)]\n}\n","/**\n * Small text helpers shared by the host modules (v0.20.0).\n *\n * `snippetOf` used to exist as two identical private copies (tools.ts for the\n * `tiddlywiki_recent` render, routes.ts for the quick-note/recent/search JSON)\n * with two different default widths — a drift waiting to happen.\n *\n * @module dsh-tiddlywiki/host/text-util\n */\n\n/** Flat one-line snippet: whitespace collapsed, truncated with an ellipsis. */\nexport function snippetOf(text: string, max = 160): string {\n const flat = text.replace(/\\s+/g, ' ').trim()\n return flat.length <= max ? flat : `${flat.slice(0, max)}…`\n}\n","/**\n * 共享写策略(v0.19.1):所有「写 TiddlyWiki」的路径(agent 工具 + HTTP 路由)\n * 必须走这里,别再各自拼 PUT body。\n *\n * 为什么抽出这一层:TW 的 REST PUT 是**整体替换**。v0.19.0 只在工具层修了\n * 「以已有条目为基底、不传 tags 就保留标签与自定义字段」,而 `/note`、`/edit`\n * 这些人类入口仍然直接 `put({title,text,tags})` —— 用一个同名标题保存就会把\n * 人类笔记的标签和 `q`/`due`/`clip-url` 等自定义字段清空(v0.19.1 审计发现)。\n * 把策略收成一个模块,两条路径共用,回归就不会再各修各的。\n *\n * 另外两个必须记住的事实:\n * - 单条 GET 返回的自定义字段**嵌套在 `fields` 下**(core-server 的\n * get-tiddler.js 把 knownFields 之外的字段收进 `fields`),PUT 却接受平铺\n * 字段,所以 `cleanTiddler()` 必须摊平,否则 rename/append/trash 静默丢字段;\n * - `revision`(GET 的 changeCount)是唯一「刚 PUT 还没落盘」时也存在的并发\n * 令牌——那种条目没有 `modified`,两个令牌都支持。\n *\n * @module dsh-tiddlywiki/host/write-policy\n */\nimport type { Tiddler } from './tw-api.ts'\nimport { parseTiddlerDate, toIsoDateString } from './tw-api.ts'\n\n/** 约定标签:标记「由 Agent 撰写」的笔记(新建时自动补打)。 */\nexport const AGENT_WRITTEN_TAG = 'agent-written'\n\n/** 约定标签:标记「Agent 撰写后又经人类编辑」的笔记。 */\nexport const HUMAN_EDITED_TAG = 'human-edited'\n\n/** Agent / 人类笔记的默认内容类型(TW 对无 type 的条目按 wikitext 解析)。 */\nexport const DEFAULT_NOTE_TYPE = 'text/markdown'\n\n/**\n * 构造 PUT body 时跳过的字段(身份/内容/TW 自己的时间戳)。\n *\n * ⚠️ `type` **不在**这里(v0.20.1 修复):它曾被误列为跳过字段,于是\n * `cleanTiddler(existing)` 把条目的内容类型丢掉,`finalTypeForWrite()` 随后又\n * 补上默认的 `text/markdown`(或干脆不写 type → TW 回落 `text/vnd.tiddlywiki`)。\n * 结果:任何「覆盖已有条目」的路径都会静默改掉类型——\n * - `text/css` 的样式条目被改成 `text/markdown` → 整篇 CSS 被当 Markdown 渲染;\n * - `text/markdown` 笔记被改成 `text/vnd.tiddlywiki` → `##`/`**粗体**`/表格全按\n * wikitext 解析(磁盘上 `.md` + `.meta` 也变成 `.tid`)。\n * 内容类型是条目的解析方式,必须与 tags/自定义字段一样按「以已有条目为基底」保留。\n */\nconst CLEAN_SKIP_FIELDS = new Set(['title', 'text', 'tags', 'created', 'modified', 'fields'])\n\n/**\n * 调用方**不得**通过 `fields` 覆盖的保留字段:它们是条目的身份/内容,时间戳\n * 归 TW 所有。`type` 故意放行——那是显式指定内容类型的正规入口。\n */\nconst RESERVED_TIDDLER_FIELDS = new Set(['title', 'text', 'tags', 'created', 'modified'])\n\n/**\n * TW 服务端 GET/listing 会为缺省 `type` 的条目**补上** `text/vnd.tiddlywiki`\n * (get-tiddler.js / get-tiddlers-json.js 都有 `type = type || \"text/vnd.tiddlywiki\"`),\n * 而 `bag` 也是响应里补的传输字段。回写时把这两个透传回去要么污染文件(type)、\n * 要么被 TW 自行剥掉(bag/revision),所以从展示与回写里都排除。\n */\nconst TRANSPORT_FIELDS = new Set(['bag', 'revision', 'recipe', 'uri', 'permissions'])\n\n/** 写入冲突(乐观并发令牌不匹配):路由层把它映射成 HTTP 409。 */\nexport class WriteConflictError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'WriteConflictError'\n }\n}\n\n/**\n * 把单条 GET 返回的字段**摊平**成模型/路由可直接读的形状:\n * 嵌套 `fields` 里的自定义字段提到顶层,`bag`/`revision` 等传输字段排除。\n */\nexport function flattenTiddlerFields(tiddler: Tiddler): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(tiddler)) {\n if (key === 'title' || key === 'text' || key === 'tags' || key === 'fields') continue\n if (TRANSPORT_FIELDS.has(key)) continue\n out[key] = value\n }\n const nested = tiddler.fields\n if (typeof nested === 'object' && nested !== null && !Array.isArray(nested)) {\n for (const [key, value] of Object.entries(nested)) {\n if (key === 'title' || key === 'text' || key === 'tags' || key === 'fields') continue\n if (value === undefined) continue\n if (out[key] === undefined) out[key] = value\n }\n }\n return out\n}\n\n/**\n * 由已有条目构造一份可 PUT 的副本:字段摊平、tags 归数组、TW 拥有的时间戳丢掉。\n * `type` 保留(含服务端补的默认值——语义等价,且能防止 Markdown 笔记被回退成\n * wikitext;用户若显式改名内容类型,走 `fields.type`)。\n */\nexport function cleanTiddler(t: Tiddler): Tiddler {\n const out: Tiddler = { title: t.title, text: t.text ?? '', tags: t.tags ?? [] }\n for (const [k, v] of Object.entries(t)) {\n if (CLEAN_SKIP_FIELDS.has(k) || TRANSPORT_FIELDS.has(k)) continue\n out[k] = v\n }\n const nested = t.fields\n if (typeof nested === 'object' && nested !== null && !Array.isArray(nested)) {\n for (const [k, v] of Object.entries(nested)) {\n if (CLEAN_SKIP_FIELDS.has(k) || TRANSPORT_FIELDS.has(k) || v === undefined) continue\n out[k] = v\n }\n }\n return out\n}\n\n/** 合并调用方显式提供的自定义字段(跳过保留字段与 undefined)。 */\nexport function applyCustomFields(tiddler: Tiddler, fields: Record<string, unknown> | undefined): void {\n if (fields === undefined || fields === null || typeof fields !== 'object') return\n for (const [key, value] of Object.entries(fields)) {\n if (RESERVED_TIDDLER_FIELDS.has(key)) continue\n if (value === undefined) continue\n tiddler[key] = value\n }\n}\n\n/** 归一化工具/路由的 `tags` 参数(非数组或全空 → undefined)。 */\nexport function normalizeTagArg(tags: unknown): string[] | undefined {\n if (!Array.isArray(tags)) return undefined\n const list = tags.filter((t): t is string => typeof t === 'string' && t.trim().length > 0).map((t) => t.trim())\n return list.length > 0 ? list : undefined\n}\n\n/**\n * 计算最终标签:\n * - 已存在的条目 → 调用方给什么就是什么(不给 = 保留基底里的原标签);\n * - 新条目 + `agentTag` → 自动补 `agent-written`(`$:/` 系统条目除外);\n * - 新条目 + 人类入口(`agentTag: false`,如快速笔记 / `/note`)→ 不补,\n * 人类写的笔记不该被标成 agent 撰写。\n */\nexport function finalTagsForWrite(title: string, existing: Tiddler | undefined, tags: string[], agentTag: boolean): string[] {\n if (existing !== undefined) return tags\n if (!agentTag) return tags\n if (title.startsWith('$:/')) return tags\n if (tags.includes(AGENT_WRITTEN_TAG)) return tags\n return [...tags, AGENT_WRITTEN_TAG]\n}\n\n/**\n * 计算最终内容类型(原地改 tiddler):显式 type 优先;**只有新建条目**才回落\n * Markdown,`$:/` 条目保持 TW 默认。\n *\n * v0.20.1:`isNew=false`(覆盖既有条目)时**绝不**发明类型——基底里有什么就是\n * 什么;基底没有 type(极少数情况)也保持「没有」,让 TW 继续按 wikitext 处理,\n * 而不是把它升级成 Markdown。改类型是调用方的显式动作(`fields.type`)。\n */\nexport function finalTypeForWrite(title: string, tiddler: Tiddler, isNew = true): { defaulted: boolean } {\n if (typeof tiddler.type === 'string' && tiddler.type.length > 0) return { defaulted: false }\n if (!isNew) return { defaulted: false }\n if (title.startsWith('$:/')) return { defaulted: false }\n tiddler.type = DEFAULT_NOTE_TYPE\n return { defaulted: true }\n}\n\nexport interface BuildWriteOptions {\n /** 已有条目(`wiki.get()` 的结果;读失败不得吞成 undefined)。 */\n existing?: Tiddler | undefined\n /** 显式标签:给了就整体替换,没给就保留基底里的原标签。 */\n tags?: string[] | undefined\n /** 显式自定义字段(逐个覆盖在基底之上)。 */\n fields?: Record<string, unknown> | undefined\n /** 新建条目时是否自动补 `agent-written`(agent 工具 true,人类入口 false)。 */\n agentTag?: boolean\n /** 新建条目且未显式给标签时使用的默认标签(人类入口,如 note.tag)。 */\n defaultTags?: string[] | undefined\n}\n\n/**\n * 构造一次写要 PUT 的 tiddler。\n *\n * - 已有条目 → 以其为基底(标签、自定义字段、**内容类型**全部保留);\n * - 新条目 → `{title, text}` + 默认/显式标签(`$:/` 不带 agent 标签);\n * - `fields` 逐个覆盖;`type` 未指定时**仅新建条目**补 Markdown(`$:/` 除外)。\n */\nexport function buildWriteTiddler(\n title: string,\n text: string,\n options: BuildWriteOptions = {},\n): { tiddler: Tiddler; typeDefaulted: boolean } {\n const { existing, fields } = options\n const agentTag = options.agentTag !== false\n const tiddler: Tiddler = existing !== undefined ? { ...cleanTiddler(existing), title, text } : { title, text }\n if (options.tags !== undefined) {\n const finalTags = finalTagsForWrite(title, existing, options.tags, agentTag)\n if (finalTags.length > 0) tiddler.tags = finalTags\n else delete tiddler.tags\n } else if (existing === undefined) {\n const seedTags = options.defaultTags !== undefined && options.defaultTags.length > 0 ? options.defaultTags : []\n const finalTags = finalTagsForWrite(title, undefined, seedTags, agentTag)\n if (finalTags.length > 0) tiddler.tags = finalTags\n }\n applyCustomFields(tiddler, fields)\n // v0.20.1: the Markdown default is for NEW tiddlers only — an overwrite keeps\n // the content type it was read with (see finalTypeForWrite).\n const { defaulted } = finalTypeForWrite(title, tiddler, existing === undefined)\n return { tiddler, typeDefaulted: defaulted }\n}\n\nexport interface ConflictTokens {\n expectedModified?: string\n expectedRevision?: string | number\n force?: boolean\n}\n\n/**\n * 乐观并发守卫:调用方可以传 `tiddlywiki_get` 读到的 `modified` 或 `revision`;\n * 与当前值不一致就拒绝写入,避免覆盖人类在 TW 编辑器里的并发修改。\n */\nexport function assertNoConflict(title: string, existing: Tiddler | undefined, expected: ConflictTokens): void {\n if (expected.force === true || existing === undefined) return\n const wantsToken = expected.expectedModified !== undefined || expected.expectedRevision !== undefined\n if (!wantsToken) return\n // 两种日期格式都接受:get 给模型的是 ISO,REST 存的是 TW 紧凑格式。\n const expectedMs = parseTiddlerDate(expected.expectedModified)\n const currentMs = parseTiddlerDate(existing.modified)\n if (expectedMs !== undefined && currentMs !== undefined && expectedMs === currentMs) return\n const currentRevision = existing.revision\n if (expected.expectedRevision !== undefined && currentRevision !== undefined\n && String(currentRevision) === String(expected.expectedRevision)) return\n throw new WriteConflictError(\n `写入冲突:tiddler「${title}」在你读取之后已被改动(当前 revision=${currentRevision ?? '?'} modified=${toIsoDateString(existing.modified) ?? '?'};`\n + `期望 revision=${expected.expectedRevision ?? '?'} modified=${expected.expectedModified ?? '?'})。`\n + '请重新读取最新内容后重试;确认要用你的版本覆盖时可传 force: true。',\n )\n}\n","/**\n * DSH webserver routes for dsh-tiddlywiki (design doc §10).\n *\n * Every route is registered with an explicit method whitelist and wrapped in\n * `guardHandler` (see the registration block at the bottom of this file); the\n * authoritative list, including the admin routes registered in admin.ts:\n *\n * | route | method | purpose |\n * |----------------------------------|--------|------------------------------------------|\n * | /dsh-tiddlywiki/status | GET | panel health (service / url / git / tag) |\n * | /dsh-tiddlywiki/note | POST | quick-note → independent tiddler |\n * | /dsh-tiddlywiki/edit | POST | save + open in TW's native editor (draft)|\n * | /dsh-tiddlywiki/tags | GET | tag vocabulary + counts (limit/sort) |\n * | /dsh-tiddlywiki/recent | GET | recently modified notes |\n * | /dsh-tiddlywiki/get | GET | one tiddler for the quick-note picker |\n * | /dsh-tiddlywiki/search | GET | keyword search (reply-stream tool card) |\n * | /dsh-tiddlywiki/render | POST | TW render → sanitized HTML fragment |\n * | /dsh-tiddlywiki/sync | POST | pull → commit → push (mutex) |\n * | /dsh-tiddlywiki/upload | POST | upload into wiki/files/ |\n * | /dsh-tiddlywiki/restart | POST | one-click retry/restart of the TW child |\n * | /dsh-tiddlywiki/session/summary | POST | per-session wiki summary (§「知识库」Tab) |\n * | /dsh-tiddlywiki/agent/sessions | GET | send-to-agent session picker |\n * | /dsh-tiddlywiki/agent/modes | GET | agent / permission preset rosters |\n * | /dsh-tiddlywiki/agent/send | POST | deliver a note into a session |\n * | /dsh-tiddlywiki/agent/create | POST | create session (+workspace) and deliver |\n * | /dsh-tiddlywiki/api/* | any | passthrough to the TW service (JSON) |\n * | /dsh-tiddlywiki/tw/* | any | SAME-ORIGIN TW proxy (index + files + API)|\n * | /dsh-tiddlywiki/admin/state | GET | settings page: info + catalog + config |\n * | /dsh-tiddlywiki/admin/info | POST | write tiddlywiki.info plugins/themes |\n * | /dsh-tiddlywiki/admin/config | POST | write the config tiddler |\n * | /dsh-tiddlywiki/admin/restart | POST | restart the TW child |\n * | /dsh-tiddlywiki/admin/seeds | GET | seed status |\n * | /dsh-tiddlywiki/admin/seeds/run | POST | run seeds (include force re-init) |\n * | /dsh-tiddlywiki/admin/seeds/remove| POST | 反初始化 (remove non-core seeds) |\n *\n * Matching is exact-over-prefix, so the exact routes win and the `/api` /\n * `/tw` prefixes catch the rest. Client calls are same-origin (the DSH web\n * server), so no CORS is involved. The `/tw` proxy is the remote-access\n * bridge: it serves the ENTIRE TW frontend to the browser through the DSH\n * origin (see TW_PROXY_PATH in wiki.ts), so the embedded editor works no\n * matter which host/domain the user reaches DSH on.\n *\n * Titles under `$:/plugins/dsh-tiddlywiki/` are NEVER served to a caller\n * (`isBlockedProxyTitle`): that namespace holds the shared tokens and the git\n * remote, and every caller-supplied title must pass through the predicate.\n *\n * @module dsh-tiddlywiki/host/routes\n */\nimport type { IncomingHttpHeaders, IncomingMessage, ServerResponse } from 'node:http'\nimport { mkdir, writeFile, stat } from 'node:fs/promises'\nimport { basename, extname, isAbsolute, join } from 'node:path'\nimport { Readable } from 'node:stream'\nimport type { TiddlyWebClient } from './tw-api.ts'\nimport { isBinaryType, toIsoDateString } from './tw-api.ts'\nimport type { WikiServer } from './wiki.ts'\nimport type { GitFace, GitStatusView } from './git.ts'\nimport { PATH_PREFIX, TW_PROXY_PREFIX, TW_PROXY_PATH } from './wiki.ts'\nimport { writeSessionSummary, type SessionQueryFace } from './session-summary.ts'\nimport { readBody, readBodyBuffer, json, guardHandler, errorStatus, rejectCrossSiteWrite, rejectNonRead, safeTokenEqual, MAX_PROXY_BODY_BYTES, MAX_UPLOAD_BYTES } from './http.ts'\nimport { sanitizeTwFragment } from './sanitize.ts'\nimport { flushPendingWrites } from './seeds.ts'\nimport { snippetOf } from './text-util.ts'\nimport { WriteConflictError, assertNoConflict, buildWriteTiddler, flattenTiddlerFields } from './write-policy.ts'\n\nexport { writeSessionSummary, SESSION_SUMMARY_PREFIX } from './session-summary.ts'\nexport type { SessionQueryFace, SessionSummaryResult } from './session-summary.ts'\n\nexport const ROUTE_PREFIX = PATH_PREFIX\n\n/** Tiddler type for quick-notes: Markdown, so the uploaded images/links and\n * any Markdown in the note actually render in TW (a type-less tiddler is\n * treated as plain wiki text and shows raw `![..]`/`[..]` instead). */\nconst NOTE_TYPE = 'text/markdown'\n\n/** Structural webserver face (a subset of dsh-host-webserver). */\nexport interface WebServerFace {\n register(route: { kind: 'exact' | 'prefix'; path: string; handler: (req: IncomingMessage, res: ServerResponse) => void }): () => void\n}\n\n/**\n * Structural face over the DSH `sessionController` service (a subset of\n * dsh-api-session-controller). Only the methods the agent-send routes need are\n * declared; the runtime instance is a real Service, never inspected data.\n */\nexport interface SessionControllerFace {\n prompt(\n request: { requestId: string; sessionId: string; mode: 'queue' | 'steer'; content: Array<{ type: 'text'; text: string }> },\n signal: AbortSignal,\n ): Promise<{ accepted: boolean }>\n list(\n request: { cursor?: string },\n signal: AbortSignal,\n ): Promise<{\n items: Array<{\n sessionId: string\n updatedAt?: number\n running?: boolean\n blank?: boolean\n parentSessionId?: string\n cwd?: string\n }>\n }>\n create(request: { cwd?: string; workspaceId?: string; agentPreset?: string }): Promise<{ sessionId: string }>\n}\n\n/**\n * Structural face over the DSH `workspaceRegistry` service (a subset of\n * dsh-workspace). Only what the agent-create route needs is declared; the\n * runtime instance is a real Service, never inspected data.\n */\nexport interface WorkspaceRegistryFace {\n /** Resolve or create the workspace owning `path` — idempotent by canonical path. */\n create(path: string, title?: string): Promise<{ id: string; path: string }>\n}\n\n/**\n * Structural face over the DSH `agentPresets` service (a subset of\n * dsh-agent-presets). It is the deployment's \"工作模式\" registry — the agent\n * presets a session can be composed from (default / cordis / blade / …). Only\n * `list` + `resolve` (default id) are needed by the agent-modes route.\n */\nexport interface AgentPresetsFace {\n /** Every preset the configured roots currently supply. */\n list(): Promise<Array<{ id: string; name?: string; description?: string; trust?: string; broken?: string }>>\n /** Resolve one preset by id (`undefined` = the deployment default). */\n resolve(id?: string): Promise<{ id: string; name?: string; description?: string }>\n}\n\n/**\n * Structural face over the DSH `sessionPersistence` service. Only the\n * lightweight `list` (metadata headers, no log parse) is needed so the\n * agent-sessions route can attach each session's recorded `agentPreset`.\n */\nexport interface SessionPersistenceFace {\n /** One header per materialized session (carries `agentPreset` when set). */\n list(signal?: AbortSignal): Promise<Array<{ id: string; agentPreset?: string }>>\n}\n\n/**\n * Structural face over the DSH `permissionPresets` service (a subset of\n * dsh-permission-presets). It owns the deployment's permission presets — each\n * bundles a sandbox mode + approval policy (e.g. `workspace-write` = write\n * inside the workspace with approval, `danger-full-access` = no prompts). The\n * agent-modes route exposes the option list to the TW picker, and agent-create\n * applies the chosen preset to the new session's log via `set`.\n */\nexport interface PermissionPresetsFace {\n /** Every switchable preset name, in declaration order. */\n readonly names: readonly string[]\n /** The preset currently selected as the default for new sessions. */\n readonly defaultPreset: string\n /** Build the client option ({ value, name, description? }) for one preset. */\n optionOf(name: string): { value: string; name: string; description?: string }\n /** Record a preset switch on a live session (durable, log-only user intent). */\n set(session: unknown, name: string): void\n}\n\n/**\n * Structural face over the DSH `sessions` in-memory store (a subset of\n * dsh-session). Only `get` is needed: after `sessionController.create`\n * resolves, the new session is already materialized here, so agent-create can\n * hand it to `permissionPresets.set`.\n */\nexport interface SessionsFace {\n get(id: string): unknown\n}\n\n/** Effective UI flags returned by /status (mirror index.ts). */\nexport interface UiDefaultsPublic {\n showQuickNote: boolean\n /** 聊天输入框上方的「快速笔记」快捷按钮(conversation.input.dock 槽位)。 */\n showQuickNoteDock: boolean\n /** 点击「快速笔记」后的打开方式:native=直达 TW 原生编辑器;card=Markdown 卡片。 */\n quickNoteMode: 'native' | 'card'\n /** 左侧侧边栏 TW 入口的显示名称。 */\n sidebarLabel: string\n showPanelStatus: boolean\n showSyncButton: boolean\n /** 嵌入式 TW 是否跟随 DSH 深浅主题(false 时客户端停止 palette 同步)。 */\n followDshTheme: boolean\n /** DSH 暗色时 TW 使用的 palette tiddler 标题。 */\n darkPalette: string\n /** 会话顶部「知识库」Tab 的显示名称(默认「知识库」)。 */\n tabLabel: string\n /** 是否在会话顶部显示「知识库」Tab(会话相关 wiki 汇总,默认 true)。 */\n showSessionTab: boolean\n /** 是否在 DSH 右侧边栏提供 TiddlyWiki 入口/Tab(默认 true)。 */\n showRightbarTab: boolean\n}\n\nexport interface RouteDeps {\n server: WikiServer\n getClient: () => TiddlyWebClient | undefined\n git: GitFace\n autoCommit: () => void\n noteDefaults: () => { tag: string }\n uiDefaults: () => UiDefaultsPublic\n getWikiPath: () => string\n /** Optional DSH sessionController service (agent-send routes only); resolved\n * lazily per request because it may register after webServer appears. */\n getSessionController: () => SessionControllerFace | undefined\n /** Optional DSH workspaceRegistry service (agent-create route); resolves a\n * cwd to a real Workspace so new sessions land inside it instead of the\n * ungrouped bucket. */\n getWorkspaceRegistry: () => WorkspaceRegistryFace | undefined\n /** Optional DSH agentPresets service (agent-modes route): the deployment's\n * \"工作模式\" roster. Resolved lazily per request like sessionController. */\n getAgentPresets: () => AgentPresetsFace | undefined\n /** Optional DSH sessionPersistence service (agent-sessions route): attaches\n * each session's recorded agentPreset so the picker can badge it. */\n getSessionPersistence: () => SessionPersistenceFace | undefined\n /** Optional DSH permissionPresets service (agent-modes permissions list +\n * agent-create applies the chosen permission preset to the new session). */\n getPermissionPresets: () => PermissionPresetsFace | undefined\n /** Optional DSH `sessions` in-memory store (agent-create hands the created\n * live session to permissionPresets.set). */\n getSessions: () => SessionsFace | undefined\n /** Optional DSH sessionQuery service (session-summary route): reads a\n * session's complete event log + descendant tree to decide which wiki notes\n * belong to this conversation. Resolved lazily like the other services. */\n getSessionQuery: () => SessionQueryFace | undefined\n /** Whether the one-click send-to-agent feature is enabled (config switch). */\n sendToAgentEnabled: () => boolean\n /** Optional shared token that must match `x-send-to-agent-token` when set. */\n sendToAgentToken: () => string\n}\n\n/** Header names forwarded to the upstream TW service by the proxy routes. */\nconst FORWARD_HEADER_NAMES = [\n 'accept', 'accept-encoding', 'content-type', 'cookie', 'authorization',\n 'if-none-match', 'if-modified-since', 'origin', 'referer', 'user-agent',\n] as const\n\n/** Copy a safe, string-valued subset of the request headers upstream. */\nfunction forwardHeaders(headers: IncomingHttpHeaders): Record<string, string> {\n const out: Record<string, string> = {}\n for (const name of FORWARD_HEADER_NAMES) {\n // Index through the string index signature so known header names (typed\n // `string`) do not hide the `string[]` repeat case via their specific\n // property declarations.\n const value: string | string[] | undefined = headers[name as string]\n if (typeof value === 'string') out[name] = value\n else if (Array.isArray(value) && value.length > 0) out[name] = value.join(', ')\n }\n return out\n}\n\n/**\n * Extensions that the browser would execute ON THE DSH ORIGIN: `/upload`\n * writes into `wiki/files/`, which TW's core server serves back through the\n * same-origin `/tw` proxy with an extension-derived Content-Type. An uploaded\n * `.html`/`.svg` would therefore be same-origin script (able to read\n * `/admin/state` and write the wiki), i.e. self-XSS with a persistence layer.\n * Documents/images/archives stay allowed; only the executable-by-browser set is\n * refused (v0.19.0).\n */\nconst DANGEROUS_UPLOAD_EXTENSIONS = new Set([\n '.html', '.htm', '.xhtml', '.shtml', '.hta', '.svg', '.xml', '.xsl', '.xslt',\n '.js', '.mjs', '.cjs', '.swf', '.htc',\n])\n\n/** Windows device names: `NUL.txt` cannot be created and makes the route 500. */\nconst WINDOWS_RESERVED_NAMES = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\\..*)?$/i\n\n/**\n * Sanitize an uploaded filename into a safe bare name (no path separators,\n * no `..`, no control characters). Returns '' when nothing usable remains.\n */\nfunction sanitizeUploadName(input: unknown): string {\n if (typeof input !== 'string') return ''\n let name = basename(input.trim().replace(/[\\\\/]+/g, '/'))\n .replace(/[\\u0000-\\u001f\\u007f]/g, '')\n .replace(/[<>:\"|?*]/g, '_')\n .replace(/^\\.+/, '')\n .trim()\n if (name.length === 0 || name === '.' || name === '..') return ''\n // Windows stores a trailing dot/space verbatim but never strips it, and\n // `extname('evil.html.')` is just `.` — so the extension denylist was\n // bypassable with `evil.html.` (v0.19.3). Canonicalize before the check.\n name = name.replace(/[. ]+$/, '')\n if (name.length === 0) return ''\n // `CON`, `NUL`, `COM1…` are not creatable on Windows — prefix instead of 500.\n if (WINDOWS_RESERVED_NAMES.test(name)) name = `_${name}`\n if (name.length > 160) name = name.slice(0, 160)\n return name\n}\n\n/**\n * Strip credentials embedded in a git remote URL (`https://user:token@host/x`)\n * before it reaches an unauthenticated HTTP response (v0.19.0). The token in a\n * remote URL is a real secret and `/status` is reachable without credentials.\n */\nexport function redactRemoteUrl(remote: string): string {\n return remote.replace(/\\/\\/[^/@\\s]+@/g, '//***@')\n}\n\n/**\n * Redact secrets that can appear in the TW child's captured stdout/stderr log\n * (the spawn line carries `password=…`). Belt-and-braces on top of the\n * redaction in wiki.ts, because `/status` is unauthenticated.\n */\nexport function redactLogLines(logs: readonly string[]): string[] {\n return logs.map((line) => line\n .replace(/(password=)\\S+/gi, '$1***')\n .replace(/(authorization:\\s*basic\\s+)[A-Za-z0-9+/=]+/gi, '$1***'))\n}\n\nfunction pad(n: number): string {\n return n < 10 ? `0${n}` : String(n)\n}\n\n/** Max `limit` accepted by the list routes (bounded payloads, v0.19.4). */\nconst MAX_LIST_LIMIT = 200\n\n/** Max `limit` accepted by `/tags` — the tag list is a small wrapper around one\n * full listing, so a larger cap is fine while still bounding the payload. */\nconst MAX_TAGS_LIMIT = 500\n\n/** Clamp a `limit` query param. `fallback` covers absent/unparsable values. */\nfunction readLimit(url: URL, fallback: number, max = MAX_LIST_LIMIT): number {\n const raw = Number(url.searchParams.get('limit') ?? fallback)\n return Number.isFinite(raw) ? Math.max(1, Math.min(Math.floor(raw), max)) : fallback\n}\n\n/** Optional `limit` query param: `undefined` when absent/unparsable (= no cap). */\nfunction readOptionalLimit(url: URL, max: number): number | undefined {\n const raw = url.searchParams.get('limit')\n if (raw === null || raw.trim().length === 0) return undefined\n const parsed = Number(raw)\n if (!Number.isFinite(parsed)) return undefined\n return Math.max(1, Math.min(Math.floor(parsed), max))\n}\n\n/** Default note title: `YYYY-MM-DD HH:mm` (design doc D6). */\nfunction timestampTitle(date = new Date()): string {\n return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`\n}\n\n/**\n * Open a tiddler in TW's NATIVE editor: save the tiddler (when text is\n * non-empty) as Markdown, reuse or create a DRAFT tiddler carrying\n * `draft.of`/`draft.title` (TW's story view renders drafts with the\n * EditTemplate — list.js: `isDraft && editTemplate`), and return the draft\n * title so the client can navigate the panel iframe to `#<draftTitle>`.\n * The draft carries the same `text/markdown` type as the note so saving it in\n * TW keeps Markdown (a draft without a matching type would overwrite the\n * note's type back to plain wiki text).\n */\nexport async function openInTwEditor(\n client: TiddlyWebClient,\n title: string,\n text: string,\n tags: string[] | undefined,\n options: { defaultTags?: string[]; expectedModified?: string; expectedRevision?: string | number; force?: boolean } = {},\n): Promise<{ title: string; draftTitle: string }> {\n // One read drives everything: the write base (tags/custom fields/type), the\n // conflict check, and — when the body carried no text — the draft content.\n const existing = await client.get(title)\n assertNoConflict(title, existing, options)\n if (text.trim().length > 0) {\n // PRESERVE, do not blind-replace (v0.19.1): the old code PUT\n // `{title, text, tags, type}` with no read, wiping the note's custom fields\n // and (because `tags` defaulted to the note tag) its tags too.\n const { tiddler } = buildWriteTiddler(title, text, {\n existing,\n tags,\n defaultTags: options.defaultTags,\n agentTag: false,\n })\n await client.put(tiddler)\n }\n // Draft content: the provided text, else the existing tiddler's content.\n const draftText = text.trim().length > 0 ? text : (existing?.text ?? '')\n // Draft TYPE (v0.19.5): must match the note's real content type. Hardcoding\n // `text/markdown` meant that editing a wikitext note through the quick-note\n // surface downgraded it — TW's save copies the draft's fields back onto the\n // original, so `fields.type` flipped to markdown and the body started\n // rendering as source (`!` headings, `<$list>`, `[[links]]` all broke).\n const draftType = typeof existing?.type === 'string' && existing.type.length > 0 ? existing.type : NOTE_TYPE\n // Draft lookup. The canonical TW name is probed with a single GET first — the\n // old code always pulled the ENTIRE listing (megabytes on a big wiki) just to\n // find a draft; the listing stays as the fallback for a differently-named one.\n //\n // NEVER CLOBBER AN EXISTING DRAFT (v0.20.0): the v0.19.5 refactor inverted\n // this branch — when the canonical `Draft of \"X\"` already existed it wrote\n // straight into it, destroying whatever the user had typed in TW's editor,\n // and when it found a differently-named draft it discarded that title and\n // minted a fresh timestamped one (leaving an orphan). The pre-refactor code\n // was `free ? canonical : canonical+timestamp`; that is restored below, plus\n // reuse of any existing draft title found in the fallback listing.\n const canonical = `Draft of \"${title}\"`\n let draftTitle: string | undefined\n let canonicalFree: boolean | undefined\n try {\n canonicalFree = (await client.get(canonical)) === undefined\n } catch {\n canonicalFree = undefined\n }\n if (canonicalFree === true) {\n // Free canonical name → use it, so TW's own \"save draft\" bookkeeping lines up.\n draftTitle = canonical\n } else {\n // Canonical draft exists (or the probe failed): reuse an existing draft of\n // this note when there is one, otherwise take a timestamped name so the\n // existing draft is preserved.\n try {\n const items = await client.list(undefined, false)\n for (const item of items) {\n if (item['draft.of'] === title && typeof item.title === 'string') {\n draftTitle = item.title\n break\n }\n }\n } catch {\n /* fall back to a fresh draft */\n }\n if (draftTitle === undefined) draftTitle = `${canonical} ${Date.now()}`\n }\n await client.put({ title: draftTitle, text: draftText, 'draft.of': title, 'draft.title': title, type: draftType })\n return { title, draftTitle }\n}\n\n/**\n * Resolve note tags from the request body: `tags` array wins, then the legacy\n * single `tag` string. Returns **undefined** when the body asks for nothing —\n * the caller (`buildWriteTiddler`) then preserves the existing note's tags\n * (v0.19.1 data safety) or falls back to the configured default for new notes.\n * The old version always returned `[defaultTag]`, so re-saving an existing note\n * under its own title silently replaced its tags with the default.\n */\nfunction resolveTags(body: { tag?: unknown; tags?: unknown }): string[] | undefined {\n if (Array.isArray(body.tags)) {\n const tags = body.tags.filter((t): t is string => typeof t === 'string' && t.trim().length > 0).map((t) => t.trim())\n if (tags.length > 0) return tags\n }\n if (typeof body.tag === 'string' && body.tag.trim().length > 0) {\n return body.tag.trim().split(/\\s+/).filter(Boolean)\n }\n return undefined\n}\n\n/** Body fields accepted by the note routes for optimistic concurrency. */\nfunction conflictTokens(body: { expectedModified?: unknown; expectedRevision?: unknown; force?: unknown }): {\n expectedModified?: string\n expectedRevision?: string | number\n force?: boolean\n} {\n return {\n ...(typeof body.expectedModified === 'string' && body.expectedModified.length > 0 ? { expectedModified: body.expectedModified } : {}),\n ...(typeof body.expectedRevision === 'string' || typeof body.expectedRevision === 'number'\n ? { expectedRevision: body.expectedRevision }\n : {}),\n ...(body.force === true ? { force: true } : {}),\n }\n}\n\nexport function registerRoutes(ctx: { webServer: WebServerFace }, deps: RouteDeps): () => void {\n /**\n * `/status` is polled by the GUI (30s), every mounted TW frame and the FAB,\n * and each call shells out to up to five `git` processes — so the git summary\n * is cached for a couple of seconds. Any route that mutates the repo\n * invalidates it so a sync/upload is reflected immediately.\n */\n const GIT_STATUS_TTL_MS = 2_000\n /** Cached git-status PROMISE (not value): a burst of concurrent /status polls\n * then shares ONE probe instead of each spawning up to five git processes\n * (v0.19.0 — value-caching still let every concurrent miss run its own). */\n let gitStatusCache: { at: number; value: Promise<GitStatusViewPublic | null> } | undefined\n const invalidateGitStatus = (): void => { gitStatusCache = undefined }\n const cachedGitStatus = (): Promise<GitStatusViewPublic | null> => {\n if (gitStatusCache !== undefined && Date.now() - gitStatusCache.at < GIT_STATUS_TTL_MS) return gitStatusCache.value\n const pending = (async (): Promise<GitStatusViewPublic | null> => {\n try {\n const view = await deps.git.status(deps.getWikiPath())\n // The remote URL can carry a PAT (`https://user:token@…`) and /status is\n // unauthenticated — never hand the credential to the browser.\n return { ...view, remote: redactRemoteUrl(typeof view.remote === 'string' ? view.remote : '') }\n } catch {\n return null\n }\n })()\n gitStatusCache = { at: Date.now(), value: pending }\n return pending\n }\n\n /**\n * Serialize the heavyweight mutating routes (restart / sync). A burst of\n * concurrent calls used to stack pull/restart operations on top of each\n * other (and a restart racing a restart is what left orphan TW children).\n * A second concurrent caller gets 429 instead of piling on (v0.19.0).\n */\n let mutationInFlight: string | undefined\n const beginMutation = (label: string): boolean => {\n if (mutationInFlight !== undefined) return false\n mutationInFlight = label\n return true\n }\n const endMutation = (): void => { mutationInFlight = undefined }\n\n /**\n * Titles the browser-facing TW surfaces must never serve (v0.19.3 / v0.20.0).\n *\n * `$:/plugins/dsh-tiddlywiki/config` holds the shared tokens and the git\n * remote (possibly with a PAT); `/tw` and `/api` forward ANY path to the\n * loopback TW child, whose TiddlyWeb REST answers for `$:/…` titles — a\n * route-level guard on `/get` was therefore trivially bypassed by\n * `GET /dsh-tiddlywiki/tw/recipes/default/tiddlers/%24%3A%2Fplugins%2F…`\n * (verified). The whole plugin namespace is blocked: nothing under it is\n * needed by the TW frontend, and the host itself talks to TW directly.\n *\n * v0.20.0: the SAME predicate now also guards `POST /render`, which had been\n * left open — TW's `/render` route renders ANY tiddler by title, so\n * `{\"title\":\"$:/plugins/dsh-tiddlywiki/config\"}` returned the raw config\n * (tokens + PAT in plain text inside `<pre><code>`, where the fragment\n * sanitizer keeps it). Verified end-to-end against a scratch wiki before the\n * fix. Every route that turns a caller-supplied title into TW output must use\n * `isBlockedProxyTitle`.\n */\n const BLOCKED_PROXY_TITLE_PREFIXES = ['$:/plugins/dsh-tiddlywiki/']\n\n /** True when a caller-supplied tiddler title addresses the secret namespace. */\n const isBlockedProxyTitle = (title: string): boolean =>\n BLOCKED_PROXY_TITLE_PREFIXES.some((prefix) => title.startsWith(prefix))\n\n /** True when a proxied pathname addresses a blocked (secret-bearing) tiddler. */\n const isBlockedProxyPath = (pathname: string): boolean => {\n const marker = '/tiddlers/'\n const at = pathname.indexOf(marker)\n if (at < 0) return false\n const raw = pathname.slice(at + marker.length)\n let title = raw\n try {\n title = decodeURIComponent(raw)\n } catch { /* malformed encoding: check the raw form */ }\n return isBlockedProxyTitle(title)\n }\n\n const handleStatus = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n if (rejectNonRead(req, res)) return\n const view = deps.server.status()\n const gitSummary = await cachedGitStatus()\n json(res, {\n ok: true,\n ...view,\n // Child-process logs are served to an unauthenticated caller: never leak\n // the spawn line's `password=…` (or a forwarded Authorization header).\n logs: redactLogLines(view.logs),\n twProxy: TW_PROXY_PATH,\n git: gitSummary,\n note: { tag: deps.noteDefaults().tag },\n ui: deps.uiDefaults(),\n })\n }\n\n /**\n * POST /dsh-tiddlywiki/session/summary — 生成当前会话的 wiki 汇总页(「知识库」\n * Tab 的后端)。body `{ session: <会话ID> }`;后端用 sessionQuery 读本会话(含\n * 后代 subagent)的完整事件日志,按「产生/读取/检索」收集 tiddlywiki_* 笔记,\n * 查询每篇当前状态,组装 TW wikitext 写入 `$:/temp/dsh/session-summary/<会话ID>`\n * (volatile:不落盘、不进 git),返回生成的 tiddler title 供前端 iframe 打开。\n */\n const handleSessionSummary = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n let body: { session?: unknown } = {}\n try {\n body = JSON.parse(await readBody(req)) as { session?: unknown }\n } catch {\n /* malformed body → session check below rejects */\n }\n const session = typeof body.session === 'string' && body.session.trim().length > 0 ? body.session.trim() : ''\n if (session.length === 0) {\n json(res, { ok: false, error: 'session is required' }, 400)\n return\n }\n // The id becomes part of a tiddler title (`$:/temp/dsh/session-summary/<id>`)\n // and is echoed into the summary wikitext — keep it to a safe charset\n // instead of trusting the request body (v0.19.3).\n if (!/^[A-Za-z0-9._:-]{1,120}$/.test(session)) {\n json(res, { ok: false, error: 'session id has an unsupported format' }, 400)\n return\n }\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n const sq = deps.getSessionQuery()\n if (sq === undefined) {\n json(res, { ok: false, error: 'session query service unavailable' }, 503)\n return\n }\n const result = await writeSessionSummary(client, sq, session)\n json(res, { ok: true, ...result, twUrl: TW_PROXY_PATH })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n /**\n * GET /dsh-tiddlywiki/agent/sessions — visible ordinary sessions for the TW\n * one-click picker (excludes subagent sessions, activity-descending). Each\n * item also carries its recorded `agentPreset` (工作模式), when known, so the\n * picker can badge existing sessions — read from the lightweight persistence\n * header list, never a full log parse.\n */\n const handleAgentSessions = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectNonRead(req, res)) return\n // Same guard as the sibling /agent/* routes: a deployment that protects\n // send-to-agent with a token must not leak the session roster to an\n // unauthenticated caller (v0.19.0 — this route used to be the odd one out).\n if (!guardSendToAgent(req, res)) return\n const sc = deps.getSessionController()\n if (sc === undefined) {\n json(res, { ok: false, error: 'session service unavailable' }, 503)\n return\n }\n const list = await sc.list({}, AbortSignal.timeout(10_000))\n // sessionId → agentPreset, from the durable header list (degrade silently).\n const presetById: Record<string, string> = {}\n const pers = deps.getSessionPersistence()\n if (pers !== undefined) {\n try {\n const headers = await pers.list(AbortSignal.timeout(5_000))\n for (const h of headers) {\n if (typeof h.agentPreset === 'string' && h.agentPreset.length > 0) presetById[h.id] = h.agentPreset\n }\n } catch {\n /* header list unavailable → no mode badges, picker still works */\n }\n }\n const items = (list.items ?? [])\n .filter((s) => s.parentSessionId === undefined)\n .map((s) => ({\n sessionId: s.sessionId,\n cwd: s.cwd ?? null,\n running: !!s.running,\n blank: !!s.blank,\n updatedAt: s.updatedAt ?? 0,\n agentPreset: presetById[s.sessionId] ?? null,\n }))\n .sort((a, b) => b.updatedAt - a.updatedAt)\n json(res, { ok: true, items })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n /**\n * Shared gate for the TW-side send-to-agent routes: feature switch\n * (`ui.sendToAgent.enabled`) then, when a shared token is configured, the\n * `x-send-to-agent-token` header must match. Returns false after writing the\n * error response — the caller just does `if (!guard(...)) return`.\n */\n const guardSendToAgent = (req: IncomingMessage, res: ServerResponse): boolean => {\n if (!deps.sendToAgentEnabled()) {\n json(res, { ok: false, error: 'send-to-agent is disabled' }, 403)\n return false\n }\n const token = deps.sendToAgentToken().trim()\n if (token.length === 0) return true\n const got = req.headers['x-send-to-agent-token']\n const value = typeof got === 'string' ? got : Array.isArray(got) ? got[0] ?? '' : ''\n // Constant-time comparison (hash-then-compare): `===` leaks the token's\n // length and matched-prefix timing to a caller who can probe the route.\n if (safeTokenEqual(value, token)) return true\n json(res, { ok: false, error: 'unauthorized' }, 401)\n return false\n }\n\n /**\n * GET /dsh-tiddlywiki/agent/modes — available \"工作模式\" (Agent presets) for\n * the TW picker: id/name/description per preset plus the deployment default.\n * Guards mirror the other agent routes (feature switch + optional token).\n */\n const handleAgentModes = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectNonRead(req, res)) return\n if (!guardSendToAgent(req, res)) return\n const ap = deps.getAgentPresets()\n if (ap === undefined) {\n json(res, { ok: false, error: 'agent presets service unavailable' }, 503)\n return\n }\n const presets = await ap.list()\n let defaultId: string | undefined\n try {\n defaultId = (await ap.resolve())?.id\n } catch {\n defaultId = undefined\n }\n // The picker also needs the permission-preset roster (a \"权限\" selector\n // for newly created sessions). Best-effort: when the permissionPresets\n // service is not mounted (older host), `permissions` is null and the\n // picker simply hides the selector — modes still work.\n let permissions: { defaultId: string | null; items: Array<{ value: string; name: string; description?: string }> } | null = null\n const pp = deps.getPermissionPresets()\n if (pp !== undefined) {\n try {\n permissions = {\n defaultId: pp.defaultPreset ?? null,\n items: pp.names.map((n) => pp.optionOf(n)),\n }\n } catch {\n permissions = null\n }\n }\n json(res, {\n ok: true,\n defaultId: defaultId ?? null,\n items: presets.map((p) => ({\n id: p.id,\n name: p.name ?? p.id,\n description: p.description ?? '',\n trust: p.trust ?? 'user',\n broken: p.broken ?? null,\n isDefault: p.id === defaultId,\n })),\n permissions,\n })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n /**\n * POST /dsh-tiddlywiki/agent/send — deliver a note to one agent session as a\n * queued user message (sessionController.prompt, the same API the GUI chat\n * input uses). Guards: feature switch, optional shared token, body shape.\n */\n const handleAgentSend = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n if (!guardSendToAgent(req, res)) return\n const body = JSON.parse(await readBody(req)) as { sessionId?: unknown; text?: unknown }\n const sessionId = typeof body.sessionId === 'string' && body.sessionId.trim().length > 0 ? body.sessionId.trim() : ''\n const text = typeof body.text === 'string' && body.text.trim().length > 0 ? body.text.trim() : ''\n if (sessionId.length === 0 || text.length === 0) {\n json(res, { ok: false, error: 'sessionId and text are required' }, 400)\n return\n }\n const sc = deps.getSessionController()\n if (sc === undefined) {\n json(res, { ok: false, error: 'session service unavailable' }, 503)\n return\n }\n const requestId = `tw-send-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`\n const accepted = await sc.prompt(\n { requestId, sessionId, mode: 'queue', content: [{ type: 'text', text }] },\n AbortSignal.timeout(20_000),\n )\n // A refusal (session gone / busy) must not be reported as success.\n if (accepted !== undefined && accepted.accepted === false) {\n json(res, { ok: false, error: '会话未接受该消息(可能已结束或正忙)', requestId, sessionId }, 409)\n return\n }\n json(res, { ok: true, requestId, sessionId })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n /**\n * POST /dsh-tiddlywiki/agent/create — create (or adopt) one ordinary session\n * inside a real DSH workspace resolved from the requested path. The picker\n * uses it for \"new workspace / new session\": the directory is materialised so\n * a brand-new workspace actually exists on disk, the path is resolved to its\n * (idempotent) Workspace, and the session is created with `workspaceId` so it\n * lands under that workspace in the sidebar. Creating with bare `cwd` instead\n * would leave the session in the ungrouped bucket even when its working\n * directory matches an existing workspace path.\n *\n * Optional `mode` names the \"工作模式\" (an Agent preset id, e.g. from\n * /agent/modes); it is forwarded to `sessionController.create(agentPreset)`\n * so the new session launches under that preset. Omitted → deployment default.\n *\n * Optional `permission` names a \"权限\" preset (e.g. from /agent/modes'\n * `permissions` roster). After the session is created it is applied to the\n * live session's log via `permissionPresets.set` (durable knob events:\n * `permission/preset`, `sandbox/mode`, `approval/policy`), overriding the\n * deployment default pinned at creation. Omitted → keep the default.\n */\n const handleAgentCreate = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n if (!guardSendToAgent(req, res)) return\n const body = JSON.parse(await readBody(req)) as { cwd?: unknown; mode?: unknown; permission?: unknown }\n const cwd = typeof body.cwd === 'string' ? body.cwd.trim() : ''\n const mode = typeof body.mode === 'string' && body.mode.trim().length > 0 ? body.mode.trim() : undefined\n const permission = typeof body.permission === 'string' && body.permission.trim().length > 0 ? body.permission.trim() : undefined\n // Validate the permission preset BEFORE creating the session (fail fast,\n // so a bad name never leaves an orphaned session behind).\n const pp = deps.getPermissionPresets()\n if (permission !== undefined) {\n if (pp === undefined) {\n json(res, { ok: false, error: 'permission selected but the permission-presets service is unavailable' }, 503)\n return\n }\n if (!pp.names.includes(permission)) {\n json(res, { ok: false, error: `unknown permission preset \"${permission}\" (available: ${pp.names.join(', ')})` }, 400)\n return\n }\n }\n const sc = deps.getSessionController()\n if (sc === undefined) {\n json(res, { ok: false, error: 'session service unavailable' }, 503)\n return\n }\n // Validate the agent preset (工作模式) BEFORE any side effect (v0.19.3):\n // the old code created the cwd directory + workspace first, so an unknown\n // mode left an orphaned directory behind a 500 from `sc.create`.\n if (mode !== undefined) {\n const ap = deps.getAgentPresets()\n if (ap === undefined) {\n json(res, { ok: false, error: 'agent presets service unavailable' }, 503)\n return\n }\n try {\n const presets = await ap.list()\n if (!presets.some((preset) => preset.id === mode)) {\n json(res, { ok: false, error: `unknown agent preset \"${mode}\" (available: ${presets.map((preset) => preset.id).join(', ')})` }, 400)\n return\n }\n } catch (err) {\n json(res, { ok: false, error: `cannot validate agent preset: ${err instanceof Error ? err.message : String(err)}` }, 503)\n return\n }\n }\n const ws = deps.getWorkspaceRegistry()\n if (cwd.length > 0) {\n // Only absolute paths, and never clobber an existing non-directory:\n // `mkdir -p` on a file path throws ENOTDIR and used to surface as a 500.\n if (!isAbsolute(cwd)) {\n json(res, { ok: false, error: 'cwd must be an absolute path' }, 400)\n return\n }\n try {\n const info = await stat(cwd)\n if (!info.isDirectory()) {\n json(res, { ok: false, error: 'cwd exists but is not a directory' }, 400)\n return\n }\n } catch {\n await mkdir(cwd, { recursive: true })\n }\n }\n let created: { sessionId: string }\n let workspaceId: string | undefined\n if (cwd.length > 0 && ws !== undefined) {\n const workspace = await ws.create(cwd)\n workspaceId = workspace.id\n created = await sc.create({ workspaceId, agentPreset: mode })\n } else {\n created = await sc.create({ cwd: cwd.length > 0 ? cwd : undefined, agentPreset: mode })\n }\n // Apply the chosen permission preset to the just-created live session\n // (best-effort — the session is already created either way).\n let permissionApplied = false\n if (permission !== undefined) {\n const sessionsSvc = deps.getSessions()\n if (sessionsSvc !== undefined) {\n try {\n const session = sessionsSvc.get(created.sessionId)\n if (session !== undefined) {\n pp?.set(session, permission)\n permissionApplied = true\n }\n } catch {\n /* permission is an optional convenience; never fail the create */\n }\n }\n }\n json(res, {\n ok: true,\n sessionId: created.sessionId,\n cwd: cwd || null,\n workspaceId: workspaceId ?? null,\n mode: mode ?? null,\n permission: permission ?? null,\n permissionApplied,\n })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n const handleNote = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n const body = JSON.parse(await readBody(req)) as { title?: unknown; tag?: unknown; tags?: unknown; text?: unknown; expectedModified?: unknown; expectedRevision?: unknown; force?: unknown }\n const text = typeof body.text === 'string' && body.text.trim().length > 0 ? body.text.trim() : null\n if (text === null) {\n json(res, { ok: false, error: 'text is required' }, 400)\n return\n }\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n const title = typeof body.title === 'string' && body.title.trim().length > 0 ? body.title.trim() : timestampTitle()\n // System tiddlers ($:/…) are TW internals (theme, config, plugins): the\n // quick-note route is a user path and must not let a request overwrite\n // them. Use the editor / admin surface for that (v0.19.0).\n if (title.startsWith('$:/')) {\n json(res, { ok: false, error: 'title must not be a system tiddler ($:/…)' }, 400)\n return\n }\n const tags = resolveTags(body)\n // PRESERVE, do not blind-replace (v0.19.1): read first (404 = new note,\n // any other failure propagates), keep the existing tags/custom fields\n // unless the body explicitly provides them, and honour the optimistic\n // concurrency tokens the quick-note card sends for a note it loaded.\n const existing = await client.get(title)\n assertNoConflict(title, existing, conflictTokens(body))\n const { tiddler } = buildWriteTiddler(title, text, {\n existing,\n tags,\n defaultTags: [deps.noteDefaults().tag],\n agentTag: false,\n })\n await client.put(tiddler)\n deps.autoCommit()\n invalidateGitStatus()\n const finalTags = tiddler.tags ?? []\n json(res, { ok: true, title, tag: finalTags.join(' '), tags: finalTags, text, type: typeof tiddler.type === 'string' ? tiddler.type : NOTE_TYPE })\n } catch (err) {\n if (err instanceof WriteConflictError) {\n json(res, { ok: false, error: err.message, conflict: true }, 409)\n return\n }\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n const handleEdit = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n const body = JSON.parse(await readBody(req)) as { title?: unknown; tag?: unknown; tags?: unknown; text?: unknown; expectedModified?: unknown; expectedRevision?: unknown; force?: unknown }\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n const title = typeof body.title === 'string' && body.title.trim().length > 0 ? body.title.trim() : timestampTitle()\n if (title.startsWith('$:/')) {\n json(res, { ok: false, error: 'title must not be a system tiddler ($:/…)' }, 400)\n return\n }\n const text = typeof body.text === 'string' ? body.text : ''\n const result = await openInTwEditor(client, title, text, resolveTags(body), {\n defaultTags: [deps.noteDefaults().tag],\n ...conflictTokens(body),\n })\n deps.autoCommit()\n invalidateGitStatus()\n json(res, { ok: true, ...result, twUrl: TW_PROXY_PATH })\n } catch (err) {\n if (err instanceof WriteConflictError) {\n json(res, { ok: false, error: err.message, conflict: true }, 409)\n return\n }\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n /** Distinct non-system tags for the quick-note tag autocomplete. The flat\n * `tags` array feeds note-widget's chip autocomplete; the parallel `items`\n * (tag → tiddler count) feeds the reply-stream `tiddlywiki_list_tags` tool\n * card, so both consumers share one endpoint.\n *\n * Query params (v0.19.4): `limit` caps the payload (absent = every tag, so\n * the autocomplete keeps its full vocabulary), `sort=count` orders by usage\n * (default `alpha`). `tags`/`items` are always the same set in the same\n * order, plus `total`/`truncated` so a capped caller can say \"N of M\". */\n const handleTags = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n if (rejectNonRead(req, res)) return\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n try {\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\n const limit = readOptionalLimit(url, MAX_TAGS_LIMIT)\n const byCount = (url.searchParams.get('sort') ?? 'alpha') === 'count'\n // Text-bearing tiddlers only (server-side filter) — the same counting the\n // `tiddlywiki_list_tags` tool uses, so the card and the model agree.\n const { total, tags: all } = await client.tagStats()\n const ordered = byCount ? all : [...all].sort((a, b) => a.tag.localeCompare(b.tag, 'zh'))\n const picked = limit === undefined ? ordered : ordered.slice(0, limit)\n json(res, {\n ok: true,\n tags: picked.map((t) => t.tag),\n items: picked,\n total,\n truncated: picked.length < total,\n })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n /** Recent non-system tiddlers for the quick-note \"最近\" picker (newest first). */\n const handleRecent = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n if (rejectNonRead(req, res)) return\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n try {\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\n const limit = readLimit(url, 15)\n const items = await client.recent(limit)\n json(res, {\n ok: true,\n limit,\n items: items.map((t) => ({\n title: t.title,\n tags: t.tags ?? [],\n modified: toIsoDateString(t.modified),\n snippet: snippetOf(t.text ?? '', 120),\n })),\n })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n /** Full tiddler for the quick-note \"最近\" picker (load into the editor). */\n const handleGet = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n if (rejectNonRead(req, res)) return\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n try {\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\n const title = url.searchParams.get('title') ?? ''\n if (title.length === 0) {\n json(res, { ok: false, error: 'missing title' }, 400)\n return\n }\n // The plugin's own config tiddler can carry shared tokens (bridge.token /\n // sendToAgent.token) and this route is unauthenticated: serve the note\n // picker but never the plugin's secret-bearing config (v0.19.0).\n if (title.startsWith('$:/plugins/dsh-tiddlywiki/')) {\n json(res, { ok: false, error: 'system tiddler not exposed' }, 403)\n return\n }\n const t = await client.get(title)\n if (t === undefined) {\n json(res, { ok: false, notFound: true, title }, 404)\n return\n }\n // Custom fields are FLATTENED (v0.19.1): a single-tiddler GET nests every\n // non-known field under `fields`, so the old copy-top-level loop shipped\n // `fields: {fields: {q: …}}` — consumers could never read `q`/`due`/\n // `clip-url`. `revision` is surfaced explicitly as the concurrency token\n // the quick-note card echoes back on save.\n const binary = isBinaryType(typeof t.type === 'string' ? t.type : undefined)\n json(res, {\n ok: true,\n title: t.title,\n // Binary attachments carry base64 in `text`: never ship 20MB to the\n // note picker (it only needs metadata) — same rule as tiddlywiki_get.\n text: binary ? '' : (t.text ?? ''),\n binary,\n binaryChars: binary ? (t.text ?? '').length : undefined,\n tags: t.tags ?? [],\n type: t.type ?? 'text/vnd.tiddlywiki',\n modified: toIsoDateString(t.modified),\n revision: t.revision ?? null,\n fields: flattenTiddlerFields(t),\n })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n /**\n * GET /dsh-tiddlywiki/search — keyword search for the reply-stream tool card\n * (mirrors tools.ts tiddlywiki_search: same local substring/tag/since/type\n * matching via the TiddlyWebClient). Returns hit titles/tags/modified/\n * snippets so the card can list clickable wiki links.\n */\n const handleSearch = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n if (rejectNonRead(req, res)) return\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n try {\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\n const query = url.searchParams.get('query') ?? ''\n const tags = url.searchParams.getAll('tags').filter((t) => t.length > 0)\n const tag = url.searchParams.get('tag') ?? undefined\n const since = url.searchParams.get('since') ?? undefined\n const type = url.searchParams.get('type') ?? undefined\n // Custom-field filter (v0.20.0): the agent tool has always accepted\n // field/value, and the reply-stream card echoes the tool's arguments —\n // without these params the card listed UNFILTERED hits, contradicting the\n // model-visible result.\n const field = url.searchParams.get('field') ?? undefined\n const value = url.searchParams.get('value') ?? undefined\n const limit = readLimit(url, 30)\n const { items, total } = await client.search(query, { tags, tag, since, type, field, value, limit })\n json(res, {\n ok: true,\n query,\n tags,\n tag: tag ?? null,\n since: since ?? null,\n type: type ?? null,\n field: field ?? null,\n value: value ?? null,\n limit,\n total,\n items: items.map((t) => ({\n title: t.title,\n tags: t.tags ?? [],\n modified: toIsoDateString(t.modified),\n snippet: snippetOf(t.text ?? '', 120),\n })),\n })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n const handleRestart = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n if (!beginMutation('restart')) {\n json(res, { ok: false, error: '另一个重启/同步正在进行中,请稍候' }, 429)\n return\n }\n try {\n await deps.server.restart()\n } finally {\n endMutation()\n }\n json(res, { ok: true, status: deps.server.status().status })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n /** One-click git sync for the floating button / settings page: pull →\n * commit → push, then return the fresh status. Mirrors the agent tool's\n * `action=sync` (design doc §7 conflict policy — rebase conflict aborts).\n * When the pull actually changed the working tree, the running TW child\n * still holds the old in-memory snapshot — restart it (same port) so the\n * UI reflects the pulled files instead of looking stale. */\n const handleSync = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n if (!beginMutation('sync')) {\n json(res, { ok: false, error: '另一个重启/同步正在进行中,请稍候' }, 429)\n return\n }\n invalidateGitStatus()\n const dir = deps.getWikiPath()\n const status = async (): Promise<GitStatusViewPublic | null> => {\n try { return await deps.git.status(dir) } catch { return null }\n }\n try {\n const pulled = await deps.git.pull(dir)\n if (!pulled.ok) {\n json(res, {\n ok: false,\n action: 'sync',\n message: pulled.message,\n ...(pulled.conflictFiles !== undefined ? { conflictFiles: pulled.conflictFiles } : {}),\n status: await status(),\n }, 409)\n return\n }\n let restarted = false\n let restartError: string | undefined\n if (pulled.changed === true) {\n try {\n // DRAIN THE SYNCER FIRST (v0.19.1, data safety): a REST PUT answers 204\n // while the filesystem syncer still holds the tiddler (~250ms timer).\n // Restarting TW before the flush kills those writes — the restarted\n // server boots from the pre-write snapshot and the note is gone, and\n // the `git commit` right below cannot recover what never hit disk.\n // Seeds/admin/index already used this sentinel; this route did not.\n const flushClient = deps.getClient()\n if (flushClient !== undefined) {\n await flushPendingWrites(flushClient, join(dir, 'tiddlers')).catch(() => undefined)\n }\n await deps.server.restart()\n restarted = true\n } catch (err) {\n restartError = err instanceof Error ? err.message : String(err)\n }\n }\n const committed = await deps.git.commit(dir, `sync ${new Date().toISOString()}`)\n const pushed = await deps.git.push(dir)\n const fresh = await status()\n json(res, {\n ok: pushed.ok,\n action: 'sync',\n message: pushed.ok ? '同步完成' : pushed.message,\n pull: 'ok',\n ...(pulled.changed === true ? { changed: true } : {}),\n restarted,\n ...(restartError !== undefined ? { restartError } : {}),\n commit: committed.message,\n push: pushed.message,\n status: fresh,\n lastSync: new Date().toISOString(),\n }, pushed.ok ? 200 : 502)\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n } finally {\n endMutation()\n }\n }\n\n /**\n * Save an uploaded file into the wiki's `files/` folder (git-tracked; TW's\n * core server serves it at `/files/<name>`, get-file.js — no restart\n * needed). Body is the raw file; the name arrives in `X-Filename`. A\n * collision appends `-1`, `-2`, … so nothing is ever overwritten.\n */\n const handleUpload = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n const buf = await readBodyBuffer(req)\n // Name comes from ?name= (URL-encoded) or the X-Filename header.\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\n const nameParam = url.searchParams.get('name')\n let name = sanitizeUploadName(nameParam ?? '')\n if (name.length === 0 && typeof req.headers['x-filename'] === 'string') {\n let decoded = ''\n try { decoded = decodeURIComponent(req.headers['x-filename'] as string) } catch { decoded = req.headers['x-filename'] as string }\n name = sanitizeUploadName(decoded)\n }\n if (name.length === 0) {\n json(res, { ok: false, error: 'missing or invalid filename' }, 400)\n return\n }\n // Refuse files the browser would EXECUTE on the DSH origin: `/files/*` is\n // served back through the same-origin `/tw` proxy, so an uploaded .html /\n // .svg would be same-origin script with access to the admin routes.\n const extension = extname(name).toLowerCase()\n if (DANGEROUS_UPLOAD_EXTENSIONS.has(extension)) {\n json(res, { ok: false, error: `不允许上传可执行/可脚本化的文件类型:${extension}` }, 400)\n return\n }\n const filesDir = join(deps.getWikiPath(), 'files')\n await mkdir(filesDir, { recursive: true })\n // Collision avoidance with an ATOMIC create (v0.19.3): the old code did\n // `access(candidate)` then `writeFile(candidate)`, so (a) any access error\n // (EACCES/EBUSY, not just ENOENT) was read as \"name free\" and (b) two\n // concurrent uploads could both pick the same name and one silently\n // replaced the other — contradicting the route's own \"nothing is ever\n // overwritten\" contract. `flag: 'wx'` fails with EEXIST instead, and we\n // walk the suffix chain on that error only. Bounded, so a pathological\n // directory cannot spin this loop forever.\n const ext = extname(name)\n const stem = ext.length > 0 ? name.slice(0, -ext.length) : name\n let candidate = ''\n let wrote = false\n for (let i = 0; i <= 10_000 && !wrote; i++) {\n candidate = i === 0 ? name : `${stem}-${i}${ext}`\n try {\n await writeFile(join(filesDir, candidate), buf, { flag: 'wx' })\n wrote = true\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'EEXIST') throw err\n }\n }\n if (!wrote) {\n json(res, { ok: false, error: '同名文件过多,请换一个文件名' }, 409)\n return\n }\n deps.autoCommit()\n invalidateGitStatus()\n json(res, {\n ok: true,\n name: candidate,\n path: `files/${candidate}`,\n // Same-origin proxy URL: the embedded TW editor resolves image links\n // against the DSH origin, so a root-absolute `/files/...` would miss.\n url: `${TW_PROXY_PATH}files/${encodeURIComponent(candidate)}`,\n size: buf.length,\n type: req.headers['content-type'] ?? 'application/octet-stream',\n })\n } catch (err) {\n // Same mapping as every other route (v0.19.5): the old bespoke `/too\n // large/i` test disagreed with `errorStatus`'s `/body too large/i`, so the\n // 413 was decided by whichever wording the thrown error happened to use.\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n /**\n * POST /dsh-tiddlywiki/render — the ONLY render endpoint the GUI uses.\n *\n * Proxies to TW's own `/render` server route (installed by the `render-route`\n * seed) and **sanitizes the fragment before it leaves the host**.\n *\n * WHY (v0.19.1, security): the reply-stream tool card and the session\n * 「知识库」 Tab inject that HTML with `dangerouslySetInnerHTML` inside the DSH\n * page. TW's wikitext/markdown parsers only strip `on*` attributes — measured\n * against the live `/render`: `<iframe src=\"javascript:…\">`,\n * `<a href=\"javascript:…\">` and `<form action=\"javascript:…\">` all pass\n * through, i.e. any note text (agent-written, clipped, imported) could run\n * script on the DSH origin and call the unauthenticated `/dsh-tiddlywiki/*`\n * routes. Sanitizing host-side also protects wikis whose ONE-SHOT render\n * bundle predates this fix (the client can never see raw TW output).\n */\n const handleRender = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n let body: { title?: unknown; text?: unknown; type?: unknown; contextTitle?: unknown; parseAsInline?: unknown } = {}\n try {\n body = JSON.parse(await readBody(req, MAX_PROXY_BODY_BYTES)) as typeof body\n } catch {\n json(res, { ok: false, error: 'invalid JSON body' }, 400)\n return\n }\n const title = typeof body.title === 'string' ? body.title.trim() : ''\n const text = typeof body.text === 'string' ? body.text : undefined\n if (title.length === 0 && text === undefined) {\n json(res, { ok: false, error: 'body must provide \"title\" or \"text\"' }, 400)\n return\n }\n // SECURITY (v0.20.0): TW's /render answers for ANY title, including\n // `$:/plugins/dsh-tiddlywiki/config` (bridge/send-to-agent tokens + the\n // git remote, possibly with a PAT). This is the same secret the `/get`,\n // `/tw` and `/api` guards protect — /render was simply missed. Verified:\n // before this guard the rendered fragment contained both secrets verbatim.\n if (isBlockedProxyTitle(title)) {\n json(res, { ok: false, error: 'system tiddler not exposed' }, 403)\n return\n }\n const request = title.length > 0\n ? { title }\n : {\n text: text as string,\n ...(typeof body.type === 'string' && body.type.length > 0 ? { type: body.type } : {}),\n ...(typeof body.contextTitle === 'string' && body.contextTitle.length > 0 ? { contextTitle: body.contextTitle } : {}),\n ...(body.parseAsInline === true ? { parseAsInline: true } : {}),\n }\n const html = await client.render(request)\n const safe = sanitizeTwFragment(html)\n res.writeHead(200, {\n 'content-type': 'text/html; charset=utf-8',\n 'cache-control': 'no-store',\n 'content-length': Buffer.byteLength(safe, 'utf8'),\n })\n res.end(safe)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n if (/HTTP 404/.test(message)) {\n json(res, { ok: false, notFound: true, error: message }, 404)\n return\n }\n json(res, { ok: false, error: message }, 502)\n }\n }\n\n /** Passthrough /dsh-tiddlywiki/api/<rest> → TW root /<rest>. */\n const handleApiProxy = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n // Explicit method whitelist (v0.19.5): the host webserver dispatches by\n // pathname only, so without it every method (TRACE, or a typo'd verb) was\n // forwarded to the TW child. The set is the TiddlyWeb API surface the TW\n // frontend uses.\n if (rejectCrossSiteWrite(req, res, ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'OPTIONS'])) return\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n try {\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\n const rest = url.pathname.replace(/^\\/dsh-tiddlywiki\\/api/, '') || '/'\n if (isBlockedProxyPath(rest)) {\n json(res, { ok: false, error: 'system tiddler not exposed' }, 403)\n return\n }\n // Share the /tw proxy's header forwarding so `authorization`/`cookie`\n // reach the TW child: in locked-down mode (auth.username configured) the\n // /api passthrough used to 401 on every call because it dropped them.\n const headers: Record<string, string> = forwardHeaders(req.headers)\n const method = (req.method ?? 'GET').toUpperCase()\n // TW's CSRF gate requires X-Requested-With on writes; forward it through.\n if (method === 'PUT' || method === 'DELETE' || method === 'POST') headers['x-requested-with'] = 'TiddlyWiki'\n const init: RequestInit = { method, headers, signal: AbortSignal.timeout(15_000) }\n if (method === 'PUT' || method === 'POST') init.body = await readBody(req, MAX_PROXY_BODY_BYTES)\n const upstream = await fetch(`${deps.server.url}${rest}${url.search}`, init)\n const data = await upstream.text()\n res.writeHead(upstream.status, {\n 'content-type': upstream.headers.get('content-type') ?? 'application/json; charset=utf-8',\n 'cache-control': 'no-store',\n })\n res.end(data)\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 502)\n }\n }\n\n /**\n * SAME-ORIGIN proxy /dsh-tiddlywiki/tw/<rest> → TW root /<rest>. Serves the\n * ENTIRE TW frontend (index HTML, /files/*, the TiddlyWeb API) to the\n * browser through the DSH origin, so the embedded editor works from any\n * host/domain the user reaches DSH on (loopback, LAN, Tailscale, domain,\n * HTTPS). The browser never talks to the loopback TW child directly; DSH\n * does, on the same machine. Binary responses are buffered losslessly\n * (arrayBuffer) — unlike the /api JSON proxy, this route must never .text().\n */\n const handleTwProxy = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n if (rejectCrossSiteWrite(req, res)) return\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\n const rest = url.pathname.replace(new RegExp(`^${TW_PROXY_PREFIX}(?=/|$)`), '') || '/'\n if (isBlockedProxyPath(rest)) {\n json(res, { ok: false, error: 'system tiddler not exposed' }, 403)\n return\n }\n try {\n const method = (req.method ?? 'GET').toUpperCase()\n const headers = forwardHeaders(req.headers)\n // TW's CSRF gate requires X-Requested-With on writes; forward it through.\n if (method === 'PUT' || method === 'DELETE' || method === 'POST') headers['x-requested-with'] = 'TiddlyWiki'\n // Abort the upstream fetch when the CLIENT goes away: without this the TW\n // child kept streaming a large attachment into the DSH process until the\n // 30s timeout fired, long after the browser had cancelled (v0.19.3).\n const abort = new AbortController()\n const timeout = AbortSignal.timeout(30_000)\n const signal = typeof AbortSignal.any === 'function' ? AbortSignal.any([abort.signal, timeout]) : timeout\n const init: RequestInit = { method, headers, signal }\n if (method === 'PUT' || method === 'POST') init.body = await readBodyBuffer(req, MAX_UPLOAD_BYTES)\n // A DELETE with a body would otherwise stay unread on the socket: the\n // upstream fetch goes out, but this request never drains, so the client\n // sits on a half-open connection until the 30s timeout (v0.19.5). The TW\n // frontend does not send DELETE bodies, but the proxy is a generic\n // passthrough — drain whatever is there.\n else if (req.readableEnded === false && (req.headers['content-length'] !== undefined || req.headers['transfer-encoding'] !== undefined)) req.resume()\n const upstream = await fetch(`${deps.server.url}${rest}${url.search}`, init)\n const responseHeaders: Record<string, string> = {\n 'content-type': upstream.headers.get('content-type') ?? 'application/octet-stream',\n 'cache-control': upstream.headers.get('cache-control') ?? 'no-store',\n }\n for (const name of ['etag', 'last-modified', 'content-disposition', 'accept-ranges']) {\n const value = upstream.headers.get(name)\n if (value !== null) responseHeaders[name] = value\n }\n // Locked-down mode (auth.username configured) puts the TW frontend behind\n // HTTP Basic auth: without the challenge header the browser never prompts\n // and the embedded editor would just show a bare 401.\n const challenge = upstream.headers.get('www-authenticate')\n if (challenge !== null) responseHeaders['www-authenticate'] = challenge\n res.writeHead(upstream.status, responseHeaders)\n if (upstream.body === null) {\n res.end()\n return\n }\n // STREAM the body (not `arrayBuffer()`): fetching a large attachment\n // through /tw/files/… used to buffer the whole file in the DSH process\n // (v0.19.0). `content-length` is deliberately NOT forwarded — undici\n // decodes compressed responses, so the upstream length can be stale.\n const body = Readable.fromWeb(upstream.body as unknown as import('node:stream/web').ReadableStream)\n await new Promise<void>((resolveP, rejectP) => {\n body.on('error', rejectP)\n res.on('error', rejectP)\n res.on('close', () => {\n // Client hung up (aborted download / closed tab): stop pulling from the\n // TW child instead of draining it until the timeout.\n try { abort.abort() } catch { /* already aborted */ }\n try { body.destroy() } catch { /* already closed */ }\n resolveP()\n })\n res.on('finish', () => resolveP())\n body.pipe(res)\n })\n } catch (err) {\n if (res.headersSent) {\n res.end()\n return\n }\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 502)\n }\n }\n\n // Every handler goes through guardHandler (v0.19.3): a rejection — including a\n // synchronous throw before the handler's own try, e.g. `new URL(req.url)` —\n // becomes a 413/500 JSON response instead of an unhandled rejection that\n // would exit the dsh web process with the request hanging.\n const disposers = [\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/status`, handler: guardHandler(handleStatus) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/note`, handler: guardHandler(handleNote) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/edit`, handler: guardHandler(handleEdit) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/tags`, handler: guardHandler(handleTags) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/recent`, handler: guardHandler(handleRecent) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/get`, handler: guardHandler(handleGet) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/search`, handler: guardHandler(handleSearch) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/render`, handler: guardHandler(handleRender) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/sync`, handler: guardHandler(handleSync) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/upload`, handler: guardHandler(handleUpload) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/restart`, handler: guardHandler(handleRestart) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/session/summary`, handler: guardHandler(handleSessionSummary) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/agent/sessions`, handler: guardHandler(handleAgentSessions) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/agent/modes`, handler: guardHandler(handleAgentModes) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/agent/send`, handler: guardHandler(handleAgentSend) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/agent/create`, handler: guardHandler(handleAgentCreate) }),\n ctx.webServer.register({ kind: 'prefix', path: `${ROUTE_PREFIX}/api`, handler: guardHandler(handleApiProxy) }),\n ctx.webServer.register({ kind: 'prefix', path: `${TW_PROXY_PREFIX}`, handler: guardHandler(handleTwProxy) }),\n ]\n return () => {\n for (const dispose of disposers) dispose()\n }\n}\n\n/** Public shape of the git status summary sent to the panel.\n * Single declaration lives in git.ts (v0.20.0 — this file used to repeat it). */\nexport type GitStatusViewPublic = GitStatusView\n","/**\n * Admin surface for the plugin settings page (design doc §13, config panel).\n *\n * - dynamic plugin/theme management: enumerate the bundled catalog from the\n * installed tiddlywiki package, read/write the wiki's `tiddlywiki.info`\n * plugins/themes arrays, then restart the TW child so the change applies;\n * - extensible config: the settings page reads/writes a config tiddler\n * ($:/plugins/dsh-tiddlywiki/config, a JSON string) that overlays the\n * cordis `config:` block — future config fields just extend the shape.\n *\n * Routes (all under ROUTE_PREFIX/admin, JSON):\n * GET /admin/state current info + catalog + effective config + status\n * POST /admin/info { plugins?, themes? } → write info → restart TW\n * POST /admin/config { ...patch } → write config tiddler\n * POST /admin/restart restart the TW child\n *\n * @module dsh-tiddlywiki/host/admin\n */\nimport { createRequire } from 'node:module'\nimport { readFile, writeFile, readdir, rename } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { TiddlyWebClient } from './tw-api.ts'\nimport type { WikiServer } from './wiki.ts'\nimport { ROUTE_PREFIX, redactLogLines, redactRemoteUrl, type WebServerFace } from './routes.ts'\nimport { type ConfigStore, type PluginConfigShape } from './config.ts'\nimport { readBody, json, guardHandler, errorStatus, rejectCrossSiteWrite, rejectNonRead } from './http.ts'\nimport { waitForFileWrite, needsRestartAfterSeeds, flushPendingWrites } from './seeds.ts'\nimport { RENDER_PLUGIN_FILE } from './seed-render.ts'\nimport type { WikiLocationInfo } from './wiki-location.ts'\nimport type { WikiSwitchResult } from './wiki-switch.ts'\nimport { GitFace } from './git.ts'\n\n/** One bundled plugin/theme from the catalog. */\nexport interface CatalogEntry {\n /** Short name as used in tiddlywiki.info, e.g. \"tiddlywiki/katex\". */\n name: string\n /** Full tiddler title, e.g. \"$:/plugins/tiddlywiki/katex\". */\n title: string\n label: string\n description: string\n /** Dependent theme NAMES (converted from plugin.info `dependents`), e.g. heavier → [\"tiddlywiki/snowwhite\"]. */\n dependents?: string[]\n}\n\nexport interface Catalog {\n plugins: CatalogEntry[]\n themes: CatalogEntry[]\n /** Bundled language plugins (tiddlywiki package `languages/` dir). */\n languages: CatalogEntry[]\n}\n\n/** Shape of tiddlywiki.info (plugins/themes/languages + build/description). */\nexport interface WikiInfo {\n description?: string\n plugins: string[]\n themes: string[]\n languages?: string[]\n build?: Record<string, unknown>\n [key: string]: unknown\n}\n\n/** Resolve the installed tiddlywiki package root (for the catalog). */\nexport function resolveTwRoot(): string {\n const require = createRequire(import.meta.url)\n return dirname(require.resolve('tiddlywiki/package.json'))\n}\n\n/**\n * Read the wiki's tiddlywiki.info.\n *\n * ONLY a missing file (ENOENT) means \"no configuration yet\" (first run). Every\n * other failure — EACCES/EBUSY, a truncated write, malformed JSON — PROPAGATES:\n * the settings page used to receive an empty config object, rewrite the file\n * from it and thereby DELETE the existing plugins/themes/`build`/hand-written\n * fields (v0.19.0 — the AGENTS §8 \"don't treat a read failure as absence\" rule,\n * applied to this path at last).\n */\nexport async function readWikiInfo(wikiPath: string): Promise<WikiInfo> {\n const file = join(wikiPath, 'tiddlywiki.info')\n let raw: string\n try {\n raw = await readFile(file, 'utf8')\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return { plugins: [], themes: [], languages: [] }\n }\n throw new Error(`读取 tiddlywiki.info 失败:${err instanceof Error ? err.message : String(err)}`)\n }\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n throw new Error('tiddlywiki.info 不是合法 JSON;请先修复该文件再保存设置(已保留原文件)')\n }\n // A top-level `null`/array/primitive is NOT a valid tiddlywiki.info; the old\n // code spread it and crashed with \"Cannot read properties of null\" (a\n // confusing 500 instead of the intended readable error).\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw new Error('tiddlywiki.info 顶层不是对象;请先修复该文件再保存设置(已保留原文件)')\n }\n const obj = parsed as Partial<WikiInfo>\n return {\n // Spread FIRST, then the normalized fields: the old order let a literal\n // `\"plugins\": null` (or a non-array) overwrite the `[]` default and crash\n // every consumer with `.includes is not a function`.\n ...obj,\n description: obj.description,\n plugins: Array.isArray(obj.plugins) ? obj.plugins : [],\n themes: Array.isArray(obj.themes) ? obj.themes : [],\n languages: Array.isArray(obj.languages) ? obj.languages : [],\n }\n}\n\n/**\n * Write the wiki's tiddlywiki.info (pretty-printed, ordering preserved).\n * Keeps a `.bak` of the previous content and swaps the new content in via a\n * temp file + rename, so a crash or a full disk cannot leave a truncated file\n * behind (readWikiInfo now refuses to guess at one).\n */\nexport async function writeWikiInfo(wikiPath: string, info: WikiInfo): Promise<void> {\n const file = join(wikiPath, 'tiddlywiki.info')\n try {\n await writeFile(`${file}.bak`, await readFile(file, 'utf8'), 'utf8')\n } catch {\n /* first write — nothing to back up */\n }\n const tmp = `${file}.tmp`\n await writeFile(tmp, `${JSON.stringify(info, null, 4)}\\n`, 'utf8')\n await rename(tmp, file)\n}\n\n/**\n * Ensure one bundled official plugin is listed in tiddlywiki.info `plugins`\n * (idempotent). Returns whether the file changed — the caller restarts TW.\n *\n * WHY (v0.19.0): `WikiServer.ensureWiki()` scaffolds a fresh wiki with\n * `--init server`, and that edition ships ONLY tiddlyweb/filesystem/highlight.\n * The plugin nevertheless writes every agent note, quick-note, draft and clip\n * as `text/markdown`, and the `text/markdown` parser is provided exclusively by\n * the `tiddlywiki/markdown` plugin — without it a brand-new install renders\n * every Markdown note as raw wikitext/source. The author's own wiki already had\n * the plugin (added by an earlier import workflow), which is why this went\n * unnoticed.\n */\nexport async function ensurePlugin(wikiPath: string, twRoot: string, name: string): Promise<boolean> {\n const info = await readWikiInfo(wikiPath)\n if (info.plugins.includes(name)) return false\n const catalog = await bundledCatalog(twRoot)\n if (!catalog.plugins.some((p) => p.name === name)) {\n throw new Error(`unknown bundled plugin: ${name}`)\n }\n info.plugins = [...info.plugins, name]\n await writeWikiInfo(wikiPath, info)\n return true\n}\n\n/** Enumerate bundled official plugins + themes + languages of tiddlywiki. */\nexport async function bundledCatalog(twRoot: string): Promise<Catalog> {\n // TW themes are SKINS layered on the vanilla base (which carries the full\n // 70KB base stylesheet). A theme whose stylesheet body is empty is a broken\n // stub (e.g. tight-heavier in some releases) — skip it so the settings list\n // never offers a no-op theme. vanilla itself is always kept.\n const themeHasCss = async (dir: string): Promise<boolean> => {\n for (const name of ['base.tid', 'styles.tid']) {\n try {\n const raw = await readFile(join(twRoot, 'themes', 'tiddlywiki', dir, name), 'utf8')\n const body = raw\n .replace(/^[\\s\\S]*?\\r?\\n\\r?\\n/, '')\n .split('\\n')\n .filter((line) => !/^\\\\rules\\b/.test(line.trim()))\n .join('\\n')\n .trim()\n if (body.length > 0) return true\n } catch {\n /* file absent */\n }\n }\n return false\n }\n const scan = async (sub: 'plugins' | 'themes'): Promise<CatalogEntry[]> => {\n const root = join(twRoot, sub, 'tiddlywiki')\n let dirs: string[]\n try {\n dirs = await readdir(root)\n } catch {\n return []\n }\n const out: CatalogEntry[] = []\n for (const dir of dirs) {\n let info: { name?: string; description?: string; dependents?: string[] } = {}\n try {\n info = JSON.parse(await readFile(join(root, dir, 'plugin.info'), 'utf8')) as typeof info\n } catch {\n info = {}\n }\n if (sub === 'themes' && dir !== 'vanilla' && !(await themeHasCss(dir))) continue\n out.push({\n name: `tiddlywiki/${dir}`,\n title: sub === 'plugins' ? `$:/plugins/tiddlywiki/${dir}` : `$:/themes/tiddlywiki/${dir}`,\n label: info.name ?? dir,\n description: info.description ?? '',\n // plugin.info `dependents` are full plugin titles → convert to names.\n dependents: Array.isArray(info.dependents)\n ? info.dependents.map((dep) => dep.replace(/^\\$:\\/themes\\/tiddlywiki\\//, 'tiddlywiki/'))\n : undefined,\n })\n }\n out.sort((a, b) => a.name.localeCompare(b.name))\n return out\n }\n // Language plugins live in the package ROOT `languages/` dir (not plugins/),\n // and are enabled via the tiddlywiki.info `languages` array (boot resolves\n // them through $tw.config.languagesPath). Fully offline — official builds.\n const scanLanguages = async (): Promise<CatalogEntry[]> => {\n const root = join(twRoot, 'languages')\n let dirs: string[]\n try {\n dirs = await readdir(root)\n } catch {\n return []\n }\n const out: CatalogEntry[] = []\n for (const dir of dirs) {\n let info: { name?: string; description?: string } = {}\n try {\n info = JSON.parse(await readFile(join(root, dir, 'plugin.info'), 'utf8')) as typeof info\n } catch {\n info = {}\n }\n out.push({\n name: dir,\n title: `$:/languages/${dir}`,\n label: info.name ?? dir,\n description: info.description ?? '',\n })\n }\n out.sort((a, b) => a.name.localeCompare(b.name))\n return out\n }\n const [plugins, themes, languages] = await Promise.all([scan('plugins'), scan('themes'), scanLanguages()])\n return { plugins, themes, languages }\n}\n\n/**\n * Normalize a theme selection into the tiddlywiki.info `themes` array.\n *\n * TW themes are SKINS with a dependency chain (plugin.info `dependents`):\n * vanilla ← snowwhite ← heavier / centralised / readonly / starlight\n * vanilla ← tight / seamless\n * The ACTIVE theme is `$:/theme`, and switching to it registers the theme PLUS\n * its transitive dependents (boot.js accumulatePlugin) — if a dependent isn't\n * loaded, the vanilla base stylesheet is lost and the UI breaks. So we always\n * emit the transitive closure, dependency-first (base first, active overlay\n * last), and force vanilla in as the base. Empty selection → vanilla.\n */\nexport function normalizeThemes(selected: string[], deps: Record<string, string[]> = {}): string[] {\n const sel = selected.filter((name) => typeof name === 'string' && name.length > 0)\n if (sel.length === 0) sel.push('tiddlywiki/vanilla')\n const out: string[] = []\n const seen = new Set<string>()\n const visit = (name: string): void => {\n if (seen.has(name)) return\n seen.add(name)\n for (const dep of deps[name] ?? []) {\n if (dep !== name) visit(dep)\n }\n out.push(name)\n }\n for (const name of sel) visit(name)\n if (!out.includes('tiddlywiki/vanilla')) out.unshift('tiddlywiki/vanilla')\n return out\n}\n\n/**\n * Ensure a language code (e.g. \"zh-Hans\") is in tiddlywiki.info `languages`.\n * Returns whether tiddlywiki.info changed (caller decides whether to restart).\n */\nexport async function ensureLanguage(wikiPath: string, twRoot: string, lang: string): Promise<boolean> {\n if (typeof lang !== 'string' || lang.trim().length === 0) return false\n const code = lang.trim()\n const catalog = await bundledCatalog(twRoot)\n if (!catalog.languages.some((l) => l.name === code)) {\n throw new Error(`unknown language plugin: ${code}`)\n }\n const info = await readWikiInfo(wikiPath)\n const current = info.languages ?? []\n if (current.includes(code)) return false\n info.languages = [...current, code]\n await writeWikiInfo(wikiPath, info)\n return true\n}\n\nexport interface AdminDeps {\n server: WikiServer\n getClient: () => TiddlyWebClient | undefined\n getWikiPath: () => string\n twRoot: () => string\n config: ConfigStore\n /**\n * Called after a successful settings-page save (v0.21.0). The plugin rebuilds\n * its system-prompt section here, so a prompt.* edit applies to the running\n * session from its next model step — without restarting dsh web.\n */\n onConfigChanged?: () => void\n /**\n * The system-prompt text that WOULD be injected right now, for the settings\n * page preview (v0.21.0). Read-only; absent in headless contexts.\n */\n getPrompt?: () => { enabled: boolean; mode: string; text: string }\n /**\n * Runtime wiki location (v0.22.0): where the wiki folder is, how that was\n * decided, and the two operations the settings page can perform. Absent in\n * headless contexts.\n */\n wiki?: {\n info: () => Promise<WikiLocationInfo>\n switch: (target: { root?: unknown; name?: unknown }) => Promise<WikiSwitchResult>\n reset: () => Promise<WikiSwitchResult>\n }\n /** Seed registry for the settings-page \"初始化\" section. */\n seeds: {\n /**\n * `updateAvailable` / `userModified` / `legacyMarker` come from the marker's\n * content hashes (v0.22.0): the page shows 「有更新」 and refuses to imply a\n * local edit was checked for when the marker predates hashes.\n */\n checkAll: (client: TiddlyWebClient) => Promise<Array<{ id: string; title: string; description: string; present: boolean; removable: boolean; detail?: string; updateAvailable?: boolean; userModified?: boolean; legacyMarker?: boolean }>>\n run: (client: TiddlyWebClient, id: string | undefined, force: boolean) => Promise<Array<{ id: string; ok: boolean; wrote: boolean; detail?: string; error?: string }>>\n /** 反初始化: remove one (or all) optional seed's seeded tiddlers + markers. */\n remove: (client: TiddlyWebClient, id: string | undefined) => Promise<Array<{ id: string; ok: boolean; wrote: boolean; detail?: string; error?: string }>>\n }\n}\n\n/**\n * Sentinel returned instead of a stored secret (`bridge.token` /\n * `ui.sendToAgent.token`) by `GET /admin/state` and by `POST /admin/config`'s\n * echo. `/admin/*` is an unauthenticated surface (only CSRF-hardened), and the\n * config tiddler legitimately carries shared tokens — handing them to any\n * caller of the loopback/LAN-reachable GUI is a real leak (v0.19.3). The\n * settings page renders the sentinel verbatim; posting it back is a no-op\n * (`stripMaskedSecrets` drops it), so the real value never leaves the host.\n */\nexport const MASKED_SECRET = '********'\n\n/**\n * Mask secrets (and credentials inside the git remote URL) for an HTTP caller.\n *\n * v0.20.0: `auth.password` is masked too. The v0.19.3 implementation (and the\n * docs) claimed it was, but only `bridge.token` / `ui.sendToAgent.token` /\n * `git.remote` were handled — a config tiddler carrying `auth.password` (the\n * config tiddler is a wiki tiddler; a user can hand-edit it) was echoed in\n * clear text by the unauthenticated `GET /admin/state`. `passwordSet` mirrors\n * the `tokenSet` flag so the settings UI can show \"a password is stored\"\n * without ever receiving it.\n */\nexport function maskConfigSecrets(config: PluginConfigShape): PluginConfigShape {\n const bridge = (config.bridge ?? {}) as Record<string, unknown>\n const ui = (config.ui ?? {}) as Record<string, unknown>\n const sendToAgent = (ui.sendToAgent ?? {}) as Record<string, unknown>\n const git = (config.git ?? {}) as Record<string, unknown>\n const auth = (config.auth ?? {}) as Record<string, unknown>\n const maskToken = (value: unknown): string => (typeof value === 'string' && value.length > 0 ? MASKED_SECRET : '')\n return {\n ...config,\n auth: {\n ...auth,\n password: maskToken(auth.password),\n passwordSet: typeof auth.password === 'string' && auth.password.length > 0,\n },\n git: { ...git, remote: typeof git.remote === 'string' ? redactRemoteUrl(git.remote) : git.remote },\n bridge: {\n ...bridge,\n token: maskToken(bridge.token),\n tokenSet: typeof bridge.token === 'string' && bridge.token.length > 0,\n },\n ui: {\n ...ui,\n sendToAgent: {\n ...sendToAgent,\n token: maskToken(sendToAgent.token),\n tokenSet: typeof sendToAgent.token === 'string' && sendToAgent.token.length > 0,\n },\n },\n } as PluginConfigShape\n}\n\n/**\n * Drop the masked placeholders from an incoming config patch so saving the\n * settings page never overwrites a stored secret with `********` (or the\n * redacted git remote with `https://***@…`). `bridge.token`/`ui.sendToAgent.token`\n * are cleared only when the caller actually sends an empty string.\n */\nexport function stripMaskedSecrets<T extends Record<string, unknown>>(patch: T, current: PluginConfigShape): T {\n const copy = { ...patch } as Record<string, unknown>\n const cleanToken = (container: unknown): unknown => {\n if (typeof container !== 'object' || container === null) return container\n const obj = { ...(container as Record<string, unknown>) }\n if (obj.token === MASKED_SECRET) delete obj.token\n delete obj.tokenSet\n return obj\n }\n if (copy.bridge !== undefined) copy.bridge = cleanToken(copy.bridge)\n // v0.20.0: the same round-trip rule for auth.password (`passwordSet` is a\n // display-only flag and must never be persisted).\n if (copy.auth !== undefined && typeof copy.auth === 'object' && copy.auth !== null) {\n const auth = { ...(copy.auth as Record<string, unknown>) }\n if (auth.password === MASKED_SECRET) delete auth.password\n delete auth.passwordSet\n copy.auth = auth\n }\n if (copy.ui !== undefined) {\n const ui = typeof copy.ui === 'object' && copy.ui !== null\n ? { ...(copy.ui as Record<string, unknown>) }\n : copy.ui\n if (typeof ui === 'object' && ui !== null && (ui as Record<string, unknown>).sendToAgent !== undefined) {\n ;(ui as Record<string, unknown>).sendToAgent = cleanToken((ui as Record<string, unknown>).sendToAgent)\n }\n copy.ui = ui\n }\n if (copy.git !== undefined && typeof copy.git === 'object' && copy.git !== null) {\n const git = { ...(copy.git as Record<string, unknown>) }\n const storedRemote = typeof current.git?.remote === 'string' ? current.git.remote : ''\n if (typeof git.remote === 'string' && storedRemote.length > 0 && git.remote === redactRemoteUrl(storedRemote)) {\n delete git.remote\n }\n copy.git = git\n }\n return copy as T\n}\n\nexport function registerAdminRoutes(ctx: { webServer: WebServerFace }, deps: AdminDeps): () => void {\n const handleState = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectNonRead(req, res)) return\n const wikiPath = deps.getWikiPath()\n const [info, catalog] = await Promise.all([readWikiInfo(wikiPath), bundledCatalog(deps.twRoot())])\n let git: unknown = null\n try {\n const status = await new GitFace().status(wikiPath)\n git = { ...status, remote: redactRemoteUrl(status.remote ?? '') }\n } catch {\n git = null\n }\n const view = deps.server.status()\n json(res, {\n ok: true,\n // Same redaction as GET /status: this route is unauthenticated too.\n server: { ...view, logs: redactLogLines(view.logs) },\n info: { plugins: info.plugins, themes: info.themes, languages: info.languages ?? [] },\n catalog,\n config: maskConfigSecrets(deps.config.get()),\n git,\n })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\n }\n }\n\n const handleInfo = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n const body = JSON.parse(await readBody(req)) as { plugins?: unknown; themes?: unknown; themeActive?: unknown; languages?: unknown }\n const wikiPath = deps.getWikiPath()\n // A read failure must NEVER be turned into \"the wiki has no plugins\":\n // saving would then rewrite the file without them. Fail with 500 and\n // leave the file untouched.\n let info: WikiInfo\n try {\n info = await readWikiInfo(wikiPath)\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\n return\n }\n const catalog = await bundledCatalog(deps.twRoot())\n const known = new Set([...catalog.plugins, ...catalog.themes].map((c) => c.name))\n const knownLangs = new Set(catalog.languages.map((c) => c.name))\n /** Snapshot for the no-op guard below (JSON compare is enough here). */\n const beforeInfo = JSON.stringify(info)\n /** Set only when the request carried a themes array with a resolvable active pick. */\n let activatedTheme: string | undefined\n const applyList = (field: 'plugins' | 'themes', raw: unknown): string[] => {\n if (!Array.isArray(raw)) return info[field]\n const next: string[] = []\n for (const name of raw) {\n if (typeof name !== 'string') continue\n if (!known.has(name) && !info[field].includes(name)) {\n throw new Error(`unknown plugin/theme: ${name}`)\n }\n if (!next.includes(name)) next.push(name)\n }\n return next\n }\n const applyLanguages = (raw: unknown): string[] => {\n if (!Array.isArray(raw)) return info.languages ?? []\n const next: string[] = []\n for (const code of raw) {\n if (typeof code !== 'string') continue\n if (!knownLangs.has(code) && !(info.languages ?? []).includes(code)) {\n throw new Error(`unknown language plugin: ${code}`)\n }\n if (!next.includes(code)) next.push(code)\n }\n return next\n }\n // Validation + in-memory mutation only: a rejected name must NOT touch\n // tiddlywiki.info (400 straight out), while a failure of the write/restart\n // below is an internal error (500) — the old code reported both as 400.\n try {\n info.plugins = applyList('plugins', body.plugins)\n // Activate the chosen theme: load its full dependency chain AND set\n // `$:/theme` so the browser's themeManager actually applies it (the\n // `themes` array alone only makes the plugin available).\n if (Array.isArray(body.themes)) {\n const themeDeps: Record<string, string[]> = {}\n for (const theme of catalog.themes) {\n if (theme.dependents && theme.dependents.length > 0) themeDeps[theme.name] = theme.dependents\n }\n const selected = applyList('themes', body.themes)\n // Explicit active-theme pick (new two-layer UI): validate and auto-add\n // it to the loaded set so its dependency closure is loaded. Without an\n // explicit pick, activate the deepest loaded overlay (old single-radio).\n const explicitActive = typeof body.themeActive === 'string' && body.themeActive.length > 0\n if (explicitActive) {\n const activeName = body.themeActive as string\n if (known.has(activeName) || info.themes.includes(activeName)) {\n if (!selected.includes(activeName)) selected.push(activeName)\n activatedTheme = activeName\n }\n }\n info.themes = normalizeThemes(selected, themeDeps)\n if (activatedTheme === undefined && info.themes.length > 0) {\n activatedTheme = info.themes[info.themes.length - 1]\n }\n } else {\n info.themes = applyList('themes', body.themes)\n }\n if (Array.isArray(body.languages)) info.languages = applyLanguages(body.languages)\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 400)\n return\n }\n // NO-OP GUARD (v0.19.3): a body that changes nothing (e.g. `{}`, or the\n // same lists re-sent) used to rewrite tiddlywiki.info + restart TW — a\n // visible TW interruption for a request that asked for nothing.\n const changed = JSON.stringify(info) !== beforeInfo\n if (changed) {\n await writeWikiInfo(wikiPath, info)\n await deps.server.restart()\n }\n // Activate the chosen theme tiddler (mirrors TW's own Control Panel).\n if (Array.isArray(body.themes) && activatedTheme !== undefined) {\n const client = deps.getClient()\n if (client !== undefined) {\n await client\n .put({ title: '$:/theme', text: `$:/themes/${activatedTheme}`, type: 'text/vnd.tiddlywiki', tags: [] })\n .catch((err) => { console.warn('[dsh-tiddlywiki] activating theme failed:', err) })\n }\n }\n // After a languages change, pin the active language tiddler: first\n // enabled language, or en-GB when none is enabled. Only when the request\n // actually carried a languages array (plugins/themes restarts skip this).\n if (Array.isArray(body.languages)) {\n const client = deps.getClient()\n if (client !== undefined) {\n const langs = info.languages ?? []\n const active = langs.length > 0 ? `$:/languages/${langs[0]}` : '$:/languages/en-GB'\n await client.put({ title: '$:/language', text: active, type: 'text/plain', tags: [] })\n .catch((err) => { console.warn('[dsh-tiddlywiki] pinning $:/language failed:', err) })\n // Keep the startup auto-apply hint (config uiLanguage) consistent with\n // the active language, so a later dsh-web restart doesn't re-enable\n // a language the user just disabled here.\n const hint = langs.length > 0 ? langs[0] : ''\n if ((deps.config.get().uiLanguage ?? '') !== hint) {\n await deps.config.set(client, { uiLanguage: hint }).catch(() => undefined)\n }\n }\n }\n json(res, { ok: true, changed, info: { plugins: info.plugins, themes: info.themes, languages: info.languages ?? [] } })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\n }\n }\n\n const handleConfig = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n const body = JSON.parse(await readBody(req)) as PluginConfigShape\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n // Never persist the masked placeholders the page read back from /state\n // (v0.19.3): saving an untouched form must not clobber a real token.\n await deps.config.set(client, stripMaskedSecrets(body as Record<string, unknown>, deps.config.get()) as PluginConfigShape)\n // prompt.* may have changed: let the plugin re-register its prompt\n // section now (a no-op when the built text is unchanged).\n deps.onConfigChanged?.()\n json(res, { ok: true, config: maskConfigSecrets(deps.config.get()) })\n } catch (err) {\n // Not a blanket 400 (v0.19.5): a refused/dead wiki client surfaces as a\n // 500/413 like everywhere else — reporting「参数错误」for a service failure\n // sent the settings page down the wrong recovery path.\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n const handleRestart = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n await deps.server.restart()\n json(res, { ok: true, status: deps.server.status().status })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\n }\n }\n\n /**\n * GET /dsh-tiddlywiki/admin/seeds — status of every one-time seed\n * (doc-note / send-to-agent / home-index / tw-web-host) for the settings\n * page's 初始化 section.\n */\n const handleSeeds = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectNonRead(req, res)) return\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n const items = await deps.seeds.checkAll(client)\n json(res, { ok: true, items })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\n }\n }\n\n /**\n * POST /dsh-tiddlywiki/admin/seeds/run — run one seed (or all when `id` is\n * absent); `force: true` is the manual \"重新初始化\" (overwrite + re-marker),\n * `force: false` keeps the one-shot write-if-missing semantics.\n */\n const handleSeedsRun = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n const body = JSON.parse(await readBody(req)) as { id?: unknown; force?: unknown }\n const id = typeof body.id === 'string' && body.id.trim().length > 0 ? body.id.trim() : undefined\n const force = body.force === true\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n const seedStartedAt = Date.now()\n const results = await deps.seeds.run(client, id, force)\n // A seeded SERVER-route plugin (render) only loads at TW BOOT, and TW's\n // syncer flushes REST writes on a ~250ms timer. Without the flush-wait +\n // restart the settings page reported「已重新初始化」while /render kept\n // 404ing (tool cards and wiki-link rendering stayed degraded). Mirrors the\n // startup path in src/index.ts — and, like there, the restart fires ONLY\n // for a seed that really rewrote the route (restarting over unflushed\n // content writes would lose them).\n let restarted = false\n let restartError: string | undefined\n if (needsRestartAfterSeeds(results)) {\n try {\n const flushed = await waitForFileWrite(join(deps.getWikiPath(), 'tiddlers', RENDER_PLUGIN_FILE), 8_000, 150, seedStartedAt)\n if (!flushed) console.warn('[dsh-tiddlywiki] seeded render plugin file not seen on disk before restart')\n // Drain the rest of the syncer queue too: force-all writes every seed,\n // and a restart that boots from a stale snapshot silently loses the\n // ones still queued (v0.19.0 — repeatedly lost tw-web-host here).\n const drained = await flushPendingWrites(client, join(deps.getWikiPath(), 'tiddlers'))\n if (!drained) console.warn('[dsh-tiddlywiki] seed writes may not have been flushed before restart')\n await deps.server.restart()\n restarted = true\n } catch (err) {\n restartError = err instanceof Error ? err.message : String(err)\n console.warn('[dsh-tiddlywiki] restart after seeding failed:', restartError)\n }\n }\n const ok = results.every((r) => r.ok)\n json(res, { ok, results, restarted, ...(restartError !== undefined ? { restartError } : {}) }, ok ? 200 : 400)\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\n }\n }\n\n /**\n * POST /dsh-tiddlywiki/admin/seeds/remove — 反初始化: remove one optional\n * seed (or all optional seeds when `id` is absent) — delete the seeded\n * tiddlers + markers so the wiki returns to the \"never seeded\" state.\n * Core seeds (功能必需) cannot be removed.\n */\n const handleSeedsRemove = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n const body = JSON.parse(await readBody(req)) as { id?: unknown }\n const id = typeof body.id === 'string' && body.id.trim().length > 0 ? body.id.trim() : undefined\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n const results = await deps.seeds.remove(client, id)\n const ok = results.every((r) => r.ok)\n json(res, { ok, results }, ok ? 200 : 400)\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\n }\n }\n\n /**\n * GET /dsh-tiddlywiki/admin/prompt — the system-prompt text that would be\n * injected right now, with the effective mode/enabled flag (v0.21.0). The\n * settings page renders it verbatim so a prompt edit is never a black box.\n * Read-only + CSRF-hardened like every other admin read.\n */\n const handlePrompt = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectNonRead(req, res)) return\n const prompt = deps.getPrompt?.()\n if (prompt === undefined) {\n json(res, { ok: false, error: 'prompt preview is not available' }, 503)\n return\n }\n json(res, { ok: true, enabled: prompt.enabled, mode: prompt.mode, length: prompt.text.length, text: prompt.text })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\n }\n }\n\n /**\n * GET /dsh-tiddlywiki/admin/wiki/location — the folder the plugin currently\n * serves, how that was decided (pointer file / cordis config / default), the\n * config default to fall back to, and the wiki-looking folders next to it\n * (settings-page「知识库位置」, v0.22.0). Read-only + CSRF-hardened.\n */\n const handleWikiLocation = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectNonRead(req, res)) return\n if (deps.wiki === undefined) {\n json(res, { ok: false, error: 'wiki location is not available' }, 503)\n return\n }\n json(res, { ok: true, ...(await deps.wiki.info()) })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\n }\n }\n\n /**\n * POST /dsh-tiddlywiki/admin/wiki/switch { root, name } — repoint the running\n * plugin at another wiki folder (v0.22.0). Serialized against itself by the\n * host; a failure is reported with `rolledBack` so the page can say whether\n * the old wiki is still serving.\n */\n const handleWikiSwitch = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n if (deps.wiki === undefined) {\n json(res, { ok: false, error: 'wiki location is not available' }, 503)\n return\n }\n const body = JSON.parse(await readBody(req)) as { root?: unknown; name?: unknown }\n const result = await deps.wiki.switch({ root: body.root, name: body.name })\n json(res, result.ok ? { ...result, status: deps.server.status().status } : result, result.ok ? 200 : 400)\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n /**\n * POST /dsh-tiddlywiki/admin/wiki/reset — 「恢复为配置默认」: delete the pointer\n * file and (when needed) switch back to the cordis default folder (v0.22.0).\n */\n const handleWikiReset = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n if (deps.wiki === undefined) {\n json(res, { ok: false, error: 'wiki location is not available' }, 503)\n return\n }\n const result = await deps.wiki.reset()\n json(res, result.ok ? { ...result, status: deps.server.status().status } : result, result.ok ? 200 : 400)\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n // Same rejection safety net as routes.ts (v0.19.3).\n const disposers = [\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/state`, handler: guardHandler(handleState) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/prompt`, handler: guardHandler(handlePrompt) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/wiki/location`, handler: guardHandler(handleWikiLocation) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/wiki/switch`, handler: guardHandler(handleWikiSwitch) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/wiki/reset`, handler: guardHandler(handleWikiReset) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/info`, handler: guardHandler(handleInfo) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/config`, handler: guardHandler(handleConfig) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/restart`, handler: guardHandler(handleRestart) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/seeds`, handler: guardHandler(handleSeeds) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/seeds/run`, handler: guardHandler(handleSeedsRun) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/seeds/remove`, handler: guardHandler(handleSeedsRemove) }),\n ]\n return () => {\n for (const dispose of disposers) dispose()\n }\n}\n","/**\n * 本地剪藏桥(bookmarklet 剪藏小工具的后端)。\n *\n * A tiny loopback-only HTTP bridge that turns a browser bookmarklet's\n * \"剪藏\" request into TiddlyWiki tiddlers. The bookmarklet (JS in the\n * browser's bookmarks bar) POSTs { title, url, text, images } to the bridge;\n * the bridge writes ONE note tiddler (markdown, or wikitext when images are\n * stored) plus optional IMAGE ATTACHMENTS through the SAME TiddlyWebClient\n * every other writer uses (D1 — there is never a second write path).\n *\n * Images (v0.16.25): TW 5.4.1's REST facade is JSON-only (put-tiddler.js\n * JSON.parses the body — there is no binary upload route), but its binary\n * tiddlers ARE representable as `{ type: image/*, text: <base64> }` — verified\n * empirically: type + base64 roundtrip byte-exact, tags survive, custom fields\n * nest under `fields`. So the bridge downloads each chosen image (server-side,\n * no CORS; referer + UA sent for hotlink-protected CDNs), stores it as a\n * binary ATTACHMENT tiddler, and embeds it in the note with `[img[Title]]`.\n * A failed download degrades gracefully to a plain source-URL line.\n *\n * Security posture (deliberate):\n * - binds `127.0.0.1` ONLY — never reachable from the network;\n * - Host-header whitelist (127.0.0.1 / localhost / ::1) blocks DNS rebinding:\n * a malicious website that resolves its own domain to the loopback cannot\n * use the bridge with its own Host header;\n * - optional shared `token` — when set, every write must carry\n * `x-clip-token`. The bookmarklet ships it; a random webpage on the\n * internet cannot know it, so it cannot pollute the wiki;\n * - CORS preflight answered with `Access-Control-Allow-Origin: *` +\n * `Access-Control-Allow-Private-Network: true` so (a) a bookmarklet running\n * on an https page may read the response, and (b) Chromium's Private Network\n * Access preflight for public→loopback requests succeeds;\n * - `enabled` is evaluated PER REQUEST against the effective (settings-page)\n * config, so toggling the flag takes effect immediately — no dsh web\n * restart. Changing the PORT still needs a restart (the listener binds once).\n *\n * @module dsh-tiddlywiki/host/clip-bridge\n */\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http'\nimport { get as httpGet } from 'node:http'\nimport { get as httpsGet } from 'node:https'\nimport { lookup } from 'node:dns/promises'\nimport { isIP } from 'node:net'\nimport type { AddressInfo, LookupFunction } from 'node:net'\nimport type { Tiddler } from './tw-api.ts'\nimport { readBody, safeTokenEqual } from './http.ts'\n\n/** Effective bridge config (resolved from the plugin config store). */\nexport interface BridgeConfig {\n /** 是否启用剪藏桥(每个请求实时判定,保存设置即生效)。 */\n enabled: boolean\n /** 监听端口(仅启动时绑定一次;改动端口需重启 dsh web)。 */\n port: number\n /** 共享口令;非空时每个写入须带 `x-clip-token` 头。 */\n token: string\n /** 剪藏笔记默认 tag。 */\n tag: string\n}\n\n/** A downloaded image (raw bytes + declared content-type, nullable). */\nexport interface ClipImageDownload {\n buffer: Buffer\n type: string | null\n}\n\n/** Everything the bridge needs from the plugin (no @deepseek-ai deps). */\nexport interface ClipBridgeDeps {\n /** Resolve the EFFECTIVE bridge config per request. */\n getConfig(): BridgeConfig\n /** Write one tiddler; throws when the wiki is not ready. */\n write(tiddler: Tiddler): Promise<unknown>\n /** True when a tiddler with that title already exists (title dedupe). */\n exists(title: string): Promise<boolean>\n /** Download an image's bytes (server-side fetch; throws on failure). */\n download(url: string, referer: string): Promise<ClipImageDownload>\n /** Optional logger (console.info prefixed by the caller). */\n log?(message: string): void\n}\n\n/** Cap on the clipped text (characters) — plenty for a page selection. */\nexport const MAX_CLIP_TEXT_LENGTH = 200_000\n/** Cap on a single /clip request body (bytes). */\nexport const MAX_CLIP_BODY_BYTES = 256 * 1024\n/** Cap on one downloaded image (bytes) — 15 MB keeps the wiki sane. */\nexport const MAX_IMAGE_BYTES = 15 * 1024 * 1024\n/** Max images stored per clip (the picker already caps the page list). */\nexport const MAX_CLIP_IMAGES = 10\n\n/** Validated request body accepted by POST /clip (all fields normalized). */\nexport interface ClipPayload {\n title: string\n url: string\n text: string\n /** Optional extra tiddler tags (merged with the configured clip tag). */\n tags: string[]\n /** Where the clip came from (\"bookmarklet\" default; \"api\"/\"script\"…). */\n source: string\n /** Chosen image URLs to download + store as attachments (≤ MAX_CLIP_IMAGES). */\n images: string[]\n}\n\n/** Per-image result echoed back to the bookmarklet. */\nexport interface ClipImageResult {\n url: string\n ok: boolean\n /** Stored attachment tiddler title (when ok). */\n title?: string\n /** Human-readable failure reason (when !ok). */\n error?: string\n}\n\n/** Loopback-only Host header whitelist (DNS-rebinding defense). */\nexport function hostAllowed(host: string | undefined, port: number): boolean {\n if (typeof host !== 'string') return false\n const h = host.trim().toLowerCase()\n const ok = (base: string): boolean => h === base || h === `${base}:${port}`\n return ok('127.0.0.1') || ok('localhost') || ok('[::1]') || ok('::1')\n}\n\n/** IPv4 ranges that must never be fetched on behalf of a web page. */\nconst PRIVATE_V4_PATTERNS = [\n /^0\\./, // \"this network\"\n /^10\\./, // private\n /^100\\.(6[4-9]|[7-9]\\d|1[01]\\d|12[0-7])\\./, // CGNAT 100.64/10\n /^127\\./, // loopback\n /^169\\.254\\./, // link-local (cloud metadata 169.254.169.254)\n /^172\\.(1[6-9]|2\\d|3[01])\\./, // private\n /^192\\.0\\.0\\./, // IETF protocol assignments\n /^192\\.0\\.2\\./, // TEST-NET-1\n /^192\\.88\\.99\\./, // 6to4 relay anycast\n /^192\\.168\\./, // private\n /^198\\.1[89]\\./, // benchmarking 198.18/15\n /^198\\.51\\.100\\./, // TEST-NET-2\n /^203\\.0\\.113\\./, // TEST-NET-3\n /^22[4-9]\\./, /^23\\d\\./, // multicast 224/4\n /^24\\d\\./, /^25[0-5]\\./, // reserved 240/4 (incl. 255.255.255.255 broadcast)\n]\n\n/** Parse a dotted-quad IPv4 into 4 bytes, or null. */\nfunction parseIpv4(input: string): number[] | null {\n const parts = input.split('.')\n if (parts.length !== 4) return null\n const out: number[] = []\n for (const part of parts) {\n if (!/^\\d{1,3}$/.test(part)) return null\n const value = Number(part)\n if (value > 255) return null\n out.push(value)\n }\n return out\n}\n\n/**\n * Expand ANY IPv6 textual form (compressed `::`, expanded, embedded dotted\n * IPv4 tail, zone id, bracketed) into exactly 16 bytes — or null when it is not\n * a valid IPv6 literal. Written by hand because the guard must be total:\n * `net.isIP()` accepts forms such as `0:0:0:0:0:0:0:1` (which IS `::1`) that the\n * old prefix-regex guard let through.\n */\nfunction ipv6ToBytes(input: string): number[] | null {\n let ip = input.trim().toLowerCase()\n if (ip.startsWith('[') && ip.endsWith(']')) ip = ip.slice(1, -1)\n const zone = ip.indexOf('%')\n if (zone >= 0) ip = ip.slice(0, zone)\n const halves = ip.split('::')\n if (halves.length > 2) return null\n const parseGroups = (text: string): number[] | null => {\n if (text.length === 0) return []\n const out: number[] = []\n for (const group of text.split(':')) {\n if (group.length === 0) return null\n if (group.includes('.')) {\n const v4 = parseIpv4(group)\n if (v4 === null) return null\n out.push((v4[0] ?? 0) * 256 + (v4[1] ?? 0), (v4[2] ?? 0) * 256 + (v4[3] ?? 0))\n continue\n }\n if (!/^[0-9a-f]{1,4}$/.test(group)) return null\n out.push(Number.parseInt(group, 16))\n }\n return out\n }\n const head = parseGroups(halves[0] ?? '')\n if (head === null) return null\n let groups: number[]\n if (halves.length === 2) {\n const tail = parseGroups(halves[1] ?? '')\n if (tail === null) return null\n const missing = 8 - head.length - tail.length\n if (missing < 0) return null\n groups = [...head, ...new Array<number>(missing).fill(0), ...tail]\n } else {\n groups = head\n }\n if (groups.length !== 8) return null\n const bytes: number[] = []\n for (const group of groups) bytes.push((group >> 8) & 0xff, group & 0xff)\n return bytes\n}\n\n/**\n * True for loopback / link-local / private / unique-local / CGNAT / multicast\n * addresses. IPv6 is judged on its PARSED BYTES, not on string prefixes\n * (v0.19.0): `0:0:0:0:0:0:0:1`, `::0:1` and `::ffff:7f00:1` all denote `::1` /\n * `127.0.0.1` and were previously ALLOWED, i.e. the SSRF guard was bypassable.\n * IPv4-mapped (`::ffff:0:0/96`), IPv4-compatible (`::/96`) and 6to4\n * (`2002::/16`) forms embed an IPv4 address and are judged as that address.\n */\nexport function isPrivateAddress(address: string): boolean {\n const raw = address.trim().toLowerCase()\n const ip = raw.startsWith('[') && raw.endsWith(']') ? raw.slice(1, -1) : raw\n const family = isIP(ip)\n if (family === 4) return PRIVATE_V4_PATTERNS.some((re) => re.test(ip))\n if (family !== 6) return false\n const bytes = ipv6ToBytes(ip)\n // Unparseable but accepted by isIP(): fail closed.\n if (bytes === null) return true\n const isZero = (slice: number[]): boolean => slice.every((b) => b === 0)\n const at = (index: number): number => bytes[index] ?? 0\n const v4 = (offset: number): string => `${at(offset)}.${at(offset + 1)}.${at(offset + 2)}.${at(offset + 3)}`\n if (isZero(bytes)) return true // ::\n if (isZero(bytes.slice(0, 15)) && at(15) === 1) return true // ::1\n if ((at(0) & 0xfe) === 0xfc) return true // fc00::/7 unique-local\n if (at(0) === 0xfe && (at(1) & 0xc0) === 0x80) return true // fe80::/10\n if (at(0) === 0xff) return true // ff00::/8 multicast\n if (at(0) === 0x20 && at(1) === 0x01 && at(2) === 0x0d && at(3) === 0xb8) return true // 2001:db8::/32\n // IPv4-mapped ::ffff:a.b.c.d\n if (isZero(bytes.slice(0, 10)) && at(10) === 0xff && at(11) === 0xff) return isPrivateAddress(v4(12))\n // 6to4 2002:<v4>:… and NAT64 64:ff9b::<v4>\n if (at(0) === 0x20 && at(1) === 0x02) return isPrivateAddress(v4(2))\n if (at(0) === 0x00 && at(1) === 0x64 && at(2) === 0xff && at(3) === 0x9b) return isPrivateAddress(v4(12))\n // IPv4-compatible ::a.b.c.d (deprecated, still routed by some stacks)\n if (isZero(bytes.slice(0, 12))) return isPrivateAddress(v4(12))\n return false\n}\n\n/**\n * SSRF guard for a clip image URL. The bridge downloads on behalf of a page in\n * the user's browser, so it must never become a proxy into the local network\n * (or a cloud metadata endpoint). Rejects non-http(s) schemes, loopback/LAN\n * hostnames, literal private addresses, and public names that RESOLVE to a\n * private address. Redirects are re-validated per hop by the caller.\n *\n * v0.20.0: this used to be a second, hand-maintained copy of the policy inside\n * `resolvePublicTarget` (the two had to be kept in sync by hand). It now simply\n * performs the same resolve-and-validate step and discards the pinned target;\n * `downloadClipImage` still pins the approved address, so the check is not\n * duplicated at connect time either.\n */\nexport async function assertPublicImageUrl(rawUrl: string): Promise<void> {\n await resolvePublicTarget(rawUrl)\n}\n\n/** One hop's outcome: either a redirect target, or the downloaded bytes. */\ninterface ImageHopResult {\n buffer: Buffer\n type: string | undefined\n redirect?: string\n}\n\n/** A validated URL plus the exact address the guard approved for connecting. */\ninterface PublicTarget {\n url: URL\n address: string\n family: number\n}\n\n/**\n * Resolve one image URL to a PUBLIC address and PIN it. The guard validates the\n * DNS answer here; the request then connects to that exact address (custom\n * `lookup`), so a rebinding domain cannot pass the check with a public answer\n * and then be re-resolved to 127.0.0.1 at connect time (v0.19.0 — the previous\n * implementation validated the name and then let `fetch` resolve it again).\n */\nasync function resolvePublicTarget(rawUrl: string): Promise<PublicTarget> {\n let url: URL\n try {\n url = new URL(rawUrl)\n } catch {\n throw new Error('图片地址不是合法 URL')\n }\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n throw new Error(`不支持的图片协议 ${url.protocol}`)\n }\n const host = url.hostname.toLowerCase()\n if (host === 'localhost' || host.endsWith('.localhost') || host.endsWith('.local') || host.endsWith('.internal') || host.endsWith('.home.arpa')) {\n throw new Error('拒绝下载内网主机名的图片')\n }\n const literal = host.startsWith('[') && host.endsWith(']') ? host.slice(1, -1) : host\n const literalFamily = isIP(literal)\n if (literalFamily !== 0) {\n if (isPrivateAddress(literal)) throw new Error('拒绝下载内网地址的图片')\n return { url, address: literal, family: literalFamily }\n }\n let addresses: Array<{ address: string; family: number }>\n try {\n addresses = await lookup(host, { all: true })\n } catch {\n throw new Error(`图片主机无法解析:${host}`)\n }\n if (addresses.length === 0) throw new Error(`图片主机无法解析:${host}`)\n if (addresses.some((entry) => isPrivateAddress(entry.address))) {\n throw new Error('拒绝下载解析到内网地址的图片')\n }\n const first = addresses[0]\n if (first === undefined) throw new Error(`图片主机无法解析:${host}`)\n return { url, address: first.address, family: first.family }\n}\n\n/** One GET against an already-validated, pinned target (no redirect following). */\nfunction requestImageHop(target: PublicTarget, referer: string, maxBytes: number, timeoutMs: number): Promise<ImageHopResult> {\n return new Promise<ImageHopResult>((resolveP, rejectP) => {\n const isHttps = target.url.protocol === 'https:'\n const requester = isHttps ? httpsGet : httpGet\n // Node calls `lookup(hostname, options, cb)`; answer with the address the\n // SSRF guard approved so DNS is never consulted a second time.\n const pinnedLookup: LookupFunction = (_hostname, options, callback) => {\n if (options.all === true) {\n callback(null, [{ address: target.address, family: target.family }])\n return\n }\n callback(null, target.address, target.family)\n }\n const request = requester({\n protocol: target.url.protocol,\n hostname: target.url.hostname,\n port: target.url.port.length > 0 ? Number(target.url.port) : (isHttps ? 443 : 80),\n path: `${target.url.pathname}${target.url.search}`,\n method: 'GET',\n headers: {\n 'user-agent': 'Mozilla/5.0 (compatible; dsh-tiddlywiki clip bridge)',\n referer,\n accept: 'image/*,*/*;q=0.8',\n // No transparent decompression: the byte cap must apply to what we store.\n 'accept-encoding': 'identity',\n },\n lookup: pinnedLookup,\n }, (res) => {\n const status = res.statusCode ?? 0\n if (status >= 300 && status < 400) {\n const location = res.headers.location\n res.resume()\n if (location === undefined || location.length === 0) {\n rejectP(new Error(`重定向缺少 Location(HTTP ${status})`))\n return\n }\n resolveP({ buffer: Buffer.alloc(0), type: undefined, redirect: new URL(location, target.url).href })\n return\n }\n if (status < 200 || status >= 300) {\n res.resume()\n rejectP(new Error(`下载失败 HTTP ${status}`))\n return\n }\n // Cheap up-front rejection when the server declares a size.\n const declared = Number(res.headers['content-length'] ?? Number.NaN)\n if (Number.isFinite(declared) && declared > maxBytes) {\n res.destroy()\n rejectP(new Error('图片超过 15MB 上限'))\n return\n }\n const chunks: Buffer[] = []\n let size = 0\n res.on('data', (chunk: Buffer) => {\n size += chunk.length\n if (size > maxBytes) {\n // STREAMING cap: stop reading instead of buffering the whole body and\n // checking afterwards (a chunked or zip-bomb response used to fill\n // memory before the limit was applied).\n res.destroy()\n rejectP(new Error('图片超过 15MB 上限'))\n return\n }\n chunks.push(chunk)\n })\n res.on('end', () => {\n if (size === 0) {\n rejectP(new Error('下载内容为空'))\n return\n }\n resolveP({ buffer: Buffer.concat(chunks), type: res.headers['content-type'] })\n })\n res.on('error', rejectP)\n })\n request.on('timeout', () => { request.destroy(new Error('图片下载超时')) })\n request.on('error', rejectP)\n request.setTimeout(timeoutMs)\n })\n}\n\n/**\n * Download one clip image with the full SSRF posture: every hop is validated\n * AND pinned, redirects are followed manually (max `maxRedirects`), the byte\n * cap is enforced while streaming, and compression is disabled.\n */\nexport async function downloadClipImage(\n imageUrl: string,\n referer: string,\n options: { maxBytes?: number; maxRedirects?: number; timeoutMs?: number } = {},\n): Promise<{ buffer: Buffer; type: string | undefined }> {\n const maxBytes = options.maxBytes ?? MAX_IMAGE_BYTES\n const maxRedirects = options.maxRedirects ?? 3\n const timeoutMs = options.timeoutMs ?? 20_000\n let current = imageUrl\n for (let hop = 0; hop <= maxRedirects; hop++) {\n const target = await resolvePublicTarget(current)\n const result = await requestImageHop(target, referer, maxBytes, timeoutMs)\n if (result.redirect !== undefined) {\n current = result.redirect\n continue\n }\n return { buffer: result.buffer, type: result.type }\n }\n throw new Error(`图片重定向次数超过 ${maxRedirects} 次`)\n}\n\n/**\n * Pick a collision-free tiddler title: `base`, then `base(2)…(20)` while an\n * existing tiddler holds the name (so re-clipping the same page never\n * overwrites the previous clip), then a timestamp fallback.\n */\nexport async function resolveClipTitle(exists: (title: string) => Promise<boolean>, base: string): Promise<string> {\n if (!(await exists(base))) return base\n for (let i = 2; i <= 20; i++) {\n const candidate = `${base}(${i})`\n if (!(await exists(candidate))) return candidate\n }\n return `${base}(${new Date().toISOString().slice(0, 19).replace(/[T:]/g, '-')})`\n}\n\n/** Build the markdown tiddler for ONE TEXT-ONLY clip (no images). */\nexport function buildClipTiddler(opts: {\n title: string\n url: string\n text?: string\n tag: string\n source?: string\n at?: string\n extraTags?: string[]\n}): Tiddler {\n const at = opts.at ?? new Date().toISOString()\n const selection = typeof opts.text === 'string' ? opts.text.trim() : ''\n const lines = [`> 来源:${opts.url}`, '']\n if (selection.length > 0) lines.push(selection, '')\n lines.push('---', `剪藏时间:${at}`)\n const tags = [opts.tag, ...(opts.extraTags ?? [])].filter((t) => t.trim().length > 0)\n return {\n title: opts.title,\n text: lines.join('\\n'),\n type: 'text/markdown',\n ...(tags.length > 0 ? { tags } : {}),\n 'clip-url': opts.url,\n 'clip-source': opts.source ?? 'bookmarklet',\n 'clip-at': at,\n }\n}\n\n/**\n * Build the note tiddler for a clip WITH stored images: wikitext so the\n * attachments render inline via `[img[Title]]` (works in the embedded TW,\n * story view and /tw/render). Failed downloads degrade to plain URL lines.\n */\nexport function buildImageNoteTiddler(opts: {\n title: string\n url: string\n text?: string\n tag: string\n source?: string\n at?: string\n stored: string[]\n failed: Array<{ url: string; error: string }>\n extraTags?: string[]\n}): Tiddler {\n const at = opts.at ?? new Date().toISOString()\n const selection = typeof opts.text === 'string' ? opts.text.trim() : ''\n const lines = [`> 来源:${opts.url}`, '']\n if (selection.length > 0) lines.push(selection, '')\n lines.push('---', '!! 图片')\n for (const title of opts.stored) lines.push(`[img[${title}]]`)\n for (const f of opts.failed) lines.push(`* ${f.url}(${f.error})`)\n lines.push('', '---', `剪藏时间:${at}`)\n const tags = [opts.tag, ...(opts.extraTags ?? [])].filter((t) => t.trim().length > 0)\n return {\n title: opts.title,\n text: lines.join('\\n'),\n type: 'text/vnd.tiddlywiki',\n ...(tags.length > 0 ? { tags } : {}),\n 'clip-url': opts.url,\n 'clip-source': opts.source ?? 'bookmarklet',\n 'clip-at': at,\n }\n}\n\n/** Build a binary attachment tiddler: `type: <mime>`, `text: <base64>`. */\nexport function buildBinaryTiddler(opts: {\n title: string\n mime: string\n buffer: Uint8Array | Buffer\n srcUrl: string\n noteTitle: string\n tag: string\n source?: string\n at?: string\n}): Tiddler {\n const at = opts.at ?? new Date().toISOString()\n const tags = [opts.tag].filter((t) => t.trim().length > 0)\n return {\n title: opts.title,\n type: opts.mime,\n text: Buffer.from(opts.buffer).toString('base64'),\n ...(tags.length > 0 ? { tags } : {}),\n 'clip-url': opts.srcUrl,\n 'clip-source': opts.source ?? 'bookmarklet',\n 'clip-at': at,\n 'clip-note': opts.noteTitle,\n }\n}\n\n/** Known image extensions → mime (used when a CDN mislabels as octet-stream). */\nconst IMAGE_EXT_MIME: Record<string, string> = {\n png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif',\n webp: 'image/webp', svg: 'image/svg+xml', avif: 'image/avif', bmp: 'image/bmp', ico: 'image/x-icon',\n}\n\n/**\n * Decide the stored mime for a downloaded image: an `image/*` content-type\n * wins; otherwise a known image extension in the URL rescues CDNs that serve\n * `application/octet-stream`. null = not (recognizably) an image.\n */\nexport function pickImageMime(contentType: string | null, url: string): string | null {\n const ct = ((contentType ?? '').split(';')[0] ?? '').trim().toLowerCase()\n if (ct.startsWith('image/')) return ct\n const ext = ((url.split('?')[0] ?? '').split('.').pop() ?? '').toLowerCase()\n if (ct === 'application/octet-stream' && IMAGE_EXT_MIME[ext] !== undefined) return IMAGE_EXT_MIME[ext]\n return null\n}\n\n/** File extension for a stored image mime. */\nexport function imageExtensionForMime(mime: string): string {\n const map: Record<string, string> = {\n 'image/png': 'png', 'image/jpeg': 'jpg', 'image/gif': 'gif', 'image/webp': 'webp',\n 'image/svg+xml': 'svg', 'image/avif': 'avif', 'image/bmp': 'bmp', 'image/x-icon': 'ico',\n }\n if (map[mime] !== undefined) return map[mime]\n const subtype = (mime.split('/')[1] ?? 'img').replace(/[^a-z0-9]/gi, '')\n return subtype.length > 0 ? subtype.slice(0, 8) : 'img'\n}\n\nfunction cleanStr(value: unknown): string {\n return typeof value === 'string' ? value : ''\n}\n\n/** Validate a clip payload → normalized value or an error. */\nexport function parseClipPayload(body: unknown): { ok: true; value: ClipPayload } | { ok: false; error: string } {\n if (typeof body !== 'object' || body === null || Array.isArray(body)) {\n return { ok: false, error: '请求体必须是 JSON 对象' }\n }\n const raw = body as Record<string, unknown>\n const title = cleanStr(raw.title).trim().slice(0, 300)\n const url = cleanStr(raw.url).trim().slice(0, 2000)\n const text = cleanStr(raw.text).slice(0, MAX_CLIP_TEXT_LENGTH)\n const source = cleanStr(raw.source).trim().slice(0, 40)\n let extraTags: string[] = []\n if (Array.isArray(raw.tags)) {\n extraTags = raw.tags.filter((t): t is string => typeof t === 'string').map((t) => t.trim()).filter(Boolean).slice(0, 10)\n }\n let images: string[] = []\n if (Array.isArray(raw.images)) {\n images = raw.images\n .filter((u): u is string => typeof u === 'string')\n .map((u) => u.trim())\n .filter((u) => u.length > 0 && u.length <= 2000)\n .slice(0, MAX_CLIP_IMAGES)\n }\n if (title.length === 0) return { ok: false, error: '缺少 title(页面标题)' }\n if (url.length === 0) return { ok: false, error: '缺少 url(页面地址)' }\n return { ok: true, value: { title, url, text, tags: extraTags, source, images } }\n}\n\n/** CORS + no-store headers for every bridge response. */\nconst CORS_HEADERS: Record<string, string> = {\n 'access-control-allow-origin': '*',\n 'access-control-allow-methods': 'POST, GET, OPTIONS',\n 'access-control-allow-headers': 'content-type, x-clip-token',\n 'access-control-allow-private-network': 'true',\n 'access-control-max-age': '86400',\n 'cache-control': 'no-store',\n}\n\n/**\n * The loopback clip bridge. `start(port)` binds once (127.0.0.1); config\n * (enabled/token/tag) is re-resolved per request through `deps.getConfig`,\n * so settings-page saves apply live. `stop()` is idempotent.\n */\nexport class ClipBridge {\n private server: Server | undefined\n private boundPort = 0\n\n constructor(private readonly deps: ClipBridgeDeps) {}\n\n /** The port currently bound (0 until start succeeds). */\n get port(): number {\n return this.boundPort\n }\n\n /** Bind 127.0.0.1:port; rejects on EADDRINUSE etc. (caller logs & carries on). */\n start(port: number): Promise<void> {\n if (this.server !== undefined) return Promise.resolve()\n const server = createServer((req, res) => {\n void this.handle(req, res).catch((err: unknown) => {\n const message = err instanceof Error ? err.message : String(err)\n this.deps.log?.(`request failed: ${message}`)\n if (!res.headersSent) {\n this.respond(res, { ok: false, error: '内部错误' }, 500)\n } else {\n res.destroy()\n }\n })\n })\n // Malformed HTTP must not crash the bridge (default behavior would emit\n // an unhandled 'clientError').\n server.on('clientError', (_err, socket) => {\n socket.end('HTTP/1.1 400 Bad Request\\r\\n\\r\\n')\n })\n this.server = server\n return new Promise<void>((resolve, reject) => {\n const onError = (err: Error): void => {\n server.removeListener('listening', onListening)\n if (this.server === server) this.server = undefined\n this.deps.log?.(`bind 127.0.0.1:${port} failed: ${err.message}`)\n reject(err)\n }\n const onListening = (): void => {\n server.removeListener('error', onError)\n // Replace the bind-time handler with a PERMANENT one (v0.19.3): without\n // any 'error' listener a later server error (e.g. EMFILE/ECONNRESET on a\n // accept) is an unhandled 'error' event → Node throws → the whole dsh\n // web process dies. Log and keep serving.\n server.on('error', (err: Error) => {\n this.deps.log?.(`server error after listen: ${err.message}`)\n })\n const addr = server.address() as AddressInfo\n this.boundPort = addr.port\n resolve()\n }\n server.once('error', onError)\n server.once('listening', onListening)\n server.listen(port, '127.0.0.1')\n })\n }\n\n /** Close the listener (idempotent; awaited by the host teardown). */\n stop(): Promise<void> {\n const server = this.server\n this.server = undefined\n this.boundPort = 0\n if (server === undefined) return Promise.resolve()\n // `server.close()` only resolves once every open connection ends, and the\n // bookmarklet's keep-alive socket can hold it open forever — the effect\n // disposer (plugin unload) would then hang (v0.19.3). Force-close the\n // connections and cap the wait.\n return new Promise<void>((resolve) => {\n let done = false\n const finish = (): void => {\n if (done) return\n done = true\n resolve()\n }\n try {\n ;(server as unknown as { closeAllConnections?: () => void }).closeAllConnections?.()\n } catch { /* not available on this Node */ }\n server.close(() => finish())\n setTimeout(finish, 1_000).unref?.()\n })\n }\n\n private respond(res: ServerResponse, payload: unknown, status = 200): void {\n res.writeHead(status, { 'content-type': 'application/json; charset=utf-8', ...CORS_HEADERS })\n res.end(JSON.stringify(payload))\n }\n\n private async handle(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const cfg = this.deps.getConfig()\n const urlPath = (req.url ?? '/').split('?')[0] ?? '/'\n\n // CORS preflight is answered unconditionally (Cross-Origin requests from a\n // bookmarklet always preflight: content-type application/json is not a\n // \"simple\" header, and Chromium's PNA wants Access-Control-Allow-Private-Network).\n if (req.method === 'OPTIONS') {\n res.writeHead(204, CORS_HEADERS)\n res.end()\n return\n }\n // DNS-rebinding defense BEFORE anything else: only loopback Host headers.\n if (!hostAllowed(req.headers.host, this.boundPort)) {\n this.respond(res, { ok: false, error: 'Forbidden host' }, 403)\n return\n }\n\n if (req.method === 'GET' && (urlPath === '/' || urlPath === '/health')) {\n // Minimal liveness answer: CORS is `*`, so any web page can read this.\n // `tag` / `tokenSet` used to be echoed here — a page could fingerprint\n // the bridge (and whether a token is configured) without knowing it\n // (v0.19.0).\n this.respond(res, {\n ok: true,\n name: 'dsh-tiddlywiki clip bridge',\n enabled: cfg.enabled,\n })\n return\n }\n\n if (req.method !== 'POST' || urlPath !== '/clip') {\n this.respond(res, { ok: false, error: `未知路径 ${req.method} ${urlPath}` }, 404)\n return\n }\n\n // Enabled + token gates: the port stays bound even while disabled so the\n // settings-page toggle works without a dsh web restart; a disabled bridge\n // answers 503 (and the token check never runs for it).\n if (!cfg.enabled) {\n this.respond(res, { ok: false, error: '剪藏桥未启用:请在 DSH 设置 → 常规配置 勾选「启用本地剪藏桥」' }, 503)\n return\n }\n if (cfg.token.length > 0) {\n const got = req.headers['x-clip-token']\n if (typeof got !== 'string' || !safeTokenEqual(got, cfg.token)) {\n this.respond(res, { ok: false, error: 'token 校验失败' }, 401)\n return\n }\n }\n\n let body: unknown\n try {\n body = JSON.parse(await readBody(req, MAX_CLIP_BODY_BYTES)) as unknown\n } catch {\n this.respond(res, { ok: false, error: '请求体不是合法的 JSON(或过大)' }, 400)\n return\n }\n const parsed = parseClipPayload(body)\n if (!parsed.ok) {\n this.respond(res, { ok: false, error: parsed.error }, 400)\n return\n }\n const { title, url, text, tags, source, images } = parsed.value\n\n let resolvedTitle: string\n try {\n resolvedTitle = await resolveClipTitle((t) => this.deps.exists(t), title)\n } catch {\n this.respond(res, { ok: false, error: 'TiddlyWiki 服务暂不可用,请稍后重试' }, 503)\n return\n }\n\n const at = new Date().toISOString()\n const sourceName = source.length > 0 ? source : 'bookmarklet'\n\n // Download + store the chosen images as binary attachment tiddlers.\n const stored: string[] = []\n const failed: Array<{ url: string; error: string }> = []\n const imageResults: ClipImageResult[] = []\n for (let i = 0; i < images.length; i++) {\n const imageUrl = images[i]\n if (imageUrl === undefined) continue\n try {\n // SSRF gate (policy lives here, not in the caller's downloader): never\n // fetch a loopback/LAN/metadata URL on behalf of a web page.\n await assertPublicImageUrl(imageUrl)\n const { buffer, type } = await this.deps.download(imageUrl, url)\n if (buffer.length > MAX_IMAGE_BYTES) {\n throw new Error('图片超过 15MB 上限')\n }\n const mime = pickImageMime(type, imageUrl)\n if (mime === null) {\n throw new Error('内容不是可识别的图片')\n }\n const ext = imageExtensionForMime(mime)\n const imageTitle = await resolveClipTitle((t) => this.deps.exists(t), `${resolvedTitle} 图片 ${i + 1}.${ext}`)\n await this.deps.write(buildBinaryTiddler({\n title: imageTitle,\n mime,\n buffer,\n srcUrl: imageUrl,\n noteTitle: resolvedTitle,\n tag: cfg.tag,\n source: sourceName,\n at,\n }))\n stored.push(imageTitle)\n imageResults.push({ url: imageUrl, ok: true, title: imageTitle })\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n failed.push({ url: imageUrl, error: message })\n imageResults.push({ url: imageUrl, ok: false, error: message })\n }\n }\n\n // The note: when images were chosen (stored or failed), wikitext with\n // `[img[Title]]` embeds + failed ones degrade to URL lines; a pure\n // text-only clip stays markdown as before.\n const noteTiddler = stored.length > 0 || images.length > 0\n ? buildImageNoteTiddler({\n title: resolvedTitle, url, ...(text.length > 0 ? { text } : {}),\n tag: cfg.tag, source: sourceName, at, stored, failed, extraTags: tags,\n })\n : buildClipTiddler({\n title: resolvedTitle, url, ...(text.length > 0 ? { text } : {}),\n tag: cfg.tag, source: sourceName, at, extraTags: tags,\n })\n try {\n await this.deps.write(noteTiddler)\n } catch {\n // The note is the primary artifact — fail the clip (images may already\n // be stored; they are still referenced by the note if it lands later).\n this.respond(res, { ok: false, error: 'TiddlyWiki 服务暂不可用,剪藏未写入' }, 503)\n return\n }\n this.respond(res, { ok: true, title: resolvedTitle, url, tag: cfg.tag, source: sourceName, images: imageResults })\n }\n}","/**\n * Self-contained replacements for the @deepseek-ai runtime imports the host\n * half must NEVER take from npm-mirror SDK packages (dsh-home-paths,\n * dsh-tools' defineTool).\n *\n * Why (design doc §4.4, taskboard lesson): a published copy must not resolve\n * `@deepseek-ai/dsh-tools` from the profile's node_modules — an npm-mirror\n * dsh-tools there shadows the CLI-internal build for the WHOLE base layer and\n * breaks the agent loop. Everything here is a pure, structure-compatible\n * reimplementation of the exact behavior the registry relies on:\n *\n * - `dshHomePath` mirrors `join(resolve(env.DSH_HOME ?? ~/.dsh), ...segments)`;\n * - `defineTool` compiles author-facing parameter specs into the same raw\n * JSON-Schema subset the registry expects and pre-validates model arguments.\n *\n * @module dsh-tiddlywiki/sdk\n */\nimport { homedir } from 'node:os'\nimport { join, resolve } from 'node:path'\n\n/** The DSH user home (DSH_HOME overrides). */\nexport function dshHomePath(...segments: string[]): string {\n const override = process.env.DSH_HOME\n const home = resolve(override !== undefined && override.length > 0 ? override : join(homedir(), '.dsh'))\n return join(home, ...segments)\n}\n\n/** Author-facing scalar spec. */\ninterface ScalarSpec {\n readonly type: 'string' | 'number' | 'integer' | 'boolean' | 'null'\n readonly description?: string\n readonly enum?: readonly unknown[]\n readonly const?: unknown\n}\n\n/** Author-facing object spec (additionalProperties is mandatory). */\ninterface ObjectSpec {\n readonly type: 'object'\n readonly additionalProperties: boolean\n readonly description?: string\n /** Nested properties may also declare `required` (array items etc.). */\n readonly properties?: Readonly<Record<string, ParameterSpec>>\n}\n\n/** Author-facing value spec. */\ntype ValueSpec = ScalarSpec | ObjectSpec | { readonly type: 'json'; readonly description?: string } | { readonly type: 'array'; readonly items?: ValueSpec; readonly description?: string }\n\n/** Author-facing parameter entry (a value spec plus top-level required). */\ntype ParameterSpec = ValueSpec & { readonly required?: boolean }\n\n/** Raw JSON-Schema subset node. */\ntype RawSchema = Record<string, unknown>\n\n/** Compile one value spec to the raw subset (json → annotation-only). */\nfunction compileValue(spec: ValueSpec): RawSchema {\n const node: RawSchema = {}\n const description = (spec as { description?: string }).description\n if (typeof description === 'string' && description.length > 0) node.description = description\n const type = (spec as { type?: string }).type\n if (type === undefined || type === 'json') return node\n if (type === 'object') {\n const objectSpec = spec as ObjectSpec\n node.type = 'object'\n node.additionalProperties = objectSpec.additionalProperties\n if (objectSpec.properties !== undefined) {\n const compiled = compilePropertyMap(objectSpec.properties)\n node.properties = compiled.properties\n // Nested objects also carry their own `required` list (array items, etc.).\n if (compiled.required !== undefined) node.required = compiled.required\n }\n return node\n }\n if (type === 'array') {\n node.type = 'array'\n const items = (spec as { items?: ValueSpec }).items\n if (items !== undefined) node.items = compileValue(items)\n return node\n }\n node.type = type\n const enumValues = (spec as ScalarSpec).enum\n if (enumValues !== undefined) node.enum = [...enumValues]\n const constValue = (spec as ScalarSpec).const\n if (constValue !== undefined) node.const = constValue\n return node\n}\n\n/** Compile a property map: properties + collected required list. */\nfunction compilePropertyMap(spec: Readonly<Record<string, ParameterSpec>>): { properties: Record<string, RawSchema>; required?: string[] } {\n const properties: Record<string, RawSchema> = {}\n const required: string[] = []\n for (const [name, entry] of Object.entries(spec)) {\n const { required: isRequired, ...valueSpec } = entry as ParameterSpec & Record<string, unknown>\n properties[name] = compileValue(valueSpec as ValueSpec)\n if (isRequired === true) required.push(name)\n }\n return required.length > 0 ? { properties, required } : { properties }\n}\n\n/** Does a JS value match a raw-subset scalar type? */\nfunction matchesScalarType(value: unknown, type: string): boolean {\n switch (type) {\n case 'string': return typeof value === 'string'\n case 'number': return typeof value === 'number'\n case 'integer': return typeof value === 'number' && Number.isInteger(value)\n case 'boolean': return typeof value === 'boolean'\n case 'null': return value === null\n default: return true\n }\n}\n\n/** Validate a value against the compiled subset; returns path-qualified violations. */\nfunction validateValue(schema: RawSchema, value: unknown, path: string): string[] {\n if (typeof schema.type !== 'string' || schema.type.length === 0) return []\n if (schema.type === 'object') {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return [`${path} must be an object`]\n const violations: string[] = []\n const present = value as Record<string, unknown>\n for (const key of (schema.required as string[] | undefined) ?? []) {\n if (!(key in present)) violations.push(`${path}.${key} is required`)\n }\n if (schema.additionalProperties === false) {\n const known = new Set(Object.keys((schema.properties as Record<string, RawSchema> | undefined) ?? {}))\n for (const key of Object.keys(present)) {\n if (!known.has(key)) violations.push(`${path}.${key} is not a declared property`)\n }\n }\n for (const [key, child] of Object.entries((schema.properties as Record<string, RawSchema> | undefined) ?? {})) {\n if (key in present) violations.push(...validateValue(child, present[key], `${path}.${key}`))\n }\n return violations\n }\n if (schema.type === 'array') {\n if (!Array.isArray(value)) return [`${path} must be an array`]\n const violations: string[] = []\n const items = schema.items as RawSchema | undefined\n if (items !== undefined) {\n value.forEach((item, index) => { violations.push(...validateValue(items, item, `${path}[${index}]`)) })\n }\n return violations\n }\n if (!matchesScalarType(value, schema.type)) return [`${path} must be ${schema.type}`]\n const enumValues = schema.enum as unknown[] | undefined\n if (enumValues !== undefined && !enumValues.some(v => v === value)) {\n return [`${path} must be one of ${enumValues.map(String).join(', ')}`]\n }\n const constValue = (schema as { const?: unknown }).const\n if (constValue !== undefined && constValue !== value) {\n return [`${path} must be ${String(constValue)}`]\n }\n return []\n}\n\n/** Options shape we consume (a structural subset of the SDK's defineTool). */\nexport interface DefineToolOptions<A, V> {\n readonly name: string\n readonly description: string\n readonly parameters: Readonly<Record<string, ParameterSpec>>\n readonly output: {\n render(args: A, value: V): Array<{ type: 'text'; text: string }>\n }\n execute(args: A, exec: unknown): Promise<V>\n}\n\n/** A registry-ready tool definition (structure-compatible with the SDK's). */\nexport interface ToolDefinition<A = unknown, V = unknown> {\n readonly name: string\n readonly description: string\n readonly parameters: RawSchema\n readonly output: {\n readonly schema: RawSchema\n render(args: A, value: V): Array<{ type: 'text'; text: string }>\n }\n execute(args: A, exec: unknown): Promise<V>\n}\n\n/**\n * Define a first-party tool: compile the parameter spec, pre-validate\n * arguments, and pass through the execution.\n */\nexport function defineTool<A extends Record<string, unknown>, V>(options: DefineToolOptions<A, V>): ToolDefinition<A, V> {\n const compiled = compilePropertyMap(options.parameters as Readonly<Record<string, ParameterSpec>>)\n const parameters: RawSchema = { type: 'object', properties: compiled.properties }\n if (compiled.required !== undefined) parameters.required = compiled.required\n const userExecute = options.execute\n return {\n name: options.name,\n description: options.description,\n parameters,\n output: {\n // The registry's render contract only consumes `render()`; the declared\n // `schema` is kept as the empty raw node it always effectively was (the\n // per-tool `schema: {type:'json'}` literals were never read — removed in\n // v0.20.0 so the option list matches what the code does).\n schema: {},\n render(args, value) {\n return options.output.render(args, value)\n },\n },\n async execute(args, exec) {\n const violations = validateValue(parameters, args, 'arguments')\n if (violations.length > 0) {\n throw new Error(`Error: invalid arguments: ${violations.join('; ')}`)\n }\n return userExecute(args, exec)\n },\n }\n}\n","/**\n * The `tiddlywiki_*` agent tools (design doc §11, D8) plus the extension\n * point: `registerTiddlywikiTools(ctx, deps)` registers tools list-style, so a\n * new tool is just one more `defineTool` in the array — index.ts never changes.\n *\n * Toolset (v0.19):\n * search / get / put / batch_put / append / rename / delete / trash /\n * backlinks / attach / lint / recent / list_tags / git_sync / git_resolve\n *\n * RENDER CONTRACT (design doc §4.3): the registry feeds `output.render(args,\n * value)` into the loop — the model sees ONLY the rendered text, never the raw\n * JSON `value`. Every render must carry the complete facts an agent needs to\n * act (titles, tags, snippets, git state); a terse UI summary starves it.\n *\n * @module dsh-tiddlywiki/host/tools\n */\nimport { defineTool } from '../sdk.ts'\nimport { readFile, stat } from 'node:fs/promises'\nimport { basename, extname, isAbsolute, join } from 'node:path'\nimport { MISSING_TYPE_FILTER, isBinaryType, toIsoDateString } from './tw-api.ts'\nimport type { TiddlyWebClient, Tiddler } from './tw-api.ts'\nimport type { GitFace, GitStatusView } from './git.ts'\nimport { downloadClipImage } from './clip-bridge.ts'\nimport { flushPendingWrites } from './seeds.ts'\nimport { snippetOf } from './text-util.ts'\nimport {\n assertNoConflict,\n buildWriteTiddler,\n cleanTiddler,\n flattenTiddlerFields,\n normalizeTagArg,\n} from './write-policy.ts'\n\nexport { AGENT_WRITTEN_TAG, DEFAULT_NOTE_TYPE, HUMAN_EDITED_TAG } from './write-policy.ts'\n\nimport type { PromptToolSummary } from './prompt.ts'\n\n/**\n * Summaries of the tools registered by the LAST `registerTiddlywikiTools()`\n * pass — the single source for the `full` prompt catalogue and for\n * `scripts/verify-prompt.mjs` (v0.21.0).\n *\n * History: the prompt carried a HAND-WRITTEN parameter catalogue that was last\n * updated in v0.19.0 and silently drifted 6 signatures behind the tools by\n * v0.20.1. Collecting them here from the same definitions that reach the model\n * makes that class of drift impossible.\n */\nconst REGISTERED_TOOL_SUMMARY: PromptToolSummary[] = []\n\n/** Tool summaries of the last registration pass (empty before one has run). */\nexport function tiddlywikiToolSummary(): readonly PromptToolSummary[] {\n return REGISTERED_TOOL_SUMMARY\n}\n\n/** Structural shape the collector needs from a registry-ready tool. */\ninterface RegistrableTool {\n readonly name: string\n readonly parameters: Record<string, unknown>\n}\n\n\n/** Structural tool-registry face (subset of the dsh tools service). */\nexport interface ToolsCtx {\n tools: { register(tool: unknown): () => void }\n}\n\nexport interface ToolsDeps {\n /** Lazy TW client — undefined while the service is not up. */\n wiki: () => TiddlyWebClient | undefined\n git: GitFace\n wikiPath: () => string\n /** Debounced auto-commit touch (fires after our writes). */\n autoCommit: () => void\n /** Restart the TW child (same port). Called after a pull that changed the\n * working tree, so the server drops its stale in-memory snapshot and the\n * agent sees the pulled content. Optional — absent in headless contexts. */\n restartWiki?: () => Promise<void>\n}\n\n/**\n * Snippet centred on the FIRST match of `query` (v0.19.0). The old snippet was\n * always the first 160 characters of the note, so a hit deep inside a long note\n * showed the model text that did not contain the term it searched for.\n */\nfunction snippetAround(text: string, query: string, max = 160): string {\n const flat = text.replace(/\\s+/g, ' ').trim()\n if (flat.length <= max) return flat\n const needle = query.trim().toLowerCase()\n if (needle.length === 0) return `${flat.slice(0, max)}…`\n const at = flat.toLowerCase().indexOf(needle)\n if (at < 0) return `${flat.slice(0, max)}…`\n const half = Math.max(0, Math.floor((max - needle.length) / 2))\n const start = Math.max(0, at - half)\n const end = Math.min(flat.length, start + max)\n const prefix = start > 0 ? '…' : ''\n const suffix = end < flat.length ? '…' : ''\n return `${prefix}${flat.slice(start, end)}${suffix}`\n}\n\n/** Trash namespace for soft-deleted tiddlers (system titles: never listed by\n * the default recipe filter, so deleted notes stop appearing in search/recent\n * while staying recoverable). */\nexport const TRASH_PREFIX = '$:/dsh-tiddlywiki/trash/'\n\n/**\n * Trash INDEX tiddler. A `$:/`-titled tiddler cannot be ENUMERATED through the\n * recipe listing at all: a fresh wiki has `$:/config/SyncSystemTiddlersFromServer\n * = \"no\"`, and `get-tiddlers-json.js` then appends `+[!is[system]]` to every\n * filter (verified). The trash therefore keeps its own index and reads it with a\n * plain GET (which works for system titles). (v0.19.0)\n */\nexport const TRASH_INDEX_TITLE = '$:/dsh-tiddlywiki/trash-index'\n\ninterface TrashIndexEntry { trash: string; of: string; at: string }\n\n/** Build the trash title that holds one soft-deleted tiddler. */\nfunction trashTitleFor(title: string, at = new Date()): string {\n const stamp = at.toISOString().replace(/[:.]/g, '-')\n return `${TRASH_PREFIX}${stamp}/${title}`\n}\n\n/**\n * Read the trash index.\n *\n * `readOk:false` distinguishes「索引读不到」(a failure: 5xx/timeout/auth — the\n * tool layer MUST stop, because overwriting the index with whatever we managed\n * to read would drop every earlier entry and orphan those trashed tiddlers) from\n *「索引不存在」(404 = a brand-new wiki with an empty trash) and「JSON 坏了」\n * (rebuildable: the trash tiddlers themselves are still there, only the index\n * entries are lost).\n *\n * Swallowing the error into `[]` was the v0.19.4 defect: one transient failure\n * during `tiddlywiki_delete` rewrote the whole index to a single-entry array.\n */\nasync function readTrashIndex(wiki: TiddlyWebClient): Promise<{ readOk: boolean; corrupted: boolean; entries: TrashIndexEntry[] }> {\n let tiddler: Tiddler | undefined\n try {\n // Only 404 yields undefined (tw-api): any other throw is a real read failure.\n tiddler = await wiki.get(TRASH_INDEX_TITLE)\n } catch {\n return { readOk: false, corrupted: false, entries: [] }\n }\n if (tiddler === undefined || typeof tiddler.text !== 'string' || tiddler.text.trim().length === 0) {\n return { readOk: true, corrupted: false, entries: [] }\n }\n try {\n const parsed = JSON.parse(tiddler.text) as unknown\n if (!Array.isArray(parsed)) return { readOk: true, corrupted: true, entries: [] }\n const entries = parsed.filter((entry): entry is TrashIndexEntry => {\n if (typeof entry !== 'object' || entry === null) return false\n const e = entry as Record<string, unknown>\n return typeof e.trash === 'string' && typeof e.of === 'string'\n }).map((e) => ({ trash: e.trash, of: e.of, at: typeof e.at === 'string' ? e.at : '' }))\n return { readOk: true, corrupted: false, entries }\n } catch {\n return { readOk: true, corrupted: true, entries: [] }\n }\n}\n\n/** Persist the trash index. */\nasync function writeTrashIndex(wiki: TiddlyWebClient, entries: TrashIndexEntry[]): Promise<void> {\n await wiki.put({ title: TRASH_INDEX_TITLE, text: JSON.stringify(entries, null, 2), type: 'application/json', tags: [] })\n}\n\n/** Raised when the trash index cannot be read: the operation must abort rather\n * than rebuild the index from an empty base (data-loss guard, v0.19.5). */\nexport class TrashIndexUnavailableError extends Error {\n constructor() {\n super('回收站索引暂时读不到(TiddlyWiki 可能正在重启或超时);为避免覆盖索引、丢回收站记录,本次操作已中止,请稍后重试。')\n this.name = 'TrashIndexUnavailableError'\n }\n}\n\n/** Mime types offered by `tiddlywiki_attach` for local files. */\nconst ATTACH_MIME_BY_EXT: Record<string, string> = {\n png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif',\n webp: 'image/webp', svg: 'image/svg+xml', avif: 'image/avif', bmp: 'image/bmp',\n ico: 'image/x-icon', pdf: 'application/pdf', zip: 'application/zip',\n txt: 'text/plain', md: 'text/markdown', json: 'application/json', csv: 'text/csv',\n mp3: 'audio/mpeg', wav: 'audio/wav', ogg: 'audio/ogg', mp4: 'video/mp4',\n epub: 'application/epub+zip',\n}\n\n/** Size cap for one attachment (mirrors the clip bridge's image cap). */\nexport const MAX_ATTACH_BYTES = 15 * 1024 * 1024\n\n/**\n * Count references to `target` inside wiki text: `[[target]]`,\n * `[[display|target]]`, `[[target|display]]` is NOT a reference to target as a\n * link target in TW (the first part is the text, the second the target), so\n * only the second position counts, plus `{{target}}` transclusions.\n */\nfunction countRefsTo(text: string, target: string): number {\n if (target.length === 0) return 0\n const escaped = target.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n const patterns = [\n new RegExp(`\\\\[\\\\[${escaped}\\\\]\\\\]`, 'g'),\n new RegExp(`\\\\[\\\\[[^\\\\]|]*\\\\|${escaped}\\\\]\\\\]`, 'g'),\n new RegExp(`\\\\{\\\\{${escaped}\\\\}\\\\}`, 'g'),\n ]\n let count = 0\n for (const re of patterns) count += (text.match(re) ?? []).length\n return count\n}\n\n/**\n * Junk tags produced by tooling mistakes rather than by a human. The live wiki\n * still carries `筛选器错误: Missing [ in filter expression` from the v0.16.22\n * filter bug — a lint must be able to find that class of damage again.\n */\nfunction isJunkTag(tag: string): boolean {\n if (tag.length === 0) return false\n if (/筛选器错误|Missing \\[|in filter expression/i.test(tag)) return true\n if (/[[\\]{}|<>]/.test(tag)) return true\n if (/^(in|filter|expression|Missing|tags:)$/i.test(tag)) return true\n if (/^[A-Za-z]:\\\\/.test(tag)) return true\n if (tag.trim() !== tag) return true\n return false\n}\n\n/**\n * tiddler 的非内容字段(自定义字段 + 内容类型 + 时间戳 + revision),供模型读。\n *\n * v0.19.1 修复:单条 GET 把自定义字段**嵌在 `fields` 里**,旧实现只把顶层条目\n * 抄一遍,于是渲染出来的 `字段:` 行是 `fields=[object Object]` —— `q`/`due`/\n * `clip-url`/`workspace` 这些笔记元数据对模型完全不可见(实测)。现在统一走\n * `flattenTiddlerFields()` 摊平,并显式带上 `revision`(乐观并发令牌)。\n */\nfunction pickFields(t: Tiddler): Record<string, unknown> {\n const out = flattenTiddlerFields(t)\n if (t.revision !== undefined) out.revision = t.revision\n return out\n}\n\n/**\n * 覆盖写入前后内容类型的差异(v0.20.1)。内容类型决定 TW 用哪个 parser,静默变化\n * 会让 CSS 被当 Markdown、Markdown 笔记被当 wikitext,所以覆盖路径必须把这个差异\n * 回执给模型。只有两边都拿得到 `type` 且不同才报告(新建 → 无 from)。\n */\nfunction typeChangeOf(existing: Tiddler | undefined, next: Tiddler): { typeChanged?: { from: string; to: string } } {\n if (existing === undefined) return {}\n const from = typeof existing.type === 'string' && existing.type.length > 0 ? existing.type : undefined\n const to = typeof next.type === 'string' && next.type.length > 0 ? next.type : undefined\n if (from === undefined || to === undefined || from === to) return {}\n return { typeChanged: { from, to } }\n}\n\n/**\n * Rewrite TiddlyWiki references to a title inside wiki text: `[[Title]]`,\n * `[[display|Title]]`, `{{Title}}` → the new title. Best-effort link/text\n * migration for tiddlywiki_rename; returns the rewritten text + hit count.\n */\nfunction rewriteRefs(text: string, oldTitle: string, newTitle: string): { text: string; count: number } {\n if (oldTitle.length === 0) return { text, count: 0 }\n const escaped = oldTitle.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n const re = new RegExp(`(\\\\[\\\\[[^\\\\]|]*\\\\|)${escaped}(\\\\]\\\\])|(\\\\[\\\\[)${escaped}(\\\\]\\\\])|(\\\\{\\\\{)${escaped}(\\\\}\\\\})`, 'g')\n let count = 0\n const out = text.replace(re, (...args: Array<string | number>) => {\n const p = args as string[]\n count++\n const prefix = p[1] ?? p[3] ?? p[5] ?? ''\n const suffix = p[2] ?? p[4] ?? p[6] ?? ''\n return `${prefix}${newTitle}${suffix}`\n })\n return { text: out, count }\n}\n\n/**\n * Insert `addition` at the end of the section introduced by `heading` (Markdown\n * `#`/`##`… or wikitext `!`/`!!`…). Falls back to appending at the end of the\n * document when the heading is not found. Used by tiddlywiki_append so an agent\n * can add to one section of a long note without rewriting the whole file.\n */\nfunction insertIntoSection(base: string, heading: string, addition: string): string {\n const lines = base.split('\\n')\n const headingText = (line: string): string | null => {\n const md = /^#{1,6}\\s+(.*)$/.exec(line)\n if (md !== null) return (md[1] ?? '').trim()\n const tw = /^!{1,6}\\s*(.*)$/.exec(line)\n if (tw !== null) return (tw[1] ?? '').trim()\n return null\n }\n const start = lines.findIndex((line) => headingText(line) === heading)\n if (start < 0) {\n return base.trim().length === 0 ? addition : `${base.replace(/\\s+$/, '')}\\n\\n${addition}`\n }\n let end = lines.length\n for (let i = start + 1; i < lines.length; i++) {\n if (headingText(lines[i] ?? '') !== null) {\n end = i\n break\n }\n }\n const before = lines.slice(0, end).join('\\n').replace(/\\s+$/, '')\n const after = lines.slice(end).join('\\n')\n const head = `${before}\\n\\n${addition}`\n return after.trim().length === 0 ? head : `${head}\\n\\n${after.replace(/^\\s+/, '')}`\n}\n\n\nexport function registerTiddlywikiTools(ctx: ToolsCtx, deps: ToolsDeps): Array<() => void> {\n const disposers: Array<() => void> = []\n // Re-collect on every pass: a hot reload re-registers the whole toolset, and\n // a stale entry must never linger in the prompt catalogue.\n REGISTERED_TOOL_SUMMARY.length = 0\n const register = (tool: RegistrableTool): void => {\n const properties = (tool.parameters.properties ?? {}) as Record<string, unknown>\n const required = Array.isArray(tool.parameters.required) ? (tool.parameters.required as string[]) : []\n REGISTERED_TOOL_SUMMARY.push({\n name: tool.name,\n params: Object.keys(properties).map((name) => ({ name, required: required.includes(name) })),\n })\n disposers.push(ctx.tools.register(tool))\n }\n\n /** Every read/write tool needs a live TW client — shared guard for the 8\n * wiki-facing tools (git tools operate on the repo path instead). */\n const requireWiki = (): TiddlyWebClient => {\n const wiki = deps.wiki()\n if (wiki === undefined) throw new Error('TiddlyWiki 服务未运行(tiddlywiki_status 可查)')\n return wiki\n }\n\n // ── tiddlywiki_search ────────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_search',\n description: '检索 TiddlyWiki 持久知识库:按关键词(可选 tags 数组 / since 修改时间 / type / field+value / limit)搜索非系统 tiddler,按相关度排序返回标题、标签、修改时间与命中处上下文片段。二进制 tiddler(图片等附件,正文为 base64)不参与检索。',\n parameters: {\n query: { type: 'string', description: '搜索关键词(大小写不敏感,子串匹配;命中标题/标签/正文,标题命中权重最高)', required: true },\n tags: { type: 'array', items: { type: 'string' }, description: '可选:要求同时包含的标签(AND)' },\n tag: { type: 'string', description: '可选:单个精确标签(与 tags 同为 AND)' },\n since: { type: 'string', description: '可选:ISO 时间(如 2026-09-01 或 2026-09-01T00:00:00Z),只返回修改时间不早于它的 tiddler' },\n type: { type: 'string', description: '可选:精确 tiddler 类型。⚠️ 不传则不限类型(推荐);插件默认把笔记写成 text/markdown,传 \"text/vnd.tiddlywiki\" 会把 Markdown 笔记全部排除' },\n field: { type: 'string', description: '可选:按自定义字段过滤(如 \"q\"、\"clip-url\"、\"workspace\")' },\n value: { type: 'string', description: '可选:field 必须等于该值(不传则只要求该字段存在)' },\n limit: { type: 'integer', description: '可选:返回条数上限(默认 30,最大 200)' },\n },\n output: {\n render: (_args, value: SearchResult) => {\n const filters: string[] = []\n if (value.tags.length > 0) filters.push(`tags=${value.tags.join(',')}`)\n if (value.since !== null) filters.push(`since=${value.since}`)\n if (value.type !== null) filters.push(`type=${value.type}`)\n if (value.field !== null) filters.push(`field=${value.field}${value.value !== null ? `=${value.value}` : ''}`)\n const head = `TiddlyWiki 搜索「${value.query}」${filters.length > 0 ? ` (${filters.join(' · ')})` : ''}:命中 ${value.total} 条(按相关度排序)。`\n const lines = [head]\n if (value.results.length === 0) lines.push('没有匹配的 tiddler。')\n for (const r of value.results) {\n const tags = r.tags.length > 0 ? ` [${r.tags.join(', ')}]` : ''\n const modified = r.modified !== null ? ` (${r.modified})` : ''\n lines.push(`- ${r.title}${tags}${modified}`)\n if (r.snippet.length > 0) lines.push(` ${r.snippet}`)\n }\n if (value.total > value.results.length) lines.push(`(另有 ${value.total - value.results.length} 条未展开,可用 tiddlywiki_get 读取具体标题,或提高 limit)`)\n return [{ type: 'text', text: lines.join('\\n') }]\n },\n },\n execute: async (args: { query: string; tags?: string[]; tag?: string; since?: string; type?: string; field?: string; value?: string; limit?: number }): Promise<SearchResult> => {\n const wiki = requireWiki()\n const { items, total } = await wiki.search(args.query, {\n tags: args.tags,\n tag: args.tag,\n since: args.since,\n type: args.type,\n field: args.field,\n value: args.value,\n limit: args.limit,\n })\n return {\n query: args.query,\n tags: args.tags ?? [],\n since: args.since ?? null,\n type: args.type ?? null,\n field: args.field ?? null,\n value: args.value ?? null,\n total,\n results: items.map((t) => ({ title: t.title, tags: t.tags ?? [], modified: toIsoDateString(t.modified), snippet: snippetAround(t.text ?? '', args.query) })),\n }\n },\n }))\n\n // ── tiddlywiki_recent ────────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_recent',\n description: '查看 TiddlyWiki 知识库最近修改的笔记(按修改时间倒序,排除系统 tiddler 与图片等二进制附件),返回标题、标签、修改时间与摘要。适合开工时快速了解近期动态。',\n parameters: {\n limit: { type: 'integer', description: '可选:返回条数(默认 15,最大 200)' },\n since: { type: 'string', description: '可选:只返回修改时间不早于该 ISO 时间的 tiddler' },\n },\n output: {\n render: (_args, value: RecentResult) => {\n const lines = [`TiddlyWiki 最近修改(最近 ${value.results.length} 条${value.since !== null ? `,since=${value.since}` : ''}):`]\n if (value.results.length === 0) lines.push('暂无笔记。')\n for (const r of value.results) {\n const tags = r.tags.length > 0 ? ` [${r.tags.join(', ')}]` : ''\n lines.push(`- ${r.title}${tags} (${r.modified ?? '?'})`)\n if (r.snippet.length > 0) lines.push(` ${r.snippet}`)\n }\n return [{ type: 'text', text: lines.join('\\n') }]\n },\n },\n execute: async (args: { limit?: number; since?: string }): Promise<RecentResult> => {\n const wiki = requireWiki()\n const items = await wiki.recent(args.limit ?? 15, args.since)\n return {\n since: args.since ?? null,\n results: items.map((t) => ({ title: t.title, tags: t.tags ?? [], modified: toIsoDateString(t.modified), snippet: snippetOf(t.text ?? '') })),\n }\n },\n }))\n\n // ── tiddlywiki_list_tags ─────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_list_tags',\n description: '列出 TiddlyWiki 知识库现有的非系统标签及各自计数(按使用次数降序),方便决定给笔记打什么 tag。默认最多返回 200 个(`limit` 可调,上限 1000),被截断时结果里会带 total/truncated。',\n parameters: {\n limit: { type: 'integer', description: '可选:最多返回多少个标签(按使用次数降序),默认 200,上限 1000。' },\n },\n output: {\n render: (_args, value: TagListResult) => {\n if (value.tags.length === 0) return [{ type: 'text', text: '知识库暂无标签。' }]\n const lines = [\n value.truncated\n ? `现有标签(共 ${value.total} 个,仅列出使用最多的 ${value.tags.length} 个,按使用次数降序):`\n : `现有标签(${value.total} 个,按使用次数降序):`,\n ]\n for (const t of value.tags) lines.push(`- ${t.tag} × ${t.count}`)\n if (value.truncated) lines.push(`(其余 ${value.total - value.tags.length} 个较少使用的标签未列出;需要时可提高 limit 重试)`)\n return [{ type: 'text', text: lines.join('\\n') }]\n },\n },\n execute: async (args: { limit?: number }): Promise<TagListResult> => {\n const wiki = requireWiki()\n const stats = await wiki.tagStats()\n const limit = typeof args.limit === 'number' && Number.isFinite(args.limit)\n ? Math.max(1, Math.min(Math.floor(args.limit), 1000))\n : 200\n const tags = stats.tags.slice(0, limit)\n return { count: tags.length, total: stats.total, truncated: tags.length < stats.total, tags }\n },\n }))\n\n // ── tiddlywiki_get ───────────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_get',\n description: '读取一个 TiddlyWiki tiddler 的完整内容(标题、全文、标签、自定义字段)。二进制 tiddler(图片等附件)只返回元数据,不返回 base64 正文。',\n parameters: {\n title: { type: 'string', description: 'tiddler 标题(精确匹配)', required: true },\n },\n output: {\n render: (_args, value: GetResult) => {\n if (value.notFound) return [{ type: 'text', text: `tiddler「${value.title}」不存在。可用 tiddlywiki_search 检索,或用 tiddlywiki_put 新建。` }]\n if (value.binary === true) {\n const lines = [`tiddler「${value.title}」是二进制附件(type=${value.binaryType ?? '?'},base64 正文约 ${value.binaryChars ?? 0} 字符),不返回正文。`]\n if (value.tags.length > 0) lines.push(`标签: ${value.tags.join(', ')}`)\n if (value.modified !== null) lines.push(`修改: ${value.modified}`)\n const fields = Object.entries(value.fields)\n if (fields.length > 0) lines.push(`字段: ${fields.map(([k, v]) => `${k}=${String(v)}`).join(', ')}`)\n lines.push(`如需查看附件本身,可打开 [${value.title}](/dsh-tiddlywiki/tw/#${encodeURIComponent(value.title)})。`)\n return [{ type: 'text', text: lines.join('\\n') }]\n }\n const lines = [`tiddler「${value.title}」`]\n if (value.tags.length > 0) lines.push(`标签: ${value.tags.join(', ')}`)\n if (value.modified !== null) lines.push(`修改: ${value.modified}`)\n const fields = Object.entries(value.fields)\n if (fields.length > 0) lines.push(`字段: ${fields.map(([k, v]) => `${k}=${String(v)}`).join(', ')}`)\n lines.push('--- 全文 ---')\n lines.push(value.text.length > 0 ? value.text : '(空)')\n return [{ type: 'text', text: lines.join('\\n') }]\n },\n },\n execute: async (args: { title: string }): Promise<GetResult> => {\n const wiki = requireWiki()\n const t = await wiki.get(args.title)\n if (t === undefined) return { notFound: true, title: args.title, text: '', tags: [], fields: {}, modified: null }\n const binary = isBinaryType(typeof t.type === 'string' ? t.type : undefined)\n const result: GetResult = {\n notFound: false,\n title: t.title,\n text: binary ? '' : (t.text ?? ''),\n tags: t.tags ?? [],\n fields: pickFields(t),\n modified: toIsoDateString(t.modified),\n }\n if (binary) {\n result.binary = true\n result.binaryType = typeof t.type === 'string' ? t.type : undefined\n result.binaryChars = (t.text ?? '').length\n }\n return result\n },\n }))\n\n // ── tiddlywiki_put ───────────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_put',\n description: '写入(新建或覆盖)一个 TiddlyWiki tiddler。同名覆盖;**覆盖已有条目时,未传的 tags / 自定义字段 / 内容类型都会原样保留**(不会静默丢掉笔记原有的标签、字段或 type;显式传 tags 才整体替换标签)。写入后触发防抖自动 commit(默认 60s;手动同步用 tiddlywiki_git_sync)。新建(title 不存在)时自动补打 agent-written 标签标记「由 Agent 撰写」,无需手动添加。内容类型:**只有新建**条目且未指定时才默认 text/markdown($:/ 系统条目除外)——覆盖 text/css、wikitext 等既有条目时保持原类型;要改类型用 fields 传 {\"type\":\"...\"}。⚠️ fields.type 是 TW 的内容类型保留字段,不要把业务分类值(如 \"meeting\")写进去——业务分类请放 tags。',\n parameters: {\n title: { type: 'string', description: 'tiddler 标题(精确匹配,覆盖同名)', required: true },\n text: { type: 'string', description: 'tiddler 全文(默认按 Markdown 解析)', required: true },\n tags: { type: 'array', items: { type: 'string' }, description: '标签数组(可选)' },\n fields: { type: 'json', description: '附加自定义字段,如 {\"date\":\"2026-09-02\"}(可选)。fields.type 是**改内容类型的正规入口**(如 {\"type\":\"text/css\"}):新建条目未指定时默认 text/markdown,覆盖既有条目时保留原类型。注意不要把业务分类值(如 \"meeting\")写进 type——业务分类请放 tags' },\n expectedModified: { type: 'string', description: '可选:乐观并发保护。传 tiddlywiki_get 读到的 modified 值,若该条目已被他人改动则拒绝写入(避免覆盖人类在 TW 编辑器里的修改)' },\n expectedRevision: { type: 'integer', description: '可选:乐观并发保护的另一种令牌——传 tiddlywiki_get 返回字段里的 revision(刚写入、还没落盘的条目没有 modified,此时用 revision)' },\n force: { type: 'boolean', description: '可选:true 时忽略 expectedModified/expectedRevision 强制覆盖(默认 false)' },\n },\n output: {\n render: (_args, value: PutResult) => {\n const lines = [`已写入 tiddler「${value.title}」`]\n if (value.tags.length > 0) lines.push(`标签: ${value.tags.join(', ')}`)\n if (value.type !== null) lines.push(`类型: ${value.type}${value.typeDefaulted === true ? '(新建且未指定,已默认 markdown)' : ''}`)\n // 覆盖时若类型真的变了,必须说出来(v0.20.1)——内容类型决定解析方式,\n // 静默变化会让 CSS/JS 被当 Markdown、Markdown 笔记被当 wikitext。\n if (value.typeChanged !== undefined) lines.push(`⚠️ 内容类型已从 ${value.typeChanged.from} 改为 ${value.typeChanged.to}(覆盖前请确认这是你要的)`)\n if (value.fields !== null) {\n const entries = Object.entries(value.fields)\n if (entries.length > 0) lines.push(`字段: ${entries.map(([k, v]) => `${k}=${String(v)}`).join(', ')}`)\n }\n return [{ type: 'text', text: lines.join('\\n') }]\n },\n },\n execute: async (args: { title: string; text: string; tags?: string[]; fields?: Record<string, unknown>; expectedModified?: string; expectedRevision?: number; force?: boolean }): Promise<PutResult> => {\n const wiki = requireWiki()\n if (args.title.trim().length === 0) throw new Error('tiddlywiki_put: title 不能为空')\n // Read WITHOUT swallowing errors: only a 404 means \"new tiddler\". A\n // transient failure treated as \"new\" would silently tag an existing\n // human note as agent-written.\n const existing = await wiki.get(args.title)\n assertNoConflict(args.title, existing, {\n expectedModified: args.expectedModified,\n expectedRevision: args.expectedRevision,\n force: args.force,\n })\n const tags = normalizeTagArg(args.tags)\n const { tiddler, typeDefaulted } = buildWriteTiddler(args.title, args.text, { existing, tags, fields: args.fields })\n await wiki.put(tiddler)\n deps.autoCommit()\n return {\n ok: true,\n title: args.title,\n tags: tiddler.tags ?? [],\n type: typeof tiddler.type === 'string' ? tiddler.type : null,\n ...(typeDefaulted ? { typeDefaulted: true } : {}),\n ...typeChangeOf(existing, tiddler),\n fields: args.fields ?? null,\n }\n },\n }))\n\n // ── tiddlywiki_batch_put ─────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_batch_put',\n description: '批量写入/覆盖多个 TiddlyWiki tiddler(一次工具调用)。overwrite=false 时跳过已存在的标题;返回逐条结果(单条失败不影响其余条目,失败原因逐条列出)。写入后触发防抖自动 commit(默认 60s)。新建(title 不存在)的条目会自动补打 agent-written 标签,无需手动添加。内容类型:只有**新建**条目未指定 fields.type 时才默认 text/markdown($:/ 系统条目除外);**覆盖既有条目时保留其原有 type/tags/自定义字段**。',\n parameters: {\n items: {\n type: 'array',\n items: {\n type: 'object',\n additionalProperties: true,\n description: '要写入的 tiddler 数组',\n properties: {\n // NOTHING is `required` here on purpose (v0.19.0): argument\n // pre-validation runs BEFORE the per-item try/catch, so a declared\n // requirement made one malformed item abort the WHOLE batch and the\n // documented \"one failure does not lose the others\" contract was\n // unreachable. The implementation validates per item instead.\n title: { type: 'string', description: '标题(精确匹配,覆盖同名)' },\n text: { type: 'string', description: '全文(默认按 Markdown 解析)' },\n tags: { type: 'array', items: { type: 'string' }, description: '标签数组(可选)' },\n fields: { type: 'json', description: '附加自定义字段(可选)。注意:fields.type 是 TW 内容类型(保留字段,默认已自动补 text/markdown),不要写业务分类值' },\n },\n },\n },\n overwrite: { type: 'boolean', description: '可选:true=覆盖同名(默认),false=跳过已存在的标题' },\n },\n output: {\n render: (_args, value: BatchResult) => {\n const lines = [`批量写入完成:成功 ${value.written},跳过 ${value.skipped},失败 ${value.failed},共 ${value.items.length} 条。`]\n for (const r of value.items) {\n lines.push(`- ${r.title}:${r.written ? '已写入' : r.skipped ? '已跳过(存在)' : `失败(${r.error ?? '未知错误'})`}`)\n }\n return [{ type: 'text', text: lines.join('\\n') }]\n },\n },\n execute: async (args: { items: Array<{ title: string; text: string; tags?: string[]; fields?: Record<string, unknown> }>; overwrite?: boolean }): Promise<BatchResult> => {\n const wiki = requireWiki()\n const list = Array.isArray(args.items) ? args.items : []\n if (list.length === 0) return { ok: true, written: 0, skipped: 0, failed: 0, items: [] }\n const overwrite = args.overwrite !== false\n const results: BatchItemResult[] = new Array<BatchItemResult>(list.length)\n let written = 0\n let skipped = 0\n let failed = 0\n // One item failing (validation, network, a rejected title) must not lose\n // the report of the items that DID land — the model needs per-item truth.\n //\n // BOUNDED CONCURRENCY (v0.19.5): the previous sequential loop issued 2N\n // REST round-trips (a GET + a PUT per item) — a 200-item import spent\n // minutes in request latency. Four workers share the same client (its\n // listing caches are invalidated per write, which only costs a refetch),\n // and results are stored BY INDEX so the report keeps the caller's order\n // regardless of completion order.\n const writeOne = async (item: { title: string; text: string; tags?: string[]; fields?: Record<string, unknown> }, index: number): Promise<void> => {\n const title = typeof item?.title === 'string' ? item.title : ''\n try {\n if (title.length === 0) throw new Error('缺少非空 title')\n if (typeof item.text !== 'string') throw new Error('缺少 text')\n // Read WITHOUT swallowing errors: only a 404 means \"new tiddler\".\n const existing = await wiki.get(title)\n if (!overwrite && existing !== undefined) {\n skipped++\n results[index] = { title, written: false, skipped: true, failed: false }\n return\n }\n const { tiddler } = buildWriteTiddler(title, item.text, { existing, tags: normalizeTagArg(item.tags), fields: item.fields })\n await wiki.put(tiddler)\n written++\n results[index] = { title, written: true, skipped: false, failed: false }\n } catch (err) {\n failed++\n results[index] = { title: title.length > 0 ? title : '(无标题)', written: false, skipped: false, failed: true, error: err instanceof Error ? err.message : String(err) }\n }\n }\n const workers = Math.max(1, Math.min(4, list.length))\n let next = 0\n await Promise.all(Array.from({ length: workers }, async () => {\n for (;;) {\n const index = next++\n if (index >= list.length) return\n await writeOne(list[index] as { title: string; text: string }, index)\n }\n }))\n deps.autoCommit()\n return { ok: failed === 0, written, skipped, failed, items: results }\n },\n }))\n\n // ── tiddlywiki_rename ────────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_rename',\n description: '重命名一个 TiddlyWiki tiddler:把旧标题的内容复制到新标题、删除旧标题,并可选地更新其他 tiddler 里的 [[旧标题]] / {{旧标题}} 引用(最佳努力)。',\n parameters: {\n oldTitle: { type: 'string', description: '当前标题', required: true },\n newTitle: { type: 'string', description: '新标题', required: true },\n updateRefs: { type: 'boolean', description: '可选:是否同步更新其他 tiddler 里的引用(默认 true)' },\n },\n output: {\n render: (_args, value: RenameResult) => {\n const lines = [`已重命名「${value.from}」→「${value.to}」`]\n lines.push(`更新了 ${value.refsUpdated} 处引用(${value.refsTiddlers} 个 tiddler)`)\n if (value.warning !== undefined) lines.push(`注意: ${value.warning}`)\n return [{ type: 'text', text: lines.join('\\n') }]\n },\n },\n execute: async (args: { oldTitle: string; newTitle: string; updateRefs?: boolean }): Promise<RenameResult> => {\n const wiki = requireWiki()\n const { oldTitle, newTitle } = args\n if (oldTitle === newTitle) return { ok: true, from: oldTitle, to: newTitle, refsUpdated: 0, refsTiddlers: 0 }\n const existing = await wiki.get(oldTitle)\n if (existing === undefined) throw new Error(`tiddler「${oldTitle}」不存在`)\n const target = await wiki.get(newTitle)\n if (target !== undefined) throw new Error(`新标题「${newTitle}」已存在(可先用 tiddlywiki_delete 删除)`)\n let refsUpdated = 0\n let refsTiddlers = 0\n let warning: string | undefined\n if (args.updateRefs !== false) {\n const all = await wiki.list(undefined, true)\n for (const t of all) {\n if (t.title === oldTitle || t.title === newTitle) continue\n if (t.title.startsWith('$:/')) continue\n const text = t.text ?? ''\n if (text.length === 0) continue\n const rewritten = rewriteRefs(text, oldTitle, newTitle)\n if (rewritten.count > 0) {\n await wiki.put({ ...cleanTiddler(t), text: rewritten.text })\n refsUpdated += rewritten.count\n refsTiddlers++\n }\n }\n }\n await wiki.put({ ...cleanTiddler(existing), title: newTitle })\n // The new title is already written; a failure here leaves BOTH copies on\n // disk, so report the partial state instead of throwing a bare error (the\n // caller would otherwise assume the rename never happened) — v0.19.5.\n let deleteFailed: string | undefined\n try {\n await wiki.delete(oldTitle)\n } catch (err) {\n deleteFailed = err instanceof Error ? err.message : String(err)\n }\n if (refsTiddlers === 0) {\n warning = '未找到任何其他 tiddler 引用旧标题;如确实需要,可手动补充链接。'\n }\n if (deleteFailed !== undefined) {\n const partial = `新标题「${newTitle}」已写入,但旧标题「${oldTitle}」删除失败(${deleteFailed}):现在两个标题都存在同一份内容,请手动删除旧标题。`\n warning = warning === undefined ? partial : `${warning} ${partial}`\n }\n deps.autoCommit()\n return { ok: true, from: oldTitle, to: newTitle, refsUpdated, refsTiddlers, ...(warning !== undefined ? { warning } : {}) }\n },\n }))\n\n // ── tiddlywiki_delete ────────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_delete',\n description: '删除一个 TiddlyWiki tiddler(不存在时是幂等空操作)。默认是**软删除**:内容移入回收站($:/dsh-tiddlywiki/trash/…,不再出现在检索/最近列表里)后删除原条目,可用 tiddlywiki_trash 恢复;permanent=true 才真正永久删除。删除后触发自动 commit。',\n parameters: {\n title: { type: 'string', description: 'tiddler 标题(精确匹配)', required: true },\n permanent: { type: 'boolean', description: '可选:true = 永久删除(回收站也拿不回来,仅剩 git 历史);默认 false = 移入回收站' },\n expectedModified: { type: 'string', description: '可选:乐观并发保护。传 tiddlywiki_get 读到的 modified;若条目在你读取之后被改动(人类在 TW 编辑器里改过)则拒绝删除' },\n expectedRevision: { type: 'integer', description: '可选:乐观并发保护的另一种令牌——传 tiddlywiki_get 返回字段里的 revision' },\n force: { type: 'boolean', description: '可选:true 时忽略 expectedModified/expectedRevision 强制删除(默认 false)' },\n },\n output: {\n render: (_args, value: DeleteResult) => [{\n type: 'text',\n text: value.trashed === true\n ? `已把 tiddler「${value.title}」移入回收站($:/dsh-tiddlywiki/trash/,可用 tiddlywiki_trash action=restore 恢复)。`\n : `已永久删除 tiddler「${value.title}」。`,\n }],\n },\n execute: async (args: { title: string; permanent?: boolean; expectedModified?: string; expectedRevision?: number; force?: boolean }): Promise<DeleteResult> => {\n const wiki = requireWiki()\n const existing = await wiki.get(args.title)\n // Deleting something the caller never saw is fine only when it does not\n // exist; a token means the note WAS read, so a changed revision since then\n // must abort (v0.19.5 — delete used to have no concurrency protection at\n // all, while put/batch_put did: `get` → human edits in TW → `delete` threw\n // the human's work into the trash with no warning).\n assertNoConflict(args.title, existing, {\n expectedModified: args.expectedModified,\n expectedRevision: args.expectedRevision,\n force: args.force,\n })\n // Trash entries themselves and system tiddlers are never nested.\n const canTrash = existing !== undefined && args.permanent !== true && !args.title.startsWith(TRASH_PREFIX)\n if (!canTrash) {\n await wiki.delete(args.title)\n deps.autoCommit()\n return { ok: true, title: args.title, trashed: false }\n }\n const trashTitle = trashTitleFor(args.title)\n const at = new Date().toISOString()\n await wiki.put({ ...cleanTiddler(existing), title: trashTitle, 'trash-of': args.title, 'trash-at': at })\n await wiki.delete(args.title)\n // Read the index BEFORE touching it, and ABORT when the read failed or the\n // JSON is broken: overwriting it with `[] + thisEntry` after a transient\n // failure dropped every earlier entry — the trashed tiddlers survive but\n // become unreachable orphans (`trash list` cannot see them, `trash empty`\n // cannot clean them). A corrupt index is treated the same way: we cannot\n // know what it held, so we never silently replace it (v0.19.5).\n const index = await readTrashIndex(wiki)\n if (!index.readOk || index.corrupted) throw new TrashIndexUnavailableError()\n index.entries.push({ trash: trashTitle, of: args.title, at })\n await writeTrashIndex(wiki, index.entries)\n deps.autoCommit()\n return { ok: true, title: args.title, trashed: true, trashTitle }\n },\n }))\n\n // ── tiddlywiki_trash ─────────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_trash',\n description: '回收站(软删除的笔记):action=list 列出、action=restore 恢复某条、action=empty 清空。配合 tiddlywiki_delete(默认软删除)使用。',\n parameters: {\n action: { type: 'string', enum: ['list', 'restore', 'empty'], description: 'list=列出回收站;restore=恢复(需 title);empty=永久清空', required: true },\n title: { type: 'string', description: 'action=restore 时的原标题(也接受回收站标题)' },\n limit: { type: 'integer', description: 'action=list 的返回上限(默认 30,最大 200)' },\n },\n output: {\n render: (_args, value: TrashResult) => {\n const lines = [`回收站 ${value.action}:${value.message}`]\n for (const item of value.items ?? []) lines.push(`- ${item.title}(删除于 ${item.at ?? '?'}${item.of !== undefined ? `,原名「${item.of}」` : ''})`)\n return [{ type: 'text', text: lines.join('\\n') }]\n },\n },\n execute: async (args: { action: 'list' | 'restore' | 'empty'; title?: string; limit?: number }): Promise<TrashResult> => {\n const wiki = requireWiki()\n const index = await readTrashIndex(wiki)\n // A failed/corrupt index must never be treated as「回收站是空的」: `empty`\n // would then report success while doing nothing, and `restore` would claim\n // the note was never trashed (v0.19.5).\n if (!index.readOk) throw new TrashIndexUnavailableError()\n if (index.corrupted) {\n throw new Error('回收站索引已损坏(JSON 解析失败),本次操作已中止以免误删记录;可手动检查 $:/dsh-tiddlywiki/trash-index 后重试。')\n }\n const indexed = index.entries\n if (args.action === 'list') {\n const limit = Math.max(1, Math.min(args.limit ?? 30, 200))\n return {\n action: 'list',\n message: `共 ${indexed.length} 条`,\n items: indexed.slice(-limit).reverse().map((entry) => ({ title: entry.trash, at: entry.at, of: entry.of })),\n }\n }\n if (args.action === 'empty') {\n for (const entry of indexed) await wiki.delete(entry.trash)\n await writeTrashIndex(wiki, [])\n deps.autoCommit()\n return { action: 'empty', message: `已清空 ${indexed.length} 条` }\n }\n const wanted = typeof args.title === 'string' ? args.title.trim() : ''\n if (wanted.length === 0) throw new Error('tiddlywiki_trash: action=restore 需要 title')\n const match = indexed.slice().reverse().find((entry) => entry.of === wanted || entry.trash === wanted)\n if (match === undefined) throw new Error(`回收站里没有「${wanted}」`)\n const stored = await wiki.get(match.trash)\n if (stored === undefined) {\n // Stale index entry (the tiddler was removed by hand): prune and report.\n await writeTrashIndex(wiki, indexed.filter((entry) => entry.trash !== match.trash))\n throw new Error(`回收站条目「${match.trash}」已不存在(索引已清理)`)\n }\n if ((await wiki.get(match.of)) !== undefined) {\n throw new Error(`无法恢复:标题「${match.of}」已被占用,请先处理现有条目`)\n }\n const restored = cleanTiddler(stored)\n delete restored['trash-of']\n delete restored['trash-at']\n await wiki.put({ ...restored, title: match.of })\n await wiki.delete(match.trash)\n await writeTrashIndex(wiki, indexed.filter((entry) => entry.trash !== match.trash))\n deps.autoCommit()\n return { action: 'restore', message: `已恢复「${match.of}」`, items: [] }\n },\n }))\n\n // ── tiddlywiki_append ────────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_append',\n description: '向已有 tiddler 追加/前插文本,或写入指定标题段落的末尾(无需先读全文、不会整篇覆盖)——适合日志、批注、清单的增量写入。条目不存在时默认新建(createIfMissing=false 则报错)。**写入既有条目走与 tiddlywiki_put 同一套写策略**:原有 tags、自定义字段与**内容类型**全部保留(不会把 Markdown 笔记改成 wikitext、也不会把 CSS 改成 Markdown);可用 fields 显式覆盖字段/类型。新建条目未指定内容类型时才默认 text/markdown。',\n parameters: {\n title: { type: 'string', description: 'tiddler 标题', required: true },\n text: { type: 'string', description: '要追加/前插的文本(默认按 Markdown 写;既有条目保持它自己的内容类型)', required: true },\n mode: { type: 'string', enum: ['append', 'prepend'], description: '可选:append(默认,追加到末尾)/ prepend(插到开头)' },\n heading: { type: 'string', description: '可选:append 时改为插入到该标题(Markdown # 或 wikitext ! 标题,按标题文本匹配)对应段落的末尾' },\n createIfMissing: { type: 'boolean', description: '可选:条目不存在时是否新建(默认 true)' },\n tags: { type: 'array', items: { type: 'string' }, description: '可选:标签(不传则保留既有条目的原标签;新建条目会额外自动补 agent-written)' },\n fields: { type: 'json', description: '可选:显式覆盖的自定义字段(如 {\"type\":\"text/css\"})。不传则保留既有条目的原字段与内容类型' },\n },\n output: {\n render: (_args, value: AppendResult) => {\n const lines = [`${value.created ? '已新建并写入' : '已增量写入'} tiddler「${value.title}」(${value.mode}${value.heading !== null ? ` · 段落「${value.heading}」` : ''}):新增 ${value.added} 字符,现共 ${value.total} 字符。`]\n if (value.type !== null) lines.push(`类型: ${value.type}${value.typeDefaulted === true ? '(新建且未指定,已默认 markdown)' : ''}`)\n if (value.typeChanged !== undefined) lines.push(`⚠️ 内容类型已从 ${value.typeChanged.from} 改为 ${value.typeChanged.to}`)\n return [{ type: 'text', text: lines.join('\\n') }]\n },\n },\n execute: async (args: { title: string; text: string; mode?: 'append' | 'prepend'; heading?: string; createIfMissing?: boolean; tags?: string[]; fields?: Record<string, unknown> }): Promise<AppendResult> => {\n const wiki = requireWiki()\n const title = args.title.trim()\n if (title.length === 0) throw new Error('tiddlywiki_append: title 不能为空')\n const existing = await wiki.get(title)\n if (existing === undefined && args.createIfMissing === false) {\n throw new Error(`tiddler「${title}」不存在(createIfMissing=false)`)\n }\n const mode = args.mode === 'prepend' ? 'prepend' : 'append'\n const base = existing?.text ?? ''\n const addition = args.text\n let next: string\n if (mode === 'append' && typeof args.heading === 'string' && args.heading.trim().length > 0) {\n next = insertIntoSection(base, args.heading.trim(), addition)\n } else if (mode === 'prepend') {\n next = base.trim().length === 0 ? addition : `${addition}\\n\\n${base}`\n } else {\n next = base.trim().length === 0 ? addition : `${base.replace(/\\s+$/, '')}\\n\\n${addition}`\n }\n // v0.20.1: ONE write policy for every write path. The old append built its\n // PUT by hand, which (a) dropped the existing tiddler's `type` and\n // (b) ignored `fields` entirely — appending a paragraph to a Markdown note\n // silently downgraded it to wikitext. buildWriteTiddler preserves\n // tags/custom fields/type for an existing tiddler and only defaults the\n // type when creating.\n const { tiddler, typeDefaulted } = buildWriteTiddler(title, next, {\n existing,\n tags: normalizeTagArg(args.tags),\n fields: args.fields,\n })\n await wiki.put(tiddler)\n deps.autoCommit()\n return {\n ok: true,\n title,\n mode,\n heading: typeof args.heading === 'string' && args.heading.trim().length > 0 ? args.heading.trim() : null,\n created: existing === undefined,\n added: addition.length,\n total: next.length,\n type: typeof tiddler.type === 'string' ? tiddler.type : null,\n ...(typeDefaulted ? { typeDefaulted: true } : {}),\n ...typeChangeOf(existing, tiddler),\n }\n },\n }))\n\n // ── tiddlywiki_backlinks ─────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_backlinks',\n description: '查反向链接:哪些笔记引用了目标 tiddler([[标题]] / [[显示|标题]] / {{标题}}),以及哪些笔记把它当作标签。用于知识图谱导航、改动前评估影响面。',\n parameters: {\n title: { type: 'string', description: '目标 tiddler 标题', required: true },\n includeTags: { type: 'boolean', description: '可选:是否把「以该标题为标签」的笔记也算作反向链接(默认 true)' },\n limit: { type: 'integer', description: '可选:最多返回多少条(默认 30,最大 200)' },\n },\n output: {\n render: (_args, value: BacklinkResult) => {\n const lines = [`「${value.title}」的反向链接:${value.total} 条(引用 ${value.linkCount} · 标签 ${value.tagCount})`]\n if (value.items.length === 0) lines.push('没有任何笔记引用它。')\n for (const item of value.items) {\n lines.push(`- ${item.title}(${item.via === 'tag' ? '标签' : `${item.refs} 处引用`})`)\n }\n return [{ type: 'text', text: lines.join('\\n') }]\n },\n },\n execute: async (args: { title: string; includeTags?: boolean; limit?: number }): Promise<BacklinkResult> => {\n const wiki = requireWiki()\n const target = args.title.trim()\n if (target.length === 0) throw new Error('tiddlywiki_backlinks: title 不能为空')\n const includeTags = args.includeTags !== false\n const limit = Math.max(1, Math.min(args.limit ?? 30, 200))\n const items = await wiki.list(undefined, true)\n const hits: Array<{ title: string; refs: number; via: 'link' | 'tag'; modified: string | null }> = []\n let linkCount = 0\n let tagCount = 0\n for (const t of items) {\n if (t.title === target || t.title.startsWith('$:/')) continue\n const refs = countRefsTo(t.text ?? '', target)\n const tagged = includeTags && (t.tags ?? []).includes(target)\n if (refs === 0 && !tagged) continue\n if (refs > 0) linkCount++\n if (tagged) tagCount++\n hits.push({ title: t.title, refs, via: refs > 0 ? 'link' : 'tag', modified: toIsoDateString(t.modified) })\n }\n hits.sort((a, b) => b.refs - a.refs || a.title.localeCompare(b.title, 'zh'))\n return { title: target, total: hits.length, linkCount, tagCount, items: hits.slice(0, limit) }\n },\n }))\n\n // ── tiddlywiki_attach ────────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_attach',\n description: '把一个本机文件或公网 http(s) 地址存成 wiki 的二进制附件 tiddler(图片 / PDF / 压缩包等,type + base64 正文,随 wiki 进 git)。可选把附件嵌入/链接进某篇笔记。这是 agent 唯一能写入二进制附件的途径。',\n parameters: {\n title: { type: 'string', description: '附件 tiddler 标题(同时决定其在 wiki 里的名字)', required: true },\n path: { type: 'string', description: '本机绝对路径(与 url 二选一)' },\n url: { type: 'string', description: '公网 http(s) 地址(与 path 二选一;含 SSRF 守卫,拒绝内网/回环地址)' },\n tags: { type: 'array', items: { type: 'string' }, description: '可选:附件标签' },\n noteTitle: { type: 'string', description: '可选:把该附件嵌入到这篇笔记末尾(图片用 [img[标题]],其它用 [[标题]] 链接)' },\n expectedModified: { type: 'string', description: '可选:乐观并发保护,仅在同名 tiddler 已存在时有意义(传 tiddlywiki_get 读到的 modified)' },\n expectedRevision: { type: 'integer', description: '可选:乐观并发保护的另一种令牌——传 tiddlywiki_get 返回字段里的 revision' },\n force: { type: 'boolean', description: '可选:true 时忽略 expectedModified/expectedRevision,允许覆盖同名 tiddler(默认 false;即便覆盖也会保留其 tags 与自定义字段)' },\n },\n output: {\n render: (_args, value: AttachResult) => {\n const lines = [`已保存附件「${value.title}」(${value.mime},${value.bytes} 字节,base64 约 ${value.chars} 字符)`]\n if (value.source !== null) lines.push(`来源: ${value.source}`)\n if (value.embedInto !== null) lines.push(`已嵌入笔记「${value.embedInto}」`)\n lines.push(`打开: [${value.title}](/dsh-tiddlywiki/tw/#${encodeURIComponent(value.title)})`)\n return [{ type: 'text', text: lines.join('\\n') }]\n },\n },\n execute: async (args: { title: string; path?: string; url?: string; tags?: string[]; noteTitle?: string; expectedModified?: string; expectedRevision?: number; force?: boolean }): Promise<AttachResult> => {\n const wiki = requireWiki()\n const title = args.title.trim()\n if (title.length === 0) throw new Error('tiddlywiki_attach: title 不能为空')\n const hasPath = typeof args.path === 'string' && args.path.trim().length > 0\n const hasUrl = typeof args.url === 'string' && args.url.trim().length > 0\n if (hasPath === hasUrl) throw new Error('tiddlywiki_attach: path 与 url 必须且只能提供一个')\n let buffer: Buffer\n let mime: string\n let source: string\n if (hasPath) {\n const filePath = (args.path as string).trim()\n // Absolute only: a relative path would resolve against the DSH process\n // cwd, which is not something the model can reason about.\n if (!isAbsolute(filePath)) throw new Error('tiddlywiki_attach: path 必须是绝对路径')\n const info = await stat(filePath)\n if (!info.isFile()) throw new Error(`tiddlywiki_attach: ${basename(filePath)} 不是普通文件`)\n if (info.size > MAX_ATTACH_BYTES) throw new Error(`tiddlywiki_attach: 文件超过 ${Math.floor(MAX_ATTACH_BYTES / 1024 / 1024)}MB 上限`)\n buffer = await readFile(filePath)\n const ext = extname(filePath).slice(1).toLowerCase()\n mime = ATTACH_MIME_BY_EXT[ext] ?? 'application/octet-stream'\n source = filePath\n } else {\n const imageUrl = (args.url as string).trim()\n const downloaded = await downloadClipImage(imageUrl, '')\n buffer = downloaded.buffer\n const ext = extname(new URL(imageUrl).pathname).slice(1).toLowerCase()\n mime = downloaded.type?.split(';')[0]?.trim() || ATTACH_MIME_BY_EXT[ext] || 'application/octet-stream'\n source = imageUrl\n }\n if (buffer.length === 0) throw new Error('tiddlywiki_attach: 内容为空')\n if (buffer.length > MAX_ATTACH_BYTES) throw new Error(`tiddlywiki_attach: 内容超过 ${Math.floor(MAX_ATTACH_BYTES / 1024 / 1024)}MB 上限`)\n // DATA SAFETY (v0.19.5): this used to `wiki.put({title, type, text})` with\n // no read at all — attaching an image whose title collided with an existing\n // note silently replaced that note (tags, custom fields and body all gone),\n // the exact class the write policy exists to prevent. It also never added\n // the `agent-written` tag every other creating tool adds. Read first (404 =\n // new tiddler; any other failure propagates), then build the PUT from the\n // existing tiddler so a same-named note keeps everything but its body.\n const existing = await wiki.get(title)\n assertNoConflict(title, existing, {\n expectedModified: args.expectedModified,\n expectedRevision: args.expectedRevision,\n force: args.force,\n })\n // An attachment sharing a title with an existing tiddler is almost always\n // a mistake (the old code silently destroyed the note). Require explicit\n // consent: a concurrency token (the caller proved it read the tiddler) or\n // `force: true`. `expectedRevision`/`expectedModified` alone does NOT\n // consent to an overwrite — `assertNoConflict` above only rejects when the\n // tiddler changed since that token, so a stale-but-matching token would\n // still wipe the body.\n if (existing !== undefined && args.force !== true) {\n throw new Error(\n `tiddlywiki_attach: 标题「${title}」已存在(type=${typeof existing.type === 'string' ? existing.type : '?'})。`\n + '为避免静默覆盖既有笔记,请换一个附件标题;确认要覆盖时传 force: true(tags 与自定义字段仍会保留)。',\n )\n }\n const explicitTags = Array.isArray(args.tags)\n ? args.tags.filter((t) => typeof t === 'string' && t.trim().length > 0)\n : undefined\n const { tiddler } = buildWriteTiddler(title, buffer.toString('base64'), {\n existing,\n ...(explicitTags !== undefined && explicitTags.length > 0 ? { tags: explicitTags } : {}),\n fields: {\n type: mime,\n 'attach-source': source,\n 'attach-at': new Date().toISOString(),\n },\n })\n await wiki.put(tiddler)\n let embedInto: string | null = null\n if (typeof args.noteTitle === 'string' && args.noteTitle.trim().length > 0) {\n embedInto = args.noteTitle.trim()\n const note = await wiki.get(embedInto)\n // `[img[Title]]` breaks if the attachment title itself contains `]]`;\n // fall back to a plain link (which has the same constraint, so strip the\n // sequence) instead of silently producing a broken embed.\n const safeTitle = title.replace(/\\]\\]/g, '] ]')\n const embed = mime.startsWith('image/') ? `[img[${safeTitle}]]` : `[[${safeTitle}]]`\n const text = note === undefined ? embed : `${(note.text ?? '').replace(/\\s+$/, '')}\\n\\n${embed}`\n // Same read-modify-write policy as everywhere else: preserve the note's\n // tags/custom fields/type, only append the embed.\n const { tiddler: noteTiddler } = buildWriteTiddler(embedInto, text, { existing: note })\n await wiki.put(noteTiddler)\n }\n deps.autoCommit()\n return { ok: true, title, mime, bytes: buffer.length, chars: buffer.toString('base64').length, source, embedInto }\n },\n }))\n\n // ── tiddlywiki_lint ──────────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_lint',\n description: '知识库体检(只读):找出垃圾/异常标签、指向不存在条目的死链、空笔记、疑似 Markdown 却缺 type 的笔记。返回按类别分组的问题清单与修复建议。',\n parameters: {\n limit: { type: 'integer', description: '可选:每类最多返回多少条示例(默认 10,最大 100)' },\n checks: { type: 'array', items: { type: 'string' }, description: '可选:只跑指定检查(junk-tags / broken-links / empty-notes / missing-type)' },\n },\n output: {\n render: (_args, value: LintResult) => {\n const lines = [`知识库体检:扫描 ${value.scanned} 条文本笔记,发现 ${value.issues.reduce((sum, i) => sum + i.count, 0)} 个问题。`]\n if (value.issues.length === 0) lines.push('没有发现问题。')\n for (const issue of value.issues) {\n lines.push(`- ${issue.kind}:${issue.count} 处 — ${issue.hint}`)\n for (const sample of issue.samples) lines.push(` · ${sample}`)\n }\n return [{ type: 'text', text: lines.join('\\n') }]\n },\n },\n execute: async (args: { limit?: number; checks?: string[] }): Promise<LintResult> => {\n const wiki = requireWiki()\n const limit = Math.max(1, Math.min(args.limit ?? 10, 100))\n const wanted = Array.isArray(args.checks) && args.checks.length > 0 ? new Set(args.checks) : undefined\n const want = (kind: string): boolean => wanted === undefined || wanted.has(kind)\n const items = await wiki.list(undefined, true)\n // 死链检查需要**全部**标题(含图片/PDF 等二进制附件)——只拿文本列表会\n // 把每一条 `[[图.png]]` / `{{附件}}` 都误报成死链(v0.19.1 修复)。\n // 瘦列表(不带正文)一次请求就能拿到全部标题。\n const titles = want('broken-links')\n ? new Set((await wiki.list()).map((t) => t.title))\n : new Set(items.map((t) => t.title))\n // 缺 type 判定要走 `[!has[type]]` 过滤器:TW 服务端会给 listing 里**每条**\n // 无 type 的条目补 `text/vnd.tiddlywiki`,靠响应里的 type 永远认不出来\n // (v0.19.1 修复:旧实现里这个检查永远不触发)。过滤器在服务端按真实字段\n // 求值,返回的标题就是真正没有 type 的那些。\n const typelessTitles = want('missing-type')\n ? new Set((await wiki.list(MISSING_TYPE_FILTER)).map((t) => t.title))\n : new Set<string>()\n const issues: LintIssue[] = []\n\n if (want('junk-tags')) {\n const samples: string[] = []\n let count = 0\n for (const t of items) {\n for (const tag of t.tags ?? []) {\n if (!isJunkTag(tag)) continue\n count++\n if (samples.length < limit) samples.push(`「${t.title}」的标签「${tag}」`)\n }\n }\n if (count > 0) issues.push({ kind: 'junk-tags', count, hint: '明显是写入事故产生的标签(如筛选器错误文本);用 tiddlywiki_put 重写该条目的 tags 清理', samples })\n }\n\n if (want('broken-links')) {\n const samples: string[] = []\n let count = 0\n const linkRe = /\\[\\[([^\\]|]+)(?:\\|([^\\]]+))?\\]\\]|\\{\\{([^}]+)\\}\\}/g\n for (const t of items) {\n const text = t.text ?? ''\n let match: RegExpExecArray | null\n while ((match = linkRe.exec(text)) !== null) {\n const target = (match[3] ?? match[2] ?? match[1] ?? '').trim()\n if (target.length === 0 || target.startsWith('$:/') || /^(https?|mailto|file):/.test(target)) continue\n if (target.includes('$') || target.includes('<')) continue // variable/macro, not a tiddler link\n if (titles.has(target)) continue\n count++\n if (samples.length < limit) samples.push(`「${t.title}」→「${target}」`)\n }\n }\n if (count > 0) issues.push({ kind: 'broken-links', count, hint: '被引用条目标题不存在;确认是笔误还是应新建该条目', samples })\n }\n\n if (want('empty-notes')) {\n const samples: string[] = []\n let count = 0\n for (const t of items) {\n if (t.title.startsWith('$:/')) continue\n if (isBinaryType(typeof t.type === 'string' ? t.type : undefined)) continue\n if ((t.text ?? '').trim().length === 0) {\n count++\n if (samples.length < limit) samples.push(`「${t.title}」`)\n }\n }\n if (count > 0) issues.push({ kind: 'empty-notes', count, hint: '正文为空的笔记;补内容或删除', samples })\n }\n\n if (want('missing-type')) {\n const samples: string[] = []\n let count = 0\n for (const t of items) {\n if (t.title.startsWith('$:/')) continue\n if (!typelessTitles.has(t.title)) continue\n const text = t.text ?? ''\n if (/^#{1,6}\\s|\\n#{1,6}\\s|^\\s*[-*]\\s|\\*\\*[^*]+\\*\\*/m.test(text)) {\n count++\n if (samples.length < limit) samples.push(`「${t.title}」(无 type 字段,当前按 wikitext 渲染)`)\n }\n }\n if (count > 0) issues.push({ kind: 'missing-type', count, hint: '正文像 Markdown 但条目没有 type 字段,TW 会按 wikitext 渲染;用 tiddlywiki_put 的 fields.type 明确内容类型', samples })\n }\n\n return { scanned: items.length, issues }\n },\n }))\n\n // ── tiddlywiki_git_sync ──────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_git_sync',\n description: '对 TiddlyWiki 知识库的 git 仓库做同步:pull(拉取远端并 rebase 本地,冲突则 abort 并报文件)、push(推送本地提交到远端)、sync(pull → commit 本地改动 → push)。未配置 git.remote 时 push 会失败并提示。',\n parameters: {\n action: { type: 'string', enum: ['pull', 'push', 'sync'], description: '要执行的 git 操作', required: true },\n message: { type: 'string', description: 'commit 信息(可选,仅 sync 的本地 commit 使用)' },\n },\n output: {\n render: (_args, value: SyncResult) => renderSync(value),\n },\n execute: async (args: { action: 'pull' | 'push' | 'sync'; message?: string }): Promise<SyncResult> => {\n const dir = deps.wikiPath()\n /** Restart TW after a pull that changed the tree (stale snapshot drop). */\n const restartIfChanged = async (pulled: { changed?: boolean }): Promise<{ restarted?: boolean; restartError?: string }> => {\n if (pulled.changed !== true || deps.restartWiki === undefined) return {}\n try {\n // DRAIN THE SYNCER FIRST (v0.19.1, data safety): a PUT answers 204 as\n // soon as the tiddler is in TW's in-memory store; the filesystem\n // syncer writes it ~250ms later. Restarting TW before that flush KILLS\n // the write (the restarted server boots from the old snapshot and the\n // note is gone — not even the git commit that follows can recover it).\n // The agent path is the risky one: `tiddlywiki_put` → `git_sync`\n // back-to-back. Same sentinel trick as seeds.ts / index.ts.\n await flushPendingWrites(requireWiki(), join(dir, 'tiddlers')).catch(() => undefined)\n await deps.restartWiki()\n return { restarted: true }\n } catch (err) {\n return { restartError: err instanceof Error ? err.message : String(err) }\n }\n }\n switch (args.action) {\n case 'pull': {\n const r = await deps.git.pull(dir)\n const restart = await restartIfChanged(r)\n return { action: args.action, ok: r.ok, message: r.message, ...(r.conflictFiles !== undefined ? { conflictFiles: r.conflictFiles } : {}), ...(r.changed === true ? { changed: true } : {}), ...restart }\n }\n case 'push': {\n const r = await deps.git.push(dir)\n return { action: args.action, ok: r.ok, message: r.message }\n }\n case 'sync': {\n const pulled = await deps.git.pull(dir)\n if (!pulled.ok) return { action: args.action, ok: false, message: pulled.message, ...(pulled.conflictFiles !== undefined ? { conflictFiles: pulled.conflictFiles } : {}) }\n const restart = await restartIfChanged(pulled)\n const committed = await deps.git.commit(dir, args.message ?? `sync ${new Date().toISOString()}`)\n const pushed = await deps.git.push(dir)\n const status = await deps.git.status(dir)\n return {\n action: args.action,\n ok: pushed.ok,\n message: pushed.ok ? '同步完成' : pushed.message,\n pull: 'ok',\n ...(pulled.changed === true ? { changed: true } : {}),\n ...restart,\n commit: committed.message,\n push: pushed.message,\n status,\n }\n }\n }\n },\n }))\n\n // ── tiddlywiki_git_resolve ───────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_git_resolve',\n description: '在 tiddlywiki_git_sync action=pull 冲突(已 abort)后,按 tiddler 二选一解决:keep-local 保留本地版本;keep-remote 用 git fetch 拉取远端并检出远端版本(需已配置 git.remote);list 仅报告当前状态。解决后建议重新 pull/sync 整合其余改动。',\n parameters: {\n strategy: { type: 'string', enum: ['keep-local', 'keep-remote', 'list'], description: 'keep-local=保留本地;keep-remote=改用远端版本;list=仅报告当前 git 状态', required: true },\n files: { type: 'array', items: { type: 'string' }, description: '冲突文件名数组(来自 pull 返回的 conflictFiles;list 时忽略)' },\n },\n output: {\n render: (_args, value: ResolveResult) => {\n const lines = [`git resolve ${value.action}: ${value.ok ? '成功' : '失败'}`]\n lines.push(` ${value.message}`)\n if (value.files !== undefined && value.files.length > 0) lines.push(`涉及文件: ${value.files.join(', ')}`)\n if (value.commit !== undefined) lines.push(`本地 commit: ${value.commit}`)\n if (value.status !== undefined) lines.push(`状态: ${gitStatusBits(value.status)}`)\n return [{ type: 'text', text: lines.join('\\n') }]\n },\n },\n execute: async (args: { strategy: 'keep-local' | 'keep-remote' | 'list'; files?: string[] }): Promise<ResolveResult> => {\n const dir = deps.wikiPath()\n if (args.strategy === 'list') {\n const status = await deps.git.status(dir)\n return { ok: true, action: 'list', message: '当前仓库状态(pull 冲突已 abort,工作区即本地版本,不会残留未合并状态)', status }\n }\n const files = (args.files ?? []).filter((f) => typeof f === 'string' && f.length > 0)\n if (files.length === 0) {\n return { ok: false, action: args.strategy, message: '请提供 conflictFiles(来自 pull 返回)', hint: '可先执行 tiddlywiki_git_sync action=pull 查看冲突文件' }\n }\n if (args.strategy === 'keep-local') {\n const status = await deps.git.status(dir)\n return {\n ok: true,\n action: 'keep-local',\n message: `已保留本地版本(${files.length} 个文件;abort 后本地即为工作区内容)。建议重新 tiddlywiki_git_sync action=sync 整合远端其余改动。`,\n files,\n status,\n }\n }\n const fetched = await deps.git.fetch(dir)\n if (!fetched.ok) {\n return { ok: false, action: 'keep-remote', message: `fetch 失败(可能未配置 git.remote):${fetched.message}` }\n }\n const checked = await deps.git.checkoutFetchHead(dir, files)\n if (!checked.ok) {\n return { ok: false, action: 'keep-remote', message: `从远端检出失败:${checked.message}` }\n }\n const committed = await deps.git.commit(dir, `resolve conflict (keep remote) ${new Date().toISOString()}`)\n deps.autoCommit()\n const status = await deps.git.status(dir)\n return {\n ok: true,\n action: 'keep-remote',\n message: `已把 ${files.length} 个冲突文件改为远端版本并提交。建议继续 tiddlywiki_git_sync action=sync 完成整合与推送。`,\n files,\n commit: committed.message,\n status,\n }\n },\n }))\n\n return disposers\n}\n\n// ── tool result shapes + renders ───────────────────────────────────────────\n\ninterface SearchHit { title: string; tags: string[]; modified: string | null; snippet: string }\ninterface SearchResult { query: string; tags: string[]; since: string | null; type: string | null; field: string | null; value: string | null; total: number; results: SearchHit[] }\ninterface RecentResult { since: string | null; results: SearchHit[] }\ninterface TagListResult { count: number; total: number; truncated: boolean; tags: Array<{ tag: string; count: number }> }\ninterface GetResult {\n notFound: boolean\n title: string\n text: string\n tags: string[]\n fields: Record<string, unknown>\n modified: string | null\n /** Binary attachment (image/audio/…): `text` is withheld, these carry its type+size. */\n binary?: boolean\n binaryType?: string\n binaryChars?: number\n}\ninterface PutResult { ok: boolean; title: string; tags: string[]; type: string | null; typeDefaulted?: boolean; /** 覆盖时内容类型真的变了(v0.20.1)。 */ typeChanged?: { from: string; to: string }; fields: Record<string, unknown> | null }\ninterface BatchItemResult { title: string; written: boolean; skipped: boolean; failed: boolean; error?: string }\ninterface BatchResult { ok: boolean; written: number; skipped: number; failed: number; items: BatchItemResult[] }\ninterface RenameResult { ok: boolean; from: string; to: string; refsUpdated: number; refsTiddlers: number; warning?: string }\ninterface DeleteResult { ok: boolean; title: string; /** true = moved to the trash (recoverable). */ trashed?: boolean; trashTitle?: string }\ninterface TrashItem { title: string; at?: string; of?: string }\ninterface TrashResult { action: string; message: string; items?: TrashItem[] }\ninterface AppendResult { ok: boolean; title: string; mode: 'append' | 'prepend'; heading: string | null; created: boolean; added: number; total: number; type?: string | null; typeDefaulted?: boolean; typeChanged?: { from: string; to: string } }\ninterface BacklinkHit { title: string; refs: number; via: 'link' | 'tag'; modified: string | null }\ninterface BacklinkResult { title: string; total: number; linkCount: number; tagCount: number; items: BacklinkHit[] }\ninterface AttachResult { ok: boolean; title: string; mime: string; bytes: number; chars: number; source: string | null; embedInto: string | null }\ninterface LintIssue { kind: string; count: number; hint: string; samples: string[] }\ninterface LintResult { scanned: number; issues: LintIssue[] }\ninterface SyncResult {\n action: string\n ok: boolean\n message: string\n conflictFiles?: string[]\n pull?: string\n commit?: string\n push?: string\n changed?: boolean\n restarted?: boolean\n restartError?: string\n status?: GitStatusView\n}\ninterface ResolveResult {\n ok: boolean\n action: string\n message: string\n files?: string[]\n commit?: string\n hint?: string\n status?: GitStatusView\n}\n\n/** One-line 状态 summary: 分支 … 领先 … 落后 … 工作区未提交 … 最近提交. */\nfunction gitStatusBits(s: GitStatusView): string {\n const bits = [`分支 ${s.branch}`]\n if (s.ahead !== undefined) bits.push(`领先 ${s.ahead}`)\n if (s.behind !== undefined) bits.push(`落后 ${s.behind}`)\n if (s.dirty) bits.push(`工作区有 ${s.dirtyFiles.length} 个未提交改动`)\n if (s.lastCommit !== undefined) bits.push(`最近提交 ${s.lastCommit}`)\n return bits.join(' · ')\n}\n\nfunction renderSync(value: SyncResult): Array<{ type: 'text'; text: string }> {\n const lines = [`git ${value.action}: ${value.ok ? '成功' : '失败'}`]\n lines.push(` ${value.message}`)\n if (value.conflictFiles !== undefined && value.conflictFiles.length > 0) {\n lines.push(`冲突文件(rebase 已 abort,勿自动覆盖):`)\n for (const f of value.conflictFiles) lines.push(` - ${f}`)\n lines.push('处理方式:用 tiddlywiki_git_resolve files=[以上文件] strategy=keep-local|keep-remote 按 tiddler 二选一解决,再重新 sync;也可以直接让用户处理。')\n }\n if (value.commit !== undefined) lines.push(`本地 commit: ${value.commit}`)\n if (value.push !== undefined) lines.push(`远端 push: ${value.push}`)\n if (value.changed === true) {\n lines.push(value.restarted === true\n ? '本次 pull 拉取了新内容,TW 服务已自动重启(同端口),读取/搜索均为最新快照。'\n : '本次 pull 拉取了新内容,但 TW 服务未能自动重启(如需最新快照,请手动重启 TW)。')\n }\n if (value.restartError !== undefined) lines.push(`TW 重启失败: ${value.restartError}`)\n if (value.status !== undefined) {\n lines.push(`状态: ${gitStatusBits(value.status)}`)\n const s = value.status\n if (s.dirty && s.dirtyFiles.length > 0) lines.push(` 未提交: ${s.dirtyFiles.join(', ')}`)\n }\n return [{ type: 'text', text: lines.join('\\n') }]\n}\n","/**\n * Where the wiki lives (v0.22.0 runtime switching).\n *\n * WHY A FILE OUTSIDE THE WIKI\n * ---------------------------\n * The settings page stores its overrides in the wiki itself\n * (`$:/plugins/dsh-tiddlywiki/config`). \"Which wiki am I?\" cannot live there:\n * switching to another wiki would take the setting with it (the new wiki would\n * read its own config tiddler and forget the choice). The active location is\n * therefore a small pointer file under the DSH home, outside every wiki:\n *\n * $DSH_HOME/dsh-tiddlywiki/location.json\n * { \"version\": 1, \"active\": { \"root\": \"D:/notes\", \"name\": \"main\" } }\n *\n * PRECEDENCE (see `resolveWikiLocation()` in src/index.ts):\n * state file > cordis `config:` block > $DSH_HOME/tiddlywiki + \"main\"\n *\n * The cordis block keeps acting as the *default* — 「恢复为配置默认」 simply\n * deletes the pointer file. A malformed pointer is reported (never silently\n * acted upon) and falls back to the default, so a hand-edited JSON cannot make\n * the plugin boot somewhere unexpected.\n *\n * @module dsh-tiddlywiki/host/wiki-location\n */\nimport { mkdir, readFile, readdir, rename, rm, stat, writeFile } from 'node:fs/promises'\nimport { dirname, isAbsolute, join, resolve } from 'node:path'\nimport { dshHomePath } from '../sdk.ts'\n\n/** Pointer file schema version (bumped only on an incompatible change). */\nexport const LOCATION_STATE_VERSION = 1\n\n/** A wiki folder = `root` + folder `name` (`.` means \"root itself\"). */\nexport interface WikiLocation {\n root: string\n name: string\n}\n\n/** Result of reading the pointer file. */\nexport interface WikiLocationState {\n /** Present only when a valid pointer exists. */\n active?: WikiLocation\n /** Non-fatal read problem worth surfacing in the settings page. */\n error?: string\n}\n\n/** Where the CURRENT location came from (settings-page display). */\nexport type WikiLocationSource = 'state' | 'config' | 'default'\n\n/** Payload of `GET /admin/wiki/location` (built in src/index.ts). */\nexport interface WikiLocationInfo {\n current: { root: string; name: string; path: string; source: WikiLocationSource }\n default: { root: string; name: string; path: string }\n /** Pointer file path (shown so a user can find/delete it by hand). */\n stateFile: string\n /** Wiki-looking folders under the current root (folder names, `.` = the root). */\n candidates: string[]\n /** Set when the pointer file exists but could not be trusted. */\n error?: string\n}\n\n/** Default pointer file: `$DSH_HOME/dsh-tiddlywiki/location.json`. */\nexport function defaultLocationStateFile(): string {\n return dshHomePath('dsh-tiddlywiki', 'location.json')\n}\n\n/**\n * Expand `$VAR` / `${VAR}` / `%VAR%` from process.env (config uses `$DSH_HOME`).\n * Moved here (v0.22.0) so both `resolveWikiRoot()` and the location module\n * expand user input identically — the settings page accepts the same syntax as\n * the cordis config block.\n */\nexport function expandEnvPath(input: string, env: NodeJS.ProcessEnv = process.env): string {\n return input\n .replace(/\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g, (_m, name: string) => env[name] ?? '')\n .replace(/\\$([A-Za-z_][A-Za-z0-9_]*)/g, (_m, name: string) => env[name] ?? '')\n .replace(/%([A-Za-z_][A-Za-z0-9_]*)%/g, (_m, name: string) => env[name] ?? '')\n}\n\n/**\n * Normalise + validate a candidate location.\n *\n * `root` must be ABSOLUTE (a relative root would resolve against the dsh web\n * process cwd, i.e. \"wherever it happened to be launched from\" — the docs say\n * so, and now the code refuses to store it). `name` is a single folder name;\n * `.` means \"the root folder itself\".\n */\nexport function normalizeLocation(input: { root?: unknown; name?: unknown }): { location?: WikiLocation; error?: string } {\n const rawRoot = typeof input.root === 'string' ? input.root.trim() : ''\n if (rawRoot.length === 0) return { error: '路径不能为空' }\n const root = expandEnvPath(rawRoot)\n if (root.includes('\\0')) return { error: '路径含非法字符' }\n if (!isAbsolute(root)) return { error: `请使用绝对路径(当前:${root})——相对路径会按 dsh web 的启动目录解析` }\n const rawName = typeof input.name === 'string' && input.name.trim().length > 0 ? input.name.trim() : 'main'\n if (rawName !== '.' && (rawName.includes('/') || rawName.includes('\\\\') || rawName === '..' || isAbsolute(rawName))) {\n return { error: `文件夹名不能包含路径分隔符(当前:${rawName})` }\n }\n return { location: { root: resolve(root), name: rawName } }\n}\n\n/** Absolute folder a location points at. */\nexport function locationPath(location: WikiLocation): string {\n return resolve(location.root, location.name)\n}\n\n/** Read the pointer file. Missing → `{}`; malformed → `{ error }` (never throws). */\nexport async function readLocationState(file: string = defaultLocationStateFile()): Promise<WikiLocationState> {\n let raw: string\n try {\n raw = await readFile(file, 'utf8')\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code\n if (code === 'ENOENT') return {}\n return { error: `位置指针文件读不到(${file}):${err instanceof Error ? err.message : String(err)}` }\n }\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n return { error: `位置指针文件不是合法 JSON(${file})——已回退到配置默认值` }\n }\n if (typeof parsed !== 'object' || parsed === null) return { error: `位置指针文件格式不对(${file})` }\n const active = (parsed as { active?: unknown }).active\n if (typeof active !== 'object' || active === null) return { error: `位置指针文件缺少 active 字段(${file})` }\n const normalized = normalizeLocation(active as { root?: unknown; name?: unknown })\n if (normalized.location === undefined) {\n return { error: `位置指针内容非法(${normalized.error ?? '未知原因'})——已回退到配置默认值` }\n }\n return { active: normalized.location }\n}\n\n/** Write the pointer file atomically (tmp + rename, mkdir -p). */\nexport async function writeLocationState(location: WikiLocation, file: string = defaultLocationStateFile()): Promise<void> {\n const payload = JSON.stringify({ version: LOCATION_STATE_VERSION, active: location, updatedAt: new Date().toISOString() }, null, 2)\n await mkdir(dirname(file), { recursive: true })\n const tmp = `${file}.tmp`\n await writeFile(tmp, payload, 'utf8')\n await rename(tmp, file)\n}\n\n/** Delete the pointer file (「恢复为配置默认」). Missing file is a no-op. */\nexport async function clearLocationState(file: string = defaultLocationStateFile()): Promise<void> {\n await rm(file, { force: true })\n}\n\n/**\n * Folders under `root` that look like a TiddlyWiki server wiki (they contain\n * `tiddlywiki.info`), for the settings-page picker. `root` itself is reported\n * as `\".\"` when it is a wiki. Bounded: an unreadable/short directory is not an\n * error — the picker is a convenience, never a requirement.\n */\nexport async function listWikiCandidates(root: string, limit = 30): Promise<string[]> {\n const names: string[] = []\n try {\n const rootStat = await stat(root)\n if (!rootStat.isDirectory()) return names\n try {\n await stat(join(root, 'tiddlywiki.info'))\n names.push('.')\n } catch { /* root itself is not a wiki */ }\n const entries = await readdir(root, { withFileTypes: true })\n for (const entry of entries) {\n if (names.length >= limit) break\n if (!entry.isDirectory() || entry.name.startsWith('.')) continue\n try {\n await stat(join(root, entry.name, 'tiddlywiki.info'))\n names.push(entry.name)\n } catch { /* not a wiki folder */ }\n }\n } catch { /* missing/unreadable root → empty picker */ }\n return names.sort((a, b) => a.localeCompare(b))\n}\n","/**\n * Runtime wiki switching (v0.22.0).\n *\n * Rebinding the plugin from wiki A to wiki B is a *stateful* operation: the TW\n * child, the auto-committer, the fs watcher and the config-tiddler overlay all\n * hold the old folder. Doing it half-way leaves the process describing two\n * different wikis, so the sequence below is deliberately boring:\n *\n * stop child → release extras → repoint → start child → reload config\n * → core-bootstrap the new wiki → re-arm extras → persist the pointer\n *\n * The pointer is written LAST: if any earlier step fails we roll the server\n * back to the old folder and the on-disk pointer still names the working wiki,\n * so a dsh web restart can never land on a half-initialised target.\n *\n * The orchestration is separated from src/index.ts so it can be driven by a\n * real test (scripts/verify-wiki-switch.mjs) with a real WikiServer and two\n * temp folders — the rollback path is the part that must not rot.\n *\n * @module dsh-tiddlywiki/host/wiki-switch\n */\nimport { locationPath, normalizeLocation, type WikiLocation } from './wiki-location.ts'\n\n/** Everything the switch needs from the plugin host, as plain callbacks. */\nexport interface WikiSwitchDeps {\n /** Location currently served (undefined when the plugin is on the default). */\n currentLocation: () => WikiLocation | undefined\n /** Absolute path currently served. */\n currentPath: () => string\n /** Stop the TW child. */\n stopServer: () => Promise<void>\n /** Apply a location to the (stopped) server; throws when a child is running. */\n applyLocation: (target: WikiLocation) => void\n /** Start the TW child; throws when it never becomes ready. */\n startServer: () => Promise<void>\n /** Stop the auto-committer + fs watcher (they hold the old folder). */\n teardownExtras: () => Promise<void>\n /** Re-create the auto-committer + fs watcher for the CURRENT folder. */\n setupExtras: () => void\n /** Re-read the config tiddler overlay from the new wiki. */\n reloadConfig: () => Promise<void>\n /** Startup-equivalent bootstrap of a wiki (markdown plugin, core seeds, language). */\n bootstrap: () => Promise<void>\n /** Persist the pointer file (failure is reported, not rolled back). */\n savePointer: (target: WikiLocation) => Promise<void>\n /** Log a rollback / non-fatal problem. */\n log?: (message: string) => void\n}\n\n/** Successful switch. */\nexport interface WikiSwitchOk {\n ok: true\n location: WikiLocation\n path: string\n /** Set when the wiki switched but the pointer could not be persisted. */\n warning?: string\n}\n\n/** Failed switch (the old wiki keeps serving when `rolledBack` is true). */\nexport interface WikiSwitchFail {\n ok: false\n error: string\n rolledBack: boolean\n}\n\nexport type WikiSwitchResult = WikiSwitchOk | WikiSwitchFail\n\n/**\n * Switch to `input` (root + folder name, same shape as the cordis config).\n * Never throws: failures are returned, and the previous wiki is restored.\n */\nexport async function switchWiki(\n deps: WikiSwitchDeps,\n input: { root?: unknown; name?: unknown },\n): Promise<WikiSwitchResult> {\n const normalized = normalizeLocation(input)\n if (normalized.location === undefined) return { ok: false, error: normalized.error ?? '位置非法', rolledBack: true }\n const target = normalized.location\n const targetPath = locationPath(target)\n const previous = deps.currentLocation()\n const previousPath = deps.currentPath()\n if (targetPath === previousPath) {\n return { ok: true, location: target, path: targetPath }\n }\n\n const restore = async (): Promise<boolean> => {\n try {\n await deps.stopServer()\n deps.applyLocation(previous ?? { root: previousPath, name: '.' })\n await deps.startServer()\n await deps.reloadConfig()\n deps.setupExtras()\n deps.log?.(`wiki switch rolled back to ${previousPath}`)\n return true\n } catch (err) {\n deps.log?.(`wiki switch rollback FAILED: ${err instanceof Error ? err.message : String(err)}`)\n return false\n }\n }\n\n try {\n await deps.stopServer()\n await deps.teardownExtras()\n deps.applyLocation(target)\n await deps.startServer()\n await deps.reloadConfig()\n await deps.bootstrap()\n deps.setupExtras()\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n const rolledBack = await restore()\n return { ok: false, error: message, rolledBack }\n }\n\n // The wiki is live: persist the pointer. A failure here is NOT rolled back —\n // the running wiki is the one the user asked for; only the restart-survival\n // is lost, and saying so is more useful than reverting a working switch.\n try {\n await deps.savePointer(target)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n deps.log?.(`wiki switch: pointer write failed: ${message}`)\n return { ok: true, location: target, path: targetPath, warning: `位置已切换,但指针文件写入失败(重启 dsh web 后会回到原知识库):${message}` }\n }\n return { ok: true, location: target, path: targetPath }\n}\n","/**\n * dsh-tiddlywiki — host half.\n *\n * TiddlyWiki 5 as the DSH persistent knowledge base. Wiring:\n * - WikiServer spawns/kills/self-heals the TW 5 child process (loopback, auto\n * port) and scaffolds the wiki folder on first run;\n * - the git face bootstraps the wiki folder as a repository and wires the\n * debounced auto-committer;\n * - `tiddlywiki_*` agent tools + a system-prompt section;\n * - /dsh-tiddlywiki routes when a webServer is present.\n *\n * Export shape follows dsh-taskboard: a function/namespace plugin —\n * `name` / `inject` / `apply`, NO default export. Config arrives as the\n * second apply() argument (Cordis `runtime.callback(ctx, config)`).\n *\n * Extra exports (WikiServer / TiddlyWebClient / GitFace / ...) exist for the\n * headless selftest and future reuse; the loader only reads name/inject/apply.\n *\n * @module dsh-tiddlywiki\n */\nimport { watch, type FSWatcher } from 'node:fs'\nimport { readFile, writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { AutoCommitter, GitFace } from './host/git.ts'\nimport { registerRoutes, type AgentPresetsFace, type PermissionPresetsFace, type SessionControllerFace, type SessionPersistenceFace, type SessionsFace, type SessionQueryFace, type UiDefaultsPublic, type WebServerFace, type WorkspaceRegistryFace } from './host/routes.ts'\nimport { ConfigStore, DARK_PALETTE_DEFAULT, TW_WEB_HOST_TIDDLER, TW_WEB_HOST_DEFAULT, type PluginConfigShape } from './host/config.ts'\nimport { registerAdminRoutes, ensureLanguage, ensurePlugin, resolveTwRoot, type AdminDeps } from './host/admin.ts'\nimport { runAllSeeds, checkAllSeeds, runSeedById, removeSeedById, waitForFileWrite, flushPendingWrites, needsRestartAfterSeeds } from './host/seeds.ts'\nimport { RENDER_PLUGIN_FILE } from './host/seed-render.ts'\nimport { TiddlyWebClient, isBinaryType, TEXT_LIST_FILTER } from './host/tw-api.ts'\nimport { ClipBridge, downloadClipImage, type BridgeConfig, type ClipImageDownload } from './host/clip-bridge.ts'\nimport { registerTiddlywikiTools, tiddlywikiToolSummary, type ToolsDeps } from './host/tools.ts'\nimport { buildPromptText, normalizePromptMode, PROMPT_SECTION_NAME, PROMPT_SECTION_ORDER, type PromptConfig } from './host/prompt.ts'\nimport {\n clearLocationState,\n defaultLocationStateFile,\n expandEnvPath,\n listWikiCandidates,\n locationPath,\n readLocationState,\n writeLocationState,\n type WikiLocation,\n type WikiLocationInfo,\n type WikiLocationSource,\n} from './host/wiki-location.ts'\nimport { switchWiki, type WikiSwitchResult } from './host/wiki-switch.ts'\nimport { PATH_PREFIX, TW_PROXY_PATH, TW_PROXY_PREFIX, WikiServer } from './host/wiki.ts'\nimport { dshHomePath, defineTool } from './sdk.ts'\n\n/** Cordis plugin name (also the client loader id / profile row id). */\nexport const name = 'dsh-tiddlywiki'\n\n/** Required host services (tool registry + prompt assembly). */\nexport const inject = ['tools', 'systemPrompt']\n\n/** Re-exports for the headless selftest and future consumers. */\nexport { AutoCommitter, GitFace, PATH_PREFIX, TW_PROXY_PATH, TW_PROXY_PREFIX, TiddlyWebClient, isBinaryType, TEXT_LIST_FILTER, WikiServer, dshHomePath, defineTool }\nexport { ConfigStore, deepMerge } from './host/config.ts'\nexport { sanitizeTwFragment, isSafeUrl } from './host/sanitize.ts'\nexport { MISSING_TYPE_FILTER } from './host/tw-api.ts'\nexport {\n AGENT_WRITTEN_TAG,\n DEFAULT_NOTE_TYPE,\n HUMAN_EDITED_TAG,\n WriteConflictError,\n assertNoConflict,\n buildWriteTiddler,\n cleanTiddler,\n flattenTiddlerFields,\n} from './host/write-policy.ts'\nexport { openInTwEditor, registerRoutes } from './host/routes.ts'\nexport { writeSessionSummary, SESSION_SUMMARY_PREFIX } from './host/routes.ts'\nexport type { SessionQueryFace, SessionSummaryResult } from './host/routes.ts'\nexport { registerAdminRoutes, resolveTwRoot, readWikiInfo, writeWikiInfo, ensurePlugin, bundledCatalog, ensureLanguage, normalizeThemes, MASKED_SECRET, maskConfigSecrets, stripMaskedSecrets } from './host/admin.ts'\nexport { escapeInline } from './host/session-summary.ts'\nexport { seedDocNote, docNoteText, DOC_NOTE_TITLE, DOC_NOTE_TAG, DOC_NOTE_TEXT } from './host/seed-notes.ts'\nexport { hashText, parseSeedMarker, readSeedMarker, writeSeedMarker, SEED_MARKER_VERSION } from './host/seed-util.ts'\nexport { seedStarterDocs, STARTER_DOCS_ITEMS, STARTER_DOCS_MARKER_TITLE, DSH_DOCS_TAG } from './host/seed-starter-docs.ts'\nexport { seedSendToAgent, SEND_TO_AGENT_PLUGIN_TITLE, SEND_TO_AGENT_MARKER_TITLE, SEND_TO_AGENT_BUNDLE_TEXT } from './host/seed-send-to-agent.ts'\nexport { seedRenderRoute, RENDER_PLUGIN_TITLE, RENDER_MARKER_TITLE, RENDER_PLUGIN_FILE, RENDER_BUNDLE_TEXT } from './host/seed-render.ts'\nexport { seedHomeIndex, HOME_INDEX_ITEMS, HOME_INDEX_MARKER_TITLE, HOME_DEFAULT_TIDDLERS } from './host/seed-home.ts'\nexport { seedAllArticles, ALL_ARTICLES_TITLE, ALL_ARTICLES_MARKER_TITLE, ALL_ARTICLES_TEXT } from './host/seed-all-articles.ts'\nexport { seedUiStyles, UI_STYLE_ITEMS, UI_STYLES_MARKER_TITLE } from './host/seed-ui-styles.ts'\nexport { seedMenubarTheme, MENUBAR_THEME_TIDDLER, MENUBAR_THEME_MARKER_TITLE, MENUBAR_THEME_TEXT } from './host/seed-menubar-theme.ts'\nexport { seedClipBridge, unseedClipBridge, CLIP_BRIDGE_DOC_TITLE, CLIP_BRIDGE_MARKER_TITLE, CLIP_BRIDGE_DOC_TEXT, CLIP_BRIDGE_BOOKMARKLET, CLIP_BRIDGE_DRAG_HREF } from './host/seed-clip-bridge.ts'\nexport { runAllSeeds, checkAllSeeds, runSeedById, removeSeedById, waitForFileWrite, flushPendingWrites, needsRestartAfterSeeds, SEED_DEFS, type SeedStatus, type SeedRunResult } from './host/seeds.ts'\nexport { registerTiddlywikiTools, TRASH_PREFIX, TRASH_INDEX_TITLE, TrashIndexUnavailableError } from './host/tools.ts'\nexport { tiddlywikiToolSummary } from './host/tools.ts'\nexport {\n buildPromptText,\n escapePromptBraces,\n normalizePromptMode,\n toolSignatureLines,\n PROMPT_GOVERNANCE_BLOCKS,\n PROMPT_MODES,\n PROMPT_SECTION_NAME,\n PROMPT_SECTION_ORDER,\n DEFAULT_PROMPT_MODE,\n type PromptConfig,\n type PromptMode,\n type PromptToolSummary,\n} from './host/prompt.ts'\nexport {\n clearLocationState,\n defaultLocationStateFile,\n expandEnvPath,\n listWikiCandidates,\n locationPath,\n normalizeLocation,\n readLocationState,\n writeLocationState,\n LOCATION_STATE_VERSION,\n type WikiLocation,\n type WikiLocationInfo,\n type WikiLocationSource,\n type WikiLocationState,\n} from './host/wiki-location.ts'\nexport { switchWiki, type WikiSwitchResult, type WikiSwitchDeps } from './host/wiki-switch.ts'\nexport { ClipBridge, buildClipTiddler, buildImageNoteTiddler, buildBinaryTiddler, downloadClipImage, hostAllowed, parseClipPayload, pickImageMime, imageExtensionForMime, isPrivateAddress, assertPublicImageUrl, resolveClipTitle, type BridgeConfig, type ClipBridgeDeps, type ClipImageDownload, type ClipImageResult } from './host/clip-bridge.ts'\nexport type { PluginConfigShape } from './host/config.ts'\nexport type { GitStatusView } from './host/git.ts'\nexport type { Tiddler } from './host/tw-api.ts'\nexport type { WikiServerOptions, WikiStatusView } from './host/wiki.ts'\n\n/** Plugin config (design doc §13). Defaults are applied in apply().\n * Mirrors PluginConfigShape (src/host/config.ts) — the cordis `config:` block;\n * keep the two shapes in lockstep when adding a config field. */\nexport interface TiddlywikiConfig {\n wikiRoot?: string\n wiki?: string\n port?: number\n git?: { autoCommit?: boolean; debounceMs?: number; remote?: string; branch?: string }\n note?: { tag?: string }\n /** 本地剪藏桥(书签小工具):见 host/clip-bridge.ts 与 seed-clip-bridge.ts 文档。 */\n bridge?: { enabled?: boolean; port?: number; token?: string; tag?: string }\n /** 注入给每个会话的系统提示词(v0.21.0,见 host/prompt.ts)。 */\n prompt?: { enabled?: boolean; mode?: 'slim' | 'full'; extra?: string; override?: string }\n ui?: { showQuickNote?: boolean; showQuickNoteDock?: boolean; quickNoteMode?: 'native' | 'card'; sidebarLabel?: string; showPanelStatus?: boolean; showSyncButton?: boolean; followDshTheme?: boolean; darkPalette?: string; tabLabel?: string; showSessionTab?: boolean; showRightbarTab?: boolean; sendToAgent?: { enabled?: boolean; endpoint?: string; token?: string }; allArticles?: { pageSize?: number } }\n /** 启动时自动启用的 TW 语言代码(如 \"zh-Hans\"),也受配置 tiddler 覆盖。 */\n uiLanguage?: string\n auth?: { username?: string; password?: string }\n}\n\n/** Structural host context (subset of the dsh host + cordis surfaces). */\nexport interface HostCtx {\n tools: { register(tool: unknown): () => void }\n systemPrompt: { section(opts: { name: string; order: number; text: string }): () => void }\n inject<T = unknown>(names: string | string[], callback: (ctx: HostCtx) => T, config?: unknown): unknown\n effect(fn: () => unknown, label?: string): void\n get(name: string): unknown\n [key: string]: unknown\n}\n\n/** Resolved plugin config (defaults merged with the `config:` block). */\ninterface ResolvedConfig {\n wikiRoot: string\n wiki: string\n port: number\n git: { autoCommit: boolean; debounceMs: number; remote: string; branch: string }\n note: { tag: string }\n bridge: { enabled: boolean; port: number; token: string; tag: string }\n ui: { showQuickNote: boolean; showQuickNoteDock: boolean; quickNoteMode: 'native' | 'card'; sidebarLabel: string; showPanelStatus: boolean; showSyncButton: boolean; followDshTheme: boolean; darkPalette: string; tabLabel: string; showSessionTab: boolean; showRightbarTab: boolean; sendToAgent: { enabled: boolean; endpoint?: string; token?: string }; allArticles: { pageSize: number } }\n uiLanguage: string\n auth: { username?: string; password?: string }\n}\n\n/** 剪藏桥默认端口(与 seed 文档书签代码里的地址保持一致)。 */\nexport const CLIP_BRIDGE_DEFAULT_PORT = 8618\n\n/**\n * Official plugin whose parser every note this plugin writes depends on\n * (`text/markdown`). `--init server` does NOT include it — see ensurePlugin().\n */\nexport const MARKDOWN_PLUGIN = 'tiddlywiki/markdown'\n\nconst DEFAULTS: ResolvedConfig = {\n wikiRoot: '',\n wiki: 'main',\n port: 0,\n git: { autoCommit: true, debounceMs: 60_000, remote: '', branch: 'main' },\n note: { tag: 'inbox' },\n bridge: { enabled: false, port: CLIP_BRIDGE_DEFAULT_PORT, token: '', tag: 'clip' },\n ui: { showQuickNote: true, showQuickNoteDock: true, quickNoteMode: 'native', sidebarLabel: 'TiddlyWiki', showPanelStatus: true, showSyncButton: true, followDshTheme: true, darkPalette: DARK_PALETTE_DEFAULT, tabLabel: '知识库', showSessionTab: true, showRightbarTab: true, sendToAgent: { enabled: true }, allArticles: { pageSize: 10 } },\n uiLanguage: '',\n auth: { username: '', password: '' },\n}\n\n/** Config tiddler steering TW's frontend API base (tiddlywebadaptor). */\nexport { TW_WEB_HOST_TIDDLER, TW_WEB_HOST_DEFAULT } from './host/config.ts'\n\n/**\n * Point TW's frontend at the same-origin DSH proxy (remote-access mode, R1).\n * The tiddlywebadaptor builds every API URL from $:/config/tiddlyweb/host; its\n * default `$protocol$//$host$/` resolves to the iframe's origin ROOT, which\n * 404s whenever the browser is not on the same machine as TW. Written only\n * when the tiddler is missing or still the legacy default — a user override is\n * honored (e.g. someone who really does expose TW on a dedicated origin).\n */\nexport async function ensureTwWebHost(client: TiddlyWebClient | undefined): Promise<void> {\n if (client === undefined) return\n // Read WITHOUT swallowing: only a 404 means \"missing\". A transient failure\n // must not look like \"absent\" and overwrite a user's custom host value.\n const current = (await client.get(TW_WEB_HOST_TIDDLER))?.text\n if (current !== undefined && current.trim() !== TW_WEB_HOST_DEFAULT) return\n await client.put({ title: TW_WEB_HOST_TIDDLER, text: TW_PROXY_PATH, type: 'text/plain', tags: [] })\n}\n\n/**\n * Resolve the DEFAULT wikiRoot: explicit config (env-expanded) else\n * $DSH_HOME/tiddlywiki. The runtime pointer file can override it — see\n * `readLocationState()` in host/wiki-location.ts.\n */\nfunction resolveWikiRoot(config: TiddlywikiConfig): string {\n if (config.wikiRoot !== undefined && config.wikiRoot.trim().length > 0) {\n return expandEnvPath(config.wikiRoot.trim())\n }\n return dshHomePath('tiddlywiki')\n}\n\n/**\n * Ensure the wiki's `.gitignore` covers TW's transient artifacts, WITHOUT\n * clobbering rules the user added.\n *\n * This used to rewrite the whole file on every start, silently deleting any\n * custom ignore rules the user had put in `wiki/.gitignore`. Now the file is\n * only touched when one of the managed lines is missing, and the user's own\n * content is preserved verbatim.\n */\nconst MANAGED_GITIGNORE_LINES = [\n 'tiddlers/$__temp_*',\n 'tiddlers/$__StoryList*',\n 'tiddlers/$__HistoryList*',\n '*.meta.tmp',\n // flush 哨兵(每次「重启前排干 syncer」都会重写,内容是时间戳):不进 git,\n // 否则每次同步/播种都会给用户仓库塞一个无意义的 diff(v0.19.1)。\n 'tiddlers/$__plugins_dsh-tiddlywiki_flush-probe.tid',\n 'tiddlers/$__plugins_dsh-tiddlywiki_flush-probe.txt',\n 'tiddlers/$__plugins_dsh-tiddlywiki_flush-probe.txt.meta',\n]\n\nasync function writeGitignore(wikiPath: string): Promise<void> {\n const file = join(wikiPath, '.gitignore')\n let current = ''\n try {\n current = await readFile(file, 'utf8')\n } catch {\n /* absent → first run, write the default block */\n }\n const existing = current.split(/\\r?\\n/).map((line) => line.trim())\n const missing = MANAGED_GITIGNORE_LINES.filter((line) => !existing.includes(line))\n if (missing.length === 0) return\n const block = [\n '# TiddlyWiki transient artifacts (auto-managed by dsh-tiddlywiki)',\n ...missing,\n '',\n ].join('\\n')\n const prefix = current.length === 0 ? '' : current.endsWith('\\n') ? `${current}\\n` : `${current}\\n\\n`\n await writeFile(file, `${prefix}${block}`, 'utf8')\n}\n\n/** Watch the wiki folders and touch the auto-committer on changes. */\nfunction watchWiki(wikiPath: string, onChange: () => void): () => void {\n const watchers: FSWatcher[] = []\n for (const dir of [join(wikiPath, 'tiddlers'), wikiPath]) {\n try {\n const watcher = watch(dir, { persistent: false }, () => onChange())\n watchers.push(watcher)\n } catch {\n /* directory may not exist yet; the committer also fires on our writes */\n }\n }\n return () => {\n for (const watcher of watchers) {\n try { watcher.close() } catch { /* already closed */ }\n }\n }\n}\n\n/**\n * Wait until a file exists on disk (polling), up to `timeoutMs`. TW's syncer\n * flushes REST writes to the filesystem on a ~250ms task timer, so a freshly\n * seeded tiddler is not on disk the moment PUT resolves — a caller that must\n * restart TW right after (so a seeded server route loads) has to wait for the\n * flush first, or the restarted TW would boot from a stale snapshot and the\n * seeded module would be missing.\n *\n * The implementation lives in host/seeds.ts (shared with the settings page's\n * seed-run route), which is where `waitForFileWrite` is imported from.\n */\n\n\n/**\n * Mount the host half.\n * @param ctx - the plugin context (tools + systemPrompt injected).\n * @param rawConfig - the plugin row's `config:` block (Cordis second arg).\n */\nexport function apply(ctx: HostCtx, rawConfig: TiddlywikiConfig = {}): void {\n const config: ResolvedConfig = {\n wikiRoot: resolveWikiRoot(rawConfig),\n wiki: rawConfig.wiki ?? DEFAULTS.wiki,\n port: rawConfig.port ?? DEFAULTS.port,\n git: { ...DEFAULTS.git, ...(rawConfig.git ?? {}) },\n note: { ...DEFAULTS.note, ...(rawConfig.note ?? {}) },\n bridge: { ...DEFAULTS.bridge, ...(rawConfig.bridge ?? {}) },\n ui: {\n ...DEFAULTS.ui,\n ...(rawConfig.ui ?? {}),\n // Merge nested ui.* groups explicitly so defaults stay required (a plain\n // spread of the lax cordis shape would widen them to optional).\n sendToAgent: { ...DEFAULTS.ui.sendToAgent, ...(rawConfig.ui?.sendToAgent ?? {}) },\n allArticles: { ...DEFAULTS.ui.allArticles, ...(rawConfig.ui?.allArticles ?? {}) },\n },\n uiLanguage: typeof rawConfig.uiLanguage === 'string' ? rawConfig.uiLanguage.trim() : DEFAULTS.uiLanguage,\n auth: { ...DEFAULTS.auth, ...(rawConfig.auth ?? {}) },\n }\n // The location is MUTABLE since v0.22.0: the runtime pointer file (read below\n // in the startup task) and the settings page's「切换」both repoint it. Every\n // consumer reads it through a getter (`() => wikiPath`), never a captured copy.\n let wikiPath = join(config.wikiRoot, config.wiki)\n /** Pointer file the runtime switch persists (outside every wiki, see wiki-location.ts). */\n const locationStateFile = defaultLocationStateFile()\n const git = new GitFace()\n\n // Runtime-editable config (settings page): the cordis `config:` block is the\n // BASE; a config tiddler ($:/plugins/dsh-tiddlywiki/config) written by the\n // settings page overlays it. Effective values come from configStore.get().\n const configStore = new ConfigStore({ note: config.note, git: config.git, ui: config.ui, uiLanguage: config.uiLanguage, bridge: config.bridge } satisfies PluginConfigShape)\n const eff = (): PluginConfigShape => configStore.get()\n const effectiveNoteTag = (): string => {\n const tag = eff().note?.tag\n return typeof tag === 'string' && tag.trim().length > 0 ? tag : config.note.tag\n }\n /**\n * Effective bridge config (defaults + settings-page overlay). Used PER\n * REQUEST by the clip bridge — enabled/token/tag edits on the settings page\n * apply immediately; only the port is fixed at startup (listener binds once).\n */\n const effectiveBridge = (): BridgeConfig => {\n const b = eff().bridge ?? {}\n const port = typeof b.port === 'number' && Number.isInteger(b.port) && b.port > 0 && b.port < 65536 ? b.port : config.bridge.port\n const token = typeof b.token === 'string' ? b.token : ''\n const tag = typeof b.tag === 'string' && b.tag.trim().length > 0 ? b.tag.trim() : config.bridge.tag\n return { enabled: b.enabled === true, port, token, tag }\n }\n // Return type comes from routes.ts (single declaration of the UI payload the\n // client consumes — v0.20.0, previously mirrored inline here).\n const effectiveUi = (): UiDefaultsPublic => {\n const ui = eff().ui ?? {}\n const palette = typeof ui.darkPalette === 'string' && ui.darkPalette.trim().length > 0 ? ui.darkPalette.trim() : DARK_PALETTE_DEFAULT\n const label = typeof ui.sidebarLabel === 'string' && ui.sidebarLabel.trim().length > 0 ? ui.sidebarLabel.trim() : config.ui.sidebarLabel\n const tabLabel = typeof ui.tabLabel === 'string' && ui.tabLabel.trim().length > 0 ? ui.tabLabel.trim() : config.ui.tabLabel\n const quickNoteMode: 'native' | 'card' = ui.quickNoteMode === 'card' ? 'card' : 'native'\n return {\n showQuickNote: ui.showQuickNote !== false,\n showQuickNoteDock: ui.showQuickNoteDock !== false,\n quickNoteMode,\n sidebarLabel: label,\n showPanelStatus: ui.showPanelStatus !== false,\n showSyncButton: ui.showSyncButton !== false,\n followDshTheme: ui.followDshTheme !== false,\n darkPalette: palette,\n tabLabel,\n showSessionTab: ui.showSessionTab !== false,\n showRightbarTab: ui.showRightbarTab !== false,\n }\n }\n\n const disposers: Array<() => void> = []\n const disposeAll = (): void => {\n for (const dispose of disposers.splice(0)) dispose()\n }\n\n // ── System prompt section (v0.21.0: configurable + live) ──────────────────\n // The section text is built from the EFFECTIVE config (cordis base + config\n // tiddler overlay), so the settings page controls it. DSH emits\n // `system-prompt/change` on registration/disposal and re-renders the section\n // in history, so a settings save applies to the CURRENT session from its next\n // model step — no dsh web restart, no new session (the pre-v0.21 behaviour).\n //\n // `applyPrompt()` is called (a) after the wiki is up and the config tiddler\n // has been loaded, and (b) after every settings-page save (admin route →\n // `onConfigChanged`). Re-registration is skipped when the built text is\n // unchanged, so saving an unrelated setting never churns the prompt.\n let disposePromptSection: (() => void) | undefined\n let currentPromptText: string | undefined\n /** Built text for the current effective config (also the preview endpoint). */\n const promptText = (): string => {\n const p = (eff().prompt ?? {}) as PromptConfig\n return buildPromptText({\n enabled: p.enabled !== false,\n mode: normalizePromptMode(p.mode),\n extra: typeof p.extra === 'string' ? p.extra : '',\n override: typeof p.override === 'string' ? p.override : '',\n // Signature catalogue for `full` mode comes from the live tool registry,\n // never from hand-written prose (v0.21.0 — the old copy had drifted).\n tools: tiddlywikiToolSummary(),\n })\n }\n const applyPrompt = (): void => {\n const next = promptText()\n if (next === currentPromptText) return\n currentPromptText = next\n disposePromptSection?.()\n disposePromptSection = undefined\n if (next.length === 0) return\n disposePromptSection = ctx.systemPrompt.section({ name: PROMPT_SECTION_NAME, order: PROMPT_SECTION_ORDER, text: next })\n }\n disposers.push(() => {\n disposePromptSection?.()\n disposePromptSection = undefined\n })\n\n // TW child server.\n const server = new WikiServer({\n wikiRoot: config.wikiRoot,\n wiki: config.wiki,\n port: config.port,\n username: config.auth.username,\n password: config.auth.password,\n })\n\n // Lazy TW client. Rebuilt whenever the bound PORT changes (the wiki re-probes\n // a free port when a crash happened before readiness, so a cached client must\n // not stay pinned to a dead port). Credentials are attached preemptively: when\n // auth.username is configured the TW child runs behind `readers`/`writers`, so\n // EVERY request (reads included) needs Basic auth.\n let clientCache: TiddlyWebClient | undefined\n let clientPort: number | undefined\n const client = (): TiddlyWebClient | undefined => {\n const port = server.currentPort\n if (port === undefined) return undefined\n if (clientCache === undefined || clientPort !== port) {\n clientCache = new TiddlyWebClient(`http://127.0.0.1:${port}`, { username: config.auth.username, password: config.auth.password })\n clientPort = port\n }\n return clientCache\n }\n\n // 本地剪藏桥(书签小工具后端):只监听 127.0.0.1,per-request 读 effective\n // config —— enabled/token/tag 在设置页保存后立即生效;port 只在启动时绑定\n // 一次(改端口需重启 dsh web,seed 文档已说明)。写入走唯一的 TiddlyWebClient\n // 通道(D1),不存在第二条写路径。\n const clipBridge = new ClipBridge({\n getConfig: effectiveBridge,\n write: async (tiddler) => {\n const c = client()\n if (c === undefined) throw new Error('wiki not ready')\n await c.put(tiddler)\n },\n exists: async (title) => {\n const c = client()\n if (c === undefined) throw new Error('wiki not ready')\n return (await c.get(title)) !== undefined\n },\n // Server-side image download: no browser CORS; a browser-ish UA + the clip\n // source page as Referer get past most hotlink-protected CDNs. The whole\n // SSRF posture (public http(s) only, per-hop validation with the resolved\n // address PINNED so DNS cannot rebind between check and connect, manual\n // redirects, streaming 15MB cap, no transparent decompression) lives in\n // downloadClipImage (v0.19.0).\n download: async (imageUrl, referer): Promise<ClipImageDownload> => {\n const result = await downloadClipImage(imageUrl, referer)\n return { buffer: result.buffer, type: result.type ?? null }\n },\n log: (m) => console.info('[dsh-tiddlywiki] clip bridge:', m),\n })\n ctx.effect(() => () => clipBridge.stop(), 'dsh-tiddlywiki: clip bridge')\n\n // Auto-committer + filesystem watcher (created after the wiki dir exists).\n // Reads the EFFECTIVE config so a settings-page git change survives a restart.\n //\n // SPLIT SETUP/TEARDOWN (v0.22.0): a runtime wiki switch has to release both\n // (they hold the OLD folder) and re-arm them for the new one. The teardown is\n // registered exactly ONCE below — a per-switch `disposers.push()` would leak a\n // disposer per switch.\n let committer: AutoCommitter | undefined\n let unwatch: (() => void) | undefined\n const setupCommitter = (): void => {\n const g = eff().git ?? {}\n committer = new AutoCommitter({\n git,\n dir: wikiPath,\n enabled: g.autoCommit ?? config.git.autoCommit,\n debounceMs: g.debounceMs ?? config.git.debounceMs,\n message: () => `wiki autocommit ${new Date().toISOString()}`,\n onError: (err) => console.warn('[dsh-tiddlywiki] autocommit:', err),\n })\n unwatch = watchWiki(wikiPath, () => committer?.touch())\n }\n const teardownCommitter = async (): Promise<void> => {\n try { unwatch?.() } catch { /* already closed */ }\n unwatch = undefined\n const current = committer\n committer = undefined\n // Flush a pending auto-commit BEFORE dropping the folder, so a write made\n // within the debounce window is not left uncommitted (switch + shutdown).\n try { await current?.flush() } catch { /* best-effort */ }\n current?.dispose()\n }\n disposers.push(() => { void teardownCommitter() })\n\n // Git bootstrap: repo init + initial commit + .gitignore (+ remote/first push).\n const bootstrapGit = async (): Promise<void> => {\n const g = eff().git ?? {}\n const branch = g.branch ?? config.git.branch\n const remote = g.remote ?? config.git.remote\n const isRepo = await git.isRepo(wikiPath)\n if (!isRepo) {\n await git.init(wikiPath, branch)\n await writeGitignore(wikiPath)\n await git.initialCommit(wikiPath)\n } else {\n await writeGitignore(wikiPath)\n }\n if (remote.trim().length > 0) {\n const ensured = await git.ensureRemote(wikiPath, remote.trim())\n if (ensured.ok) {\n const first = await git.firstPush(wikiPath)\n if (!first.ok) console.warn('[dsh-tiddlywiki] first push failed (retry with tiddlywiki_git_sync):', first.message)\n } else {\n console.warn('[dsh-tiddlywiki] git remote setup:', ensured.message)\n }\n }\n }\n\n // Tools (works even while the wiki is down; wiki() resolves lazily).\n const toolsDeps: ToolsDeps = {\n wiki: client,\n git,\n wikiPath: () => wikiPath,\n autoCommit: () => committer?.touch(),\n // After a pull that changed the working tree, restart TW so the server\n // (and the agent's reads) see the pulled content, not the old snapshot.\n restartWiki: async () => { await server.restart() },\n }\n disposers.push(...registerTiddlywikiTools(ctx, toolsDeps))\n // Register the prompt section only AFTER the tools: `full` mode's signature\n // catalogue is generated from the registry filled by that call (v0.21.0).\n applyPrompt()\n\n // Bring the wiki up, load the override config, then bootstrap git + committer.\n //\n // DISPOSAL-AWARE (v0.19.1): this task is fire-and-forget, but teardown can run\n // while it is still awaiting (plugin hot-reload / disable / dsh web exit\n // within the first seconds). Without the flag the disposer finished first and\n // the startup task then spawned the TW child / bound the clip-bridge port —\n // an orphan process and a leaked listener. Every await below is followed by a\n // `disposed` check, and the task stops the child itself when it notices.\n let disposed = false\n /**\n * Core-bootstrap the CURRENT `wikiPath`: markdown parser plugin, the core +\n * starter seeds, and the configured UI language. Extracted from the startup\n * task (v0.22.0) because a runtime wiki switch must run exactly this on the\n * new folder — the two paths must not drift apart.\n *\n * Only `render-route` needs a TW restart (it carries a SERVER route loaded at\n * boot); plain-content seeds must NOT restart, or the syncer's unflushed REST\n * writes would be lost.\n */\n const bootstrapWiki = async (): Promise<void> => {\n const seedClient = client()\n if (seedClient === undefined) return\n let pluginAdded = false\n try {\n pluginAdded = await ensurePlugin(wikiPath, resolveTwRoot(), MARKDOWN_PLUGIN)\n if (pluginAdded) console.info(`[dsh-tiddlywiki] enabled ${MARKDOWN_PLUGIN} for this wiki`)\n } catch (err) {\n console.warn(`[dsh-tiddlywiki] enabling ${MARKDOWN_PLUGIN}:`, err)\n }\n const seedStartedAt = Date.now()\n const results = await runAllSeeds({ client: seedClient, tools: tiddlywikiToolSummary() })\n for (const r of results) {\n if (!r.ok) console.warn(`[dsh-tiddlywiki] seed ${r.id} failed:`, r.error ?? r.detail)\n }\n if (needsRestartAfterSeeds(results)) {\n const renderFile = join(wikiPath, 'tiddlers', RENDER_PLUGIN_FILE)\n const flushed = await waitForFileWrite(renderFile, 8_000, 150, seedStartedAt)\n if (!flushed) console.warn('[dsh-tiddlywiki] seeded render plugin file not seen on disk before restart')\n // Drain the rest of the syncer queue as well: a restart that boots from a\n // stale snapshot loses every write still queued (v0.19.0).\n const drained = await flushPendingWrites(seedClient, join(wikiPath, 'tiddlers'))\n if (!drained) console.warn('[dsh-tiddlywiki] seed writes may not have been flushed before restart')\n if (!disposed) await server.restart()\n } else if (pluginAdded) {\n // tiddlywiki.info is written directly by us (no TW flush to wait for),\n // but TW only loads the plugin at boot.\n if (!disposed) await server.restart()\n }\n // Apply the configured UI language (e.g. \"zh-Hans\"): enable the bundled\n // language plugin in tiddlywiki.info.languages + restart once so TW loads\n // it at boot (fully offline — official language packs ship in the pkg).\n const uiLang = eff().uiLanguage\n if (typeof uiLang === 'string' && uiLang.trim().length > 0) {\n try {\n const code = uiLang.trim()\n const changed = await ensureLanguage(wikiPath, resolveTwRoot(), code)\n if (changed && !disposed) await server.restart()\n // Pin the active language tiddler so TW's UI actually switches.\n const langClient = client()\n if (langClient !== undefined) {\n await langClient.put({ title: '$:/language', text: `$:/languages/${code}`, type: 'text/plain', tags: [] }).catch(() => undefined)\n }\n } catch (err) {\n console.warn('[dsh-tiddlywiki] applying uiLanguage:', err)\n }\n }\n }\n\n const startupTask = (async () => {\n try {\n // Runtime pointer FIRST (v0.22.0): the settings page's「切换」persists the\n // chosen location in $DSH_HOME/dsh-tiddlywiki/location.json, which must\n // win over the cordis config default. Applied while the child is still\n // stopped, so the very first spawn already serves the right folder.\n const saved = await readLocationState(locationStateFile)\n if (saved.error !== undefined) console.warn('[dsh-tiddlywiki]', saved.error)\n if (saved.active !== undefined) {\n server.setLocation(saved.active)\n wikiPath = locationPath(saved.active)\n console.info(`[dsh-tiddlywiki] wiki location (pointer file): ${wikiPath}`)\n }\n await server.start()\n if (disposed) {\n await server.stop().catch(() => undefined)\n return\n }\n await configStore.load(client())\n // The config tiddler may carry prompt.* overrides: rebuild the already\n // registered section so a saved preference applies without a restart.\n applyPrompt()\n if (disposed) {\n await server.stop().catch(() => undefined)\n return\n }\n // Clip bridge: bind once on the configured port (works even while\n // disabled — every request re-checks the effective enabled flag, so the\n // settings-page toggle applies without a dsh web restart).\n try {\n await clipBridge.start(effectiveBridge().port)\n console.info(`[dsh-tiddlywiki] clip bridge listening on 127.0.0.1:${clipBridge.port} (enabled=${effectiveBridge().enabled})`)\n } catch (err) {\n console.warn('[dsh-tiddlywiki] clip bridge start:', err)\n }\n if (disposed) {\n try { await clipBridge.stop() } catch { /* already closing */ }\n await server.stop().catch(() => undefined)\n return\n }\n // Point TW's frontend at the same-origin DSH proxy (remote-access mode):\n // part of the seed registry (tw-web-host), so it is also covered by the\n // settings page's 重新初始化. Runs before git bootstrap so the config\n // tiddler joins the first commit.\n //\n // Seeds run after the effective config is loaded: every one-time\n // \"与 dsh 联动需要 wiki 预置\" item lives in the SEED_DEFS registry, and the\n // startup path seeds ONLY the core items (功能必需:发送给 Agent 按钮 +\n // TW 前端 API 基址 + 原生渲染路由) plus the starter docs — NON-force, so a\n // same-named tiddler is never overwritten. Optional seeds (首页 / 所有文章 /\n // 自定义样式 / menubar 顶栏主题自适应 / 剪藏桥说明) are opt-in from the\n // settings page「初始化」section.\n // Markdown parser + seeds + UI language: the SAME routine a runtime wiki\n // switch runs on its new folder (v0.22.0, host/wiki-switch.ts).\n try {\n await bootstrapWiki()\n } catch (err) {\n console.warn('[dsh-tiddlywiki] seeding wiki:', err)\n }\n } catch (err) {\n console.warn('[dsh-tiddlywiki] startup issue (self-healing is armed):', err)\n }\n // Git bootstrap + the auto-committer do not depend on the TW child, so run\n // them even when the wiki failed to start: every write is still versioned\n // (and a retry/restart later finds a ready repository). Skipped entirely\n // once disposed — otherwise this would register a watcher/timer (and an\n // initial commit) after teardown already ran.\n if (disposed) return\n try {\n await bootstrapGit()\n if (disposed) return\n setupCommitter()\n } catch (err) {\n console.warn('[dsh-tiddlywiki] git bootstrap failed:', err)\n }\n })()\n\n // ── Runtime wiki location (v0.22.0) ────────────────────────────────────────\n // The cordis `config:` block stays the DEFAULT; the settings page can switch\n // the running plugin to another folder and persists that choice in a pointer\n // file OUTSIDE every wiki (host/wiki-location.ts explains why). Everything\n // below is read through getters, so a switch is visible to tools, routes and\n // the git face immediately.\n const defaultLocation: WikiLocation = { root: config.wikiRoot, name: config.wiki }\n /** Source of the CURRENT location, for the settings page. */\n const locationSource = async (): Promise<WikiLocationSource> => {\n const state = await readLocationState(locationStateFile)\n if (state.active !== undefined && locationPath(state.active) === wikiPath) return 'state'\n return typeof rawConfig.wikiRoot === 'string' && rawConfig.wikiRoot.trim().length > 0 ? 'config' : 'default'\n }\n const locationInfo = async (): Promise<WikiLocationInfo> => {\n const state = await readLocationState(locationStateFile)\n const current = server.currentLocation\n return {\n current: { ...current, path: wikiPath, source: await locationSource() },\n default: { ...defaultLocation, path: locationPath(defaultLocation) },\n stateFile: locationStateFile,\n candidates: await listWikiCandidates(current.root),\n ...(state.error !== undefined ? { error: state.error } : {}),\n }\n }\n /** Single-flight guard: two concurrent switches would fight over the child. */\n let switching = false\n const runSwitch = async (target: { root?: unknown; name?: unknown }, persist: (t: WikiLocation) => Promise<void>): Promise<WikiSwitchResult> => {\n if (switching) return { ok: false, error: '正在切换知识库,请稍候再试', rolledBack: true }\n switching = true\n try {\n return await switchWiki({\n currentLocation: () => server.currentLocation,\n currentPath: () => wikiPath,\n stopServer: () => server.stop(),\n applyLocation: (nextLocation) => {\n server.setLocation({ root: nextLocation.root, name: nextLocation.name })\n wikiPath = locationPath(nextLocation)\n // The cached REST client holds a 2s list cache belonging to the OLD\n // wiki; drop it so the next request reads the new one.\n clientCache = undefined\n clientPort = undefined\n },\n startServer: async () => { await server.start() },\n teardownExtras: () => teardownCommitter(),\n setupExtras: () => { setupCommitter() },\n reloadConfig: async () => {\n await configStore.load(client())\n // The new wiki carries its own config tiddler → its own prompt.*.\n applyPrompt()\n },\n bootstrap: async () => {\n await bootstrapWiki()\n await bootstrapGit()\n },\n savePointer: persist,\n log: (message) => console.warn('[dsh-tiddlywiki]', message),\n }, target)\n } finally {\n switching = false\n }\n }\n /** Switch to another folder and remember the choice. */\n const switchWikiLocation = (target: { root?: unknown; name?: unknown }): Promise<WikiSwitchResult> =>\n runSwitch(target, async (t) => { await writeLocationState(t, locationStateFile) })\n /**\n * 「恢复为配置默认」: drop the pointer and go back to the cordis default. The\n * pointer must be cleared EVEN when the current folder already IS the default\n * (otherwise the stale pointer would win again after the next restart).\n */\n const resetWikiLocation = async (): Promise<WikiSwitchResult> => {\n if (locationPath(defaultLocation) === wikiPath) {\n await clearLocationState(locationStateFile)\n return { ok: true, location: defaultLocation, path: wikiPath }\n }\n return runSwitch(defaultLocation, async () => { await clearLocationState(locationStateFile) })\n }\n\n // Routes + settings-panel admin surface (lazy webServer).\n ctx.inject(['webServer'], (webCtx: HostCtx) => {\n const ws = (webCtx as unknown as { webServer: WebServerFace }).webServer\n // sessionController is a core host service; read it LAZILY per request (via\n // the plugin root ctx) because it may be registered after webServer, and the\n // agent routes must work whenever a request actually arrives.\n const getSessionController = (): SessionControllerFace | undefined =>\n ctx.get('sessionController') as SessionControllerFace | undefined\n const getWorkspaceRegistry = (): WorkspaceRegistryFace | undefined =>\n ctx.get('workspaceRegistry') as WorkspaceRegistryFace | undefined\n // agentPresets (工作模式 roster) + sessionPersistence (per-session preset\n // badges) are also core host services — resolve them lazily like the above.\n const getAgentPresets = (): AgentPresetsFace | undefined =>\n ctx.get('agentPresets') as AgentPresetsFace | undefined\n const getSessionPersistence = (): SessionPersistenceFace | undefined =>\n ctx.get('sessionPersistence') as SessionPersistenceFace | undefined\n // permissionPresets (权限 preset roster for the picker + applying the\n // chosen permission to new sessions) and the `sessions` in-memory store\n // (the created live session handed to permissionPresets.set) are core host\n // services — resolve them lazily like the ones above.\n const getPermissionPresets = (): PermissionPresetsFace | undefined =>\n ctx.get('permissionPresets') as PermissionPresetsFace | undefined\n const getSessions = (): SessionsFace | undefined =>\n ctx.get('sessions') as SessionsFace | undefined\n // sessionQuery (会话日志查询,供「知识库」Tab 判定「本会话相关笔记」) 也是核心\n // host 服务 —— 可选注入(本部署存在),懒解析。\n const getSessionQuery = (): SessionQueryFace | undefined =>\n ctx.get('sessionQuery') as SessionQueryFace | undefined\n const disposeRoutes = registerRoutes({ webServer: ws }, {\n server,\n getClient: client,\n git,\n autoCommit: () => committer?.touch(),\n noteDefaults: () => ({ tag: effectiveNoteTag() }),\n uiDefaults: () => effectiveUi(),\n getWikiPath: () => wikiPath,\n getSessionController,\n getWorkspaceRegistry,\n getAgentPresets,\n getSessionPersistence,\n getPermissionPresets,\n getSessions,\n getSessionQuery,\n sendToAgentEnabled: () => eff().ui?.sendToAgent?.enabled !== false,\n sendToAgentToken: () => {\n const token = eff().ui?.sendToAgent?.token\n return typeof token === 'string' ? token : ''\n },\n })\n const adminDeps: AdminDeps = {\n server,\n getClient: client,\n getWikiPath: () => wikiPath,\n twRoot: resolveTwRoot,\n config: configStore,\n // A settings-page save may change prompt.*: re-register the section (no\n // dsh web restart) and expose the built text for the preview panel.\n onConfigChanged: () => applyPrompt(),\n getPrompt: () => ({\n enabled: eff().prompt?.enabled !== false,\n mode: normalizePromptMode(eff().prompt?.mode),\n text: promptText(),\n }),\n // Runtime wiki location (v0.22.0): read the current folder + how it was\n // decided, switch to another one, or drop back to the cordis default.\n wiki: {\n info: locationInfo,\n switch: switchWikiLocation,\n reset: resetWikiLocation,\n },\n seeds: {\n // Tool summaries feed the GENERATED seed content (the doc note's tool\n // list, v0.22.0) — pass them everywhere the registry can be re-run.\n checkAll: async (c) => checkAllSeeds({ client: c, tools: tiddlywikiToolSummary() }),\n run: async (c, id, force) => runSeedById({ client: c, tools: tiddlywikiToolSummary() }, id, force),\n remove: async (c, id) => removeSeedById({ client: c }, id),\n },\n }\n const disposeAdmin = registerAdminRoutes({ webServer: ws }, adminDeps)\n return () => {\n disposeRoutes()\n disposeAdmin()\n }\n })\n\n // Teardown: everything reversible (R6 — hot reload must not leak).\n // The disposer RETURNS its promise: cordis fiber.dispose() awaits effect\n // disposers, and dsh web's shutdown controller awaits fiber.dispose() with a\n // 5s grace — so 结束/退出/重启 dsh web 时 TW 后端子进程会被真正停掉,而不是\n // fire-and-forget 里与进程退出竞速(竞速会在 Windows 上遗留孤儿 TW 进程)。\n ctx.effect(() => () => {\n return (async () => {\n // Signal the startup task first (v0.19.1): it checks `disposed` after every\n // await and stops the child it may have spawned, instead of racing us and\n // leaving an orphan TW process / a bound clip-bridge port.\n disposed = true\n try {\n await Promise.race([\n startupTask.catch(() => undefined),\n new Promise<void>((r) => { setTimeout(r, 3_000).unref?.() }),\n ])\n } catch { /* startup issues are already logged */ }\n // Flush a pending auto-commit BEFORE teardown, so a write made within the\n // debounce window is not left uncommitted when dsh web stops (best-effort).\n try { await committer?.flush() } catch { /* best-effort */ }\n disposeAll()\n await server.stop()\n })()\n }, 'dsh-tiddlywiki: host teardown')\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["httpsGet","httpGet","createServer"],"sources":["../src/host/git.ts","../src/host/tw-api.ts","../src/host/wiki.ts","../src/host/session-summary.ts","../src/host/http.ts","../src/host/sanitize.ts","../src/host/seed-util.ts","../src/host/prompt.ts","../src/host/seed-notes.ts","../src/host/seed-starter-docs.ts","../src/host/seed-send-to-agent.ts","../src/host/seed-home.ts","../src/host/seed-all-articles.ts","../src/host/seed-ui-styles.ts","../src/host/seed-menubar-theme.ts","../src/host/seed-clip-bridge.ts","../src/host/seed-render.ts","../src/host/config.ts","../src/host/seeds.ts","../src/host/text-util.ts","../src/host/write-policy.ts","../src/host/routes.ts","../src/host/admin.ts","../src/host/clip-bridge.ts","../src/sdk.ts","../src/host/tools.ts","../src/host/wiki-location.ts","../src/host/wiki-switch.ts","../src/index.ts"],"sourcesContent":["/**\n * Git face (design doc §7, D11) — the ONLY place dsh-tiddlywiki shells out to\n * git. The wiki folder itself is the repository; the folder is pure text\n * (FileSystemAdaptor writes one file per tiddler), so git is a natural sync /\n * backup channel.\n *\n * Sync model is the single-thread alternating one:\n * 1. start of work: `git pull --rebase --autostash`\n * 2. end of work: `git add -A && git commit && git push`\n * 3. auto-commit: debounced 60s commit after wiki writes (AutoCommitter)\n *\n * Conflict policy (user-confirmed, no complex handling): a rebase conflict\n * (only reachable by \"forgot to pull before writing\") → `git rebase --abort`\n * + report the unmerged files. Never auto-merge data.\n *\n * @module dsh-tiddlywiki/host/git\n */\nimport { execFile } from 'node:child_process'\nimport { promisify } from 'node:util'\n\nconst execFileP = promisify(execFile)\n\n/** Timeout for quick read-only queries. */\nconst QUICK_TIMEOUT_MS = 5_000\n\n/** Timeout for structural/network operations. */\nconst HEAVY_TIMEOUT_MS = 60_000\n\nexport interface ExecResult { ok: boolean; stdout: string; stderr: string }\nexport type ExecFn = (args: string[], options: { cwd?: string; timeout?: number }) => Promise<ExecResult>\n\n/** Default exec layer: run `git <args>` under a cwd with a timeout. */\nconst defaultExec: ExecFn = async (args, options) => {\n try {\n const { stdout, stderr } = await execFileP('git', args, {\n cwd: options.cwd,\n timeout: options.timeout ?? QUICK_TIMEOUT_MS,\n windowsHide: true,\n encoding: 'utf8',\n maxBuffer: 32 * 1024 * 1024,\n })\n return { ok: true, stdout, stderr }\n } catch (err) {\n const e = err as { stdout?: string; stderr?: string; message?: string }\n return { ok: false, stdout: e.stdout ?? '', stderr: e.stderr ?? String(e.message ?? err) }\n }\n}\n\nexport interface GitStatusView {\n exists: boolean\n branch: string\n dirty: boolean\n dirtyFiles: string[]\n remote: string\n lastCommit?: string\n ahead?: number\n behind?: number\n}\n\nexport interface GitActionResult { ok: boolean; message: string; conflictFiles?: string[] }\n\nfunction parseCount(line: string, re: RegExp): number | undefined {\n const m = line.match(re)\n return m === null ? undefined : Number(m[1])\n}\n\nexport class GitFace {\n /**\n * FIFO mutex for the index-touching operations (`commit` and `pull\n * --rebase`). The debounced AutoCommitter can fire while an explicit\n * `tiddlywiki_git_sync` is rebasing; both would fight over\n * `.git/index.lock` and intermittently fail. Per-instance, so a second\n * GitFace (e.g. the settings page's status probe) is unaffected.\n */\n private lock: Promise<unknown> = Promise.resolve()\n\n private withLock<T>(fn: () => Promise<T>): Promise<T> {\n const run = this.lock.then(fn, fn)\n this.lock = run.then(() => undefined, () => undefined)\n return run\n }\n\n constructor(private readonly exec: ExecFn = defaultExec) {}\n\n async isRepo(dir: string): Promise<boolean> {\n const r = await this.exec(['rev-parse', '--is-inside-work-tree'], { cwd: dir, timeout: 2_000 })\n return r.ok && r.stdout.trim() === 'true'\n }\n\n async init(dir: string, branch = 'main'): Promise<boolean> {\n const r = await this.exec(['init', '-b', branch], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n return r.ok\n }\n\n /** Initial commit for a fresh repo (tolerates an empty index). */\n async initialCommit(dir: string): Promise<boolean> {\n await this.exec(['add', '-A'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n const r = await this.exec([...identity(), 'commit', '-m', 'chore(dsh-tiddlywiki): initial commit'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n return r.ok || /nothing to commit/.test(r.stderr + r.stdout)\n }\n\n /**\n * Stage everything and commit; a local identity is always provided so the\n * plugin never depends on the machine's global git config. Returns whether\n * a commit actually happened. Serialized against `pull()` (index.lock).\n */\n commit(dir: string, message: string): Promise<{ committed: boolean; message: string }> {\n return this.withLock(() => this.commitUnlocked(dir, message))\n }\n\n private async commitUnlocked(dir: string, message: string): Promise<{ committed: boolean; message: string }> {\n await this.exec(['add', '-A'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n const staged = await this.exec(['diff', '--cached', '--quiet'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\n // `diff --cached --quiet` exits 0 when nothing is staged → nothing to commit.\n if (staged.ok) return { committed: false, message: 'nothing to commit' }\n const r = await this.exec([...identity(), 'commit', '-m', message], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n return r.ok\n ? { committed: true, message }\n : { committed: false, message: `commit failed: ${(r.stderr.trim() || r.stdout.trim()).slice(0, 500)}` }\n }\n\n async status(dir: string): Promise<GitStatusView> {\n const empty: GitStatusView = { exists: false, branch: '', dirty: false, dirtyFiles: [], remote: '' }\n const r = await this.exec(['status', '--porcelain', '-b'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\n if (!r.ok) return empty\n const lines = r.stdout.split('\\n').filter((l) => l.length > 0)\n const branchLine = lines.find((l) => l.startsWith('## '))\n const branch = branchLine === undefined ? '' : branchLine.slice(3).split('...')[0] ?? ''\n const ahead = branchLine === undefined ? undefined : parseCount(branchLine, /ahead (\\d+)/)\n const behind = branchLine === undefined ? undefined : parseCount(branchLine, /behind (\\d+)/)\n const dirty = lines.some((l) => !l.startsWith('## '))\n const dirtyFiles = lines.filter((l) => !l.startsWith('## ')).map((l) => l.slice(3).trim()).filter(Boolean)\n const remoteR = await this.exec(['remote', '-v'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\n const remote = remoteR.ok ? remoteR.stdout.split('\\n').map((l) => l.trim()).find(Boolean) ?? '' : ''\n const lastR = await this.exec(['log', '-1', '--format=%h %s'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\n const lastCommit = lastR.ok && lastR.stdout.trim().length > 0 ? lastR.stdout.trim() : undefined\n return { exists: true, branch, dirty, dirtyFiles, remote, ...(lastCommit !== undefined ? { lastCommit } : {}), ...(ahead !== undefined ? { ahead } : {}), ...(behind !== undefined ? { behind } : {}) }\n }\n\n /** `git pull --rebase --autostash`; on conflict: abort + report files.\n * On success, `changed: true` means HEAD actually moved (files came in /\n * commits were replayed) — callers use it to decide whether a running TW\n * child needs a restart to drop its stale in-memory snapshot.\n * Serialized against `commit()` (index.lock). */\n pull(dir: string): Promise<GitActionResult & { changed?: boolean }> {\n return this.withLock(() => this.pullUnlocked(dir))\n }\n\n private async pullUnlocked(dir: string): Promise<GitActionResult & { changed?: boolean }> {\n const before = await this.exec(['rev-parse', 'HEAD'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\n const beforeHead = before.ok ? before.stdout.trim() : ''\n const r = await this.exec(['pull', '--rebase', '--autostash'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n if (r.ok) {\n const after = await this.exec(['rev-parse', 'HEAD'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\n const afterHead = after.ok ? after.stdout.trim() : ''\n const changed = beforeHead.length > 0 && beforeHead !== afterHead\n return { ok: true, message: r.stdout.trim() || 'pull ok', ...(changed ? { changed: true } : {}) }\n }\n const conflictFiles = await this.unmergedFiles(dir)\n await this.exec(['rebase', '--abort'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n const reason = (r.stderr.trim() || r.stdout.trim()).slice(0, 500)\n return { ok: false, message: conflictFiles.length > 0 ? `conflict in ${conflictFiles.join(', ')} (rebase aborted): ${reason}` : `pull failed: ${reason}`, ...(conflictFiles.length > 0 ? { conflictFiles } : {}) }\n }\n\n async push(dir: string): Promise<GitActionResult> {\n const r = await this.exec(['push'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n return r.ok\n ? { ok: true, message: r.stdout.trim() || 'push ok' }\n : { ok: false, message: (r.stderr.trim() || r.stdout.trim()).slice(0, 500) }\n }\n\n /** First push with upstream tracking (called once after a remote is set). */\n async firstPush(dir: string): Promise<GitActionResult> {\n const branch = (await this.status(dir)).branch || 'main'\n const r = await this.exec(['push', '-u', 'origin', branch], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n return r.ok\n ? { ok: true, message: `pushed ${branch} to origin` }\n : { ok: false, message: (r.stderr.trim() || r.stdout.trim()).slice(0, 500) }\n }\n\n /** `git fetch` (no remote configured → failure reported by the caller). */\n async fetch(dir: string): Promise<GitActionResult> {\n const r = await this.exec(['fetch'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n return r.ok\n ? { ok: true, message: r.stdout.trim() || 'fetch ok' }\n : { ok: false, message: (r.stderr.trim() || r.stdout.trim()).slice(0, 500) }\n }\n\n /**\n * Restore the given files from the freshly fetched remote HEAD (FETCH_HEAD)\n * into the working tree + index — the \"keep remote version\" half of\n * tiddler-granular conflict resolution. Callers must `git fetch` first.\n */\n async checkoutFetchHead(dir: string, files: string[]): Promise<GitActionResult> {\n if (files.length === 0) return { ok: true, message: 'no files given' }\n const r = await this.exec(['checkout', 'FETCH_HEAD', '--', ...files], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n return r.ok\n ? { ok: true, message: `已从远端检出 ${files.length} 个文件` }\n : { ok: false, message: (r.stderr.trim() || r.stdout.trim()).slice(0, 500) }\n }\n\n /** Ensure `origin` points at `url` (add or set-url). */\n async ensureRemote(dir: string, url: string): Promise<GitActionResult> {\n const cur = await this.exec(['remote', 'get-url', 'origin'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\n if (cur.ok) {\n if (cur.stdout.trim() === url) return { ok: true, message: 'remote origin already set' }\n const set = await this.exec(['remote', 'set-url', 'origin', url], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n return set.ok ? { ok: true, message: `remote origin → ${url}` } : { ok: false, message: set.stderr.trim() || 'remote set-url failed' }\n }\n const add = await this.exec(['remote', 'add', 'origin', url], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n return add.ok ? { ok: true, message: `remote origin → ${url}` } : { ok: false, message: add.stderr.trim() || 'remote add failed' }\n }\n\n private async unmergedFiles(dir: string): Promise<string[]> {\n const r = await this.exec(['diff', '--name-only', '--diff-filter=U'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\n return r.ok ? r.stdout.split('\\n').map((l) => l.trim()).filter(Boolean) : []\n }\n}\n\n/** Always-on local identity so commits never depend on global git config. */\nfunction identity(): string[] {\n return ['-c', 'user.name=dsh-tiddlywiki', '-c', 'user.email=dsh-tiddlywiki@local']\n}\n\nexport interface AutoCommitterOptions {\n git: GitFace\n dir: string\n enabled: boolean\n debounceMs: number\n message: () => string\n onError?: (err: unknown) => void\n /** Observability hook: fired after each flush attempt (used by selftest to\n * assert the debounce really committed). */\n onCommit?: (info: { committed: boolean; message: string }) => void\n}\n\n/**\n * Debounced auto-committer: every wiki write calls `touch()`; the commit\n * fires once writes settle for `debounceMs`. Disable with git.autoCommit.\n */\nexport class AutoCommitter {\n private timer: NodeJS.Timeout | undefined\n private disposed = false\n\n constructor(private readonly options: AutoCommitterOptions) {}\n\n touch(): void {\n if (!this.options.enabled || this.disposed) return\n if (this.timer !== undefined) clearTimeout(this.timer)\n this.timer = setTimeout(() => { void this.flush() }, this.options.debounceMs)\n }\n\n /** Run a commit now (also cancels the pending debounce). */\n async flush(): Promise<void> {\n if (this.timer !== undefined) {\n clearTimeout(this.timer)\n this.timer = undefined\n }\n if (!this.options.enabled || this.disposed) return\n try {\n const result = await this.options.git.commit(this.options.dir, this.options.message())\n this.options.onCommit?.(result)\n } catch (err) {\n this.options.onError?.(err)\n }\n }\n\n dispose(): void {\n this.disposed = true\n if (this.timer !== undefined) {\n clearTimeout(this.timer)\n this.timer = undefined\n }\n }\n}\n","/**\n * TiddlyWeb REST client (design doc §5) — the ONLY way every writer reaches\n * the wiki (quick notes, agent tools, editor saves all go through the TW\n * service, D1), so there is never a second write path.\n *\n * ROUTES ARE EMPIRICALLY VERIFIED against tiddlywiki 5.4.1's core-server\n * (`core-server/server/routes/`):\n * GET /recipes/default/tiddlers.json[?exclude=...] list (skinny)\n * GET /recipes/default/tiddlers/<title> read one (404 absent)\n * PUT /recipes/default/tiddlers/<title> write one (204)\n * DELETE /bags/default/tiddlers/<title> delete one (204)\n * Writes require the `X-Requested-With: TiddlyWiki` header (TW CSRF), which\n * this client always sends. Tags arrive as a whitespace-joined STRING and are\n * normalized to arrays here.\n *\n * SEARCH (R2): the server blocks arbitrary `filter=` queries with 403 unless\n * the exact filter is whitelisted in $:/config/Server/ExternalFilters. So\n * `search()` fetches the TEXT-BEARING listing WITH text (TEXT_LIST_FILTER +\n * `?exclude=` a sentinel, whitelist self-healed on first 403) and matches\n * locally — one request, no 403, no per-tiddler round-trips, and binary\n * tiddlers (images etc.) never ship their base64 payload (v0.16.20).\n *\n * @module dsh-tiddlywiki/host/tw-api\n */\n\n/** A tiddler's readable fields (loose on purpose). */\nexport interface Tiddler {\n title: string\n text?: string\n tags?: string[]\n type?: string\n created?: string\n modified?: string\n /** Extra custom fields returned by the server are folded under `fields`. */\n fields?: Record<string, unknown>\n [key: string]: unknown\n}\n\nconst REQUEST_TIMEOUT_MS = 10_000\n\n/** TW's CSRF gate: writes must carry this header (TW's own UI always does). */\nconst CSRF_HEADER = { 'x-requested-with': 'TiddlyWiki' }\n\n/** Sentinel `exclude` value: excludes nothing, so `text` stays in the list. */\nconst LIST_WITH_TEXT_EXCLUDE = '__dsh_tw_none__'\n\n/**\n * Recipe-list filter that keeps ONLY text-bearing tiddlers (notes): no `type`\n * field at all, or a `text/*` type. Binary tiddlers (images/audio/video/fonts/\n * PDF/zip…) carry their payload as a base64 `text` field — on a big wiki (e.g.\n * thousands of scanned book pages) the full listing serializes 500+MB of base64\n * and times every agent read out. This filter drops them SERVER-SIDE so the\n * listing stays ~6MB (v0.16.20, verified on a 2418-tiddler wiki:\n * 515MB/17s → 6MB/0.3s).\n *\n * TW 5.4.1 filter facts that matter here (all verified empirically):\n * - `prefix`/`match` operators match the TITLE only — field matching needs\n * `regexp:<field>` / `field:<field>`.\n * - `[has[type]]` = \"has a non-empty type field\"; `[has:type[]]` is a DIFFERENT\n * call (suffix \"type\" + empty operand) that matches everything.\n * - space-separated operations UNION (no prefix = \"or\"); `+` means\n * intersection.\n * - negated `regexp:type`/`field:type` DROP type-less tiddlers (their field\n * string is null), so \"exclude binary\" must be written as the positive\n * \"no type OR text-ish\" union below.\n *\n * FILENAME BUDGET (Windows, verified): the whitelist tiddler below is stored\n * as a .tid file whose name contains the ENTIRE filter string, so the filter\n * must stay short — a ~180-char filter produced a ~217-char filename that\n * broke `git add` on Windows (\"Filename too long\", MAX_PATH) and killed the\n * auto-committer. 86 chars keeps the file at ~123 chars even on a deep wiki\n * path. (Consequence: application/json tiddlers are not searchable — on this\n * wiki there are none, and they are config/data tiddlers, not notes.)\n */\nexport const TEXT_LIST_FILTER = [\n '[all[tiddlers]!is[system]!has[type]]',\n '[all[tiddlers]!is[system]regexp:type[(?i)^text/]]',\n].join(' ')\n\n/**\n * 真正「没有 type 字段」的条目(v0.19.1)。\n *\n * 必须靠过滤器判定:TW 服务端在 listing 与单条 GET 里都会给无 type 的条目\n * **补上** `text/vnd.tiddlywiki`(`tiddlerFields.type = tiddlerFields.type ||\n * \"text/vnd.tiddlywiki\"`),所以响应里的 `type` 永远非空,客户端无法区分\n * 「本来是 wikitext」与「压根没有 type」。过滤器在服务端按真实字段求值,返回\n * 的标题就是真的没有 type 的那些。串长 36 字符(白名单 tiddler 文件名 = 整个\n * filter,必须短)。\n */\nexport const MISSING_TYPE_FILTER = '[all[tiddlers]!is[system]!has[type]]'\n\n/** TiddlyWeb blocks every non-default filter with 403 unless the EXACT filter\n * string is whitelisted at `$:/config/Server/ExternalFilters/<filter>` = \"yes\".\n * This client writes that tiddler once on 403 (self-heal, idempotent) and\n * retries; the tiddler lives in the wiki and travels with its git history. */\nfunction externalFilterWhitelistTitle(filter: string): string {\n return `$:/config/Server/ExternalFilters/${filter}`\n}\n\n/** Binary MIME type prefixes — their `text` field is base64 payload, not content. */\nconst BINARY_TYPE_PREFIXES = ['image/', 'audio/', 'video/', 'font/']\nconst BINARY_TYPE_EXACT = new Set([\n 'application/octet-stream', 'application/pdf', 'application/zip', 'application/gzip',\n 'application/x-gzip', 'application/x-7z-compressed', 'application/x-rar-compressed',\n 'application/epub+zip', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n])\n\n/** True when a tiddler's `type` marks it as binary (base64 in `text`). */\nexport function isBinaryType(type: string | undefined): boolean {\n if (typeof type !== 'string' || type.length === 0) return false\n return BINARY_TYPE_PREFIXES.some((p) => type.startsWith(p)) || BINARY_TYPE_EXACT.has(type)\n}\n\n/**\n * Parse a tiddler date field. TW's REST listing returns dates in the COMPACT\n * form (`20260101000000000` = YYYYMMDDhhmmssSSS, UTC), NOT ISO — the old code\n * fed that to `new Date()`, got Invalid Date, and therefore made every\n * `since`-filtered search/recent return nothing (v0.19.0). Both forms are\n * accepted here; undefined = absent/unparseable.\n */\nexport function parseTiddlerDate(value: unknown): number | undefined {\n if (typeof value !== 'string' || value.length === 0) return undefined\n const compact = /^(\\d{4})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{3})$/.exec(value.trim())\n if (compact !== null) {\n return Date.UTC(\n Number(compact[1]), Number(compact[2]) - 1, Number(compact[3]),\n Number(compact[4]), Number(compact[5]), Number(compact[6]), Number(compact[7]),\n )\n }\n const ms = new Date(value.trim()).getTime()\n return Number.isNaN(ms) ? undefined : ms\n}\n\n/** Normalize a tiddler date field to ISO-8601 (for display), or null. */\nexport function toIsoDateString(value: unknown): string | null {\n const ms = parseTiddlerDate(value)\n return ms === undefined ? null : new Date(ms).toISOString()\n}\n\n/** Split TW's whitespace-joined tags string into an array. */\nfunction normalizeTags(tags: unknown): string[] | undefined {\n if (tags === undefined) return undefined\n if (Array.isArray(tags)) return tags.map(String)\n if (typeof tags === 'string') {\n const parts = tags.trim().split(/\\s+/).filter(Boolean)\n return parts.length > 0 ? parts : []\n }\n return []\n}\n\n/** Normalize a raw server tiddler (tags string → array, unknown fields nested). */\nfunction normalizeTiddler(raw: Record<string, unknown>): Tiddler {\n const out = { ...raw } as Tiddler\n const tags = normalizeTags(raw.tags)\n if (tags !== undefined) out.tags = tags\n return out\n}\n\nexport class TiddlyWebClient {\n /** Preemptive Basic credentials, when the wiki runs in locked-down mode. */\n private readonly authHeader: string | undefined\n\n /**\n * Short-TTL cache for the TEXT-bearing listing. `search` / `recent` /\n * `listTags`-style calls each pull the whole listing (≈6MB on a 5k-tiddler\n * wiki); a burst of tool calls re-downloaded and re-parsed it every time.\n * Cached as a PROMISE so concurrent callers share one request, invalidated by\n * every write this client performs (v0.19.0).\n */\n private textListing: { at: number; value: Promise<Tiddler[]> } | undefined\n /**\n * Same short-TTL + in-flight-merge cache for the SKINNY listings (v0.19.1):\n * `/tags` (`list(TEXT_LIST_FILTER, false)`) and the lint title/dedup listings\n * each pull the whole title list; without a cache every `/status`-adjacent\n * poll and every lint re-downloaded + re-parsed it. Keyed by filter string\n * ('' = server default listing).\n */\n private readonly skinnyListings = new Map<string, { at: number; value: Promise<Tiddler[]> }>()\n private static readonly TEXT_LISTING_TTL_MS = 2_000\n\n /** Drop the listing caches — called after any write so reads never go stale. */\n private invalidateListing(): void {\n this.textListing = undefined\n this.skinnyListings.clear()\n }\n\n constructor(private readonly baseUrl: string, auth?: { username?: string; password?: string }) {\n const username = typeof auth?.username === 'string' ? auth.username : ''\n if (username.length > 0) {\n const password = typeof auth?.password === 'string' ? auth.password : ''\n this.authHeader = `Basic ${Buffer.from(`${username}:${password}`, 'utf8').toString('base64')}`\n }\n }\n\n private async request(path: string, init?: RequestInit): Promise<Response> {\n const headers: Record<string, string> = { ...((init?.headers as Record<string, string> | undefined) ?? {}) }\n // The plugin spawns TW with `readers`/`writers` whenever auth.username is\n // configured, so EVERY request (reads included) needs the credentials —\n // otherwise the whole client 401s. TW's BasicAuthenticator accepts a\n // preemptive Authorization header (no challenge round-trip needed).\n if (this.authHeader !== undefined) headers.authorization = this.authHeader\n return fetch(`${this.baseUrl}${path}`, {\n ...init,\n headers,\n signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),\n })\n }\n\n /** GET /status → { username, anonymous, space, tiddlywiki_version, ... }. */\n async status(): Promise<Record<string, unknown>> {\n const res = await this.request('/status')\n if (!res.ok) throw new Error(`TiddlyWeb /status HTTP ${res.status}`)\n return res.json() as Promise<Record<string, unknown>>\n }\n\n /** Read one tiddler; undefined when it does not exist (404). */\n async get(title: string): Promise<Tiddler | undefined> {\n const res = await this.request(`/recipes/default/tiddlers/${encodeURIComponent(title)}`)\n if (res.status === 404) return undefined\n if (!res.ok) throw new Error(`TiddlyWeb GET /recipes/default/tiddlers/${title} HTTP ${res.status}`)\n return normalizeTiddler((await res.json()) as Record<string, unknown>)\n }\n\n /**\n * Render a tiddler (or raw wiki text) to an HTML fragment through TW's own\n * `/render` server route — the bundle the `render-route` seed installs.\n *\n * READ-ONLY, but the route is a POST (handler contract) and TW's server gates\n * POST behind the writer CSRF header, so the CSRF header is sent like a write.\n * Throws on 404 (`notFound`) so callers can distinguish \"no such tiddler\".\n */\n async render(body: { title?: string; text?: string; type?: string; contextTitle?: string; parseAsInline?: boolean }): Promise<string> {\n const res = await this.request('/render', {\n method: 'POST',\n headers: { 'content-type': 'application/json', ...CSRF_HEADER },\n body: JSON.stringify(body),\n })\n if (res.status === 404) throw new Error('TiddlyWeb POST /render HTTP 404: tiddler not found')\n if (!res.ok) {\n const detail = await res.text().catch(() => '')\n throw new Error(`TiddlyWeb POST /render HTTP ${res.status}: ${detail.slice(0, 200)}`)\n }\n return res.text()\n }\n\n /** Write (create or overwrite) one tiddler via PUT (204 on success). */\n async put(tiddler: Tiddler): Promise<Tiddler> {\n const title = tiddler.title\n const res = await this.request(`/recipes/default/tiddlers/${encodeURIComponent(title)}`, {\n method: 'PUT',\n headers: { 'content-type': 'application/json', ...CSRF_HEADER },\n body: JSON.stringify(tiddler),\n })\n this.invalidateListing()\n if (!res.ok) {\n const detail = await res.text().catch(() => '')\n throw new Error(`TiddlyWeb PUT /recipes/default/tiddlers/${title} HTTP ${res.status}: ${detail.slice(0, 300)}`)\n }\n return tiddler\n }\n\n /** Delete one tiddler via the bags route (204); a missing one is a no-op. */\n async delete(title: string): Promise<void> {\n const res = await this.request(`/bags/default/tiddlers/${encodeURIComponent(title)}`, {\n method: 'DELETE',\n headers: CSRF_HEADER,\n })\n this.invalidateListing()\n if (res.status === 404) return\n if (!res.ok) throw new Error(`TiddlyWeb DELETE /bags/default/tiddlers/${title} HTTP ${res.status}`)\n }\n\n /**\n * List tiddlers via the default server filter. Arbitrary `filter=` queries\n * are blocked by the server (403) unless whitelisted, so callers needing a\n * subset should use search(); a supplied filter that is 403-blocked falls\n * back to the text-bearing filter.\n *\n * `includeText=true` returns only TEXT-BEARING tiddlers WITH their `text`\n * (never the base64 payloads of binary tiddlers): on 403 the whitelist\n * tiddler for TEXT_LIST_FILTER is written once and the request retried\n * (self-heal); if that write fails (read-only wiki) it degrades to the\n * skinny listing (title/tags matchable, empty snippets).\n */\n async list(filter?: string, includeText = false): Promise<Tiddler[]> {\n if (!includeText) {\n // Skinny listings share the short-TTL promise cache (v0.19.1): `/tags` and\n // the lint title listings are re-requested by every poll/lint, and each\n // miss transferred + parsed the whole title list.\n const key = filter ?? ''\n const cached = this.skinnyListings.get(key)\n if (cached !== undefined && Date.now() - cached.at < TiddlyWebClient.TEXT_LISTING_TTL_MS) return cached.value\n const pending = this.fetchListing(filter)\n this.skinnyListings.set(key, { at: Date.now(), value: pending })\n pending.catch(() => { if (this.skinnyListings.get(key)?.value === pending) this.skinnyListings.delete(key) })\n return pending\n }\n const explicit = filter !== undefined && filter.length > 0\n // Explicit filter: the caller asked for a SPECIFIC set — never silently\n // answer with a different one (the old code fell back to TEXT_LIST_FILTER,\n // i.e. it returned set B for a request of set A).\n if (explicit) return this.fetchTextListing(filter as string, false)\n const cached = this.textListing\n if (cached !== undefined && Date.now() - cached.at < TiddlyWebClient.TEXT_LISTING_TTL_MS) return cached.value\n const pending = this.fetchTextListing(TEXT_LIST_FILTER, true)\n this.textListing = { at: Date.now(), value: pending }\n // Never cache a rejection: the next call must retry the network.\n pending.catch(() => { if (this.textListing?.value === pending) this.textListing = undefined })\n return pending\n }\n\n /**\n * Fetch a text-bearing listing for `filter`, self-healing the external-filter\n * whitelist on 403. `allowSkinnyFallback` is true only for the plugin's OWN\n * default filter (TEXT_LIST_FILTER), where degrading to the skinny listing is\n * a documented last resort on a read-only wiki; an explicit caller filter\n * propagates the 403 instead.\n */\n private async fetchTextListing(filter: string, allowSkinnyFallback: boolean): Promise<Tiddler[]> {\n let res = await this.requestListWithText(filter)\n if (res.status === 403) {\n await this.ensureExternalFilterWhitelist(filter).catch(() => undefined)\n res = await this.requestListWithText(filter)\n }\n if (res.status === 403) {\n if (allowSkinnyFallback) return this.fetchListing(undefined)\n throw new Error(`TiddlyWeb 拒绝该 filter(未在 $:/config/Server/ExternalFilters 白名单中):${filter.slice(0, 100)}`)\n }\n if (!res.ok) throw new Error(`TiddlyWeb recipe list HTTP ${res.status}`)\n return this.parseList(res)\n }\n\n /** GET the recipe listing WITHOUT text payloads (server default excludes `text`). */\n private async fetchListing(filter?: string): Promise<Tiddler[]> {\n const params = new URLSearchParams()\n if (filter !== undefined && filter.length > 0) params.set('filter', filter)\n const query = params.toString()\n let res = await this.request(`/recipes/default/tiddlers.json${query.length > 0 ? `?${query}` : ''}`)\n if (!res.ok && res.status === 403 && filter !== undefined && filter.length > 0) {\n // Not whitelisted: write the whitelist tiddler once and retry the SAME\n // filter. (The old code silently refetched the DEFAULT listing — the\n // caller asked for set A and got set B.)\n await this.ensureExternalFilterWhitelist(filter).catch(() => undefined)\n res = await this.request(`/recipes/default/tiddlers.json?${query}`)\n if (!res.ok && res.status === 403) {\n throw new Error(`TiddlyWeb 拒绝该 filter(未在 $:/config/Server/ExternalFilters 白名单中):${filter.slice(0, 100)}`)\n }\n }\n if (!res.ok) throw new Error(`TiddlyWeb recipe list HTTP ${res.status}`)\n return this.parseList(res)\n }\n\n /** GET the recipe listing WITH text for a specific (whitelisted) filter. */\n private async requestListWithText(filter: string): Promise<Response> {\n return this.request(`/recipes/default/tiddlers.json?filter=${encodeURIComponent(filter)}&exclude=${LIST_WITH_TEXT_EXCLUDE}`)\n }\n\n /** Write `$:/config/Server/ExternalFilters/<filter>` = \"yes\" (idempotent). */\n private async ensureExternalFilterWhitelist(filter: string): Promise<void> {\n await this.put({ title: externalFilterWhitelistTitle(filter), text: 'yes' })\n }\n\n private async parseList(res: Response): Promise<Tiddler[]> {\n const data = (await res.json()) as Array<Record<string, unknown>> | { tiddlers?: Array<Record<string, unknown>> }\n const items = Array.isArray(data) ? data : (data.tiddlers ?? [])\n return items.map(normalizeTiddler)\n }\n\n /**\n * Search text-bearing tiddlers: one request (text-bearing listing with text)\n * plus local case-insensitive substring matching on title + text, optional\n * exact tags (AND), a `since` modified-time floor, an exact `type`, capped at\n * `limit`. Robust against the server's external-filter 403 (whitelist is\n * self-healed). Binary tiddlers (images/audio/…) are NOT in the listing, so\n * they can never match — a deliberate flood guard on big wikis.\n */\n /**\n * Search text-bearing tiddlers: one request (text-bearing listing with text,\n * short-TTL cached) plus local case-insensitive substring matching on title,\n * tags and text, optional exact tags (AND), a `since` modified-time floor, an\n * exact `type`, an exact custom `field`+`value` pair, and a `limit`.\n *\n * Results are RANKED (title hit > tag hit > body hit count, then newest\n * first) rather than returned in listing order, and `limit` is clamped to\n * 1…200 like the HTTP route always did (v0.19.0). Binary tiddlers are not in\n * the listing at all, so they can never flood the results.\n */\n async search(query: string, options: SearchOptions = {}): Promise<{ items: Tiddler[]; total: number }> {\n const items = await this.list(undefined, true)\n const needle = query.toLowerCase()\n const sinceTime = parseSince(options.since)\n const wantedTags = [...(options.tags ?? []), options.tag].filter((t): t is string => typeof t === 'string' && t.trim().length > 0)\n const limit = clampLimit(options.limit, 30)\n const fieldName = typeof options.field === 'string' && options.field.length > 0 ? options.field : undefined\n const fieldValue = typeof options.value === 'string' ? options.value : undefined\n const matched: Array<{ t: Tiddler; score: number }> = []\n for (const t of items) {\n if (t.title.startsWith('$:/')) continue\n const titleHit = needle.length > 0 && t.title.toLowerCase().includes(needle)\n const text = t.text ?? ''\n const textHit = needle.length > 0 && text.toLowerCase().includes(needle)\n const tagHit = needle.length > 0 && (t.tags ?? []).some((tag) => tag.toLowerCase().includes(needle))\n if (!titleHit && !textHit && !tagHit) continue\n if (sinceTime !== undefined) {\n const modified = parseTiddlerDate(t.modified)\n if (modified === undefined || modified < sinceTime) continue\n }\n if (options.type !== undefined && options.type.length > 0 && (t.type ?? 'text/vnd.tiddlywiki') !== options.type) continue\n if (wantedTags.length > 0) {\n const tags = (t.tags ?? []).map((tag) => tag.toLowerCase())\n if (!wantedTags.every((w) => tags.includes(w.toLowerCase()))) continue\n }\n // Custom-field match: exact value comparison on any field (note metadata\n // such as `q`, `due`, `clip-url` is not part of title/text, so it used to\n // be unsearchable).\n if (fieldName !== undefined) {\n const actual = t[fieldName]\n if (typeof actual !== 'string') continue\n if (fieldValue !== undefined && actual !== fieldValue) continue\n }\n let score = 0\n if (titleHit) score += 6\n if (tagHit) score += 3\n if (needle.length > 0) score += Math.min(countOccurrences(text.toLowerCase(), needle), 5)\n matched.push({ t, score })\n }\n matched.sort((a, b) => {\n if (b.score !== a.score) return b.score - a.score\n return modifiedAt(b.t) - modifiedAt(a.t)\n })\n return { items: matched.slice(0, limit).map((entry) => entry.t), total: matched.length }\n }\n\n /**\n * List the most recently modified TEXT-BEARING (non-system, non-binary)\n * tiddlers, newest first (missing/modified-less tiddlers sort to the tail).\n * `since` keeps only tiddlers modified at/after that instant. Binary\n * attachments never appear — a book import touching hundreds of images must\n * not crowd the \"最近修改\" list.\n */\n async recent(limit = 15, since?: string): Promise<Tiddler[]> {\n const items = await this.list(undefined, true)\n const sinceTime = parseSince(since)\n const filtered = items.filter((t) => {\n if (t.title.startsWith('$:/')) return false\n if (sinceTime !== undefined) {\n const modified = parseTiddlerDate(t.modified)\n if (modified === undefined || modified < sinceTime) return false\n }\n return true\n })\n filtered.sort((a, b) => modifiedAt(b) - modifiedAt(a))\n return filtered.slice(0, Math.max(1, Math.min(limit, 200)))\n }\n\n /**\n * Distinct non-system tags with their tiddler counts, most-used first then\n * zh-locale. One skinny listing request (no text payloads).\n *\n * `limit` (v0.19.4) caps the returned array after sorting; the caller loses\n * the tail count unless it also asks `tagStats()`.\n */\n async listTags(limit?: number): Promise<Array<{ tag: string; count: number }>> {\n const tags = (await this.tagStats()).tags\n if (limit === undefined) return tags\n return tags.slice(0, Math.max(1, Math.min(Math.floor(limit), 1000)))\n }\n\n /** Full tag vocabulary + its size, so callers that cap can still report the\n * total (\"showing 200 of N\") — one listing request either way. */\n async tagStats(): Promise<{ total: number; tags: Array<{ tag: string; count: number }> }> {\n // TEXT_LIST_FILTER (server-side, whitelist self-healed) so a tag that only\n // hangs off binary attachments is not counted as a knowledge-base tag\n // (v0.19.0 — the unfiltered skinny listing included image/book-page tags).\n const items = await this.list(TEXT_LIST_FILTER, false)\n const map = new Map<string, number>()\n for (const t of items) {\n if (t.title.startsWith('$:/')) continue\n for (const tag of t.tags ?? []) {\n if (tag.startsWith('$:/')) continue\n map.set(tag, (map.get(tag) ?? 0) + 1)\n }\n }\n const tags = [...map.entries()]\n .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0], 'zh'))\n .map(([tag, count]) => ({ tag, count }))\n return { total: tags.length, tags }\n }\n}\n\n/** Optional search / recent filters. */\nexport interface SearchOptions {\n /** Every tag in this list must be present (AND semantics). */\n tags?: string[]\n /** Legacy single exact tag (AND with `tags`). */\n tag?: string\n /** ISO 8601 instant; keep only tiddlers modified at/after it. */\n since?: string\n /** Exact tiddler type (default type is \"text/vnd.tiddlywiki\"). */\n type?: string\n /** Custom field name to filter on (exact match on the field's string value). */\n field?: string\n /** Required value for `field` (omit to require only that the field exists). */\n value?: string\n /** Max results (default 30, capped 200). */\n limit?: number\n}\n\n/** Clamp a caller-supplied limit into 1…200 (default when absent/invalid). */\nexport function clampLimit(limit: unknown, fallback: number): number {\n const value = typeof limit === 'number' && Number.isFinite(limit) ? Math.floor(limit) : fallback\n return Math.max(1, Math.min(value, 200))\n}\n\n/** Count non-overlapping occurrences of `needle` in `haystack`. */\nfunction countOccurrences(haystack: string, needle: string): number {\n if (needle.length === 0) return 0\n let count = 0\n let index = haystack.indexOf(needle)\n while (index !== -1) {\n count++\n index = haystack.indexOf(needle, index + needle.length)\n }\n return count\n}\n\n/** `modified` as epoch ms (0 when absent/unparseable) — for result ranking. */\nfunction modifiedAt(tiddler: Tiddler): number {\n return parseTiddlerDate(tiddler.modified) ?? 0\n}\n\n/** Parse a `since` value into an epoch ms, or undefined when absent/invalid. */\nfunction parseSince(since: string | undefined): number | undefined {\n if (typeof since !== 'string' || since.trim().length === 0) return undefined\n const ms = new Date(since.trim()).getTime()\n return Number.isNaN(ms) ? undefined : ms\n}\n","/**\n * WikiServer — the TiddlyWiki 5 child-process lifecycle (design doc §9, D3).\n *\n * Zero-friction rules:\n * - ensure the wiki folder exists (scaffold with `--init server` once)\n * - git bootstrap is NOT this class's job (index.ts owns the GitFace)\n * - auto-detect a free loopback port unless one is pinned in config\n * - spawn `node <tw>/tiddlywiki.js <wiki> --listen host=127.0.0.1 ...`\n * and poll /status until it answers 200\n * - the TW child serves at the ROOT of its own dedicated loopback port (no\n * `path-prefix`): TW's browser frontend builds its API URLs from\n * `$protocol$//$host$/` only, so any path-prefix makes every frontend call\n * ../../status → 404 (verified against tiddlywiki 5.4.1). Namespacing lives\n * on the DSH webserver side (/dsh-tiddlywiki/* routes), never in TW itself.\n * - crash → restart with exponential backoff (1s,2s,4s… cap 30s), reset on\n * a successful readiness\n * - stop() is deterministic: SIGTERM, escalate to SIGKILL after a grace\n * period, and never leave a timer that would respawn during teardown\n *\n * @module dsh-tiddlywiki/host/wiki\n */\nimport { spawn, execFile, type ChildProcessByStdio } from 'node:child_process'\nimport { existsSync } from 'node:fs'\nimport { mkdir } from 'node:fs/promises'\nimport { createRequire } from 'node:module'\nimport { createServer } from 'node:net'\nimport { join, resolve } from 'node:path'\nimport type { Readable } from 'node:stream'\n\n/** The DSH webserver route prefix (NOT a TW path-prefix; see module header). */\nexport const PATH_PREFIX = '/dsh-tiddlywiki'\n\n/**\n * Same-origin TW proxy route on the DSH webserver (remote-access mode, R1).\n * The browser only ever talks to the DSH origin — which it already reaches\n * over loopback, LAN, Tailscale, a domain or HTTPS — and DSH proxies to the\n * loopback TW child. TW's frontend is pointed at this prefix via the\n * `$:/config/tiddlyweb/host` tiddler so every API call stays same-origin.\n */\nexport const TW_PROXY_PREFIX = `${PATH_PREFIX}/tw`\n\n/** The proxy base path (trailing slash) handed to browsers / TW's frontend. */\nexport const TW_PROXY_PATH = `${TW_PROXY_PREFIX}/`\n\n/** How long to wait for the wiki to answer /status. */\nconst READY_TIMEOUT_MS = 20_000\n\n/** Poll cadence while waiting for readiness. */\nconst READY_POLL_MS = 500\n\n/** Backoff ceiling for crash restarts. */\nconst MAX_RESTART_BACKOFF_MS = 30_000\n\n/** SIGTERM → SIGKILL escalation grace. */\nconst KILL_GRACE_MS = 3_000\n\n/** Ring-buffer cap for the stdout/stderr log. */\nconst LOG_BUFFER_LIMIT = 200\n\n/** One-shot scaffold timeout for `--init server`. */\nconst INIT_TIMEOUT_MS = 30_000\n\nexport interface WikiServerOptions {\n /** Root that holds one folder per wiki (default $DSH_HOME/tiddlywiki). */\n wikiRoot: string\n /** Wiki folder name under wikiRoot (default \"main\"). */\n wiki: string\n /** Port; 0 = auto-detect a free loopback port. */\n port: number\n /** Optional Basic Auth (loopback anonymous by default). */\n username?: string\n password?: string\n logBufferLimit?: number\n}\n\nexport type WikiHealth = 'starting' | 'running' | 'stopped' | 'failed'\n\nexport interface WikiStatusView {\n status: WikiHealth\n url?: string\n port?: number\n wikiPath: string\n pid?: number\n lastStartedAt?: number\n error?: string\n logs: string[]\n}\n\n/** Resolve the absolute entry of the installed `tiddlywiki` package. */\nfunction resolveTwEntry(): string {\n const require = createRequire(import.meta.url)\n return require.resolve('tiddlywiki/tiddlywiki.js')\n}\n\nexport class WikiServer {\n private child: ChildProcessByStdio<null, Readable, Readable> | undefined\n /**\n * The wiki folder this server currently serves. MUTABLE since v0.22.0: a\n * runtime switch calls {@link setLocation} while the child is stopped.\n */\n private wikiPath: string\n private readonly logs: string[] = []\n private readonly logLimit: number\n private health: WikiHealth = 'stopped'\n private port: number | undefined\n private stopping = false\n private restartTimer: NodeJS.Timeout | undefined\n private restartDelay = 1_000\n private lastStartedAt: number | undefined\n private error: string | undefined\n /** In-flight start() promise: single-flight guard (v0.19.0). */\n private startPromise: Promise<WikiStatusView> | undefined\n /** In-flight restart(): two concurrent restarts must not spawn two children (v0.19.1). */\n private restartPromise: Promise<WikiStatusView> | undefined\n /** Set by a successful readiness probe; a crash BEFORE readiness means the\n * auto-chosen port may have been taken, so it is re-probed on restart. */\n private wasReady = false\n\n constructor(private readonly options: WikiServerOptions) {\n this.wikiPath = resolve(options.wikiRoot, options.wiki)\n this.location = { root: options.wikiRoot, name: options.wiki }\n this.logLimit = options.logBufferLimit ?? LOG_BUFFER_LIMIT\n }\n\n /**\n * The wiki folder this server currently serves (v0.22.0: mutable — a runtime\n * switch calls {@link setLocation} while the child is stopped).\n */\n\n /** Last location applied through {@link setLocation} / the constructor. */\n private location: { root: string; name: string }\n\n /** Current location (root + folder name), for the settings page. */\n get currentLocation(): { root: string; name: string } {\n return { ...this.location }\n }\n\n /**\n * Point this server at a DIFFERENT wiki folder (runtime switch, v0.22.0).\n *\n * Refuses while a child is running: a switch that forgot the folder while the\n * old process still served it would leave `/status`, the REST client and the\n * git face describing two different wikis. Callers stop() first (see\n * `switchWiki()` in host/wiki-switch.ts), then start() again.\n *\n * The bound port is INTENTIONALLY kept: reusing it across a switch keeps the\n * iframe src and the cached REST client valid (same reasoning as restart()).\n */\n setLocation(location: { root: string; name: string }): void {\n if (this.child !== undefined) throw new Error('wiki 必须先在停止状态下才能切换位置')\n const next = resolve(location.root, location.name)\n if (next === this.wikiPath) {\n this.location = { root: location.root, name: location.name }\n return\n }\n this.location = { root: location.root, name: location.name }\n this.wikiPath = next\n // A different folder is a different wiki: clear the stale failure message\n // and the readiness flag (a crash before readiness re-probes the port).\n this.wasReady = false\n this.error = undefined\n if (this.child === undefined) this.health = 'stopped'\n this.log(`location → ${next}`)\n }\n\n /** Base URL of the TW service, once a port is bound (root, no path prefix). */\n get url(): string | undefined {\n return this.port === undefined ? undefined : `http://127.0.0.1:${this.port}`\n }\n\n /** The currently bound port (undefined until first spawn). */\n get currentPort(): number | undefined {\n return this.port\n }\n\n private log(line: string): void {\n const ts = new Date().toISOString()\n this.logs.push(`[${ts}] ${line}`)\n if (this.logs.length > this.logLimit) this.logs.splice(0, this.logs.length - this.logLimit)\n }\n\n /** Scaffold the wiki folder with `--init server` when it is absent. */\n async ensureWiki(): Promise<void> {\n await mkdir(this.wikiPath, { recursive: true })\n if (existsSync(join(this.wikiPath, 'tiddlywiki.info'))) return\n const tw = resolveTwEntry()\n this.log(`init: ${process.execPath} ${tw} ${this.wikiPath} --init server`)\n await new Promise<void>((resolveP, rejectP) => {\n execFile(process.execPath, [tw, this.wikiPath, '--init', 'server'], { timeout: INIT_TIMEOUT_MS, windowsHide: true }, (err) => {\n if (err) rejectP(err as Error)\n else resolveP()\n })\n })\n }\n\n /** Probe a free loopback port. */\n private async findFreePort(): Promise<number> {\n return new Promise<number>((resolveP, rejectP) => {\n const server = createServer()\n server.unref()\n server.once('error', rejectP)\n server.listen(0, '127.0.0.1', () => {\n const address = server.address()\n if (address === null || typeof address === 'string') {\n server.close()\n rejectP(new Error('cannot resolve a free port'))\n return\n }\n const port = address.port\n server.close(() => resolveP(port))\n })\n })\n }\n\n /**\n * Start (or restart) the TW child. Resolves once `/status` answers 200 or\n * the readiness deadline passes. Never throws on a crash — the exit handler\n * schedules a self-healing restart unless we are stopping.\n *\n * SINGLE-FLIGHT (v0.19.0): two concurrent callers (double restart click, a\n * restart racing the self-heal timer) both used to pass the\n * `child !== undefined` check, probe their own port and spawn a child each —\n * the loser became an orphan process holding a port until dsh web exited.\n */\n async start(): Promise<WikiStatusView> {\n if (this.startPromise !== undefined) return this.startPromise\n const run = this.startOnce()\n this.startPromise = run\n try {\n return await run\n } finally {\n this.startPromise = undefined\n }\n }\n\n private async startOnce(): Promise<WikiStatusView> {\n this.stopping = false\n this.restartDelay = 1_000\n // Clear a previous failure's message (v0.19.5): a self-healed restart left\n // `this.error` set forever, so `/status` (and the panel/FAB tooltip) kept\n // reporting a stale fault even though the wiki was healthy. A fresh attempt\n // is not a failure until it proves to be one.\n this.error = undefined\n await this.ensureWiki()\n if (this.child !== undefined) return this.status()\n this.health = 'starting'\n // Reuse an existing auto port across restarts (restart() → stop() → start())\n // so a fixed-baseUrl TiddlyWebClient stays valid and iframe src is stable.\n const port = this.options.port > 0 ? this.options.port : ((this.port ?? 0) > 0 ? this.port as number : await this.findFreePort())\n this.port = port\n const tw = resolveTwEntry()\n const args = [tw, this.wikiPath, '--listen', 'host=127.0.0.1', `port=${port}`]\n if (this.options.username) {\n // Locked-down mode for non-loopback exposure: Basic Auth + access lists.\n args.push(`username=${this.options.username}`)\n args.push(`password=${this.options.password ?? ''}`)\n args.push(`readers=${this.options.username}`)\n args.push(`writers=${this.options.username}`)\n }\n // Anonymous loopback mode carries NO auth args: TW's defaults open the\n // wiki to anonymous read/write on the bound (loopback) address. Passing\n // anon-username/readers/writers here was verified to 401 every request\n // ('undefined' is not authorized), so the anonymous branch stays bare.\n //\n // NEVER log the raw argv: it carries `password=…`, the ring buffer is\n // returned by the UNAUTHENTICATED GET /status, and the same string could\n // end up in a crash dump. (v0.19.0 — the spawn line used to be logged\n // verbatim; the password still lives in the OS process list, which is why\n // the wiki binds loopback by default.)\n this.log(`spawn: ${process.execPath} ${args.map((a) => (/^password=/.test(a) ? 'password=***' : a)).join(' ')}`)\n const child = spawn(process.execPath, args, { cwd: this.wikiPath, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true })\n this.child = child\n child.stdout.on('data', (chunk: Buffer) => this.log(`[out] ${String(chunk).trimEnd()}`))\n child.stderr.on('data', (chunk: Buffer) => this.log(`[err] ${String(chunk).trimEnd()}`))\n child.once('exit', (code, signal) => {\n // IDENTITY CHECK (v0.19.0): restart() SIGKILLs the old child and start()\n // immediately flips `stopping` back to false, so the old child's exit\n // event can arrive AFTER the new one is running. Without this check it\n // cleared `this.child`/`this.port` and marked the healthy new child\n // 'stopped' — waitReady() then polled `undefined/status` and every route\n // 503'd while TW was actually up.\n if (this.child !== child) {\n this.log(`exit code=${code} signal=${signal ?? ''} (superseded child — ignored)`)\n return\n }\n this.log(`exit code=${code} signal=${signal ?? ''} stopping=${this.stopping}`)\n this.child = undefined\n this.health = 'stopped'\n if (!this.stopping) {\n // Never became ready → most likely `EADDRINUSE` on the remembered auto\n // port. Forget it so the restart probes a fresh one instead of looping\n // on a port this process no longer owns. An explicitly configured port\n // is always honored as-is.\n if (this.options.port === 0 && !this.wasReady) this.port = undefined\n this.scheduleRestart()\n }\n })\n child.once('error', (err) => {\n this.log(`spawn error: ${err.message}`)\n this.error = err.message\n if (this.child !== child) return\n this.child = undefined\n this.health = 'failed'\n if (!this.stopping) this.scheduleRestart()\n })\n this.lastStartedAt = Date.now()\n this.wasReady = false\n await this.waitReady()\n return this.status()\n }\n\n /** Poll /status until 200 or the deadline; throws only on deadline/crash. */\n private async waitReady(): Promise<void> {\n const deadline = Date.now() + READY_TIMEOUT_MS\n // Locked-down mode (`username` configured) puts /status behind TW's\n // `readers` list: an anonymous probe gets 401 forever and the wiki would\n // never be reported ready. Authenticate preemptively, exactly like the\n // REST client does.\n const headers = this.options.username !== undefined && this.options.username.length > 0\n ? { authorization: `Basic ${Buffer.from(`${this.options.username}:${this.options.password ?? ''}`, 'utf8').toString('base64')}` }\n : undefined\n for (;;) {\n if (this.child === undefined) throw new Error('wiki process exited before ready')\n try {\n const res = await fetch(`${this.url}/status`, {\n signal: AbortSignal.timeout(2_000),\n ...(headers !== undefined ? { headers } : {}),\n })\n if (res.ok) {\n this.health = 'running'\n this.wasReady = true\n this.error = undefined\n this.log('ready: /status 200')\n return\n }\n } catch {\n /* not ready yet */\n }\n if (Date.now() > deadline) {\n this.health = 'failed'\n this.error = 'wiki server did not become ready in time'\n this.log(this.error)\n throw new Error(this.error)\n }\n await new Promise<void>((r) => setTimeout(r, READY_POLL_MS))\n }\n }\n\n private scheduleRestart(): void {\n if (this.stopping || this.restartTimer !== undefined) return\n const delay = this.restartDelay\n this.restartDelay = Math.min(this.restartDelay * 2, MAX_RESTART_BACKOFF_MS)\n this.log(`restart scheduled in ${delay}ms`)\n this.health = 'starting'\n this.restartTimer = setTimeout(() => {\n this.restartTimer = undefined\n void this.start().catch((err) => {\n this.health = 'failed'\n this.error = err instanceof Error ? err.message : String(err)\n this.log(`restart failed: ${this.error}`)\n })\n }, delay)\n }\n\n /**\n * One-click restart (routes `/restart`, `/sync`, every `/admin/*` restart).\n *\n * SINGLE-FLIGHT (v0.19.1): the route-level mutex in routes.ts could not cover\n * the admin routes (separate closure), so two concurrent restarts — a double\n * click, or `/restart` racing `/admin/seeds/run` — used to run `stop()` twice\n * and spawn two children; the loser became an orphan process holding a port\n * until dsh web exited. Serializing here fixes every caller at once.\n */\n async restart(): Promise<WikiStatusView> {\n if (this.restartPromise !== undefined) return this.restartPromise\n const run = (async (): Promise<WikiStatusView> => {\n await this.stop()\n return this.start()\n })()\n this.restartPromise = run\n try {\n return await run\n } finally {\n this.restartPromise = undefined\n }\n }\n\n /** Deterministic teardown: cancel timers, SIGTERM, escalate to SIGKILL. */\n async stop(): Promise<void> {\n this.stopping = true\n if (this.restartTimer !== undefined) {\n clearTimeout(this.restartTimer)\n this.restartTimer = undefined\n }\n const child = this.child\n this.child = undefined\n if (child !== undefined && child.exitCode === null && child.signalCode === null) {\n try {\n child.kill('SIGTERM')\n } catch { /* already gone */ }\n await Promise.race([\n new Promise<void>((r) => child.once('exit', () => r())),\n new Promise<void>((r) => {\n setTimeout(() => {\n try { child.kill('SIGKILL') } catch { /* already gone */ }\n r()\n }, KILL_GRACE_MS).unref?.()\n }),\n ])\n // After the SIGKILL escalation, give the OS a moment to actually reap the\n // process: an immediate start() that rebinds the same port would\n // otherwise hit EADDRINUSE and enter the backoff loop (v0.19.0).\n if (child.exitCode === null && child.signalCode === null) {\n await Promise.race([\n new Promise<void>((r) => child.once('exit', () => r())),\n new Promise<void>((r) => { setTimeout(r, 1_000).unref?.() }),\n ])\n }\n }\n // Only report 'stopped' when no replacement child has been spawned in the\n // meantime (restart() runs stop() → start()).\n if (this.child === undefined) this.health = 'stopped'\n }\n\n /** Live status view (health, url, git-independent, recent logs). */\n status(): WikiStatusView {\n return {\n status: this.health,\n url: this.url,\n port: this.port,\n wikiPath: this.wikiPath,\n pid: this.child?.pid,\n lastStartedAt: this.lastStartedAt,\n ...(this.error !== undefined ? { error: this.error } : {}),\n logs: [...this.logs],\n }\n }\n}\n","/**\n * 会话相关 wiki 汇总(会话顶部「知识库」Tab 的后端)——把一次 DSH 会话(含其后代\n * subagent)在本会话中产生 / 读取 / 检索过的 wiki 笔记汇总成一篇 TW wikitext,写入\n * TW 的 volatile 命名空间 `$:/temp/dsh/session-summary/<会话ID>`。\n *\n * 判定来源(设计定稿,探查验证):\n * - Host 服务 `sessionQuery`(可选注入):`readSession(sessionId)` 返回完整事件日志\n * (events 升序),`traceSession` 返回祖先/后代树。绝不直接解析\n * `~/.dsh/sessions` 目录下的 session 文件——那是 zstd 压缩的。\n * - `tool/call` 事件精确记录每次 `tiddlywiki_*` 调用的 `name` 与\n * `arguments`(JSON 字符串)。\n * - `assistant/message` 事件 `message.content` 的 text 块可扫\n * `/dsh-tiddlywiki/tw/#...` 引用链接(视为「读取」)。\n *\n * 归类规则(§三 定稿):\n * 产生 📝 tiddlywiki_put / batch_put / rename(arguments 取 title)\n * 读取 👀 tiddlywiki_get(title)+ 助手回复里的 wiki 引用链接\n * 检索 🔍 search / recent(记关键词,不算单篇)\n * 范围 本会话 + 后代 subagent(traceSession 递归,仅子代理触达的笔记标注)\n *\n * 渲染(§四):写入 `$:/temp/...`——TW 5.4.1 默认 `$:/config/SyncFilter` 显式\n * `-[prefix[$:/temp/]]`(core/wiki/config/SyncFilter.tid),服务端 syncer 从不把\n * $:/temp 落盘:内存态、不进 git、TW 重启即消失(已实测:PUT 204、tiddlers/ 无文件、\n * git clean)。TW 用自己主题/链接/导航原生渲染,不另造 UI。\n *\n * @module dsh-tiddlywiki/host/session-summary\n */\nimport { parseTiddlerDate, type TiddlyWebClient } from './tw-api.ts'\n\n/** 会话汇总 tiddler 的 $:/temp 命名空间前缀。 */\nexport const SESSION_SUMMARY_PREFIX = '$:/temp/dsh/session-summary/'\n\n/** 会话内能触达 wiki 的 tiddlywiki_* 工具名(search/recent 只记检索记录,不算单篇)。 */\nconst NOTE_TOOL_NAMES = new Set(['tiddlywiki_put', 'tiddlywiki_batch_put', 'tiddlywiki_rename', 'tiddlywiki_get', 'tiddlywiki_search', 'tiddlywiki_recent'])\n\n/** 每篇笔记的触达摘要(按 title 去重,时间取最近一次)。 */\nexport interface NoteEntry {\n title: string\n action: 'produced' | 'read'\n /** 最近一次触达的 epoch ms。 */\n time: number\n /** 附加说明(如 rename 的旧标题)。 */\n detail?: string\n /** true = 仅由后代 subagent 触达过(本会话自己没碰过)。 */\n subagent: boolean\n}\n\n/** 一次检索类调用(search / recent),不算单篇笔记。 */\nexport interface SearchRecord {\n kind: 'search' | 'recent'\n query: string\n tags: string[]\n time: number\n subagent: boolean\n}\n\n/** 一次会话扫描的收集结果。 */\nexport interface Collected {\n produced: Map<string, NoteEntry>\n read: Map<string, NoteEntry>\n searches: SearchRecord[]\n}\n\n/**\n * 结构化 `sessionQuery` 服务脸(dsh-api-session-query 的极小子集,只声明本模块用到\n * 的方法;运行时实例是真实 Service,绝不是 Inspect 数据)。\n */\nexport interface SessionQueryFace {\n /** Read and replay-validate one complete logical session log (events ascending). */\n readSession(sessionId: string): Promise<SessionLogSnapshot>\n /** Trace known ancestry and descendants from one corpus observation. */\n traceSession(sessionId: string, signal?: AbortSignal): Promise<SessionLineageTrace>\n}\n\nexport interface SessionLogSnapshot {\n session: { id: string }\n events: SessionEvent[]\n}\n\nexport interface SessionEvent {\n type: string\n seq: number\n time: number\n data: Record<string, unknown>\n}\n\nexport interface SessionLineageTrace {\n descendants: SessionLineageNode[]\n}\n\nexport interface SessionLineageNode {\n session: { header: { id: string } }\n descendants: SessionLineageNode[]\n}\n\n/**\n * Bounds so one enormous session tree cannot make the「知识库」Tab unresponsive:\n * the summary reads every listed session's FULL event log and then probes each\n * touched tiddler over REST.\n */\nconst MAX_SESSIONS = 40\nconst MAX_SEARCH_RECORDS = 200\nconst MAX_ENRICH_TITLES = 300\n\n/** 递归收集后代树里所有 session id(不含根自身,超过上限即停)。 */\nfunction collectDescendantIds(nodes: SessionLineageNode[] | undefined, out: string[]): void {\n if (!Array.isArray(nodes)) return\n for (const node of nodes) {\n if (out.length >= MAX_SESSIONS) return\n const id = node?.session?.header?.id\n if (typeof id === 'string' && id.length > 0) {\n out.push(id)\n collectDescendantIds(node?.descendants, out)\n }\n }\n}\n\n/**\n * 汇总条目会直接写进 `[[标题]]` wikitext——标题既要能安全渲染、又不能制造死链。\n * 会话日志里常混入模型的示例/残缺写法(`#…`、`#标题`、带反引号的截断链接、\n * 控制字符等),这类收进来会被 TW 渲染成「佚失条目」;一律过滤。\n */\nfunction isPlausibleTitle(title: string): boolean {\n if (title.length === 0 || title.length > 300) return false\n if (title.trim().length === 0) return false\n if (/[\\u0000-\\u001f\\u007f]/.test(title)) return false // 控制字符 / 换行\n if (/[\\]|`#]/.test(title)) return false // 会破坏 [[…]] 链接语法\n if (title.startsWith('…') || title === '标题') return false // 占位 / 示例写法\n return true\n}\n\n/** 记录一篇笔记的触达;同标题合并(时间取最新、detail 取最新、subagent 取 AND)。 */\nfunction record(map: Map<string, NoteEntry>, entry: NoteEntry): void {\n if (entry.title.startsWith('$:/')) return\n if (!isPlausibleTitle(entry.title)) return\n const prev = map.get(entry.title)\n if (prev !== undefined) {\n prev.time = Math.max(prev.time, entry.time)\n if (entry.detail !== undefined) prev.detail = entry.detail\n prev.subagent = prev.subagent && entry.subagent\n return\n }\n map.set(entry.title, { ...entry })\n}\n\n/** 扫助手回复文本里的 `/dsh-tiddlywiki/tw/#标题` 引用链接 → 读取。 */\nfunction scanRefs(text: string, time: number, subagent: boolean, collected: Collected): void {\n // 只认到下一个 `)` / 空白 / `]` / `#` 为止——`#` 会截断后续碎片(模型示例常写成\n // `#…` 或 `#标题`,这些由 isPlausibleTitle 在 record 时过滤)。\n const re = /\\/dsh-tiddlywiki\\/tw\\/#([^#)\\s\\]]+)/g\n let m: RegExpExecArray | null\n while ((m = re.exec(text)) !== null) {\n const raw = m[1] ?? ''\n if (raw.length === 0) continue\n let title = raw\n try {\n title = decodeURIComponent(raw)\n } catch {\n /* keep raw on malformed percent-encoding */\n }\n if (title.length === 0) continue\n record(collected.read, { title, action: 'read', time, subagent })\n }\n}\n\n/** 扫描一次会话的事件日志,按 §三 规则收集笔记触达与检索记录。 */\nfunction scanSnapshot(snap: SessionLogSnapshot | undefined, subagent: boolean, collected: Collected): void {\n const events = snap?.events\n if (!Array.isArray(events)) return\n for (const ev of events) {\n const type = ev?.type\n const t = typeof ev?.time === 'number' ? ev.time : Date.now()\n if (type === 'tool/call') {\n const name = ev.data?.name\n if (typeof name !== 'string' || !NOTE_TOOL_NAMES.has(name)) continue\n let args: Record<string, unknown> = {}\n if (typeof ev.data?.arguments === 'string') {\n try {\n args = JSON.parse(ev.data.arguments) as Record<string, unknown>\n } catch {\n args = {}\n }\n }\n switch (name) {\n case 'tiddlywiki_put':\n if (typeof args.title === 'string' && args.title.length > 0) {\n record(collected.produced, { title: args.title, action: 'produced', time: t, subagent })\n }\n break\n case 'tiddlywiki_batch_put':\n if (Array.isArray(args.items)) {\n for (const item of args.items) {\n if (item !== null && typeof item === 'object' && typeof (item as { title?: unknown }).title === 'string') {\n const title = (item as { title: string }).title\n if (title.length > 0) record(collected.produced, { title, action: 'produced', time: t, subagent })\n }\n }\n }\n break\n case 'tiddlywiki_rename':\n if (typeof args.newTitle === 'string' && args.newTitle.length > 0) {\n const old = typeof args.oldTitle === 'string' && args.oldTitle.length > 0 ? args.oldTitle : undefined\n record(collected.produced, {\n title: args.newTitle,\n action: 'produced',\n time: t,\n detail: old !== undefined && isPlausibleTitle(old) ? `重命名自「${old}」` : undefined,\n subagent,\n })\n }\n break\n case 'tiddlywiki_get':\n if (typeof args.title === 'string' && args.title.length > 0) {\n record(collected.read, { title: args.title, action: 'read', time: t, subagent })\n }\n break\n case 'tiddlywiki_search': {\n if (collected.searches.length >= MAX_SEARCH_RECORDS) break\n const query = typeof args.query === 'string' ? args.query : ''\n const tags = Array.isArray(args.tags) ? args.tags.filter((x): x is string => typeof x === 'string') : []\n collected.searches.push({ kind: 'search', query, tags, time: t, subagent })\n break\n }\n case 'tiddlywiki_recent':\n if (collected.searches.length < MAX_SEARCH_RECORDS) {\n collected.searches.push({ kind: 'recent', query: '', tags: [], time: t, subagent })\n }\n break\n }\n } else if (type === 'assistant/message') {\n const message = ev.data?.message\n const content = message !== null && typeof message === 'object' ? (message as { content?: unknown }).content : undefined\n if (Array.isArray(content)) {\n for (const block of content) {\n if (block === null || typeof block !== 'object') continue\n const b = block as { type?: unknown; text?: unknown }\n if (b.type === 'text' && typeof b.text === 'string' && b.text.length > 0) {\n scanRefs(b.text, t, subagent, collected)\n }\n }\n }\n }\n }\n}\n\n/**\n * 内联文本净化(v0.19.3):汇总 wikitext 会被 TW 渲染成 HTML 片段再注入 DSH 页面,\n * 而 TW 的解析器**原样透传 HTML**。凡是来自会话日志/请求体的字符串(sessionId、\n * rename 的旧标题、检索词)都要先转义 HTML 元字符并去掉控制字符,别指望上层净化。\n */\nexport function escapeInline(text: string, max = 200): string {\n return text\n .replace(/[\\u0000-\\u001f\\u007f]/g, ' ')\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/[[\\]|]/g, ' ')\n .slice(0, max)\n}\n\n/** 当前状态(存在?标签?修改时间?摘要)。 */\ninterface TitleState {\n exists: boolean\n tags: string[]\n modified?: string\n snippet?: string\n /** 查询失败(非 404):状态未知,不能报「已删除/不存在」。 */\n unknown?: boolean\n}\n\n/** 并发(8 路)查询每篇笔记的当前状态(404 → 不存在/已删除)。 */\nasync function enrichTitles(client: TiddlyWebClient, titles: string[]): Promise<Map<string, TitleState>> {\n const out = new Map<string, TitleState>()\n const enquire = async (title: string): Promise<[string, TitleState]> => {\n try {\n const t = await client.get(title)\n if (t === undefined) return [title, { exists: false, tags: [] }]\n const flat = typeof t.text === 'string' ? t.text.replace(/\\s+/g, ' ').trim() : ''\n return [title, {\n exists: true,\n tags: t.tags ?? [],\n modified: typeof t.modified === 'string' ? t.modified : undefined,\n snippet: flat.length > 48 ? `${flat.slice(0, 48)}…` : flat,\n }]\n } catch {\n // 只有 404 才算「不存在」(tw-api 的 get 只在 404 返回 undefined):\n // 超时/5xx/服务重启中的查询失败必须报「状态未知」,否则一篇还在的笔记\n // 会被标成「已删除」——正是项目里反复强调的「读取失败 ≠ 条目不存在」。\n return [title, { exists: false, tags: [], unknown: true }]\n }\n }\n // Bounded batches: parallel within a batch, insertion order preserved across\n // batches (Map order = the caller's title order, same as the old sequential).\n for (let i = 0; i < titles.length; i += 8) {\n const batch = titles.slice(i, i + 8)\n for (const [title, state] of await Promise.all(batch.map(enquire))) out.set(title, state)\n }\n return out\n}\n\n/** epoch ms → 本地 `YYYY-MM-DD HH:mm`。 */\nfunction fmtTime(ms: number): string {\n const d = new Date(ms)\n if (Number.isNaN(d.getTime())) return ''\n const p = (n: number): string => String(n).padStart(2, '0')\n return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`\n}\n\n/**\n * TW `modified` → 本地时间(解析失败则原样返回)。\n *\n * v0.20.0: the REST layer returns the COMPACT form (`20260101000000000`,\n * YYYYMMDDhhmmssSSS UTC), which `Date.parse()` rejects with NaN — the summary\n * therefore printed the raw 17-digit string. `parseTiddlerDate` handles both\n * the compact and the ISO form (same rule as the rest of the host).\n */\nfunction fmtModified(value: string | undefined): string {\n if (value === undefined || value.length === 0) return ''\n const ms = parseTiddlerDate(value)\n return ms === undefined ? value : fmtTime(ms)\n}\n\n/** 组装分组 wikitext:产生 / 读取(未产生过的)/ 检索记录。 */\nfunction buildWikitext(\n sessionId: string,\n collected: Collected,\n producedTitles: string[],\n readTitles: string[],\n stateByTitle: Map<string, TitleState>,\n): string {\n const lines: string[] = []\n lines.push('! 会话相关 wiki 汇总')\n lines.push('')\n lines.push(\n `本页列出会话 \\`${escapeInline(sessionId, 120)}\\`(含其后代子代理)在本会话中产生 / 读取 / 检索过的知识库笔记。`,\n )\n lines.push('')\n const producedCount = producedTitles.length\n const readCount = readTitles.length\n const searchCount = collected.searches.length\n // Was anything touched ONLY through a descendant subagent? (The old column\n // said「本会话 + 子代理」whenever anything existed at all.)\n const anySubagent =\n [...collected.produced.values()].some((e) => e.subagent) ||\n [...collected.read.values()].some((e) => e.subagent) ||\n collected.searches.some((s) => s.subagent)\n if (producedCount + readCount + searchCount === 0) {\n lines.push('> 本会话暂时没有产生、读取或检索过任何知识库笔记。')\n lines.push('>')\n lines.push('> 本页为会话自动生成的临时汇总(`$:/temp`,不落盘、不进 git,重启 TW 即消失);需要时点 Tab 顶栏的「🔄 刷新」重新生成。')\n return lines.join('\\n')\n }\n lines.push(\n '| 产生 📝 | 读取 👀 | 检索 🔍 | 涉及会话 |',\n '| --- | --- | --- | --- |',\n `| ${producedCount} | ${readCount} | ${searchCount} | ${anySubagent ? '本会话 + 子代理' : '本会话'} |`,\n )\n lines.push('')\n\n const entryLine = (title: string, state: TitleState | undefined, entry: NoteEntry): string => {\n const bits: string[] = [`[[${title}]]`]\n if (state === undefined) {\n // Not probed at all: `enrichTitles` only queries the first\n // MAX_ENRICH_TITLES titles, so a long session's tail must NOT be reported\n // as「已删除/不存在」(v0.19.5) — that is a false accusation, not a status.\n bits.push('(未探测,超出单次查询上限)')\n } else if (state.unknown === true) {\n bits.push('⚠️ 状态未知(查询失败)')\n } else if (!state.exists) {\n bits.push('⚠️ 已删除/不存在')\n } else {\n if (state.tags.length > 0) bits.push(`标签 ${state.tags.slice(0, 6).map((tag) => escapeInline(tag, 40)).join('、')}${state.tags.length > 6 ? '…' : ''}`)\n const mod = fmtModified(state.modified)\n if (mod.length > 0) bits.push(`修改 ${mod}`)\n }\n const t = fmtTime(entry.time)\n if (t.length > 0) bits.push(`会话内 ${t}`)\n if (entry.detail !== undefined) bits.push(escapeInline(entry.detail, 200))\n if (entry.subagent) bits.push('(子代理)')\n return `* ${bits.join(' · ')}`\n }\n\n if (producedCount > 0) {\n lines.push('!!! 📝 产生')\n for (const title of producedTitles) {\n const entry = collected.produced.get(title)\n if (entry !== undefined) lines.push(entryLine(title, stateByTitle.get(title), entry))\n }\n lines.push('')\n }\n if (readCount > 0) {\n lines.push('!!! 👀 读取')\n for (const title of readTitles) {\n const entry = collected.read.get(title)\n if (entry !== undefined) lines.push(entryLine(title, stateByTitle.get(title), entry))\n }\n lines.push('')\n }\n if (searchCount > 0) {\n lines.push('!!! 🔍 检索记录')\n for (const r of collected.searches) {\n const bits: string[] = []\n if (r.kind === 'search') {\n bits.push(`\\`tiddlywiki_search\\` query=「${r.query.length > 0 ? escapeInline(r.query, 120) : '(空)'}」`)\n if (r.tags.length > 0) bits.push(`tags ${r.tags.map((tag) => escapeInline(tag, 40)).join('、')}`)\n } else {\n bits.push('`tiddlywiki_recent`(最近修改)')\n }\n const t = fmtTime(r.time)\n if (t.length > 0) bits.push(t)\n if (r.subagent) bits.push('(子代理)')\n lines.push(`* ${bits.join(' · ')}`)\n }\n lines.push('')\n }\n lines.push('---')\n lines.push('> 本页为会话自动生成的临时汇总(`$:/temp`,不落盘、不进 git,重启 TW 即消失);需要时点 Tab 顶栏的「🔄 刷新」重新生成。')\n return lines.join('\\n')\n}\n\nexport interface SessionSummaryResult {\n title: string\n counts: { produced: number; read: number; searches: number; sessions: number }\n empty: boolean\n generatedAt: string\n}\n\n/**\n * 生成并写入一篇会话汇总 tiddler($:/temp,volatile)。\n * @param client 可用 TW 客户端(调用方已保证 wiki 服务在线)。\n * @param sq sessionQuery 服务(调用方已保证可用)。\n * @param sessionId 目标会话 id(本会话 + 后代 subagent)。\n */\nexport async function writeSessionSummary(client: TiddlyWebClient, sq: SessionQueryFace, sessionId: string): Promise<SessionSummaryResult> {\n // 1) 会话范围:自身 + traceSession 后代树(递归)。trace 失败(会话已归档/不存在)\n // 就只汇总自身,绝不让单次失败拖垮整页。\n const ids: string[] = [sessionId]\n try {\n const trace = await sq.traceSession(sessionId, AbortSignal.timeout(10_000))\n collectDescendantIds(trace?.descendants, ids)\n } catch {\n /* trace unavailable → self only */\n }\n\n // 2) 逐个会话读完整事件日志并收集(任一失败跳过,其余照常)。\n const collected: Collected = { produced: new Map(), read: new Map(), searches: [] }\n for (const id of ids) {\n try {\n const snap = await sq.readSession(id)\n scanSnapshot(snap, id !== sessionId, collected)\n } catch {\n /* one bad session must not fail the whole summary */\n }\n }\n\n // 3) 产生优先:同时被产生+读取的笔记只列在「产生」,读取区只放未产生过的。\n const producedTitles = [...collected.produced.keys()].sort((a, b) => (collected.produced.get(b)?.time ?? 0) - (collected.produced.get(a)?.time ?? 0))\n const readTitles = [...collected.read.keys()]\n .filter((t) => !collected.produced.has(t))\n .sort((a, b) => (collected.read.get(b)?.time ?? 0) - (collected.read.get(a)?.time ?? 0))\n collected.searches.sort((a, b) => b.time - a.time)\n\n // 4) 查询当前状态(存在?标签?时间?)→ 组装 wikitext → PUT volatile tiddler。\n // 探测量有上限:一篇超长会话可能触碰成百上千篇笔记。\n const allTitles = [...new Set([...producedTitles, ...readTitles])]\n const probedTitles = allTitles.slice(0, MAX_ENRICH_TITLES)\n const stateByTitle = await enrichTitles(client, probedTitles)\n const text = buildWikitext(sessionId, collected, producedTitles, readTitles, stateByTitle)\n const title = `${SESSION_SUMMARY_PREFIX}${sessionId}`\n await client.put({ title, text, type: 'text/vnd.tiddlywiki', tags: [] })\n\n return {\n title,\n counts: { produced: producedTitles.length, read: readTitles.length, searches: collected.searches.length, sessions: ids.length },\n empty: producedTitles.length + readTitles.length + collected.searches.length === 0,\n generatedAt: new Date().toISOString(),\n }\n}\n","/**\n * Shared node:http helpers for the DSH webserver routes (routes.ts, admin.ts).\n *\n * One implementation of body reading + JSON response writing so the route\n * layers cannot drift (they previously carried two/three copies).\n *\n * @module dsh-tiddlywiki/host/http\n */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { createHash, timingSafeEqual } from 'node:crypto'\n\n/**\n * Constant-time string comparison for shared tokens: both sides are hashed\n * first, so neither the content nor the LENGTH of the expected token leaks\n * through timing (v0.19.0). One implementation for every caller — routes.ts\n * and clip-bridge.ts each carried a private copy (v0.20.0).\n */\nexport function safeTokenEqual(a: string, b: string): boolean {\n const ha = createHash('sha256').update(a, 'utf8').digest()\n const hb = createHash('sha256').update(b, 'utf8').digest()\n return timingSafeEqual(ha, hb)\n}\n\n/** Default cap for small JSON bodies (note/restart/config…). */\nexport const MAX_JSON_BODY_BYTES = 2 * 1024 * 1024\n\n/** Cap for the /api passthrough body (tiddler content can be large). */\nexport const MAX_PROXY_BODY_BYTES = 16 * 1024 * 1024\n\n/** Cap for uploaded file bodies (/tw proxy, /upload). */\nexport const MAX_UPLOAD_BYTES = 64 * 1024 * 1024\n\nfunction readBodyStream(req: IncomingMessage, limit: number, encoding: true): Promise<string>\nfunction readBodyStream(req: IncomingMessage, limit: number, encoding: false): Promise<Buffer>\nfunction readBodyStream(req: IncomingMessage, limit: number, encoding: boolean): Promise<string | Buffer> {\n return new Promise((resolveP, rejectP) => {\n let size = 0\n let settled = false\n const chunks: Buffer[] = []\n const fail = (err: Error): void => {\n if (settled) return\n settled = true\n rejectP(err)\n }\n const succeed = (value: string | Buffer): void => {\n if (settled) return\n settled = true\n resolveP(value)\n }\n req.on('data', (chunk: Buffer) => {\n size += chunk.length\n if (size > limit) {\n fail(new Error('body too large'))\n req.destroy()\n return\n }\n chunks.push(chunk)\n })\n req.on('end', () => {\n const buf = Buffer.concat(chunks)\n succeed(encoding ? buf.toString('utf8') : buf)\n })\n req.on('error', (err) => fail(err instanceof Error ? err : new Error(String(err))))\n // A caller that hangs up mid-body used to leave this promise pending\n // forever (the route handler never settled, and the socket was already\n // gone). `close` also fires after a normal `end`, hence the readableEnded\n // guard.\n req.on('aborted', () => fail(new Error('request aborted before the body was fully received')))\n req.on('close', () => {\n if (!req.readableEnded) fail(new Error('request closed before the body was read'))\n })\n })\n}\n\n/** Read a JSON (utf8) request body up to `limit` bytes (default MAX_JSON_BODY_BYTES). */\nexport function readBody(req: IncomingMessage, limit = MAX_JSON_BODY_BYTES): Promise<string> {\n return readBodyStream(req, limit, true)\n}\n\n/** Read a raw (binary-safe) request body up to `limit` bytes (default MAX_UPLOAD_BYTES). */\nexport function readBodyBuffer(req: IncomingMessage, limit = MAX_UPLOAD_BYTES): Promise<Buffer> {\n return readBodyStream(req, limit, false)\n}\n\n/** Write a JSON response (no-store, utf-8). */\nexport function json(res: ServerResponse, payload: unknown, status = 200): void {\n res.writeHead(status, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' })\n res.end(JSON.stringify(payload))\n}\n\n/**\n * Map a thrown error to an HTTP status. Oversized bodies are a client problem\n * (413), everything else is ours (500) — the routes used to answer 500 for\n * `/note` and `/admin/*` but 413 for `/upload` for the identical condition.\n */\nexport function errorStatus(err: unknown): number {\n const message = err instanceof Error ? err.message : String(err)\n return /body too large/i.test(message) ? 413 : 500\n}\n\n/** The route-handler shape the registries take. */\nexport type RouteHandler = (req: IncomingMessage, res: ServerResponse) => Promise<void>\n\n/**\n * Wrap an async route handler so a rejected promise can never become an\n * unhandled rejection (v0.19.3).\n *\n * WHY: every registration used to be `void handleX(req, res)`, and the host\n * installs no `unhandledRejection` handler — Node's default policy then EXITS\n * the whole dsh web process, while the client request hangs with no response.\n * A handler that throws (including a sync throw before its own try block, e.g.\n * `new URL(req.url)` outside the try in the proxies) now always ends in a\n * response: 413/500 JSON, or a bare `end()` when headers are already sent.\n */\nexport function guardHandler(handler: RouteHandler): (req: IncomingMessage, res: ServerResponse) => void {\n return (req, res) => {\n void handler(req, res).catch((err) => {\n try {\n if (!res.headersSent) json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n else res.end()\n } catch { /* response already gone */ }\n })\n }\n}\n\n/**\n * True when a NON-GET/HEAD/OPTIONS request is a cross-site (CSRF) request.\n *\n * Every legitimate caller of these routes is same-origin: the DSH GUI, the\n * embedded TW (served through the `/tw` proxy), and the TW-side \"发送给 Agent\"\n * button all run on the DSH origin. A page from another site can still fire a\n * simple request at a loopback/LAN-reachable DSH, and the browser tags it with\n * `Origin` / `Sec-Fetch-Site` — which is what we reject here.\n *\n * Only WRITES are checked: a cross-site navigation (Sec-Fetch-Site\n * `cross-site`) must still be able to open `/tw/` in a new tab, and cross-site\n * GETs cannot read the response (no CORS headers) so they cannot exfiltrate.\n * Requests that carry neither header (curl, server-to-server, old browsers)\n * are allowed on purpose: this is CSRF hardening, NOT an auth boundary — the\n * host's own auth is what protects a network-exposed DSH.\n */\nexport function isCrossSiteWrite(req: IncomingMessage): boolean {\n const method = (req.method ?? 'GET').toUpperCase()\n if (method === 'GET' || method === 'HEAD' || method === 'OPTIONS') return false\n // Widen the header types explicitly: `IncomingHttpHeaders` types both as\n // `string | undefined`, so an Array.isArray() branch would otherwise be\n // narrowed to `never` (repeat headers are still possible at runtime).\n const siteHeader: string | string[] | undefined = req.headers['sec-fetch-site']\n const site = typeof siteHeader === 'string' ? siteHeader.toLowerCase() : Array.isArray(siteHeader) ? String(siteHeader[0] ?? '').toLowerCase() : ''\n if (site === 'cross-site') return true\n const originHeader: string | string[] | undefined = req.headers.origin\n const origin = typeof originHeader === 'string' ? originHeader : Array.isArray(originHeader) ? String(originHeader[0] ?? '') : ''\n if (origin.length === 0) return false\n if (origin === 'null') return true\n const host = req.headers.host\n if (typeof host !== 'string' || host.length === 0) return false\n try {\n return new URL(origin).host.toLowerCase() !== host.toLowerCase()\n } catch {\n return true\n }\n}\n\n/**\n * Guard for a route handler: enforces the expected HTTP method(s), then the\n * CSRF check for writes. Returns true after writing the error response — the\n * caller just does `if (rejectCrossSiteWrite(req, res, ['POST'])) return`.\n *\n * WHY the method check is mandatory (v0.19.0): the host webserver dispatches\n * routes by PATHNAME ONLY (dsh-host-webserver matches `rawPath` and calls the\n * handler, no method filter), and `isCrossSiteWrite` deliberately IGNORES\n * read methods. Together that meant `GET /dsh-tiddlywiki/sync` ran a\n * pull+commit+push, `GET /restart` restarted the TW child and `GET /upload`\n * wrote a file — all reachable from any web page with a bare\n * `<img src=\"http://127.0.0.1:3080/dsh-tiddlywiki/sync\">`, since browsers send\n * no Origin/Sec-Fetch-Site that we would reject on a GET. Declaring the method\n * per route closes that class: a cross-site GET now gets 405 and no effect.\n *\n * @param allowedMethods when given, any other method → 405 (no side effect).\n */\nexport function rejectCrossSiteWrite(\n req: IncomingMessage,\n res: ServerResponse,\n allowedMethods?: readonly string[],\n): boolean {\n const method = (req.method ?? 'GET').toUpperCase()\n if (allowedMethods !== undefined && !allowedMethods.includes(method)) {\n res.writeHead(405, {\n 'content-type': 'application/json; charset=utf-8',\n 'cache-control': 'no-store',\n allow: [...allowedMethods, 'OPTIONS'].join(', '),\n })\n res.end(JSON.stringify({ ok: false, error: `method not allowed: ${method}` }))\n return true\n }\n if (!isCrossSiteWrite(req)) return false\n json(res, { ok: false, error: 'cross-site request rejected' }, 403)\n return true\n}\n\n/** Read-only route guard: GET/HEAD only, no side effects on any other method. */\nexport function rejectNonRead(req: IncomingMessage, res: ServerResponse): boolean {\n return rejectCrossSiteWrite(req, res, ['GET', 'HEAD'])\n}\n","/**\n * TiddlyWiki 渲染片段的白名单净化器(v0.19.1,安全)。\n *\n * 背景:`/render` 把 tiddler 正文渲染成 HTML 片段,客户端(回复流工具卡 +\n * 会话「知识库」Tab)用 `dangerouslySetInnerHTML` 注入 **DSH 同源页面**。\n * TW 的 wikitext/markdown 解析器只剥 `on*` 事件属性(`<script>` 会变成\n * `<safe-script>`),但 `<iframe src=\"javascript:…\">`、`<a href=\"javascript:…\">`、\n * `<form action=\"javascript:…\">` 之类会**原样通过**(headless 实测)——一段\n * 写进 wiki 的正文就能在 DSH 页面里执行脚本(可调 `/dsh-tiddlywiki/*` 管理路由)。\n *\n * 因此所有要注入 DSH 页面的 TW 片段都必须先过这里。策略是**白名单 + 丢弃**,\n * 不是黑名单补丁:\n * - 丢掉整棵子树:script/style/iframe/frame/frameset/object/applet/form/\n * base/meta/link/noscript/template/svg/math/xmp/plaintext/listing/\n * basefont/bgsound/title;\n * - 丢掉任何 `on*` 属性、`srcdoc`、`srcset`;\n * - URL 属性(href/src/xlink:href/action/poster/data/…)只允许\n * http/https/mailto/tel、相对路径/锚点,以及栅格 `data:image/*`\n * (`data:image/svg+xml` 与 `data:text/html` 一律拒绝);\n * - 属性值先做实体解码 + 去控制符再判 scheme,挡住 `&#106;avascript:`、\n * `java\\tscript:` 这类混淆;\n * - 其它未知标签/属性原样保留(TW 片段大量使用 `tc-*`/`$`/`data-*`)。\n *\n * 纯字符串实现(不依赖 DOM):host 侧在返回给浏览器之前净化,且能被\n * `scripts/verify-render-sanitizer.mjs` 在 Node 里逐条断言。\n *\n * @module dsh-tiddlywiki/host/sanitize\n */\n\n/** 连内容一起丢掉的元素(其 innerText 也不可信,直接跳过到闭合标签之后)。 */\nconst DROP_WITH_CONTENT = new Set([\n 'script', 'style', 'iframe', 'frame', 'frameset', 'object', 'embed', 'applet', 'form',\n 'base', 'meta', 'link', 'noscript', 'template', 'svg', 'math',\n 'xmp', 'plaintext', 'listing', 'basefont', 'bgsound', 'title',\n])\n\n/** 自闭合(void)元素——不需要闭合标签。 */\nconst VOID_ELEMENTS = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta',\n 'param', 'source', 'track', 'wbr',\n])\n\n/** 需要做 scheme 检查的 URL 属性。 */\nconst URL_ATTRIBUTES = new Set([\n 'href', 'src', 'xlink:href', 'action', 'formaction', 'poster', 'data',\n 'dynsrc', 'lowsrc', 'background', 'cite', 'longdesc', 'usemap', 'manifest',\n])\n\n/** 直接丢弃的属性(不参与 URL 检查)。 */\nconst DROP_ATTRIBUTES = new Set(['srcdoc', 'srcset', 'formaction', 'action', 'ping'])\n\n/** 允许的 URL scheme(其余带 scheme 的值一律拒绝)。 */\nconst ALLOWED_SCHEMES = new Set(['http', 'https', 'mailto', 'tel'])\n\n/** 允许的 data: 图片 MIME(栅格;SVG 可携带脚本,拒绝)。 */\nconst ALLOWED_DATA_IMAGE = /^data:image\\/(png|jpe?g|gif|webp|avif|bmp|x-icon|vnd\\.microsoft\\.icon)[;,]/i\n\n/** HTML 实体解码(只覆盖判定 scheme 需要的那些;数字实体 + 关键命名实体)。 */\nfunction decodeEntities(value: string): string {\n return value\n .replace(/&#x([0-9a-f]+);?/gi, (_m, hex: string) => {\n const code = Number.parseInt(hex, 16)\n return Number.isFinite(code) && code > 0 ? String.fromCodePoint(code) : ''\n })\n .replace(/&#(\\d+);?/g, (_m, dec: string) => {\n const code = Number.parseInt(dec, 10)\n return Number.isFinite(code) && code > 0 ? String.fromCodePoint(code) : ''\n })\n .replace(/&(colon|tab|newline|sol|period);/gi, (m) => {\n const key = m.slice(1, -1).toLowerCase()\n return key === 'colon' ? ':' : key === 'tab' ? '\\t' : key === 'newline' ? '\\n' : key === 'sol' ? '/' : '.'\n })\n .replace(/&amp;/gi, '&')\n}\n\n/** 该属性值是否是安全的 URL(相对路径/锚点/白名单 scheme/栅格 data 图片)。 */\nexport function isSafeUrl(rawValue: string): boolean {\n // 去实体 + 去控制符/空白(浏览器解析 URL 时会丢弃这些,故先归一)。\n const value = decodeEntities(rawValue).replace(/[\\u0000-\\u0020\\u007f\\u00a0]+/g, '').trim()\n if (value.length === 0) return true\n if (value.startsWith('#') || value.startsWith('/') || value.startsWith('?') || value.startsWith('.')) return true\n const schemeMatch = /^([a-z][a-z0-9+.-]*):/i.exec(value)\n if (schemeMatch === null) return true // 相对 URL\n const scheme = (schemeMatch[1] ?? '').toLowerCase()\n if (scheme === 'data') return ALLOWED_DATA_IMAGE.test(value)\n return ALLOWED_SCHEMES.has(scheme)\n}\n\n/** 属性名是否允许保留。 */\nfunction isSafeAttributeName(name: string): boolean {\n const lower = name.toLowerCase()\n if (lower.startsWith('on')) return false // onclick / onerror / onload …\n if (DROP_ATTRIBUTES.has(lower)) return false\n return /^[a-z_:][a-z0-9_:.-]*$/i.test(name)\n}\n\n/**\n * 重新输出属性值时的转义:只去控制符、只转义双引号。\n *\n * ⚠️ 不要转义 `&`:源值里的 `&amp;` 是**已经实体化**的文本(TW 渲染输出),\n * 再转一次会变成 `&amp;amp;`,浏览器解码后 URL 里就多了一个字面 `&amp;`\n * (实测踩过:`?b=1&amp;c=2` 被改成 `?b=1&amp;amp;c=2`)。`\"` 必须转义,\n * 否则单引号形式的属性值里的引号会逃出双引号上下文。\n */\nfunction escapeAttr(value: string): string {\n return value.replace(/[\\u0000-\\u001f\\u007f]/g, '').replace(/\"/g, '&quot;')\n}\n\ninterface ParsedTag {\n /** 标签结束位置(`>` 之后)。 */\n end: number\n name: string\n closing: boolean\n selfClosing: boolean\n attrs: Array<{ name: string; value: string }>\n}\n\n/**\n * 解析 `<` 起始处的一个标签。返回 null 表示不是标签(当普通文本处理)。\n * 严格性重要:属性值支持双引号/单引号/无引号三种写法,未闭合的属性值\n * (例如 `<img src=x onerror=\"…>` 里带引号却没闭合)会被吞到 `>` 或字符串末尾,\n * 后续照样被丢弃,不会漏出可执行内容。\n */\nfunction parseTag(html: string, start: number): ParsedTag | null {\n let i = start + 1\n if (i >= html.length) return null\n let closing = false\n if (html[i] === '/') {\n closing = true\n i++\n }\n const nameStart = i\n while (i < html.length && /[a-zA-Z0-9:_-]/.test(html[i] as string)) i++\n const name = html.slice(nameStart, i)\n if (name.length === 0) return null\n\n const attrs: Array<{ name: string; value: string }> = []\n for (;;) {\n while (i < html.length && /\\s/.test(html[i] as string)) i++\n if (i >= html.length) return null\n const ch = html[i]\n if (ch === '>') return { end: i + 1, name, closing, selfClosing: false, attrs }\n if (ch === '/') {\n // `/` 只可能是自闭合标记\n i++\n while (i < html.length && /\\s/.test(html[i] as string)) i++\n if (i < html.length && html[i] === '>') return { end: i + 1, name, closing, selfClosing: true, attrs }\n continue\n }\n const nameStart2 = i\n while (i < html.length && !/[\\s=/>]/.test(html[i] as string)) i++\n const attrName = html.slice(nameStart2, i)\n if (attrName.length === 0) return null\n while (i < html.length && /\\s/.test(html[i] as string)) i++\n let value = ''\n if (i < html.length && html[i] === '=') {\n i++\n while (i < html.length && /\\s/.test(html[i] as string)) i++\n const quote = html[i]\n if (quote === '\"' || quote === \"'\") {\n i++\n const valueStart = i\n while (i < html.length && html[i] !== quote) i++\n value = html.slice(valueStart, i)\n if (i < html.length) i++ // 吃掉收尾引号\n } else {\n const valueStart = i\n while (i < html.length && !/[\\s>]/.test(html[i] as string)) i++\n value = html.slice(valueStart, i)\n }\n }\n attrs.push({ name: attrName, value })\n }\n}\n\n/** 从 `from` 起找到 `</name` 之后的位置(找不到 → 字符串末尾)。 */\nfunction skipElement(html: string, from: number, name: string): number {\n const lower = html.toLowerCase()\n const needle = `</${name.toLowerCase()}`\n const close = lower.indexOf(needle, from)\n if (close < 0) return html.length\n const gt = html.indexOf('>', close + needle.length)\n return gt < 0 ? html.length : gt + 1\n}\n\n/**\n * 净化一个 TW 渲染片段:返回可以安全 `innerHTML` 的 HTML 字符串。\n * 未知标签/属性保留;危险元素整棵丢掉;危险属性丢掉;危险 URL 丢掉。\n */\nexport function sanitizeTwFragment(html: string): string {\n if (typeof html !== 'string' || html.length === 0) return ''\n const out: string[] = []\n let i = 0\n while (i < html.length) {\n const lt = html.indexOf('<', i)\n if (lt < 0) {\n out.push(html.slice(i))\n break\n }\n out.push(html.slice(i, lt))\n // 注释 / doctype / 处理指令:整体丢弃。\n if (html.startsWith('<!--', lt)) {\n const end = html.indexOf('-->', lt + 4)\n i = end < 0 ? html.length : end + 3\n continue\n }\n if (html.startsWith('<!', lt) || html.startsWith('<?', lt)) {\n const end = html.indexOf('>', lt)\n i = end < 0 ? html.length : end + 1\n continue\n }\n const tag = parseTag(html, lt)\n if (tag === null) {\n // 不是标签(例如 `a < b`):按文本输出 `<`,继续扫描。\n out.push('&lt;')\n i = lt + 1\n continue\n }\n const name = tag.name.toLowerCase()\n if (DROP_WITH_CONTENT.has(name)) {\n i = tag.closing || tag.selfClosing || VOID_ELEMENTS.has(name)\n ? tag.end\n : skipElement(html, tag.end, name)\n continue\n }\n if (tag.closing) {\n if (!VOID_ELEMENTS.has(name)) out.push(`</${name}>`)\n i = tag.end\n continue\n }\n const safeAttrs: string[] = []\n for (const attr of tag.attrs) {\n if (!isSafeAttributeName(attr.name)) continue\n const lower = attr.name.toLowerCase()\n if (URL_ATTRIBUTES.has(lower) && !isSafeUrl(attr.value)) continue\n safeAttrs.push(`${attr.name}=\"${escapeAttr(attr.value)}\"`)\n }\n const attrText = safeAttrs.length > 0 ? ` ${safeAttrs.join(' ')}` : ''\n out.push(VOID_ELEMENTS.has(name) || tag.selfClosing ? `<${name}${attrText}>` : `<${name}${attrText}>`)\n i = tag.end\n }\n return out.join('')\n}\n","/**\n * Shared helpers for the ONE-SHOT seeds (`src/host/seed-*.ts`).\n *\n * ERROR POLICY (v0.18.0). A seed may only treat a tiddler as \"missing\" when the\n * server actually answered 404 — `TiddlyWebClient.get()` already does exactly\n * that (404 → `undefined`, everything else throws). Seeds must therefore NOT\n * wrap reads in `.catch(() => undefined)`: that turned a transient failure (TW\n * restarting, timeout, 5xx) into \"the user has no such tiddler\", after which the\n * NON-force startup seeds happily `put()` the built-in content and OVERWROTE\n * user-owned tiddlers (`$:/DefaultTiddlers`, the send-to-agent / render plugin\n * bundles, the doc note…). Letting the error propagate makes the seed report\n * `ok:false` and skip the write, which is the safe outcome.\n *\n * MARKER CONTENT HASHES (v0.22.0). A one-shot marker used to be the literal\n * text `seeded-once` — enough to mean \"offered once\", useless for telling\n * whether the BUILT-IN content changed since (which the settings page needs to\n * say 「有更新」) or whether the USER edited their copy (which must never be\n * overwritten silently). The marker is now JSON:\n *\n * { \"version\": 1, \"hashes\": { \"<title>\": \"<sha256/16>\" }, \"at\": \"<ISO>\" }\n *\n * Old markers (`seeded-once`) are read as `legacy` and compared by TEXT\n * instead: identical to the built-in → ownership is proven and the marker is\n * upgraded in place; different → reported as \"maybe updated / maybe edited\",\n * never silently rewritten.\n *\n * The marker write stays best-effort (it is bookkeeping, not user content) but\n * is logged instead of silently swallowed, so a broken one-shot guarantee is at\n * least visible.\n *\n * @module dsh-tiddlywiki/host/seed-util\n */\nimport { createHash } from 'node:crypto'\nimport type { Tiddler, TiddlyWebClient } from './tw-api.ts'\n\n/** Marker schema version (bump only on an incompatible change). */\nexport const SEED_MARKER_VERSION = 1\n\n/** Parsed marker tiddler. */\nexport interface SeedMarker {\n version: number\n /** title → hash of the text this seed wrote there. */\n hashes: Record<string, string>\n at?: string\n}\n\n/** What `readSeedMarker()` found. */\nexport interface SeedMarkerState {\n /** Present when the marker is our JSON (any version we can read). */\n marker?: SeedMarker\n /**\n * The marker tiddler exists but is not our JSON — written before v0.22.0\n * (the literal `seeded-once`) or hand-edited. Hashes are unavailable; callers\n * fall back to comparing text.\n */\n legacy?: boolean\n}\n\n/** Short content hash used for markers (16 hex chars of sha256). */\nexport function hashText(text: string): string {\n return createHash('sha256').update(text, 'utf8').digest('hex').slice(0, 16)\n}\n\n/**\n * Read one tiddler for a seed. 404 → `undefined`; every other failure (network,\n * timeout, 5xx, auth) propagates to the caller, which turns it into a failed\n * seed result instead of an overwrite.\n */\nexport function readSeedTiddler(client: TiddlyWebClient, title: string): Promise<Tiddler | undefined> {\n return client.get(title)\n}\n\n/** Parse a marker tiddler's text (never throws). */\nexport function parseSeedMarker(text: string | undefined): SeedMarkerState {\n if (typeof text !== 'string' || text.trim().length === 0) return {}\n try {\n const parsed = JSON.parse(text) as { version?: unknown; hashes?: unknown }\n if (typeof parsed !== 'object' || parsed === null) return { legacy: true }\n const hashes = parsed.hashes\n if (typeof hashes !== 'object' || hashes === null) return { legacy: true }\n const clean: Record<string, string> = {}\n for (const [title, value] of Object.entries(hashes as Record<string, unknown>)) {\n if (typeof value === 'string' && value.length > 0) clean[title] = value\n }\n const version = typeof parsed.version === 'number' ? parsed.version : 1\n return { marker: { version, hashes: clean } }\n } catch {\n return { legacy: true }\n }\n}\n\n/**\n * Read a seed marker. 404 → `{}` (never seeded); a read FAILURE propagates like\n * every other seed read (v0.18.0 error policy).\n */\nexport async function readSeedMarker(client: TiddlyWebClient, title: string): Promise<SeedMarkerState> {\n const tiddler = await readSeedTiddler(client, title)\n if (tiddler === undefined) return {}\n return parseSeedMarker(typeof tiddler.text === 'string' ? tiddler.text : '')\n}\n\n/**\n * Write a one-shot marker. Best-effort: a failure is logged (never silent) and\n * does not fail the seed — the content write already succeeded and the marker\n * only records \"offered once\" + which built-in content was written.\n */\nexport async function writeSeedMarker(client: TiddlyWebClient, title: string, hashes?: Record<string, string>): Promise<void> {\n const payload: SeedMarker = { version: SEED_MARKER_VERSION, hashes: hashes ?? {}, at: new Date().toISOString() }\n try {\n await client.put({ title, text: JSON.stringify(payload), type: 'application/json', tags: [] })\n } catch (err) {\n console.warn(`[dsh-tiddlywiki] seed marker write failed (${title}):`, err instanceof Error ? err.message : err)\n }\n}\n","/**\n * System-prompt section for the plugin (v0.21.0).\n *\n * WHY THIS MODULE EXISTS\n * ----------------------\n * Until v0.20.1 the section text was a hand-written template literal inside\n * `src/index.ts` that **duplicated the tool schemas in prose**. It drifted\n * exactly as expected: the signature catalogue was last touched in v0.19.0,\n * while v0.19.4 (list_tags limit), v0.19.5 (delete/attach/batch_put\n * concurrency tokens + attach's refuse-to-overwrite) and v0.20.1 (append\n * `fields`) all changed the tools — so the injected prompt advertised 6 stale\n * signatures for four releases.\n *\n * The fix has two halves:\n * 1. `slim` (default) drops the parameter catalogue entirely — the model\n * already receives every tool's schema, so the section only carries the\n * conventions the schemas CANNOT express (sync discipline, workspace\n * tags, agent-written/human-edited, clickable wiki links). Nothing left\n * to drift.\n * 2. `full` still offers a signature index, but it is **generated from the\n * live tool registry** (`tiddlywikiToolSummary()`), so it can never go\n * stale again.\n *\n * Both modes are composed from the same governance blocks; the verify script\n * asserts every block survives in both (a re-write may not silently drop a\n * rule the user relies on).\n *\n * @module dsh-tiddlywiki/host/prompt\n */\n\n/** Prompt section name (stable id; a re-registration replaces the old one). */\nexport const PROMPT_SECTION_NAME = 'dsh-tiddlywiki'\n\n/** Prompt section order (the plugin's section sits before plan/team policy). */\nexport const PROMPT_SECTION_ORDER = 100\n\n/** Selectable prompt shapes. `slim` is the default since v0.21.0. */\nexport const PROMPT_MODES = ['slim', 'full'] as const\n\n/** One selectable prompt shape. */\nexport type PromptMode = (typeof PROMPT_MODES)[number]\n\n/** Default prompt shape (config `prompt.mode`; v0.21.0 switched to `slim`). */\nexport const DEFAULT_PROMPT_MODE: PromptMode = 'slim'\n\n/**\n * System-prompt configuration (config tiddler overlay `prompt.*`, editable on\n * the settings page; re-applied live — see `applyPrompt()` in src/index.ts).\n */\nexport interface PromptConfig {\n /** false = register no section at all (no prompt text is injected). */\n enabled?: boolean\n /** Shape of the built-in text. `override` (when set) wins over this. */\n mode?: PromptMode\n /** Free-form text always APPENDED after the built-in/override body. */\n extra?: string\n /** Replaces the built-in body entirely (`extra` is still appended). */\n override?: string\n}\n\n/** One registered tool, as summarised for the `full` catalogue. */\nexport interface PromptToolSummary {\n name: string\n /** Parameter names in declaration order, with their required flag. */\n params: Array<{ name: string; required: boolean }>\n}\n\n/** Normalise a config value to a known mode (unknown values fall back to slim). */\nexport function normalizePromptMode(value: unknown): PromptMode {\n return value === 'full' ? 'full' : DEFAULT_PROMPT_MODE\n}\n\n/**\n * Neutralise `{{…}}` groups in USER-provided prompt text.\n *\n * DSH interpolates `{{name}}` in every section before delivery and **throws**\n * on an unknown or malformed name — one `{{cwd}}` typed into `prompt.extra`\n * would break system-prompt assembly for every session. A zero-width space\n * between the braces keeps the text readable while making it literal prose\n * (the interpolation scanner no longer sees a `{{` group).\n */\nexport function escapePromptBraces(text: string): string {\n return text.replace(/\\{\\{/g, '{\\u200B{')\n}\n\n/** Governance block: how to write without clobbering human edits. */\nconst WRITE_RULES = `### 写入与并发\n- 覆盖或删除已有笔记前先 \\`tiddlywiki_get\\` 读一次,把读到的 \\`modified\\`(或 \\`revision\\`)作为 \\`expectedModified\\`(或 \\`expectedRevision\\`)传回去;若期间有人(在 TW 编辑器里)改过,写入会被拒绝并告诉你当前值——此时重新读一遍再决定,**不要用 \\`force\\` 硬覆盖**。\\`tiddlywiki_attach\\` 对同名已有条目默认拒绝写入,理由相同(要覆盖得显式 \\`force: true\\`)。\n- 纯增量内容(日志、批注、清单)优先用 \\`tiddlywiki_append\\`,不必读全文。\n- 覆盖既有条目时**不传 \\`tags\\` 就保留原有标签、自定义字段与内容类型**(只改正文);显式传 \\`tags\\` 才整体替换标签,要改内容类型用 \\`fields.type\\`。\n- 内容类型:新笔记默认写成 Markdown(工具自动补 \\`text/markdown\\`,\\`$:/\\` 系统条目除外);覆盖或追加既有条目时保留它原有的 \\`type\\`。要写 TW 原生 wikitext 才显式传 \\`fields: {\"type\":\"text/vnd.tiddlywiki\"}\\`。⚠️ \\`fields.type\\` 是 TW 的**内容类型**保留字段,业务分类请放 \\`tags\\`。`\n\n/** Governance block: the three-step git discipline + conflict recovery. */\nconst SYNC_RULES = `### 同步纪律(三条)\n1. 开工先 pull:\\`tiddlywiki_git_sync action=pull\\`(rebase + autostash;真冲突会自动 abort 并报冲突文件)。\n2. 收工 commit + push:\\`tiddlywiki_git_sync action=sync\\`(pull → commit → push)。\n3. 插件会自动防抖 commit(默认 60s),需要立刻同步时用上面的工具。\npull 冲突后:先 \\`tiddlywiki_git_resolve files=[冲突文件] strategy=keep-local\\`(保留本地)或 \\`strategy=keep-remote\\`(改用远端版本),再重新 pull/sync 整合其余改动。`\n\n/** Governance block: knowledge-base conventions (tags, memory, links). */\nconst NOTE_RULES = `### 笔记约定\n- 把 wiki 当作长期记忆与知识沉淀的地方:会议纪要、决策记录、调研笔记、随手的想法都可存成独立 tiddler(tag 建议用 inbox/meeting/decision 等便于检索)。\n- **把有价值但不在当前执行范围内的想法沉淀进 wiki**:遇到「未来可能有用 / 值得做」的想法,用 \\`tiddlywiki_put\\` 写成独立 tiddler,打上 \\`todo\\` + \\`agent-written\\` 标签(并附当前工作区名),正文简要说明来源(会话 / 工作区 / 项目背景),由用户决定是否继续。\n- 自动创建笔记时,除了业务性 tag 外,请把**当前工作区(项目)的名字**也作为标签之一,方便按项目归集与检索。\n- \\`agent-written\\`:\\`put\\`/\\`batch_put\\` 新建条目时工具自动补打,无需手动添加、也不要手动移除(除非用户明确要求);若某篇 Agent 笔记之后被人类编辑过,请补打 \\`human-edited\\`。\n- **引用 wiki 笔记用可点击链接**:在回复流中引用笔记时用 \\`[标题](/dsh-tiddlywiki/tw/#标题)\\`(标题含空格/特殊字符时做 URL 编码,如 \\`A%20B\\`;中文可直接写)。点击会打开中央 TW 面板并跳转到该笔记,请优先用它代替纯文本标题。`\n\n/**\n * Governance blocks that MUST survive in every mode — exported so\n * `scripts/verify-prompt.mjs` can assert a re-write never silently drops one.\n */\nexport const PROMPT_GOVERNANCE_BLOCKS: readonly string[] = [WRITE_RULES, SYNC_RULES, NOTE_RULES]\n\n/**\n * One line per tool: `bullet \\`name\\`(p1, p2?, …)`; `?` marks an optional\n * parameter. Shared by the `full` prompt catalogue and by the TW-side doc seed\n * (which reuses it as wikitext bullets), so both stay generated (v0.22.0).\n */\nexport function toolSignatureLines(tools: readonly PromptToolSummary[], bullet = '-'): string[] {\n return tools.map((tool) => {\n if (tool.params.length === 0) return `${bullet} \\`${tool.name}\\``\n const params = tool.params.map((p) => (p.required ? p.name : `${p.name}?`)).join(', ')\n return `${bullet} \\`${tool.name}\\`(${params})`\n })\n}\n\n/** Heading + a one-line capability pointer (slim intro). */\nfunction slimIntro(count: number): string {\n return `## TiddlyWiki 持久知识库\n\n本机有一个 TiddlyWiki 5 持久知识库(wiki 文件夹即 git 仓库)。插件提供 ${count} 个 \\`tiddlywiki_*\\` 工具:检索与读取、写入与批量写入、增量追加、重命名、删除与回收站、反向链接、二进制附件、知识库体检、git 同步与冲突解决。**每个工具的参数与返回契约以工具 schema 的 description 为准**,本段只补充 schema 之外仍需知道的规则。`\n}\n\n/** Heading + the generated signature catalogue (full intro). */\nfunction fullIntro(tools: readonly PromptToolSummary[]): string {\n const lines = toolSignatureLines(tools).join('\\n')\n return `## TiddlyWiki 持久知识库\n\n本机有一个 TiddlyWiki 5 持久知识库(wiki 文件夹即 git 仓库)。可用工具(${tools.length} 个,\\`?\\` 表示可选参数;详细契约以各工具的 description 为准):\n\n${lines}`\n}\n\n/**\n * Build the section text.\n *\n * Composition order: body → `extra`. The body is `override` when non-empty,\n * otherwise the built-in text for `mode`. User text is brace-escaped (see\n * `escapePromptBraces`); the built-in text is not (it never contains `{{`).\n * Returns `''` when `enabled === false` — callers must then register no\n * section at all rather than an empty one.\n */\nexport function buildPromptText(options: {\n mode?: PromptMode\n extra?: string\n override?: string\n tools?: readonly PromptToolSummary[]\n enabled?: boolean\n}): string {\n if (options.enabled === false) return ''\n const tools = options.tools ?? []\n const override = typeof options.override === 'string' ? options.override.trim() : ''\n const body = override.length > 0\n ? escapePromptBraces(override)\n : [normalizePromptMode(options.mode) === 'full' ? fullIntro(tools) : slimIntro(tools.length), WRITE_RULES, SYNC_RULES, NOTE_RULES].join('\\n\\n')\n const extra = typeof options.extra === 'string' ? escapePromptBraces(options.extra.trim()) : ''\n return extra.length > 0 ? `${body}\\n\\n${extra}` : body\n}\n","/**\n * Built-in doc note for the plugin (design doc §14): a short user-facing\n * \"how to use dsh-tiddlywiki\" note seeded into the wiki the first time the\n * plugin runs on a wiki.\n *\n * ONE-SHOT seed: a marker tiddler (`seed-doc-note`) records that the note has\n * been offered once; after that the note is the user's own content — deleting\n * it and restarting dsh web does NOT recreate it, and edits are never\n * overwritten.\n *\n * @module dsh-tiddlywiki/host/seed-notes\n */\nimport type { TiddlyWebClient } from './tw-api.ts'\nimport { readSeedTiddler, writeSeedMarker } from './seed-util.ts'\nimport { toolSignatureLines, type PromptToolSummary } from './prompt.ts'\n\n/** Note tiddler title (a normal, searchable note — not a system tiddler). */\nexport const DOC_NOTE_TITLE = 'dsh-tiddlywiki 插件说明'\n\n/** Tag that makes the note easy to find via `tiddlywiki_search tag=docs`. */\nexport const DOC_NOTE_TAG = 'docs'\n\n/**\n * Shared tag that collects every plugin-seeded doc into the seeded home's\n *「📚 插件文档」tabs strip (see seed-starter-docs.ts DSH_DOCS_TAG).\n */\nexport const DOC_NOTE_DSH_DOCS_TAG = 'dsh-docs'\n\n/** One-time marker: its presence means \"the note was offered once — hands off\". */\nexport const SEED_MARKER_TITLE = '$:/plugins/dsh-tiddlywiki/seed-doc-note'\n\n/** The note body, TiddlyWiki wiki-text. */\nconst DOC_NOTE_HEAD = `! dsh-tiddlywiki 插件说明\n\n本插件把 **TiddlyWiki 5** 作为 DSH 的持久知识库(wiki 文件夹本身就是一个 git 仓库,随内容自动提交/同步)。\n\n!! 它能做什么\n`\n\n/**\n * Tool-list bullet for the doc note, GENERATED from the live tool registry\n * (v0.22.0). The note used to hard-code 「10 个 agent 工具」 and was 5 tools\n * behind by the time anyone noticed — the same class of drift the prompt\n * catalogue had. With no registry available (headless callers) it degrades to\n * a pointer instead of an outdated list.\n */\nfunction docToolBullet(tools: readonly PromptToolSummary[]): string {\n if (tools.length === 0) {\n return \"* **agent 工具**:`tiddlywiki_*` 系列(检索 / 读写 / 批量写 / 增量追加 / 重命名 / 删除与回收站 / 反向链接 / 附件 / 体检 / git 同步与冲突解决)——完整清单与参数见 DSH 设置页与各工具的 schema。\"\n }\n return ['* **' + String(tools.length) + ' 个 agent 工具**(参数以工具 schema 为准,\\u0060?\\u0060 表示可选):', ...toolSignatureLines(tools, '* ')].join('\\n')\n}\n\nconst DOC_NOTE_TAIL = `* **TW 编辑器面板**:侧边栏「TiddlyWiki」按钮 → 在界面中央打开完整版 TW 编辑器。\n* **快速笔记**:点击聊天输入框上方或右下角「知识库」菜单里的「📝 快速笔记」——默认**直达 TW 原生编辑页**(独立小窗,草稿自动续写);也可在设置页切回 Markdown 卡片(语法高亮、文件上传、多选 tag、草稿自动保存、Ctrl+Enter 保存)。「✏️ 在 TW 中编辑」会弹出独立小窗用 TW 原生编辑器编辑。\n* **一键同步**:「知识库」按钮 → 「🔁 同步」一键 pull → commit → push,按钮上的状态点实时反映 git 状态。\n* **git 同步**:写入自动防抖 commit(默认 60 秒);手动 \\`tiddlywiki_git_sync action=sync\\` 做 pull → commit → push。\n* **设置页**:DSH 设置 → 「TiddlyWiki 知识库」管理插件/主题/语言与运行配置(含「知识库」按钮相关显示开关)。\n* **注入给 Agent 的提示词可配置**(设置页「系统提示词」区块):默认**精简版**只约定同步纪律/标签/链接格式(工具参数由工具 schema 提供,不重复);可切完整版(附参数索引)、追加自定义规范(\\`extra\\`)、整段替换(\\`override\\`)或整体停用。保存即生效,无需重启 dsh web;点「查看当前注入文本」可预览下一步实际注入的全文。\n\n!! 知识库纪律(四条)\n\n1. 开工先 \\`tiddlywiki_git_sync action=pull\\`(rebase + autostash,真冲突会自动 abort 并报文件)。\n2. 冲突后:\\`tiddlywiki_git_resolve files=[冲突文件] strategy=keep-local|keep-remote\\` 按 tiddler 二选一解决,再重新 sync。\n3. 收工 \\`tiddlywiki_git_sync action=sync\\`。\n4. 插件自动 commit 兜底,手动 sync 用于需要主动推送的场合。\n\n!! 主题与语言\n\n* **主题**分两层:每行一个「☑ 加载」(多选 = TW 里可用的主题,依赖链自动带上)和「◉ 活动」(单选 = 当前视觉主题)。应用后自动重启 TW。\n* **语言**:设置页勾选 \\`zh-Hans\\`(简体)并应用,TW 界面即切换为中文。\n\n!! 说明\n\n* 本笔记由插件在**首次启动**时自动写入(一次性:只写一次)。删除后重启 dsh web **不会自动恢复**——它从此归你所有。\n* 本笔记带 \\`dsh-docs\\` 标签,会出现在首页「📚 插件文档」栏(与「示例与文档」seed 的教程/模板一起),不需要可自由删除。\n* 更多细节见插件仓库 README。`\n\n/**\n * The doc note text. `tools` should be the live registry summary\n * (`tiddlywikiToolSummary()`), so the tool list can never go stale.\n */\nexport function docNoteText(tools: readonly PromptToolSummary[] = []): string {\n return `${DOC_NOTE_HEAD}${docToolBullet(tools)}${DOC_NOTE_TAIL}`\n}\n\n/** Back-compat constant: the note as built without a live registry. */\nexport const DOC_NOTE_TEXT = docNoteText()\n\n/**\n * Seed the doc note once per wiki (mirrors the one-shot policy). A marker\n * tiddler records that the note has been offered; from then on the note is\n * user-owned and is never re-created (deleting it survives restarts).\n *\n * With `opts.force` the note is (re)written even when it already exists and\n * the marker is (re)written — the settings page uses this for\n * \"重新初始化\". Returns whether a note was written this call. Throws when a read fails (the seed registry reports it as `ok:false`).\n */\nexport async function seedDocNote(client: TiddlyWebClient, opts?: { force?: boolean; tools?: readonly PromptToolSummary[] }): Promise<boolean> {\n const force = opts?.force === true\n if (!force) {\n const marker = await readSeedTiddler(client, SEED_MARKER_TITLE)\n if (marker !== undefined) return false\n }\n const existing = await readSeedTiddler(client, DOC_NOTE_TITLE)\n let wrote = false\n if (force || existing === undefined) {\n await client.put({\n title: DOC_NOTE_TITLE,\n text: docNoteText(opts?.tools ?? []),\n type: 'text/vnd.tiddlywiki',\n tags: [DOC_NOTE_TAG, DOC_NOTE_DSH_DOCS_TAG],\n })\n wrote = true\n }\n // Record the offer regardless, so an existing note (upgrade from an older\n // create-if-missing version) also becomes user-owned from here on.\n await writeSeedMarker(client, SEED_MARKER_TITLE)\n return wrote\n}\n\n/**\n * Un-seed (反初始化): remove the doc note and its one-shot marker, returning\n * the wiki to the \"never offered\" state. Deletion is idempotent — a tiddler\n * that was already gone is simply not listed. Throws when a read fails (the seed registry reports it as `ok:false`).\n */\nexport async function unseedDocNote(client: TiddlyWebClient): Promise<{ removed: string[] }> {\n const removed: string[] = []\n for (const title of [DOC_NOTE_TITLE, SEED_MARKER_TITLE]) {\n const t = await readSeedTiddler(client, title)\n if (t !== undefined) {\n await client.delete(title)\n removed.push(title)\n }\n }\n return { removed }\n}\n","/**\n * The \"示例与文档\" starter seed (v0.16.22): the generic docs / templates /\n * example pages that give a fresh wiki a working \"文档中心\" out of the box —\n * 主题汇总页·模板 (blank template) + 教程:按主题/标签做汇总页 (how-to) + three\n * runnable 主题页 examples (日志 / 决策记录 / 排障).\n *\n * Sanitized: every tiddler carries NO personal data — the tutorial's\n * 配套产物清单 points only at the tiddlers this seed actually provides, and\n * the example pages auto-collect by tag (text verbatim from the author's wiki,\n * they were already generic). Every item gets the shared `dsh-docs` tag so the\n * seeded home's「📚 插件文档」tabs strip collects them automatically.\n *\n * Tier: STARTER — seeded automatically on first install (safe-skip: any\n * same-named tiddler already present is never overwritten) and removable via\n *「反初始化」from the settings page (ONE-SHOT marker-gated, like doc-note).\n *\n * @module dsh-tiddlywiki/host/seed-starter-docs\n */\nimport type { TiddlyWebClient } from './tw-api.ts'\nimport { readSeedTiddler, writeSeedMarker } from './seed-util.ts'\n\n/** One-time marker: presence means \"the docs were offered once — hands off\". */\nexport const STARTER_DOCS_MARKER_TITLE = '$:/plugins/dsh-tiddlywiki/seed-starter-docs'\n\n/** Shared tag that collects every plugin-seeded doc into the home docs tab. */\nexport const DSH_DOCS_TAG = 'dsh-docs'\n\nexport interface StarterDocItem {\n title: string\n tags: string[]\n type: string\n text: string\n}\n\n/** The docs, exactly as seeded (user-owned afterwards, freely deletable). */\nexport const STARTER_DOCS_ITEMS: StarterDocItem[] = [\n {\n title: '主题汇总页·模板',\n tags: ['索引', 'dsh-docs'],\n type: 'text/vnd.tiddlywiki',\n text: `\\\\whitespace trim\n\n!! 📌 主题汇总页\n\n<div style=\"color:#888; font-size:0.85em; border:1px dashed rgba(128,128,128,0.35); border-radius:8px; padding:6px 10px; margin-bottom:8px;\">【模板】复制本页 → 改名(如「XX主题汇总」)→ 把正文里的 <code>主题A</code> 全部替换成你的标签名 → 保存。给笔记打上该标签即自动收录,本页无需维护。</div>\n\n<div class=\"tc-message-box\">自动收集带 <code>主题A</code> 标签的笔记,按最近修改排序,共 <strong>{{{[tag[主题A]!is[system]!has[draft.of]count[]]}}}</strong> 篇。</div>\n\n<<list-links \"[tag[主题A]!is[system]!has[draft.of]] +[!sort[modified]]\">>\n\n---\n\n> 想自定义每行样式,或把本页收进「一页多主题」tabs?见 [[教程:按主题/标签做汇总页]]。\n`,\n },\n {\n title: '教程:按主题/标签做汇总页',\n tags: ['教程', 'dsh-docs'],\n type: 'text/markdown',\n text: `# 教程:按主题/标签做汇总页\n\n> 适用:本 wiki 的 TiddlyWiki 5.3.x 内核。⚠️ 网上大量教程是 5.1/5.2 时代写的,其中 \\`<<tabs \"筛选器\" \"标签\">>\\` 和 \\`<<count \"筛选器\">>\\` 两种写法在本版本已**改版/移除**,照抄会报错,注意甄别。\n\n## 核心思路(一句话)\n\n「汇总页」不是 TW 的特殊功能,而是:**一个普通笔记 + 正文里一段筛选器(filter)**。渲染时 TW 实时从整个 wiki 挑出符合条件的笔记列出来。**新笔记只要打上对应标签,汇总页自动出现,零维护**——不用像手工目录那样每次手动更新。\n\n## 方法一:零代码,直接用内置标签页\n\n- 打开任意笔记,点正文底部的**标签链接**;\n- 或侧边栏「标签」(标签云)里点某个标签。\n- TW 会自动生成该标签的页面,列出所有带此标签的笔记。\n- 优点:一行代码都不用写;缺点:不能加说明文字、不能自定义排序/样式,也做不了「多主题合一」。\n\n## 方法二:一页一主题(最常用)\n\n新建一个笔记(wikitext),正文写:\n\n\\`\\`\\`wikitext\n!! 📔 日志主题汇总\n<div class=\"tc-message-box\">自动收集带 <code>日志</code> 标签的笔记,共 <strong>{{{[tag[日志]!is[system]!has[draft.of]count[]]}}}</strong> 篇,按最近修改排序。</div>\n\n<<list-links \"[tag[日志]!is[system]!has[draft.of]] +[!sort[modified]]\">>\n\\`\\`\\`\n\n各片段含义:\n\n- \\`[tag[日志]]\\`:挑出所有带「日志」标签的笔记;\n- \\`!is[system]\\`:排除 \\`$:/\\` 系统页;\n- \\`!has[draft.of]\\`:排除未保存的草稿副本;\n- \\`+[!sort[modified]]\\`:按修改时间**倒序**(\\`sort[modified]\\` 为正序);\n- \\`{{{[...count[]]}}}\\`:筛选器计数(旧版 \\`<<count>>\\` 宏已移除,这是 5.3 新写法)。\n\n想自定义每行样式(带相对时间、复选框等),用你更熟悉的 \\`<$list>\\` 写法:\n\n\\`\\`\\`wikitext\n<ul>\n<$list filter=\"[tag[日志]!is[system]!has[draft.of]] +[!sort[modified]]\">\n<li><$link to=<<currentTiddler>>><$view field=\"title\"/></$link><span style=\"color:#aaa; font-size:0.85em;\"> · <$view field=\"modified\" format=\"relativedate\"/></span></li>\n</$list>\n</ul>\n\\`\\`\\`\n\n## 方法三:一页多主题(Dashboard,5.3 新版 tabs)\n\n5.3 的 \\`tabs\\` 宏签名:\\`<<tabs \"选中标签页的筛选器\" \"默认选中项\">>\\`。思路分两步:\n\n1. **每个主题各建一个汇总页**(就是方法二那种),全部打上同一个标签(本插件 seed 的示例用 \\`主题页\\`);要自定义按钮文字,就给该页加一个 \\`caption\\` 字段(如 \\`📔 日志\\`)。\n2. **建一个总览页**,正文只写一行:\n\n\\`\\`\\`wikitext\n<<tabs \"[tag[主题页]!is[system]]\" \"主题页·日志\">>\n\\`\\`\\`\n\n效果:页面上方一排按钮,点哪个就内嵌显示哪个主题的汇总。第二个参数是默认展开的页(可省略,省略则初始不展开)。\n\n> 老教程的 \\`<<tabs \"筛选器A\" \"标签A\" \"筛选器B\" \"标签B\">>\\` 在 5.3 已失效,别用。\n\n本插件已 seed 了可直接运行的示例:[主题页·日志]、[主题页·决策记录]、[主题页·排障](都带 \\`主题页\\` 标签),复制改造即可。\n\n## 筛选器速查表(背熟这几行就够用)\n\n| 想要 | 写法 |\n| --- | --- |\n| 某标签下的笔记 | \\`[tag[主题]]\\` |\n| 排除系统页 / 草稿 | \\`[tag[主题]!is[system]!has[draft.of]]\\` |\n| 修改时间倒序 / 正序 | \\`+[!sort[modified]]\\` / \\`+[sort[modified]]\\` |\n| 按标题排序 | \\`+[sort[title]]\\` |\n| 交集(同时有两个标签) | \\`[tag[A]+tag[B]]\\` |\n| 并集(有任一标签) | \\`[tag[A]] [tag[B]]\\` |\n| 差集(有 A 无 B) | \\`[tag[A]-tag[B]]\\` |\n| 排除某标签 | \\`-[tag[排除项]]\\` |\n| 限定时间段 | \\`[tag[主题]modified[2026-09]]\\` |\n| 计数 | \\`{{{[tag[主题]!is[system]count[]]}}}\\`(或 \\`<$count filter=\"[tag[主题]]\"/>\\`) |\n| 整个 wiki 的非系统非草稿 | \\`all[tiddlers]!is[system]!has[draft.of]\\` |\n\n## 进阶玩法\n\n1. **按字段筛选**:\\`[tag[todo]get[due]compare:date:lt<today>]\\` 可列出「到期日早于今天」的笔记(主页的逾期看板就是这么写的)。\n2. **树状目录**:\\`<<toc tag:\"主题\">>\\` 显示层级目录,适合一个主题下还有子主题的情况。\n3. **模板复用**:复制 [主题汇总页·模板] 改标题和标签即可,不用每次重写。\n4. **打开直达**:把 \\`$:/DefaultTiddlers\\` 改成 \\`[[主题页·日志]]\\`,启动即打开汇总页(当前默认是 [🏠 主页])。\n5. **导航收录**:给汇总页打 \\`索引\\` 标签,它就会从「全部笔记 / 所有文章」里消失、只作为导航页存在;再在 [🏠 主页] 的按钮区加一个入口按钮即可。\n6. **标签整理**:控制台 → 工具 → 标签管理器,可重命名/合并标签,改名后所有笔记自动跟随,汇总页筛选器无需改动(筛选器按标签名匹配,自动跟随改名)。\n\n## 配套产物清单(插件 seed 提供了什么)\n\n- [主题页·日志]、[主题页·决策记录]、[主题页·排障] —— 单主题汇总页实例(各自可独立打开,也可作为「一页多主题」tabs 的标签页)\n- [主题汇总页·模板] —— 空白模板,复制即用\n- 本教程 —— 就是这一页\n\n以上都来自「示例与文档」seed(tag \\`dsh-docs\\`),不需要可自由删除,删除后不会自动恢复。\n`,\n },\n {\n title: '主题页·日志',\n tags: ['主题页', '索引', 'dsh-docs'],\n type: 'text/vnd.tiddlywiki',\n text: `\\\\whitespace trim\n\n!! 📔 日志主题汇总\n\n<div class=\"tc-message-box\">自动收集带 <code>日志</code> 标签的笔记,按最近修改排序,共 <strong>{{{[tag[日志]!is[system]!has[draft.of]count[]]}}}</strong> 篇。给笔记打上「日志」标签即自动收录,本页无需维护(做法见 [[教程:按主题/标签做汇总页]])。</div>\n\n<<list-links \"[tag[日志]!is[system]!has[draft.of]] +[!sort[modified]]\">>\n`,\n },\n {\n title: '主题页·决策记录',\n tags: ['主题页', '索引', 'dsh-docs'],\n type: 'text/vnd.tiddlywiki',\n text: `\\\\whitespace trim\n\n!! 📌 决策记录主题汇总\n\n<div class=\"tc-message-box\">自动收集带 <code>决策记录</code> 标签的笔记,按最近修改排序,共 <strong>{{{[tag[决策记录]!is[system]!has[draft.of]count[]]}}}</strong> 篇。给笔记打上「决策记录」标签即自动收录,本页无需维护(做法见 [[教程:按主题/标签做汇总页]])。</div>\n\n<<list-links \"[tag[决策记录]!is[system]!has[draft.of]] +[!sort[modified]]\">>\n`,\n },\n {\n title: '主题页·排障',\n tags: ['主题页', '索引', 'dsh-docs'],\n type: 'text/vnd.tiddlywiki',\n text: `\\\\whitespace trim\n\n!! 🔧 排障主题汇总\n\n<div class=\"tc-message-box\">自动收集带 <code>troubleshooting</code> 标签的笔记,按最近修改排序,共 <strong>{{{[tag[troubleshooting]!is[system]!has[draft.of]count[]]}}}</strong> 篇。给笔记打上「troubleshooting」标签即自动收录,本页无需维护(做法见 [[教程:按主题/标签做汇总页]])。</div>\n\n<<list-links \"[tag[troubleshooting]!is[system]!has[draft.of]] +[!sort[modified]]\">>\n`,\n },\n]\n\n/**\n * Seed the starter docs once per wiki (safe-skip: same-named tiddlers already\n * present are NEVER overwritten — user data stays user data). Marker-gated\n * ONE-SHOT; with `opts.force` the tiddlers are (re)written and the marker\n * (re)recorded — the settings page uses this for \"重新初始化\".\n * Returns whether anything was written this call. Throws when a read fails (the seed registry reports it as `ok:false`).\n */\nexport async function seedStarterDocs(client: TiddlyWebClient, opts?: { force?: boolean }): Promise<boolean> {\n const force = opts?.force === true\n if (!force) {\n const marker = await readSeedTiddler(client, STARTER_DOCS_MARKER_TITLE)\n if (marker !== undefined) return false\n }\n let wrote = false\n for (const item of STARTER_DOCS_ITEMS) {\n const existing = await readSeedTiddler(client, item.title)\n if (force || existing === undefined) {\n await client.put({ title: item.title, text: item.text, type: item.type, tags: item.tags })\n wrote = true\n }\n }\n await writeSeedMarker(client, STARTER_DOCS_MARKER_TITLE)\n return wrote\n}\n\n/**\n * Un-seed (反初始化): remove the starter docs and their marker. Deletion is\n * idempotent — a tiddler already gone is not listed. Throws when a read fails (the seed registry reports it as `ok:false`).\n */\nexport async function unseedStarterDocs(client: TiddlyWebClient): Promise<{ removed: string[] }> {\n const removed: string[] = []\n for (const item of STARTER_DOCS_ITEMS) {\n const t = await readSeedTiddler(client, item.title)\n if (t !== undefined) {\n await client.delete(item.title)\n removed.push(item.title)\n }\n }\n const marker = await readSeedTiddler(client, STARTER_DOCS_MARKER_TITLE)\n if (marker !== undefined) {\n await client.delete(STARTER_DOCS_MARKER_TITLE)\n removed.push(STARTER_DOCS_MARKER_TITLE)\n }\n return { removed }\n}","/**\n * Generated from the wiki bundle (do not hand-edit the constant).\n * Source: send-to-agent.bundle.json\n *\n * The \"发送给 Agent\" TW button, packaged as a TiddlyWiki plugin bundle\n * (`{\"tiddlers\": {...}}`), seeded into fresh wikis by seedSendToAgent.\n *\n * @module dsh-tiddlywiki/host/seed-send-to-agent\n */\nimport type { TiddlyWebClient } from './tw-api.ts'\nimport { readSeedTiddler, writeSeedMarker } from './seed-util.ts'\n\n/** The packaged plugin tiddler title (a TW system tiddler, type application/json). */\nexport const SEND_TO_AGENT_PLUGIN_TITLE = '$:/plugins/dsh/send-to-agent'\n\n/** One-time marker: presence means \"the button was offered once — hands off\". */\nexport const SEND_TO_AGENT_MARKER_TITLE = '$:/plugins/dsh-tiddlywiki/seed-send-to-agent'\n\n/** The bundle's JSON text (`{\"tiddlers\": {...}}`), exactly as TW stores it. */\nexport const SEND_TO_AGENT_BUNDLE_TEXT = \"{\\n \\\"tiddlers\\\": {\\n \\\"$:/plugins/dsh/send-to-agent/plugin.info\\\": {\\n \\\"title\\\": \\\"$:/plugins/dsh/send-to-agent/plugin.info\\\",\\n \\\"type\\\": \\\"application/json\\\",\\n \\\"text\\\": \\\"{\\\\\\\"title\\\\\\\":\\\\\\\"$:/plugins/dsh/send-to-agent\\\\\\\",\\\\\\\"name\\\\\\\":\\\\\\\"Send to Agent\\\\\\\",\\\\\\\"description\\\\\\\":\\\\\\\"把当前笔记一键发送给 DSH Agent(TiddlyWiki → DSH 会话注入)\\\\\\\",\\\\\\\"author\\\\\\\":\\\\\\\"dsh-tiddlywiki\\\\\\\",\\\\\\\"version\\\\\\\":\\\\\\\"0.3.5\\\\\\\",\\\\\\\"plugin-type\\\\\\\":\\\\\\\"plugin\\\\\\\"}\\\"\\n },\\n \\\"$:/plugins/dsh/send-to-agent/startup.js\\\": {\\n \\\"title\\\": \\\"$:/plugins/dsh/send-to-agent/startup.js\\\",\\n \\\"type\\\": \\\"application/javascript\\\",\\n \\\"module-type\\\": \\\"startup\\\",\\n \\\"text\\\": \\\"/*\\\\\\\\\\\\ntitle: $:/plugins/dsh/send-to-agent/startup.js\\\\ntype: application/javascript\\\\nmodule-type: startup\\\\n\\\\n\\\\\\\\*/\\\\n(function(){\\\\n\\\\n/*jslint node: true, browser: true */\\\\n/*global $tw: false */\\\\n\\\\\\\"use strict\\\\\\\";\\\\n\\\\nexports.name = \\\\\\\"dsh-send-to-agent\\\\\\\";\\\\nexports.after = [\\\\\\\"story\\\\\\\"];\\\\nexports.platforms = [\\\\\\\"browser\\\\\\\"];\\\\n\\\\nfunction readConfig() {\\\\n\\\\tvar config = { enabled: true, endpoint: \\\\\\\"\\\\\\\", token: \\\\\\\"\\\\\\\" };\\\\n\\\\ttry {\\\\n\\\\t\\\\tvar t = $tw.wiki.getTiddler(\\\\\\\"$:/plugins/dsh-tiddlywiki/config\\\\\\\");\\\\n\\\\t\\\\tif (t && t.fields && typeof t.fields.text === \\\\\\\"string\\\\\\\") {\\\\n\\\\t\\\\t\\\\tvar parsed = JSON.parse(t.fields.text);\\\\n\\\\t\\\\t\\\\tvar s2a = parsed && parsed.ui && parsed.ui.sendToAgent;\\\\n\\\\t\\\\t\\\\tif (s2a) {\\\\n\\\\t\\\\t\\\\t\\\\tif (typeof s2a.enabled === \\\\\\\"boolean\\\\\\\") { config.enabled = s2a.enabled; }\\\\n\\\\t\\\\t\\\\t\\\\tif (typeof s2a.endpoint === \\\\\\\"string\\\\\\\" && s2a.endpoint.length > 0) { config.endpoint = s2a.endpoint; }\\\\n\\\\t\\\\t\\\\t\\\\tif (typeof s2a.token === \\\\\\\"string\\\\\\\" && s2a.token.length > 0) { config.token = s2a.token; }\\\\n\\\\t\\\\t\\\\t}\\\\n\\\\t\\\\t}\\\\n\\\\t} catch (e) {}\\\\n\\\\treturn config;\\\\n}\\\\n\\\\nfunction baseEndpoint() {\\\\n\\\\tvar config = readConfig();\\\\n\\\\tif (config.endpoint.length > 0) { return config.endpoint.replace(/\\\\\\\\/+$/, \\\\\\\"\\\\\\\"); }\\\\n\\\\tif (typeof location !== \\\\\\\"undefined\\\\\\\" && location.origin) { return location.origin + \\\\\\\"/dsh-tiddlywiki\\\\\\\"; }\\\\n\\\\treturn \\\\\\\"/dsh-tiddlywiki\\\\\\\";\\\\n}\\\\n\\\\n/*\\\\nShow a transient message.\\\\n\\\\nTW's notifier renders a TIDDLER by title and does **nothing at all** when that\\\\ntiddler does not exist (core/modules/utils/dom/notifier.js: \\\\\\\"Don't do anything\\\\nif the tiddler doesn't exist\\\\\\\"). Passing free text as the title — which is what\\\\nthis function used to do — therefore made every success/failure notice of the\\\\n「发送给 Agent」 button invisible: the user clicked, chose a session, and saw\\\\nnothing. Store the message in a $:/temp tiddler first (volatile: never synced,\\\\nnever written to disk) and display THAT title.\\\\n*/\\\\nvar NOTICE_TITLE = \\\\\\\"$:/temp/dsh/send-to-agent/notice\\\\\\\";\\\\nfunction notify(msg) {\\\\n\\\\ttry {\\\\n\\\\t\\\\tif ($tw.wiki && $tw.notifier && typeof $tw.notifier.display === \\\\\\\"function\\\\\\\" && typeof $tw.wiki.addTiddler === \\\\\\\"function\\\\\\\") {\\\\n\\\\t\\\\t\\\\t$tw.wiki.addTiddler({ title: NOTICE_TITLE, text: String(msg), type: \\\\\\\"text/vnd.tiddlywiki\\\\\\\" });\\\\n\\\\t\\\\t\\\\t$tw.notifier.display(NOTICE_TITLE);\\\\n\\\\t\\\\t\\\\treturn;\\\\n\\\\t\\\\t}\\\\n\\\\t} catch (e) {}\\\\n\\\\tif (typeof alert === \\\\\\\"function\\\\\\\") { alert(msg); }\\\\n}\\\\n\\\\nfunction doSend(payload, sessionId, note) {\\\\n\\\\tvar config = readConfig();\\\\n\\\\tvar lines = [];\\\\n\\\\tlines.push(\\\\\\\"《\\\\\\\" + payload.title + \\\\\\\"》\\\\\\\");\\\\n\\\\tlines.push(\\\\\\\"标签: \\\\\\\" + (payload.tags || []).join(\\\\\\\", \\\\\\\"));\\\\n\\\\tlines.push(\\\\\\\"类型: \\\\\\\" + (payload.type || \\\\\\\"无\\\\\\\"));\\\\n\\\\tlines.push(\\\\\\\"\\\\\\\");\\\\n\\\\tlines.push(\\\\\\\"【待办说明】以下内容是我(用户)提前编辑在 TiddlyWiki 知识库中的待办事项,通过「发送给 Agent」一键发送给你处理。请按内容执行;如有任何不清楚的地方,请主动向我提问,不要臆测或擅自发挥。\\\\\\\");\\\\n\\\\tlines.push(\\\\\\\"\\\\\\\");\\\\n\\\\tlines.push(payload.text || \\\\\\\"\\\\\\\");\\\\n\\\\t// 附加说明放在消息最后:正文之后、作为我(用户)的最终补充要求,优先遵循。\\\\n\\\\tif (note && String(note).trim().length > 0) {\\\\n\\\\t\\\\tlines.push(\\\\\\\"\\\\\\\");\\\\n\\\\t\\\\tlines.push(\\\\\\\"【附加说明】\\\\\\\" + String(note).trim());\\\\n\\\\t}\\\\n\\\\tvar text = lines.join(\\\\\\\"\\\\\\\\n\\\\\\\");\\\\n\\\\tvar headers = { \\\\\\\"Content-Type\\\\\\\": \\\\\\\"application/json\\\\\\\" };\\\\n\\\\tif (config.token.length > 0) { headers[\\\\\\\"x-send-to-agent-token\\\\\\\"] = config.token; }\\\\n\\\\t$tw.utils.httpRequest({\\\\n\\\\t\\\\turl: baseEndpoint() + \\\\\\\"/agent/send\\\\\\\",\\\\n\\\\t\\\\ttype: \\\\\\\"POST\\\\\\\",\\\\n\\\\t\\\\theaders: headers,\\\\n\\\\t\\\\tdata: JSON.stringify({ sessionId: sessionId, text: text }),\\\\n\\\\t\\\\tcallback: function(err, data) {\\\\n\\\\t\\\\t\\\\tvar parsed = null;\\\\n\\\\t\\\\t\\\\ttry { parsed = JSON.parse(data || \\\\\\\"\\\\\\\"); } catch (e) {}\\\\n\\\\t\\\\t\\\\tif (err) { notify(\\\\\\\"发送失败:\\\\\\\" + err); return; }\\\\n\\\\t\\\\t\\\\tif (parsed && parsed.ok) {\\\\n\\\\t\\\\t\\\\t\\\\tnotify(\\\\\\\"已发送 ✓ 会话 \\\\\\\" + sessionId.slice(0, 8));\\\\n\\\\t\\\\t\\\\t} else {\\\\n\\\\t\\\\t\\\\t\\\\tnotify(\\\\\\\"发送失败:\\\\\\\" + ((parsed && parsed.error) || data || \\\\\\\"未知错误\\\\\\\"));\\\\n\\\\t\\\\t\\\\t}\\\\n\\\\t\\\\t}\\\\n\\\\t});\\\\n}\\\\n\\\\nfunction closeOverlay(overlay, escHandler) {\\\\n\\\\tif (escHandler) { document.removeEventListener(\\\\\\\"keydown\\\\\\\", escHandler); }\\\\n\\\\tif (overlay && overlay.parentNode) { overlay.parentNode.removeChild(overlay); }\\\\n}\\\\n\\\\nfunction showPicker(payload, items, modes, defaultId, permissions) {\\\\n\\\\tif (typeof document === \\\\\\\"undefined\\\\\\\") { return; }\\\\n\\\\t// remove any previously-open picker\\\\n\\\\tvar old = document.getElementById(\\\\\\\"dsh-send-picker\\\\\\\");\\\\n\\\\tif (old && old.parentNode) { old.parentNode.removeChild(old); }\\\\n\\\\n\\\\tvar state = { workspace: null, mode: \\\\\\\"\\\\\\\", note: \\\\\\\"\\\\\\\", permission: \\\\\\\"\\\\\\\" };\\\\n\\\\n\\\\tvar overlay = document.createElement(\\\\\\\"div\\\\\\\");\\\\n\\\\toverlay.id = \\\\\\\"dsh-send-picker\\\\\\\";\\\\n\\\\toverlay.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.35);z-index:99999;display:flex;align-items:center;justify-content:center;\\\\\\\");\\\\n\\\\tvar box = document.createElement(\\\\\\\"div\\\\\\\");\\\\n\\\\tbox.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"background:#fff;color:#333;border-radius:8px;padding:14px;min-width:340px;max-width:560px;max-height:82vh;display:flex;flex-direction:column;box-shadow:0 8px 30px rgba(0,0,0,.3);font-family:system-ui,-apple-system,sans-serif;\\\\\\\");\\\\n\\\\n\\\\t// header\\\\n\\\\tvar header = document.createElement(\\\\\\\"div\\\\\\\");\\\\n\\\\theader.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"display:flex;align-items:center;justify-content:space-between;margin-bottom:6px;\\\\\\\");\\\\n\\\\tvar h = document.createElement(\\\\\\\"h3\\\\\\\");\\\\n\\\\th.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"margin:0;font-size:15px;\\\\\\\");\\\\n\\\\th.textContent = \\\\\\\"发送给 Agent · 选择目标\\\\\\\";\\\\n\\\\tvar closeX = document.createElement(\\\\\\\"button\\\\\\\");\\\\n\\\\tcloseX.type = \\\\\\\"button\\\\\\\";\\\\n\\\\tcloseX.textContent = \\\\\\\"✕\\\\\\\";\\\\n\\\\tcloseX.title = \\\\\\\"关闭\\\\\\\";\\\\n\\\\tcloseX.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"border:none;background:transparent;font-size:15px;cursor:pointer;color:#888;padding:2px 8px;border-radius:4px;\\\\\\\");\\\\n\\\\tcloseX.addEventListener(\\\\\\\"click\\\\\\\", function() { closeOverlay(overlay, escHandler); });\\\\n\\\\theader.appendChild(h);\\\\n\\\\theader.appendChild(closeX);\\\\n\\\\tbox.appendChild(header);\\\\n\\\\n\\\\t// description\\\\n\\\\tvar desc = document.createElement(\\\\\\\"p\\\\\\\");\\\\n\\\\tdesc.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"margin:0 0 8px;font-size:12px;color:#888;word-break:break-all;\\\\\\\");\\\\n\\\\tdesc.textContent = \\\\\\\"《\\\\\\\" + payload.title + \\\\\\\"》将作为消息注入所选会话\\\\\\\";\\\\n\\\\tbox.appendChild(desc);\\\\n\\\\n\\\\t// 附加说明(可选)— any extra context the user wants to attach to the\\\\n\\\\t// message, e.g. what to focus on or how to handle it.\\\\n\\\\tvar noteRow = document.createElement(\\\\\\\"div\\\\\\\");\\\\n\\\\tnoteRow.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"margin:0 0 8px;\\\\\\\");\\\\n\\\\tvar noteLbl = document.createElement(\\\\\\\"label\\\\\\\");\\\\n\\\\tnoteLbl.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"display:block;font-size:12px;color:#666;margin-bottom:4px;\\\\\\\");\\\\n\\\\tnoteLbl.textContent = \\\\\\\"附加说明(可选,随笔记一起发给 Agent)\\\\\\\";\\\\n\\\\tnoteRow.appendChild(noteLbl);\\\\n\\\\tvar noteTa = document.createElement(\\\\\\\"textarea\\\\\\\");\\\\n\\\\tnoteTa.placeholder = \\\\\\\"例如:请重点看第 3 条;我希望你按 XX 方式处理…\\\\\\\";\\\\n\\\\tnoteTa.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"width:100%;box-sizing:border-box;padding:6px 8px;border:1px solid #ccc;border-radius:6px;font-size:13px;min-height:44px;resize:vertical;font-family:inherit;\\\\\\\");\\\\n\\\\tnoteTa.addEventListener(\\\\\\\"input\\\\\\\", function() { state.note = noteTa.value; });\\\\n\\\\tnoteRow.appendChild(noteTa);\\\\n\\\\tbox.appendChild(noteRow);\\\\n\\\\n\\\\t// 工作模式(Agent 预设)selector — applies to newly created sessions; the\\\\n\\\\t// modes come from GET /agent/modes (id/name/description + deployment\\\\n\\\\t// default). When the modes endpoint is unreachable (e.g. an older host) the\\\\n\\\\t// row degrades to a hint and no mode is sent — DSH uses its default.\\\\n\\\\tvar modeRow = document.createElement(\\\\\\\"div\\\\\\\");\\\\n\\\\tmodeRow.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"margin:0 0 8px;\\\\\\\");\\\\n\\\\tif (modes && modes.length > 0) {\\\\n\\\\t\\\\tvar modeLbl = document.createElement(\\\\\\\"label\\\\\\\");\\\\n\\\\t\\\\tmodeLbl.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"display:block;font-size:12px;color:#666;margin-bottom:4px;\\\\\\\");\\\\n\\\\t\\\\tmodeLbl.textContent = \\\\\\\"工作模式(Agent 预设)— 用于新建会话\\\\\\\";\\\\n\\\\t\\\\tmodeRow.appendChild(modeLbl);\\\\n\\\\t\\\\tvar sel = document.createElement(\\\\\\\"select\\\\\\\");\\\\n\\\\t\\\\tsel.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"width:100%;box-sizing:border-box;padding:6px 8px;border:1px solid #ccc;border-radius:6px;font-size:13px;background:#fff;\\\\\\\");\\\\n\\\\t\\\\tvar optDefault = document.createElement(\\\\\\\"option\\\\\\\");\\\\n\\\\t\\\\toptDefault.value = \\\\\\\"\\\\\\\";\\\\n\\\\t\\\\toptDefault.textContent = \\\\\\\"(默认模式)\\\\\\\";\\\\n\\\\t\\\\tsel.appendChild(optDefault);\\\\n\\\\t\\\\tmodes.forEach(function(m) {\\\\n\\\\t\\\\t\\\\tvar o = document.createElement(\\\\\\\"option\\\\\\\");\\\\n\\\\t\\\\t\\\\to.value = m.id || \\\\\\\"\\\\\\\";\\\\n\\\\t\\\\t\\\\to.textContent = (m.name || m.id) + (m.isDefault ? \\\\\\\"(默认)\\\\\\\" : \\\\\\\"\\\\\\\");\\\\n\\\\t\\\\t\\\\tsel.appendChild(o);\\\\n\\\\t\\\\t});\\\\n\\\\t\\\\t// preselect the deployment default when listed\\\\n\\\\t\\\\tif (defaultId) {\\\\n\\\\t\\\\t\\\\tfor (var i = 0; i < sel.options.length; i++) {\\\\n\\\\t\\\\t\\\\t\\\\tif (sel.options[i].value === defaultId) { sel.selectedIndex = i; break; }\\\\n\\\\t\\\\t\\\\t}\\\\n\\\\t\\\\t}\\\\n\\\\t\\\\tstate.mode = sel.options[sel.selectedIndex] ? sel.options[sel.selectedIndex].value : \\\\\\\"\\\\\\\";\\\\n\\\\t\\\\tsel.addEventListener(\\\\\\\"change\\\\\\\", function() { state.mode = sel.value; });\\\\n\\\\t\\\\tmodeRow.appendChild(sel);\\\\n\\\\t} else {\\\\n\\\\t\\\\tvar modeHint = document.createElement(\\\\\\\"p\\\\\\\");\\\\n\\\\t\\\\tmodeHint.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"margin:0;font-size:12px;color:#999;\\\\\\\");\\\\n\\\\t\\\\tmodeHint.textContent = \\\\\\\"(未获取到可用工作模式,将使用 DSH 默认模式)\\\\\\\";\\\\n\\\\t\\\\tmodeRow.appendChild(modeHint);\\\\n\\\\t}\\\\n\\\\tbox.appendChild(modeRow);\\\\n\\\\n\\\\t// 权限(权限预设)selector — applies to newly created sessions only; the\\\\n\\\\t// options come from GET /agent/modes' `permissions` roster (each bundles a\\\\n\\\\t// sandbox mode + approval policy). Existing sessions keep their own\\\\n\\\\t// permission, so this only affects \\\\\\\"新建会话并发送\\\\\\\".\\\\n\\\\tvar permRow = document.createElement(\\\\\\\"div\\\\\\\");\\\\n\\\\tpermRow.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"margin:0 0 8px;\\\\\\\");\\\\n\\\\tif (permissions && Array.isArray(permissions.items) && permissions.items.length > 0) {\\\\n\\\\t\\\\tvar permLbl = document.createElement(\\\\\\\"label\\\\\\\");\\\\n\\\\t\\\\tpermLbl.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"display:block;font-size:12px;color:#666;margin-bottom:4px;\\\\\\\");\\\\n\\\\t\\\\tpermLbl.textContent = \\\\\\\"权限(权限预设)— 用于新建会话\\\\\\\";\\\\n\\\\t\\\\tpermRow.appendChild(permLbl);\\\\n\\\\t\\\\tvar psel = document.createElement(\\\\\\\"select\\\\\\\");\\\\n\\\\t\\\\tpsel.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"width:100%;box-sizing:border-box;padding:6px 8px;border:1px solid #ccc;border-radius:6px;font-size:13px;background:#fff;\\\\\\\");\\\\n\\\\t\\\\tpermissions.items.forEach(function(p) {\\\\n\\\\t\\\\t\\\\tvar o = document.createElement(\\\\\\\"option\\\\\\\");\\\\n\\\\t\\\\t\\\\to.value = p.value || \\\\\\\"\\\\\\\";\\\\n\\\\t\\\\t\\\\to.textContent = p.name || p.value;\\\\n\\\\t\\\\t\\\\tif (p.description) { o.title = p.description; }\\\\n\\\\t\\\\t\\\\tpsel.appendChild(o);\\\\n\\\\t\\\\t});\\\\n\\\\t\\\\t// preselect the deployment default when listed\\\\n\\\\t\\\\tif (permissions.defaultId) {\\\\n\\\\t\\\\t\\\\tfor (var j = 0; j < psel.options.length; j++) {\\\\n\\\\t\\\\t\\\\t\\\\tif (psel.options[j].value === permissions.defaultId) { psel.selectedIndex = j; break; }\\\\n\\\\t\\\\t\\\\t}\\\\n\\\\t\\\\t}\\\\n\\\\t\\\\tstate.permission = psel.options[psel.selectedIndex] ? psel.options[psel.selectedIndex].value : \\\\\\\"\\\\\\\";\\\\n\\\\t\\\\tpsel.addEventListener(\\\\\\\"change\\\\\\\", function() { state.permission = psel.value; });\\\\n\\\\t\\\\tpermRow.appendChild(psel);\\\\n\\\\t} else {\\\\n\\\\t\\\\tvar permHint = document.createElement(\\\\\\\"p\\\\\\\");\\\\n\\\\t\\\\tpermHint.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"margin:0;font-size:12px;color:#999;\\\\\\\");\\\\n\\\\t\\\\tpermHint.textContent = \\\\\\\"(未获取到权限预设,新建会话将使用 DSH 默认权限)\\\\\\\";\\\\n\\\\t\\\\tpermRow.appendChild(permHint);\\\\n\\\\t}\\\\n\\\\tbox.appendChild(permRow);\\\\n\\\\n\\\\t// scrollable body\\\\n\\\\tvar body = document.createElement(\\\\\\\"div\\\\\\\");\\\\n\\\\tbody.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"overflow:auto;flex:1;min-height:0;\\\\\\\");\\\\n\\\\tbox.appendChild(body);\\\\n\\\\n\\\\t// footer with cancel\\\\n\\\\tvar footer = document.createElement(\\\\\\\"div\\\\\\\");\\\\n\\\\tfooter.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"display:flex;justify-content:flex-end;gap:8px;margin-top:10px;\\\\\\\");\\\\n\\\\tvar cancel = document.createElement(\\\\\\\"button\\\\\\\");\\\\n\\\\tcancel.type = \\\\\\\"button\\\\\\\";\\\\n\\\\tcancel.textContent = \\\\\\\"取消\\\\\\\";\\\\n\\\\tcancel.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"padding:6px 14px;border:1px solid #ccc;border-radius:6px;background:#fff;cursor:pointer;font-size:13px;\\\\\\\");\\\\n\\\\tcancel.addEventListener(\\\\\\\"click\\\\\\\", function() { closeOverlay(overlay, escHandler); });\\\\n\\\\tfooter.appendChild(cancel);\\\\n\\\\tbox.appendChild(footer);\\\\n\\\\n\\\\toverlay.appendChild(box);\\\\n\\\\tdocument.body.appendChild(overlay);\\\\n\\\\n\\\\t// backdrop click + Esc close\\\\n\\\\toverlay.addEventListener(\\\\\\\"click\\\\\\\", function(e) {\\\\n\\\\t\\\\tif (e.target === overlay) { closeOverlay(overlay, escHandler); }\\\\n\\\\t});\\\\n\\\\tvar escHandler = function(e) { if (e.key === \\\\\\\"Escape\\\\\\\") { closeOverlay(overlay, escHandler); } };\\\\n\\\\tdocument.addEventListener(\\\\\\\"keydown\\\\\\\", escHandler);\\\\n\\\\n\\\\t// group sessions by workspace (cwd)\\\\n\\\\tvar groups = {};\\\\n\\\\tvar order = [];\\\\n\\\\t(items || []).forEach(function(s) {\\\\n\\\\t\\\\tvar key = (s.cwd && String(s.cwd).length > 0) ? s.cwd : \\\\\\\"__default__\\\\\\\";\\\\n\\\\t\\\\tif (!groups[key]) { groups[key] = { cwd: s.cwd, sessions: [], max: 0 }; order.push(key); }\\\\n\\\\t\\\\tgroups[key].sessions.push(s);\\\\n\\\\t\\\\tif ((s.updatedAt || 0) > groups[key].max) { groups[key].max = s.updatedAt || 0; }\\\\n\\\\t});\\\\n\\\\torder.sort(function(a, b) { return groups[b].max - groups[a].max; });\\\\n\\\\n\\\\tfunction btnStyle() {\\\\n\\\\t\\\\treturn \\\\\\\"display:block;width:100%;text-align:left;padding:8px 10px;margin:4px 0;border:1px solid #ddd;border-radius:6px;background:#f7f7f7;cursor:pointer;font-size:13px;\\\\\\\";\\\\n\\\\t}\\\\n\\\\tfunction primaryBtnStyle() {\\\\n\\\\t\\\\treturn \\\\\\\"display:block;width:100%;text-align:center;padding:7px 10px;margin:6px 0 0;border:1px solid #4a90d9;border-radius:6px;background:#4a90d9;color:#fff;cursor:pointer;font-size:13px;\\\\\\\";\\\\n\\\\t}\\\\n\\\\tfunction smallBtnStyle() {\\\\n\\\\t\\\\treturn \\\\\\\"margin:0 0 6px;padding:4px 10px;border:1px solid #ccc;border-radius:6px;background:#fff;cursor:pointer;font-size:12px;color:#555;\\\\\\\";\\\\n\\\\t}\\\\n\\\\n\\\\tfunction shortSessionLabel(s) {\\\\n\\\\t\\\\tvar id = s.sessionId || \\\\\\\"\\\\\\\";\\\\n\\\\t\\\\tvar parts = id.split(\\\\\\\"-\\\\\\\");\\\\n\\\\t\\\\tvar short = (parts.length > 1 ? parts[parts.length - 1] : id).slice(-10);\\\\n\\\\t\\\\tvar when = \\\\\\\"\\\\\\\";\\\\n\\\\t\\\\tif (s.updatedAt) {\\\\n\\\\t\\\\t\\\\tvar diff = Date.now() - s.updatedAt;\\\\n\\\\t\\\\t\\\\tif (diff < 60000) { when = \\\\\\\"刚刚\\\\\\\"; }\\\\n\\\\t\\\\t\\\\telse if (diff < 3600000) { when = Math.floor(diff / 60000) + \\\\\\\" 分钟前\\\\\\\"; }\\\\n\\\\t\\\\t\\\\telse if (diff < 86400000) { when = Math.floor(diff / 3600000) + \\\\\\\" 小时前\\\\\\\"; }\\\\n\\\\t\\\\t\\\\telse {\\\\n\\\\t\\\\t\\\\t\\\\tvar d = new Date(s.updatedAt);\\\\n\\\\t\\\\t\\\\t\\\\twhen = (d.getMonth() + 1) + \\\\\\\"-\\\\\\\" + d.getDate() + \\\\\\\" \\\\\\\" + (\\\\\\\"0\\\\\\\" + d.getHours()).slice(-2) + \\\\\\\":\\\\\\\" + (\\\\\\\"0\\\\\\\" + d.getMinutes()).slice(-2);\\\\n\\\\t\\\\t\\\\t}\\\\n\\\\t\\\\t}\\\\n\\\\t\\\\treturn \\\\\\\"#\\\\\\\" + short + (when ? \\\\\\\" · \\\\\\\" + when : \\\\\\\"\\\\\\\") + (s.running ? \\\\\\\" · ●运行中\\\\\\\" : \\\\\\\"\\\\\\\") + (s.blank ? \\\\\\\" · (空)\\\\\\\" : \\\\\\\"\\\\\\\");\\\\n\\\\t}\\\\n\\\\n\\\\tfunction modeName(id) {\\\\n\\\\t\\\\tif (!id) { return \\\\\\\"\\\\\\\"; }\\\\n\\\\t\\\\tfor (var i = 0; i < (modes || []).length; i++) {\\\\n\\\\t\\\\t\\\\tif (modes[i].id === id) { return modes[i].name || modes[i].id; }\\\\n\\\\t\\\\t}\\\\n\\\\t\\\\treturn id;\\\\n\\\\t}\\\\n\\\\n\\\\tfunction render() {\\\\n\\\\t\\\\tbody.innerHTML = \\\\\\\"\\\\\\\";\\\\n\\\\t\\\\tif (state.workspace === null) { renderWorkspaces(); } else { renderSessions(state.workspace); }\\\\n\\\\t}\\\\n\\\\n\\\\tfunction renderWorkspaces() {\\\\n\\\\t\\\\tif (order.length === 0) {\\\\n\\\\t\\\\t\\\\tvar none = document.createElement(\\\\\\\"p\\\\\\\");\\\\n\\\\t\\\\t\\\\tnone.textContent = \\\\\\\"还没有任何会话——在下方新建一个吧\\\\\\\";\\\\n\\\\t\\\\t\\\\tnone.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"color:#999;font-size:13px;margin:0 0 8px;\\\\\\\");\\\\n\\\\t\\\\t\\\\tbody.appendChild(none);\\\\n\\\\t\\\\t} else {\\\\n\\\\t\\\\t\\\\torder.forEach(function(key) {\\\\n\\\\t\\\\t\\\\t\\\\tvar g = groups[key];\\\\n\\\\t\\\\t\\\\t\\\\tvar label = g.cwd || \\\\\\\"(默认工作区)\\\\\\\";\\\\n\\\\t\\\\t\\\\t\\\\tvar b = document.createElement(\\\\\\\"button\\\\\\\");\\\\n\\\\t\\\\t\\\\t\\\\tb.type = \\\\\\\"button\\\\\\\";\\\\n\\\\t\\\\t\\\\t\\\\tb.setAttribute(\\\\\\\"style\\\\\\\", btnStyle());\\\\n\\\\t\\\\t\\\\t\\\\tb.textContent = \\\\\\\"📁 \\\\\\\" + label + \\\\\\\" · \\\\\\\" + g.sessions.length + \\\\\\\" 个会话\\\\\\\";\\\\n\\\\t\\\\t\\\\t\\\\tb.title = g.cwd || \\\\\\\"默认工作区\\\\\\\";\\\\n\\\\t\\\\t\\\\t\\\\tb.addEventListener(\\\\\\\"click\\\\\\\", function() { state.workspace = key; render(); });\\\\n\\\\t\\\\t\\\\t\\\\tbody.appendChild(b);\\\\n\\\\t\\\\t\\\\t});\\\\n\\\\t\\\\t}\\\\n\\\\t\\\\t// new workspace / session\\\\n\\\\t\\\\tvar newRow = document.createElement(\\\\\\\"div\\\\\\\");\\\\n\\\\t\\\\tnewRow.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"margin-top:10px;border-top:1px solid #eee;padding-top:8px;\\\\\\\");\\\\n\\\\t\\\\tvar lbl = document.createElement(\\\\\\\"p\\\\\\\");\\\\n\\\\t\\\\tlbl.textContent = \\\\\\\"新建工作区 / 会话\\\\\\\";\\\\n\\\\t\\\\tlbl.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"margin:0 0 6px;font-size:12px;color:#666;\\\\\\\");\\\\n\\\\t\\\\tnewRow.appendChild(lbl);\\\\n\\\\t\\\\tvar input = document.createElement(\\\\\\\"input\\\\\\\");\\\\n\\\\t\\\\tinput.type = \\\\\\\"text\\\\\\\";\\\\n\\\\t\\\\tinput.placeholder = \\\\\\\"输入工作区路径,留空为默认工作区\\\\\\\";\\\\n\\\\t\\\\tinput.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"width:100%;box-sizing:border-box;padding:6px 8px;border:1px solid #ccc;border-radius:6px;font-size:13px;\\\\\\\");\\\\n\\\\t\\\\tnewRow.appendChild(input);\\\\n\\\\t\\\\tvar go = document.createElement(\\\\\\\"button\\\\\\\");\\\\n\\\\t\\\\tgo.type = \\\\\\\"button\\\\\\\";\\\\n\\\\t\\\\tgo.textContent = \\\\\\\"创建并发送\\\\\\\";\\\\n\\\\t\\\\tgo.setAttribute(\\\\\\\"style\\\\\\\", primaryBtnStyle());\\\\n\\\\t\\\\tgo.addEventListener(\\\\\\\"click\\\\\\\", function() { createAndSend(payload, input.value.trim()); });\\\\n\\\\t\\\\tnewRow.appendChild(go);\\\\n\\\\t\\\\tinput.addEventListener(\\\\\\\"keydown\\\\\\\", function(e) { if (e.key === \\\\\\\"Enter\\\\\\\") { createAndSend(payload, input.value.trim()); } });\\\\n\\\\t\\\\tbody.appendChild(newRow);\\\\n\\\\t}\\\\n\\\\n\\\\tfunction renderSessions(key) {\\\\n\\\\t\\\\tvar g = groups[key];\\\\n\\\\t\\\\tvar back = document.createElement(\\\\\\\"button\\\\\\\");\\\\n\\\\t\\\\tback.type = \\\\\\\"button\\\\\\\";\\\\n\\\\t\\\\tback.textContent = \\\\\\\"← 返回工作区列表\\\\\\\";\\\\n\\\\t\\\\tback.setAttribute(\\\\\\\"style\\\\\\\", smallBtnStyle());\\\\n\\\\t\\\\tback.addEventListener(\\\\\\\"click\\\\\\\", function() { state.workspace = null; render(); });\\\\n\\\\t\\\\tbody.appendChild(back);\\\\n\\\\n\\\\t\\\\tvar wsName = document.createElement(\\\\\\\"p\\\\\\\");\\\\n\\\\t\\\\twsName.textContent = g.cwd ? \\\\\\\"📁 \\\\\\\" + g.cwd : \\\\\\\"(默认工作区)\\\\\\\";\\\\n\\\\t\\\\twsName.setAttribute(\\\\\\\"style\\\\\\\", \\\\\\\"margin:0 0 6px;font-size:13px;font-weight:600;word-break:break-all;\\\\\\\");\\\\n\\\\t\\\\tbody.appendChild(wsName);\\\\n\\\\n\\\\t\\\\t(g.sessions || []).forEach(function(s) {\\\\n\\\\t\\\\t\\\\tvar b = document.createElement(\\\\\\\"button\\\\\\\");\\\\n\\\\t\\\\t\\\\tb.type = \\\\\\\"button\\\\\\\";\\\\n\\\\t\\\\t\\\\tb.setAttribute(\\\\\\\"style\\\\\\\", btnStyle());\\\\n\\\\t\\\\t\\\\tvar label = shortSessionLabel(s);\\\\n\\\\t\\\\t\\\\tif (s.agentPreset) { label += \\\\\\\" · 🧭 \\\\\\\" + modeName(s.agentPreset); }\\\\n\\\\t\\\\t\\\\tb.textContent = label;\\\\n\\\\t\\\\t\\\\tb.title = \\\\\\\"会话 \\\\\\\" + s.sessionId + (s.agentPreset ? \\\\\\\" · 工作模式 \\\\\\\" + modeName(s.agentPreset) : \\\\\\\"\\\\\\\");\\\\n\\\\t\\\\t\\\\tb.addEventListener(\\\\\\\"click\\\\\\\", function() { closeOverlay(overlay, escHandler); doSend(payload, s.sessionId, state.note); });\\\\n\\\\t\\\\t\\\\tbody.appendChild(b);\\\\n\\\\t\\\\t});\\\\n\\\\n\\\\t\\\\tvar newBtn = document.createElement(\\\\\\\"button\\\\\\\");\\\\n\\\\t\\\\tnewBtn.type = \\\\\\\"button\\\\\\\";\\\\n\\\\t\\\\tnewBtn.textContent = \\\\\\\"➕ 在此工作区新建会话并发送\\\\\\\";\\\\n\\\\t\\\\tnewBtn.setAttribute(\\\\\\\"style\\\\\\\", primaryBtnStyle());\\\\n\\\\t\\\\tnewBtn.addEventListener(\\\\\\\"click\\\\\\\", function() { createAndSend(payload, g.cwd); });\\\\n\\\\t\\\\tbody.appendChild(newBtn);\\\\n\\\\t}\\\\n\\\\n\\\\tfunction createAndSend(payload2, cwd) {\\\\n\\\\t\\\\tvar config = readConfig();\\\\n\\\\t\\\\tvar headers = { \\\\\\\"Content-Type\\\\\\\": \\\\\\\"application/json\\\\\\\" };\\\\n\\\\t\\\\tif (config.token.length > 0) { headers[\\\\\\\"x-send-to-agent-token\\\\\\\"] = config.token; }\\\\n\\\\t\\\\tvar body = { cwd: cwd || \\\\\\\"\\\\\\\" };\\\\n\\\\t\\\\tif (state.mode && String(state.mode).length > 0) { body.mode = state.mode; }\\\\n\\\\t\\\\tif (state.permission && String(state.permission).length > 0) { body.permission = state.permission; }\\\\n\\\\t\\\\t$tw.utils.httpRequest({\\\\n\\\\t\\\\t\\\\turl: baseEndpoint() + \\\\\\\"/agent/create\\\\\\\",\\\\n\\\\t\\\\t\\\\ttype: \\\\\\\"POST\\\\\\\",\\\\n\\\\t\\\\t\\\\theaders: headers,\\\\n\\\\t\\\\t\\\\tdata: JSON.stringify(body),\\\\n\\\\t\\\\t\\\\tcallback: function(err, data) {\\\\n\\\\t\\\\t\\\\t\\\\tvar parsed = null;\\\\n\\\\t\\\\t\\\\t\\\\ttry { parsed = JSON.parse(data || \\\\\\\"\\\\\\\"); } catch (e) {}\\\\n\\\\t\\\\t\\\\t\\\\tif (err) { notify(\\\\\\\"新建会话失败:\\\\\\\" + err); return; }\\\\n\\\\t\\\\t\\\\t\\\\tif (parsed && parsed.ok && parsed.sessionId) {\\\\n\\\\t\\\\t\\\\t\\\\t\\\\tcloseOverlay(overlay, escHandler);\\\\n\\\\t\\\\t\\\\t\\\\t\\\\tvar modeNote = parsed.mode ? \\\\\\\"(模式 \\\\\\\" + modeName(parsed.mode) + \\\\\\\")\\\\\\\" : \\\\\\\"\\\\\\\";\\\\n\\\\t\\\\t\\\\t\\\\t\\\\tvar permNote = parsed.permissionApplied ? \\\\\\\"(权限 \\\\\\\" + (parsed.permission || \\\\\\\"\\\\\\\") + \\\\\\\")\\\\\\\" : \\\\\\\"\\\\\\\";\\\\n\\\\t\\\\t\\\\t\\\\t\\\\tnotify(\\\\\\\"已创建会话并发送 ✓ \\\\\\\" + parsed.sessionId.slice(0, 8) + modeNote + permNote);\\\\n\\\\t\\\\t\\\\t\\\\t\\\\tdoSend(payload2, parsed.sessionId, state.note);\\\\n\\\\t\\\\t\\\\t\\\\t} else {\\\\n\\\\t\\\\t\\\\t\\\\t\\\\tnotify(\\\\\\\"新建会话失败:\\\\\\\" + ((parsed && parsed.error) || data || \\\\\\\"未知错误\\\\\\\"));\\\\n\\\\t\\\\t\\\\t\\\\t}\\\\n\\\\t\\\\t\\\\t}\\\\n\\\\t\\\\t});\\\\n\\\\t}\\\\n\\\\n\\\\trender();\\\\n}\\\\n\\\\nfunction handleSend(title) {\\\\n\\\\tif (!title) { notify(\\\\\\\"无法确定当前笔记标题\\\\\\\"); return; }\\\\n\\\\tvar tiddler = $tw.wiki.getTiddler(title);\\\\n\\\\tif (!tiddler) { notify(\\\\\\\"找不到笔记:\\\\\\\" + title); return; }\\\\n\\\\tvar config = readConfig();\\\\n\\\\tif (!config.enabled) { notify(\\\\\\\"「发送给 Agent」特性未启用(可在 TW 配置中打开)\\\\\\\"); return; }\\\\n\\\\tvar payload = {\\\\n\\\\t\\\\ttitle: title,\\\\n\\\\t\\\\ttext: tiddler.fields.text || \\\\\\\"\\\\\\\",\\\\n\\\\t\\\\ttags: tiddler.fields.tags || [],\\\\n\\\\t\\\\ttype: tiddler.fields.type || \\\\\\\"\\\\\\\"\\\\n\\\\t};\\\\n\\\\tvar headers = {};\\\\n\\\\tif (config.token.length > 0) { headers[\\\\\\\"x-send-to-agent-token\\\\\\\"] = config.token; }\\\\n\\\\t$tw.utils.httpRequest({\\\\n\\\\t\\\\turl: baseEndpoint() + \\\\\\\"/agent/sessions\\\\\\\",\\\\n\\\\t\\\\ttype: \\\\\\\"GET\\\\\\\",\\\\n\\\\t\\\\theaders: headers,\\\\n\\\\t\\\\tcallback: function(err, data) {\\\\n\\\\t\\\\t\\\\tvar parsed = null;\\\\n\\\\t\\\\t\\\\ttry { parsed = JSON.parse(data || \\\\\\\"\\\\\\\"); } catch (e) {}\\\\n\\\\t\\\\t\\\\tif (err) { notify(\\\\\\\"获取会话列表失败:\\\\\\\" + err); return; }\\\\n\\\\t\\\\t\\\\tif (!parsed || !parsed.ok || !parsed.items) { notify(\\\\\\\"获取会话列表失败:\\\\\\\" + ((parsed && parsed.error) || \\\\\\\"未知错误\\\\\\\")); return; }\\\\n\\\\t\\\\t\\\\t// fetch the available 工作模式 (Agent presets) alongside; degrade to\\\\n\\\\t\\\\t\\\\t// an empty roster (default mode) when the endpoint is missing.\\\\n\\\\t\\\\t\\\\t$tw.utils.httpRequest({\\\\n\\\\t\\\\t\\\\t\\\\turl: baseEndpoint() + \\\\\\\"/agent/modes\\\\\\\",\\\\n\\\\t\\\\t\\\\t\\\\ttype: \\\\\\\"GET\\\\\\\",\\\\n\\\\t\\\\t\\\\t\\\\theaders: headers,\\\\n\\\\t\\\\t\\\\t\\\\tcallback: function(err2, data2) {\\\\n\\\\t\\\\t\\\\t\\\\t\\\\tvar modes = [];\\\\n\\\\t\\\\t\\\\t\\\\t\\\\tvar defaultId = \\\\\\\"\\\\\\\";\\\\n\\\\t\\\\t\\\\t\\\\t\\\\tvar permissions = null;\\\\n\\\\t\\\\t\\\\t\\\\t\\\\tvar parsed2 = null;\\\\n\\\\t\\\\t\\\\t\\\\t\\\\ttry { parsed2 = JSON.parse(data2 || \\\\\\\"\\\\\\\"); } catch (e) {}\\\\n\\\\t\\\\t\\\\t\\\\t\\\\tif (!err2 && parsed2 && parsed2.ok) {\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\tif (Array.isArray(parsed2.items)) {\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\tmodes = parsed2.items;\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\tdefaultId = parsed2.defaultId || \\\\\\\"\\\\\\\";\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t}\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\tif (parsed2.permissions && Array.isArray(parsed2.permissions.items)) {\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\tpermissions = parsed2.permissions;\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t}\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t}\\\\n\\\\t\\\\t\\\\t\\\\t\\\\tshowPicker(payload, parsed.items, modes, defaultId, permissions);\\\\n\\\\t\\\\t\\\\t\\\\t}\\\\n\\\\t\\\\t\\\\t});\\\\n\\\\t\\\\t}\\\\n\\\\t});\\\\n}\\\\n\\\\nexports.startup = function() {\\\\n\\\\tconsole.log(\\\\\\\"[dsh-send-to-agent] startup ran\\\\\\\");\\\\n\\\\tif (!$tw.rootWidget || typeof $tw.rootWidget.addEventListener !== \\\\\\\"function\\\\\\\") { return; }\\\\n\\\\t$tw.rootWidget.addEventListener(\\\\\\\"dsh-send-to-agent\\\\\\\", function(event) {\\\\n\\\\t\\\\thandleSend(event.param);\\\\n\\\\t});\\\\n};\\\\n\\\\n})();\\\\n\\\"\\n },\\n \\\"$:/plugins/dsh/send-to-agent/ui/icon\\\": {\\n \\\"title\\\": \\\"$:/plugins/dsh/send-to-agent/ui/icon\\\",\\n \\\"tags\\\": [\\n \\\"$:/tags/Image\\\"\\n ],\\n \\\"text\\\": \\\"\\\\\\\\parameters (size:\\\\\\\"22pt\\\\\\\")\\\\n<svg width=<<size>> height=<<size>> class=\\\\\\\"tc-image-send-to-agent tc-image-button\\\\\\\" viewBox=\\\\\\\"0 0 24 24\\\\\\\"><path d=\\\\\\\"M3.478 2.404a.75.75 0 0 0-.926.941l2.432 7.905H13.5a.75.75 0 0 1 0 1.5H4.984l-2.432 7.905a.75.75 0 0 0 .926.94 60.519 60.519 0 0 0 18.445-8.986.75.75 0 0 0 0-1.218A60.517 60.517 0 0 0 3.478 2.404Z\\\\\\\"/></svg>\\\\n\\\"\\n },\\n \\\"$:/plugins/dsh/send-to-agent/ui/ViewToolbar/SendToAgent\\\": {\\n \\\"title\\\": \\\"$:/plugins/dsh/send-to-agent/ui/ViewToolbar/SendToAgent\\\",\\n \\\"type\\\": \\\"text/vnd.tiddlywiki\\\",\\n \\\"tags\\\": [\\n \\\"$:/tags/ViewToolbar\\\"\\n ],\\n \\\"icon\\\": \\\"$:/plugins/dsh/send-to-agent/ui/icon\\\",\\n \\\"caption\\\": \\\"发送给 Agent\\\",\\n \\\"description\\\": \\\"把当前笔记一键发送给 DSH Agent(TiddlyWiki → DSH 会话注入)\\\",\\n \\\"text\\\": \\\"\\\\\\\\whitespace trim\\\\n<$button message=\\\\\\\"dsh-send-to-agent\\\\\\\"\\\\n\\\\tparam=<<currentTiddler>>\\\\n\\\\ttooltip=\\\\\\\"把当前笔记一键发送给 DSH Agent\\\\\\\"\\\\n\\\\taria-label=\\\\\\\"把当前笔记一键发送给 DSH Agent\\\\\\\"\\\\n\\\\tclass=<<tv-config-toolbar-class>>\\\\n>\\\\n\\\\t<%if [<tv-config-toolbar-icons>match[yes]] %>\\\\n\\\\t\\\\t{{$:/plugins/dsh/send-to-agent/ui/icon}}\\\\n\\\\t<%endif%>\\\\n\\\\t<%if [<tv-config-toolbar-text>match[yes]] %>\\\\n\\\\t\\\\t<span class=\\\\\\\"tc-btn-text\\\\\\\">\\\\n\\\\t\\\\t<$text text=\\\\\\\"发送给 Agent\\\\\\\"/>\\\\n\\\\t</span>\\\\n\\\\t<%endif%>\\\\n</$button>\\\\n\\\"\\n },\\n \\\"$:/core/ui/ControlPanel/Toolbars/ItemTemplate\\\": {\\n \\\"title\\\": \\\"$:/core/ui/ControlPanel/Toolbars/ItemTemplate\\\",\\n \\\"type\\\": \\\"text/vnd.tiddlywiki\\\",\\n \\\"text\\\": \\\"\\\\\\\\define config-title()\\\\n$(config-base)$$(currentTiddler)$\\\\n\\\\\\\\end\\\\n\\\\\\\\whitespace trim\\\\n\\\\n<$draggable tiddler=<<currentTiddler>>>\\\\n<$checkbox tiddler=<<config-title>> field=\\\\\\\"text\\\\\\\" checked=\\\\\\\"show\\\\\\\" unchecked=\\\\\\\"hide\\\\\\\" default=\\\\\\\"show\\\\\\\"/>\\\\n&#32;\\\\n<span class=\\\\\\\"tc-icon-wrapper\\\\\\\"><$transclude tiddler={{!!icon}}/></span>\\\\n&#32;\\\\n<$transclude field=\\\\\\\"caption\\\\\\\"/>\\\\n&#32;--&#32;\\\\n<i class=\\\\\\\"tc-muted\\\\\\\"><$transclude field=\\\\\\\"description\\\\\\\"/></i>\\\\n</$draggable>\\\\n\\\"\\n }\\n }\\n}\"\n\n/**\n * Seed the \"发送给 Agent\" TW button exactly once per wiki (mirrors the doc-note\n * one-shot policy). The marker records the offer; afterwards the bundle is\n * user-owned — deleting it and restarting dsh web does NOT recreate it, and\n * edits are never overwritten. With `opts.force` the bundle is (re)written even\n * when it already exists and the marker is (re)written — the settings page uses\n * this for \"重新初始化\". Returns whether a bundle was written this call.\n * Throws when a read fails (the seed registry reports it as `ok:false`).\n */\nexport async function seedSendToAgent(client: TiddlyWebClient, opts?: { force?: boolean }): Promise<boolean> {\n const force = opts?.force === true\n if (!force) {\n const marker = await readSeedTiddler(client, SEND_TO_AGENT_MARKER_TITLE)\n if (marker !== undefined) return false\n }\n const existing = await readSeedTiddler(client, SEND_TO_AGENT_PLUGIN_TITLE)\n let wrote = false\n if (force || existing === undefined) {\n await client.put({\n title: SEND_TO_AGENT_PLUGIN_TITLE,\n text: SEND_TO_AGENT_BUNDLE_TEXT,\n type: 'application/json',\n tags: [],\n // TW only REGISTERS a wiki tiddler as a plugin (boot.js\n // registerPluginTiddlers) when the OUTER tiddler carries a\n // `plugin-type` field — without it the bundle is never unpacked and its\n // startup module never runs in the embedded TW. Mirrors what the live\n // wiki's send-to-agent bundle tiddler carries.\n 'plugin-type': 'plugin',\n name: 'Send to Agent',\n author: 'dsh-tiddlywiki',\n version: '0.3.5',\n description: '把当前笔记一键发送给 DSH Agent(TiddlyWiki → DSH 会话注入)',\n })\n wrote = true\n }\n // Record the offer regardless, so an existing bundle (upgrade from a\n // pre-seed wiki) also becomes user-owned from here on.\n await writeSeedMarker(client, SEND_TO_AGENT_MARKER_TITLE)\n return wrote\n}\n","/**\n * Generated from the wiki home tiddlers (do not hand-edit the constants).\n * Source: 🏠 主页.tid, 所有标签.tid, 标签笔记.tid\n *\n * Sanitized with --strip-private: the seed home is the GENERIC home (without\n * the wiki owner's personal 主题页 tabs / private entry buttons), plus the\n *「📚 插件文档」tabs strip (tag dsh-docs) so seeded docs are one click away.\n *\n * The \"首页\" tiddlers that the plugin's system prompt promises (待办四象限 +\n * 所有标签统计 + 标签笔记): seeded into fresh wikis by seedHomeIndex, so new\n * users get the same home page instead of an empty wiki. 🏠 主页 is the\n * default home (the seed also ensures $:/DefaultTiddlers → [[🏠 主页]]).\n *\n * @module dsh-tiddlywiki/host/seed-home\n */\nimport type { TiddlyWebClient } from './tw-api.ts'\nimport { readSeedTiddler, writeSeedMarker } from './seed-util.ts'\n\n/** One-time marker: presence means \"the home was offered once — hands off\". */\nexport const HOME_INDEX_MARKER_TITLE = '$:/plugins/dsh-tiddlywiki/seed-home-index'\n\n/** The $:/DefaultTiddlers body so 🏠 主页 opens by default in fresh wikis. */\nexport const HOME_DEFAULT_TIDDLERS = \"[[🏠 主页]]\"\n\nexport interface HomeIndexItem {\n title: string\n tags: string[]\n type: string\n text: string\n}\n\n/** The home tiddlers, exactly as seeded (user-owned afterwards). */\nexport const HOME_INDEX_ITEMS: HomeIndexItem[] = [{\"title\":\"🏠 主页\",\"tags\":[\"索引\",\"TableOfContents\"],\"type\":\"text/vnd.tiddlywiki\",\"text\":\"\\\\whitespace trim\\n\\n\\\\define quadrant-board()\\n<div style=\\\"display:grid; grid-template-columns:1fr 1fr; gap:10px; margin-top:8px;\\\">\\n<$droppable class=\\\"tc-quadrant tc-quadrant-q1\\\" effect=\\\"move\\\"\\nstyle.border=\\\"1px solid rgba(128,128,128,0.22)\\\"\\nstyle.border-radius=\\\"10px\\\"\\nstyle.padding=\\\"8px 12px\\\"\\nstyle.background=\\\"linear-gradient(135deg,#e74c3c1a,transparent)\\\"\\nactions=\\\"\\\"\\\"<$action-setfield $tiddler=<<actionTiddler>> $field=\\\"q\\\" $value=\\\"q1\\\"/>\\\"\\\"\\\">\\n<div style=\\\"font-weight:700; margin-bottom:6px;\\\">🔴 重要 · 紧急 <span style=\\\"color:#888; font-weight:400; font-size:0.82em;\\\">{{{[tag[todo]!tag[done]field:q[q1]count[]]}}} 件</span></div>\\n<$list filter=\\\"[tag[todo]!tag[done]field:q[q1]sort[modified]]\\\">\\n<div style=\\\"display:flex; gap:6px; align-items:baseline; padding:3px 0; border-bottom:1px dashed rgba(128,128,128,0.15);\\\">\\n<$checkbox tiddler=<<currentTiddler>> tag=\\\"done\\\"/>\\n<$link to=<<currentTiddler>> style=\\\"flex:1; cursor:grab;\\\"><$view field=\\\"title\\\"/></$link>\\n<$list filter=\\\"[<currentTiddler>get[due]compare:date:lt<today>]\\\"><span style=\\\"color:#c0392b; font-size:0.78em;\\\">逾期</span></$list>\\n<$list filter=\\\"[<currentTiddler>get[due]!is[blank]]\\\"><span style=\\\"color:#888; font-size:0.8em;\\\"><$view field=\\\"due\\\" format=\\\"date\\\" template=\\\"MM-DD\\\"/></span></$list>\\n</div>\\n</$list>\\n<$list filter=\\\"[tag[todo]!tag[done]field:q[q1]count[]match[0]]\\\"><div style=\\\"color:#aaa; font-size:0.85em;\\\">(暂无)</div></$list>\\n</$droppable>\\n<$droppable class=\\\"tc-quadrant tc-quadrant-q2\\\" effect=\\\"move\\\"\\nstyle.border=\\\"1px solid rgba(128,128,128,0.22)\\\"\\nstyle.border-radius=\\\"10px\\\"\\nstyle.padding=\\\"8px 12px\\\"\\nstyle.background=\\\"linear-gradient(135deg,#3498db1a,transparent)\\\"\\nactions=\\\"\\\"\\\"<$action-setfield $tiddler=<<actionTiddler>> $field=\\\"q\\\" $value=\\\"q2\\\"/>\\\"\\\"\\\">\\n<div style=\\\"font-weight:700; margin-bottom:6px;\\\">🔵 重要 · 不紧急 <span style=\\\"color:#888; font-weight:400; font-size:0.82em;\\\">{{{[tag[todo]!tag[done]field:q[q2]count[]]}}} 件</span></div>\\n<$list filter=\\\"[tag[todo]!tag[done]field:q[q2]sort[modified]]\\\">\\n<div style=\\\"display:flex; gap:6px; align-items:baseline; padding:3px 0; border-bottom:1px dashed rgba(128,128,128,0.15);\\\">\\n<$checkbox tiddler=<<currentTiddler>> tag=\\\"done\\\"/>\\n<$link to=<<currentTiddler>> style=\\\"flex:1; cursor:grab;\\\"><$view field=\\\"title\\\"/></$link>\\n<$list filter=\\\"[<currentTiddler>get[due]compare:date:lt<today>]\\\"><span style=\\\"color:#c0392b; font-size:0.78em;\\\">逾期</span></$list>\\n<$list filter=\\\"[<currentTiddler>get[due]!is[blank]]\\\"><span style=\\\"color:#888; font-size:0.8em;\\\"><$view field=\\\"due\\\" format=\\\"date\\\" template=\\\"MM-DD\\\"/></span></$list>\\n</div>\\n</$list>\\n<$list filter=\\\"[tag[todo]!tag[done]field:q[q2]count[]match[0]]\\\"><div style=\\\"color:#aaa; font-size:0.85em;\\\">(暂无)</div></$list>\\n</$droppable>\\n<$droppable class=\\\"tc-quadrant tc-quadrant-q3\\\" effect=\\\"move\\\"\\nstyle.border=\\\"1px solid rgba(128,128,128,0.22)\\\"\\nstyle.border-radius=\\\"10px\\\"\\nstyle.padding=\\\"8px 12px\\\"\\nstyle.background=\\\"linear-gradient(135deg,#f39c121a,transparent)\\\"\\nactions=\\\"\\\"\\\"<$action-setfield $tiddler=<<actionTiddler>> $field=\\\"q\\\" $value=\\\"q3\\\"/>\\\"\\\"\\\">\\n<div style=\\\"font-weight:700; margin-bottom:6px;\\\">🟠 紧急 · 不重要 <span style=\\\"color:#888; font-weight:400; font-size:0.82em;\\\">{{{[tag[todo]!tag[done]field:q[q3]count[]]}}} 件</span></div>\\n<$list filter=\\\"[tag[todo]!tag[done]field:q[q3]sort[modified]]\\\">\\n<div style=\\\"display:flex; gap:6px; align-items:baseline; padding:3px 0; border-bottom:1px dashed rgba(128,128,128,0.15);\\\">\\n<$checkbox tiddler=<<currentTiddler>> tag=\\\"done\\\"/>\\n<$link to=<<currentTiddler>> style=\\\"flex:1; cursor:grab;\\\"><$view field=\\\"title\\\"/></$link>\\n<$list filter=\\\"[<currentTiddler>get[due]compare:date:lt<today>]\\\"><span style=\\\"color:#c0392b; font-size:0.78em;\\\">逾期</span></$list>\\n<$list filter=\\\"[<currentTiddler>get[due]!is[blank]]\\\"><span style=\\\"color:#888; font-size:0.8em;\\\"><$view field=\\\"due\\\" format=\\\"date\\\" template=\\\"MM-DD\\\"/></span></$list>\\n</div>\\n</$list>\\n<$list filter=\\\"[tag[todo]!tag[done]field:q[q3]count[]match[0]]\\\"><div style=\\\"color:#aaa; font-size:0.85em;\\\">(暂无)</div></$list>\\n</$droppable>\\n<$droppable class=\\\"tc-quadrant tc-quadrant-q4\\\" effect=\\\"move\\\"\\nstyle.border=\\\"1px solid rgba(128,128,128,0.22)\\\"\\nstyle.border-radius=\\\"10px\\\"\\nstyle.padding=\\\"8px 12px\\\"\\nstyle.background=\\\"linear-gradient(135deg,#95a5a61a,transparent)\\\"\\nactions=\\\"\\\"\\\"<$action-setfield $tiddler=<<actionTiddler>> $field=\\\"q\\\" $value=\\\"q4\\\"/>\\\"\\\"\\\">\\n<div style=\\\"font-weight:700; margin-bottom:6px;\\\">⚪ 不重要 · 不紧急 <span style=\\\"color:#888; font-weight:400; font-size:0.82em;\\\">{{{[tag[todo]!tag[done]field:q[q4]count[]]}}} 件</span></div>\\n<$list filter=\\\"[tag[todo]!tag[done]field:q[q4]sort[modified]]\\\">\\n<div style=\\\"display:flex; gap:6px; align-items:baseline; padding:3px 0; border-bottom:1px dashed rgba(128,128,128,0.15);\\\">\\n<$checkbox tiddler=<<currentTiddler>> tag=\\\"done\\\"/>\\n<$link to=<<currentTiddler>> style=\\\"flex:1; cursor:grab;\\\"><$view field=\\\"title\\\"/></$link>\\n<$list filter=\\\"[<currentTiddler>get[due]compare:date:lt<today>]\\\"><span style=\\\"color:#c0392b; font-size:0.78em;\\\">逾期</span></$list>\\n<$list filter=\\\"[<currentTiddler>get[due]!is[blank]]\\\"><span style=\\\"color:#888; font-size:0.8em;\\\"><$view field=\\\"due\\\" format=\\\"date\\\" template=\\\"MM-DD\\\"/></span></$list>\\n</div>\\n</$list>\\n<$list filter=\\\"[tag[todo]!tag[done]field:q[q4]count[]match[0]]\\\"><div style=\\\"color:#aaa; font-size:0.85em;\\\">(暂无)</div></$list>\\n</$droppable>\\n</div>\\n\\\\end\\n\\n<$set name=\\\"today\\\" value=<<now \\\"YYYY0MM0DD\\\">>>\\n\\n!! 🏠 主页\\n\\n<div class=\\\"tc-message-box\\\">待办看板与知识库入口。</div>\\n\\n<div style=\\\"display:flex; gap:8px; flex-wrap:wrap; margin:8px 0;\\\">\\n<$button class=\\\"tc-btn-invisible tc-tiddlylink\\\" style=\\\"padding:6px 16px; border:1px solid rgba(128,128,128,0.35); border-radius:8px; font-weight:600;\\\">\\n<$action-navigate $to=\\\"所有标签\\\"/>\\n🏷 所有标签\\n</$button>\\n<$button class=\\\"tc-btn-invisible tc-tiddlylink\\\" style=\\\"padding:6px 16px; border:1px solid rgba(128,128,128,0.35); border-radius:8px; font-weight:600;\\\">\\n<$action-navigate $to=\\\"所有文章\\\"/>\\n📚 所有文章\\n</$button>\\n</div>\\n\\n!! 📚 插件文档\\n\\n<div class=\\\"tc-message-box\\\">这里收纳插件初始化时 seed 进来的说明 / 教程 / 模板。给新文档打上 <code>dsh-docs</code> 标签即自动出现在本栏;不需要可自由删除(删除后不会自动恢复)。</div>\\n\\n<<tabs \\\"[tag[dsh-docs]!is[system]]\\\" \\\"dsh-tiddlywiki 插件说明\\\">>\\n\\n!! ✍️ 快速记笔记(完整编辑器)\\n\\n<div class=\\\"tc-message-box\\\">直接在首页写笔记:填标题、用工具栏写正文(支持 Markdown:**加粗**、*斜体*、# 标题、- 列表、> 引用 等)、加标签,点「💾 保存」即创建新笔记(默认 Markdown,标题留空用当前时间)。勾选「同时加入待办」并选象限,可一并进入下方四象限看板。</div>\\n\\n<div style=\\\"border:1px solid rgba(128,128,128,0.22); border-radius:10px; padding:10px 12px; margin:10px 0;\\\">\\n\\n<div style=\\\"display:flex; gap:6px; align-items:center; margin-bottom:6px;\\\">\\n<$edit-text tiddler=\\\"$:/state/home-note/title\\\" tag=\\\"input\\\" placeholder=\\\"标题(留空用当前时间)\\\" style=\\\"flex:1; min-width:200px; padding:6px 10px; border-radius:6px; border:1px solid rgba(128,128,128,0.3);\\\"/>\\n</div>\\n\\n<$edit-text tiddler=\\\"$:/state/home-note/text\\\" tag=\\\"textarea\\\" class=\\\"tc-edit-texteditor\\\" autoHeight=\\\"yes\\\" minHeight=\\\"150px\\\" placeholder=\\\"正文:支持 **加粗**、*斜体*、# 标题、- 列表、> 引用 等 Markdown 语法\\\" style=\\\"width:100%; box-sizing:border-box; padding:6px 10px; border-radius:6px; border:1px solid rgba(128,128,128,0.3);\\\">\\n<$button class=\\\"tc-btn-invisible\\\" tooltip=\\\"加粗\\\" style=\\\"padding:2px 8px; font-weight:700;\\\"><$action-sendmessage $message=\\\"tm-edit-text-operation\\\" $param=\\\"wrap-selection\\\" prefix=\\\"**\\\" suffix=\\\"**\\\" trimSelection=\\\"yes\\\"/>B</$button>\\n<$button class=\\\"tc-btn-invisible\\\" tooltip=\\\"斜体\\\" style=\\\"padding:2px 8px; font-style:italic;\\\"><$action-sendmessage $message=\\\"tm-edit-text-operation\\\" $param=\\\"wrap-selection\\\" prefix=\\\"*\\\" suffix=\\\"*\\\" trimSelection=\\\"yes\\\"/>I</$button>\\n<$button class=\\\"tc-btn-invisible\\\" tooltip=\\\"删除线\\\" style=\\\"padding:2px 8px;\\\"><$action-sendmessage $message=\\\"tm-edit-text-operation\\\" $param=\\\"wrap-selection\\\" prefix=\\\"~~\\\" suffix=\\\"~~\\\" trimSelection=\\\"yes\\\"/>S</$button>\\n<$button class=\\\"tc-btn-invisible\\\" tooltip=\\\"标题\\\" style=\\\"padding:2px 8px;\\\"><$action-sendmessage $message=\\\"tm-edit-text-operation\\\" $param=\\\"prefix-lines\\\" character=\\\"#\\\" count=\\\"1\\\"/>H</$button>\\n<$button class=\\\"tc-btn-invisible\\\" tooltip=\\\"无序列表\\\" style=\\\"padding:2px 8px;\\\"><$action-sendmessage $message=\\\"tm-edit-text-operation\\\" $param=\\\"prefix-lines\\\" character=\\\"-\\\" count=\\\"1\\\"/>•</$button>\\n<$button class=\\\"tc-btn-invisible\\\" tooltip=\\\"有序列表\\\" style=\\\"padding:2px 8px;\\\"><$action-sendmessage $message=\\\"tm-edit-text-operation\\\" $param=\\\"prefix-lines\\\" character=\\\"1.\\\" count=\\\"1\\\"/>1.</$button>\\n<$button class=\\\"tc-btn-invisible\\\" tooltip=\\\"引用\\\" style=\\\"padding:2px 8px;\\\"><$action-sendmessage $message=\\\"tm-edit-text-operation\\\" $param=\\\"prefix-lines\\\" character=\\\">\\\" count=\\\"1\\\"/>❝</$button>\\n<$button class=\\\"tc-btn-invisible\\\" tooltip=\\\"行内代码\\\" style=\\\"padding:2px 8px;\\\"><$action-sendmessage $message=\\\"tm-edit-text-operation\\\" $param=\\\"wrap-selection\\\" prefix=\\\"`\\\" suffix=\\\"`\\\" trimSelection=\\\"yes\\\"/>&lt;/&gt;</$button>\\n</$edit-text>\\n\\n<div style=\\\"display:flex; gap:6px; flex-wrap:wrap; align-items:center; margin-top:6px;\\\">\\n<$edit-text tiddler=\\\"$:/state/home-note/tags\\\" tag=\\\"input\\\" placeholder=\\\"标签(逗号分隔,可选)\\\" style=\\\"flex:1; min-width:160px; padding:6px 10px; border-radius:6px; border:1px solid rgba(128,128,128,0.3);\\\"/>\\n<$checkbox tiddler=\\\"$:/state/home-note/todo\\\" field=\\\"text\\\" checked=\\\"yes\\\" unchecked=\\\"no\\\"> 同时加入待办</$checkbox>\\n<$select tiddler=\\\"$:/state/home-note/quadrant\\\" default=\\\"q2\\\" style=\\\"padding:6px 10px; border-radius:6px; border:1px solid rgba(128,128,128,0.3);\\\">\\n<option value=\\\"q1\\\">Q1 重要·紧急</option>\\n<option value=\\\"q2\\\">Q2 重要·不紧急</option>\\n<option value=\\\"q3\\\">Q3 紧急·不重要</option>\\n<option value=\\\"q4\\\">Q4 不重要·不紧急</option>\\n</$select>\\n</div>\\n\\n<div style=\\\"display:flex; gap:6px; align-items:center; margin-top:8px; flex-wrap:wrap;\\\">\\n<$button class=\\\"tc-btn-invisible\\\" style=\\\"padding:6px 16px; border-radius:6px; border:1px solid rgba(128,128,128,0.35); font-weight:600; background:rgba(128,128,128,0.06);\\\">\\n<$list filter=\\\"[{$:/state/home-note/title}!is[blank]] [{$:/state/home-note/text}!is[blank]]\\\">\\n<$action-createtiddler\\n$basetitle={{{ [{$:/state/home-note/title}!is[blank]then{$:/state/home-note/title}] ~[<now \\\"YYYY-0MM-0DD-0hh0mm\\\">] }}}\\ntext={{{ [{$:/state/home-note/text}] }}}\\ntags={{{ [{$:/state/home-note/tags}split[,]split[,]trim[]] [{$:/state/home-note/todo}match[yes]then[todo]] +[join[ ]] }}}\\nq={{{ [{$:/state/home-note/todo}match[yes]then{$:/state/home-note/quadrant}!is[blank]] ~[{$:/state/home-note/todo}match[yes]then[q2]] }}}\\ntype=\\\"text/markdown\\\"\\n$savetitle=\\\"$:/temp/home-note/last-created\\\"/>\\n<$action-setfield $tiddler=\\\"$:/state/home-note/title\\\" text=\\\"\\\"/>\\n<$action-setfield $tiddler=\\\"$:/state/home-note/text\\\" text=\\\"\\\"/>\\n<$action-setfield $tiddler=\\\"$:/state/home-note/tags\\\" text=\\\"\\\"/>\\n</$list>\\n💾 保存\\n</$button>\\n<$button class=\\\"tc-btn-invisible\\\" tooltip=\\\"清空草稿\\\" style=\\\"padding:6px 12px; border-radius:6px; border:1px solid rgba(128,128,128,0.3);\\\">\\n<$action-setfield $tiddler=\\\"$:/state/home-note/title\\\" text=\\\"\\\"/>\\n<$action-setfield $tiddler=\\\"$:/state/home-note/text\\\" text=\\\"\\\"/>\\n<$action-setfield $tiddler=\\\"$:/state/home-note/tags\\\" text=\\\"\\\"/>\\n🗑 清空\\n</$button>\\n<$list filter=\\\"[{$:/temp/home-note/last-created}!is[blank]]\\\">\\n<span style=\\\"color:#2e7d32; font-size:0.9em;\\\">✅ 已保存:<$link to={{{ [{$:/temp/home-note/last-created}] }}}>{{{$:/temp/home-note/last-created}}}</$link></span>\\n</$list>\\n</div>\\n\\n</div>\\n\\n!! ✅ 待办 · 四象限\\n\\n<div class=\\\"tc-message-box\\\">在上方「快速记笔记」勾选「同时加入待办」即建任务(默认 Q2 重要·不紧急);也可以给任意笔记打上 <code>todo</code> 标签收集到下方。点任务左侧方框即完成(自动加 <code>done</code> 标签并从看板消失)。下方「未分类待办」可直接拖动到上方任一象限,拖入即自动写入 <code>q</code> 字段完成分类(象限之间也可互相拖动调整);把象限中的待办拖回「未分类」可取消分类。</div>\\n\\n<div style=\\\"margin:6px 0; color:#555;\\\">📅 今日到期 <b>{{{[tag[todo]!tag[done]field:due<today>count[]]}}}</b> 件 · ⏰ 已逾期 <b>{{{[tag[todo]!tag[done]get[due]compare:date:lt<today>count[]]}}}</b> 件</div>\\n\\n<<quadrant-board>>\\n\\n<div style=\\\"margin-top:10px; border:1px dashed rgba(128,128,128,0.35); border-radius:10px; padding:8px 12px;\\\">\\n<div style=\\\"font-weight:700; margin-bottom:6px;\\\">📥 未分类待办 <span style=\\\"color:#888; font-weight:400; font-size:0.82em;\\\">{{{[tag[todo]!tag[done]!has[q]count[]]}}} 件</span> <span style=\\\"color:#aaa; font-weight:400; font-size:0.78em;\\\">· 拖到上方象限自动分类</span></div>\\n<$droppable class=\\\"tc-todo-inbox\\\" effect=\\\"move\\\" actions=\\\"\\\"\\\"<$action-deletefield $tiddler=<<actionTiddler>> $field=\\\"q\\\"/>\\\"\\\"\\\">\\n<$list filter=\\\"[tag[todo]!tag[done]!has[q]sort[modified]]\\\">\\n<div style=\\\"display:flex; gap:6px; align-items:baseline; padding:3px 0; border-bottom:1px dashed rgba(128,128,128,0.15);\\\">\\n<$checkbox tiddler=<<currentTiddler>> tag=\\\"done\\\"/>\\n<$link to=<<currentTiddler>> style=\\\"flex:1; cursor:grab;\\\"><$view field=\\\"title\\\"/></$link>\\n<$list filter=\\\"[<currentTiddler>get[due]compare:date:lt<today>]\\\"><span style=\\\"color:#c0392b; font-size:0.78em;\\\">逾期</span></$list>\\n</div>\\n</$list>\\n<$list filter=\\\"[tag[todo]!tag[done]!has[q]count[]match[0]]\\\"><div style=\\\"color:#aaa; font-size:0.85em;\\\">(暂无 · 把象限中的待办拖回这里可取消分类)</div></$list>\\n</$droppable>\\n</div>\\n\\n</$set>\\n\"},{\"title\":\"所有标签\",\"tags\":[\"索引\",\"TableOfContents\"],\"type\":\"text/vnd.tiddlywiki\",\"text\":\"\\\\whitespace trim\\n\\n\\\\define tag-count() [all[tiddlers]!is[system]!tag[agent-written]tag<currentTiddler>count[]]\\n\\\\define tag-list() [all[tiddlers]!is[system]!tag[agent-written]tags[]!prefix[$:/tags/]!match[索引]]\\n\\n\\\\define agent-notes-pure() [all[tiddlers]!is[system]tag[agent-written]!tag[human-edited]count[]]\\n\\\\define agent-notes-mixed() [all[tiddlers]!is[system]tag[agent-written]tag[human-edited]count[]]\\n\\\\define agent-tags-pure() [all[tiddlers]!is[system]tag[agent-written]!tag[human-edited]tags[]!prefix[$:/tags/]!match[索引]!match[agent-written]!match[human-edited]]\\n\\\\define agent-tags-mixed() [all[tiddlers]!is[system]tag[agent-written]tag[human-edited]tags[]!prefix[$:/tags/]!match[索引]!match[agent-written]!match[human-edited]]\\n\\\\define agent-count-pure() [all[tiddlers]!is[system]tag[agent-written]!tag[human-edited]tag<currentTiddler>count[]]\\n\\\\define agent-count-mixed() [all[tiddlers]!is[system]tag[agent-written]tag[human-edited]tag<currentTiddler>count[]]\\n\\n<$button class=\\\"tc-btn-invisible tc-tiddlylink\\\" style=\\\"margin:6px 0; padding:4px 10px; border:1px solid rgba(128,128,128,0.3); border-radius:6px; font-size:0.9em;\\\">\\n<$action-navigate $to=\\\"🏠 主页\\\"/>\\n← 回主页\\n</$button>\\n\\n!! 🏷 所有标签\\n\\n<div class=\\\"tc-message-box\\\">按笔记数量从多到少排序,仅统计人类笔记(Agent 撰写的笔记已排除,见下方「🤖 Agent 撰写的标签」)。点击标签,查看包含该标签的所有笔记。</div>\\n\\n<div style=\\\"margin-top:12px;\\\">\\n<$list filter=\\\"[subfilter<tag-list>] +[!sortsub:number<tag-count>]\\\">\\n<$set name=\\\"count\\\" value={{{ [subfilter<tag-count>] }}}>\\n<div style=\\\"padding:7px 2px; border-bottom:1px solid rgba(128,128,128,0.15);\\\">\\n<$button set=\\\"$:/state/tag\\\" setTo=<<currentTiddler>> class=\\\"tc-btn-invisible tc-tiddlylink\\\" style=\\\"width:100%; text-align:left;\\\">\\n<$action-navigate $to=\\\"标签笔记\\\"/>\\n<span style=\\\"font-weight:600; font-size:1.05em;\\\"><<currentTiddler>></span>\\n<span style=\\\"color:#888; font-size:0.85em; margin-left:8px;\\\"><<count>> 篇</span>\\n</$button>\\n</div>\\n</$set>\\n</$list>\\n</div>\\n\\n!! 🤖 Agent 撰写的标签\\n\\n<div class=\\\"tc-message-box\\\">以下标签来自带有 <code>agent-written</code> 标签(由 Agent 撰写)的笔记,与上方主列表分开统计。若某篇 Agent 笔记又被人类编辑过,请给它补打 <code>human-edited</code> 标签,即归入下方「Agent + 人工」档。</div>\\n\\n<div style=\\\"font-weight:700; margin:8px 0 4px;\\\">🦾 纯 Agent <span style=\\\"color:#888; font-weight:400; font-size:0.82em;\\\">{{{[subfilter<agent-notes-pure>]}}} 篇</span></div>\\n<div style=\\\"margin-left:10px;\\\">\\n<$list filter=\\\"[subfilter<agent-tags-pure>] +[!sortsub:number<agent-count-pure>]\\\">\\n<$set name=\\\"count\\\" value={{{ [subfilter<agent-count-pure>] }}}>\\n<div style=\\\"padding:7px 2px; border-bottom:1px solid rgba(128,128,128,0.15);\\\">\\n<$button set=\\\"$:/state/tag\\\" setTo=<<currentTiddler>> class=\\\"tc-btn-invisible tc-tiddlylink\\\" style=\\\"width:100%; text-align:left;\\\">\\n<$action-navigate $to=\\\"标签笔记\\\"/>\\n<span style=\\\"font-weight:600; font-size:1.05em;\\\"><<currentTiddler>></span>\\n<span style=\\\"color:#888; font-size:0.85em; margin-left:8px;\\\"><<count>> 篇</span>\\n</$button>\\n</div>\\n</$set>\\n</$list>\\n<$list filter=\\\"[subfilter<agent-tags-pure>count[]match[0]]\\\"><div style=\\\"color:#aaa; font-size:0.85em;\\\">(暂无纯 Agent 笔记)</div></$list>\\n</div>\\n\\n<div style=\\\"font-weight:700; margin:10px 0 4px;\\\">🤝 Agent + 人工 <span style=\\\"color:#888; font-weight:400; font-size:0.82em;\\\">{{{[subfilter<agent-notes-mixed>]}}} 篇</span></div>\\n<div style=\\\"margin-left:10px;\\\">\\n<$list filter=\\\"[subfilter<agent-tags-mixed>] +[!sortsub:number<agent-count-mixed>]\\\">\\n<$set name=\\\"count\\\" value={{{ [subfilter<agent-count-mixed>] }}}>\\n<div style=\\\"padding:7px 2px; border-bottom:1px solid rgba(128,128,128,0.15);\\\">\\n<$button set=\\\"$:/state/tag\\\" setTo=<<currentTiddler>> class=\\\"tc-btn-invisible tc-tiddlylink\\\" style=\\\"width:100%; text-align:left;\\\">\\n<$action-navigate $to=\\\"标签笔记\\\"/>\\n<span style=\\\"font-weight:600; font-size:1.05em;\\\"><<currentTiddler>></span>\\n<span style=\\\"color:#888; font-size:0.85em; margin-left:8px;\\\"><<count>> 篇</span>\\n</$button>\\n</div>\\n</$set>\\n</$list>\\n<$list filter=\\\"[subfilter<agent-tags-mixed>count[]match[0]]\\\"><div style=\\\"color:#aaa; font-size:0.85em;\\\">(暂无「Agent + 人工」笔记)</div></$list>\\n</div>\\n\"},{\"title\":\"标签笔记\",\"tags\":[\"索引\"],\"type\":\"text/vnd.tiddlywiki\",\"text\":\"\\\\whitespace trim\\n\\n<$set name=\\\"sel\\\" value={{{ [{$:/state/tag}] }}}>\\n\\n!! 标签:<<sel>>\\n\\n<div class=\\\"tc-message-box\\\">包含标签 <strong><<sel>></strong> 的所有笔记(自动收集,按最近修改排序)。</div>\\n\\n<ul>\\n<$list filter=\\\"[all[tiddlers]!is[system]!has[draft.of]tag<sel>] +[!sort[modified]]\\\">\\n<li><$link to=<<currentTiddler>>><$view field=\\\"title\\\"/></$link><span style=\\\"color:#aaa; font-size:0.85em;\\\"> · <$view field=\\\"modified\\\" format=\\\"relativedate\\\"/></span></li>\\n</$list>\\n</ul>\\n\\n</$set>\\n\"}]\n\n/**\n * Seed the home/index tiddlers exactly once per wiki (mirrors the doc-note\n * one-shot policy). Also writes $:/DefaultTiddlers → [[🏠 主页]] so the\n * new home opens by default. With `force` the tiddlers are overwritten with the\n * built-in content and the marker is (re)written — the settings page uses this\n * for \"重新初始化\". Returns whether anything was written this call. Throws when a read fails (the seed registry reports it as `ok:false`).\n */\nexport async function seedHomeIndex(client: TiddlyWebClient, opts?: { force?: boolean }): Promise<boolean> {\n const force = opts?.force === true\n if (!force) {\n const marker = await readSeedTiddler(client, HOME_INDEX_MARKER_TITLE)\n if (marker !== undefined) return false\n }\n let wrote = false\n for (const item of HOME_INDEX_ITEMS) {\n const existing = await readSeedTiddler(client, item.title)\n if (force || existing === undefined) {\n await client.put({ title: item.title, text: item.text, type: item.type, tags: item.tags })\n wrote = true\n }\n }\n // Ensure the default home page is 🏠 主页. A fresh wiki's $:/DefaultTiddlers is\n // the core shadow \"GettingStarted\" (or absent), so a first seed writes it;\n // a user-customised DefaultTiddlers is left alone unless force.\n const dt = await readSeedTiddler(client, '$:/DefaultTiddlers')\n const dtText = typeof dt?.text === 'string' ? dt.text.trim() : ''\n if (force || dt === undefined || dtText === 'GettingStarted' || dtText === '[[GettingStarted]]') {\n await client.put({ title: '$:/DefaultTiddlers', text: HOME_DEFAULT_TIDDLERS, type: 'text/vnd.tiddlywiki', tags: [] })\n wrote = true\n }\n await writeSeedMarker(client, HOME_INDEX_MARKER_TITLE)\n return wrote\n}\n\n/**\n * Un-seed (反初始化): remove the home tiddlers and their marker, restoring the\n * wiki's default home only when it still points at the seeded 🏠 主页\n * (a user-customised $:/DefaultTiddlers is left alone). Deletion is idempotent —\n * a tiddler already gone is not listed. Throws when a read fails (the seed registry reports it as `ok:false`).\n */\nexport async function unseedHomeIndex(client: TiddlyWebClient): Promise<{ removed: string[] }> {\n const removed: string[] = []\n for (const item of HOME_INDEX_ITEMS) {\n const t = await readSeedTiddler(client, item.title)\n if (t !== undefined) {\n await client.delete(item.title)\n removed.push(item.title)\n }\n }\n const marker = await readSeedTiddler(client, HOME_INDEX_MARKER_TITLE)\n if (marker !== undefined) {\n await client.delete(HOME_INDEX_MARKER_TITLE)\n removed.push(HOME_INDEX_MARKER_TITLE)\n }\n const dt = await readSeedTiddler(client, '$:/DefaultTiddlers')\n if (dt !== undefined && typeof dt.text === 'string' && dt.text.trim() === \"[[🏠 主页]]\") {\n await client.put({ title: '$:/DefaultTiddlers', text: '[[GettingStarted]]', type: 'text/vnd.tiddlywiki', tags: [] })\n removed.push('$:/DefaultTiddlers')\n }\n return { removed }\n}\n","/**\n * The \"所有文章\" two-column paginated index page, seeded into wikis by\n * seedAllArticles. Source: 所有文章.tid (the wiki's live tiddler).\n *\n * The page lists every non-system, non-draft tiddler in two independently\n * paginated columns:\n * - 🤖 Agent 撰写: agent-written and not human-edited\n * - 👤 人工 / 人类: not agent-written, OR agent-written + human-edited\n * Page size comes live from the plugin config tiddler\n * ($:/plugins/dsh-tiddlywiki/config → ui.allArticles.pageSize, default 10).\n *\n * @module dsh-tiddlywiki/host/seed-all-articles\n */\nimport type { TiddlyWebClient } from './tw-api.ts'\nimport { readSeedTiddler, writeSeedMarker } from './seed-util.ts'\n\n/** One-time marker: presence means \"the page was offered once — hands off\". */\nexport const ALL_ARTICLES_MARKER_TITLE = '$:/plugins/dsh-tiddlywiki/seed-all-articles'\n\n/** The 所有文章 page title. */\nexport const ALL_ARTICLES_TITLE = '所有文章'\n\n/** The page body, exactly as seeded (user-owned afterwards). */\nexport const ALL_ARTICLES_TEXT = `\\\\whitespace trim\n\n\\\\define page-size() [{$:/plugins/dsh-tiddlywiki/config}jsonget[ui],[allArticles],[pageSize]else[10]]\n\\\\define agent-list() [all[tiddlers]!is[system]!has[draft.of]!tag[索引]tag[agent-written]!tag[human-edited]!sort[modified]]\n\\\\define human-list() [all[tiddlers]!is[system]!has[draft.of]!tag[索引]!tag[agent-written]] [all[tiddlers]!is[system]!has[draft.of]!tag[索引]tag[agent-written]tag[human-edited]] +[!sort[modified]]\n\n<$button class=\"tc-btn-invisible tc-tiddlylink\" style=\"margin:6px 0; padding:4px 10px; border:1px solid rgba(128,128,128,0.3); border-radius:6px; font-size:0.9em;\">\n<$action-navigate $to=\"🏠 主页\"/>\n← 回主页\n</$button>\n\n!! 📚 所有文章\n\n<div class=\"tc-message-box\">分两列汇总全部 wiki 条目(不含系统页、草稿与带 <code>索引</code> 标签的导航页):左列 🤖 Agent 撰写(带 <code>agent-written</code> 且未被人工编辑);右列 👤 人工 / 人类(不含 <code>agent-written</code> 的人类笔记 + 被人工编辑过的 <code>human-edited</code> 条目,即使带 <code>agent-written</code>)。两列各自分页,每页条数可在插件设置里调整。</div>\n\n<div style=\"display:grid; grid-template-columns:1fr 1fr; gap:12px; margin-top:8px;\">\n<div style=\"border:1px solid rgba(128,128,128,0.22); border-radius:10px; padding:10px 12px;\">\n<div style=\"font-weight:700; margin-bottom:6px;\">🤖 Agent 撰写 <span style=\"color:#888; font-weight:400; font-size:0.82em;\">{{{[subfilter<agent-list>count[]]}}} 篇</span></div>\n<$set name=\"page\" tiddler=\"$:/state/dsh/all-articles/page/agent\" emptyValue=\"0\">\n<$set name=\"ps\" value={{{ [subfilter<page-size>] }}}>\n<$set name=\"total\" value={{{ [subfilter<agent-list>count[]] }}}>\n<$set name=\"last\" value={{{ [<total>] +[divide<ps>] +[ceil[0]] +[subtract[1]] +[max[0]] }}}>\n<$set name=\"pagec\" value={{{ [<page>] +[min<last>] +[max[0]] }}}>\n<$set name=\"offset\" value={{{ [<pagec>] +[multiply<ps>] }}}>\n<div>\n<$list filter=\"[subfilter<agent-list>] +[rest<offset>] +[first<ps>]\">\n<div style=\"display:flex; gap:6px; align-items:baseline; padding:3px 0; border-bottom:1px dashed rgba(128,128,128,0.15);\">\n<$link to=<<currentTiddler>> style=\"flex:1;\"><$view field=\"title\"/></$link>\n<span style=\"color:#aaa; font-size:0.82em;\"><$view field=\"modified\" format=\"relativedate\"/></span>\n</div>\n</$list>\n<$list filter=\"[subfilter<agent-list>count[]] +[match[0]]\"><div style=\"color:#aaa; font-size:0.85em;\">(暂无)</div></$list>\n</div>\n<$list filter=\"[<total>!match[0]]\">\n<div style=\"display:flex; gap:8px; align-items:center; margin-top:8px;\">\n<$list filter=\"[<pagec>compare:number:gt[0]]\">\n<$button class=\"tc-btn-invisible\" style=\"padding:2px 10px; border:1px solid rgba(128,128,128,0.35); border-radius:6px;\">\n<$action-setfield $tiddler=\"$:/state/dsh/all-articles/page/agent\" $field=\"text\" $value={{{ [<pagec>] +[subtract[1]] +[max[0]] }}}/>\n◀ 上一页\n</$button>\n</$list>\n<span style=\"color:#888; font-size:0.85em;\">第 {{{ [<pagec>] +[add[1]] }}} / {{{ [<total>] +[divide<ps>] +[ceil[0]] }}} 页</span>\n<$list filter=\"[<pagec>compare:number:lt<last>]\">\n<$button class=\"tc-btn-invisible\" style=\"padding:2px 10px; border:1px solid rgba(128,128,128,0.35); border-radius:6px;\">\n<$action-setfield $tiddler=\"$:/state/dsh/all-articles/page/agent\" $field=\"text\" $value={{{ [<pagec>] +[add[1]] +[min<last>] }}}/>\n下一页 ▶\n</$button>\n</$list>\n</div>\n</$list>\n</$set>\n</$set>\n</$set>\n</$set>\n</$set>\n</$set>\n</div>\n<div style=\"border:1px solid rgba(128,128,128,0.22); border-radius:10px; padding:10px 12px;\">\n<div style=\"font-weight:700; margin-bottom:6px;\">👤 人工 / 人类 <span style=\"color:#888; font-weight:400; font-size:0.82em;\">{{{[subfilter<human-list>count[]]}}} 篇</span></div>\n<$set name=\"page\" tiddler=\"$:/state/dsh/all-articles/page/human\" emptyValue=\"0\">\n<$set name=\"ps\" value={{{ [subfilter<page-size>] }}}>\n<$set name=\"total\" value={{{ [subfilter<human-list>count[]] }}}>\n<$set name=\"last\" value={{{ [<total>] +[divide<ps>] +[ceil[0]] +[subtract[1]] +[max[0]] }}}>\n<$set name=\"pagec\" value={{{ [<page>] +[min<last>] +[max[0]] }}}>\n<$set name=\"offset\" value={{{ [<pagec>] +[multiply<ps>] }}}>\n<div>\n<$list filter=\"[subfilter<human-list>] +[rest<offset>] +[first<ps>]\">\n<div style=\"display:flex; gap:6px; align-items:baseline; padding:3px 0; border-bottom:1px dashed rgba(128,128,128,0.15);\">\n<$link to=<<currentTiddler>> style=\"flex:1;\"><$view field=\"title\"/></$link>\n<span style=\"color:#aaa; font-size:0.82em;\"><$view field=\"modified\" format=\"relativedate\"/></span>\n</div>\n</$list>\n<$list filter=\"[subfilter<human-list>count[]] +[match[0]]\"><div style=\"color:#aaa; font-size:0.85em;\">(暂无)</div></$list>\n</div>\n<$list filter=\"[<total>!match[0]]\">\n<div style=\"display:flex; gap:8px; align-items:center; margin-top:8px;\">\n<$list filter=\"[<pagec>compare:number:gt[0]]\">\n<$button class=\"tc-btn-invisible\" style=\"padding:2px 10px; border:1px solid rgba(128,128,128,0.35); border-radius:6px;\">\n<$action-setfield $tiddler=\"$:/state/dsh/all-articles/page/human\" $field=\"text\" $value={{{ [<pagec>] +[subtract[1]] +[max[0]] }}}/>\n◀ 上一页\n</$button>\n</$list>\n<span style=\"color:#888; font-size:0.85em;\">第 {{{ [<pagec>] +[add[1]] }}} / {{{ [<total>] +[divide<ps>] +[ceil[0]] }}} 页</span>\n<$list filter=\"[<pagec>compare:number:lt<last>]\">\n<$button class=\"tc-btn-invisible\" style=\"padding:2px 10px; border:1px solid rgba(128,128,128,0.35); border-radius:6px;\">\n<$action-setfield $tiddler=\"$:/state/dsh/all-articles/page/human\" $field=\"text\" $value={{{ [<pagec>] +[add[1]] +[min<last>] }}}/>\n下一页 ▶\n</$button>\n</$list>\n</div>\n</$list>\n</$set>\n</$set>\n</$set>\n</$set>\n</$set>\n</$set>\n</div>\n</div>\n`\n\n/**\n * Seed the 所有文章 page exactly once per wiki (mirrors the doc-note one-shot\n * policy). With `opts.force` the page is overwritten with the built-in content\n * and the marker is (re)written — the settings page uses this for\n * \"重新初始化\". Returns whether the page was written this call. Throws when a read fails (the seed registry reports it as `ok:false`).\n */\nexport async function seedAllArticles(client: TiddlyWebClient, opts?: { force?: boolean }): Promise<boolean> {\n const force = opts?.force === true\n if (!force) {\n const marker = await readSeedTiddler(client, ALL_ARTICLES_MARKER_TITLE)\n if (marker !== undefined) return false\n }\n const existing = await readSeedTiddler(client, ALL_ARTICLES_TITLE)\n let wrote = false\n if (force || existing === undefined) {\n await client.put({ title: ALL_ARTICLES_TITLE, text: ALL_ARTICLES_TEXT, type: 'text/vnd.tiddlywiki', tags: ['索引'] })\n wrote = true\n }\n await writeSeedMarker(client, ALL_ARTICLES_MARKER_TITLE)\n return wrote\n}\n\n/**\n * Un-seed (反初始化): remove the 所有文章 page and its one-shot marker.\n * Deletion is idempotent — a tiddler that was already gone is not listed.\n * Throws when a read fails (the seed registry reports it as `ok:false`).\n */\nexport async function unseedAllArticles(client: TiddlyWebClient): Promise<{ removed: string[] }> {\n const removed: string[] = []\n for (const title of [ALL_ARTICLES_TITLE, ALL_ARTICLES_MARKER_TITLE]) {\n const t = await readSeedTiddler(client, title)\n if (t !== undefined) {\n await client.delete(title)\n removed.push(title)\n }\n }\n return { removed }\n}\n","/**\n * Generated from the wiki's custom stylesheet tiddlers (do not hand-edit the\n * constants). Source: 编辑器美化 CSS.css, 标题与按钮区分开.css, 侧边栏窄屏自动隐藏.css, 批注弹窗样式.css, menubar 顶栏加高样式.css\n *\n * The「自定义样式」optional seed: the generic UI stylesheets the wiki owner\n * curated (编辑器美化 / 标题与按钮区分开 / 侧边栏窄屏自动隐藏 / 批注弹窗 /\n * menubar 顶栏加高). Only the functional tag $:/tags/Stylesheet is kept —\n * wiki-local tags (自定义 / agent-written …) are NOT seeded, and none of the\n * sheets carry personal data. Seeded ONE-SHOT (marker-gated, never overwrites\n * user edits) + force 重新初始化 + remove 反初始化, like the other optional seeds.\n *\n * @module dsh-tiddlywiki/host/seed-ui-styles\n */\nimport type { TiddlyWebClient } from './tw-api.ts'\nimport { readSeedTiddler, writeSeedMarker } from './seed-util.ts'\n\n/** One-time marker: presence means \"the styles were offered once — hands off\". */\nexport const UI_STYLES_MARKER_TITLE = '$:/plugins/dsh-tiddlywiki/seed-ui-styles'\n\nexport interface UiStyleItem {\n title: string\n tags: string[]\n type: string\n text: string\n}\n\n/** The stylesheets, exactly as seeded (user-owned afterwards). */\nexport const UI_STYLE_ITEMS: UiStyleItem[] = [{\"title\":\"编辑器美化 CSS\",\"tags\":[\"$:/tags/Stylesheet\"],\"type\":\"text/css\",\"text\":\"/* ===== 编辑器美化(TiddlyWiki 5.4 + CodeMirror)===== */\\n\\n/* 字体 / 字号 / 行高 */\\n.tc-editor .CodeMirror {\\n font-family: \\\"Fira Code VF\\\", Consolas, \\\"Courier New\\\", monospace;\\n font-size: 14px;\\n line-height: 1.7;\\n}\\n\\n/* 编辑区内边距(呼吸感) */\\n.tc-editor .CodeMirror-lines {\\n padding: 12px 14px;\\n}\\n\\n/* 光标颜色 */\\n.tc-editor .CodeMirror-cursor {\\n border-left: 2px solid #2e80ff !important;\\n}\\n\\n/* 当前行高亮 */\\n.tc-editor .CodeMirror-activeline-background {\\n background: rgba(46, 128, 255, 0.08);\\n}\\n\\n/* 行号 */\\n.tc-editor .CodeMirror-linenumber {\\n color: #9aa0a6;\\n padding-right: 10px;\\n}\\n\\n/* 选区配色 */\\n.tc-editor .CodeMirror ::selection {\\n background: rgba(46, 128, 255, 0.22);\\n}\\n\\n/* 工具栏按钮:圆角 + 间距 */\\n.tc-editor-toolbar button {\\n border-radius: 6px;\\n margin: 0 2px 2px 0;\\n}\\n\\n/* 编辑页标题输入框:vanilla 默认 2.35em 过大,缩小字号与内边距 */\\n.tc-tiddler-frame input.tc-edit-texteditor.tc-titlebar,\\n.tc-tiddler-frame .tc-titlebar.tc-edit-texteditor {\\n font-size: 1.2em !important;\\n line-height: 1.35em !important;\\n padding: 4px 8px !important;\\n}\\n\\n/* 查看模式标题:vanilla 默认 2.35em 过大,调小一些 */\\n.tc-tiddler-frame .tc-tiddler-title .tc-titlebar,\\n.tc-tiddler-frame .tc-tiddler-title h2.tc-title {\\n font-size: 1.3em !important;\\n line-height: 1.1em !important;\\n}\\n\\n/* 侧边栏站点标题(sidebar 顶部的 wiki 站名):vanilla 默认 2.35em 过大,调小 */\\n.tc-sidebar .tc-site-title {\\n font-size: 1.5em !important;\\n line-height: 1.3em !important;\\n}\\n\"},{\"title\":\"标题与按钮区分开\",\"tags\":[\"$:/tags/Stylesheet\"],\"type\":\"text/css\",\"text\":\".tc-titlebar h2 {\\n\\tdisplay: table-header-group;\\n\\tword-wrap:break-word;\\n\\tword-break:break-all;\\n}\\n\"},{\"title\":\"侧边栏窄屏自动隐藏.css\",\"tags\":[\"$:/tags/Stylesheet\"],\"type\":\"text/css\",\"text\":\"/* ===== 侧边栏窄屏自动隐藏 =====\\n 视口(TW 面板/iframe 宽度)< 960px 时自动完全隐藏右侧 TiddlyWiki 侧边栏,\\n 正文占满整行;窗口变宽 >= 960px 后自动恢复。\\n 断点 959px 对齐主题默认 sidebarbreakpoint=960px(vanilla/snowwhite/starlight)。\\n 想改断点:改下面媒体查询里的数值即可。\\n 想恢复默认:删除本 tiddler。 */\\n\\n@media (max-width: 959px) {\\n\\n\\t/* 侧边栏整栏隐藏(含站点标题、各 sidebar 板块) */\\n\\t.tc-sidebar-scrollable {\\n\\t\\tdisplay: none !important;\\n\\t}\\n\\n\\t/* 右上角「显示/隐藏侧边栏」chevron 按钮一并隐藏:\\n\\t 窄屏下侧边栏已被 CSS 隐藏,此时按钮点击只会改 $:/state/sidebar 状态、\\n\\t 界面却看不到变化,反而误导,故隐藏 */\\n\\t.tc-hide-sidebar-btn,\\n\\t.tc-show-sidebar-btn {\\n\\t\\tdisplay: none !important;\\n\\t}\\n\\n}\\n\"},{\"title\":\"批注弹窗样式\",\"tags\":[\"$:/tags/Stylesheet\"],\"type\":\"text/css\",\"text\":\"/* 批注功能弹窗美化(dsh-ann-*) */\\n.dsh-ann-popup {\\n\\tborder-radius: 12px;\\n\\tpadding: 14px 16px;\\n\\tmax-width: 520px;\\n\\twidth: max-content;\\n\\tmin-width: 300px;\\n\\tbox-shadow: 0 10px 30px rgba(0,0,0,.22);\\n\\tborder: 1px solid rgba(127,127,127,.25);\\n}\\n.dsh-ann-popup .dsh-ann-head {\\n\\tdisplay: flex;\\n\\talign-items: center;\\n\\tjustify-content: space-between;\\n\\tgap: 10px;\\n\\tfont-weight: 700;\\n\\tfont-size: 1.02em;\\n\\tmargin-bottom: 8px;\\n}\\n.dsh-ann-icon-btn {\\n\\tborder: none;\\n\\tbackground: transparent;\\n\\tcursor: pointer;\\n\\tfont-size: 1em;\\n\\tline-height: 1;\\n\\tpadding: 2px 8px;\\n\\tborder-radius: 6px;\\n\\topacity: .55;\\n}\\n.dsh-ann-icon-btn:hover { opacity: 1; background: rgba(127,127,127,.15); }\\n.dsh-ann-sub { font-size: .88em; margin: 4px 0; }\\n.dsh-ann-quote {\\n\\tbackground: rgba(127,127,127,.14);\\n\\tpadding: 2px 8px;\\n\\tborder-radius: 6px;\\n\\tfont-style: italic;\\n}\\n.dsh-ann-chip {\\n\\tdisplay: inline-block;\\n\\twidth: 12px;\\n\\theight: 12px;\\n\\tborder-radius: 50%;\\n\\tmargin: 0 4px 0 8px;\\n\\tborder: 1px solid rgba(0,0,0,.18);\\n\\tvertical-align: -1px;\\n}\\n.dsh-ann-label { font-size: .9em; margin: 10px 0 4px; }\\n.dsh-ann-input {\\n\\twidth: 100%;\\n\\tborder-radius: 8px;\\n\\tborder: 1px solid rgba(127,127,127,.35);\\n\\tpadding: 6px 9px;\\n\\tfont-size: .95em;\\n\\tresize: vertical;\\n}\\n.dsh-ann-input:focus {\\n\\toutline: none;\\n\\tborder-color: #4a90d9;\\n\\tbox-shadow: 0 0 0 2px rgba(74,144,217,.22);\\n}\\n.dsh-ann-actions {\\n\\tdisplay: flex;\\n\\tjustify-content: flex-end;\\n\\talign-items: center;\\n\\tgap: 8px;\\n\\tmargin-top: 12px;\\n}\\n.dsh-ann-btn {\\n\\tdisplay: inline-block;\\n\\tborder-radius: 8px;\\n\\tpadding: 5px 14px;\\n\\tcursor: pointer;\\n\\tborder: 1px solid rgba(127,127,127,.35);\\n\\tbackground: rgba(127,127,127,.08);\\n\\tfont-size: .9em;\\n\\ttext-decoration: none !important;\\n}\\n.dsh-ann-btn:hover { background: rgba(127,127,127,.18); }\\n.dsh-ann-btn-primary {\\n\\tbackground: #4a90d9;\\n\\tborder-color: #4a90d9;\\n\\tcolor: #fff !important;\\n}\\n.dsh-ann-btn-primary:hover { background: #3a80c9; }\\n.dsh-ann-colors {\\n\\tdisplay: flex;\\n\\tgap: 10px;\\n\\tmargin: 10px 0 12px;\\n}\\n.dsh-ann-swatch {\\n\\twidth: 34px;\\n\\theight: 34px;\\n\\tborder-radius: 50%;\\n\\tborder: 2px solid rgba(0,0,0,.14);\\n\\tcursor: pointer;\\n\\tpadding: 0;\\n\\ttransition: transform .12s ease, box-shadow .12s ease;\\n}\\n.dsh-ann-swatch:hover {\\n\\ttransform: scale(1.18);\\n\\tbox-shadow: 0 2px 8px rgba(0,0,0,.28);\\n}\\n.dsh-ann-sel { font-size: .88em; margin-top: 4px; }\\n\"},{\"title\":\"menubar 顶栏加高样式\",\"tags\":[\"$:/tags/Stylesheet\"],\"type\":\"text/css\",\"text\":\"/* ==== menubar 顶栏加高 ====\\n 默认顶栏约 28px(菜单项 padding 0.5em + line-height 1)。\\n 想调高度:改 min-height 数值即可(如 40 / 48 / 56px)。\\n 想恢复默认:删除本 tiddler。\\n flex 布局让菜单项在加高的栏内垂直居中;窄屏汉堡展开时保持纵向堆叠。 */\\n\\nnav.tc-menubar ul.tc-menubar-list {\\n\\tdisplay: flex;\\n\\talign-items: center;\\n\\tbox-sizing: border-box;\\n\\tmin-height: 44px;\\n}\\n\\nnav.tc-menubar .tc-menubar-narrow ul.tc-menubar-list {\\n\\tflex-direction: column;\\n\\talign-items: stretch;\\n}\\n\"}]\n\n/**\n * Seed the custom stylesheet tiddlers once per wiki (mirrors the one-shot\n * policy). With opts.force the tiddlers are overwritten with the built-in\n * content and the marker (re)written — the settings page uses this for\n * \"重新初始化\". Returns whether anything was written this call. Throws when a read fails (the seed registry reports it as `ok:false`).\n */\nexport async function seedUiStyles(client: TiddlyWebClient, opts?: { force?: boolean }): Promise<boolean> {\n const force = opts?.force === true\n if (!force) {\n const marker = await readSeedTiddler(client, UI_STYLES_MARKER_TITLE)\n if (marker !== undefined) return false\n }\n let wrote = false\n for (const item of UI_STYLE_ITEMS) {\n const existing = await readSeedTiddler(client, item.title)\n if (force || existing === undefined) {\n await client.put({ title: item.title, text: item.text, type: item.type, tags: item.tags })\n wrote = true\n }\n }\n await writeSeedMarker(client, UI_STYLES_MARKER_TITLE)\n return wrote\n}\n\n/**\n * Un-seed (反初始化): remove the stylesheet tiddlers and their marker.\n * Deletion is idempotent — a tiddler already gone is not listed. Throws when a read fails (the seed registry reports it as `ok:false`).\n */\nexport async function unseedUiStyles(client: TiddlyWebClient): Promise<{ removed: string[] }> {\n const removed: string[] = []\n for (const item of UI_STYLE_ITEMS) {\n const t = await readSeedTiddler(client, item.title)\n if (t !== undefined) {\n await client.delete(item.title)\n removed.push(item.title)\n }\n }\n const marker = await readSeedTiddler(client, UI_STYLES_MARKER_TITLE)\n if (marker !== undefined) {\n await client.delete(UI_STYLES_MARKER_TITLE)\n removed.push(UI_STYLES_MARKER_TITLE)\n }\n return { removed }\n}\n","/**\n * The menubar theme override seed.\n *\n * The `tiddlywiki/menubar` plugin colours its top bar with\n * `<<colour menubar-background>>`. Most light palettes (Vanilla, Blanca, …) do\n * NOT define `menubar-background`, so the `colour` macro falls through to the\n * plugin's hardcoded `$:/config/DefaultColourMappings/` → `#5778d8` (blue) —\n * the menubar stays blue in every light theme no matter which palette is\n * active. Dark palettes that DO define it (e.g. CupertinoDark → #464646) look\n * fine, but light ones never adapt.\n *\n * The fix lives entirely on the TW side: a small stylesheet tiddler (tagged\n * `$:/tags/Stylesheet`) that pins the menubar's bar/items to the ACTIVE\n * palette's `background` / `foreground` with `!important`. TW re-renders all\n * stylesheets live whenever `$:/palette` changes, and the DSH theme-sync\n * (src/client/theme-sync.ts) flips `$:/palette` in-memory to follow the DSH\n * light/dark theme — so the menubar follows DSH automatically.\n *\n * Registered as the `menubar-theme` seed: ONE-SHOT (marker-gated, never\n * overwrites user edits) + force \"重新初始化\" from the settings page, exactly\n * like the other seeds.\n *\n * @module dsh-tiddlywiki/host/seed-menubar-theme\n */\nimport type { TiddlyWebClient } from './tw-api.ts'\nimport { readSeedTiddler, writeSeedMarker } from './seed-util.ts'\n\n/** One-time marker: presence means \"the override was offered once — hands off\". */\nexport const MENUBAR_THEME_MARKER_TITLE = '$:/plugins/dsh-tiddlywiki/seed-menubar-theme'\n\n/** The stylesheet tiddler that adapts the menubar to the active palette. */\nexport const MENUBAR_THEME_TIDDLER = '$:/plugins/dsh-tiddlywiki/menubar-theme'\n\n/**\n * The override stylesheet body, exactly as seeded (user-owned afterwards).\n * `<<colour background>>` / `<<colour foreground>>` resolve against the ACTIVE\n * palette at render time; `!important` beats the menubar plugin's own rules\n * regardless of stylesheet ordering. `\\rules` mirrors the menubar plugin's own\n * styles.tid (macrocallinline needed for `<<colour>>`).\n */\nexport const MENUBAR_THEME_TEXT = `\\\\rules only filteredtranscludeinline transcludeinline macrodef macrocallinline\n\nnav.tc-menubar ul.tc-menubar-list {\n\tbackground: <<colour background>> !important;\n}\n\nnav.tc-menubar li.tc-menubar-item > a,\nnav.tc-menubar li.tc-menubar-item > button {\n\tcolor: <<colour foreground>> !important;\n\tfill: <<colour foreground>> !important;\n\tborder-radius: 6px !important;\n\ttransition: background-color 120ms ease, color 120ms ease;\n}\n\nnav.tc-menubar li.tc-menubar-item svg {\n\tfill: <<colour foreground>> !important;\n}\n\nnav.tc-menubar li.tc-menubar-item > a:hover,\nnav.tc-menubar li.tc-menubar-item > button:hover {\n\tbackground: color-mix(in srgb, <<colour foreground>> 12%, transparent) !important;\n\tcolor: <<colour foreground>> !important;\n\tfill: <<colour foreground>> !important;\n}\n\nnav.tc-menubar li.tc-menubar-item > a:active,\nnav.tc-menubar li.tc-menubar-item > button:active {\n\tbackground: color-mix(in srgb, <<colour foreground>> 20%, transparent) !important;\n\tcolor: <<colour foreground>> !important;\n\tfill: <<colour foreground>> !important;\n}\n\nnav.tc-menubar li.tc-menubar-item > a.tc-selected,\nnav.tc-menubar li.tc-menubar-item > button.tc-selected {\n\tbackground: color-mix(in srgb, <<colour foreground>> 16%, transparent) !important;\n\tcolor: <<colour foreground>> !important;\n\tfill: <<colour foreground>> !important;\n}\n\nnav.tc-menubar li.tc-menubar-item > a:focus-visible,\nnav.tc-menubar li.tc-menubar-item > button:focus-visible {\n\toutline: none !important;\n\tbox-shadow: none !important;\n}\n`\n\n/**\n * Seed the menubar theme override exactly once per wiki (mirrors the other\n * one-shot seeds). With `opts.force` the tiddler is overwritten with the\n * built-in content and the marker (re)written — the settings page uses this\n * for \"重新初始化\". Returns whether anything was written this call. Throws when a read fails (the seed registry reports it as `ok:false`).\n */\nexport async function seedMenubarTheme(client: TiddlyWebClient, opts?: { force?: boolean }): Promise<boolean> {\n const force = opts?.force === true\n if (!force) {\n const marker = await readSeedTiddler(client, MENUBAR_THEME_MARKER_TITLE)\n if (marker !== undefined) return false\n }\n const existing = await readSeedTiddler(client, MENUBAR_THEME_TIDDLER)\n let wrote = false\n if (force || existing === undefined) {\n await client.put({\n title: MENUBAR_THEME_TIDDLER,\n text: MENUBAR_THEME_TEXT,\n type: 'text/vnd.tiddlywiki',\n tags: ['$:/tags/Stylesheet'],\n })\n wrote = true\n }\n await writeSeedMarker(client, MENUBAR_THEME_MARKER_TITLE)\n return wrote\n}\n\n/**\n * Un-seed (反初始化): remove the menubar override stylesheet and its marker,\n * returning the wiki to the pre-seed state (the tiddlywiki/menubar top bar\n * falls back to its original colour-mapping behaviour). Deletion is\n * idempotent — a tiddler already gone is not listed. Throws when a read fails (the seed registry reports it as `ok:false`).\n */\nexport async function unseedMenubarTheme(client: TiddlyWebClient): Promise<{ removed: string[] }> {\n const removed: string[] = []\n for (const title of [MENUBAR_THEME_TIDDLER, MENUBAR_THEME_MARKER_TITLE]) {\n const t = await readSeedTiddler(client, title)\n if (t !== undefined) {\n await client.delete(title)\n removed.push(title)\n }\n }\n return { removed }\n}\n","/**\n * Optional seed: 「本地剪藏桥(书签小工具)」使用说明 tiddler (hand-maintained\n * doc constant, like seed-notes / seed-starter-docs — no generator script).\n *\n * The feature itself lives in src/host/clip-bridge.ts (loopback HTTP bridge\n * inside the DSH host process, config group `bridge.*`). This seed only ships\n * the user-facing instruction tiddler: what it is, how to enable it, the\n * bookmarklet JS to paste into the browser's bookmarks bar, security notes and\n * troubleshooting. Marked `dsh-docs` so the seeded home「📚 插件文档」tabs strip\n * picks it up automatically (v0.16.22 convention).\n *\n * v0.16.25: the bookmarklet opens a PICKER overlay (title / selection text +\n * a thumbnailed image checklist, og:image auto-marked 封面); chosen images are\n * downloaded by the bridge and stored as binary attachment tiddlers.\n * v0.16.26: fixed a real-page crash (overlay fields were populated via\n * document.getElementById while p was still detached) — now resolved via\n * p.querySelector scoped to the overlay, values set after appendChild.\n * v0.16.27: the doc additionally ships a DRAGGABLE anchor (raw HTML inside the\n * markdown — verified: TW 5.4.1 markdown keeps a javascript: href + draggable\n * intact, real-TW + headless-Chrome spike) so users can drag it straight to\n * the bookmarks bar; the copy-paste code fence stays as the fallback. The\n * anchor href and the fence share the SAME CLIP_BRIDGE_BOOKMARKLET constant,\n * with a sync guard in verify-clip-bridge.mjs + a browser E2E in\n * verify-clip-bridge-browser.mjs.\n * v0.16.28: FIXES the drag-install — the href is now percent-ENCODED\n * (CLIP_BRIDGE_DRAG_HREF). v0.16.27 used HTML entity escaping, which real\n * Chrome does NOT decode for javascript: URLs (a.href keeps the raw attribute,\n * so the dragged bookmark executed entitized text → 'SyntaxError: Unexpected\n * token';' on every page). Chrome DOES percent-decode before executing.\n *\n * The bookmarklet is ONE long line by design (bookmarks-bar URL fields; no\n * backticks / ${} inside, which also lets it live in this backtick literal).\n *\n * ONE-SHOT + marker semantics identical to seed-notes: write only when the\n * marker is missing (non-force), force re-writes, remove deletes doc + marker.\n *\n * @module dsh-tiddlywiki/host/seed-clip-bridge\n */\nimport type { TiddlyWebClient } from './tw-api.ts'\nimport { readSeedTiddler, writeSeedMarker } from './seed-util.ts'\n\n/** The instruction tiddler's title (a normal, searchable note). */\nexport const CLIP_BRIDGE_DOC_TITLE = '本地剪藏桥(书签小工具)'\n\n/** One-time marker: presence = \"the doc was offered once — hands off\". */\nexport const CLIP_BRIDGE_MARKER_TITLE = '$:/plugins/dsh-tiddlywiki/seed-clip-bridge'\n\n/**\n * The bookmarklet itself (ONE line, no backticks / ${}). Mirrors the bridge's\n * defaults (port 8618, empty token); users adjust the two placeholders. Used\n * BOTH as the copy-paste code fence AND (HTML-attribute-escaped) as the\n * draggable anchor href — keep them in sync (verify-clip-bridge.mjs asserts it).\n */\nexport const CLIP_BRIDGE_BOOKMARKLET = `javascript:(function(){var t=(document.title||location.hostname).trim();var sel=(window.getSelection()?window.getSelection().toString():'').trim();var imgs=[],seen={};function add(u,a,c){u=String(u||'').trim();if(!u||u.indexOf('data:')===0||u.indexOf('blob:')===0||seen[u])return;seen[u]=1;imgs.push({u:u,a:(a||'').slice(0,60),c:!!c})}var mm=document.querySelector('meta[property=\"og:image\"],meta[name=\"twitter:image\"],link[rel=\"image_src\"]');if(mm){var mu=mm.content||mm.href;if(mu)add(mu,'封面',true)}for(var ii=0;ii<document.images.length;ii++){var im=document.images[ii],iu=im.currentSrc||im.src;if(!iu)continue;if(im.naturalWidth&&im.naturalHeight&&(im.naturalWidth<80||im.naturalHeight<80))continue;add(iu,im.alt||im.title,false)}if(imgs.length>60)imgs=imgs.slice(0,60);var ov=document.createElement('div');ov.style.cssText='position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(10,14,20,.62);z-index:2147483647;display:flex;align-items:flex-start;justify-content:center;padding:4vh 12px';var p=document.createElement('div');p.id='cb_p';p.style.cssText='background:#1c2128;border:1px solid #3a414c;border-radius:12px;max-width:680px;width:100%;max-height:86vh;overflow:auto;padding:16px 18px;color:#e6e6e6;font-family:system-ui,-apple-system,\"Segoe UI\",sans-serif;box-sizing:border-box';p.innerHTML='<div style=\"font-size:16px;font-weight:700\">剪藏到 TiddlyWiki</div><label style=\"display:block;font-size:12px;opacity:.7;margin:10px 0 2px\">标题</label><input id=\"cb_t\" style=\"width:100%;box-sizing:border-box;background:#0f131a;border:1px solid #3a414c;border-radius:6px;color:#eee;padding:7px 9px;font-size:13px\" value=\"\"><label style=\"display:block;font-size:12px;opacity:.7;margin:10px 0 2px\">正文 / 选中文字(可选)</label><textarea id=\"cb_s\" rows=\"5\" style=\"width:100%;box-sizing:border-box;background:#0f131a;border:1px solid #3a414c;border-radius:6px;color:#eee;padding:7px 9px;font-size:13px;resize:vertical\"></textarea>';var Q=function(id){return p.querySelector('#'+id)};var grid=document.createElement('div');grid.style.cssText='display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:8px;margin:10px 0';for(var j=0;j<imgs.length;j++){(function(idx){var it=imgs[idx],lab=document.createElement('label');lab.style.cssText='display:flex;flex-direction:column;gap:4px;cursor:pointer;background:#0f131a;border:1px solid #3a414c;border-radius:8px;padding:6px;overflow:hidden';var top=document.createElement('div');top.style.cssText='position:relative';var imgE=document.createElement('img');imgE.style.cssText='display:block;width:100%;height:76px;object-fit:cover;border-radius:4px;background:#000';imgE.src=it.u;imgE.loading='lazy';imgE.referrerPolicy='no-referrer';top.appendChild(imgE);if(it.c){var bd=document.createElement('span');bd.style.cssText='position:absolute;top:2px;left:2px;background:#f5a623;color:#000;font-size:10px;padding:0 4px;border-radius:3px';bd.textContent='封面';top.appendChild(bd)}var row=document.createElement('div');row.style.cssText='display:flex;align-items:center;gap:6px';var ch=document.createElement('input');ch.type='checkbox';ch.id='cb_i_'+idx;ch.checked=it.c;var nm=document.createElement('span');nm.style.cssText='font-size:11px;opacity:.85;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1';nm.textContent=it.a||(it.u.split('/').pop()||'');nm.title=it.u;row.appendChild(ch);row.appendChild(nm);lab.appendChild(top);lab.appendChild(row);grid.appendChild(lab)})(j)};var foot=document.createElement('div');foot.style.cssText='display:flex;align-items:center;justify-content:space-between;margin-top:12px';var cnt=document.createElement('span');cnt.id='cb_cnt';cnt.style.cssText='font-size:12px;opacity:.75';foot.appendChild(cnt);var btns=document.createElement('div');btns.style.cssText='display:flex;gap:8px';var cancel=document.createElement('button');cancel.textContent='取消';cancel.style.cssText='cursor:pointer;background:#333a45;border:0;color:#eee;border-radius:6px;padding:7px 14px;font-size:13px';var go=document.createElement('button');go.id='cb_btn';go.textContent='剪藏';go.style.cssText='cursor:pointer;background:#2f6feb;border:0;color:#fff;border-radius:6px;padding:7px 14px;font-size:13px';btns.appendChild(cancel);btns.appendChild(go);foot.appendChild(btns);p.appendChild(grid);p.appendChild(foot);ov.appendChild(p);document.body.appendChild(ov);Q('cb_t').value=t;Q('cb_s').value=sel;function upd(){var n=0;for(var q=0;q<imgs.length;q++){var c=Q('cb_i_'+q);if(c&&c.checked)n++}cnt.textContent=(imgs.length?('共 '+imgs.length+' 张,已选 '+n+' 张'):'页面没有可选图片(仍可剪藏文字)')}upd();grid.onchange=function(){upd()};function close(){document.removeEventListener('keydown',onKey,true);if(ov.parentNode){document.body.removeChild(ov)}}var onKey=function(ev){if(ev.key==='Escape')close()};cancel.onclick=close;ov.onclick=function(ev){if(ev.target===ov)close()};p.onclick=function(ev){ev.stopPropagation()};document.addEventListener('keydown',onKey,true);function doClip(){var tt=(Q('cb_t').value||'').trim()||t;var urls=[];for(var k=0;k<imgs.length;k++){var ck=Q('cb_i_'+k);if(ck&&ck.checked)urls.push(imgs[k].u)}var btn=Q('cb_btn');btn.disabled=true;btn.textContent='剪藏中…';fetch('http://127.0.0.1:8618/clip',{method:'POST',headers:{'content-type':'application/json','x-clip-token':''},body:JSON.stringify({title:tt,url:location.href,text:(Q('cb_s').value||''),images:urls})}).then(function(r){return r.json().then(function(j){return {ok:r.ok&&!!j.ok,error:(j.error||('HTTP '+r.status)),title:j.title,images:j.images||[]}})}).then(function(res){var okN=0;for(var v=0;v<res.images.length;v++)if(res.images[v].ok)okN++;var line=res.ok?('已剪藏:'+(res.title||tt)+(res.images.length?(' 图片 '+okN+'/'+res.images.length+' 成功'):'')):('剪藏失败:'+res.error);p.innerHTML='<div style=\"font-size:15px;font-weight:700;margin:8px 0\">'+line+'</div><div style=\"font-size:12px;opacity:.8;margin-bottom:12px\">图片以附件形式存进知识库(失败项已降级为链接)。</div><button id=\"cb_d\" style=\"cursor:pointer;background:#2f6feb;border:0;color:#fff;border-radius:6px;padding:7px 14px;font-size:13px\">完成</button>';Q('cb_d').onclick=close}).catch(function(e){p.innerHTML='<div style=\"font-size:14px;color:#ff8080;font-weight:700;margin:8px 0\">剪藏桥不可达</div><div style=\"font-size:12px;opacity:.85;margin-bottom:12px\">'+(e&&e.message?e.message:'')+' 请确认:① dsh web 在运行;② 设置页已启用「本地剪藏桥」;③ 书签里的端口与设置一致;④ 设置了 token 的话书签已带上。</div><button id=\"cb_d\" style=\"cursor:pointer;background:#333a45;border:0;color:#fff;border-radius:6px;padding:7px 14px;font-size:13px\">关闭</button>';Q('cb_d').onclick=close})}go.onclick=doClip})()`\n\n/**\n * The drag-install href: `javascript:` + percent-ENCODED code. v0.16.27 used\n * HTML-entity escaping (&quot;/&amp;/&lt;) which BREAKS dragged bookmarks —\n * real-Chrome experiment: `a.href` keeps the raw attribute (no entity decode),\n * so the stored bookmark executes the entitized text → SyntaxError. Chrome\n * DOES percent-decode javascript: URLs before executing (verified: encoded\n * alert sets a flag), and %XX needs no attribute escaping — so encoding is the\n * correct distribution form. decodeURIComponent(encoded) === the clean code\n * (guarded in verify-clip-bridge.mjs).\n */\nexport const CLIP_BRIDGE_DRAG_HREF = `javascript:${encodeURIComponent(CLIP_BRIDGE_BOOKMARKLET.slice('javascript:'.length))}`\n\n/**\n * The instruction body, Markdown. Ships BOTH a draggable anchor (raw HTML;\n * verified to survive TW 5.4.1 markdown rendering with its javascript: href\n * intact) and the copy-paste code fence — same CLIP_BRIDGE_BOOKMARKLET.\n */\nexport const CLIP_BRIDGE_DOC_TEXT = `# 本地剪藏桥(书签小工具)\n\n把任意网页**一键剪藏进 TiddlyWiki**:浏览器里点一下书签,会弹出一个小浮层——确认(或修改)标题与选中文字、**勾选想要的图片**,点「剪藏」即写入本插件的知识库(默认打 \\`clip\\` 标签,随 wiki 自动 git commit),之后随时可在 TW 里整理、让 Agent 检索。\n\n## 原理\n\nDSH 进程里运行着一个只监听 **127.0.0.1**(本机回环)的 HTTP 小桥——端口默认 \\`8618\\`,可在设置页改。书签浮层把 {标题, URL, 选中文字, 图片 URL 列表} POST 给它,小桥经插件唯一的写入通道写进 TiddlyWiki:\n\n- **文字** → 一篇笔记(Markdown;选了图则自动改用 wikitext,图片用 \\`[img[标题]]\\` 内嵌展示);\n- **图片** → 由小桥(DSH 进程侧,无浏览器跨域限制)**下载字节**,存成**二进制附件 tiddler**(\\`type: image/…\\` + base64 正文,即 TW 原生二进制附件形态),同一篇笔记里内嵌显示并保留 \\`clip-url\\` / \\`clip-note\\` 溯源字段;\n- 某张图下载失败(防盗链/断链)→ **自动降级为链接**,不阻塞整次剪藏。\n\n## 启用(三步)\n\n1. **开启**:DSH 设置 →「TiddlyWiki 知识库」→ 常规配置 → 勾选 **「启用本地剪藏桥」** → 保存。**立即生效,无需重启 dsh web**(未勾选时桥不会放行任何写入)。\n2. **(可选)设置 token**:强烈建议在同一处填一个 **\\`bridge.token\\`** 共享口令——非空后,书签请求必须带上 \\`x-clip-token\\` 头才能写入,防止你在浏览器里访问的任意网页偷偷往 wiki 里塞内容。**改端口(\\`bridge.port\\`)则需要重启 dsh web 生效**。\n3. **装书签**(二选一):\n - **拖拽安装**:按住下面这个按钮,**拖到浏览器书签栏**(或地址栏)松手即装好,自动命名为「剪藏」:\n\n <a href=\"${CLIP_BRIDGE_DRAG_HREF}\" draggable=\"true\" title=\"按住我,拖到浏览器书签栏即可安装\" style=\"display:inline-block;padding:8px 16px;border-radius:8px;background:#2f6feb;color:#fff;text-decoration:none;font-size:14px;cursor:grab;margin:4px 0\">📌 剪藏 — 拖到书签栏</a>\n\n - **复制粘贴**(部分浏览器/环境禁止从页面拖动书签时用):浏览器书签栏新建书签,名称随意(如「剪藏」),**地址(URL)粘贴下面整段代码**。若设置了 token,把代码里 \\`'x-clip-token': ''\\` 的空字符串换成你的口令;若改了端口,把 \\`127.0.0.1:8618\\` 一起改掉:\n\n\\`\\`\\`javascript\n${CLIP_BRIDGE_BOOKMARKLET}\n\\`\\`\\`\n\n## 使用\n\n打开任意网页 → 选中想保留的文字(可选)→ 点书签 → 浮层里:\n\n- **标题**可改;**正文**自动填入选中的文字(可编辑);\n- **图片区**列出页面图片的缩略图(自动跳过 <80px 的小图标与 data:/blob: 占位,去重;页面声明了 \\`og:image\\` 的会标「封面」并默认勾选)——勾选想剪的(最多 10 张);\n- 点「**剪藏**」→ 提示「已剪藏:〈标题〉(图片 n/m 成功)」。\n\n笔记以**页面标题**命名(重名自动追加 \\`(2)\\`、\\`(3)\\`…,绝不覆盖已有笔记)。**图片是二进制附件**(打开笔记直接看到图;每张带 \\`clip-url\\`、\\`clip-note\\` 字段溯源);某张图没存成(网站防盗链等)会在笔记里留一行链接。可用「发送给 Agent」一键把整篇丢给会话整理。\n\n## 安全说明\n\n- 桥**只监听 127.0.0.1**,并校验请求的 \\`Host\\` 头(只放行 127.0.0.1 / localhost / ::1)——恶意网页即使把自己的域名解析到本机回环(DNS rebinding)也调不动它。\n- **强烈建议设置 \\`bridge.token\\`**:不设 token 时,你在浏览器里访问的**任何网页**都能向本机桥提交内容(它们读不到回应,但能往 wiki 里写——只是污染笔记,不是执行代码)。token 只有你知道,网页拿不到。\n- 图片**只下载你勾选的那几个 URL**,由 DSH 本机发起(无跨域限制),存进的是字节副本;不做任何代码执行。\n- 图片附件**不参与 \\`search\\` / \\`recent\\`**(按设计排除二进制,避免刷屏);\\`get\\` 对附件只返回元数据。想删某张图,直接在 TW 里删除对应 tiddler 即可。\n\n## 配置项\n\n| 项 | 默认 | 说明 |\n|---|---|---|\n| \\`bridge.enabled\\` | \\`false\\` | 是否启用剪藏桥(设置页保存后立即生效) |\n| \\`bridge.port\\` | \\`8618\\` | 监听端口(改动需**重启 dsh web** 生效) |\n| \\`bridge.token\\` | 空 | 共享口令;非空时校验 \\`x-clip-token\\` 请求头 |\n| \\`bridge.tag\\` | \\`clip\\` | 剪藏笔记默认 tag |\n\n## 命令行/脚本方式(可选)\n\n不想用书签时,直接 POST 也行(适合脚本、API 调用;\\`images\\` 字段可选,传了就存附件):\n\n\\`\\`\\`bash\ncurl -X POST http://127.0.0.1:8618/clip ^\n -H \"content-type: application/json\" ^\n -H \"x-clip-token: 你的token(没设置就删掉这行)\" ^\n -d \"{\\\\\"title\\\\\":\\\\\"测试\\\\\",\\\\\"url\\\\\":\\\\\"https://example.com\\\\\",\\\\\"text\\\\\":\\\\\"测试选中文字\\\\\",\\\\\"images\\\\\":[\\\\\"https://example.com/a.png\\\\\"]}\"\n\\`\\`\\`\n\n成功返回 \\`{\"ok\":true,\"title\":\"测试\",...,\"images\":[{\"url\":\"...\",\"ok\":true,\"title\":\"测试 图片 1.png\"}]}\\`;未启用时返回 503。\n\n## 常见问题\n\n- 点书签提示「剪藏桥不可达」:① dsh web 没在运行;② 设置页没勾选启用;③ 书签里的端口和设置不一致;④ 设置了 token 但书签没带上。\n- 图片显示「× 失败」:多为网站防盗链/禁止外链下载,笔记里会留链接兜底;也可先在浏览器里把图片另存,再手动拖进 TW。\n- 端口被占用:换一个 \\`bridge.port\\` 后**重启 dsh web**。\n- 想彻底关掉:设置页取消勾选保存即可(端口虽在监听但拒绝任何写入),或直接改插件行 config 的 \\`bridge.enabled: false\\`。\n`\n\n/**\n * Seed the instruction tiddler once per wiki (mirrors seed-notes one-shot\n * policy: marker = offered; from then on the doc is user-owned and never\n * re-created). force re-writes doc + marker (settings page「重新初始化」).\n * Throws when a read fails (the seed registry reports it as `ok:false`).\n */\nexport async function seedClipBridge(client: TiddlyWebClient, opts?: { force?: boolean }): Promise<boolean> {\n const force = opts?.force === true\n if (!force) {\n const marker = await readSeedTiddler(client, CLIP_BRIDGE_MARKER_TITLE)\n if (marker !== undefined) return false\n }\n const existing = await readSeedTiddler(client, CLIP_BRIDGE_DOC_TITLE)\n let wrote = false\n if (force || existing === undefined) {\n await client.put({\n title: CLIP_BRIDGE_DOC_TITLE,\n text: CLIP_BRIDGE_DOC_TEXT,\n type: 'text/markdown',\n tags: ['docs', 'dsh-docs'],\n })\n wrote = true\n }\n await writeSeedMarker(client, CLIP_BRIDGE_MARKER_TITLE)\n return wrote\n}\n\n/** 反初始化: remove the doc tiddler + its one-shot marker (idempotent). */\nexport async function unseedClipBridge(client: TiddlyWebClient): Promise<{ removed: string[] }> {\n const removed: string[] = []\n for (const title of [CLIP_BRIDGE_DOC_TITLE, CLIP_BRIDGE_MARKER_TITLE]) {\n const t = await readSeedTiddler(client, title)\n if (t !== undefined) {\n await client.delete(title)\n removed.push(title)\n }\n }\n return { removed }\n}","/**\n * Generated from the wiki bundle (do not hand-edit the constant).\n * Source: render.bundle.json\n *\n * The \"原生渲染路由\" TW plugin, packaged as a TiddlyWiki plugin bundle\n * (`{\"tiddlers\": {...}}`), seeded into fresh wikis by seedRenderRoute. It\n * registers a server-side route module (`server-routes/render.js`,\n * module-type: route) that renders wiki text into a pure HTML fragment with\n * internal links rewritten to the same-origin DSH proxy hash.\n *\n * @module dsh-tiddlywiki/host/seed-render\n */\nimport type { TiddlyWebClient } from './tw-api.ts'\nimport { readSeedTiddler, writeSeedMarker } from './seed-util.ts'\n\n/** The packaged plugin tiddler title (a TW system tiddler, type application/json). */\nexport const RENDER_PLUGIN_TITLE = '$:/plugins/dsh/render'\n\n/** One-time marker: presence means \"the route was offered once — hands off\". */\nexport const RENDER_MARKER_TITLE = '$:/plugins/dsh-tiddlywiki/seed-render'\n\n/**\n * On-disk tiddler filename for RENDER_PLUGIN_TITLE (TW's FileSystemAdaptor\n * encoding: `$:` → `$__`, `/` → `_`). Callers that must restart TW so the\n * seeded server route loads poll for this file after a seed run.\n */\nexport const RENDER_PLUGIN_FILE = '$__plugins_dsh_render.json'\n\n/** The bundle's JSON text (`{\"tiddlers\": {...}}`), exactly as TW stores it. */\nexport const RENDER_BUNDLE_TEXT = \"{\\n \\\"tiddlers\\\": {\\n \\\"$:/plugins/dsh/render/plugin.info\\\": {\\n \\\"title\\\": \\\"$:/plugins/dsh/render/plugin.info\\\",\\n \\\"type\\\": \\\"application/json\\\",\\n \\\"text\\\": \\\"{\\\\\\\"title\\\\\\\":\\\\\\\"$:/plugins/dsh/render\\\\\\\",\\\\\\\"name\\\\\\\":\\\\\\\"DSH Wiki Render\\\\\\\",\\\\\\\"description\\\\\\\":\\\\\\\"把 wiki 文本原生渲染成 HTML 片段(POST /render 服务端路由),供 DSH 回复流工具卡与 wiki 链接跳转使用\\\\\\\",\\\\\\\"author\\\\\\\":\\\\\\\"dsh-tiddlywiki\\\\\\\",\\\\\\\"version\\\\\\\":\\\\\\\"0.2.0\\\\\\\",\\\\\\\"plugin-type\\\\\\\":\\\\\\\"plugin\\\\\\\"}\\\"\\n },\\n \\\"$:/plugins/dsh/render/server-routes/render.js\\\": {\\n \\\"title\\\": \\\"$:/plugins/dsh/render/server-routes/render.js\\\",\\n \\\"type\\\": \\\"application/javascript\\\",\\n \\\"module-type\\\": \\\"route\\\",\\n \\\"text\\\": \\\"/*\\\\\\\\\\\\ntitle: $:/plugins/dsh/render/server-routes/render.js\\\\ntype: application/javascript\\\\nmodule-type: route\\\\n\\\\nPOST /render — native TiddlyWiki → HTML fragment for the DSH reply stream\\\\n\\\\nBody (JSON in state.data):\\\\n { title } render an existing tiddler's body with ITS type\\\\n { text, type?, contextTitle?, parseAsInline? }\\\\n render arbitrary content as a fragment\\\\n\\\\\\\\*/\\\\n\\\\\\\"use strict\\\\\\\";\\\\n\\\\n/*\\\\nThe fragment's internal wiki links are rewritten to the SAME-ORIGIN DSH proxy\\\\nhash (`/dsh-tiddlywiki/tw/#<title>`) by overriding the `tv-wikilink-template`\\\\nvariable while rendering. Verified against tiddlywiki 5.4.1's link widget:\\\\n`link.js` expands `$uri_encoded$` (encodeURIComponentExtended) into the href,\\\\nso the default `#Title` becomes `/dsh-tiddlywiki/tw/#Title`. The DSH page's\\\\ndocument-level click interceptor + the embedded TW's hash navigation\\\\n(story.js reads the hash via decodeURIComponentSafe) both handle that href —\\\\nno post-hoc string rewriting needed.\\\\n*/\\\\nvar WIKILINK_TEMPLATE = \\\\\\\"/dsh-tiddlywiki/tw/#$uri_encoded$\\\\\\\";\\\\n\\\\nexports.methods = [\\\\\\\"POST\\\\\\\"];\\\\n\\\\nexports.path = /^\\\\\\\\/render$/;\\\\n\\\\nexports.info = {\\\\n\\\\tpriority: 100\\\\n};\\\\n\\\\nfunction sendJson(response,status,payload) {\\\\n\\\\tresponse.writeHead(status,{\\\\\\\"Content-Type\\\\\\\":\\\\\\\"application/json; charset=utf-8\\\\\\\",\\\\\\\"Cache-Control\\\\\\\":\\\\\\\"no-store\\\\\\\"});\\\\n\\\\tresponse.end(JSON.stringify(payload));\\\\n}\\\\n\\\\nexports.handler = function(request,response,state) {\\\\n\\\\tvar body;\\\\n\\\\ttry {\\\\n\\\\t\\\\tbody = JSON.parse(state.data || \\\\\\\"{}\\\\\\\");\\\\n\\\\t} catch(e) {\\\\n\\\\t\\\\tsendJson(response,400,{ok:false,error:\\\\\\\"invalid JSON body\\\\\\\"});\\\\n\\\\t\\\\treturn;\\\\n\\\\t}\\\\n\\\\ttry {\\\\n\\\\t\\\\tvar variables = {\\\\n\\\\t\\\\t\\\\t\\\\\\\"tv-wikilink-template\\\\\\\": WIKILINK_TEMPLATE\\\\n\\\\t\\\\t};\\\\n\\\\t\\\\tif(typeof body.title === \\\\\\\"string\\\\\\\" && body.title.length > 0) {\\\\n\\\\t\\\\t\\\\t// Render an existing tiddler's body with ITS OWN content type (block\\\\n\\\\t\\\\t\\\\t// parse, so headings / tables / lists render natively). The type must\\\\n\\\\t\\\\t\\\\t// be honored: dsh-tiddlywiki writes agent notes as `text/markdown`\\\\n\\\\t\\\\t\\\\t// (tiddlywiki/markdown is enabled), and hardcoding wikitext rendered\\\\n\\\\t\\\\t\\\\t// every markdown note as source (`## x` became a wikitext list).\\\\n\\\\t\\\\t\\\\t// $tw.utils.getParser() falls back to text/vnd.tiddlywiki when no\\\\n\\\\t\\\\t\\\\t// parser is registered for the type, so passing the raw type is safe.\\\\n\\\\t\\\\t\\\\tvar tiddler = state.wiki.getTiddler(body.title);\\\\n\\\\t\\\\t\\\\tif(!tiddler) {\\\\n\\\\t\\\\t\\\\t\\\\tsendJson(response,404,{ok:false,notFound:true,title:body.title});\\\\n\\\\t\\\\t\\\\t\\\\treturn;\\\\n\\\\t\\\\t\\\\t}\\\\n\\\\t\\\\t\\\\tvariables.currentTiddler = body.title;\\\\n\\\\t\\\\t\\\\tvar tiddlerType = typeof tiddler.fields.type === \\\\\\\"string\\\\\\\" && tiddler.fields.type.length > 0\\\\n\\\\t\\\\t\\\\t\\\\t? tiddler.fields.type\\\\n\\\\t\\\\t\\\\t\\\\t: \\\\\\\"text/vnd.tiddlywiki\\\\\\\";\\\\n\\\\t\\\\t\\\\tvar html = state.wiki.renderText(\\\\\\\"text/html\\\\\\\",tiddlerType,tiddler.fields.text,{\\\\n\\\\t\\\\t\\\\t\\\\tparseAsInline: false,\\\\n\\\\t\\\\t\\\\t\\\\tvariables: variables\\\\n\\\\t\\\\t\\\\t});\\\\n\\\\t\\\\t\\\\tresponse.writeHead(200,{\\\\\\\"Content-Type\\\\\\\":\\\\\\\"text/html; charset=utf-8\\\\\\\",\\\\\\\"Cache-Control\\\\\\\":\\\\\\\"no-store\\\\\\\"});\\\\n\\\\t\\\\t\\\\tresponse.end(html);\\\\n\\\\t\\\\t\\\\treturn;\\\\n\\\\t\\\\t}\\\\n\\\\t\\\\tif(typeof body.text === \\\\\\\"string\\\\\\\") {\\\\n\\\\t\\\\t\\\\t// Render arbitrary wiki text (contextTitle gives currentTiddler so\\\\n\\\\t\\\\t\\\\t// links/transclusions resolve in that tiddler's context). `type`\\\\n\\\\t\\\\t\\\\t// optionally selects the parser (e.g. text/markdown); the default is\\\\n\\\\t\\\\t\\\\t// wikitext, and unknown types fall back to it inside TW.\\\\n\\\\t\\\\t\\\\tif(typeof body.contextTitle === \\\\\\\"string\\\\\\\" && body.contextTitle.length > 0) {\\\\n\\\\t\\\\t\\\\t\\\\tvariables.currentTiddler = body.contextTitle;\\\\n\\\\t\\\\t\\\\t}\\\\n\\\\t\\\\t\\\\tvar inline = body.parseAsInline === true;\\\\n\\\\t\\\\t\\\\tvar bodyType = typeof body.type === \\\\\\\"string\\\\\\\" && body.type.length > 0 ? body.type : \\\\\\\"text/vnd.tiddlywiki\\\\\\\";\\\\n\\\\t\\\\t\\\\tvar textHtml = state.wiki.renderText(\\\\\\\"text/html\\\\\\\",bodyType,body.text,{\\\\n\\\\t\\\\t\\\\t\\\\tparseAsInline: inline,\\\\n\\\\t\\\\t\\\\t\\\\tvariables: variables\\\\n\\\\t\\\\t\\\\t});\\\\n\\\\t\\\\t\\\\tresponse.writeHead(200,{\\\\\\\"Content-Type\\\\\\\":\\\\\\\"text/html; charset=utf-8\\\\\\\",\\\\\\\"Cache-Control\\\\\\\":\\\\\\\"no-store\\\\\\\"});\\\\n\\\\t\\\\t\\\\tresponse.end(textHtml);\\\\n\\\\t\\\\t\\\\treturn;\\\\n\\\\t\\\\t}\\\\n\\\\t\\\\tsendJson(response,400,{ok:false,error:\\\\\\\"body must provide \\\\\\\\\\\\\\\"title\\\\\\\\\\\\\\\" or \\\\\\\\\\\\\\\"text\\\\\\\\\\\\\\\"\\\\\\\"});\\\\n\\\\t} catch(e) {\\\\n\\\\t\\\\tsendJson(response,500,{ok:false,error:String((e && e.message) || e)});\\\\n\\\\t}\\\\n};\\\\n\\\"\\n }\\n }\\n}\"\n\n/**\n * Seed the \"原生渲染路由\" TW plugin exactly once per wiki (mirrors the\n * send-to-agent one-shot policy). The marker records the offer; afterwards the\n * bundle is user-owned — deleting it and restarting dsh web does NOT recreate\n * it, and edits are never overwritten. With `opts.force` the bundle is\n * (re)written even when it already exists and the marker is (re)written — the\n * settings page uses this for \"重新初始化\". Returns whether a bundle was\n * written this call. Throws when a read fails (the seed registry reports it as `ok:false`).\n */\nexport async function seedRenderRoute(client: TiddlyWebClient, opts?: { force?: boolean }): Promise<boolean> {\n const force = opts?.force === true\n if (!force) {\n const marker = await readSeedTiddler(client, RENDER_MARKER_TITLE)\n if (marker !== undefined) return false\n }\n const existing = await readSeedTiddler(client, RENDER_PLUGIN_TITLE)\n let wrote = false\n if (force || existing === undefined) {\n await client.put({\n title: RENDER_PLUGIN_TITLE,\n text: RENDER_BUNDLE_TEXT,\n type: 'application/json',\n tags: [],\n // TW only REGISTERS a wiki tiddler as a plugin (boot.js\n // registerPluginTiddlers) when the OUTER tiddler carries a\n // `plugin-type` field — without it the bundle is never unpacked and its\n // server-routes module is never defined, so /render 404s. The metadata\n // mirrors what the live send-to-agent bundle's tiddler carries.\n 'plugin-type': 'plugin',\n name: 'DSH Wiki Render',\n author: 'dsh-tiddlywiki',\n version: '0.2.0',\n description: '把 wiki 文本原生渲染成 HTML 片段(POST /render 服务端路由),供 DSH 回复流工具卡与 wiki 链接跳转使用',\n })\n wrote = true\n }\n // Record the offer regardless, so an existing bundle (upgrade from a\n // pre-seed wiki) also becomes user-owned from here on.\n await writeSeedMarker(client, RENDER_MARKER_TITLE)\n return wrote\n}\n","/**\n * Extensible plugin config (design doc §13, config panel).\n *\n * Two layers:\n * base — the cordis `config:` block (profile composition, defaults);\n * overrides — a user-editable config tiddler ($:/plugins/dsh-tiddlywiki/config,\n * a JSON string) written by the settings page.\n * The tiddler overlays the base (tiddler wins), so future config fields just\n * extend the shape — no schema, no @deepseek-ai dependency, and the config\n * travels with the wiki's git history.\n *\n * @module dsh-tiddlywiki/host/config\n */\nimport type { TiddlyWebClient } from './tw-api.ts'\n\n/** Config tiddler (JSON string) where the settings page stores overrides. */\nexport const CONFIG_TIDDLER = '$:/plugins/dsh-tiddlywiki/config'\n\n/** TW frontend API base tiddler, pointed at the same-origin DSH proxy. */\nexport const TW_WEB_HOST_TIDDLER = '$:/config/tiddlyweb/host'\n\n/** The legacy default TW host value this plugin replaces with the proxy. */\nexport const TW_WEB_HOST_DEFAULT = '$protocol$//$host$/'\n\n/**\n * Default dark palette the embedded TW switches to when DSH is dark\n * (mirrored in src/client/theme-sync.ts — the two bundles cannot share code).\n */\nexport const DARK_PALETTE_DEFAULT = '$:/palettes/CupertinoDark'\n\n/** Extensible, loose plugin config shape (future fields just appear here). */\nexport interface PluginConfigShape {\n /**\n * 本地剪藏桥(书签小工具):DSH 进程内监听 127.0.0.1 的 HTTP 桥,接收\n * 书签 POST 的 {title,url,text} 并写入 wiki。`enabled` 保存后立即生效\n * (每个请求实时判定);`token` 非空时校验 `x-clip-token` 头;`tag` 为\n * 剪藏笔记默认 tag;`port` 改动需重启 dsh web(监听只在启动时绑定一次)。\n */\n bridge?: { enabled?: boolean; port?: number; token?: string; tag?: string }\n note?: { tag?: string }\n /**\n * 注入给每个会话的系统提示词(v0.21.0)。默认 `slim`:只保留工具 schema\n * 表达不了的治理约定(同步纪律 / 标签 / 链接格式),不再手抄工具参数;\n * `full` 额外附一份**由工具注册表实时生成**的参数索引(永不过期)。\n * `override` 整段替换内置正文,`extra` 永远追加在最后。\n * 设置页保存后无需重启 dsh web:section 会重新注册,当前会话下一步即生效。\n */\n prompt?: { enabled?: boolean; mode?: 'slim' | 'full'; extra?: string; override?: string }\n git?: { autoCommit?: boolean; debounceMs?: number; remote?: string; branch?: string }\n ui?: {\n /** 是否在界面右下角显示「快速笔记」悬浮按钮(默认 true)。 */\n showQuickNote?: boolean\n /**\n * 是否在聊天输入框上方(conversation.input.dock 槽位)显示「快速笔记」\n * 快捷按钮(默认 true)。与其它插件(todo/cost-meter/goal/queue/git-graph\n * 等)注入的内容同处一个纵向排列的 dock 区,天然不重叠。\n */\n showQuickNoteDock?: boolean\n /**\n * 点击「快速笔记」后的打开方式(默认 `native`):\n * `native` = 直接弹出 TW 原生编辑器(新建/恢复草稿);`card` = 弹出现有\n * 的 Markdown 快速笔记卡片。设置页可切换。\n */\n quickNoteMode?: 'native' | 'card'\n /** 左侧侧边栏 TW 入口的显示名称(默认「TiddlyWiki」,可自定义)。 */\n sidebarLabel?: string\n /** 是否显示 TW 面板右下角的「状态/重载」悬浮按钮(默认 true)。 */\n showPanelStatus?: boolean\n /** 是否在界面右下角显示「同步」悬浮按钮(默认 true)。 */\n showSyncButton?: boolean\n /**\n * 嵌入式 TW 是否跟随 DSH 深浅主题(默认 true):DSH 暗色时把 TW 活动\n * palette 临时切到 `darkPalette`,浅色时恢复用户原 palette;仅内存生效,\n * 不会写回 wiki。\n */\n followDshTheme?: boolean\n /** DSH 暗色时 TW 使用的 palette tiddler 标题(默认 CupertinoDark)。 */\n darkPalette?: string\n /**\n * 一键发送给 Agent(TW 笔记 → DSH 会话注入)。\n * `enabled: false` 时路由 403、TW 端按钮点击提示未启用;`token` 非空时\n * 路由要求请求头 `x-send-to-agent-token` 与之匹配;`endpoint` 可覆盖\n * TW 端默认的 `location.origin + /dsh-tiddlywiki`。\n */\n sendToAgent?: { enabled?: boolean; endpoint?: string; token?: string }\n /**\n * 「所有文章」两列分页页面的每页条数(默认 10)。页面在渲染时实时读取\n * 该值(见 seed-all-articles),改后无需重新初始化。\n */\n allArticles?: { pageSize?: number }\n /**\n * 会话顶部「知识库」Tab 的显示名称(默认「知识库」)。该 Tab 显示本会话\n * 产生/读取/检索过的 wiki 笔记汇总(conversation.view 槽位,见\n * src/client/session-summary.ts)。\n */\n tabLabel?: string\n /** 是否在会话顶部显示「知识库」Tab(会话相关 wiki 汇总,默认 true)。 */\n showSessionTab?: boolean\n /**\n * 是否在 DSH 右侧边栏(new rightbar)提供 TiddlyWiki 入口/Tab(默认\n * true):在 rightbar 首页(guide 页)注册「TiddlyWiki 知识库」入口盒,\n * 点击即在右侧栏以 tab 形式打开完整 TW 编辑器(与聊天并排)。老版本\n * DSH 无 rightbar 时该配置不生效(客户端自动跳过)。\n */\n showRightbarTab?: boolean\n }\n uiLanguage?: string\n [key: string]: unknown\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** Deep-merge: `over` wins; nested plain objects merge recursively. */\nexport function deepMerge(base: Record<string, unknown>, over: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = { ...base }\n for (const [key, value] of Object.entries(over)) {\n if (value === undefined) continue\n if (isPlainObject(value) && isPlainObject(out[key])) {\n out[key] = deepMerge(out[key] as Record<string, unknown>, value)\n } else {\n out[key] = value\n }\n }\n return out\n}\n\n/**\n * Runtime config store: caches the override tiddler and exposes the effective\n * (merged) config. `load` runs at startup and after every write/restart.\n */\nexport class ConfigStore {\n private overrides: PluginConfigShape = {}\n\n constructor(private readonly base: PluginConfigShape) {}\n\n /** Effective config = cordis base overlaid with the user override tiddler. */\n get(): PluginConfigShape {\n return deepMerge(this.base, this.overrides) as PluginConfigShape\n }\n\n /**\n * Reload the override tiddler (no-op when the wiki is unavailable).\n *\n * A MISSING tiddler (404) legitimately means \"no overrides\" and clears the\n * cache. A transient FAILURE (wiki restarting, timeout, 5xx) must NOT: wiping\n * the cache on error silently reverted every user setting for the rest of the\n * session, and the next `set()` then persisted a config tiddler without the\n * user's other overrides.\n */\n async load(client: TiddlyWebClient | undefined): Promise<void> {\n if (client === undefined) {\n this.overrides = {}\n return\n }\n let tiddler\n try {\n tiddler = await client.get(CONFIG_TIDDLER)\n } catch (err) {\n // Keep whatever we already have (usually the previous overrides).\n console.warn('[dsh-tiddlywiki] config tiddler unreadable, keeping cached overrides:', err instanceof Error ? err.message : err)\n return\n }\n if (tiddler === undefined) {\n this.overrides = {}\n return\n }\n try {\n const parsed = JSON.parse(typeof tiddler.text === 'string' ? tiddler.text : '') as unknown\n this.overrides = isPlainObject(parsed) ? (parsed as PluginConfigShape) : {}\n } catch {\n // Malformed JSON in the config tiddler: keep the cache rather than\n // silently reverting to the cordis base.\n console.warn('[dsh-tiddlywiki] config tiddler is not valid JSON, keeping cached overrides')\n }\n }\n\n /**\n * Merge a patch into the overrides and persist the tiddler.\n *\n * The patch is merged onto the STORED overrides (re-read here), not just the\n * in-memory cache: if the startup `load()` failed transiently, saving one\n * setting must not drop every other override the user had stored.\n */\n async set(client: TiddlyWebClient, patch: PluginConfigShape): Promise<PluginConfigShape> {\n let stored: PluginConfigShape = this.overrides\n try {\n const tiddler = await client.get(CONFIG_TIDDLER)\n if (tiddler !== undefined && typeof tiddler.text === 'string') {\n const parsed = JSON.parse(tiddler.text) as unknown\n if (isPlainObject(parsed)) stored = parsed as PluginConfigShape\n }\n } catch {\n // Unreadable config tiddler → merge onto the in-memory cache.\n }\n this.overrides = deepMerge(stored, patch) as PluginConfigShape\n await client.put({\n title: CONFIG_TIDDLER,\n text: JSON.stringify(this.overrides, null, 2),\n type: 'application/json',\n tags: [],\n })\n return this.get()\n }\n}\n","/**\n * Unified seed registry (design: every one-time \"与 dsh 联动需要 wiki 预置\"\n * item is a SeedDef here).\n *\n * Three tiers (v0.16.22):\n * - CORE seeds (`core: true`) are functionally required by the plugin's\n * own features — the startup path seeds exactly these (non-force) and\n * they can never be 反初始化'd.\n * - STARTER seeds (`startup: true`, removable) are docs / examples that\n * give a fresh wiki a working「文档中心」out of the box — the startup path\n * ALSO seeds them on first install (safe-skip: same-named tiddlers are\n * never overwritten), and the user can 反初始化 them anytime.\n * - OPTIONAL seeds (`core: false`, `startup: false`) are nice-to-have\n * content (首页 / 所有文章 / 自定义样式 / menubar 顶栏主题自适应 / 剪藏桥\n * 说明文档) — they are NEVER auto-seeded; the user opts in from the\n * settings page「初始化」section (重新初始化) and can opt out again with\n * 反初始化 (remove).\n *\n * Each seed owns:\n * - `check` — current state (present / missing / needs-update) for the UI;\n * - `run(force)` — non-force keeps the ONE-SHOT / user-owned semantics\n * (write only when missing, never overwrite), force (re)writes the\n * built-in content and (re)records the marker;\n * - `remove` — optional + starter seeds only: delete the seeded tiddlers +\n * markers, returning the wiki to the \"never seeded\" state.\n *\n * Registry: doc-note / starter-docs / send-to-agent / render-route /\n * home-index / all-articles / ui-styles / menubar-theme / clip-bridge /\n * tw-web-host.\n *\n * @module dsh-tiddlywiki/host/seeds\n */\nimport type { TiddlyWebClient } from './tw-api.ts'\nimport type { PromptToolSummary } from './prompt.ts'\nimport { hashText, readSeedMarker, readSeedTiddler, writeSeedMarker } from './seed-util.ts'\nimport { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { seedDocNote, unseedDocNote, docNoteText, DOC_NOTE_TITLE, SEED_MARKER_TITLE } from './seed-notes.ts'\nimport { seedStarterDocs, unseedStarterDocs, STARTER_DOCS_ITEMS, STARTER_DOCS_MARKER_TITLE } from './seed-starter-docs.ts'\nimport { seedSendToAgent, SEND_TO_AGENT_PLUGIN_TITLE, SEND_TO_AGENT_MARKER_TITLE, SEND_TO_AGENT_BUNDLE_TEXT } from './seed-send-to-agent.ts'\nimport { seedHomeIndex, unseedHomeIndex, HOME_INDEX_ITEMS, HOME_INDEX_MARKER_TITLE } from './seed-home.ts'\nimport { seedAllArticles, unseedAllArticles, ALL_ARTICLES_TITLE, ALL_ARTICLES_MARKER_TITLE, ALL_ARTICLES_TEXT } from './seed-all-articles.ts'\nimport { seedUiStyles, unseedUiStyles, UI_STYLE_ITEMS, UI_STYLES_MARKER_TITLE } from './seed-ui-styles.ts'\nimport { seedMenubarTheme, unseedMenubarTheme, MENUBAR_THEME_TIDDLER, MENUBAR_THEME_MARKER_TITLE, MENUBAR_THEME_TEXT } from './seed-menubar-theme.ts'\nimport { seedClipBridge, unseedClipBridge, CLIP_BRIDGE_DOC_TITLE, CLIP_BRIDGE_MARKER_TITLE, CLIP_BRIDGE_DOC_TEXT } from './seed-clip-bridge.ts'\nimport { seedRenderRoute, RENDER_PLUGIN_TITLE, RENDER_MARKER_TITLE, RENDER_BUNDLE_TEXT } from './seed-render.ts'\nimport { TW_WEB_HOST_TIDDLER, TW_WEB_HOST_DEFAULT } from './config.ts'\nimport { TW_PROXY_PATH } from './wiki.ts'\n\n/** One seed's status as reported to the settings page. */\nexport interface SeedStatus {\n id: string\n title: string\n description: string\n /** true = target tiddler(s) present (or host value correct). */\n present: boolean\n /**\n * true = the seed can be 反初始化 (removed). Core seeds that the plugin's\n * own features depend on are never removable from the settings page.\n */\n removable: boolean\n /** Human detail, e.g. which tiddlers are missing. */\n detail?: string\n /**\n * true = the BUILT-IN content moved on since this wiki was seeded, so\n * 「重新初始化」would bring something new (v0.22.0 content hashes). Only set\n * for seeds that declare `content`.\n */\n updateAvailable?: boolean\n /**\n * true = the stored tiddler no longer matches what we wrote (a human edited\n * it); `undefined` = cannot tell (marker written before hashes existed);\n * `false` = untouched. The settings page warns before overwriting.\n */\n userModified?: boolean\n /** true = the marker tiddler predates v0.22.0 (no hashes recorded yet). */\n legacyMarker?: boolean\n}\n\n/** Result of running one seed. */\nexport interface SeedRunResult {\n id: string\n ok: boolean\n /** true = something was written this call. */\n wrote: boolean\n detail?: string\n error?: string\n}\n\n/** Context a seed needs (client to the live TW server). */\nexport interface SeedContext {\n client: TiddlyWebClient\n /**\n * Live tool registry summary, so generated seed content (the doc note's tool\n * list) can never go stale (v0.22.0). Absent in headless callers — generated\n * content then degrades to a pointer instead of an outdated list.\n */\n tools?: readonly PromptToolSummary[]\n}\n\n/**\n * Wait until a file exists on disk (polling), up to `timeoutMs`.\n *\n * TW's syncer flushes REST writes to the filesystem on a ~250ms task timer, so\n * a freshly seeded tiddler is not on disk the moment PUT resolves. A caller\n * that must restart TW right after (so a seeded SERVER-route plugin loads) has\n * to wait for the flush first — otherwise the restarted TW boots from a stale\n * snapshot, loses every in-memory write that had not been flushed yet, and the\n * seeded route is missing.\n *\n * `newerThanMs` makes the wait meaningful when the file ALREADY exists: it must\n * also have been (re)written after that timestamp, which is what a re-seed\n * produces. Without it an existing file would satisfy the wait immediately and\n * the caller would restart TW over in-flight writes (the v0.18.0 regression the\n * seeds-admin verify test caught).\n */\nexport async function waitForFileWrite(filePath: string, timeoutMs = 8_000, pollMs = 150, newerThanMs?: number): Promise<boolean> {\n const deadline = Date.now() + timeoutMs\n for (;;) {\n try {\n if (existsSync(filePath)) {\n if (newerThanMs === undefined) return true\n if (statSync(filePath).mtimeMs >= newerThanMs) return true\n }\n } catch { /* transient */ }\n if (Date.now() >= deadline) return false\n await new Promise<void>((r) => setTimeout(r, pollMs))\n }\n}\n\n/** The only seed that carries a TW SERVER route — it needs a TW restart to load. */\nexport const RESTART_REQUIRED_SEED_IDS = ['render-route'] as const\n\n/**\n * Flush sentinel. TW's REST layer answers 204 as soon as the tiddler is in the\n * in-memory store; the filesystem syncer writes it on a ~250ms timer. A restart\n * in that window boots from the OLD snapshot and silently loses every write\n * still queued — the v0.18.0 review caught it for the render plugin file, but\n * any write could be lost (force-all repeatedly lost `tw-web-host`).\n *\n * A sentinel alone is NOT enough (v0.22.0). TW's syncer defers a title whose\n * last save is younger than `throttleInterval` (`$:/config/SyncThrottleInterval`,\n * default 1s): `chooseNextTask()` picks the first title that `hasChanged` AND is\n * ready, and SKIPS throttled ones — so the sentinel (a different, never-saved\n * title) can land while another dirty title is still deferred. v0.19.0's\n * \"probe landed ⇒ queue drained\" assumption is therefore false, and the extra\n * marker writes of v0.22.0 were enough to make `tw-web-host` the deferred title\n * every time. See `flushPendingWrites()` for the two-phase fix.\n */\nexport const FLUSH_PROBE_TITLE = '$:/plugins/dsh-tiddlywiki/flush-probe'\n/**\n * 哨兵 tiddler 落盘后的**文件名片段**(用 `includes` 匹配,不写死扩展名)。\n *\n * 为什么不写死 `.tid`:TW 的文件系统适配器按内容类型决定扩展名——`text/plain`\n * 写成 `.txt` + `.txt.meta`,`application/json` 写成 `.json`,只有\n * `text/vnd.tiddlywiki` 才是 `.tid`。v0.19.0 的实现传了 `type: 'text/plain'`\n * 却去等 `.tid`,于是**永远等不到**,而所有调用方都只把返回值当 warning ——\n * 「排干 syncer」其实一直没生效(v0.19.1 的 selftest 断言把它抓了出来)。\n * 现在哨兵用默认 wikitext(落 `.tid`),判定再按「文件名含本片段 + 内容含本次\n * 随机戳」来做,扩展名怎么变都不会静默失效。\n */\nexport const FLUSH_PROBE_FILE_HINT = 'dsh-tiddlywiki_flush-probe'\n\n/** TW default `$:/config/SyncThrottleInterval` (ms) when the tiddler is absent. */\nconst DEFAULT_SYNC_THROTTLE_MS = 1_000\n\n/** Read TW's sync throttle interval (ms), clamped; a read failure → the default. */\nasync function readSyncThrottleMs(client: TiddlyWebClient): Promise<number> {\n try {\n const raw = (await client.get('$:/config/SyncThrottleInterval'))?.text?.trim()\n const parsed = raw === undefined || raw.length === 0 ? Number.NaN : Number.parseInt(raw, 10)\n if (!Number.isFinite(parsed) || parsed < 0) return DEFAULT_SYNC_THROTTLE_MS\n return Math.min(parsed, 10_000)\n } catch {\n return DEFAULT_SYNC_THROTTLE_MS\n }\n}\n\n/**\n * Flush the syncer queue before a restart.\n *\n * Returns true only when the store is provably QUIESCENT. Two phases, because a\n * single probe can overtake a throttled title (see the doc block above):\n *\n * 1. write probe A and wait for A on disk — this drains everything the syncer\n * is willing to write now;\n * 2. wait out one throttle window (no writes of ours in between), then write\n * probe B and wait for IT. Any title still dirty after phase 1 becomes\n * ready inside that window and is written, so only the probe remains.\n *\n * Returns false on timeout / a failed probe PUT; callers treat that as a warning\n * (the operation still proceeds — this is best-effort hardening, not a gate).\n */\nexport async function flushPendingWrites(client: TiddlyWebClient, tiddlersDir: string, timeoutMs = 12_000): Promise<boolean> {\n // ⚠️ 判定不能靠 mtime(v0.19.1 教训之二):哨兵是在等待**紧接**它之前写的,\n // Windows 上新文件的 mtime 可能落在 `Date.now()` 同一/前一个时钟刻,\n // `mtime >= startedAt` 于是永远不成立、轮询到超时返回 false。改成写一段唯一\n // 内容、轮询文件里是否出现它:内容一致才算真的落盘,与时钟精度无关。\n const deadline = Date.now() + timeoutMs\n const probeAndWait = async (phase: string): Promise<boolean> => {\n const stamp = `flush ${phase} ${Date.now()} ${Math.random().toString(36).slice(2)}`\n try {\n // 不传 type → TW 按默认 wikitext 存成 .tid。\n await client.put({ title: FLUSH_PROBE_TITLE, text: stamp })\n } catch {\n return false\n }\n for (;;) {\n try {\n for (const name of readdirSync(tiddlersDir)) {\n if (!name.includes(FLUSH_PROBE_FILE_HINT)) continue\n try {\n if (readFileSync(join(tiddlersDir, name), 'utf8').includes(stamp)) return true\n } catch { /* transient read failure */ }\n }\n } catch { /* tiddlers dir not readable yet */ }\n if (Date.now() >= deadline) return false\n await new Promise<void>((r) => setTimeout(r, 150))\n }\n }\n if (!(await probeAndWait('a'))) return false\n // Phase 2: let every throttled title become ready and be written. The probe\n // itself was just saved, so it is throttled too — which is exactly why the\n // second send must wait out the window (otherwise probe B could land while\n // probe A's contemporaries are still deferred).\n const waitMs = Math.min((await readSyncThrottleMs(client)) + 400, 4_000)\n await new Promise<void>((r) => setTimeout(r, waitMs))\n return probeAndWait('b')\n}\n\n/** Did one of the RESTART_REQUIRED seeds actually write something? */\nexport function needsRestartAfterSeeds(results: Array<{ id: string; ok: boolean; wrote: boolean }>): boolean {\n return results.some((r) => r.ok && r.wrote && (RESTART_REQUIRED_SEED_IDS as readonly string[]).includes(r.id))\n}\n\n/** A registered seed: check current state + run (optionally force) + remove. */\nexport interface SeedDef {\n id: string\n title: string\n description: string\n /**\n * Core seeds are seeded automatically on startup (功能必需) and can never\n * be 反初始化'd — removing them would break a plugin feature.\n */\n core: boolean\n /**\n * STARTER tier (v0.16.22): docs / examples that the startup path ALSO seeds\n * on first install (safe-skip: same-named tiddlers never overwritten), but\n * which stay removable via「反初始化」. Meaningful only when `core` is false.\n */\n startup?: boolean\n /**\n * One-shot marker tiddler (v0.22.0): its `hashes` record what the built-in\n * content looked like when this wiki was seeded, which is what lets the\n * settings page tell 「内置内容有更新」 from 「用户自己改过」.\n */\n markerTitle?: string\n /**\n * The tiddlers this seed writes, in a stable order (title → text). Declaring\n * it opts the seed into update detection + marker hashes; the seed modules\n * export the very constants their writers use, so the two cannot drift.\n */\n content?: (ctx: SeedContext) => Promise<Array<{ title: string; text: string }>>\n check(ctx: SeedContext): Promise<SeedStatus>\n run(ctx: SeedContext, force: boolean): Promise<SeedRunResult>\n /** Non-core seeds only: delete the seeded tiddlers + markers (反初始化). */\n remove?(ctx: SeedContext): Promise<SeedRunResult>\n}\n\n/**\n * Presence probe for a seed's `check`. A 404 means \"missing\"; every other\n * failure PROPAGATES (the caller `checkAllSeeds` turns it into a failed check)\n * — swallowing it here would report \"缺失\" for a service that is merely\n * unavailable and invite an overwrite.\n */\nconst presentOf = (ctx: SeedContext, title: string): Promise<boolean> =>\n ctx.client.get(title).then((t) => t !== undefined)\n\n/** Build the per-item detail from an unseed result. */\nconst removedDetail = (id: string, removed: string[]): SeedRunResult => ({\n id,\n ok: true,\n wrote: false,\n detail: removed.length > 0\n ? `已移除:${removed.join('、')}`\n : '本就不存在,无需移除',\n})\n\n/** Metadata shared by every seed and echoed in its status/result objects. */\ninterface SeedMeta {\n id: string\n title: string\n description: string\n core: boolean\n /** STARTER tier: also auto-seeded on first install (removable). */\n startup?: boolean\n /** One-shot marker tiddler (enables content-hash bookkeeping). */\n markerTitle?: string\n}\n\ntype SeedWriter = (client: TiddlyWebClient, opts?: { force?: boolean; tools?: readonly PromptToolSummary[] }) => Promise<boolean>\ntype SeedUnseeder = (client: TiddlyWebClient) => Promise<{ removed: string[] }>\n/** The tiddlers a seed writes, in a stable order (title → built-in text). */\ntype SeedContent = (ctx: SeedContext) => Promise<Array<{ title: string; text: string }>>\n\n/**\n * Compare a seed's built-in content with what is in the wiki + with the hashes\n * recorded when it was seeded (v0.22.0).\n *\n * Decision table (per tiddler):\n * - recorded hash === current built-in → nothing changed;\n * - recorded hash !== current built-in → the built-in MOVED ON (`updateAvailable`);\n * - stored text !== recorded hash → a human edited it (`userModified: true`);\n * - no recorded hash + text === built-in → LEGACY marker (pre-hash); ownership\n * is proven, so the caller may upgrade the marker;\n * - no recorded hash + text !== built-in → either the user edited it or the\n * built-in changed: `updateAvailable` with `userModified: undefined`\n * (unknown) — never silently overwrite on that basis.\n */\nasync function inspectSeedContent(\n ctx: SeedContext,\n content: Array<{ title: string; text: string }>,\n markerTitle: string | undefined,\n): Promise<{ missing: string[]; updateAvailable: boolean; userModified: boolean | undefined; legacyMarker: boolean }> {\n const markerState = markerTitle === undefined ? {} : await readSeedMarker(ctx.client, markerTitle)\n const hashes = markerState.marker?.hashes ?? {}\n const missing: string[] = []\n let updateAvailable = false\n let userModified: boolean | undefined = false\n let legacyMarker = markerState.legacy === true\n for (const item of content) {\n const current = await readSeedTiddler(ctx.client, item.title)\n if (current === undefined) {\n missing.push(item.title)\n continue\n }\n const stored = typeof current.text === 'string' ? current.text : ''\n const builtinHash = hashText(item.text)\n const recorded = hashes[item.title]\n if (recorded !== undefined) {\n if (recorded !== builtinHash) updateAvailable = true\n if (hashText(stored) !== recorded) userModified = true\n } else if (stored === item.text) {\n legacyMarker = true\n } else {\n updateAvailable = true\n if (userModified !== true) userModified = undefined\n }\n }\n return { missing, updateAvailable, userModified, legacyMarker }\n}\n\n/**\n * Record/refresh the marker's content hashes after a run (v0.22.0). Only\n * tiddlers whose stored text equals the built-in text are hashed — that is what\n * \"we own this content\" means, and it keeps user-edited copies out of the\n * bookkeeping. Existing hashes are merged (never dropped), so a run that\n * skipped an edited tiddler does not forget the others.\n */\nasync function refreshSeedMarker(\n content: SeedContent,\n markerTitle: string,\n ctx: SeedContext,\n previousHashes: Record<string, string>,\n): Promise<void> {\n try {\n const hashes: Record<string, string> = { ...previousHashes }\n for (const item of await content(ctx)) {\n const current = await readSeedTiddler(ctx.client, item.title)\n const stored = current === undefined ? undefined : (typeof current.text === 'string' ? current.text : '')\n if (stored === item.text) hashes[item.title] = hashText(item.text)\n }\n // Skip the PUT when nothing changed: the startup path runs this for every\n // typed seed on EVERY boot, and a no-op marker write per boot is noise (and\n // one more REST write before the syncer flush that follows).\n const unchanged = Object.keys(hashes).length === Object.keys(previousHashes).length\n && Object.keys(hashes).every((key) => hashes[key] === previousHashes[key])\n if (!unchanged) await writeSeedMarker(ctx.client, markerTitle, hashes)\n } catch (err) {\n // Bookkeeping only — the content write already succeeded.\n console.warn(`[dsh-tiddlywiki] seed marker hash refresh failed (${markerTitle}):`, err instanceof Error ? err.message : err)\n }\n}\n\n/**\n * Define a seed from a small spec, killing the id/title/description\n * triplication and the try/catch boilerplate every registry entry used to\n * repeat by hand:\n * - `presentTitle` → default check: is that tiddler present?\n * - `check` → custom presence check (overrides presentTitle).\n * - `write` → built-in content writer; true = (re)written this call.\n * - `run` → custom runner (overrides the standard write wrapper;\n * e.g. tw-web-host honors a user-chosen host value).\n * - `unseed` → remove the seeded tiddlers + markers (removable seeds).\n */\nfunction defineSeed(meta: SeedMeta, impl: {\n presentTitle?: string\n check?: (ctx: SeedContext) => Promise<SeedStatus>\n write?: SeedWriter\n run?: (ctx: SeedContext, force: boolean) => Promise<SeedRunResult>\n unseed?: SeedUnseeder\n content?: SeedContent\n}): SeedDef {\n const { id, title, description, core, startup, markerTitle } = meta\n const removable = !core\n if (impl.check === undefined && (impl.presentTitle === undefined || impl.presentTitle.length === 0)) {\n // Fail fast at module load: a seed without a presence probe would query an\n // EMPTY title, always report \"missing\", and be re-run on every boot.\n throw new Error(`seed \"${id}\" needs either presentTitle or check`)\n }\n if (impl.run === undefined && impl.write === undefined) {\n throw new Error(`seed \"${id}\" needs either write or run`)\n }\n // Presence only — every seed gets one. `content`-aware seeds WRAP this below\n // (v0.22.0) instead of replacing it, so a seed's own present/detail semantics\n // survive and the hash fields are added on top.\n const presenceCheck: SeedDef['check'] = impl.check ?? (async (ctx) => {\n const present = await presentOf(ctx, impl.presentTitle ?? '')\n return { id, title, description, present, removable, detail: present ? '已存在' : '缺失' }\n })\n /**\n * A `content`-declaring seed reports update/modification state ALONGSIDE its\n * own presence check. ⚠️ This must wrap the custom check, not be skipped when\n * one exists: `starter-docs` / `home-index` / `ui-styles` all have custom\n * checks AND custom runners, and an earlier version of this wiring silently\n * left them without update detection (the gap was invisible because the E2E\n * only covered `doc-note`).\n */\n const check: SeedDef['check'] = impl.content === undefined ? presenceCheck : async (ctx) => {\n const base = await presenceCheck(ctx)\n const state = await inspectSeedContent(ctx, await impl.content!(ctx), markerTitle)\n const missingDetail = state.missing.length > 0 ? `缺失:${state.missing.join('、')}` : (base.detail ?? '缺失')\n const bits: string[] = [base.present ? (base.detail ?? '已存在') : missingDetail]\n if (state.updateAvailable) bits.push('内置内容有更新')\n if (state.userModified === true) bits.push('本地已修改')\n else if (state.userModified === undefined && state.updateAvailable) bits.push('无法确认是否本地已修改')\n else if (state.legacyMarker) bits.push('更新检测尚未启用(重新初始化一次即可)')\n return {\n ...base,\n updateAvailable: state.updateAvailable,\n userModified: state.userModified,\n legacyMarker: state.legacyMarker,\n detail: bits.join(' · '),\n }\n }\n /**\n * Run wrapper. It ALWAYS wraps (v0.22.0): a custom `run` still needs the\n * marker-hash refresh afterwards, which is what makes「内置内容有更新」work\n * for the seeds that write through their own runner.\n */\n const run: SeedDef['run'] = async (ctx, force) => {\n try {\n // Capture the marker BEFORE the writer rewrites it, so refreshed hashes\n // merge onto what we already knew instead of erasing it.\n const previous = markerTitle === undefined ? {} : (await readSeedMarker(ctx.client, markerTitle)).marker?.hashes ?? {}\n const result = impl.run !== undefined\n ? await impl.run(ctx, force)\n : await (async (): Promise<SeedRunResult> => {\n const wrote = await impl.write!(ctx.client, { force, tools: ctx.tools })\n return { id, ok: true, wrote, detail: wrote ? (force ? '已重新初始化' : '已写入') : (force ? '内容已是最新(未重写)' : '已存在,跳过') }\n })()\n if (impl.content !== undefined && markerTitle !== undefined && result.ok) {\n await refreshSeedMarker(impl.content, markerTitle, ctx, previous)\n }\n return result\n } catch (err) {\n return { id, ok: false, wrote: false, error: err instanceof Error ? err.message : String(err) }\n }\n }\n const remove: SeedDef['remove'] = impl.unseed === undefined ? undefined : async (ctx) => {\n try {\n return removedDetail(id, (await impl.unseed!(ctx.client)).removed)\n } catch (err) {\n return { id, ok: false, wrote: false, error: err instanceof Error ? err.message : String(err) }\n }\n }\n return {\n id, title, description, core,\n ...(startup === undefined ? {} : { startup }),\n ...(markerTitle === undefined ? {} : { markerTitle }),\n ...(impl.content === undefined ? {} : { content: impl.content }),\n check, run,\n ...(remove === undefined ? {} : { remove }),\n }\n}\n\n/** The full registry, in display order. */\nexport const SEED_DEFS: SeedDef[] = [\n defineSeed(\n { id: 'doc-note', title: '插件说明笔记', description: '「dsh-tiddlywiki 插件说明」——入门说明笔记(首次安装默认写入;ONE-SHOT,用户可改可删,标记 dsh-docs 自动进首页「📚 插件文档」栏)。', markerTitle: SEED_MARKER_TITLE, core: false, startup: true },\n { content: async (ctx) => [{ title: DOC_NOTE_TITLE, text: docNoteText(ctx.tools ?? []) }], presentTitle: DOC_NOTE_TITLE, write: seedDocNote, unseed: unseedDocNote },\n ),\n defineSeed(\n { id: 'starter-docs', title: '示例与文档(汇总模板 / 教程 / 主题页示例)', description: '新手文档中心起步包:主题汇总页·模板、教程(按主题/标签做汇总页)、三个可直接运行的示例主题页(日志 / 决策记录 / 排障)。全部打 dsh-docs 标签,自动出现在首页「📚 插件文档」栏;纯示例无个人数据,同名 tiddler 已存在则安全跳过,不会覆盖。首次安装默认写入,可反初始化。', markerTitle: STARTER_DOCS_MARKER_TITLE, core: false, startup: true },\n {\n content: async () => STARTER_DOCS_ITEMS.map((i) => ({ title: i.title, text: i.text })),\n check: async (ctx) => {\n const missing: string[] = []\n for (const item of STARTER_DOCS_ITEMS) {\n if (!(await presentOf(ctx, item.title))) missing.push(item.title)\n }\n return { id: 'starter-docs', title: '示例与文档(汇总模板 / 教程 / 主题页示例)', description: '新手文档中心起步包:主题汇总页·模板、教程(按主题/标签做汇总页)、三个可直接运行的示例主题页(日志 / 决策记录 / 排障)。全部打 dsh-docs 标签,自动出现在首页「📚 插件文档」栏;纯示例无个人数据,同名 tiddler 已存在则安全跳过,不会覆盖。首次安装默认写入,可反初始化。', present: missing.length === 0, removable: true, detail: missing.length === 0 ? '已存在' : `缺失:${missing.join('、')}` }\n },\n write: seedStarterDocs,\n unseed: unseedStarterDocs,\n },\n ),\n defineSeed(\n { id: 'send-to-agent', title: '「发送给 Agent」按钮', description: 'TW 笔记工具栏「发送给 Agent」按钮插件($:/plugins/dsh/send-to-agent)——把笔记一键注入 DSH 会话。', markerTitle: SEND_TO_AGENT_MARKER_TITLE, core: true },\n { content: async () => [{ title: SEND_TO_AGENT_PLUGIN_TITLE, text: SEND_TO_AGENT_BUNDLE_TEXT }], presentTitle: SEND_TO_AGENT_PLUGIN_TITLE, write: seedSendToAgent },\n ),\n defineSeed(\n { id: 'render-route', title: '原生渲染路由(/render)', description: 'TW 服务端路由插件($:/plugins/dsh/render,server-routes/render.js)——把 wiki 文本在运行中的 TW 里原生渲染成 HTML 片段,回复流工具卡与 wiki 链接跳转依赖它。seed 写入后需重启 TW 使路由生效。', markerTitle: RENDER_MARKER_TITLE, core: true },\n { content: async () => [{ title: RENDER_PLUGIN_TITLE, text: RENDER_BUNDLE_TEXT }], presentTitle: RENDER_PLUGIN_TITLE, write: seedRenderRoute },\n ),\n defineSeed(\n { id: 'home-index', title: '首页(主页 / 所有标签 / 标签笔记)', description: '默认主页:四象限待办 + 「所有标签」「所有文章」入口;所有标签:标签统计 + Agent 区块(纯 Agent / Agent+人工);标签笔记:按标签浏览。系统提示承诺的首页由这里 seed,主页(🏠 主页)同时写入 $:/DefaultTiddlers。', markerTitle: HOME_INDEX_MARKER_TITLE, core: false },\n {\n content: async () => HOME_INDEX_ITEMS.map((i) => ({ title: i.title, text: i.text })),\n check: async (ctx) => {\n const missing: string[] = []\n for (const item of HOME_INDEX_ITEMS) {\n if (!(await presentOf(ctx, item.title))) missing.push(item.title)\n }\n return { id: 'home-index', title: '首页(主页 / 所有标签 / 标签笔记)', description: '默认主页:四象限待办 + 「所有标签」「所有文章」入口;所有标签:标签统计 + Agent 区块(纯 Agent / Agent+人工);标签笔记:按标签浏览。系统提示承诺的首页由这里 seed,主页(🏠 主页)同时写入 $:/DefaultTiddlers。', present: missing.length === 0, removable: true, detail: missing.length === 0 ? '已存在' : `缺失:${missing.join('、')}` }\n },\n write: seedHomeIndex,\n unseed: unseedHomeIndex,\n },\n ),\n defineSeed(\n { id: 'all-articles', title: '所有文章(两列分页总览)', description: '「所有文章」——全部条目分两列(🤖 Agent 撰写 / 👤 人工·人类)各自分页展示。每页条数取插件设置 ui.allArticles.pageSize(默认 10)。', markerTitle: ALL_ARTICLES_MARKER_TITLE, core: false },\n { content: async () => [{ title: ALL_ARTICLES_TITLE, text: ALL_ARTICLES_TEXT }], presentTitle: ALL_ARTICLES_TITLE, write: seedAllArticles, unseed: unseedAllArticles },\n ),\n defineSeed(\n { id: 'ui-styles', title: '自定义样式(编辑器美化 / 窄屏侧栏 / menubar 加高 / 批注弹窗)', description: '5 张通用样式表(tag $:/tags/Stylesheet):编辑器美化(CodeMirror 字体/光标/行号)、标题与按钮区分开、侧边栏窄屏自动隐藏(<960px)、menubar 顶栏加高、批注弹窗美化。纯样式无个人数据。', markerTitle: UI_STYLES_MARKER_TITLE, core: false },\n {\n content: async () => UI_STYLE_ITEMS.map((i) => ({ title: i.title, text: i.text })),\n check: async (ctx) => {\n const missing: string[] = []\n for (const item of UI_STYLE_ITEMS) {\n if (!(await presentOf(ctx, item.title))) missing.push(item.title)\n }\n return { id: 'ui-styles', title: '自定义样式(编辑器美化 / 窄屏侧栏 / menubar 加高 / 批注弹窗)', description: '5 张通用样式表(tag $:/tags/Stylesheet):编辑器美化(CodeMirror 字体/光标/行号)、标题与按钮区分开、侧边栏窄屏自动隐藏(<960px)、menubar 顶栏加高、批注弹窗美化。纯样式无个人数据。', present: missing.length === 0, removable: true, detail: missing.length === 0 ? '已存在' : `缺失:${missing.join('、')}` }\n },\n write: seedUiStyles,\n unseed: unseedUiStyles,\n },\n ),\n defineSeed(\n { id: 'menubar-theme', title: 'menubar 顶栏主题自适应', description: '样式表覆盖($:/plugins/dsh-tiddlywiki/menubar-theme,tag $:/tags/Stylesheet)——把 tiddlywiki/menubar 顶栏从「默认色映射的蓝色」改为跟随当前 palette 的 background/foreground,随 DSH 主题切换($:/palette 翻转)自动换色。', markerTitle: MENUBAR_THEME_MARKER_TITLE, core: false },\n { content: async () => [{ title: MENUBAR_THEME_TIDDLER, text: MENUBAR_THEME_TEXT }], presentTitle: MENUBAR_THEME_TIDDLER, write: seedMenubarTheme, unseed: unseedMenubarTheme },\n ),\n defineSeed(\n { id: 'clip-bridge', title: '本地剪藏桥(书签小工具)', description: '「本地剪藏桥 + 书签小工具」使用说明(Markdown 文档,带书签代码/启用步骤/安全说明):DSH 监听 127.0.0.1 端口接收剪藏请求,浏览器书签一键把当前页标题/URL/选中文字写进知识库(配置 bridge.*,默认 clip 标签)。真功能在插件运行时代码里,此 seed 只预置说明文档。', markerTitle: CLIP_BRIDGE_MARKER_TITLE, core: false },\n { content: async () => [{ title: CLIP_BRIDGE_DOC_TITLE, text: CLIP_BRIDGE_DOC_TEXT }], presentTitle: CLIP_BRIDGE_DOC_TITLE, write: seedClipBridge, unseed: unseedClipBridge },\n ),\n defineSeed(\n { id: 'tw-web-host', title: 'TW 前端 API 基址(同源代理)', description: '把 $:/config/tiddlyweb/host 指向 DSH 同源代理,嵌入式 TW 才能经 DSH origin 访问(远程访问模式的前提)。', core: true },\n {\n check: async (ctx) => {\n // `run` (below) honors a USER-CHOSEN base: it only writes when the\n // value is missing or still the legacy default. The check must agree,\n // otherwise a deliberate custom host is reported as「缺失」.\n //\n // Read WITHOUT swallowing (v0.19.0): the old bare `catch` turned any\n // transient failure into `present: false` — i.e. it reported the user's\n // deliberately customised base as「缺失」and invited the settings page's\n // 「重新初始化」to overwrite it. A failure propagates to checkAllSeeds,\n // which reports「检查失败」instead of a bogus missing state.\n const current = (await ctx.client.get(TW_WEB_HOST_TIDDLER))?.text?.trim()\n const present = typeof current === 'string' && current.length > 0 && current !== TW_WEB_HOST_DEFAULT\n const detail = present\n ? (current === TW_PROXY_PATH ? `已指向 ${TW_PROXY_PATH}` : `已指向自定义基址 ${current}(保留,不会覆盖)`)\n : `当前:${current ?? '(缺失)'},应为 ${TW_PROXY_PATH}`\n return { id: 'tw-web-host', title: 'TW 前端 API 基址(同源代理)', description: '把 $:/config/tiddlyweb/host 指向 DSH 同源代理,嵌入式 TW 才能经 DSH origin 访问(远程访问模式的前提)。', present, removable: false, detail }\n },\n run: async (ctx, force) => {\n try {\n // Read without swallowing: a transient failure must NOT look like\n // \"missing\" (that would overwrite a user's custom base).\n const tiddler = await ctx.client.get(TW_WEB_HOST_TIDDLER)\n const current = tiddler?.text?.trim()\n // Non-force keeps the ensure semantics: write only when missing or still\n // the legacy default (a user override pointing elsewhere is honored).\n if (!force && current !== undefined && current !== TW_WEB_HOST_DEFAULT) {\n return { id: 'tw-web-host', ok: true, wrote: false, detail: '已指向自定义基址,未覆盖' }\n }\n await ctx.client.put({ title: TW_WEB_HOST_TIDDLER, text: TW_PROXY_PATH, type: 'text/plain', tags: [] })\n return { id: 'tw-web-host', ok: true, wrote: true, detail: force ? '已重新初始化(强制写回代理基址)' : '已写入代理基址' }\n } catch (err) {\n return { id: 'tw-web-host', ok: false, wrote: false, error: err instanceof Error ? err.message : String(err) }\n }\n },\n },\n ),\n]\n\n/** Check every seed, returning statuses in registry order. */\nexport async function checkAllSeeds(ctx: SeedContext): Promise<SeedStatus[]> {\n const out: SeedStatus[] = []\n for (const def of SEED_DEFS) {\n try {\n out.push(await def.check(ctx))\n } catch (err) {\n out.push({\n id: def.id,\n title: def.title,\n description: def.description,\n present: false,\n removable: !def.core,\n detail: `检查失败:${err instanceof Error ? err.message : String(err)}`,\n })\n }\n }\n return out\n}\n\n/**\n * Run one seed (or all, when id is undefined). Non-force = one-shot semantics;\n * force = manual \"重新初始化\" from the settings page. A manual run with no id\n * (重新初始化 all) covers every registry item, core and optional alike.\n */\nexport async function runSeedById(ctx: SeedContext, id: string | undefined, force: boolean): Promise<SeedRunResult[]> {\n const targets = id === undefined ? SEED_DEFS : SEED_DEFS.filter((d) => d.id === id)\n if (targets.length === 0) {\n return [{ id: id ?? '', ok: false, wrote: false, error: `unknown seed: ${id}` }]\n }\n const out: SeedRunResult[] = []\n for (const def of targets) {\n out.push(await def.run(ctx, force))\n }\n return out\n}\n\n/**\n * Startup path: seed the CORE items (功能必需:发送给 Agent 按钮 + 原生渲染\n * 路由 + TW 前端 API 基址) AND the STARTER items (首次安装默认:插件说明 +\n * 示例与文档), all non-force (write only what is missing — a same-named\n * tiddler already present is NEVER overwritten, so user data stays user data).\n * Remaining optional seeds (首页 / 所有文章 / 自定义样式 / menubar 顶栏主题自\n * 适应) are never forced on users — they opt in from the settings page\n * 「初始化」section.\n */\nexport async function runAllSeeds(ctx: SeedContext): Promise<SeedRunResult[]> {\n const out: SeedRunResult[] = []\n for (const def of SEED_DEFS) {\n if (!def.core && def.startup !== true) continue\n out.push(await def.run(ctx, false))\n }\n return out\n}\n\n/**\n * 反初始化 (remove): delete one non-core seed's (starter 或 optional) seeded\n * tiddlers + markers, or all non-core seeds when `id` is undefined. Core\n * seeds are functionally required and cannot be removed — a direct request\n * for one returns an error result (and is skipped when removing all).\n */\nexport async function removeSeedById(ctx: SeedContext, id: string | undefined): Promise<SeedRunResult[]> {\n if (id === undefined) {\n const out: SeedRunResult[] = []\n for (const def of SEED_DEFS) {\n if (def.core || def.remove === undefined) continue\n out.push(await def.remove(ctx))\n }\n return out\n }\n const def = SEED_DEFS.find((d) => d.id === id)\n if (def === undefined) {\n return [{ id, ok: false, wrote: false, error: `unknown seed: ${id}` }]\n }\n if (def.core) {\n return [{ id, ok: false, wrote: false, error: '该 seed 为核心项(功能必需),不可反初始化' }]\n }\n if (def.remove === undefined) {\n return [{ id, ok: false, wrote: false, error: '该 seed 不支持移除' }]\n }\n return [await def.remove(ctx)]\n}\n","/**\n * Small text helpers shared by the host modules (v0.20.0).\n *\n * `snippetOf` used to exist as two identical private copies (tools.ts for the\n * `tiddlywiki_recent` render, routes.ts for the quick-note/recent/search JSON)\n * with two different default widths — a drift waiting to happen.\n *\n * @module dsh-tiddlywiki/host/text-util\n */\n\n/** Flat one-line snippet: whitespace collapsed, truncated with an ellipsis. */\nexport function snippetOf(text: string, max = 160): string {\n const flat = text.replace(/\\s+/g, ' ').trim()\n return flat.length <= max ? flat : `${flat.slice(0, max)}…`\n}\n","/**\n * 共享写策略(v0.19.1):所有「写 TiddlyWiki」的路径(agent 工具 + HTTP 路由)\n * 必须走这里,别再各自拼 PUT body。\n *\n * 为什么抽出这一层:TW 的 REST PUT 是**整体替换**。v0.19.0 只在工具层修了\n * 「以已有条目为基底、不传 tags 就保留标签与自定义字段」,而 `/note`、`/edit`\n * 这些人类入口仍然直接 `put({title,text,tags})` —— 用一个同名标题保存就会把\n * 人类笔记的标签和 `q`/`due`/`clip-url` 等自定义字段清空(v0.19.1 审计发现)。\n * 把策略收成一个模块,两条路径共用,回归就不会再各修各的。\n *\n * 另外两个必须记住的事实:\n * - 单条 GET 返回的自定义字段**嵌套在 `fields` 下**(core-server 的\n * get-tiddler.js 把 knownFields 之外的字段收进 `fields`),PUT 却接受平铺\n * 字段,所以 `cleanTiddler()` 必须摊平,否则 rename/append/trash 静默丢字段;\n * - `revision`(GET 的 changeCount)是唯一「刚 PUT 还没落盘」时也存在的并发\n * 令牌——那种条目没有 `modified`,两个令牌都支持。\n *\n * @module dsh-tiddlywiki/host/write-policy\n */\nimport type { Tiddler } from './tw-api.ts'\nimport { parseTiddlerDate, toIsoDateString } from './tw-api.ts'\n\n/** 约定标签:标记「由 Agent 撰写」的笔记(新建时自动补打)。 */\nexport const AGENT_WRITTEN_TAG = 'agent-written'\n\n/** 约定标签:标记「Agent 撰写后又经人类编辑」的笔记。 */\nexport const HUMAN_EDITED_TAG = 'human-edited'\n\n/** Agent / 人类笔记的默认内容类型(TW 对无 type 的条目按 wikitext 解析)。 */\nexport const DEFAULT_NOTE_TYPE = 'text/markdown'\n\n/**\n * 构造 PUT body 时跳过的字段(身份/内容/TW 自己的时间戳)。\n *\n * ⚠️ `type` **不在**这里(v0.20.1 修复):它曾被误列为跳过字段,于是\n * `cleanTiddler(existing)` 把条目的内容类型丢掉,`finalTypeForWrite()` 随后又\n * 补上默认的 `text/markdown`(或干脆不写 type → TW 回落 `text/vnd.tiddlywiki`)。\n * 结果:任何「覆盖已有条目」的路径都会静默改掉类型——\n * - `text/css` 的样式条目被改成 `text/markdown` → 整篇 CSS 被当 Markdown 渲染;\n * - `text/markdown` 笔记被改成 `text/vnd.tiddlywiki` → `##`/`**粗体**`/表格全按\n * wikitext 解析(磁盘上 `.md` + `.meta` 也变成 `.tid`)。\n * 内容类型是条目的解析方式,必须与 tags/自定义字段一样按「以已有条目为基底」保留。\n */\nconst CLEAN_SKIP_FIELDS = new Set(['title', 'text', 'tags', 'created', 'modified', 'fields'])\n\n/**\n * 调用方**不得**通过 `fields` 覆盖的保留字段:它们是条目的身份/内容,时间戳\n * 归 TW 所有。`type` 故意放行——那是显式指定内容类型的正规入口。\n */\nconst RESERVED_TIDDLER_FIELDS = new Set(['title', 'text', 'tags', 'created', 'modified'])\n\n/**\n * TW 服务端 GET/listing 会为缺省 `type` 的条目**补上** `text/vnd.tiddlywiki`\n * (get-tiddler.js / get-tiddlers-json.js 都有 `type = type || \"text/vnd.tiddlywiki\"`),\n * 而 `bag` 也是响应里补的传输字段。回写时把这两个透传回去要么污染文件(type)、\n * 要么被 TW 自行剥掉(bag/revision),所以从展示与回写里都排除。\n */\nconst TRANSPORT_FIELDS = new Set(['bag', 'revision', 'recipe', 'uri', 'permissions'])\n\n/** 写入冲突(乐观并发令牌不匹配):路由层把它映射成 HTTP 409。 */\nexport class WriteConflictError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'WriteConflictError'\n }\n}\n\n/**\n * 把单条 GET 返回的字段**摊平**成模型/路由可直接读的形状:\n * 嵌套 `fields` 里的自定义字段提到顶层,`bag`/`revision` 等传输字段排除。\n */\nexport function flattenTiddlerFields(tiddler: Tiddler): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(tiddler)) {\n if (key === 'title' || key === 'text' || key === 'tags' || key === 'fields') continue\n if (TRANSPORT_FIELDS.has(key)) continue\n out[key] = value\n }\n const nested = tiddler.fields\n if (typeof nested === 'object' && nested !== null && !Array.isArray(nested)) {\n for (const [key, value] of Object.entries(nested)) {\n if (key === 'title' || key === 'text' || key === 'tags' || key === 'fields') continue\n if (value === undefined) continue\n if (out[key] === undefined) out[key] = value\n }\n }\n return out\n}\n\n/**\n * 由已有条目构造一份可 PUT 的副本:字段摊平、tags 归数组、TW 拥有的时间戳丢掉。\n * `type` 保留(含服务端补的默认值——语义等价,且能防止 Markdown 笔记被回退成\n * wikitext;用户若显式改名内容类型,走 `fields.type`)。\n */\nexport function cleanTiddler(t: Tiddler): Tiddler {\n const out: Tiddler = { title: t.title, text: t.text ?? '', tags: t.tags ?? [] }\n for (const [k, v] of Object.entries(t)) {\n if (CLEAN_SKIP_FIELDS.has(k) || TRANSPORT_FIELDS.has(k)) continue\n out[k] = v\n }\n const nested = t.fields\n if (typeof nested === 'object' && nested !== null && !Array.isArray(nested)) {\n for (const [k, v] of Object.entries(nested)) {\n if (CLEAN_SKIP_FIELDS.has(k) || TRANSPORT_FIELDS.has(k) || v === undefined) continue\n out[k] = v\n }\n }\n return out\n}\n\n/** 合并调用方显式提供的自定义字段(跳过保留字段与 undefined)。 */\nexport function applyCustomFields(tiddler: Tiddler, fields: Record<string, unknown> | undefined): void {\n if (fields === undefined || fields === null || typeof fields !== 'object') return\n for (const [key, value] of Object.entries(fields)) {\n if (RESERVED_TIDDLER_FIELDS.has(key)) continue\n if (value === undefined) continue\n tiddler[key] = value\n }\n}\n\n/** 归一化工具/路由的 `tags` 参数(非数组或全空 → undefined)。 */\nexport function normalizeTagArg(tags: unknown): string[] | undefined {\n if (!Array.isArray(tags)) return undefined\n const list = tags.filter((t): t is string => typeof t === 'string' && t.trim().length > 0).map((t) => t.trim())\n return list.length > 0 ? list : undefined\n}\n\n/**\n * 计算最终标签:\n * - 已存在的条目 → 调用方给什么就是什么(不给 = 保留基底里的原标签);\n * - 新条目 + `agentTag` → 自动补 `agent-written`(`$:/` 系统条目除外);\n * - 新条目 + 人类入口(`agentTag: false`,如快速笔记 / `/note`)→ 不补,\n * 人类写的笔记不该被标成 agent 撰写。\n */\nexport function finalTagsForWrite(title: string, existing: Tiddler | undefined, tags: string[], agentTag: boolean): string[] {\n if (existing !== undefined) return tags\n if (!agentTag) return tags\n if (title.startsWith('$:/')) return tags\n if (tags.includes(AGENT_WRITTEN_TAG)) return tags\n return [...tags, AGENT_WRITTEN_TAG]\n}\n\n/**\n * 计算最终内容类型(原地改 tiddler):显式 type 优先;**只有新建条目**才回落\n * Markdown,`$:/` 条目保持 TW 默认。\n *\n * v0.20.1:`isNew=false`(覆盖既有条目)时**绝不**发明类型——基底里有什么就是\n * 什么;基底没有 type(极少数情况)也保持「没有」,让 TW 继续按 wikitext 处理,\n * 而不是把它升级成 Markdown。改类型是调用方的显式动作(`fields.type`)。\n */\nexport function finalTypeForWrite(title: string, tiddler: Tiddler, isNew = true): { defaulted: boolean } {\n if (typeof tiddler.type === 'string' && tiddler.type.length > 0) return { defaulted: false }\n if (!isNew) return { defaulted: false }\n if (title.startsWith('$:/')) return { defaulted: false }\n tiddler.type = DEFAULT_NOTE_TYPE\n return { defaulted: true }\n}\n\nexport interface BuildWriteOptions {\n /** 已有条目(`wiki.get()` 的结果;读失败不得吞成 undefined)。 */\n existing?: Tiddler | undefined\n /** 显式标签:给了就整体替换,没给就保留基底里的原标签。 */\n tags?: string[] | undefined\n /** 显式自定义字段(逐个覆盖在基底之上)。 */\n fields?: Record<string, unknown> | undefined\n /** 新建条目时是否自动补 `agent-written`(agent 工具 true,人类入口 false)。 */\n agentTag?: boolean\n /** 新建条目且未显式给标签时使用的默认标签(人类入口,如 note.tag)。 */\n defaultTags?: string[] | undefined\n}\n\n/**\n * 构造一次写要 PUT 的 tiddler。\n *\n * - 已有条目 → 以其为基底(标签、自定义字段、**内容类型**全部保留);\n * - 新条目 → `{title, text}` + 默认/显式标签(`$:/` 不带 agent 标签);\n * - `fields` 逐个覆盖;`type` 未指定时**仅新建条目**补 Markdown(`$:/` 除外)。\n */\nexport function buildWriteTiddler(\n title: string,\n text: string,\n options: BuildWriteOptions = {},\n): { tiddler: Tiddler; typeDefaulted: boolean } {\n const { existing, fields } = options\n const agentTag = options.agentTag !== false\n const tiddler: Tiddler = existing !== undefined ? { ...cleanTiddler(existing), title, text } : { title, text }\n if (options.tags !== undefined) {\n const finalTags = finalTagsForWrite(title, existing, options.tags, agentTag)\n if (finalTags.length > 0) tiddler.tags = finalTags\n else delete tiddler.tags\n } else if (existing === undefined) {\n const seedTags = options.defaultTags !== undefined && options.defaultTags.length > 0 ? options.defaultTags : []\n const finalTags = finalTagsForWrite(title, undefined, seedTags, agentTag)\n if (finalTags.length > 0) tiddler.tags = finalTags\n }\n applyCustomFields(tiddler, fields)\n // v0.20.1: the Markdown default is for NEW tiddlers only — an overwrite keeps\n // the content type it was read with (see finalTypeForWrite).\n const { defaulted } = finalTypeForWrite(title, tiddler, existing === undefined)\n return { tiddler, typeDefaulted: defaulted }\n}\n\nexport interface ConflictTokens {\n expectedModified?: string\n expectedRevision?: string | number\n force?: boolean\n}\n\n/**\n * 乐观并发守卫:调用方可以传 `tiddlywiki_get` 读到的 `modified` 或 `revision`;\n * 与当前值不一致就拒绝写入,避免覆盖人类在 TW 编辑器里的并发修改。\n */\nexport function assertNoConflict(title: string, existing: Tiddler | undefined, expected: ConflictTokens): void {\n if (expected.force === true || existing === undefined) return\n const wantsToken = expected.expectedModified !== undefined || expected.expectedRevision !== undefined\n if (!wantsToken) return\n // 两种日期格式都接受:get 给模型的是 ISO,REST 存的是 TW 紧凑格式。\n const expectedMs = parseTiddlerDate(expected.expectedModified)\n const currentMs = parseTiddlerDate(existing.modified)\n if (expectedMs !== undefined && currentMs !== undefined && expectedMs === currentMs) return\n const currentRevision = existing.revision\n if (expected.expectedRevision !== undefined && currentRevision !== undefined\n && String(currentRevision) === String(expected.expectedRevision)) return\n throw new WriteConflictError(\n `写入冲突:tiddler「${title}」在你读取之后已被改动(当前 revision=${currentRevision ?? '?'} modified=${toIsoDateString(existing.modified) ?? '?'};`\n + `期望 revision=${expected.expectedRevision ?? '?'} modified=${expected.expectedModified ?? '?'})。`\n + '请重新读取最新内容后重试;确认要用你的版本覆盖时可传 force: true。',\n )\n}\n","/**\n * DSH webserver routes for dsh-tiddlywiki (design doc §10).\n *\n * Every route is registered with an explicit method whitelist and wrapped in\n * `guardHandler` (see the registration block at the bottom of this file); the\n * authoritative list, including the admin routes registered in admin.ts:\n *\n * | route | method | purpose |\n * |----------------------------------|--------|------------------------------------------|\n * | /dsh-tiddlywiki/status | GET | panel health (service / url / git / tag) |\n * | /dsh-tiddlywiki/note | POST | quick-note → independent tiddler |\n * | /dsh-tiddlywiki/edit | POST | save + open in TW's native editor (draft)|\n * | /dsh-tiddlywiki/tags | GET | tag vocabulary + counts (limit/sort) |\n * | /dsh-tiddlywiki/recent | GET | recently modified notes |\n * | /dsh-tiddlywiki/get | GET | one tiddler for the quick-note picker |\n * | /dsh-tiddlywiki/search | GET | keyword search (reply-stream tool card) |\n * | /dsh-tiddlywiki/render | POST | TW render → sanitized HTML fragment |\n * | /dsh-tiddlywiki/sync | POST | pull → commit → push (mutex) |\n * | /dsh-tiddlywiki/upload | POST | upload into wiki/files/ |\n * | /dsh-tiddlywiki/restart | POST | one-click retry/restart of the TW child |\n * | /dsh-tiddlywiki/session/summary | POST | per-session wiki summary (§「知识库」Tab) |\n * | /dsh-tiddlywiki/agent/sessions | GET | send-to-agent session picker |\n * | /dsh-tiddlywiki/agent/modes | GET | agent / permission preset rosters |\n * | /dsh-tiddlywiki/agent/send | POST | deliver a note into a session |\n * | /dsh-tiddlywiki/agent/create | POST | create session (+workspace) and deliver |\n * | /dsh-tiddlywiki/api/* | any | passthrough to the TW service (JSON) |\n * | /dsh-tiddlywiki/tw/* | any | SAME-ORIGIN TW proxy (index + files + API)|\n * | /dsh-tiddlywiki/admin/state | GET | settings page: info + catalog + config |\n * | /dsh-tiddlywiki/admin/info | POST | write tiddlywiki.info plugins/themes |\n * | /dsh-tiddlywiki/admin/config | POST | write the config tiddler |\n * | /dsh-tiddlywiki/admin/restart | POST | restart the TW child |\n * | /dsh-tiddlywiki/admin/seeds | GET | seed status |\n * | /dsh-tiddlywiki/admin/seeds/run | POST | run seeds (include force re-init) |\n * | /dsh-tiddlywiki/admin/seeds/remove| POST | 反初始化 (remove non-core seeds) |\n *\n * Matching is exact-over-prefix, so the exact routes win and the `/api` /\n * `/tw` prefixes catch the rest. Client calls are same-origin (the DSH web\n * server), so no CORS is involved. The `/tw` proxy is the remote-access\n * bridge: it serves the ENTIRE TW frontend to the browser through the DSH\n * origin (see TW_PROXY_PATH in wiki.ts), so the embedded editor works no\n * matter which host/domain the user reaches DSH on.\n *\n * Titles under `$:/plugins/dsh-tiddlywiki/` are NEVER served to a caller\n * (`isBlockedProxyTitle`): that namespace holds the shared tokens and the git\n * remote, and every caller-supplied title must pass through the predicate.\n *\n * @module dsh-tiddlywiki/host/routes\n */\nimport type { IncomingHttpHeaders, IncomingMessage, ServerResponse } from 'node:http'\nimport { mkdir, writeFile, stat } from 'node:fs/promises'\nimport { basename, extname, isAbsolute, join } from 'node:path'\nimport { Readable } from 'node:stream'\nimport type { TiddlyWebClient } from './tw-api.ts'\nimport { isBinaryType, toIsoDateString } from './tw-api.ts'\nimport type { WikiServer } from './wiki.ts'\nimport type { GitFace, GitStatusView } from './git.ts'\nimport { PATH_PREFIX, TW_PROXY_PREFIX, TW_PROXY_PATH } from './wiki.ts'\nimport { writeSessionSummary, type SessionQueryFace } from './session-summary.ts'\nimport { readBody, readBodyBuffer, json, guardHandler, errorStatus, rejectCrossSiteWrite, rejectNonRead, safeTokenEqual, MAX_PROXY_BODY_BYTES, MAX_UPLOAD_BYTES } from './http.ts'\nimport { sanitizeTwFragment } from './sanitize.ts'\nimport { flushPendingWrites } from './seeds.ts'\nimport { snippetOf } from './text-util.ts'\nimport { WriteConflictError, assertNoConflict, buildWriteTiddler, flattenTiddlerFields } from './write-policy.ts'\n\nexport { writeSessionSummary, SESSION_SUMMARY_PREFIX } from './session-summary.ts'\nexport type { SessionQueryFace, SessionSummaryResult } from './session-summary.ts'\n\nexport const ROUTE_PREFIX = PATH_PREFIX\n\n/** Tiddler type for quick-notes: Markdown, so the uploaded images/links and\n * any Markdown in the note actually render in TW (a type-less tiddler is\n * treated as plain wiki text and shows raw `![..]`/`[..]` instead). */\nconst NOTE_TYPE = 'text/markdown'\n\n/** Structural webserver face (a subset of dsh-host-webserver). */\nexport interface WebServerFace {\n register(route: { kind: 'exact' | 'prefix'; path: string; handler: (req: IncomingMessage, res: ServerResponse) => void }): () => void\n}\n\n/**\n * Structural face over the DSH `sessionController` service (a subset of\n * dsh-api-session-controller). Only the methods the agent-send routes need are\n * declared; the runtime instance is a real Service, never inspected data.\n */\nexport interface SessionControllerFace {\n prompt(\n request: { requestId: string; sessionId: string; mode: 'queue' | 'steer'; content: Array<{ type: 'text'; text: string }> },\n signal: AbortSignal,\n ): Promise<{ accepted: boolean }>\n list(\n request: { cursor?: string },\n signal: AbortSignal,\n ): Promise<{\n items: Array<{\n sessionId: string\n updatedAt?: number\n running?: boolean\n blank?: boolean\n parentSessionId?: string\n cwd?: string\n }>\n }>\n create(request: { cwd?: string; workspaceId?: string; agentPreset?: string }): Promise<{ sessionId: string }>\n}\n\n/**\n * Structural face over the DSH `workspaceRegistry` service (a subset of\n * dsh-workspace). Only what the agent-create route needs is declared; the\n * runtime instance is a real Service, never inspected data.\n */\nexport interface WorkspaceRegistryFace {\n /** Resolve or create the workspace owning `path` — idempotent by canonical path. */\n create(path: string, title?: string): Promise<{ id: string; path: string }>\n}\n\n/**\n * Structural face over the DSH `agentPresets` service (a subset of\n * dsh-agent-presets). It is the deployment's \"工作模式\" registry — the agent\n * presets a session can be composed from (default / cordis / blade / …). Only\n * `list` + `resolve` (default id) are needed by the agent-modes route.\n */\nexport interface AgentPresetsFace {\n /** Every preset the configured roots currently supply. */\n list(): Promise<Array<{ id: string; name?: string; description?: string; trust?: string; broken?: string }>>\n /** Resolve one preset by id (`undefined` = the deployment default). */\n resolve(id?: string): Promise<{ id: string; name?: string; description?: string }>\n}\n\n/**\n * Structural face over the DSH `sessionPersistence` service. Only the\n * lightweight `list` (metadata headers, no log parse) is needed so the\n * agent-sessions route can attach each session's recorded `agentPreset`.\n */\nexport interface SessionPersistenceFace {\n /** One header per materialized session (carries `agentPreset` when set). */\n list(signal?: AbortSignal): Promise<Array<{ id: string; agentPreset?: string }>>\n}\n\n/**\n * Structural face over the DSH `permissionPresets` service (a subset of\n * dsh-permission-presets). It owns the deployment's permission presets — each\n * bundles a sandbox mode + approval policy (e.g. `workspace-write` = write\n * inside the workspace with approval, `danger-full-access` = no prompts). The\n * agent-modes route exposes the option list to the TW picker, and agent-create\n * applies the chosen preset to the new session's log via `set`.\n */\nexport interface PermissionPresetsFace {\n /** Every switchable preset name, in declaration order. */\n readonly names: readonly string[]\n /** The preset currently selected as the default for new sessions. */\n readonly defaultPreset: string\n /** Build the client option ({ value, name, description? }) for one preset. */\n optionOf(name: string): { value: string; name: string; description?: string }\n /** Record a preset switch on a live session (durable, log-only user intent). */\n set(session: unknown, name: string): void\n}\n\n/**\n * Structural face over the DSH `sessions` in-memory store (a subset of\n * dsh-session). Only `get` is needed: after `sessionController.create`\n * resolves, the new session is already materialized here, so agent-create can\n * hand it to `permissionPresets.set`.\n */\nexport interface SessionsFace {\n get(id: string): unknown\n}\n\n/** Effective UI flags returned by /status (mirror index.ts). */\nexport interface UiDefaultsPublic {\n showQuickNote: boolean\n /** 聊天输入框上方的「快速笔记」快捷按钮(conversation.input.dock 槽位)。 */\n showQuickNoteDock: boolean\n /** 点击「快速笔记」后的打开方式:native=直达 TW 原生编辑器;card=Markdown 卡片。 */\n quickNoteMode: 'native' | 'card'\n /** 左侧侧边栏 TW 入口的显示名称。 */\n sidebarLabel: string\n showPanelStatus: boolean\n showSyncButton: boolean\n /** 嵌入式 TW 是否跟随 DSH 深浅主题(false 时客户端停止 palette 同步)。 */\n followDshTheme: boolean\n /** DSH 暗色时 TW 使用的 palette tiddler 标题。 */\n darkPalette: string\n /** 会话顶部「知识库」Tab 的显示名称(默认「知识库」)。 */\n tabLabel: string\n /** 是否在会话顶部显示「知识库」Tab(会话相关 wiki 汇总,默认 true)。 */\n showSessionTab: boolean\n /** 是否在 DSH 右侧边栏提供 TiddlyWiki 入口/Tab(默认 true)。 */\n showRightbarTab: boolean\n}\n\nexport interface RouteDeps {\n server: WikiServer\n getClient: () => TiddlyWebClient | undefined\n git: GitFace\n autoCommit: () => void\n noteDefaults: () => { tag: string }\n uiDefaults: () => UiDefaultsPublic\n getWikiPath: () => string\n /** Optional DSH sessionController service (agent-send routes only); resolved\n * lazily per request because it may register after webServer appears. */\n getSessionController: () => SessionControllerFace | undefined\n /** Optional DSH workspaceRegistry service (agent-create route); resolves a\n * cwd to a real Workspace so new sessions land inside it instead of the\n * ungrouped bucket. */\n getWorkspaceRegistry: () => WorkspaceRegistryFace | undefined\n /** Optional DSH agentPresets service (agent-modes route): the deployment's\n * \"工作模式\" roster. Resolved lazily per request like sessionController. */\n getAgentPresets: () => AgentPresetsFace | undefined\n /** Optional DSH sessionPersistence service (agent-sessions route): attaches\n * each session's recorded agentPreset so the picker can badge it. */\n getSessionPersistence: () => SessionPersistenceFace | undefined\n /** Optional DSH permissionPresets service (agent-modes permissions list +\n * agent-create applies the chosen permission preset to the new session). */\n getPermissionPresets: () => PermissionPresetsFace | undefined\n /** Optional DSH `sessions` in-memory store (agent-create hands the created\n * live session to permissionPresets.set). */\n getSessions: () => SessionsFace | undefined\n /** Optional DSH sessionQuery service (session-summary route): reads a\n * session's complete event log + descendant tree to decide which wiki notes\n * belong to this conversation. Resolved lazily like the other services. */\n getSessionQuery: () => SessionQueryFace | undefined\n /** Whether the one-click send-to-agent feature is enabled (config switch). */\n sendToAgentEnabled: () => boolean\n /** Optional shared token that must match `x-send-to-agent-token` when set. */\n sendToAgentToken: () => string\n}\n\n/** Header names forwarded to the upstream TW service by the proxy routes. */\nconst FORWARD_HEADER_NAMES = [\n 'accept', 'accept-encoding', 'content-type', 'cookie', 'authorization',\n 'if-none-match', 'if-modified-since', 'origin', 'referer', 'user-agent',\n] as const\n\n/** Copy a safe, string-valued subset of the request headers upstream. */\nfunction forwardHeaders(headers: IncomingHttpHeaders): Record<string, string> {\n const out: Record<string, string> = {}\n for (const name of FORWARD_HEADER_NAMES) {\n // Index through the string index signature so known header names (typed\n // `string`) do not hide the `string[]` repeat case via their specific\n // property declarations.\n const value: string | string[] | undefined = headers[name as string]\n if (typeof value === 'string') out[name] = value\n else if (Array.isArray(value) && value.length > 0) out[name] = value.join(', ')\n }\n return out\n}\n\n/**\n * Extensions that the browser would execute ON THE DSH ORIGIN: `/upload`\n * writes into `wiki/files/`, which TW's core server serves back through the\n * same-origin `/tw` proxy with an extension-derived Content-Type. An uploaded\n * `.html`/`.svg` would therefore be same-origin script (able to read\n * `/admin/state` and write the wiki), i.e. self-XSS with a persistence layer.\n * Documents/images/archives stay allowed; only the executable-by-browser set is\n * refused (v0.19.0).\n */\nconst DANGEROUS_UPLOAD_EXTENSIONS = new Set([\n '.html', '.htm', '.xhtml', '.shtml', '.hta', '.svg', '.xml', '.xsl', '.xslt',\n '.js', '.mjs', '.cjs', '.swf', '.htc',\n])\n\n/** Windows device names: `NUL.txt` cannot be created and makes the route 500. */\nconst WINDOWS_RESERVED_NAMES = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\\..*)?$/i\n\n/**\n * Sanitize an uploaded filename into a safe bare name (no path separators,\n * no `..`, no control characters). Returns '' when nothing usable remains.\n */\nfunction sanitizeUploadName(input: unknown): string {\n if (typeof input !== 'string') return ''\n let name = basename(input.trim().replace(/[\\\\/]+/g, '/'))\n .replace(/[\\u0000-\\u001f\\u007f]/g, '')\n .replace(/[<>:\"|?*]/g, '_')\n .replace(/^\\.+/, '')\n .trim()\n if (name.length === 0 || name === '.' || name === '..') return ''\n // Windows stores a trailing dot/space verbatim but never strips it, and\n // `extname('evil.html.')` is just `.` — so the extension denylist was\n // bypassable with `evil.html.` (v0.19.3). Canonicalize before the check.\n name = name.replace(/[. ]+$/, '')\n if (name.length === 0) return ''\n // `CON`, `NUL`, `COM1…` are not creatable on Windows — prefix instead of 500.\n if (WINDOWS_RESERVED_NAMES.test(name)) name = `_${name}`\n if (name.length > 160) name = name.slice(0, 160)\n return name\n}\n\n/**\n * Strip credentials embedded in a git remote URL (`https://user:token@host/x`)\n * before it reaches an unauthenticated HTTP response (v0.19.0). The token in a\n * remote URL is a real secret and `/status` is reachable without credentials.\n */\nexport function redactRemoteUrl(remote: string): string {\n return remote.replace(/\\/\\/[^/@\\s]+@/g, '//***@')\n}\n\n/**\n * Redact secrets that can appear in the TW child's captured stdout/stderr log\n * (the spawn line carries `password=…`). Belt-and-braces on top of the\n * redaction in wiki.ts, because `/status` is unauthenticated.\n */\nexport function redactLogLines(logs: readonly string[]): string[] {\n return logs.map((line) => line\n .replace(/(password=)\\S+/gi, '$1***')\n .replace(/(authorization:\\s*basic\\s+)[A-Za-z0-9+/=]+/gi, '$1***'))\n}\n\nfunction pad(n: number): string {\n return n < 10 ? `0${n}` : String(n)\n}\n\n/** Max `limit` accepted by the list routes (bounded payloads, v0.19.4). */\nconst MAX_LIST_LIMIT = 200\n\n/** Max `limit` accepted by `/tags` — the tag list is a small wrapper around one\n * full listing, so a larger cap is fine while still bounding the payload. */\nconst MAX_TAGS_LIMIT = 500\n\n/** Clamp a `limit` query param. `fallback` covers absent/unparsable values. */\nfunction readLimit(url: URL, fallback: number, max = MAX_LIST_LIMIT): number {\n const raw = Number(url.searchParams.get('limit') ?? fallback)\n return Number.isFinite(raw) ? Math.max(1, Math.min(Math.floor(raw), max)) : fallback\n}\n\n/** Optional `limit` query param: `undefined` when absent/unparsable (= no cap). */\nfunction readOptionalLimit(url: URL, max: number): number | undefined {\n const raw = url.searchParams.get('limit')\n if (raw === null || raw.trim().length === 0) return undefined\n const parsed = Number(raw)\n if (!Number.isFinite(parsed)) return undefined\n return Math.max(1, Math.min(Math.floor(parsed), max))\n}\n\n/** Default note title: `YYYY-MM-DD HH:mm` (design doc D6). */\nfunction timestampTitle(date = new Date()): string {\n return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`\n}\n\n/**\n * Open a tiddler in TW's NATIVE editor: save the tiddler (when text is\n * non-empty) as Markdown, reuse or create a DRAFT tiddler carrying\n * `draft.of`/`draft.title` (TW's story view renders drafts with the\n * EditTemplate — list.js: `isDraft && editTemplate`), and return the draft\n * title so the client can navigate the panel iframe to `#<draftTitle>`.\n * The draft carries the same `text/markdown` type as the note so saving it in\n * TW keeps Markdown (a draft without a matching type would overwrite the\n * note's type back to plain wiki text).\n */\nexport async function openInTwEditor(\n client: TiddlyWebClient,\n title: string,\n text: string,\n tags: string[] | undefined,\n options: { defaultTags?: string[]; expectedModified?: string; expectedRevision?: string | number; force?: boolean } = {},\n): Promise<{ title: string; draftTitle: string }> {\n // One read drives everything: the write base (tags/custom fields/type), the\n // conflict check, and — when the body carried no text — the draft content.\n const existing = await client.get(title)\n assertNoConflict(title, existing, options)\n if (text.trim().length > 0) {\n // PRESERVE, do not blind-replace (v0.19.1): the old code PUT\n // `{title, text, tags, type}` with no read, wiping the note's custom fields\n // and (because `tags` defaulted to the note tag) its tags too.\n const { tiddler } = buildWriteTiddler(title, text, {\n existing,\n tags,\n defaultTags: options.defaultTags,\n agentTag: false,\n })\n await client.put(tiddler)\n }\n // Draft content: the provided text, else the existing tiddler's content.\n const draftText = text.trim().length > 0 ? text : (existing?.text ?? '')\n // Draft TYPE (v0.19.5): must match the note's real content type. Hardcoding\n // `text/markdown` meant that editing a wikitext note through the quick-note\n // surface downgraded it — TW's save copies the draft's fields back onto the\n // original, so `fields.type` flipped to markdown and the body started\n // rendering as source (`!` headings, `<$list>`, `[[links]]` all broke).\n const draftType = typeof existing?.type === 'string' && existing.type.length > 0 ? existing.type : NOTE_TYPE\n // Draft lookup. The canonical TW name is probed with a single GET first — the\n // old code always pulled the ENTIRE listing (megabytes on a big wiki) just to\n // find a draft; the listing stays as the fallback for a differently-named one.\n //\n // NEVER CLOBBER AN EXISTING DRAFT (v0.20.0): the v0.19.5 refactor inverted\n // this branch — when the canonical `Draft of \"X\"` already existed it wrote\n // straight into it, destroying whatever the user had typed in TW's editor,\n // and when it found a differently-named draft it discarded that title and\n // minted a fresh timestamped one (leaving an orphan). The pre-refactor code\n // was `free ? canonical : canonical+timestamp`; that is restored below, plus\n // reuse of any existing draft title found in the fallback listing.\n const canonical = `Draft of \"${title}\"`\n let draftTitle: string | undefined\n let canonicalFree: boolean | undefined\n try {\n canonicalFree = (await client.get(canonical)) === undefined\n } catch {\n canonicalFree = undefined\n }\n if (canonicalFree === true) {\n // Free canonical name → use it, so TW's own \"save draft\" bookkeeping lines up.\n draftTitle = canonical\n } else {\n // Canonical draft exists (or the probe failed): reuse an existing draft of\n // this note when there is one, otherwise take a timestamped name so the\n // existing draft is preserved.\n try {\n const items = await client.list(undefined, false)\n for (const item of items) {\n if (item['draft.of'] === title && typeof item.title === 'string') {\n draftTitle = item.title\n break\n }\n }\n } catch {\n /* fall back to a fresh draft */\n }\n if (draftTitle === undefined) draftTitle = `${canonical} ${Date.now()}`\n }\n await client.put({ title: draftTitle, text: draftText, 'draft.of': title, 'draft.title': title, type: draftType })\n return { title, draftTitle }\n}\n\n/**\n * Resolve note tags from the request body: `tags` array wins, then the legacy\n * single `tag` string. Returns **undefined** when the body asks for nothing —\n * the caller (`buildWriteTiddler`) then preserves the existing note's tags\n * (v0.19.1 data safety) or falls back to the configured default for new notes.\n * The old version always returned `[defaultTag]`, so re-saving an existing note\n * under its own title silently replaced its tags with the default.\n */\nfunction resolveTags(body: { tag?: unknown; tags?: unknown }): string[] | undefined {\n if (Array.isArray(body.tags)) {\n const tags = body.tags.filter((t): t is string => typeof t === 'string' && t.trim().length > 0).map((t) => t.trim())\n if (tags.length > 0) return tags\n }\n if (typeof body.tag === 'string' && body.tag.trim().length > 0) {\n return body.tag.trim().split(/\\s+/).filter(Boolean)\n }\n return undefined\n}\n\n/** Body fields accepted by the note routes for optimistic concurrency. */\nfunction conflictTokens(body: { expectedModified?: unknown; expectedRevision?: unknown; force?: unknown }): {\n expectedModified?: string\n expectedRevision?: string | number\n force?: boolean\n} {\n return {\n ...(typeof body.expectedModified === 'string' && body.expectedModified.length > 0 ? { expectedModified: body.expectedModified } : {}),\n ...(typeof body.expectedRevision === 'string' || typeof body.expectedRevision === 'number'\n ? { expectedRevision: body.expectedRevision }\n : {}),\n ...(body.force === true ? { force: true } : {}),\n }\n}\n\nexport function registerRoutes(ctx: { webServer: WebServerFace }, deps: RouteDeps): () => void {\n /**\n * `/status` is polled by the GUI (30s), every mounted TW frame and the FAB,\n * and each call shells out to up to five `git` processes — so the git summary\n * is cached for a couple of seconds. Any route that mutates the repo\n * invalidates it so a sync/upload is reflected immediately.\n */\n const GIT_STATUS_TTL_MS = 2_000\n /** Cached git-status PROMISE (not value): a burst of concurrent /status polls\n * then shares ONE probe instead of each spawning up to five git processes\n * (v0.19.0 — value-caching still let every concurrent miss run its own). */\n let gitStatusCache: { at: number; value: Promise<GitStatusViewPublic | null> } | undefined\n const invalidateGitStatus = (): void => { gitStatusCache = undefined }\n const cachedGitStatus = (): Promise<GitStatusViewPublic | null> => {\n if (gitStatusCache !== undefined && Date.now() - gitStatusCache.at < GIT_STATUS_TTL_MS) return gitStatusCache.value\n const pending = (async (): Promise<GitStatusViewPublic | null> => {\n try {\n const view = await deps.git.status(deps.getWikiPath())\n // The remote URL can carry a PAT (`https://user:token@…`) and /status is\n // unauthenticated — never hand the credential to the browser.\n return { ...view, remote: redactRemoteUrl(typeof view.remote === 'string' ? view.remote : '') }\n } catch {\n return null\n }\n })()\n gitStatusCache = { at: Date.now(), value: pending }\n return pending\n }\n\n /**\n * Serialize the heavyweight mutating routes (restart / sync). A burst of\n * concurrent calls used to stack pull/restart operations on top of each\n * other (and a restart racing a restart is what left orphan TW children).\n * A second concurrent caller gets 429 instead of piling on (v0.19.0).\n */\n let mutationInFlight: string | undefined\n const beginMutation = (label: string): boolean => {\n if (mutationInFlight !== undefined) return false\n mutationInFlight = label\n return true\n }\n const endMutation = (): void => { mutationInFlight = undefined }\n\n /**\n * Titles the browser-facing TW surfaces must never serve (v0.19.3 / v0.20.0).\n *\n * `$:/plugins/dsh-tiddlywiki/config` holds the shared tokens and the git\n * remote (possibly with a PAT); `/tw` and `/api` forward ANY path to the\n * loopback TW child, whose TiddlyWeb REST answers for `$:/…` titles — a\n * route-level guard on `/get` was therefore trivially bypassed by\n * `GET /dsh-tiddlywiki/tw/recipes/default/tiddlers/%24%3A%2Fplugins%2F…`\n * (verified). The whole plugin namespace is blocked: nothing under it is\n * needed by the TW frontend, and the host itself talks to TW directly.\n *\n * v0.20.0: the SAME predicate now also guards `POST /render`, which had been\n * left open — TW's `/render` route renders ANY tiddler by title, so\n * `{\"title\":\"$:/plugins/dsh-tiddlywiki/config\"}` returned the raw config\n * (tokens + PAT in plain text inside `<pre><code>`, where the fragment\n * sanitizer keeps it). Verified end-to-end against a scratch wiki before the\n * fix. Every route that turns a caller-supplied title into TW output must use\n * `isBlockedProxyTitle`.\n */\n const BLOCKED_PROXY_TITLE_PREFIXES = ['$:/plugins/dsh-tiddlywiki/']\n\n /** True when a caller-supplied tiddler title addresses the secret namespace. */\n const isBlockedProxyTitle = (title: string): boolean =>\n BLOCKED_PROXY_TITLE_PREFIXES.some((prefix) => title.startsWith(prefix))\n\n /** True when a proxied pathname addresses a blocked (secret-bearing) tiddler. */\n const isBlockedProxyPath = (pathname: string): boolean => {\n const marker = '/tiddlers/'\n const at = pathname.indexOf(marker)\n if (at < 0) return false\n const raw = pathname.slice(at + marker.length)\n let title = raw\n try {\n title = decodeURIComponent(raw)\n } catch { /* malformed encoding: check the raw form */ }\n return isBlockedProxyTitle(title)\n }\n\n const handleStatus = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n if (rejectNonRead(req, res)) return\n const view = deps.server.status()\n const gitSummary = await cachedGitStatus()\n json(res, {\n ok: true,\n ...view,\n // Child-process logs are served to an unauthenticated caller: never leak\n // the spawn line's `password=…` (or a forwarded Authorization header).\n logs: redactLogLines(view.logs),\n twProxy: TW_PROXY_PATH,\n git: gitSummary,\n note: { tag: deps.noteDefaults().tag },\n ui: deps.uiDefaults(),\n })\n }\n\n /**\n * POST /dsh-tiddlywiki/session/summary — 生成当前会话的 wiki 汇总页(「知识库」\n * Tab 的后端)。body `{ session: <会话ID> }`;后端用 sessionQuery 读本会话(含\n * 后代 subagent)的完整事件日志,按「产生/读取/检索」收集 tiddlywiki_* 笔记,\n * 查询每篇当前状态,组装 TW wikitext 写入 `$:/temp/dsh/session-summary/<会话ID>`\n * (volatile:不落盘、不进 git),返回生成的 tiddler title 供前端 iframe 打开。\n */\n const handleSessionSummary = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n let body: { session?: unknown } = {}\n try {\n body = JSON.parse(await readBody(req)) as { session?: unknown }\n } catch {\n /* malformed body → session check below rejects */\n }\n const session = typeof body.session === 'string' && body.session.trim().length > 0 ? body.session.trim() : ''\n if (session.length === 0) {\n json(res, { ok: false, error: 'session is required' }, 400)\n return\n }\n // The id becomes part of a tiddler title (`$:/temp/dsh/session-summary/<id>`)\n // and is echoed into the summary wikitext — keep it to a safe charset\n // instead of trusting the request body (v0.19.3).\n if (!/^[A-Za-z0-9._:-]{1,120}$/.test(session)) {\n json(res, { ok: false, error: 'session id has an unsupported format' }, 400)\n return\n }\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n const sq = deps.getSessionQuery()\n if (sq === undefined) {\n json(res, { ok: false, error: 'session query service unavailable' }, 503)\n return\n }\n const result = await writeSessionSummary(client, sq, session)\n json(res, { ok: true, ...result, twUrl: TW_PROXY_PATH })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n /**\n * GET /dsh-tiddlywiki/agent/sessions — visible ordinary sessions for the TW\n * one-click picker (excludes subagent sessions, activity-descending). Each\n * item also carries its recorded `agentPreset` (工作模式), when known, so the\n * picker can badge existing sessions — read from the lightweight persistence\n * header list, never a full log parse.\n */\n const handleAgentSessions = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectNonRead(req, res)) return\n // Same guard as the sibling /agent/* routes: a deployment that protects\n // send-to-agent with a token must not leak the session roster to an\n // unauthenticated caller (v0.19.0 — this route used to be the odd one out).\n if (!guardSendToAgent(req, res)) return\n const sc = deps.getSessionController()\n if (sc === undefined) {\n json(res, { ok: false, error: 'session service unavailable' }, 503)\n return\n }\n const list = await sc.list({}, AbortSignal.timeout(10_000))\n // sessionId → agentPreset, from the durable header list (degrade silently).\n const presetById: Record<string, string> = {}\n const pers = deps.getSessionPersistence()\n if (pers !== undefined) {\n try {\n const headers = await pers.list(AbortSignal.timeout(5_000))\n for (const h of headers) {\n if (typeof h.agentPreset === 'string' && h.agentPreset.length > 0) presetById[h.id] = h.agentPreset\n }\n } catch {\n /* header list unavailable → no mode badges, picker still works */\n }\n }\n const items = (list.items ?? [])\n .filter((s) => s.parentSessionId === undefined)\n .map((s) => ({\n sessionId: s.sessionId,\n cwd: s.cwd ?? null,\n running: !!s.running,\n blank: !!s.blank,\n updatedAt: s.updatedAt ?? 0,\n agentPreset: presetById[s.sessionId] ?? null,\n }))\n .sort((a, b) => b.updatedAt - a.updatedAt)\n json(res, { ok: true, items })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n /**\n * Shared gate for the TW-side send-to-agent routes: feature switch\n * (`ui.sendToAgent.enabled`) then, when a shared token is configured, the\n * `x-send-to-agent-token` header must match. Returns false after writing the\n * error response — the caller just does `if (!guard(...)) return`.\n */\n const guardSendToAgent = (req: IncomingMessage, res: ServerResponse): boolean => {\n if (!deps.sendToAgentEnabled()) {\n json(res, { ok: false, error: 'send-to-agent is disabled' }, 403)\n return false\n }\n const token = deps.sendToAgentToken().trim()\n if (token.length === 0) return true\n const got = req.headers['x-send-to-agent-token']\n const value = typeof got === 'string' ? got : Array.isArray(got) ? got[0] ?? '' : ''\n // Constant-time comparison (hash-then-compare): `===` leaks the token's\n // length and matched-prefix timing to a caller who can probe the route.\n if (safeTokenEqual(value, token)) return true\n json(res, { ok: false, error: 'unauthorized' }, 401)\n return false\n }\n\n /**\n * GET /dsh-tiddlywiki/agent/modes — available \"工作模式\" (Agent presets) for\n * the TW picker: id/name/description per preset plus the deployment default.\n * Guards mirror the other agent routes (feature switch + optional token).\n */\n const handleAgentModes = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectNonRead(req, res)) return\n if (!guardSendToAgent(req, res)) return\n const ap = deps.getAgentPresets()\n if (ap === undefined) {\n json(res, { ok: false, error: 'agent presets service unavailable' }, 503)\n return\n }\n const presets = await ap.list()\n let defaultId: string | undefined\n try {\n defaultId = (await ap.resolve())?.id\n } catch {\n defaultId = undefined\n }\n // The picker also needs the permission-preset roster (a \"权限\" selector\n // for newly created sessions). Best-effort: when the permissionPresets\n // service is not mounted (older host), `permissions` is null and the\n // picker simply hides the selector — modes still work.\n let permissions: { defaultId: string | null; items: Array<{ value: string; name: string; description?: string }> } | null = null\n const pp = deps.getPermissionPresets()\n if (pp !== undefined) {\n try {\n permissions = {\n defaultId: pp.defaultPreset ?? null,\n items: pp.names.map((n) => pp.optionOf(n)),\n }\n } catch {\n permissions = null\n }\n }\n json(res, {\n ok: true,\n defaultId: defaultId ?? null,\n items: presets.map((p) => ({\n id: p.id,\n name: p.name ?? p.id,\n description: p.description ?? '',\n trust: p.trust ?? 'user',\n broken: p.broken ?? null,\n isDefault: p.id === defaultId,\n })),\n permissions,\n })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n /**\n * POST /dsh-tiddlywiki/agent/send — deliver a note to one agent session as a\n * queued user message (sessionController.prompt, the same API the GUI chat\n * input uses). Guards: feature switch, optional shared token, body shape.\n */\n const handleAgentSend = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n if (!guardSendToAgent(req, res)) return\n const body = JSON.parse(await readBody(req)) as { sessionId?: unknown; text?: unknown }\n const sessionId = typeof body.sessionId === 'string' && body.sessionId.trim().length > 0 ? body.sessionId.trim() : ''\n const text = typeof body.text === 'string' && body.text.trim().length > 0 ? body.text.trim() : ''\n if (sessionId.length === 0 || text.length === 0) {\n json(res, { ok: false, error: 'sessionId and text are required' }, 400)\n return\n }\n const sc = deps.getSessionController()\n if (sc === undefined) {\n json(res, { ok: false, error: 'session service unavailable' }, 503)\n return\n }\n const requestId = `tw-send-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`\n const accepted = await sc.prompt(\n { requestId, sessionId, mode: 'queue', content: [{ type: 'text', text }] },\n AbortSignal.timeout(20_000),\n )\n // A refusal (session gone / busy) must not be reported as success.\n if (accepted !== undefined && accepted.accepted === false) {\n json(res, { ok: false, error: '会话未接受该消息(可能已结束或正忙)', requestId, sessionId }, 409)\n return\n }\n json(res, { ok: true, requestId, sessionId })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n /**\n * POST /dsh-tiddlywiki/agent/create — create (or adopt) one ordinary session\n * inside a real DSH workspace resolved from the requested path. The picker\n * uses it for \"new workspace / new session\": the directory is materialised so\n * a brand-new workspace actually exists on disk, the path is resolved to its\n * (idempotent) Workspace, and the session is created with `workspaceId` so it\n * lands under that workspace in the sidebar. Creating with bare `cwd` instead\n * would leave the session in the ungrouped bucket even when its working\n * directory matches an existing workspace path.\n *\n * Optional `mode` names the \"工作模式\" (an Agent preset id, e.g. from\n * /agent/modes); it is forwarded to `sessionController.create(agentPreset)`\n * so the new session launches under that preset. Omitted → deployment default.\n *\n * Optional `permission` names a \"权限\" preset (e.g. from /agent/modes'\n * `permissions` roster). After the session is created it is applied to the\n * live session's log via `permissionPresets.set` (durable knob events:\n * `permission/preset`, `sandbox/mode`, `approval/policy`), overriding the\n * deployment default pinned at creation. Omitted → keep the default.\n */\n const handleAgentCreate = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n if (!guardSendToAgent(req, res)) return\n const body = JSON.parse(await readBody(req)) as { cwd?: unknown; mode?: unknown; permission?: unknown }\n const cwd = typeof body.cwd === 'string' ? body.cwd.trim() : ''\n const mode = typeof body.mode === 'string' && body.mode.trim().length > 0 ? body.mode.trim() : undefined\n const permission = typeof body.permission === 'string' && body.permission.trim().length > 0 ? body.permission.trim() : undefined\n // Validate the permission preset BEFORE creating the session (fail fast,\n // so a bad name never leaves an orphaned session behind).\n const pp = deps.getPermissionPresets()\n if (permission !== undefined) {\n if (pp === undefined) {\n json(res, { ok: false, error: 'permission selected but the permission-presets service is unavailable' }, 503)\n return\n }\n if (!pp.names.includes(permission)) {\n json(res, { ok: false, error: `unknown permission preset \"${permission}\" (available: ${pp.names.join(', ')})` }, 400)\n return\n }\n }\n const sc = deps.getSessionController()\n if (sc === undefined) {\n json(res, { ok: false, error: 'session service unavailable' }, 503)\n return\n }\n // Validate the agent preset (工作模式) BEFORE any side effect (v0.19.3):\n // the old code created the cwd directory + workspace first, so an unknown\n // mode left an orphaned directory behind a 500 from `sc.create`.\n if (mode !== undefined) {\n const ap = deps.getAgentPresets()\n if (ap === undefined) {\n json(res, { ok: false, error: 'agent presets service unavailable' }, 503)\n return\n }\n try {\n const presets = await ap.list()\n if (!presets.some((preset) => preset.id === mode)) {\n json(res, { ok: false, error: `unknown agent preset \"${mode}\" (available: ${presets.map((preset) => preset.id).join(', ')})` }, 400)\n return\n }\n } catch (err) {\n json(res, { ok: false, error: `cannot validate agent preset: ${err instanceof Error ? err.message : String(err)}` }, 503)\n return\n }\n }\n const ws = deps.getWorkspaceRegistry()\n if (cwd.length > 0) {\n // Only absolute paths, and never clobber an existing non-directory:\n // `mkdir -p` on a file path throws ENOTDIR and used to surface as a 500.\n if (!isAbsolute(cwd)) {\n json(res, { ok: false, error: 'cwd must be an absolute path' }, 400)\n return\n }\n try {\n const info = await stat(cwd)\n if (!info.isDirectory()) {\n json(res, { ok: false, error: 'cwd exists but is not a directory' }, 400)\n return\n }\n } catch {\n await mkdir(cwd, { recursive: true })\n }\n }\n let created: { sessionId: string }\n let workspaceId: string | undefined\n if (cwd.length > 0 && ws !== undefined) {\n const workspace = await ws.create(cwd)\n workspaceId = workspace.id\n created = await sc.create({ workspaceId, agentPreset: mode })\n } else {\n created = await sc.create({ cwd: cwd.length > 0 ? cwd : undefined, agentPreset: mode })\n }\n // Apply the chosen permission preset to the just-created live session\n // (best-effort — the session is already created either way).\n let permissionApplied = false\n if (permission !== undefined) {\n const sessionsSvc = deps.getSessions()\n if (sessionsSvc !== undefined) {\n try {\n const session = sessionsSvc.get(created.sessionId)\n if (session !== undefined) {\n pp?.set(session, permission)\n permissionApplied = true\n }\n } catch {\n /* permission is an optional convenience; never fail the create */\n }\n }\n }\n json(res, {\n ok: true,\n sessionId: created.sessionId,\n cwd: cwd || null,\n workspaceId: workspaceId ?? null,\n mode: mode ?? null,\n permission: permission ?? null,\n permissionApplied,\n })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n const handleNote = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n const body = JSON.parse(await readBody(req)) as { title?: unknown; tag?: unknown; tags?: unknown; text?: unknown; expectedModified?: unknown; expectedRevision?: unknown; force?: unknown }\n const text = typeof body.text === 'string' && body.text.trim().length > 0 ? body.text.trim() : null\n if (text === null) {\n json(res, { ok: false, error: 'text is required' }, 400)\n return\n }\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n const title = typeof body.title === 'string' && body.title.trim().length > 0 ? body.title.trim() : timestampTitle()\n // System tiddlers ($:/…) are TW internals (theme, config, plugins): the\n // quick-note route is a user path and must not let a request overwrite\n // them. Use the editor / admin surface for that (v0.19.0).\n if (title.startsWith('$:/')) {\n json(res, { ok: false, error: 'title must not be a system tiddler ($:/…)' }, 400)\n return\n }\n const tags = resolveTags(body)\n // PRESERVE, do not blind-replace (v0.19.1): read first (404 = new note,\n // any other failure propagates), keep the existing tags/custom fields\n // unless the body explicitly provides them, and honour the optimistic\n // concurrency tokens the quick-note card sends for a note it loaded.\n const existing = await client.get(title)\n assertNoConflict(title, existing, conflictTokens(body))\n const { tiddler } = buildWriteTiddler(title, text, {\n existing,\n tags,\n defaultTags: [deps.noteDefaults().tag],\n agentTag: false,\n })\n await client.put(tiddler)\n deps.autoCommit()\n invalidateGitStatus()\n const finalTags = tiddler.tags ?? []\n json(res, { ok: true, title, tag: finalTags.join(' '), tags: finalTags, text, type: typeof tiddler.type === 'string' ? tiddler.type : NOTE_TYPE })\n } catch (err) {\n if (err instanceof WriteConflictError) {\n json(res, { ok: false, error: err.message, conflict: true }, 409)\n return\n }\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n const handleEdit = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n const body = JSON.parse(await readBody(req)) as { title?: unknown; tag?: unknown; tags?: unknown; text?: unknown; expectedModified?: unknown; expectedRevision?: unknown; force?: unknown }\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n const title = typeof body.title === 'string' && body.title.trim().length > 0 ? body.title.trim() : timestampTitle()\n if (title.startsWith('$:/')) {\n json(res, { ok: false, error: 'title must not be a system tiddler ($:/…)' }, 400)\n return\n }\n const text = typeof body.text === 'string' ? body.text : ''\n const result = await openInTwEditor(client, title, text, resolveTags(body), {\n defaultTags: [deps.noteDefaults().tag],\n ...conflictTokens(body),\n })\n deps.autoCommit()\n invalidateGitStatus()\n json(res, { ok: true, ...result, twUrl: TW_PROXY_PATH })\n } catch (err) {\n if (err instanceof WriteConflictError) {\n json(res, { ok: false, error: err.message, conflict: true }, 409)\n return\n }\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n /** Distinct non-system tags for the quick-note tag autocomplete. The flat\n * `tags` array feeds note-widget's chip autocomplete; the parallel `items`\n * (tag → tiddler count) feeds the reply-stream `tiddlywiki_list_tags` tool\n * card, so both consumers share one endpoint.\n *\n * Query params (v0.19.4): `limit` caps the payload (absent = every tag, so\n * the autocomplete keeps its full vocabulary), `sort=count` orders by usage\n * (default `alpha`). `tags`/`items` are always the same set in the same\n * order, plus `total`/`truncated` so a capped caller can say \"N of M\". */\n const handleTags = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n if (rejectNonRead(req, res)) return\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n try {\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\n const limit = readOptionalLimit(url, MAX_TAGS_LIMIT)\n const byCount = (url.searchParams.get('sort') ?? 'alpha') === 'count'\n // Text-bearing tiddlers only (server-side filter) — the same counting the\n // `tiddlywiki_list_tags` tool uses, so the card and the model agree.\n const { total, tags: all } = await client.tagStats()\n const ordered = byCount ? all : [...all].sort((a, b) => a.tag.localeCompare(b.tag, 'zh'))\n const picked = limit === undefined ? ordered : ordered.slice(0, limit)\n json(res, {\n ok: true,\n tags: picked.map((t) => t.tag),\n items: picked,\n total,\n truncated: picked.length < total,\n })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n /** Recent non-system tiddlers for the quick-note \"最近\" picker (newest first). */\n const handleRecent = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n if (rejectNonRead(req, res)) return\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n try {\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\n const limit = readLimit(url, 15)\n const items = await client.recent(limit)\n json(res, {\n ok: true,\n limit,\n items: items.map((t) => ({\n title: t.title,\n tags: t.tags ?? [],\n modified: toIsoDateString(t.modified),\n snippet: snippetOf(t.text ?? '', 120),\n })),\n })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n /** Full tiddler for the quick-note \"最近\" picker (load into the editor). */\n const handleGet = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n if (rejectNonRead(req, res)) return\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n try {\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\n const title = url.searchParams.get('title') ?? ''\n if (title.length === 0) {\n json(res, { ok: false, error: 'missing title' }, 400)\n return\n }\n // The plugin's own config tiddler can carry shared tokens (bridge.token /\n // sendToAgent.token) and this route is unauthenticated: serve the note\n // picker but never the plugin's secret-bearing config (v0.19.0).\n if (title.startsWith('$:/plugins/dsh-tiddlywiki/')) {\n json(res, { ok: false, error: 'system tiddler not exposed' }, 403)\n return\n }\n const t = await client.get(title)\n if (t === undefined) {\n json(res, { ok: false, notFound: true, title }, 404)\n return\n }\n // Custom fields are FLATTENED (v0.19.1): a single-tiddler GET nests every\n // non-known field under `fields`, so the old copy-top-level loop shipped\n // `fields: {fields: {q: …}}` — consumers could never read `q`/`due`/\n // `clip-url`. `revision` is surfaced explicitly as the concurrency token\n // the quick-note card echoes back on save.\n const binary = isBinaryType(typeof t.type === 'string' ? t.type : undefined)\n json(res, {\n ok: true,\n title: t.title,\n // Binary attachments carry base64 in `text`: never ship 20MB to the\n // note picker (it only needs metadata) — same rule as tiddlywiki_get.\n text: binary ? '' : (t.text ?? ''),\n binary,\n binaryChars: binary ? (t.text ?? '').length : undefined,\n tags: t.tags ?? [],\n type: t.type ?? 'text/vnd.tiddlywiki',\n modified: toIsoDateString(t.modified),\n revision: t.revision ?? null,\n fields: flattenTiddlerFields(t),\n })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n /**\n * GET /dsh-tiddlywiki/search — keyword search for the reply-stream tool card\n * (mirrors tools.ts tiddlywiki_search: same local substring/tag/since/type\n * matching via the TiddlyWebClient). Returns hit titles/tags/modified/\n * snippets so the card can list clickable wiki links.\n */\n const handleSearch = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n if (rejectNonRead(req, res)) return\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n try {\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\n const query = url.searchParams.get('query') ?? ''\n const tags = url.searchParams.getAll('tags').filter((t) => t.length > 0)\n const tag = url.searchParams.get('tag') ?? undefined\n const since = url.searchParams.get('since') ?? undefined\n const type = url.searchParams.get('type') ?? undefined\n // Custom-field filter (v0.20.0): the agent tool has always accepted\n // field/value, and the reply-stream card echoes the tool's arguments —\n // without these params the card listed UNFILTERED hits, contradicting the\n // model-visible result.\n const field = url.searchParams.get('field') ?? undefined\n const value = url.searchParams.get('value') ?? undefined\n const limit = readLimit(url, 30)\n const { items, total } = await client.search(query, { tags, tag, since, type, field, value, limit })\n json(res, {\n ok: true,\n query,\n tags,\n tag: tag ?? null,\n since: since ?? null,\n type: type ?? null,\n field: field ?? null,\n value: value ?? null,\n limit,\n total,\n items: items.map((t) => ({\n title: t.title,\n tags: t.tags ?? [],\n modified: toIsoDateString(t.modified),\n snippet: snippetOf(t.text ?? '', 120),\n })),\n })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n const handleRestart = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n if (!beginMutation('restart')) {\n json(res, { ok: false, error: '另一个重启/同步正在进行中,请稍候' }, 429)\n return\n }\n try {\n await deps.server.restart()\n } finally {\n endMutation()\n }\n json(res, { ok: true, status: deps.server.status().status })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n /** One-click git sync for the floating button / settings page: pull →\n * commit → push, then return the fresh status. Mirrors the agent tool's\n * `action=sync` (design doc §7 conflict policy — rebase conflict aborts).\n * When the pull actually changed the working tree, the running TW child\n * still holds the old in-memory snapshot — restart it (same port) so the\n * UI reflects the pulled files instead of looking stale. */\n const handleSync = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n if (!beginMutation('sync')) {\n json(res, { ok: false, error: '另一个重启/同步正在进行中,请稍候' }, 429)\n return\n }\n invalidateGitStatus()\n const dir = deps.getWikiPath()\n const status = async (): Promise<GitStatusViewPublic | null> => {\n try { return await deps.git.status(dir) } catch { return null }\n }\n try {\n const pulled = await deps.git.pull(dir)\n if (!pulled.ok) {\n json(res, {\n ok: false,\n action: 'sync',\n message: pulled.message,\n ...(pulled.conflictFiles !== undefined ? { conflictFiles: pulled.conflictFiles } : {}),\n status: await status(),\n }, 409)\n return\n }\n let restarted = false\n let restartError: string | undefined\n if (pulled.changed === true) {\n try {\n // DRAIN THE SYNCER FIRST (v0.19.1, data safety): a REST PUT answers 204\n // while the filesystem syncer still holds the tiddler (~250ms timer).\n // Restarting TW before the flush kills those writes — the restarted\n // server boots from the pre-write snapshot and the note is gone, and\n // the `git commit` right below cannot recover what never hit disk.\n // Seeds/admin/index already used this sentinel; this route did not.\n const flushClient = deps.getClient()\n if (flushClient !== undefined) {\n await flushPendingWrites(flushClient, join(dir, 'tiddlers')).catch(() => undefined)\n }\n await deps.server.restart()\n restarted = true\n } catch (err) {\n restartError = err instanceof Error ? err.message : String(err)\n }\n }\n const committed = await deps.git.commit(dir, `sync ${new Date().toISOString()}`)\n const pushed = await deps.git.push(dir)\n const fresh = await status()\n json(res, {\n ok: pushed.ok,\n action: 'sync',\n message: pushed.ok ? '同步完成' : pushed.message,\n pull: 'ok',\n ...(pulled.changed === true ? { changed: true } : {}),\n restarted,\n ...(restartError !== undefined ? { restartError } : {}),\n commit: committed.message,\n push: pushed.message,\n status: fresh,\n lastSync: new Date().toISOString(),\n }, pushed.ok ? 200 : 502)\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n } finally {\n endMutation()\n }\n }\n\n /**\n * Save an uploaded file into the wiki's `files/` folder (git-tracked; TW's\n * core server serves it at `/files/<name>`, get-file.js — no restart\n * needed). Body is the raw file; the name arrives in `X-Filename`. A\n * collision appends `-1`, `-2`, … so nothing is ever overwritten.\n */\n const handleUpload = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n const buf = await readBodyBuffer(req)\n // Name comes from ?name= (URL-encoded) or the X-Filename header.\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\n const nameParam = url.searchParams.get('name')\n let name = sanitizeUploadName(nameParam ?? '')\n if (name.length === 0 && typeof req.headers['x-filename'] === 'string') {\n let decoded = ''\n try { decoded = decodeURIComponent(req.headers['x-filename'] as string) } catch { decoded = req.headers['x-filename'] as string }\n name = sanitizeUploadName(decoded)\n }\n if (name.length === 0) {\n json(res, { ok: false, error: 'missing or invalid filename' }, 400)\n return\n }\n // Refuse files the browser would EXECUTE on the DSH origin: `/files/*` is\n // served back through the same-origin `/tw` proxy, so an uploaded .html /\n // .svg would be same-origin script with access to the admin routes.\n const extension = extname(name).toLowerCase()\n if (DANGEROUS_UPLOAD_EXTENSIONS.has(extension)) {\n json(res, { ok: false, error: `不允许上传可执行/可脚本化的文件类型:${extension}` }, 400)\n return\n }\n const filesDir = join(deps.getWikiPath(), 'files')\n await mkdir(filesDir, { recursive: true })\n // Collision avoidance with an ATOMIC create (v0.19.3): the old code did\n // `access(candidate)` then `writeFile(candidate)`, so (a) any access error\n // (EACCES/EBUSY, not just ENOENT) was read as \"name free\" and (b) two\n // concurrent uploads could both pick the same name and one silently\n // replaced the other — contradicting the route's own \"nothing is ever\n // overwritten\" contract. `flag: 'wx'` fails with EEXIST instead, and we\n // walk the suffix chain on that error only. Bounded, so a pathological\n // directory cannot spin this loop forever.\n const ext = extname(name)\n const stem = ext.length > 0 ? name.slice(0, -ext.length) : name\n let candidate = ''\n let wrote = false\n for (let i = 0; i <= 10_000 && !wrote; i++) {\n candidate = i === 0 ? name : `${stem}-${i}${ext}`\n try {\n await writeFile(join(filesDir, candidate), buf, { flag: 'wx' })\n wrote = true\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'EEXIST') throw err\n }\n }\n if (!wrote) {\n json(res, { ok: false, error: '同名文件过多,请换一个文件名' }, 409)\n return\n }\n deps.autoCommit()\n invalidateGitStatus()\n json(res, {\n ok: true,\n name: candidate,\n path: `files/${candidate}`,\n // Same-origin proxy URL: the embedded TW editor resolves image links\n // against the DSH origin, so a root-absolute `/files/...` would miss.\n url: `${TW_PROXY_PATH}files/${encodeURIComponent(candidate)}`,\n size: buf.length,\n type: req.headers['content-type'] ?? 'application/octet-stream',\n })\n } catch (err) {\n // Same mapping as every other route (v0.19.5): the old bespoke `/too\n // large/i` test disagreed with `errorStatus`'s `/body too large/i`, so the\n // 413 was decided by whichever wording the thrown error happened to use.\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n /**\n * POST /dsh-tiddlywiki/render — the ONLY render endpoint the GUI uses.\n *\n * Proxies to TW's own `/render` server route (installed by the `render-route`\n * seed) and **sanitizes the fragment before it leaves the host**.\n *\n * WHY (v0.19.1, security): the reply-stream tool card and the session\n * 「知识库」 Tab inject that HTML with `dangerouslySetInnerHTML` inside the DSH\n * page. TW's wikitext/markdown parsers only strip `on*` attributes — measured\n * against the live `/render`: `<iframe src=\"javascript:…\">`,\n * `<a href=\"javascript:…\">` and `<form action=\"javascript:…\">` all pass\n * through, i.e. any note text (agent-written, clipped, imported) could run\n * script on the DSH origin and call the unauthenticated `/dsh-tiddlywiki/*`\n * routes. Sanitizing host-side also protects wikis whose ONE-SHOT render\n * bundle predates this fix (the client can never see raw TW output).\n */\n const handleRender = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n let body: { title?: unknown; text?: unknown; type?: unknown; contextTitle?: unknown; parseAsInline?: unknown } = {}\n try {\n body = JSON.parse(await readBody(req, MAX_PROXY_BODY_BYTES)) as typeof body\n } catch {\n json(res, { ok: false, error: 'invalid JSON body' }, 400)\n return\n }\n const title = typeof body.title === 'string' ? body.title.trim() : ''\n const text = typeof body.text === 'string' ? body.text : undefined\n if (title.length === 0 && text === undefined) {\n json(res, { ok: false, error: 'body must provide \"title\" or \"text\"' }, 400)\n return\n }\n // SECURITY (v0.20.0): TW's /render answers for ANY title, including\n // `$:/plugins/dsh-tiddlywiki/config` (bridge/send-to-agent tokens + the\n // git remote, possibly with a PAT). This is the same secret the `/get`,\n // `/tw` and `/api` guards protect — /render was simply missed. Verified:\n // before this guard the rendered fragment contained both secrets verbatim.\n if (isBlockedProxyTitle(title)) {\n json(res, { ok: false, error: 'system tiddler not exposed' }, 403)\n return\n }\n const request = title.length > 0\n ? { title }\n : {\n text: text as string,\n ...(typeof body.type === 'string' && body.type.length > 0 ? { type: body.type } : {}),\n ...(typeof body.contextTitle === 'string' && body.contextTitle.length > 0 ? { contextTitle: body.contextTitle } : {}),\n ...(body.parseAsInline === true ? { parseAsInline: true } : {}),\n }\n const html = await client.render(request)\n const safe = sanitizeTwFragment(html)\n res.writeHead(200, {\n 'content-type': 'text/html; charset=utf-8',\n 'cache-control': 'no-store',\n 'content-length': Buffer.byteLength(safe, 'utf8'),\n })\n res.end(safe)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n if (/HTTP 404/.test(message)) {\n json(res, { ok: false, notFound: true, error: message }, 404)\n return\n }\n json(res, { ok: false, error: message }, 502)\n }\n }\n\n /** Passthrough /dsh-tiddlywiki/api/<rest> → TW root /<rest>. */\n const handleApiProxy = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n // Explicit method whitelist (v0.19.5): the host webserver dispatches by\n // pathname only, so without it every method (TRACE, or a typo'd verb) was\n // forwarded to the TW child. The set is the TiddlyWeb API surface the TW\n // frontend uses.\n if (rejectCrossSiteWrite(req, res, ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'OPTIONS'])) return\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n try {\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\n const rest = url.pathname.replace(/^\\/dsh-tiddlywiki\\/api/, '') || '/'\n if (isBlockedProxyPath(rest)) {\n json(res, { ok: false, error: 'system tiddler not exposed' }, 403)\n return\n }\n // Share the /tw proxy's header forwarding so `authorization`/`cookie`\n // reach the TW child: in locked-down mode (auth.username configured) the\n // /api passthrough used to 401 on every call because it dropped them.\n const headers: Record<string, string> = forwardHeaders(req.headers)\n const method = (req.method ?? 'GET').toUpperCase()\n // TW's CSRF gate requires X-Requested-With on writes; forward it through.\n if (method === 'PUT' || method === 'DELETE' || method === 'POST') headers['x-requested-with'] = 'TiddlyWiki'\n const init: RequestInit = { method, headers, signal: AbortSignal.timeout(15_000) }\n if (method === 'PUT' || method === 'POST') init.body = await readBody(req, MAX_PROXY_BODY_BYTES)\n const upstream = await fetch(`${deps.server.url}${rest}${url.search}`, init)\n const data = await upstream.text()\n res.writeHead(upstream.status, {\n 'content-type': upstream.headers.get('content-type') ?? 'application/json; charset=utf-8',\n 'cache-control': 'no-store',\n })\n res.end(data)\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 502)\n }\n }\n\n /**\n * SAME-ORIGIN proxy /dsh-tiddlywiki/tw/<rest> → TW root /<rest>. Serves the\n * ENTIRE TW frontend (index HTML, /files/*, the TiddlyWeb API) to the\n * browser through the DSH origin, so the embedded editor works from any\n * host/domain the user reaches DSH on (loopback, LAN, Tailscale, domain,\n * HTTPS). The browser never talks to the loopback TW child directly; DSH\n * does, on the same machine. Binary responses are buffered losslessly\n * (arrayBuffer) — unlike the /api JSON proxy, this route must never .text().\n */\n const handleTwProxy = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n if (rejectCrossSiteWrite(req, res)) return\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\n const rest = url.pathname.replace(new RegExp(`^${TW_PROXY_PREFIX}(?=/|$)`), '') || '/'\n if (isBlockedProxyPath(rest)) {\n json(res, { ok: false, error: 'system tiddler not exposed' }, 403)\n return\n }\n try {\n const method = (req.method ?? 'GET').toUpperCase()\n const headers = forwardHeaders(req.headers)\n // TW's CSRF gate requires X-Requested-With on writes; forward it through.\n if (method === 'PUT' || method === 'DELETE' || method === 'POST') headers['x-requested-with'] = 'TiddlyWiki'\n // Abort the upstream fetch when the CLIENT goes away: without this the TW\n // child kept streaming a large attachment into the DSH process until the\n // 30s timeout fired, long after the browser had cancelled (v0.19.3).\n const abort = new AbortController()\n const timeout = AbortSignal.timeout(30_000)\n const signal = typeof AbortSignal.any === 'function' ? AbortSignal.any([abort.signal, timeout]) : timeout\n const init: RequestInit = { method, headers, signal }\n if (method === 'PUT' || method === 'POST') init.body = await readBodyBuffer(req, MAX_UPLOAD_BYTES)\n // A DELETE with a body would otherwise stay unread on the socket: the\n // upstream fetch goes out, but this request never drains, so the client\n // sits on a half-open connection until the 30s timeout (v0.19.5). The TW\n // frontend does not send DELETE bodies, but the proxy is a generic\n // passthrough — drain whatever is there.\n else if (req.readableEnded === false && (req.headers['content-length'] !== undefined || req.headers['transfer-encoding'] !== undefined)) req.resume()\n const upstream = await fetch(`${deps.server.url}${rest}${url.search}`, init)\n const responseHeaders: Record<string, string> = {\n 'content-type': upstream.headers.get('content-type') ?? 'application/octet-stream',\n 'cache-control': upstream.headers.get('cache-control') ?? 'no-store',\n }\n for (const name of ['etag', 'last-modified', 'content-disposition', 'accept-ranges']) {\n const value = upstream.headers.get(name)\n if (value !== null) responseHeaders[name] = value\n }\n // Locked-down mode (auth.username configured) puts the TW frontend behind\n // HTTP Basic auth: without the challenge header the browser never prompts\n // and the embedded editor would just show a bare 401.\n const challenge = upstream.headers.get('www-authenticate')\n if (challenge !== null) responseHeaders['www-authenticate'] = challenge\n res.writeHead(upstream.status, responseHeaders)\n if (upstream.body === null) {\n res.end()\n return\n }\n // STREAM the body (not `arrayBuffer()`): fetching a large attachment\n // through /tw/files/… used to buffer the whole file in the DSH process\n // (v0.19.0). `content-length` is deliberately NOT forwarded — undici\n // decodes compressed responses, so the upstream length can be stale.\n const body = Readable.fromWeb(upstream.body as unknown as import('node:stream/web').ReadableStream)\n await new Promise<void>((resolveP, rejectP) => {\n body.on('error', rejectP)\n res.on('error', rejectP)\n res.on('close', () => {\n // Client hung up (aborted download / closed tab): stop pulling from the\n // TW child instead of draining it until the timeout.\n try { abort.abort() } catch { /* already aborted */ }\n try { body.destroy() } catch { /* already closed */ }\n resolveP()\n })\n res.on('finish', () => resolveP())\n body.pipe(res)\n })\n } catch (err) {\n if (res.headersSent) {\n res.end()\n return\n }\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 502)\n }\n }\n\n // Every handler goes through guardHandler (v0.19.3): a rejection — including a\n // synchronous throw before the handler's own try, e.g. `new URL(req.url)` —\n // becomes a 413/500 JSON response instead of an unhandled rejection that\n // would exit the dsh web process with the request hanging.\n const disposers = [\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/status`, handler: guardHandler(handleStatus) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/note`, handler: guardHandler(handleNote) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/edit`, handler: guardHandler(handleEdit) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/tags`, handler: guardHandler(handleTags) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/recent`, handler: guardHandler(handleRecent) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/get`, handler: guardHandler(handleGet) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/search`, handler: guardHandler(handleSearch) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/render`, handler: guardHandler(handleRender) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/sync`, handler: guardHandler(handleSync) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/upload`, handler: guardHandler(handleUpload) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/restart`, handler: guardHandler(handleRestart) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/session/summary`, handler: guardHandler(handleSessionSummary) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/agent/sessions`, handler: guardHandler(handleAgentSessions) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/agent/modes`, handler: guardHandler(handleAgentModes) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/agent/send`, handler: guardHandler(handleAgentSend) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/agent/create`, handler: guardHandler(handleAgentCreate) }),\n ctx.webServer.register({ kind: 'prefix', path: `${ROUTE_PREFIX}/api`, handler: guardHandler(handleApiProxy) }),\n ctx.webServer.register({ kind: 'prefix', path: `${TW_PROXY_PREFIX}`, handler: guardHandler(handleTwProxy) }),\n ]\n return () => {\n for (const dispose of disposers) dispose()\n }\n}\n\n/** Public shape of the git status summary sent to the panel.\n * Single declaration lives in git.ts (v0.20.0 — this file used to repeat it). */\nexport type GitStatusViewPublic = GitStatusView\n","/**\n * Admin surface for the plugin settings page (design doc §13, config panel).\n *\n * - dynamic plugin/theme management: enumerate the bundled catalog from the\n * installed tiddlywiki package, read/write the wiki's `tiddlywiki.info`\n * plugins/themes arrays, then restart the TW child so the change applies;\n * - extensible config: the settings page reads/writes a config tiddler\n * ($:/plugins/dsh-tiddlywiki/config, a JSON string) that overlays the\n * cordis `config:` block — future config fields just extend the shape.\n *\n * Routes (all under ROUTE_PREFIX/admin, JSON):\n * GET /admin/state current info + catalog + effective config + status\n * POST /admin/info { plugins?, themes? } → write info → restart TW\n * POST /admin/config { ...patch } → write config tiddler\n * POST /admin/restart restart the TW child\n *\n * @module dsh-tiddlywiki/host/admin\n */\nimport { createRequire } from 'node:module'\nimport { readFile, writeFile, readdir, rename } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { TiddlyWebClient } from './tw-api.ts'\nimport type { WikiServer } from './wiki.ts'\nimport { ROUTE_PREFIX, redactLogLines, redactRemoteUrl, type WebServerFace } from './routes.ts'\nimport { type ConfigStore, type PluginConfigShape } from './config.ts'\nimport { readBody, json, guardHandler, errorStatus, rejectCrossSiteWrite, rejectNonRead } from './http.ts'\nimport { waitForFileWrite, needsRestartAfterSeeds, flushPendingWrites } from './seeds.ts'\nimport { RENDER_PLUGIN_FILE } from './seed-render.ts'\nimport type { WikiLocationInfo } from './wiki-location.ts'\nimport type { WikiSwitchResult } from './wiki-switch.ts'\nimport { GitFace } from './git.ts'\n\n/** One bundled plugin/theme from the catalog. */\nexport interface CatalogEntry {\n /** Short name as used in tiddlywiki.info, e.g. \"tiddlywiki/katex\". */\n name: string\n /** Full tiddler title, e.g. \"$:/plugins/tiddlywiki/katex\". */\n title: string\n label: string\n description: string\n /** Dependent theme NAMES (converted from plugin.info `dependents`), e.g. heavier → [\"tiddlywiki/snowwhite\"]. */\n dependents?: string[]\n}\n\nexport interface Catalog {\n plugins: CatalogEntry[]\n themes: CatalogEntry[]\n /** Bundled language plugins (tiddlywiki package `languages/` dir). */\n languages: CatalogEntry[]\n}\n\n/** Shape of tiddlywiki.info (plugins/themes/languages + build/description). */\nexport interface WikiInfo {\n description?: string\n plugins: string[]\n themes: string[]\n languages?: string[]\n build?: Record<string, unknown>\n [key: string]: unknown\n}\n\n/** Resolve the installed tiddlywiki package root (for the catalog). */\nexport function resolveTwRoot(): string {\n const require = createRequire(import.meta.url)\n return dirname(require.resolve('tiddlywiki/package.json'))\n}\n\n/**\n * Read the wiki's tiddlywiki.info.\n *\n * ONLY a missing file (ENOENT) means \"no configuration yet\" (first run). Every\n * other failure — EACCES/EBUSY, a truncated write, malformed JSON — PROPAGATES:\n * the settings page used to receive an empty config object, rewrite the file\n * from it and thereby DELETE the existing plugins/themes/`build`/hand-written\n * fields (v0.19.0 — the AGENTS §8 \"don't treat a read failure as absence\" rule,\n * applied to this path at last).\n */\nexport async function readWikiInfo(wikiPath: string): Promise<WikiInfo> {\n const file = join(wikiPath, 'tiddlywiki.info')\n let raw: string\n try {\n raw = await readFile(file, 'utf8')\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return { plugins: [], themes: [], languages: [] }\n }\n throw new Error(`读取 tiddlywiki.info 失败:${err instanceof Error ? err.message : String(err)}`)\n }\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n throw new Error('tiddlywiki.info 不是合法 JSON;请先修复该文件再保存设置(已保留原文件)')\n }\n // A top-level `null`/array/primitive is NOT a valid tiddlywiki.info; the old\n // code spread it and crashed with \"Cannot read properties of null\" (a\n // confusing 500 instead of the intended readable error).\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw new Error('tiddlywiki.info 顶层不是对象;请先修复该文件再保存设置(已保留原文件)')\n }\n const obj = parsed as Partial<WikiInfo>\n return {\n // Spread FIRST, then the normalized fields: the old order let a literal\n // `\"plugins\": null` (or a non-array) overwrite the `[]` default and crash\n // every consumer with `.includes is not a function`.\n ...obj,\n description: obj.description,\n plugins: Array.isArray(obj.plugins) ? obj.plugins : [],\n themes: Array.isArray(obj.themes) ? obj.themes : [],\n languages: Array.isArray(obj.languages) ? obj.languages : [],\n }\n}\n\n/**\n * Write the wiki's tiddlywiki.info (pretty-printed, ordering preserved).\n * Keeps a `.bak` of the previous content and swaps the new content in via a\n * temp file + rename, so a crash or a full disk cannot leave a truncated file\n * behind (readWikiInfo now refuses to guess at one).\n */\nexport async function writeWikiInfo(wikiPath: string, info: WikiInfo): Promise<void> {\n const file = join(wikiPath, 'tiddlywiki.info')\n try {\n await writeFile(`${file}.bak`, await readFile(file, 'utf8'), 'utf8')\n } catch {\n /* first write — nothing to back up */\n }\n const tmp = `${file}.tmp`\n await writeFile(tmp, `${JSON.stringify(info, null, 4)}\\n`, 'utf8')\n await rename(tmp, file)\n}\n\n/**\n * Ensure one bundled official plugin is listed in tiddlywiki.info `plugins`\n * (idempotent). Returns whether the file changed — the caller restarts TW.\n *\n * WHY (v0.19.0): `WikiServer.ensureWiki()` scaffolds a fresh wiki with\n * `--init server`, and that edition ships ONLY tiddlyweb/filesystem/highlight.\n * The plugin nevertheless writes every agent note, quick-note, draft and clip\n * as `text/markdown`, and the `text/markdown` parser is provided exclusively by\n * the `tiddlywiki/markdown` plugin — without it a brand-new install renders\n * every Markdown note as raw wikitext/source. The author's own wiki already had\n * the plugin (added by an earlier import workflow), which is why this went\n * unnoticed.\n */\nexport async function ensurePlugin(wikiPath: string, twRoot: string, name: string): Promise<boolean> {\n const info = await readWikiInfo(wikiPath)\n if (info.plugins.includes(name)) return false\n const catalog = await bundledCatalog(twRoot)\n if (!catalog.plugins.some((p) => p.name === name)) {\n throw new Error(`unknown bundled plugin: ${name}`)\n }\n info.plugins = [...info.plugins, name]\n await writeWikiInfo(wikiPath, info)\n return true\n}\n\n/** Enumerate bundled official plugins + themes + languages of tiddlywiki. */\nexport async function bundledCatalog(twRoot: string): Promise<Catalog> {\n // TW themes are SKINS layered on the vanilla base (which carries the full\n // 70KB base stylesheet). A theme whose stylesheet body is empty is a broken\n // stub (e.g. tight-heavier in some releases) — skip it so the settings list\n // never offers a no-op theme. vanilla itself is always kept.\n const themeHasCss = async (dir: string): Promise<boolean> => {\n for (const name of ['base.tid', 'styles.tid']) {\n try {\n const raw = await readFile(join(twRoot, 'themes', 'tiddlywiki', dir, name), 'utf8')\n const body = raw\n .replace(/^[\\s\\S]*?\\r?\\n\\r?\\n/, '')\n .split('\\n')\n .filter((line) => !/^\\\\rules\\b/.test(line.trim()))\n .join('\\n')\n .trim()\n if (body.length > 0) return true\n } catch {\n /* file absent */\n }\n }\n return false\n }\n const scan = async (sub: 'plugins' | 'themes'): Promise<CatalogEntry[]> => {\n const root = join(twRoot, sub, 'tiddlywiki')\n let dirs: string[]\n try {\n dirs = await readdir(root)\n } catch {\n return []\n }\n const out: CatalogEntry[] = []\n for (const dir of dirs) {\n let info: { name?: string; description?: string; dependents?: string[] } = {}\n try {\n info = JSON.parse(await readFile(join(root, dir, 'plugin.info'), 'utf8')) as typeof info\n } catch {\n info = {}\n }\n if (sub === 'themes' && dir !== 'vanilla' && !(await themeHasCss(dir))) continue\n out.push({\n name: `tiddlywiki/${dir}`,\n title: sub === 'plugins' ? `$:/plugins/tiddlywiki/${dir}` : `$:/themes/tiddlywiki/${dir}`,\n label: info.name ?? dir,\n description: info.description ?? '',\n // plugin.info `dependents` are full plugin titles → convert to names.\n dependents: Array.isArray(info.dependents)\n ? info.dependents.map((dep) => dep.replace(/^\\$:\\/themes\\/tiddlywiki\\//, 'tiddlywiki/'))\n : undefined,\n })\n }\n out.sort((a, b) => a.name.localeCompare(b.name))\n return out\n }\n // Language plugins live in the package ROOT `languages/` dir (not plugins/),\n // and are enabled via the tiddlywiki.info `languages` array (boot resolves\n // them through $tw.config.languagesPath). Fully offline — official builds.\n const scanLanguages = async (): Promise<CatalogEntry[]> => {\n const root = join(twRoot, 'languages')\n let dirs: string[]\n try {\n dirs = await readdir(root)\n } catch {\n return []\n }\n const out: CatalogEntry[] = []\n for (const dir of dirs) {\n let info: { name?: string; description?: string } = {}\n try {\n info = JSON.parse(await readFile(join(root, dir, 'plugin.info'), 'utf8')) as typeof info\n } catch {\n info = {}\n }\n out.push({\n name: dir,\n title: `$:/languages/${dir}`,\n label: info.name ?? dir,\n description: info.description ?? '',\n })\n }\n out.sort((a, b) => a.name.localeCompare(b.name))\n return out\n }\n const [plugins, themes, languages] = await Promise.all([scan('plugins'), scan('themes'), scanLanguages()])\n return { plugins, themes, languages }\n}\n\n/**\n * Normalize a theme selection into the tiddlywiki.info `themes` array.\n *\n * TW themes are SKINS with a dependency chain (plugin.info `dependents`):\n * vanilla ← snowwhite ← heavier / centralised / readonly / starlight\n * vanilla ← tight / seamless\n * The ACTIVE theme is `$:/theme`, and switching to it registers the theme PLUS\n * its transitive dependents (boot.js accumulatePlugin) — if a dependent isn't\n * loaded, the vanilla base stylesheet is lost and the UI breaks. So we always\n * emit the transitive closure, dependency-first (base first, active overlay\n * last), and force vanilla in as the base. Empty selection → vanilla.\n */\nexport function normalizeThemes(selected: string[], deps: Record<string, string[]> = {}): string[] {\n const sel = selected.filter((name) => typeof name === 'string' && name.length > 0)\n if (sel.length === 0) sel.push('tiddlywiki/vanilla')\n const out: string[] = []\n const seen = new Set<string>()\n const visit = (name: string): void => {\n if (seen.has(name)) return\n seen.add(name)\n for (const dep of deps[name] ?? []) {\n if (dep !== name) visit(dep)\n }\n out.push(name)\n }\n for (const name of sel) visit(name)\n if (!out.includes('tiddlywiki/vanilla')) out.unshift('tiddlywiki/vanilla')\n return out\n}\n\n/**\n * Ensure a language code (e.g. \"zh-Hans\") is in tiddlywiki.info `languages`.\n * Returns whether tiddlywiki.info changed (caller decides whether to restart).\n */\nexport async function ensureLanguage(wikiPath: string, twRoot: string, lang: string): Promise<boolean> {\n if (typeof lang !== 'string' || lang.trim().length === 0) return false\n const code = lang.trim()\n const catalog = await bundledCatalog(twRoot)\n if (!catalog.languages.some((l) => l.name === code)) {\n throw new Error(`unknown language plugin: ${code}`)\n }\n const info = await readWikiInfo(wikiPath)\n const current = info.languages ?? []\n if (current.includes(code)) return false\n info.languages = [...current, code]\n await writeWikiInfo(wikiPath, info)\n return true\n}\n\nexport interface AdminDeps {\n server: WikiServer\n getClient: () => TiddlyWebClient | undefined\n getWikiPath: () => string\n twRoot: () => string\n config: ConfigStore\n /**\n * Called after a successful settings-page save (v0.21.0). The plugin rebuilds\n * its system-prompt section here, so a prompt.* edit applies to the running\n * session from its next model step — without restarting dsh web.\n */\n onConfigChanged?: () => void\n /**\n * The system-prompt text that WOULD be injected right now, for the settings\n * page preview (v0.21.0). Read-only; absent in headless contexts.\n */\n getPrompt?: () => { enabled: boolean; mode: string; text: string }\n /**\n * Runtime wiki location (v0.22.0): where the wiki folder is, how that was\n * decided, and the two operations the settings page can perform. Absent in\n * headless contexts.\n */\n wiki?: {\n info: () => Promise<WikiLocationInfo>\n switch: (target: { root?: unknown; name?: unknown }) => Promise<WikiSwitchResult>\n reset: () => Promise<WikiSwitchResult>\n }\n /** Seed registry for the settings-page \"初始化\" section. */\n seeds: {\n /**\n * `updateAvailable` / `userModified` / `legacyMarker` come from the marker's\n * content hashes (v0.22.0): the page shows 「有更新」 and refuses to imply a\n * local edit was checked for when the marker predates hashes.\n */\n checkAll: (client: TiddlyWebClient) => Promise<Array<{ id: string; title: string; description: string; present: boolean; removable: boolean; detail?: string; updateAvailable?: boolean; userModified?: boolean; legacyMarker?: boolean }>>\n run: (client: TiddlyWebClient, id: string | undefined, force: boolean) => Promise<Array<{ id: string; ok: boolean; wrote: boolean; detail?: string; error?: string }>>\n /** 反初始化: remove one (or all) optional seed's seeded tiddlers + markers. */\n remove: (client: TiddlyWebClient, id: string | undefined) => Promise<Array<{ id: string; ok: boolean; wrote: boolean; detail?: string; error?: string }>>\n }\n}\n\n/**\n * Sentinel returned instead of a stored secret (`bridge.token` /\n * `ui.sendToAgent.token`) by `GET /admin/state` and by `POST /admin/config`'s\n * echo. `/admin/*` is an unauthenticated surface (only CSRF-hardened), and the\n * config tiddler legitimately carries shared tokens — handing them to any\n * caller of the loopback/LAN-reachable GUI is a real leak (v0.19.3). The\n * settings page renders the sentinel verbatim; posting it back is a no-op\n * (`stripMaskedSecrets` drops it), so the real value never leaves the host.\n */\nexport const MASKED_SECRET = '********'\n\n/**\n * Mask secrets (and credentials inside the git remote URL) for an HTTP caller.\n *\n * v0.20.0: `auth.password` is masked too. The v0.19.3 implementation (and the\n * docs) claimed it was, but only `bridge.token` / `ui.sendToAgent.token` /\n * `git.remote` were handled — a config tiddler carrying `auth.password` (the\n * config tiddler is a wiki tiddler; a user can hand-edit it) was echoed in\n * clear text by the unauthenticated `GET /admin/state`. `passwordSet` mirrors\n * the `tokenSet` flag so the settings UI can show \"a password is stored\"\n * without ever receiving it.\n */\nexport function maskConfigSecrets(config: PluginConfigShape): PluginConfigShape {\n const bridge = (config.bridge ?? {}) as Record<string, unknown>\n const ui = (config.ui ?? {}) as Record<string, unknown>\n const sendToAgent = (ui.sendToAgent ?? {}) as Record<string, unknown>\n const git = (config.git ?? {}) as Record<string, unknown>\n const auth = (config.auth ?? {}) as Record<string, unknown>\n const maskToken = (value: unknown): string => (typeof value === 'string' && value.length > 0 ? MASKED_SECRET : '')\n return {\n ...config,\n auth: {\n ...auth,\n password: maskToken(auth.password),\n passwordSet: typeof auth.password === 'string' && auth.password.length > 0,\n },\n git: { ...git, remote: typeof git.remote === 'string' ? redactRemoteUrl(git.remote) : git.remote },\n bridge: {\n ...bridge,\n token: maskToken(bridge.token),\n tokenSet: typeof bridge.token === 'string' && bridge.token.length > 0,\n },\n ui: {\n ...ui,\n sendToAgent: {\n ...sendToAgent,\n token: maskToken(sendToAgent.token),\n tokenSet: typeof sendToAgent.token === 'string' && sendToAgent.token.length > 0,\n },\n },\n } as PluginConfigShape\n}\n\n/**\n * Drop the masked placeholders from an incoming config patch so saving the\n * settings page never overwrites a stored secret with `********` (or the\n * redacted git remote with `https://***@…`). `bridge.token`/`ui.sendToAgent.token`\n * are cleared only when the caller actually sends an empty string.\n */\nexport function stripMaskedSecrets<T extends Record<string, unknown>>(patch: T, current: PluginConfigShape): T {\n const copy = { ...patch } as Record<string, unknown>\n const cleanToken = (container: unknown): unknown => {\n if (typeof container !== 'object' || container === null) return container\n const obj = { ...(container as Record<string, unknown>) }\n if (obj.token === MASKED_SECRET) delete obj.token\n delete obj.tokenSet\n return obj\n }\n if (copy.bridge !== undefined) copy.bridge = cleanToken(copy.bridge)\n // v0.20.0: the same round-trip rule for auth.password (`passwordSet` is a\n // display-only flag and must never be persisted).\n if (copy.auth !== undefined && typeof copy.auth === 'object' && copy.auth !== null) {\n const auth = { ...(copy.auth as Record<string, unknown>) }\n if (auth.password === MASKED_SECRET) delete auth.password\n delete auth.passwordSet\n copy.auth = auth\n }\n if (copy.ui !== undefined) {\n const ui = typeof copy.ui === 'object' && copy.ui !== null\n ? { ...(copy.ui as Record<string, unknown>) }\n : copy.ui\n if (typeof ui === 'object' && ui !== null && (ui as Record<string, unknown>).sendToAgent !== undefined) {\n ;(ui as Record<string, unknown>).sendToAgent = cleanToken((ui as Record<string, unknown>).sendToAgent)\n }\n copy.ui = ui\n }\n if (copy.git !== undefined && typeof copy.git === 'object' && copy.git !== null) {\n const git = { ...(copy.git as Record<string, unknown>) }\n const storedRemote = typeof current.git?.remote === 'string' ? current.git.remote : ''\n if (typeof git.remote === 'string' && storedRemote.length > 0 && git.remote === redactRemoteUrl(storedRemote)) {\n delete git.remote\n }\n copy.git = git\n }\n return copy as T\n}\n\nexport function registerAdminRoutes(ctx: { webServer: WebServerFace }, deps: AdminDeps): () => void {\n const handleState = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectNonRead(req, res)) return\n const wikiPath = deps.getWikiPath()\n const [info, catalog] = await Promise.all([readWikiInfo(wikiPath), bundledCatalog(deps.twRoot())])\n let git: unknown = null\n try {\n const status = await new GitFace().status(wikiPath)\n git = { ...status, remote: redactRemoteUrl(status.remote ?? '') }\n } catch {\n git = null\n }\n const view = deps.server.status()\n json(res, {\n ok: true,\n // Same redaction as GET /status: this route is unauthenticated too.\n server: { ...view, logs: redactLogLines(view.logs) },\n info: { plugins: info.plugins, themes: info.themes, languages: info.languages ?? [] },\n catalog,\n config: maskConfigSecrets(deps.config.get()),\n git,\n })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\n }\n }\n\n const handleInfo = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n const body = JSON.parse(await readBody(req)) as { plugins?: unknown; themes?: unknown; themeActive?: unknown; languages?: unknown }\n const wikiPath = deps.getWikiPath()\n // A read failure must NEVER be turned into \"the wiki has no plugins\":\n // saving would then rewrite the file without them. Fail with 500 and\n // leave the file untouched.\n let info: WikiInfo\n try {\n info = await readWikiInfo(wikiPath)\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\n return\n }\n const catalog = await bundledCatalog(deps.twRoot())\n const known = new Set([...catalog.plugins, ...catalog.themes].map((c) => c.name))\n const knownLangs = new Set(catalog.languages.map((c) => c.name))\n /** Snapshot for the no-op guard below (JSON compare is enough here). */\n const beforeInfo = JSON.stringify(info)\n /** Set only when the request carried a themes array with a resolvable active pick. */\n let activatedTheme: string | undefined\n const applyList = (field: 'plugins' | 'themes', raw: unknown): string[] => {\n if (!Array.isArray(raw)) return info[field]\n const next: string[] = []\n for (const name of raw) {\n if (typeof name !== 'string') continue\n if (!known.has(name) && !info[field].includes(name)) {\n throw new Error(`unknown plugin/theme: ${name}`)\n }\n if (!next.includes(name)) next.push(name)\n }\n return next\n }\n const applyLanguages = (raw: unknown): string[] => {\n if (!Array.isArray(raw)) return info.languages ?? []\n const next: string[] = []\n for (const code of raw) {\n if (typeof code !== 'string') continue\n if (!knownLangs.has(code) && !(info.languages ?? []).includes(code)) {\n throw new Error(`unknown language plugin: ${code}`)\n }\n if (!next.includes(code)) next.push(code)\n }\n return next\n }\n // Validation + in-memory mutation only: a rejected name must NOT touch\n // tiddlywiki.info (400 straight out), while a failure of the write/restart\n // below is an internal error (500) — the old code reported both as 400.\n try {\n info.plugins = applyList('plugins', body.plugins)\n // Activate the chosen theme: load its full dependency chain AND set\n // `$:/theme` so the browser's themeManager actually applies it (the\n // `themes` array alone only makes the plugin available).\n if (Array.isArray(body.themes)) {\n const themeDeps: Record<string, string[]> = {}\n for (const theme of catalog.themes) {\n if (theme.dependents && theme.dependents.length > 0) themeDeps[theme.name] = theme.dependents\n }\n const selected = applyList('themes', body.themes)\n // Explicit active-theme pick (new two-layer UI): validate and auto-add\n // it to the loaded set so its dependency closure is loaded. Without an\n // explicit pick, activate the deepest loaded overlay (old single-radio).\n const explicitActive = typeof body.themeActive === 'string' && body.themeActive.length > 0\n if (explicitActive) {\n const activeName = body.themeActive as string\n if (known.has(activeName) || info.themes.includes(activeName)) {\n if (!selected.includes(activeName)) selected.push(activeName)\n activatedTheme = activeName\n }\n }\n info.themes = normalizeThemes(selected, themeDeps)\n if (activatedTheme === undefined && info.themes.length > 0) {\n activatedTheme = info.themes[info.themes.length - 1]\n }\n } else {\n info.themes = applyList('themes', body.themes)\n }\n if (Array.isArray(body.languages)) info.languages = applyLanguages(body.languages)\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 400)\n return\n }\n // NO-OP GUARD (v0.19.3): a body that changes nothing (e.g. `{}`, or the\n // same lists re-sent) used to rewrite tiddlywiki.info + restart TW — a\n // visible TW interruption for a request that asked for nothing.\n const changed = JSON.stringify(info) !== beforeInfo\n if (changed) {\n await writeWikiInfo(wikiPath, info)\n await deps.server.restart()\n }\n // Activate the chosen theme tiddler (mirrors TW's own Control Panel).\n if (Array.isArray(body.themes) && activatedTheme !== undefined) {\n const client = deps.getClient()\n if (client !== undefined) {\n await client\n .put({ title: '$:/theme', text: `$:/themes/${activatedTheme}`, type: 'text/vnd.tiddlywiki', tags: [] })\n .catch((err) => { console.warn('[dsh-tiddlywiki] activating theme failed:', err) })\n }\n }\n // After a languages change, pin the active language tiddler: first\n // enabled language, or en-GB when none is enabled. Only when the request\n // actually carried a languages array (plugins/themes restarts skip this).\n if (Array.isArray(body.languages)) {\n const client = deps.getClient()\n if (client !== undefined) {\n const langs = info.languages ?? []\n const active = langs.length > 0 ? `$:/languages/${langs[0]}` : '$:/languages/en-GB'\n await client.put({ title: '$:/language', text: active, type: 'text/plain', tags: [] })\n .catch((err) => { console.warn('[dsh-tiddlywiki] pinning $:/language failed:', err) })\n // Keep the startup auto-apply hint (config uiLanguage) consistent with\n // the active language, so a later dsh-web restart doesn't re-enable\n // a language the user just disabled here.\n const hint = langs.length > 0 ? langs[0] : ''\n if ((deps.config.get().uiLanguage ?? '') !== hint) {\n await deps.config.set(client, { uiLanguage: hint }).catch(() => undefined)\n }\n }\n }\n json(res, { ok: true, changed, info: { plugins: info.plugins, themes: info.themes, languages: info.languages ?? [] } })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\n }\n }\n\n const handleConfig = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n const body = JSON.parse(await readBody(req)) as PluginConfigShape\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n // Never persist the masked placeholders the page read back from /state\n // (v0.19.3): saving an untouched form must not clobber a real token.\n await deps.config.set(client, stripMaskedSecrets(body as Record<string, unknown>, deps.config.get()) as PluginConfigShape)\n // prompt.* may have changed: let the plugin re-register its prompt\n // section now (a no-op when the built text is unchanged).\n deps.onConfigChanged?.()\n json(res, { ok: true, config: maskConfigSecrets(deps.config.get()) })\n } catch (err) {\n // Not a blanket 400 (v0.19.5): a refused/dead wiki client surfaces as a\n // 500/413 like everywhere else — reporting「参数错误」for a service failure\n // sent the settings page down the wrong recovery path.\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n const handleRestart = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n await deps.server.restart()\n json(res, { ok: true, status: deps.server.status().status })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\n }\n }\n\n /**\n * GET /dsh-tiddlywiki/admin/seeds — status of every one-time seed\n * (doc-note / send-to-agent / home-index / tw-web-host) for the settings\n * page's 初始化 section.\n */\n const handleSeeds = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectNonRead(req, res)) return\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n const items = await deps.seeds.checkAll(client)\n json(res, { ok: true, items })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\n }\n }\n\n /**\n * POST /dsh-tiddlywiki/admin/seeds/run — run one seed (or all when `id` is\n * absent); `force: true` is the manual \"重新初始化\" (overwrite + re-marker),\n * `force: false` keeps the one-shot write-if-missing semantics.\n */\n const handleSeedsRun = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n const body = JSON.parse(await readBody(req)) as { id?: unknown; force?: unknown }\n const id = typeof body.id === 'string' && body.id.trim().length > 0 ? body.id.trim() : undefined\n const force = body.force === true\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n const seedStartedAt = Date.now()\n const results = await deps.seeds.run(client, id, force)\n // A seeded SERVER-route plugin (render) only loads at TW BOOT, and TW's\n // syncer flushes REST writes on a ~250ms timer. Without the flush-wait +\n // restart the settings page reported「已重新初始化」while /render kept\n // 404ing (tool cards and wiki-link rendering stayed degraded). Mirrors the\n // startup path in src/index.ts — and, like there, the restart fires ONLY\n // for a seed that really rewrote the route (restarting over unflushed\n // content writes would lose them).\n let restarted = false\n let restartError: string | undefined\n if (needsRestartAfterSeeds(results)) {\n try {\n const flushed = await waitForFileWrite(join(deps.getWikiPath(), 'tiddlers', RENDER_PLUGIN_FILE), 8_000, 150, seedStartedAt)\n if (!flushed) console.warn('[dsh-tiddlywiki] seeded render plugin file not seen on disk before restart')\n // Drain the rest of the syncer queue too: force-all writes every seed,\n // and a restart that boots from a stale snapshot silently loses the\n // ones still queued (v0.19.0 — repeatedly lost tw-web-host here).\n const drained = await flushPendingWrites(client, join(deps.getWikiPath(), 'tiddlers'))\n if (!drained) console.warn('[dsh-tiddlywiki] seed writes may not have been flushed before restart')\n await deps.server.restart()\n restarted = true\n } catch (err) {\n restartError = err instanceof Error ? err.message : String(err)\n console.warn('[dsh-tiddlywiki] restart after seeding failed:', restartError)\n }\n }\n const ok = results.every((r) => r.ok)\n json(res, { ok, results, restarted, ...(restartError !== undefined ? { restartError } : {}) }, ok ? 200 : 400)\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\n }\n }\n\n /**\n * POST /dsh-tiddlywiki/admin/seeds/remove — 反初始化: remove one optional\n * seed (or all optional seeds when `id` is absent) — delete the seeded\n * tiddlers + markers so the wiki returns to the \"never seeded\" state.\n * Core seeds (功能必需) cannot be removed.\n */\n const handleSeedsRemove = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n const body = JSON.parse(await readBody(req)) as { id?: unknown }\n const id = typeof body.id === 'string' && body.id.trim().length > 0 ? body.id.trim() : undefined\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n const results = await deps.seeds.remove(client, id)\n const ok = results.every((r) => r.ok)\n json(res, { ok, results }, ok ? 200 : 400)\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\n }\n }\n\n /**\n * GET /dsh-tiddlywiki/admin/prompt — the system-prompt text that would be\n * injected right now, with the effective mode/enabled flag (v0.21.0). The\n * settings page renders it verbatim so a prompt edit is never a black box.\n * Read-only + CSRF-hardened like every other admin read.\n */\n const handlePrompt = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectNonRead(req, res)) return\n const prompt = deps.getPrompt?.()\n if (prompt === undefined) {\n json(res, { ok: false, error: 'prompt preview is not available' }, 503)\n return\n }\n json(res, { ok: true, enabled: prompt.enabled, mode: prompt.mode, length: prompt.text.length, text: prompt.text })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\n }\n }\n\n /**\n * GET /dsh-tiddlywiki/admin/wiki/location — the folder the plugin currently\n * serves, how that was decided (pointer file / cordis config / default), the\n * config default to fall back to, and the wiki-looking folders next to it\n * (settings-page「知识库位置」, v0.22.0). Read-only + CSRF-hardened.\n */\n const handleWikiLocation = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectNonRead(req, res)) return\n if (deps.wiki === undefined) {\n json(res, { ok: false, error: 'wiki location is not available' }, 503)\n return\n }\n json(res, { ok: true, ...(await deps.wiki.info()) })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\n }\n }\n\n /**\n * POST /dsh-tiddlywiki/admin/wiki/switch { root, name } — repoint the running\n * plugin at another wiki folder (v0.22.0). Serialized against itself by the\n * host; a failure is reported with `rolledBack` so the page can say whether\n * the old wiki is still serving.\n */\n const handleWikiSwitch = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n if (deps.wiki === undefined) {\n json(res, { ok: false, error: 'wiki location is not available' }, 503)\n return\n }\n const body = JSON.parse(await readBody(req)) as { root?: unknown; name?: unknown }\n const result = await deps.wiki.switch({ root: body.root, name: body.name })\n json(res, result.ok ? { ...result, status: deps.server.status().status } : result, result.ok ? 200 : 400)\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n /**\n * POST /dsh-tiddlywiki/admin/wiki/reset — 「恢复为配置默认」: delete the pointer\n * file and (when needed) switch back to the cordis default folder (v0.22.0).\n */\n const handleWikiReset = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (rejectCrossSiteWrite(req, res, ['POST'])) return\n if (deps.wiki === undefined) {\n json(res, { ok: false, error: 'wiki location is not available' }, 503)\n return\n }\n const result = await deps.wiki.reset()\n json(res, result.ok ? { ...result, status: deps.server.status().status } : result, result.ok ? 200 : 400)\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, errorStatus(err))\n }\n }\n\n // Same rejection safety net as routes.ts (v0.19.3).\n const disposers = [\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/state`, handler: guardHandler(handleState) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/prompt`, handler: guardHandler(handlePrompt) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/wiki/location`, handler: guardHandler(handleWikiLocation) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/wiki/switch`, handler: guardHandler(handleWikiSwitch) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/wiki/reset`, handler: guardHandler(handleWikiReset) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/info`, handler: guardHandler(handleInfo) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/config`, handler: guardHandler(handleConfig) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/restart`, handler: guardHandler(handleRestart) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/seeds`, handler: guardHandler(handleSeeds) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/seeds/run`, handler: guardHandler(handleSeedsRun) }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/seeds/remove`, handler: guardHandler(handleSeedsRemove) }),\n ]\n return () => {\n for (const dispose of disposers) dispose()\n }\n}\n","/**\n * 本地剪藏桥(bookmarklet 剪藏小工具的后端)。\n *\n * A tiny loopback-only HTTP bridge that turns a browser bookmarklet's\n * \"剪藏\" request into TiddlyWiki tiddlers. The bookmarklet (JS in the\n * browser's bookmarks bar) POSTs { title, url, text, images } to the bridge;\n * the bridge writes ONE note tiddler (markdown, or wikitext when images are\n * stored) plus optional IMAGE ATTACHMENTS through the SAME TiddlyWebClient\n * every other writer uses (D1 — there is never a second write path).\n *\n * Images (v0.16.25): TW 5.4.1's REST facade is JSON-only (put-tiddler.js\n * JSON.parses the body — there is no binary upload route), but its binary\n * tiddlers ARE representable as `{ type: image/*, text: <base64> }` — verified\n * empirically: type + base64 roundtrip byte-exact, tags survive, custom fields\n * nest under `fields`. So the bridge downloads each chosen image (server-side,\n * no CORS; referer + UA sent for hotlink-protected CDNs), stores it as a\n * binary ATTACHMENT tiddler, and embeds it in the note with `[img[Title]]`.\n * A failed download degrades gracefully to a plain source-URL line.\n *\n * Security posture (deliberate):\n * - binds `127.0.0.1` ONLY — never reachable from the network;\n * - Host-header whitelist (127.0.0.1 / localhost / ::1) blocks DNS rebinding:\n * a malicious website that resolves its own domain to the loopback cannot\n * use the bridge with its own Host header;\n * - optional shared `token` — when set, every write must carry\n * `x-clip-token`. The bookmarklet ships it; a random webpage on the\n * internet cannot know it, so it cannot pollute the wiki;\n * - CORS preflight answered with `Access-Control-Allow-Origin: *` +\n * `Access-Control-Allow-Private-Network: true` so (a) a bookmarklet running\n * on an https page may read the response, and (b) Chromium's Private Network\n * Access preflight for public→loopback requests succeeds;\n * - `enabled` is evaluated PER REQUEST against the effective (settings-page)\n * config, so toggling the flag takes effect immediately — no dsh web\n * restart. Changing the PORT still needs a restart (the listener binds once).\n *\n * @module dsh-tiddlywiki/host/clip-bridge\n */\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http'\nimport { get as httpGet } from 'node:http'\nimport { get as httpsGet } from 'node:https'\nimport { lookup } from 'node:dns/promises'\nimport { isIP } from 'node:net'\nimport type { AddressInfo, LookupFunction } from 'node:net'\nimport type { Tiddler } from './tw-api.ts'\nimport { readBody, safeTokenEqual } from './http.ts'\n\n/** Effective bridge config (resolved from the plugin config store). */\nexport interface BridgeConfig {\n /** 是否启用剪藏桥(每个请求实时判定,保存设置即生效)。 */\n enabled: boolean\n /** 监听端口(仅启动时绑定一次;改动端口需重启 dsh web)。 */\n port: number\n /** 共享口令;非空时每个写入须带 `x-clip-token` 头。 */\n token: string\n /** 剪藏笔记默认 tag。 */\n tag: string\n}\n\n/** A downloaded image (raw bytes + declared content-type, nullable). */\nexport interface ClipImageDownload {\n buffer: Buffer\n type: string | null\n}\n\n/** Everything the bridge needs from the plugin (no @deepseek-ai deps). */\nexport interface ClipBridgeDeps {\n /** Resolve the EFFECTIVE bridge config per request. */\n getConfig(): BridgeConfig\n /** Write one tiddler; throws when the wiki is not ready. */\n write(tiddler: Tiddler): Promise<unknown>\n /** True when a tiddler with that title already exists (title dedupe). */\n exists(title: string): Promise<boolean>\n /** Download an image's bytes (server-side fetch; throws on failure). */\n download(url: string, referer: string): Promise<ClipImageDownload>\n /** Optional logger (console.info prefixed by the caller). */\n log?(message: string): void\n}\n\n/** Cap on the clipped text (characters) — plenty for a page selection. */\nexport const MAX_CLIP_TEXT_LENGTH = 200_000\n/** Cap on a single /clip request body (bytes). */\nexport const MAX_CLIP_BODY_BYTES = 256 * 1024\n/** Cap on one downloaded image (bytes) — 15 MB keeps the wiki sane. */\nexport const MAX_IMAGE_BYTES = 15 * 1024 * 1024\n/** Max images stored per clip (the picker already caps the page list). */\nexport const MAX_CLIP_IMAGES = 10\n\n/** Validated request body accepted by POST /clip (all fields normalized). */\nexport interface ClipPayload {\n title: string\n url: string\n text: string\n /** Optional extra tiddler tags (merged with the configured clip tag). */\n tags: string[]\n /** Where the clip came from (\"bookmarklet\" default; \"api\"/\"script\"…). */\n source: string\n /** Chosen image URLs to download + store as attachments (≤ MAX_CLIP_IMAGES). */\n images: string[]\n}\n\n/** Per-image result echoed back to the bookmarklet. */\nexport interface ClipImageResult {\n url: string\n ok: boolean\n /** Stored attachment tiddler title (when ok). */\n title?: string\n /** Human-readable failure reason (when !ok). */\n error?: string\n}\n\n/** Loopback-only Host header whitelist (DNS-rebinding defense). */\nexport function hostAllowed(host: string | undefined, port: number): boolean {\n if (typeof host !== 'string') return false\n const h = host.trim().toLowerCase()\n const ok = (base: string): boolean => h === base || h === `${base}:${port}`\n return ok('127.0.0.1') || ok('localhost') || ok('[::1]') || ok('::1')\n}\n\n/** IPv4 ranges that must never be fetched on behalf of a web page. */\nconst PRIVATE_V4_PATTERNS = [\n /^0\\./, // \"this network\"\n /^10\\./, // private\n /^100\\.(6[4-9]|[7-9]\\d|1[01]\\d|12[0-7])\\./, // CGNAT 100.64/10\n /^127\\./, // loopback\n /^169\\.254\\./, // link-local (cloud metadata 169.254.169.254)\n /^172\\.(1[6-9]|2\\d|3[01])\\./, // private\n /^192\\.0\\.0\\./, // IETF protocol assignments\n /^192\\.0\\.2\\./, // TEST-NET-1\n /^192\\.88\\.99\\./, // 6to4 relay anycast\n /^192\\.168\\./, // private\n /^198\\.1[89]\\./, // benchmarking 198.18/15\n /^198\\.51\\.100\\./, // TEST-NET-2\n /^203\\.0\\.113\\./, // TEST-NET-3\n /^22[4-9]\\./, /^23\\d\\./, // multicast 224/4\n /^24\\d\\./, /^25[0-5]\\./, // reserved 240/4 (incl. 255.255.255.255 broadcast)\n]\n\n/** Parse a dotted-quad IPv4 into 4 bytes, or null. */\nfunction parseIpv4(input: string): number[] | null {\n const parts = input.split('.')\n if (parts.length !== 4) return null\n const out: number[] = []\n for (const part of parts) {\n if (!/^\\d{1,3}$/.test(part)) return null\n const value = Number(part)\n if (value > 255) return null\n out.push(value)\n }\n return out\n}\n\n/**\n * Expand ANY IPv6 textual form (compressed `::`, expanded, embedded dotted\n * IPv4 tail, zone id, bracketed) into exactly 16 bytes — or null when it is not\n * a valid IPv6 literal. Written by hand because the guard must be total:\n * `net.isIP()` accepts forms such as `0:0:0:0:0:0:0:1` (which IS `::1`) that the\n * old prefix-regex guard let through.\n */\nfunction ipv6ToBytes(input: string): number[] | null {\n let ip = input.trim().toLowerCase()\n if (ip.startsWith('[') && ip.endsWith(']')) ip = ip.slice(1, -1)\n const zone = ip.indexOf('%')\n if (zone >= 0) ip = ip.slice(0, zone)\n const halves = ip.split('::')\n if (halves.length > 2) return null\n const parseGroups = (text: string): number[] | null => {\n if (text.length === 0) return []\n const out: number[] = []\n for (const group of text.split(':')) {\n if (group.length === 0) return null\n if (group.includes('.')) {\n const v4 = parseIpv4(group)\n if (v4 === null) return null\n out.push((v4[0] ?? 0) * 256 + (v4[1] ?? 0), (v4[2] ?? 0) * 256 + (v4[3] ?? 0))\n continue\n }\n if (!/^[0-9a-f]{1,4}$/.test(group)) return null\n out.push(Number.parseInt(group, 16))\n }\n return out\n }\n const head = parseGroups(halves[0] ?? '')\n if (head === null) return null\n let groups: number[]\n if (halves.length === 2) {\n const tail = parseGroups(halves[1] ?? '')\n if (tail === null) return null\n const missing = 8 - head.length - tail.length\n if (missing < 0) return null\n groups = [...head, ...new Array<number>(missing).fill(0), ...tail]\n } else {\n groups = head\n }\n if (groups.length !== 8) return null\n const bytes: number[] = []\n for (const group of groups) bytes.push((group >> 8) & 0xff, group & 0xff)\n return bytes\n}\n\n/**\n * True for loopback / link-local / private / unique-local / CGNAT / multicast\n * addresses. IPv6 is judged on its PARSED BYTES, not on string prefixes\n * (v0.19.0): `0:0:0:0:0:0:0:1`, `::0:1` and `::ffff:7f00:1` all denote `::1` /\n * `127.0.0.1` and were previously ALLOWED, i.e. the SSRF guard was bypassable.\n * IPv4-mapped (`::ffff:0:0/96`), IPv4-compatible (`::/96`) and 6to4\n * (`2002::/16`) forms embed an IPv4 address and are judged as that address.\n */\nexport function isPrivateAddress(address: string): boolean {\n const raw = address.trim().toLowerCase()\n const ip = raw.startsWith('[') && raw.endsWith(']') ? raw.slice(1, -1) : raw\n const family = isIP(ip)\n if (family === 4) return PRIVATE_V4_PATTERNS.some((re) => re.test(ip))\n if (family !== 6) return false\n const bytes = ipv6ToBytes(ip)\n // Unparseable but accepted by isIP(): fail closed.\n if (bytes === null) return true\n const isZero = (slice: number[]): boolean => slice.every((b) => b === 0)\n const at = (index: number): number => bytes[index] ?? 0\n const v4 = (offset: number): string => `${at(offset)}.${at(offset + 1)}.${at(offset + 2)}.${at(offset + 3)}`\n if (isZero(bytes)) return true // ::\n if (isZero(bytes.slice(0, 15)) && at(15) === 1) return true // ::1\n if ((at(0) & 0xfe) === 0xfc) return true // fc00::/7 unique-local\n if (at(0) === 0xfe && (at(1) & 0xc0) === 0x80) return true // fe80::/10\n if (at(0) === 0xff) return true // ff00::/8 multicast\n if (at(0) === 0x20 && at(1) === 0x01 && at(2) === 0x0d && at(3) === 0xb8) return true // 2001:db8::/32\n // IPv4-mapped ::ffff:a.b.c.d\n if (isZero(bytes.slice(0, 10)) && at(10) === 0xff && at(11) === 0xff) return isPrivateAddress(v4(12))\n // 6to4 2002:<v4>:… and NAT64 64:ff9b::<v4>\n if (at(0) === 0x20 && at(1) === 0x02) return isPrivateAddress(v4(2))\n if (at(0) === 0x00 && at(1) === 0x64 && at(2) === 0xff && at(3) === 0x9b) return isPrivateAddress(v4(12))\n // IPv4-compatible ::a.b.c.d (deprecated, still routed by some stacks)\n if (isZero(bytes.slice(0, 12))) return isPrivateAddress(v4(12))\n return false\n}\n\n/**\n * SSRF guard for a clip image URL. The bridge downloads on behalf of a page in\n * the user's browser, so it must never become a proxy into the local network\n * (or a cloud metadata endpoint). Rejects non-http(s) schemes, loopback/LAN\n * hostnames, literal private addresses, and public names that RESOLVE to a\n * private address. Redirects are re-validated per hop by the caller.\n *\n * v0.20.0: this used to be a second, hand-maintained copy of the policy inside\n * `resolvePublicTarget` (the two had to be kept in sync by hand). It now simply\n * performs the same resolve-and-validate step and discards the pinned target;\n * `downloadClipImage` still pins the approved address, so the check is not\n * duplicated at connect time either.\n */\nexport async function assertPublicImageUrl(rawUrl: string): Promise<void> {\n await resolvePublicTarget(rawUrl)\n}\n\n/** One hop's outcome: either a redirect target, or the downloaded bytes. */\ninterface ImageHopResult {\n buffer: Buffer\n type: string | undefined\n redirect?: string\n}\n\n/** A validated URL plus the exact address the guard approved for connecting. */\ninterface PublicTarget {\n url: URL\n address: string\n family: number\n}\n\n/**\n * Resolve one image URL to a PUBLIC address and PIN it. The guard validates the\n * DNS answer here; the request then connects to that exact address (custom\n * `lookup`), so a rebinding domain cannot pass the check with a public answer\n * and then be re-resolved to 127.0.0.1 at connect time (v0.19.0 — the previous\n * implementation validated the name and then let `fetch` resolve it again).\n */\nasync function resolvePublicTarget(rawUrl: string): Promise<PublicTarget> {\n let url: URL\n try {\n url = new URL(rawUrl)\n } catch {\n throw new Error('图片地址不是合法 URL')\n }\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n throw new Error(`不支持的图片协议 ${url.protocol}`)\n }\n const host = url.hostname.toLowerCase()\n if (host === 'localhost' || host.endsWith('.localhost') || host.endsWith('.local') || host.endsWith('.internal') || host.endsWith('.home.arpa')) {\n throw new Error('拒绝下载内网主机名的图片')\n }\n const literal = host.startsWith('[') && host.endsWith(']') ? host.slice(1, -1) : host\n const literalFamily = isIP(literal)\n if (literalFamily !== 0) {\n if (isPrivateAddress(literal)) throw new Error('拒绝下载内网地址的图片')\n return { url, address: literal, family: literalFamily }\n }\n let addresses: Array<{ address: string; family: number }>\n try {\n addresses = await lookup(host, { all: true })\n } catch {\n throw new Error(`图片主机无法解析:${host}`)\n }\n if (addresses.length === 0) throw new Error(`图片主机无法解析:${host}`)\n if (addresses.some((entry) => isPrivateAddress(entry.address))) {\n throw new Error('拒绝下载解析到内网地址的图片')\n }\n const first = addresses[0]\n if (first === undefined) throw new Error(`图片主机无法解析:${host}`)\n return { url, address: first.address, family: first.family }\n}\n\n/** One GET against an already-validated, pinned target (no redirect following). */\nfunction requestImageHop(target: PublicTarget, referer: string, maxBytes: number, timeoutMs: number): Promise<ImageHopResult> {\n return new Promise<ImageHopResult>((resolveP, rejectP) => {\n const isHttps = target.url.protocol === 'https:'\n const requester = isHttps ? httpsGet : httpGet\n // Node calls `lookup(hostname, options, cb)`; answer with the address the\n // SSRF guard approved so DNS is never consulted a second time.\n const pinnedLookup: LookupFunction = (_hostname, options, callback) => {\n if (options.all === true) {\n callback(null, [{ address: target.address, family: target.family }])\n return\n }\n callback(null, target.address, target.family)\n }\n const request = requester({\n protocol: target.url.protocol,\n hostname: target.url.hostname,\n port: target.url.port.length > 0 ? Number(target.url.port) : (isHttps ? 443 : 80),\n path: `${target.url.pathname}${target.url.search}`,\n method: 'GET',\n headers: {\n 'user-agent': 'Mozilla/5.0 (compatible; dsh-tiddlywiki clip bridge)',\n referer,\n accept: 'image/*,*/*;q=0.8',\n // No transparent decompression: the byte cap must apply to what we store.\n 'accept-encoding': 'identity',\n },\n lookup: pinnedLookup,\n }, (res) => {\n const status = res.statusCode ?? 0\n if (status >= 300 && status < 400) {\n const location = res.headers.location\n res.resume()\n if (location === undefined || location.length === 0) {\n rejectP(new Error(`重定向缺少 Location(HTTP ${status})`))\n return\n }\n resolveP({ buffer: Buffer.alloc(0), type: undefined, redirect: new URL(location, target.url).href })\n return\n }\n if (status < 200 || status >= 300) {\n res.resume()\n rejectP(new Error(`下载失败 HTTP ${status}`))\n return\n }\n // Cheap up-front rejection when the server declares a size.\n const declared = Number(res.headers['content-length'] ?? Number.NaN)\n if (Number.isFinite(declared) && declared > maxBytes) {\n res.destroy()\n rejectP(new Error('图片超过 15MB 上限'))\n return\n }\n const chunks: Buffer[] = []\n let size = 0\n res.on('data', (chunk: Buffer) => {\n size += chunk.length\n if (size > maxBytes) {\n // STREAMING cap: stop reading instead of buffering the whole body and\n // checking afterwards (a chunked or zip-bomb response used to fill\n // memory before the limit was applied).\n res.destroy()\n rejectP(new Error('图片超过 15MB 上限'))\n return\n }\n chunks.push(chunk)\n })\n res.on('end', () => {\n if (size === 0) {\n rejectP(new Error('下载内容为空'))\n return\n }\n resolveP({ buffer: Buffer.concat(chunks), type: res.headers['content-type'] })\n })\n res.on('error', rejectP)\n })\n request.on('timeout', () => { request.destroy(new Error('图片下载超时')) })\n request.on('error', rejectP)\n request.setTimeout(timeoutMs)\n })\n}\n\n/**\n * Download one clip image with the full SSRF posture: every hop is validated\n * AND pinned, redirects are followed manually (max `maxRedirects`), the byte\n * cap is enforced while streaming, and compression is disabled.\n */\nexport async function downloadClipImage(\n imageUrl: string,\n referer: string,\n options: { maxBytes?: number; maxRedirects?: number; timeoutMs?: number } = {},\n): Promise<{ buffer: Buffer; type: string | undefined }> {\n const maxBytes = options.maxBytes ?? MAX_IMAGE_BYTES\n const maxRedirects = options.maxRedirects ?? 3\n const timeoutMs = options.timeoutMs ?? 20_000\n let current = imageUrl\n for (let hop = 0; hop <= maxRedirects; hop++) {\n const target = await resolvePublicTarget(current)\n const result = await requestImageHop(target, referer, maxBytes, timeoutMs)\n if (result.redirect !== undefined) {\n current = result.redirect\n continue\n }\n return { buffer: result.buffer, type: result.type }\n }\n throw new Error(`图片重定向次数超过 ${maxRedirects} 次`)\n}\n\n/**\n * Pick a collision-free tiddler title: `base`, then `base(2)…(20)` while an\n * existing tiddler holds the name (so re-clipping the same page never\n * overwrites the previous clip), then a timestamp fallback.\n */\nexport async function resolveClipTitle(exists: (title: string) => Promise<boolean>, base: string): Promise<string> {\n if (!(await exists(base))) return base\n for (let i = 2; i <= 20; i++) {\n const candidate = `${base}(${i})`\n if (!(await exists(candidate))) return candidate\n }\n return `${base}(${new Date().toISOString().slice(0, 19).replace(/[T:]/g, '-')})`\n}\n\n/** Build the markdown tiddler for ONE TEXT-ONLY clip (no images). */\nexport function buildClipTiddler(opts: {\n title: string\n url: string\n text?: string\n tag: string\n source?: string\n at?: string\n extraTags?: string[]\n}): Tiddler {\n const at = opts.at ?? new Date().toISOString()\n const selection = typeof opts.text === 'string' ? opts.text.trim() : ''\n const lines = [`> 来源:${opts.url}`, '']\n if (selection.length > 0) lines.push(selection, '')\n lines.push('---', `剪藏时间:${at}`)\n const tags = [opts.tag, ...(opts.extraTags ?? [])].filter((t) => t.trim().length > 0)\n return {\n title: opts.title,\n text: lines.join('\\n'),\n type: 'text/markdown',\n ...(tags.length > 0 ? { tags } : {}),\n 'clip-url': opts.url,\n 'clip-source': opts.source ?? 'bookmarklet',\n 'clip-at': at,\n }\n}\n\n/**\n * Build the note tiddler for a clip WITH stored images: wikitext so the\n * attachments render inline via `[img[Title]]` (works in the embedded TW,\n * story view and /tw/render). Failed downloads degrade to plain URL lines.\n */\nexport function buildImageNoteTiddler(opts: {\n title: string\n url: string\n text?: string\n tag: string\n source?: string\n at?: string\n stored: string[]\n failed: Array<{ url: string; error: string }>\n extraTags?: string[]\n}): Tiddler {\n const at = opts.at ?? new Date().toISOString()\n const selection = typeof opts.text === 'string' ? opts.text.trim() : ''\n const lines = [`> 来源:${opts.url}`, '']\n if (selection.length > 0) lines.push(selection, '')\n lines.push('---', '!! 图片')\n for (const title of opts.stored) lines.push(`[img[${title}]]`)\n for (const f of opts.failed) lines.push(`* ${f.url}(${f.error})`)\n lines.push('', '---', `剪藏时间:${at}`)\n const tags = [opts.tag, ...(opts.extraTags ?? [])].filter((t) => t.trim().length > 0)\n return {\n title: opts.title,\n text: lines.join('\\n'),\n type: 'text/vnd.tiddlywiki',\n ...(tags.length > 0 ? { tags } : {}),\n 'clip-url': opts.url,\n 'clip-source': opts.source ?? 'bookmarklet',\n 'clip-at': at,\n }\n}\n\n/** Build a binary attachment tiddler: `type: <mime>`, `text: <base64>`. */\nexport function buildBinaryTiddler(opts: {\n title: string\n mime: string\n buffer: Uint8Array | Buffer\n srcUrl: string\n noteTitle: string\n tag: string\n source?: string\n at?: string\n}): Tiddler {\n const at = opts.at ?? new Date().toISOString()\n const tags = [opts.tag].filter((t) => t.trim().length > 0)\n return {\n title: opts.title,\n type: opts.mime,\n text: Buffer.from(opts.buffer).toString('base64'),\n ...(tags.length > 0 ? { tags } : {}),\n 'clip-url': opts.srcUrl,\n 'clip-source': opts.source ?? 'bookmarklet',\n 'clip-at': at,\n 'clip-note': opts.noteTitle,\n }\n}\n\n/** Known image extensions → mime (used when a CDN mislabels as octet-stream). */\nconst IMAGE_EXT_MIME: Record<string, string> = {\n png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif',\n webp: 'image/webp', svg: 'image/svg+xml', avif: 'image/avif', bmp: 'image/bmp', ico: 'image/x-icon',\n}\n\n/**\n * Decide the stored mime for a downloaded image: an `image/*` content-type\n * wins; otherwise a known image extension in the URL rescues CDNs that serve\n * `application/octet-stream`. null = not (recognizably) an image.\n */\nexport function pickImageMime(contentType: string | null, url: string): string | null {\n const ct = ((contentType ?? '').split(';')[0] ?? '').trim().toLowerCase()\n if (ct.startsWith('image/')) return ct\n const ext = ((url.split('?')[0] ?? '').split('.').pop() ?? '').toLowerCase()\n if (ct === 'application/octet-stream' && IMAGE_EXT_MIME[ext] !== undefined) return IMAGE_EXT_MIME[ext]\n return null\n}\n\n/** File extension for a stored image mime. */\nexport function imageExtensionForMime(mime: string): string {\n const map: Record<string, string> = {\n 'image/png': 'png', 'image/jpeg': 'jpg', 'image/gif': 'gif', 'image/webp': 'webp',\n 'image/svg+xml': 'svg', 'image/avif': 'avif', 'image/bmp': 'bmp', 'image/x-icon': 'ico',\n }\n if (map[mime] !== undefined) return map[mime]\n const subtype = (mime.split('/')[1] ?? 'img').replace(/[^a-z0-9]/gi, '')\n return subtype.length > 0 ? subtype.slice(0, 8) : 'img'\n}\n\nfunction cleanStr(value: unknown): string {\n return typeof value === 'string' ? value : ''\n}\n\n/** Validate a clip payload → normalized value or an error. */\nexport function parseClipPayload(body: unknown): { ok: true; value: ClipPayload } | { ok: false; error: string } {\n if (typeof body !== 'object' || body === null || Array.isArray(body)) {\n return { ok: false, error: '请求体必须是 JSON 对象' }\n }\n const raw = body as Record<string, unknown>\n const title = cleanStr(raw.title).trim().slice(0, 300)\n const url = cleanStr(raw.url).trim().slice(0, 2000)\n const text = cleanStr(raw.text).slice(0, MAX_CLIP_TEXT_LENGTH)\n const source = cleanStr(raw.source).trim().slice(0, 40)\n let extraTags: string[] = []\n if (Array.isArray(raw.tags)) {\n extraTags = raw.tags.filter((t): t is string => typeof t === 'string').map((t) => t.trim()).filter(Boolean).slice(0, 10)\n }\n let images: string[] = []\n if (Array.isArray(raw.images)) {\n images = raw.images\n .filter((u): u is string => typeof u === 'string')\n .map((u) => u.trim())\n .filter((u) => u.length > 0 && u.length <= 2000)\n .slice(0, MAX_CLIP_IMAGES)\n }\n if (title.length === 0) return { ok: false, error: '缺少 title(页面标题)' }\n if (url.length === 0) return { ok: false, error: '缺少 url(页面地址)' }\n return { ok: true, value: { title, url, text, tags: extraTags, source, images } }\n}\n\n/** CORS + no-store headers for every bridge response. */\nconst CORS_HEADERS: Record<string, string> = {\n 'access-control-allow-origin': '*',\n 'access-control-allow-methods': 'POST, GET, OPTIONS',\n 'access-control-allow-headers': 'content-type, x-clip-token',\n 'access-control-allow-private-network': 'true',\n 'access-control-max-age': '86400',\n 'cache-control': 'no-store',\n}\n\n/**\n * The loopback clip bridge. `start(port)` binds once (127.0.0.1); config\n * (enabled/token/tag) is re-resolved per request through `deps.getConfig`,\n * so settings-page saves apply live. `stop()` is idempotent.\n */\nexport class ClipBridge {\n private server: Server | undefined\n private boundPort = 0\n\n constructor(private readonly deps: ClipBridgeDeps) {}\n\n /** The port currently bound (0 until start succeeds). */\n get port(): number {\n return this.boundPort\n }\n\n /** Bind 127.0.0.1:port; rejects on EADDRINUSE etc. (caller logs & carries on). */\n start(port: number): Promise<void> {\n if (this.server !== undefined) return Promise.resolve()\n const server = createServer((req, res) => {\n void this.handle(req, res).catch((err: unknown) => {\n const message = err instanceof Error ? err.message : String(err)\n this.deps.log?.(`request failed: ${message}`)\n if (!res.headersSent) {\n this.respond(res, { ok: false, error: '内部错误' }, 500)\n } else {\n res.destroy()\n }\n })\n })\n // Malformed HTTP must not crash the bridge (default behavior would emit\n // an unhandled 'clientError').\n server.on('clientError', (_err, socket) => {\n socket.end('HTTP/1.1 400 Bad Request\\r\\n\\r\\n')\n })\n this.server = server\n return new Promise<void>((resolve, reject) => {\n const onError = (err: Error): void => {\n server.removeListener('listening', onListening)\n if (this.server === server) this.server = undefined\n this.deps.log?.(`bind 127.0.0.1:${port} failed: ${err.message}`)\n reject(err)\n }\n const onListening = (): void => {\n server.removeListener('error', onError)\n // Replace the bind-time handler with a PERMANENT one (v0.19.3): without\n // any 'error' listener a later server error (e.g. EMFILE/ECONNRESET on a\n // accept) is an unhandled 'error' event → Node throws → the whole dsh\n // web process dies. Log and keep serving.\n server.on('error', (err: Error) => {\n this.deps.log?.(`server error after listen: ${err.message}`)\n })\n const addr = server.address() as AddressInfo\n this.boundPort = addr.port\n resolve()\n }\n server.once('error', onError)\n server.once('listening', onListening)\n server.listen(port, '127.0.0.1')\n })\n }\n\n /** Close the listener (idempotent; awaited by the host teardown). */\n stop(): Promise<void> {\n const server = this.server\n this.server = undefined\n this.boundPort = 0\n if (server === undefined) return Promise.resolve()\n // `server.close()` only resolves once every open connection ends, and the\n // bookmarklet's keep-alive socket can hold it open forever — the effect\n // disposer (plugin unload) would then hang (v0.19.3). Force-close the\n // connections and cap the wait.\n return new Promise<void>((resolve) => {\n let done = false\n const finish = (): void => {\n if (done) return\n done = true\n resolve()\n }\n try {\n ;(server as unknown as { closeAllConnections?: () => void }).closeAllConnections?.()\n } catch { /* not available on this Node */ }\n server.close(() => finish())\n setTimeout(finish, 1_000).unref?.()\n })\n }\n\n private respond(res: ServerResponse, payload: unknown, status = 200): void {\n res.writeHead(status, { 'content-type': 'application/json; charset=utf-8', ...CORS_HEADERS })\n res.end(JSON.stringify(payload))\n }\n\n private async handle(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const cfg = this.deps.getConfig()\n const urlPath = (req.url ?? '/').split('?')[0] ?? '/'\n\n // CORS preflight is answered unconditionally (Cross-Origin requests from a\n // bookmarklet always preflight: content-type application/json is not a\n // \"simple\" header, and Chromium's PNA wants Access-Control-Allow-Private-Network).\n if (req.method === 'OPTIONS') {\n res.writeHead(204, CORS_HEADERS)\n res.end()\n return\n }\n // DNS-rebinding defense BEFORE anything else: only loopback Host headers.\n if (!hostAllowed(req.headers.host, this.boundPort)) {\n this.respond(res, { ok: false, error: 'Forbidden host' }, 403)\n return\n }\n\n if (req.method === 'GET' && (urlPath === '/' || urlPath === '/health')) {\n // Minimal liveness answer: CORS is `*`, so any web page can read this.\n // `tag` / `tokenSet` used to be echoed here — a page could fingerprint\n // the bridge (and whether a token is configured) without knowing it\n // (v0.19.0).\n this.respond(res, {\n ok: true,\n name: 'dsh-tiddlywiki clip bridge',\n enabled: cfg.enabled,\n })\n return\n }\n\n if (req.method !== 'POST' || urlPath !== '/clip') {\n this.respond(res, { ok: false, error: `未知路径 ${req.method} ${urlPath}` }, 404)\n return\n }\n\n // Enabled + token gates: the port stays bound even while disabled so the\n // settings-page toggle works without a dsh web restart; a disabled bridge\n // answers 503 (and the token check never runs for it).\n if (!cfg.enabled) {\n this.respond(res, { ok: false, error: '剪藏桥未启用:请在 DSH 设置 → 常规配置 勾选「启用本地剪藏桥」' }, 503)\n return\n }\n if (cfg.token.length > 0) {\n const got = req.headers['x-clip-token']\n if (typeof got !== 'string' || !safeTokenEqual(got, cfg.token)) {\n this.respond(res, { ok: false, error: 'token 校验失败' }, 401)\n return\n }\n }\n\n let body: unknown\n try {\n body = JSON.parse(await readBody(req, MAX_CLIP_BODY_BYTES)) as unknown\n } catch {\n this.respond(res, { ok: false, error: '请求体不是合法的 JSON(或过大)' }, 400)\n return\n }\n const parsed = parseClipPayload(body)\n if (!parsed.ok) {\n this.respond(res, { ok: false, error: parsed.error }, 400)\n return\n }\n const { title, url, text, tags, source, images } = parsed.value\n\n let resolvedTitle: string\n try {\n resolvedTitle = await resolveClipTitle((t) => this.deps.exists(t), title)\n } catch {\n this.respond(res, { ok: false, error: 'TiddlyWiki 服务暂不可用,请稍后重试' }, 503)\n return\n }\n\n const at = new Date().toISOString()\n const sourceName = source.length > 0 ? source : 'bookmarklet'\n\n // Download + store the chosen images as binary attachment tiddlers.\n const stored: string[] = []\n const failed: Array<{ url: string; error: string }> = []\n const imageResults: ClipImageResult[] = []\n for (let i = 0; i < images.length; i++) {\n const imageUrl = images[i]\n if (imageUrl === undefined) continue\n try {\n // SSRF gate (policy lives here, not in the caller's downloader): never\n // fetch a loopback/LAN/metadata URL on behalf of a web page.\n await assertPublicImageUrl(imageUrl)\n const { buffer, type } = await this.deps.download(imageUrl, url)\n if (buffer.length > MAX_IMAGE_BYTES) {\n throw new Error('图片超过 15MB 上限')\n }\n const mime = pickImageMime(type, imageUrl)\n if (mime === null) {\n throw new Error('内容不是可识别的图片')\n }\n const ext = imageExtensionForMime(mime)\n const imageTitle = await resolveClipTitle((t) => this.deps.exists(t), `${resolvedTitle} 图片 ${i + 1}.${ext}`)\n await this.deps.write(buildBinaryTiddler({\n title: imageTitle,\n mime,\n buffer,\n srcUrl: imageUrl,\n noteTitle: resolvedTitle,\n tag: cfg.tag,\n source: sourceName,\n at,\n }))\n stored.push(imageTitle)\n imageResults.push({ url: imageUrl, ok: true, title: imageTitle })\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n failed.push({ url: imageUrl, error: message })\n imageResults.push({ url: imageUrl, ok: false, error: message })\n }\n }\n\n // The note: when images were chosen (stored or failed), wikitext with\n // `[img[Title]]` embeds + failed ones degrade to URL lines; a pure\n // text-only clip stays markdown as before.\n const noteTiddler = stored.length > 0 || images.length > 0\n ? buildImageNoteTiddler({\n title: resolvedTitle, url, ...(text.length > 0 ? { text } : {}),\n tag: cfg.tag, source: sourceName, at, stored, failed, extraTags: tags,\n })\n : buildClipTiddler({\n title: resolvedTitle, url, ...(text.length > 0 ? { text } : {}),\n tag: cfg.tag, source: sourceName, at, extraTags: tags,\n })\n try {\n await this.deps.write(noteTiddler)\n } catch {\n // The note is the primary artifact — fail the clip (images may already\n // be stored; they are still referenced by the note if it lands later).\n this.respond(res, { ok: false, error: 'TiddlyWiki 服务暂不可用,剪藏未写入' }, 503)\n return\n }\n this.respond(res, { ok: true, title: resolvedTitle, url, tag: cfg.tag, source: sourceName, images: imageResults })\n }\n}","/**\n * Self-contained replacements for the @deepseek-ai runtime imports the host\n * half must NEVER take from npm-mirror SDK packages (dsh-home-paths,\n * dsh-tools' defineTool).\n *\n * Why (design doc §4.4, taskboard lesson): a published copy must not resolve\n * `@deepseek-ai/dsh-tools` from the profile's node_modules — an npm-mirror\n * dsh-tools there shadows the CLI-internal build for the WHOLE base layer and\n * breaks the agent loop. Everything here is a pure, structure-compatible\n * reimplementation of the exact behavior the registry relies on:\n *\n * - `dshHomePath` mirrors `join(resolve(env.DSH_HOME ?? ~/.dsh), ...segments)`;\n * - `defineTool` compiles author-facing parameter specs into the same raw\n * JSON-Schema subset the registry expects and pre-validates model arguments.\n *\n * @module dsh-tiddlywiki/sdk\n */\nimport { homedir } from 'node:os'\nimport { join, resolve } from 'node:path'\n\n/** The DSH user home (DSH_HOME overrides). */\nexport function dshHomePath(...segments: string[]): string {\n const override = process.env.DSH_HOME\n const home = resolve(override !== undefined && override.length > 0 ? override : join(homedir(), '.dsh'))\n return join(home, ...segments)\n}\n\n/** Author-facing scalar spec. */\ninterface ScalarSpec {\n readonly type: 'string' | 'number' | 'integer' | 'boolean' | 'null'\n readonly description?: string\n readonly enum?: readonly unknown[]\n readonly const?: unknown\n}\n\n/** Author-facing object spec (additionalProperties is mandatory). */\ninterface ObjectSpec {\n readonly type: 'object'\n readonly additionalProperties: boolean\n readonly description?: string\n /** Nested properties may also declare `required` (array items etc.). */\n readonly properties?: Readonly<Record<string, ParameterSpec>>\n}\n\n/** Author-facing value spec. */\ntype ValueSpec = ScalarSpec | ObjectSpec | { readonly type: 'json'; readonly description?: string } | { readonly type: 'array'; readonly items?: ValueSpec; readonly description?: string }\n\n/** Author-facing parameter entry (a value spec plus top-level required). */\ntype ParameterSpec = ValueSpec & { readonly required?: boolean }\n\n/** Raw JSON-Schema subset node. */\ntype RawSchema = Record<string, unknown>\n\n/** Compile one value spec to the raw subset (json → annotation-only). */\nfunction compileValue(spec: ValueSpec): RawSchema {\n const node: RawSchema = {}\n const description = (spec as { description?: string }).description\n if (typeof description === 'string' && description.length > 0) node.description = description\n const type = (spec as { type?: string }).type\n if (type === undefined || type === 'json') return node\n if (type === 'object') {\n const objectSpec = spec as ObjectSpec\n node.type = 'object'\n node.additionalProperties = objectSpec.additionalProperties\n if (objectSpec.properties !== undefined) {\n const compiled = compilePropertyMap(objectSpec.properties)\n node.properties = compiled.properties\n // Nested objects also carry their own `required` list (array items, etc.).\n if (compiled.required !== undefined) node.required = compiled.required\n }\n return node\n }\n if (type === 'array') {\n node.type = 'array'\n const items = (spec as { items?: ValueSpec }).items\n if (items !== undefined) node.items = compileValue(items)\n return node\n }\n node.type = type\n const enumValues = (spec as ScalarSpec).enum\n if (enumValues !== undefined) node.enum = [...enumValues]\n const constValue = (spec as ScalarSpec).const\n if (constValue !== undefined) node.const = constValue\n return node\n}\n\n/** Compile a property map: properties + collected required list. */\nfunction compilePropertyMap(spec: Readonly<Record<string, ParameterSpec>>): { properties: Record<string, RawSchema>; required?: string[] } {\n const properties: Record<string, RawSchema> = {}\n const required: string[] = []\n for (const [name, entry] of Object.entries(spec)) {\n const { required: isRequired, ...valueSpec } = entry as ParameterSpec & Record<string, unknown>\n properties[name] = compileValue(valueSpec as ValueSpec)\n if (isRequired === true) required.push(name)\n }\n return required.length > 0 ? { properties, required } : { properties }\n}\n\n/** Does a JS value match a raw-subset scalar type? */\nfunction matchesScalarType(value: unknown, type: string): boolean {\n switch (type) {\n case 'string': return typeof value === 'string'\n case 'number': return typeof value === 'number'\n case 'integer': return typeof value === 'number' && Number.isInteger(value)\n case 'boolean': return typeof value === 'boolean'\n case 'null': return value === null\n default: return true\n }\n}\n\n/** Validate a value against the compiled subset; returns path-qualified violations. */\nfunction validateValue(schema: RawSchema, value: unknown, path: string): string[] {\n if (typeof schema.type !== 'string' || schema.type.length === 0) return []\n if (schema.type === 'object') {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return [`${path} must be an object`]\n const violations: string[] = []\n const present = value as Record<string, unknown>\n for (const key of (schema.required as string[] | undefined) ?? []) {\n if (!(key in present)) violations.push(`${path}.${key} is required`)\n }\n if (schema.additionalProperties === false) {\n const known = new Set(Object.keys((schema.properties as Record<string, RawSchema> | undefined) ?? {}))\n for (const key of Object.keys(present)) {\n if (!known.has(key)) violations.push(`${path}.${key} is not a declared property`)\n }\n }\n for (const [key, child] of Object.entries((schema.properties as Record<string, RawSchema> | undefined) ?? {})) {\n if (key in present) violations.push(...validateValue(child, present[key], `${path}.${key}`))\n }\n return violations\n }\n if (schema.type === 'array') {\n if (!Array.isArray(value)) return [`${path} must be an array`]\n const violations: string[] = []\n const items = schema.items as RawSchema | undefined\n if (items !== undefined) {\n value.forEach((item, index) => { violations.push(...validateValue(items, item, `${path}[${index}]`)) })\n }\n return violations\n }\n if (!matchesScalarType(value, schema.type)) return [`${path} must be ${schema.type}`]\n const enumValues = schema.enum as unknown[] | undefined\n if (enumValues !== undefined && !enumValues.some(v => v === value)) {\n return [`${path} must be one of ${enumValues.map(String).join(', ')}`]\n }\n const constValue = (schema as { const?: unknown }).const\n if (constValue !== undefined && constValue !== value) {\n return [`${path} must be ${String(constValue)}`]\n }\n return []\n}\n\n/** Options shape we consume (a structural subset of the SDK's defineTool). */\nexport interface DefineToolOptions<A, V> {\n readonly name: string\n readonly description: string\n readonly parameters: Readonly<Record<string, ParameterSpec>>\n readonly output: {\n render(args: A, value: V): Array<{ type: 'text'; text: string }>\n }\n execute(args: A, exec: unknown): Promise<V>\n}\n\n/** A registry-ready tool definition (structure-compatible with the SDK's). */\nexport interface ToolDefinition<A = unknown, V = unknown> {\n readonly name: string\n readonly description: string\n readonly parameters: RawSchema\n readonly output: {\n readonly schema: RawSchema\n render(args: A, value: V): Array<{ type: 'text'; text: string }>\n }\n execute(args: A, exec: unknown): Promise<V>\n}\n\n/**\n * Define a first-party tool: compile the parameter spec, pre-validate\n * arguments, and pass through the execution.\n */\nexport function defineTool<A extends Record<string, unknown>, V>(options: DefineToolOptions<A, V>): ToolDefinition<A, V> {\n const compiled = compilePropertyMap(options.parameters as Readonly<Record<string, ParameterSpec>>)\n const parameters: RawSchema = { type: 'object', properties: compiled.properties }\n if (compiled.required !== undefined) parameters.required = compiled.required\n const userExecute = options.execute\n return {\n name: options.name,\n description: options.description,\n parameters,\n output: {\n // The registry's render contract only consumes `render()`; the declared\n // `schema` is kept as the empty raw node it always effectively was (the\n // per-tool `schema: {type:'json'}` literals were never read — removed in\n // v0.20.0 so the option list matches what the code does).\n schema: {},\n render(args, value) {\n return options.output.render(args, value)\n },\n },\n async execute(args, exec) {\n const violations = validateValue(parameters, args, 'arguments')\n if (violations.length > 0) {\n throw new Error(`Error: invalid arguments: ${violations.join('; ')}`)\n }\n return userExecute(args, exec)\n },\n }\n}\n","/**\n * The `tiddlywiki_*` agent tools (design doc §11, D8) plus the extension\n * point: `registerTiddlywikiTools(ctx, deps)` registers tools list-style, so a\n * new tool is just one more `defineTool` in the array — index.ts never changes.\n *\n * Toolset (v0.19):\n * search / get / put / batch_put / append / rename / delete / trash /\n * backlinks / attach / lint / recent / list_tags / git_sync / git_resolve\n *\n * RENDER CONTRACT (design doc §4.3): the registry feeds `output.render(args,\n * value)` into the loop — the model sees ONLY the rendered text, never the raw\n * JSON `value`. Every render must carry the complete facts an agent needs to\n * act (titles, tags, snippets, git state); a terse UI summary starves it.\n *\n * @module dsh-tiddlywiki/host/tools\n */\nimport { defineTool } from '../sdk.ts'\nimport { readFile, stat } from 'node:fs/promises'\nimport { basename, extname, isAbsolute, join } from 'node:path'\nimport { MISSING_TYPE_FILTER, isBinaryType, toIsoDateString } from './tw-api.ts'\nimport type { TiddlyWebClient, Tiddler } from './tw-api.ts'\nimport type { GitFace, GitStatusView } from './git.ts'\nimport { downloadClipImage } from './clip-bridge.ts'\nimport { flushPendingWrites } from './seeds.ts'\nimport { snippetOf } from './text-util.ts'\nimport {\n assertNoConflict,\n buildWriteTiddler,\n cleanTiddler,\n flattenTiddlerFields,\n normalizeTagArg,\n} from './write-policy.ts'\n\nexport { AGENT_WRITTEN_TAG, DEFAULT_NOTE_TYPE, HUMAN_EDITED_TAG } from './write-policy.ts'\n\nimport type { PromptToolSummary } from './prompt.ts'\n\n/**\n * Summaries of the tools registered by the LAST `registerTiddlywikiTools()`\n * pass — the single source for the `full` prompt catalogue and for\n * `scripts/verify-prompt.mjs` (v0.21.0).\n *\n * History: the prompt carried a HAND-WRITTEN parameter catalogue that was last\n * updated in v0.19.0 and silently drifted 6 signatures behind the tools by\n * v0.20.1. Collecting them here from the same definitions that reach the model\n * makes that class of drift impossible.\n */\nconst REGISTERED_TOOL_SUMMARY: PromptToolSummary[] = []\n\n/** Tool summaries of the last registration pass (empty before one has run). */\nexport function tiddlywikiToolSummary(): readonly PromptToolSummary[] {\n return REGISTERED_TOOL_SUMMARY\n}\n\n/** Structural shape the collector needs from a registry-ready tool. */\ninterface RegistrableTool {\n readonly name: string\n readonly parameters: Record<string, unknown>\n}\n\n\n/** Structural tool-registry face (subset of the dsh tools service). */\nexport interface ToolsCtx {\n tools: { register(tool: unknown): () => void }\n}\n\nexport interface ToolsDeps {\n /** Lazy TW client — undefined while the service is not up. */\n wiki: () => TiddlyWebClient | undefined\n git: GitFace\n wikiPath: () => string\n /** Debounced auto-commit touch (fires after our writes). */\n autoCommit: () => void\n /** Restart the TW child (same port). Called after a pull that changed the\n * working tree, so the server drops its stale in-memory snapshot and the\n * agent sees the pulled content. Optional — absent in headless contexts. */\n restartWiki?: () => Promise<void>\n}\n\n/**\n * Snippet centred on the FIRST match of `query` (v0.19.0). The old snippet was\n * always the first 160 characters of the note, so a hit deep inside a long note\n * showed the model text that did not contain the term it searched for.\n */\nfunction snippetAround(text: string, query: string, max = 160): string {\n const flat = text.replace(/\\s+/g, ' ').trim()\n if (flat.length <= max) return flat\n const needle = query.trim().toLowerCase()\n if (needle.length === 0) return `${flat.slice(0, max)}…`\n const at = flat.toLowerCase().indexOf(needle)\n if (at < 0) return `${flat.slice(0, max)}…`\n const half = Math.max(0, Math.floor((max - needle.length) / 2))\n const start = Math.max(0, at - half)\n const end = Math.min(flat.length, start + max)\n const prefix = start > 0 ? '…' : ''\n const suffix = end < flat.length ? '…' : ''\n return `${prefix}${flat.slice(start, end)}${suffix}`\n}\n\n/** Trash namespace for soft-deleted tiddlers (system titles: never listed by\n * the default recipe filter, so deleted notes stop appearing in search/recent\n * while staying recoverable). */\nexport const TRASH_PREFIX = '$:/dsh-tiddlywiki/trash/'\n\n/**\n * Trash INDEX tiddler. A `$:/`-titled tiddler cannot be ENUMERATED through the\n * recipe listing at all: a fresh wiki has `$:/config/SyncSystemTiddlersFromServer\n * = \"no\"`, and `get-tiddlers-json.js` then appends `+[!is[system]]` to every\n * filter (verified). The trash therefore keeps its own index and reads it with a\n * plain GET (which works for system titles). (v0.19.0)\n */\nexport const TRASH_INDEX_TITLE = '$:/dsh-tiddlywiki/trash-index'\n\ninterface TrashIndexEntry { trash: string; of: string; at: string }\n\n/** Build the trash title that holds one soft-deleted tiddler. */\nfunction trashTitleFor(title: string, at = new Date()): string {\n const stamp = at.toISOString().replace(/[:.]/g, '-')\n return `${TRASH_PREFIX}${stamp}/${title}`\n}\n\n/**\n * Read the trash index.\n *\n * `readOk:false` distinguishes「索引读不到」(a failure: 5xx/timeout/auth — the\n * tool layer MUST stop, because overwriting the index with whatever we managed\n * to read would drop every earlier entry and orphan those trashed tiddlers) from\n *「索引不存在」(404 = a brand-new wiki with an empty trash) and「JSON 坏了」\n * (rebuildable: the trash tiddlers themselves are still there, only the index\n * entries are lost).\n *\n * Swallowing the error into `[]` was the v0.19.4 defect: one transient failure\n * during `tiddlywiki_delete` rewrote the whole index to a single-entry array.\n */\nasync function readTrashIndex(wiki: TiddlyWebClient): Promise<{ readOk: boolean; corrupted: boolean; entries: TrashIndexEntry[] }> {\n let tiddler: Tiddler | undefined\n try {\n // Only 404 yields undefined (tw-api): any other throw is a real read failure.\n tiddler = await wiki.get(TRASH_INDEX_TITLE)\n } catch {\n return { readOk: false, corrupted: false, entries: [] }\n }\n if (tiddler === undefined || typeof tiddler.text !== 'string' || tiddler.text.trim().length === 0) {\n return { readOk: true, corrupted: false, entries: [] }\n }\n try {\n const parsed = JSON.parse(tiddler.text) as unknown\n if (!Array.isArray(parsed)) return { readOk: true, corrupted: true, entries: [] }\n const entries = parsed.filter((entry): entry is TrashIndexEntry => {\n if (typeof entry !== 'object' || entry === null) return false\n const e = entry as Record<string, unknown>\n return typeof e.trash === 'string' && typeof e.of === 'string'\n }).map((e) => ({ trash: e.trash, of: e.of, at: typeof e.at === 'string' ? e.at : '' }))\n return { readOk: true, corrupted: false, entries }\n } catch {\n return { readOk: true, corrupted: true, entries: [] }\n }\n}\n\n/** Persist the trash index. */\nasync function writeTrashIndex(wiki: TiddlyWebClient, entries: TrashIndexEntry[]): Promise<void> {\n await wiki.put({ title: TRASH_INDEX_TITLE, text: JSON.stringify(entries, null, 2), type: 'application/json', tags: [] })\n}\n\n/** Raised when the trash index cannot be read: the operation must abort rather\n * than rebuild the index from an empty base (data-loss guard, v0.19.5). */\nexport class TrashIndexUnavailableError extends Error {\n constructor() {\n super('回收站索引暂时读不到(TiddlyWiki 可能正在重启或超时);为避免覆盖索引、丢回收站记录,本次操作已中止,请稍后重试。')\n this.name = 'TrashIndexUnavailableError'\n }\n}\n\n/** Mime types offered by `tiddlywiki_attach` for local files. */\nconst ATTACH_MIME_BY_EXT: Record<string, string> = {\n png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif',\n webp: 'image/webp', svg: 'image/svg+xml', avif: 'image/avif', bmp: 'image/bmp',\n ico: 'image/x-icon', pdf: 'application/pdf', zip: 'application/zip',\n txt: 'text/plain', md: 'text/markdown', json: 'application/json', csv: 'text/csv',\n mp3: 'audio/mpeg', wav: 'audio/wav', ogg: 'audio/ogg', mp4: 'video/mp4',\n epub: 'application/epub+zip',\n}\n\n/** Size cap for one attachment (mirrors the clip bridge's image cap). */\nexport const MAX_ATTACH_BYTES = 15 * 1024 * 1024\n\n/**\n * Count references to `target` inside wiki text: `[[target]]`,\n * `[[display|target]]`, `[[target|display]]` is NOT a reference to target as a\n * link target in TW (the first part is the text, the second the target), so\n * only the second position counts, plus `{{target}}` transclusions.\n */\nfunction countRefsTo(text: string, target: string): number {\n if (target.length === 0) return 0\n const escaped = target.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n const patterns = [\n new RegExp(`\\\\[\\\\[${escaped}\\\\]\\\\]`, 'g'),\n new RegExp(`\\\\[\\\\[[^\\\\]|]*\\\\|${escaped}\\\\]\\\\]`, 'g'),\n new RegExp(`\\\\{\\\\{${escaped}\\\\}\\\\}`, 'g'),\n ]\n let count = 0\n for (const re of patterns) count += (text.match(re) ?? []).length\n return count\n}\n\n/**\n * Junk tags produced by tooling mistakes rather than by a human. The live wiki\n * still carries `筛选器错误: Missing [ in filter expression` from the v0.16.22\n * filter bug — a lint must be able to find that class of damage again.\n */\nfunction isJunkTag(tag: string): boolean {\n if (tag.length === 0) return false\n if (/筛选器错误|Missing \\[|in filter expression/i.test(tag)) return true\n if (/[[\\]{}|<>]/.test(tag)) return true\n if (/^(in|filter|expression|Missing|tags:)$/i.test(tag)) return true\n if (/^[A-Za-z]:\\\\/.test(tag)) return true\n if (tag.trim() !== tag) return true\n return false\n}\n\n/**\n * tiddler 的非内容字段(自定义字段 + 内容类型 + 时间戳 + revision),供模型读。\n *\n * v0.19.1 修复:单条 GET 把自定义字段**嵌在 `fields` 里**,旧实现只把顶层条目\n * 抄一遍,于是渲染出来的 `字段:` 行是 `fields=[object Object]` —— `q`/`due`/\n * `clip-url`/`workspace` 这些笔记元数据对模型完全不可见(实测)。现在统一走\n * `flattenTiddlerFields()` 摊平,并显式带上 `revision`(乐观并发令牌)。\n */\nfunction pickFields(t: Tiddler): Record<string, unknown> {\n const out = flattenTiddlerFields(t)\n if (t.revision !== undefined) out.revision = t.revision\n return out\n}\n\n/**\n * 覆盖写入前后内容类型的差异(v0.20.1)。内容类型决定 TW 用哪个 parser,静默变化\n * 会让 CSS 被当 Markdown、Markdown 笔记被当 wikitext,所以覆盖路径必须把这个差异\n * 回执给模型。只有两边都拿得到 `type` 且不同才报告(新建 → 无 from)。\n */\nfunction typeChangeOf(existing: Tiddler | undefined, next: Tiddler): { typeChanged?: { from: string; to: string } } {\n if (existing === undefined) return {}\n const from = typeof existing.type === 'string' && existing.type.length > 0 ? existing.type : undefined\n const to = typeof next.type === 'string' && next.type.length > 0 ? next.type : undefined\n if (from === undefined || to === undefined || from === to) return {}\n return { typeChanged: { from, to } }\n}\n\n/**\n * Rewrite TiddlyWiki references to a title inside wiki text: `[[Title]]`,\n * `[[display|Title]]`, `{{Title}}` → the new title. Best-effort link/text\n * migration for tiddlywiki_rename; returns the rewritten text + hit count.\n */\nfunction rewriteRefs(text: string, oldTitle: string, newTitle: string): { text: string; count: number } {\n if (oldTitle.length === 0) return { text, count: 0 }\n const escaped = oldTitle.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n const re = new RegExp(`(\\\\[\\\\[[^\\\\]|]*\\\\|)${escaped}(\\\\]\\\\])|(\\\\[\\\\[)${escaped}(\\\\]\\\\])|(\\\\{\\\\{)${escaped}(\\\\}\\\\})`, 'g')\n let count = 0\n const out = text.replace(re, (...args: Array<string | number>) => {\n const p = args as string[]\n count++\n const prefix = p[1] ?? p[3] ?? p[5] ?? ''\n const suffix = p[2] ?? p[4] ?? p[6] ?? ''\n return `${prefix}${newTitle}${suffix}`\n })\n return { text: out, count }\n}\n\n/**\n * Insert `addition` at the end of the section introduced by `heading` (Markdown\n * `#`/`##`… or wikitext `!`/`!!`…). Falls back to appending at the end of the\n * document when the heading is not found. Used by tiddlywiki_append so an agent\n * can add to one section of a long note without rewriting the whole file.\n */\nfunction insertIntoSection(base: string, heading: string, addition: string): string {\n const lines = base.split('\\n')\n const headingText = (line: string): string | null => {\n const md = /^#{1,6}\\s+(.*)$/.exec(line)\n if (md !== null) return (md[1] ?? '').trim()\n const tw = /^!{1,6}\\s*(.*)$/.exec(line)\n if (tw !== null) return (tw[1] ?? '').trim()\n return null\n }\n const start = lines.findIndex((line) => headingText(line) === heading)\n if (start < 0) {\n return base.trim().length === 0 ? addition : `${base.replace(/\\s+$/, '')}\\n\\n${addition}`\n }\n let end = lines.length\n for (let i = start + 1; i < lines.length; i++) {\n if (headingText(lines[i] ?? '') !== null) {\n end = i\n break\n }\n }\n const before = lines.slice(0, end).join('\\n').replace(/\\s+$/, '')\n const after = lines.slice(end).join('\\n')\n const head = `${before}\\n\\n${addition}`\n return after.trim().length === 0 ? head : `${head}\\n\\n${after.replace(/^\\s+/, '')}`\n}\n\n\nexport function registerTiddlywikiTools(ctx: ToolsCtx, deps: ToolsDeps): Array<() => void> {\n const disposers: Array<() => void> = []\n // Re-collect on every pass: a hot reload re-registers the whole toolset, and\n // a stale entry must never linger in the prompt catalogue.\n REGISTERED_TOOL_SUMMARY.length = 0\n const register = (tool: RegistrableTool): void => {\n const properties = (tool.parameters.properties ?? {}) as Record<string, unknown>\n const required = Array.isArray(tool.parameters.required) ? (tool.parameters.required as string[]) : []\n REGISTERED_TOOL_SUMMARY.push({\n name: tool.name,\n params: Object.keys(properties).map((name) => ({ name, required: required.includes(name) })),\n })\n disposers.push(ctx.tools.register(tool))\n }\n\n /** Every read/write tool needs a live TW client — shared guard for the 8\n * wiki-facing tools (git tools operate on the repo path instead). */\n const requireWiki = (): TiddlyWebClient => {\n const wiki = deps.wiki()\n if (wiki === undefined) throw new Error('TiddlyWiki 服务未运行(tiddlywiki_status 可查)')\n return wiki\n }\n\n // ── tiddlywiki_search ────────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_search',\n description: '检索 TiddlyWiki 持久知识库:按关键词(可选 tags 数组 / since 修改时间 / type / field+value / limit)搜索非系统 tiddler,按相关度排序返回标题、标签、修改时间与命中处上下文片段。二进制 tiddler(图片等附件,正文为 base64)不参与检索。',\n parameters: {\n query: { type: 'string', description: '搜索关键词(大小写不敏感,子串匹配;命中标题/标签/正文,标题命中权重最高)', required: true },\n tags: { type: 'array', items: { type: 'string' }, description: '可选:要求同时包含的标签(AND)' },\n tag: { type: 'string', description: '可选:单个精确标签(与 tags 同为 AND)' },\n since: { type: 'string', description: '可选:ISO 时间(如 2026-09-01 或 2026-09-01T00:00:00Z),只返回修改时间不早于它的 tiddler' },\n type: { type: 'string', description: '可选:精确 tiddler 类型。⚠️ 不传则不限类型(推荐);插件默认把笔记写成 text/markdown,传 \"text/vnd.tiddlywiki\" 会把 Markdown 笔记全部排除' },\n field: { type: 'string', description: '可选:按自定义字段过滤(如 \"q\"、\"clip-url\"、\"workspace\")' },\n value: { type: 'string', description: '可选:field 必须等于该值(不传则只要求该字段存在)' },\n limit: { type: 'integer', description: '可选:返回条数上限(默认 30,最大 200)' },\n },\n output: {\n render: (_args, value: SearchResult) => {\n const filters: string[] = []\n if (value.tags.length > 0) filters.push(`tags=${value.tags.join(',')}`)\n if (value.since !== null) filters.push(`since=${value.since}`)\n if (value.type !== null) filters.push(`type=${value.type}`)\n if (value.field !== null) filters.push(`field=${value.field}${value.value !== null ? `=${value.value}` : ''}`)\n const head = `TiddlyWiki 搜索「${value.query}」${filters.length > 0 ? ` (${filters.join(' · ')})` : ''}:命中 ${value.total} 条(按相关度排序)。`\n const lines = [head]\n if (value.results.length === 0) lines.push('没有匹配的 tiddler。')\n for (const r of value.results) {\n const tags = r.tags.length > 0 ? ` [${r.tags.join(', ')}]` : ''\n const modified = r.modified !== null ? ` (${r.modified})` : ''\n lines.push(`- ${r.title}${tags}${modified}`)\n if (r.snippet.length > 0) lines.push(` ${r.snippet}`)\n }\n if (value.total > value.results.length) lines.push(`(另有 ${value.total - value.results.length} 条未展开,可用 tiddlywiki_get 读取具体标题,或提高 limit)`)\n return [{ type: 'text', text: lines.join('\\n') }]\n },\n },\n execute: async (args: { query: string; tags?: string[]; tag?: string; since?: string; type?: string; field?: string; value?: string; limit?: number }): Promise<SearchResult> => {\n const wiki = requireWiki()\n const { items, total } = await wiki.search(args.query, {\n tags: args.tags,\n tag: args.tag,\n since: args.since,\n type: args.type,\n field: args.field,\n value: args.value,\n limit: args.limit,\n })\n return {\n query: args.query,\n tags: args.tags ?? [],\n since: args.since ?? null,\n type: args.type ?? null,\n field: args.field ?? null,\n value: args.value ?? null,\n total,\n results: items.map((t) => ({ title: t.title, tags: t.tags ?? [], modified: toIsoDateString(t.modified), snippet: snippetAround(t.text ?? '', args.query) })),\n }\n },\n }))\n\n // ── tiddlywiki_recent ────────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_recent',\n description: '查看 TiddlyWiki 知识库最近修改的笔记(按修改时间倒序,排除系统 tiddler 与图片等二进制附件),返回标题、标签、修改时间与摘要。适合开工时快速了解近期动态。',\n parameters: {\n limit: { type: 'integer', description: '可选:返回条数(默认 15,最大 200)' },\n since: { type: 'string', description: '可选:只返回修改时间不早于该 ISO 时间的 tiddler' },\n },\n output: {\n render: (_args, value: RecentResult) => {\n const lines = [`TiddlyWiki 最近修改(最近 ${value.results.length} 条${value.since !== null ? `,since=${value.since}` : ''}):`]\n if (value.results.length === 0) lines.push('暂无笔记。')\n for (const r of value.results) {\n const tags = r.tags.length > 0 ? ` [${r.tags.join(', ')}]` : ''\n lines.push(`- ${r.title}${tags} (${r.modified ?? '?'})`)\n if (r.snippet.length > 0) lines.push(` ${r.snippet}`)\n }\n return [{ type: 'text', text: lines.join('\\n') }]\n },\n },\n execute: async (args: { limit?: number; since?: string }): Promise<RecentResult> => {\n const wiki = requireWiki()\n const items = await wiki.recent(args.limit ?? 15, args.since)\n return {\n since: args.since ?? null,\n results: items.map((t) => ({ title: t.title, tags: t.tags ?? [], modified: toIsoDateString(t.modified), snippet: snippetOf(t.text ?? '') })),\n }\n },\n }))\n\n // ── tiddlywiki_list_tags ─────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_list_tags',\n description: '列出 TiddlyWiki 知识库现有的非系统标签及各自计数(按使用次数降序),方便决定给笔记打什么 tag。默认最多返回 200 个(`limit` 可调,上限 1000),被截断时结果里会带 total/truncated。',\n parameters: {\n limit: { type: 'integer', description: '可选:最多返回多少个标签(按使用次数降序),默认 200,上限 1000。' },\n },\n output: {\n render: (_args, value: TagListResult) => {\n if (value.tags.length === 0) return [{ type: 'text', text: '知识库暂无标签。' }]\n const lines = [\n value.truncated\n ? `现有标签(共 ${value.total} 个,仅列出使用最多的 ${value.tags.length} 个,按使用次数降序):`\n : `现有标签(${value.total} 个,按使用次数降序):`,\n ]\n for (const t of value.tags) lines.push(`- ${t.tag} × ${t.count}`)\n if (value.truncated) lines.push(`(其余 ${value.total - value.tags.length} 个较少使用的标签未列出;需要时可提高 limit 重试)`)\n return [{ type: 'text', text: lines.join('\\n') }]\n },\n },\n execute: async (args: { limit?: number }): Promise<TagListResult> => {\n const wiki = requireWiki()\n const stats = await wiki.tagStats()\n const limit = typeof args.limit === 'number' && Number.isFinite(args.limit)\n ? Math.max(1, Math.min(Math.floor(args.limit), 1000))\n : 200\n const tags = stats.tags.slice(0, limit)\n return { count: tags.length, total: stats.total, truncated: tags.length < stats.total, tags }\n },\n }))\n\n // ── tiddlywiki_get ───────────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_get',\n description: '读取一个 TiddlyWiki tiddler 的完整内容(标题、全文、标签、自定义字段)。二进制 tiddler(图片等附件)只返回元数据,不返回 base64 正文。',\n parameters: {\n title: { type: 'string', description: 'tiddler 标题(精确匹配)', required: true },\n },\n output: {\n render: (_args, value: GetResult) => {\n if (value.notFound) return [{ type: 'text', text: `tiddler「${value.title}」不存在。可用 tiddlywiki_search 检索,或用 tiddlywiki_put 新建。` }]\n if (value.binary === true) {\n const lines = [`tiddler「${value.title}」是二进制附件(type=${value.binaryType ?? '?'},base64 正文约 ${value.binaryChars ?? 0} 字符),不返回正文。`]\n if (value.tags.length > 0) lines.push(`标签: ${value.tags.join(', ')}`)\n if (value.modified !== null) lines.push(`修改: ${value.modified}`)\n const fields = Object.entries(value.fields)\n if (fields.length > 0) lines.push(`字段: ${fields.map(([k, v]) => `${k}=${String(v)}`).join(', ')}`)\n lines.push(`如需查看附件本身,可打开 [${value.title}](/dsh-tiddlywiki/tw/#${encodeURIComponent(value.title)})。`)\n return [{ type: 'text', text: lines.join('\\n') }]\n }\n const lines = [`tiddler「${value.title}」`]\n if (value.tags.length > 0) lines.push(`标签: ${value.tags.join(', ')}`)\n if (value.modified !== null) lines.push(`修改: ${value.modified}`)\n const fields = Object.entries(value.fields)\n if (fields.length > 0) lines.push(`字段: ${fields.map(([k, v]) => `${k}=${String(v)}`).join(', ')}`)\n lines.push('--- 全文 ---')\n lines.push(value.text.length > 0 ? value.text : '(空)')\n return [{ type: 'text', text: lines.join('\\n') }]\n },\n },\n execute: async (args: { title: string }): Promise<GetResult> => {\n const wiki = requireWiki()\n const t = await wiki.get(args.title)\n if (t === undefined) return { notFound: true, title: args.title, text: '', tags: [], fields: {}, modified: null }\n const binary = isBinaryType(typeof t.type === 'string' ? t.type : undefined)\n const result: GetResult = {\n notFound: false,\n title: t.title,\n text: binary ? '' : (t.text ?? ''),\n tags: t.tags ?? [],\n fields: pickFields(t),\n modified: toIsoDateString(t.modified),\n }\n if (binary) {\n result.binary = true\n result.binaryType = typeof t.type === 'string' ? t.type : undefined\n result.binaryChars = (t.text ?? '').length\n }\n return result\n },\n }))\n\n // ── tiddlywiki_put ───────────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_put',\n description: '写入(新建或覆盖)一个 TiddlyWiki tiddler。同名覆盖;**覆盖已有条目时,未传的 tags / 自定义字段 / 内容类型都会原样保留**(不会静默丢掉笔记原有的标签、字段或 type;显式传 tags 才整体替换标签)。写入后触发防抖自动 commit(默认 60s;手动同步用 tiddlywiki_git_sync)。新建(title 不存在)时自动补打 agent-written 标签标记「由 Agent 撰写」,无需手动添加。内容类型:**只有新建**条目且未指定时才默认 text/markdown($:/ 系统条目除外)——覆盖 text/css、wikitext 等既有条目时保持原类型;要改类型用 fields 传 {\"type\":\"...\"}。⚠️ fields.type 是 TW 的内容类型保留字段,不要把业务分类值(如 \"meeting\")写进去——业务分类请放 tags。',\n parameters: {\n title: { type: 'string', description: 'tiddler 标题(精确匹配,覆盖同名)', required: true },\n text: { type: 'string', description: 'tiddler 全文(默认按 Markdown 解析)', required: true },\n tags: { type: 'array', items: { type: 'string' }, description: '标签数组(可选)' },\n fields: { type: 'json', description: '附加自定义字段,如 {\"date\":\"2026-09-02\"}(可选)。fields.type 是**改内容类型的正规入口**(如 {\"type\":\"text/css\"}):新建条目未指定时默认 text/markdown,覆盖既有条目时保留原类型。注意不要把业务分类值(如 \"meeting\")写进 type——业务分类请放 tags' },\n expectedModified: { type: 'string', description: '可选:乐观并发保护。传 tiddlywiki_get 读到的 modified 值,若该条目已被他人改动则拒绝写入(避免覆盖人类在 TW 编辑器里的修改)' },\n expectedRevision: { type: 'integer', description: '可选:乐观并发保护的另一种令牌——传 tiddlywiki_get 返回字段里的 revision(刚写入、还没落盘的条目没有 modified,此时用 revision)' },\n force: { type: 'boolean', description: '可选:true 时忽略 expectedModified/expectedRevision 强制覆盖(默认 false)' },\n },\n output: {\n render: (_args, value: PutResult) => {\n const lines = [`已写入 tiddler「${value.title}」`]\n if (value.tags.length > 0) lines.push(`标签: ${value.tags.join(', ')}`)\n if (value.type !== null) lines.push(`类型: ${value.type}${value.typeDefaulted === true ? '(新建且未指定,已默认 markdown)' : ''}`)\n // 覆盖时若类型真的变了,必须说出来(v0.20.1)——内容类型决定解析方式,\n // 静默变化会让 CSS/JS 被当 Markdown、Markdown 笔记被当 wikitext。\n if (value.typeChanged !== undefined) lines.push(`⚠️ 内容类型已从 ${value.typeChanged.from} 改为 ${value.typeChanged.to}(覆盖前请确认这是你要的)`)\n if (value.fields !== null) {\n const entries = Object.entries(value.fields)\n if (entries.length > 0) lines.push(`字段: ${entries.map(([k, v]) => `${k}=${String(v)}`).join(', ')}`)\n }\n return [{ type: 'text', text: lines.join('\\n') }]\n },\n },\n execute: async (args: { title: string; text: string; tags?: string[]; fields?: Record<string, unknown>; expectedModified?: string; expectedRevision?: number; force?: boolean }): Promise<PutResult> => {\n const wiki = requireWiki()\n if (args.title.trim().length === 0) throw new Error('tiddlywiki_put: title 不能为空')\n // Read WITHOUT swallowing errors: only a 404 means \"new tiddler\". A\n // transient failure treated as \"new\" would silently tag an existing\n // human note as agent-written.\n const existing = await wiki.get(args.title)\n assertNoConflict(args.title, existing, {\n expectedModified: args.expectedModified,\n expectedRevision: args.expectedRevision,\n force: args.force,\n })\n const tags = normalizeTagArg(args.tags)\n const { tiddler, typeDefaulted } = buildWriteTiddler(args.title, args.text, { existing, tags, fields: args.fields })\n await wiki.put(tiddler)\n deps.autoCommit()\n return {\n ok: true,\n title: args.title,\n tags: tiddler.tags ?? [],\n type: typeof tiddler.type === 'string' ? tiddler.type : null,\n ...(typeDefaulted ? { typeDefaulted: true } : {}),\n ...typeChangeOf(existing, tiddler),\n fields: args.fields ?? null,\n }\n },\n }))\n\n // ── tiddlywiki_batch_put ─────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_batch_put',\n description: '批量写入/覆盖多个 TiddlyWiki tiddler(一次工具调用)。overwrite=false 时跳过已存在的标题;返回逐条结果(单条失败不影响其余条目,失败原因逐条列出)。写入后触发防抖自动 commit(默认 60s)。新建(title 不存在)的条目会自动补打 agent-written 标签,无需手动添加。内容类型:只有**新建**条目未指定 fields.type 时才默认 text/markdown($:/ 系统条目除外);**覆盖既有条目时保留其原有 type/tags/自定义字段**。',\n parameters: {\n items: {\n type: 'array',\n items: {\n type: 'object',\n additionalProperties: true,\n description: '要写入的 tiddler 数组',\n properties: {\n // NOTHING is `required` here on purpose (v0.19.0): argument\n // pre-validation runs BEFORE the per-item try/catch, so a declared\n // requirement made one malformed item abort the WHOLE batch and the\n // documented \"one failure does not lose the others\" contract was\n // unreachable. The implementation validates per item instead.\n title: { type: 'string', description: '标题(精确匹配,覆盖同名)' },\n text: { type: 'string', description: '全文(默认按 Markdown 解析)' },\n tags: { type: 'array', items: { type: 'string' }, description: '标签数组(可选)' },\n fields: { type: 'json', description: '附加自定义字段(可选)。注意:fields.type 是 TW 内容类型(保留字段,默认已自动补 text/markdown),不要写业务分类值' },\n },\n },\n },\n overwrite: { type: 'boolean', description: '可选:true=覆盖同名(默认),false=跳过已存在的标题' },\n },\n output: {\n render: (_args, value: BatchResult) => {\n const lines = [`批量写入完成:成功 ${value.written},跳过 ${value.skipped},失败 ${value.failed},共 ${value.items.length} 条。`]\n for (const r of value.items) {\n lines.push(`- ${r.title}:${r.written ? '已写入' : r.skipped ? '已跳过(存在)' : `失败(${r.error ?? '未知错误'})`}`)\n }\n return [{ type: 'text', text: lines.join('\\n') }]\n },\n },\n execute: async (args: { items: Array<{ title: string; text: string; tags?: string[]; fields?: Record<string, unknown> }>; overwrite?: boolean }): Promise<BatchResult> => {\n const wiki = requireWiki()\n const list = Array.isArray(args.items) ? args.items : []\n if (list.length === 0) return { ok: true, written: 0, skipped: 0, failed: 0, items: [] }\n const overwrite = args.overwrite !== false\n const results: BatchItemResult[] = new Array<BatchItemResult>(list.length)\n let written = 0\n let skipped = 0\n let failed = 0\n // One item failing (validation, network, a rejected title) must not lose\n // the report of the items that DID land — the model needs per-item truth.\n //\n // BOUNDED CONCURRENCY (v0.19.5): the previous sequential loop issued 2N\n // REST round-trips (a GET + a PUT per item) — a 200-item import spent\n // minutes in request latency. Four workers share the same client (its\n // listing caches are invalidated per write, which only costs a refetch),\n // and results are stored BY INDEX so the report keeps the caller's order\n // regardless of completion order.\n const writeOne = async (item: { title: string; text: string; tags?: string[]; fields?: Record<string, unknown> }, index: number): Promise<void> => {\n const title = typeof item?.title === 'string' ? item.title : ''\n try {\n if (title.length === 0) throw new Error('缺少非空 title')\n if (typeof item.text !== 'string') throw new Error('缺少 text')\n // Read WITHOUT swallowing errors: only a 404 means \"new tiddler\".\n const existing = await wiki.get(title)\n if (!overwrite && existing !== undefined) {\n skipped++\n results[index] = { title, written: false, skipped: true, failed: false }\n return\n }\n const { tiddler } = buildWriteTiddler(title, item.text, { existing, tags: normalizeTagArg(item.tags), fields: item.fields })\n await wiki.put(tiddler)\n written++\n results[index] = { title, written: true, skipped: false, failed: false }\n } catch (err) {\n failed++\n results[index] = { title: title.length > 0 ? title : '(无标题)', written: false, skipped: false, failed: true, error: err instanceof Error ? err.message : String(err) }\n }\n }\n const workers = Math.max(1, Math.min(4, list.length))\n let next = 0\n await Promise.all(Array.from({ length: workers }, async () => {\n for (;;) {\n const index = next++\n if (index >= list.length) return\n await writeOne(list[index] as { title: string; text: string }, index)\n }\n }))\n deps.autoCommit()\n return { ok: failed === 0, written, skipped, failed, items: results }\n },\n }))\n\n // ── tiddlywiki_rename ────────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_rename',\n description: '重命名一个 TiddlyWiki tiddler:把旧标题的内容复制到新标题、删除旧标题,并可选地更新其他 tiddler 里的 [[旧标题]] / {{旧标题}} 引用(最佳努力)。',\n parameters: {\n oldTitle: { type: 'string', description: '当前标题', required: true },\n newTitle: { type: 'string', description: '新标题', required: true },\n updateRefs: { type: 'boolean', description: '可选:是否同步更新其他 tiddler 里的引用(默认 true)' },\n },\n output: {\n render: (_args, value: RenameResult) => {\n const lines = [`已重命名「${value.from}」→「${value.to}」`]\n lines.push(`更新了 ${value.refsUpdated} 处引用(${value.refsTiddlers} 个 tiddler)`)\n if (value.warning !== undefined) lines.push(`注意: ${value.warning}`)\n return [{ type: 'text', text: lines.join('\\n') }]\n },\n },\n execute: async (args: { oldTitle: string; newTitle: string; updateRefs?: boolean }): Promise<RenameResult> => {\n const wiki = requireWiki()\n const { oldTitle, newTitle } = args\n if (oldTitle === newTitle) return { ok: true, from: oldTitle, to: newTitle, refsUpdated: 0, refsTiddlers: 0 }\n const existing = await wiki.get(oldTitle)\n if (existing === undefined) throw new Error(`tiddler「${oldTitle}」不存在`)\n const target = await wiki.get(newTitle)\n if (target !== undefined) throw new Error(`新标题「${newTitle}」已存在(可先用 tiddlywiki_delete 删除)`)\n let refsUpdated = 0\n let refsTiddlers = 0\n let warning: string | undefined\n if (args.updateRefs !== false) {\n const all = await wiki.list(undefined, true)\n for (const t of all) {\n if (t.title === oldTitle || t.title === newTitle) continue\n if (t.title.startsWith('$:/')) continue\n const text = t.text ?? ''\n if (text.length === 0) continue\n const rewritten = rewriteRefs(text, oldTitle, newTitle)\n if (rewritten.count > 0) {\n await wiki.put({ ...cleanTiddler(t), text: rewritten.text })\n refsUpdated += rewritten.count\n refsTiddlers++\n }\n }\n }\n await wiki.put({ ...cleanTiddler(existing), title: newTitle })\n // The new title is already written; a failure here leaves BOTH copies on\n // disk, so report the partial state instead of throwing a bare error (the\n // caller would otherwise assume the rename never happened) — v0.19.5.\n let deleteFailed: string | undefined\n try {\n await wiki.delete(oldTitle)\n } catch (err) {\n deleteFailed = err instanceof Error ? err.message : String(err)\n }\n if (refsTiddlers === 0) {\n warning = '未找到任何其他 tiddler 引用旧标题;如确实需要,可手动补充链接。'\n }\n if (deleteFailed !== undefined) {\n const partial = `新标题「${newTitle}」已写入,但旧标题「${oldTitle}」删除失败(${deleteFailed}):现在两个标题都存在同一份内容,请手动删除旧标题。`\n warning = warning === undefined ? partial : `${warning} ${partial}`\n }\n deps.autoCommit()\n return { ok: true, from: oldTitle, to: newTitle, refsUpdated, refsTiddlers, ...(warning !== undefined ? { warning } : {}) }\n },\n }))\n\n // ── tiddlywiki_delete ────────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_delete',\n description: '删除一个 TiddlyWiki tiddler(不存在时是幂等空操作)。默认是**软删除**:内容移入回收站($:/dsh-tiddlywiki/trash/…,不再出现在检索/最近列表里)后删除原条目,可用 tiddlywiki_trash 恢复;permanent=true 才真正永久删除。删除后触发自动 commit。',\n parameters: {\n title: { type: 'string', description: 'tiddler 标题(精确匹配)', required: true },\n permanent: { type: 'boolean', description: '可选:true = 永久删除(回收站也拿不回来,仅剩 git 历史);默认 false = 移入回收站' },\n expectedModified: { type: 'string', description: '可选:乐观并发保护。传 tiddlywiki_get 读到的 modified;若条目在你读取之后被改动(人类在 TW 编辑器里改过)则拒绝删除' },\n expectedRevision: { type: 'integer', description: '可选:乐观并发保护的另一种令牌——传 tiddlywiki_get 返回字段里的 revision' },\n force: { type: 'boolean', description: '可选:true 时忽略 expectedModified/expectedRevision 强制删除(默认 false)' },\n },\n output: {\n render: (_args, value: DeleteResult) => [{\n type: 'text',\n text: value.trashed === true\n ? `已把 tiddler「${value.title}」移入回收站($:/dsh-tiddlywiki/trash/,可用 tiddlywiki_trash action=restore 恢复)。`\n : `已永久删除 tiddler「${value.title}」。`,\n }],\n },\n execute: async (args: { title: string; permanent?: boolean; expectedModified?: string; expectedRevision?: number; force?: boolean }): Promise<DeleteResult> => {\n const wiki = requireWiki()\n const existing = await wiki.get(args.title)\n // Deleting something the caller never saw is fine only when it does not\n // exist; a token means the note WAS read, so a changed revision since then\n // must abort (v0.19.5 — delete used to have no concurrency protection at\n // all, while put/batch_put did: `get` → human edits in TW → `delete` threw\n // the human's work into the trash with no warning).\n assertNoConflict(args.title, existing, {\n expectedModified: args.expectedModified,\n expectedRevision: args.expectedRevision,\n force: args.force,\n })\n // Trash entries themselves and system tiddlers are never nested.\n const canTrash = existing !== undefined && args.permanent !== true && !args.title.startsWith(TRASH_PREFIX)\n if (!canTrash) {\n await wiki.delete(args.title)\n deps.autoCommit()\n return { ok: true, title: args.title, trashed: false }\n }\n const trashTitle = trashTitleFor(args.title)\n const at = new Date().toISOString()\n await wiki.put({ ...cleanTiddler(existing), title: trashTitle, 'trash-of': args.title, 'trash-at': at })\n await wiki.delete(args.title)\n // Read the index BEFORE touching it, and ABORT when the read failed or the\n // JSON is broken: overwriting it with `[] + thisEntry` after a transient\n // failure dropped every earlier entry — the trashed tiddlers survive but\n // become unreachable orphans (`trash list` cannot see them, `trash empty`\n // cannot clean them). A corrupt index is treated the same way: we cannot\n // know what it held, so we never silently replace it (v0.19.5).\n const index = await readTrashIndex(wiki)\n if (!index.readOk || index.corrupted) throw new TrashIndexUnavailableError()\n index.entries.push({ trash: trashTitle, of: args.title, at })\n await writeTrashIndex(wiki, index.entries)\n deps.autoCommit()\n return { ok: true, title: args.title, trashed: true, trashTitle }\n },\n }))\n\n // ── tiddlywiki_trash ─────────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_trash',\n description: '回收站(软删除的笔记):action=list 列出、action=restore 恢复某条、action=empty 清空。配合 tiddlywiki_delete(默认软删除)使用。',\n parameters: {\n action: { type: 'string', enum: ['list', 'restore', 'empty'], description: 'list=列出回收站;restore=恢复(需 title);empty=永久清空', required: true },\n title: { type: 'string', description: 'action=restore 时的原标题(也接受回收站标题)' },\n limit: { type: 'integer', description: 'action=list 的返回上限(默认 30,最大 200)' },\n },\n output: {\n render: (_args, value: TrashResult) => {\n const lines = [`回收站 ${value.action}:${value.message}`]\n for (const item of value.items ?? []) lines.push(`- ${item.title}(删除于 ${item.at ?? '?'}${item.of !== undefined ? `,原名「${item.of}」` : ''})`)\n return [{ type: 'text', text: lines.join('\\n') }]\n },\n },\n execute: async (args: { action: 'list' | 'restore' | 'empty'; title?: string; limit?: number }): Promise<TrashResult> => {\n const wiki = requireWiki()\n const index = await readTrashIndex(wiki)\n // A failed/corrupt index must never be treated as「回收站是空的」: `empty`\n // would then report success while doing nothing, and `restore` would claim\n // the note was never trashed (v0.19.5).\n if (!index.readOk) throw new TrashIndexUnavailableError()\n if (index.corrupted) {\n throw new Error('回收站索引已损坏(JSON 解析失败),本次操作已中止以免误删记录;可手动检查 $:/dsh-tiddlywiki/trash-index 后重试。')\n }\n const indexed = index.entries\n if (args.action === 'list') {\n const limit = Math.max(1, Math.min(args.limit ?? 30, 200))\n return {\n action: 'list',\n message: `共 ${indexed.length} 条`,\n items: indexed.slice(-limit).reverse().map((entry) => ({ title: entry.trash, at: entry.at, of: entry.of })),\n }\n }\n if (args.action === 'empty') {\n for (const entry of indexed) await wiki.delete(entry.trash)\n await writeTrashIndex(wiki, [])\n deps.autoCommit()\n return { action: 'empty', message: `已清空 ${indexed.length} 条` }\n }\n const wanted = typeof args.title === 'string' ? args.title.trim() : ''\n if (wanted.length === 0) throw new Error('tiddlywiki_trash: action=restore 需要 title')\n const match = indexed.slice().reverse().find((entry) => entry.of === wanted || entry.trash === wanted)\n if (match === undefined) throw new Error(`回收站里没有「${wanted}」`)\n const stored = await wiki.get(match.trash)\n if (stored === undefined) {\n // Stale index entry (the tiddler was removed by hand): prune and report.\n await writeTrashIndex(wiki, indexed.filter((entry) => entry.trash !== match.trash))\n throw new Error(`回收站条目「${match.trash}」已不存在(索引已清理)`)\n }\n if ((await wiki.get(match.of)) !== undefined) {\n throw new Error(`无法恢复:标题「${match.of}」已被占用,请先处理现有条目`)\n }\n const restored = cleanTiddler(stored)\n delete restored['trash-of']\n delete restored['trash-at']\n await wiki.put({ ...restored, title: match.of })\n await wiki.delete(match.trash)\n await writeTrashIndex(wiki, indexed.filter((entry) => entry.trash !== match.trash))\n deps.autoCommit()\n return { action: 'restore', message: `已恢复「${match.of}」`, items: [] }\n },\n }))\n\n // ── tiddlywiki_append ────────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_append',\n description: '向已有 tiddler 追加/前插文本,或写入指定标题段落的末尾(无需先读全文、不会整篇覆盖)——适合日志、批注、清单的增量写入。条目不存在时默认新建(createIfMissing=false 则报错)。**写入既有条目走与 tiddlywiki_put 同一套写策略**:原有 tags、自定义字段与**内容类型**全部保留(不会把 Markdown 笔记改成 wikitext、也不会把 CSS 改成 Markdown);可用 fields 显式覆盖字段/类型。新建条目未指定内容类型时才默认 text/markdown。',\n parameters: {\n title: { type: 'string', description: 'tiddler 标题', required: true },\n text: { type: 'string', description: '要追加/前插的文本(默认按 Markdown 写;既有条目保持它自己的内容类型)', required: true },\n mode: { type: 'string', enum: ['append', 'prepend'], description: '可选:append(默认,追加到末尾)/ prepend(插到开头)' },\n heading: { type: 'string', description: '可选:append 时改为插入到该标题(Markdown # 或 wikitext ! 标题,按标题文本匹配)对应段落的末尾' },\n createIfMissing: { type: 'boolean', description: '可选:条目不存在时是否新建(默认 true)' },\n tags: { type: 'array', items: { type: 'string' }, description: '可选:标签(不传则保留既有条目的原标签;新建条目会额外自动补 agent-written)' },\n fields: { type: 'json', description: '可选:显式覆盖的自定义字段(如 {\"type\":\"text/css\"})。不传则保留既有条目的原字段与内容类型' },\n },\n output: {\n render: (_args, value: AppendResult) => {\n const lines = [`${value.created ? '已新建并写入' : '已增量写入'} tiddler「${value.title}」(${value.mode}${value.heading !== null ? ` · 段落「${value.heading}」` : ''}):新增 ${value.added} 字符,现共 ${value.total} 字符。`]\n if (value.type !== null) lines.push(`类型: ${value.type}${value.typeDefaulted === true ? '(新建且未指定,已默认 markdown)' : ''}`)\n if (value.typeChanged !== undefined) lines.push(`⚠️ 内容类型已从 ${value.typeChanged.from} 改为 ${value.typeChanged.to}`)\n return [{ type: 'text', text: lines.join('\\n') }]\n },\n },\n execute: async (args: { title: string; text: string; mode?: 'append' | 'prepend'; heading?: string; createIfMissing?: boolean; tags?: string[]; fields?: Record<string, unknown> }): Promise<AppendResult> => {\n const wiki = requireWiki()\n const title = args.title.trim()\n if (title.length === 0) throw new Error('tiddlywiki_append: title 不能为空')\n const existing = await wiki.get(title)\n if (existing === undefined && args.createIfMissing === false) {\n throw new Error(`tiddler「${title}」不存在(createIfMissing=false)`)\n }\n const mode = args.mode === 'prepend' ? 'prepend' : 'append'\n const base = existing?.text ?? ''\n const addition = args.text\n let next: string\n if (mode === 'append' && typeof args.heading === 'string' && args.heading.trim().length > 0) {\n next = insertIntoSection(base, args.heading.trim(), addition)\n } else if (mode === 'prepend') {\n next = base.trim().length === 0 ? addition : `${addition}\\n\\n${base}`\n } else {\n next = base.trim().length === 0 ? addition : `${base.replace(/\\s+$/, '')}\\n\\n${addition}`\n }\n // v0.20.1: ONE write policy for every write path. The old append built its\n // PUT by hand, which (a) dropped the existing tiddler's `type` and\n // (b) ignored `fields` entirely — appending a paragraph to a Markdown note\n // silently downgraded it to wikitext. buildWriteTiddler preserves\n // tags/custom fields/type for an existing tiddler and only defaults the\n // type when creating.\n const { tiddler, typeDefaulted } = buildWriteTiddler(title, next, {\n existing,\n tags: normalizeTagArg(args.tags),\n fields: args.fields,\n })\n await wiki.put(tiddler)\n deps.autoCommit()\n return {\n ok: true,\n title,\n mode,\n heading: typeof args.heading === 'string' && args.heading.trim().length > 0 ? args.heading.trim() : null,\n created: existing === undefined,\n added: addition.length,\n total: next.length,\n type: typeof tiddler.type === 'string' ? tiddler.type : null,\n ...(typeDefaulted ? { typeDefaulted: true } : {}),\n ...typeChangeOf(existing, tiddler),\n }\n },\n }))\n\n // ── tiddlywiki_backlinks ─────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_backlinks',\n description: '查反向链接:哪些笔记引用了目标 tiddler([[标题]] / [[显示|标题]] / {{标题}}),以及哪些笔记把它当作标签。用于知识图谱导航、改动前评估影响面。',\n parameters: {\n title: { type: 'string', description: '目标 tiddler 标题', required: true },\n includeTags: { type: 'boolean', description: '可选:是否把「以该标题为标签」的笔记也算作反向链接(默认 true)' },\n limit: { type: 'integer', description: '可选:最多返回多少条(默认 30,最大 200)' },\n },\n output: {\n render: (_args, value: BacklinkResult) => {\n const lines = [`「${value.title}」的反向链接:${value.total} 条(引用 ${value.linkCount} · 标签 ${value.tagCount})`]\n if (value.items.length === 0) lines.push('没有任何笔记引用它。')\n for (const item of value.items) {\n lines.push(`- ${item.title}(${item.via === 'tag' ? '标签' : `${item.refs} 处引用`})`)\n }\n return [{ type: 'text', text: lines.join('\\n') }]\n },\n },\n execute: async (args: { title: string; includeTags?: boolean; limit?: number }): Promise<BacklinkResult> => {\n const wiki = requireWiki()\n const target = args.title.trim()\n if (target.length === 0) throw new Error('tiddlywiki_backlinks: title 不能为空')\n const includeTags = args.includeTags !== false\n const limit = Math.max(1, Math.min(args.limit ?? 30, 200))\n const items = await wiki.list(undefined, true)\n const hits: Array<{ title: string; refs: number; via: 'link' | 'tag'; modified: string | null }> = []\n let linkCount = 0\n let tagCount = 0\n for (const t of items) {\n if (t.title === target || t.title.startsWith('$:/')) continue\n const refs = countRefsTo(t.text ?? '', target)\n const tagged = includeTags && (t.tags ?? []).includes(target)\n if (refs === 0 && !tagged) continue\n if (refs > 0) linkCount++\n if (tagged) tagCount++\n hits.push({ title: t.title, refs, via: refs > 0 ? 'link' : 'tag', modified: toIsoDateString(t.modified) })\n }\n hits.sort((a, b) => b.refs - a.refs || a.title.localeCompare(b.title, 'zh'))\n return { title: target, total: hits.length, linkCount, tagCount, items: hits.slice(0, limit) }\n },\n }))\n\n // ── tiddlywiki_attach ────────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_attach',\n description: '把一个本机文件或公网 http(s) 地址存成 wiki 的二进制附件 tiddler(图片 / PDF / 压缩包等,type + base64 正文,随 wiki 进 git)。可选把附件嵌入/链接进某篇笔记。这是 agent 唯一能写入二进制附件的途径。',\n parameters: {\n title: { type: 'string', description: '附件 tiddler 标题(同时决定其在 wiki 里的名字)', required: true },\n path: { type: 'string', description: '本机绝对路径(与 url 二选一)' },\n url: { type: 'string', description: '公网 http(s) 地址(与 path 二选一;含 SSRF 守卫,拒绝内网/回环地址)' },\n tags: { type: 'array', items: { type: 'string' }, description: '可选:附件标签' },\n noteTitle: { type: 'string', description: '可选:把该附件嵌入到这篇笔记末尾(图片用 [img[标题]],其它用 [[标题]] 链接)' },\n expectedModified: { type: 'string', description: '可选:乐观并发保护,仅在同名 tiddler 已存在时有意义(传 tiddlywiki_get 读到的 modified)' },\n expectedRevision: { type: 'integer', description: '可选:乐观并发保护的另一种令牌——传 tiddlywiki_get 返回字段里的 revision' },\n force: { type: 'boolean', description: '可选:true 时忽略 expectedModified/expectedRevision,允许覆盖同名 tiddler(默认 false;即便覆盖也会保留其 tags 与自定义字段)' },\n },\n output: {\n render: (_args, value: AttachResult) => {\n const lines = [`已保存附件「${value.title}」(${value.mime},${value.bytes} 字节,base64 约 ${value.chars} 字符)`]\n if (value.source !== null) lines.push(`来源: ${value.source}`)\n if (value.embedInto !== null) lines.push(`已嵌入笔记「${value.embedInto}」`)\n lines.push(`打开: [${value.title}](/dsh-tiddlywiki/tw/#${encodeURIComponent(value.title)})`)\n return [{ type: 'text', text: lines.join('\\n') }]\n },\n },\n execute: async (args: { title: string; path?: string; url?: string; tags?: string[]; noteTitle?: string; expectedModified?: string; expectedRevision?: number; force?: boolean }): Promise<AttachResult> => {\n const wiki = requireWiki()\n const title = args.title.trim()\n if (title.length === 0) throw new Error('tiddlywiki_attach: title 不能为空')\n const hasPath = typeof args.path === 'string' && args.path.trim().length > 0\n const hasUrl = typeof args.url === 'string' && args.url.trim().length > 0\n if (hasPath === hasUrl) throw new Error('tiddlywiki_attach: path 与 url 必须且只能提供一个')\n let buffer: Buffer\n let mime: string\n let source: string\n if (hasPath) {\n const filePath = (args.path as string).trim()\n // Absolute only: a relative path would resolve against the DSH process\n // cwd, which is not something the model can reason about.\n if (!isAbsolute(filePath)) throw new Error('tiddlywiki_attach: path 必须是绝对路径')\n const info = await stat(filePath)\n if (!info.isFile()) throw new Error(`tiddlywiki_attach: ${basename(filePath)} 不是普通文件`)\n if (info.size > MAX_ATTACH_BYTES) throw new Error(`tiddlywiki_attach: 文件超过 ${Math.floor(MAX_ATTACH_BYTES / 1024 / 1024)}MB 上限`)\n buffer = await readFile(filePath)\n const ext = extname(filePath).slice(1).toLowerCase()\n mime = ATTACH_MIME_BY_EXT[ext] ?? 'application/octet-stream'\n source = filePath\n } else {\n const imageUrl = (args.url as string).trim()\n const downloaded = await downloadClipImage(imageUrl, '')\n buffer = downloaded.buffer\n const ext = extname(new URL(imageUrl).pathname).slice(1).toLowerCase()\n mime = downloaded.type?.split(';')[0]?.trim() || ATTACH_MIME_BY_EXT[ext] || 'application/octet-stream'\n source = imageUrl\n }\n if (buffer.length === 0) throw new Error('tiddlywiki_attach: 内容为空')\n if (buffer.length > MAX_ATTACH_BYTES) throw new Error(`tiddlywiki_attach: 内容超过 ${Math.floor(MAX_ATTACH_BYTES / 1024 / 1024)}MB 上限`)\n // DATA SAFETY (v0.19.5): this used to `wiki.put({title, type, text})` with\n // no read at all — attaching an image whose title collided with an existing\n // note silently replaced that note (tags, custom fields and body all gone),\n // the exact class the write policy exists to prevent. It also never added\n // the `agent-written` tag every other creating tool adds. Read first (404 =\n // new tiddler; any other failure propagates), then build the PUT from the\n // existing tiddler so a same-named note keeps everything but its body.\n const existing = await wiki.get(title)\n assertNoConflict(title, existing, {\n expectedModified: args.expectedModified,\n expectedRevision: args.expectedRevision,\n force: args.force,\n })\n // An attachment sharing a title with an existing tiddler is almost always\n // a mistake (the old code silently destroyed the note). Require explicit\n // consent: a concurrency token (the caller proved it read the tiddler) or\n // `force: true`. `expectedRevision`/`expectedModified` alone does NOT\n // consent to an overwrite — `assertNoConflict` above only rejects when the\n // tiddler changed since that token, so a stale-but-matching token would\n // still wipe the body.\n if (existing !== undefined && args.force !== true) {\n throw new Error(\n `tiddlywiki_attach: 标题「${title}」已存在(type=${typeof existing.type === 'string' ? existing.type : '?'})。`\n + '为避免静默覆盖既有笔记,请换一个附件标题;确认要覆盖时传 force: true(tags 与自定义字段仍会保留)。',\n )\n }\n const explicitTags = Array.isArray(args.tags)\n ? args.tags.filter((t) => typeof t === 'string' && t.trim().length > 0)\n : undefined\n const { tiddler } = buildWriteTiddler(title, buffer.toString('base64'), {\n existing,\n ...(explicitTags !== undefined && explicitTags.length > 0 ? { tags: explicitTags } : {}),\n fields: {\n type: mime,\n 'attach-source': source,\n 'attach-at': new Date().toISOString(),\n },\n })\n await wiki.put(tiddler)\n let embedInto: string | null = null\n if (typeof args.noteTitle === 'string' && args.noteTitle.trim().length > 0) {\n embedInto = args.noteTitle.trim()\n const note = await wiki.get(embedInto)\n // `[img[Title]]` breaks if the attachment title itself contains `]]`;\n // fall back to a plain link (which has the same constraint, so strip the\n // sequence) instead of silently producing a broken embed.\n const safeTitle = title.replace(/\\]\\]/g, '] ]')\n const embed = mime.startsWith('image/') ? `[img[${safeTitle}]]` : `[[${safeTitle}]]`\n const text = note === undefined ? embed : `${(note.text ?? '').replace(/\\s+$/, '')}\\n\\n${embed}`\n // Same read-modify-write policy as everywhere else: preserve the note's\n // tags/custom fields/type, only append the embed.\n const { tiddler: noteTiddler } = buildWriteTiddler(embedInto, text, { existing: note })\n await wiki.put(noteTiddler)\n }\n deps.autoCommit()\n return { ok: true, title, mime, bytes: buffer.length, chars: buffer.toString('base64').length, source, embedInto }\n },\n }))\n\n // ── tiddlywiki_lint ──────────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_lint',\n description: '知识库体检(只读):找出垃圾/异常标签、指向不存在条目的死链、空笔记、疑似 Markdown 却缺 type 的笔记。返回按类别分组的问题清单与修复建议。',\n parameters: {\n limit: { type: 'integer', description: '可选:每类最多返回多少条示例(默认 10,最大 100)' },\n checks: { type: 'array', items: { type: 'string' }, description: '可选:只跑指定检查(junk-tags / broken-links / empty-notes / missing-type)' },\n },\n output: {\n render: (_args, value: LintResult) => {\n const lines = [`知识库体检:扫描 ${value.scanned} 条文本笔记,发现 ${value.issues.reduce((sum, i) => sum + i.count, 0)} 个问题。`]\n if (value.issues.length === 0) lines.push('没有发现问题。')\n for (const issue of value.issues) {\n lines.push(`- ${issue.kind}:${issue.count} 处 — ${issue.hint}`)\n for (const sample of issue.samples) lines.push(` · ${sample}`)\n }\n return [{ type: 'text', text: lines.join('\\n') }]\n },\n },\n execute: async (args: { limit?: number; checks?: string[] }): Promise<LintResult> => {\n const wiki = requireWiki()\n const limit = Math.max(1, Math.min(args.limit ?? 10, 100))\n const wanted = Array.isArray(args.checks) && args.checks.length > 0 ? new Set(args.checks) : undefined\n const want = (kind: string): boolean => wanted === undefined || wanted.has(kind)\n const items = await wiki.list(undefined, true)\n // 死链检查需要**全部**标题(含图片/PDF 等二进制附件)——只拿文本列表会\n // 把每一条 `[[图.png]]` / `{{附件}}` 都误报成死链(v0.19.1 修复)。\n // 瘦列表(不带正文)一次请求就能拿到全部标题。\n const titles = want('broken-links')\n ? new Set((await wiki.list()).map((t) => t.title))\n : new Set(items.map((t) => t.title))\n // 缺 type 判定要走 `[!has[type]]` 过滤器:TW 服务端会给 listing 里**每条**\n // 无 type 的条目补 `text/vnd.tiddlywiki`,靠响应里的 type 永远认不出来\n // (v0.19.1 修复:旧实现里这个检查永远不触发)。过滤器在服务端按真实字段\n // 求值,返回的标题就是真正没有 type 的那些。\n const typelessTitles = want('missing-type')\n ? new Set((await wiki.list(MISSING_TYPE_FILTER)).map((t) => t.title))\n : new Set<string>()\n const issues: LintIssue[] = []\n\n if (want('junk-tags')) {\n const samples: string[] = []\n let count = 0\n for (const t of items) {\n for (const tag of t.tags ?? []) {\n if (!isJunkTag(tag)) continue\n count++\n if (samples.length < limit) samples.push(`「${t.title}」的标签「${tag}」`)\n }\n }\n if (count > 0) issues.push({ kind: 'junk-tags', count, hint: '明显是写入事故产生的标签(如筛选器错误文本);用 tiddlywiki_put 重写该条目的 tags 清理', samples })\n }\n\n if (want('broken-links')) {\n const samples: string[] = []\n let count = 0\n const linkRe = /\\[\\[([^\\]|]+)(?:\\|([^\\]]+))?\\]\\]|\\{\\{([^}]+)\\}\\}/g\n for (const t of items) {\n const text = t.text ?? ''\n let match: RegExpExecArray | null\n while ((match = linkRe.exec(text)) !== null) {\n const target = (match[3] ?? match[2] ?? match[1] ?? '').trim()\n if (target.length === 0 || target.startsWith('$:/') || /^(https?|mailto|file):/.test(target)) continue\n if (target.includes('$') || target.includes('<')) continue // variable/macro, not a tiddler link\n if (titles.has(target)) continue\n count++\n if (samples.length < limit) samples.push(`「${t.title}」→「${target}」`)\n }\n }\n if (count > 0) issues.push({ kind: 'broken-links', count, hint: '被引用条目标题不存在;确认是笔误还是应新建该条目', samples })\n }\n\n if (want('empty-notes')) {\n const samples: string[] = []\n let count = 0\n for (const t of items) {\n if (t.title.startsWith('$:/')) continue\n if (isBinaryType(typeof t.type === 'string' ? t.type : undefined)) continue\n if ((t.text ?? '').trim().length === 0) {\n count++\n if (samples.length < limit) samples.push(`「${t.title}」`)\n }\n }\n if (count > 0) issues.push({ kind: 'empty-notes', count, hint: '正文为空的笔记;补内容或删除', samples })\n }\n\n if (want('missing-type')) {\n const samples: string[] = []\n let count = 0\n for (const t of items) {\n if (t.title.startsWith('$:/')) continue\n if (!typelessTitles.has(t.title)) continue\n const text = t.text ?? ''\n if (/^#{1,6}\\s|\\n#{1,6}\\s|^\\s*[-*]\\s|\\*\\*[^*]+\\*\\*/m.test(text)) {\n count++\n if (samples.length < limit) samples.push(`「${t.title}」(无 type 字段,当前按 wikitext 渲染)`)\n }\n }\n if (count > 0) issues.push({ kind: 'missing-type', count, hint: '正文像 Markdown 但条目没有 type 字段,TW 会按 wikitext 渲染;用 tiddlywiki_put 的 fields.type 明确内容类型', samples })\n }\n\n return { scanned: items.length, issues }\n },\n }))\n\n // ── tiddlywiki_git_sync ──────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_git_sync',\n description: '对 TiddlyWiki 知识库的 git 仓库做同步:pull(拉取远端并 rebase 本地,冲突则 abort 并报文件)、push(推送本地提交到远端)、sync(pull → commit 本地改动 → push)。未配置 git.remote 时 push 会失败并提示。',\n parameters: {\n action: { type: 'string', enum: ['pull', 'push', 'sync'], description: '要执行的 git 操作', required: true },\n message: { type: 'string', description: 'commit 信息(可选,仅 sync 的本地 commit 使用)' },\n },\n output: {\n render: (_args, value: SyncResult) => renderSync(value),\n },\n execute: async (args: { action: 'pull' | 'push' | 'sync'; message?: string }): Promise<SyncResult> => {\n const dir = deps.wikiPath()\n /** Restart TW after a pull that changed the tree (stale snapshot drop). */\n const restartIfChanged = async (pulled: { changed?: boolean }): Promise<{ restarted?: boolean; restartError?: string }> => {\n if (pulled.changed !== true || deps.restartWiki === undefined) return {}\n try {\n // DRAIN THE SYNCER FIRST (v0.19.1, data safety): a PUT answers 204 as\n // soon as the tiddler is in TW's in-memory store; the filesystem\n // syncer writes it ~250ms later. Restarting TW before that flush KILLS\n // the write (the restarted server boots from the old snapshot and the\n // note is gone — not even the git commit that follows can recover it).\n // The agent path is the risky one: `tiddlywiki_put` → `git_sync`\n // back-to-back. Same sentinel trick as seeds.ts / index.ts.\n await flushPendingWrites(requireWiki(), join(dir, 'tiddlers')).catch(() => undefined)\n await deps.restartWiki()\n return { restarted: true }\n } catch (err) {\n return { restartError: err instanceof Error ? err.message : String(err) }\n }\n }\n switch (args.action) {\n case 'pull': {\n const r = await deps.git.pull(dir)\n const restart = await restartIfChanged(r)\n return { action: args.action, ok: r.ok, message: r.message, ...(r.conflictFiles !== undefined ? { conflictFiles: r.conflictFiles } : {}), ...(r.changed === true ? { changed: true } : {}), ...restart }\n }\n case 'push': {\n const r = await deps.git.push(dir)\n return { action: args.action, ok: r.ok, message: r.message }\n }\n case 'sync': {\n const pulled = await deps.git.pull(dir)\n if (!pulled.ok) return { action: args.action, ok: false, message: pulled.message, ...(pulled.conflictFiles !== undefined ? { conflictFiles: pulled.conflictFiles } : {}) }\n const restart = await restartIfChanged(pulled)\n const committed = await deps.git.commit(dir, args.message ?? `sync ${new Date().toISOString()}`)\n const pushed = await deps.git.push(dir)\n const status = await deps.git.status(dir)\n return {\n action: args.action,\n ok: pushed.ok,\n message: pushed.ok ? '同步完成' : pushed.message,\n pull: 'ok',\n ...(pulled.changed === true ? { changed: true } : {}),\n ...restart,\n commit: committed.message,\n push: pushed.message,\n status,\n }\n }\n }\n },\n }))\n\n // ── tiddlywiki_git_resolve ───────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_git_resolve',\n description: '在 tiddlywiki_git_sync action=pull 冲突(已 abort)后,按 tiddler 二选一解决:keep-local 保留本地版本;keep-remote 用 git fetch 拉取远端并检出远端版本(需已配置 git.remote);list 仅报告当前状态。解决后建议重新 pull/sync 整合其余改动。',\n parameters: {\n strategy: { type: 'string', enum: ['keep-local', 'keep-remote', 'list'], description: 'keep-local=保留本地;keep-remote=改用远端版本;list=仅报告当前 git 状态', required: true },\n files: { type: 'array', items: { type: 'string' }, description: '冲突文件名数组(来自 pull 返回的 conflictFiles;list 时忽略)' },\n },\n output: {\n render: (_args, value: ResolveResult) => {\n const lines = [`git resolve ${value.action}: ${value.ok ? '成功' : '失败'}`]\n lines.push(` ${value.message}`)\n if (value.files !== undefined && value.files.length > 0) lines.push(`涉及文件: ${value.files.join(', ')}`)\n if (value.commit !== undefined) lines.push(`本地 commit: ${value.commit}`)\n if (value.status !== undefined) lines.push(`状态: ${gitStatusBits(value.status)}`)\n return [{ type: 'text', text: lines.join('\\n') }]\n },\n },\n execute: async (args: { strategy: 'keep-local' | 'keep-remote' | 'list'; files?: string[] }): Promise<ResolveResult> => {\n const dir = deps.wikiPath()\n if (args.strategy === 'list') {\n const status = await deps.git.status(dir)\n return { ok: true, action: 'list', message: '当前仓库状态(pull 冲突已 abort,工作区即本地版本,不会残留未合并状态)', status }\n }\n const files = (args.files ?? []).filter((f) => typeof f === 'string' && f.length > 0)\n if (files.length === 0) {\n return { ok: false, action: args.strategy, message: '请提供 conflictFiles(来自 pull 返回)', hint: '可先执行 tiddlywiki_git_sync action=pull 查看冲突文件' }\n }\n if (args.strategy === 'keep-local') {\n const status = await deps.git.status(dir)\n return {\n ok: true,\n action: 'keep-local',\n message: `已保留本地版本(${files.length} 个文件;abort 后本地即为工作区内容)。建议重新 tiddlywiki_git_sync action=sync 整合远端其余改动。`,\n files,\n status,\n }\n }\n const fetched = await deps.git.fetch(dir)\n if (!fetched.ok) {\n return { ok: false, action: 'keep-remote', message: `fetch 失败(可能未配置 git.remote):${fetched.message}` }\n }\n const checked = await deps.git.checkoutFetchHead(dir, files)\n if (!checked.ok) {\n return { ok: false, action: 'keep-remote', message: `从远端检出失败:${checked.message}` }\n }\n const committed = await deps.git.commit(dir, `resolve conflict (keep remote) ${new Date().toISOString()}`)\n deps.autoCommit()\n const status = await deps.git.status(dir)\n return {\n ok: true,\n action: 'keep-remote',\n message: `已把 ${files.length} 个冲突文件改为远端版本并提交。建议继续 tiddlywiki_git_sync action=sync 完成整合与推送。`,\n files,\n commit: committed.message,\n status,\n }\n },\n }))\n\n return disposers\n}\n\n// ── tool result shapes + renders ───────────────────────────────────────────\n\ninterface SearchHit { title: string; tags: string[]; modified: string | null; snippet: string }\ninterface SearchResult { query: string; tags: string[]; since: string | null; type: string | null; field: string | null; value: string | null; total: number; results: SearchHit[] }\ninterface RecentResult { since: string | null; results: SearchHit[] }\ninterface TagListResult { count: number; total: number; truncated: boolean; tags: Array<{ tag: string; count: number }> }\ninterface GetResult {\n notFound: boolean\n title: string\n text: string\n tags: string[]\n fields: Record<string, unknown>\n modified: string | null\n /** Binary attachment (image/audio/…): `text` is withheld, these carry its type+size. */\n binary?: boolean\n binaryType?: string\n binaryChars?: number\n}\ninterface PutResult { ok: boolean; title: string; tags: string[]; type: string | null; typeDefaulted?: boolean; /** 覆盖时内容类型真的变了(v0.20.1)。 */ typeChanged?: { from: string; to: string }; fields: Record<string, unknown> | null }\ninterface BatchItemResult { title: string; written: boolean; skipped: boolean; failed: boolean; error?: string }\ninterface BatchResult { ok: boolean; written: number; skipped: number; failed: number; items: BatchItemResult[] }\ninterface RenameResult { ok: boolean; from: string; to: string; refsUpdated: number; refsTiddlers: number; warning?: string }\ninterface DeleteResult { ok: boolean; title: string; /** true = moved to the trash (recoverable). */ trashed?: boolean; trashTitle?: string }\ninterface TrashItem { title: string; at?: string; of?: string }\ninterface TrashResult { action: string; message: string; items?: TrashItem[] }\ninterface AppendResult { ok: boolean; title: string; mode: 'append' | 'prepend'; heading: string | null; created: boolean; added: number; total: number; type?: string | null; typeDefaulted?: boolean; typeChanged?: { from: string; to: string } }\ninterface BacklinkHit { title: string; refs: number; via: 'link' | 'tag'; modified: string | null }\ninterface BacklinkResult { title: string; total: number; linkCount: number; tagCount: number; items: BacklinkHit[] }\ninterface AttachResult { ok: boolean; title: string; mime: string; bytes: number; chars: number; source: string | null; embedInto: string | null }\ninterface LintIssue { kind: string; count: number; hint: string; samples: string[] }\ninterface LintResult { scanned: number; issues: LintIssue[] }\ninterface SyncResult {\n action: string\n ok: boolean\n message: string\n conflictFiles?: string[]\n pull?: string\n commit?: string\n push?: string\n changed?: boolean\n restarted?: boolean\n restartError?: string\n status?: GitStatusView\n}\ninterface ResolveResult {\n ok: boolean\n action: string\n message: string\n files?: string[]\n commit?: string\n hint?: string\n status?: GitStatusView\n}\n\n/** One-line 状态 summary: 分支 … 领先 … 落后 … 工作区未提交 … 最近提交. */\nfunction gitStatusBits(s: GitStatusView): string {\n const bits = [`分支 ${s.branch}`]\n if (s.ahead !== undefined) bits.push(`领先 ${s.ahead}`)\n if (s.behind !== undefined) bits.push(`落后 ${s.behind}`)\n if (s.dirty) bits.push(`工作区有 ${s.dirtyFiles.length} 个未提交改动`)\n if (s.lastCommit !== undefined) bits.push(`最近提交 ${s.lastCommit}`)\n return bits.join(' · ')\n}\n\nfunction renderSync(value: SyncResult): Array<{ type: 'text'; text: string }> {\n const lines = [`git ${value.action}: ${value.ok ? '成功' : '失败'}`]\n lines.push(` ${value.message}`)\n if (value.conflictFiles !== undefined && value.conflictFiles.length > 0) {\n lines.push(`冲突文件(rebase 已 abort,勿自动覆盖):`)\n for (const f of value.conflictFiles) lines.push(` - ${f}`)\n lines.push('处理方式:用 tiddlywiki_git_resolve files=[以上文件] strategy=keep-local|keep-remote 按 tiddler 二选一解决,再重新 sync;也可以直接让用户处理。')\n }\n if (value.commit !== undefined) lines.push(`本地 commit: ${value.commit}`)\n if (value.push !== undefined) lines.push(`远端 push: ${value.push}`)\n if (value.changed === true) {\n lines.push(value.restarted === true\n ? '本次 pull 拉取了新内容,TW 服务已自动重启(同端口),读取/搜索均为最新快照。'\n : '本次 pull 拉取了新内容,但 TW 服务未能自动重启(如需最新快照,请手动重启 TW)。')\n }\n if (value.restartError !== undefined) lines.push(`TW 重启失败: ${value.restartError}`)\n if (value.status !== undefined) {\n lines.push(`状态: ${gitStatusBits(value.status)}`)\n const s = value.status\n if (s.dirty && s.dirtyFiles.length > 0) lines.push(` 未提交: ${s.dirtyFiles.join(', ')}`)\n }\n return [{ type: 'text', text: lines.join('\\n') }]\n}\n","/**\n * Where the wiki lives (v0.22.0 runtime switching).\n *\n * WHY A FILE OUTSIDE THE WIKI\n * ---------------------------\n * The settings page stores its overrides in the wiki itself\n * (`$:/plugins/dsh-tiddlywiki/config`). \"Which wiki am I?\" cannot live there:\n * switching to another wiki would take the setting with it (the new wiki would\n * read its own config tiddler and forget the choice). The active location is\n * therefore a small pointer file under the DSH home, outside every wiki:\n *\n * $DSH_HOME/dsh-tiddlywiki/location.json\n * { \"version\": 1, \"active\": { \"root\": \"D:/notes\", \"name\": \"main\" } }\n *\n * PRECEDENCE (see `resolveWikiLocation()` in src/index.ts):\n * state file > cordis `config:` block > $DSH_HOME/tiddlywiki + \"main\"\n *\n * The cordis block keeps acting as the *default* — 「恢复为配置默认」 simply\n * deletes the pointer file. A malformed pointer is reported (never silently\n * acted upon) and falls back to the default, so a hand-edited JSON cannot make\n * the plugin boot somewhere unexpected.\n *\n * @module dsh-tiddlywiki/host/wiki-location\n */\nimport { mkdir, readFile, readdir, rename, rm, stat, writeFile } from 'node:fs/promises'\nimport { dirname, isAbsolute, join, resolve } from 'node:path'\nimport { dshHomePath } from '../sdk.ts'\n\n/** Pointer file schema version (bumped only on an incompatible change). */\nexport const LOCATION_STATE_VERSION = 1\n\n/** A wiki folder = `root` + folder `name` (`.` means \"root itself\"). */\nexport interface WikiLocation {\n root: string\n name: string\n}\n\n/** Result of reading the pointer file. */\nexport interface WikiLocationState {\n /** Present only when a valid pointer exists. */\n active?: WikiLocation\n /** Non-fatal read problem worth surfacing in the settings page. */\n error?: string\n}\n\n/** Where the CURRENT location came from (settings-page display). */\nexport type WikiLocationSource = 'state' | 'config' | 'default'\n\n/** Payload of `GET /admin/wiki/location` (built in src/index.ts). */\nexport interface WikiLocationInfo {\n current: { root: string; name: string; path: string; source: WikiLocationSource }\n default: { root: string; name: string; path: string }\n /** Pointer file path (shown so a user can find/delete it by hand). */\n stateFile: string\n /** Wiki-looking folders under the current root (folder names, `.` = the root). */\n candidates: string[]\n /** Set when the pointer file exists but could not be trusted. */\n error?: string\n}\n\n/** Default pointer file: `$DSH_HOME/dsh-tiddlywiki/location.json`. */\nexport function defaultLocationStateFile(): string {\n return dshHomePath('dsh-tiddlywiki', 'location.json')\n}\n\n/**\n * Expand `$VAR` / `${VAR}` / `%VAR%` from process.env (config uses `$DSH_HOME`).\n * Moved here (v0.22.0) so both `resolveWikiRoot()` and the location module\n * expand user input identically — the settings page accepts the same syntax as\n * the cordis config block.\n */\nexport function expandEnvPath(input: string, env: NodeJS.ProcessEnv = process.env): string {\n return input\n .replace(/\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g, (_m, name: string) => env[name] ?? '')\n .replace(/\\$([A-Za-z_][A-Za-z0-9_]*)/g, (_m, name: string) => env[name] ?? '')\n .replace(/%([A-Za-z_][A-Za-z0-9_]*)%/g, (_m, name: string) => env[name] ?? '')\n}\n\n/**\n * Normalise + validate a candidate location.\n *\n * `root` must be ABSOLUTE (a relative root would resolve against the dsh web\n * process cwd, i.e. \"wherever it happened to be launched from\" — the docs say\n * so, and now the code refuses to store it). `name` is a single folder name;\n * `.` means \"the root folder itself\".\n */\nexport function normalizeLocation(input: { root?: unknown; name?: unknown }): { location?: WikiLocation; error?: string } {\n const rawRoot = typeof input.root === 'string' ? input.root.trim() : ''\n if (rawRoot.length === 0) return { error: '路径不能为空' }\n const root = expandEnvPath(rawRoot)\n if (root.includes('\\0')) return { error: '路径含非法字符' }\n if (!isAbsolute(root)) return { error: `请使用绝对路径(当前:${root})——相对路径会按 dsh web 的启动目录解析` }\n const rawName = typeof input.name === 'string' && input.name.trim().length > 0 ? input.name.trim() : 'main'\n if (rawName !== '.' && (rawName.includes('/') || rawName.includes('\\\\') || rawName === '..' || isAbsolute(rawName))) {\n return { error: `文件夹名不能包含路径分隔符(当前:${rawName})` }\n }\n return { location: { root: resolve(root), name: rawName } }\n}\n\n/** Absolute folder a location points at. */\nexport function locationPath(location: WikiLocation): string {\n return resolve(location.root, location.name)\n}\n\n/** Read the pointer file. Missing → `{}`; malformed → `{ error }` (never throws). */\nexport async function readLocationState(file: string = defaultLocationStateFile()): Promise<WikiLocationState> {\n let raw: string\n try {\n raw = await readFile(file, 'utf8')\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code\n if (code === 'ENOENT') return {}\n return { error: `位置指针文件读不到(${file}):${err instanceof Error ? err.message : String(err)}` }\n }\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n return { error: `位置指针文件不是合法 JSON(${file})——已回退到配置默认值` }\n }\n if (typeof parsed !== 'object' || parsed === null) return { error: `位置指针文件格式不对(${file})` }\n const active = (parsed as { active?: unknown }).active\n if (typeof active !== 'object' || active === null) return { error: `位置指针文件缺少 active 字段(${file})` }\n const normalized = normalizeLocation(active as { root?: unknown; name?: unknown })\n if (normalized.location === undefined) {\n return { error: `位置指针内容非法(${normalized.error ?? '未知原因'})——已回退到配置默认值` }\n }\n return { active: normalized.location }\n}\n\n/** Write the pointer file atomically (tmp + rename, mkdir -p). */\nexport async function writeLocationState(location: WikiLocation, file: string = defaultLocationStateFile()): Promise<void> {\n const payload = JSON.stringify({ version: LOCATION_STATE_VERSION, active: location, updatedAt: new Date().toISOString() }, null, 2)\n await mkdir(dirname(file), { recursive: true })\n const tmp = `${file}.tmp`\n await writeFile(tmp, payload, 'utf8')\n await rename(tmp, file)\n}\n\n/** Delete the pointer file (「恢复为配置默认」). Missing file is a no-op. */\nexport async function clearLocationState(file: string = defaultLocationStateFile()): Promise<void> {\n await rm(file, { force: true })\n}\n\n/**\n * Folders under `root` that look like a TiddlyWiki server wiki (they contain\n * `tiddlywiki.info`), for the settings-page picker. `root` itself is reported\n * as `\".\"` when it is a wiki. Bounded: an unreadable/short directory is not an\n * error — the picker is a convenience, never a requirement.\n */\nexport async function listWikiCandidates(root: string, limit = 30): Promise<string[]> {\n const names: string[] = []\n try {\n const rootStat = await stat(root)\n if (!rootStat.isDirectory()) return names\n try {\n await stat(join(root, 'tiddlywiki.info'))\n names.push('.')\n } catch { /* root itself is not a wiki */ }\n const entries = await readdir(root, { withFileTypes: true })\n for (const entry of entries) {\n if (names.length >= limit) break\n if (!entry.isDirectory() || entry.name.startsWith('.')) continue\n try {\n await stat(join(root, entry.name, 'tiddlywiki.info'))\n names.push(entry.name)\n } catch { /* not a wiki folder */ }\n }\n } catch { /* missing/unreadable root → empty picker */ }\n return names.sort((a, b) => a.localeCompare(b))\n}\n","/**\n * Runtime wiki switching (v0.22.0).\n *\n * Rebinding the plugin from wiki A to wiki B is a *stateful* operation: the TW\n * child, the auto-committer, the fs watcher and the config-tiddler overlay all\n * hold the old folder. Doing it half-way leaves the process describing two\n * different wikis, so the sequence below is deliberately boring:\n *\n * stop child → release extras → repoint → start child → reload config\n * → core-bootstrap the new wiki → re-arm extras → persist the pointer\n *\n * The pointer is written LAST: if any earlier step fails we roll the server\n * back to the old folder and the on-disk pointer still names the working wiki,\n * so a dsh web restart can never land on a half-initialised target.\n *\n * The orchestration is separated from src/index.ts so it can be driven by a\n * real test (scripts/verify-wiki-switch.mjs) with a real WikiServer and two\n * temp folders — the rollback path is the part that must not rot.\n *\n * @module dsh-tiddlywiki/host/wiki-switch\n */\nimport { locationPath, normalizeLocation, type WikiLocation } from './wiki-location.ts'\n\n/** Everything the switch needs from the plugin host, as plain callbacks. */\nexport interface WikiSwitchDeps {\n /** Location currently served (undefined when the plugin is on the default). */\n currentLocation: () => WikiLocation | undefined\n /** Absolute path currently served. */\n currentPath: () => string\n /** Stop the TW child. */\n stopServer: () => Promise<void>\n /** Apply a location to the (stopped) server; throws when a child is running. */\n applyLocation: (target: WikiLocation) => void\n /** Start the TW child; throws when it never becomes ready. */\n startServer: () => Promise<void>\n /** Stop the auto-committer + fs watcher (they hold the old folder). */\n teardownExtras: () => Promise<void>\n /** Re-create the auto-committer + fs watcher for the CURRENT folder. */\n setupExtras: () => void\n /** Re-read the config tiddler overlay from the new wiki. */\n reloadConfig: () => Promise<void>\n /** Startup-equivalent bootstrap of a wiki (markdown plugin, core seeds, language). */\n bootstrap: () => Promise<void>\n /** Persist the pointer file (failure is reported, not rolled back). */\n savePointer: (target: WikiLocation) => Promise<void>\n /** Log a rollback / non-fatal problem. */\n log?: (message: string) => void\n}\n\n/** Successful switch. */\nexport interface WikiSwitchOk {\n ok: true\n location: WikiLocation\n path: string\n /** Set when the wiki switched but the pointer could not be persisted. */\n warning?: string\n}\n\n/** Failed switch (the old wiki keeps serving when `rolledBack` is true). */\nexport interface WikiSwitchFail {\n ok: false\n error: string\n rolledBack: boolean\n}\n\nexport type WikiSwitchResult = WikiSwitchOk | WikiSwitchFail\n\n/**\n * Switch to `input` (root + folder name, same shape as the cordis config).\n * Never throws: failures are returned, and the previous wiki is restored.\n */\nexport async function switchWiki(\n deps: WikiSwitchDeps,\n input: { root?: unknown; name?: unknown },\n): Promise<WikiSwitchResult> {\n const normalized = normalizeLocation(input)\n if (normalized.location === undefined) return { ok: false, error: normalized.error ?? '位置非法', rolledBack: true }\n const target = normalized.location\n const targetPath = locationPath(target)\n const previous = deps.currentLocation()\n const previousPath = deps.currentPath()\n if (targetPath === previousPath) {\n return { ok: true, location: target, path: targetPath }\n }\n\n const restore = async (): Promise<boolean> => {\n try {\n await deps.stopServer()\n deps.applyLocation(previous ?? { root: previousPath, name: '.' })\n await deps.startServer()\n await deps.reloadConfig()\n deps.setupExtras()\n deps.log?.(`wiki switch rolled back to ${previousPath}`)\n return true\n } catch (err) {\n deps.log?.(`wiki switch rollback FAILED: ${err instanceof Error ? err.message : String(err)}`)\n return false\n }\n }\n\n try {\n await deps.stopServer()\n await deps.teardownExtras()\n deps.applyLocation(target)\n await deps.startServer()\n await deps.reloadConfig()\n await deps.bootstrap()\n deps.setupExtras()\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n const rolledBack = await restore()\n return { ok: false, error: message, rolledBack }\n }\n\n // The wiki is live: persist the pointer. A failure here is NOT rolled back —\n // the running wiki is the one the user asked for; only the restart-survival\n // is lost, and saying so is more useful than reverting a working switch.\n try {\n await deps.savePointer(target)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n deps.log?.(`wiki switch: pointer write failed: ${message}`)\n return { ok: true, location: target, path: targetPath, warning: `位置已切换,但指针文件写入失败(重启 dsh web 后会回到原知识库):${message}` }\n }\n return { ok: true, location: target, path: targetPath }\n}\n","/**\n * dsh-tiddlywiki — host half.\n *\n * TiddlyWiki 5 as the DSH persistent knowledge base. Wiring:\n * - WikiServer spawns/kills/self-heals the TW 5 child process (loopback, auto\n * port) and scaffolds the wiki folder on first run;\n * - the git face bootstraps the wiki folder as a repository and wires the\n * debounced auto-committer;\n * - `tiddlywiki_*` agent tools + a system-prompt section;\n * - /dsh-tiddlywiki routes when a webServer is present.\n *\n * Export shape follows dsh-taskboard: a function/namespace plugin —\n * `name` / `inject` / `apply`, NO default export. Config arrives as the\n * second apply() argument (Cordis `runtime.callback(ctx, config)`).\n *\n * Extra exports (WikiServer / TiddlyWebClient / GitFace / ...) exist for the\n * headless selftest and future reuse; the loader only reads name/inject/apply.\n *\n * @module dsh-tiddlywiki\n */\nimport { watch, type FSWatcher } from 'node:fs'\nimport { readFile, writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { AutoCommitter, GitFace } from './host/git.ts'\nimport { registerRoutes, type AgentPresetsFace, type PermissionPresetsFace, type SessionControllerFace, type SessionPersistenceFace, type SessionsFace, type SessionQueryFace, type UiDefaultsPublic, type WebServerFace, type WorkspaceRegistryFace } from './host/routes.ts'\nimport { ConfigStore, DARK_PALETTE_DEFAULT, TW_WEB_HOST_TIDDLER, TW_WEB_HOST_DEFAULT, type PluginConfigShape } from './host/config.ts'\nimport { registerAdminRoutes, ensureLanguage, ensurePlugin, resolveTwRoot, type AdminDeps } from './host/admin.ts'\nimport { runAllSeeds, checkAllSeeds, runSeedById, removeSeedById, waitForFileWrite, flushPendingWrites, needsRestartAfterSeeds } from './host/seeds.ts'\nimport { RENDER_PLUGIN_FILE } from './host/seed-render.ts'\nimport { TiddlyWebClient, isBinaryType, TEXT_LIST_FILTER } from './host/tw-api.ts'\nimport { ClipBridge, downloadClipImage, type BridgeConfig, type ClipImageDownload } from './host/clip-bridge.ts'\nimport { registerTiddlywikiTools, tiddlywikiToolSummary, type ToolsDeps } from './host/tools.ts'\nimport { buildPromptText, normalizePromptMode, PROMPT_SECTION_NAME, PROMPT_SECTION_ORDER, type PromptConfig } from './host/prompt.ts'\nimport {\n clearLocationState,\n defaultLocationStateFile,\n expandEnvPath,\n listWikiCandidates,\n locationPath,\n readLocationState,\n writeLocationState,\n type WikiLocation,\n type WikiLocationInfo,\n type WikiLocationSource,\n} from './host/wiki-location.ts'\nimport { switchWiki, type WikiSwitchResult } from './host/wiki-switch.ts'\nimport { PATH_PREFIX, TW_PROXY_PATH, TW_PROXY_PREFIX, WikiServer } from './host/wiki.ts'\nimport { dshHomePath, defineTool } from './sdk.ts'\n\n/** Cordis plugin name (also the client loader id / profile row id). */\nexport const name = 'dsh-tiddlywiki'\n\n/** Required host services (tool registry + prompt assembly). */\nexport const inject = ['tools', 'systemPrompt']\n\n/** Re-exports for the headless selftest and future consumers. */\nexport { AutoCommitter, GitFace, PATH_PREFIX, TW_PROXY_PATH, TW_PROXY_PREFIX, TiddlyWebClient, isBinaryType, TEXT_LIST_FILTER, WikiServer, dshHomePath, defineTool }\nexport { ConfigStore, deepMerge } from './host/config.ts'\nexport { sanitizeTwFragment, isSafeUrl } from './host/sanitize.ts'\nexport { MISSING_TYPE_FILTER } from './host/tw-api.ts'\nexport {\n AGENT_WRITTEN_TAG,\n DEFAULT_NOTE_TYPE,\n HUMAN_EDITED_TAG,\n WriteConflictError,\n assertNoConflict,\n buildWriteTiddler,\n cleanTiddler,\n flattenTiddlerFields,\n} from './host/write-policy.ts'\nexport { openInTwEditor, registerRoutes } from './host/routes.ts'\nexport { writeSessionSummary, SESSION_SUMMARY_PREFIX } from './host/routes.ts'\nexport type { SessionQueryFace, SessionSummaryResult } from './host/routes.ts'\nexport { registerAdminRoutes, resolveTwRoot, readWikiInfo, writeWikiInfo, ensurePlugin, bundledCatalog, ensureLanguage, normalizeThemes, MASKED_SECRET, maskConfigSecrets, stripMaskedSecrets } from './host/admin.ts'\nexport { escapeInline } from './host/session-summary.ts'\nexport { seedDocNote, docNoteText, DOC_NOTE_TITLE, DOC_NOTE_TAG, DOC_NOTE_TEXT } from './host/seed-notes.ts'\nexport { hashText, parseSeedMarker, readSeedMarker, writeSeedMarker, SEED_MARKER_VERSION } from './host/seed-util.ts'\nexport { seedStarterDocs, STARTER_DOCS_ITEMS, STARTER_DOCS_MARKER_TITLE, DSH_DOCS_TAG } from './host/seed-starter-docs.ts'\nexport { seedSendToAgent, SEND_TO_AGENT_PLUGIN_TITLE, SEND_TO_AGENT_MARKER_TITLE, SEND_TO_AGENT_BUNDLE_TEXT } from './host/seed-send-to-agent.ts'\nexport { seedRenderRoute, RENDER_PLUGIN_TITLE, RENDER_MARKER_TITLE, RENDER_PLUGIN_FILE, RENDER_BUNDLE_TEXT } from './host/seed-render.ts'\nexport { seedHomeIndex, HOME_INDEX_ITEMS, HOME_INDEX_MARKER_TITLE, HOME_DEFAULT_TIDDLERS } from './host/seed-home.ts'\nexport { seedAllArticles, ALL_ARTICLES_TITLE, ALL_ARTICLES_MARKER_TITLE, ALL_ARTICLES_TEXT } from './host/seed-all-articles.ts'\nexport { seedUiStyles, UI_STYLE_ITEMS, UI_STYLES_MARKER_TITLE } from './host/seed-ui-styles.ts'\nexport { seedMenubarTheme, MENUBAR_THEME_TIDDLER, MENUBAR_THEME_MARKER_TITLE, MENUBAR_THEME_TEXT } from './host/seed-menubar-theme.ts'\nexport { seedClipBridge, unseedClipBridge, CLIP_BRIDGE_DOC_TITLE, CLIP_BRIDGE_MARKER_TITLE, CLIP_BRIDGE_DOC_TEXT, CLIP_BRIDGE_BOOKMARKLET, CLIP_BRIDGE_DRAG_HREF } from './host/seed-clip-bridge.ts'\nexport { runAllSeeds, checkAllSeeds, runSeedById, removeSeedById, waitForFileWrite, flushPendingWrites, needsRestartAfterSeeds, SEED_DEFS, type SeedStatus, type SeedRunResult } from './host/seeds.ts'\nexport { registerTiddlywikiTools, TRASH_PREFIX, TRASH_INDEX_TITLE, TrashIndexUnavailableError } from './host/tools.ts'\nexport { tiddlywikiToolSummary } from './host/tools.ts'\nexport {\n buildPromptText,\n escapePromptBraces,\n normalizePromptMode,\n toolSignatureLines,\n PROMPT_GOVERNANCE_BLOCKS,\n PROMPT_MODES,\n PROMPT_SECTION_NAME,\n PROMPT_SECTION_ORDER,\n DEFAULT_PROMPT_MODE,\n type PromptConfig,\n type PromptMode,\n type PromptToolSummary,\n} from './host/prompt.ts'\nexport {\n clearLocationState,\n defaultLocationStateFile,\n expandEnvPath,\n listWikiCandidates,\n locationPath,\n normalizeLocation,\n readLocationState,\n writeLocationState,\n LOCATION_STATE_VERSION,\n type WikiLocation,\n type WikiLocationInfo,\n type WikiLocationSource,\n type WikiLocationState,\n} from './host/wiki-location.ts'\nexport { switchWiki, type WikiSwitchResult, type WikiSwitchDeps } from './host/wiki-switch.ts'\nexport { ClipBridge, buildClipTiddler, buildImageNoteTiddler, buildBinaryTiddler, downloadClipImage, hostAllowed, parseClipPayload, pickImageMime, imageExtensionForMime, isPrivateAddress, assertPublicImageUrl, resolveClipTitle, type BridgeConfig, type ClipBridgeDeps, type ClipImageDownload, type ClipImageResult } from './host/clip-bridge.ts'\nexport type { PluginConfigShape } from './host/config.ts'\nexport type { GitStatusView } from './host/git.ts'\nexport type { Tiddler } from './host/tw-api.ts'\nexport type { WikiServerOptions, WikiStatusView } from './host/wiki.ts'\n\n/** Plugin config (design doc §13). Defaults are applied in apply().\n * Mirrors PluginConfigShape (src/host/config.ts) — the cordis `config:` block;\n * keep the two shapes in lockstep when adding a config field. */\nexport interface TiddlywikiConfig {\n wikiRoot?: string\n wiki?: string\n port?: number\n git?: { autoCommit?: boolean; debounceMs?: number; remote?: string; branch?: string }\n note?: { tag?: string }\n /** 本地剪藏桥(书签小工具):见 host/clip-bridge.ts 与 seed-clip-bridge.ts 文档。 */\n bridge?: { enabled?: boolean; port?: number; token?: string; tag?: string }\n /** 注入给每个会话的系统提示词(v0.21.0,见 host/prompt.ts)。 */\n prompt?: { enabled?: boolean; mode?: 'slim' | 'full'; extra?: string; override?: string }\n ui?: { showQuickNote?: boolean; showQuickNoteDock?: boolean; quickNoteMode?: 'native' | 'card'; sidebarLabel?: string; showPanelStatus?: boolean; showSyncButton?: boolean; followDshTheme?: boolean; darkPalette?: string; tabLabel?: string; showSessionTab?: boolean; showRightbarTab?: boolean; sendToAgent?: { enabled?: boolean; endpoint?: string; token?: string }; allArticles?: { pageSize?: number } }\n /** 启动时自动启用的 TW 语言代码(如 \"zh-Hans\"),也受配置 tiddler 覆盖。 */\n uiLanguage?: string\n auth?: { username?: string; password?: string }\n}\n\n/** Structural host context (subset of the dsh host + cordis surfaces). */\nexport interface HostCtx {\n tools: { register(tool: unknown): () => void }\n systemPrompt: { section(opts: { name: string; order: number; text: string }): () => void }\n inject<T = unknown>(names: string | string[], callback: (ctx: HostCtx) => T, config?: unknown): unknown\n effect(fn: () => unknown, label?: string): void\n get(name: string): unknown\n [key: string]: unknown\n}\n\n/** Resolved plugin config (defaults merged with the `config:` block). */\ninterface ResolvedConfig {\n wikiRoot: string\n wiki: string\n port: number\n git: { autoCommit: boolean; debounceMs: number; remote: string; branch: string }\n note: { tag: string }\n bridge: { enabled: boolean; port: number; token: string; tag: string }\n ui: { showQuickNote: boolean; showQuickNoteDock: boolean; quickNoteMode: 'native' | 'card'; sidebarLabel: string; showPanelStatus: boolean; showSyncButton: boolean; followDshTheme: boolean; darkPalette: string; tabLabel: string; showSessionTab: boolean; showRightbarTab: boolean; sendToAgent: { enabled: boolean; endpoint?: string; token?: string }; allArticles: { pageSize: number } }\n uiLanguage: string\n auth: { username?: string; password?: string }\n}\n\n/** 剪藏桥默认端口(与 seed 文档书签代码里的地址保持一致)。 */\nexport const CLIP_BRIDGE_DEFAULT_PORT = 8618\n\n/**\n * Official plugin whose parser every note this plugin writes depends on\n * (`text/markdown`). `--init server` does NOT include it — see ensurePlugin().\n */\nexport const MARKDOWN_PLUGIN = 'tiddlywiki/markdown'\n\nconst DEFAULTS: ResolvedConfig = {\n wikiRoot: '',\n wiki: 'main',\n port: 0,\n git: { autoCommit: true, debounceMs: 60_000, remote: '', branch: 'main' },\n note: { tag: 'inbox' },\n bridge: { enabled: false, port: CLIP_BRIDGE_DEFAULT_PORT, token: '', tag: 'clip' },\n ui: { showQuickNote: true, showQuickNoteDock: true, quickNoteMode: 'native', sidebarLabel: 'TiddlyWiki', showPanelStatus: true, showSyncButton: true, followDshTheme: true, darkPalette: DARK_PALETTE_DEFAULT, tabLabel: '知识库', showSessionTab: true, showRightbarTab: true, sendToAgent: { enabled: true }, allArticles: { pageSize: 10 } },\n uiLanguage: '',\n auth: { username: '', password: '' },\n}\n\n/** Config tiddler steering TW's frontend API base (tiddlywebadaptor). */\nexport { TW_WEB_HOST_TIDDLER, TW_WEB_HOST_DEFAULT } from './host/config.ts'\n\n/**\n * Point TW's frontend at the same-origin DSH proxy (remote-access mode, R1).\n * The tiddlywebadaptor builds every API URL from $:/config/tiddlyweb/host; its\n * default `$protocol$//$host$/` resolves to the iframe's origin ROOT, which\n * 404s whenever the browser is not on the same machine as TW. Written only\n * when the tiddler is missing or still the legacy default — a user override is\n * honored (e.g. someone who really does expose TW on a dedicated origin).\n */\nexport async function ensureTwWebHost(client: TiddlyWebClient | undefined): Promise<void> {\n if (client === undefined) return\n // Read WITHOUT swallowing: only a 404 means \"missing\". A transient failure\n // must not look like \"absent\" and overwrite a user's custom host value.\n const current = (await client.get(TW_WEB_HOST_TIDDLER))?.text\n if (current !== undefined && current.trim() !== TW_WEB_HOST_DEFAULT) return\n await client.put({ title: TW_WEB_HOST_TIDDLER, text: TW_PROXY_PATH, type: 'text/plain', tags: [] })\n}\n\n/**\n * Resolve the DEFAULT wikiRoot: explicit config (env-expanded) else\n * $DSH_HOME/tiddlywiki. The runtime pointer file can override it — see\n * `readLocationState()` in host/wiki-location.ts.\n */\nfunction resolveWikiRoot(config: TiddlywikiConfig): string {\n if (config.wikiRoot !== undefined && config.wikiRoot.trim().length > 0) {\n return expandEnvPath(config.wikiRoot.trim())\n }\n return dshHomePath('tiddlywiki')\n}\n\n/**\n * Ensure the wiki's `.gitignore` covers TW's transient artifacts, WITHOUT\n * clobbering rules the user added.\n *\n * This used to rewrite the whole file on every start, silently deleting any\n * custom ignore rules the user had put in `wiki/.gitignore`. Now the file is\n * only touched when one of the managed lines is missing, and the user's own\n * content is preserved verbatim.\n */\nconst MANAGED_GITIGNORE_LINES = [\n 'tiddlers/$__temp_*',\n 'tiddlers/$__StoryList*',\n 'tiddlers/$__HistoryList*',\n '*.meta.tmp',\n // flush 哨兵(每次「重启前排干 syncer」都会重写,内容是时间戳):不进 git,\n // 否则每次同步/播种都会给用户仓库塞一个无意义的 diff(v0.19.1)。\n 'tiddlers/$__plugins_dsh-tiddlywiki_flush-probe.tid',\n 'tiddlers/$__plugins_dsh-tiddlywiki_flush-probe.txt',\n 'tiddlers/$__plugins_dsh-tiddlywiki_flush-probe.txt.meta',\n]\n\nasync function writeGitignore(wikiPath: string): Promise<void> {\n const file = join(wikiPath, '.gitignore')\n let current = ''\n try {\n current = await readFile(file, 'utf8')\n } catch {\n /* absent → first run, write the default block */\n }\n const existing = current.split(/\\r?\\n/).map((line) => line.trim())\n const missing = MANAGED_GITIGNORE_LINES.filter((line) => !existing.includes(line))\n if (missing.length === 0) return\n const block = [\n '# TiddlyWiki transient artifacts (auto-managed by dsh-tiddlywiki)',\n ...missing,\n '',\n ].join('\\n')\n const prefix = current.length === 0 ? '' : current.endsWith('\\n') ? `${current}\\n` : `${current}\\n\\n`\n await writeFile(file, `${prefix}${block}`, 'utf8')\n}\n\n/** Watch the wiki folders and touch the auto-committer on changes. */\nfunction watchWiki(wikiPath: string, onChange: () => void): () => void {\n const watchers: FSWatcher[] = []\n for (const dir of [join(wikiPath, 'tiddlers'), wikiPath]) {\n try {\n const watcher = watch(dir, { persistent: false }, () => onChange())\n watchers.push(watcher)\n } catch {\n /* directory may not exist yet; the committer also fires on our writes */\n }\n }\n return () => {\n for (const watcher of watchers) {\n try { watcher.close() } catch { /* already closed */ }\n }\n }\n}\n\n/**\n * Wait until a file exists on disk (polling), up to `timeoutMs`. TW's syncer\n * flushes REST writes to the filesystem on a ~250ms task timer, so a freshly\n * seeded tiddler is not on disk the moment PUT resolves — a caller that must\n * restart TW right after (so a seeded server route loads) has to wait for the\n * flush first, or the restarted TW would boot from a stale snapshot and the\n * seeded module would be missing.\n *\n * The implementation lives in host/seeds.ts (shared with the settings page's\n * seed-run route), which is where `waitForFileWrite` is imported from.\n */\n\n\n/**\n * Mount the host half.\n * @param ctx - the plugin context (tools + systemPrompt injected).\n * @param rawConfig - the plugin row's `config:` block (Cordis second arg).\n */\nexport function apply(ctx: HostCtx, rawConfig: TiddlywikiConfig = {}): void {\n const config: ResolvedConfig = {\n wikiRoot: resolveWikiRoot(rawConfig),\n wiki: rawConfig.wiki ?? DEFAULTS.wiki,\n port: rawConfig.port ?? DEFAULTS.port,\n git: { ...DEFAULTS.git, ...(rawConfig.git ?? {}) },\n note: { ...DEFAULTS.note, ...(rawConfig.note ?? {}) },\n bridge: { ...DEFAULTS.bridge, ...(rawConfig.bridge ?? {}) },\n ui: {\n ...DEFAULTS.ui,\n ...(rawConfig.ui ?? {}),\n // Merge nested ui.* groups explicitly so defaults stay required (a plain\n // spread of the lax cordis shape would widen them to optional).\n sendToAgent: { ...DEFAULTS.ui.sendToAgent, ...(rawConfig.ui?.sendToAgent ?? {}) },\n allArticles: { ...DEFAULTS.ui.allArticles, ...(rawConfig.ui?.allArticles ?? {}) },\n },\n uiLanguage: typeof rawConfig.uiLanguage === 'string' ? rawConfig.uiLanguage.trim() : DEFAULTS.uiLanguage,\n auth: { ...DEFAULTS.auth, ...(rawConfig.auth ?? {}) },\n }\n // The location is MUTABLE since v0.22.0: the runtime pointer file (read below\n // in the startup task) and the settings page's「切换」both repoint it. Every\n // consumer reads it through a getter (`() => wikiPath`), never a captured copy.\n let wikiPath = join(config.wikiRoot, config.wiki)\n /** Pointer file the runtime switch persists (outside every wiki, see wiki-location.ts). */\n const locationStateFile = defaultLocationStateFile()\n const git = new GitFace()\n\n // Runtime-editable config (settings page): the cordis `config:` block is the\n // BASE; a config tiddler ($:/plugins/dsh-tiddlywiki/config) written by the\n // settings page overlays it. Effective values come from configStore.get().\n const configStore = new ConfigStore({ note: config.note, git: config.git, ui: config.ui, uiLanguage: config.uiLanguage, bridge: config.bridge } satisfies PluginConfigShape)\n const eff = (): PluginConfigShape => configStore.get()\n const effectiveNoteTag = (): string => {\n const tag = eff().note?.tag\n return typeof tag === 'string' && tag.trim().length > 0 ? tag : config.note.tag\n }\n /**\n * Effective bridge config (defaults + settings-page overlay). Used PER\n * REQUEST by the clip bridge — enabled/token/tag edits on the settings page\n * apply immediately; only the port is fixed at startup (listener binds once).\n */\n const effectiveBridge = (): BridgeConfig => {\n const b = eff().bridge ?? {}\n const port = typeof b.port === 'number' && Number.isInteger(b.port) && b.port > 0 && b.port < 65536 ? b.port : config.bridge.port\n const token = typeof b.token === 'string' ? b.token : ''\n const tag = typeof b.tag === 'string' && b.tag.trim().length > 0 ? b.tag.trim() : config.bridge.tag\n return { enabled: b.enabled === true, port, token, tag }\n }\n // Return type comes from routes.ts (single declaration of the UI payload the\n // client consumes — v0.20.0, previously mirrored inline here).\n const effectiveUi = (): UiDefaultsPublic => {\n const ui = eff().ui ?? {}\n const palette = typeof ui.darkPalette === 'string' && ui.darkPalette.trim().length > 0 ? ui.darkPalette.trim() : DARK_PALETTE_DEFAULT\n const label = typeof ui.sidebarLabel === 'string' && ui.sidebarLabel.trim().length > 0 ? ui.sidebarLabel.trim() : config.ui.sidebarLabel\n const tabLabel = typeof ui.tabLabel === 'string' && ui.tabLabel.trim().length > 0 ? ui.tabLabel.trim() : config.ui.tabLabel\n const quickNoteMode: 'native' | 'card' = ui.quickNoteMode === 'card' ? 'card' : 'native'\n return {\n showQuickNote: ui.showQuickNote !== false,\n showQuickNoteDock: ui.showQuickNoteDock !== false,\n quickNoteMode,\n sidebarLabel: label,\n showPanelStatus: ui.showPanelStatus !== false,\n showSyncButton: ui.showSyncButton !== false,\n followDshTheme: ui.followDshTheme !== false,\n darkPalette: palette,\n tabLabel,\n showSessionTab: ui.showSessionTab !== false,\n showRightbarTab: ui.showRightbarTab !== false,\n }\n }\n\n const disposers: Array<() => void> = []\n const disposeAll = (): void => {\n for (const dispose of disposers.splice(0)) dispose()\n }\n\n // ── System prompt section (v0.21.0: configurable + live) ──────────────────\n // The section text is built from the EFFECTIVE config (cordis base + config\n // tiddler overlay), so the settings page controls it. DSH emits\n // `system-prompt/change` on registration/disposal and re-renders the section\n // in history, so a settings save applies to the CURRENT session from its next\n // model step — no dsh web restart, no new session (the pre-v0.21 behaviour).\n //\n // `applyPrompt()` is called (a) after the wiki is up and the config tiddler\n // has been loaded, and (b) after every settings-page save (admin route →\n // `onConfigChanged`). Re-registration is skipped when the built text is\n // unchanged, so saving an unrelated setting never churns the prompt.\n let disposePromptSection: (() => void) | undefined\n let currentPromptText: string | undefined\n /** Built text for the current effective config (also the preview endpoint). */\n const promptText = (): string => {\n const p = (eff().prompt ?? {}) as PromptConfig\n return buildPromptText({\n enabled: p.enabled !== false,\n mode: normalizePromptMode(p.mode),\n extra: typeof p.extra === 'string' ? p.extra : '',\n override: typeof p.override === 'string' ? p.override : '',\n // Signature catalogue for `full` mode comes from the live tool registry,\n // never from hand-written prose (v0.21.0 — the old copy had drifted).\n tools: tiddlywikiToolSummary(),\n })\n }\n const applyPrompt = (): void => {\n const next = promptText()\n if (next === currentPromptText) return\n currentPromptText = next\n disposePromptSection?.()\n disposePromptSection = undefined\n if (next.length === 0) return\n disposePromptSection = ctx.systemPrompt.section({ name: PROMPT_SECTION_NAME, order: PROMPT_SECTION_ORDER, text: next })\n }\n disposers.push(() => {\n disposePromptSection?.()\n disposePromptSection = undefined\n })\n\n // TW child server.\n const server = new WikiServer({\n wikiRoot: config.wikiRoot,\n wiki: config.wiki,\n port: config.port,\n username: config.auth.username,\n password: config.auth.password,\n })\n\n // Lazy TW client. Rebuilt whenever the bound PORT changes (the wiki re-probes\n // a free port when a crash happened before readiness, so a cached client must\n // not stay pinned to a dead port). Credentials are attached preemptively: when\n // auth.username is configured the TW child runs behind `readers`/`writers`, so\n // EVERY request (reads included) needs Basic auth.\n let clientCache: TiddlyWebClient | undefined\n let clientPort: number | undefined\n const client = (): TiddlyWebClient | undefined => {\n const port = server.currentPort\n if (port === undefined) return undefined\n if (clientCache === undefined || clientPort !== port) {\n clientCache = new TiddlyWebClient(`http://127.0.0.1:${port}`, { username: config.auth.username, password: config.auth.password })\n clientPort = port\n }\n return clientCache\n }\n\n // 本地剪藏桥(书签小工具后端):只监听 127.0.0.1,per-request 读 effective\n // config —— enabled/token/tag 在设置页保存后立即生效;port 只在启动时绑定\n // 一次(改端口需重启 dsh web,seed 文档已说明)。写入走唯一的 TiddlyWebClient\n // 通道(D1),不存在第二条写路径。\n const clipBridge = new ClipBridge({\n getConfig: effectiveBridge,\n write: async (tiddler) => {\n const c = client()\n if (c === undefined) throw new Error('wiki not ready')\n await c.put(tiddler)\n },\n exists: async (title) => {\n const c = client()\n if (c === undefined) throw new Error('wiki not ready')\n return (await c.get(title)) !== undefined\n },\n // Server-side image download: no browser CORS; a browser-ish UA + the clip\n // source page as Referer get past most hotlink-protected CDNs. The whole\n // SSRF posture (public http(s) only, per-hop validation with the resolved\n // address PINNED so DNS cannot rebind between check and connect, manual\n // redirects, streaming 15MB cap, no transparent decompression) lives in\n // downloadClipImage (v0.19.0).\n download: async (imageUrl, referer): Promise<ClipImageDownload> => {\n const result = await downloadClipImage(imageUrl, referer)\n return { buffer: result.buffer, type: result.type ?? null }\n },\n log: (m) => console.info('[dsh-tiddlywiki] clip bridge:', m),\n })\n ctx.effect(() => () => clipBridge.stop(), 'dsh-tiddlywiki: clip bridge')\n\n // Auto-committer + filesystem watcher (created after the wiki dir exists).\n // Reads the EFFECTIVE config so a settings-page git change survives a restart.\n //\n // SPLIT SETUP/TEARDOWN (v0.22.0): a runtime wiki switch has to release both\n // (they hold the OLD folder) and re-arm them for the new one. The teardown is\n // registered exactly ONCE below — a per-switch `disposers.push()` would leak a\n // disposer per switch.\n let committer: AutoCommitter | undefined\n let unwatch: (() => void) | undefined\n const setupCommitter = (): void => {\n const g = eff().git ?? {}\n committer = new AutoCommitter({\n git,\n dir: wikiPath,\n enabled: g.autoCommit ?? config.git.autoCommit,\n debounceMs: g.debounceMs ?? config.git.debounceMs,\n message: () => `wiki autocommit ${new Date().toISOString()}`,\n onError: (err) => console.warn('[dsh-tiddlywiki] autocommit:', err),\n })\n unwatch = watchWiki(wikiPath, () => committer?.touch())\n }\n const teardownCommitter = async (): Promise<void> => {\n try { unwatch?.() } catch { /* already closed */ }\n unwatch = undefined\n const current = committer\n committer = undefined\n // Flush a pending auto-commit BEFORE dropping the folder, so a write made\n // within the debounce window is not left uncommitted (switch + shutdown).\n try { await current?.flush() } catch { /* best-effort */ }\n current?.dispose()\n }\n disposers.push(() => { void teardownCommitter() })\n\n // Git bootstrap: repo init + initial commit + .gitignore (+ remote/first push).\n const bootstrapGit = async (): Promise<void> => {\n const g = eff().git ?? {}\n const branch = g.branch ?? config.git.branch\n const remote = g.remote ?? config.git.remote\n const isRepo = await git.isRepo(wikiPath)\n if (!isRepo) {\n await git.init(wikiPath, branch)\n await writeGitignore(wikiPath)\n await git.initialCommit(wikiPath)\n } else {\n await writeGitignore(wikiPath)\n }\n if (remote.trim().length > 0) {\n const ensured = await git.ensureRemote(wikiPath, remote.trim())\n if (ensured.ok) {\n const first = await git.firstPush(wikiPath)\n if (!first.ok) console.warn('[dsh-tiddlywiki] first push failed (retry with tiddlywiki_git_sync):', first.message)\n } else {\n console.warn('[dsh-tiddlywiki] git remote setup:', ensured.message)\n }\n }\n }\n\n // Tools (works even while the wiki is down; wiki() resolves lazily).\n const toolsDeps: ToolsDeps = {\n wiki: client,\n git,\n wikiPath: () => wikiPath,\n autoCommit: () => committer?.touch(),\n // After a pull that changed the working tree, restart TW so the server\n // (and the agent's reads) see the pulled content, not the old snapshot.\n restartWiki: async () => { await server.restart() },\n }\n disposers.push(...registerTiddlywikiTools(ctx, toolsDeps))\n // Register the prompt section only AFTER the tools: `full` mode's signature\n // catalogue is generated from the registry filled by that call (v0.21.0).\n applyPrompt()\n\n // Bring the wiki up, load the override config, then bootstrap git + committer.\n //\n // DISPOSAL-AWARE (v0.19.1): this task is fire-and-forget, but teardown can run\n // while it is still awaiting (plugin hot-reload / disable / dsh web exit\n // within the first seconds). Without the flag the disposer finished first and\n // the startup task then spawned the TW child / bound the clip-bridge port —\n // an orphan process and a leaked listener. Every await below is followed by a\n // `disposed` check, and the task stops the child itself when it notices.\n let disposed = false\n /**\n * Core-bootstrap the CURRENT `wikiPath`: markdown parser plugin, the core +\n * starter seeds, and the configured UI language. Extracted from the startup\n * task (v0.22.0) because a runtime wiki switch must run exactly this on the\n * new folder — the two paths must not drift apart.\n *\n * Only `render-route` needs a TW restart (it carries a SERVER route loaded at\n * boot); plain-content seeds must NOT restart, or the syncer's unflushed REST\n * writes would be lost.\n */\n const bootstrapWiki = async (): Promise<void> => {\n const seedClient = client()\n if (seedClient === undefined) return\n let pluginAdded = false\n try {\n pluginAdded = await ensurePlugin(wikiPath, resolveTwRoot(), MARKDOWN_PLUGIN)\n if (pluginAdded) console.info(`[dsh-tiddlywiki] enabled ${MARKDOWN_PLUGIN} for this wiki`)\n } catch (err) {\n console.warn(`[dsh-tiddlywiki] enabling ${MARKDOWN_PLUGIN}:`, err)\n }\n const seedStartedAt = Date.now()\n const results = await runAllSeeds({ client: seedClient, tools: tiddlywikiToolSummary() })\n for (const r of results) {\n if (!r.ok) console.warn(`[dsh-tiddlywiki] seed ${r.id} failed:`, r.error ?? r.detail)\n }\n if (needsRestartAfterSeeds(results)) {\n const renderFile = join(wikiPath, 'tiddlers', RENDER_PLUGIN_FILE)\n const flushed = await waitForFileWrite(renderFile, 8_000, 150, seedStartedAt)\n if (!flushed) console.warn('[dsh-tiddlywiki] seeded render plugin file not seen on disk before restart')\n // Drain the rest of the syncer queue as well: a restart that boots from a\n // stale snapshot loses every write still queued (v0.19.0).\n const drained = await flushPendingWrites(seedClient, join(wikiPath, 'tiddlers'))\n if (!drained) console.warn('[dsh-tiddlywiki] seed writes may not have been flushed before restart')\n if (!disposed) await server.restart()\n } else if (pluginAdded) {\n // tiddlywiki.info is written directly by us (no TW flush to wait for),\n // but TW only loads the plugin at boot.\n if (!disposed) await server.restart()\n }\n // Apply the configured UI language (e.g. \"zh-Hans\"): enable the bundled\n // language plugin in tiddlywiki.info.languages + restart once so TW loads\n // it at boot (fully offline — official language packs ship in the pkg).\n const uiLang = eff().uiLanguage\n if (typeof uiLang === 'string' && uiLang.trim().length > 0) {\n try {\n const code = uiLang.trim()\n const changed = await ensureLanguage(wikiPath, resolveTwRoot(), code)\n if (changed && !disposed) await server.restart()\n // Pin the active language tiddler so TW's UI actually switches.\n const langClient = client()\n if (langClient !== undefined) {\n await langClient.put({ title: '$:/language', text: `$:/languages/${code}`, type: 'text/plain', tags: [] }).catch(() => undefined)\n }\n } catch (err) {\n console.warn('[dsh-tiddlywiki] applying uiLanguage:', err)\n }\n }\n }\n\n const startupTask = (async () => {\n try {\n // Runtime pointer FIRST (v0.22.0): the settings page's「切换」persists the\n // chosen location in $DSH_HOME/dsh-tiddlywiki/location.json, which must\n // win over the cordis config default. Applied while the child is still\n // stopped, so the very first spawn already serves the right folder.\n const saved = await readLocationState(locationStateFile)\n if (saved.error !== undefined) console.warn('[dsh-tiddlywiki]', saved.error)\n if (saved.active !== undefined) {\n server.setLocation(saved.active)\n wikiPath = locationPath(saved.active)\n console.info(`[dsh-tiddlywiki] wiki location (pointer file): ${wikiPath}`)\n }\n await server.start()\n if (disposed) {\n await server.stop().catch(() => undefined)\n return\n }\n await configStore.load(client())\n // The config tiddler may carry prompt.* overrides: rebuild the already\n // registered section so a saved preference applies without a restart.\n applyPrompt()\n if (disposed) {\n await server.stop().catch(() => undefined)\n return\n }\n // Clip bridge: bind once on the configured port (works even while\n // disabled — every request re-checks the effective enabled flag, so the\n // settings-page toggle applies without a dsh web restart).\n try {\n await clipBridge.start(effectiveBridge().port)\n console.info(`[dsh-tiddlywiki] clip bridge listening on 127.0.0.1:${clipBridge.port} (enabled=${effectiveBridge().enabled})`)\n } catch (err) {\n console.warn('[dsh-tiddlywiki] clip bridge start:', err)\n }\n if (disposed) {\n try { await clipBridge.stop() } catch { /* already closing */ }\n await server.stop().catch(() => undefined)\n return\n }\n // Point TW's frontend at the same-origin DSH proxy (remote-access mode):\n // part of the seed registry (tw-web-host), so it is also covered by the\n // settings page's 重新初始化. Runs before git bootstrap so the config\n // tiddler joins the first commit.\n //\n // Seeds run after the effective config is loaded: every one-time\n // \"与 dsh 联动需要 wiki 预置\" item lives in the SEED_DEFS registry, and the\n // startup path seeds ONLY the core items (功能必需:发送给 Agent 按钮 +\n // TW 前端 API 基址 + 原生渲染路由) plus the starter docs — NON-force, so a\n // same-named tiddler is never overwritten. Optional seeds (首页 / 所有文章 /\n // 自定义样式 / menubar 顶栏主题自适应 / 剪藏桥说明) are opt-in from the\n // settings page「初始化」section.\n // Markdown parser + seeds + UI language: the SAME routine a runtime wiki\n // switch runs on its new folder (v0.22.0, host/wiki-switch.ts).\n try {\n await bootstrapWiki()\n } catch (err) {\n console.warn('[dsh-tiddlywiki] seeding wiki:', err)\n }\n } catch (err) {\n console.warn('[dsh-tiddlywiki] startup issue (self-healing is armed):', err)\n }\n // Git bootstrap + the auto-committer do not depend on the TW child, so run\n // them even when the wiki failed to start: every write is still versioned\n // (and a retry/restart later finds a ready repository). Skipped entirely\n // once disposed — otherwise this would register a watcher/timer (and an\n // initial commit) after teardown already ran.\n if (disposed) return\n try {\n await bootstrapGit()\n if (disposed) return\n setupCommitter()\n } catch (err) {\n console.warn('[dsh-tiddlywiki] git bootstrap failed:', err)\n }\n })()\n\n // ── Runtime wiki location (v0.22.0) ────────────────────────────────────────\n // The cordis `config:` block stays the DEFAULT; the settings page can switch\n // the running plugin to another folder and persists that choice in a pointer\n // file OUTSIDE every wiki (host/wiki-location.ts explains why). Everything\n // below is read through getters, so a switch is visible to tools, routes and\n // the git face immediately.\n const defaultLocation: WikiLocation = { root: config.wikiRoot, name: config.wiki }\n /** Source of the CURRENT location, for the settings page. */\n const locationSource = async (): Promise<WikiLocationSource> => {\n const state = await readLocationState(locationStateFile)\n if (state.active !== undefined && locationPath(state.active) === wikiPath) return 'state'\n return typeof rawConfig.wikiRoot === 'string' && rawConfig.wikiRoot.trim().length > 0 ? 'config' : 'default'\n }\n const locationInfo = async (): Promise<WikiLocationInfo> => {\n const state = await readLocationState(locationStateFile)\n const current = server.currentLocation\n return {\n current: { ...current, path: wikiPath, source: await locationSource() },\n default: { ...defaultLocation, path: locationPath(defaultLocation) },\n stateFile: locationStateFile,\n candidates: await listWikiCandidates(current.root),\n ...(state.error !== undefined ? { error: state.error } : {}),\n }\n }\n /** Single-flight guard: two concurrent switches would fight over the child. */\n let switching = false\n const runSwitch = async (target: { root?: unknown; name?: unknown }, persist: (t: WikiLocation) => Promise<void>): Promise<WikiSwitchResult> => {\n if (switching) return { ok: false, error: '正在切换知识库,请稍候再试', rolledBack: true }\n switching = true\n try {\n return await switchWiki({\n currentLocation: () => server.currentLocation,\n currentPath: () => wikiPath,\n stopServer: () => server.stop(),\n applyLocation: (nextLocation) => {\n server.setLocation({ root: nextLocation.root, name: nextLocation.name })\n wikiPath = locationPath(nextLocation)\n // The cached REST client holds a 2s list cache belonging to the OLD\n // wiki; drop it so the next request reads the new one.\n clientCache = undefined\n clientPort = undefined\n },\n startServer: async () => { await server.start() },\n teardownExtras: () => teardownCommitter(),\n setupExtras: () => { setupCommitter() },\n reloadConfig: async () => {\n await configStore.load(client())\n // The new wiki carries its own config tiddler → its own prompt.*.\n applyPrompt()\n },\n bootstrap: async () => {\n await bootstrapWiki()\n await bootstrapGit()\n },\n savePointer: persist,\n log: (message) => console.warn('[dsh-tiddlywiki]', message),\n }, target)\n } finally {\n switching = false\n }\n }\n /** Switch to another folder and remember the choice. */\n const switchWikiLocation = (target: { root?: unknown; name?: unknown }): Promise<WikiSwitchResult> =>\n runSwitch(target, async (t) => { await writeLocationState(t, locationStateFile) })\n /**\n * 「恢复为配置默认」: drop the pointer and go back to the cordis default. The\n * pointer must be cleared EVEN when the current folder already IS the default\n * (otherwise the stale pointer would win again after the next restart).\n */\n const resetWikiLocation = async (): Promise<WikiSwitchResult> => {\n if (locationPath(defaultLocation) === wikiPath) {\n await clearLocationState(locationStateFile)\n return { ok: true, location: defaultLocation, path: wikiPath }\n }\n return runSwitch(defaultLocation, async () => { await clearLocationState(locationStateFile) })\n }\n\n // Routes + settings-panel admin surface (lazy webServer).\n ctx.inject(['webServer'], (webCtx: HostCtx) => {\n const ws = (webCtx as unknown as { webServer: WebServerFace }).webServer\n // sessionController is a core host service; read it LAZILY per request (via\n // the plugin root ctx) because it may be registered after webServer, and the\n // agent routes must work whenever a request actually arrives.\n const getSessionController = (): SessionControllerFace | undefined =>\n ctx.get('sessionController') as SessionControllerFace | undefined\n const getWorkspaceRegistry = (): WorkspaceRegistryFace | undefined =>\n ctx.get('workspaceRegistry') as WorkspaceRegistryFace | undefined\n // agentPresets (工作模式 roster) + sessionPersistence (per-session preset\n // badges) are also core host services — resolve them lazily like the above.\n const getAgentPresets = (): AgentPresetsFace | undefined =>\n ctx.get('agentPresets') as AgentPresetsFace | undefined\n const getSessionPersistence = (): SessionPersistenceFace | undefined =>\n ctx.get('sessionPersistence') as SessionPersistenceFace | undefined\n // permissionPresets (权限 preset roster for the picker + applying the\n // chosen permission to new sessions) and the `sessions` in-memory store\n // (the created live session handed to permissionPresets.set) are core host\n // services — resolve them lazily like the ones above.\n const getPermissionPresets = (): PermissionPresetsFace | undefined =>\n ctx.get('permissionPresets') as PermissionPresetsFace | undefined\n const getSessions = (): SessionsFace | undefined =>\n ctx.get('sessions') as SessionsFace | undefined\n // sessionQuery (会话日志查询,供「知识库」Tab 判定「本会话相关笔记」) 也是核心\n // host 服务 —— 可选注入(本部署存在),懒解析。\n const getSessionQuery = (): SessionQueryFace | undefined =>\n ctx.get('sessionQuery') as SessionQueryFace | undefined\n const disposeRoutes = registerRoutes({ webServer: ws }, {\n server,\n getClient: client,\n git,\n autoCommit: () => committer?.touch(),\n noteDefaults: () => ({ tag: effectiveNoteTag() }),\n uiDefaults: () => effectiveUi(),\n getWikiPath: () => wikiPath,\n getSessionController,\n getWorkspaceRegistry,\n getAgentPresets,\n getSessionPersistence,\n getPermissionPresets,\n getSessions,\n getSessionQuery,\n sendToAgentEnabled: () => eff().ui?.sendToAgent?.enabled !== false,\n sendToAgentToken: () => {\n const token = eff().ui?.sendToAgent?.token\n return typeof token === 'string' ? token : ''\n },\n })\n const adminDeps: AdminDeps = {\n server,\n getClient: client,\n getWikiPath: () => wikiPath,\n twRoot: resolveTwRoot,\n config: configStore,\n // A settings-page save may change prompt.*: re-register the section (no\n // dsh web restart) and expose the built text for the preview panel.\n onConfigChanged: () => applyPrompt(),\n getPrompt: () => ({\n enabled: eff().prompt?.enabled !== false,\n mode: normalizePromptMode(eff().prompt?.mode),\n text: promptText(),\n }),\n // Runtime wiki location (v0.22.0): read the current folder + how it was\n // decided, switch to another one, or drop back to the cordis default.\n wiki: {\n info: locationInfo,\n switch: switchWikiLocation,\n reset: resetWikiLocation,\n },\n seeds: {\n // Tool summaries feed the GENERATED seed content (the doc note's tool\n // list, v0.22.0) — pass them everywhere the registry can be re-run.\n checkAll: async (c) => checkAllSeeds({ client: c, tools: tiddlywikiToolSummary() }),\n run: async (c, id, force) => runSeedById({ client: c, tools: tiddlywikiToolSummary() }, id, force),\n remove: async (c, id) => removeSeedById({ client: c }, id),\n },\n }\n const disposeAdmin = registerAdminRoutes({ webServer: ws }, adminDeps)\n return () => {\n disposeRoutes()\n disposeAdmin()\n }\n })\n\n // Teardown: everything reversible (R6 — hot reload must not leak).\n // The disposer RETURNS its promise: cordis fiber.dispose() awaits effect\n // disposers, and dsh web's shutdown controller awaits fiber.dispose() with a\n // 5s grace — so 结束/退出/重启 dsh web 时 TW 后端子进程会被真正停掉,而不是\n // fire-and-forget 里与进程退出竞速(竞速会在 Windows 上遗留孤儿 TW 进程)。\n ctx.effect(() => () => {\n return (async () => {\n // Signal the startup task first (v0.19.1): it checks `disposed` after every\n // await and stops the child it may have spawned, instead of racing us and\n // leaving an orphan TW process / a bound clip-bridge port.\n disposed = true\n try {\n await Promise.race([\n startupTask.catch(() => undefined),\n new Promise<void>((r) => { setTimeout(r, 3_000).unref?.() }),\n ])\n } catch { /* startup issues are already logged */ }\n // Flush a pending auto-commit BEFORE teardown, so a write made within the\n // debounce window is not left uncommitted when dsh web stops (best-effort).\n try { await committer?.flush() } catch { /* best-effort */ }\n disposeAll()\n await server.stop()\n })()\n }, 'dsh-tiddlywiki: host teardown')\n}\n"],"mappings":"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"}