dsh-tiddlywiki 0.16.21 → 0.16.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +306 -271
- package/docs/seed-initialization.md +47 -22
- package/lib/index.js +392 -16
- package/lib/index.js.map +1 -1
- package/package.json +86 -86
- package/src/host/seed-home.ts +96 -92
- package/src/host/seed-notes.ts +8 -1
- package/src/host/seed-starter-docs.ts +251 -0
- package/src/host/seed-ui-styles.ts +74 -0
- package/src/host/seeds.ts +71 -25
- package/src/index.ts +2 -0
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["snippetOf"],"sources":["../src/host/git.ts","../src/host/wiki.ts","../src/host/session-summary.ts","../src/host/http.ts","../src/host/routes.ts","../src/host/config.ts","../src/host/admin.ts","../src/host/seed-notes.ts","../src/host/seed-send-to-agent.ts","../src/host/seed-home.ts","../src/host/seed-all-articles.ts","../src/host/seed-menubar-theme.ts","../src/host/seed-render.ts","../src/host/seeds.ts","../src/host/tw-api.ts","../src/sdk.ts","../src/host/tools.ts","../src/index.ts"],"sourcesContent":["/**\r\n * Git face (design doc §7, D11) — the ONLY place dsh-tiddlywiki shells out to\r\n * git. The wiki folder itself is the repository; the folder is pure text\r\n * (FileSystemAdaptor writes one file per tiddler), so git is a natural sync /\r\n * backup channel.\r\n *\r\n * Sync model is the single-thread alternating one:\r\n * 1. start of work: `git pull --rebase --autostash`\r\n * 2. end of work: `git add -A && git commit && git push`\r\n * 3. auto-commit: debounced 60s commit after wiki writes (AutoCommitter)\r\n *\r\n * Conflict policy (user-confirmed, no complex handling): a rebase conflict\r\n * (only reachable by \"forgot to pull before writing\") → `git rebase --abort`\r\n * + report the unmerged files. Never auto-merge data.\r\n *\r\n * @module dsh-tiddlywiki/host/git\r\n */\r\nimport { execFile } from 'node:child_process'\r\nimport { promisify } from 'node:util'\r\n\r\nconst execFileP = promisify(execFile)\r\n\r\n/** Timeout for quick read-only queries. */\r\nconst QUICK_TIMEOUT_MS = 5_000\r\n\r\n/** Timeout for structural/network operations. */\r\nconst HEAVY_TIMEOUT_MS = 60_000\r\n\r\nexport interface ExecResult { ok: boolean; stdout: string; stderr: string }\r\nexport type ExecFn = (args: string[], options: { cwd?: string; timeout?: number }) => Promise<ExecResult>\r\n\r\n/** Default exec layer: run `git <args>` under a cwd with a timeout. */\r\nconst defaultExec: ExecFn = async (args, options) => {\r\n try {\r\n const { stdout, stderr } = await execFileP('git', args, {\r\n cwd: options.cwd,\r\n timeout: options.timeout ?? QUICK_TIMEOUT_MS,\r\n windowsHide: true,\r\n encoding: 'utf8',\r\n maxBuffer: 32 * 1024 * 1024,\r\n })\r\n return { ok: true, stdout, stderr }\r\n } catch (err) {\r\n const e = err as { stdout?: string; stderr?: string; message?: string }\r\n return { ok: false, stdout: e.stdout ?? '', stderr: e.stderr ?? String(e.message ?? err) }\r\n }\r\n}\r\n\r\nexport interface GitStatusView {\r\n exists: boolean\r\n branch: string\r\n dirty: boolean\r\n dirtyFiles: string[]\r\n remote: string\r\n lastCommit?: string\r\n ahead?: number\r\n behind?: number\r\n}\r\n\r\nexport interface GitActionResult { ok: boolean; message: string; conflictFiles?: string[] }\r\n\r\nfunction parseCount(line: string, re: RegExp): number | undefined {\r\n const m = line.match(re)\r\n return m === null ? undefined : Number(m[1])\r\n}\r\n\r\nexport class GitFace {\r\n constructor(private readonly exec: ExecFn = defaultExec) {}\r\n\r\n async isRepo(dir: string): Promise<boolean> {\r\n const r = await this.exec(['rev-parse', '--is-inside-work-tree'], { cwd: dir, timeout: 2_000 })\r\n return r.ok && r.stdout.trim() === 'true'\r\n }\r\n\r\n async init(dir: string, branch = 'main'): Promise<boolean> {\r\n const r = await this.exec(['init', '-b', branch], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\r\n return r.ok\r\n }\r\n\r\n /** Initial commit for a fresh repo (tolerates an empty index). */\r\n async initialCommit(dir: string): Promise<boolean> {\r\n await this.exec(['add', '-A'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\r\n const r = await this.exec([...identity(), 'commit', '-m', 'chore(dsh-tiddlywiki): initial commit'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\r\n return r.ok || /nothing to commit/.test(r.stderr + r.stdout)\r\n }\r\n\r\n /**\r\n * Stage everything and commit; a local identity is always provided so the\r\n * plugin never depends on the machine's global git config. Returns whether\r\n * a commit actually happened.\r\n */\r\n async commit(dir: string, message: string): Promise<{ committed: boolean; message: string }> {\r\n await this.exec(['add', '-A'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\r\n const staged = await this.exec(['diff', '--cached', '--quiet'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\r\n // `diff --cached --quiet` exits 0 when nothing is staged → nothing to commit.\r\n if (staged.ok) return { committed: false, message: 'nothing to commit' }\r\n const r = await this.exec([...identity(), 'commit', '-m', message], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\r\n return r.ok\r\n ? { committed: true, message }\r\n : { committed: false, message: `commit failed: ${(r.stderr.trim() || r.stdout.trim()).slice(0, 500)}` }\r\n }\r\n\r\n async status(dir: string): Promise<GitStatusView> {\r\n const empty: GitStatusView = { exists: false, branch: '', dirty: false, dirtyFiles: [], remote: '' }\r\n const r = await this.exec(['status', '--porcelain', '-b'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\r\n if (!r.ok) return empty\r\n const lines = r.stdout.split('\\n').filter((l) => l.length > 0)\r\n const branchLine = lines.find((l) => l.startsWith('## '))\r\n const branch = branchLine === undefined ? '' : branchLine.slice(3).split('...')[0] ?? ''\r\n const ahead = branchLine === undefined ? undefined : parseCount(branchLine, /ahead (\\d+)/)\r\n const behind = branchLine === undefined ? undefined : parseCount(branchLine, /behind (\\d+)/)\r\n const dirty = lines.some((l) => !l.startsWith('## '))\r\n const dirtyFiles = lines.filter((l) => !l.startsWith('## ')).map((l) => l.slice(3).trim()).filter(Boolean)\r\n const remoteR = await this.exec(['remote', '-v'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\r\n const remote = remoteR.ok ? remoteR.stdout.split('\\n').map((l) => l.trim()).find(Boolean) ?? '' : ''\r\n const lastR = await this.exec(['log', '-1', '--format=%h %s'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\r\n const lastCommit = lastR.ok && lastR.stdout.trim().length > 0 ? lastR.stdout.trim() : undefined\r\n return { exists: true, branch, dirty, dirtyFiles, remote, ...(lastCommit !== undefined ? { lastCommit } : {}), ...(ahead !== undefined ? { ahead } : {}), ...(behind !== undefined ? { behind } : {}) }\r\n }\r\n\r\n /** `git pull --rebase --autostash`; on conflict: abort + report files.\r\n * On success, `changed: true` means HEAD actually moved (files came in /\r\n * commits were replayed) — callers use it to decide whether a running TW\r\n * child needs a restart to drop its stale in-memory snapshot. */\r\n async pull(dir: string): Promise<GitActionResult & { changed?: boolean }> {\r\n const before = await this.exec(['rev-parse', 'HEAD'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\r\n const beforeHead = before.ok ? before.stdout.trim() : ''\r\n const r = await this.exec(['pull', '--rebase', '--autostash'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\r\n if (r.ok) {\r\n const after = await this.exec(['rev-parse', 'HEAD'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\r\n const afterHead = after.ok ? after.stdout.trim() : ''\r\n const changed = beforeHead.length > 0 && beforeHead !== afterHead\r\n return { ok: true, message: r.stdout.trim() || 'pull ok', ...(changed ? { changed: true } : {}) }\r\n }\r\n const conflictFiles = await this.unmergedFiles(dir)\r\n await this.exec(['rebase', '--abort'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\r\n const reason = (r.stderr.trim() || r.stdout.trim()).slice(0, 500)\r\n return { ok: false, message: conflictFiles.length > 0 ? `conflict in ${conflictFiles.join(', ')} (rebase aborted): ${reason}` : `pull failed: ${reason}`, ...(conflictFiles.length > 0 ? { conflictFiles } : {}) }\r\n }\r\n\r\n async push(dir: string): Promise<GitActionResult> {\r\n const r = await this.exec(['push'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\r\n return r.ok\r\n ? { ok: true, message: r.stdout.trim() || 'push ok' }\r\n : { ok: false, message: (r.stderr.trim() || r.stdout.trim()).slice(0, 500) }\r\n }\r\n\r\n /** First push with upstream tracking (called once after a remote is set). */\r\n async firstPush(dir: string): Promise<GitActionResult> {\r\n const branch = (await this.status(dir)).branch || 'main'\r\n const r = await this.exec(['push', '-u', 'origin', branch], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\r\n return r.ok\r\n ? { ok: true, message: `pushed ${branch} to origin` }\r\n : { ok: false, message: (r.stderr.trim() || r.stdout.trim()).slice(0, 500) }\r\n }\r\n\r\n /** `git fetch` (no remote configured → failure reported by the caller). */\r\n async fetch(dir: string): Promise<GitActionResult> {\r\n const r = await this.exec(['fetch'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\r\n return r.ok\r\n ? { ok: true, message: r.stdout.trim() || 'fetch ok' }\r\n : { ok: false, message: (r.stderr.trim() || r.stdout.trim()).slice(0, 500) }\r\n }\r\n\r\n /**\r\n * Restore the given files from the freshly fetched remote HEAD (FETCH_HEAD)\r\n * into the working tree + index — the \"keep remote version\" half of\r\n * tiddler-granular conflict resolution. Callers must `git fetch` first.\r\n */\r\n async checkoutFetchHead(dir: string, files: string[]): Promise<GitActionResult> {\r\n if (files.length === 0) return { ok: true, message: 'no files given' }\r\n const r = await this.exec(['checkout', 'FETCH_HEAD', '--', ...files], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\r\n return r.ok\r\n ? { ok: true, message: `已从远端检出 ${files.length} 个文件` }\r\n : { ok: false, message: (r.stderr.trim() || r.stdout.trim()).slice(0, 500) }\r\n }\r\n\r\n /** Ensure `origin` points at `url` (add or set-url). */\r\n async ensureRemote(dir: string, url: string): Promise<GitActionResult> {\r\n const cur = await this.exec(['remote', 'get-url', 'origin'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\r\n if (cur.ok) {\r\n if (cur.stdout.trim() === url) return { ok: true, message: 'remote origin already set' }\r\n const set = await this.exec(['remote', 'set-url', 'origin', url], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\r\n return set.ok ? { ok: true, message: `remote origin → ${url}` } : { ok: false, message: set.stderr.trim() || 'remote set-url failed' }\r\n }\r\n const add = await this.exec(['remote', 'add', 'origin', url], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\r\n return add.ok ? { ok: true, message: `remote origin → ${url}` } : { ok: false, message: add.stderr.trim() || 'remote add failed' }\r\n }\r\n\r\n private async unmergedFiles(dir: string): Promise<string[]> {\r\n const r = await this.exec(['diff', '--name-only', '--diff-filter=U'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\r\n return r.ok ? r.stdout.split('\\n').map((l) => l.trim()).filter(Boolean) : []\r\n }\r\n}\r\n\r\n/** Always-on local identity so commits never depend on global git config. */\r\nfunction identity(): string[] {\r\n return ['-c', 'user.name=dsh-tiddlywiki', '-c', 'user.email=dsh-tiddlywiki@local']\r\n}\r\n\r\nexport interface AutoCommitterOptions {\r\n git: GitFace\r\n dir: string\r\n enabled: boolean\r\n debounceMs: number\r\n message: () => string\r\n onError?: (err: unknown) => void\r\n onCommit?: (info: { committed: boolean; message: string }) => void\r\n}\r\n\r\n/**\r\n * Debounced auto-committer: every wiki write calls `touch()`; the commit\r\n * fires once writes settle for `debounceMs`. Disable with git.autoCommit.\r\n */\r\nexport class AutoCommitter {\r\n private timer: NodeJS.Timeout | undefined\r\n private disposed = false\r\n\r\n constructor(private readonly options: AutoCommitterOptions) {}\r\n\r\n touch(): void {\r\n if (!this.options.enabled || this.disposed) return\r\n if (this.timer !== undefined) clearTimeout(this.timer)\r\n this.timer = setTimeout(() => { void this.flush() }, this.options.debounceMs)\r\n }\r\n\r\n /** Run a commit now (also cancels the pending debounce). */\r\n async flush(): Promise<void> {\r\n if (this.timer !== undefined) {\r\n clearTimeout(this.timer)\r\n this.timer = undefined\r\n }\r\n if (!this.options.enabled || this.disposed) return\r\n try {\r\n const result = await this.options.git.commit(this.options.dir, this.options.message())\r\n this.options.onCommit?.(result)\r\n } catch (err) {\r\n this.options.onError?.(err)\r\n }\r\n }\r\n\r\n dispose(): void {\r\n this.disposed = true\r\n if (this.timer !== undefined) {\r\n clearTimeout(this.timer)\r\n this.timer = undefined\r\n }\r\n }\r\n}\r\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 private readonly 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\n constructor(private readonly options: WikiServerOptions) {\n this.wikiPath = resolve(options.wikiRoot, options.wiki)\n this.logLimit = options.logBufferLimit ?? LOG_BUFFER_LIMIT\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 async start(): Promise<WikiStatusView> {\n this.stopping = false\n this.restartDelay = 1_000\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 this.log(`spawn: ${process.execPath} ${args.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 this.log(`exit code=${code} signal=${signal ?? ''} stopping=${this.stopping}`)\n this.child = undefined\n this.health = 'stopped'\n if (!this.stopping) this.scheduleRestart()\n })\n child.once('error', (err) => {\n this.log(`spawn error: ${err.message}`)\n this.error = err.message\n this.child = undefined\n this.health = 'failed'\n if (!this.stopping) this.scheduleRestart()\n })\n this.lastStartedAt = Date.now()\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 for (;;) {\n if (this.child === undefined) throw new Error('wiki process exited before ready')\n try {\n const res = await fetch(`${this.url}/status`, { signal: AbortSignal.timeout(2_000) })\n if (res.ok) {\n this.health = 'running'\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 /** One-click restart (route /dsh-tiddlywiki/restart, panel retry button). */\n async restart(): Promise<WikiStatusView> {\n await this.stop()\n return this.start()\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 }\n 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","/**\r\n * 会话相关 wiki 汇总(会话顶部「知识库」Tab 的后端)——把一次 DSH 会话(含其后代\r\n * subagent)在本会话中产生 / 读取 / 检索过的 wiki 笔记汇总成一篇 TW wikitext,写入\r\n * TW 的 volatile 命名空间 `$:/temp/dsh/session-summary/<会话ID>`。\r\n *\r\n * 判定来源(设计定稿,探查验证):\r\n * - Host 服务 `sessionQuery`(可选注入):`readSession(sessionId)` 返回完整事件日志\r\n * (events 升序),`traceSession` 返回祖先/后代树。绝不直接解析\r\n * `~/.dsh/sessions` 目录下的 session 文件——那是 zstd 压缩的。\r\n * - `tool/call` 事件精确记录每次 `tiddlywiki_*` 调用的 `name` 与\r\n * `arguments`(JSON 字符串)。\r\n * - `assistant/message` 事件 `message.content` 的 text 块可扫\r\n * `/dsh-tiddlywiki/tw/#...` 引用链接(视为「读取」)。\r\n *\r\n * 归类规则(§三 定稿):\r\n * 产生 📝 tiddlywiki_put / batch_put / rename(arguments 取 title)\r\n * 读取 👀 tiddlywiki_get(title)+ 助手回复里的 wiki 引用链接\r\n * 检索 🔍 search / recent(记关键词,不算单篇)\r\n * 范围 本会话 + 后代 subagent(traceSession 递归,仅子代理触达的笔记标注)\r\n *\r\n * 渲染(§四):写入 `$:/temp/...`——TW 5.4.1 默认 `$:/config/SyncFilter` 显式\r\n * `-[prefix[$:/temp/]]`(core/wiki/config/SyncFilter.tid),服务端 syncer 从不把\r\n * $:/temp 落盘:内存态、不进 git、TW 重启即消失(已实测:PUT 204、tiddlers/ 无文件、\r\n * git clean)。TW 用自己主题/链接/导航原生渲染,不另造 UI。\r\n *\r\n * @module dsh-tiddlywiki/host/session-summary\r\n */\r\nimport type { TiddlyWebClient } from './tw-api.ts'\r\n\r\n/** 会话汇总 tiddler 的 $:/temp 命名空间前缀。 */\r\nexport const SESSION_SUMMARY_PREFIX = '$:/temp/dsh/session-summary/'\r\n\r\n/** 会话内能触达 wiki 的 tiddlywiki_* 工具名(search/recent 只记检索记录,不算单篇)。 */\r\nconst NOTE_TOOL_NAMES = new Set(['tiddlywiki_put', 'tiddlywiki_batch_put', 'tiddlywiki_rename', 'tiddlywiki_get', 'tiddlywiki_search', 'tiddlywiki_recent'])\r\n\r\n/** 每篇笔记的触达摘要(按 title 去重,时间取最近一次)。 */\r\nexport interface NoteEntry {\r\n title: string\r\n action: 'produced' | 'read'\r\n /** 最近一次触达的 epoch ms。 */\r\n time: number\r\n /** 附加说明(如 rename 的旧标题)。 */\r\n detail?: string\r\n /** true = 仅由后代 subagent 触达过(本会话自己没碰过)。 */\r\n subagent: boolean\r\n}\r\n\r\n/** 一次检索类调用(search / recent),不算单篇笔记。 */\r\nexport interface SearchRecord {\r\n kind: 'search' | 'recent'\r\n query: string\r\n tags: string[]\r\n time: number\r\n subagent: boolean\r\n}\r\n\r\n/** 一次会话扫描的收集结果。 */\r\nexport interface Collected {\r\n produced: Map<string, NoteEntry>\r\n read: Map<string, NoteEntry>\r\n searches: SearchRecord[]\r\n}\r\n\r\n/**\r\n * 结构化 `sessionQuery` 服务脸(dsh-api-session-query 的极小子集,只声明本模块用到\r\n * 的方法;运行时实例是真实 Service,绝不是 Inspect 数据)。\r\n */\r\nexport interface SessionQueryFace {\r\n /** Read and replay-validate one complete logical session log (events ascending). */\r\n readSession(sessionId: string): Promise<SessionLogSnapshot>\r\n /** Trace known ancestry and descendants from one corpus observation. */\r\n traceSession(sessionId: string, signal?: AbortSignal): Promise<SessionLineageTrace>\r\n}\r\n\r\nexport interface SessionLogSnapshot {\r\n session: { id: string }\r\n events: SessionEvent[]\r\n}\r\n\r\nexport interface SessionEvent {\r\n type: string\r\n seq: number\r\n time: number\r\n data: Record<string, unknown>\r\n}\r\n\r\nexport interface SessionLineageTrace {\r\n descendants: SessionLineageNode[]\r\n}\r\n\r\nexport interface SessionLineageNode {\r\n session: { header: { id: string } }\r\n descendants: SessionLineageNode[]\r\n}\r\n\r\n/** 递归收集后代树里所有 session id(不含根自身)。 */\r\nfunction collectDescendantIds(nodes: SessionLineageNode[] | undefined, out: string[]): void {\r\n if (!Array.isArray(nodes)) return\r\n for (const node of nodes) {\r\n const id = node?.session?.header?.id\r\n if (typeof id === 'string' && id.length > 0) {\r\n out.push(id)\r\n collectDescendantIds(node?.descendants, out)\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * 汇总条目会直接写进 `[[标题]]` wikitext——标题既要能安全渲染、又不能制造死链。\r\n * 会话日志里常混入模型的示例/残缺写法(`#…`、`#标题`、带反引号的截断链接、\r\n * 控制字符等),这类收进来会被 TW 渲染成「佚失条目」;一律过滤。\r\n */\r\nfunction isPlausibleTitle(title: string): boolean {\r\n if (title.length === 0 || title.length > 300) return false\r\n if (title.trim().length === 0) return false\r\n if (/[\\u0000-\\u001f\\u007f]/.test(title)) return false // 控制字符 / 换行\r\n if (/[\\]|`#]/.test(title)) return false // 会破坏 [[…]] 链接语法\r\n if (title.startsWith('…') || title === '标题') return false // 占位 / 示例写法\r\n return true\r\n}\r\n\r\n/** 记录一篇笔记的触达;同标题合并(时间取最新、detail 取最新、subagent 取 AND)。 */\r\nfunction record(map: Map<string, NoteEntry>, entry: NoteEntry): void {\r\n if (entry.title.startsWith('$:/')) return\r\n if (!isPlausibleTitle(entry.title)) return\r\n const prev = map.get(entry.title)\r\n if (prev !== undefined) {\r\n prev.time = Math.max(prev.time, entry.time)\r\n if (entry.detail !== undefined) prev.detail = entry.detail\r\n prev.subagent = prev.subagent && entry.subagent\r\n return\r\n }\r\n map.set(entry.title, { ...entry })\r\n}\r\n\r\n/** 扫助手回复文本里的 `/dsh-tiddlywiki/tw/#标题` 引用链接 → 读取。 */\r\nfunction scanRefs(text: string, time: number, subagent: boolean, collected: Collected): void {\r\n // 只认到下一个 `)` / 空白 / `]` / `#` 为止——`#` 会截断后续碎片(模型示例常写成\r\n // `#…` 或 `#标题`,这些由 isPlausibleTitle 在 record 时过滤)。\r\n const re = /\\/dsh-tiddlywiki\\/tw\\/#([^#)\\s\\]]+)/g\r\n let m: RegExpExecArray | null\r\n while ((m = re.exec(text)) !== null) {\r\n const raw = m[1] ?? ''\r\n if (raw.length === 0) continue\r\n let title = raw\r\n try {\r\n title = decodeURIComponent(raw)\r\n } catch {\r\n /* keep raw on malformed percent-encoding */\r\n }\r\n if (title.length === 0) continue\r\n record(collected.read, { title, action: 'read', time, subagent })\r\n }\r\n}\r\n\r\n/** 扫描一次会话的事件日志,按 §三 规则收集笔记触达与检索记录。 */\r\nfunction scanSnapshot(snap: SessionLogSnapshot | undefined, subagent: boolean, collected: Collected): void {\r\n const events = snap?.events\r\n if (!Array.isArray(events)) return\r\n for (const ev of events) {\r\n const type = ev?.type\r\n const t = typeof ev?.time === 'number' ? ev.time : Date.now()\r\n if (type === 'tool/call') {\r\n const name = ev.data?.name\r\n if (typeof name !== 'string' || !NOTE_TOOL_NAMES.has(name)) continue\r\n let args: Record<string, unknown> = {}\r\n if (typeof ev.data?.arguments === 'string') {\r\n try {\r\n args = JSON.parse(ev.data.arguments) as Record<string, unknown>\r\n } catch {\r\n args = {}\r\n }\r\n }\r\n switch (name) {\r\n case 'tiddlywiki_put':\r\n if (typeof args.title === 'string' && args.title.length > 0) {\r\n record(collected.produced, { title: args.title, action: 'produced', time: t, subagent })\r\n }\r\n break\r\n case 'tiddlywiki_batch_put':\r\n if (Array.isArray(args.items)) {\r\n for (const item of args.items) {\r\n if (item !== null && typeof item === 'object' && typeof (item as { title?: unknown }).title === 'string') {\r\n const title = (item as { title: string }).title\r\n if (title.length > 0) record(collected.produced, { title, action: 'produced', time: t, subagent })\r\n }\r\n }\r\n }\r\n break\r\n case 'tiddlywiki_rename':\r\n if (typeof args.newTitle === 'string' && args.newTitle.length > 0) {\r\n const old = typeof args.oldTitle === 'string' && args.oldTitle.length > 0 ? args.oldTitle : undefined\r\n record(collected.produced, {\r\n title: args.newTitle,\r\n action: 'produced',\r\n time: t,\r\n detail: old !== undefined ? `重命名自「${old}」` : undefined,\r\n subagent,\r\n })\r\n }\r\n break\r\n case 'tiddlywiki_get':\r\n if (typeof args.title === 'string' && args.title.length > 0) {\r\n record(collected.read, { title: args.title, action: 'read', time: t, subagent })\r\n }\r\n break\r\n case 'tiddlywiki_search': {\r\n const query = typeof args.query === 'string' ? args.query : ''\r\n const tags = Array.isArray(args.tags) ? args.tags.filter((x): x is string => typeof x === 'string') : []\r\n collected.searches.push({ kind: 'search', query, tags, time: t, subagent })\r\n break\r\n }\r\n case 'tiddlywiki_recent':\r\n collected.searches.push({ kind: 'recent', query: '', tags: [], time: t, subagent })\r\n break\r\n }\r\n } else if (type === 'assistant/message') {\r\n const message = ev.data?.message\r\n const content = message !== null && typeof message === 'object' ? (message as { content?: unknown }).content : undefined\r\n if (Array.isArray(content)) {\r\n for (const block of content) {\r\n if (block === null || typeof block !== 'object') continue\r\n const b = block as { type?: unknown; text?: unknown }\r\n if (b.type === 'text' && typeof b.text === 'string' && b.text.length > 0) {\r\n scanRefs(b.text, t, subagent, collected)\r\n }\r\n }\r\n }\r\n }\r\n }\r\n}\r\n\r\n/** 当前状态(存在?标签?修改时间?摘要)。 */\r\ninterface TitleState {\r\n exists: boolean\r\n tags: string[]\r\n modified?: string\r\n snippet?: string\r\n}\r\n\r\n/** 并发(8 路)查询每篇笔记的当前状态(404 → 不存在/已删除)。 */\r\nasync function enrichTitles(client: TiddlyWebClient, titles: string[]): Promise<Map<string, TitleState>> {\r\n const out = new Map<string, TitleState>()\r\n const enquire = async (title: string): Promise<[string, TitleState]> => {\r\n try {\r\n const t = await client.get(title)\r\n if (t === undefined) return [title, { exists: false, tags: [] }]\r\n const flat = typeof t.text === 'string' ? t.text.replace(/\\s+/g, ' ').trim() : ''\r\n return [title, {\r\n exists: true,\r\n tags: t.tags ?? [],\r\n modified: typeof t.modified === 'string' ? t.modified : undefined,\r\n snippet: flat.length > 48 ? `${flat.slice(0, 48)}…` : flat,\r\n }]\r\n } catch {\r\n return [title, { exists: false, tags: [] }]\r\n }\r\n }\r\n // Bounded batches: parallel within a batch, insertion order preserved across\r\n // batches (Map order = the caller's title order, same as the old sequential).\r\n for (let i = 0; i < titles.length; i += 8) {\r\n const batch = titles.slice(i, i + 8)\r\n for (const [title, state] of await Promise.all(batch.map(enquire))) out.set(title, state)\r\n }\r\n return out\r\n}\r\n\r\n/** epoch ms → 本地 `YYYY-MM-DD HH:mm`。 */\r\nfunction fmtTime(ms: number): string {\r\n const d = new Date(ms)\r\n if (Number.isNaN(d.getTime())) return ''\r\n const p = (n: number): string => String(n).padStart(2, '0')\r\n return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`\r\n}\r\n\r\n/** TW modified ISO → 本地时间(解析失败则原样返回)。 */\r\nfunction fmtModified(iso: string | undefined): string {\r\n if (iso === undefined || iso.length === 0) return ''\r\n const ms = Date.parse(iso)\r\n return Number.isNaN(ms) ? iso : fmtTime(ms)\r\n}\r\n\r\n/** 组装分组 wikitext:产生 / 读取(未产生过的)/ 检索记录。 */\r\nfunction buildWikitext(\r\n sessionId: string,\r\n collected: Collected,\r\n producedTitles: string[],\r\n readTitles: string[],\r\n stateByTitle: Map<string, TitleState>,\r\n): string {\r\n const lines: string[] = []\r\n lines.push('! 会话相关 wiki 汇总')\r\n lines.push('')\r\n lines.push(\r\n `本页列出会话 \\`${sessionId}\\`(含其后代子代理)在本会话中产生 / 读取 / 检索过的知识库笔记。`,\r\n )\r\n lines.push('')\r\n const producedCount = producedTitles.length\r\n const readCount = readTitles.length\r\n const searchCount = collected.searches.length\r\n if (producedCount + readCount + searchCount === 0) {\r\n lines.push('> 本会话暂时没有产生、读取或检索过任何知识库笔记。')\r\n lines.push('>')\r\n lines.push('> 本页为会话自动生成的临时汇总(`$:/temp`,不落盘、不进 git,重启 TW 即消失);需要时点 Tab 顶栏的「🔄 刷新」重新生成。')\r\n return lines.join('\\n')\r\n }\r\n lines.push(\r\n '| 产生 📝 | 读取 👀 | 检索 🔍 | 涉及会话 |',\r\n '| --- | --- | --- | --- |',\r\n `| ${producedCount} | ${readCount} | ${searchCount} | ${collected.produced.size + readTitles.length > 0 ? '本会话 + 子代理' : '本会话'} |`,\r\n )\r\n lines.push('')\r\n\r\n const entryLine = (title: string, state: TitleState | undefined, entry: NoteEntry): string => {\r\n const bits: string[] = [`[[${title}]]`]\r\n if (state === undefined || !state.exists) {\r\n bits.push('⚠️ 已删除/不存在')\r\n } else {\r\n if (state.tags.length > 0) bits.push(`标签 ${state.tags.slice(0, 6).join('、')}${state.tags.length > 6 ? '…' : ''}`)\r\n const mod = fmtModified(state.modified)\r\n if (mod.length > 0) bits.push(`修改 ${mod}`)\r\n }\r\n const t = fmtTime(entry.time)\r\n if (t.length > 0) bits.push(`会话内 ${t}`)\r\n if (entry.detail !== undefined) bits.push(entry.detail)\r\n if (entry.subagent) bits.push('(子代理)')\r\n return `* ${bits.join(' · ')}`\r\n }\r\n\r\n if (producedCount > 0) {\r\n lines.push('!!! 📝 产生')\r\n for (const title of producedTitles) {\r\n const entry = collected.produced.get(title)\r\n if (entry !== undefined) lines.push(entryLine(title, stateByTitle.get(title), entry))\r\n }\r\n lines.push('')\r\n }\r\n if (readCount > 0) {\r\n lines.push('!!! 👀 读取')\r\n for (const title of readTitles) {\r\n const entry = collected.read.get(title)\r\n if (entry !== undefined) lines.push(entryLine(title, stateByTitle.get(title), entry))\r\n }\r\n lines.push('')\r\n }\r\n if (searchCount > 0) {\r\n lines.push('!!! 🔍 检索记录')\r\n for (const r of collected.searches) {\r\n const bits: string[] = []\r\n if (r.kind === 'search') {\r\n bits.push(`\\`tiddlywiki_search\\` query=「${r.query.length > 0 ? r.query : '(空)'}」`)\r\n if (r.tags.length > 0) bits.push(`tags ${r.tags.join('、')}`)\r\n } else {\r\n bits.push('`tiddlywiki_recent`(最近修改)')\r\n }\r\n const t = fmtTime(r.time)\r\n if (t.length > 0) bits.push(t)\r\n if (r.subagent) bits.push('(子代理)')\r\n lines.push(`* ${bits.join(' · ')}`)\r\n }\r\n lines.push('')\r\n }\r\n lines.push('---')\r\n lines.push('> 本页为会话自动生成的临时汇总(`$:/temp`,不落盘、不进 git,重启 TW 即消失);需要时点 Tab 顶栏的「🔄 刷新」重新生成。')\r\n return lines.join('\\n')\r\n}\r\n\r\nexport interface SessionSummaryResult {\r\n title: string\r\n counts: { produced: number; read: number; searches: number; sessions: number }\r\n empty: boolean\r\n generatedAt: string\r\n}\r\n\r\n/**\r\n * 生成并写入一篇会话汇总 tiddler($:/temp,volatile)。\r\n * @param client 可用 TW 客户端(调用方已保证 wiki 服务在线)。\r\n * @param sq sessionQuery 服务(调用方已保证可用)。\r\n * @param sessionId 目标会话 id(本会话 + 后代 subagent)。\r\n */\r\nexport async function writeSessionSummary(client: TiddlyWebClient, sq: SessionQueryFace, sessionId: string): Promise<SessionSummaryResult> {\r\n // 1) 会话范围:自身 + traceSession 后代树(递归)。trace 失败(会话已归档/不存在)\r\n // 就只汇总自身,绝不让单次失败拖垮整页。\r\n const ids: string[] = [sessionId]\r\n try {\r\n const trace = await sq.traceSession(sessionId, AbortSignal.timeout(10_000))\r\n collectDescendantIds(trace?.descendants, ids)\r\n } catch {\r\n /* trace unavailable → self only */\r\n }\r\n\r\n // 2) 逐个会话读完整事件日志并收集(任一失败跳过,其余照常)。\r\n const collected: Collected = { produced: new Map(), read: new Map(), searches: [] }\r\n for (const id of ids) {\r\n try {\r\n const snap = await sq.readSession(id)\r\n scanSnapshot(snap, id !== sessionId, collected)\r\n } catch {\r\n /* one bad session must not fail the whole summary */\r\n }\r\n }\r\n\r\n // 3) 产生优先:同时被产生+读取的笔记只列在「产生」,读取区只放未产生过的。\r\n const producedTitles = [...collected.produced.keys()].sort((a, b) => (collected.produced.get(b)?.time ?? 0) - (collected.produced.get(a)?.time ?? 0))\r\n const readTitles = [...collected.read.keys()]\r\n .filter((t) => !collected.produced.has(t))\r\n .sort((a, b) => (collected.read.get(b)?.time ?? 0) - (collected.read.get(a)?.time ?? 0))\r\n collected.searches.sort((a, b) => b.time - a.time)\r\n\r\n // 4) 查询当前状态(存在?标签?时间?)→ 组装 wikitext → PUT volatile tiddler。\r\n const stateByTitle = await enrichTitles(client, [...new Set([...producedTitles, ...readTitles])])\r\n const text = buildWikitext(sessionId, collected, producedTitles, readTitles, stateByTitle)\r\n const title = `${SESSION_SUMMARY_PREFIX}${sessionId}`\r\n await client.put({ title, text, type: 'text/vnd.tiddlywiki', tags: [] })\r\n\r\n return {\r\n title,\r\n counts: { produced: producedTitles.length, read: readTitles.length, searches: collected.searches.length, sessions: ids.length },\r\n empty: producedTitles.length + readTitles.length + collected.searches.length === 0,\r\n generatedAt: new Date().toISOString(),\r\n }\r\n}\r\n","/**\r\n * Shared node:http helpers for the DSH webserver routes (routes.ts, admin.ts).\r\n *\r\n * One implementation of body reading + JSON response writing so the route\r\n * layers cannot drift (they previously carried two/three copies).\r\n *\r\n * @module dsh-tiddlywiki/host/http\r\n */\r\nimport type { IncomingMessage, ServerResponse } from 'node:http'\r\n\r\n/** Default cap for small JSON bodies (note/restart/config…). */\r\nexport const MAX_JSON_BODY_BYTES = 2 * 1024 * 1024\r\n\r\n/** Cap for the /api passthrough body (tiddler content can be large). */\r\nexport const MAX_PROXY_BODY_BYTES = 16 * 1024 * 1024\r\n\r\n/** Cap for uploaded file bodies (/tw proxy, /upload). */\r\nexport const MAX_UPLOAD_BYTES = 64 * 1024 * 1024\r\n\r\nfunction readBodyStream(req: IncomingMessage, limit: number, encoding: true): Promise<string>\r\nfunction readBodyStream(req: IncomingMessage, limit: number, encoding: false): Promise<Buffer>\r\nfunction readBodyStream(req: IncomingMessage, limit: number, encoding: boolean): Promise<string | Buffer> {\r\n return new Promise((resolveP, rejectP) => {\r\n let size = 0\r\n const chunks: Buffer[] = []\r\n req.on('data', (chunk: Buffer) => {\r\n size += chunk.length\r\n if (size > limit) {\r\n rejectP(new Error('body too large'))\r\n req.destroy()\r\n return\r\n }\r\n chunks.push(chunk)\r\n })\r\n req.on('end', () => {\r\n const buf = Buffer.concat(chunks)\r\n resolveP(encoding ? buf.toString('utf8') : buf)\r\n })\r\n req.on('error', rejectP)\r\n })\r\n}\r\n\r\n/** Read a JSON (utf8) request body up to `limit` bytes (default MAX_JSON_BODY_BYTES). */\r\nexport function readBody(req: IncomingMessage, limit = MAX_JSON_BODY_BYTES): Promise<string> {\r\n return readBodyStream(req, limit, true)\r\n}\r\n\r\n/** Read a raw (binary-safe) request body up to `limit` bytes (default MAX_UPLOAD_BYTES). */\r\nexport function readBodyBuffer(req: IncomingMessage, limit = MAX_UPLOAD_BYTES): Promise<Buffer> {\r\n return readBodyStream(req, limit, false)\r\n}\r\n\r\n/** Write a JSON response (no-store, utf-8). */\r\nexport function json(res: ServerResponse, payload: unknown, status = 200): void {\r\n res.writeHead(status, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' })\r\n res.end(JSON.stringify(payload))\r\n}\r\n","/**\r\n * DSH webserver routes for dsh-tiddlywiki (design doc §10).\r\n *\r\n * | route | method | purpose |\r\n * |---------------------------|--------|------------------------------------------|\r\n * | /dsh-tiddlywiki/status | GET | panel health (service / url / git / tag) |\r\n * | /dsh-tiddlywiki/note | POST | quick-note → independent tiddler |\r\n * | /dsh-tiddlywiki/restart | POST | one-click retry/restart of the TW child |\r\n * | /dsh-tiddlywiki/search | GET | keyword search (reply-stream tool card) |\r\n * | /dsh-tiddlywiki/api/* | any | passthrough to the TW service (JSON) |\r\n * | /dsh-tiddlywiki/tw/* | any | SAME-ORIGIN TW proxy (index + files + TiddlyWeb API) |\r\n *\r\n * Matching is exact-over-prefix, so the exact routes win and the `/api` /\r\n * `/tw` prefixes catch the rest. Client calls are same-origin (the DSH web\r\n * server), so no CORS is involved. The `/tw` proxy is the remote-access\r\n * bridge: it serves the ENTIRE TW frontend to the browser through the DSH\r\n * origin (see TW_PROXY_PATH in wiki.ts), so the embedded editor works no\r\n * matter which host/domain the user reaches DSH on.\r\n *\r\n * @module dsh-tiddlywiki/host/routes\r\n */\r\nimport type { IncomingHttpHeaders, IncomingMessage, ServerResponse } from 'node:http'\r\nimport { mkdir, writeFile, access } from 'node:fs/promises'\r\nimport { basename, extname, join } from 'node:path'\r\nimport type { TiddlyWebClient } from './tw-api.ts'\r\nimport type { WikiServer } from './wiki.ts'\r\nimport type { GitFace } from './git.ts'\r\nimport { PATH_PREFIX, TW_PROXY_PREFIX, TW_PROXY_PATH } from './wiki.ts'\r\nimport { writeSessionSummary, type SessionQueryFace } from './session-summary.ts'\r\nimport { readBody, readBodyBuffer, json, MAX_PROXY_BODY_BYTES, MAX_UPLOAD_BYTES } from './http.ts'\r\n\r\nexport { writeSessionSummary, SESSION_SUMMARY_PREFIX } from './session-summary.ts'\r\nexport type { SessionQueryFace, SessionSummaryResult } from './session-summary.ts'\r\n\r\nexport const ROUTE_PREFIX = PATH_PREFIX\r\n\r\n/** Tiddler type for quick-notes: Markdown, so the uploaded images/links and\r\n * any Markdown in the note actually render in TW (a type-less tiddler is\r\n * treated as plain wiki text and shows raw `![..]`/`[..]` instead). */\r\nconst NOTE_TYPE = 'text/markdown'\r\n\r\n/** Structural webserver face (a subset of dsh-host-webserver). */\r\nexport interface WebServerFace {\r\n register(route: { kind: 'exact' | 'prefix'; path: string; handler: (req: IncomingMessage, res: ServerResponse) => void }): () => void\r\n}\r\n\r\n/**\r\n * Structural face over the DSH `sessionController` service (a subset of\r\n * dsh-api-session-controller). Only the methods the agent-send routes need are\r\n * declared; the runtime instance is a real Service, never inspected data.\r\n */\r\nexport interface SessionControllerFace {\r\n prompt(\r\n request: { requestId: string; sessionId: string; mode: 'queue' | 'steer'; content: Array<{ type: 'text'; text: string }> },\r\n signal: AbortSignal,\r\n ): Promise<{ accepted: boolean }>\r\n list(\r\n request: { cursor?: string },\r\n signal: AbortSignal,\r\n ): Promise<{\r\n items: Array<{\r\n sessionId: string\r\n updatedAt?: number\r\n running?: boolean\r\n blank?: boolean\r\n parentSessionId?: string\r\n cwd?: string\r\n }>\r\n }>\r\n create(request: { cwd?: string; workspaceId?: string; agentPreset?: string }): Promise<{ sessionId: string }>\r\n}\r\n\r\n/**\r\n * Structural face over the DSH `workspaceRegistry` service (a subset of\r\n * dsh-workspace). Only what the agent-create route needs is declared; the\r\n * runtime instance is a real Service, never inspected data.\r\n */\r\nexport interface WorkspaceRegistryFace {\r\n /** Resolve or create the workspace owning `path` — idempotent by canonical path. */\r\n create(path: string, title?: string): Promise<{ id: string; path: string }>\r\n}\r\n\r\n/**\r\n * Structural face over the DSH `agentPresets` service (a subset of\r\n * dsh-agent-presets). It is the deployment's \"工作模式\" registry — the agent\r\n * presets a session can be composed from (default / cordis / blade / …). Only\r\n * `list` + `resolve` (default id) are needed by the agent-modes route.\r\n */\r\nexport interface AgentPresetsFace {\r\n /** Every preset the configured roots currently supply. */\r\n list(): Promise<Array<{ id: string; name?: string; description?: string; trust?: string; broken?: string }>>\r\n /** Resolve one preset by id (`undefined` = the deployment default). */\r\n resolve(id?: string): Promise<{ id: string; name?: string; description?: string }>\r\n}\r\n\r\n/**\r\n * Structural face over the DSH `sessionPersistence` service. Only the\r\n * lightweight `list` (metadata headers, no log parse) is needed so the\r\n * agent-sessions route can attach each session's recorded `agentPreset`.\r\n */\r\nexport interface SessionPersistenceFace {\r\n /** One header per materialized session (carries `agentPreset` when set). */\r\n list(signal?: AbortSignal): Promise<Array<{ id: string; agentPreset?: string }>>\r\n}\r\n\r\n/**\r\n * Structural face over the DSH `permissionPresets` service (a subset of\r\n * dsh-permission-presets). It owns the deployment's permission presets — each\r\n * bundles a sandbox mode + approval policy (e.g. `workspace-write` = write\r\n * inside the workspace with approval, `danger-full-access` = no prompts). The\r\n * agent-modes route exposes the option list to the TW picker, and agent-create\r\n * applies the chosen preset to the new session's log via `set`.\r\n */\r\nexport interface PermissionPresetsFace {\r\n /** Every switchable preset name, in declaration order. */\r\n readonly names: readonly string[]\r\n /** The preset currently selected as the default for new sessions. */\r\n readonly defaultPreset: string\r\n /** Build the client option ({ value, name, description? }) for one preset. */\r\n optionOf(name: string): { value: string; name: string; description?: string }\r\n /** Record a preset switch on a live session (durable, log-only user intent). */\r\n set(session: unknown, name: string): void\r\n}\r\n\r\n/**\r\n * Structural face over the DSH `sessions` in-memory store (a subset of\r\n * dsh-session). Only `get` is needed: after `sessionController.create`\r\n * resolves, the new session is already materialized here, so agent-create can\r\n * hand it to `permissionPresets.set`.\r\n */\r\nexport interface SessionsFace {\r\n get(id: string): unknown\r\n}\r\n\r\n/** Effective UI flags returned by /status (mirror index.ts). */\r\nexport interface UiDefaultsPublic {\r\n showQuickNote: boolean\r\n /** 聊天输入框上方的「快速笔记」快捷按钮(conversation.input.dock 槽位)。 */\r\n showQuickNoteDock: boolean\r\n /** 点击「快速笔记」后的打开方式:native=直达 TW 原生编辑器;card=Markdown 卡片。 */\r\n quickNoteMode: 'native' | 'card'\r\n /** 左侧侧边栏 TW 入口的显示名称。 */\r\n sidebarLabel: string\r\n showPanelStatus: boolean\r\n showSyncButton: boolean\r\n /** 嵌入式 TW 是否跟随 DSH 深浅主题(false 时客户端停止 palette 同步)。 */\r\n followDshTheme: boolean\r\n /** DSH 暗色时 TW 使用的 palette tiddler 标题。 */\r\n darkPalette: string\r\n /** 会话顶部「知识库」Tab 的显示名称(默认「知识库」)。 */\r\n tabLabel: string\r\n /** 是否在会话顶部显示「知识库」Tab(会话相关 wiki 汇总,默认 true)。 */\r\n showSessionTab: boolean\r\n /** 是否在 DSH 右侧边栏提供 TiddlyWiki 入口/Tab(默认 true)。 */\r\n showRightbarTab: boolean\r\n}\r\n\r\nexport interface RouteDeps {\r\n server: WikiServer\r\n getClient: () => TiddlyWebClient | undefined\r\n git: GitFace\r\n autoCommit: () => void\r\n noteDefaults: () => { tag: string }\r\n uiDefaults: () => UiDefaultsPublic\r\n getWikiPath: () => string\r\n /** Optional DSH sessionController service (agent-send routes only); resolved\r\n * lazily per request because it may register after webServer appears. */\r\n getSessionController: () => SessionControllerFace | undefined\r\n /** Optional DSH workspaceRegistry service (agent-create route); resolves a\r\n * cwd to a real Workspace so new sessions land inside it instead of the\r\n * ungrouped bucket. */\r\n getWorkspaceRegistry: () => WorkspaceRegistryFace | undefined\r\n /** Optional DSH agentPresets service (agent-modes route): the deployment's\r\n * \"工作模式\" roster. Resolved lazily per request like sessionController. */\r\n getAgentPresets: () => AgentPresetsFace | undefined\r\n /** Optional DSH sessionPersistence service (agent-sessions route): attaches\r\n * each session's recorded agentPreset so the picker can badge it. */\r\n getSessionPersistence: () => SessionPersistenceFace | undefined\r\n /** Optional DSH permissionPresets service (agent-modes permissions list +\r\n * agent-create applies the chosen permission preset to the new session). */\r\n getPermissionPresets: () => PermissionPresetsFace | undefined\r\n /** Optional DSH `sessions` in-memory store (agent-create hands the created\r\n * live session to permissionPresets.set). */\r\n getSessions: () => SessionsFace | undefined\r\n /** Optional DSH sessionQuery service (session-summary route): reads a\r\n * session's complete event log + descendant tree to decide which wiki notes\r\n * belong to this conversation. Resolved lazily like the other services. */\r\n getSessionQuery: () => SessionQueryFace | undefined\r\n /** Whether the one-click send-to-agent feature is enabled (config switch). */\r\n sendToAgentEnabled: () => boolean\r\n /** Optional shared token that must match `x-send-to-agent-token` when set. */\r\n sendToAgentToken: () => string\r\n}\r\n\r\n/** Header names forwarded to the upstream TW service by the proxy routes. */\r\nconst FORWARD_HEADER_NAMES = [\r\n 'accept', 'accept-encoding', 'content-type', 'cookie', 'authorization',\r\n 'if-none-match', 'if-modified-since', 'origin', 'referer', 'user-agent',\r\n] as const\r\n\r\n/** Copy a safe, string-valued subset of the request headers upstream. */\r\nfunction forwardHeaders(headers: IncomingHttpHeaders): Record<string, string> {\r\n const out: Record<string, string> = {}\r\n for (const name of FORWARD_HEADER_NAMES) {\r\n // Index through the string index signature so known header names (typed\r\n // `string`) do not hide the `string[]` repeat case via their specific\r\n // property declarations.\r\n const value: string | string[] | undefined = headers[name as string]\r\n if (typeof value === 'string') out[name] = value\r\n else if (Array.isArray(value) && value.length > 0) out[name] = value.join(', ')\r\n }\r\n return out\r\n}\r\n\r\n/**\r\n * Sanitize an uploaded filename into a safe bare name (no path separators,\r\n * no `..`, no control characters). Returns '' when nothing usable remains.\r\n */\r\nfunction sanitizeUploadName(input: unknown): string {\r\n if (typeof input !== 'string') return ''\r\n const name = basename(input.trim().replace(/[\\\\/]+/g, '/'))\r\n .replace(/[\\u0000-\\u001f\\u007f]/g, '')\r\n .replace(/[<>:\"|?*]/g, '_')\r\n .replace(/^\\.+/, '')\r\n .trim()\r\n if (name.length === 0 || name === '.' || name === '..') return ''\r\n if (name.length > 160) return name.slice(0, 160)\r\n return name\r\n}\r\n\r\nfunction pad(n: number): string {\r\n return n < 10 ? `0${n}` : String(n)\r\n}\r\n\r\n/** Flat one-line snippet for the recent-notes picker. */\r\nfunction snippetOf(text: string, max = 120): string {\r\n const flat = text.replace(/\\s+/g, ' ').trim()\r\n return flat.length <= max ? flat : `${flat.slice(0, max)}…`\r\n}\r\n\r\n/** Default note title: `YYYY-MM-DD HH:mm` (design doc D6). */\r\nfunction timestampTitle(date = new Date()): string {\r\n return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`\r\n}\r\n\r\n/**\r\n * Open a tiddler in TW's NATIVE editor: save the tiddler (when text is\r\n * non-empty) as Markdown, reuse or create a DRAFT tiddler carrying\r\n * `draft.of`/`draft.title` (TW's story view renders drafts with the\r\n * EditTemplate — list.js: `isDraft && editTemplate`), and return the draft\r\n * title so the client can navigate the panel iframe to `#<draftTitle>`.\r\n * The draft carries the same `text/markdown` type as the note so saving it in\r\n * TW keeps Markdown (a draft without a matching type would overwrite the\r\n * note's type back to plain wiki text).\r\n */\r\nexport async function openInTwEditor(\r\n client: TiddlyWebClient,\r\n title: string,\r\n text: string,\r\n tags: string[],\r\n): Promise<{ title: string; draftTitle: string }> {\r\n if (text.trim().length > 0) {\r\n await client.put({ title, text, tags, type: NOTE_TYPE })\r\n }\r\n // Draft content: the provided text, else the existing tiddler's content.\r\n let draftText = text\r\n if (draftText.trim().length === 0) {\r\n const existing = await client.get(title)\r\n draftText = existing?.text ?? ''\r\n }\r\n // Reuse an existing draft for this title (mirrors wiki.findDraft).\r\n let draftTitle: string | undefined\r\n try {\r\n const items = await client.list(undefined, true)\r\n for (const item of items) {\r\n if (item['draft.of'] === title && typeof item.title === 'string') {\r\n draftTitle = item.title\r\n break\r\n }\r\n }\r\n } catch {\r\n /* fall back to a fresh draft */\r\n }\r\n if (draftTitle === undefined) draftTitle = `Draft of \"${title}\" ${Date.now()}`\r\n await client.put({ title: draftTitle, text: draftText, 'draft.of': title, 'draft.title': title, type: NOTE_TYPE })\r\n return { title, draftTitle }\r\n}\r\n\r\n/** Resolve note tags from the request body: `tags` array wins, then the\r\n * legacy single `tag` string, then the configured default tag. */\r\nfunction resolveTags(\r\n body: { tag?: unknown; tags?: unknown },\r\n defaultTag: string,\r\n): string[] {\r\n if (Array.isArray(body.tags)) {\r\n const tags = body.tags.filter((t): t is string => typeof t === 'string' && t.trim().length > 0).map((t) => t.trim())\r\n if (tags.length > 0) return tags\r\n }\r\n if (typeof body.tag === 'string' && body.tag.trim().length > 0) {\r\n return body.tag.trim().split(/\\s+/).filter(Boolean)\r\n }\r\n return [defaultTag]\r\n}\r\n\r\nexport function registerRoutes(ctx: { webServer: WebServerFace }, deps: RouteDeps): () => void {\r\n const handleStatus = async (_req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n const view = deps.server.status()\r\n let gitSummary: GitStatusViewPublic | null = null\r\n try {\r\n gitSummary = await deps.git.status(deps.getWikiPath())\r\n } catch {\r\n gitSummary = null\r\n }\r\n json(res, { ok: true, ...view, twProxy: TW_PROXY_PATH, git: gitSummary, note: { tag: deps.noteDefaults().tag }, ui: deps.uiDefaults() })\r\n }\r\n\r\n /**\r\n * POST /dsh-tiddlywiki/session/summary — 生成当前会话的 wiki 汇总页(「知识库」\r\n * Tab 的后端)。body `{ session: <会话ID> }`;后端用 sessionQuery 读本会话(含\r\n * 后代 subagent)的完整事件日志,按「产生/读取/检索」收集 tiddlywiki_* 笔记,\r\n * 查询每篇当前状态,组装 TW wikitext 写入 `$:/temp/dsh/session-summary/<会话ID>`\r\n * (volatile:不落盘、不进 git),返回生成的 tiddler title 供前端 iframe 打开。\r\n */\r\n const handleSessionSummary = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n let body: { session?: unknown } = {}\r\n try {\r\n body = JSON.parse(await readBody(req)) as { session?: unknown }\r\n } catch {\r\n /* malformed body → session check below rejects */\r\n }\r\n const session = typeof body.session === 'string' && body.session.trim().length > 0 ? body.session.trim() : ''\r\n if (session.length === 0) {\r\n json(res, { ok: false, error: 'session is required' }, 400)\r\n return\r\n }\r\n const client = deps.getClient()\r\n if (client === undefined) {\r\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\r\n return\r\n }\r\n const sq = deps.getSessionQuery()\r\n if (sq === undefined) {\r\n json(res, { ok: false, error: 'session query service unavailable' }, 503)\r\n return\r\n }\r\n const result = await writeSessionSummary(client, sq, session)\r\n json(res, { ok: true, ...result, twUrl: TW_PROXY_PATH })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n /**\r\n * GET /dsh-tiddlywiki/agent/sessions — visible ordinary sessions for the TW\r\n * one-click picker (excludes subagent sessions, activity-descending). Each\r\n * item also carries its recorded `agentPreset` (工作模式), when known, so the\r\n * picker can badge existing sessions — read from the lightweight persistence\r\n * header list, never a full log parse.\r\n */\r\n const handleAgentSessions = async (_req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n const sc = deps.getSessionController()\r\n if (sc === undefined) {\r\n json(res, { ok: false, error: 'session service unavailable' }, 503)\r\n return\r\n }\r\n const list = await sc.list({}, AbortSignal.timeout(10_000))\r\n // sessionId → agentPreset, from the durable header list (degrade silently).\r\n const presetById: Record<string, string> = {}\r\n const pers = deps.getSessionPersistence()\r\n if (pers !== undefined) {\r\n try {\r\n const headers = await pers.list(AbortSignal.timeout(5_000))\r\n for (const h of headers) {\r\n if (typeof h.agentPreset === 'string' && h.agentPreset.length > 0) presetById[h.id] = h.agentPreset\r\n }\r\n } catch {\r\n /* header list unavailable → no mode badges, picker still works */\r\n }\r\n }\r\n const items = (list.items ?? [])\r\n .filter((s) => s.parentSessionId === undefined)\r\n .map((s) => ({\r\n sessionId: s.sessionId,\r\n cwd: s.cwd ?? null,\r\n running: !!s.running,\r\n blank: !!s.blank,\r\n updatedAt: s.updatedAt ?? 0,\r\n agentPreset: presetById[s.sessionId] ?? null,\r\n }))\r\n .sort((a, b) => b.updatedAt - a.updatedAt)\r\n json(res, { ok: true, items })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n /**\r\n * Shared gate for the TW-side send-to-agent routes: feature switch\r\n * (`ui.sendToAgent.enabled`) then, when a shared token is configured, the\r\n * `x-send-to-agent-token` header must match. Returns false after writing the\r\n * error response — the caller just does `if (!guard(...)) return`.\r\n */\r\n const guardSendToAgent = (req: IncomingMessage, res: ServerResponse): boolean => {\r\n if (!deps.sendToAgentEnabled()) {\r\n json(res, { ok: false, error: 'send-to-agent is disabled' }, 403)\r\n return false\r\n }\r\n const token = deps.sendToAgentToken().trim()\r\n if (token.length === 0) return true\r\n const got = req.headers['x-send-to-agent-token']\r\n const value = typeof got === 'string' ? got : Array.isArray(got) ? got[0] ?? '' : ''\r\n if (value === token) return true\r\n json(res, { ok: false, error: 'unauthorized' }, 401)\r\n return false\r\n }\r\n\r\n /**\r\n * GET /dsh-tiddlywiki/agent/modes — available \"工作模式\" (Agent presets) for\r\n * the TW picker: id/name/description per preset plus the deployment default.\r\n * Guards mirror the other agent routes (feature switch + optional token).\r\n */\r\n const handleAgentModes = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n if (!guardSendToAgent(req, res)) return\r\n const ap = deps.getAgentPresets()\r\n if (ap === undefined) {\r\n json(res, { ok: false, error: 'agent presets service unavailable' }, 503)\r\n return\r\n }\r\n const presets = await ap.list()\r\n let defaultId: string | undefined\r\n try {\r\n defaultId = (await ap.resolve())?.id\r\n } catch {\r\n defaultId = undefined\r\n }\r\n // The picker also needs the permission-preset roster (a \"权限\" selector\r\n // for newly created sessions). Best-effort: when the permissionPresets\r\n // service is not mounted (older host), `permissions` is null and the\r\n // picker simply hides the selector — modes still work.\r\n let permissions: { defaultId: string | null; items: Array<{ value: string; name: string; description?: string }> } | null = null\r\n const pp = deps.getPermissionPresets()\r\n if (pp !== undefined) {\r\n try {\r\n permissions = {\r\n defaultId: pp.defaultPreset ?? null,\r\n items: pp.names.map((n) => pp.optionOf(n)),\r\n }\r\n } catch {\r\n permissions = null\r\n }\r\n }\r\n json(res, {\r\n ok: true,\r\n defaultId: defaultId ?? null,\r\n items: presets.map((p) => ({\r\n id: p.id,\r\n name: p.name ?? p.id,\r\n description: p.description ?? '',\r\n trust: p.trust ?? 'user',\r\n broken: p.broken ?? null,\r\n isDefault: p.id === defaultId,\r\n })),\r\n permissions,\r\n })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n /**\r\n * POST /dsh-tiddlywiki/agent/send — deliver a note to one agent session as a\r\n * queued user message (sessionController.prompt, the same API the GUI chat\r\n * input uses). Guards: feature switch, optional shared token, body shape.\r\n */\r\n const handleAgentSend = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n if (!guardSendToAgent(req, res)) return\r\n const body = JSON.parse(await readBody(req)) as { sessionId?: unknown; text?: unknown }\r\n const sessionId = typeof body.sessionId === 'string' && body.sessionId.trim().length > 0 ? body.sessionId.trim() : ''\r\n const text = typeof body.text === 'string' && body.text.trim().length > 0 ? body.text.trim() : ''\r\n if (sessionId.length === 0 || text.length === 0) {\r\n json(res, { ok: false, error: 'sessionId and text are required' }, 400)\r\n return\r\n }\r\n const sc = deps.getSessionController()\r\n if (sc === undefined) {\r\n json(res, { ok: false, error: 'session service unavailable' }, 503)\r\n return\r\n }\r\n const requestId = `tw-send-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`\r\n await sc.prompt(\r\n { requestId, sessionId, mode: 'queue', content: [{ type: 'text', text }] },\r\n AbortSignal.timeout(20_000),\r\n )\r\n json(res, { ok: true, requestId, sessionId })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n /**\r\n * POST /dsh-tiddlywiki/agent/create — create (or adopt) one ordinary session\r\n * inside a real DSH workspace resolved from the requested path. The picker\r\n * uses it for \"new workspace / new session\": the directory is materialised so\r\n * a brand-new workspace actually exists on disk, the path is resolved to its\r\n * (idempotent) Workspace, and the session is created with `workspaceId` so it\r\n * lands under that workspace in the sidebar. Creating with bare `cwd` instead\r\n * would leave the session in the ungrouped bucket even when its working\r\n * directory matches an existing workspace path.\r\n *\r\n * Optional `mode` names the \"工作模式\" (an Agent preset id, e.g. from\r\n * /agent/modes); it is forwarded to `sessionController.create(agentPreset)`\r\n * so the new session launches under that preset. Omitted → deployment default.\r\n *\r\n * Optional `permission` names a \"权限\" preset (e.g. from /agent/modes'\r\n * `permissions` roster). After the session is created it is applied to the\r\n * live session's log via `permissionPresets.set` (durable knob events:\r\n * `permission/preset`, `sandbox/mode`, `approval/policy`), overriding the\r\n * deployment default pinned at creation. Omitted → keep the default.\r\n */\r\n const handleAgentCreate = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n if (!guardSendToAgent(req, res)) return\r\n const body = JSON.parse(await readBody(req)) as { cwd?: unknown; mode?: unknown; permission?: unknown }\r\n const cwd = typeof body.cwd === 'string' ? body.cwd.trim() : ''\r\n const mode = typeof body.mode === 'string' && body.mode.trim().length > 0 ? body.mode.trim() : undefined\r\n const permission = typeof body.permission === 'string' && body.permission.trim().length > 0 ? body.permission.trim() : undefined\r\n // Validate the permission preset BEFORE creating the session (fail fast,\r\n // so a bad name never leaves an orphaned session behind).\r\n const pp = deps.getPermissionPresets()\r\n if (permission !== undefined) {\r\n if (pp === undefined) {\r\n json(res, { ok: false, error: 'permission selected but the permission-presets service is unavailable' }, 503)\r\n return\r\n }\r\n if (!pp.names.includes(permission)) {\r\n json(res, { ok: false, error: `unknown permission preset \"${permission}\" (available: ${pp.names.join(', ')})` }, 400)\r\n return\r\n }\r\n }\r\n const sc = deps.getSessionController()\r\n if (sc === undefined) {\r\n json(res, { ok: false, error: 'session service unavailable' }, 503)\r\n return\r\n }\r\n const ws = deps.getWorkspaceRegistry()\r\n if (cwd.length > 0) {\r\n await mkdir(cwd, { recursive: true })\r\n }\r\n let created: { sessionId: string }\r\n let workspaceId: string | undefined\r\n if (cwd.length > 0 && ws !== undefined) {\r\n const workspace = await ws.create(cwd)\r\n workspaceId = workspace.id\r\n created = await sc.create({ workspaceId, agentPreset: mode })\r\n } else {\r\n created = await sc.create({ cwd: cwd.length > 0 ? cwd : undefined, agentPreset: mode })\r\n }\r\n // Apply the chosen permission preset to the just-created live session\r\n // (best-effort — the session is already created either way).\r\n let permissionApplied = false\r\n if (permission !== undefined) {\r\n const sessionsSvc = deps.getSessions()\r\n if (sessionsSvc !== undefined) {\r\n try {\r\n const session = sessionsSvc.get(created.sessionId)\r\n if (session !== undefined) {\r\n pp?.set(session, permission)\r\n permissionApplied = true\r\n }\r\n } catch {\r\n /* permission is an optional convenience; never fail the create */\r\n }\r\n }\r\n }\r\n json(res, {\r\n ok: true,\r\n sessionId: created.sessionId,\r\n cwd: cwd || null,\r\n workspaceId: workspaceId ?? null,\r\n mode: mode ?? null,\r\n permission: permission ?? null,\r\n permissionApplied,\r\n })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n const handleNote = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n const body = JSON.parse(await readBody(req)) as { title?: unknown; tag?: unknown; tags?: unknown; text?: unknown }\r\n const text = typeof body.text === 'string' && body.text.trim().length > 0 ? body.text.trim() : null\r\n if (text === null) {\r\n json(res, { ok: false, error: 'text is required' }, 400)\r\n return\r\n }\r\n const client = deps.getClient()\r\n if (client === undefined) {\r\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\r\n return\r\n }\r\n const title = typeof body.title === 'string' && body.title.trim().length > 0 ? body.title.trim() : timestampTitle()\r\n const tags = resolveTags(body, deps.noteDefaults().tag)\r\n await client.put({ title, text, tags, type: NOTE_TYPE })\r\n deps.autoCommit()\r\n json(res, { ok: true, title, tag: tags.join(' '), tags, text, type: NOTE_TYPE })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n const handleEdit = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n const body = JSON.parse(await readBody(req)) as { title?: unknown; tag?: unknown; tags?: unknown; text?: unknown }\r\n const client = deps.getClient()\r\n if (client === undefined) {\r\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\r\n return\r\n }\r\n const title = typeof body.title === 'string' && body.title.trim().length > 0 ? body.title.trim() : timestampTitle()\r\n const tags = resolveTags(body, deps.noteDefaults().tag)\r\n const text = typeof body.text === 'string' ? body.text : ''\r\n const result = await openInTwEditor(client, title, text, tags)\r\n deps.autoCommit()\r\n json(res, { ok: true, ...result, twUrl: TW_PROXY_PATH })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n /** Distinct non-system tags for the quick-note tag autocomplete. The flat\r\n * `tags` array feeds note-widget's chip autocomplete; the parallel `items`\r\n * (tag → tiddler count) feeds the reply-stream `tiddlywiki_list_tags` tool\r\n * card, so both consumers share one endpoint. */\r\n const handleTags = async (_req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n const client = deps.getClient()\r\n if (client === undefined) {\r\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\r\n return\r\n }\r\n try {\r\n const items = await client.list(undefined, false)\r\n const counts = new Map<string, number>()\r\n for (const item of items) {\r\n for (const tag of item.tags ?? []) {\r\n if (tag.length > 0 && !tag.startsWith('$:/')) counts.set(tag, (counts.get(tag) ?? 0) + 1)\r\n }\r\n }\r\n const tags = [...counts.keys()].sort((a, b) => a.localeCompare(b, 'zh'))\r\n json(res, {\r\n ok: true,\r\n tags,\r\n items: tags.map((tag) => ({ tag, count: counts.get(tag) ?? 0 })),\r\n })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n /** Recent non-system tiddlers for the quick-note \"最近\" picker (newest first). */\r\n const handleRecent = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n const client = deps.getClient()\r\n if (client === undefined) {\r\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\r\n return\r\n }\r\n try {\r\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\r\n const limitRaw = Number(url.searchParams.get('limit') ?? 15)\r\n const limit = Number.isFinite(limitRaw) ? Math.max(1, Math.min(Math.floor(limitRaw), 200)) : 15\r\n const items = await client.recent(limit)\r\n json(res, {\r\n ok: true,\r\n limit,\r\n items: items.map((t) => ({\r\n title: t.title,\r\n tags: t.tags ?? [],\r\n modified: typeof t.modified === 'string' ? t.modified : null,\r\n snippet: snippetOf(t.text ?? ''),\r\n })),\r\n })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n /** Full tiddler for the quick-note \"最近\" picker (load into the editor). */\r\n const handleGet = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n const client = deps.getClient()\r\n if (client === undefined) {\r\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\r\n return\r\n }\r\n try {\r\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\r\n const title = url.searchParams.get('title') ?? ''\r\n if (title.length === 0) {\r\n json(res, { ok: false, error: 'missing title' }, 400)\r\n return\r\n }\r\n const t = await client.get(title)\r\n if (t === undefined) {\r\n json(res, { ok: false, notFound: true, title }, 404)\r\n return\r\n }\r\n const fields: Record<string, unknown> = {}\r\n for (const [k, v] of Object.entries(t)) {\r\n if (k === 'title' || k === 'text' || k === 'tags') continue\r\n fields[k] = v\r\n }\r\n json(res, {\r\n ok: true,\r\n title: t.title,\r\n text: t.text ?? '',\r\n tags: t.tags ?? [],\r\n type: t.type ?? 'text/vnd.tiddlywiki',\r\n modified: typeof t.modified === 'string' ? t.modified : null,\r\n fields,\r\n })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n /**\r\n * GET /dsh-tiddlywiki/search — keyword search for the reply-stream tool card\r\n * (mirrors tools.ts tiddlywiki_search: same local substring/tag/since/type\r\n * matching via the TiddlyWebClient). Returns hit titles/tags/modified/\r\n * snippets so the card can list clickable wiki links.\r\n */\r\n const handleSearch = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n const client = deps.getClient()\r\n if (client === undefined) {\r\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\r\n return\r\n }\r\n try {\r\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\r\n const query = url.searchParams.get('query') ?? ''\r\n const tags = url.searchParams.getAll('tags').filter((t) => t.length > 0)\r\n const tag = url.searchParams.get('tag') ?? undefined\r\n const since = url.searchParams.get('since') ?? undefined\r\n const type = url.searchParams.get('type') ?? undefined\r\n const limitRaw = Number(url.searchParams.get('limit') ?? 30)\r\n const limit = Number.isFinite(limitRaw) ? Math.max(1, Math.min(Math.floor(limitRaw), 200)) : 30\r\n const { items, total } = await client.search(query, { tags, tag, since, type, limit })\r\n json(res, {\r\n ok: true,\r\n query,\r\n tags,\r\n tag: tag ?? null,\r\n since: since ?? null,\r\n type: type ?? null,\r\n limit,\r\n total,\r\n items: items.map((t) => ({\r\n title: t.title,\r\n tags: t.tags ?? [],\r\n modified: typeof t.modified === 'string' ? t.modified : null,\r\n snippet: snippetOf(t.text ?? ''),\r\n })),\r\n })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n const handleRestart = async (_req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n await deps.server.restart()\r\n json(res, { ok: true, status: deps.server.status().status })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n /** One-click git sync for the floating button / settings page: pull →\r\n * commit → push, then return the fresh status. Mirrors the agent tool's\r\n * `action=sync` (design doc §7 conflict policy — rebase conflict aborts).\r\n * When the pull actually changed the working tree, the running TW child\r\n * still holds the old in-memory snapshot — restart it (same port) so the\r\n * UI reflects the pulled files instead of looking stale. */\r\n const handleSync = async (_req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n const dir = deps.getWikiPath()\r\n const status = async (): Promise<GitStatusViewPublic | null> => {\r\n try { return await deps.git.status(dir) } catch { return null }\r\n }\r\n try {\r\n const pulled = await deps.git.pull(dir)\r\n if (!pulled.ok) {\r\n json(res, {\r\n ok: false,\r\n action: 'sync',\r\n message: pulled.message,\r\n ...(pulled.conflictFiles !== undefined ? { conflictFiles: pulled.conflictFiles } : {}),\r\n status: await status(),\r\n }, 409)\r\n return\r\n }\r\n let restarted = false\r\n let restartError: string | undefined\r\n if (pulled.changed === true) {\r\n try {\r\n await deps.server.restart()\r\n restarted = true\r\n } catch (err) {\r\n restartError = err instanceof Error ? err.message : String(err)\r\n }\r\n }\r\n const committed = await deps.git.commit(dir, `sync ${new Date().toISOString()}`)\r\n const pushed = await deps.git.push(dir)\r\n const fresh = await status()\r\n json(res, {\r\n ok: pushed.ok,\r\n action: 'sync',\r\n message: pushed.ok ? '同步完成' : pushed.message,\r\n pull: 'ok',\r\n ...(pulled.changed === true ? { changed: true } : {}),\r\n restarted,\r\n ...(restartError !== undefined ? { restartError } : {}),\r\n commit: committed.message,\r\n push: pushed.message,\r\n status: fresh,\r\n lastSync: new Date().toISOString(),\r\n }, pushed.ok ? 200 : 502)\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n /**\r\n * Save an uploaded file into the wiki's `files/` folder (git-tracked; TW's\r\n * core server serves it at `/files/<name>`, get-file.js — no restart\r\n * needed). Body is the raw file; the name arrives in `X-Filename`. A\r\n * collision appends `-1`, `-2`, … so nothing is ever overwritten.\r\n */\r\n const handleUpload = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n const buf = await readBodyBuffer(req)\r\n // Name comes from ?name= (URL-encoded) or the X-Filename header.\r\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\r\n const nameParam = url.searchParams.get('name')\r\n let name = sanitizeUploadName(nameParam ?? '')\r\n if (name.length === 0 && typeof req.headers['x-filename'] === 'string') {\r\n let decoded = ''\r\n try { decoded = decodeURIComponent(req.headers['x-filename'] as string) } catch { decoded = req.headers['x-filename'] as string }\r\n name = sanitizeUploadName(decoded)\r\n }\r\n if (name.length === 0) {\r\n json(res, { ok: false, error: 'missing or invalid filename' }, 400)\r\n return\r\n }\r\n const filesDir = join(deps.getWikiPath(), 'files')\r\n await mkdir(filesDir, { recursive: true })\r\n const ext = extname(name)\r\n const stem = ext.length > 0 ? name.slice(0, -ext.length) : name\r\n // Collision avoidance: files/some.txt, files/some-1.txt, …\r\n let candidate = name\r\n for (let i = 1; ; i++) {\r\n try { await access(join(filesDir, candidate)) } catch { break }\r\n candidate = `${stem}-${i}${ext}`\r\n }\r\n await writeFile(join(filesDir, candidate), buf)\r\n deps.autoCommit()\r\n json(res, {\r\n ok: true,\r\n name: candidate,\r\n path: `files/${candidate}`,\r\n // Same-origin proxy URL: the embedded TW editor resolves image links\r\n // against the DSH origin, so a root-absolute `/files/...` would miss.\r\n url: `${TW_PROXY_PATH}files/${encodeURIComponent(candidate)}`,\r\n size: buf.length,\r\n type: req.headers['content-type'] ?? 'application/octet-stream',\r\n })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, err instanceof Error && /too large/.test(err.message) ? 413 : 500)\r\n }\r\n }\r\n\r\n /** Passthrough /dsh-tiddlywiki/api/<rest> → TW root /<rest>. */\r\n const handleApiProxy = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n const client = deps.getClient()\r\n if (client === undefined) {\r\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\r\n return\r\n }\r\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\r\n const rest = url.pathname.replace(/^\\/dsh-tiddlywiki\\/api/, '') || '/'\r\n try {\r\n const headers: Record<string, string> = {}\r\n const ct = req.headers['content-type']\r\n if (typeof ct === 'string') headers['content-type'] = ct\r\n const method = (req.method ?? 'GET').toUpperCase()\r\n // TW's CSRF gate requires X-Requested-With on writes; forward it through.\r\n if (method === 'PUT' || method === 'DELETE' || method === 'POST') headers['x-requested-with'] = 'TiddlyWiki'\r\n const init: RequestInit = { method, headers, signal: AbortSignal.timeout(15_000) }\r\n if (method === 'PUT' || method === 'POST') init.body = await readBody(req, MAX_PROXY_BODY_BYTES)\r\n const upstream = await fetch(`${deps.server.url}${rest}${url.search}`, init)\r\n const data = await upstream.text()\r\n res.writeHead(upstream.status, {\r\n 'content-type': upstream.headers.get('content-type') ?? 'application/json; charset=utf-8',\r\n 'cache-control': 'no-store',\r\n })\r\n res.end(data)\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 502)\r\n }\r\n }\r\n\r\n /**\r\n * SAME-ORIGIN proxy /dsh-tiddlywiki/tw/<rest> → TW root /<rest>. Serves the\r\n * ENTIRE TW frontend (index HTML, /files/*, the TiddlyWeb API) to the\r\n * browser through the DSH origin, so the embedded editor works from any\r\n * host/domain the user reaches DSH on (loopback, LAN, Tailscale, domain,\r\n * HTTPS). The browser never talks to the loopback TW child directly; DSH\r\n * does, on the same machine. Binary responses are buffered losslessly\r\n * (arrayBuffer) — unlike the /api JSON proxy, this route must never .text().\r\n */\r\n const handleTwProxy = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n const client = deps.getClient()\r\n if (client === undefined) {\r\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\r\n return\r\n }\r\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\r\n const rest = url.pathname.replace(new RegExp(`^${TW_PROXY_PREFIX}(?=/|$)`), '') || '/'\r\n try {\r\n const method = (req.method ?? 'GET').toUpperCase()\r\n const headers = forwardHeaders(req.headers)\r\n // TW's CSRF gate requires X-Requested-With on writes; forward it through.\r\n if (method === 'PUT' || method === 'DELETE' || method === 'POST') headers['x-requested-with'] = 'TiddlyWiki'\r\n const init: RequestInit = { method, headers, signal: AbortSignal.timeout(30_000) }\r\n if (method === 'PUT' || method === 'POST') init.body = await readBodyBuffer(req, MAX_UPLOAD_BYTES)\r\n const upstream = await fetch(`${deps.server.url}${rest}${url.search}`, init)\r\n const data = Buffer.from(await upstream.arrayBuffer())\r\n const responseHeaders: Record<string, string> = {\r\n 'content-type': upstream.headers.get('content-type') ?? 'application/octet-stream',\r\n 'cache-control': upstream.headers.get('cache-control') ?? 'no-store',\r\n }\r\n for (const name of ['etag', 'last-modified', 'content-disposition']) {\r\n const value = upstream.headers.get(name)\r\n if (value !== null) responseHeaders[name] = value\r\n }\r\n res.writeHead(upstream.status, responseHeaders)\r\n res.end(data)\r\n } catch (err) {\r\n if (res.headersSent) {\r\n res.end()\r\n return\r\n }\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 502)\r\n }\r\n }\r\n\r\n const disposers = [\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/status`, handler: (req, res) => { void handleStatus(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/note`, handler: (req, res) => { void handleNote(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/edit`, handler: (req, res) => { void handleEdit(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/tags`, handler: (req, res) => { void handleTags(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/recent`, handler: (req, res) => { void handleRecent(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/get`, handler: (req, res) => { void handleGet(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/search`, handler: (req, res) => { void handleSearch(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/sync`, handler: (req, res) => { void handleSync(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/upload`, handler: (req, res) => { void handleUpload(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/restart`, handler: (req, res) => { void handleRestart(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/session/summary`, handler: (req, res) => { void handleSessionSummary(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/agent/sessions`, handler: (req, res) => { void handleAgentSessions(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/agent/modes`, handler: (req, res) => { void handleAgentModes(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/agent/send`, handler: (req, res) => { void handleAgentSend(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/agent/create`, handler: (req, res) => { void handleAgentCreate(req, res) } }),\r\n ctx.webServer.register({ kind: 'prefix', path: `${ROUTE_PREFIX}/api`, handler: (req, res) => { void handleApiProxy(req, res) } }),\r\n ctx.webServer.register({ kind: 'prefix', path: `${TW_PROXY_PREFIX}`, handler: (req, res) => { void handleTwProxy(req, res) } }),\r\n ]\r\n return () => {\r\n for (const dispose of disposers) dispose()\r\n }\r\n}\r\n\r\n/** Public shape of the git status summary sent to the panel. */\r\nexport interface GitStatusViewPublic {\r\n exists: boolean\r\n branch: string\r\n dirty: boolean\r\n dirtyFiles: string[]\r\n remote: string\r\n lastCommit?: string\r\n ahead?: number\r\n behind?: number\r\n}\r\n","/**\r\n * Extensible plugin config (design doc §13, config panel).\r\n *\r\n * Two layers:\r\n * base — the cordis `config:` block (profile composition, defaults);\r\n * overrides — a user-editable config tiddler ($:/plugins/dsh-tiddlywiki/config,\r\n * a JSON string) written by the settings page.\r\n * The tiddler overlays the base (tiddler wins), so future config fields just\r\n * extend the shape — no schema, no @deepseek-ai dependency, and the config\r\n * travels with the wiki's git history.\r\n *\r\n * @module dsh-tiddlywiki/host/config\r\n */\r\nimport type { TiddlyWebClient } from './tw-api.ts'\r\n\r\n/** Config tiddler (JSON string) where the settings page stores overrides. */\r\nexport const CONFIG_TIDDLER = '$:/plugins/dsh-tiddlywiki/config'\r\n\r\n/** TW frontend API base tiddler, pointed at the same-origin DSH proxy. */\r\nexport const TW_WEB_HOST_TIDDLER = '$:/config/tiddlyweb/host'\r\n\r\n/** The legacy default TW host value this plugin replaces with the proxy. */\r\nexport const TW_WEB_HOST_DEFAULT = '$protocol$//$host$/'\r\n\r\n/**\r\n * Default dark palette the embedded TW switches to when DSH is dark\r\n * (mirrored in src/client/theme-sync.ts — the two bundles cannot share code).\r\n */\r\nexport const DARK_PALETTE_DEFAULT = '$:/palettes/CupertinoDark'\r\n\r\n/** Extensible, loose plugin config shape (future fields just appear here). */\r\nexport interface PluginConfigShape {\r\n note?: { tag?: string }\r\n git?: { autoCommit?: boolean; debounceMs?: number; remote?: string; branch?: string }\r\n ui?: {\r\n /** 是否在界面右下角显示「快速笔记」悬浮按钮(默认 true)。 */\r\n showQuickNote?: boolean\r\n /**\r\n * 是否在聊天输入框上方(conversation.input.dock 槽位)显示「快速笔记」\r\n * 快捷按钮(默认 true)。与其它插件(todo/cost-meter/goal/queue/git-graph\r\n * 等)注入的内容同处一个纵向排列的 dock 区,天然不重叠。\r\n */\r\n showQuickNoteDock?: boolean\r\n /**\r\n * 点击「快速笔记」后的打开方式(默认 `native`):\r\n * `native` = 直接弹出 TW 原生编辑器(新建/恢复草稿);`card` = 弹出现有\r\n * 的 Markdown 快速笔记卡片。设置页可切换。\r\n */\r\n quickNoteMode?: 'native' | 'card'\r\n /** 左侧侧边栏 TW 入口的显示名称(默认「TiddlyWiki」,可自定义)。 */\r\n sidebarLabel?: string\r\n /** 是否显示 TW 面板右下角的「状态/重载」悬浮按钮(默认 true)。 */\r\n showPanelStatus?: boolean\r\n /** 是否在界面右下角显示「同步」悬浮按钮(默认 true)。 */\r\n showSyncButton?: boolean\r\n /**\r\n * 嵌入式 TW 是否跟随 DSH 深浅主题(默认 true):DSH 暗色时把 TW 活动\r\n * palette 临时切到 `darkPalette`,浅色时恢复用户原 palette;仅内存生效,\r\n * 不会写回 wiki。\r\n */\r\n followDshTheme?: boolean\r\n /** DSH 暗色时 TW 使用的 palette tiddler 标题(默认 CupertinoDark)。 */\r\n darkPalette?: string\r\n /**\r\n * 一键发送给 Agent(TW 笔记 → DSH 会话注入)。\r\n * `enabled: false` 时路由 403、TW 端按钮点击提示未启用;`token` 非空时\r\n * 路由要求请求头 `x-send-to-agent-token` 与之匹配;`endpoint` 可覆盖\r\n * TW 端默认的 `location.origin + /dsh-tiddlywiki`。\r\n */\r\n sendToAgent?: { enabled?: boolean; endpoint?: string; token?: string }\r\n /**\r\n * 「所有文章」两列分页页面的每页条数(默认 10)。页面在渲染时实时读取\r\n * 该值(见 seed-all-articles),改后无需重新初始化。\r\n */\r\n allArticles?: { pageSize?: number }\r\n /**\r\n * 会话顶部「知识库」Tab 的显示名称(默认「知识库」)。该 Tab 显示本会话\r\n * 产生/读取/检索过的 wiki 笔记汇总(conversation.view 槽位,见\r\n * src/client/session-summary.ts)。\r\n */\r\n tabLabel?: string\r\n /** 是否在会话顶部显示「知识库」Tab(会话相关 wiki 汇总,默认 true)。 */\r\n showSessionTab?: boolean\r\n /**\r\n * 是否在 DSH 右侧边栏(new rightbar)提供 TiddlyWiki 入口/Tab(默认\r\n * true):在 rightbar 首页(guide 页)注册「TiddlyWiki 知识库」入口盒,\r\n * 点击即在右侧栏以 tab 形式打开完整 TW 编辑器(与聊天并排)。老版本\r\n * DSH 无 rightbar 时该配置不生效(客户端自动跳过)。\r\n */\r\n showRightbarTab?: boolean\r\n }\r\n uiLanguage?: string\r\n [key: string]: unknown\r\n}\r\n\r\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\r\n return typeof value === 'object' && value !== null && !Array.isArray(value)\r\n}\r\n\r\n/** Deep-merge: `over` wins; nested plain objects merge recursively. */\r\nexport function deepMerge(base: Record<string, unknown>, over: Record<string, unknown>): Record<string, unknown> {\r\n const out: Record<string, unknown> = { ...base }\r\n for (const [key, value] of Object.entries(over)) {\r\n if (value === undefined) continue\r\n if (isPlainObject(value) && isPlainObject(out[key])) {\r\n out[key] = deepMerge(out[key] as Record<string, unknown>, value)\r\n } else {\r\n out[key] = value\r\n }\r\n }\r\n return out\r\n}\r\n\r\n/**\r\n * Runtime config store: caches the override tiddler and exposes the effective\r\n * (merged) config. `load` runs at startup and after every write/restart.\r\n */\r\nexport class ConfigStore {\r\n private overrides: PluginConfigShape = {}\r\n\r\n constructor(private readonly base: PluginConfigShape) {}\r\n\r\n /** Effective config = cordis base overlaid with the user override tiddler. */\r\n get(): PluginConfigShape {\r\n return deepMerge(this.base, this.overrides) as PluginConfigShape\r\n }\r\n\r\n /** Reload the override tiddler (no-op when the wiki is unavailable). */\r\n async load(client: TiddlyWebClient | undefined): Promise<void> {\r\n this.overrides = {}\r\n if (client === undefined) return\r\n try {\r\n const tiddler = await client.get(CONFIG_TIDDLER)\r\n if (tiddler !== undefined && typeof tiddler.text === 'string') {\r\n const parsed = JSON.parse(tiddler.text) as unknown\r\n if (isPlainObject(parsed)) this.overrides = parsed as PluginConfigShape\r\n }\r\n } catch {\r\n // Wiki not ready or config tiddler unreadable → keep empty overrides.\r\n this.overrides = {}\r\n }\r\n }\r\n\r\n /** Merge a patch into the overrides and persist the tiddler. */\r\n async set(client: TiddlyWebClient, patch: PluginConfigShape): Promise<PluginConfigShape> {\r\n this.overrides = deepMerge(this.overrides, patch) as PluginConfigShape\r\n await client.put({\r\n title: CONFIG_TIDDLER,\r\n text: JSON.stringify(this.overrides, null, 2),\r\n type: 'application/json',\r\n tags: [],\r\n })\r\n return this.get()\r\n }\r\n}\r\n","/**\r\n * Admin surface for the plugin settings page (design doc §13, config panel).\r\n *\r\n * - dynamic plugin/theme management: enumerate the bundled catalog from the\r\n * installed tiddlywiki package, read/write the wiki's `tiddlywiki.info`\r\n * plugins/themes arrays, then restart the TW child so the change applies;\r\n * - extensible config: the settings page reads/writes a config tiddler\r\n * ($:/plugins/dsh-tiddlywiki/config, a JSON string) that overlays the\r\n * cordis `config:` block — future config fields just extend the shape.\r\n *\r\n * Routes (all under ROUTE_PREFIX/admin, JSON):\r\n * GET /admin/state current info + catalog + effective config + status\r\n * POST /admin/info { plugins?, themes? } → write info → restart TW\r\n * POST /admin/config { ...patch } → write config tiddler\r\n * POST /admin/restart restart the TW child\r\n *\r\n * @module dsh-tiddlywiki/host/admin\r\n */\r\nimport { createRequire } from 'node:module'\r\nimport { readFile, writeFile, readdir } from 'node:fs/promises'\r\nimport { dirname, join } from 'node:path'\r\nimport type { IncomingMessage, ServerResponse } from 'node:http'\r\nimport type { TiddlyWebClient } from './tw-api.ts'\r\nimport type { WikiServer } from './wiki.ts'\r\nimport { ROUTE_PREFIX, type WebServerFace } from './routes.ts'\r\nimport { CONFIG_TIDDLER, type ConfigStore, type PluginConfigShape } from './config.ts'\r\nimport { readBody, json } from './http.ts'\r\nimport { GitFace } from './git.ts'\r\n\r\n/** One bundled plugin/theme from the catalog. */\r\nexport interface CatalogEntry {\r\n /** Short name as used in tiddlywiki.info, e.g. \"tiddlywiki/katex\". */\r\n name: string\r\n /** Full tiddler title, e.g. \"$:/plugins/tiddlywiki/katex\". */\r\n title: string\r\n label: string\r\n description: string\r\n /** Dependent theme NAMES (converted from plugin.info `dependents`), e.g. heavier → [\"tiddlywiki/snowwhite\"]. */\r\n dependents?: string[]\r\n}\r\n\r\nexport interface Catalog {\r\n plugins: CatalogEntry[]\r\n themes: CatalogEntry[]\r\n /** Bundled language plugins (tiddlywiki package `languages/` dir). */\r\n languages: CatalogEntry[]\r\n}\r\n\r\n/** Shape of tiddlywiki.info (plugins/themes/languages + build/description). */\r\nexport interface WikiInfo {\r\n description?: string\r\n plugins: string[]\r\n themes: string[]\r\n languages?: string[]\r\n build?: Record<string, unknown>\r\n [key: string]: unknown\r\n}\r\n\r\n/** Resolve the installed tiddlywiki package root (for the catalog). */\r\nexport function resolveTwRoot(): string {\r\n const require = createRequire(import.meta.url)\r\n return dirname(require.resolve('tiddlywiki/package.json'))\r\n}\r\n\r\n/** Read the wiki's tiddlywiki.info. */\r\nexport async function readWikiInfo(wikiPath: string): Promise<WikiInfo> {\r\n let raw: string\r\n try {\r\n raw = await readFile(join(wikiPath, 'tiddlywiki.info'), 'utf8')\r\n } catch {\r\n return { plugins: [], themes: [], languages: [] }\r\n }\r\n const parsed = JSON.parse(raw) as Partial<WikiInfo>\r\n return {\r\n description: parsed.description,\r\n plugins: parsed.plugins ?? [],\r\n themes: parsed.themes ?? [],\r\n languages: parsed.languages ?? [],\r\n ...parsed,\r\n }\r\n}\r\n\r\n/** Write the wiki's tiddlywiki.info (pretty-printed, ordering preserved). */\r\nexport async function writeWikiInfo(wikiPath: string, info: WikiInfo): Promise<void> {\r\n await writeFile(join(wikiPath, 'tiddlywiki.info'), `${JSON.stringify(info, null, 4)}\\n`, 'utf8')\r\n}\r\n\r\n/** Enumerate bundled official plugins + themes + languages of tiddlywiki. */\r\nexport async function bundledCatalog(twRoot: string): Promise<Catalog> {\r\n // TW themes are SKINS layered on the vanilla base (which carries the full\r\n // 70KB base stylesheet). A theme whose stylesheet body is empty is a broken\r\n // stub (e.g. tight-heavier in some releases) — skip it so the settings list\r\n // never offers a no-op theme. vanilla itself is always kept.\r\n const themeHasCss = async (dir: string): Promise<boolean> => {\r\n for (const name of ['base.tid', 'styles.tid']) {\r\n try {\r\n const raw = await readFile(join(twRoot, 'themes', 'tiddlywiki', dir, name), 'utf8')\r\n const body = raw\r\n .replace(/^[\\s\\S]*?\\r?\\n\\r?\\n/, '')\r\n .split('\\n')\r\n .filter((line) => !/^\\\\rules\\b/.test(line.trim()))\r\n .join('\\n')\r\n .trim()\r\n if (body.length > 0) return true\r\n } catch {\r\n /* file absent */\r\n }\r\n }\r\n return false\r\n }\r\n const scan = async (sub: 'plugins' | 'themes'): Promise<CatalogEntry[]> => {\r\n const root = join(twRoot, sub, 'tiddlywiki')\r\n let dirs: string[]\r\n try {\r\n dirs = await readdir(root)\r\n } catch {\r\n return []\r\n }\r\n const out: CatalogEntry[] = []\r\n for (const dir of dirs) {\r\n let info: { name?: string; description?: string; dependents?: string[] } = {}\r\n try {\r\n info = JSON.parse(await readFile(join(root, dir, 'plugin.info'), 'utf8')) as typeof info\r\n } catch {\r\n info = {}\r\n }\r\n if (sub === 'themes' && dir !== 'vanilla' && !(await themeHasCss(dir))) continue\r\n out.push({\r\n name: `tiddlywiki/${dir}`,\r\n title: sub === 'plugins' ? `$:/plugins/tiddlywiki/${dir}` : `$:/themes/tiddlywiki/${dir}`,\r\n label: info.name ?? dir,\r\n description: info.description ?? '',\r\n // plugin.info `dependents` are full plugin titles → convert to names.\r\n dependents: Array.isArray(info.dependents)\r\n ? info.dependents.map((dep) => dep.replace(/^\\$:\\/themes\\/tiddlywiki\\//, 'tiddlywiki/'))\r\n : undefined,\r\n })\r\n }\r\n out.sort((a, b) => a.name.localeCompare(b.name))\r\n return out\r\n }\r\n // Language plugins live in the package ROOT `languages/` dir (not plugins/),\r\n // and are enabled via the tiddlywiki.info `languages` array (boot resolves\r\n // them through $tw.config.languagesPath). Fully offline — official builds.\r\n const scanLanguages = async (): Promise<CatalogEntry[]> => {\r\n const root = join(twRoot, 'languages')\r\n let dirs: string[]\r\n try {\r\n dirs = await readdir(root)\r\n } catch {\r\n return []\r\n }\r\n const out: CatalogEntry[] = []\r\n for (const dir of dirs) {\r\n let info: { name?: string; description?: string } = {}\r\n try {\r\n info = JSON.parse(await readFile(join(root, dir, 'plugin.info'), 'utf8')) as typeof info\r\n } catch {\r\n info = {}\r\n }\r\n out.push({\r\n name: dir,\r\n title: `$:/languages/${dir}`,\r\n label: info.name ?? dir,\r\n description: info.description ?? '',\r\n })\r\n }\r\n out.sort((a, b) => a.name.localeCompare(b.name))\r\n return out\r\n }\r\n const [plugins, themes, languages] = await Promise.all([scan('plugins'), scan('themes'), scanLanguages()])\r\n return { plugins, themes, languages }\r\n}\r\n\r\n/**\r\n * Normalize a theme selection into the tiddlywiki.info `themes` array.\r\n *\r\n * TW themes are SKINS with a dependency chain (plugin.info `dependents`):\r\n * vanilla ← snowwhite ← heavier / centralised / readonly / starlight\r\n * vanilla ← tight / seamless\r\n * The ACTIVE theme is `$:/theme`, and switching to it registers the theme PLUS\r\n * its transitive dependents (boot.js accumulatePlugin) — if a dependent isn't\r\n * loaded, the vanilla base stylesheet is lost and the UI breaks. So we always\r\n * emit the transitive closure, dependency-first (base first, active overlay\r\n * last), and force vanilla in as the base. Empty selection → vanilla.\r\n */\r\nexport function normalizeThemes(selected: string[], deps: Record<string, string[]> = {}): string[] {\r\n const sel = selected.filter((name) => typeof name === 'string' && name.length > 0)\r\n if (sel.length === 0) sel.push('tiddlywiki/vanilla')\r\n const out: string[] = []\r\n const seen = new Set<string>()\r\n const visit = (name: string): void => {\r\n if (seen.has(name)) return\r\n seen.add(name)\r\n for (const dep of deps[name] ?? []) {\r\n if (dep !== name) visit(dep)\r\n }\r\n out.push(name)\r\n }\r\n for (const name of sel) visit(name)\r\n if (!out.includes('tiddlywiki/vanilla')) out.unshift('tiddlywiki/vanilla')\r\n return out\r\n}\r\n\r\n/**\r\n * Ensure a language code (e.g. \"zh-Hans\") is in tiddlywiki.info `languages`.\r\n * Returns whether tiddlywiki.info changed (caller decides whether to restart).\r\n */\r\nexport async function ensureLanguage(wikiPath: string, twRoot: string, lang: string): Promise<boolean> {\r\n if (typeof lang !== 'string' || lang.trim().length === 0) return false\r\n const code = lang.trim()\r\n const catalog = await bundledCatalog(twRoot)\r\n if (!catalog.languages.some((l) => l.name === code)) {\r\n throw new Error(`unknown language plugin: ${code}`)\r\n }\r\n const info = await readWikiInfo(wikiPath)\r\n const current = info.languages ?? []\r\n if (current.includes(code)) return false\r\n info.languages = [...current, code]\r\n await writeWikiInfo(wikiPath, info)\r\n return true\r\n}\r\n\r\nexport interface AdminDeps {\r\n server: WikiServer\r\n getClient: () => TiddlyWebClient | undefined\r\n getWikiPath: () => string\r\n twRoot: () => string\r\n config: ConfigStore\r\n /** Seed registry for the settings-page \"初始化\" section. */\r\n seeds: {\r\n checkAll: (client: TiddlyWebClient) => Promise<Array<{ id: string; title: string; description: string; present: boolean; removable: boolean; detail?: string }>>\r\n run: (client: TiddlyWebClient, id: string | undefined, force: boolean) => Promise<Array<{ id: string; ok: boolean; wrote: boolean; detail?: string; error?: string }>>\r\n /** 反初始化: remove one (or all) optional seed's seeded tiddlers + markers. */\r\n remove: (client: TiddlyWebClient, id: string | undefined) => Promise<Array<{ id: string; ok: boolean; wrote: boolean; detail?: string; error?: string }>>\r\n }\r\n}\r\n\r\nexport function registerAdminRoutes(ctx: { webServer: WebServerFace }, deps: AdminDeps): () => void {\r\n const handleState = async (_req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n const wikiPath = deps.getWikiPath()\r\n const [info, catalog] = await Promise.all([readWikiInfo(wikiPath), bundledCatalog(deps.twRoot())])\r\n let git: unknown = null\r\n try {\r\n git = await new GitFace().status(wikiPath)\r\n } catch {\r\n git = null\r\n }\r\n json(res, {\r\n ok: true,\r\n server: deps.server.status(),\r\n info: { plugins: info.plugins, themes: info.themes, languages: info.languages ?? [] },\r\n catalog,\r\n config: deps.config.get(),\r\n git,\r\n })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n const handleInfo = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n const body = JSON.parse(await readBody(req)) as { plugins?: unknown; themes?: unknown; themeActive?: unknown; languages?: unknown }\r\n const wikiPath = deps.getWikiPath()\r\n const info = await readWikiInfo(wikiPath)\r\n const catalog = await bundledCatalog(deps.twRoot())\r\n const known = new Set([...catalog.plugins, ...catalog.themes].map((c) => c.name))\r\n const knownLangs = new Set(catalog.languages.map((c) => c.name))\r\n const applyList = (field: 'plugins' | 'themes', raw: unknown): string[] => {\r\n if (!Array.isArray(raw)) return info[field]\r\n const next: string[] = []\r\n for (const name of raw) {\r\n if (typeof name !== 'string') continue\r\n if (!known.has(name) && !info[field].includes(name)) {\r\n throw new Error(`unknown plugin/theme: ${name}`)\r\n }\r\n if (!next.includes(name)) next.push(name)\r\n }\r\n return next\r\n }\r\n const applyLanguages = (raw: unknown): string[] => {\r\n if (!Array.isArray(raw)) return info.languages ?? []\r\n const next: string[] = []\r\n for (const code of raw) {\r\n if (typeof code !== 'string') continue\r\n if (!knownLangs.has(code) && !(info.languages ?? []).includes(code)) {\r\n throw new Error(`unknown language plugin: ${code}`)\r\n }\r\n if (!next.includes(code)) next.push(code)\r\n }\r\n return next\r\n }\r\n info.plugins = applyList('plugins', body.plugins)\r\n // Activate the chosen theme: load its full dependency chain AND set\r\n // `$:/theme` so the browser's themeManager actually applies it (the\r\n // `themes` array alone only makes the plugin available).\r\n let activatedTheme: string | undefined\r\n if (Array.isArray(body.themes)) {\r\n const themeDeps: Record<string, string[]> = {}\r\n for (const theme of catalog.themes) {\r\n if (theme.dependents && theme.dependents.length > 0) themeDeps[theme.name] = theme.dependents\r\n }\r\n let selected = applyList('themes', body.themes)\r\n // Explicit active-theme pick (new two-layer UI): validate and auto-add\r\n // it to the loaded set so its dependency closure is loaded. Without an\r\n // explicit pick, activate the deepest loaded overlay (old single-radio).\r\n const explicitActive = typeof body.themeActive === 'string' && body.themeActive.length > 0\r\n if (explicitActive) {\r\n const activeName = body.themeActive as string\r\n if (known.has(activeName) || info.themes.includes(activeName)) {\r\n if (!selected.includes(activeName)) selected.push(activeName)\r\n activatedTheme = activeName\r\n }\r\n }\r\n info.themes = normalizeThemes(selected, themeDeps)\r\n if (activatedTheme === undefined && info.themes.length > 0) {\r\n activatedTheme = info.themes[info.themes.length - 1]\r\n }\r\n } else {\r\n info.themes = applyList('themes', body.themes)\r\n }\r\n if (Array.isArray(body.languages)) info.languages = applyLanguages(body.languages)\r\n await writeWikiInfo(wikiPath, info)\r\n await deps.server.restart()\r\n // Activate the chosen theme tiddler (mirrors TW's own Control Panel).\r\n if (activatedTheme !== undefined) {\r\n const client = deps.getClient()\r\n if (client !== undefined) {\r\n await client\r\n .put({ title: '$:/theme', text: `$:/themes/${activatedTheme}`, type: 'text/vnd.tiddlywiki', tags: [] })\r\n .catch(() => undefined)\r\n }\r\n }\r\n // After a languages change, pin the active language tiddler: first\r\n // enabled language, or en-GB when none is enabled. Only when the request\r\n // actually carried a languages array (plugins/themes restarts skip this).\r\n if (Array.isArray(body.languages)) {\r\n const client = deps.getClient()\r\n if (client !== undefined) {\r\n const langs = info.languages ?? []\r\n const active = langs.length > 0 ? `$:/languages/${langs[0]}` : '$:/languages/en-GB'\r\n await client.put({ title: '$:/language', text: active, type: 'text/plain', tags: [] }).catch(() => undefined)\r\n // Keep the startup auto-apply hint (config uiLanguage) consistent with\r\n // the active language, so a later dsh-web restart doesn't re-enable\r\n // a language the user just disabled here.\r\n const hint = langs.length > 0 ? langs[0] : ''\r\n if ((deps.config.get().uiLanguage ?? '') !== hint) {\r\n await deps.config.set(client, { uiLanguage: hint }).catch(() => undefined)\r\n }\r\n }\r\n }\r\n json(res, { ok: true, info: { plugins: info.plugins, themes: info.themes, languages: info.languages ?? [] } })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 400)\r\n }\r\n }\r\n\r\n const handleConfig = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n const body = JSON.parse(await readBody(req)) as PluginConfigShape\r\n const client = deps.getClient()\r\n if (client === undefined) {\r\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\r\n return\r\n }\r\n await deps.config.set(client, body)\r\n json(res, { ok: true, config: deps.config.get() })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 400)\r\n }\r\n }\r\n\r\n const handleRestart = async (_req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n await deps.server.restart()\r\n json(res, { ok: true, status: deps.server.status().status })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n /**\r\n * GET /dsh-tiddlywiki/admin/seeds — status of every one-time seed\r\n * (doc-note / send-to-agent / home-index / tw-web-host) for the settings\r\n * page's 初始化 section.\r\n */\r\n const handleSeeds = async (_req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n const client = deps.getClient()\r\n if (client === undefined) {\r\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\r\n return\r\n }\r\n const items = await deps.seeds.checkAll(client)\r\n json(res, { ok: true, items })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n /**\r\n * POST /dsh-tiddlywiki/admin/seeds/run — run one seed (or all when `id` is\r\n * absent); `force: true` is the manual \"重新初始化\" (overwrite + re-marker),\r\n * `force: false` keeps the one-shot write-if-missing semantics.\r\n */\r\n const handleSeedsRun = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n const body = JSON.parse(await readBody(req)) as { id?: unknown; force?: unknown }\r\n const id = typeof body.id === 'string' && body.id.trim().length > 0 ? body.id.trim() : undefined\r\n const force = body.force === true\r\n const client = deps.getClient()\r\n if (client === undefined) {\r\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\r\n return\r\n }\r\n const results = await deps.seeds.run(client, id, force)\r\n const ok = results.every((r) => r.ok)\r\n json(res, { ok, results }, ok ? 200 : 400)\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n /**\r\n * POST /dsh-tiddlywiki/admin/seeds/remove — 反初始化: remove one optional\r\n * seed (or all optional seeds when `id` is absent) — delete the seeded\r\n * tiddlers + markers so the wiki returns to the \"never seeded\" state.\r\n * Core seeds (功能必需) cannot be removed.\r\n */\r\n const handleSeedsRemove = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n const body = JSON.parse(await readBody(req)) as { id?: unknown }\r\n const id = typeof body.id === 'string' && body.id.trim().length > 0 ? body.id.trim() : undefined\r\n const client = deps.getClient()\r\n if (client === undefined) {\r\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\r\n return\r\n }\r\n const results = await deps.seeds.remove(client, id)\r\n const ok = results.every((r) => r.ok)\r\n json(res, { ok, results }, ok ? 200 : 400)\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n const disposers = [\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/state`, handler: (req, res) => { void handleState(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/info`, handler: (req, res) => { void handleInfo(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/config`, handler: (req, res) => { void handleConfig(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/restart`, handler: (req, res) => { void handleRestart(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/seeds`, handler: (req, res) => { void handleSeeds(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/seeds/run`, handler: (req, res) => { void handleSeedsRun(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/seeds/remove`, handler: (req, res) => { void handleSeedsRemove(req, res) } }),\r\n ]\r\n return () => {\r\n for (const dispose of disposers) dispose()\r\n }\r\n}\r\n","/**\r\n * Built-in doc note for the plugin (design doc §14): a short user-facing\r\n * \"how to use dsh-tiddlywiki\" note seeded into the wiki the first time the\r\n * plugin runs on a wiki.\r\n *\r\n * ONE-SHOT seed: a marker tiddler (`seed-doc-note`) records that the note has\r\n * been offered once; after that the note is the user's own content — deleting\r\n * it and restarting dsh web does NOT recreate it, and edits are never\r\n * overwritten.\r\n *\r\n * @module dsh-tiddlywiki/host/seed-notes\r\n */\r\nimport type { TiddlyWebClient } from './tw-api.ts'\r\n\r\n/** Note tiddler title (a normal, searchable note — not a system tiddler). */\r\nexport const DOC_NOTE_TITLE = 'dsh-tiddlywiki 插件说明'\r\n\r\n/** Tag that makes the note easy to find via `tiddlywiki_search tag=docs`. */\r\nexport const DOC_NOTE_TAG = 'docs'\r\n\r\n/** One-time marker: its presence means \"the note was offered once — hands off\". */\r\nexport const SEED_MARKER_TITLE = '$:/plugins/dsh-tiddlywiki/seed-doc-note'\r\n\r\n/** The note body, TiddlyWiki wiki-text. */\r\nexport const DOC_NOTE_TEXT = `! dsh-tiddlywiki 插件说明\r\n\r\n本插件把 **TiddlyWiki 5** 作为 DSH 的持久知识库(wiki 文件夹本身就是一个 git 仓库,随内容自动提交/同步)。\r\n\r\n!! 它能做什么\r\n\r\n* **10 个 agent 工具**:\\`tiddlywiki_search\\`(检索,支持 tags/since/type/limit 过滤)/ \\`tiddlywiki_get\\`(读)/ \\`tiddlywiki_put\\`(写)/ \\`tiddlywiki_batch_put\\`(批量写)/ \\`tiddlywiki_rename\\`(重命名+同步引用)/ \\`tiddlywiki_delete\\`(删)/ \\`tiddlywiki_recent\\`(最近修改)/ \\`tiddlywiki_list_tags\\`(标签清单)/ \\`tiddlywiki_git_sync\\`(git 同步)/ \\`tiddlywiki_git_resolve\\`(冲突按 tiddler 二选一)。\r\n* **TW 编辑器面板**:侧边栏「TiddlyWiki」按钮 → 在界面中央打开完整版 TW 编辑器。\r\n* **快速笔记**:点击聊天输入框上方或右下角「知识库」菜单里的「📝 快速笔记」——默认**直达 TW 原生编辑页**(独立小窗,草稿自动续写);也可在设置页切回 Markdown 卡片(语法高亮、文件上传、多选 tag、草稿自动保存、Ctrl+Enter 保存)。「✏️ 在 TW 中编辑」会弹出独立小窗用 TW 原生编辑器编辑。\r\n* **一键同步**:「知识库」按钮 → 「🔁 同步」一键 pull → commit → push,按钮上的状态点实时反映 git 状态。\r\n* **git 同步**:写入自动防抖 commit(默认 60 秒);手动 \\`tiddlywiki_git_sync action=sync\\` 做 pull → commit → push。\r\n* **设置页**:DSH 设置 → 「TiddlyWiki 知识库」管理插件/主题/语言与运行配置(含「知识库」按钮相关显示开关)。\r\n\r\n!! 知识库纪律(四条)\r\n\r\n1. 开工先 \\`tiddlywiki_git_sync action=pull\\`(rebase + autostash,真冲突会自动 abort 并报文件)。\r\n2. 冲突后:\\`tiddlywiki_git_resolve files=[冲突文件] strategy=keep-local|keep-remote\\` 按 tiddler 二选一解决,再重新 sync。\r\n3. 收工 \\`tiddlywiki_git_sync action=sync\\`。\r\n4. 插件自动 commit 兜底,手动 sync 用于需要主动推送的场合。\r\n\r\n!! 主题与语言\r\n\r\n* **主题**分两层:每行一个「☑ 加载」(多选 = TW 里可用的主题,依赖链自动带上)和「◉ 活动」(单选 = 当前视觉主题)。应用后自动重启 TW。\r\n* **语言**:设置页勾选 \\`zh-Hans\\`(简体)并应用,TW 界面即切换为中文。\r\n\r\n!! 说明\r\n\r\n* 本笔记由插件在**首次启动**时自动写入(一次性:只写一次)。删除后重启 dsh web **不会自动恢复**——它从此归你所有。\r\n* 更多细节见插件仓库 README。`\r\n\r\n/**\r\n * Seed the doc note once per wiki (mirrors the one-shot policy). A marker\r\n * tiddler records that the note has been offered; from then on the note is\r\n * user-owned and is never re-created (deleting it survives restarts).\r\n *\r\n * With `opts.force` the note is (re)written even when it already exists and\r\n * the marker is (re)written — the settings page uses this for\r\n * \"重新初始化\". Returns whether a note was written this call. Never throws.\r\n */\r\nexport async function seedDocNote(client: TiddlyWebClient, opts?: { force?: boolean }): Promise<boolean> {\r\n const force = opts?.force === true\r\n if (!force) {\r\n const marker = await client.get(SEED_MARKER_TITLE).catch(() => undefined)\r\n if (marker !== undefined) return false\r\n }\r\n const existing = await client.get(DOC_NOTE_TITLE).catch(() => undefined)\r\n let wrote = false\r\n if (force || existing === undefined) {\r\n await client.put({\r\n title: DOC_NOTE_TITLE,\r\n text: DOC_NOTE_TEXT,\r\n type: 'text/vnd.tiddlywiki',\r\n tags: [DOC_NOTE_TAG],\r\n })\r\n wrote = true\r\n }\r\n // Record the offer regardless, so an existing note (upgrade from an older\r\n // create-if-missing version) also becomes user-owned from here on.\r\n await client\r\n .put({ title: SEED_MARKER_TITLE, text: 'seeded-once', type: 'text/plain', tags: [] })\r\n .catch(() => undefined)\r\n return wrote\r\n}\r\n\r\n/**\r\n * Un-seed (反初始化): remove the doc note and its one-shot marker, returning\r\n * the wiki to the \"never offered\" state. Deletion is idempotent — a tiddler\r\n * that was already gone is simply not listed. Never throws.\r\n */\r\nexport async function unseedDocNote(client: TiddlyWebClient): Promise<{ removed: string[] }> {\r\n const removed: string[] = []\r\n for (const title of [DOC_NOTE_TITLE, SEED_MARKER_TITLE]) {\r\n const t = await client.get(title).catch(() => undefined)\r\n if (t !== undefined) {\r\n await client.delete(title)\r\n removed.push(title)\r\n }\r\n }\r\n return { removed }\r\n}\r\n","/**\r\n * Generated from the wiki bundle (do not hand-edit the constant).\r\n * Source: send-to-agent.bundle.json\r\n *\r\n * The \"发送给 Agent\" TW button, packaged as a TiddlyWiki plugin bundle\r\n * (`{\"tiddlers\": {...}}`), seeded into fresh wikis by seedSendToAgent.\r\n *\r\n * @module dsh-tiddlywiki/host/seed-send-to-agent\r\n */\r\nimport type { TiddlyWebClient } from './tw-api.ts'\r\n\r\n/** The packaged plugin tiddler title (a TW system tiddler, type application/json). */\r\nexport const SEND_TO_AGENT_PLUGIN_TITLE = '$:/plugins/dsh/send-to-agent'\r\n\r\n/** One-time marker: presence means \"the button was offered once — hands off\". */\r\nexport const SEND_TO_AGENT_MARKER_TITLE = '$:/plugins/dsh-tiddlywiki/seed-send-to-agent'\r\n\r\n/** The bundle's JSON text (`{\"tiddlers\": {...}}`), exactly as TW stores it. */\r\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.4\\\\\\\",\\\\\\\"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\\\\nfunction notify(msg) {\\\\n\\\\tif ($tw.notifier && typeof $tw.notifier.display === \\\\\\\"function\\\\\\\") {\\\\n\\\\t\\\\t$tw.notifier.display(msg);\\\\n\\\\t} else if (typeof alert === \\\\\\\"function\\\\\\\") {\\\\n\\\\t\\\\talert(msg);\\\\n\\\\t}\\\\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 \\\\n<span class=\\\\\\\"tc-icon-wrapper\\\\\\\"><$transclude tiddler={{!!icon}}/></span>\\\\n \\\\n<$transclude field=\\\\\\\"caption\\\\\\\"/>\\\\n -- \\\\n<i class=\\\\\\\"tc-muted\\\\\\\"><$transclude field=\\\\\\\"description\\\\\\\"/></i>\\\\n</$draggable>\\\\n\\\"\\n }\\n }\\n}\"\r\n\r\n/**\r\n * Seed the \"发送给 Agent\" TW button exactly once per wiki (mirrors the doc-note\r\n * one-shot policy). The marker records the offer; afterwards the bundle is\r\n * user-owned — deleting it and restarting dsh web does NOT recreate it, and\r\n * edits are never overwritten. With `opts.force` the bundle is (re)written even\r\n * when it already exists and the marker is (re)written — the settings page uses\r\n * this for \"重新初始化\". Returns whether a bundle was written this call.\r\n * Never throws.\r\n */\r\nexport async function seedSendToAgent(client: TiddlyWebClient, opts?: { force?: boolean }): Promise<boolean> {\r\n const force = opts?.force === true\r\n if (!force) {\r\n const marker = await client.get(SEND_TO_AGENT_MARKER_TITLE).catch(() => undefined)\r\n if (marker !== undefined) return false\r\n }\r\n const existing = await client.get(SEND_TO_AGENT_PLUGIN_TITLE).catch(() => undefined)\r\n let wrote = false\r\n if (force || existing === undefined) {\r\n await client.put({\r\n title: SEND_TO_AGENT_PLUGIN_TITLE,\r\n text: SEND_TO_AGENT_BUNDLE_TEXT,\r\n type: 'application/json',\r\n tags: [],\r\n // TW only REGISTERS a wiki tiddler as a plugin (boot.js\r\n // registerPluginTiddlers) when the OUTER tiddler carries a\r\n // `plugin-type` field — without it the bundle is never unpacked and its\r\n // startup module never runs in the embedded TW. Mirrors what the live\r\n // wiki's send-to-agent bundle tiddler carries.\r\n 'plugin-type': 'plugin',\r\n name: 'Send to Agent',\r\n author: 'dsh-tiddlywiki',\r\n version: '0.3.2',\r\n description: '把当前笔记一键发送给 DSH Agent(TiddlyWiki → DSH 会话注入)',\r\n })\r\n wrote = true\r\n }\r\n // Record the offer regardless, so an existing bundle (upgrade from a\r\n // pre-seed wiki) also becomes user-owned from here on.\r\n await client\r\n .put({ title: SEND_TO_AGENT_MARKER_TITLE, text: 'seeded-once', type: 'text/plain', tags: [] })\r\n .catch(() => undefined)\r\n return wrote\r\n}\r\n","/**\r\n * Generated from the wiki home tiddlers (do not hand-edit the constants).\r\n * Source: 🏠 主页.tid, 所有标签.tid, 标签笔记.tid\r\n *\r\n * The \"首页\" tiddlers that the plugin's system prompt promises (待办四象限 +\r\n * 所有标签统计 + 标签笔记): seeded into fresh wikis by seedHomeIndex, so new\r\n * users get the same home page instead of an empty wiki. 🏠 主页 is the\r\n * default home (the seed also ensures $:/DefaultTiddlers → [[🏠 主页]]).\r\n *\r\n * @module dsh-tiddlywiki/host/seed-home\r\n */\r\nimport type { TiddlyWebClient } from './tw-api.ts'\r\n\r\n/** One-time marker: presence means \"the home was offered once — hands off\". */\r\nexport const HOME_INDEX_MARKER_TITLE = '$:/plugins/dsh-tiddlywiki/seed-home-index'\r\n\r\n/** The $:/DefaultTiddlers body so 🏠 主页 opens by default in fresh wikis. */\r\nexport const HOME_DEFAULT_TIDDLERS = \"[[🏠 主页]]\"\r\n\r\nexport interface HomeIndexItem {\r\n title: string\r\n tags: string[]\r\n type: string\r\n text: string\r\n}\r\n\r\n/** The home tiddlers, exactly as seeded (user-owned afterwards). */\r\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<div style=\\\"border:1px solid rgba(128,128,128,0.22); border-radius:10px; padding:8px 12px; background:linear-gradient(135deg,#e74c3c1a,transparent);\\\">\\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;\\\"><$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[]]\\\"><div style=\\\"color:#aaa; font-size:0.85em;\\\">(暂无)</div></$list>\\n</div>\\n<div style=\\\"border:1px solid rgba(128,128,128,0.22); border-radius:10px; padding:8px 12px; background:linear-gradient(135deg,#3498db1a,transparent);\\\">\\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;\\\"><$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[]]\\\"><div style=\\\"color:#aaa; font-size:0.85em;\\\">(暂无)</div></$list>\\n</div>\\n<div style=\\\"border:1px solid rgba(128,128,128,0.22); border-radius:10px; padding:8px 12px; background:linear-gradient(135deg,#f39c121a,transparent);\\\">\\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;\\\"><$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[]]\\\"><div style=\\\"color:#aaa; font-size:0.85em;\\\">(暂无)</div></$list>\\n</div>\\n<div style=\\\"border:1px solid rgba(128,128,128,0.22); border-radius:10px; padding:8px 12px; background:linear-gradient(135deg,#95a5a61a,transparent);\\\">\\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;\\\"><$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[]]\\\"><div style=\\\"color:#aaa; font-size:0.85em;\\\">(暂无)</div></$list>\\n</div>\\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<$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\\\">直接在首页写笔记:填标题、用工具栏写正文(支持 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\\\"/></></$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>todo</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<$list filter=\\\"[tag[todo]!tag[done]!has[q]count[]!match[0]]\\\">\\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></div>\\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;\\\"><$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</div>\\n</$list>\\n\\n</$set>\"},{\"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[]]\\\"><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[]]\\\"><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\"}]\r\n\r\n/**\r\n * Seed the home/index tiddlers exactly once per wiki (mirrors the doc-note\r\n * one-shot policy). Also writes $:/DefaultTiddlers → [[🏠 主页]] so the\r\n * new home opens by default. With `force` the tiddlers are overwritten with the\r\n * built-in content and the marker is (re)written — the settings page uses this\r\n * for \"重新初始化\". Returns whether anything was written this call. Never throws.\r\n */\r\nexport async function seedHomeIndex(client: TiddlyWebClient, opts?: { force?: boolean }): Promise<boolean> {\r\n const force = opts?.force === true\r\n if (!force) {\r\n const marker = await client.get(HOME_INDEX_MARKER_TITLE).catch(() => undefined)\r\n if (marker !== undefined) return false\r\n }\r\n let wrote = false\r\n for (const item of HOME_INDEX_ITEMS) {\r\n const existing = await client.get(item.title).catch(() => undefined)\r\n if (force || existing === undefined) {\r\n await client.put({ title: item.title, text: item.text, type: item.type, tags: item.tags })\r\n wrote = true\r\n }\r\n }\r\n // Ensure the default home page is 🏠 主页. A fresh wiki's $:/DefaultTiddlers is\r\n // the core shadow \"GettingStarted\" (or absent), so a first seed writes it;\r\n // a user-customised DefaultTiddlers is left alone unless force.\r\n const dt = await client.get('$:/DefaultTiddlers').catch(() => undefined)\r\n const dtText = typeof dt?.text === 'string' ? dt.text.trim() : ''\r\n if (force || dt === undefined || dtText === 'GettingStarted' || dtText === '[[GettingStarted]]') {\r\n await client.put({ title: '$:/DefaultTiddlers', text: HOME_DEFAULT_TIDDLERS, type: 'text/vnd.tiddlywiki', tags: [] })\r\n wrote = true\r\n }\r\n await client\r\n .put({ title: HOME_INDEX_MARKER_TITLE, text: 'seeded-once', type: 'text/plain', tags: [] })\r\n .catch(() => undefined)\r\n return wrote\r\n}\r\n\r\n/**\r\n * Un-seed (反初始化): remove the home tiddlers and their marker, restoring the\r\n * wiki's default home only when it still points at the seeded 🏠 主页\r\n * (a user-customised $:/DefaultTiddlers is left alone). Deletion is idempotent —\r\n * a tiddler already gone is not listed. Never throws.\r\n */\r\nexport async function unseedHomeIndex(client: TiddlyWebClient): Promise<{ removed: string[] }> {\r\n const removed: string[] = []\r\n for (const item of HOME_INDEX_ITEMS) {\r\n const t = await client.get(item.title).catch(() => undefined)\r\n if (t !== undefined) {\r\n await client.delete(item.title)\r\n removed.push(item.title)\r\n }\r\n }\r\n const marker = await client.get(HOME_INDEX_MARKER_TITLE).catch(() => undefined)\r\n if (marker !== undefined) {\r\n await client.delete(HOME_INDEX_MARKER_TITLE)\r\n removed.push(HOME_INDEX_MARKER_TITLE)\r\n }\r\n const dt = await client.get('$:/DefaultTiddlers').catch(() => undefined)\r\n if (dt !== undefined && typeof dt.text === 'string' && dt.text.trim() === \"[[🏠 主页]]\") {\r\n await client.put({ title: '$:/DefaultTiddlers', text: '[[GettingStarted]]', type: 'text/vnd.tiddlywiki', tags: [] })\r\n removed.push('$:/DefaultTiddlers')\r\n }\r\n return { removed }\r\n}\r\n","/**\r\n * The \"所有文章\" two-column paginated index page, seeded into wikis by\r\n * seedAllArticles. Source: 所有文章.tid (the wiki's live tiddler).\r\n *\r\n * The page lists every non-system, non-draft tiddler in two independently\r\n * paginated columns:\r\n * - 🤖 Agent 撰写: agent-written and not human-edited\r\n * - 👤 人工 / 人类: not agent-written, OR agent-written + human-edited\r\n * Page size comes live from the plugin config tiddler\r\n * ($:/plugins/dsh-tiddlywiki/config → ui.allArticles.pageSize, default 10).\r\n *\r\n * @module dsh-tiddlywiki/host/seed-all-articles\r\n */\r\nimport type { TiddlyWebClient } from './tw-api.ts'\r\n\r\n/** One-time marker: presence means \"the page was offered once — hands off\". */\r\nexport const ALL_ARTICLES_MARKER_TITLE = '$:/plugins/dsh-tiddlywiki/seed-all-articles'\r\n\r\n/** The 所有文章 page title. */\r\nexport const ALL_ARTICLES_TITLE = '所有文章'\r\n\r\n/** The page body, exactly as seeded (user-owned afterwards). */\r\nexport const ALL_ARTICLES_TEXT = `\\\\whitespace trim\r\n\r\n\\\\define page-size() [{$:/plugins/dsh-tiddlywiki/config}jsonget[ui],[allArticles],[pageSize]else[10]]\r\n\\\\define agent-list() [all[tiddlers]!is[system]!has[draft.of]!tag[索引]tag[agent-written]!tag[human-edited]!sort[modified]]\r\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]]\r\n\r\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;\">\r\n<$action-navigate $to=\"🏠 主页\"/>\r\n← 回主页\r\n</$button>\r\n\r\n!! 📚 所有文章\r\n\r\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>\r\n\r\n<div style=\"display:grid; grid-template-columns:1fr 1fr; gap:12px; margin-top:8px;\">\r\n<div style=\"border:1px solid rgba(128,128,128,0.22); border-radius:10px; padding:10px 12px;\">\r\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>\r\n<$set name=\"page\" tiddler=\"$:/state/dsh/all-articles/page/agent\" emptyValue=\"0\">\r\n<$set name=\"ps\" value={{{ [subfilter<page-size>] }}}>\r\n<$set name=\"total\" value={{{ [subfilter<agent-list>count[]] }}}>\r\n<$set name=\"last\" value={{{ [<total>] +[divide<ps>] +[ceil[0]] +[subtract[1]] +[max[0]] }}}>\r\n<$set name=\"pagec\" value={{{ [<page>] +[min<last>] +[max[0]] }}}>\r\n<$set name=\"offset\" value={{{ [<pagec>] +[multiply<ps>] }}}>\r\n<div>\r\n<$list filter=\"[subfilter<agent-list>] +[rest<offset>] +[first<ps>]\">\r\n<div style=\"display:flex; gap:6px; align-items:baseline; padding:3px 0; border-bottom:1px dashed rgba(128,128,128,0.15);\">\r\n<$link to=<<currentTiddler>> style=\"flex:1;\"><$view field=\"title\"/></$link>\r\n<span style=\"color:#aaa; font-size:0.82em;\"><$view field=\"modified\" format=\"relativedate\"/></span>\r\n</div>\r\n</$list>\r\n<$list filter=\"[subfilter<agent-list>count[]] +[match[0]]\"><div style=\"color:#aaa; font-size:0.85em;\">(暂无)</div></$list>\r\n</div>\r\n<$list filter=\"[<total>!match[0]]\">\r\n<div style=\"display:flex; gap:8px; align-items:center; margin-top:8px;\">\r\n<$list filter=\"[<pagec>compare:number:gt[0]]\">\r\n<$button class=\"tc-btn-invisible\" style=\"padding:2px 10px; border:1px solid rgba(128,128,128,0.35); border-radius:6px;\">\r\n<$action-setfield $tiddler=\"$:/state/dsh/all-articles/page/agent\" $field=\"text\" $value={{{ [<pagec>] +[subtract[1]] +[max[0]] }}}/>\r\n◀ 上一页\r\n</$button>\r\n</$list>\r\n<span style=\"color:#888; font-size:0.85em;\">第 {{{ [<pagec>] +[add[1]] }}} / {{{ [<total>] +[divide<ps>] +[ceil[0]] }}} 页</span>\r\n<$list filter=\"[<pagec>compare:number:lt<last>]\">\r\n<$button class=\"tc-btn-invisible\" style=\"padding:2px 10px; border:1px solid rgba(128,128,128,0.35); border-radius:6px;\">\r\n<$action-setfield $tiddler=\"$:/state/dsh/all-articles/page/agent\" $field=\"text\" $value={{{ [<pagec>] +[add[1]] +[min<last>] }}}/>\r\n下一页 ▶\r\n</$button>\r\n</$list>\r\n</div>\r\n</$list>\r\n</$set>\r\n</$set>\r\n</$set>\r\n</$set>\r\n</$set>\r\n</$set>\r\n</div>\r\n<div style=\"border:1px solid rgba(128,128,128,0.22); border-radius:10px; padding:10px 12px;\">\r\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>\r\n<$set name=\"page\" tiddler=\"$:/state/dsh/all-articles/page/human\" emptyValue=\"0\">\r\n<$set name=\"ps\" value={{{ [subfilter<page-size>] }}}>\r\n<$set name=\"total\" value={{{ [subfilter<human-list>count[]] }}}>\r\n<$set name=\"last\" value={{{ [<total>] +[divide<ps>] +[ceil[0]] +[subtract[1]] +[max[0]] }}}>\r\n<$set name=\"pagec\" value={{{ [<page>] +[min<last>] +[max[0]] }}}>\r\n<$set name=\"offset\" value={{{ [<pagec>] +[multiply<ps>] }}}>\r\n<div>\r\n<$list filter=\"[subfilter<human-list>] +[rest<offset>] +[first<ps>]\">\r\n<div style=\"display:flex; gap:6px; align-items:baseline; padding:3px 0; border-bottom:1px dashed rgba(128,128,128,0.15);\">\r\n<$link to=<<currentTiddler>> style=\"flex:1;\"><$view field=\"title\"/></$link>\r\n<span style=\"color:#aaa; font-size:0.82em;\"><$view field=\"modified\" format=\"relativedate\"/></span>\r\n</div>\r\n</$list>\r\n<$list filter=\"[subfilter<human-list>count[]] +[match[0]]\"><div style=\"color:#aaa; font-size:0.85em;\">(暂无)</div></$list>\r\n</div>\r\n<$list filter=\"[<total>!match[0]]\">\r\n<div style=\"display:flex; gap:8px; align-items:center; margin-top:8px;\">\r\n<$list filter=\"[<pagec>compare:number:gt[0]]\">\r\n<$button class=\"tc-btn-invisible\" style=\"padding:2px 10px; border:1px solid rgba(128,128,128,0.35); border-radius:6px;\">\r\n<$action-setfield $tiddler=\"$:/state/dsh/all-articles/page/human\" $field=\"text\" $value={{{ [<pagec>] +[subtract[1]] +[max[0]] }}}/>\r\n◀ 上一页\r\n</$button>\r\n</$list>\r\n<span style=\"color:#888; font-size:0.85em;\">第 {{{ [<pagec>] +[add[1]] }}} / {{{ [<total>] +[divide<ps>] +[ceil[0]] }}} 页</span>\r\n<$list filter=\"[<pagec>compare:number:lt<last>]\">\r\n<$button class=\"tc-btn-invisible\" style=\"padding:2px 10px; border:1px solid rgba(128,128,128,0.35); border-radius:6px;\">\r\n<$action-setfield $tiddler=\"$:/state/dsh/all-articles/page/human\" $field=\"text\" $value={{{ [<pagec>] +[add[1]] +[min<last>] }}}/>\r\n下一页 ▶\r\n</$button>\r\n</$list>\r\n</div>\r\n</$list>\r\n</$set>\r\n</$set>\r\n</$set>\r\n</$set>\r\n</$set>\r\n</$set>\r\n</div>\r\n</div>\r\n`\r\n\r\n/**\r\n * Seed the 所有文章 page exactly once per wiki (mirrors the doc-note one-shot\r\n * policy). With `opts.force` the page is overwritten with the built-in content\r\n * and the marker is (re)written — the settings page uses this for\r\n * \"重新初始化\". Returns whether the page was written this call. Never throws.\r\n */\r\nexport async function seedAllArticles(client: TiddlyWebClient, opts?: { force?: boolean }): Promise<boolean> {\r\n const force = opts?.force === true\r\n if (!force) {\r\n const marker = await client.get(ALL_ARTICLES_MARKER_TITLE).catch(() => undefined)\r\n if (marker !== undefined) return false\r\n }\r\n const existing = await client.get(ALL_ARTICLES_TITLE).catch(() => undefined)\r\n let wrote = false\r\n if (force || existing === undefined) {\r\n await client.put({ title: ALL_ARTICLES_TITLE, text: ALL_ARTICLES_TEXT, type: 'text/vnd.tiddlywiki', tags: ['索引'] })\r\n wrote = true\r\n }\r\n await client\r\n .put({ title: ALL_ARTICLES_MARKER_TITLE, text: 'seeded-once', type: 'text/plain', tags: [] })\r\n .catch(() => undefined)\r\n return wrote\r\n}\r\n\r\n/**\r\n * Un-seed (反初始化): remove the 所有文章 page and its one-shot marker.\r\n * Deletion is idempotent — a tiddler that was already gone is not listed.\r\n * Never throws.\r\n */\r\nexport async function unseedAllArticles(client: TiddlyWebClient): Promise<{ removed: string[] }> {\r\n const removed: string[] = []\r\n for (const title of [ALL_ARTICLES_TITLE, ALL_ARTICLES_MARKER_TITLE]) {\r\n const t = await client.get(title).catch(() => undefined)\r\n if (t !== undefined) {\r\n await client.delete(title)\r\n removed.push(title)\r\n }\r\n }\r\n return { removed }\r\n}\r\n","/**\r\n * The menubar theme override seed.\r\n *\r\n * The `tiddlywiki/menubar` plugin colours its top bar with\r\n * `<<colour menubar-background>>`. Most light palettes (Vanilla, Blanca, …) do\r\n * NOT define `menubar-background`, so the `colour` macro falls through to the\r\n * plugin's hardcoded `$:/config/DefaultColourMappings/` → `#5778d8` (blue) —\r\n * the menubar stays blue in every light theme no matter which palette is\r\n * active. Dark palettes that DO define it (e.g. CupertinoDark → #464646) look\r\n * fine, but light ones never adapt.\r\n *\r\n * The fix lives entirely on the TW side: a small stylesheet tiddler (tagged\r\n * `$:/tags/Stylesheet`) that pins the menubar's bar/items to the ACTIVE\r\n * palette's `background` / `foreground` with `!important`. TW re-renders all\r\n * stylesheets live whenever `$:/palette` changes, and the DSH theme-sync\r\n * (src/client/theme-sync.ts) flips `$:/palette` in-memory to follow the DSH\r\n * light/dark theme — so the menubar follows DSH automatically.\r\n *\r\n * Registered as the `menubar-theme` seed: ONE-SHOT (marker-gated, never\r\n * overwrites user edits) + force \"重新初始化\" from the settings page, exactly\r\n * like the other seeds.\r\n *\r\n * @module dsh-tiddlywiki/host/seed-menubar-theme\r\n */\r\nimport type { TiddlyWebClient } from './tw-api.ts'\r\n\r\n/** One-time marker: presence means \"the override was offered once — hands off\". */\r\nexport const MENUBAR_THEME_MARKER_TITLE = '$:/plugins/dsh-tiddlywiki/seed-menubar-theme'\r\n\r\n/** The stylesheet tiddler that adapts the menubar to the active palette. */\r\nexport const MENUBAR_THEME_TIDDLER = '$:/plugins/dsh-tiddlywiki/menubar-theme'\r\n\r\n/**\r\n * The override stylesheet body, exactly as seeded (user-owned afterwards).\r\n * `<<colour background>>` / `<<colour foreground>>` resolve against the ACTIVE\r\n * palette at render time; `!important` beats the menubar plugin's own rules\r\n * regardless of stylesheet ordering. `\\rules` mirrors the menubar plugin's own\r\n * styles.tid (macrocallinline needed for `<<colour>>`).\r\n */\r\nexport const MENUBAR_THEME_TEXT = `\\\\rules only filteredtranscludeinline transcludeinline macrodef macrocallinline\r\n\r\nnav.tc-menubar ul.tc-menubar-list {\r\n\tbackground: <<colour background>> !important;\r\n}\r\n\r\nnav.tc-menubar li.tc-menubar-item > a,\r\nnav.tc-menubar li.tc-menubar-item > button {\r\n\tcolor: <<colour foreground>> !important;\r\n\tfill: <<colour foreground>> !important;\r\n\tborder-radius: 6px !important;\r\n\ttransition: background-color 120ms ease, color 120ms ease;\r\n}\r\n\r\nnav.tc-menubar li.tc-menubar-item svg {\r\n\tfill: <<colour foreground>> !important;\r\n}\r\n\r\nnav.tc-menubar li.tc-menubar-item > a:hover,\r\nnav.tc-menubar li.tc-menubar-item > button:hover {\r\n\tbackground: color-mix(in srgb, <<colour foreground>> 12%, transparent) !important;\r\n\tcolor: <<colour foreground>> !important;\r\n\tfill: <<colour foreground>> !important;\r\n}\r\n\r\nnav.tc-menubar li.tc-menubar-item > a:active,\r\nnav.tc-menubar li.tc-menubar-item > button:active {\r\n\tbackground: color-mix(in srgb, <<colour foreground>> 20%, transparent) !important;\r\n\tcolor: <<colour foreground>> !important;\r\n\tfill: <<colour foreground>> !important;\r\n}\r\n\r\nnav.tc-menubar li.tc-menubar-item > a.tc-selected,\r\nnav.tc-menubar li.tc-menubar-item > button.tc-selected {\r\n\tbackground: color-mix(in srgb, <<colour foreground>> 16%, transparent) !important;\r\n\tcolor: <<colour foreground>> !important;\r\n\tfill: <<colour foreground>> !important;\r\n}\r\n\r\nnav.tc-menubar li.tc-menubar-item > a:focus-visible,\r\nnav.tc-menubar li.tc-menubar-item > button:focus-visible {\r\n\toutline: none !important;\r\n\tbox-shadow: none !important;\r\n}\r\n`\r\n\r\n/**\r\n * Seed the menubar theme override exactly once per wiki (mirrors the other\r\n * one-shot seeds). With `opts.force` the tiddler is overwritten with the\r\n * built-in content and the marker (re)written — the settings page uses this\r\n * for \"重新初始化\". Returns whether anything was written this call. Never throws.\r\n */\r\nexport async function seedMenubarTheme(client: TiddlyWebClient, opts?: { force?: boolean }): Promise<boolean> {\r\n const force = opts?.force === true\r\n if (!force) {\r\n const marker = await client.get(MENUBAR_THEME_MARKER_TITLE).catch(() => undefined)\r\n if (marker !== undefined) return false\r\n }\r\n const existing = await client.get(MENUBAR_THEME_TIDDLER).catch(() => undefined)\r\n let wrote = false\r\n if (force || existing === undefined) {\r\n await client.put({\r\n title: MENUBAR_THEME_TIDDLER,\r\n text: MENUBAR_THEME_TEXT,\r\n type: 'text/vnd.tiddlywiki',\r\n tags: ['$:/tags/Stylesheet'],\r\n })\r\n wrote = true\r\n }\r\n await client\r\n .put({ title: MENUBAR_THEME_MARKER_TITLE, text: 'seeded-once', type: 'text/plain', tags: [] })\r\n .catch(() => undefined)\r\n return wrote\r\n}\r\n\r\n/**\r\n * Un-seed (反初始化): remove the menubar override stylesheet and its marker,\r\n * returning the wiki to the pre-seed state (the tiddlywiki/menubar top bar\r\n * falls back to its original colour-mapping behaviour). Deletion is\r\n * idempotent — a tiddler already gone is not listed. Never throws.\r\n */\r\nexport async function unseedMenubarTheme(client: TiddlyWebClient): Promise<{ removed: string[] }> {\r\n const removed: string[] = []\r\n for (const title of [MENUBAR_THEME_TIDDLER, MENUBAR_THEME_MARKER_TITLE]) {\r\n const t = await client.get(title).catch(() => undefined)\r\n if (t !== undefined) {\r\n await client.delete(title)\r\n removed.push(title)\r\n }\r\n }\r\n return { removed }\r\n}\r\n","/**\r\n * Generated from the wiki bundle (do not hand-edit the constant).\r\n * Source: render.bundle.json\r\n *\r\n * The \"原生渲染路由\" TW plugin, packaged as a TiddlyWiki plugin bundle\r\n * (`{\"tiddlers\": {...}}`), seeded into fresh wikis by seedRenderRoute. It\r\n * registers a server-side route module (`server-routes/render.js`,\r\n * module-type: route) that renders wiki text into a pure HTML fragment with\r\n * internal links rewritten to the same-origin DSH proxy hash.\r\n *\r\n * @module dsh-tiddlywiki/host/seed-render\r\n */\r\nimport type { TiddlyWebClient } from './tw-api.ts'\r\n\r\n/** The packaged plugin tiddler title (a TW system tiddler, type application/json). */\r\nexport const RENDER_PLUGIN_TITLE = '$:/plugins/dsh/render'\r\n\r\n/** One-time marker: presence means \"the route was offered once — hands off\". */\r\nexport const RENDER_MARKER_TITLE = '$:/plugins/dsh-tiddlywiki/seed-render'\r\n\r\n/** The bundle's JSON text (`{\"tiddlers\": {...}}`), exactly as TW stores it. */\r\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.1.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 wikified body\\\\n { text, contextTitle?, parseAsInline? }\\\\n render arbitrary wiki text 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 wikified body (block parse, so\\\\n\\\\t\\\\t\\\\t// headings / tables / lists render natively).\\\\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 html = state.wiki.renderText(\\\\\\\"text/html\\\\\\\",\\\\\\\"text/vnd.tiddlywiki\\\\\\\",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).\\\\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 textHtml = state.wiki.renderText(\\\\\\\"text/html\\\\\\\",\\\\\\\"text/vnd.tiddlywiki\\\\\\\",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}\"\r\n\r\n/**\r\n * Seed the \"原生渲染路由\" TW plugin exactly once per wiki (mirrors the\r\n * send-to-agent one-shot policy). The marker records the offer; afterwards the\r\n * bundle is user-owned — deleting it and restarting dsh web does NOT recreate\r\n * it, and edits are never overwritten. With `opts.force` the bundle is\r\n * (re)written even when it already exists and the marker is (re)written — the\r\n * settings page uses this for \"重新初始化\". Returns whether a bundle was\r\n * written this call. Never throws.\r\n */\r\nexport async function seedRenderRoute(client: TiddlyWebClient, opts?: { force?: boolean }): Promise<boolean> {\r\n const force = opts?.force === true\r\n if (!force) {\r\n const marker = await client.get(RENDER_MARKER_TITLE).catch(() => undefined)\r\n if (marker !== undefined) return false\r\n }\r\n const existing = await client.get(RENDER_PLUGIN_TITLE).catch(() => undefined)\r\n let wrote = false\r\n if (force || existing === undefined) {\r\n await client.put({\r\n title: RENDER_PLUGIN_TITLE,\r\n text: RENDER_BUNDLE_TEXT,\r\n type: 'application/json',\r\n tags: [],\r\n // TW only REGISTERS a wiki tiddler as a plugin (boot.js\r\n // registerPluginTiddlers) when the OUTER tiddler carries a\r\n // `plugin-type` field — without it the bundle is never unpacked and its\r\n // server-routes module is never defined, so /render 404s. The metadata\r\n // mirrors what the live send-to-agent bundle's tiddler carries.\r\n 'plugin-type': 'plugin',\r\n name: 'DSH Wiki Render',\r\n author: 'dsh-tiddlywiki',\r\n version: '0.1.0',\r\n description: '把 wiki 文本原生渲染成 HTML 片段(POST /render 服务端路由),供 DSH 回复流工具卡与 wiki 链接跳转使用',\r\n })\r\n wrote = true\r\n }\r\n // Record the offer regardless, so an existing bundle (upgrade from a\r\n // pre-seed wiki) also becomes user-owned from here on.\r\n await client\r\n .put({ title: RENDER_MARKER_TITLE, text: 'seeded-once', type: 'text/plain', tags: [] })\r\n .catch(() => undefined)\r\n return wrote\r\n}\r\n","/**\r\n * Unified seed registry (design: every one-time \"与 dsh 联动需要 wiki 预置\"\r\n * item is a SeedDef here).\r\n *\r\n * Two tiers (v0.15.0):\r\n * - CORE seeds (`core: true`) are functionally required by the plugin's\r\n * own features — the startup path seeds exactly these (non-force).\r\n * - OPTIONAL seeds (`core: false`) are nice-to-have content (说明笔记 /\r\n * 首页 / 所有文章 / menubar 顶栏主题自适应) — they are NEVER auto-seeded;\r\n * the user opts in from the settings page「初始化」section (重新初始化)\r\n * and can opt out again with 反初始化 (remove).\r\n *\r\n * Each seed owns:\r\n * - `check` — current state (present / missing / needs-update) for the UI;\r\n * - `run(force)` — non-force keeps the ONE-SHOT / user-owned semantics\r\n * (write only when missing, never overwrite), force (re)writes the\r\n * built-in content and (re)records the marker;\r\n * - `remove` — optional seeds only: delete the seeded tiddlers + markers,\r\n * returning the wiki to the \"never seeded\" state.\r\n *\r\n * Registry: doc-note / send-to-agent / render-route / home-index /\r\n * all-articles / menubar-theme / tw-web-host.\r\n *\r\n * @module dsh-tiddlywiki/host/seeds\r\n */\r\nimport type { TiddlyWebClient } from './tw-api.ts'\r\nimport { seedDocNote, unseedDocNote, DOC_NOTE_TITLE } from './seed-notes.ts'\r\nimport { seedSendToAgent, SEND_TO_AGENT_PLUGIN_TITLE } from './seed-send-to-agent.ts'\r\nimport { seedHomeIndex, unseedHomeIndex, HOME_INDEX_ITEMS } from './seed-home.ts'\r\nimport { seedAllArticles, unseedAllArticles, ALL_ARTICLES_TITLE } from './seed-all-articles.ts'\r\nimport { seedMenubarTheme, unseedMenubarTheme, MENUBAR_THEME_TIDDLER } from './seed-menubar-theme.ts'\r\nimport { seedRenderRoute, RENDER_PLUGIN_TITLE } from './seed-render.ts'\r\nimport { TW_WEB_HOST_TIDDLER, TW_WEB_HOST_DEFAULT } from './config.ts'\r\nimport { TW_PROXY_PATH } from './wiki.ts'\r\n\r\n/** One seed's status as reported to the settings page. */\r\nexport interface SeedStatus {\r\n id: string\r\n title: string\r\n description: string\r\n /** true = target tiddler(s) present (or host value correct). */\r\n present: boolean\r\n /**\r\n * true = the seed can be 反初始化 (removed). Core seeds that the plugin's\r\n * own features depend on are never removable from the settings page.\r\n */\r\n removable: boolean\r\n /** Human detail, e.g. which tiddlers are missing. */\r\n detail?: string\r\n}\r\n\r\n/** Result of running one seed. */\r\nexport interface SeedRunResult {\r\n id: string\r\n ok: boolean\r\n /** true = something was written this call. */\r\n wrote: boolean\r\n detail?: string\r\n error?: string\r\n}\r\n\r\n/** Context a seed needs (client to the live TW server). */\r\nexport interface SeedContext {\r\n client: TiddlyWebClient\r\n}\r\n\r\n/** A registered seed: check current state + run (optionally force) + remove. */\r\nexport interface SeedDef {\r\n id: string\r\n title: string\r\n description: string\r\n /**\r\n * Core seeds are seeded automatically on startup (功能必需). Optional\r\n * seeds are manual-only from the settings page — never forced on users.\r\n */\r\n core: boolean\r\n check(ctx: SeedContext): Promise<SeedStatus>\r\n run(ctx: SeedContext, force: boolean): Promise<SeedRunResult>\r\n /** Optional seeds only: delete the seeded tiddlers + markers (反初始化). */\r\n remove?(ctx: SeedContext): Promise<SeedRunResult>\r\n}\r\n\r\nconst presentOf = (ctx: SeedContext, title: string): Promise<boolean> =>\r\n ctx.client.get(title).then((t) => t !== undefined).catch(() => false)\r\n\r\n/** Build the per-item detail from an unseed result. */\r\nconst removedDetail = (id: string, removed: string[]): SeedRunResult => ({\r\n id,\r\n ok: true,\r\n wrote: false,\r\n detail: removed.length > 0\r\n ? `已移除:${removed.join('、')}`\r\n : '本就不存在,无需移除',\r\n})\r\n\r\n/** Metadata shared by every seed and echoed in its status/result objects. */\r\ninterface SeedMeta {\r\n id: string\r\n title: string\r\n description: string\r\n core: boolean\r\n}\r\n\r\ntype SeedWriter = (client: TiddlyWebClient, opts?: { force?: boolean }) => Promise<boolean>\r\ntype SeedUnseeder = (client: TiddlyWebClient) => Promise<{ removed: string[] }>\r\n\r\n/**\r\n * Define a seed from a small spec, killing the id/title/description\r\n * triplication and the try/catch boilerplate every registry entry used to\r\n * repeat by hand:\r\n * - `presentTitle` → default check: is that tiddler present?\r\n * - `check` → custom presence check (overrides presentTitle).\r\n * - `write` → built-in content writer; true = (re)written this call.\r\n * - `run` → custom runner (overrides the standard write wrapper;\r\n * e.g. tw-web-host honors a user-chosen host value).\r\n * - `unseed` → remove the seeded tiddlers + markers (removable seeds).\r\n */\r\nfunction defineSeed(meta: SeedMeta, impl: {\r\n presentTitle?: string\r\n check?: (ctx: SeedContext) => Promise<SeedStatus>\r\n write?: SeedWriter\r\n run?: (ctx: SeedContext, force: boolean) => Promise<SeedRunResult>\r\n unseed?: SeedUnseeder\r\n}): SeedDef {\r\n const { id, title, description, core } = meta\r\n const removable = !core\r\n const check: SeedDef['check'] = impl.check ?? (async (ctx) => {\r\n const present = await presentOf(ctx, impl.presentTitle ?? '')\r\n return { id, title, description, present, removable, detail: present ? '已存在' : '缺失' }\r\n })\r\n const run: SeedDef['run'] = impl.run ?? (async (ctx, force) => {\r\n try {\r\n const wrote = await impl.write!(ctx.client, { force })\r\n return { id, ok: true, wrote, detail: wrote ? (force ? '已重新初始化' : '已写入') : (force ? '内容已是最新(未重写)' : '已存在,跳过') }\r\n } catch (err) {\r\n return { id, ok: false, wrote: false, error: err instanceof Error ? err.message : String(err) }\r\n }\r\n })\r\n const remove: SeedDef['remove'] = impl.unseed === undefined ? undefined : async (ctx) => {\r\n try {\r\n return removedDetail(id, (await impl.unseed!(ctx.client)).removed)\r\n } catch (err) {\r\n return { id, ok: false, wrote: false, error: err instanceof Error ? err.message : String(err) }\r\n }\r\n }\r\n return { id, title, description, core, check, run, ...(remove === undefined ? {} : { remove }) }\r\n}\r\n\r\n/** The full registry, in display order. */\r\nexport const SEED_DEFS: SeedDef[] = [\r\n defineSeed(\r\n { id: 'doc-note', title: '插件说明笔记', description: '「dsh-tiddlywiki 插件说明」——入门说明笔记(可选,设置页「初始化」手动写入;ONE-SHOT,用户可改可删)。', core: false },\r\n { presentTitle: DOC_NOTE_TITLE, write: seedDocNote, unseed: unseedDocNote },\r\n ),\r\n defineSeed(\r\n { id: 'send-to-agent', title: '「发送给 Agent」按钮', description: 'TW 笔记工具栏「发送给 Agent」按钮插件($:/plugins/dsh/send-to-agent)——把笔记一键注入 DSH 会话。', core: true },\r\n { presentTitle: SEND_TO_AGENT_PLUGIN_TITLE, write: seedSendToAgent },\r\n ),\r\n defineSeed(\r\n { id: 'render-route', title: '原生渲染路由(/render)', description: 'TW 服务端路由插件($:/plugins/dsh/render,server-routes/render.js)——把 wiki 文本在运行中的 TW 里原生渲染成 HTML 片段,回复流工具卡与 wiki 链接跳转依赖它。seed 写入后需重启 TW 使路由生效。', core: true },\r\n { presentTitle: RENDER_PLUGIN_TITLE, write: seedRenderRoute },\r\n ),\r\n defineSeed(\r\n { id: 'home-index', title: '首页(主页 / 所有标签 / 标签笔记)', description: '默认主页:四象限待办 + 「所有标签」「所有文章」入口;所有标签:标签统计 + Agent 区块(纯 Agent / Agent+人工);标签笔记:按标签浏览。系统提示承诺的首页由这里 seed,主页(🏠 主页)同时写入 $:/DefaultTiddlers。', core: false },\r\n {\r\n check: async (ctx) => {\r\n const missing: string[] = []\r\n for (const item of HOME_INDEX_ITEMS) {\r\n if (!(await presentOf(ctx, item.title))) missing.push(item.title)\r\n }\r\n return { id: 'home-index', title: '首页(主页 / 所有标签 / 标签笔记)', description: '默认主页:四象限待办 + 「所有标签」「所有文章」入口;所有标签:标签统计 + Agent 区块(纯 Agent / Agent+人工);标签笔记:按标签浏览。系统提示承诺的首页由这里 seed,主页(🏠 主页)同时写入 $:/DefaultTiddlers。', present: missing.length === 0, removable: true, detail: missing.length === 0 ? '已存在' : `缺失:${missing.join('、')}` }\r\n },\r\n write: seedHomeIndex,\r\n unseed: unseedHomeIndex,\r\n },\r\n ),\r\n defineSeed(\r\n { id: 'all-articles', title: '所有文章(两列分页总览)', description: '「所有文章」——全部条目分两列(🤖 Agent 撰写 / 👤 人工·人类)各自分页展示。每页条数取插件设置 ui.allArticles.pageSize(默认 10)。', core: false },\r\n { presentTitle: ALL_ARTICLES_TITLE, write: seedAllArticles, unseed: unseedAllArticles },\r\n ),\r\n defineSeed(\r\n { id: 'menubar-theme', title: 'menubar 顶栏主题自适应', description: '样式表覆盖($:/plugins/dsh-tiddlywiki/menubar-theme,tag $:/tags/Stylesheet)——把 tiddlywiki/menubar 顶栏从「默认色映射的蓝色」改为跟随当前 palette 的 background/foreground,随 DSH 主题切换($:/palette 翻转)自动换色。', core: false },\r\n { presentTitle: MENUBAR_THEME_TIDDLER, write: seedMenubarTheme, unseed: unseedMenubarTheme },\r\n ),\r\n defineSeed(\r\n { id: 'tw-web-host', title: 'TW 前端 API 基址(同源代理)', description: '把 $:/config/tiddlyweb/host 指向 DSH 同源代理,嵌入式 TW 才能经 DSH origin 访问(远程访问模式的前提)。', core: true },\r\n {\r\n check: async (ctx) => {\r\n let current: string | undefined\r\n try {\r\n current = (await ctx.client.get(TW_WEB_HOST_TIDDLER))?.text?.trim()\r\n } catch {\r\n current = undefined\r\n }\r\n const ok = current === TW_PROXY_PATH\r\n return { id: 'tw-web-host', title: 'TW 前端 API 基址(同源代理)', description: '把 $:/config/tiddlyweb/host 指向 DSH 同源代理,嵌入式 TW 才能经 DSH origin 访问(远程访问模式的前提)。', present: ok, removable: false, detail: ok ? `已指向 ${TW_PROXY_PATH}` : `当前:${current ?? '(缺失)'},应为 ${TW_PROXY_PATH}` }\r\n },\r\n run: async (ctx, force) => {\r\n try {\r\n let current: string | undefined\r\n try {\r\n current = (await ctx.client.get(TW_WEB_HOST_TIDDLER))?.text?.trim()\r\n } catch {\r\n current = undefined\r\n }\r\n // Non-force keeps the ensure semantics: write only when missing or still\r\n // the legacy default (a user override pointing elsewhere is honored).\r\n if (!force && current !== undefined && current !== TW_WEB_HOST_DEFAULT) {\r\n return { id: 'tw-web-host', ok: true, wrote: false, detail: '已指向自定义基址,未覆盖' }\r\n }\r\n await ctx.client.put({ title: TW_WEB_HOST_TIDDLER, text: TW_PROXY_PATH, type: 'text/plain', tags: [] })\r\n return { id: 'tw-web-host', ok: true, wrote: true, detail: force ? '已重新初始化(强制写回代理基址)' : '已写入代理基址' }\r\n } catch (err) {\r\n return { id: 'tw-web-host', ok: false, wrote: false, error: err instanceof Error ? err.message : String(err) }\r\n }\r\n },\r\n },\r\n ),\r\n]\r\n\r\n/** Check every seed, returning statuses in registry order. */\r\nexport async function checkAllSeeds(ctx: SeedContext): Promise<SeedStatus[]> {\r\n const out: SeedStatus[] = []\r\n for (const def of SEED_DEFS) {\r\n try {\r\n out.push(await def.check(ctx))\r\n } catch (err) {\r\n out.push({\r\n id: def.id,\r\n title: def.title,\r\n description: def.description,\r\n present: false,\r\n removable: !def.core,\r\n detail: `检查失败:${err instanceof Error ? err.message : String(err)}`,\r\n })\r\n }\r\n }\r\n return out\r\n}\r\n\r\n/**\r\n * Run one seed (or all, when id is undefined). Non-force = one-shot semantics;\r\n * force = manual \"重新初始化\" from the settings page. A manual run with no id\r\n * (重新初始化 all) covers every registry item, core and optional alike.\r\n */\r\nexport async function runSeedById(ctx: SeedContext, id: string | undefined, force: boolean): Promise<SeedRunResult[]> {\r\n const targets = id === undefined ? SEED_DEFS : SEED_DEFS.filter((d) => d.id === id)\r\n if (targets.length === 0) {\r\n return [{ id: id ?? '', ok: false, wrote: false, error: `unknown seed: ${id}` }]\r\n }\r\n const out: SeedRunResult[] = []\r\n for (const def of targets) {\r\n out.push(await def.run(ctx, force))\r\n }\r\n return out\r\n}\r\n\r\n/**\r\n * Startup path: seed ONLY the core items (功能必需:发送给 Agent 按钮 +\r\n * TW 前端 API 基址), non-force (write only what is missing). Optional seeds\r\n * (说明笔记 / 首页 / 所有文章 / menubar 顶栏主题自适应) are never forced on\r\n * users — they opt in from the settings page「初始化」section.\r\n */\r\nexport async function runAllSeeds(ctx: SeedContext): Promise<SeedRunResult[]> {\r\n const out: SeedRunResult[] = []\r\n for (const def of SEED_DEFS) {\r\n if (!def.core) continue\r\n out.push(await def.run(ctx, false))\r\n }\r\n return out\r\n}\r\n\r\n/**\r\n * 反初始化 (remove): delete one optional seed's seeded tiddlers + markers, or\r\n * all optional seeds when `id` is undefined. Core seeds are functionally\r\n * required and cannot be removed — a direct request for one returns an error\r\n * result (and is skipped when removing all).\r\n */\r\nexport async function removeSeedById(ctx: SeedContext, id: string | undefined): Promise<SeedRunResult[]> {\r\n if (id === undefined) {\r\n const out: SeedRunResult[] = []\r\n for (const def of SEED_DEFS) {\r\n if (def.core || def.remove === undefined) continue\r\n out.push(await def.remove(ctx))\r\n }\r\n return out\r\n }\r\n const def = SEED_DEFS.find((d) => d.id === id)\r\n if (def === undefined) {\r\n return [{ id, ok: false, wrote: false, error: `unknown seed: ${id}` }]\r\n }\r\n if (def.core) {\r\n return [{ id, ok: false, wrote: false, error: '该 seed 为核心项(功能必需),不可反初始化' }]\r\n }\r\n if (def.remove === undefined) {\r\n return [{ id, ok: false, wrote: false, error: '该 seed 不支持移除' }]\r\n }\r\n return [await def.remove(ctx)]\r\n}\r\n","/**\r\n * TiddlyWeb REST client (design doc §5) — the ONLY way every writer reaches\r\n * the wiki (quick notes, agent tools, editor saves all go through the TW\r\n * service, D1), so there is never a second write path.\r\n *\r\n * ROUTES ARE EMPIRICALLY VERIFIED against tiddlywiki 5.4.1's core-server\r\n * (`core-server/server/routes/`):\r\n * GET /recipes/default/tiddlers.json[?exclude=...] list (skinny)\r\n * GET /recipes/default/tiddlers/<title> read one (404 absent)\r\n * PUT /recipes/default/tiddlers/<title> write one (204)\r\n * DELETE /bags/default/tiddlers/<title> delete one (204)\r\n * Writes require the `X-Requested-With: TiddlyWiki` header (TW CSRF), which\r\n * this client always sends. Tags arrive as a whitespace-joined STRING and are\r\n * normalized to arrays here.\r\n *\r\n * SEARCH (R2): the server blocks arbitrary `filter=` queries with 403 unless\r\n * the exact filter is whitelisted in $:/config/Server/ExternalFilters. So\r\n * `search()` fetches the TEXT-BEARING listing WITH text (TEXT_LIST_FILTER +\r\n * `?exclude=` a sentinel, whitelist self-healed on first 403) and matches\r\n * locally — one request, no 403, no per-tiddler round-trips, and binary\r\n * tiddlers (images etc.) never ship their base64 payload (v0.16.20).\r\n *\r\n * @module dsh-tiddlywiki/host/tw-api\r\n */\r\n\r\n/** A tiddler's readable fields (loose on purpose). */\r\nexport interface Tiddler {\r\n title: string\r\n text?: string\r\n tags?: string[]\r\n type?: string\r\n created?: string\r\n modified?: string\r\n /** Extra custom fields returned by the server are folded under `fields`. */\r\n fields?: Record<string, unknown>\r\n [key: string]: unknown\r\n}\r\n\r\nconst REQUEST_TIMEOUT_MS = 10_000\r\n\r\n/** TW's CSRF gate: writes must carry this header (TW's own UI always does). */\r\nconst CSRF_HEADER = { 'x-requested-with': 'TiddlyWiki' }\r\n\r\n/** Sentinel `exclude` value: excludes nothing, so `text` stays in the list. */\r\nconst LIST_WITH_TEXT_EXCLUDE = '__dsh_tw_none__'\r\n\r\n/**\r\n * Recipe-list filter that keeps ONLY text-bearing tiddlers (notes): no `type`\r\n * field at all, or a `text/*` type. Binary tiddlers (images/audio/video/fonts/\r\n * PDF/zip…) carry their payload as a base64 `text` field — on a big wiki (e.g.\r\n * thousands of scanned book pages) the full listing serializes 500+MB of base64\r\n * and times every agent read out. This filter drops them SERVER-SIDE so the\r\n * listing stays ~6MB (v0.16.20, verified on a 2418-tiddler wiki:\r\n * 515MB/17s → 6MB/0.3s).\r\n *\r\n * TW 5.4.1 filter facts that matter here (all verified empirically):\r\n * - `prefix`/`match` operators match the TITLE only — field matching needs\r\n * `regexp:<field>` / `field:<field>`.\r\n * - `[has[type]]` = \"has a non-empty type field\"; `[has:type[]]` is a DIFFERENT\r\n * call (suffix \"type\" + empty operand) that matches everything.\r\n * - space-separated operations UNION (no prefix = \"or\"); `+` means\r\n * intersection.\r\n * - negated `regexp:type`/`field:type` DROP type-less tiddlers (their field\r\n * string is null), so \"exclude binary\" must be written as the positive\r\n * \"no type OR text-ish\" union below.\r\n *\r\n * FILENAME BUDGET (Windows, verified): the whitelist tiddler below is stored\r\n * as a .tid file whose name contains the ENTIRE filter string, so the filter\r\n * must stay short — a ~180-char filter produced a ~217-char filename that\r\n * broke `git add` on Windows (\"Filename too long\", MAX_PATH) and killed the\r\n * auto-committer. 86 chars keeps the file at ~123 chars even on a deep wiki\r\n * path. (Consequence: application/json tiddlers are not searchable — on this\r\n * wiki there are none, and they are config/data tiddlers, not notes.)\r\n */\r\nexport const TEXT_LIST_FILTER = [\r\n '[all[tiddlers]!is[system]!has[type]]',\r\n '[all[tiddlers]!is[system]regexp:type[(?i)^text/]]',\r\n].join(' ')\r\n\r\n/** TiddlyWeb blocks every non-default filter with 403 unless the EXACT filter\r\n * string is whitelisted at `$:/config/Server/ExternalFilters/<filter>` = \"yes\".\r\n * This client writes that tiddler once on 403 (self-heal, idempotent) and\r\n * retries; the tiddler lives in the wiki and travels with its git history. */\r\nfunction externalFilterWhitelistTitle(filter: string): string {\r\n return `$:/config/Server/ExternalFilters/${filter}`\r\n}\r\n\r\n/** Binary MIME type prefixes — their `text` field is base64 payload, not content. */\r\nconst BINARY_TYPE_PREFIXES = ['image/', 'audio/', 'video/', 'font/']\r\nconst BINARY_TYPE_EXACT = new Set([\r\n 'application/octet-stream', 'application/pdf', 'application/zip', 'application/gzip',\r\n 'application/x-gzip', 'application/x-7z-compressed', 'application/x-rar-compressed',\r\n 'application/epub+zip', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\r\n])\r\n\r\n/** True when a tiddler's `type` marks it as binary (base64 in `text`). */\r\nexport function isBinaryType(type: string | undefined): boolean {\r\n if (typeof type !== 'string' || type.length === 0) return false\r\n return BINARY_TYPE_PREFIXES.some((p) => type.startsWith(p)) || BINARY_TYPE_EXACT.has(type)\r\n}\r\n\r\n/** Split TW's whitespace-joined tags string into an array. */\r\nfunction normalizeTags(tags: unknown): string[] | undefined {\r\n if (tags === undefined) return undefined\r\n if (Array.isArray(tags)) return tags.map(String)\r\n if (typeof tags === 'string') {\r\n const parts = tags.trim().split(/\\s+/).filter(Boolean)\r\n return parts.length > 0 ? parts : []\r\n }\r\n return []\r\n}\r\n\r\n/** Normalize a raw server tiddler (tags string → array, unknown fields nested). */\r\nfunction normalizeTiddler(raw: Record<string, unknown>): Tiddler {\r\n const out = { ...raw } as Tiddler\r\n const tags = normalizeTags(raw.tags)\r\n if (tags !== undefined) out.tags = tags\r\n return out\r\n}\r\n\r\nexport class TiddlyWebClient {\r\n constructor(private readonly baseUrl: string) {}\r\n\r\n private async request(path: string, init?: RequestInit): Promise<Response> {\r\n return fetch(`${this.baseUrl}${path}`, {\r\n ...init,\r\n signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),\r\n })\r\n }\r\n\r\n /** GET /status → { username, anonymous, space, tiddlywiki_version, ... }. */\r\n async status(): Promise<Record<string, unknown>> {\r\n const res = await this.request('/status')\r\n if (!res.ok) throw new Error(`TiddlyWeb /status HTTP ${res.status}`)\r\n return res.json() as Promise<Record<string, unknown>>\r\n }\r\n\r\n /** Read one tiddler; undefined when it does not exist (404). */\r\n async get(title: string): Promise<Tiddler | undefined> {\r\n const res = await this.request(`/recipes/default/tiddlers/${encodeURIComponent(title)}`)\r\n if (res.status === 404) return undefined\r\n if (!res.ok) throw new Error(`TiddlyWeb GET /recipes/default/tiddlers/${title} HTTP ${res.status}`)\r\n return normalizeTiddler((await res.json()) as Record<string, unknown>)\r\n }\r\n\r\n /** Write (create or overwrite) one tiddler via PUT (204 on success). */\r\n async put(tiddler: Tiddler): Promise<Tiddler> {\r\n const title = tiddler.title\r\n const res = await this.request(`/recipes/default/tiddlers/${encodeURIComponent(title)}`, {\r\n method: 'PUT',\r\n headers: { 'content-type': 'application/json', ...CSRF_HEADER },\r\n body: JSON.stringify(tiddler),\r\n })\r\n if (!res.ok) {\r\n const detail = await res.text().catch(() => '')\r\n throw new Error(`TiddlyWeb PUT /recipes/default/tiddlers/${title} HTTP ${res.status}: ${detail.slice(0, 300)}`)\r\n }\r\n return tiddler\r\n }\r\n\r\n /** Delete one tiddler via the bags route (204); a missing one is a no-op. */\r\n async delete(title: string): Promise<void> {\r\n const res = await this.request(`/bags/default/tiddlers/${encodeURIComponent(title)}`, {\r\n method: 'DELETE',\r\n headers: CSRF_HEADER,\r\n })\r\n if (res.status === 404) return\r\n if (!res.ok) throw new Error(`TiddlyWeb DELETE /bags/default/tiddlers/${title} HTTP ${res.status}`)\r\n }\r\n\r\n /**\r\n * List tiddlers via the default server filter. Arbitrary `filter=` queries\r\n * are blocked by the server (403) unless whitelisted, so callers needing a\r\n * subset should use search(); a supplied filter that is 403-blocked falls\r\n * back to the text-bearing filter.\r\n *\r\n * `includeText=true` returns only TEXT-BEARING tiddlers WITH their `text`\r\n * (never the base64 payloads of binary tiddlers): on 403 the whitelist\r\n * tiddler for TEXT_LIST_FILTER is written once and the request retried\r\n * (self-heal); if that write fails (read-only wiki) it degrades to the\r\n * skinny listing (title/tags matchable, empty snippets).\r\n */\r\n async list(filter?: string, includeText = false): Promise<Tiddler[]> {\r\n if (!includeText) return this.fetchListing(filter)\r\n const explicit = filter !== undefined && filter.length > 0\r\n const target = explicit ? (filter as string) : TEXT_LIST_FILTER\r\n let res = await this.requestListWithText(target)\r\n if (res.status === 403) {\r\n await this.ensureExternalFilterWhitelist(TEXT_LIST_FILTER).catch(() => undefined)\r\n res = await this.requestListWithText(TEXT_LIST_FILTER)\r\n }\r\n if (res.status === 403) return this.fetchListing(undefined)\r\n if (!res.ok) throw new Error(`TiddlyWeb recipe list HTTP ${res.status}`)\r\n return this.parseList(res)\r\n }\r\n\r\n /** GET the recipe listing WITHOUT text payloads (server default excludes `text`). */\r\n private async fetchListing(filter?: string): Promise<Tiddler[]> {\r\n const params = new URLSearchParams()\r\n if (filter !== undefined && filter.length > 0) params.set('filter', filter)\r\n const query = params.toString()\r\n let res = await this.request(`/recipes/default/tiddlers.json${query.length > 0 ? `?${query}` : ''}`)\r\n if (!res.ok && res.status === 403 && filter !== undefined && filter.length > 0) {\r\n // Filter not whitelisted → refetch with the default (whitelisted) filter.\r\n res = await this.request('/recipes/default/tiddlers.json')\r\n }\r\n if (!res.ok) throw new Error(`TiddlyWeb recipe list HTTP ${res.status}`)\r\n return this.parseList(res)\r\n }\r\n\r\n /** GET the recipe listing WITH text for a specific (whitelisted) filter. */\r\n private async requestListWithText(filter: string): Promise<Response> {\r\n return this.request(`/recipes/default/tiddlers.json?filter=${encodeURIComponent(filter)}&exclude=${LIST_WITH_TEXT_EXCLUDE}`)\r\n }\r\n\r\n /** Write `$:/config/Server/ExternalFilters/<filter>` = \"yes\" (idempotent). */\r\n private async ensureExternalFilterWhitelist(filter: string): Promise<void> {\r\n await this.put({ title: externalFilterWhitelistTitle(filter), text: 'yes' })\r\n }\r\n\r\n private async parseList(res: Response): Promise<Tiddler[]> {\r\n const data = (await res.json()) as Array<Record<string, unknown>> | { tiddlers?: Array<Record<string, unknown>> }\r\n const items = Array.isArray(data) ? data : (data.tiddlers ?? [])\r\n return items.map(normalizeTiddler)\r\n }\r\n\r\n /**\r\n * Search text-bearing tiddlers: one request (text-bearing listing with text)\r\n * plus local case-insensitive substring matching on title + text, optional\r\n * exact tags (AND), a `since` modified-time floor, an exact `type`, capped at\r\n * `limit`. Robust against the server's external-filter 403 (whitelist is\r\n * self-healed). Binary tiddlers (images/audio/…) are NOT in the listing, so\r\n * they can never match — a deliberate flood guard on big wikis.\r\n */\r\n async search(query: string, options: SearchOptions = {}): Promise<{ items: Tiddler[]; total: number }> {\r\n const items = await this.list(undefined, true)\r\n const needle = query.toLowerCase()\r\n const sinceTime = parseSince(options.since)\r\n const wantedTags = [...(options.tags ?? []), options.tag].filter((t): t is string => typeof t === 'string' && t.trim().length > 0)\r\n const limit = options.limit ?? 30\r\n const matched = items.filter((t) => {\r\n if (t.title.startsWith('$:/')) return false\r\n if (!t.title.toLowerCase().includes(needle) && !(t.text ?? '').toLowerCase().includes(needle)) return false\r\n if (sinceTime !== undefined) {\r\n const modified = typeof t.modified === 'string' ? new Date(t.modified).getTime() : NaN\r\n if (Number.isNaN(modified) || modified < sinceTime) return false\r\n }\r\n if (options.type !== undefined && options.type.length > 0 && (t.type ?? 'text/vnd.tiddlywiki') !== options.type) return false\r\n if (wantedTags.length > 0) {\r\n const tags = (t.tags ?? []).map((tag) => tag.toLowerCase())\r\n if (!wantedTags.every((w) => tags.includes(w.toLowerCase()))) return false\r\n }\r\n return true\r\n })\r\n return { items: matched.slice(0, limit), total: matched.length }\r\n }\r\n\r\n /**\r\n * List the most recently modified TEXT-BEARING (non-system, non-binary)\r\n * tiddlers, newest first (missing/modified-less tiddlers sort to the tail).\r\n * `since` keeps only tiddlers modified at/after that instant. Binary\r\n * attachments never appear — a book import touching hundreds of images must\r\n * not crowd the \"最近修改\" list.\r\n */\r\n async recent(limit = 15, since?: string): Promise<Tiddler[]> {\r\n const items = await this.list(undefined, true)\r\n const sinceTime = parseSince(since)\r\n const filtered = items.filter((t) => {\r\n if (t.title.startsWith('$:/')) return false\r\n if (sinceTime !== undefined) {\r\n const modified = typeof t.modified === 'string' ? new Date(t.modified).getTime() : NaN\r\n if (Number.isNaN(modified) || modified < sinceTime) return false\r\n }\r\n return true\r\n })\r\n filtered.sort((a, b) => {\r\n const am = typeof a.modified === 'string' ? new Date(a.modified).getTime() : 0\r\n const bm = typeof b.modified === 'string' ? new Date(b.modified).getTime() : 0\r\n return bm - am\r\n })\r\n return filtered.slice(0, Math.max(1, Math.min(limit, 200)))\r\n }\r\n\r\n /**\r\n * Distinct non-system tags with their tiddler counts, most-used first then\r\n * zh-locale. One skinny listing request (no text payloads).\r\n */\r\n async listTags(): Promise<Array<{ tag: string; count: number }>> {\r\n const items = await this.list(undefined, false)\r\n const map = new Map<string, number>()\r\n for (const t of items) {\r\n if (t.title.startsWith('$:/')) continue\r\n for (const tag of t.tags ?? []) {\r\n if (tag.startsWith('$:/')) continue\r\n map.set(tag, (map.get(tag) ?? 0) + 1)\r\n }\r\n }\r\n return [...map.entries()]\r\n .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0], 'zh'))\r\n .map(([tag, count]) => ({ tag, count }))\r\n }\r\n}\r\n\r\n/** Optional search / recent filters. */\r\nexport interface SearchOptions {\r\n /** Every tag in this list must be present (AND semantics). */\r\n tags?: string[]\r\n /** Legacy single exact tag (AND with `tags`). */\r\n tag?: string\r\n /** ISO 8601 instant; keep only tiddlers modified at/after it. */\r\n since?: string\r\n /** Exact tiddler type (default type is \"text/vnd.tiddlywiki\"). */\r\n type?: string\r\n /** Max results (search default 30, capped 200). */\r\n limit?: number\r\n}\r\n\r\n/** Parse a `since` value into an epoch ms, or undefined when absent/invalid. */\r\nfunction parseSince(since: string | undefined): number | undefined {\r\n if (typeof since !== 'string' || since.trim().length === 0) return undefined\r\n const ms = new Date(since.trim()).getTime()\r\n return Number.isNaN(ms) ? undefined : ms\r\n}\r\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 readonly schema: { readonly type: 'json' }\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 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","/**\r\n * The `tiddlywiki_*` agent tools (design doc §11, D8) plus the extension\r\n * point: `registerTiddlywikiTools(ctx, deps)` registers tools list-style, so a\r\n * new tool is just one more `defineTool` in the array — index.ts never changes.\r\n *\r\n * Toolset (v0.5):\r\n * search / get / put / batch_put / rename / delete / recent / list_tags /\r\n * git_sync / git_resolve\r\n *\r\n * RENDER CONTRACT (design doc §4.3): the registry feeds `output.render(args,\r\n * value)` into the loop — the model sees ONLY the rendered text, never the raw\r\n * JSON `value`. Every render must carry the complete facts an agent needs to\r\n * act (titles, tags, snippets, git state); a terse UI summary starves it.\r\n *\r\n * @module dsh-tiddlywiki/host/tools\r\n */\r\nimport { defineTool } from '../sdk.ts'\r\nimport { isBinaryType } from './tw-api.ts'\r\nimport type { TiddlyWebClient, Tiddler } from './tw-api.ts'\r\nimport type { GitFace } from './git.ts'\r\n\r\n/**\r\n * 约定标签:标记「由 Agent 撰写」的笔记。\r\n * 新建(title 不存在)时由 tiddlywiki_put / tiddlywiki_batch_put 自动补打;\r\n * 首页据此把这类笔记单独列在「Agent 区块」并从主标签列表排除。\r\n */\r\nexport const AGENT_WRITTEN_TAG = 'agent-written'\r\n\r\n/**\r\n * 约定标签:标记「Agent 撰写后又经人类编辑」的笔记(双标签分级第二档)。\r\n * 人类在编辑某篇 Agent 笔记时手动补打,首页把这类笔记归入「Agent + 人工」档。\r\n */\r\nexport const HUMAN_EDITED_TAG = 'human-edited'\r\n\r\n/**\r\n * Agent 写入的默认内容类型。agent 正文按约定是 Markdown,而 TW 对无 type 的\r\n * tiddler 按 wikitext(text/vnd.tiddlywiki)解析——`##`/`**` 之类原样显示,显示\r\n * 解析全坏(v0.16.15 起 put/batch_put 自动补该默认;wiki 已启用 tiddlywiki/markdown)。\r\n */\r\nexport const DEFAULT_NOTE_TYPE = 'text/markdown'\r\n\r\n/** Structural tool-registry face (subset of the dsh tools service). */\r\nexport interface ToolsCtx {\r\n tools: { register(tool: unknown): () => void }\r\n}\r\n\r\nexport interface ToolsDeps {\r\n /** Lazy TW client — undefined while the service is not up. */\r\n wiki: () => TiddlyWebClient | undefined\r\n git: GitFace\r\n wikiPath: () => string\r\n noteTag: () => string\r\n /** Debounced auto-commit touch (fires after our writes). */\r\n autoCommit: () => void\r\n /** Restart the TW child (same port). Called after a pull that changed the\r\n * working tree, so the server drops its stale in-memory snapshot and the\r\n * agent sees the pulled content. Optional — absent in headless contexts. */\r\n restartWiki?: () => Promise<void>\r\n}\r\n\r\nfunction snippetOf(text: string, max = 160): string {\r\n const flat = text.replace(/\\s+/g, ' ').trim()\r\n return flat.length <= max ? flat : `${flat.slice(0, max)}…`\r\n}\r\n\r\n/** Strip dsh-tiddlywiki internal fields from a tiddler for the model. */\r\nfunction pickFields(t: Tiddler): Record<string, unknown> {\r\n const out: Record<string, unknown> = {}\r\n for (const [k, v] of Object.entries(t)) {\r\n if (k === 'title' || k === 'text' || k === 'tags') continue\r\n out[k] = v\r\n }\r\n return out\r\n}\r\n\r\n/** A put-ready copy of a tiddler (no created/modified, tags as array). */\r\nfunction cleanTiddler(t: Tiddler): Tiddler {\r\n const out: Tiddler = { title: t.title, text: t.text ?? '', tags: t.tags ?? [] }\r\n if (typeof t.type === 'string') out.type = t.type\r\n for (const [k, v] of Object.entries(t)) {\r\n if (k === 'title' || k === 'text' || k === 'tags' || k === 'type' || k === 'created' || k === 'modified' || k === 'fields') continue\r\n out[k] = v\r\n }\r\n return out\r\n}\r\n\r\n/**\r\n * Rewrite TiddlyWiki references to a title inside wiki text: `[[Title]]`,\r\n * `[[display|Title]]`, `{{Title}}` → the new title. Best-effort link/text\r\n * migration for tiddlywiki_rename; returns the rewritten text + hit count.\r\n */\r\nfunction rewriteRefs(text: string, oldTitle: string, newTitle: string): { text: string; count: number } {\r\n if (oldTitle.length === 0) return { text, count: 0 }\r\n const escaped = oldTitle.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\r\n const re = new RegExp(`(\\\\[\\\\[[^\\\\]|]*\\\\|)${escaped}(\\\\]\\\\])|(\\\\[\\\\[)${escaped}(\\\\]\\\\])|(\\\\{\\\\{)${escaped}(\\\\}\\\\})`, 'g')\r\n let count = 0\r\n const out = text.replace(re, (...args: Array<string | number>) => {\r\n const p = args as string[]\r\n count++\r\n const prefix = p[1] ?? p[3] ?? p[5] ?? ''\r\n const suffix = p[2] ?? p[4] ?? p[6] ?? ''\r\n return `${prefix}${newTitle}${suffix}`\r\n })\r\n return { text: out, count }\r\n}\r\n\r\n/**\r\n * Compute the final tags for a write:\r\n * - NEW tiddler (created by this tool) → auto-append the agent-written tag,\r\n * unless it is a `$:/` system tiddler (config/language/theme internals).\r\n * - Existing tiddler → keep the caller's tags as-is (an agent maintaining a\r\n * human note must NOT silently claim authorship; an already agent-written\r\n * note keeps its tag because the caller provides the full tag list).\r\n */\r\nfunction finalTagsForWrite(title: string, existing: Tiddler | undefined, tags: string[]): string[] {\r\n if (existing !== undefined) return tags\r\n if (title.startsWith('$:/')) return tags\r\n if (tags.includes(AGENT_WRITTEN_TAG)) return tags\r\n return [...tags, AGENT_WRITTEN_TAG]\r\n}\r\n\r\n/**\r\n * Compute the final content type for a write (mutates the tiddler in place):\r\n * - explicit `type` (normally via fields) wins — untouched;\r\n * - `$:/` system tiddlers keep TW's own default (config/plugin internals must\r\n * not be force-marked as markdown);\r\n * - everything else defaults to Markdown (DEFAULT_NOTE_TYPE) — agent notes are\r\n * written in Markdown and TW would otherwise parse them as wikitext.\r\n */\r\nfunction finalTypeForWrite(title: string, tiddler: Tiddler): { defaulted: boolean } {\r\n if (typeof tiddler.type === 'string' && tiddler.type.length > 0) return { defaulted: false }\r\n if (title.startsWith('$:/')) return { defaulted: false }\r\n tiddler.type = DEFAULT_NOTE_TYPE\r\n return { defaulted: true }\r\n}\r\n\r\nexport function registerTiddlywikiTools(ctx: ToolsCtx, deps: ToolsDeps): Array<() => void> {\r\n const disposers: Array<() => void> = []\r\n const register = (tool: unknown): void => { disposers.push(ctx.tools.register(tool)) }\r\n\r\n /** Every read/write tool needs a live TW client — shared guard for the 8\r\n * wiki-facing tools (git tools operate on the repo path instead). */\r\n const requireWiki = (): TiddlyWebClient => {\r\n const wiki = deps.wiki()\r\n if (wiki === undefined) throw new Error('TiddlyWiki 服务未运行(tiddlywiki_status 可查)')\r\n return wiki\r\n }\r\n\r\n // ── tiddlywiki_search ────────────────────────────────────────────────────\r\n register(defineTool({\r\n name: 'tiddlywiki_search',\r\n description: '检索 TiddlyWiki 持久知识库:按关键词(可选 tags 数组 / since 修改时间 / type / limit)搜索非系统 tiddler,返回标题、标签、修改时间与摘要片段。二进制 tiddler(图片等附件,正文为 base64)不参与检索。',\r\n parameters: {\r\n query: { type: 'string', description: '搜索关键词(大小写不敏感,子串匹配)', required: true },\r\n tags: { type: 'array', items: { type: 'string' }, description: '可选:要求同时包含的标签(AND)' },\r\n tag: { type: 'string', description: '可选:单个精确标签(与 tags 同为 AND)' },\r\n since: { type: 'string', description: '可选:ISO 时间(如 2026-09-01 或 2026-09-01T00:00:00Z),只返回修改时间不早于它的 tiddler' },\r\n type: { type: 'string', description: '可选:精确 tiddler 类型(默认 text/vnd.tiddlywiki)' },\r\n limit: { type: 'integer', description: '可选:返回条数上限(默认 30,最大 200)' },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args, value: SearchResult) => {\r\n const filters: string[] = []\r\n if (value.tags.length > 0) filters.push(`tags=${value.tags.join(',')}`)\r\n if (value.since !== null) filters.push(`since=${value.since}`)\r\n if (value.type !== null) filters.push(`type=${value.type}`)\r\n const head = `TiddlyWiki 搜索「${value.query}」${filters.length > 0 ? ` (${filters.join(' · ')})` : ''}:命中 ${value.total} 条。`\r\n const lines = [head]\r\n if (value.results.length === 0) lines.push('没有匹配的 tiddler。')\r\n for (const r of value.results) {\r\n const tags = r.tags.length > 0 ? ` [${r.tags.join(', ')}]` : ''\r\n const modified = r.modified !== null ? ` (${r.modified})` : ''\r\n lines.push(`- ${r.title}${tags}${modified}`)\r\n if (r.snippet.length > 0) lines.push(` ${r.snippet}`)\r\n }\r\n if (value.total > value.results.length) lines.push(`(另有 ${value.total - value.results.length} 条未展开,可用 tiddlywiki_get 读取具体标题,或提高 limit)`)\r\n return [{ type: 'text', text: lines.join('\\n') }]\r\n },\r\n },\r\n execute: async (args: { query: string; tags?: string[]; tag?: string; since?: string; type?: string; limit?: number }): Promise<SearchResult> => {\r\n const wiki = requireWiki()\r\n const { items, total } = await wiki.search(args.query, {\r\n tags: args.tags,\r\n tag: args.tag,\r\n since: args.since,\r\n type: args.type,\r\n limit: args.limit,\r\n })\r\n return {\r\n query: args.query,\r\n tags: args.tags ?? [],\r\n since: args.since ?? null,\r\n type: args.type ?? null,\r\n total,\r\n results: items.map((t) => ({ title: t.title, tags: t.tags ?? [], modified: typeof t.modified === 'string' ? t.modified : null, snippet: snippetOf(t.text ?? '') })),\r\n }\r\n },\r\n }))\r\n\r\n // ── tiddlywiki_recent ────────────────────────────────────────────────────\r\n register(defineTool({\r\n name: 'tiddlywiki_recent',\r\n description: '查看 TiddlyWiki 知识库最近修改的笔记(按修改时间倒序,排除系统 tiddler 与图片等二进制附件),返回标题、标签、修改时间与摘要。适合开工时快速了解近期动态。',\r\n parameters: {\r\n limit: { type: 'integer', description: '可选:返回条数(默认 15,最大 200)' },\r\n since: { type: 'string', description: '可选:只返回修改时间不早于该 ISO 时间的 tiddler' },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args, value: RecentResult) => {\r\n const lines = [`TiddlyWiki 最近修改(最近 ${value.results.length} 条${value.since !== null ? `,since=${value.since}` : ''}):`]\r\n if (value.results.length === 0) lines.push('暂无笔记。')\r\n for (const r of value.results) {\r\n const tags = r.tags.length > 0 ? ` [${r.tags.join(', ')}]` : ''\r\n lines.push(`- ${r.title}${tags} (${r.modified ?? '?'})`)\r\n if (r.snippet.length > 0) lines.push(` ${r.snippet}`)\r\n }\r\n return [{ type: 'text', text: lines.join('\\n') }]\r\n },\r\n },\r\n execute: async (args: { limit?: number; since?: string }): Promise<RecentResult> => {\r\n const wiki = requireWiki()\r\n const items = await wiki.recent(args.limit ?? 15, args.since)\r\n return {\r\n since: args.since ?? null,\r\n results: items.map((t) => ({ title: t.title, tags: t.tags ?? [], modified: typeof t.modified === 'string' ? t.modified : null, snippet: snippetOf(t.text ?? '') })),\r\n }\r\n },\r\n }))\r\n\r\n // ── tiddlywiki_list_tags ─────────────────────────────────────────────────\r\n register(defineTool({\r\n name: 'tiddlywiki_list_tags',\r\n description: '列出 TiddlyWiki 知识库现有的非系统标签及各自计数(按使用次数降序),方便决定给笔记打什么 tag。',\r\n parameters: {},\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args, value: TagListResult) => {\r\n if (value.tags.length === 0) return [{ type: 'text', text: '知识库暂无标签。' }]\r\n const lines = [`现有标签(${value.tags.length} 个,按使用次数降序):`]\r\n for (const t of value.tags) lines.push(`- ${t.tag} × ${t.count}`)\r\n return [{ type: 'text', text: lines.join('\\n') }]\r\n },\r\n },\r\n execute: async (): Promise<TagListResult> => {\r\n const wiki = requireWiki()\r\n const tags = await wiki.listTags()\r\n return { count: tags.length, tags }\r\n },\r\n }))\r\n\r\n // ── tiddlywiki_get ───────────────────────────────────────────────────────\r\n register(defineTool({\r\n name: 'tiddlywiki_get',\r\n description: '读取一个 TiddlyWiki tiddler 的完整内容(标题、全文、标签、自定义字段)。二进制 tiddler(图片等附件)只返回元数据,不返回 base64 正文。',\r\n parameters: {\r\n title: { type: 'string', description: 'tiddler 标题(精确匹配)', required: true },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args, value: GetResult) => {\r\n if (value.notFound) return [{ type: 'text', text: `tiddler「${value.title}」不存在。可用 tiddlywiki_search 检索,或用 tiddlywiki_put 新建。` }]\r\n if (value.binary === true) {\r\n const lines = [`tiddler「${value.title}」是二进制附件(type=${value.binaryType ?? '?'},base64 正文约 ${value.binaryChars ?? 0} 字符),不返回正文。`]\r\n if (value.tags.length > 0) lines.push(`标签: ${value.tags.join(', ')}`)\r\n if (value.modified !== null) lines.push(`修改: ${value.modified}`)\r\n const fields = Object.entries(value.fields)\r\n if (fields.length > 0) lines.push(`字段: ${fields.map(([k, v]) => `${k}=${String(v)}`).join(', ')}`)\r\n lines.push(`如需查看附件本身,可打开 [${value.title}](/dsh-tiddlywiki/tw/#${encodeURIComponent(value.title)})。`)\r\n return [{ type: 'text', text: lines.join('\\n') }]\r\n }\r\n const lines = [`tiddler「${value.title}」`]\r\n if (value.tags.length > 0) lines.push(`标签: ${value.tags.join(', ')}`)\r\n if (value.modified !== null) lines.push(`修改: ${value.modified}`)\r\n const fields = Object.entries(value.fields)\r\n if (fields.length > 0) lines.push(`字段: ${fields.map(([k, v]) => `${k}=${String(v)}`).join(', ')}`)\r\n lines.push('--- 全文 ---')\r\n lines.push(value.text.length > 0 ? value.text : '(空)')\r\n return [{ type: 'text', text: lines.join('\\n') }]\r\n },\r\n },\r\n execute: async (args: { title: string }): Promise<GetResult> => {\r\n const wiki = requireWiki()\r\n const t = await wiki.get(args.title)\r\n if (t === undefined) return { notFound: true, title: args.title, text: '', tags: [], fields: {}, modified: null }\r\n const binary = isBinaryType(typeof t.type === 'string' ? t.type : undefined)\r\n const result: GetResult = {\r\n notFound: false,\r\n title: t.title,\r\n text: binary ? '' : (t.text ?? ''),\r\n tags: t.tags ?? [],\r\n fields: pickFields(t),\r\n modified: typeof t.modified === 'string' ? t.modified : null,\r\n }\r\n if (binary) {\r\n result.binary = true\r\n result.binaryType = typeof t.type === 'string' ? t.type : undefined\r\n result.binaryChars = (t.text ?? '').length\r\n }\r\n return result\r\n },\r\n }))\r\n\r\n // ── tiddlywiki_put ───────────────────────────────────────────────────────\r\n register(defineTool({\r\n name: 'tiddlywiki_put',\r\n description: '写入(新建或覆盖)一个 TiddlyWiki tiddler。同名覆盖;tags 为标签数组,fields 为附加自定义字段(json 对象,会写入 tiddler 字段)。写入后触发自动 commit。新建(title 不存在)时自动补打 agent-written 标签标记「由 Agent 撰写」,无需手动添加。未指定内容类型时自动默认 text/markdown($:/ 系统条目除外);要写原生 wikitext 需显式在 fields 传 {\"type\":\"text/vnd.tiddlywiki\"}。⚠️ fields.type 是 TW 的内容类型保留字段,不要把业务分类值(如 \"meeting\")写进去——业务分类请放 tags。',\r\n parameters: {\r\n title: { type: 'string', description: 'tiddler 标题(精确匹配,覆盖同名)', required: true },\r\n text: { type: 'string', description: 'tiddler 全文(默认按 Markdown 解析)', required: true },\r\n tags: { type: 'array', items: { type: 'string' }, description: '标签数组(可选)' },\r\n fields: { type: 'json', description: '附加自定义字段,如 {\"date\":\"2026-09-02\"}(可选)。注意:fields.type 是 TW 内容类型(保留字段,默认已自动补 text/markdown),不要写业务分类值' },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args, value: PutResult) => {\r\n const lines = [`已写入 tiddler「${value.title}」`]\r\n if (value.tags.length > 0) lines.push(`标签: ${value.tags.join(', ')}`)\r\n if (value.type !== null) lines.push(`类型: ${value.type}${value.typeDefaulted === true ? '(未指定,已默认 markdown)' : ''}`)\r\n if (value.fields !== null) {\r\n const entries = Object.entries(value.fields)\r\n if (entries.length > 0) lines.push(`字段: ${entries.map(([k, v]) => `${k}=${String(v)}`).join(', ')}`)\r\n }\r\n return [{ type: 'text', text: lines.join('\\n') }]\r\n },\r\n },\r\n execute: async (args: { title: string; text: string; tags?: string[]; fields?: Record<string, unknown> }): Promise<PutResult> => {\r\n const wiki = requireWiki()\r\n const existing = await wiki.get(args.title).catch(() => undefined)\r\n const tags = finalTagsForWrite(args.title, existing, Array.isArray(args.tags) ? args.tags.filter((t) => typeof t === 'string' && t.trim().length > 0) : [])\r\n const tiddler: Tiddler = { title: args.title, text: args.text }\r\n if (tags.length > 0) tiddler.tags = tags\r\n if (args.fields !== undefined && typeof args.fields === 'object' && args.fields !== null) Object.assign(tiddler, args.fields)\r\n const { defaulted } = finalTypeForWrite(args.title, tiddler)\r\n await wiki.put(tiddler)\r\n deps.autoCommit()\r\n return { ok: true, title: args.title, tags, type: typeof tiddler.type === 'string' ? tiddler.type : null, ...(defaulted ? { typeDefaulted: true } : {}), fields: args.fields ?? null }\r\n },\r\n }))\r\n\r\n // ── tiddlywiki_batch_put ─────────────────────────────────────────────────\r\n register(defineTool({\r\n name: 'tiddlywiki_batch_put',\r\n description: '批量写入/覆盖多个 TiddlyWiki tiddler(一次工具调用)。overwrite=false 时跳过已存在的标题;返回逐条结果。写入后触发自动 commit。新建(title 不存在)的条目会自动补打 agent-written 标签,无需手动添加。未指定内容类型(fields.type)的条目自动默认 text/markdown($:/ 系统条目除外)。',\r\n parameters: {\r\n items: {\r\n type: 'array',\r\n items: {\r\n type: 'object',\r\n additionalProperties: true,\r\n description: '要写入的 tiddler 数组',\r\n properties: {\r\n title: { type: 'string', description: '标题(精确匹配,覆盖同名)', required: true },\r\n text: { type: 'string', description: '全文(默认按 Markdown 解析)', required: true },\r\n tags: { type: 'array', items: { type: 'string' }, description: '标签数组(可选)' },\r\n fields: { type: 'json', description: '附加自定义字段(可选)。注意:fields.type 是 TW 内容类型(保留字段,默认已自动补 text/markdown),不要写业务分类值' },\r\n },\r\n },\r\n },\r\n overwrite: { type: 'boolean', description: '可选:true=覆盖同名(默认),false=跳过已存在的标题' },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args, value: BatchResult) => {\r\n const lines = [`批量写入完成:成功 ${value.written},跳过 ${value.skipped},共 ${value.items.length} 条。`]\r\n for (const r of value.items) {\r\n lines.push(`- ${r.title}:${r.written ? '已写入' : '已跳过(存在)'}`)\r\n }\r\n return [{ type: 'text', text: lines.join('\\n') }]\r\n },\r\n },\r\n execute: async (args: { items: Array<{ title: string; text: string; tags?: string[]; fields?: Record<string, unknown> }>; overwrite?: boolean }): Promise<BatchResult> => {\r\n const wiki = requireWiki()\r\n const list = Array.isArray(args.items) ? args.items : []\r\n if (list.length === 0) return { ok: true, written: 0, skipped: 0, items: [] }\r\n const overwrite = args.overwrite !== false\r\n const results: Array<{ title: string; written: boolean; skipped: boolean }> = []\r\n let written = 0\r\n let skipped = 0\r\n for (const item of list) {\r\n if (typeof item.title !== 'string' || item.title.length === 0) throw new Error('batch_put: 每条 items 都需要非空 title')\r\n if (typeof item.text !== 'string') throw new Error(`batch_put: items「${item.title}」缺少 text`)\r\n const existing = await wiki.get(item.title).catch(() => undefined)\r\n if (!overwrite && existing !== undefined) {\r\n skipped++\r\n results.push({ title: item.title, written: false, skipped: true })\r\n continue\r\n }\r\n const tags = finalTagsForWrite(item.title, existing, Array.isArray(item.tags) ? item.tags.filter((t) => typeof t === 'string' && t.trim().length > 0) : [])\r\n const tiddler: Tiddler = { title: item.title, text: item.text }\r\n if (tags.length > 0) tiddler.tags = tags\r\n if (item.fields !== undefined && typeof item.fields === 'object' && item.fields !== null) Object.assign(tiddler, item.fields)\r\n finalTypeForWrite(item.title, tiddler)\r\n await wiki.put(tiddler)\r\n written++\r\n results.push({ title: item.title, written: true, skipped: false })\r\n }\r\n deps.autoCommit()\r\n return { ok: true, written, skipped, items: results }\r\n },\r\n }))\r\n\r\n // ── tiddlywiki_rename ────────────────────────────────────────────────────\r\n register(defineTool({\r\n name: 'tiddlywiki_rename',\r\n description: '重命名一个 TiddlyWiki tiddler:把旧标题的内容复制到新标题、删除旧标题,并可选地更新其他 tiddler 里的 [[旧标题]] / {{旧标题}} 引用(最佳努力)。',\r\n parameters: {\r\n oldTitle: { type: 'string', description: '当前标题', required: true },\r\n newTitle: { type: 'string', description: '新标题', required: true },\r\n updateRefs: { type: 'boolean', description: '可选:是否同步更新其他 tiddler 里的引用(默认 true)' },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args, value: RenameResult) => {\r\n const lines = [`已重命名「${value.from}」→「${value.to}」`]\r\n lines.push(`更新了 ${value.refsUpdated} 处引用(${value.refsTiddlers} 个 tiddler)`)\r\n if (value.warning !== undefined) lines.push(`注意: ${value.warning}`)\r\n return [{ type: 'text', text: lines.join('\\n') }]\r\n },\r\n },\r\n execute: async (args: { oldTitle: string; newTitle: string; updateRefs?: boolean }): Promise<RenameResult> => {\r\n const wiki = requireWiki()\r\n const { oldTitle, newTitle } = args\r\n if (oldTitle === newTitle) return { ok: true, from: oldTitle, to: newTitle, refsUpdated: 0, refsTiddlers: 0 }\r\n const existing = await wiki.get(oldTitle)\r\n if (existing === undefined) throw new Error(`tiddler「${oldTitle}」不存在`)\r\n const target = await wiki.get(newTitle)\r\n if (target !== undefined) throw new Error(`新标题「${newTitle}」已存在(可先用 tiddlywiki_delete 删除)`)\r\n let refsUpdated = 0\r\n let refsTiddlers = 0\r\n let warning: string | undefined\r\n if (args.updateRefs !== false) {\r\n const all = await wiki.list(undefined, true)\r\n for (const t of all) {\r\n if (t.title === oldTitle || t.title === newTitle) continue\r\n if (t.title.startsWith('$:/')) continue\r\n const text = t.text ?? ''\r\n if (text.length === 0) continue\r\n const rewritten = rewriteRefs(text, oldTitle, newTitle)\r\n if (rewritten.count > 0) {\r\n await wiki.put({ ...cleanTiddler(t), text: rewritten.text })\r\n refsUpdated += rewritten.count\r\n refsTiddlers++\r\n }\r\n }\r\n }\r\n await wiki.put({ ...cleanTiddler(existing), title: newTitle })\r\n await wiki.delete(oldTitle)\r\n if (refsTiddlers === 0) {\r\n warning = '未找到任何其他 tiddler 引用旧标题;如确实需要,可手动补充链接。'\r\n }\r\n deps.autoCommit()\r\n return { ok: true, from: oldTitle, to: newTitle, refsUpdated, refsTiddlers, ...(warning !== undefined ? { warning } : {}) }\r\n },\r\n }))\r\n\r\n // ── tiddlywiki_delete ────────────────────────────────────────────────────\r\n register(defineTool({\r\n name: 'tiddlywiki_delete',\r\n description: '删除一个 TiddlyWiki tiddler(不存在时是幂等空操作)。删除后触发自动 commit。',\r\n parameters: {\r\n title: { type: 'string', description: 'tiddler 标题(精确匹配)', required: true },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args, value: DeleteResult) => [{ type: 'text', text: `已删除 tiddler「${value.title}」。` }],\r\n },\r\n execute: async (args: { title: string }): Promise<DeleteResult> => {\r\n const wiki = requireWiki()\r\n await wiki.delete(args.title)\r\n deps.autoCommit()\r\n return { ok: true, title: args.title }\r\n },\r\n }))\r\n\r\n // ── tiddlywiki_git_sync ──────────────────────────────────────────────────\r\n register(defineTool({\r\n name: 'tiddlywiki_git_sync',\r\n description: '对 TiddlyWiki 知识库的 git 仓库做同步:pull(拉取远端并 rebase 本地,冲突则 abort 并报文件)、push(推送本地提交到远端)、sync(pull → commit 本地改动 → push)。未配置 git.remote 时 push 会失败并提示。',\r\n parameters: {\r\n action: { type: 'string', enum: ['pull', 'push', 'sync'], description: '要执行的 git 操作', required: true },\r\n message: { type: 'string', description: 'commit 信息(可选,仅 sync 的本地 commit 使用)' },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args, value: SyncResult) => renderSync(value),\r\n },\r\n execute: async (args: { action: 'pull' | 'push' | 'sync'; message?: string }): Promise<SyncResult> => {\r\n const dir = deps.wikiPath()\r\n /** Restart TW after a pull that changed the tree (stale snapshot drop). */\r\n const restartIfChanged = async (pulled: { changed?: boolean }): Promise<{ restarted?: boolean; restartError?: string }> => {\r\n if (pulled.changed !== true || deps.restartWiki === undefined) return {}\r\n try {\r\n await deps.restartWiki()\r\n return { restarted: true }\r\n } catch (err) {\r\n return { restartError: err instanceof Error ? err.message : String(err) }\r\n }\r\n }\r\n switch (args.action) {\r\n case 'pull': {\r\n const r = await deps.git.pull(dir)\r\n const restart = await restartIfChanged(r)\r\n return { action: args.action, ok: r.ok, message: r.message, ...(r.conflictFiles !== undefined ? { conflictFiles: r.conflictFiles } : {}), ...(r.changed === true ? { changed: true } : {}), ...restart }\r\n }\r\n case 'push': {\r\n const r = await deps.git.push(dir)\r\n return { action: args.action, ok: r.ok, message: r.message }\r\n }\r\n case 'sync': {\r\n const pulled = await deps.git.pull(dir)\r\n if (!pulled.ok) return { action: args.action, ok: false, message: pulled.message, ...(pulled.conflictFiles !== undefined ? { conflictFiles: pulled.conflictFiles } : {}) }\r\n const restart = await restartIfChanged(pulled)\r\n const committed = await deps.git.commit(dir, args.message ?? `sync ${new Date().toISOString()}`)\r\n const pushed = await deps.git.push(dir)\r\n const status = await deps.git.status(dir)\r\n return {\r\n action: args.action,\r\n ok: pushed.ok,\r\n message: pushed.ok ? '同步完成' : pushed.message,\r\n pull: 'ok',\r\n ...(pulled.changed === true ? { changed: true } : {}),\r\n ...restart,\r\n commit: committed.message,\r\n push: pushed.message,\r\n status,\r\n }\r\n }\r\n }\r\n },\r\n }))\r\n\r\n // ── tiddlywiki_git_resolve ───────────────────────────────────────────────\r\n register(defineTool({\r\n name: 'tiddlywiki_git_resolve',\r\n description: '在 tiddlywiki_git_sync action=pull 冲突(已 abort)后,按 tiddler 二选一解决:keep-local 保留本地版本;keep-remote 用 git fetch 拉取远端并检出远端版本(需已配置 git.remote);list 仅报告当前状态。解决后建议重新 pull/sync 整合其余改动。',\r\n parameters: {\r\n strategy: { type: 'string', enum: ['keep-local', 'keep-remote', 'list'], description: 'keep-local=保留本地;keep-remote=改用远端版本;list=仅报告当前 git 状态', required: true },\r\n files: { type: 'array', items: { type: 'string' }, description: '冲突文件名数组(来自 pull 返回的 conflictFiles;list 时忽略)' },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args, value: ResolveResult) => {\r\n const lines = [`git resolve ${value.action}: ${value.ok ? '成功' : '失败'}`]\r\n lines.push(` ${value.message}`)\r\n if (value.files !== undefined && value.files.length > 0) lines.push(`涉及文件: ${value.files.join(', ')}`)\r\n if (value.commit !== undefined) lines.push(`本地 commit: ${value.commit}`)\r\n if (value.status !== undefined) lines.push(`状态: ${gitStatusBits(value.status)}`)\r\n return [{ type: 'text', text: lines.join('\\n') }]\r\n },\r\n },\r\n execute: async (args: { strategy: 'keep-local' | 'keep-remote' | 'list'; files?: string[] }): Promise<ResolveResult> => {\r\n const dir = deps.wikiPath()\r\n if (args.strategy === 'list') {\r\n const status = await deps.git.status(dir)\r\n return { ok: true, action: 'list', message: '当前仓库状态(pull 冲突已 abort,工作区即本地版本,不会残留未合并状态)', status }\r\n }\r\n const files = (args.files ?? []).filter((f) => typeof f === 'string' && f.length > 0)\r\n if (files.length === 0) {\r\n return { ok: false, action: args.strategy, message: '请提供 conflictFiles(来自 pull 返回)', hint: '可先执行 tiddlywiki_git_sync action=pull 查看冲突文件' }\r\n }\r\n if (args.strategy === 'keep-local') {\r\n const status = await deps.git.status(dir)\r\n return {\r\n ok: true,\r\n action: 'keep-local',\r\n message: `已保留本地版本(${files.length} 个文件;abort 后本地即为工作区内容)。建议重新 tiddlywiki_git_sync action=sync 整合远端其余改动。`,\r\n files,\r\n status,\r\n }\r\n }\r\n const fetched = await deps.git.fetch(dir)\r\n if (!fetched.ok) {\r\n return { ok: false, action: 'keep-remote', message: `fetch 失败(可能未配置 git.remote):${fetched.message}` }\r\n }\r\n const checked = await deps.git.checkoutFetchHead(dir, files)\r\n if (!checked.ok) {\r\n return { ok: false, action: 'keep-remote', message: `从远端检出失败:${checked.message}` }\r\n }\r\n const committed = await deps.git.commit(dir, `resolve conflict (keep remote) ${new Date().toISOString()}`)\r\n deps.autoCommit()\r\n const status = await deps.git.status(dir)\r\n return {\r\n ok: true,\r\n action: 'keep-remote',\r\n message: `已把 ${files.length} 个冲突文件改为远端版本并提交。建议继续 tiddlywiki_git_sync action=sync 完成整合与推送。`,\r\n files,\r\n commit: committed.message,\r\n status,\r\n }\r\n },\r\n }))\r\n\r\n return disposers\r\n}\r\n\r\n// ── tool result shapes + renders ───────────────────────────────────────────\r\n\r\ninterface SearchHit { title: string; tags: string[]; modified: string | null; snippet: string }\r\ninterface SearchResult { query: string; tags: string[]; since: string | null; type: string | null; total: number; results: SearchHit[] }\r\ninterface RecentResult { since: string | null; results: SearchHit[] }\r\ninterface TagListResult { count: number; tags: Array<{ tag: string; count: number }> }\r\ninterface GetResult {\r\n notFound: boolean\r\n title: string\r\n text: string\r\n tags: string[]\r\n fields: Record<string, unknown>\r\n modified: string | null\r\n /** Binary attachment (image/audio/…): `text` is withheld, these carry its type+size. */\r\n binary?: boolean\r\n binaryType?: string\r\n binaryChars?: number\r\n}\r\ninterface PutResult { ok: boolean; title: string; tags: string[]; type: string | null; typeDefaulted?: boolean; fields: Record<string, unknown> | null }\r\ninterface BatchResult { ok: boolean; written: number; skipped: number; items: Array<{ title: string; written: boolean; skipped: boolean }> }\r\ninterface RenameResult { ok: boolean; from: string; to: string; refsUpdated: number; refsTiddlers: number; warning?: string }\r\ninterface DeleteResult { ok: boolean; title: string }\r\ninterface SyncResult {\r\n action: string\r\n ok: boolean\r\n message: string\r\n conflictFiles?: string[]\r\n pull?: string\r\n commit?: string\r\n push?: string\r\n changed?: boolean\r\n restarted?: boolean\r\n restartError?: string\r\n status?: GitStatusView\r\n}\r\ninterface ResolveResult {\r\n ok: boolean\r\n action: string\r\n message: string\r\n files?: string[]\r\n commit?: string\r\n hint?: string\r\n status?: GitStatusView\r\n}\r\n\r\n/** Shape of deps.git.status() as surfaced to the model (shared by renders). */\r\ninterface GitStatusView {\r\n branch: string\r\n dirty: boolean\r\n dirtyFiles: string[]\r\n remote: string\r\n lastCommit?: string\r\n ahead?: number\r\n behind?: number\r\n}\r\n\r\n/** One-line 状态 summary: 分支 … 领先 … 落后 … 工作区未提交 … 最近提交. */\r\nfunction gitStatusBits(s: GitStatusView): string {\r\n const bits = [`分支 ${s.branch}`]\r\n if (s.ahead !== undefined) bits.push(`领先 ${s.ahead}`)\r\n if (s.behind !== undefined) bits.push(`落后 ${s.behind}`)\r\n if (s.dirty) bits.push(`工作区有 ${s.dirtyFiles.length} 个未提交改动`)\r\n if (s.lastCommit !== undefined) bits.push(`最近提交 ${s.lastCommit}`)\r\n return bits.join(' · ')\r\n}\r\n\r\nfunction renderSync(value: SyncResult): Array<{ type: 'text'; text: string }> {\r\n const lines = [`git ${value.action}: ${value.ok ? '成功' : '失败'}`]\r\n lines.push(` ${value.message}`)\r\n if (value.conflictFiles !== undefined && value.conflictFiles.length > 0) {\r\n lines.push(`冲突文件(rebase 已 abort,勿自动覆盖):`)\r\n for (const f of value.conflictFiles) lines.push(` - ${f}`)\r\n lines.push('处理方式:用 tiddlywiki_git_resolve files=[以上文件] strategy=keep-local|keep-remote 按 tiddler 二选一解决,再重新 sync;也可以直接让用户处理。')\r\n }\r\n if (value.commit !== undefined) lines.push(`本地 commit: ${value.commit}`)\r\n if (value.push !== undefined) lines.push(`远端 push: ${value.push}`)\r\n if (value.changed === true) {\r\n lines.push(value.restarted === true\r\n ? '本次 pull 拉取了新内容,TW 服务已自动重启(同端口),读取/搜索均为最新快照。'\r\n : '本次 pull 拉取了新内容,但 TW 服务未能自动重启(如需最新快照,请手动重启 TW)。')\r\n }\r\n if (value.restartError !== undefined) lines.push(`TW 重启失败: ${value.restartError}`)\r\n if (value.status !== undefined) {\r\n lines.push(`状态: ${gitStatusBits(value.status)}`)\r\n const s = value.status\r\n if (s.dirty && s.dirtyFiles.length > 0) lines.push(` 未提交: ${s.dirtyFiles.join(', ')}`)\r\n }\r\n return [{ type: 'text', text: lines.join('\\n') }]\r\n}\r\n","/**\r\n * dsh-tiddlywiki — host half.\r\n *\r\n * TiddlyWiki 5 as the DSH persistent knowledge base. Wiring:\r\n * - WikiServer spawns/kills/self-heals the TW 5 child process (loopback, auto\r\n * port) and scaffolds the wiki folder on first run;\r\n * - the git face bootstraps the wiki folder as a repository and wires the\r\n * debounced auto-committer;\r\n * - `tiddlywiki_*` agent tools + a system-prompt section;\r\n * - /dsh-tiddlywiki routes when a webServer is present.\r\n *\r\n * Export shape follows dsh-taskboard: a function/namespace plugin —\r\n * `name` / `inject` / `apply`, NO default export. Config arrives as the\r\n * second apply() argument (Cordis `runtime.callback(ctx, config)`).\r\n *\r\n * Extra exports (WikiServer / TiddlyWebClient / GitFace / ...) exist for the\r\n * headless selftest and future reuse; the loader only reads name/inject/apply.\r\n *\r\n * @module dsh-tiddlywiki\r\n */\r\nimport { existsSync, watch, type FSWatcher } from 'node:fs'\r\nimport { writeFile } from 'node:fs/promises'\r\nimport { join } from 'node:path'\r\nimport { AutoCommitter, GitFace } from './host/git.ts'\r\nimport { registerRoutes, type AgentPresetsFace, type PermissionPresetsFace, type SessionControllerFace, type SessionPersistenceFace, type SessionsFace, type SessionQueryFace, type WebServerFace, type WorkspaceRegistryFace } from './host/routes.ts'\r\nimport { ConfigStore, deepMerge, DARK_PALETTE_DEFAULT, TW_WEB_HOST_TIDDLER, TW_WEB_HOST_DEFAULT, type PluginConfigShape } from './host/config.ts'\r\nimport { registerAdminRoutes, ensureLanguage, resolveTwRoot, type AdminDeps } from './host/admin.ts'\r\nimport { runAllSeeds, checkAllSeeds, runSeedById, removeSeedById, SEED_DEFS, type SeedStatus, type SeedRunResult } from './host/seeds.ts'\r\nimport { TiddlyWebClient, isBinaryType, TEXT_LIST_FILTER } from './host/tw-api.ts'\r\nimport { registerTiddlywikiTools, type ToolsDeps } from './host/tools.ts'\r\nimport { PATH_PREFIX, TW_PROXY_PATH, TW_PROXY_PREFIX, WikiServer, type WikiServerOptions } from './host/wiki.ts'\r\nimport { dshHomePath, defineTool } from './sdk.ts'\r\n\r\n/** Cordis plugin name (also the client loader id / profile row id). */\r\nexport const name = 'dsh-tiddlywiki'\r\n\r\n/** Required host services (tool registry + prompt assembly). */\r\nexport const inject = ['tools', 'systemPrompt']\r\n\r\n/** Re-exports for the headless selftest and future consumers. */\r\nexport { AutoCommitter, GitFace, PATH_PREFIX, TW_PROXY_PATH, TW_PROXY_PREFIX, TiddlyWebClient, isBinaryType, TEXT_LIST_FILTER, WikiServer, dshHomePath, defineTool }\r\nexport { ConfigStore, deepMerge } from './host/config.ts'\r\nexport { openInTwEditor, registerRoutes } from './host/routes.ts'\r\nexport { writeSessionSummary, SESSION_SUMMARY_PREFIX } from './host/routes.ts'\r\nexport type { SessionQueryFace, SessionSummaryResult } from './host/routes.ts'\r\nexport { registerAdminRoutes, resolveTwRoot, readWikiInfo, writeWikiInfo, bundledCatalog, ensureLanguage, normalizeThemes } from './host/admin.ts'\r\nexport { seedDocNote, DOC_NOTE_TITLE, DOC_NOTE_TAG, DOC_NOTE_TEXT } from './host/seed-notes.ts'\r\nexport { seedSendToAgent, SEND_TO_AGENT_PLUGIN_TITLE, SEND_TO_AGENT_MARKER_TITLE, SEND_TO_AGENT_BUNDLE_TEXT } from './host/seed-send-to-agent.ts'\r\nexport { seedRenderRoute, RENDER_PLUGIN_TITLE, RENDER_MARKER_TITLE, RENDER_BUNDLE_TEXT } from './host/seed-render.ts'\r\nexport { seedHomeIndex, HOME_INDEX_ITEMS, HOME_INDEX_MARKER_TITLE, HOME_DEFAULT_TIDDLERS } from './host/seed-home.ts'\r\nexport { seedAllArticles, ALL_ARTICLES_TITLE, ALL_ARTICLES_MARKER_TITLE, ALL_ARTICLES_TEXT } from './host/seed-all-articles.ts'\r\nexport { seedMenubarTheme, MENUBAR_THEME_TIDDLER, MENUBAR_THEME_MARKER_TITLE, MENUBAR_THEME_TEXT } from './host/seed-menubar-theme.ts'\r\nexport { runAllSeeds, checkAllSeeds, runSeedById, removeSeedById, SEED_DEFS, type SeedStatus, type SeedRunResult } from './host/seeds.ts'\r\nexport { registerTiddlywikiTools } from './host/tools.ts'\r\nexport type { PluginConfigShape } from './host/config.ts'\r\nexport type { GitStatusView } from './host/git.ts'\r\nexport type { Tiddler } from './host/tw-api.ts'\r\nexport type { WikiServerOptions, WikiStatusView } from './host/wiki.ts'\r\n\r\n/** Plugin config (design doc §13). Defaults are applied in apply().\r\n * Mirrors PluginConfigShape (src/host/config.ts) — the cordis `config:` block;\r\n * keep the two shapes in lockstep when adding a config field. */\r\nexport interface TiddlywikiConfig {\r\n wikiRoot?: string\r\n wiki?: string\r\n port?: number\r\n git?: { autoCommit?: boolean; debounceMs?: number; remote?: string; branch?: string }\r\n note?: { tag?: string }\r\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 } }\r\n /** 启动时自动启用的 TW 语言代码(如 \"zh-Hans\"),也受配置 tiddler 覆盖。 */\r\n uiLanguage?: string\r\n auth?: { username?: string; password?: string }\r\n}\r\n\r\n/** Structural host context (subset of the dsh host + cordis surfaces). */\r\nexport interface HostCtx {\r\n tools: { register(tool: unknown): () => void }\r\n systemPrompt: { section(opts: { name: string; order: number; text: string }): () => void }\r\n inject<T = unknown>(names: string | string[], callback: (ctx: HostCtx) => T, config?: unknown): unknown\r\n effect(fn: () => unknown, label?: string): void\r\n get(name: string): unknown\r\n [key: string]: unknown\r\n}\r\n\r\n/** Resolved plugin config (defaults merged with the `config:` block). */\r\ninterface ResolvedConfig {\r\n wikiRoot: string\r\n wiki: string\r\n port: number\r\n git: { autoCommit: boolean; debounceMs: number; remote: string; branch: string }\r\n note: { tag: string }\r\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 } }\r\n uiLanguage: string\r\n auth: { username?: string; password?: string }\r\n}\r\n\r\nconst DEFAULTS: ResolvedConfig = {\r\n wikiRoot: '',\r\n wiki: 'main',\r\n port: 0,\r\n git: { autoCommit: true, debounceMs: 60_000, remote: '', branch: 'main' },\r\n note: { tag: 'inbox' },\r\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 } },\r\n uiLanguage: '',\r\n auth: { username: '', password: '' },\r\n}\r\n\r\n/** Config tiddler steering TW's frontend API base (tiddlywebadaptor). */\r\nexport { TW_WEB_HOST_TIDDLER, TW_WEB_HOST_DEFAULT } from './host/config.ts'\r\n\r\n/**\r\n * Point TW's frontend at the same-origin DSH proxy (remote-access mode, R1).\r\n * The tiddlywebadaptor builds every API URL from $:/config/tiddlyweb/host; its\r\n * default `$protocol$//$host$/` resolves to the iframe's origin ROOT, which\r\n * 404s whenever the browser is not on the same machine as TW. Written only\r\n * when the tiddler is missing or still the legacy default — a user override is\r\n * honored (e.g. someone who really does expose TW on a dedicated origin).\r\n */\r\nexport async function ensureTwWebHost(client: TiddlyWebClient | undefined): Promise<void> {\r\n if (client === undefined) return\r\n let current: string | undefined\r\n try {\r\n current = (await client.get(TW_WEB_HOST_TIDDLER))?.text\r\n } catch {\r\n current = undefined\r\n }\r\n if (current !== undefined && current.trim() !== TW_WEB_HOST_DEFAULT) return\r\n await client.put({ title: TW_WEB_HOST_TIDDLER, text: TW_PROXY_PATH, type: 'text/plain', tags: [] })\r\n}\r\n\r\n/** Expand $VAR / ${VAR} / %VAR% from process.env (config uses $DSH_HOME). */\r\nfunction expandEnvPath(input: string): string {\r\n return input\r\n .replace(/\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g, (_, k: string) => process.env[k] ?? '')\r\n .replace(/\\$([A-Za-z_][A-Za-z0-9_]*)/g, (_, k: string) => process.env[k] ?? '')\r\n .replace(/%([A-Za-z_][A-Za-z0-9_]*%)/g, (_, k: string) => process.env[k.slice(0, -1)] ?? '')\r\n}\r\n\r\n/** Resolve wikiRoot: explicit config (env-expanded) else $DSH_HOME/tiddlywiki. */\r\nfunction resolveWikiRoot(config: TiddlywikiConfig): string {\r\n if (config.wikiRoot !== undefined && config.wikiRoot.trim().length > 0) {\r\n return expandEnvPath(config.wikiRoot.trim())\r\n }\r\n return dshHomePath('tiddlywiki')\r\n}\r\n\r\n/** Write the .gitignore for TW transient artifacts (idempotent). */\r\nasync function writeGitignore(wikiPath: string): Promise<void> {\r\n const lines = [\r\n '# TiddlyWiki transient artifacts (auto-managed by dsh-tiddlywiki)',\r\n 'tiddlers/$__temp_*',\r\n 'tiddlers/$__StoryList*',\r\n 'tiddlers/$__HistoryList*',\r\n '*.meta.tmp',\r\n '',\r\n ]\r\n await writeFile(join(wikiPath, '.gitignore'), lines.join('\\n'), 'utf8')\r\n}\r\n\r\n/** Watch the wiki folders and touch the auto-committer on changes. */\r\nfunction watchWiki(wikiPath: string, onChange: () => void): () => void {\r\n const watchers: FSWatcher[] = []\r\n for (const dir of [join(wikiPath, 'tiddlers'), wikiPath]) {\r\n try {\r\n const watcher = watch(dir, { persistent: false }, () => onChange())\r\n watchers.push(watcher)\r\n } catch {\r\n /* directory may not exist yet; the committer also fires on our writes */\r\n }\r\n }\r\n return () => {\r\n for (const watcher of watchers) {\r\n try { watcher.close() } catch { /* already closed */ }\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Wait until a file exists on disk (polling), up to `timeoutMs`. TW's syncer\r\n * flushes REST writes to the filesystem on a ~250ms task timer, so a freshly\r\n * seeded tiddler is not on disk the moment PUT resolves — a caller that must\r\n * restart TW right after (so a seeded server route loads) has to wait for the\r\n * flush first, or the restarted TW would boot from a stale snapshot and the\r\n * seeded module would be missing.\r\n */\r\nasync function waitForFileWrite(filePath: string, timeoutMs = 8_000, pollMs = 150): Promise<boolean> {\r\n const deadline = Date.now() + timeoutMs\r\n for (;;) {\r\n try {\r\n if (existsSync(filePath)) return true\r\n } catch { /* transient */ }\r\n if (Date.now() >= deadline) return false\r\n await new Promise<void>((r) => setTimeout(r, pollMs))\r\n }\r\n}\r\n\r\n/** System-prompt section text (design doc §11 D8). */\r\nconst PROMPT_SECTION_NAME = 'dsh-tiddlywiki'\r\nconst PROMPT_SECTION_ORDER = 100\r\nconst PROMPT_TEXT = `## TiddlyWiki 持久知识库\r\n\r\n本机有一个 TiddlyWiki 5 持久知识库(wiki 文件夹即 git 仓库)。你可以用工具读写 tiddler:\r\n\r\n- \\`tiddlywiki_search\\`(query 必填;可选 tags[]/tag、since 修改时间、type、limit)检索(图片等二进制附件不参与检索);\\`tiddlywiki_get\\`(title)读全文(二进制附件只返回元数据,不含 base64 正文);\\`tiddlywiki_put\\`(title, text, tags?, fields?)写/覆盖;\\`tiddlywiki_batch_put\\`(items[])批量写;\\`tiddlywiki_rename\\`(oldTitle, newTitle, updateRefs?)重命名并尽量同步引用;\\`tiddlywiki_delete\\`(title)删除。\r\n- \\`tiddlywiki_recent\\`(limit?, since?)看最近修改的笔记(不含图片等二进制附件);\\`tiddlywiki_list_tags\\` 看现有 tag 及计数。\r\n- \\`tiddlywiki_git_sync\\`(pull|push|sync)做 git 同步;\\`tiddlywiki_git_resolve\\`(files, strategy=keep-local|keep-remote|list)在 pull 冲突后按 tiddler 二选一解决。\r\n\r\n知识库同步纪律(三条):\r\n1. 开工先 pull:\\`tiddlywiki_git_sync action=pull\\`(rebase + autostash;真冲突会自动 abort 并报冲突文件)。\r\n2. 收工 commit + push:\\`tiddlywiki_git_sync action=sync\\`(pull → commit → push)。\r\n3. 插件会自动防抖 commit(默认 60s),手动同步用上面的工具。\r\n\r\npull 冲突后:先 \\`tiddlywiki_git_resolve files=[冲突文件] strategy=keep-local\\`(保留本地)或 \\`strategy=keep-remote\\`(改用远端版本),再重新 pull/sync 整合其余改动。\r\n\r\n把 wiki 当作长期记忆与知识沉淀的地方:会议纪要、决策记录、调研笔记、随手的想法都可存成独立 tiddler(tag 建议用 inbox/meeting/decision 等便于检索)。\r\n\r\n**把有价值但不在当前执行范围内的想法沉淀进 wiki**:遇到「未来可能有用 / 值得做」的想法、或不在当前任务范围内但有实现价值的事项时,用 \\`tiddlywiki_put\\` 写成独立 tiddler,打上 \\`todo\\` + \\`agent-written\\` 标签(并附当前工作区名),正文简要说明来源(会话 / 工作区 / 项目背景),方便日后回溯,由用户决定是否继续。\r\n\r\n用本插件自动创建笔记时,除了业务性 tag 外,请把「当前工作区(项目)的名字」也作为标签之一加上去(例如 \\`tiddlywiki_put\\` 的 tags 里带上当前 workspace 名),这样笔记能按项目归集、检索。\r\n\r\n**Agent 笔记标签约定**:\\`tiddlywiki_put\\` / \\`tiddlywiki_batch_put\\` 新建笔记时,插件会自动补打 \\`agent-written\\` 标签(标记「由 Agent 撰写」),无需手动添加,也不要手动移除它(除非用户明确要求)。首页会把 Agent 笔记单独列在「Agent 区块」,主标签列表只统计人类笔记。若某篇 Agent 笔记后续被人类编辑过,请在该笔记上补打 \\`human-edited\\` 标签,首页会把它归入「Agent + 人工」档。覆盖写入已有的(人类)笔记时不会自动加 agent-written,请保持笔记原本的归属。\r\n\r\n**内容类型约定**:agent 笔记正文默认用 **Markdown** 写;\\`tiddlywiki_put\\` / \\`tiddlywiki_batch_put\\` 未指定内容类型时自动按 \\`text/markdown\\` 写入(\\`$:/\\` 系统条目除外),无需手动指定。要写 TW 原生 wikitext 才需要在 fields 里显式传 \\`{\"type\":\"text/vnd.tiddlywiki\"}\\`。⚠️ \\`fields.type\\` 是 TW 的**内容类型**保留字段——不要把业务分类值(如 \\`\"meeting\"\\`)写进去(会破坏渲染),业务分类请放 \\`tags\\`。\r\n\r\n**引用 wiki 笔记用可点击链接**:在回复流中引用某篇笔记时,用格式 \\`[标题](/dsh-tiddlywiki/tw/#标题)\\` 输出(标题含空格/特殊字符时做 URL 编码,如 \\`A%20B\\`;中文标题可直接写)。这类链接会被界面自动接管:点击后打开中央 TW 面板并跳转到该笔记的原生页面。回复里也优先用这个链接格式代替纯文本标题,让用户能一键跳到 wiki。`\r\n\r\n/**\r\n * Mount the host half.\r\n * @param ctx - the plugin context (tools + systemPrompt injected).\r\n * @param rawConfig - the plugin row's `config:` block (Cordis second arg).\r\n */\r\nexport function apply(ctx: HostCtx, rawConfig: TiddlywikiConfig = {}): void {\r\n const config: ResolvedConfig = {\r\n wikiRoot: resolveWikiRoot(rawConfig),\r\n wiki: rawConfig.wiki ?? DEFAULTS.wiki,\r\n port: rawConfig.port ?? DEFAULTS.port,\r\n git: { ...DEFAULTS.git, ...(rawConfig.git ?? {}) },\r\n note: { ...DEFAULTS.note, ...(rawConfig.note ?? {}) },\r\n ui: {\r\n ...DEFAULTS.ui,\r\n ...(rawConfig.ui ?? {}),\r\n // Merge nested ui.* groups explicitly so defaults stay required (a plain\r\n // spread of the lax cordis shape would widen them to optional).\r\n sendToAgent: { ...DEFAULTS.ui.sendToAgent, ...(rawConfig.ui?.sendToAgent ?? {}) },\r\n allArticles: { ...DEFAULTS.ui.allArticles, ...(rawConfig.ui?.allArticles ?? {}) },\r\n },\r\n uiLanguage: typeof rawConfig.uiLanguage === 'string' ? rawConfig.uiLanguage.trim() : DEFAULTS.uiLanguage,\r\n auth: { ...DEFAULTS.auth, ...(rawConfig.auth ?? {}) },\r\n }\r\n const wikiPath = join(config.wikiRoot, config.wiki)\r\n const git = new GitFace()\r\n\r\n // Runtime-editable config (settings page): the cordis `config:` block is the\r\n // BASE; a config tiddler ($:/plugins/dsh-tiddlywiki/config) written by the\r\n // settings page overlays it. Effective values come from configStore.get().\r\n const configStore = new ConfigStore({ note: config.note, git: config.git, ui: config.ui, uiLanguage: config.uiLanguage } satisfies PluginConfigShape)\r\n const eff = (): PluginConfigShape => configStore.get()\r\n const effectiveNoteTag = (): string => {\r\n const tag = eff().note?.tag\r\n return typeof tag === 'string' && tag.trim().length > 0 ? tag : config.note.tag\r\n }\r\n const effectiveUi = (): { showQuickNote: boolean; showQuickNoteDock: boolean; quickNoteMode: 'native' | 'card'; sidebarLabel: string; showPanelStatus: boolean; showSyncButton: boolean; followDshTheme: boolean; darkPalette: string; tabLabel: string; showSessionTab: boolean; showRightbarTab: boolean } => {\r\n const ui = eff().ui ?? {}\r\n const palette = typeof ui.darkPalette === 'string' && ui.darkPalette.trim().length > 0 ? ui.darkPalette.trim() : DARK_PALETTE_DEFAULT\r\n const label = typeof ui.sidebarLabel === 'string' && ui.sidebarLabel.trim().length > 0 ? ui.sidebarLabel.trim() : config.ui.sidebarLabel\r\n const tabLabel = typeof ui.tabLabel === 'string' && ui.tabLabel.trim().length > 0 ? ui.tabLabel.trim() : config.ui.tabLabel\r\n const quickNoteMode: 'native' | 'card' = ui.quickNoteMode === 'card' ? 'card' : 'native'\r\n return {\r\n showQuickNote: ui.showQuickNote !== false,\r\n showQuickNoteDock: ui.showQuickNoteDock !== false,\r\n quickNoteMode,\r\n sidebarLabel: label,\r\n showPanelStatus: ui.showPanelStatus !== false,\r\n showSyncButton: ui.showSyncButton !== false,\r\n followDshTheme: ui.followDshTheme !== false,\r\n darkPalette: palette,\r\n tabLabel,\r\n showSessionTab: ui.showSessionTab !== false,\r\n showRightbarTab: ui.showRightbarTab !== false,\r\n }\r\n }\r\n\r\n const disposers: Array<() => void> = []\r\n const disposeAll = (): void => {\r\n for (const dispose of disposers.splice(0)) dispose()\r\n }\r\n\r\n // System prompt section (independent of the wiki service).\r\n const disposeSection = ctx.systemPrompt.section({ name: PROMPT_SECTION_NAME, order: PROMPT_SECTION_ORDER, text: PROMPT_TEXT })\r\n ctx.effect(() => disposeSection, 'dsh-tiddlywiki: prompt section')\r\n\r\n // TW child server.\r\n const server = new WikiServer({\r\n wikiRoot: config.wikiRoot,\r\n wiki: config.wiki,\r\n port: config.port,\r\n username: config.auth.username,\r\n password: config.auth.password,\r\n })\r\n\r\n // Lazy TW client (rebuilt when the port is bound).\r\n let clientCache: TiddlyWebClient | undefined\r\n const client = (): TiddlyWebClient | undefined => {\r\n const port = server.currentPort\r\n if (port === undefined) return undefined\r\n clientCache ??= new TiddlyWebClient(`http://127.0.0.1:${port}`)\r\n return clientCache\r\n }\r\n\r\n // Auto-committer + filesystem watcher (created after the wiki dir exists).\r\n // Reads the EFFECTIVE config so a settings-page git change survives a restart.\r\n let committer: AutoCommitter | undefined\r\n let unwatch: (() => void) | undefined\r\n const setupCommitter = (): void => {\r\n const g = eff().git ?? {}\r\n committer = new AutoCommitter({\r\n git,\r\n dir: wikiPath,\r\n enabled: g.autoCommit ?? config.git.autoCommit,\r\n debounceMs: g.debounceMs ?? config.git.debounceMs,\r\n message: () => `wiki autocommit ${new Date().toISOString()}`,\r\n onError: (err) => console.warn('[dsh-tiddlywiki] autocommit:', err),\r\n })\r\n unwatch = watchWiki(wikiPath, () => committer?.touch())\r\n disposers.push(() => {\r\n committer?.dispose()\r\n unwatch?.()\r\n })\r\n }\r\n\r\n // Git bootstrap: repo init + initial commit + .gitignore (+ remote/first push).\r\n const bootstrapGit = async (): Promise<void> => {\r\n const g = eff().git ?? {}\r\n const branch = g.branch ?? config.git.branch\r\n const remote = g.remote ?? config.git.remote\r\n const isRepo = await git.isRepo(wikiPath)\r\n if (!isRepo) {\r\n await git.init(wikiPath, branch)\r\n await writeGitignore(wikiPath)\r\n await git.initialCommit(wikiPath)\r\n } else {\r\n await writeGitignore(wikiPath)\r\n }\r\n if (remote.trim().length > 0) {\r\n const ensured = await git.ensureRemote(wikiPath, remote.trim())\r\n if (ensured.ok) {\r\n const first = await git.firstPush(wikiPath)\r\n if (!first.ok) console.warn('[dsh-tiddlywiki] first push failed (retry with tiddlywiki_git_sync):', first.message)\r\n } else {\r\n console.warn('[dsh-tiddlywiki] git remote setup:', ensured.message)\r\n }\r\n }\r\n }\r\n\r\n // Tools (works even while the wiki is down; wiki() resolves lazily).\r\n const toolsDeps: ToolsDeps = {\r\n wiki: client,\r\n git,\r\n wikiPath: () => wikiPath,\r\n noteTag: effectiveNoteTag,\r\n autoCommit: () => committer?.touch(),\r\n // After a pull that changed the working tree, restart TW so the server\r\n // (and the agent's reads) see the pulled content, not the old snapshot.\r\n restartWiki: async () => { await server.restart() },\r\n }\r\n disposers.push(...registerTiddlywikiTools(ctx, toolsDeps))\r\n\r\n // Bring the wiki up, load the override config, then bootstrap git + committer.\r\n void (async () => {\r\n try {\r\n await server.start()\r\n await configStore.load(client())\r\n // Point TW's frontend at the same-origin DSH proxy (remote-access mode):\r\n // part of the seed registry below (tw-web-host), so it is also covered by\r\n // the settings page's 重新初始化. Runs before git bootstrap so the config\r\n // tiddler joins the first commit.\r\n // Seeds (run after the effective config is loaded): every one-time\r\n // \"与 dsh 联动需要 wiki 预置\" item lives in the SEED_DEFS registry.\r\n // The startup path seeds ONLY the CORE items (功能必需:发送给 Agent\r\n // 按钮 + TW 前端 API 基址) NON-force — write only what is missing, never\r\n // overwrite user content. Optional seeds (说明笔记 / 首页 / 所有文章 /\r\n // menubar 顶栏主题自适应) are never forced on users: they opt in from\r\n // the settings page「初始化」section (重新初始化) and can opt out again\r\n // with 反初始化 (remove).\r\n try {\r\n const seedClient = client()\r\n if (seedClient !== undefined) {\r\n const results = await runAllSeeds({ client: seedClient })\r\n for (const r of results) {\r\n if (!r.ok) console.warn(`[dsh-tiddlywiki] seed ${r.id} failed:`, r.error ?? r.detail)\r\n }\r\n // Seeding wrote something (e.g. the render-route plugin on this\r\n // upgrade). A seeded SERVER route only loads at TW boot, so restart\r\n // once to pick it up. Wait for the bundle file to actually reach the\r\n // disk first — TW's syncer flushes REST writes on a ~250ms task\r\n // timer, and restarting from a stale snapshot would boot WITHOUT the\r\n // just-seeded module.\r\n if (results.some((r) => r.ok && r.wrote)) {\r\n const renderFile = join(wikiPath, 'tiddlers', '$__plugins_dsh_render.json')\r\n const flushed = await waitForFileWrite(renderFile)\r\n if (!flushed) console.warn('[dsh-tiddlywiki] seeded render plugin file not seen on disk before restart')\r\n await server.restart()\r\n }\r\n }\r\n } catch (err) {\r\n console.warn('[dsh-tiddlywiki] seeding wiki:', err)\r\n }\r\n // Apply the configured UI language (e.g. \"zh-Hans\"): enable the bundled\r\n // language plugin in tiddlywiki.info.languages + restart once so TW loads\r\n // it at boot (fully offline — official language packs ship in the pkg).\r\n const uiLang = eff().uiLanguage\r\n if (typeof uiLang === 'string' && uiLang.trim().length > 0) {\r\n try {\r\n const code = uiLang.trim()\r\n const changed = await ensureLanguage(wikiPath, resolveTwRoot(), code)\r\n if (changed) await server.restart()\r\n // Pin the active language tiddler so TW's UI actually switches.\r\n const langClient = client()\r\n if (langClient !== undefined) {\r\n await langClient.put({ title: '$:/language', text: `$:/languages/${code}`, type: 'text/plain', tags: [] }).catch(() => undefined)\r\n }\r\n } catch (err) {\r\n console.warn('[dsh-tiddlywiki] applying uiLanguage:', err)\r\n }\r\n }\r\n await bootstrapGit()\r\n setupCommitter()\r\n } catch (err) {\r\n console.warn('[dsh-tiddlywiki] startup issue (self-healing is armed):', err)\r\n }\r\n })()\r\n\r\n // Routes + settings-panel admin surface (lazy webServer).\r\n ctx.inject(['webServer'], (webCtx: HostCtx) => {\r\n const ws = (webCtx as unknown as { webServer: WebServerFace }).webServer\r\n // sessionController is a core host service; read it LAZILY per request (via\r\n // the plugin root ctx) because it may be registered after webServer, and the\r\n // agent routes must work whenever a request actually arrives.\r\n const getSessionController = (): SessionControllerFace | undefined =>\r\n ctx.get('sessionController') as SessionControllerFace | undefined\r\n const getWorkspaceRegistry = (): WorkspaceRegistryFace | undefined =>\r\n ctx.get('workspaceRegistry') as WorkspaceRegistryFace | undefined\r\n // agentPresets (工作模式 roster) + sessionPersistence (per-session preset\r\n // badges) are also core host services — resolve them lazily like the above.\r\n const getAgentPresets = (): AgentPresetsFace | undefined =>\r\n ctx.get('agentPresets') as AgentPresetsFace | undefined\r\n const getSessionPersistence = (): SessionPersistenceFace | undefined =>\r\n ctx.get('sessionPersistence') as SessionPersistenceFace | undefined\r\n // permissionPresets (权限 preset roster for the picker + applying the\r\n // chosen permission to new sessions) and the `sessions` in-memory store\r\n // (the created live session handed to permissionPresets.set) are core host\r\n // services — resolve them lazily like the ones above.\r\n const getPermissionPresets = (): PermissionPresetsFace | undefined =>\r\n ctx.get('permissionPresets') as PermissionPresetsFace | undefined\r\n const getSessions = (): SessionsFace | undefined =>\r\n ctx.get('sessions') as SessionsFace | undefined\r\n // sessionQuery (会话日志查询,供「知识库」Tab 判定「本会话相关笔记」) 也是核心\r\n // host 服务 —— 可选注入(本部署存在),懒解析。\r\n const getSessionQuery = (): SessionQueryFace | undefined =>\r\n ctx.get('sessionQuery') as SessionQueryFace | undefined\r\n const disposeRoutes = registerRoutes({ webServer: ws }, {\r\n server,\r\n getClient: client,\r\n git,\r\n autoCommit: () => committer?.touch(),\r\n noteDefaults: () => ({ tag: effectiveNoteTag() }),\r\n uiDefaults: () => effectiveUi(),\r\n getWikiPath: () => wikiPath,\r\n getSessionController,\r\n getWorkspaceRegistry,\r\n getAgentPresets,\r\n getSessionPersistence,\r\n getPermissionPresets,\r\n getSessions,\r\n getSessionQuery,\r\n sendToAgentEnabled: () => eff().ui?.sendToAgent?.enabled !== false,\r\n sendToAgentToken: () => {\r\n const token = eff().ui?.sendToAgent?.token\r\n return typeof token === 'string' ? token : ''\r\n },\r\n })\r\n const adminDeps: AdminDeps = {\r\n server,\r\n getClient: client,\r\n getWikiPath: () => wikiPath,\r\n twRoot: resolveTwRoot,\r\n config: configStore,\r\n seeds: {\r\n checkAll: async (c) => checkAllSeeds({ client: c }),\r\n run: async (c, id, force) => runSeedById({ client: c }, id, force),\r\n remove: async (c, id) => removeSeedById({ client: c }, id),\r\n },\r\n }\r\n const disposeAdmin = registerAdminRoutes({ webServer: ws }, adminDeps)\r\n return () => {\r\n disposeRoutes()\r\n disposeAdmin()\r\n }\r\n })\r\n\r\n // Teardown: everything reversible (R6 — hot reload must not leak).\r\n // The disposer RETURNS its promise: cordis fiber.dispose() awaits effect\r\n // disposers, and dsh web's shutdown controller awaits fiber.dispose() with a\r\n // 5s grace — so 结束/退出/重启 dsh web 时 TW 后端子进程会被真正停掉,而不是\r\n // fire-and-forget 里与进程退出竞速(竞速会在 Windows 上遗留孤儿 TW 进程)。\r\n ctx.effect(() => () => {\r\n return (async () => {\r\n // Flush a pending auto-commit BEFORE teardown, so a write made within the\r\n // debounce window is not left uncommitted when dsh web stops (best-effort).\r\n try { await committer?.flush() } catch { /* best-effort */ }\r\n disposeAll()\r\n await server.stop()\r\n })()\r\n }, 'dsh-tiddlywiki: host teardown')\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["snippetOf"],"sources":["../src/host/git.ts","../src/host/wiki.ts","../src/host/session-summary.ts","../src/host/http.ts","../src/host/routes.ts","../src/host/config.ts","../src/host/admin.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-render.ts","../src/host/seeds.ts","../src/host/tw-api.ts","../src/sdk.ts","../src/host/tools.ts","../src/index.ts"],"sourcesContent":["/**\r\n * Git face (design doc §7, D11) — the ONLY place dsh-tiddlywiki shells out to\r\n * git. The wiki folder itself is the repository; the folder is pure text\r\n * (FileSystemAdaptor writes one file per tiddler), so git is a natural sync /\r\n * backup channel.\r\n *\r\n * Sync model is the single-thread alternating one:\r\n * 1. start of work: `git pull --rebase --autostash`\r\n * 2. end of work: `git add -A && git commit && git push`\r\n * 3. auto-commit: debounced 60s commit after wiki writes (AutoCommitter)\r\n *\r\n * Conflict policy (user-confirmed, no complex handling): a rebase conflict\r\n * (only reachable by \"forgot to pull before writing\") → `git rebase --abort`\r\n * + report the unmerged files. Never auto-merge data.\r\n *\r\n * @module dsh-tiddlywiki/host/git\r\n */\r\nimport { execFile } from 'node:child_process'\r\nimport { promisify } from 'node:util'\r\n\r\nconst execFileP = promisify(execFile)\r\n\r\n/** Timeout for quick read-only queries. */\r\nconst QUICK_TIMEOUT_MS = 5_000\r\n\r\n/** Timeout for structural/network operations. */\r\nconst HEAVY_TIMEOUT_MS = 60_000\r\n\r\nexport interface ExecResult { ok: boolean; stdout: string; stderr: string }\r\nexport type ExecFn = (args: string[], options: { cwd?: string; timeout?: number }) => Promise<ExecResult>\r\n\r\n/** Default exec layer: run `git <args>` under a cwd with a timeout. */\r\nconst defaultExec: ExecFn = async (args, options) => {\r\n try {\r\n const { stdout, stderr } = await execFileP('git', args, {\r\n cwd: options.cwd,\r\n timeout: options.timeout ?? QUICK_TIMEOUT_MS,\r\n windowsHide: true,\r\n encoding: 'utf8',\r\n maxBuffer: 32 * 1024 * 1024,\r\n })\r\n return { ok: true, stdout, stderr }\r\n } catch (err) {\r\n const e = err as { stdout?: string; stderr?: string; message?: string }\r\n return { ok: false, stdout: e.stdout ?? '', stderr: e.stderr ?? String(e.message ?? err) }\r\n }\r\n}\r\n\r\nexport interface GitStatusView {\r\n exists: boolean\r\n branch: string\r\n dirty: boolean\r\n dirtyFiles: string[]\r\n remote: string\r\n lastCommit?: string\r\n ahead?: number\r\n behind?: number\r\n}\r\n\r\nexport interface GitActionResult { ok: boolean; message: string; conflictFiles?: string[] }\r\n\r\nfunction parseCount(line: string, re: RegExp): number | undefined {\r\n const m = line.match(re)\r\n return m === null ? undefined : Number(m[1])\r\n}\r\n\r\nexport class GitFace {\r\n constructor(private readonly exec: ExecFn = defaultExec) {}\r\n\r\n async isRepo(dir: string): Promise<boolean> {\r\n const r = await this.exec(['rev-parse', '--is-inside-work-tree'], { cwd: dir, timeout: 2_000 })\r\n return r.ok && r.stdout.trim() === 'true'\r\n }\r\n\r\n async init(dir: string, branch = 'main'): Promise<boolean> {\r\n const r = await this.exec(['init', '-b', branch], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\r\n return r.ok\r\n }\r\n\r\n /** Initial commit for a fresh repo (tolerates an empty index). */\r\n async initialCommit(dir: string): Promise<boolean> {\r\n await this.exec(['add', '-A'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\r\n const r = await this.exec([...identity(), 'commit', '-m', 'chore(dsh-tiddlywiki): initial commit'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\r\n return r.ok || /nothing to commit/.test(r.stderr + r.stdout)\r\n }\r\n\r\n /**\r\n * Stage everything and commit; a local identity is always provided so the\r\n * plugin never depends on the machine's global git config. Returns whether\r\n * a commit actually happened.\r\n */\r\n async commit(dir: string, message: string): Promise<{ committed: boolean; message: string }> {\r\n await this.exec(['add', '-A'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\r\n const staged = await this.exec(['diff', '--cached', '--quiet'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\r\n // `diff --cached --quiet` exits 0 when nothing is staged → nothing to commit.\r\n if (staged.ok) return { committed: false, message: 'nothing to commit' }\r\n const r = await this.exec([...identity(), 'commit', '-m', message], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\r\n return r.ok\r\n ? { committed: true, message }\r\n : { committed: false, message: `commit failed: ${(r.stderr.trim() || r.stdout.trim()).slice(0, 500)}` }\r\n }\r\n\r\n async status(dir: string): Promise<GitStatusView> {\r\n const empty: GitStatusView = { exists: false, branch: '', dirty: false, dirtyFiles: [], remote: '' }\r\n const r = await this.exec(['status', '--porcelain', '-b'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\r\n if (!r.ok) return empty\r\n const lines = r.stdout.split('\\n').filter((l) => l.length > 0)\r\n const branchLine = lines.find((l) => l.startsWith('## '))\r\n const branch = branchLine === undefined ? '' : branchLine.slice(3).split('...')[0] ?? ''\r\n const ahead = branchLine === undefined ? undefined : parseCount(branchLine, /ahead (\\d+)/)\r\n const behind = branchLine === undefined ? undefined : parseCount(branchLine, /behind (\\d+)/)\r\n const dirty = lines.some((l) => !l.startsWith('## '))\r\n const dirtyFiles = lines.filter((l) => !l.startsWith('## ')).map((l) => l.slice(3).trim()).filter(Boolean)\r\n const remoteR = await this.exec(['remote', '-v'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\r\n const remote = remoteR.ok ? remoteR.stdout.split('\\n').map((l) => l.trim()).find(Boolean) ?? '' : ''\r\n const lastR = await this.exec(['log', '-1', '--format=%h %s'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\r\n const lastCommit = lastR.ok && lastR.stdout.trim().length > 0 ? lastR.stdout.trim() : undefined\r\n return { exists: true, branch, dirty, dirtyFiles, remote, ...(lastCommit !== undefined ? { lastCommit } : {}), ...(ahead !== undefined ? { ahead } : {}), ...(behind !== undefined ? { behind } : {}) }\r\n }\r\n\r\n /** `git pull --rebase --autostash`; on conflict: abort + report files.\r\n * On success, `changed: true` means HEAD actually moved (files came in /\r\n * commits were replayed) — callers use it to decide whether a running TW\r\n * child needs a restart to drop its stale in-memory snapshot. */\r\n async pull(dir: string): Promise<GitActionResult & { changed?: boolean }> {\r\n const before = await this.exec(['rev-parse', 'HEAD'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\r\n const beforeHead = before.ok ? before.stdout.trim() : ''\r\n const r = await this.exec(['pull', '--rebase', '--autostash'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\r\n if (r.ok) {\r\n const after = await this.exec(['rev-parse', 'HEAD'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\r\n const afterHead = after.ok ? after.stdout.trim() : ''\r\n const changed = beforeHead.length > 0 && beforeHead !== afterHead\r\n return { ok: true, message: r.stdout.trim() || 'pull ok', ...(changed ? { changed: true } : {}) }\r\n }\r\n const conflictFiles = await this.unmergedFiles(dir)\r\n await this.exec(['rebase', '--abort'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\r\n const reason = (r.stderr.trim() || r.stdout.trim()).slice(0, 500)\r\n return { ok: false, message: conflictFiles.length > 0 ? `conflict in ${conflictFiles.join(', ')} (rebase aborted): ${reason}` : `pull failed: ${reason}`, ...(conflictFiles.length > 0 ? { conflictFiles } : {}) }\r\n }\r\n\r\n async push(dir: string): Promise<GitActionResult> {\r\n const r = await this.exec(['push'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\r\n return r.ok\r\n ? { ok: true, message: r.stdout.trim() || 'push ok' }\r\n : { ok: false, message: (r.stderr.trim() || r.stdout.trim()).slice(0, 500) }\r\n }\r\n\r\n /** First push with upstream tracking (called once after a remote is set). */\r\n async firstPush(dir: string): Promise<GitActionResult> {\r\n const branch = (await this.status(dir)).branch || 'main'\r\n const r = await this.exec(['push', '-u', 'origin', branch], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\r\n return r.ok\r\n ? { ok: true, message: `pushed ${branch} to origin` }\r\n : { ok: false, message: (r.stderr.trim() || r.stdout.trim()).slice(0, 500) }\r\n }\r\n\r\n /** `git fetch` (no remote configured → failure reported by the caller). */\r\n async fetch(dir: string): Promise<GitActionResult> {\r\n const r = await this.exec(['fetch'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\r\n return r.ok\r\n ? { ok: true, message: r.stdout.trim() || 'fetch ok' }\r\n : { ok: false, message: (r.stderr.trim() || r.stdout.trim()).slice(0, 500) }\r\n }\r\n\r\n /**\r\n * Restore the given files from the freshly fetched remote HEAD (FETCH_HEAD)\r\n * into the working tree + index — the \"keep remote version\" half of\r\n * tiddler-granular conflict resolution. Callers must `git fetch` first.\r\n */\r\n async checkoutFetchHead(dir: string, files: string[]): Promise<GitActionResult> {\r\n if (files.length === 0) return { ok: true, message: 'no files given' }\r\n const r = await this.exec(['checkout', 'FETCH_HEAD', '--', ...files], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\r\n return r.ok\r\n ? { ok: true, message: `已从远端检出 ${files.length} 个文件` }\r\n : { ok: false, message: (r.stderr.trim() || r.stdout.trim()).slice(0, 500) }\r\n }\r\n\r\n /** Ensure `origin` points at `url` (add or set-url). */\r\n async ensureRemote(dir: string, url: string): Promise<GitActionResult> {\r\n const cur = await this.exec(['remote', 'get-url', 'origin'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\r\n if (cur.ok) {\r\n if (cur.stdout.trim() === url) return { ok: true, message: 'remote origin already set' }\r\n const set = await this.exec(['remote', 'set-url', 'origin', url], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\r\n return set.ok ? { ok: true, message: `remote origin → ${url}` } : { ok: false, message: set.stderr.trim() || 'remote set-url failed' }\r\n }\r\n const add = await this.exec(['remote', 'add', 'origin', url], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\r\n return add.ok ? { ok: true, message: `remote origin → ${url}` } : { ok: false, message: add.stderr.trim() || 'remote add failed' }\r\n }\r\n\r\n private async unmergedFiles(dir: string): Promise<string[]> {\r\n const r = await this.exec(['diff', '--name-only', '--diff-filter=U'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\r\n return r.ok ? r.stdout.split('\\n').map((l) => l.trim()).filter(Boolean) : []\r\n }\r\n}\r\n\r\n/** Always-on local identity so commits never depend on global git config. */\r\nfunction identity(): string[] {\r\n return ['-c', 'user.name=dsh-tiddlywiki', '-c', 'user.email=dsh-tiddlywiki@local']\r\n}\r\n\r\nexport interface AutoCommitterOptions {\r\n git: GitFace\r\n dir: string\r\n enabled: boolean\r\n debounceMs: number\r\n message: () => string\r\n onError?: (err: unknown) => void\r\n onCommit?: (info: { committed: boolean; message: string }) => void\r\n}\r\n\r\n/**\r\n * Debounced auto-committer: every wiki write calls `touch()`; the commit\r\n * fires once writes settle for `debounceMs`. Disable with git.autoCommit.\r\n */\r\nexport class AutoCommitter {\r\n private timer: NodeJS.Timeout | undefined\r\n private disposed = false\r\n\r\n constructor(private readonly options: AutoCommitterOptions) {}\r\n\r\n touch(): void {\r\n if (!this.options.enabled || this.disposed) return\r\n if (this.timer !== undefined) clearTimeout(this.timer)\r\n this.timer = setTimeout(() => { void this.flush() }, this.options.debounceMs)\r\n }\r\n\r\n /** Run a commit now (also cancels the pending debounce). */\r\n async flush(): Promise<void> {\r\n if (this.timer !== undefined) {\r\n clearTimeout(this.timer)\r\n this.timer = undefined\r\n }\r\n if (!this.options.enabled || this.disposed) return\r\n try {\r\n const result = await this.options.git.commit(this.options.dir, this.options.message())\r\n this.options.onCommit?.(result)\r\n } catch (err) {\r\n this.options.onError?.(err)\r\n }\r\n }\r\n\r\n dispose(): void {\r\n this.disposed = true\r\n if (this.timer !== undefined) {\r\n clearTimeout(this.timer)\r\n this.timer = undefined\r\n }\r\n }\r\n}\r\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 private readonly 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\n constructor(private readonly options: WikiServerOptions) {\n this.wikiPath = resolve(options.wikiRoot, options.wiki)\n this.logLimit = options.logBufferLimit ?? LOG_BUFFER_LIMIT\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 async start(): Promise<WikiStatusView> {\n this.stopping = false\n this.restartDelay = 1_000\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 this.log(`spawn: ${process.execPath} ${args.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 this.log(`exit code=${code} signal=${signal ?? ''} stopping=${this.stopping}`)\n this.child = undefined\n this.health = 'stopped'\n if (!this.stopping) this.scheduleRestart()\n })\n child.once('error', (err) => {\n this.log(`spawn error: ${err.message}`)\n this.error = err.message\n this.child = undefined\n this.health = 'failed'\n if (!this.stopping) this.scheduleRestart()\n })\n this.lastStartedAt = Date.now()\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 for (;;) {\n if (this.child === undefined) throw new Error('wiki process exited before ready')\n try {\n const res = await fetch(`${this.url}/status`, { signal: AbortSignal.timeout(2_000) })\n if (res.ok) {\n this.health = 'running'\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 /** One-click restart (route /dsh-tiddlywiki/restart, panel retry button). */\n async restart(): Promise<WikiStatusView> {\n await this.stop()\n return this.start()\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 }\n 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","/**\r\n * 会话相关 wiki 汇总(会话顶部「知识库」Tab 的后端)——把一次 DSH 会话(含其后代\r\n * subagent)在本会话中产生 / 读取 / 检索过的 wiki 笔记汇总成一篇 TW wikitext,写入\r\n * TW 的 volatile 命名空间 `$:/temp/dsh/session-summary/<会话ID>`。\r\n *\r\n * 判定来源(设计定稿,探查验证):\r\n * - Host 服务 `sessionQuery`(可选注入):`readSession(sessionId)` 返回完整事件日志\r\n * (events 升序),`traceSession` 返回祖先/后代树。绝不直接解析\r\n * `~/.dsh/sessions` 目录下的 session 文件——那是 zstd 压缩的。\r\n * - `tool/call` 事件精确记录每次 `tiddlywiki_*` 调用的 `name` 与\r\n * `arguments`(JSON 字符串)。\r\n * - `assistant/message` 事件 `message.content` 的 text 块可扫\r\n * `/dsh-tiddlywiki/tw/#...` 引用链接(视为「读取」)。\r\n *\r\n * 归类规则(§三 定稿):\r\n * 产生 📝 tiddlywiki_put / batch_put / rename(arguments 取 title)\r\n * 读取 👀 tiddlywiki_get(title)+ 助手回复里的 wiki 引用链接\r\n * 检索 🔍 search / recent(记关键词,不算单篇)\r\n * 范围 本会话 + 后代 subagent(traceSession 递归,仅子代理触达的笔记标注)\r\n *\r\n * 渲染(§四):写入 `$:/temp/...`——TW 5.4.1 默认 `$:/config/SyncFilter` 显式\r\n * `-[prefix[$:/temp/]]`(core/wiki/config/SyncFilter.tid),服务端 syncer 从不把\r\n * $:/temp 落盘:内存态、不进 git、TW 重启即消失(已实测:PUT 204、tiddlers/ 无文件、\r\n * git clean)。TW 用自己主题/链接/导航原生渲染,不另造 UI。\r\n *\r\n * @module dsh-tiddlywiki/host/session-summary\r\n */\r\nimport type { TiddlyWebClient } from './tw-api.ts'\r\n\r\n/** 会话汇总 tiddler 的 $:/temp 命名空间前缀。 */\r\nexport const SESSION_SUMMARY_PREFIX = '$:/temp/dsh/session-summary/'\r\n\r\n/** 会话内能触达 wiki 的 tiddlywiki_* 工具名(search/recent 只记检索记录,不算单篇)。 */\r\nconst NOTE_TOOL_NAMES = new Set(['tiddlywiki_put', 'tiddlywiki_batch_put', 'tiddlywiki_rename', 'tiddlywiki_get', 'tiddlywiki_search', 'tiddlywiki_recent'])\r\n\r\n/** 每篇笔记的触达摘要(按 title 去重,时间取最近一次)。 */\r\nexport interface NoteEntry {\r\n title: string\r\n action: 'produced' | 'read'\r\n /** 最近一次触达的 epoch ms。 */\r\n time: number\r\n /** 附加说明(如 rename 的旧标题)。 */\r\n detail?: string\r\n /** true = 仅由后代 subagent 触达过(本会话自己没碰过)。 */\r\n subagent: boolean\r\n}\r\n\r\n/** 一次检索类调用(search / recent),不算单篇笔记。 */\r\nexport interface SearchRecord {\r\n kind: 'search' | 'recent'\r\n query: string\r\n tags: string[]\r\n time: number\r\n subagent: boolean\r\n}\r\n\r\n/** 一次会话扫描的收集结果。 */\r\nexport interface Collected {\r\n produced: Map<string, NoteEntry>\r\n read: Map<string, NoteEntry>\r\n searches: SearchRecord[]\r\n}\r\n\r\n/**\r\n * 结构化 `sessionQuery` 服务脸(dsh-api-session-query 的极小子集,只声明本模块用到\r\n * 的方法;运行时实例是真实 Service,绝不是 Inspect 数据)。\r\n */\r\nexport interface SessionQueryFace {\r\n /** Read and replay-validate one complete logical session log (events ascending). */\r\n readSession(sessionId: string): Promise<SessionLogSnapshot>\r\n /** Trace known ancestry and descendants from one corpus observation. */\r\n traceSession(sessionId: string, signal?: AbortSignal): Promise<SessionLineageTrace>\r\n}\r\n\r\nexport interface SessionLogSnapshot {\r\n session: { id: string }\r\n events: SessionEvent[]\r\n}\r\n\r\nexport interface SessionEvent {\r\n type: string\r\n seq: number\r\n time: number\r\n data: Record<string, unknown>\r\n}\r\n\r\nexport interface SessionLineageTrace {\r\n descendants: SessionLineageNode[]\r\n}\r\n\r\nexport interface SessionLineageNode {\r\n session: { header: { id: string } }\r\n descendants: SessionLineageNode[]\r\n}\r\n\r\n/** 递归收集后代树里所有 session id(不含根自身)。 */\r\nfunction collectDescendantIds(nodes: SessionLineageNode[] | undefined, out: string[]): void {\r\n if (!Array.isArray(nodes)) return\r\n for (const node of nodes) {\r\n const id = node?.session?.header?.id\r\n if (typeof id === 'string' && id.length > 0) {\r\n out.push(id)\r\n collectDescendantIds(node?.descendants, out)\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * 汇总条目会直接写进 `[[标题]]` wikitext——标题既要能安全渲染、又不能制造死链。\r\n * 会话日志里常混入模型的示例/残缺写法(`#…`、`#标题`、带反引号的截断链接、\r\n * 控制字符等),这类收进来会被 TW 渲染成「佚失条目」;一律过滤。\r\n */\r\nfunction isPlausibleTitle(title: string): boolean {\r\n if (title.length === 0 || title.length > 300) return false\r\n if (title.trim().length === 0) return false\r\n if (/[\\u0000-\\u001f\\u007f]/.test(title)) return false // 控制字符 / 换行\r\n if (/[\\]|`#]/.test(title)) return false // 会破坏 [[…]] 链接语法\r\n if (title.startsWith('…') || title === '标题') return false // 占位 / 示例写法\r\n return true\r\n}\r\n\r\n/** 记录一篇笔记的触达;同标题合并(时间取最新、detail 取最新、subagent 取 AND)。 */\r\nfunction record(map: Map<string, NoteEntry>, entry: NoteEntry): void {\r\n if (entry.title.startsWith('$:/')) return\r\n if (!isPlausibleTitle(entry.title)) return\r\n const prev = map.get(entry.title)\r\n if (prev !== undefined) {\r\n prev.time = Math.max(prev.time, entry.time)\r\n if (entry.detail !== undefined) prev.detail = entry.detail\r\n prev.subagent = prev.subagent && entry.subagent\r\n return\r\n }\r\n map.set(entry.title, { ...entry })\r\n}\r\n\r\n/** 扫助手回复文本里的 `/dsh-tiddlywiki/tw/#标题` 引用链接 → 读取。 */\r\nfunction scanRefs(text: string, time: number, subagent: boolean, collected: Collected): void {\r\n // 只认到下一个 `)` / 空白 / `]` / `#` 为止——`#` 会截断后续碎片(模型示例常写成\r\n // `#…` 或 `#标题`,这些由 isPlausibleTitle 在 record 时过滤)。\r\n const re = /\\/dsh-tiddlywiki\\/tw\\/#([^#)\\s\\]]+)/g\r\n let m: RegExpExecArray | null\r\n while ((m = re.exec(text)) !== null) {\r\n const raw = m[1] ?? ''\r\n if (raw.length === 0) continue\r\n let title = raw\r\n try {\r\n title = decodeURIComponent(raw)\r\n } catch {\r\n /* keep raw on malformed percent-encoding */\r\n }\r\n if (title.length === 0) continue\r\n record(collected.read, { title, action: 'read', time, subagent })\r\n }\r\n}\r\n\r\n/** 扫描一次会话的事件日志,按 §三 规则收集笔记触达与检索记录。 */\r\nfunction scanSnapshot(snap: SessionLogSnapshot | undefined, subagent: boolean, collected: Collected): void {\r\n const events = snap?.events\r\n if (!Array.isArray(events)) return\r\n for (const ev of events) {\r\n const type = ev?.type\r\n const t = typeof ev?.time === 'number' ? ev.time : Date.now()\r\n if (type === 'tool/call') {\r\n const name = ev.data?.name\r\n if (typeof name !== 'string' || !NOTE_TOOL_NAMES.has(name)) continue\r\n let args: Record<string, unknown> = {}\r\n if (typeof ev.data?.arguments === 'string') {\r\n try {\r\n args = JSON.parse(ev.data.arguments) as Record<string, unknown>\r\n } catch {\r\n args = {}\r\n }\r\n }\r\n switch (name) {\r\n case 'tiddlywiki_put':\r\n if (typeof args.title === 'string' && args.title.length > 0) {\r\n record(collected.produced, { title: args.title, action: 'produced', time: t, subagent })\r\n }\r\n break\r\n case 'tiddlywiki_batch_put':\r\n if (Array.isArray(args.items)) {\r\n for (const item of args.items) {\r\n if (item !== null && typeof item === 'object' && typeof (item as { title?: unknown }).title === 'string') {\r\n const title = (item as { title: string }).title\r\n if (title.length > 0) record(collected.produced, { title, action: 'produced', time: t, subagent })\r\n }\r\n }\r\n }\r\n break\r\n case 'tiddlywiki_rename':\r\n if (typeof args.newTitle === 'string' && args.newTitle.length > 0) {\r\n const old = typeof args.oldTitle === 'string' && args.oldTitle.length > 0 ? args.oldTitle : undefined\r\n record(collected.produced, {\r\n title: args.newTitle,\r\n action: 'produced',\r\n time: t,\r\n detail: old !== undefined ? `重命名自「${old}」` : undefined,\r\n subagent,\r\n })\r\n }\r\n break\r\n case 'tiddlywiki_get':\r\n if (typeof args.title === 'string' && args.title.length > 0) {\r\n record(collected.read, { title: args.title, action: 'read', time: t, subagent })\r\n }\r\n break\r\n case 'tiddlywiki_search': {\r\n const query = typeof args.query === 'string' ? args.query : ''\r\n const tags = Array.isArray(args.tags) ? args.tags.filter((x): x is string => typeof x === 'string') : []\r\n collected.searches.push({ kind: 'search', query, tags, time: t, subagent })\r\n break\r\n }\r\n case 'tiddlywiki_recent':\r\n collected.searches.push({ kind: 'recent', query: '', tags: [], time: t, subagent })\r\n break\r\n }\r\n } else if (type === 'assistant/message') {\r\n const message = ev.data?.message\r\n const content = message !== null && typeof message === 'object' ? (message as { content?: unknown }).content : undefined\r\n if (Array.isArray(content)) {\r\n for (const block of content) {\r\n if (block === null || typeof block !== 'object') continue\r\n const b = block as { type?: unknown; text?: unknown }\r\n if (b.type === 'text' && typeof b.text === 'string' && b.text.length > 0) {\r\n scanRefs(b.text, t, subagent, collected)\r\n }\r\n }\r\n }\r\n }\r\n }\r\n}\r\n\r\n/** 当前状态(存在?标签?修改时间?摘要)。 */\r\ninterface TitleState {\r\n exists: boolean\r\n tags: string[]\r\n modified?: string\r\n snippet?: string\r\n}\r\n\r\n/** 并发(8 路)查询每篇笔记的当前状态(404 → 不存在/已删除)。 */\r\nasync function enrichTitles(client: TiddlyWebClient, titles: string[]): Promise<Map<string, TitleState>> {\r\n const out = new Map<string, TitleState>()\r\n const enquire = async (title: string): Promise<[string, TitleState]> => {\r\n try {\r\n const t = await client.get(title)\r\n if (t === undefined) return [title, { exists: false, tags: [] }]\r\n const flat = typeof t.text === 'string' ? t.text.replace(/\\s+/g, ' ').trim() : ''\r\n return [title, {\r\n exists: true,\r\n tags: t.tags ?? [],\r\n modified: typeof t.modified === 'string' ? t.modified : undefined,\r\n snippet: flat.length > 48 ? `${flat.slice(0, 48)}…` : flat,\r\n }]\r\n } catch {\r\n return [title, { exists: false, tags: [] }]\r\n }\r\n }\r\n // Bounded batches: parallel within a batch, insertion order preserved across\r\n // batches (Map order = the caller's title order, same as the old sequential).\r\n for (let i = 0; i < titles.length; i += 8) {\r\n const batch = titles.slice(i, i + 8)\r\n for (const [title, state] of await Promise.all(batch.map(enquire))) out.set(title, state)\r\n }\r\n return out\r\n}\r\n\r\n/** epoch ms → 本地 `YYYY-MM-DD HH:mm`。 */\r\nfunction fmtTime(ms: number): string {\r\n const d = new Date(ms)\r\n if (Number.isNaN(d.getTime())) return ''\r\n const p = (n: number): string => String(n).padStart(2, '0')\r\n return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`\r\n}\r\n\r\n/** TW modified ISO → 本地时间(解析失败则原样返回)。 */\r\nfunction fmtModified(iso: string | undefined): string {\r\n if (iso === undefined || iso.length === 0) return ''\r\n const ms = Date.parse(iso)\r\n return Number.isNaN(ms) ? iso : fmtTime(ms)\r\n}\r\n\r\n/** 组装分组 wikitext:产生 / 读取(未产生过的)/ 检索记录。 */\r\nfunction buildWikitext(\r\n sessionId: string,\r\n collected: Collected,\r\n producedTitles: string[],\r\n readTitles: string[],\r\n stateByTitle: Map<string, TitleState>,\r\n): string {\r\n const lines: string[] = []\r\n lines.push('! 会话相关 wiki 汇总')\r\n lines.push('')\r\n lines.push(\r\n `本页列出会话 \\`${sessionId}\\`(含其后代子代理)在本会话中产生 / 读取 / 检索过的知识库笔记。`,\r\n )\r\n lines.push('')\r\n const producedCount = producedTitles.length\r\n const readCount = readTitles.length\r\n const searchCount = collected.searches.length\r\n if (producedCount + readCount + searchCount === 0) {\r\n lines.push('> 本会话暂时没有产生、读取或检索过任何知识库笔记。')\r\n lines.push('>')\r\n lines.push('> 本页为会话自动生成的临时汇总(`$:/temp`,不落盘、不进 git,重启 TW 即消失);需要时点 Tab 顶栏的「🔄 刷新」重新生成。')\r\n return lines.join('\\n')\r\n }\r\n lines.push(\r\n '| 产生 📝 | 读取 👀 | 检索 🔍 | 涉及会话 |',\r\n '| --- | --- | --- | --- |',\r\n `| ${producedCount} | ${readCount} | ${searchCount} | ${collected.produced.size + readTitles.length > 0 ? '本会话 + 子代理' : '本会话'} |`,\r\n )\r\n lines.push('')\r\n\r\n const entryLine = (title: string, state: TitleState | undefined, entry: NoteEntry): string => {\r\n const bits: string[] = [`[[${title}]]`]\r\n if (state === undefined || !state.exists) {\r\n bits.push('⚠️ 已删除/不存在')\r\n } else {\r\n if (state.tags.length > 0) bits.push(`标签 ${state.tags.slice(0, 6).join('、')}${state.tags.length > 6 ? '…' : ''}`)\r\n const mod = fmtModified(state.modified)\r\n if (mod.length > 0) bits.push(`修改 ${mod}`)\r\n }\r\n const t = fmtTime(entry.time)\r\n if (t.length > 0) bits.push(`会话内 ${t}`)\r\n if (entry.detail !== undefined) bits.push(entry.detail)\r\n if (entry.subagent) bits.push('(子代理)')\r\n return `* ${bits.join(' · ')}`\r\n }\r\n\r\n if (producedCount > 0) {\r\n lines.push('!!! 📝 产生')\r\n for (const title of producedTitles) {\r\n const entry = collected.produced.get(title)\r\n if (entry !== undefined) lines.push(entryLine(title, stateByTitle.get(title), entry))\r\n }\r\n lines.push('')\r\n }\r\n if (readCount > 0) {\r\n lines.push('!!! 👀 读取')\r\n for (const title of readTitles) {\r\n const entry = collected.read.get(title)\r\n if (entry !== undefined) lines.push(entryLine(title, stateByTitle.get(title), entry))\r\n }\r\n lines.push('')\r\n }\r\n if (searchCount > 0) {\r\n lines.push('!!! 🔍 检索记录')\r\n for (const r of collected.searches) {\r\n const bits: string[] = []\r\n if (r.kind === 'search') {\r\n bits.push(`\\`tiddlywiki_search\\` query=「${r.query.length > 0 ? r.query : '(空)'}」`)\r\n if (r.tags.length > 0) bits.push(`tags ${r.tags.join('、')}`)\r\n } else {\r\n bits.push('`tiddlywiki_recent`(最近修改)')\r\n }\r\n const t = fmtTime(r.time)\r\n if (t.length > 0) bits.push(t)\r\n if (r.subagent) bits.push('(子代理)')\r\n lines.push(`* ${bits.join(' · ')}`)\r\n }\r\n lines.push('')\r\n }\r\n lines.push('---')\r\n lines.push('> 本页为会话自动生成的临时汇总(`$:/temp`,不落盘、不进 git,重启 TW 即消失);需要时点 Tab 顶栏的「🔄 刷新」重新生成。')\r\n return lines.join('\\n')\r\n}\r\n\r\nexport interface SessionSummaryResult {\r\n title: string\r\n counts: { produced: number; read: number; searches: number; sessions: number }\r\n empty: boolean\r\n generatedAt: string\r\n}\r\n\r\n/**\r\n * 生成并写入一篇会话汇总 tiddler($:/temp,volatile)。\r\n * @param client 可用 TW 客户端(调用方已保证 wiki 服务在线)。\r\n * @param sq sessionQuery 服务(调用方已保证可用)。\r\n * @param sessionId 目标会话 id(本会话 + 后代 subagent)。\r\n */\r\nexport async function writeSessionSummary(client: TiddlyWebClient, sq: SessionQueryFace, sessionId: string): Promise<SessionSummaryResult> {\r\n // 1) 会话范围:自身 + traceSession 后代树(递归)。trace 失败(会话已归档/不存在)\r\n // 就只汇总自身,绝不让单次失败拖垮整页。\r\n const ids: string[] = [sessionId]\r\n try {\r\n const trace = await sq.traceSession(sessionId, AbortSignal.timeout(10_000))\r\n collectDescendantIds(trace?.descendants, ids)\r\n } catch {\r\n /* trace unavailable → self only */\r\n }\r\n\r\n // 2) 逐个会话读完整事件日志并收集(任一失败跳过,其余照常)。\r\n const collected: Collected = { produced: new Map(), read: new Map(), searches: [] }\r\n for (const id of ids) {\r\n try {\r\n const snap = await sq.readSession(id)\r\n scanSnapshot(snap, id !== sessionId, collected)\r\n } catch {\r\n /* one bad session must not fail the whole summary */\r\n }\r\n }\r\n\r\n // 3) 产生优先:同时被产生+读取的笔记只列在「产生」,读取区只放未产生过的。\r\n const producedTitles = [...collected.produced.keys()].sort((a, b) => (collected.produced.get(b)?.time ?? 0) - (collected.produced.get(a)?.time ?? 0))\r\n const readTitles = [...collected.read.keys()]\r\n .filter((t) => !collected.produced.has(t))\r\n .sort((a, b) => (collected.read.get(b)?.time ?? 0) - (collected.read.get(a)?.time ?? 0))\r\n collected.searches.sort((a, b) => b.time - a.time)\r\n\r\n // 4) 查询当前状态(存在?标签?时间?)→ 组装 wikitext → PUT volatile tiddler。\r\n const stateByTitle = await enrichTitles(client, [...new Set([...producedTitles, ...readTitles])])\r\n const text = buildWikitext(sessionId, collected, producedTitles, readTitles, stateByTitle)\r\n const title = `${SESSION_SUMMARY_PREFIX}${sessionId}`\r\n await client.put({ title, text, type: 'text/vnd.tiddlywiki', tags: [] })\r\n\r\n return {\r\n title,\r\n counts: { produced: producedTitles.length, read: readTitles.length, searches: collected.searches.length, sessions: ids.length },\r\n empty: producedTitles.length + readTitles.length + collected.searches.length === 0,\r\n generatedAt: new Date().toISOString(),\r\n }\r\n}\r\n","/**\r\n * Shared node:http helpers for the DSH webserver routes (routes.ts, admin.ts).\r\n *\r\n * One implementation of body reading + JSON response writing so the route\r\n * layers cannot drift (they previously carried two/three copies).\r\n *\r\n * @module dsh-tiddlywiki/host/http\r\n */\r\nimport type { IncomingMessage, ServerResponse } from 'node:http'\r\n\r\n/** Default cap for small JSON bodies (note/restart/config…). */\r\nexport const MAX_JSON_BODY_BYTES = 2 * 1024 * 1024\r\n\r\n/** Cap for the /api passthrough body (tiddler content can be large). */\r\nexport const MAX_PROXY_BODY_BYTES = 16 * 1024 * 1024\r\n\r\n/** Cap for uploaded file bodies (/tw proxy, /upload). */\r\nexport const MAX_UPLOAD_BYTES = 64 * 1024 * 1024\r\n\r\nfunction readBodyStream(req: IncomingMessage, limit: number, encoding: true): Promise<string>\r\nfunction readBodyStream(req: IncomingMessage, limit: number, encoding: false): Promise<Buffer>\r\nfunction readBodyStream(req: IncomingMessage, limit: number, encoding: boolean): Promise<string | Buffer> {\r\n return new Promise((resolveP, rejectP) => {\r\n let size = 0\r\n const chunks: Buffer[] = []\r\n req.on('data', (chunk: Buffer) => {\r\n size += chunk.length\r\n if (size > limit) {\r\n rejectP(new Error('body too large'))\r\n req.destroy()\r\n return\r\n }\r\n chunks.push(chunk)\r\n })\r\n req.on('end', () => {\r\n const buf = Buffer.concat(chunks)\r\n resolveP(encoding ? buf.toString('utf8') : buf)\r\n })\r\n req.on('error', rejectP)\r\n })\r\n}\r\n\r\n/** Read a JSON (utf8) request body up to `limit` bytes (default MAX_JSON_BODY_BYTES). */\r\nexport function readBody(req: IncomingMessage, limit = MAX_JSON_BODY_BYTES): Promise<string> {\r\n return readBodyStream(req, limit, true)\r\n}\r\n\r\n/** Read a raw (binary-safe) request body up to `limit` bytes (default MAX_UPLOAD_BYTES). */\r\nexport function readBodyBuffer(req: IncomingMessage, limit = MAX_UPLOAD_BYTES): Promise<Buffer> {\r\n return readBodyStream(req, limit, false)\r\n}\r\n\r\n/** Write a JSON response (no-store, utf-8). */\r\nexport function json(res: ServerResponse, payload: unknown, status = 200): void {\r\n res.writeHead(status, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' })\r\n res.end(JSON.stringify(payload))\r\n}\r\n","/**\r\n * DSH webserver routes for dsh-tiddlywiki (design doc §10).\r\n *\r\n * | route | method | purpose |\r\n * |---------------------------|--------|------------------------------------------|\r\n * | /dsh-tiddlywiki/status | GET | panel health (service / url / git / tag) |\r\n * | /dsh-tiddlywiki/note | POST | quick-note → independent tiddler |\r\n * | /dsh-tiddlywiki/restart | POST | one-click retry/restart of the TW child |\r\n * | /dsh-tiddlywiki/search | GET | keyword search (reply-stream tool card) |\r\n * | /dsh-tiddlywiki/api/* | any | passthrough to the TW service (JSON) |\r\n * | /dsh-tiddlywiki/tw/* | any | SAME-ORIGIN TW proxy (index + files + TiddlyWeb API) |\r\n *\r\n * Matching is exact-over-prefix, so the exact routes win and the `/api` /\r\n * `/tw` prefixes catch the rest. Client calls are same-origin (the DSH web\r\n * server), so no CORS is involved. The `/tw` proxy is the remote-access\r\n * bridge: it serves the ENTIRE TW frontend to the browser through the DSH\r\n * origin (see TW_PROXY_PATH in wiki.ts), so the embedded editor works no\r\n * matter which host/domain the user reaches DSH on.\r\n *\r\n * @module dsh-tiddlywiki/host/routes\r\n */\r\nimport type { IncomingHttpHeaders, IncomingMessage, ServerResponse } from 'node:http'\r\nimport { mkdir, writeFile, access } from 'node:fs/promises'\r\nimport { basename, extname, join } from 'node:path'\r\nimport type { TiddlyWebClient } from './tw-api.ts'\r\nimport type { WikiServer } from './wiki.ts'\r\nimport type { GitFace } from './git.ts'\r\nimport { PATH_PREFIX, TW_PROXY_PREFIX, TW_PROXY_PATH } from './wiki.ts'\r\nimport { writeSessionSummary, type SessionQueryFace } from './session-summary.ts'\r\nimport { readBody, readBodyBuffer, json, MAX_PROXY_BODY_BYTES, MAX_UPLOAD_BYTES } from './http.ts'\r\n\r\nexport { writeSessionSummary, SESSION_SUMMARY_PREFIX } from './session-summary.ts'\r\nexport type { SessionQueryFace, SessionSummaryResult } from './session-summary.ts'\r\n\r\nexport const ROUTE_PREFIX = PATH_PREFIX\r\n\r\n/** Tiddler type for quick-notes: Markdown, so the uploaded images/links and\r\n * any Markdown in the note actually render in TW (a type-less tiddler is\r\n * treated as plain wiki text and shows raw `![..]`/`[..]` instead). */\r\nconst NOTE_TYPE = 'text/markdown'\r\n\r\n/** Structural webserver face (a subset of dsh-host-webserver). */\r\nexport interface WebServerFace {\r\n register(route: { kind: 'exact' | 'prefix'; path: string; handler: (req: IncomingMessage, res: ServerResponse) => void }): () => void\r\n}\r\n\r\n/**\r\n * Structural face over the DSH `sessionController` service (a subset of\r\n * dsh-api-session-controller). Only the methods the agent-send routes need are\r\n * declared; the runtime instance is a real Service, never inspected data.\r\n */\r\nexport interface SessionControllerFace {\r\n prompt(\r\n request: { requestId: string; sessionId: string; mode: 'queue' | 'steer'; content: Array<{ type: 'text'; text: string }> },\r\n signal: AbortSignal,\r\n ): Promise<{ accepted: boolean }>\r\n list(\r\n request: { cursor?: string },\r\n signal: AbortSignal,\r\n ): Promise<{\r\n items: Array<{\r\n sessionId: string\r\n updatedAt?: number\r\n running?: boolean\r\n blank?: boolean\r\n parentSessionId?: string\r\n cwd?: string\r\n }>\r\n }>\r\n create(request: { cwd?: string; workspaceId?: string; agentPreset?: string }): Promise<{ sessionId: string }>\r\n}\r\n\r\n/**\r\n * Structural face over the DSH `workspaceRegistry` service (a subset of\r\n * dsh-workspace). Only what the agent-create route needs is declared; the\r\n * runtime instance is a real Service, never inspected data.\r\n */\r\nexport interface WorkspaceRegistryFace {\r\n /** Resolve or create the workspace owning `path` — idempotent by canonical path. */\r\n create(path: string, title?: string): Promise<{ id: string; path: string }>\r\n}\r\n\r\n/**\r\n * Structural face over the DSH `agentPresets` service (a subset of\r\n * dsh-agent-presets). It is the deployment's \"工作模式\" registry — the agent\r\n * presets a session can be composed from (default / cordis / blade / …). Only\r\n * `list` + `resolve` (default id) are needed by the agent-modes route.\r\n */\r\nexport interface AgentPresetsFace {\r\n /** Every preset the configured roots currently supply. */\r\n list(): Promise<Array<{ id: string; name?: string; description?: string; trust?: string; broken?: string }>>\r\n /** Resolve one preset by id (`undefined` = the deployment default). */\r\n resolve(id?: string): Promise<{ id: string; name?: string; description?: string }>\r\n}\r\n\r\n/**\r\n * Structural face over the DSH `sessionPersistence` service. Only the\r\n * lightweight `list` (metadata headers, no log parse) is needed so the\r\n * agent-sessions route can attach each session's recorded `agentPreset`.\r\n */\r\nexport interface SessionPersistenceFace {\r\n /** One header per materialized session (carries `agentPreset` when set). */\r\n list(signal?: AbortSignal): Promise<Array<{ id: string; agentPreset?: string }>>\r\n}\r\n\r\n/**\r\n * Structural face over the DSH `permissionPresets` service (a subset of\r\n * dsh-permission-presets). It owns the deployment's permission presets — each\r\n * bundles a sandbox mode + approval policy (e.g. `workspace-write` = write\r\n * inside the workspace with approval, `danger-full-access` = no prompts). The\r\n * agent-modes route exposes the option list to the TW picker, and agent-create\r\n * applies the chosen preset to the new session's log via `set`.\r\n */\r\nexport interface PermissionPresetsFace {\r\n /** Every switchable preset name, in declaration order. */\r\n readonly names: readonly string[]\r\n /** The preset currently selected as the default for new sessions. */\r\n readonly defaultPreset: string\r\n /** Build the client option ({ value, name, description? }) for one preset. */\r\n optionOf(name: string): { value: string; name: string; description?: string }\r\n /** Record a preset switch on a live session (durable, log-only user intent). */\r\n set(session: unknown, name: string): void\r\n}\r\n\r\n/**\r\n * Structural face over the DSH `sessions` in-memory store (a subset of\r\n * dsh-session). Only `get` is needed: after `sessionController.create`\r\n * resolves, the new session is already materialized here, so agent-create can\r\n * hand it to `permissionPresets.set`.\r\n */\r\nexport interface SessionsFace {\r\n get(id: string): unknown\r\n}\r\n\r\n/** Effective UI flags returned by /status (mirror index.ts). */\r\nexport interface UiDefaultsPublic {\r\n showQuickNote: boolean\r\n /** 聊天输入框上方的「快速笔记」快捷按钮(conversation.input.dock 槽位)。 */\r\n showQuickNoteDock: boolean\r\n /** 点击「快速笔记」后的打开方式:native=直达 TW 原生编辑器;card=Markdown 卡片。 */\r\n quickNoteMode: 'native' | 'card'\r\n /** 左侧侧边栏 TW 入口的显示名称。 */\r\n sidebarLabel: string\r\n showPanelStatus: boolean\r\n showSyncButton: boolean\r\n /** 嵌入式 TW 是否跟随 DSH 深浅主题(false 时客户端停止 palette 同步)。 */\r\n followDshTheme: boolean\r\n /** DSH 暗色时 TW 使用的 palette tiddler 标题。 */\r\n darkPalette: string\r\n /** 会话顶部「知识库」Tab 的显示名称(默认「知识库」)。 */\r\n tabLabel: string\r\n /** 是否在会话顶部显示「知识库」Tab(会话相关 wiki 汇总,默认 true)。 */\r\n showSessionTab: boolean\r\n /** 是否在 DSH 右侧边栏提供 TiddlyWiki 入口/Tab(默认 true)。 */\r\n showRightbarTab: boolean\r\n}\r\n\r\nexport interface RouteDeps {\r\n server: WikiServer\r\n getClient: () => TiddlyWebClient | undefined\r\n git: GitFace\r\n autoCommit: () => void\r\n noteDefaults: () => { tag: string }\r\n uiDefaults: () => UiDefaultsPublic\r\n getWikiPath: () => string\r\n /** Optional DSH sessionController service (agent-send routes only); resolved\r\n * lazily per request because it may register after webServer appears. */\r\n getSessionController: () => SessionControllerFace | undefined\r\n /** Optional DSH workspaceRegistry service (agent-create route); resolves a\r\n * cwd to a real Workspace so new sessions land inside it instead of the\r\n * ungrouped bucket. */\r\n getWorkspaceRegistry: () => WorkspaceRegistryFace | undefined\r\n /** Optional DSH agentPresets service (agent-modes route): the deployment's\r\n * \"工作模式\" roster. Resolved lazily per request like sessionController. */\r\n getAgentPresets: () => AgentPresetsFace | undefined\r\n /** Optional DSH sessionPersistence service (agent-sessions route): attaches\r\n * each session's recorded agentPreset so the picker can badge it. */\r\n getSessionPersistence: () => SessionPersistenceFace | undefined\r\n /** Optional DSH permissionPresets service (agent-modes permissions list +\r\n * agent-create applies the chosen permission preset to the new session). */\r\n getPermissionPresets: () => PermissionPresetsFace | undefined\r\n /** Optional DSH `sessions` in-memory store (agent-create hands the created\r\n * live session to permissionPresets.set). */\r\n getSessions: () => SessionsFace | undefined\r\n /** Optional DSH sessionQuery service (session-summary route): reads a\r\n * session's complete event log + descendant tree to decide which wiki notes\r\n * belong to this conversation. Resolved lazily like the other services. */\r\n getSessionQuery: () => SessionQueryFace | undefined\r\n /** Whether the one-click send-to-agent feature is enabled (config switch). */\r\n sendToAgentEnabled: () => boolean\r\n /** Optional shared token that must match `x-send-to-agent-token` when set. */\r\n sendToAgentToken: () => string\r\n}\r\n\r\n/** Header names forwarded to the upstream TW service by the proxy routes. */\r\nconst FORWARD_HEADER_NAMES = [\r\n 'accept', 'accept-encoding', 'content-type', 'cookie', 'authorization',\r\n 'if-none-match', 'if-modified-since', 'origin', 'referer', 'user-agent',\r\n] as const\r\n\r\n/** Copy a safe, string-valued subset of the request headers upstream. */\r\nfunction forwardHeaders(headers: IncomingHttpHeaders): Record<string, string> {\r\n const out: Record<string, string> = {}\r\n for (const name of FORWARD_HEADER_NAMES) {\r\n // Index through the string index signature so known header names (typed\r\n // `string`) do not hide the `string[]` repeat case via their specific\r\n // property declarations.\r\n const value: string | string[] | undefined = headers[name as string]\r\n if (typeof value === 'string') out[name] = value\r\n else if (Array.isArray(value) && value.length > 0) out[name] = value.join(', ')\r\n }\r\n return out\r\n}\r\n\r\n/**\r\n * Sanitize an uploaded filename into a safe bare name (no path separators,\r\n * no `..`, no control characters). Returns '' when nothing usable remains.\r\n */\r\nfunction sanitizeUploadName(input: unknown): string {\r\n if (typeof input !== 'string') return ''\r\n const name = basename(input.trim().replace(/[\\\\/]+/g, '/'))\r\n .replace(/[\\u0000-\\u001f\\u007f]/g, '')\r\n .replace(/[<>:\"|?*]/g, '_')\r\n .replace(/^\\.+/, '')\r\n .trim()\r\n if (name.length === 0 || name === '.' || name === '..') return ''\r\n if (name.length > 160) return name.slice(0, 160)\r\n return name\r\n}\r\n\r\nfunction pad(n: number): string {\r\n return n < 10 ? `0${n}` : String(n)\r\n}\r\n\r\n/** Flat one-line snippet for the recent-notes picker. */\r\nfunction snippetOf(text: string, max = 120): string {\r\n const flat = text.replace(/\\s+/g, ' ').trim()\r\n return flat.length <= max ? flat : `${flat.slice(0, max)}…`\r\n}\r\n\r\n/** Default note title: `YYYY-MM-DD HH:mm` (design doc D6). */\r\nfunction timestampTitle(date = new Date()): string {\r\n return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`\r\n}\r\n\r\n/**\r\n * Open a tiddler in TW's NATIVE editor: save the tiddler (when text is\r\n * non-empty) as Markdown, reuse or create a DRAFT tiddler carrying\r\n * `draft.of`/`draft.title` (TW's story view renders drafts with the\r\n * EditTemplate — list.js: `isDraft && editTemplate`), and return the draft\r\n * title so the client can navigate the panel iframe to `#<draftTitle>`.\r\n * The draft carries the same `text/markdown` type as the note so saving it in\r\n * TW keeps Markdown (a draft without a matching type would overwrite the\r\n * note's type back to plain wiki text).\r\n */\r\nexport async function openInTwEditor(\r\n client: TiddlyWebClient,\r\n title: string,\r\n text: string,\r\n tags: string[],\r\n): Promise<{ title: string; draftTitle: string }> {\r\n if (text.trim().length > 0) {\r\n await client.put({ title, text, tags, type: NOTE_TYPE })\r\n }\r\n // Draft content: the provided text, else the existing tiddler's content.\r\n let draftText = text\r\n if (draftText.trim().length === 0) {\r\n const existing = await client.get(title)\r\n draftText = existing?.text ?? ''\r\n }\r\n // Reuse an existing draft for this title (mirrors wiki.findDraft).\r\n let draftTitle: string | undefined\r\n try {\r\n const items = await client.list(undefined, true)\r\n for (const item of items) {\r\n if (item['draft.of'] === title && typeof item.title === 'string') {\r\n draftTitle = item.title\r\n break\r\n }\r\n }\r\n } catch {\r\n /* fall back to a fresh draft */\r\n }\r\n if (draftTitle === undefined) draftTitle = `Draft of \"${title}\" ${Date.now()}`\r\n await client.put({ title: draftTitle, text: draftText, 'draft.of': title, 'draft.title': title, type: NOTE_TYPE })\r\n return { title, draftTitle }\r\n}\r\n\r\n/** Resolve note tags from the request body: `tags` array wins, then the\r\n * legacy single `tag` string, then the configured default tag. */\r\nfunction resolveTags(\r\n body: { tag?: unknown; tags?: unknown },\r\n defaultTag: string,\r\n): string[] {\r\n if (Array.isArray(body.tags)) {\r\n const tags = body.tags.filter((t): t is string => typeof t === 'string' && t.trim().length > 0).map((t) => t.trim())\r\n if (tags.length > 0) return tags\r\n }\r\n if (typeof body.tag === 'string' && body.tag.trim().length > 0) {\r\n return body.tag.trim().split(/\\s+/).filter(Boolean)\r\n }\r\n return [defaultTag]\r\n}\r\n\r\nexport function registerRoutes(ctx: { webServer: WebServerFace }, deps: RouteDeps): () => void {\r\n const handleStatus = async (_req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n const view = deps.server.status()\r\n let gitSummary: GitStatusViewPublic | null = null\r\n try {\r\n gitSummary = await deps.git.status(deps.getWikiPath())\r\n } catch {\r\n gitSummary = null\r\n }\r\n json(res, { ok: true, ...view, twProxy: TW_PROXY_PATH, git: gitSummary, note: { tag: deps.noteDefaults().tag }, ui: deps.uiDefaults() })\r\n }\r\n\r\n /**\r\n * POST /dsh-tiddlywiki/session/summary — 生成当前会话的 wiki 汇总页(「知识库」\r\n * Tab 的后端)。body `{ session: <会话ID> }`;后端用 sessionQuery 读本会话(含\r\n * 后代 subagent)的完整事件日志,按「产生/读取/检索」收集 tiddlywiki_* 笔记,\r\n * 查询每篇当前状态,组装 TW wikitext 写入 `$:/temp/dsh/session-summary/<会话ID>`\r\n * (volatile:不落盘、不进 git),返回生成的 tiddler title 供前端 iframe 打开。\r\n */\r\n const handleSessionSummary = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n let body: { session?: unknown } = {}\r\n try {\r\n body = JSON.parse(await readBody(req)) as { session?: unknown }\r\n } catch {\r\n /* malformed body → session check below rejects */\r\n }\r\n const session = typeof body.session === 'string' && body.session.trim().length > 0 ? body.session.trim() : ''\r\n if (session.length === 0) {\r\n json(res, { ok: false, error: 'session is required' }, 400)\r\n return\r\n }\r\n const client = deps.getClient()\r\n if (client === undefined) {\r\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\r\n return\r\n }\r\n const sq = deps.getSessionQuery()\r\n if (sq === undefined) {\r\n json(res, { ok: false, error: 'session query service unavailable' }, 503)\r\n return\r\n }\r\n const result = await writeSessionSummary(client, sq, session)\r\n json(res, { ok: true, ...result, twUrl: TW_PROXY_PATH })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n /**\r\n * GET /dsh-tiddlywiki/agent/sessions — visible ordinary sessions for the TW\r\n * one-click picker (excludes subagent sessions, activity-descending). Each\r\n * item also carries its recorded `agentPreset` (工作模式), when known, so the\r\n * picker can badge existing sessions — read from the lightweight persistence\r\n * header list, never a full log parse.\r\n */\r\n const handleAgentSessions = async (_req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n const sc = deps.getSessionController()\r\n if (sc === undefined) {\r\n json(res, { ok: false, error: 'session service unavailable' }, 503)\r\n return\r\n }\r\n const list = await sc.list({}, AbortSignal.timeout(10_000))\r\n // sessionId → agentPreset, from the durable header list (degrade silently).\r\n const presetById: Record<string, string> = {}\r\n const pers = deps.getSessionPersistence()\r\n if (pers !== undefined) {\r\n try {\r\n const headers = await pers.list(AbortSignal.timeout(5_000))\r\n for (const h of headers) {\r\n if (typeof h.agentPreset === 'string' && h.agentPreset.length > 0) presetById[h.id] = h.agentPreset\r\n }\r\n } catch {\r\n /* header list unavailable → no mode badges, picker still works */\r\n }\r\n }\r\n const items = (list.items ?? [])\r\n .filter((s) => s.parentSessionId === undefined)\r\n .map((s) => ({\r\n sessionId: s.sessionId,\r\n cwd: s.cwd ?? null,\r\n running: !!s.running,\r\n blank: !!s.blank,\r\n updatedAt: s.updatedAt ?? 0,\r\n agentPreset: presetById[s.sessionId] ?? null,\r\n }))\r\n .sort((a, b) => b.updatedAt - a.updatedAt)\r\n json(res, { ok: true, items })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n /**\r\n * Shared gate for the TW-side send-to-agent routes: feature switch\r\n * (`ui.sendToAgent.enabled`) then, when a shared token is configured, the\r\n * `x-send-to-agent-token` header must match. Returns false after writing the\r\n * error response — the caller just does `if (!guard(...)) return`.\r\n */\r\n const guardSendToAgent = (req: IncomingMessage, res: ServerResponse): boolean => {\r\n if (!deps.sendToAgentEnabled()) {\r\n json(res, { ok: false, error: 'send-to-agent is disabled' }, 403)\r\n return false\r\n }\r\n const token = deps.sendToAgentToken().trim()\r\n if (token.length === 0) return true\r\n const got = req.headers['x-send-to-agent-token']\r\n const value = typeof got === 'string' ? got : Array.isArray(got) ? got[0] ?? '' : ''\r\n if (value === token) return true\r\n json(res, { ok: false, error: 'unauthorized' }, 401)\r\n return false\r\n }\r\n\r\n /**\r\n * GET /dsh-tiddlywiki/agent/modes — available \"工作模式\" (Agent presets) for\r\n * the TW picker: id/name/description per preset plus the deployment default.\r\n * Guards mirror the other agent routes (feature switch + optional token).\r\n */\r\n const handleAgentModes = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n if (!guardSendToAgent(req, res)) return\r\n const ap = deps.getAgentPresets()\r\n if (ap === undefined) {\r\n json(res, { ok: false, error: 'agent presets service unavailable' }, 503)\r\n return\r\n }\r\n const presets = await ap.list()\r\n let defaultId: string | undefined\r\n try {\r\n defaultId = (await ap.resolve())?.id\r\n } catch {\r\n defaultId = undefined\r\n }\r\n // The picker also needs the permission-preset roster (a \"权限\" selector\r\n // for newly created sessions). Best-effort: when the permissionPresets\r\n // service is not mounted (older host), `permissions` is null and the\r\n // picker simply hides the selector — modes still work.\r\n let permissions: { defaultId: string | null; items: Array<{ value: string; name: string; description?: string }> } | null = null\r\n const pp = deps.getPermissionPresets()\r\n if (pp !== undefined) {\r\n try {\r\n permissions = {\r\n defaultId: pp.defaultPreset ?? null,\r\n items: pp.names.map((n) => pp.optionOf(n)),\r\n }\r\n } catch {\r\n permissions = null\r\n }\r\n }\r\n json(res, {\r\n ok: true,\r\n defaultId: defaultId ?? null,\r\n items: presets.map((p) => ({\r\n id: p.id,\r\n name: p.name ?? p.id,\r\n description: p.description ?? '',\r\n trust: p.trust ?? 'user',\r\n broken: p.broken ?? null,\r\n isDefault: p.id === defaultId,\r\n })),\r\n permissions,\r\n })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n /**\r\n * POST /dsh-tiddlywiki/agent/send — deliver a note to one agent session as a\r\n * queued user message (sessionController.prompt, the same API the GUI chat\r\n * input uses). Guards: feature switch, optional shared token, body shape.\r\n */\r\n const handleAgentSend = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n if (!guardSendToAgent(req, res)) return\r\n const body = JSON.parse(await readBody(req)) as { sessionId?: unknown; text?: unknown }\r\n const sessionId = typeof body.sessionId === 'string' && body.sessionId.trim().length > 0 ? body.sessionId.trim() : ''\r\n const text = typeof body.text === 'string' && body.text.trim().length > 0 ? body.text.trim() : ''\r\n if (sessionId.length === 0 || text.length === 0) {\r\n json(res, { ok: false, error: 'sessionId and text are required' }, 400)\r\n return\r\n }\r\n const sc = deps.getSessionController()\r\n if (sc === undefined) {\r\n json(res, { ok: false, error: 'session service unavailable' }, 503)\r\n return\r\n }\r\n const requestId = `tw-send-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`\r\n await sc.prompt(\r\n { requestId, sessionId, mode: 'queue', content: [{ type: 'text', text }] },\r\n AbortSignal.timeout(20_000),\r\n )\r\n json(res, { ok: true, requestId, sessionId })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n /**\r\n * POST /dsh-tiddlywiki/agent/create — create (or adopt) one ordinary session\r\n * inside a real DSH workspace resolved from the requested path. The picker\r\n * uses it for \"new workspace / new session\": the directory is materialised so\r\n * a brand-new workspace actually exists on disk, the path is resolved to its\r\n * (idempotent) Workspace, and the session is created with `workspaceId` so it\r\n * lands under that workspace in the sidebar. Creating with bare `cwd` instead\r\n * would leave the session in the ungrouped bucket even when its working\r\n * directory matches an existing workspace path.\r\n *\r\n * Optional `mode` names the \"工作模式\" (an Agent preset id, e.g. from\r\n * /agent/modes); it is forwarded to `sessionController.create(agentPreset)`\r\n * so the new session launches under that preset. Omitted → deployment default.\r\n *\r\n * Optional `permission` names a \"权限\" preset (e.g. from /agent/modes'\r\n * `permissions` roster). After the session is created it is applied to the\r\n * live session's log via `permissionPresets.set` (durable knob events:\r\n * `permission/preset`, `sandbox/mode`, `approval/policy`), overriding the\r\n * deployment default pinned at creation. Omitted → keep the default.\r\n */\r\n const handleAgentCreate = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n if (!guardSendToAgent(req, res)) return\r\n const body = JSON.parse(await readBody(req)) as { cwd?: unknown; mode?: unknown; permission?: unknown }\r\n const cwd = typeof body.cwd === 'string' ? body.cwd.trim() : ''\r\n const mode = typeof body.mode === 'string' && body.mode.trim().length > 0 ? body.mode.trim() : undefined\r\n const permission = typeof body.permission === 'string' && body.permission.trim().length > 0 ? body.permission.trim() : undefined\r\n // Validate the permission preset BEFORE creating the session (fail fast,\r\n // so a bad name never leaves an orphaned session behind).\r\n const pp = deps.getPermissionPresets()\r\n if (permission !== undefined) {\r\n if (pp === undefined) {\r\n json(res, { ok: false, error: 'permission selected but the permission-presets service is unavailable' }, 503)\r\n return\r\n }\r\n if (!pp.names.includes(permission)) {\r\n json(res, { ok: false, error: `unknown permission preset \"${permission}\" (available: ${pp.names.join(', ')})` }, 400)\r\n return\r\n }\r\n }\r\n const sc = deps.getSessionController()\r\n if (sc === undefined) {\r\n json(res, { ok: false, error: 'session service unavailable' }, 503)\r\n return\r\n }\r\n const ws = deps.getWorkspaceRegistry()\r\n if (cwd.length > 0) {\r\n await mkdir(cwd, { recursive: true })\r\n }\r\n let created: { sessionId: string }\r\n let workspaceId: string | undefined\r\n if (cwd.length > 0 && ws !== undefined) {\r\n const workspace = await ws.create(cwd)\r\n workspaceId = workspace.id\r\n created = await sc.create({ workspaceId, agentPreset: mode })\r\n } else {\r\n created = await sc.create({ cwd: cwd.length > 0 ? cwd : undefined, agentPreset: mode })\r\n }\r\n // Apply the chosen permission preset to the just-created live session\r\n // (best-effort — the session is already created either way).\r\n let permissionApplied = false\r\n if (permission !== undefined) {\r\n const sessionsSvc = deps.getSessions()\r\n if (sessionsSvc !== undefined) {\r\n try {\r\n const session = sessionsSvc.get(created.sessionId)\r\n if (session !== undefined) {\r\n pp?.set(session, permission)\r\n permissionApplied = true\r\n }\r\n } catch {\r\n /* permission is an optional convenience; never fail the create */\r\n }\r\n }\r\n }\r\n json(res, {\r\n ok: true,\r\n sessionId: created.sessionId,\r\n cwd: cwd || null,\r\n workspaceId: workspaceId ?? null,\r\n mode: mode ?? null,\r\n permission: permission ?? null,\r\n permissionApplied,\r\n })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n const handleNote = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n const body = JSON.parse(await readBody(req)) as { title?: unknown; tag?: unknown; tags?: unknown; text?: unknown }\r\n const text = typeof body.text === 'string' && body.text.trim().length > 0 ? body.text.trim() : null\r\n if (text === null) {\r\n json(res, { ok: false, error: 'text is required' }, 400)\r\n return\r\n }\r\n const client = deps.getClient()\r\n if (client === undefined) {\r\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\r\n return\r\n }\r\n const title = typeof body.title === 'string' && body.title.trim().length > 0 ? body.title.trim() : timestampTitle()\r\n const tags = resolveTags(body, deps.noteDefaults().tag)\r\n await client.put({ title, text, tags, type: NOTE_TYPE })\r\n deps.autoCommit()\r\n json(res, { ok: true, title, tag: tags.join(' '), tags, text, type: NOTE_TYPE })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n const handleEdit = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n const body = JSON.parse(await readBody(req)) as { title?: unknown; tag?: unknown; tags?: unknown; text?: unknown }\r\n const client = deps.getClient()\r\n if (client === undefined) {\r\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\r\n return\r\n }\r\n const title = typeof body.title === 'string' && body.title.trim().length > 0 ? body.title.trim() : timestampTitle()\r\n const tags = resolveTags(body, deps.noteDefaults().tag)\r\n const text = typeof body.text === 'string' ? body.text : ''\r\n const result = await openInTwEditor(client, title, text, tags)\r\n deps.autoCommit()\r\n json(res, { ok: true, ...result, twUrl: TW_PROXY_PATH })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n /** Distinct non-system tags for the quick-note tag autocomplete. The flat\r\n * `tags` array feeds note-widget's chip autocomplete; the parallel `items`\r\n * (tag → tiddler count) feeds the reply-stream `tiddlywiki_list_tags` tool\r\n * card, so both consumers share one endpoint. */\r\n const handleTags = async (_req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n const client = deps.getClient()\r\n if (client === undefined) {\r\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\r\n return\r\n }\r\n try {\r\n const items = await client.list(undefined, false)\r\n const counts = new Map<string, number>()\r\n for (const item of items) {\r\n for (const tag of item.tags ?? []) {\r\n if (tag.length > 0 && !tag.startsWith('$:/')) counts.set(tag, (counts.get(tag) ?? 0) + 1)\r\n }\r\n }\r\n const tags = [...counts.keys()].sort((a, b) => a.localeCompare(b, 'zh'))\r\n json(res, {\r\n ok: true,\r\n tags,\r\n items: tags.map((tag) => ({ tag, count: counts.get(tag) ?? 0 })),\r\n })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n /** Recent non-system tiddlers for the quick-note \"最近\" picker (newest first). */\r\n const handleRecent = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n const client = deps.getClient()\r\n if (client === undefined) {\r\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\r\n return\r\n }\r\n try {\r\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\r\n const limitRaw = Number(url.searchParams.get('limit') ?? 15)\r\n const limit = Number.isFinite(limitRaw) ? Math.max(1, Math.min(Math.floor(limitRaw), 200)) : 15\r\n const items = await client.recent(limit)\r\n json(res, {\r\n ok: true,\r\n limit,\r\n items: items.map((t) => ({\r\n title: t.title,\r\n tags: t.tags ?? [],\r\n modified: typeof t.modified === 'string' ? t.modified : null,\r\n snippet: snippetOf(t.text ?? ''),\r\n })),\r\n })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n /** Full tiddler for the quick-note \"最近\" picker (load into the editor). */\r\n const handleGet = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n const client = deps.getClient()\r\n if (client === undefined) {\r\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\r\n return\r\n }\r\n try {\r\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\r\n const title = url.searchParams.get('title') ?? ''\r\n if (title.length === 0) {\r\n json(res, { ok: false, error: 'missing title' }, 400)\r\n return\r\n }\r\n const t = await client.get(title)\r\n if (t === undefined) {\r\n json(res, { ok: false, notFound: true, title }, 404)\r\n return\r\n }\r\n const fields: Record<string, unknown> = {}\r\n for (const [k, v] of Object.entries(t)) {\r\n if (k === 'title' || k === 'text' || k === 'tags') continue\r\n fields[k] = v\r\n }\r\n json(res, {\r\n ok: true,\r\n title: t.title,\r\n text: t.text ?? '',\r\n tags: t.tags ?? [],\r\n type: t.type ?? 'text/vnd.tiddlywiki',\r\n modified: typeof t.modified === 'string' ? t.modified : null,\r\n fields,\r\n })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n /**\r\n * GET /dsh-tiddlywiki/search — keyword search for the reply-stream tool card\r\n * (mirrors tools.ts tiddlywiki_search: same local substring/tag/since/type\r\n * matching via the TiddlyWebClient). Returns hit titles/tags/modified/\r\n * snippets so the card can list clickable wiki links.\r\n */\r\n const handleSearch = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n const client = deps.getClient()\r\n if (client === undefined) {\r\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\r\n return\r\n }\r\n try {\r\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\r\n const query = url.searchParams.get('query') ?? ''\r\n const tags = url.searchParams.getAll('tags').filter((t) => t.length > 0)\r\n const tag = url.searchParams.get('tag') ?? undefined\r\n const since = url.searchParams.get('since') ?? undefined\r\n const type = url.searchParams.get('type') ?? undefined\r\n const limitRaw = Number(url.searchParams.get('limit') ?? 30)\r\n const limit = Number.isFinite(limitRaw) ? Math.max(1, Math.min(Math.floor(limitRaw), 200)) : 30\r\n const { items, total } = await client.search(query, { tags, tag, since, type, limit })\r\n json(res, {\r\n ok: true,\r\n query,\r\n tags,\r\n tag: tag ?? null,\r\n since: since ?? null,\r\n type: type ?? null,\r\n limit,\r\n total,\r\n items: items.map((t) => ({\r\n title: t.title,\r\n tags: t.tags ?? [],\r\n modified: typeof t.modified === 'string' ? t.modified : null,\r\n snippet: snippetOf(t.text ?? ''),\r\n })),\r\n })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n const handleRestart = async (_req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n await deps.server.restart()\r\n json(res, { ok: true, status: deps.server.status().status })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n /** One-click git sync for the floating button / settings page: pull →\r\n * commit → push, then return the fresh status. Mirrors the agent tool's\r\n * `action=sync` (design doc §7 conflict policy — rebase conflict aborts).\r\n * When the pull actually changed the working tree, the running TW child\r\n * still holds the old in-memory snapshot — restart it (same port) so the\r\n * UI reflects the pulled files instead of looking stale. */\r\n const handleSync = async (_req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n const dir = deps.getWikiPath()\r\n const status = async (): Promise<GitStatusViewPublic | null> => {\r\n try { return await deps.git.status(dir) } catch { return null }\r\n }\r\n try {\r\n const pulled = await deps.git.pull(dir)\r\n if (!pulled.ok) {\r\n json(res, {\r\n ok: false,\r\n action: 'sync',\r\n message: pulled.message,\r\n ...(pulled.conflictFiles !== undefined ? { conflictFiles: pulled.conflictFiles } : {}),\r\n status: await status(),\r\n }, 409)\r\n return\r\n }\r\n let restarted = false\r\n let restartError: string | undefined\r\n if (pulled.changed === true) {\r\n try {\r\n await deps.server.restart()\r\n restarted = true\r\n } catch (err) {\r\n restartError = err instanceof Error ? err.message : String(err)\r\n }\r\n }\r\n const committed = await deps.git.commit(dir, `sync ${new Date().toISOString()}`)\r\n const pushed = await deps.git.push(dir)\r\n const fresh = await status()\r\n json(res, {\r\n ok: pushed.ok,\r\n action: 'sync',\r\n message: pushed.ok ? '同步完成' : pushed.message,\r\n pull: 'ok',\r\n ...(pulled.changed === true ? { changed: true } : {}),\r\n restarted,\r\n ...(restartError !== undefined ? { restartError } : {}),\r\n commit: committed.message,\r\n push: pushed.message,\r\n status: fresh,\r\n lastSync: new Date().toISOString(),\r\n }, pushed.ok ? 200 : 502)\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n /**\r\n * Save an uploaded file into the wiki's `files/` folder (git-tracked; TW's\r\n * core server serves it at `/files/<name>`, get-file.js — no restart\r\n * needed). Body is the raw file; the name arrives in `X-Filename`. A\r\n * collision appends `-1`, `-2`, … so nothing is ever overwritten.\r\n */\r\n const handleUpload = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n const buf = await readBodyBuffer(req)\r\n // Name comes from ?name= (URL-encoded) or the X-Filename header.\r\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\r\n const nameParam = url.searchParams.get('name')\r\n let name = sanitizeUploadName(nameParam ?? '')\r\n if (name.length === 0 && typeof req.headers['x-filename'] === 'string') {\r\n let decoded = ''\r\n try { decoded = decodeURIComponent(req.headers['x-filename'] as string) } catch { decoded = req.headers['x-filename'] as string }\r\n name = sanitizeUploadName(decoded)\r\n }\r\n if (name.length === 0) {\r\n json(res, { ok: false, error: 'missing or invalid filename' }, 400)\r\n return\r\n }\r\n const filesDir = join(deps.getWikiPath(), 'files')\r\n await mkdir(filesDir, { recursive: true })\r\n const ext = extname(name)\r\n const stem = ext.length > 0 ? name.slice(0, -ext.length) : name\r\n // Collision avoidance: files/some.txt, files/some-1.txt, …\r\n let candidate = name\r\n for (let i = 1; ; i++) {\r\n try { await access(join(filesDir, candidate)) } catch { break }\r\n candidate = `${stem}-${i}${ext}`\r\n }\r\n await writeFile(join(filesDir, candidate), buf)\r\n deps.autoCommit()\r\n json(res, {\r\n ok: true,\r\n name: candidate,\r\n path: `files/${candidate}`,\r\n // Same-origin proxy URL: the embedded TW editor resolves image links\r\n // against the DSH origin, so a root-absolute `/files/...` would miss.\r\n url: `${TW_PROXY_PATH}files/${encodeURIComponent(candidate)}`,\r\n size: buf.length,\r\n type: req.headers['content-type'] ?? 'application/octet-stream',\r\n })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, err instanceof Error && /too large/.test(err.message) ? 413 : 500)\r\n }\r\n }\r\n\r\n /** Passthrough /dsh-tiddlywiki/api/<rest> → TW root /<rest>. */\r\n const handleApiProxy = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n const client = deps.getClient()\r\n if (client === undefined) {\r\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\r\n return\r\n }\r\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\r\n const rest = url.pathname.replace(/^\\/dsh-tiddlywiki\\/api/, '') || '/'\r\n try {\r\n const headers: Record<string, string> = {}\r\n const ct = req.headers['content-type']\r\n if (typeof ct === 'string') headers['content-type'] = ct\r\n const method = (req.method ?? 'GET').toUpperCase()\r\n // TW's CSRF gate requires X-Requested-With on writes; forward it through.\r\n if (method === 'PUT' || method === 'DELETE' || method === 'POST') headers['x-requested-with'] = 'TiddlyWiki'\r\n const init: RequestInit = { method, headers, signal: AbortSignal.timeout(15_000) }\r\n if (method === 'PUT' || method === 'POST') init.body = await readBody(req, MAX_PROXY_BODY_BYTES)\r\n const upstream = await fetch(`${deps.server.url}${rest}${url.search}`, init)\r\n const data = await upstream.text()\r\n res.writeHead(upstream.status, {\r\n 'content-type': upstream.headers.get('content-type') ?? 'application/json; charset=utf-8',\r\n 'cache-control': 'no-store',\r\n })\r\n res.end(data)\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 502)\r\n }\r\n }\r\n\r\n /**\r\n * SAME-ORIGIN proxy /dsh-tiddlywiki/tw/<rest> → TW root /<rest>. Serves the\r\n * ENTIRE TW frontend (index HTML, /files/*, the TiddlyWeb API) to the\r\n * browser through the DSH origin, so the embedded editor works from any\r\n * host/domain the user reaches DSH on (loopback, LAN, Tailscale, domain,\r\n * HTTPS). The browser never talks to the loopback TW child directly; DSH\r\n * does, on the same machine. Binary responses are buffered losslessly\r\n * (arrayBuffer) — unlike the /api JSON proxy, this route must never .text().\r\n */\r\n const handleTwProxy = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n const client = deps.getClient()\r\n if (client === undefined) {\r\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\r\n return\r\n }\r\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\r\n const rest = url.pathname.replace(new RegExp(`^${TW_PROXY_PREFIX}(?=/|$)`), '') || '/'\r\n try {\r\n const method = (req.method ?? 'GET').toUpperCase()\r\n const headers = forwardHeaders(req.headers)\r\n // TW's CSRF gate requires X-Requested-With on writes; forward it through.\r\n if (method === 'PUT' || method === 'DELETE' || method === 'POST') headers['x-requested-with'] = 'TiddlyWiki'\r\n const init: RequestInit = { method, headers, signal: AbortSignal.timeout(30_000) }\r\n if (method === 'PUT' || method === 'POST') init.body = await readBodyBuffer(req, MAX_UPLOAD_BYTES)\r\n const upstream = await fetch(`${deps.server.url}${rest}${url.search}`, init)\r\n const data = Buffer.from(await upstream.arrayBuffer())\r\n const responseHeaders: Record<string, string> = {\r\n 'content-type': upstream.headers.get('content-type') ?? 'application/octet-stream',\r\n 'cache-control': upstream.headers.get('cache-control') ?? 'no-store',\r\n }\r\n for (const name of ['etag', 'last-modified', 'content-disposition']) {\r\n const value = upstream.headers.get(name)\r\n if (value !== null) responseHeaders[name] = value\r\n }\r\n res.writeHead(upstream.status, responseHeaders)\r\n res.end(data)\r\n } catch (err) {\r\n if (res.headersSent) {\r\n res.end()\r\n return\r\n }\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 502)\r\n }\r\n }\r\n\r\n const disposers = [\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/status`, handler: (req, res) => { void handleStatus(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/note`, handler: (req, res) => { void handleNote(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/edit`, handler: (req, res) => { void handleEdit(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/tags`, handler: (req, res) => { void handleTags(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/recent`, handler: (req, res) => { void handleRecent(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/get`, handler: (req, res) => { void handleGet(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/search`, handler: (req, res) => { void handleSearch(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/sync`, handler: (req, res) => { void handleSync(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/upload`, handler: (req, res) => { void handleUpload(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/restart`, handler: (req, res) => { void handleRestart(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/session/summary`, handler: (req, res) => { void handleSessionSummary(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/agent/sessions`, handler: (req, res) => { void handleAgentSessions(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/agent/modes`, handler: (req, res) => { void handleAgentModes(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/agent/send`, handler: (req, res) => { void handleAgentSend(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/agent/create`, handler: (req, res) => { void handleAgentCreate(req, res) } }),\r\n ctx.webServer.register({ kind: 'prefix', path: `${ROUTE_PREFIX}/api`, handler: (req, res) => { void handleApiProxy(req, res) } }),\r\n ctx.webServer.register({ kind: 'prefix', path: `${TW_PROXY_PREFIX}`, handler: (req, res) => { void handleTwProxy(req, res) } }),\r\n ]\r\n return () => {\r\n for (const dispose of disposers) dispose()\r\n }\r\n}\r\n\r\n/** Public shape of the git status summary sent to the panel. */\r\nexport interface GitStatusViewPublic {\r\n exists: boolean\r\n branch: string\r\n dirty: boolean\r\n dirtyFiles: string[]\r\n remote: string\r\n lastCommit?: string\r\n ahead?: number\r\n behind?: number\r\n}\r\n","/**\r\n * Extensible plugin config (design doc §13, config panel).\r\n *\r\n * Two layers:\r\n * base — the cordis `config:` block (profile composition, defaults);\r\n * overrides — a user-editable config tiddler ($:/plugins/dsh-tiddlywiki/config,\r\n * a JSON string) written by the settings page.\r\n * The tiddler overlays the base (tiddler wins), so future config fields just\r\n * extend the shape — no schema, no @deepseek-ai dependency, and the config\r\n * travels with the wiki's git history.\r\n *\r\n * @module dsh-tiddlywiki/host/config\r\n */\r\nimport type { TiddlyWebClient } from './tw-api.ts'\r\n\r\n/** Config tiddler (JSON string) where the settings page stores overrides. */\r\nexport const CONFIG_TIDDLER = '$:/plugins/dsh-tiddlywiki/config'\r\n\r\n/** TW frontend API base tiddler, pointed at the same-origin DSH proxy. */\r\nexport const TW_WEB_HOST_TIDDLER = '$:/config/tiddlyweb/host'\r\n\r\n/** The legacy default TW host value this plugin replaces with the proxy. */\r\nexport const TW_WEB_HOST_DEFAULT = '$protocol$//$host$/'\r\n\r\n/**\r\n * Default dark palette the embedded TW switches to when DSH is dark\r\n * (mirrored in src/client/theme-sync.ts — the two bundles cannot share code).\r\n */\r\nexport const DARK_PALETTE_DEFAULT = '$:/palettes/CupertinoDark'\r\n\r\n/** Extensible, loose plugin config shape (future fields just appear here). */\r\nexport interface PluginConfigShape {\r\n note?: { tag?: string }\r\n git?: { autoCommit?: boolean; debounceMs?: number; remote?: string; branch?: string }\r\n ui?: {\r\n /** 是否在界面右下角显示「快速笔记」悬浮按钮(默认 true)。 */\r\n showQuickNote?: boolean\r\n /**\r\n * 是否在聊天输入框上方(conversation.input.dock 槽位)显示「快速笔记」\r\n * 快捷按钮(默认 true)。与其它插件(todo/cost-meter/goal/queue/git-graph\r\n * 等)注入的内容同处一个纵向排列的 dock 区,天然不重叠。\r\n */\r\n showQuickNoteDock?: boolean\r\n /**\r\n * 点击「快速笔记」后的打开方式(默认 `native`):\r\n * `native` = 直接弹出 TW 原生编辑器(新建/恢复草稿);`card` = 弹出现有\r\n * 的 Markdown 快速笔记卡片。设置页可切换。\r\n */\r\n quickNoteMode?: 'native' | 'card'\r\n /** 左侧侧边栏 TW 入口的显示名称(默认「TiddlyWiki」,可自定义)。 */\r\n sidebarLabel?: string\r\n /** 是否显示 TW 面板右下角的「状态/重载」悬浮按钮(默认 true)。 */\r\n showPanelStatus?: boolean\r\n /** 是否在界面右下角显示「同步」悬浮按钮(默认 true)。 */\r\n showSyncButton?: boolean\r\n /**\r\n * 嵌入式 TW 是否跟随 DSH 深浅主题(默认 true):DSH 暗色时把 TW 活动\r\n * palette 临时切到 `darkPalette`,浅色时恢复用户原 palette;仅内存生效,\r\n * 不会写回 wiki。\r\n */\r\n followDshTheme?: boolean\r\n /** DSH 暗色时 TW 使用的 palette tiddler 标题(默认 CupertinoDark)。 */\r\n darkPalette?: string\r\n /**\r\n * 一键发送给 Agent(TW 笔记 → DSH 会话注入)。\r\n * `enabled: false` 时路由 403、TW 端按钮点击提示未启用;`token` 非空时\r\n * 路由要求请求头 `x-send-to-agent-token` 与之匹配;`endpoint` 可覆盖\r\n * TW 端默认的 `location.origin + /dsh-tiddlywiki`。\r\n */\r\n sendToAgent?: { enabled?: boolean; endpoint?: string; token?: string }\r\n /**\r\n * 「所有文章」两列分页页面的每页条数(默认 10)。页面在渲染时实时读取\r\n * 该值(见 seed-all-articles),改后无需重新初始化。\r\n */\r\n allArticles?: { pageSize?: number }\r\n /**\r\n * 会话顶部「知识库」Tab 的显示名称(默认「知识库」)。该 Tab 显示本会话\r\n * 产生/读取/检索过的 wiki 笔记汇总(conversation.view 槽位,见\r\n * src/client/session-summary.ts)。\r\n */\r\n tabLabel?: string\r\n /** 是否在会话顶部显示「知识库」Tab(会话相关 wiki 汇总,默认 true)。 */\r\n showSessionTab?: boolean\r\n /**\r\n * 是否在 DSH 右侧边栏(new rightbar)提供 TiddlyWiki 入口/Tab(默认\r\n * true):在 rightbar 首页(guide 页)注册「TiddlyWiki 知识库」入口盒,\r\n * 点击即在右侧栏以 tab 形式打开完整 TW 编辑器(与聊天并排)。老版本\r\n * DSH 无 rightbar 时该配置不生效(客户端自动跳过)。\r\n */\r\n showRightbarTab?: boolean\r\n }\r\n uiLanguage?: string\r\n [key: string]: unknown\r\n}\r\n\r\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\r\n return typeof value === 'object' && value !== null && !Array.isArray(value)\r\n}\r\n\r\n/** Deep-merge: `over` wins; nested plain objects merge recursively. */\r\nexport function deepMerge(base: Record<string, unknown>, over: Record<string, unknown>): Record<string, unknown> {\r\n const out: Record<string, unknown> = { ...base }\r\n for (const [key, value] of Object.entries(over)) {\r\n if (value === undefined) continue\r\n if (isPlainObject(value) && isPlainObject(out[key])) {\r\n out[key] = deepMerge(out[key] as Record<string, unknown>, value)\r\n } else {\r\n out[key] = value\r\n }\r\n }\r\n return out\r\n}\r\n\r\n/**\r\n * Runtime config store: caches the override tiddler and exposes the effective\r\n * (merged) config. `load` runs at startup and after every write/restart.\r\n */\r\nexport class ConfigStore {\r\n private overrides: PluginConfigShape = {}\r\n\r\n constructor(private readonly base: PluginConfigShape) {}\r\n\r\n /** Effective config = cordis base overlaid with the user override tiddler. */\r\n get(): PluginConfigShape {\r\n return deepMerge(this.base, this.overrides) as PluginConfigShape\r\n }\r\n\r\n /** Reload the override tiddler (no-op when the wiki is unavailable). */\r\n async load(client: TiddlyWebClient | undefined): Promise<void> {\r\n this.overrides = {}\r\n if (client === undefined) return\r\n try {\r\n const tiddler = await client.get(CONFIG_TIDDLER)\r\n if (tiddler !== undefined && typeof tiddler.text === 'string') {\r\n const parsed = JSON.parse(tiddler.text) as unknown\r\n if (isPlainObject(parsed)) this.overrides = parsed as PluginConfigShape\r\n }\r\n } catch {\r\n // Wiki not ready or config tiddler unreadable → keep empty overrides.\r\n this.overrides = {}\r\n }\r\n }\r\n\r\n /** Merge a patch into the overrides and persist the tiddler. */\r\n async set(client: TiddlyWebClient, patch: PluginConfigShape): Promise<PluginConfigShape> {\r\n this.overrides = deepMerge(this.overrides, patch) as PluginConfigShape\r\n await client.put({\r\n title: CONFIG_TIDDLER,\r\n text: JSON.stringify(this.overrides, null, 2),\r\n type: 'application/json',\r\n tags: [],\r\n })\r\n return this.get()\r\n }\r\n}\r\n","/**\r\n * Admin surface for the plugin settings page (design doc §13, config panel).\r\n *\r\n * - dynamic plugin/theme management: enumerate the bundled catalog from the\r\n * installed tiddlywiki package, read/write the wiki's `tiddlywiki.info`\r\n * plugins/themes arrays, then restart the TW child so the change applies;\r\n * - extensible config: the settings page reads/writes a config tiddler\r\n * ($:/plugins/dsh-tiddlywiki/config, a JSON string) that overlays the\r\n * cordis `config:` block — future config fields just extend the shape.\r\n *\r\n * Routes (all under ROUTE_PREFIX/admin, JSON):\r\n * GET /admin/state current info + catalog + effective config + status\r\n * POST /admin/info { plugins?, themes? } → write info → restart TW\r\n * POST /admin/config { ...patch } → write config tiddler\r\n * POST /admin/restart restart the TW child\r\n *\r\n * @module dsh-tiddlywiki/host/admin\r\n */\r\nimport { createRequire } from 'node:module'\r\nimport { readFile, writeFile, readdir } from 'node:fs/promises'\r\nimport { dirname, join } from 'node:path'\r\nimport type { IncomingMessage, ServerResponse } from 'node:http'\r\nimport type { TiddlyWebClient } from './tw-api.ts'\r\nimport type { WikiServer } from './wiki.ts'\r\nimport { ROUTE_PREFIX, type WebServerFace } from './routes.ts'\r\nimport { CONFIG_TIDDLER, type ConfigStore, type PluginConfigShape } from './config.ts'\r\nimport { readBody, json } from './http.ts'\r\nimport { GitFace } from './git.ts'\r\n\r\n/** One bundled plugin/theme from the catalog. */\r\nexport interface CatalogEntry {\r\n /** Short name as used in tiddlywiki.info, e.g. \"tiddlywiki/katex\". */\r\n name: string\r\n /** Full tiddler title, e.g. \"$:/plugins/tiddlywiki/katex\". */\r\n title: string\r\n label: string\r\n description: string\r\n /** Dependent theme NAMES (converted from plugin.info `dependents`), e.g. heavier → [\"tiddlywiki/snowwhite\"]. */\r\n dependents?: string[]\r\n}\r\n\r\nexport interface Catalog {\r\n plugins: CatalogEntry[]\r\n themes: CatalogEntry[]\r\n /** Bundled language plugins (tiddlywiki package `languages/` dir). */\r\n languages: CatalogEntry[]\r\n}\r\n\r\n/** Shape of tiddlywiki.info (plugins/themes/languages + build/description). */\r\nexport interface WikiInfo {\r\n description?: string\r\n plugins: string[]\r\n themes: string[]\r\n languages?: string[]\r\n build?: Record<string, unknown>\r\n [key: string]: unknown\r\n}\r\n\r\n/** Resolve the installed tiddlywiki package root (for the catalog). */\r\nexport function resolveTwRoot(): string {\r\n const require = createRequire(import.meta.url)\r\n return dirname(require.resolve('tiddlywiki/package.json'))\r\n}\r\n\r\n/** Read the wiki's tiddlywiki.info. */\r\nexport async function readWikiInfo(wikiPath: string): Promise<WikiInfo> {\r\n let raw: string\r\n try {\r\n raw = await readFile(join(wikiPath, 'tiddlywiki.info'), 'utf8')\r\n } catch {\r\n return { plugins: [], themes: [], languages: [] }\r\n }\r\n const parsed = JSON.parse(raw) as Partial<WikiInfo>\r\n return {\r\n description: parsed.description,\r\n plugins: parsed.plugins ?? [],\r\n themes: parsed.themes ?? [],\r\n languages: parsed.languages ?? [],\r\n ...parsed,\r\n }\r\n}\r\n\r\n/** Write the wiki's tiddlywiki.info (pretty-printed, ordering preserved). */\r\nexport async function writeWikiInfo(wikiPath: string, info: WikiInfo): Promise<void> {\r\n await writeFile(join(wikiPath, 'tiddlywiki.info'), `${JSON.stringify(info, null, 4)}\\n`, 'utf8')\r\n}\r\n\r\n/** Enumerate bundled official plugins + themes + languages of tiddlywiki. */\r\nexport async function bundledCatalog(twRoot: string): Promise<Catalog> {\r\n // TW themes are SKINS layered on the vanilla base (which carries the full\r\n // 70KB base stylesheet). A theme whose stylesheet body is empty is a broken\r\n // stub (e.g. tight-heavier in some releases) — skip it so the settings list\r\n // never offers a no-op theme. vanilla itself is always kept.\r\n const themeHasCss = async (dir: string): Promise<boolean> => {\r\n for (const name of ['base.tid', 'styles.tid']) {\r\n try {\r\n const raw = await readFile(join(twRoot, 'themes', 'tiddlywiki', dir, name), 'utf8')\r\n const body = raw\r\n .replace(/^[\\s\\S]*?\\r?\\n\\r?\\n/, '')\r\n .split('\\n')\r\n .filter((line) => !/^\\\\rules\\b/.test(line.trim()))\r\n .join('\\n')\r\n .trim()\r\n if (body.length > 0) return true\r\n } catch {\r\n /* file absent */\r\n }\r\n }\r\n return false\r\n }\r\n const scan = async (sub: 'plugins' | 'themes'): Promise<CatalogEntry[]> => {\r\n const root = join(twRoot, sub, 'tiddlywiki')\r\n let dirs: string[]\r\n try {\r\n dirs = await readdir(root)\r\n } catch {\r\n return []\r\n }\r\n const out: CatalogEntry[] = []\r\n for (const dir of dirs) {\r\n let info: { name?: string; description?: string; dependents?: string[] } = {}\r\n try {\r\n info = JSON.parse(await readFile(join(root, dir, 'plugin.info'), 'utf8')) as typeof info\r\n } catch {\r\n info = {}\r\n }\r\n if (sub === 'themes' && dir !== 'vanilla' && !(await themeHasCss(dir))) continue\r\n out.push({\r\n name: `tiddlywiki/${dir}`,\r\n title: sub === 'plugins' ? `$:/plugins/tiddlywiki/${dir}` : `$:/themes/tiddlywiki/${dir}`,\r\n label: info.name ?? dir,\r\n description: info.description ?? '',\r\n // plugin.info `dependents` are full plugin titles → convert to names.\r\n dependents: Array.isArray(info.dependents)\r\n ? info.dependents.map((dep) => dep.replace(/^\\$:\\/themes\\/tiddlywiki\\//, 'tiddlywiki/'))\r\n : undefined,\r\n })\r\n }\r\n out.sort((a, b) => a.name.localeCompare(b.name))\r\n return out\r\n }\r\n // Language plugins live in the package ROOT `languages/` dir (not plugins/),\r\n // and are enabled via the tiddlywiki.info `languages` array (boot resolves\r\n // them through $tw.config.languagesPath). Fully offline — official builds.\r\n const scanLanguages = async (): Promise<CatalogEntry[]> => {\r\n const root = join(twRoot, 'languages')\r\n let dirs: string[]\r\n try {\r\n dirs = await readdir(root)\r\n } catch {\r\n return []\r\n }\r\n const out: CatalogEntry[] = []\r\n for (const dir of dirs) {\r\n let info: { name?: string; description?: string } = {}\r\n try {\r\n info = JSON.parse(await readFile(join(root, dir, 'plugin.info'), 'utf8')) as typeof info\r\n } catch {\r\n info = {}\r\n }\r\n out.push({\r\n name: dir,\r\n title: `$:/languages/${dir}`,\r\n label: info.name ?? dir,\r\n description: info.description ?? '',\r\n })\r\n }\r\n out.sort((a, b) => a.name.localeCompare(b.name))\r\n return out\r\n }\r\n const [plugins, themes, languages] = await Promise.all([scan('plugins'), scan('themes'), scanLanguages()])\r\n return { plugins, themes, languages }\r\n}\r\n\r\n/**\r\n * Normalize a theme selection into the tiddlywiki.info `themes` array.\r\n *\r\n * TW themes are SKINS with a dependency chain (plugin.info `dependents`):\r\n * vanilla ← snowwhite ← heavier / centralised / readonly / starlight\r\n * vanilla ← tight / seamless\r\n * The ACTIVE theme is `$:/theme`, and switching to it registers the theme PLUS\r\n * its transitive dependents (boot.js accumulatePlugin) — if a dependent isn't\r\n * loaded, the vanilla base stylesheet is lost and the UI breaks. So we always\r\n * emit the transitive closure, dependency-first (base first, active overlay\r\n * last), and force vanilla in as the base. Empty selection → vanilla.\r\n */\r\nexport function normalizeThemes(selected: string[], deps: Record<string, string[]> = {}): string[] {\r\n const sel = selected.filter((name) => typeof name === 'string' && name.length > 0)\r\n if (sel.length === 0) sel.push('tiddlywiki/vanilla')\r\n const out: string[] = []\r\n const seen = new Set<string>()\r\n const visit = (name: string): void => {\r\n if (seen.has(name)) return\r\n seen.add(name)\r\n for (const dep of deps[name] ?? []) {\r\n if (dep !== name) visit(dep)\r\n }\r\n out.push(name)\r\n }\r\n for (const name of sel) visit(name)\r\n if (!out.includes('tiddlywiki/vanilla')) out.unshift('tiddlywiki/vanilla')\r\n return out\r\n}\r\n\r\n/**\r\n * Ensure a language code (e.g. \"zh-Hans\") is in tiddlywiki.info `languages`.\r\n * Returns whether tiddlywiki.info changed (caller decides whether to restart).\r\n */\r\nexport async function ensureLanguage(wikiPath: string, twRoot: string, lang: string): Promise<boolean> {\r\n if (typeof lang !== 'string' || lang.trim().length === 0) return false\r\n const code = lang.trim()\r\n const catalog = await bundledCatalog(twRoot)\r\n if (!catalog.languages.some((l) => l.name === code)) {\r\n throw new Error(`unknown language plugin: ${code}`)\r\n }\r\n const info = await readWikiInfo(wikiPath)\r\n const current = info.languages ?? []\r\n if (current.includes(code)) return false\r\n info.languages = [...current, code]\r\n await writeWikiInfo(wikiPath, info)\r\n return true\r\n}\r\n\r\nexport interface AdminDeps {\r\n server: WikiServer\r\n getClient: () => TiddlyWebClient | undefined\r\n getWikiPath: () => string\r\n twRoot: () => string\r\n config: ConfigStore\r\n /** Seed registry for the settings-page \"初始化\" section. */\r\n seeds: {\r\n checkAll: (client: TiddlyWebClient) => Promise<Array<{ id: string; title: string; description: string; present: boolean; removable: boolean; detail?: string }>>\r\n run: (client: TiddlyWebClient, id: string | undefined, force: boolean) => Promise<Array<{ id: string; ok: boolean; wrote: boolean; detail?: string; error?: string }>>\r\n /** 反初始化: remove one (or all) optional seed's seeded tiddlers + markers. */\r\n remove: (client: TiddlyWebClient, id: string | undefined) => Promise<Array<{ id: string; ok: boolean; wrote: boolean; detail?: string; error?: string }>>\r\n }\r\n}\r\n\r\nexport function registerAdminRoutes(ctx: { webServer: WebServerFace }, deps: AdminDeps): () => void {\r\n const handleState = async (_req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n const wikiPath = deps.getWikiPath()\r\n const [info, catalog] = await Promise.all([readWikiInfo(wikiPath), bundledCatalog(deps.twRoot())])\r\n let git: unknown = null\r\n try {\r\n git = await new GitFace().status(wikiPath)\r\n } catch {\r\n git = null\r\n }\r\n json(res, {\r\n ok: true,\r\n server: deps.server.status(),\r\n info: { plugins: info.plugins, themes: info.themes, languages: info.languages ?? [] },\r\n catalog,\r\n config: deps.config.get(),\r\n git,\r\n })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n const handleInfo = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n const body = JSON.parse(await readBody(req)) as { plugins?: unknown; themes?: unknown; themeActive?: unknown; languages?: unknown }\r\n const wikiPath = deps.getWikiPath()\r\n const info = await readWikiInfo(wikiPath)\r\n const catalog = await bundledCatalog(deps.twRoot())\r\n const known = new Set([...catalog.plugins, ...catalog.themes].map((c) => c.name))\r\n const knownLangs = new Set(catalog.languages.map((c) => c.name))\r\n const applyList = (field: 'plugins' | 'themes', raw: unknown): string[] => {\r\n if (!Array.isArray(raw)) return info[field]\r\n const next: string[] = []\r\n for (const name of raw) {\r\n if (typeof name !== 'string') continue\r\n if (!known.has(name) && !info[field].includes(name)) {\r\n throw new Error(`unknown plugin/theme: ${name}`)\r\n }\r\n if (!next.includes(name)) next.push(name)\r\n }\r\n return next\r\n }\r\n const applyLanguages = (raw: unknown): string[] => {\r\n if (!Array.isArray(raw)) return info.languages ?? []\r\n const next: string[] = []\r\n for (const code of raw) {\r\n if (typeof code !== 'string') continue\r\n if (!knownLangs.has(code) && !(info.languages ?? []).includes(code)) {\r\n throw new Error(`unknown language plugin: ${code}`)\r\n }\r\n if (!next.includes(code)) next.push(code)\r\n }\r\n return next\r\n }\r\n info.plugins = applyList('plugins', body.plugins)\r\n // Activate the chosen theme: load its full dependency chain AND set\r\n // `$:/theme` so the browser's themeManager actually applies it (the\r\n // `themes` array alone only makes the plugin available).\r\n let activatedTheme: string | undefined\r\n if (Array.isArray(body.themes)) {\r\n const themeDeps: Record<string, string[]> = {}\r\n for (const theme of catalog.themes) {\r\n if (theme.dependents && theme.dependents.length > 0) themeDeps[theme.name] = theme.dependents\r\n }\r\n let selected = applyList('themes', body.themes)\r\n // Explicit active-theme pick (new two-layer UI): validate and auto-add\r\n // it to the loaded set so its dependency closure is loaded. Without an\r\n // explicit pick, activate the deepest loaded overlay (old single-radio).\r\n const explicitActive = typeof body.themeActive === 'string' && body.themeActive.length > 0\r\n if (explicitActive) {\r\n const activeName = body.themeActive as string\r\n if (known.has(activeName) || info.themes.includes(activeName)) {\r\n if (!selected.includes(activeName)) selected.push(activeName)\r\n activatedTheme = activeName\r\n }\r\n }\r\n info.themes = normalizeThemes(selected, themeDeps)\r\n if (activatedTheme === undefined && info.themes.length > 0) {\r\n activatedTheme = info.themes[info.themes.length - 1]\r\n }\r\n } else {\r\n info.themes = applyList('themes', body.themes)\r\n }\r\n if (Array.isArray(body.languages)) info.languages = applyLanguages(body.languages)\r\n await writeWikiInfo(wikiPath, info)\r\n await deps.server.restart()\r\n // Activate the chosen theme tiddler (mirrors TW's own Control Panel).\r\n if (activatedTheme !== undefined) {\r\n const client = deps.getClient()\r\n if (client !== undefined) {\r\n await client\r\n .put({ title: '$:/theme', text: `$:/themes/${activatedTheme}`, type: 'text/vnd.tiddlywiki', tags: [] })\r\n .catch(() => undefined)\r\n }\r\n }\r\n // After a languages change, pin the active language tiddler: first\r\n // enabled language, or en-GB when none is enabled. Only when the request\r\n // actually carried a languages array (plugins/themes restarts skip this).\r\n if (Array.isArray(body.languages)) {\r\n const client = deps.getClient()\r\n if (client !== undefined) {\r\n const langs = info.languages ?? []\r\n const active = langs.length > 0 ? `$:/languages/${langs[0]}` : '$:/languages/en-GB'\r\n await client.put({ title: '$:/language', text: active, type: 'text/plain', tags: [] }).catch(() => undefined)\r\n // Keep the startup auto-apply hint (config uiLanguage) consistent with\r\n // the active language, so a later dsh-web restart doesn't re-enable\r\n // a language the user just disabled here.\r\n const hint = langs.length > 0 ? langs[0] : ''\r\n if ((deps.config.get().uiLanguage ?? '') !== hint) {\r\n await deps.config.set(client, { uiLanguage: hint }).catch(() => undefined)\r\n }\r\n }\r\n }\r\n json(res, { ok: true, info: { plugins: info.plugins, themes: info.themes, languages: info.languages ?? [] } })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 400)\r\n }\r\n }\r\n\r\n const handleConfig = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n const body = JSON.parse(await readBody(req)) as PluginConfigShape\r\n const client = deps.getClient()\r\n if (client === undefined) {\r\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\r\n return\r\n }\r\n await deps.config.set(client, body)\r\n json(res, { ok: true, config: deps.config.get() })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 400)\r\n }\r\n }\r\n\r\n const handleRestart = async (_req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n await deps.server.restart()\r\n json(res, { ok: true, status: deps.server.status().status })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n /**\r\n * GET /dsh-tiddlywiki/admin/seeds — status of every one-time seed\r\n * (doc-note / send-to-agent / home-index / tw-web-host) for the settings\r\n * page's 初始化 section.\r\n */\r\n const handleSeeds = async (_req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n const client = deps.getClient()\r\n if (client === undefined) {\r\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\r\n return\r\n }\r\n const items = await deps.seeds.checkAll(client)\r\n json(res, { ok: true, items })\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n /**\r\n * POST /dsh-tiddlywiki/admin/seeds/run — run one seed (or all when `id` is\r\n * absent); `force: true` is the manual \"重新初始化\" (overwrite + re-marker),\r\n * `force: false` keeps the one-shot write-if-missing semantics.\r\n */\r\n const handleSeedsRun = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n const body = JSON.parse(await readBody(req)) as { id?: unknown; force?: unknown }\r\n const id = typeof body.id === 'string' && body.id.trim().length > 0 ? body.id.trim() : undefined\r\n const force = body.force === true\r\n const client = deps.getClient()\r\n if (client === undefined) {\r\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\r\n return\r\n }\r\n const results = await deps.seeds.run(client, id, force)\r\n const ok = results.every((r) => r.ok)\r\n json(res, { ok, results }, ok ? 200 : 400)\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n /**\r\n * POST /dsh-tiddlywiki/admin/seeds/remove — 反初始化: remove one optional\r\n * seed (or all optional seeds when `id` is absent) — delete the seeded\r\n * tiddlers + markers so the wiki returns to the \"never seeded\" state.\r\n * Core seeds (功能必需) cannot be removed.\r\n */\r\n const handleSeedsRemove = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\r\n try {\r\n const body = JSON.parse(await readBody(req)) as { id?: unknown }\r\n const id = typeof body.id === 'string' && body.id.trim().length > 0 ? body.id.trim() : undefined\r\n const client = deps.getClient()\r\n if (client === undefined) {\r\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\r\n return\r\n }\r\n const results = await deps.seeds.remove(client, id)\r\n const ok = results.every((r) => r.ok)\r\n json(res, { ok, results }, ok ? 200 : 400)\r\n } catch (err) {\r\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\r\n }\r\n }\r\n\r\n const disposers = [\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/state`, handler: (req, res) => { void handleState(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/info`, handler: (req, res) => { void handleInfo(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/config`, handler: (req, res) => { void handleConfig(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/restart`, handler: (req, res) => { void handleRestart(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/seeds`, handler: (req, res) => { void handleSeeds(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/seeds/run`, handler: (req, res) => { void handleSeedsRun(req, res) } }),\r\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/seeds/remove`, handler: (req, res) => { void handleSeedsRemove(req, res) } }),\r\n ]\r\n return () => {\r\n for (const dispose of disposers) dispose()\r\n }\r\n}\r\n","/**\r\n * Built-in doc note for the plugin (design doc §14): a short user-facing\r\n * \"how to use dsh-tiddlywiki\" note seeded into the wiki the first time the\r\n * plugin runs on a wiki.\r\n *\r\n * ONE-SHOT seed: a marker tiddler (`seed-doc-note`) records that the note has\r\n * been offered once; after that the note is the user's own content — deleting\r\n * it and restarting dsh web does NOT recreate it, and edits are never\r\n * overwritten.\r\n *\r\n * @module dsh-tiddlywiki/host/seed-notes\r\n */\r\nimport type { TiddlyWebClient } from './tw-api.ts'\r\n\r\n/** Note tiddler title (a normal, searchable note — not a system tiddler). */\r\nexport const DOC_NOTE_TITLE = 'dsh-tiddlywiki 插件说明'\r\n\r\n/** Tag that makes the note easy to find via `tiddlywiki_search tag=docs`. */\r\nexport const DOC_NOTE_TAG = 'docs'\r\n\r\n/**\r\n * Shared tag that collects every plugin-seeded doc into the seeded home's\r\n *「📚 插件文档」tabs strip (see seed-starter-docs.ts DSH_DOCS_TAG).\r\n */\r\nexport const DOC_NOTE_DSH_DOCS_TAG = 'dsh-docs'\r\n\r\n/** One-time marker: its presence means \"the note was offered once — hands off\". */\r\nexport const SEED_MARKER_TITLE = '$:/plugins/dsh-tiddlywiki/seed-doc-note'\r\n\r\n/** The note body, TiddlyWiki wiki-text. */\r\nexport const DOC_NOTE_TEXT = `! dsh-tiddlywiki 插件说明\r\n\r\n本插件把 **TiddlyWiki 5** 作为 DSH 的持久知识库(wiki 文件夹本身就是一个 git 仓库,随内容自动提交/同步)。\r\n\r\n!! 它能做什么\r\n\r\n* **10 个 agent 工具**:\\`tiddlywiki_search\\`(检索,支持 tags/since/type/limit 过滤)/ \\`tiddlywiki_get\\`(读)/ \\`tiddlywiki_put\\`(写)/ \\`tiddlywiki_batch_put\\`(批量写)/ \\`tiddlywiki_rename\\`(重命名+同步引用)/ \\`tiddlywiki_delete\\`(删)/ \\`tiddlywiki_recent\\`(最近修改)/ \\`tiddlywiki_list_tags\\`(标签清单)/ \\`tiddlywiki_git_sync\\`(git 同步)/ \\`tiddlywiki_git_resolve\\`(冲突按 tiddler 二选一)。\r\n* **TW 编辑器面板**:侧边栏「TiddlyWiki」按钮 → 在界面中央打开完整版 TW 编辑器。\r\n* **快速笔记**:点击聊天输入框上方或右下角「知识库」菜单里的「📝 快速笔记」——默认**直达 TW 原生编辑页**(独立小窗,草稿自动续写);也可在设置页切回 Markdown 卡片(语法高亮、文件上传、多选 tag、草稿自动保存、Ctrl+Enter 保存)。「✏️ 在 TW 中编辑」会弹出独立小窗用 TW 原生编辑器编辑。\r\n* **一键同步**:「知识库」按钮 → 「🔁 同步」一键 pull → commit → push,按钮上的状态点实时反映 git 状态。\r\n* **git 同步**:写入自动防抖 commit(默认 60 秒);手动 \\`tiddlywiki_git_sync action=sync\\` 做 pull → commit → push。\r\n* **设置页**:DSH 设置 → 「TiddlyWiki 知识库」管理插件/主题/语言与运行配置(含「知识库」按钮相关显示开关)。\r\n\r\n!! 知识库纪律(四条)\r\n\r\n1. 开工先 \\`tiddlywiki_git_sync action=pull\\`(rebase + autostash,真冲突会自动 abort 并报文件)。\r\n2. 冲突后:\\`tiddlywiki_git_resolve files=[冲突文件] strategy=keep-local|keep-remote\\` 按 tiddler 二选一解决,再重新 sync。\r\n3. 收工 \\`tiddlywiki_git_sync action=sync\\`。\r\n4. 插件自动 commit 兜底,手动 sync 用于需要主动推送的场合。\r\n\r\n!! 主题与语言\r\n\r\n* **主题**分两层:每行一个「☑ 加载」(多选 = TW 里可用的主题,依赖链自动带上)和「◉ 活动」(单选 = 当前视觉主题)。应用后自动重启 TW。\r\n* **语言**:设置页勾选 \\`zh-Hans\\`(简体)并应用,TW 界面即切换为中文。\r\n\r\n!! 说明\r\n\r\n* 本笔记由插件在**首次启动**时自动写入(一次性:只写一次)。删除后重启 dsh web **不会自动恢复**——它从此归你所有。\r\n* 本笔记带 \\`dsh-docs\\` 标签,会出现在首页「📚 插件文档」栏(与「示例与文档」seed 的教程/模板一起),不需要可自由删除。\r\n* 更多细节见插件仓库 README。`\r\n\r\n/**\r\n * Seed the doc note once per wiki (mirrors the one-shot policy). A marker\r\n * tiddler records that the note has been offered; from then on the note is\r\n * user-owned and is never re-created (deleting it survives restarts).\r\n *\r\n * With `opts.force` the note is (re)written even when it already exists and\r\n * the marker is (re)written — the settings page uses this for\r\n * \"重新初始化\". Returns whether a note was written this call. Never throws.\r\n */\r\nexport async function seedDocNote(client: TiddlyWebClient, opts?: { force?: boolean }): Promise<boolean> {\r\n const force = opts?.force === true\r\n if (!force) {\r\n const marker = await client.get(SEED_MARKER_TITLE).catch(() => undefined)\r\n if (marker !== undefined) return false\r\n }\r\n const existing = await client.get(DOC_NOTE_TITLE).catch(() => undefined)\r\n let wrote = false\r\n if (force || existing === undefined) {\r\n await client.put({\r\n title: DOC_NOTE_TITLE,\r\n text: DOC_NOTE_TEXT,\r\n type: 'text/vnd.tiddlywiki',\r\n tags: [DOC_NOTE_TAG, DOC_NOTE_DSH_DOCS_TAG],\r\n })\r\n wrote = true\r\n }\r\n // Record the offer regardless, so an existing note (upgrade from an older\r\n // create-if-missing version) also becomes user-owned from here on.\r\n await client\r\n .put({ title: SEED_MARKER_TITLE, text: 'seeded-once', type: 'text/plain', tags: [] })\r\n .catch(() => undefined)\r\n return wrote\r\n}\r\n\r\n/**\r\n * Un-seed (反初始化): remove the doc note and its one-shot marker, returning\r\n * the wiki to the \"never offered\" state. Deletion is idempotent — a tiddler\r\n * that was already gone is simply not listed. Never throws.\r\n */\r\nexport async function unseedDocNote(client: TiddlyWebClient): Promise<{ removed: string[] }> {\r\n const removed: string[] = []\r\n for (const title of [DOC_NOTE_TITLE, SEED_MARKER_TITLE]) {\r\n const t = await client.get(title).catch(() => undefined)\r\n if (t !== undefined) {\r\n await client.delete(title)\r\n removed.push(title)\r\n }\r\n }\r\n return { removed }\r\n}\r\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'\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><<tabs \"[tag[主题页]!is[system]]\">></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. Never throws.\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 client.get(STARTER_DOCS_MARKER_TITLE).catch(() => undefined)\n if (marker !== undefined) return false\n }\n let wrote = false\n for (const item of STARTER_DOCS_ITEMS) {\n const existing = await client.get(item.title).catch(() => undefined)\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 client\n .put({ title: STARTER_DOCS_MARKER_TITLE, text: 'seeded-once', type: 'text/plain', tags: [] })\n .catch(() => undefined)\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. Never throws.\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 client.get(item.title).catch(() => undefined)\n if (t !== undefined) {\n await client.delete(item.title)\n removed.push(item.title)\n }\n }\n const marker = await client.get(STARTER_DOCS_MARKER_TITLE).catch(() => undefined)\n if (marker !== undefined) {\n await client.delete(STARTER_DOCS_MARKER_TITLE)\n removed.push(STARTER_DOCS_MARKER_TITLE)\n }\n return { removed }\n}","/**\r\n * Generated from the wiki bundle (do not hand-edit the constant).\r\n * Source: send-to-agent.bundle.json\r\n *\r\n * The \"发送给 Agent\" TW button, packaged as a TiddlyWiki plugin bundle\r\n * (`{\"tiddlers\": {...}}`), seeded into fresh wikis by seedSendToAgent.\r\n *\r\n * @module dsh-tiddlywiki/host/seed-send-to-agent\r\n */\r\nimport type { TiddlyWebClient } from './tw-api.ts'\r\n\r\n/** The packaged plugin tiddler title (a TW system tiddler, type application/json). */\r\nexport const SEND_TO_AGENT_PLUGIN_TITLE = '$:/plugins/dsh/send-to-agent'\r\n\r\n/** One-time marker: presence means \"the button was offered once — hands off\". */\r\nexport const SEND_TO_AGENT_MARKER_TITLE = '$:/plugins/dsh-tiddlywiki/seed-send-to-agent'\r\n\r\n/** The bundle's JSON text (`{\"tiddlers\": {...}}`), exactly as TW stores it. */\r\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.4\\\\\\\",\\\\\\\"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\\\\nfunction notify(msg) {\\\\n\\\\tif ($tw.notifier && typeof $tw.notifier.display === \\\\\\\"function\\\\\\\") {\\\\n\\\\t\\\\t$tw.notifier.display(msg);\\\\n\\\\t} else if (typeof alert === \\\\\\\"function\\\\\\\") {\\\\n\\\\t\\\\talert(msg);\\\\n\\\\t}\\\\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 \\\\n<span class=\\\\\\\"tc-icon-wrapper\\\\\\\"><$transclude tiddler={{!!icon}}/></span>\\\\n \\\\n<$transclude field=\\\\\\\"caption\\\\\\\"/>\\\\n -- \\\\n<i class=\\\\\\\"tc-muted\\\\\\\"><$transclude field=\\\\\\\"description\\\\\\\"/></i>\\\\n</$draggable>\\\\n\\\"\\n }\\n }\\n}\"\r\n\r\n/**\r\n * Seed the \"发送给 Agent\" TW button exactly once per wiki (mirrors the doc-note\r\n * one-shot policy). The marker records the offer; afterwards the bundle is\r\n * user-owned — deleting it and restarting dsh web does NOT recreate it, and\r\n * edits are never overwritten. With `opts.force` the bundle is (re)written even\r\n * when it already exists and the marker is (re)written — the settings page uses\r\n * this for \"重新初始化\". Returns whether a bundle was written this call.\r\n * Never throws.\r\n */\r\nexport async function seedSendToAgent(client: TiddlyWebClient, opts?: { force?: boolean }): Promise<boolean> {\r\n const force = opts?.force === true\r\n if (!force) {\r\n const marker = await client.get(SEND_TO_AGENT_MARKER_TITLE).catch(() => undefined)\r\n if (marker !== undefined) return false\r\n }\r\n const existing = await client.get(SEND_TO_AGENT_PLUGIN_TITLE).catch(() => undefined)\r\n let wrote = false\r\n if (force || existing === undefined) {\r\n await client.put({\r\n title: SEND_TO_AGENT_PLUGIN_TITLE,\r\n text: SEND_TO_AGENT_BUNDLE_TEXT,\r\n type: 'application/json',\r\n tags: [],\r\n // TW only REGISTERS a wiki tiddler as a plugin (boot.js\r\n // registerPluginTiddlers) when the OUTER tiddler carries a\r\n // `plugin-type` field — without it the bundle is never unpacked and its\r\n // startup module never runs in the embedded TW. Mirrors what the live\r\n // wiki's send-to-agent bundle tiddler carries.\r\n 'plugin-type': 'plugin',\r\n name: 'Send to Agent',\r\n author: 'dsh-tiddlywiki',\r\n version: '0.3.2',\r\n description: '把当前笔记一键发送给 DSH Agent(TiddlyWiki → DSH 会话注入)',\r\n })\r\n wrote = true\r\n }\r\n // Record the offer regardless, so an existing bundle (upgrade from a\r\n // pre-seed wiki) also becomes user-owned from here on.\r\n await client\r\n .put({ title: SEND_TO_AGENT_MARKER_TITLE, text: 'seeded-once', type: 'text/plain', tags: [] })\r\n .catch(() => undefined)\r\n return wrote\r\n}\r\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'\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\\\"/></></$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. Never throws.\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 client.get(HOME_INDEX_MARKER_TITLE).catch(() => undefined)\n if (marker !== undefined) return false\n }\n let wrote = false\n for (const item of HOME_INDEX_ITEMS) {\n const existing = await client.get(item.title).catch(() => undefined)\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 client.get('$:/DefaultTiddlers').catch(() => undefined)\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 client\n .put({ title: HOME_INDEX_MARKER_TITLE, text: 'seeded-once', type: 'text/plain', tags: [] })\n .catch(() => undefined)\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. Never throws.\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 client.get(item.title).catch(() => undefined)\n if (t !== undefined) {\n await client.delete(item.title)\n removed.push(item.title)\n }\n }\n const marker = await client.get(HOME_INDEX_MARKER_TITLE).catch(() => undefined)\n if (marker !== undefined) {\n await client.delete(HOME_INDEX_MARKER_TITLE)\n removed.push(HOME_INDEX_MARKER_TITLE)\n }\n const dt = await client.get('$:/DefaultTiddlers').catch(() => undefined)\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","/**\r\n * The \"所有文章\" two-column paginated index page, seeded into wikis by\r\n * seedAllArticles. Source: 所有文章.tid (the wiki's live tiddler).\r\n *\r\n * The page lists every non-system, non-draft tiddler in two independently\r\n * paginated columns:\r\n * - 🤖 Agent 撰写: agent-written and not human-edited\r\n * - 👤 人工 / 人类: not agent-written, OR agent-written + human-edited\r\n * Page size comes live from the plugin config tiddler\r\n * ($:/plugins/dsh-tiddlywiki/config → ui.allArticles.pageSize, default 10).\r\n *\r\n * @module dsh-tiddlywiki/host/seed-all-articles\r\n */\r\nimport type { TiddlyWebClient } from './tw-api.ts'\r\n\r\n/** One-time marker: presence means \"the page was offered once — hands off\". */\r\nexport const ALL_ARTICLES_MARKER_TITLE = '$:/plugins/dsh-tiddlywiki/seed-all-articles'\r\n\r\n/** The 所有文章 page title. */\r\nexport const ALL_ARTICLES_TITLE = '所有文章'\r\n\r\n/** The page body, exactly as seeded (user-owned afterwards). */\r\nexport const ALL_ARTICLES_TEXT = `\\\\whitespace trim\r\n\r\n\\\\define page-size() [{$:/plugins/dsh-tiddlywiki/config}jsonget[ui],[allArticles],[pageSize]else[10]]\r\n\\\\define agent-list() [all[tiddlers]!is[system]!has[draft.of]!tag[索引]tag[agent-written]!tag[human-edited]!sort[modified]]\r\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]]\r\n\r\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;\">\r\n<$action-navigate $to=\"🏠 主页\"/>\r\n← 回主页\r\n</$button>\r\n\r\n!! 📚 所有文章\r\n\r\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>\r\n\r\n<div style=\"display:grid; grid-template-columns:1fr 1fr; gap:12px; margin-top:8px;\">\r\n<div style=\"border:1px solid rgba(128,128,128,0.22); border-radius:10px; padding:10px 12px;\">\r\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>\r\n<$set name=\"page\" tiddler=\"$:/state/dsh/all-articles/page/agent\" emptyValue=\"0\">\r\n<$set name=\"ps\" value={{{ [subfilter<page-size>] }}}>\r\n<$set name=\"total\" value={{{ [subfilter<agent-list>count[]] }}}>\r\n<$set name=\"last\" value={{{ [<total>] +[divide<ps>] +[ceil[0]] +[subtract[1]] +[max[0]] }}}>\r\n<$set name=\"pagec\" value={{{ [<page>] +[min<last>] +[max[0]] }}}>\r\n<$set name=\"offset\" value={{{ [<pagec>] +[multiply<ps>] }}}>\r\n<div>\r\n<$list filter=\"[subfilter<agent-list>] +[rest<offset>] +[first<ps>]\">\r\n<div style=\"display:flex; gap:6px; align-items:baseline; padding:3px 0; border-bottom:1px dashed rgba(128,128,128,0.15);\">\r\n<$link to=<<currentTiddler>> style=\"flex:1;\"><$view field=\"title\"/></$link>\r\n<span style=\"color:#aaa; font-size:0.82em;\"><$view field=\"modified\" format=\"relativedate\"/></span>\r\n</div>\r\n</$list>\r\n<$list filter=\"[subfilter<agent-list>count[]] +[match[0]]\"><div style=\"color:#aaa; font-size:0.85em;\">(暂无)</div></$list>\r\n</div>\r\n<$list filter=\"[<total>!match[0]]\">\r\n<div style=\"display:flex; gap:8px; align-items:center; margin-top:8px;\">\r\n<$list filter=\"[<pagec>compare:number:gt[0]]\">\r\n<$button class=\"tc-btn-invisible\" style=\"padding:2px 10px; border:1px solid rgba(128,128,128,0.35); border-radius:6px;\">\r\n<$action-setfield $tiddler=\"$:/state/dsh/all-articles/page/agent\" $field=\"text\" $value={{{ [<pagec>] +[subtract[1]] +[max[0]] }}}/>\r\n◀ 上一页\r\n</$button>\r\n</$list>\r\n<span style=\"color:#888; font-size:0.85em;\">第 {{{ [<pagec>] +[add[1]] }}} / {{{ [<total>] +[divide<ps>] +[ceil[0]] }}} 页</span>\r\n<$list filter=\"[<pagec>compare:number:lt<last>]\">\r\n<$button class=\"tc-btn-invisible\" style=\"padding:2px 10px; border:1px solid rgba(128,128,128,0.35); border-radius:6px;\">\r\n<$action-setfield $tiddler=\"$:/state/dsh/all-articles/page/agent\" $field=\"text\" $value={{{ [<pagec>] +[add[1]] +[min<last>] }}}/>\r\n下一页 ▶\r\n</$button>\r\n</$list>\r\n</div>\r\n</$list>\r\n</$set>\r\n</$set>\r\n</$set>\r\n</$set>\r\n</$set>\r\n</$set>\r\n</div>\r\n<div style=\"border:1px solid rgba(128,128,128,0.22); border-radius:10px; padding:10px 12px;\">\r\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>\r\n<$set name=\"page\" tiddler=\"$:/state/dsh/all-articles/page/human\" emptyValue=\"0\">\r\n<$set name=\"ps\" value={{{ [subfilter<page-size>] }}}>\r\n<$set name=\"total\" value={{{ [subfilter<human-list>count[]] }}}>\r\n<$set name=\"last\" value={{{ [<total>] +[divide<ps>] +[ceil[0]] +[subtract[1]] +[max[0]] }}}>\r\n<$set name=\"pagec\" value={{{ [<page>] +[min<last>] +[max[0]] }}}>\r\n<$set name=\"offset\" value={{{ [<pagec>] +[multiply<ps>] }}}>\r\n<div>\r\n<$list filter=\"[subfilter<human-list>] +[rest<offset>] +[first<ps>]\">\r\n<div style=\"display:flex; gap:6px; align-items:baseline; padding:3px 0; border-bottom:1px dashed rgba(128,128,128,0.15);\">\r\n<$link to=<<currentTiddler>> style=\"flex:1;\"><$view field=\"title\"/></$link>\r\n<span style=\"color:#aaa; font-size:0.82em;\"><$view field=\"modified\" format=\"relativedate\"/></span>\r\n</div>\r\n</$list>\r\n<$list filter=\"[subfilter<human-list>count[]] +[match[0]]\"><div style=\"color:#aaa; font-size:0.85em;\">(暂无)</div></$list>\r\n</div>\r\n<$list filter=\"[<total>!match[0]]\">\r\n<div style=\"display:flex; gap:8px; align-items:center; margin-top:8px;\">\r\n<$list filter=\"[<pagec>compare:number:gt[0]]\">\r\n<$button class=\"tc-btn-invisible\" style=\"padding:2px 10px; border:1px solid rgba(128,128,128,0.35); border-radius:6px;\">\r\n<$action-setfield $tiddler=\"$:/state/dsh/all-articles/page/human\" $field=\"text\" $value={{{ [<pagec>] +[subtract[1]] +[max[0]] }}}/>\r\n◀ 上一页\r\n</$button>\r\n</$list>\r\n<span style=\"color:#888; font-size:0.85em;\">第 {{{ [<pagec>] +[add[1]] }}} / {{{ [<total>] +[divide<ps>] +[ceil[0]] }}} 页</span>\r\n<$list filter=\"[<pagec>compare:number:lt<last>]\">\r\n<$button class=\"tc-btn-invisible\" style=\"padding:2px 10px; border:1px solid rgba(128,128,128,0.35); border-radius:6px;\">\r\n<$action-setfield $tiddler=\"$:/state/dsh/all-articles/page/human\" $field=\"text\" $value={{{ [<pagec>] +[add[1]] +[min<last>] }}}/>\r\n下一页 ▶\r\n</$button>\r\n</$list>\r\n</div>\r\n</$list>\r\n</$set>\r\n</$set>\r\n</$set>\r\n</$set>\r\n</$set>\r\n</$set>\r\n</div>\r\n</div>\r\n`\r\n\r\n/**\r\n * Seed the 所有文章 page exactly once per wiki (mirrors the doc-note one-shot\r\n * policy). With `opts.force` the page is overwritten with the built-in content\r\n * and the marker is (re)written — the settings page uses this for\r\n * \"重新初始化\". Returns whether the page was written this call. Never throws.\r\n */\r\nexport async function seedAllArticles(client: TiddlyWebClient, opts?: { force?: boolean }): Promise<boolean> {\r\n const force = opts?.force === true\r\n if (!force) {\r\n const marker = await client.get(ALL_ARTICLES_MARKER_TITLE).catch(() => undefined)\r\n if (marker !== undefined) return false\r\n }\r\n const existing = await client.get(ALL_ARTICLES_TITLE).catch(() => undefined)\r\n let wrote = false\r\n if (force || existing === undefined) {\r\n await client.put({ title: ALL_ARTICLES_TITLE, text: ALL_ARTICLES_TEXT, type: 'text/vnd.tiddlywiki', tags: ['索引'] })\r\n wrote = true\r\n }\r\n await client\r\n .put({ title: ALL_ARTICLES_MARKER_TITLE, text: 'seeded-once', type: 'text/plain', tags: [] })\r\n .catch(() => undefined)\r\n return wrote\r\n}\r\n\r\n/**\r\n * Un-seed (反初始化): remove the 所有文章 page and its one-shot marker.\r\n * Deletion is idempotent — a tiddler that was already gone is not listed.\r\n * Never throws.\r\n */\r\nexport async function unseedAllArticles(client: TiddlyWebClient): Promise<{ removed: string[] }> {\r\n const removed: string[] = []\r\n for (const title of [ALL_ARTICLES_TITLE, ALL_ARTICLES_MARKER_TITLE]) {\r\n const t = await client.get(title).catch(() => undefined)\r\n if (t !== undefined) {\r\n await client.delete(title)\r\n removed.push(title)\r\n }\r\n }\r\n return { removed }\r\n}\r\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'\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. Never throws.\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 client.get(UI_STYLES_MARKER_TITLE).catch(() => undefined)\n if (marker !== undefined) return false\n }\n let wrote = false\n for (const item of UI_STYLE_ITEMS) {\n const existing = await client.get(item.title).catch(() => undefined)\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 client\n .put({ title: UI_STYLES_MARKER_TITLE, text: 'seeded-once', type: 'text/plain', tags: [] })\n .catch(() => undefined)\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. Never throws.\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 client.get(item.title).catch(() => undefined)\n if (t !== undefined) {\n await client.delete(item.title)\n removed.push(item.title)\n }\n }\n const marker = await client.get(UI_STYLES_MARKER_TITLE).catch(() => undefined)\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","/**\r\n * The menubar theme override seed.\r\n *\r\n * The `tiddlywiki/menubar` plugin colours its top bar with\r\n * `<<colour menubar-background>>`. Most light palettes (Vanilla, Blanca, …) do\r\n * NOT define `menubar-background`, so the `colour` macro falls through to the\r\n * plugin's hardcoded `$:/config/DefaultColourMappings/` → `#5778d8` (blue) —\r\n * the menubar stays blue in every light theme no matter which palette is\r\n * active. Dark palettes that DO define it (e.g. CupertinoDark → #464646) look\r\n * fine, but light ones never adapt.\r\n *\r\n * The fix lives entirely on the TW side: a small stylesheet tiddler (tagged\r\n * `$:/tags/Stylesheet`) that pins the menubar's bar/items to the ACTIVE\r\n * palette's `background` / `foreground` with `!important`. TW re-renders all\r\n * stylesheets live whenever `$:/palette` changes, and the DSH theme-sync\r\n * (src/client/theme-sync.ts) flips `$:/palette` in-memory to follow the DSH\r\n * light/dark theme — so the menubar follows DSH automatically.\r\n *\r\n * Registered as the `menubar-theme` seed: ONE-SHOT (marker-gated, never\r\n * overwrites user edits) + force \"重新初始化\" from the settings page, exactly\r\n * like the other seeds.\r\n *\r\n * @module dsh-tiddlywiki/host/seed-menubar-theme\r\n */\r\nimport type { TiddlyWebClient } from './tw-api.ts'\r\n\r\n/** One-time marker: presence means \"the override was offered once — hands off\". */\r\nexport const MENUBAR_THEME_MARKER_TITLE = '$:/plugins/dsh-tiddlywiki/seed-menubar-theme'\r\n\r\n/** The stylesheet tiddler that adapts the menubar to the active palette. */\r\nexport const MENUBAR_THEME_TIDDLER = '$:/plugins/dsh-tiddlywiki/menubar-theme'\r\n\r\n/**\r\n * The override stylesheet body, exactly as seeded (user-owned afterwards).\r\n * `<<colour background>>` / `<<colour foreground>>` resolve against the ACTIVE\r\n * palette at render time; `!important` beats the menubar plugin's own rules\r\n * regardless of stylesheet ordering. `\\rules` mirrors the menubar plugin's own\r\n * styles.tid (macrocallinline needed for `<<colour>>`).\r\n */\r\nexport const MENUBAR_THEME_TEXT = `\\\\rules only filteredtranscludeinline transcludeinline macrodef macrocallinline\r\n\r\nnav.tc-menubar ul.tc-menubar-list {\r\n\tbackground: <<colour background>> !important;\r\n}\r\n\r\nnav.tc-menubar li.tc-menubar-item > a,\r\nnav.tc-menubar li.tc-menubar-item > button {\r\n\tcolor: <<colour foreground>> !important;\r\n\tfill: <<colour foreground>> !important;\r\n\tborder-radius: 6px !important;\r\n\ttransition: background-color 120ms ease, color 120ms ease;\r\n}\r\n\r\nnav.tc-menubar li.tc-menubar-item svg {\r\n\tfill: <<colour foreground>> !important;\r\n}\r\n\r\nnav.tc-menubar li.tc-menubar-item > a:hover,\r\nnav.tc-menubar li.tc-menubar-item > button:hover {\r\n\tbackground: color-mix(in srgb, <<colour foreground>> 12%, transparent) !important;\r\n\tcolor: <<colour foreground>> !important;\r\n\tfill: <<colour foreground>> !important;\r\n}\r\n\r\nnav.tc-menubar li.tc-menubar-item > a:active,\r\nnav.tc-menubar li.tc-menubar-item > button:active {\r\n\tbackground: color-mix(in srgb, <<colour foreground>> 20%, transparent) !important;\r\n\tcolor: <<colour foreground>> !important;\r\n\tfill: <<colour foreground>> !important;\r\n}\r\n\r\nnav.tc-menubar li.tc-menubar-item > a.tc-selected,\r\nnav.tc-menubar li.tc-menubar-item > button.tc-selected {\r\n\tbackground: color-mix(in srgb, <<colour foreground>> 16%, transparent) !important;\r\n\tcolor: <<colour foreground>> !important;\r\n\tfill: <<colour foreground>> !important;\r\n}\r\n\r\nnav.tc-menubar li.tc-menubar-item > a:focus-visible,\r\nnav.tc-menubar li.tc-menubar-item > button:focus-visible {\r\n\toutline: none !important;\r\n\tbox-shadow: none !important;\r\n}\r\n`\r\n\r\n/**\r\n * Seed the menubar theme override exactly once per wiki (mirrors the other\r\n * one-shot seeds). With `opts.force` the tiddler is overwritten with the\r\n * built-in content and the marker (re)written — the settings page uses this\r\n * for \"重新初始化\". Returns whether anything was written this call. Never throws.\r\n */\r\nexport async function seedMenubarTheme(client: TiddlyWebClient, opts?: { force?: boolean }): Promise<boolean> {\r\n const force = opts?.force === true\r\n if (!force) {\r\n const marker = await client.get(MENUBAR_THEME_MARKER_TITLE).catch(() => undefined)\r\n if (marker !== undefined) return false\r\n }\r\n const existing = await client.get(MENUBAR_THEME_TIDDLER).catch(() => undefined)\r\n let wrote = false\r\n if (force || existing === undefined) {\r\n await client.put({\r\n title: MENUBAR_THEME_TIDDLER,\r\n text: MENUBAR_THEME_TEXT,\r\n type: 'text/vnd.tiddlywiki',\r\n tags: ['$:/tags/Stylesheet'],\r\n })\r\n wrote = true\r\n }\r\n await client\r\n .put({ title: MENUBAR_THEME_MARKER_TITLE, text: 'seeded-once', type: 'text/plain', tags: [] })\r\n .catch(() => undefined)\r\n return wrote\r\n}\r\n\r\n/**\r\n * Un-seed (反初始化): remove the menubar override stylesheet and its marker,\r\n * returning the wiki to the pre-seed state (the tiddlywiki/menubar top bar\r\n * falls back to its original colour-mapping behaviour). Deletion is\r\n * idempotent — a tiddler already gone is not listed. Never throws.\r\n */\r\nexport async function unseedMenubarTheme(client: TiddlyWebClient): Promise<{ removed: string[] }> {\r\n const removed: string[] = []\r\n for (const title of [MENUBAR_THEME_TIDDLER, MENUBAR_THEME_MARKER_TITLE]) {\r\n const t = await client.get(title).catch(() => undefined)\r\n if (t !== undefined) {\r\n await client.delete(title)\r\n removed.push(title)\r\n }\r\n }\r\n return { removed }\r\n}\r\n","/**\r\n * Generated from the wiki bundle (do not hand-edit the constant).\r\n * Source: render.bundle.json\r\n *\r\n * The \"原生渲染路由\" TW plugin, packaged as a TiddlyWiki plugin bundle\r\n * (`{\"tiddlers\": {...}}`), seeded into fresh wikis by seedRenderRoute. It\r\n * registers a server-side route module (`server-routes/render.js`,\r\n * module-type: route) that renders wiki text into a pure HTML fragment with\r\n * internal links rewritten to the same-origin DSH proxy hash.\r\n *\r\n * @module dsh-tiddlywiki/host/seed-render\r\n */\r\nimport type { TiddlyWebClient } from './tw-api.ts'\r\n\r\n/** The packaged plugin tiddler title (a TW system tiddler, type application/json). */\r\nexport const RENDER_PLUGIN_TITLE = '$:/plugins/dsh/render'\r\n\r\n/** One-time marker: presence means \"the route was offered once — hands off\". */\r\nexport const RENDER_MARKER_TITLE = '$:/plugins/dsh-tiddlywiki/seed-render'\r\n\r\n/** The bundle's JSON text (`{\"tiddlers\": {...}}`), exactly as TW stores it. */\r\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.1.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 wikified body\\\\n { text, contextTitle?, parseAsInline? }\\\\n render arbitrary wiki text 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 wikified body (block parse, so\\\\n\\\\t\\\\t\\\\t// headings / tables / lists render natively).\\\\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 html = state.wiki.renderText(\\\\\\\"text/html\\\\\\\",\\\\\\\"text/vnd.tiddlywiki\\\\\\\",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).\\\\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 textHtml = state.wiki.renderText(\\\\\\\"text/html\\\\\\\",\\\\\\\"text/vnd.tiddlywiki\\\\\\\",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}\"\r\n\r\n/**\r\n * Seed the \"原生渲染路由\" TW plugin exactly once per wiki (mirrors the\r\n * send-to-agent one-shot policy). The marker records the offer; afterwards the\r\n * bundle is user-owned — deleting it and restarting dsh web does NOT recreate\r\n * it, and edits are never overwritten. With `opts.force` the bundle is\r\n * (re)written even when it already exists and the marker is (re)written — the\r\n * settings page uses this for \"重新初始化\". Returns whether a bundle was\r\n * written this call. Never throws.\r\n */\r\nexport async function seedRenderRoute(client: TiddlyWebClient, opts?: { force?: boolean }): Promise<boolean> {\r\n const force = opts?.force === true\r\n if (!force) {\r\n const marker = await client.get(RENDER_MARKER_TITLE).catch(() => undefined)\r\n if (marker !== undefined) return false\r\n }\r\n const existing = await client.get(RENDER_PLUGIN_TITLE).catch(() => undefined)\r\n let wrote = false\r\n if (force || existing === undefined) {\r\n await client.put({\r\n title: RENDER_PLUGIN_TITLE,\r\n text: RENDER_BUNDLE_TEXT,\r\n type: 'application/json',\r\n tags: [],\r\n // TW only REGISTERS a wiki tiddler as a plugin (boot.js\r\n // registerPluginTiddlers) when the OUTER tiddler carries a\r\n // `plugin-type` field — without it the bundle is never unpacked and its\r\n // server-routes module is never defined, so /render 404s. The metadata\r\n // mirrors what the live send-to-agent bundle's tiddler carries.\r\n 'plugin-type': 'plugin',\r\n name: 'DSH Wiki Render',\r\n author: 'dsh-tiddlywiki',\r\n version: '0.1.0',\r\n description: '把 wiki 文本原生渲染成 HTML 片段(POST /render 服务端路由),供 DSH 回复流工具卡与 wiki 链接跳转使用',\r\n })\r\n wrote = true\r\n }\r\n // Record the offer regardless, so an existing bundle (upgrade from a\r\n // pre-seed wiki) also becomes user-owned from here on.\r\n await client\r\n .put({ title: RENDER_MARKER_TITLE, text: 'seeded-once', type: 'text/plain', tags: [] })\r\n .catch(() => undefined)\r\n return wrote\r\n}\r\n","/**\r\n * Unified seed registry (design: every one-time \"与 dsh 联动需要 wiki 预置\"\r\n * item is a SeedDef here).\r\n *\r\n * Three tiers (v0.16.22):\r\n * - CORE seeds (`core: true`) are functionally required by the plugin's\r\n * own features — the startup path seeds exactly these (non-force) and\r\n * they can never be 反初始化'd.\r\n * - STARTER seeds (`startup: true`, removable) are docs / examples that\r\n * give a fresh wiki a working「文档中心」out of the box — the startup path\r\n * ALSO seeds them on first install (safe-skip: same-named tiddlers are\r\n * never overwritten), and the user can 反初始化 them anytime.\r\n * - OPTIONAL seeds (`core: false`, `startup: false`) are nice-to-have\r\n * content (首页 / 所有文章 / 自定义样式 / menubar 顶栏主题自适应) — they are\r\n * NEVER auto-seeded; the user opts in from the settings page「初始化」section\r\n * (重新初始化) and can opt out again with 反初始化 (remove).\r\n *\r\n * Each seed owns:\r\n * - `check` — current state (present / missing / needs-update) for the UI;\r\n * - `run(force)` — non-force keeps the ONE-SHOT / user-owned semantics\r\n * (write only when missing, never overwrite), force (re)writes the\r\n * built-in content and (re)records the marker;\r\n * - `remove` — optional + starter seeds only: delete the seeded tiddlers +\r\n * markers, returning the wiki to the \"never seeded\" state.\r\n *\r\n * Registry: doc-note / starter-docs / send-to-agent / render-route /\r\n * home-index / all-articles / ui-styles / menubar-theme / tw-web-host.\r\n *\r\n * @module dsh-tiddlywiki/host/seeds\r\n */\r\nimport type { TiddlyWebClient } from './tw-api.ts'\r\nimport { seedDocNote, unseedDocNote, DOC_NOTE_TITLE } from './seed-notes.ts'\r\nimport { seedStarterDocs, unseedStarterDocs, STARTER_DOCS_ITEMS, STARTER_DOCS_MARKER_TITLE } from './seed-starter-docs.ts'\r\nimport { seedSendToAgent, SEND_TO_AGENT_PLUGIN_TITLE } from './seed-send-to-agent.ts'\r\nimport { seedHomeIndex, unseedHomeIndex, HOME_INDEX_ITEMS } from './seed-home.ts'\r\nimport { seedAllArticles, unseedAllArticles, ALL_ARTICLES_TITLE } from './seed-all-articles.ts'\r\nimport { seedUiStyles, unseedUiStyles, UI_STYLE_ITEMS } from './seed-ui-styles.ts'\r\nimport { seedMenubarTheme, unseedMenubarTheme, MENUBAR_THEME_TIDDLER } from './seed-menubar-theme.ts'\r\nimport { seedRenderRoute, RENDER_PLUGIN_TITLE } from './seed-render.ts'\r\nimport { TW_WEB_HOST_TIDDLER, TW_WEB_HOST_DEFAULT } from './config.ts'\r\nimport { TW_PROXY_PATH } from './wiki.ts'\r\n\r\n/** One seed's status as reported to the settings page. */\r\nexport interface SeedStatus {\r\n id: string\r\n title: string\r\n description: string\r\n /** true = target tiddler(s) present (or host value correct). */\r\n present: boolean\r\n /**\r\n * true = the seed can be 反初始化 (removed). Core seeds that the plugin's\r\n * own features depend on are never removable from the settings page.\r\n */\r\n removable: boolean\r\n /** Human detail, e.g. which tiddlers are missing. */\r\n detail?: string\r\n}\r\n\r\n/** Result of running one seed. */\r\nexport interface SeedRunResult {\r\n id: string\r\n ok: boolean\r\n /** true = something was written this call. */\r\n wrote: boolean\r\n detail?: string\r\n error?: string\r\n}\r\n\r\n/** Context a seed needs (client to the live TW server). */\r\nexport interface SeedContext {\r\n client: TiddlyWebClient\r\n}\r\n\r\n/** A registered seed: check current state + run (optionally force) + remove. */\r\nexport interface SeedDef {\r\n id: string\r\n title: string\r\n description: string\r\n /**\r\n * Core seeds are seeded automatically on startup (功能必需) and can never\r\n * be 反初始化'd — removing them would break a plugin feature.\r\n */\r\n core: boolean\r\n /**\r\n * STARTER tier (v0.16.22): docs / examples that the startup path ALSO seeds\r\n * on first install (safe-skip: same-named tiddlers never overwritten), but\r\n * which stay removable via「反初始化」. Meaningful only when `core` is false.\r\n */\r\n startup?: boolean\r\n check(ctx: SeedContext): Promise<SeedStatus>\r\n run(ctx: SeedContext, force: boolean): Promise<SeedRunResult>\r\n /** Non-core seeds only: delete the seeded tiddlers + markers (反初始化). */\r\n remove?(ctx: SeedContext): Promise<SeedRunResult>\r\n}\r\n\r\nconst presentOf = (ctx: SeedContext, title: string): Promise<boolean> =>\r\n ctx.client.get(title).then((t) => t !== undefined).catch(() => false)\r\n\r\n/** Build the per-item detail from an unseed result. */\r\nconst removedDetail = (id: string, removed: string[]): SeedRunResult => ({\r\n id,\r\n ok: true,\r\n wrote: false,\r\n detail: removed.length > 0\r\n ? `已移除:${removed.join('、')}`\r\n : '本就不存在,无需移除',\r\n})\r\n\r\n/** Metadata shared by every seed and echoed in its status/result objects. */\r\ninterface SeedMeta {\r\n id: string\r\n title: string\r\n description: string\r\n core: boolean\r\n /** STARTER tier: also auto-seeded on first install (removable). */\r\n startup?: boolean\r\n}\r\n\r\ntype SeedWriter = (client: TiddlyWebClient, opts?: { force?: boolean }) => Promise<boolean>\r\ntype SeedUnseeder = (client: TiddlyWebClient) => Promise<{ removed: string[] }>\r\n\r\n/**\r\n * Define a seed from a small spec, killing the id/title/description\r\n * triplication and the try/catch boilerplate every registry entry used to\r\n * repeat by hand:\r\n * - `presentTitle` → default check: is that tiddler present?\r\n * - `check` → custom presence check (overrides presentTitle).\r\n * - `write` → built-in content writer; true = (re)written this call.\r\n * - `run` → custom runner (overrides the standard write wrapper;\r\n * e.g. tw-web-host honors a user-chosen host value).\r\n * - `unseed` → remove the seeded tiddlers + markers (removable seeds).\r\n */\r\nfunction defineSeed(meta: SeedMeta, impl: {\r\n presentTitle?: string\r\n check?: (ctx: SeedContext) => Promise<SeedStatus>\r\n write?: SeedWriter\r\n run?: (ctx: SeedContext, force: boolean) => Promise<SeedRunResult>\r\n unseed?: SeedUnseeder\r\n}): SeedDef {\r\n const { id, title, description, core, startup } = meta\r\n const removable = !core\r\n const check: SeedDef['check'] = impl.check ?? (async (ctx) => {\r\n const present = await presentOf(ctx, impl.presentTitle ?? '')\r\n return { id, title, description, present, removable, detail: present ? '已存在' : '缺失' }\r\n })\r\n const run: SeedDef['run'] = impl.run ?? (async (ctx, force) => {\r\n try {\r\n const wrote = await impl.write!(ctx.client, { force })\r\n return { id, ok: true, wrote, detail: wrote ? (force ? '已重新初始化' : '已写入') : (force ? '内容已是最新(未重写)' : '已存在,跳过') }\r\n } catch (err) {\r\n return { id, ok: false, wrote: false, error: err instanceof Error ? err.message : String(err) }\r\n }\r\n })\r\n const remove: SeedDef['remove'] = impl.unseed === undefined ? undefined : async (ctx) => {\r\n try {\r\n return removedDetail(id, (await impl.unseed!(ctx.client)).removed)\r\n } catch (err) {\r\n return { id, ok: false, wrote: false, error: err instanceof Error ? err.message : String(err) }\r\n }\r\n }\r\n return { id, title, description, core, ...(startup === undefined ? {} : { startup }), check, run, ...(remove === undefined ? {} : { remove }) }\r\n}\r\n\r\n/** The full registry, in display order. */\r\nexport const SEED_DEFS: SeedDef[] = [\r\n defineSeed(\r\n { id: 'doc-note', title: '插件说明笔记', description: '「dsh-tiddlywiki 插件说明」——入门说明笔记(首次安装默认写入;ONE-SHOT,用户可改可删,标记 dsh-docs 自动进首页「📚 插件文档」栏)。', core: false, startup: true },\r\n { presentTitle: DOC_NOTE_TITLE, write: seedDocNote, unseed: unseedDocNote },\r\n ),\r\n defineSeed(\r\n { id: 'starter-docs', title: '示例与文档(汇总模板 / 教程 / 主题页示例)', description: '新手文档中心起步包:主题汇总页·模板、教程(按主题/标签做汇总页)、三个可直接运行的示例主题页(日志 / 决策记录 / 排障)。全部打 dsh-docs 标签,自动出现在首页「📚 插件文档」栏;纯示例无个人数据,同名 tiddler 已存在则安全跳过,不会覆盖。首次安装默认写入,可反初始化。', core: false, startup: true },\r\n {\r\n check: async (ctx) => {\r\n const missing: string[] = []\r\n for (const item of STARTER_DOCS_ITEMS) {\r\n if (!(await presentOf(ctx, item.title))) missing.push(item.title)\r\n }\r\n return { id: 'starter-docs', title: '示例与文档(汇总模板 / 教程 / 主题页示例)', description: '新手文档中心起步包:主题汇总页·模板、教程(按主题/标签做汇总页)、三个可直接运行的示例主题页(日志 / 决策记录 / 排障)。全部打 dsh-docs 标签,自动出现在首页「📚 插件文档」栏;纯示例无个人数据,同名 tiddler 已存在则安全跳过,不会覆盖。首次安装默认写入,可反初始化。', present: missing.length === 0, removable: true, detail: missing.length === 0 ? '已存在' : `缺失:${missing.join('、')}` }\r\n },\r\n write: seedStarterDocs,\r\n unseed: unseedStarterDocs,\r\n },\r\n ),\r\n defineSeed(\r\n { id: 'send-to-agent', title: '「发送给 Agent」按钮', description: 'TW 笔记工具栏「发送给 Agent」按钮插件($:/plugins/dsh/send-to-agent)——把笔记一键注入 DSH 会话。', core: true },\r\n { presentTitle: SEND_TO_AGENT_PLUGIN_TITLE, write: seedSendToAgent },\r\n ),\r\n defineSeed(\r\n { id: 'render-route', title: '原生渲染路由(/render)', description: 'TW 服务端路由插件($:/plugins/dsh/render,server-routes/render.js)——把 wiki 文本在运行中的 TW 里原生渲染成 HTML 片段,回复流工具卡与 wiki 链接跳转依赖它。seed 写入后需重启 TW 使路由生效。', core: true },\r\n { presentTitle: RENDER_PLUGIN_TITLE, write: seedRenderRoute },\r\n ),\r\n defineSeed(\r\n { id: 'home-index', title: '首页(主页 / 所有标签 / 标签笔记)', description: '默认主页:四象限待办 + 「所有标签」「所有文章」入口;所有标签:标签统计 + Agent 区块(纯 Agent / Agent+人工);标签笔记:按标签浏览。系统提示承诺的首页由这里 seed,主页(🏠 主页)同时写入 $:/DefaultTiddlers。', core: false },\r\n {\r\n check: async (ctx) => {\r\n const missing: string[] = []\r\n for (const item of HOME_INDEX_ITEMS) {\r\n if (!(await presentOf(ctx, item.title))) missing.push(item.title)\r\n }\r\n return { id: 'home-index', title: '首页(主页 / 所有标签 / 标签笔记)', description: '默认主页:四象限待办 + 「所有标签」「所有文章」入口;所有标签:标签统计 + Agent 区块(纯 Agent / Agent+人工);标签笔记:按标签浏览。系统提示承诺的首页由这里 seed,主页(🏠 主页)同时写入 $:/DefaultTiddlers。', present: missing.length === 0, removable: true, detail: missing.length === 0 ? '已存在' : `缺失:${missing.join('、')}` }\r\n },\r\n write: seedHomeIndex,\r\n unseed: unseedHomeIndex,\r\n },\r\n ),\r\n defineSeed(\r\n { id: 'all-articles', title: '所有文章(两列分页总览)', description: '「所有文章」——全部条目分两列(🤖 Agent 撰写 / 👤 人工·人类)各自分页展示。每页条数取插件设置 ui.allArticles.pageSize(默认 10)。', core: false },\r\n { presentTitle: ALL_ARTICLES_TITLE, write: seedAllArticles, unseed: unseedAllArticles },\r\n ),\r\n defineSeed(\r\n { id: 'ui-styles', title: '自定义样式(编辑器美化 / 窄屏侧栏 / menubar 加高 / 批注弹窗)', description: '5 张通用样式表(tag $:/tags/Stylesheet):编辑器美化(CodeMirror 字体/光标/行号)、标题与按钮区分开、侧边栏窄屏自动隐藏(<960px)、menubar 顶栏加高、批注弹窗美化。纯样式无个人数据。', core: false },\r\n {\r\n check: async (ctx) => {\r\n const missing: string[] = []\r\n for (const item of UI_STYLE_ITEMS) {\r\n if (!(await presentOf(ctx, item.title))) missing.push(item.title)\r\n }\r\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('、')}` }\r\n },\r\n write: seedUiStyles,\r\n unseed: unseedUiStyles,\r\n },\r\n ),\r\n defineSeed(\r\n { id: 'menubar-theme', title: 'menubar 顶栏主题自适应', description: '样式表覆盖($:/plugins/dsh-tiddlywiki/menubar-theme,tag $:/tags/Stylesheet)——把 tiddlywiki/menubar 顶栏从「默认色映射的蓝色」改为跟随当前 palette 的 background/foreground,随 DSH 主题切换($:/palette 翻转)自动换色。', core: false },\r\n { presentTitle: MENUBAR_THEME_TIDDLER, write: seedMenubarTheme, unseed: unseedMenubarTheme },\r\n ),\r\n defineSeed(\r\n { id: 'tw-web-host', title: 'TW 前端 API 基址(同源代理)', description: '把 $:/config/tiddlyweb/host 指向 DSH 同源代理,嵌入式 TW 才能经 DSH origin 访问(远程访问模式的前提)。', core: true },\r\n {\r\n check: async (ctx) => {\r\n let current: string | undefined\r\n try {\r\n current = (await ctx.client.get(TW_WEB_HOST_TIDDLER))?.text?.trim()\r\n } catch {\r\n current = undefined\r\n }\r\n const ok = current === TW_PROXY_PATH\r\n return { id: 'tw-web-host', title: 'TW 前端 API 基址(同源代理)', description: '把 $:/config/tiddlyweb/host 指向 DSH 同源代理,嵌入式 TW 才能经 DSH origin 访问(远程访问模式的前提)。', present: ok, removable: false, detail: ok ? `已指向 ${TW_PROXY_PATH}` : `当前:${current ?? '(缺失)'},应为 ${TW_PROXY_PATH}` }\r\n },\r\n run: async (ctx, force) => {\r\n try {\r\n let current: string | undefined\r\n try {\r\n current = (await ctx.client.get(TW_WEB_HOST_TIDDLER))?.text?.trim()\r\n } catch {\r\n current = undefined\r\n }\r\n // Non-force keeps the ensure semantics: write only when missing or still\r\n // the legacy default (a user override pointing elsewhere is honored).\r\n if (!force && current !== undefined && current !== TW_WEB_HOST_DEFAULT) {\r\n return { id: 'tw-web-host', ok: true, wrote: false, detail: '已指向自定义基址,未覆盖' }\r\n }\r\n await ctx.client.put({ title: TW_WEB_HOST_TIDDLER, text: TW_PROXY_PATH, type: 'text/plain', tags: [] })\r\n return { id: 'tw-web-host', ok: true, wrote: true, detail: force ? '已重新初始化(强制写回代理基址)' : '已写入代理基址' }\r\n } catch (err) {\r\n return { id: 'tw-web-host', ok: false, wrote: false, error: err instanceof Error ? err.message : String(err) }\r\n }\r\n },\r\n },\r\n ),\r\n]\r\n\r\n/** Check every seed, returning statuses in registry order. */\r\nexport async function checkAllSeeds(ctx: SeedContext): Promise<SeedStatus[]> {\r\n const out: SeedStatus[] = []\r\n for (const def of SEED_DEFS) {\r\n try {\r\n out.push(await def.check(ctx))\r\n } catch (err) {\r\n out.push({\r\n id: def.id,\r\n title: def.title,\r\n description: def.description,\r\n present: false,\r\n removable: !def.core,\r\n detail: `检查失败:${err instanceof Error ? err.message : String(err)}`,\r\n })\r\n }\r\n }\r\n return out\r\n}\r\n\r\n/**\r\n * Run one seed (or all, when id is undefined). Non-force = one-shot semantics;\r\n * force = manual \"重新初始化\" from the settings page. A manual run with no id\r\n * (重新初始化 all) covers every registry item, core and optional alike.\r\n */\r\nexport async function runSeedById(ctx: SeedContext, id: string | undefined, force: boolean): Promise<SeedRunResult[]> {\r\n const targets = id === undefined ? SEED_DEFS : SEED_DEFS.filter((d) => d.id === id)\r\n if (targets.length === 0) {\r\n return [{ id: id ?? '', ok: false, wrote: false, error: `unknown seed: ${id}` }]\r\n }\r\n const out: SeedRunResult[] = []\r\n for (const def of targets) {\r\n out.push(await def.run(ctx, force))\r\n }\r\n return out\r\n}\r\n\r\n/**\r\n * Startup path: seed the CORE items (功能必需:发送给 Agent 按钮 + 原生渲染\r\n * 路由 + TW 前端 API 基址) AND the STARTER items (首次安装默认:插件说明 +\r\n * 示例与文档), all non-force (write only what is missing — a same-named\r\n * tiddler already present is NEVER overwritten, so user data stays user data).\r\n * Remaining optional seeds (首页 / 所有文章 / 自定义样式 / menubar 顶栏主题自\r\n * 适应) are never forced on users — they opt in from the settings page\r\n * 「初始化」section.\r\n */\r\nexport async function runAllSeeds(ctx: SeedContext): Promise<SeedRunResult[]> {\r\n const out: SeedRunResult[] = []\r\n for (const def of SEED_DEFS) {\r\n if (!def.core && def.startup !== true) continue\r\n out.push(await def.run(ctx, false))\r\n }\r\n return out\r\n}\r\n\r\n/**\r\n * 反初始化 (remove): delete one non-core seed's (starter 或 optional) seeded\r\n * tiddlers + markers, or all non-core seeds when `id` is undefined. Core\r\n * seeds are functionally required and cannot be removed — a direct request\r\n * for one returns an error result (and is skipped when removing all).\r\n */\r\nexport async function removeSeedById(ctx: SeedContext, id: string | undefined): Promise<SeedRunResult[]> {\r\n if (id === undefined) {\r\n const out: SeedRunResult[] = []\r\n for (const def of SEED_DEFS) {\r\n if (def.core || def.remove === undefined) continue\r\n out.push(await def.remove(ctx))\r\n }\r\n return out\r\n }\r\n const def = SEED_DEFS.find((d) => d.id === id)\r\n if (def === undefined) {\r\n return [{ id, ok: false, wrote: false, error: `unknown seed: ${id}` }]\r\n }\r\n if (def.core) {\r\n return [{ id, ok: false, wrote: false, error: '该 seed 为核心项(功能必需),不可反初始化' }]\r\n }\r\n if (def.remove === undefined) {\r\n return [{ id, ok: false, wrote: false, error: '该 seed 不支持移除' }]\r\n }\r\n return [await def.remove(ctx)]\r\n}\r\n","/**\r\n * TiddlyWeb REST client (design doc §5) — the ONLY way every writer reaches\r\n * the wiki (quick notes, agent tools, editor saves all go through the TW\r\n * service, D1), so there is never a second write path.\r\n *\r\n * ROUTES ARE EMPIRICALLY VERIFIED against tiddlywiki 5.4.1's core-server\r\n * (`core-server/server/routes/`):\r\n * GET /recipes/default/tiddlers.json[?exclude=...] list (skinny)\r\n * GET /recipes/default/tiddlers/<title> read one (404 absent)\r\n * PUT /recipes/default/tiddlers/<title> write one (204)\r\n * DELETE /bags/default/tiddlers/<title> delete one (204)\r\n * Writes require the `X-Requested-With: TiddlyWiki` header (TW CSRF), which\r\n * this client always sends. Tags arrive as a whitespace-joined STRING and are\r\n * normalized to arrays here.\r\n *\r\n * SEARCH (R2): the server blocks arbitrary `filter=` queries with 403 unless\r\n * the exact filter is whitelisted in $:/config/Server/ExternalFilters. So\r\n * `search()` fetches the TEXT-BEARING listing WITH text (TEXT_LIST_FILTER +\r\n * `?exclude=` a sentinel, whitelist self-healed on first 403) and matches\r\n * locally — one request, no 403, no per-tiddler round-trips, and binary\r\n * tiddlers (images etc.) never ship their base64 payload (v0.16.20).\r\n *\r\n * @module dsh-tiddlywiki/host/tw-api\r\n */\r\n\r\n/** A tiddler's readable fields (loose on purpose). */\r\nexport interface Tiddler {\r\n title: string\r\n text?: string\r\n tags?: string[]\r\n type?: string\r\n created?: string\r\n modified?: string\r\n /** Extra custom fields returned by the server are folded under `fields`. */\r\n fields?: Record<string, unknown>\r\n [key: string]: unknown\r\n}\r\n\r\nconst REQUEST_TIMEOUT_MS = 10_000\r\n\r\n/** TW's CSRF gate: writes must carry this header (TW's own UI always does). */\r\nconst CSRF_HEADER = { 'x-requested-with': 'TiddlyWiki' }\r\n\r\n/** Sentinel `exclude` value: excludes nothing, so `text` stays in the list. */\r\nconst LIST_WITH_TEXT_EXCLUDE = '__dsh_tw_none__'\r\n\r\n/**\r\n * Recipe-list filter that keeps ONLY text-bearing tiddlers (notes): no `type`\r\n * field at all, or a `text/*` type. Binary tiddlers (images/audio/video/fonts/\r\n * PDF/zip…) carry their payload as a base64 `text` field — on a big wiki (e.g.\r\n * thousands of scanned book pages) the full listing serializes 500+MB of base64\r\n * and times every agent read out. This filter drops them SERVER-SIDE so the\r\n * listing stays ~6MB (v0.16.20, verified on a 2418-tiddler wiki:\r\n * 515MB/17s → 6MB/0.3s).\r\n *\r\n * TW 5.4.1 filter facts that matter here (all verified empirically):\r\n * - `prefix`/`match` operators match the TITLE only — field matching needs\r\n * `regexp:<field>` / `field:<field>`.\r\n * - `[has[type]]` = \"has a non-empty type field\"; `[has:type[]]` is a DIFFERENT\r\n * call (suffix \"type\" + empty operand) that matches everything.\r\n * - space-separated operations UNION (no prefix = \"or\"); `+` means\r\n * intersection.\r\n * - negated `regexp:type`/`field:type` DROP type-less tiddlers (their field\r\n * string is null), so \"exclude binary\" must be written as the positive\r\n * \"no type OR text-ish\" union below.\r\n *\r\n * FILENAME BUDGET (Windows, verified): the whitelist tiddler below is stored\r\n * as a .tid file whose name contains the ENTIRE filter string, so the filter\r\n * must stay short — a ~180-char filter produced a ~217-char filename that\r\n * broke `git add` on Windows (\"Filename too long\", MAX_PATH) and killed the\r\n * auto-committer. 86 chars keeps the file at ~123 chars even on a deep wiki\r\n * path. (Consequence: application/json tiddlers are not searchable — on this\r\n * wiki there are none, and they are config/data tiddlers, not notes.)\r\n */\r\nexport const TEXT_LIST_FILTER = [\r\n '[all[tiddlers]!is[system]!has[type]]',\r\n '[all[tiddlers]!is[system]regexp:type[(?i)^text/]]',\r\n].join(' ')\r\n\r\n/** TiddlyWeb blocks every non-default filter with 403 unless the EXACT filter\r\n * string is whitelisted at `$:/config/Server/ExternalFilters/<filter>` = \"yes\".\r\n * This client writes that tiddler once on 403 (self-heal, idempotent) and\r\n * retries; the tiddler lives in the wiki and travels with its git history. */\r\nfunction externalFilterWhitelistTitle(filter: string): string {\r\n return `$:/config/Server/ExternalFilters/${filter}`\r\n}\r\n\r\n/** Binary MIME type prefixes — their `text` field is base64 payload, not content. */\r\nconst BINARY_TYPE_PREFIXES = ['image/', 'audio/', 'video/', 'font/']\r\nconst BINARY_TYPE_EXACT = new Set([\r\n 'application/octet-stream', 'application/pdf', 'application/zip', 'application/gzip',\r\n 'application/x-gzip', 'application/x-7z-compressed', 'application/x-rar-compressed',\r\n 'application/epub+zip', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\r\n])\r\n\r\n/** True when a tiddler's `type` marks it as binary (base64 in `text`). */\r\nexport function isBinaryType(type: string | undefined): boolean {\r\n if (typeof type !== 'string' || type.length === 0) return false\r\n return BINARY_TYPE_PREFIXES.some((p) => type.startsWith(p)) || BINARY_TYPE_EXACT.has(type)\r\n}\r\n\r\n/** Split TW's whitespace-joined tags string into an array. */\r\nfunction normalizeTags(tags: unknown): string[] | undefined {\r\n if (tags === undefined) return undefined\r\n if (Array.isArray(tags)) return tags.map(String)\r\n if (typeof tags === 'string') {\r\n const parts = tags.trim().split(/\\s+/).filter(Boolean)\r\n return parts.length > 0 ? parts : []\r\n }\r\n return []\r\n}\r\n\r\n/** Normalize a raw server tiddler (tags string → array, unknown fields nested). */\r\nfunction normalizeTiddler(raw: Record<string, unknown>): Tiddler {\r\n const out = { ...raw } as Tiddler\r\n const tags = normalizeTags(raw.tags)\r\n if (tags !== undefined) out.tags = tags\r\n return out\r\n}\r\n\r\nexport class TiddlyWebClient {\r\n constructor(private readonly baseUrl: string) {}\r\n\r\n private async request(path: string, init?: RequestInit): Promise<Response> {\r\n return fetch(`${this.baseUrl}${path}`, {\r\n ...init,\r\n signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),\r\n })\r\n }\r\n\r\n /** GET /status → { username, anonymous, space, tiddlywiki_version, ... }. */\r\n async status(): Promise<Record<string, unknown>> {\r\n const res = await this.request('/status')\r\n if (!res.ok) throw new Error(`TiddlyWeb /status HTTP ${res.status}`)\r\n return res.json() as Promise<Record<string, unknown>>\r\n }\r\n\r\n /** Read one tiddler; undefined when it does not exist (404). */\r\n async get(title: string): Promise<Tiddler | undefined> {\r\n const res = await this.request(`/recipes/default/tiddlers/${encodeURIComponent(title)}`)\r\n if (res.status === 404) return undefined\r\n if (!res.ok) throw new Error(`TiddlyWeb GET /recipes/default/tiddlers/${title} HTTP ${res.status}`)\r\n return normalizeTiddler((await res.json()) as Record<string, unknown>)\r\n }\r\n\r\n /** Write (create or overwrite) one tiddler via PUT (204 on success). */\r\n async put(tiddler: Tiddler): Promise<Tiddler> {\r\n const title = tiddler.title\r\n const res = await this.request(`/recipes/default/tiddlers/${encodeURIComponent(title)}`, {\r\n method: 'PUT',\r\n headers: { 'content-type': 'application/json', ...CSRF_HEADER },\r\n body: JSON.stringify(tiddler),\r\n })\r\n if (!res.ok) {\r\n const detail = await res.text().catch(() => '')\r\n throw new Error(`TiddlyWeb PUT /recipes/default/tiddlers/${title} HTTP ${res.status}: ${detail.slice(0, 300)}`)\r\n }\r\n return tiddler\r\n }\r\n\r\n /** Delete one tiddler via the bags route (204); a missing one is a no-op. */\r\n async delete(title: string): Promise<void> {\r\n const res = await this.request(`/bags/default/tiddlers/${encodeURIComponent(title)}`, {\r\n method: 'DELETE',\r\n headers: CSRF_HEADER,\r\n })\r\n if (res.status === 404) return\r\n if (!res.ok) throw new Error(`TiddlyWeb DELETE /bags/default/tiddlers/${title} HTTP ${res.status}`)\r\n }\r\n\r\n /**\r\n * List tiddlers via the default server filter. Arbitrary `filter=` queries\r\n * are blocked by the server (403) unless whitelisted, so callers needing a\r\n * subset should use search(); a supplied filter that is 403-blocked falls\r\n * back to the text-bearing filter.\r\n *\r\n * `includeText=true` returns only TEXT-BEARING tiddlers WITH their `text`\r\n * (never the base64 payloads of binary tiddlers): on 403 the whitelist\r\n * tiddler for TEXT_LIST_FILTER is written once and the request retried\r\n * (self-heal); if that write fails (read-only wiki) it degrades to the\r\n * skinny listing (title/tags matchable, empty snippets).\r\n */\r\n async list(filter?: string, includeText = false): Promise<Tiddler[]> {\r\n if (!includeText) return this.fetchListing(filter)\r\n const explicit = filter !== undefined && filter.length > 0\r\n const target = explicit ? (filter as string) : TEXT_LIST_FILTER\r\n let res = await this.requestListWithText(target)\r\n if (res.status === 403) {\r\n await this.ensureExternalFilterWhitelist(TEXT_LIST_FILTER).catch(() => undefined)\r\n res = await this.requestListWithText(TEXT_LIST_FILTER)\r\n }\r\n if (res.status === 403) return this.fetchListing(undefined)\r\n if (!res.ok) throw new Error(`TiddlyWeb recipe list HTTP ${res.status}`)\r\n return this.parseList(res)\r\n }\r\n\r\n /** GET the recipe listing WITHOUT text payloads (server default excludes `text`). */\r\n private async fetchListing(filter?: string): Promise<Tiddler[]> {\r\n const params = new URLSearchParams()\r\n if (filter !== undefined && filter.length > 0) params.set('filter', filter)\r\n const query = params.toString()\r\n let res = await this.request(`/recipes/default/tiddlers.json${query.length > 0 ? `?${query}` : ''}`)\r\n if (!res.ok && res.status === 403 && filter !== undefined && filter.length > 0) {\r\n // Filter not whitelisted → refetch with the default (whitelisted) filter.\r\n res = await this.request('/recipes/default/tiddlers.json')\r\n }\r\n if (!res.ok) throw new Error(`TiddlyWeb recipe list HTTP ${res.status}`)\r\n return this.parseList(res)\r\n }\r\n\r\n /** GET the recipe listing WITH text for a specific (whitelisted) filter. */\r\n private async requestListWithText(filter: string): Promise<Response> {\r\n return this.request(`/recipes/default/tiddlers.json?filter=${encodeURIComponent(filter)}&exclude=${LIST_WITH_TEXT_EXCLUDE}`)\r\n }\r\n\r\n /** Write `$:/config/Server/ExternalFilters/<filter>` = \"yes\" (idempotent). */\r\n private async ensureExternalFilterWhitelist(filter: string): Promise<void> {\r\n await this.put({ title: externalFilterWhitelistTitle(filter), text: 'yes' })\r\n }\r\n\r\n private async parseList(res: Response): Promise<Tiddler[]> {\r\n const data = (await res.json()) as Array<Record<string, unknown>> | { tiddlers?: Array<Record<string, unknown>> }\r\n const items = Array.isArray(data) ? data : (data.tiddlers ?? [])\r\n return items.map(normalizeTiddler)\r\n }\r\n\r\n /**\r\n * Search text-bearing tiddlers: one request (text-bearing listing with text)\r\n * plus local case-insensitive substring matching on title + text, optional\r\n * exact tags (AND), a `since` modified-time floor, an exact `type`, capped at\r\n * `limit`. Robust against the server's external-filter 403 (whitelist is\r\n * self-healed). Binary tiddlers (images/audio/…) are NOT in the listing, so\r\n * they can never match — a deliberate flood guard on big wikis.\r\n */\r\n async search(query: string, options: SearchOptions = {}): Promise<{ items: Tiddler[]; total: number }> {\r\n const items = await this.list(undefined, true)\r\n const needle = query.toLowerCase()\r\n const sinceTime = parseSince(options.since)\r\n const wantedTags = [...(options.tags ?? []), options.tag].filter((t): t is string => typeof t === 'string' && t.trim().length > 0)\r\n const limit = options.limit ?? 30\r\n const matched = items.filter((t) => {\r\n if (t.title.startsWith('$:/')) return false\r\n if (!t.title.toLowerCase().includes(needle) && !(t.text ?? '').toLowerCase().includes(needle)) return false\r\n if (sinceTime !== undefined) {\r\n const modified = typeof t.modified === 'string' ? new Date(t.modified).getTime() : NaN\r\n if (Number.isNaN(modified) || modified < sinceTime) return false\r\n }\r\n if (options.type !== undefined && options.type.length > 0 && (t.type ?? 'text/vnd.tiddlywiki') !== options.type) return false\r\n if (wantedTags.length > 0) {\r\n const tags = (t.tags ?? []).map((tag) => tag.toLowerCase())\r\n if (!wantedTags.every((w) => tags.includes(w.toLowerCase()))) return false\r\n }\r\n return true\r\n })\r\n return { items: matched.slice(0, limit), total: matched.length }\r\n }\r\n\r\n /**\r\n * List the most recently modified TEXT-BEARING (non-system, non-binary)\r\n * tiddlers, newest first (missing/modified-less tiddlers sort to the tail).\r\n * `since` keeps only tiddlers modified at/after that instant. Binary\r\n * attachments never appear — a book import touching hundreds of images must\r\n * not crowd the \"最近修改\" list.\r\n */\r\n async recent(limit = 15, since?: string): Promise<Tiddler[]> {\r\n const items = await this.list(undefined, true)\r\n const sinceTime = parseSince(since)\r\n const filtered = items.filter((t) => {\r\n if (t.title.startsWith('$:/')) return false\r\n if (sinceTime !== undefined) {\r\n const modified = typeof t.modified === 'string' ? new Date(t.modified).getTime() : NaN\r\n if (Number.isNaN(modified) || modified < sinceTime) return false\r\n }\r\n return true\r\n })\r\n filtered.sort((a, b) => {\r\n const am = typeof a.modified === 'string' ? new Date(a.modified).getTime() : 0\r\n const bm = typeof b.modified === 'string' ? new Date(b.modified).getTime() : 0\r\n return bm - am\r\n })\r\n return filtered.slice(0, Math.max(1, Math.min(limit, 200)))\r\n }\r\n\r\n /**\r\n * Distinct non-system tags with their tiddler counts, most-used first then\r\n * zh-locale. One skinny listing request (no text payloads).\r\n */\r\n async listTags(): Promise<Array<{ tag: string; count: number }>> {\r\n const items = await this.list(undefined, false)\r\n const map = new Map<string, number>()\r\n for (const t of items) {\r\n if (t.title.startsWith('$:/')) continue\r\n for (const tag of t.tags ?? []) {\r\n if (tag.startsWith('$:/')) continue\r\n map.set(tag, (map.get(tag) ?? 0) + 1)\r\n }\r\n }\r\n return [...map.entries()]\r\n .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0], 'zh'))\r\n .map(([tag, count]) => ({ tag, count }))\r\n }\r\n}\r\n\r\n/** Optional search / recent filters. */\r\nexport interface SearchOptions {\r\n /** Every tag in this list must be present (AND semantics). */\r\n tags?: string[]\r\n /** Legacy single exact tag (AND with `tags`). */\r\n tag?: string\r\n /** ISO 8601 instant; keep only tiddlers modified at/after it. */\r\n since?: string\r\n /** Exact tiddler type (default type is \"text/vnd.tiddlywiki\"). */\r\n type?: string\r\n /** Max results (search default 30, capped 200). */\r\n limit?: number\r\n}\r\n\r\n/** Parse a `since` value into an epoch ms, or undefined when absent/invalid. */\r\nfunction parseSince(since: string | undefined): number | undefined {\r\n if (typeof since !== 'string' || since.trim().length === 0) return undefined\r\n const ms = new Date(since.trim()).getTime()\r\n return Number.isNaN(ms) ? undefined : ms\r\n}\r\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 readonly schema: { readonly type: 'json' }\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 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","/**\r\n * The `tiddlywiki_*` agent tools (design doc §11, D8) plus the extension\r\n * point: `registerTiddlywikiTools(ctx, deps)` registers tools list-style, so a\r\n * new tool is just one more `defineTool` in the array — index.ts never changes.\r\n *\r\n * Toolset (v0.5):\r\n * search / get / put / batch_put / rename / delete / recent / list_tags /\r\n * git_sync / git_resolve\r\n *\r\n * RENDER CONTRACT (design doc §4.3): the registry feeds `output.render(args,\r\n * value)` into the loop — the model sees ONLY the rendered text, never the raw\r\n * JSON `value`. Every render must carry the complete facts an agent needs to\r\n * act (titles, tags, snippets, git state); a terse UI summary starves it.\r\n *\r\n * @module dsh-tiddlywiki/host/tools\r\n */\r\nimport { defineTool } from '../sdk.ts'\r\nimport { isBinaryType } from './tw-api.ts'\r\nimport type { TiddlyWebClient, Tiddler } from './tw-api.ts'\r\nimport type { GitFace } from './git.ts'\r\n\r\n/**\r\n * 约定标签:标记「由 Agent 撰写」的笔记。\r\n * 新建(title 不存在)时由 tiddlywiki_put / tiddlywiki_batch_put 自动补打;\r\n * 首页据此把这类笔记单独列在「Agent 区块」并从主标签列表排除。\r\n */\r\nexport const AGENT_WRITTEN_TAG = 'agent-written'\r\n\r\n/**\r\n * 约定标签:标记「Agent 撰写后又经人类编辑」的笔记(双标签分级第二档)。\r\n * 人类在编辑某篇 Agent 笔记时手动补打,首页把这类笔记归入「Agent + 人工」档。\r\n */\r\nexport const HUMAN_EDITED_TAG = 'human-edited'\r\n\r\n/**\r\n * Agent 写入的默认内容类型。agent 正文按约定是 Markdown,而 TW 对无 type 的\r\n * tiddler 按 wikitext(text/vnd.tiddlywiki)解析——`##`/`**` 之类原样显示,显示\r\n * 解析全坏(v0.16.15 起 put/batch_put 自动补该默认;wiki 已启用 tiddlywiki/markdown)。\r\n */\r\nexport const DEFAULT_NOTE_TYPE = 'text/markdown'\r\n\r\n/** Structural tool-registry face (subset of the dsh tools service). */\r\nexport interface ToolsCtx {\r\n tools: { register(tool: unknown): () => void }\r\n}\r\n\r\nexport interface ToolsDeps {\r\n /** Lazy TW client — undefined while the service is not up. */\r\n wiki: () => TiddlyWebClient | undefined\r\n git: GitFace\r\n wikiPath: () => string\r\n noteTag: () => string\r\n /** Debounced auto-commit touch (fires after our writes). */\r\n autoCommit: () => void\r\n /** Restart the TW child (same port). Called after a pull that changed the\r\n * working tree, so the server drops its stale in-memory snapshot and the\r\n * agent sees the pulled content. Optional — absent in headless contexts. */\r\n restartWiki?: () => Promise<void>\r\n}\r\n\r\nfunction snippetOf(text: string, max = 160): string {\r\n const flat = text.replace(/\\s+/g, ' ').trim()\r\n return flat.length <= max ? flat : `${flat.slice(0, max)}…`\r\n}\r\n\r\n/** Strip dsh-tiddlywiki internal fields from a tiddler for the model. */\r\nfunction pickFields(t: Tiddler): Record<string, unknown> {\r\n const out: Record<string, unknown> = {}\r\n for (const [k, v] of Object.entries(t)) {\r\n if (k === 'title' || k === 'text' || k === 'tags') continue\r\n out[k] = v\r\n }\r\n return out\r\n}\r\n\r\n/** A put-ready copy of a tiddler (no created/modified, tags as array). */\r\nfunction cleanTiddler(t: Tiddler): Tiddler {\r\n const out: Tiddler = { title: t.title, text: t.text ?? '', tags: t.tags ?? [] }\r\n if (typeof t.type === 'string') out.type = t.type\r\n for (const [k, v] of Object.entries(t)) {\r\n if (k === 'title' || k === 'text' || k === 'tags' || k === 'type' || k === 'created' || k === 'modified' || k === 'fields') continue\r\n out[k] = v\r\n }\r\n return out\r\n}\r\n\r\n/**\r\n * Rewrite TiddlyWiki references to a title inside wiki text: `[[Title]]`,\r\n * `[[display|Title]]`, `{{Title}}` → the new title. Best-effort link/text\r\n * migration for tiddlywiki_rename; returns the rewritten text + hit count.\r\n */\r\nfunction rewriteRefs(text: string, oldTitle: string, newTitle: string): { text: string; count: number } {\r\n if (oldTitle.length === 0) return { text, count: 0 }\r\n const escaped = oldTitle.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\r\n const re = new RegExp(`(\\\\[\\\\[[^\\\\]|]*\\\\|)${escaped}(\\\\]\\\\])|(\\\\[\\\\[)${escaped}(\\\\]\\\\])|(\\\\{\\\\{)${escaped}(\\\\}\\\\})`, 'g')\r\n let count = 0\r\n const out = text.replace(re, (...args: Array<string | number>) => {\r\n const p = args as string[]\r\n count++\r\n const prefix = p[1] ?? p[3] ?? p[5] ?? ''\r\n const suffix = p[2] ?? p[4] ?? p[6] ?? ''\r\n return `${prefix}${newTitle}${suffix}`\r\n })\r\n return { text: out, count }\r\n}\r\n\r\n/**\r\n * Compute the final tags for a write:\r\n * - NEW tiddler (created by this tool) → auto-append the agent-written tag,\r\n * unless it is a `$:/` system tiddler (config/language/theme internals).\r\n * - Existing tiddler → keep the caller's tags as-is (an agent maintaining a\r\n * human note must NOT silently claim authorship; an already agent-written\r\n * note keeps its tag because the caller provides the full tag list).\r\n */\r\nfunction finalTagsForWrite(title: string, existing: Tiddler | undefined, tags: string[]): string[] {\r\n if (existing !== undefined) return tags\r\n if (title.startsWith('$:/')) return tags\r\n if (tags.includes(AGENT_WRITTEN_TAG)) return tags\r\n return [...tags, AGENT_WRITTEN_TAG]\r\n}\r\n\r\n/**\r\n * Compute the final content type for a write (mutates the tiddler in place):\r\n * - explicit `type` (normally via fields) wins — untouched;\r\n * - `$:/` system tiddlers keep TW's own default (config/plugin internals must\r\n * not be force-marked as markdown);\r\n * - everything else defaults to Markdown (DEFAULT_NOTE_TYPE) — agent notes are\r\n * written in Markdown and TW would otherwise parse them as wikitext.\r\n */\r\nfunction finalTypeForWrite(title: string, tiddler: Tiddler): { defaulted: boolean } {\r\n if (typeof tiddler.type === 'string' && tiddler.type.length > 0) return { defaulted: false }\r\n if (title.startsWith('$:/')) return { defaulted: false }\r\n tiddler.type = DEFAULT_NOTE_TYPE\r\n return { defaulted: true }\r\n}\r\n\r\nexport function registerTiddlywikiTools(ctx: ToolsCtx, deps: ToolsDeps): Array<() => void> {\r\n const disposers: Array<() => void> = []\r\n const register = (tool: unknown): void => { disposers.push(ctx.tools.register(tool)) }\r\n\r\n /** Every read/write tool needs a live TW client — shared guard for the 8\r\n * wiki-facing tools (git tools operate on the repo path instead). */\r\n const requireWiki = (): TiddlyWebClient => {\r\n const wiki = deps.wiki()\r\n if (wiki === undefined) throw new Error('TiddlyWiki 服务未运行(tiddlywiki_status 可查)')\r\n return wiki\r\n }\r\n\r\n // ── tiddlywiki_search ────────────────────────────────────────────────────\r\n register(defineTool({\r\n name: 'tiddlywiki_search',\r\n description: '检索 TiddlyWiki 持久知识库:按关键词(可选 tags 数组 / since 修改时间 / type / limit)搜索非系统 tiddler,返回标题、标签、修改时间与摘要片段。二进制 tiddler(图片等附件,正文为 base64)不参与检索。',\r\n parameters: {\r\n query: { type: 'string', description: '搜索关键词(大小写不敏感,子串匹配)', required: true },\r\n tags: { type: 'array', items: { type: 'string' }, description: '可选:要求同时包含的标签(AND)' },\r\n tag: { type: 'string', description: '可选:单个精确标签(与 tags 同为 AND)' },\r\n since: { type: 'string', description: '可选:ISO 时间(如 2026-09-01 或 2026-09-01T00:00:00Z),只返回修改时间不早于它的 tiddler' },\r\n type: { type: 'string', description: '可选:精确 tiddler 类型(默认 text/vnd.tiddlywiki)' },\r\n limit: { type: 'integer', description: '可选:返回条数上限(默认 30,最大 200)' },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args, value: SearchResult) => {\r\n const filters: string[] = []\r\n if (value.tags.length > 0) filters.push(`tags=${value.tags.join(',')}`)\r\n if (value.since !== null) filters.push(`since=${value.since}`)\r\n if (value.type !== null) filters.push(`type=${value.type}`)\r\n const head = `TiddlyWiki 搜索「${value.query}」${filters.length > 0 ? ` (${filters.join(' · ')})` : ''}:命中 ${value.total} 条。`\r\n const lines = [head]\r\n if (value.results.length === 0) lines.push('没有匹配的 tiddler。')\r\n for (const r of value.results) {\r\n const tags = r.tags.length > 0 ? ` [${r.tags.join(', ')}]` : ''\r\n const modified = r.modified !== null ? ` (${r.modified})` : ''\r\n lines.push(`- ${r.title}${tags}${modified}`)\r\n if (r.snippet.length > 0) lines.push(` ${r.snippet}`)\r\n }\r\n if (value.total > value.results.length) lines.push(`(另有 ${value.total - value.results.length} 条未展开,可用 tiddlywiki_get 读取具体标题,或提高 limit)`)\r\n return [{ type: 'text', text: lines.join('\\n') }]\r\n },\r\n },\r\n execute: async (args: { query: string; tags?: string[]; tag?: string; since?: string; type?: string; limit?: number }): Promise<SearchResult> => {\r\n const wiki = requireWiki()\r\n const { items, total } = await wiki.search(args.query, {\r\n tags: args.tags,\r\n tag: args.tag,\r\n since: args.since,\r\n type: args.type,\r\n limit: args.limit,\r\n })\r\n return {\r\n query: args.query,\r\n tags: args.tags ?? [],\r\n since: args.since ?? null,\r\n type: args.type ?? null,\r\n total,\r\n results: items.map((t) => ({ title: t.title, tags: t.tags ?? [], modified: typeof t.modified === 'string' ? t.modified : null, snippet: snippetOf(t.text ?? '') })),\r\n }\r\n },\r\n }))\r\n\r\n // ── tiddlywiki_recent ────────────────────────────────────────────────────\r\n register(defineTool({\r\n name: 'tiddlywiki_recent',\r\n description: '查看 TiddlyWiki 知识库最近修改的笔记(按修改时间倒序,排除系统 tiddler 与图片等二进制附件),返回标题、标签、修改时间与摘要。适合开工时快速了解近期动态。',\r\n parameters: {\r\n limit: { type: 'integer', description: '可选:返回条数(默认 15,最大 200)' },\r\n since: { type: 'string', description: '可选:只返回修改时间不早于该 ISO 时间的 tiddler' },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args, value: RecentResult) => {\r\n const lines = [`TiddlyWiki 最近修改(最近 ${value.results.length} 条${value.since !== null ? `,since=${value.since}` : ''}):`]\r\n if (value.results.length === 0) lines.push('暂无笔记。')\r\n for (const r of value.results) {\r\n const tags = r.tags.length > 0 ? ` [${r.tags.join(', ')}]` : ''\r\n lines.push(`- ${r.title}${tags} (${r.modified ?? '?'})`)\r\n if (r.snippet.length > 0) lines.push(` ${r.snippet}`)\r\n }\r\n return [{ type: 'text', text: lines.join('\\n') }]\r\n },\r\n },\r\n execute: async (args: { limit?: number; since?: string }): Promise<RecentResult> => {\r\n const wiki = requireWiki()\r\n const items = await wiki.recent(args.limit ?? 15, args.since)\r\n return {\r\n since: args.since ?? null,\r\n results: items.map((t) => ({ title: t.title, tags: t.tags ?? [], modified: typeof t.modified === 'string' ? t.modified : null, snippet: snippetOf(t.text ?? '') })),\r\n }\r\n },\r\n }))\r\n\r\n // ── tiddlywiki_list_tags ─────────────────────────────────────────────────\r\n register(defineTool({\r\n name: 'tiddlywiki_list_tags',\r\n description: '列出 TiddlyWiki 知识库现有的非系统标签及各自计数(按使用次数降序),方便决定给笔记打什么 tag。',\r\n parameters: {},\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args, value: TagListResult) => {\r\n if (value.tags.length === 0) return [{ type: 'text', text: '知识库暂无标签。' }]\r\n const lines = [`现有标签(${value.tags.length} 个,按使用次数降序):`]\r\n for (const t of value.tags) lines.push(`- ${t.tag} × ${t.count}`)\r\n return [{ type: 'text', text: lines.join('\\n') }]\r\n },\r\n },\r\n execute: async (): Promise<TagListResult> => {\r\n const wiki = requireWiki()\r\n const tags = await wiki.listTags()\r\n return { count: tags.length, tags }\r\n },\r\n }))\r\n\r\n // ── tiddlywiki_get ───────────────────────────────────────────────────────\r\n register(defineTool({\r\n name: 'tiddlywiki_get',\r\n description: '读取一个 TiddlyWiki tiddler 的完整内容(标题、全文、标签、自定义字段)。二进制 tiddler(图片等附件)只返回元数据,不返回 base64 正文。',\r\n parameters: {\r\n title: { type: 'string', description: 'tiddler 标题(精确匹配)', required: true },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args, value: GetResult) => {\r\n if (value.notFound) return [{ type: 'text', text: `tiddler「${value.title}」不存在。可用 tiddlywiki_search 检索,或用 tiddlywiki_put 新建。` }]\r\n if (value.binary === true) {\r\n const lines = [`tiddler「${value.title}」是二进制附件(type=${value.binaryType ?? '?'},base64 正文约 ${value.binaryChars ?? 0} 字符),不返回正文。`]\r\n if (value.tags.length > 0) lines.push(`标签: ${value.tags.join(', ')}`)\r\n if (value.modified !== null) lines.push(`修改: ${value.modified}`)\r\n const fields = Object.entries(value.fields)\r\n if (fields.length > 0) lines.push(`字段: ${fields.map(([k, v]) => `${k}=${String(v)}`).join(', ')}`)\r\n lines.push(`如需查看附件本身,可打开 [${value.title}](/dsh-tiddlywiki/tw/#${encodeURIComponent(value.title)})。`)\r\n return [{ type: 'text', text: lines.join('\\n') }]\r\n }\r\n const lines = [`tiddler「${value.title}」`]\r\n if (value.tags.length > 0) lines.push(`标签: ${value.tags.join(', ')}`)\r\n if (value.modified !== null) lines.push(`修改: ${value.modified}`)\r\n const fields = Object.entries(value.fields)\r\n if (fields.length > 0) lines.push(`字段: ${fields.map(([k, v]) => `${k}=${String(v)}`).join(', ')}`)\r\n lines.push('--- 全文 ---')\r\n lines.push(value.text.length > 0 ? value.text : '(空)')\r\n return [{ type: 'text', text: lines.join('\\n') }]\r\n },\r\n },\r\n execute: async (args: { title: string }): Promise<GetResult> => {\r\n const wiki = requireWiki()\r\n const t = await wiki.get(args.title)\r\n if (t === undefined) return { notFound: true, title: args.title, text: '', tags: [], fields: {}, modified: null }\r\n const binary = isBinaryType(typeof t.type === 'string' ? t.type : undefined)\r\n const result: GetResult = {\r\n notFound: false,\r\n title: t.title,\r\n text: binary ? '' : (t.text ?? ''),\r\n tags: t.tags ?? [],\r\n fields: pickFields(t),\r\n modified: typeof t.modified === 'string' ? t.modified : null,\r\n }\r\n if (binary) {\r\n result.binary = true\r\n result.binaryType = typeof t.type === 'string' ? t.type : undefined\r\n result.binaryChars = (t.text ?? '').length\r\n }\r\n return result\r\n },\r\n }))\r\n\r\n // ── tiddlywiki_put ───────────────────────────────────────────────────────\r\n register(defineTool({\r\n name: 'tiddlywiki_put',\r\n description: '写入(新建或覆盖)一个 TiddlyWiki tiddler。同名覆盖;tags 为标签数组,fields 为附加自定义字段(json 对象,会写入 tiddler 字段)。写入后触发自动 commit。新建(title 不存在)时自动补打 agent-written 标签标记「由 Agent 撰写」,无需手动添加。未指定内容类型时自动默认 text/markdown($:/ 系统条目除外);要写原生 wikitext 需显式在 fields 传 {\"type\":\"text/vnd.tiddlywiki\"}。⚠️ fields.type 是 TW 的内容类型保留字段,不要把业务分类值(如 \"meeting\")写进去——业务分类请放 tags。',\r\n parameters: {\r\n title: { type: 'string', description: 'tiddler 标题(精确匹配,覆盖同名)', required: true },\r\n text: { type: 'string', description: 'tiddler 全文(默认按 Markdown 解析)', required: true },\r\n tags: { type: 'array', items: { type: 'string' }, description: '标签数组(可选)' },\r\n fields: { type: 'json', description: '附加自定义字段,如 {\"date\":\"2026-09-02\"}(可选)。注意:fields.type 是 TW 内容类型(保留字段,默认已自动补 text/markdown),不要写业务分类值' },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args, value: PutResult) => {\r\n const lines = [`已写入 tiddler「${value.title}」`]\r\n if (value.tags.length > 0) lines.push(`标签: ${value.tags.join(', ')}`)\r\n if (value.type !== null) lines.push(`类型: ${value.type}${value.typeDefaulted === true ? '(未指定,已默认 markdown)' : ''}`)\r\n if (value.fields !== null) {\r\n const entries = Object.entries(value.fields)\r\n if (entries.length > 0) lines.push(`字段: ${entries.map(([k, v]) => `${k}=${String(v)}`).join(', ')}`)\r\n }\r\n return [{ type: 'text', text: lines.join('\\n') }]\r\n },\r\n },\r\n execute: async (args: { title: string; text: string; tags?: string[]; fields?: Record<string, unknown> }): Promise<PutResult> => {\r\n const wiki = requireWiki()\r\n const existing = await wiki.get(args.title).catch(() => undefined)\r\n const tags = finalTagsForWrite(args.title, existing, Array.isArray(args.tags) ? args.tags.filter((t) => typeof t === 'string' && t.trim().length > 0) : [])\r\n const tiddler: Tiddler = { title: args.title, text: args.text }\r\n if (tags.length > 0) tiddler.tags = tags\r\n if (args.fields !== undefined && typeof args.fields === 'object' && args.fields !== null) Object.assign(tiddler, args.fields)\r\n const { defaulted } = finalTypeForWrite(args.title, tiddler)\r\n await wiki.put(tiddler)\r\n deps.autoCommit()\r\n return { ok: true, title: args.title, tags, type: typeof tiddler.type === 'string' ? tiddler.type : null, ...(defaulted ? { typeDefaulted: true } : {}), fields: args.fields ?? null }\r\n },\r\n }))\r\n\r\n // ── tiddlywiki_batch_put ─────────────────────────────────────────────────\r\n register(defineTool({\r\n name: 'tiddlywiki_batch_put',\r\n description: '批量写入/覆盖多个 TiddlyWiki tiddler(一次工具调用)。overwrite=false 时跳过已存在的标题;返回逐条结果。写入后触发自动 commit。新建(title 不存在)的条目会自动补打 agent-written 标签,无需手动添加。未指定内容类型(fields.type)的条目自动默认 text/markdown($:/ 系统条目除外)。',\r\n parameters: {\r\n items: {\r\n type: 'array',\r\n items: {\r\n type: 'object',\r\n additionalProperties: true,\r\n description: '要写入的 tiddler 数组',\r\n properties: {\r\n title: { type: 'string', description: '标题(精确匹配,覆盖同名)', required: true },\r\n text: { type: 'string', description: '全文(默认按 Markdown 解析)', required: true },\r\n tags: { type: 'array', items: { type: 'string' }, description: '标签数组(可选)' },\r\n fields: { type: 'json', description: '附加自定义字段(可选)。注意:fields.type 是 TW 内容类型(保留字段,默认已自动补 text/markdown),不要写业务分类值' },\r\n },\r\n },\r\n },\r\n overwrite: { type: 'boolean', description: '可选:true=覆盖同名(默认),false=跳过已存在的标题' },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args, value: BatchResult) => {\r\n const lines = [`批量写入完成:成功 ${value.written},跳过 ${value.skipped},共 ${value.items.length} 条。`]\r\n for (const r of value.items) {\r\n lines.push(`- ${r.title}:${r.written ? '已写入' : '已跳过(存在)'}`)\r\n }\r\n return [{ type: 'text', text: lines.join('\\n') }]\r\n },\r\n },\r\n execute: async (args: { items: Array<{ title: string; text: string; tags?: string[]; fields?: Record<string, unknown> }>; overwrite?: boolean }): Promise<BatchResult> => {\r\n const wiki = requireWiki()\r\n const list = Array.isArray(args.items) ? args.items : []\r\n if (list.length === 0) return { ok: true, written: 0, skipped: 0, items: [] }\r\n const overwrite = args.overwrite !== false\r\n const results: Array<{ title: string; written: boolean; skipped: boolean }> = []\r\n let written = 0\r\n let skipped = 0\r\n for (const item of list) {\r\n if (typeof item.title !== 'string' || item.title.length === 0) throw new Error('batch_put: 每条 items 都需要非空 title')\r\n if (typeof item.text !== 'string') throw new Error(`batch_put: items「${item.title}」缺少 text`)\r\n const existing = await wiki.get(item.title).catch(() => undefined)\r\n if (!overwrite && existing !== undefined) {\r\n skipped++\r\n results.push({ title: item.title, written: false, skipped: true })\r\n continue\r\n }\r\n const tags = finalTagsForWrite(item.title, existing, Array.isArray(item.tags) ? item.tags.filter((t) => typeof t === 'string' && t.trim().length > 0) : [])\r\n const tiddler: Tiddler = { title: item.title, text: item.text }\r\n if (tags.length > 0) tiddler.tags = tags\r\n if (item.fields !== undefined && typeof item.fields === 'object' && item.fields !== null) Object.assign(tiddler, item.fields)\r\n finalTypeForWrite(item.title, tiddler)\r\n await wiki.put(tiddler)\r\n written++\r\n results.push({ title: item.title, written: true, skipped: false })\r\n }\r\n deps.autoCommit()\r\n return { ok: true, written, skipped, items: results }\r\n },\r\n }))\r\n\r\n // ── tiddlywiki_rename ────────────────────────────────────────────────────\r\n register(defineTool({\r\n name: 'tiddlywiki_rename',\r\n description: '重命名一个 TiddlyWiki tiddler:把旧标题的内容复制到新标题、删除旧标题,并可选地更新其他 tiddler 里的 [[旧标题]] / {{旧标题}} 引用(最佳努力)。',\r\n parameters: {\r\n oldTitle: { type: 'string', description: '当前标题', required: true },\r\n newTitle: { type: 'string', description: '新标题', required: true },\r\n updateRefs: { type: 'boolean', description: '可选:是否同步更新其他 tiddler 里的引用(默认 true)' },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args, value: RenameResult) => {\r\n const lines = [`已重命名「${value.from}」→「${value.to}」`]\r\n lines.push(`更新了 ${value.refsUpdated} 处引用(${value.refsTiddlers} 个 tiddler)`)\r\n if (value.warning !== undefined) lines.push(`注意: ${value.warning}`)\r\n return [{ type: 'text', text: lines.join('\\n') }]\r\n },\r\n },\r\n execute: async (args: { oldTitle: string; newTitle: string; updateRefs?: boolean }): Promise<RenameResult> => {\r\n const wiki = requireWiki()\r\n const { oldTitle, newTitle } = args\r\n if (oldTitle === newTitle) return { ok: true, from: oldTitle, to: newTitle, refsUpdated: 0, refsTiddlers: 0 }\r\n const existing = await wiki.get(oldTitle)\r\n if (existing === undefined) throw new Error(`tiddler「${oldTitle}」不存在`)\r\n const target = await wiki.get(newTitle)\r\n if (target !== undefined) throw new Error(`新标题「${newTitle}」已存在(可先用 tiddlywiki_delete 删除)`)\r\n let refsUpdated = 0\r\n let refsTiddlers = 0\r\n let warning: string | undefined\r\n if (args.updateRefs !== false) {\r\n const all = await wiki.list(undefined, true)\r\n for (const t of all) {\r\n if (t.title === oldTitle || t.title === newTitle) continue\r\n if (t.title.startsWith('$:/')) continue\r\n const text = t.text ?? ''\r\n if (text.length === 0) continue\r\n const rewritten = rewriteRefs(text, oldTitle, newTitle)\r\n if (rewritten.count > 0) {\r\n await wiki.put({ ...cleanTiddler(t), text: rewritten.text })\r\n refsUpdated += rewritten.count\r\n refsTiddlers++\r\n }\r\n }\r\n }\r\n await wiki.put({ ...cleanTiddler(existing), title: newTitle })\r\n await wiki.delete(oldTitle)\r\n if (refsTiddlers === 0) {\r\n warning = '未找到任何其他 tiddler 引用旧标题;如确实需要,可手动补充链接。'\r\n }\r\n deps.autoCommit()\r\n return { ok: true, from: oldTitle, to: newTitle, refsUpdated, refsTiddlers, ...(warning !== undefined ? { warning } : {}) }\r\n },\r\n }))\r\n\r\n // ── tiddlywiki_delete ────────────────────────────────────────────────────\r\n register(defineTool({\r\n name: 'tiddlywiki_delete',\r\n description: '删除一个 TiddlyWiki tiddler(不存在时是幂等空操作)。删除后触发自动 commit。',\r\n parameters: {\r\n title: { type: 'string', description: 'tiddler 标题(精确匹配)', required: true },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args, value: DeleteResult) => [{ type: 'text', text: `已删除 tiddler「${value.title}」。` }],\r\n },\r\n execute: async (args: { title: string }): Promise<DeleteResult> => {\r\n const wiki = requireWiki()\r\n await wiki.delete(args.title)\r\n deps.autoCommit()\r\n return { ok: true, title: args.title }\r\n },\r\n }))\r\n\r\n // ── tiddlywiki_git_sync ──────────────────────────────────────────────────\r\n register(defineTool({\r\n name: 'tiddlywiki_git_sync',\r\n description: '对 TiddlyWiki 知识库的 git 仓库做同步:pull(拉取远端并 rebase 本地,冲突则 abort 并报文件)、push(推送本地提交到远端)、sync(pull → commit 本地改动 → push)。未配置 git.remote 时 push 会失败并提示。',\r\n parameters: {\r\n action: { type: 'string', enum: ['pull', 'push', 'sync'], description: '要执行的 git 操作', required: true },\r\n message: { type: 'string', description: 'commit 信息(可选,仅 sync 的本地 commit 使用)' },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args, value: SyncResult) => renderSync(value),\r\n },\r\n execute: async (args: { action: 'pull' | 'push' | 'sync'; message?: string }): Promise<SyncResult> => {\r\n const dir = deps.wikiPath()\r\n /** Restart TW after a pull that changed the tree (stale snapshot drop). */\r\n const restartIfChanged = async (pulled: { changed?: boolean }): Promise<{ restarted?: boolean; restartError?: string }> => {\r\n if (pulled.changed !== true || deps.restartWiki === undefined) return {}\r\n try {\r\n await deps.restartWiki()\r\n return { restarted: true }\r\n } catch (err) {\r\n return { restartError: err instanceof Error ? err.message : String(err) }\r\n }\r\n }\r\n switch (args.action) {\r\n case 'pull': {\r\n const r = await deps.git.pull(dir)\r\n const restart = await restartIfChanged(r)\r\n return { action: args.action, ok: r.ok, message: r.message, ...(r.conflictFiles !== undefined ? { conflictFiles: r.conflictFiles } : {}), ...(r.changed === true ? { changed: true } : {}), ...restart }\r\n }\r\n case 'push': {\r\n const r = await deps.git.push(dir)\r\n return { action: args.action, ok: r.ok, message: r.message }\r\n }\r\n case 'sync': {\r\n const pulled = await deps.git.pull(dir)\r\n if (!pulled.ok) return { action: args.action, ok: false, message: pulled.message, ...(pulled.conflictFiles !== undefined ? { conflictFiles: pulled.conflictFiles } : {}) }\r\n const restart = await restartIfChanged(pulled)\r\n const committed = await deps.git.commit(dir, args.message ?? `sync ${new Date().toISOString()}`)\r\n const pushed = await deps.git.push(dir)\r\n const status = await deps.git.status(dir)\r\n return {\r\n action: args.action,\r\n ok: pushed.ok,\r\n message: pushed.ok ? '同步完成' : pushed.message,\r\n pull: 'ok',\r\n ...(pulled.changed === true ? { changed: true } : {}),\r\n ...restart,\r\n commit: committed.message,\r\n push: pushed.message,\r\n status,\r\n }\r\n }\r\n }\r\n },\r\n }))\r\n\r\n // ── tiddlywiki_git_resolve ───────────────────────────────────────────────\r\n register(defineTool({\r\n name: 'tiddlywiki_git_resolve',\r\n description: '在 tiddlywiki_git_sync action=pull 冲突(已 abort)后,按 tiddler 二选一解决:keep-local 保留本地版本;keep-remote 用 git fetch 拉取远端并检出远端版本(需已配置 git.remote);list 仅报告当前状态。解决后建议重新 pull/sync 整合其余改动。',\r\n parameters: {\r\n strategy: { type: 'string', enum: ['keep-local', 'keep-remote', 'list'], description: 'keep-local=保留本地;keep-remote=改用远端版本;list=仅报告当前 git 状态', required: true },\r\n files: { type: 'array', items: { type: 'string' }, description: '冲突文件名数组(来自 pull 返回的 conflictFiles;list 时忽略)' },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args, value: ResolveResult) => {\r\n const lines = [`git resolve ${value.action}: ${value.ok ? '成功' : '失败'}`]\r\n lines.push(` ${value.message}`)\r\n if (value.files !== undefined && value.files.length > 0) lines.push(`涉及文件: ${value.files.join(', ')}`)\r\n if (value.commit !== undefined) lines.push(`本地 commit: ${value.commit}`)\r\n if (value.status !== undefined) lines.push(`状态: ${gitStatusBits(value.status)}`)\r\n return [{ type: 'text', text: lines.join('\\n') }]\r\n },\r\n },\r\n execute: async (args: { strategy: 'keep-local' | 'keep-remote' | 'list'; files?: string[] }): Promise<ResolveResult> => {\r\n const dir = deps.wikiPath()\r\n if (args.strategy === 'list') {\r\n const status = await deps.git.status(dir)\r\n return { ok: true, action: 'list', message: '当前仓库状态(pull 冲突已 abort,工作区即本地版本,不会残留未合并状态)', status }\r\n }\r\n const files = (args.files ?? []).filter((f) => typeof f === 'string' && f.length > 0)\r\n if (files.length === 0) {\r\n return { ok: false, action: args.strategy, message: '请提供 conflictFiles(来自 pull 返回)', hint: '可先执行 tiddlywiki_git_sync action=pull 查看冲突文件' }\r\n }\r\n if (args.strategy === 'keep-local') {\r\n const status = await deps.git.status(dir)\r\n return {\r\n ok: true,\r\n action: 'keep-local',\r\n message: `已保留本地版本(${files.length} 个文件;abort 后本地即为工作区内容)。建议重新 tiddlywiki_git_sync action=sync 整合远端其余改动。`,\r\n files,\r\n status,\r\n }\r\n }\r\n const fetched = await deps.git.fetch(dir)\r\n if (!fetched.ok) {\r\n return { ok: false, action: 'keep-remote', message: `fetch 失败(可能未配置 git.remote):${fetched.message}` }\r\n }\r\n const checked = await deps.git.checkoutFetchHead(dir, files)\r\n if (!checked.ok) {\r\n return { ok: false, action: 'keep-remote', message: `从远端检出失败:${checked.message}` }\r\n }\r\n const committed = await deps.git.commit(dir, `resolve conflict (keep remote) ${new Date().toISOString()}`)\r\n deps.autoCommit()\r\n const status = await deps.git.status(dir)\r\n return {\r\n ok: true,\r\n action: 'keep-remote',\r\n message: `已把 ${files.length} 个冲突文件改为远端版本并提交。建议继续 tiddlywiki_git_sync action=sync 完成整合与推送。`,\r\n files,\r\n commit: committed.message,\r\n status,\r\n }\r\n },\r\n }))\r\n\r\n return disposers\r\n}\r\n\r\n// ── tool result shapes + renders ───────────────────────────────────────────\r\n\r\ninterface SearchHit { title: string; tags: string[]; modified: string | null; snippet: string }\r\ninterface SearchResult { query: string; tags: string[]; since: string | null; type: string | null; total: number; results: SearchHit[] }\r\ninterface RecentResult { since: string | null; results: SearchHit[] }\r\ninterface TagListResult { count: number; tags: Array<{ tag: string; count: number }> }\r\ninterface GetResult {\r\n notFound: boolean\r\n title: string\r\n text: string\r\n tags: string[]\r\n fields: Record<string, unknown>\r\n modified: string | null\r\n /** Binary attachment (image/audio/…): `text` is withheld, these carry its type+size. */\r\n binary?: boolean\r\n binaryType?: string\r\n binaryChars?: number\r\n}\r\ninterface PutResult { ok: boolean; title: string; tags: string[]; type: string | null; typeDefaulted?: boolean; fields: Record<string, unknown> | null }\r\ninterface BatchResult { ok: boolean; written: number; skipped: number; items: Array<{ title: string; written: boolean; skipped: boolean }> }\r\ninterface RenameResult { ok: boolean; from: string; to: string; refsUpdated: number; refsTiddlers: number; warning?: string }\r\ninterface DeleteResult { ok: boolean; title: string }\r\ninterface SyncResult {\r\n action: string\r\n ok: boolean\r\n message: string\r\n conflictFiles?: string[]\r\n pull?: string\r\n commit?: string\r\n push?: string\r\n changed?: boolean\r\n restarted?: boolean\r\n restartError?: string\r\n status?: GitStatusView\r\n}\r\ninterface ResolveResult {\r\n ok: boolean\r\n action: string\r\n message: string\r\n files?: string[]\r\n commit?: string\r\n hint?: string\r\n status?: GitStatusView\r\n}\r\n\r\n/** Shape of deps.git.status() as surfaced to the model (shared by renders). */\r\ninterface GitStatusView {\r\n branch: string\r\n dirty: boolean\r\n dirtyFiles: string[]\r\n remote: string\r\n lastCommit?: string\r\n ahead?: number\r\n behind?: number\r\n}\r\n\r\n/** One-line 状态 summary: 分支 … 领先 … 落后 … 工作区未提交 … 最近提交. */\r\nfunction gitStatusBits(s: GitStatusView): string {\r\n const bits = [`分支 ${s.branch}`]\r\n if (s.ahead !== undefined) bits.push(`领先 ${s.ahead}`)\r\n if (s.behind !== undefined) bits.push(`落后 ${s.behind}`)\r\n if (s.dirty) bits.push(`工作区有 ${s.dirtyFiles.length} 个未提交改动`)\r\n if (s.lastCommit !== undefined) bits.push(`最近提交 ${s.lastCommit}`)\r\n return bits.join(' · ')\r\n}\r\n\r\nfunction renderSync(value: SyncResult): Array<{ type: 'text'; text: string }> {\r\n const lines = [`git ${value.action}: ${value.ok ? '成功' : '失败'}`]\r\n lines.push(` ${value.message}`)\r\n if (value.conflictFiles !== undefined && value.conflictFiles.length > 0) {\r\n lines.push(`冲突文件(rebase 已 abort,勿自动覆盖):`)\r\n for (const f of value.conflictFiles) lines.push(` - ${f}`)\r\n lines.push('处理方式:用 tiddlywiki_git_resolve files=[以上文件] strategy=keep-local|keep-remote 按 tiddler 二选一解决,再重新 sync;也可以直接让用户处理。')\r\n }\r\n if (value.commit !== undefined) lines.push(`本地 commit: ${value.commit}`)\r\n if (value.push !== undefined) lines.push(`远端 push: ${value.push}`)\r\n if (value.changed === true) {\r\n lines.push(value.restarted === true\r\n ? '本次 pull 拉取了新内容,TW 服务已自动重启(同端口),读取/搜索均为最新快照。'\r\n : '本次 pull 拉取了新内容,但 TW 服务未能自动重启(如需最新快照,请手动重启 TW)。')\r\n }\r\n if (value.restartError !== undefined) lines.push(`TW 重启失败: ${value.restartError}`)\r\n if (value.status !== undefined) {\r\n lines.push(`状态: ${gitStatusBits(value.status)}`)\r\n const s = value.status\r\n if (s.dirty && s.dirtyFiles.length > 0) lines.push(` 未提交: ${s.dirtyFiles.join(', ')}`)\r\n }\r\n return [{ type: 'text', text: lines.join('\\n') }]\r\n}\r\n","/**\r\n * dsh-tiddlywiki — host half.\r\n *\r\n * TiddlyWiki 5 as the DSH persistent knowledge base. Wiring:\r\n * - WikiServer spawns/kills/self-heals the TW 5 child process (loopback, auto\r\n * port) and scaffolds the wiki folder on first run;\r\n * - the git face bootstraps the wiki folder as a repository and wires the\r\n * debounced auto-committer;\r\n * - `tiddlywiki_*` agent tools + a system-prompt section;\r\n * - /dsh-tiddlywiki routes when a webServer is present.\r\n *\r\n * Export shape follows dsh-taskboard: a function/namespace plugin —\r\n * `name` / `inject` / `apply`, NO default export. Config arrives as the\r\n * second apply() argument (Cordis `runtime.callback(ctx, config)`).\r\n *\r\n * Extra exports (WikiServer / TiddlyWebClient / GitFace / ...) exist for the\r\n * headless selftest and future reuse; the loader only reads name/inject/apply.\r\n *\r\n * @module dsh-tiddlywiki\r\n */\r\nimport { existsSync, watch, type FSWatcher } from 'node:fs'\r\nimport { writeFile } from 'node:fs/promises'\r\nimport { join } from 'node:path'\r\nimport { AutoCommitter, GitFace } from './host/git.ts'\r\nimport { registerRoutes, type AgentPresetsFace, type PermissionPresetsFace, type SessionControllerFace, type SessionPersistenceFace, type SessionsFace, type SessionQueryFace, type WebServerFace, type WorkspaceRegistryFace } from './host/routes.ts'\r\nimport { ConfigStore, deepMerge, DARK_PALETTE_DEFAULT, TW_WEB_HOST_TIDDLER, TW_WEB_HOST_DEFAULT, type PluginConfigShape } from './host/config.ts'\r\nimport { registerAdminRoutes, ensureLanguage, resolveTwRoot, type AdminDeps } from './host/admin.ts'\r\nimport { runAllSeeds, checkAllSeeds, runSeedById, removeSeedById, SEED_DEFS, type SeedStatus, type SeedRunResult } from './host/seeds.ts'\r\nimport { TiddlyWebClient, isBinaryType, TEXT_LIST_FILTER } from './host/tw-api.ts'\r\nimport { registerTiddlywikiTools, type ToolsDeps } from './host/tools.ts'\r\nimport { PATH_PREFIX, TW_PROXY_PATH, TW_PROXY_PREFIX, WikiServer, type WikiServerOptions } from './host/wiki.ts'\r\nimport { dshHomePath, defineTool } from './sdk.ts'\r\n\r\n/** Cordis plugin name (also the client loader id / profile row id). */\r\nexport const name = 'dsh-tiddlywiki'\r\n\r\n/** Required host services (tool registry + prompt assembly). */\r\nexport const inject = ['tools', 'systemPrompt']\r\n\r\n/** Re-exports for the headless selftest and future consumers. */\r\nexport { AutoCommitter, GitFace, PATH_PREFIX, TW_PROXY_PATH, TW_PROXY_PREFIX, TiddlyWebClient, isBinaryType, TEXT_LIST_FILTER, WikiServer, dshHomePath, defineTool }\r\nexport { ConfigStore, deepMerge } from './host/config.ts'\r\nexport { openInTwEditor, registerRoutes } from './host/routes.ts'\r\nexport { writeSessionSummary, SESSION_SUMMARY_PREFIX } from './host/routes.ts'\r\nexport type { SessionQueryFace, SessionSummaryResult } from './host/routes.ts'\r\nexport { registerAdminRoutes, resolveTwRoot, readWikiInfo, writeWikiInfo, bundledCatalog, ensureLanguage, normalizeThemes } from './host/admin.ts'\r\nexport { seedDocNote, DOC_NOTE_TITLE, DOC_NOTE_TAG, DOC_NOTE_TEXT } from './host/seed-notes.ts'\r\nexport { seedStarterDocs, STARTER_DOCS_ITEMS, STARTER_DOCS_MARKER_TITLE, DSH_DOCS_TAG } from './host/seed-starter-docs.ts'\r\nexport { seedSendToAgent, SEND_TO_AGENT_PLUGIN_TITLE, SEND_TO_AGENT_MARKER_TITLE, SEND_TO_AGENT_BUNDLE_TEXT } from './host/seed-send-to-agent.ts'\r\nexport { seedRenderRoute, RENDER_PLUGIN_TITLE, RENDER_MARKER_TITLE, RENDER_BUNDLE_TEXT } from './host/seed-render.ts'\r\nexport { seedHomeIndex, HOME_INDEX_ITEMS, HOME_INDEX_MARKER_TITLE, HOME_DEFAULT_TIDDLERS } from './host/seed-home.ts'\r\nexport { seedAllArticles, ALL_ARTICLES_TITLE, ALL_ARTICLES_MARKER_TITLE, ALL_ARTICLES_TEXT } from './host/seed-all-articles.ts'\r\nexport { seedUiStyles, UI_STYLE_ITEMS, UI_STYLES_MARKER_TITLE } from './host/seed-ui-styles.ts'\r\nexport { seedMenubarTheme, MENUBAR_THEME_TIDDLER, MENUBAR_THEME_MARKER_TITLE, MENUBAR_THEME_TEXT } from './host/seed-menubar-theme.ts'\r\nexport { runAllSeeds, checkAllSeeds, runSeedById, removeSeedById, SEED_DEFS, type SeedStatus, type SeedRunResult } from './host/seeds.ts'\r\nexport { registerTiddlywikiTools } from './host/tools.ts'\r\nexport type { PluginConfigShape } from './host/config.ts'\r\nexport type { GitStatusView } from './host/git.ts'\r\nexport type { Tiddler } from './host/tw-api.ts'\r\nexport type { WikiServerOptions, WikiStatusView } from './host/wiki.ts'\r\n\r\n/** Plugin config (design doc §13). Defaults are applied in apply().\r\n * Mirrors PluginConfigShape (src/host/config.ts) — the cordis `config:` block;\r\n * keep the two shapes in lockstep when adding a config field. */\r\nexport interface TiddlywikiConfig {\r\n wikiRoot?: string\r\n wiki?: string\r\n port?: number\r\n git?: { autoCommit?: boolean; debounceMs?: number; remote?: string; branch?: string }\r\n note?: { tag?: string }\r\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 } }\r\n /** 启动时自动启用的 TW 语言代码(如 \"zh-Hans\"),也受配置 tiddler 覆盖。 */\r\n uiLanguage?: string\r\n auth?: { username?: string; password?: string }\r\n}\r\n\r\n/** Structural host context (subset of the dsh host + cordis surfaces). */\r\nexport interface HostCtx {\r\n tools: { register(tool: unknown): () => void }\r\n systemPrompt: { section(opts: { name: string; order: number; text: string }): () => void }\r\n inject<T = unknown>(names: string | string[], callback: (ctx: HostCtx) => T, config?: unknown): unknown\r\n effect(fn: () => unknown, label?: string): void\r\n get(name: string): unknown\r\n [key: string]: unknown\r\n}\r\n\r\n/** Resolved plugin config (defaults merged with the `config:` block). */\r\ninterface ResolvedConfig {\r\n wikiRoot: string\r\n wiki: string\r\n port: number\r\n git: { autoCommit: boolean; debounceMs: number; remote: string; branch: string }\r\n note: { tag: string }\r\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 } }\r\n uiLanguage: string\r\n auth: { username?: string; password?: string }\r\n}\r\n\r\nconst DEFAULTS: ResolvedConfig = {\r\n wikiRoot: '',\r\n wiki: 'main',\r\n port: 0,\r\n git: { autoCommit: true, debounceMs: 60_000, remote: '', branch: 'main' },\r\n note: { tag: 'inbox' },\r\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 } },\r\n uiLanguage: '',\r\n auth: { username: '', password: '' },\r\n}\r\n\r\n/** Config tiddler steering TW's frontend API base (tiddlywebadaptor). */\r\nexport { TW_WEB_HOST_TIDDLER, TW_WEB_HOST_DEFAULT } from './host/config.ts'\r\n\r\n/**\r\n * Point TW's frontend at the same-origin DSH proxy (remote-access mode, R1).\r\n * The tiddlywebadaptor builds every API URL from $:/config/tiddlyweb/host; its\r\n * default `$protocol$//$host$/` resolves to the iframe's origin ROOT, which\r\n * 404s whenever the browser is not on the same machine as TW. Written only\r\n * when the tiddler is missing or still the legacy default — a user override is\r\n * honored (e.g. someone who really does expose TW on a dedicated origin).\r\n */\r\nexport async function ensureTwWebHost(client: TiddlyWebClient | undefined): Promise<void> {\r\n if (client === undefined) return\r\n let current: string | undefined\r\n try {\r\n current = (await client.get(TW_WEB_HOST_TIDDLER))?.text\r\n } catch {\r\n current = undefined\r\n }\r\n if (current !== undefined && current.trim() !== TW_WEB_HOST_DEFAULT) return\r\n await client.put({ title: TW_WEB_HOST_TIDDLER, text: TW_PROXY_PATH, type: 'text/plain', tags: [] })\r\n}\r\n\r\n/** Expand $VAR / ${VAR} / %VAR% from process.env (config uses $DSH_HOME). */\r\nfunction expandEnvPath(input: string): string {\r\n return input\r\n .replace(/\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g, (_, k: string) => process.env[k] ?? '')\r\n .replace(/\\$([A-Za-z_][A-Za-z0-9_]*)/g, (_, k: string) => process.env[k] ?? '')\r\n .replace(/%([A-Za-z_][A-Za-z0-9_]*%)/g, (_, k: string) => process.env[k.slice(0, -1)] ?? '')\r\n}\r\n\r\n/** Resolve wikiRoot: explicit config (env-expanded) else $DSH_HOME/tiddlywiki. */\r\nfunction resolveWikiRoot(config: TiddlywikiConfig): string {\r\n if (config.wikiRoot !== undefined && config.wikiRoot.trim().length > 0) {\r\n return expandEnvPath(config.wikiRoot.trim())\r\n }\r\n return dshHomePath('tiddlywiki')\r\n}\r\n\r\n/** Write the .gitignore for TW transient artifacts (idempotent). */\r\nasync function writeGitignore(wikiPath: string): Promise<void> {\r\n const lines = [\r\n '# TiddlyWiki transient artifacts (auto-managed by dsh-tiddlywiki)',\r\n 'tiddlers/$__temp_*',\r\n 'tiddlers/$__StoryList*',\r\n 'tiddlers/$__HistoryList*',\r\n '*.meta.tmp',\r\n '',\r\n ]\r\n await writeFile(join(wikiPath, '.gitignore'), lines.join('\\n'), 'utf8')\r\n}\r\n\r\n/** Watch the wiki folders and touch the auto-committer on changes. */\r\nfunction watchWiki(wikiPath: string, onChange: () => void): () => void {\r\n const watchers: FSWatcher[] = []\r\n for (const dir of [join(wikiPath, 'tiddlers'), wikiPath]) {\r\n try {\r\n const watcher = watch(dir, { persistent: false }, () => onChange())\r\n watchers.push(watcher)\r\n } catch {\r\n /* directory may not exist yet; the committer also fires on our writes */\r\n }\r\n }\r\n return () => {\r\n for (const watcher of watchers) {\r\n try { watcher.close() } catch { /* already closed */ }\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Wait until a file exists on disk (polling), up to `timeoutMs`. TW's syncer\r\n * flushes REST writes to the filesystem on a ~250ms task timer, so a freshly\r\n * seeded tiddler is not on disk the moment PUT resolves — a caller that must\r\n * restart TW right after (so a seeded server route loads) has to wait for the\r\n * flush first, or the restarted TW would boot from a stale snapshot and the\r\n * seeded module would be missing.\r\n */\r\nasync function waitForFileWrite(filePath: string, timeoutMs = 8_000, pollMs = 150): Promise<boolean> {\r\n const deadline = Date.now() + timeoutMs\r\n for (;;) {\r\n try {\r\n if (existsSync(filePath)) return true\r\n } catch { /* transient */ }\r\n if (Date.now() >= deadline) return false\r\n await new Promise<void>((r) => setTimeout(r, pollMs))\r\n }\r\n}\r\n\r\n/** System-prompt section text (design doc §11 D8). */\r\nconst PROMPT_SECTION_NAME = 'dsh-tiddlywiki'\r\nconst PROMPT_SECTION_ORDER = 100\r\nconst PROMPT_TEXT = `## TiddlyWiki 持久知识库\r\n\r\n本机有一个 TiddlyWiki 5 持久知识库(wiki 文件夹即 git 仓库)。你可以用工具读写 tiddler:\r\n\r\n- \\`tiddlywiki_search\\`(query 必填;可选 tags[]/tag、since 修改时间、type、limit)检索(图片等二进制附件不参与检索);\\`tiddlywiki_get\\`(title)读全文(二进制附件只返回元数据,不含 base64 正文);\\`tiddlywiki_put\\`(title, text, tags?, fields?)写/覆盖;\\`tiddlywiki_batch_put\\`(items[])批量写;\\`tiddlywiki_rename\\`(oldTitle, newTitle, updateRefs?)重命名并尽量同步引用;\\`tiddlywiki_delete\\`(title)删除。\r\n- \\`tiddlywiki_recent\\`(limit?, since?)看最近修改的笔记(不含图片等二进制附件);\\`tiddlywiki_list_tags\\` 看现有 tag 及计数。\r\n- \\`tiddlywiki_git_sync\\`(pull|push|sync)做 git 同步;\\`tiddlywiki_git_resolve\\`(files, strategy=keep-local|keep-remote|list)在 pull 冲突后按 tiddler 二选一解决。\r\n\r\n知识库同步纪律(三条):\r\n1. 开工先 pull:\\`tiddlywiki_git_sync action=pull\\`(rebase + autostash;真冲突会自动 abort 并报冲突文件)。\r\n2. 收工 commit + push:\\`tiddlywiki_git_sync action=sync\\`(pull → commit → push)。\r\n3. 插件会自动防抖 commit(默认 60s),手动同步用上面的工具。\r\n\r\npull 冲突后:先 \\`tiddlywiki_git_resolve files=[冲突文件] strategy=keep-local\\`(保留本地)或 \\`strategy=keep-remote\\`(改用远端版本),再重新 pull/sync 整合其余改动。\r\n\r\n把 wiki 当作长期记忆与知识沉淀的地方:会议纪要、决策记录、调研笔记、随手的想法都可存成独立 tiddler(tag 建议用 inbox/meeting/decision 等便于检索)。\r\n\r\n**把有价值但不在当前执行范围内的想法沉淀进 wiki**:遇到「未来可能有用 / 值得做」的想法、或不在当前任务范围内但有实现价值的事项时,用 \\`tiddlywiki_put\\` 写成独立 tiddler,打上 \\`todo\\` + \\`agent-written\\` 标签(并附当前工作区名),正文简要说明来源(会话 / 工作区 / 项目背景),方便日后回溯,由用户决定是否继续。\r\n\r\n用本插件自动创建笔记时,除了业务性 tag 外,请把「当前工作区(项目)的名字」也作为标签之一加上去(例如 \\`tiddlywiki_put\\` 的 tags 里带上当前 workspace 名),这样笔记能按项目归集、检索。\r\n\r\n**Agent 笔记标签约定**:\\`tiddlywiki_put\\` / \\`tiddlywiki_batch_put\\` 新建笔记时,插件会自动补打 \\`agent-written\\` 标签(标记「由 Agent 撰写」),无需手动添加,也不要手动移除它(除非用户明确要求)。首页会把 Agent 笔记单独列在「Agent 区块」,主标签列表只统计人类笔记。若某篇 Agent 笔记后续被人类编辑过,请在该笔记上补打 \\`human-edited\\` 标签,首页会把它归入「Agent + 人工」档。覆盖写入已有的(人类)笔记时不会自动加 agent-written,请保持笔记原本的归属。\r\n\r\n**内容类型约定**:agent 笔记正文默认用 **Markdown** 写;\\`tiddlywiki_put\\` / \\`tiddlywiki_batch_put\\` 未指定内容类型时自动按 \\`text/markdown\\` 写入(\\`$:/\\` 系统条目除外),无需手动指定。要写 TW 原生 wikitext 才需要在 fields 里显式传 \\`{\"type\":\"text/vnd.tiddlywiki\"}\\`。⚠️ \\`fields.type\\` 是 TW 的**内容类型**保留字段——不要把业务分类值(如 \\`\"meeting\"\\`)写进去(会破坏渲染),业务分类请放 \\`tags\\`。\r\n\r\n**引用 wiki 笔记用可点击链接**:在回复流中引用某篇笔记时,用格式 \\`[标题](/dsh-tiddlywiki/tw/#标题)\\` 输出(标题含空格/特殊字符时做 URL 编码,如 \\`A%20B\\`;中文标题可直接写)。这类链接会被界面自动接管:点击后打开中央 TW 面板并跳转到该笔记的原生页面。回复里也优先用这个链接格式代替纯文本标题,让用户能一键跳到 wiki。`\r\n\r\n/**\r\n * Mount the host half.\r\n * @param ctx - the plugin context (tools + systemPrompt injected).\r\n * @param rawConfig - the plugin row's `config:` block (Cordis second arg).\r\n */\r\nexport function apply(ctx: HostCtx, rawConfig: TiddlywikiConfig = {}): void {\r\n const config: ResolvedConfig = {\r\n wikiRoot: resolveWikiRoot(rawConfig),\r\n wiki: rawConfig.wiki ?? DEFAULTS.wiki,\r\n port: rawConfig.port ?? DEFAULTS.port,\r\n git: { ...DEFAULTS.git, ...(rawConfig.git ?? {}) },\r\n note: { ...DEFAULTS.note, ...(rawConfig.note ?? {}) },\r\n ui: {\r\n ...DEFAULTS.ui,\r\n ...(rawConfig.ui ?? {}),\r\n // Merge nested ui.* groups explicitly so defaults stay required (a plain\r\n // spread of the lax cordis shape would widen them to optional).\r\n sendToAgent: { ...DEFAULTS.ui.sendToAgent, ...(rawConfig.ui?.sendToAgent ?? {}) },\r\n allArticles: { ...DEFAULTS.ui.allArticles, ...(rawConfig.ui?.allArticles ?? {}) },\r\n },\r\n uiLanguage: typeof rawConfig.uiLanguage === 'string' ? rawConfig.uiLanguage.trim() : DEFAULTS.uiLanguage,\r\n auth: { ...DEFAULTS.auth, ...(rawConfig.auth ?? {}) },\r\n }\r\n const wikiPath = join(config.wikiRoot, config.wiki)\r\n const git = new GitFace()\r\n\r\n // Runtime-editable config (settings page): the cordis `config:` block is the\r\n // BASE; a config tiddler ($:/plugins/dsh-tiddlywiki/config) written by the\r\n // settings page overlays it. Effective values come from configStore.get().\r\n const configStore = new ConfigStore({ note: config.note, git: config.git, ui: config.ui, uiLanguage: config.uiLanguage } satisfies PluginConfigShape)\r\n const eff = (): PluginConfigShape => configStore.get()\r\n const effectiveNoteTag = (): string => {\r\n const tag = eff().note?.tag\r\n return typeof tag === 'string' && tag.trim().length > 0 ? tag : config.note.tag\r\n }\r\n const effectiveUi = (): { showQuickNote: boolean; showQuickNoteDock: boolean; quickNoteMode: 'native' | 'card'; sidebarLabel: string; showPanelStatus: boolean; showSyncButton: boolean; followDshTheme: boolean; darkPalette: string; tabLabel: string; showSessionTab: boolean; showRightbarTab: boolean } => {\r\n const ui = eff().ui ?? {}\r\n const palette = typeof ui.darkPalette === 'string' && ui.darkPalette.trim().length > 0 ? ui.darkPalette.trim() : DARK_PALETTE_DEFAULT\r\n const label = typeof ui.sidebarLabel === 'string' && ui.sidebarLabel.trim().length > 0 ? ui.sidebarLabel.trim() : config.ui.sidebarLabel\r\n const tabLabel = typeof ui.tabLabel === 'string' && ui.tabLabel.trim().length > 0 ? ui.tabLabel.trim() : config.ui.tabLabel\r\n const quickNoteMode: 'native' | 'card' = ui.quickNoteMode === 'card' ? 'card' : 'native'\r\n return {\r\n showQuickNote: ui.showQuickNote !== false,\r\n showQuickNoteDock: ui.showQuickNoteDock !== false,\r\n quickNoteMode,\r\n sidebarLabel: label,\r\n showPanelStatus: ui.showPanelStatus !== false,\r\n showSyncButton: ui.showSyncButton !== false,\r\n followDshTheme: ui.followDshTheme !== false,\r\n darkPalette: palette,\r\n tabLabel,\r\n showSessionTab: ui.showSessionTab !== false,\r\n showRightbarTab: ui.showRightbarTab !== false,\r\n }\r\n }\r\n\r\n const disposers: Array<() => void> = []\r\n const disposeAll = (): void => {\r\n for (const dispose of disposers.splice(0)) dispose()\r\n }\r\n\r\n // System prompt section (independent of the wiki service).\r\n const disposeSection = ctx.systemPrompt.section({ name: PROMPT_SECTION_NAME, order: PROMPT_SECTION_ORDER, text: PROMPT_TEXT })\r\n ctx.effect(() => disposeSection, 'dsh-tiddlywiki: prompt section')\r\n\r\n // TW child server.\r\n const server = new WikiServer({\r\n wikiRoot: config.wikiRoot,\r\n wiki: config.wiki,\r\n port: config.port,\r\n username: config.auth.username,\r\n password: config.auth.password,\r\n })\r\n\r\n // Lazy TW client (rebuilt when the port is bound).\r\n let clientCache: TiddlyWebClient | undefined\r\n const client = (): TiddlyWebClient | undefined => {\r\n const port = server.currentPort\r\n if (port === undefined) return undefined\r\n clientCache ??= new TiddlyWebClient(`http://127.0.0.1:${port}`)\r\n return clientCache\r\n }\r\n\r\n // Auto-committer + filesystem watcher (created after the wiki dir exists).\r\n // Reads the EFFECTIVE config so a settings-page git change survives a restart.\r\n let committer: AutoCommitter | undefined\r\n let unwatch: (() => void) | undefined\r\n const setupCommitter = (): void => {\r\n const g = eff().git ?? {}\r\n committer = new AutoCommitter({\r\n git,\r\n dir: wikiPath,\r\n enabled: g.autoCommit ?? config.git.autoCommit,\r\n debounceMs: g.debounceMs ?? config.git.debounceMs,\r\n message: () => `wiki autocommit ${new Date().toISOString()}`,\r\n onError: (err) => console.warn('[dsh-tiddlywiki] autocommit:', err),\r\n })\r\n unwatch = watchWiki(wikiPath, () => committer?.touch())\r\n disposers.push(() => {\r\n committer?.dispose()\r\n unwatch?.()\r\n })\r\n }\r\n\r\n // Git bootstrap: repo init + initial commit + .gitignore (+ remote/first push).\r\n const bootstrapGit = async (): Promise<void> => {\r\n const g = eff().git ?? {}\r\n const branch = g.branch ?? config.git.branch\r\n const remote = g.remote ?? config.git.remote\r\n const isRepo = await git.isRepo(wikiPath)\r\n if (!isRepo) {\r\n await git.init(wikiPath, branch)\r\n await writeGitignore(wikiPath)\r\n await git.initialCommit(wikiPath)\r\n } else {\r\n await writeGitignore(wikiPath)\r\n }\r\n if (remote.trim().length > 0) {\r\n const ensured = await git.ensureRemote(wikiPath, remote.trim())\r\n if (ensured.ok) {\r\n const first = await git.firstPush(wikiPath)\r\n if (!first.ok) console.warn('[dsh-tiddlywiki] first push failed (retry with tiddlywiki_git_sync):', first.message)\r\n } else {\r\n console.warn('[dsh-tiddlywiki] git remote setup:', ensured.message)\r\n }\r\n }\r\n }\r\n\r\n // Tools (works even while the wiki is down; wiki() resolves lazily).\r\n const toolsDeps: ToolsDeps = {\r\n wiki: client,\r\n git,\r\n wikiPath: () => wikiPath,\r\n noteTag: effectiveNoteTag,\r\n autoCommit: () => committer?.touch(),\r\n // After a pull that changed the working tree, restart TW so the server\r\n // (and the agent's reads) see the pulled content, not the old snapshot.\r\n restartWiki: async () => { await server.restart() },\r\n }\r\n disposers.push(...registerTiddlywikiTools(ctx, toolsDeps))\r\n\r\n // Bring the wiki up, load the override config, then bootstrap git + committer.\r\n void (async () => {\r\n try {\r\n await server.start()\r\n await configStore.load(client())\r\n // Point TW's frontend at the same-origin DSH proxy (remote-access mode):\r\n // part of the seed registry below (tw-web-host), so it is also covered by\r\n // the settings page's 重新初始化. Runs before git bootstrap so the config\r\n // tiddler joins the first commit.\r\n // Seeds (run after the effective config is loaded): every one-time\r\n // \"与 dsh 联动需要 wiki 预置\" item lives in the SEED_DEFS registry.\r\n // The startup path seeds ONLY the CORE items (功能必需:发送给 Agent\r\n // 按钮 + TW 前端 API 基址) NON-force — write only what is missing, never\r\n // overwrite user content. Optional seeds (说明笔记 / 首页 / 所有文章 /\r\n // menubar 顶栏主题自适应) are never forced on users: they opt in from\r\n // the settings page「初始化」section (重新初始化) and can opt out again\r\n // with 反初始化 (remove).\r\n try {\r\n const seedClient = client()\r\n if (seedClient !== undefined) {\r\n const results = await runAllSeeds({ client: seedClient })\r\n for (const r of results) {\r\n if (!r.ok) console.warn(`[dsh-tiddlywiki] seed ${r.id} failed:`, r.error ?? r.detail)\r\n }\r\n // Seeding wrote something (e.g. the render-route plugin on this\r\n // upgrade). A seeded SERVER route only loads at TW boot, so restart\r\n // once to pick it up. Wait for the bundle file to actually reach the\r\n // disk first — TW's syncer flushes REST writes on a ~250ms task\r\n // timer, and restarting from a stale snapshot would boot WITHOUT the\r\n // just-seeded module.\r\n if (results.some((r) => r.ok && r.wrote)) {\r\n const renderFile = join(wikiPath, 'tiddlers', '$__plugins_dsh_render.json')\r\n const flushed = await waitForFileWrite(renderFile)\r\n if (!flushed) console.warn('[dsh-tiddlywiki] seeded render plugin file not seen on disk before restart')\r\n await server.restart()\r\n }\r\n }\r\n } catch (err) {\r\n console.warn('[dsh-tiddlywiki] seeding wiki:', err)\r\n }\r\n // Apply the configured UI language (e.g. \"zh-Hans\"): enable the bundled\r\n // language plugin in tiddlywiki.info.languages + restart once so TW loads\r\n // it at boot (fully offline — official language packs ship in the pkg).\r\n const uiLang = eff().uiLanguage\r\n if (typeof uiLang === 'string' && uiLang.trim().length > 0) {\r\n try {\r\n const code = uiLang.trim()\r\n const changed = await ensureLanguage(wikiPath, resolveTwRoot(), code)\r\n if (changed) await server.restart()\r\n // Pin the active language tiddler so TW's UI actually switches.\r\n const langClient = client()\r\n if (langClient !== undefined) {\r\n await langClient.put({ title: '$:/language', text: `$:/languages/${code}`, type: 'text/plain', tags: [] }).catch(() => undefined)\r\n }\r\n } catch (err) {\r\n console.warn('[dsh-tiddlywiki] applying uiLanguage:', err)\r\n }\r\n }\r\n await bootstrapGit()\r\n setupCommitter()\r\n } catch (err) {\r\n console.warn('[dsh-tiddlywiki] startup issue (self-healing is armed):', err)\r\n }\r\n })()\r\n\r\n // Routes + settings-panel admin surface (lazy webServer).\r\n ctx.inject(['webServer'], (webCtx: HostCtx) => {\r\n const ws = (webCtx as unknown as { webServer: WebServerFace }).webServer\r\n // sessionController is a core host service; read it LAZILY per request (via\r\n // the plugin root ctx) because it may be registered after webServer, and the\r\n // agent routes must work whenever a request actually arrives.\r\n const getSessionController = (): SessionControllerFace | undefined =>\r\n ctx.get('sessionController') as SessionControllerFace | undefined\r\n const getWorkspaceRegistry = (): WorkspaceRegistryFace | undefined =>\r\n ctx.get('workspaceRegistry') as WorkspaceRegistryFace | undefined\r\n // agentPresets (工作模式 roster) + sessionPersistence (per-session preset\r\n // badges) are also core host services — resolve them lazily like the above.\r\n const getAgentPresets = (): AgentPresetsFace | undefined =>\r\n ctx.get('agentPresets') as AgentPresetsFace | undefined\r\n const getSessionPersistence = (): SessionPersistenceFace | undefined =>\r\n ctx.get('sessionPersistence') as SessionPersistenceFace | undefined\r\n // permissionPresets (权限 preset roster for the picker + applying the\r\n // chosen permission to new sessions) and the `sessions` in-memory store\r\n // (the created live session handed to permissionPresets.set) are core host\r\n // services — resolve them lazily like the ones above.\r\n const getPermissionPresets = (): PermissionPresetsFace | undefined =>\r\n ctx.get('permissionPresets') as PermissionPresetsFace | undefined\r\n const getSessions = (): SessionsFace | undefined =>\r\n ctx.get('sessions') as SessionsFace | undefined\r\n // sessionQuery (会话日志查询,供「知识库」Tab 判定「本会话相关笔记」) 也是核心\r\n // host 服务 —— 可选注入(本部署存在),懒解析。\r\n const getSessionQuery = (): SessionQueryFace | undefined =>\r\n ctx.get('sessionQuery') as SessionQueryFace | undefined\r\n const disposeRoutes = registerRoutes({ webServer: ws }, {\r\n server,\r\n getClient: client,\r\n git,\r\n autoCommit: () => committer?.touch(),\r\n noteDefaults: () => ({ tag: effectiveNoteTag() }),\r\n uiDefaults: () => effectiveUi(),\r\n getWikiPath: () => wikiPath,\r\n getSessionController,\r\n getWorkspaceRegistry,\r\n getAgentPresets,\r\n getSessionPersistence,\r\n getPermissionPresets,\r\n getSessions,\r\n getSessionQuery,\r\n sendToAgentEnabled: () => eff().ui?.sendToAgent?.enabled !== false,\r\n sendToAgentToken: () => {\r\n const token = eff().ui?.sendToAgent?.token\r\n return typeof token === 'string' ? token : ''\r\n },\r\n })\r\n const adminDeps: AdminDeps = {\r\n server,\r\n getClient: client,\r\n getWikiPath: () => wikiPath,\r\n twRoot: resolveTwRoot,\r\n config: configStore,\r\n seeds: {\r\n checkAll: async (c) => checkAllSeeds({ client: c }),\r\n run: async (c, id, force) => runSeedById({ client: c }, id, force),\r\n remove: async (c, id) => removeSeedById({ client: c }, id),\r\n },\r\n }\r\n const disposeAdmin = registerAdminRoutes({ webServer: ws }, adminDeps)\r\n return () => {\r\n disposeRoutes()\r\n disposeAdmin()\r\n }\r\n })\r\n\r\n // Teardown: everything reversible (R6 — hot reload must not leak).\r\n // The disposer RETURNS its promise: cordis fiber.dispose() awaits effect\r\n // disposers, and dsh web's shutdown controller awaits fiber.dispose() with a\r\n // 5s grace — so 结束/退出/重启 dsh web 时 TW 后端子进程会被真正停掉,而不是\r\n // fire-and-forget 里与进程退出竞速(竞速会在 Windows 上遗留孤儿 TW 进程)。\r\n ctx.effect(() => () => {\r\n return (async () => {\r\n // Flush a pending auto-commit BEFORE teardown, so a write made within the\r\n // debounce window is not left uncommitted when dsh web stops (best-effort).\r\n try { await committer?.flush() } catch { /* best-effort */ }\r\n disposeAll()\r\n await server.stop()\r\n })()\r\n }, 'dsh-tiddlywiki: host teardown')\r\n}\r\n"],"mappings":"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"}
|