ksrw-core 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts", "../../../packages/kernel/src/error.ts", "../../../packages/kernel/src/ids.ts", "../../../packages/kernel/src/lock.ts", "../../../packages/kernel/src/arxiv.ts", "../../../node_modules/.pnpm/fflate@0.8.3/node_modules/fflate/esm/index.mjs", "../../../packages/kernel/src/pdf.ts", "../../../packages/kernel/src/deepread.ts", "../../../packages/kernel/src/paper-templates.ts", "../../../packages/kernel/src/figures.ts", "../../../packages/kernel/src/venue-profile.ts", "../../../packages/kernel/src/workspace.ts", "../../../packages/kernel/src/paper.ts", "../../../packages/kernel/src/standalone.ts", "../../../packages/kernel/src/attachment.ts", "../../../packages/kernel/src/paper-workshop.ts", "../../../packages/kernel/src/knowledge.ts", "../src/api.ts", "../src/request-guard.ts", "../src/experiment.ts", "../src/lab.ts", "../src/figures.ts", "../src/remote.ts", "../src/secret-store.ts", "../src/auto-archive.ts"],
4
- "sourcesContent": ["/** ksrw-core \u2014 KSRW v2 \u5BBF\u4E3B\u63D2\u4EF6\uFF08M0 \u9AA8\u67B6\uFF09\u3002\n *\n * \u4E09\u4EF6\u4E8B\uFF1A\n * 1. exact / \u63D0\u4F9B\u81EA\u6709 auth \u9875\uFF08\u6D4F\u89C8\u5668/\u5F00\u53D1\u6001\u56DE\u9000\uFF1B\u684C\u9762\u6001\u7531\u5F15\u64CE welcome \u7A97\u627F\u8F7D\uFF0Crenderer \u7528\u540C\u4E00\u5957\u8BBE\u8BA1\uFF09\uFF1B\n * 2. /ksrw/api \u63D0\u4F9B\u4EA7\u54C1 API\uFF08M0 \u5148 health\uFF09\uFF1B/ksrw/* \u63D0\u4F9B\u9759\u6001\u8D44\u6E90\uFF1B\n * 3. \u9886\u57DF\u5DE5\u5177\u7531 ksrw-tools \u6309\u5DE5\u4F4D\u9884\u8BBE\u6302\u8F7D\uFF08\u672C\u63D2\u4EF6\u53EA\u505A UI/API/\u9759\u6001\u8D44\u6E90\uFF09\u3002\n *\n * \u8BA4\u8BC1\u80FD\u529B\u6CBF\u7528\u5F15\u64CE\uFF1A\u684C\u9762 welcome \u7A97\u7684 preload\uFF08window.dshWelcome\uFF09\u8C03\u7528 local-auth\uFF1B\n * \u6D4F\u89C8\u5668\u56DE\u9000\u9875\u8C03 /api/auth/*\uFF08accounts-local\uFF09\u3002\u4EA7\u54C1\u4E0D\u65B0\u5199\u8D26\u6237\u7CFB\u7EDF\u3002\n */\nimport { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from 'node:fs'\nimport { readFile } from 'node:fs/promises'\nimport { dirname, extname, join } from 'node:path'\nimport { homedir } from 'node:os'\nimport { fileURLToPath } from 'node:url'\n\nimport { ProjectStore, resolveKsrwHome } from 'ksrw-kernel'\n\nimport { createRequestGuard, handleApi, rejectRequest, UNAUTHENTICATED_ESCAPE } from './api.ts'\nimport { startSessionAutoArchive } from './auto-archive.ts'\nimport type { ConnectionFence, RequestGuard } from './api.ts'\nimport { migratePlaintextSecrets } from './remote.ts'\n\nexport const name = 'ksrw-core'\nexport const inject = ['webServer'] as const\nexport const VERSION = '0.1.1'\n\nexport const PUBLIC_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', 'public')\n\ninterface ServerResponseLike {\n writeHead(code: number, headers?: Record<string, string>): unknown\n end(body?: unknown): unknown\n}\ninterface ServerRequestLike { url?: string; method?: string }\ninterface RouteLike {\n kind: 'exact' | 'prefix'\n path: string\n handler: (req: ServerRequestLike, res: ServerResponseLike) => void | Promise<void>\n}\nexport interface ContextLike {\n readonly webServer: { register(route: RouteLike): () => void }\n readonly connection?: { requestRejection?(req: unknown): number | undefined }\n readonly reflect?: { get?(name: string, strict?: boolean): unknown }\n readonly effect?: (disposer: () => unknown, label?: string) => unknown\n readonly logger?: { warn?(msg: string): void; info?(msg: string): void }\n}\n\nconst MIME: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.json': 'application/json',\n '.png': 'image/png',\n}\n\n/** \u4E00\u7EA7\u6587\u4EF6\u540D\u5B88\u536B\uFF1A\u62D2\u7EDD\u7A7A\u3001\u5206\u9694\u7B26\u3001..\uFF08\u9759\u6001\u8D44\u6E90\u53EA\u5141\u8BB8\u5E73\u94FA\uFF09\u3002 */\nexport function safeStaticName(raw: string): string | null {\n if (raw === '' || raw.includes('/') || raw.includes('\\\\') || raw.includes('..')) return null\n return raw\n}\n\nasync function serveFile(fileName: string, res: ServerResponseLike): Promise<void> {\n try {\n const body = await readFile(join(PUBLIC_ROOT, fileName))\n res.writeHead(200, { 'content-type': MIME[extname(fileName).toLowerCase()] ?? 'application/octet-stream' })\n res.end(body)\n } catch {\n res.writeHead(404)\n res.end()\n }\n}\n\n/** \u628A\u5B66\u672F\u6392\u7248\u6A21\u677F\u64AD\u79CD\u5230 KSRW home\uFF08kernel \u7684 paperTemplatePath \u4ECE\u8FD9\u91CC\u8BFB\uFF09\u3002 */\nfunction seedPaperTemplate(): void {\n try {\n const home = resolveKsrwHome()\n // 1) \u4F1A\u8BAE/\u671F\u520A\u6A21\u677F\u76EE\u5F55\n const templateRoot = join(PUBLIC_ROOT, 'templates')\n const targetDir = join(home, 'assets', 'templates')\n if (existsSync(templateRoot)) {\n mkdirSync(targetDir, { recursive: true })\n for (const fileName of readdirSync(templateRoot)) {\n if (!fileName.endsWith('.docx') && fileName !== 'index.json') continue\n const source = join(templateRoot, fileName)\n const target = join(targetDir, fileName)\n const sourceSize = statSync(source).size\n if (existsSync(target) && statSync(target).size === sourceSize) continue\n copyFileSync(source, target)\n }\n }\n // 2) \u65E7\u7248\u5355\u4E00\u6A21\u677F\uFF08\u517C\u5BB9\u5DF2\u4FDD\u5B58\u7684\u9879\u76EE\uFF09\n const legacy = join(PUBLIC_ROOT, 'reference-academic.docx')\n if (!existsSync(legacy)) return\n const legacyTarget = join(home, 'assets', 'reference-academic.docx')\n if (existsSync(legacyTarget) && statSync(legacyTarget).size === statSync(legacy).size) return\n mkdirSync(dirname(legacyTarget), { recursive: true })\n copyFileSync(legacy, legacyTarget)\n } catch { /* \u64AD\u79CD\u5931\u8D25\u65F6\u9000\u56DE pandoc \u9ED8\u8BA4\u6A21\u677F\uFF0C\u4E0D\u963B\u585E\u542F\u52A8 */ }\n}\n\nexport function apply(ctx: ContextLike): void {\n const disposers: (() => void)[] = []\n const effect = (fn: () => unknown, label: string): void => {\n if (typeof ctx.effect === 'function') ctx.effect(fn, label)\n }\n\n // \u63D2\u4EF6\u6A21\u5F0F\uFF1A/ \u7531\u5F15\u64CE Web \u5E94\u7528\u63D0\u4F9B\uFF0C\u63D2\u4EF6\u4E0D\u518D\u5360\u7528\u9996\u9875\u3001\u4E0D\u81EA\u5EFA\u767B\u5F55/\u6CE8\u518C\u3002\n // \u684C\u9762\u4EA7\u54C1\u7684 welcome \u7A97\u4ECD\u7531\u684C\u9762\u58F3\u81EA\u5DF1\u7684\u9759\u6001\u9875\u627F\u8F7D\uFF0C\u4E0E\u672C\u63D2\u4EF6\u65E0\u5173\u3002\n\n const soft = (name: string): unknown => {\n try { return ctx.reflect?.get?.(name, false) ?? null } catch { return null }\n }\n // \u9274\u6743\u6743\u5A01\u662F\u5F15\u64CE\u7684 connection \u56F4\u680F\u3002\u8BF7\u6C42\u65F6\u89E3\u6790\u800C\u4E0D\u662F apply \u65F6\u89E3\u6790\uFF1A\n // connection \u7684\u6302\u8F7D\u987A\u5E8F\u53EF\u80FD\u5728 ksrw-core \u4E4B\u540E\uFF08\u65E9\u8BFB\u4F1A\u62FF\u5230 null \u5E76\u6C38\u4E45\u6536\u7D27\uFF09\u3002\n let guardWarned = false\n const requestGuard = (): RequestGuard => {\n const fence = soft('connection') as ConnectionFence | null\n if (typeof fence?.requestRejection !== 'function' && !guardWarned) {\n guardWarned = true\n if (process.env[UNAUTHENTICATED_ESCAPE] === '1') {\n ctx.logger?.warn?.(`ksrw-core: \u5BBF\u4E3B\u672A\u6302\u8F7D connection\uFF0C${UNAUTHENTICATED_ESCAPE}=1 \u5DF2\u663E\u5F0F\u653E\u884C /ksrw/api\uFF08\u4EC5\u9650\u5F00\u53D1\u6001\uFF09`)\n } else {\n ctx.logger?.warn?.('ksrw-core: \u5BBF\u4E3B\u672A\u6302\u8F7D connection\uFF0C/ksrw/api \u5DF2 fail-closed\uFF08503\uFF09\uFF1B\u684C\u9762\u7AEF\u8BF7\u786E\u8BA4 accounts \u5DF2\u542F\u7528')\n }\n }\n return createRequestGuard(fence)\n }\n /** \u8FD4\u56DE true \u8868\u793A\u5DF2\u62D2\u7EDD\u5E76\u5199\u5B8C\u54CD\u5E94\uFF08\u8C03\u7528\u65B9\u76F4\u63A5 return\uFF09\u3002 */\n const rejectIfBlocked = (req: ServerRequestLike, res: ServerResponseLike): boolean => {\n const status = requestGuard()(req as never)\n if (typeof status !== 'number') return false\n rejectRequest(res as never, status)\n return true\n }\n\n // 3) /ksrw/api\uFF1A\u9879\u76EE\u5E93 / \u7814\u7A76\u72B6\u6001 / \u9009\u9898\u5361 / \u63D0\u6848\n const store = new ProjectStore()\n disposers.push(ctx.webServer.register({\n kind: 'prefix',\n path: '/ksrw/api',\n handler: async (req, res) => {\n // \u5F15\u64CE web \u7EC4\u5408\u5C42\u6302\u8F7D\u7684 office-to-pdf\uFF1AWord/PPT \u6210\u679C\u9884\u89C8\u8F6C PDF\u3002\u61D2\u89E3\u6790\uFF0C\n // \u56E0\u4E3A\u5B83\u7684\u6302\u8F7D\u987A\u5E8F\u53EF\u80FD\u5728 ksrw-core \u4E4B\u540E\uFF08\u7F3A\u5931\u65F6\u9884\u89C8\u7AEF\u70B9\u660E\u786E\u62A5\u9519\uFF09\u3002\n const officeToPdf = soft('officeToPdf') as\n | { convert(request: Record<string, unknown>): Promise<{ pdf: Uint8Array; missingFonts?: string[] }> }\n | undefined\n // \u5B9A\u65F6\u6293\u53D6\u4E0D\u518D\u7531\u63D2\u4EF6\u81EA\u5EFA\u8C03\u5EA6\uFF1A\u8BFB\u5199\u90FD\u4EA4\u7ED9\u5F15\u64CE\u7684 schedule \u670D\u52A1\u3002\n const schedule = soft('schedule') as\n | { create(request: Record<string, unknown>): Promise<unknown> }\n | null\n await handleApi(\n req as never,\n res as never,\n store,\n requestGuard(),\n officeToPdf,\n schedule,\n )\n },\n }))\n\n // 3) /ksrw/*\uFF1Aauth \u9875\u9759\u6001\u8D44\u6E90\uFF08\u53EA\u5141\u8BB8\u4E00\u7EA7\u6587\u4EF6\u540D\uFF09\n disposers.push(ctx.webServer.register({\n kind: 'prefix',\n path: '/ksrw',\n handler: async (req, res) => {\n const pathname = (req.url ?? '').split('?')[0] ?? ''\n const raw = pathname.startsWith('/ksrw/') ? pathname.slice('/ksrw/'.length) : ''\n let decoded = raw\n try { decoded = decodeURIComponent(raw) } catch { decoded = '\\u0000' }\n const fileName = safeStaticName(decoded)\n if (fileName === null) { res.writeHead(404); res.end(); return }\n await serveFile(fileName, res)\n },\n }))\n\n seedPaperTemplate()\n\n // 4) \u8FDC\u7A0B\u76EE\u6807\u51ED\u636E\u5347\u7EA7\uFF1A\u5B58\u91CF\u660E\u6587\u5BC6\u7801\u8FC1\u8FDB\u7CFB\u7EDF\u51ED\u636E\u5B58\u50A8\uFF08\u540E\u7AEF\u4E0D\u53EF\u7528\u65F6\u53EA\u544A\u8B66\uFF0C\u4E0D\u963B\u585E\u542F\u52A8\uFF09\u3002\n try {\n const secrets = migratePlaintextSecrets()\n if (secrets.migrated > 0) {\n ctx.logger?.info?.(`ksrw-core: \u5DF2\u628A ${secrets.migrated} \u4E2A\u8FDC\u7A0B\u76EE\u6807\u7684\u5BC6\u7801\u8FC1\u5165\u7CFB\u7EDF\u51ED\u636E\u5B58\u50A8`)\n }\n if (secrets.remaining > 0) {\n ctx.logger?.warn?.(`ksrw-core: ${secrets.remaining} \u4E2A\u8FDC\u7A0B\u76EE\u6807\u4ECD\u4EE5\u660E\u6587\u4FDD\u5B58\u5BC6\u7801\uFF08\u7CFB\u7EDF\u51ED\u636E\u5B58\u50A8\u4E0D\u53EF\u7528\uFF09\uFF1A\u5EFA\u8BAE\u6539\u7528\u5BC6\u94A5\u6216 agent \u8BA4\u8BC1`)\n }\n } catch { /* \u8FC1\u79FB\u662F\u52A0\u56FA\uFF0C\u4E0D\u662F\u542F\u52A8\u5FC5\u8981\u6B65\u9AA4 */ }\n\n // \u515C\u5E95\uFF1Aagent \u5FD8\u4E86\u8C03 ksrw_archive \u65F6\uFF0C\u4ECD\u628A\u5DE5\u4F4D\u4F1A\u8BDD\u7684\u6210\u679C\u5F52\u6863\u8FDB\u9879\u76EE\u5E93\u3002\n const stopAutoArchive = startSessionAutoArchive({\n info: message => ctx.logger?.info?.(message),\n warn: message => ctx.logger?.warn?.(message),\n })\n\n effect(() => () => {\n stopAutoArchive()\n for (const dispose of disposers.reverse()) dispose()\n }, 'ksrw-core: routes + auto-archive')\n}\n", "/** KSRW \u7EDF\u4E00\u9519\u8BEF\uFF1Acode \u7A33\u5B9A\u53EF\u5224\u522B\uFF0Cmessage \u9762\u5411\u6A21\u578B/\u7528\u6237\u53EF\u8BFB\uFF08\u53CC\u8BED\u77ED\u53E5\uFF09\u3002 */\n\nexport const ERROR_CODES = [\n 'INVALID',\n 'NOT_FOUND',\n 'DUPLICATE',\n 'LOCKED',\n 'PATH_DENIED',\n 'ENGINE_MISSING',\n 'COMPILE_FAILED',\n 'NETWORK',\n 'RATE_LIMITED',\n 'AUTH_MISSING',\n 'SSH_FAILED',\n 'NO_PROVIDER',\n 'ABORTED',\n 'STALE_REVISION',\n 'INTERNAL',\n] as const\n\nexport type ErrorCode = (typeof ERROR_CODES)[number]\n\nexport class KsrwError extends Error {\n constructor(\n readonly code: ErrorCode,\n message: string,\n readonly detail?: unknown,\n ) {\n super(message)\n this.name = 'KsrwError'\n }\n\n /** \u5DE5\u5177/RPC \u7EDF\u4E00\u4FE1\u5C01\u7684\u9519\u8BEF\u5F62\u6001\u3002 */\n envelope(): { code: ErrorCode; message: string } {\n return { code: this.code, message: this.message }\n }\n}\n\nexport function ksrwEnvelope(error: unknown): { code: string; message: string } {\n if (error instanceof KsrwError) return error.envelope()\n return { code: 'internal', message: error instanceof Error ? error.message : String(error) }\n}\n", "/** \u77ED ID\uFF1A\u65F6\u95F4\u524D\u7F00\uFF08\u6BEB\u79D236\u8FDB\u5236\uFF09+ 4 \u4F4D\u968F\u673A\uFF0C\u53EF\u8BFB\u3001\u53EF\u6392\u5E8F\u3001\u78B0\u649E\u6982\u7387\u53EF\u5FFD\u7565\u3002 */\n\nexport function newId(prefix: string): string {\n const t = Date.now().toString(36)\n const r = Math.random().toString(36).slice(2, 6)\n return `${prefix}_${t}${r}`\n}\n", "/** \u9879\u76EE\u7EA7\u8DE8\u8FDB\u7A0B\u5199\u9501\uFF08`.ksrw.lock`\uFF09\uFF1A\u72EC\u5360\u521B\u5EFA + \u9648\u65E7\u63A5\u7BA1\u3002\n *\n * \u4E3A\u4EC0\u4E48\u9700\u8981\uFF1A\u9879\u76EE\u76EE\u5F55\u662F\u7528\u6237\u53EF\u89C1\u7684\u666E\u901A\u6587\u4EF6\u5939\uFF0C\u540C\u4E00\u76EE\u5F55\u53EF\u80FD\u540C\u65F6\u88AB\u684C\u9762\u58F3\u3001CLI\n * \u4E0E\u7B2C\u4E8C\u4E2A\u7A97\u53E3\u6253\u5F00\u3002\u5185\u6838\u5199\u8DEF\u5F84\u90FD\u662F\u300C\u8BFB \u2192 \u6539 \u2192 \u5199\u300D\uFF0C\u53EA\u9760 tmp+rename \u539F\u5B50\u5199\u53EA\u80FD\n * \u4FDD\u8BC1\u5355\u6B21\u5199\u4E0D\u6495\u88C2\uFF0C\u4E0D\u80FD\u9632\u6B62\u4E24\u4E2A\u8FDB\u7A0B\u5404\u81EA\u57FA\u4E8E\u65E7\u503C\u5199\u56DE\uFF08\u4E22\u66F4\u65B0\uFF1A\u63D0\u6848 apply \u4E0E\n * agent \u5199\u4EA4\u63A5\u5355\u5E76\u53D1\u65F6\u6700\u5178\u578B\uFF09\u3002\u8FD9\u91CC\u7528\u6587\u4EF6\u72EC\u5360\u521B\u5EFA\u505A\u8DE8\u8FDB\u7A0B\u4E92\u65A5\u3002\n *\n * \u5B9E\u73B0\u8981\u70B9\uFF1A\n * - \u72EC\u5360\u521B\u5EFA\uFF08open O_EXCL\uFF09\u5728 POSIX \u4E0E Windows \u4E0A\u90FD\u662F\u539F\u5B50\u7684\uFF0C\u65E0\u9700 flock \u4F9D\u8D56\uFF1B\n * - \u9501\u5185\u5BB9 {pid, host, at, nonce}\uFF1A\u53EA\u6709 nonce \u4ECD\u5C5E\u4E8E\u81EA\u5DF1\u65F6\u624D unlink\uFF0C\u907F\u514D\u9648\u65E7\n * \u63A5\u7BA1\u540E\u628A\u65B0\u6301\u6709\u8005\u7684\u9501\u5220\u6389\uFF1B\n * - \u540C\u8FDB\u7A0B\u91CD\u5165\u6309\u8DEF\u5F84\u8BA1\u6570\uFF1A\u5185\u6838\u65B9\u6CD5\u4E92\u76F8\u8C03\u7528\uFF08gate \u2192 setStage \u2192 touch\uFF09\u4E0D\u4F1A\u81EA\u9501\uFF1B\n * - \u9648\u65E7\u5224\u5B9A\u89C1 `staleReason()`\uFF1A\u540C\u4E3B\u673A\u6B7B pid\u3001\u9501\u9F84\u8D85\u8FC7 staleMs\u3001\u6216\u8D85\u8FC7\u786C\u4E0A\u9650\u3002\n */\nimport { closeSync, existsSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeSync } from 'node:fs'\nimport { hostname } from 'node:os'\nimport { resolve } from 'node:path'\n\nimport { KsrwError } from './error.ts'\n\nexport const PROJECT_LOCK_FILE = '.ksrw.lock'\n/** \u9879\u76EE\u5E93\u767B\u8BB0\u8868\uFF08~/.ksrw/projects.json\uFF09\u7684\u8DE8\u8FDB\u7A0B\u9501\u3002 */\nexport const REGISTRY_LOCK_FILE = '.projects.lock'\n\nconst DEFAULT_TIMEOUT_MS = 5_000\nconst DEFAULT_STALE_MS = 30_000\nconst DEFAULT_POLL_MS = 20\n\nexport interface LockOptions {\n /** \u7B49\u9501\u4E0A\u9650\uFF1B\u8D85\u65F6\u629B LOCKED\u3002\u9ED8\u8BA4 5s\uFF0C\u53EF\u7528 KSRW_LOCK_TIMEOUT_MS \u8986\u76D6\u3002 */\n timeoutMs?: number\n /** \u8D85\u8FC7\u8BE5\u9501\u9F84\u89C6\u4E3A\u9648\u65E7\uFF08\u6301\u6709\u8005\u53EF\u80FD\u5DF2\u5D29\uFF09\u3002\u9ED8\u8BA4 30s\uFF0CKSRW_LOCK_STALE_MS \u8986\u76D6\u3002 */\n staleMs?: number\n /** \u8F6E\u8BE2\u95F4\u9694\u3002\u9ED8\u8BA4 20ms\u3002 */\n pollMs?: number\n /** \u62A5\u9519\u91CC\u7684\u4EBA\u7C7B\u53EF\u8BFB\u5BF9\u8C61\uFF08\u9879\u76EE/\u767B\u8BB0\u8868\uFF09\u3002 */\n label?: string\n}\n\nexport interface LockInfo {\n readonly pid: number\n readonly host: string\n readonly at: number\n readonly nonce: string\n}\n\n/** \u8FDB\u7A0B\u5185\u91CD\u5165\u8BA1\u6570\uFF1Akey \u4E3A\u89E3\u6790\u540E\u7684\u9501\u6587\u4EF6\u7EDD\u5BF9\u8DEF\u5F84\u3002 */\nconst held = new Map<string, number>()\nconst nonces = new Map<string, string>()\n\n/** \u672C\u673A pid \u662F\u5426\u4ECD\u5B58\u5728\uFF08EPERM = \u5B58\u5728\u4F46\u5C5E\u4E8E\u522B\u7684\u7528\u6237\uFF09\u3002 */\nfunction pidAlive(pid: number): boolean {\n if (!Number.isInteger(pid) || pid <= 0) return false\n try {\n process.kill(pid, 0)\n return true\n } catch (error) {\n return (error as NodeJS.ErrnoException).code === 'EPERM'\n }\n}\n\n/** \u540C\u6B65\u7761\u7720\uFF1A\u9501\u662F\u540C\u6B65 API\uFF0C\u4E0D\u80FD\u7528 setTimeout\u3002 */\nfunction sleepSync(ms: number): void {\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms)\n}\n\nfunction envMs(name: string, fallback: number): number {\n const raw = process.env[name]?.trim()\n if (raw === undefined || raw === '') return fallback\n const value = Number(raw)\n return Number.isFinite(value) && value >= 0 ? value : fallback\n}\n\nfunction readInfo(lockPath: string): LockInfo | null {\n try {\n const parsed = JSON.parse(readFileSync(lockPath, 'utf8')) as Partial<LockInfo>\n if (typeof parsed.pid !== 'number' || typeof parsed.at !== 'number') return null\n return {\n pid: parsed.pid,\n host: typeof parsed.host === 'string' ? parsed.host : '',\n at: parsed.at,\n nonce: typeof parsed.nonce === 'string' ? parsed.nonce : '',\n }\n } catch {\n return null\n }\n}\n\n/** \u9501\u6587\u4EF6\u5E74\u9F84\uFF08\u8BFB\u4E0D\u5230\u5185\u5BB9\u65F6\u9000\u56DE mtime\uFF0C\u5BB9\u5FCD\u5199\u5230\u4E00\u534A\u7684\u9501\uFF09\u3002 */\nfunction lockAge(lockPath: string, info: LockInfo | null, now: number): number {\n if (info !== null) return now - info.at\n try { return now - statSync(lockPath).mtimeMs } catch { return Number.POSITIVE_INFINITY }\n}\n\n/** \u9648\u65E7\u5224\u5B9A\uFF1A\u8FD4\u56DE\u539F\u56E0\uFF08\u7528\u4E8E\u65E5\u5FD7/\u62A5\u9519\uFF09\uFF0C\u4ECD\u6709\u6D3B\u6301\u6709\u8005\u65F6\u8FD4\u56DE null\u3002 */\nfunction staleReason(lockPath: string, staleMs: number, now: number): string | null {\n const info = readInfo(lockPath)\n const age = lockAge(lockPath, info, now)\n if (info !== null && info.host === hostname() && !pidAlive(info.pid)) {\n return '\u6301\u6709\u8FDB\u7A0B\u5DF2\u9000\u51FA\uFF08pid ' + info.pid + '\uFF09'\n }\n // \u786C\u4E0A\u9650\uFF1Apid \u88AB\u590D\u7528\u3001\u6301\u6709\u8005\u6302\u6B7B\uFF08\u8C03\u8BD5\u5668\u6682\u505C\uFF09\u6216\u5199\u5230\u4E00\u534A\u5D29\u6E83\u3002\n if (age > Math.max(staleMs * 4, staleMs + 60_000)) return '\u9501\u9F84\u8D85\u8FC7\u786C\u4E0A\u9650\uFF08' + Math.round(age / 1000) + 's\uFF09'\n if (age > staleMs && (info === null || info.host !== hostname() || !pidAlive(info.pid))) {\n return '\u9501\u9F84\u8D85\u8FC7 ' + Math.round(staleMs / 1000) + 's \u4E14\u65E0\u6CD5\u786E\u8BA4\u6301\u6709\u8005'\n }\n return null\n}\n\nfunction tryBreak(lockPath: string, staleMs: number, now: number): boolean {\n if (staleReason(lockPath, staleMs, now) === null) return false\n // P2-7\uFF1A\u9648\u65E7\u63A5\u7BA1\u4E0D\u8981\u65E0\u6761\u4EF6 unlink\u3002\u5148\u628A\u9501\u6587\u4EF6\u539F\u5B50 rename \u5230\u552F\u4E00\u9694\u79BB\u540D\uFF0C\n // \u518D\u6838\u5BF9\u5185\u5BB9\u91CC\u4ECD\u662F\u540C\u4E00\u628A\u9501\uFF1B\u671F\u95F4\u88AB\u522B\u4EBA\u6362\u6389\u5C31\u539F\u6837\u653E\u56DE\uFF0C\u7EDD\u4E0D\u5220\u6D3B\u9501\u3002\n const before = readInfo(lockPath)\n const expectedNonce = before?.nonce ?? ''\n const quarantine = lockPath + '.stale.' + process.pid.toString(36) + '.' + Math.random().toString(36).slice(2, 8)\n try { renameSync(lockPath, quarantine) } catch { return false }\n const moved = readInfo(quarantine)\n if (moved !== null && expectedNonce !== '' && moved.nonce !== expectedNonce) {\n // \u8BFB\u9648\u65E7\u4E0E rename \u4E4B\u95F4\u6709\u4EBA\u63A5\u7BA1\uFF1A\u8DEF\u5F84\u7A7A\u5219\u653E\u56DE\uFF1B\u5DF2\u88AB\u518D\u6B21\u5360\u7528\u5C31\u7559\u5728\u9694\u79BB\u540D\uFF0C\n // \u8BA9\u6301\u6709\u8005\u81EA\u5DF1 release\uFF08\u5B81\u53EF\u4E0D\u5220\uFF0C\u4E5F\u4E0D\u80FD\u5220\u6389\u6D3B\u9501\uFF09\u3002\n if (!existsSync(lockPath)) {\n try { renameSync(quarantine, lockPath) } catch { /* \u4FDD\u7559\u9694\u79BB\u6587\u4EF6 */ }\n }\n return false\n }\n try { unlinkSync(quarantine) } catch { /* \u5DF2\u88AB\u6E05\u7406 */ }\n return true\n}\n\n/** \u5F53\u524D\u9879\u76EE\u9501\u662F\u5426\u88AB\u300C\u53E6\u4E00\u4E2A\u4ECD\u6D3B\u7740\u7684\u8FDB\u7A0B\u300D\u6301\u6709\uFF08API \u5C42\u5FEB\u901F\u5931\u8D25\u7528\uFF0C\u8FD4\u56DE null \u8868\u793A\u53EF\u76F4\u63A5\u5199\uFF09\u3002 */\nexport function projectLockOwner(projectPath: string): LockInfo | null {\n const lockPath = resolve(projectPath, PROJECT_LOCK_FILE)\n const info = readInfo(lockPath)\n if (info === null) return null\n if (staleReason(lockPath, envMs('KSRW_LOCK_STALE_MS', DEFAULT_STALE_MS), Date.now()) !== null) return null\n if (info.host === hostname() && info.pid === process.pid) return null\n return info\n}\n\nfunction acquire(lockPath: string, options: LockOptions): void {\n const timeoutMs = options.timeoutMs ?? envMs('KSRW_LOCK_TIMEOUT_MS', DEFAULT_TIMEOUT_MS)\n const staleMs = options.staleMs ?? envMs('KSRW_LOCK_STALE_MS', DEFAULT_STALE_MS)\n const pollMs = options.pollMs ?? DEFAULT_POLL_MS\n const label = options.label ?? '\u9879\u76EE\u76EE\u5F55'\n const deadline = Date.now() + timeoutMs\n const nonce = process.pid.toString(36) + '-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 10)\n const payload = JSON.stringify({ pid: process.pid, host: hostname(), at: Date.now(), nonce } satisfies LockInfo)\n let explained = ''\n for (;;) {\n try {\n const fd = openSync(lockPath, 'wx', 0o600)\n try { writeSync(fd, payload) } finally { closeSync(fd) }\n nonces.set(lockPath, nonce)\n return\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code\n if (code !== 'EEXIST') {\n throw new KsrwError('INTERNAL', '\u65E0\u6CD5\u521B\u5EFA\u5199\u9501 ' + lockPath + '\uFF1A' + (error as Error).message)\n }\n }\n const now = Date.now()\n const reason = staleReason(lockPath, staleMs, now)\n if (reason !== null && tryBreak(lockPath, staleMs, now)) continue\n if (now >= deadline) {\n const info = readInfo(lockPath)\n const holder = info === null\n ? '\u672A\u77E5\u6301\u6709\u8005'\n : 'pid ' + info.pid + (info.host === hostname() ? '' : ' @ ' + info.host) + '\uFF0C\u5DF2\u6301\u6709 ' + Math.round((now - info.at) / 1000) + 's'\n throw new KsrwError(\n 'LOCKED',\n label + '\u6B63\u88AB\u5176\u4ED6\u8FDB\u7A0B\u5199\u5165\uFF08' + holder + '\uFF09\uFF0C\u7B49\u5F85 ' + timeoutMs + 'ms \u540E\u4ECD\u672A\u91CA\u653E\u3002'\n + (explained === '' ? '' : '\u4E0A\u6B21\u8BCA\u65AD\uFF1A' + explained),\n )\n }\n explained = reason ?? ''\n sleepSync(Math.min(pollMs, Math.max(1, deadline - now)))\n }\n}\n\nfunction release(lockPath: string): void {\n const depth = held.get(lockPath) ?? 0\n if (depth > 1) {\n held.set(lockPath, depth - 1)\n return\n }\n held.delete(lockPath)\n const nonce = nonces.get(lockPath)\n nonces.delete(lockPath)\n const info = readInfo(lockPath)\n // \u53EA\u6709\u9501\u4ECD\u5C5E\u4E8E\u81EA\u5DF1\u624D\u5220\uFF1A\u9648\u65E7\u63A5\u7BA1\u540E\u5220\u522B\u4EBA\u7684\u9501\u4F1A\u8BA9\u4E24\u4E2A\u5199\u5165\u8005\u540C\u65F6\u8FDB\u4E34\u754C\u533A\u3002\n if (info !== null && nonce !== undefined && info.nonce !== nonce) return\n try { unlinkSync(lockPath) } catch { /* \u5DF2\u88AB\u6E05\u7406 */ }\n}\n\n/**\n * \u5728\u8DE8\u8FDB\u7A0B\u6392\u4ED6\u9501\u5185\u6267\u884C\u4E00\u6BB5\u540C\u6B65\u903B\u8F91\u3002\n * @param lockPath - \u9501\u6587\u4EF6\u7EDD\u5BF9\u8DEF\u5F84\uFF08\u4E0D\u5B58\u5728\u65F6\u521B\u5EFA\uFF09\u3002\n * @param fn - \u4E34\u754C\u533A\uFF1B\u629B\u9519\u4E5F\u4F1A\u91CA\u653E\u9501\u3002\n * @param options - \u8D85\u65F6/\u9648\u65E7/\u6807\u7B7E\u3002\n * @returns fn \u7684\u8FD4\u56DE\u503C\u3002\n * @throws KsrwError LOCKED \u7B49\u9501\u8D85\u65F6\u3002\n */\nexport function withFileLock<T>(lockPath: string, fn: () => T, options: LockOptions = {}): T {\n const key = resolve(lockPath)\n const depth = held.get(key) ?? 0\n if (depth === 0) acquire(key, options)\n held.set(key, depth + 1)\n try {\n return fn()\n } finally {\n release(key)\n }\n}\n\n/** \u9879\u76EE\u76EE\u5F55\u5199\u9501\uFF1A`<project>/.ksrw.lock`\u3002 */\nexport function withProjectLock<T>(projectPath: string, fn: () => T, options: LockOptions = {}): T {\n return withFileLock(resolve(projectPath, PROJECT_LOCK_FILE), fn, { label: '\u9879\u76EE ' + resolve(projectPath), ...options })\n}\n\n/** \u9879\u76EE\u5E93\u767B\u8BB0\u8868\u5199\u9501\uFF1A`<home>/.projects.lock`\u3002 */\nexport function withRegistryLock<T>(home: string, fn: () => T, options: LockOptions = {}): T {\n return withFileLock(resolve(home, REGISTRY_LOCK_FILE), fn, { label: '\u9879\u76EE\u5E93\u767B\u8BB0\u8868', ...options })\n}\n\n/** \u8BCA\u65AD\u7528\uFF1A\u5F53\u524D\u9501\u6301\u6709\u8005\uFF08\u65E0\u9501\u8FD4\u56DE null\uFF09\u3002 */\nexport function readProjectLock(projectPath: string): LockInfo | null {\n return readInfo(resolve(projectPath, PROJECT_LOCK_FILE))\n}\n", "/** arXiv Atom API \u5BA2\u6237\u7AEF\uFF08docs/02-design/0203 \u00A71 `arxiv_search` \u6570\u636E\u9762\uFF09\u3002\n *\n * \u7EAA\u5F8B\uFF1A\u5BFC\u51FA API \u9650\u901F 3 \u79D2/\u6B21\uFF08\u5B98\u65B9 politeness \u7EA6\u5B9A\uFF09\uFF1B429/503 \u4F18\u5148\u9075\u5FAA\n * Retry-After\uFF1B\u5168\u7F51\u7EDC\u9762\u53EF\u6CE8\u5165 fetchFn\uFF08\u6D4B\u8BD5\u7528\u51BB\u7ED3\u6837\u672C\uFF09\u3002\n * Atom \u89E3\u6790\u7528\u5BB9\u9519\u6B63\u5219\u63D0\u53D6\uFF08\u547D\u540D\u7A7A\u95F4\u65E0\u5173\uFF09\uFF0C\u4E0D\u5F15\u5165 XML \u4F9D\u8D56\u3002\n */\n\nimport { KsrwError } from './error.ts'\n\nconst API = 'https://export.arxiv.org/api/query'\nconst MIN_INTERVAL_MS = 3_000\n\nexport interface ArxivPaper {\n readonly arxivId: string\n readonly title: string\n readonly authors: string[]\n readonly abstract: string\n readonly categories: string[]\n readonly published: string\n readonly updated: string\n readonly pdfUrl?: string\n readonly absUrl: string\n}\n\nexport interface ArxivSearchOptions {\n readonly query: string\n readonly maxResults?: number\n readonly sortBy?: 'relevance' | 'lastUpdatedDate' | 'submittedDate'\n readonly sortOrder?: 'ascending' | 'descending'\n readonly categoryId?: string\n readonly signal?: AbortSignal\n}\n\nexport type FetchFn = (url: string, init?: { signal?: AbortSignal; headers?: Record<string, string> }) => Promise<{ ok: boolean; status: number; headers: { get(name: string): string | null }; text: () => Promise<string>; arrayBuffer: () => Promise<ArrayBuffer> }>\n\nexport interface ArxivClient {\n search(options: ArxivSearchOptions): Promise<ArxivPaper[]>\n fetchByIds(ids: readonly string[], signal?: AbortSignal): Promise<ArxivPaper[]>\n downloadPdf(paper: ArxivPaper, destPath: string, signal?: AbortSignal): Promise<void>\n}\n\nexport function createArxivClient(fetchFn: FetchFn = defaultFetch): ArxivClient {\n let lastCall = 0\n\n async function throttle(signal?: AbortSignal): Promise<void> {\n const wait = lastCall + MIN_INTERVAL_MS - Date.now()\n if (wait > 0) await sleep(wait, signal)\n lastCall = Date.now()\n }\n\n async function request(url: string, signal?: AbortSignal): Promise<string> {\n await throttle(signal)\n for (let attempt = 0; ; attempt++) {\n const res = await fetchFn(url, { signal })\n if (res.ok) return await res.text()\n if (res.status === 429 || res.status === 503) {\n const retryAfter = Number(res.headers.get('retry-after'))\n const waitMs = Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter * 1000 : Math.min(30_000, 2_000 * 2 ** attempt)\n if (attempt >= 2) throw new KsrwError('RATE_LIMITED', `arXiv \u9650\u6D41\uFF08HTTP ${res.status}\uFF09\uFF0C\u5DF2\u91CD\u8BD5 2 \u6B21\u4ECD\u5931\u8D25\uFF1B\u7A0D\u540E\u518D\u8BD5`)\n await sleep(waitMs, signal)\n continue\n }\n throw new KsrwError('NETWORK', `arXiv \u8BF7\u6C42\u5931\u8D25\uFF1AHTTP ${res.status}`)\n }\n }\n\n return {\n async search(options) {\n const max = clamp(options.maxResults ?? 10, 1, 30)\n const q = buildQuery(options.query, options.categoryId)\n const url = `${API}?search_query=${encodeURIComponent(q)}&start=0&max_results=${max}` +\n `&sortBy=${options.sortBy ?? 'relevance'}&sortOrder=${options.sortOrder ?? 'descending'}`\n return parseAtom(await request(url, options.signal))\n },\n\n async fetchByIds(ids, signal) {\n if (ids.length === 0) return []\n const idList = ids.map(normalizeId).join(',')\n const url = `${API}?id_list=${encodeURIComponent(idList)}&max_results=${ids.length}`\n return parseAtom(await request(url, signal))\n },\n\n async downloadPdf(paper, destPath, signal) {\n const url = paper.pdfUrl ?? `https://arxiv.org/pdf/${paper.arxivId}`\n await throttle(signal)\n const res = await fetchFn(url, { signal })\n if (!res.ok) throw new KsrwError('NETWORK', `PDF \u4E0B\u8F7D\u5931\u8D25\uFF1AHTTP ${res.status}`)\n const { mkdirSync, writeFileSync, renameSync } = await import('node:fs')\n const { dirname } = await import('node:path')\n const buf = Buffer.from(await res.arrayBuffer())\n if (buf.subarray(0, 5).toString() !== '%PDF-') {\n throw new KsrwError('NETWORK', '\u4E0B\u8F7D\u5185\u5BB9\u4E0D\u662F PDF\uFF08\u53EF\u80FD\u88AB\u91CD\u5B9A\u5411\u5230\u9A8C\u8BC1\u9875\uFF09')\n }\n mkdirSync(dirname(destPath), { recursive: true })\n const tmp = `${destPath}.tmp-${Date.now()}`\n writeFileSync(tmp, buf, { mode: 0o600 })\n renameSync(tmp, destPath)\n },\n }\n}\n\n/** \u67E5\u8BE2\u7EC4\u88C5\uFF1A\u88F8\u8BCD\u7EC4\u8D70 all: \u524D\u7F00\uFF1B\u5DF2\u5E26\u5B57\u6BB5\u524D\u7F00\uFF08ti:/au:/abs:/cat:/all:\uFF09\u539F\u6837\u4FDD\u7559\u3002 */\nexport function buildQuery(query: string, categoryId?: string): string {\n const trimmed = query.trim()\n const terms = /(^|\\s)(ti:|au:|abs:|cat:|all:)/.test(trimmed) || trimmed === ''\n ? trimmed\n : `all:${trimmed}`\n const parts = [terms]\n if (categoryId !== undefined && categoryId.trim() !== '') parts.push(`cat:${categoryId.trim()}`)\n return parts.filter(p => p !== '').join(' AND ')\n}\n\nexport function normalizeId(raw: string): string {\n return raw.trim().replace(/^https?:\\/\\/arxiv\\.org\\/(abs|pdf)\\//, '').replace(/\\.pdf$/, '').replace(/^arXiv:/, '')\n}\n\n// \u2500\u2500 Atom \u89E3\u6790\uFF08\u5BB9\u9519\uFF1A\u5B9E\u4F53\u53CD\u8F6C\u4E49 + \u6807\u7B7E\u5185\u6587\u672C\u63D0\u53D6\uFF09\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function parseAtom(xml: string): ArxivPaper[] {\n const papers: ArxivPaper[] = []\n const entries = xml.split(/<entry[\\s>]/).slice(1)\n for (const entry of entries) {\n const end = entry.indexOf('</entry>')\n const body = end === -1 ? entry : entry.slice(0, end)\n const idUrl = extract(body, 'id') ?? ''\n const arxivId = normalizeId(idUrl)\n if (arxivId === '') continue\n const absUrl = /^https?:\\/\\/arxiv\\.org\\/abs\\//.test(idUrl) ? idUrl : `https://arxiv.org/abs/${arxivId}`\n papers.push({\n arxivId,\n title: clean(extract(body, 'title') ?? ''),\n authors: collectAll(body, 'name'),\n abstract: clean(extract(body, 'summary') ?? ''),\n categories: collectAttrAll(body, 'category', 'term'),\n published: extract(body, 'published') ?? '',\n updated: extract(body, 'updated') ?? '',\n pdfUrl: extractLink(body, 'title=\"pdf\"') ?? `https://arxiv.org/pdf/${arxivId}`,\n absUrl,\n })\n }\n return papers\n}\n\nfunction extract(haystack: string, tag: string): string | undefined {\n const m = haystack.match(new RegExp(`<${tag}(?:\\\\s[^>]*)?>([\\\\s\\\\S]*?)</${tag}>`))\n return m?.[1]\n}\n\nfunction collectAll(haystack: string, tag: string): string[] {\n return [...haystack.matchAll(new RegExp(`<${tag}(?:\\\\s[^>]*)?>([\\\\s\\\\S]*?)</${tag}>`, 'g'))]\n .map(m => clean(m[1] ?? ''))\n .filter(s => s !== '')\n}\n\nfunction collectAttrAll(haystack: string, tag: string, attr: string): string[] {\n return [...haystack.matchAll(new RegExp(`<${tag}\\\\s[^>]*?${attr}=\"([^\"]*)\"`, 'g'))]\n .map(m => m[1] ?? '')\n .filter(s => s !== '')\n}\n\nfunction extractLink(haystack: string, marker: string): string | undefined {\n const m = haystack.match(new RegExp(`<link\\\\s[^>]*?${marker}[^>]*?href=\"([^\"]+)\"`))\n return m?.[1]\n}\n\nfunction clean(s: string): string {\n return s\n .replace(/<[^>]+>/g, ' ')\n .replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&').replace(/&quot;/g, '\"').replace(/&#39;/g, \"'\")\n .replace(/\\s+/g, ' ')\n .trim()\n}\n\nfunction clamp(n: number, lo: number, hi: number): number {\n return Math.min(hi, Math.max(lo, n))\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n const t = setTimeout(resolve, ms)\n signal?.addEventListener('abort', () => {\n clearTimeout(t)\n reject(new KsrwError('ABORTED', '\u5DF2\u4E2D\u6B62'))\n }, { once: true })\n })\n}\n\nconst defaultFetch: FetchFn = (url, init) => globalThis.fetch(url, init as RequestInit) as unknown as Promise<{ ok: boolean; status: number; headers: { get(name: string): string | null }; text: () => Promise<string>; arrayBuffer: () => Promise<ArrayBuffer> }>\n", "import { createRequire } from 'module';\nvar require = createRequire('/');\n// DEFLATE is a complex format; to read this code, you should probably check the RFC first:\n// https://tools.ietf.org/html/rfc1951\n// You may also wish to take a look at the guide I made about this program:\n// https://gist.github.com/101arrowz/253f31eb5abc3d9275ab943003ffecad\n// Some of the following code is similar to that of UZIP.js:\n// https://github.com/photopea/UZIP.js\n// However, the vast majority of the codebase has diverged from UZIP.js to increase performance and reduce bundle size.\n// Sometimes 0 will appear where -1 would be more appropriate. This is because using a uint\n// is better for memory in most engines (I *think*).\nvar _a;\n// Mediocre shim\nvar Worker;\nvar isMarkedAsUntransferable;\nvar workerAdd = \";var __w=require('worker_threads');__w.parentPort.on('message',function(m){onmessage({data:m})}),postMessage=function(m,t){__w.parentPort.postMessage(m,t)},close=process.exit;self=global\";\ntry {\n (_a = require('worker_threads'), Worker = _a.Worker, isMarkedAsUntransferable = _a.isMarkedAsUntransferable);\n}\ncatch (e) {\n}\nvar wk = Worker ? function (c, _, msg, transfer, cb) {\n var done = false;\n var w = new Worker(c + workerAdd, { eval: true })\n .on('error', function (e) { return cb(e, null); })\n .on('message', function (m) { return cb(null, m); })\n .on('exit', function (c) {\n if (c && !done)\n cb(new Error('exited with code ' + c), null);\n });\n if (isMarkedAsUntransferable)\n transfer = transfer.filter(function (t) { return !isMarkedAsUntransferable(t); });\n w.postMessage(msg, transfer);\n w.terminate = function () {\n done = true;\n return Worker.prototype.terminate.call(w);\n };\n return w;\n} : function (_, __, ___, ____, cb) {\n setImmediate(function () { return cb(new Error('async operations unsupported - update to Node 12+ (or Node 10-11 with the --experimental-worker CLI flag)'), null); });\n var NOP = function () { };\n return {\n terminate: NOP,\n postMessage: NOP\n };\n};\n\n// aliases for shorter compressed code (most minifers don't do this)\nvar u8 = Uint8Array, u16 = Uint16Array, i32 = Int32Array;\n// fixed length extra bits\nvar fleb = new u8([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, /* unused */ 0, 0, /* impossible */ 0]);\n// fixed distance extra bits\nvar fdeb = new u8([0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, /* unused */ 0, 0]);\n// code length index map\nvar clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);\n// get base, reverse index map from extra bits\nvar freb = function (eb, start) {\n var b = new u16(31);\n for (var i = 0; i < 31; ++i) {\n b[i] = start += 1 << eb[i - 1];\n }\n // numbers here are at max 18 bits\n var r = new i32(b[30]);\n for (var i = 1; i < 30; ++i) {\n for (var j = b[i]; j < b[i + 1]; ++j) {\n r[j] = ((j - b[i]) << 5) | i;\n }\n }\n return { b: b, r: r };\n};\nvar _a = freb(fleb, 2), fl = _a.b, revfl = _a.r;\n// we can ignore the fact that the other numbers are wrong; they never happen anyway\nfl[28] = 258, revfl[258] = 28;\nvar _b = freb(fdeb, 0), fd = _b.b, revfd = _b.r;\n// map of value to reverse (assuming 16 bits)\nvar rev = new u16(32768);\nfor (var i = 0; i < 32768; ++i) {\n // reverse table algorithm from SO\n var x = ((i & 0xAAAA) >> 1) | ((i & 0x5555) << 1);\n x = ((x & 0xCCCC) >> 2) | ((x & 0x3333) << 2);\n x = ((x & 0xF0F0) >> 4) | ((x & 0x0F0F) << 4);\n rev[i] = (((x & 0xFF00) >> 8) | ((x & 0x00FF) << 8)) >> 1;\n}\n// create huffman tree from u8 \"map\": index -> code length for code index\n// mb (max bits) must be at most 15\n// TODO: optimize/split up?\nvar hMap = (function (cd, mb, r) {\n var s = cd.length;\n // index\n var i = 0;\n // u16 \"map\": index -> # of codes with bit length = index\n var l = new u16(mb);\n // length of cd must be 288 (total # of codes)\n for (; i < s; ++i) {\n if (cd[i])\n ++l[cd[i] - 1];\n }\n // u16 \"map\": index -> minimum code for bit length = index\n var le = new u16(mb);\n for (i = 1; i < mb; ++i) {\n le[i] = (le[i - 1] + l[i - 1]) << 1;\n }\n var co;\n if (r) {\n // u16 \"map\": index -> number of actual bits, symbol for code\n co = new u16(1 << mb);\n // bits to remove for reverser\n var rvb = 15 - mb;\n for (i = 0; i < s; ++i) {\n // ignore 0 lengths\n if (cd[i]) {\n // num encoding both symbol and bits read\n var sv = (i << 4) | cd[i];\n // free bits\n var r_1 = mb - cd[i];\n // start value\n var v = le[cd[i] - 1]++ << r_1;\n // m is end value\n for (var m = v | ((1 << r_1) - 1); v <= m; ++v) {\n // every 16 bit value starting with the code yields the same result\n co[rev[v] >> rvb] = sv;\n }\n }\n }\n }\n else {\n co = new u16(s);\n for (i = 0; i < s; ++i) {\n if (cd[i]) {\n co[i] = rev[le[cd[i] - 1]++] >> (15 - cd[i]);\n }\n }\n }\n return co;\n});\n// fixed length tree\nvar flt = new u8(288);\nfor (var i = 0; i < 144; ++i)\n flt[i] = 8;\nfor (var i = 144; i < 256; ++i)\n flt[i] = 9;\nfor (var i = 256; i < 280; ++i)\n flt[i] = 7;\nfor (var i = 280; i < 288; ++i)\n flt[i] = 8;\n// fixed distance tree\nvar fdt = new u8(32);\nfor (var i = 0; i < 32; ++i)\n fdt[i] = 5;\n// fixed length map\nvar flm = /*#__PURE__*/ hMap(flt, 9, 0), flrm = /*#__PURE__*/ hMap(flt, 9, 1);\n// fixed distance map\nvar fdm = /*#__PURE__*/ hMap(fdt, 5, 0), fdrm = /*#__PURE__*/ hMap(fdt, 5, 1);\n// find max of array\nvar max = function (a) {\n var m = a[0];\n for (var i = 1; i < a.length; ++i) {\n if (a[i] > m)\n m = a[i];\n }\n return m;\n};\n// read d, starting at bit p and mask with m\nvar bits = function (d, p, m) {\n var o = (p / 8) | 0;\n return ((d[o] | (d[o + 1] << 8)) >> (p & 7)) & m;\n};\n// read d, starting at bit p continuing for at least 16 bits\nvar bits16 = function (d, p) {\n var o = (p / 8) | 0;\n return ((d[o] | (d[o + 1] << 8) | (d[o + 2] << 16)) >> (p & 7));\n};\n// get end of byte\nvar shft = function (p) { return ((p + 7) / 8) | 0; };\n// typed array slice - allows garbage collector to free original reference,\n// while being more compatible than .slice\nvar slc = function (v, s, e) {\n if (s == null || s < 0)\n s = 0;\n if (e == null || e > v.length)\n e = v.length;\n // can't use .constructor in case user-supplied\n return new u8(v.subarray(s, e));\n};\n/**\n * Codes for errors generated within this library\n */\nexport var FlateErrorCode = {\n UnexpectedEOF: 0,\n InvalidBlockType: 1,\n InvalidLengthLiteral: 2,\n InvalidDistance: 3,\n StreamFinished: 4,\n NoStreamHandler: 5,\n InvalidHeader: 6,\n NoCallback: 7,\n InvalidUTF8: 8,\n ExtraFieldTooLong: 9,\n InvalidDate: 10,\n FilenameTooLong: 11,\n StreamFinishing: 12,\n InvalidZipData: 13,\n UnknownCompressionMethod: 14\n};\n// error codes\nvar ec = [\n 'unexpected EOF',\n 'invalid block type',\n 'invalid length/literal',\n 'invalid distance',\n 'stream finished',\n 'no stream handler',\n , // determined by compression function\n 'no callback',\n 'invalid UTF-8 data',\n 'extra field too long',\n 'date not in range 1980-2099',\n 'filename too long',\n 'stream finishing',\n 'invalid zip data'\n // determined by unknown compression method\n];\n;\nvar err = function (ind, msg, nt) {\n var e = new Error(msg || ec[ind]);\n e.code = ind;\n if (Error.captureStackTrace)\n Error.captureStackTrace(e, err);\n if (!nt)\n throw e;\n return e;\n};\n// expands raw DEFLATE data\nvar inflt = function (dat, st, buf, dict) {\n // source length dict length\n var sl = dat.length, dl = dict ? dict.length : 0;\n if (!sl || st.f && !st.l)\n return buf || new u8(0);\n var noBuf = !buf;\n // have to estimate size\n var resize = noBuf || st.i != 2;\n // no state\n var noSt = st.i;\n // Assumes roughly 33% compression ratio average\n if (noBuf)\n buf = new u8(sl * 3);\n // ensure buffer can fit at least l elements\n var cbuf = function (l) {\n var bl = buf.length;\n // need to increase size to fit\n if (l > bl) {\n // Double or set to necessary, whichever is greater\n var nbuf = new u8(Math.max(bl * 2, l));\n nbuf.set(buf);\n buf = nbuf;\n }\n };\n // last chunk bitpos bytes\n var final = st.f || 0, pos = st.p || 0, bt = st.b || 0, lm = st.l, dm = st.d, lbt = st.m, dbt = st.n;\n // total bits\n var tbts = sl * 8;\n do {\n if (!lm) {\n // BFINAL - this is only 1 when last chunk is next\n final = bits(dat, pos, 1);\n // type: 0 = no compression, 1 = fixed huffman, 2 = dynamic huffman\n var type = bits(dat, pos + 1, 3);\n pos += 3;\n if (!type) {\n // go to end of byte boundary\n var s = shft(pos) + 4, l = dat[s - 4] | (dat[s - 3] << 8), t = s + l;\n if (t > sl) {\n if (noSt)\n err(0);\n break;\n }\n // ensure size\n if (resize)\n cbuf(bt + l);\n // Copy over uncompressed data\n buf.set(dat.subarray(s, t), bt);\n // Get new bitpos, update byte count\n st.b = bt += l, st.p = pos = t * 8, st.f = final;\n continue;\n }\n else if (type == 1)\n lm = flrm, dm = fdrm, lbt = 9, dbt = 5;\n else if (type == 2) {\n // literal lengths\n var hLit = bits(dat, pos, 31) + 257, hcLen = bits(dat, pos + 10, 15) + 4;\n var tl = hLit + bits(dat, pos + 5, 31) + 1;\n pos += 14;\n // length+distance tree\n var ldt = new u8(tl);\n // code length tree\n var clt = new u8(19);\n for (var i = 0; i < hcLen; ++i) {\n // use index map to get real code\n clt[clim[i]] = bits(dat, pos + i * 3, 7);\n }\n pos += hcLen * 3;\n // code lengths bits\n var clb = max(clt), clbmsk = (1 << clb) - 1;\n // code lengths map\n var clm = hMap(clt, clb, 1);\n for (var i = 0; i < tl;) {\n var r = clm[bits(dat, pos, clbmsk)];\n // bits read\n pos += r & 15;\n // symbol\n var s = r >> 4;\n // code length to copy\n if (s < 16) {\n ldt[i++] = s;\n }\n else {\n // copy count\n var c = 0, n = 0;\n if (s == 16)\n n = 3 + bits(dat, pos, 3), pos += 2, c = ldt[i - 1];\n else if (s == 17)\n n = 3 + bits(dat, pos, 7), pos += 3;\n else if (s == 18)\n n = 11 + bits(dat, pos, 127), pos += 7;\n while (n--)\n ldt[i++] = c;\n }\n }\n // length tree distance tree\n var lt = ldt.subarray(0, hLit), dt = ldt.subarray(hLit);\n // max length bits\n lbt = max(lt);\n // max dist bits\n dbt = max(dt);\n lm = hMap(lt, lbt, 1);\n dm = hMap(dt, dbt, 1);\n }\n else\n err(1);\n if (pos > tbts) {\n if (noSt)\n err(0);\n break;\n }\n }\n // Make sure the buffer can hold this + the largest possible addition\n // Maximum chunk size (practically, theoretically infinite) is 2^17\n if (resize)\n cbuf(bt + 131072);\n var lms = (1 << lbt) - 1, dms = (1 << dbt) - 1;\n var lpos = pos;\n for (;; lpos = pos) {\n // bits read, code\n var c = lm[bits16(dat, pos) & lms], sym = c >> 4;\n pos += c & 15;\n if (pos > tbts) {\n if (noSt)\n err(0);\n break;\n }\n if (!c)\n err(2);\n if (sym < 256)\n buf[bt++] = sym;\n else if (sym == 256) {\n lpos = pos, lm = null;\n break;\n }\n else {\n var add = sym - 254;\n // no extra bits needed if less\n if (sym > 264) {\n // index\n var i = sym - 257, b = fleb[i];\n add = bits(dat, pos, (1 << b) - 1) + fl[i];\n pos += b;\n }\n // dist\n var d = dm[bits16(dat, pos) & dms], dsym = d >> 4;\n if (!d)\n err(3);\n pos += d & 15;\n var dt = fd[dsym];\n if (dsym > 3) {\n var b = fdeb[dsym];\n dt += bits16(dat, pos) & (1 << b) - 1, pos += b;\n }\n if (pos > tbts) {\n if (noSt)\n err(0);\n break;\n }\n if (resize)\n cbuf(bt + 131072);\n var end = bt + add;\n if (bt < dt) {\n var shift = dl - dt, dend = Math.min(dt, end);\n if (shift + bt < 0)\n err(3);\n for (; bt < dend; ++bt)\n buf[bt] = dict[shift + bt];\n }\n for (; bt < end; ++bt)\n buf[bt] = buf[bt - dt];\n }\n }\n st.l = lm, st.p = lpos, st.b = bt, st.f = final;\n if (lm)\n final = 1, st.m = lbt, st.d = dm, st.n = dbt;\n } while (!final);\n // don't reallocate for streams or user buffers\n return bt != buf.length && noBuf ? slc(buf, 0, bt) : buf.subarray(0, bt);\n};\n// starting at p, write the minimum number of bits that can hold v to d\nvar wbits = function (d, p, v) {\n v <<= p & 7;\n var o = (p / 8) | 0;\n d[o] |= v;\n d[o + 1] |= v >> 8;\n};\n// starting at p, write the minimum number of bits (>8) that can hold v to d\nvar wbits16 = function (d, p, v) {\n v <<= p & 7;\n var o = (p / 8) | 0;\n d[o] |= v;\n d[o + 1] |= v >> 8;\n d[o + 2] |= v >> 16;\n};\n// creates code lengths from a frequency table\nvar hTree = function (d, mb) {\n // Need extra info to make a tree\n var t = [];\n for (var i = 0; i < d.length; ++i) {\n if (d[i])\n t.push({ s: i, f: d[i] });\n }\n var s = t.length;\n var t2 = t.slice();\n if (!s)\n return { t: et, l: 0 };\n if (s == 1) {\n var v = new u8(t[0].s + 1);\n v[t[0].s] = 1;\n return { t: v, l: 1 };\n }\n t.sort(function (a, b) { return a.f - b.f; });\n // after i2 reaches last ind, will be stopped\n // freq must be greater than largest possible number of symbols\n t.push({ s: -1, f: 25001 });\n var l = t[0], r = t[1], i0 = 0, i1 = 1, i2 = 2;\n t[0] = { s: -1, f: l.f + r.f, l: l, r: r };\n // efficient algorithm from UZIP.js\n // i0 is lookbehind, i2 is lookahead - after processing two low-freq\n // symbols that combined have high freq, will start processing i2 (high-freq,\n // non-composite) symbols instead\n // see https://reddit.com/r/photopea/comments/ikekht/uzipjs_questions/\n while (i1 != s - 1) {\n l = t[t[i0].f < t[i2].f ? i0++ : i2++];\n r = t[i0 != i1 && t[i0].f < t[i2].f ? i0++ : i2++];\n t[i1++] = { s: -1, f: l.f + r.f, l: l, r: r };\n }\n var maxSym = t2[0].s;\n for (var i = 1; i < s; ++i) {\n if (t2[i].s > maxSym)\n maxSym = t2[i].s;\n }\n // code lengths\n var tr = new u16(maxSym + 1);\n // max bits in tree\n var mbt = ln(t[i1 - 1], tr, 0);\n if (mbt > mb) {\n // more algorithms from UZIP.js\n // TODO: find out how this code works (debt)\n // ind debt\n var i = 0, dt = 0;\n // left cost\n var lft = mbt - mb, cst = 1 << lft;\n t2.sort(function (a, b) { return tr[b.s] - tr[a.s] || a.f - b.f; });\n for (; i < s; ++i) {\n var i2_1 = t2[i].s;\n if (tr[i2_1] > mb) {\n dt += cst - (1 << (mbt - tr[i2_1]));\n tr[i2_1] = mb;\n }\n else\n break;\n }\n dt >>= lft;\n while (dt > 0) {\n var i2_2 = t2[i].s;\n if (tr[i2_2] < mb)\n dt -= 1 << (mb - tr[i2_2]++ - 1);\n else\n ++i;\n }\n for (; i >= 0 && dt; --i) {\n var i2_3 = t2[i].s;\n if (tr[i2_3] == mb) {\n --tr[i2_3];\n ++dt;\n }\n }\n mbt = mb;\n }\n return { t: new u8(tr), l: mbt };\n};\n// get the max length and assign length codes\nvar ln = function (n, l, d) {\n return n.s == -1\n ? Math.max(ln(n.l, l, d + 1), ln(n.r, l, d + 1))\n : (l[n.s] = d);\n};\n// length codes generation\nvar lc = function (c) {\n var s = c.length;\n // Note that the semicolon was intentional\n while (s && !c[--s])\n ;\n var cl = new u16(++s);\n // ind num streak\n var cli = 0, cln = c[0], cls = 1;\n var w = function (v) { cl[cli++] = v; };\n for (var i = 1; i <= s; ++i) {\n if (c[i] == cln && i != s)\n ++cls;\n else {\n if (!cln && cls > 2) {\n for (; cls > 138; cls -= 138)\n w(32754);\n if (cls > 2) {\n w(cls > 10 ? ((cls - 11) << 5) | 28690 : ((cls - 3) << 5) | 12305);\n cls = 0;\n }\n }\n else if (cls > 3) {\n w(cln), --cls;\n for (; cls > 6; cls -= 6)\n w(8304);\n if (cls > 2)\n w(((cls - 3) << 5) | 8208), cls = 0;\n }\n while (cls--)\n w(cln);\n cls = 1;\n cln = c[i];\n }\n }\n return { c: cl.subarray(0, cli), n: s };\n};\n// calculate the length of output from tree, code lengths\nvar clen = function (cf, cl) {\n var l = 0;\n for (var i = 0; i < cl.length; ++i)\n l += cf[i] * cl[i];\n return l;\n};\n// writes a fixed block\n// returns the new bit pos\nvar wfblk = function (out, pos, dat) {\n // no need to write 00 as type: TypedArray defaults to 0\n var s = dat.length;\n var o = shft(pos + 2);\n out[o] = s & 255;\n out[o + 1] = s >> 8;\n out[o + 2] = out[o] ^ 255;\n out[o + 3] = out[o + 1] ^ 255;\n for (var i = 0; i < s; ++i)\n out[o + i + 4] = dat[i];\n return (o + 4 + s) * 8;\n};\n// writes a block\nvar wblk = function (dat, out, final, syms, lf, df, eb, li, bs, bl, p) {\n wbits(out, p++, final);\n ++lf[256];\n var _a = hTree(lf, 15), dlt = _a.t, mlb = _a.l;\n var _b = hTree(df, 15), ddt = _b.t, mdb = _b.l;\n var _c = lc(dlt), lclt = _c.c, nlc = _c.n;\n var _d = lc(ddt), lcdt = _d.c, ndc = _d.n;\n var lcfreq = new u16(19);\n for (var i = 0; i < lclt.length; ++i)\n ++lcfreq[lclt[i] & 31];\n for (var i = 0; i < lcdt.length; ++i)\n ++lcfreq[lcdt[i] & 31];\n var _e = hTree(lcfreq, 7), lct = _e.t, mlcb = _e.l;\n var nlcc = 19;\n for (; nlcc > 4 && !lct[clim[nlcc - 1]]; --nlcc)\n ;\n var flen = (bl + 5) << 3;\n var ftlen = clen(lf, flt) + clen(df, fdt) + eb;\n var dtlen = clen(lf, dlt) + clen(df, ddt) + eb + 14 + 3 * nlcc + clen(lcfreq, lct) + 2 * lcfreq[16] + 3 * lcfreq[17] + 7 * lcfreq[18];\n if (bs >= 0 && flen <= ftlen && flen <= dtlen)\n return wfblk(out, p, dat.subarray(bs, bs + bl));\n var lm, ll, dm, dl;\n wbits(out, p, 1 + (dtlen < ftlen)), p += 2;\n if (dtlen < ftlen) {\n lm = hMap(dlt, mlb, 0), ll = dlt, dm = hMap(ddt, mdb, 0), dl = ddt;\n var llm = hMap(lct, mlcb, 0);\n wbits(out, p, nlc - 257);\n wbits(out, p + 5, ndc - 1);\n wbits(out, p + 10, nlcc - 4);\n p += 14;\n for (var i = 0; i < nlcc; ++i)\n wbits(out, p + 3 * i, lct[clim[i]]);\n p += 3 * nlcc;\n var lcts = [lclt, lcdt];\n for (var it = 0; it < 2; ++it) {\n var clct = lcts[it];\n for (var i = 0; i < clct.length; ++i) {\n var len = clct[i] & 31;\n wbits(out, p, llm[len]), p += lct[len];\n if (len > 15)\n wbits(out, p, (clct[i] >> 5) & 127), p += clct[i] >> 12;\n }\n }\n }\n else {\n lm = flm, ll = flt, dm = fdm, dl = fdt;\n }\n for (var i = 0; i < li; ++i) {\n var sym = syms[i];\n if (sym > 255) {\n var len = (sym >> 18) & 31;\n wbits16(out, p, lm[len + 257]), p += ll[len + 257];\n if (len > 7)\n wbits(out, p, (sym >> 23) & 31), p += fleb[len];\n var dst = sym & 31;\n wbits16(out, p, dm[dst]), p += dl[dst];\n if (dst > 3)\n wbits16(out, p, (sym >> 5) & 8191), p += fdeb[dst];\n }\n else {\n wbits16(out, p, lm[sym]), p += ll[sym];\n }\n }\n wbits16(out, p, lm[256]);\n return p + ll[256];\n};\n// deflate options (nice << 13) | chain\nvar deo = /*#__PURE__*/ new i32([65540, 131080, 131088, 131104, 262176, 1048704, 1048832, 2114560, 2117632]);\n// empty\nvar et = /*#__PURE__*/ new u8(0);\n// compresses data into a raw DEFLATE buffer\nvar dflt = function (dat, lvl, plvl, pre, post, st) {\n var s = st.z || dat.length;\n var o = new u8(pre + s + 5 * (1 + Math.ceil(s / 7000)) + post);\n // writing to this writes to the output buffer\n var w = o.subarray(pre, o.length - post);\n var lst = st.l;\n var pos = (st.r || 0) & 7;\n if (lvl) {\n if (pos)\n w[0] = st.r >> 3;\n var opt = deo[lvl - 1];\n var n = opt >> 13, c = opt & 8191;\n var msk_1 = (1 << plvl) - 1;\n // prev 2-byte val map curr 2-byte val map\n var prev = st.p || new u16(32768), head = st.h || new u16(msk_1 + 1);\n var bs1_1 = Math.ceil(plvl / 3), bs2_1 = 2 * bs1_1;\n var hsh = function (i) { return (dat[i] ^ (dat[i + 1] << bs1_1) ^ (dat[i + 2] << bs2_1)) & msk_1; };\n // 24576 is an arbitrary number of maximum symbols per block\n // 424 buffer for last block\n var syms = new i32(25000);\n // length/literal freq distance freq\n var lf = new u16(288), df = new u16(32);\n // l/lcnt exbits index l/lind waitdx blkpos\n var lc_1 = 0, eb = 0, i = st.i || 0, li = 0, wi = st.w || 0, bs = 0;\n for (; i + 2 < s; ++i) {\n // hash value\n var hv = hsh(i);\n // index mod 32768 previous index mod\n var imod = i & 32767, pimod = head[hv];\n prev[imod] = pimod;\n head[hv] = imod;\n // We always should modify head and prev, but only add symbols if\n // this data is not yet processed (\"wait\" for wait index)\n if (wi <= i) {\n // bytes remaining\n var rem = s - i;\n if ((lc_1 > 7000 || li > 24576) && (rem > 423 || !lst)) {\n pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs, i - bs, pos);\n li = lc_1 = eb = 0, bs = i;\n for (var j = 0; j < 286; ++j)\n lf[j] = 0;\n for (var j = 0; j < 30; ++j)\n df[j] = 0;\n }\n // len dist chain\n var l = 2, d = 0, ch_1 = c, dif = imod - pimod & 32767;\n if (rem > 2 && hv == hsh(i - dif)) {\n var maxn = Math.min(n, rem) - 1;\n var maxd = Math.min(32767, i);\n // max possible length\n // not capped at dif because decompressors implement \"rolling\" index population\n var ml = Math.min(258, rem);\n while (dif <= maxd && --ch_1 && imod != pimod) {\n if (dat[i + l] == dat[i + l - dif]) {\n var nl = 0;\n for (; nl < ml && dat[i + nl] == dat[i + nl - dif]; ++nl)\n ;\n if (nl > l) {\n l = nl, d = dif;\n // break out early when we reach \"nice\" (we are satisfied enough)\n if (nl > maxn)\n break;\n // now, find the rarest 2-byte sequence within this\n // length of literals and search for that instead.\n // Much faster than just using the start\n var mmd = Math.min(dif, nl - 2);\n var md = 0;\n for (var j = 0; j < mmd; ++j) {\n var ti = i - dif + j & 32767;\n var pti = prev[ti];\n var cd = ti - pti & 32767;\n if (cd > md)\n md = cd, pimod = ti;\n }\n }\n }\n // check the previous match\n imod = pimod, pimod = prev[imod];\n dif += imod - pimod & 32767;\n }\n }\n // d will be nonzero only when a match was found\n if (d) {\n // store both dist and len data in one int32\n // Make sure this is recognized as a len/dist with 28th bit (2^28)\n syms[li++] = 268435456 | (revfl[l] << 18) | revfd[d];\n var lin = revfl[l] & 31, din = revfd[d] & 31;\n eb += fleb[lin] + fdeb[din];\n ++lf[257 + lin];\n ++df[din];\n wi = i + l;\n ++lc_1;\n }\n else {\n syms[li++] = dat[i];\n ++lf[dat[i]];\n }\n }\n }\n for (i = Math.max(i, wi); i < s; ++i) {\n syms[li++] = dat[i];\n ++lf[dat[i]];\n }\n pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i - bs, pos);\n if (!lst) {\n st.r = (pos & 7) | w[(pos / 8) | 0] << 3;\n // shft(pos) now 1 less if pos & 7 != 0\n pos -= 7;\n st.h = head, st.p = prev, st.i = i, st.w = wi;\n }\n }\n else {\n for (var i = st.w || 0; i < s + lst; i += 65535) {\n // end\n var e = i + 65535;\n if (e >= s) {\n // write final block\n w[(pos / 8) | 0] = lst;\n e = s;\n }\n pos = wfblk(w, pos + 1, dat.subarray(i, e));\n }\n st.i = s;\n }\n return slc(o, 0, pre + shft(pos) + post);\n};\n// CRC32 table\nvar crct = /*#__PURE__*/ (function () {\n var t = new Int32Array(256);\n for (var i = 0; i < 256; ++i) {\n var c = i, k = 9;\n while (--k)\n c = ((c & 1) && -306674912) ^ (c >>> 1);\n t[i] = c;\n }\n return t;\n})();\n// CRC32\nvar crc = function () {\n var c = -1;\n return {\n p: function (d) {\n // closures have awful performance\n var cr = c;\n for (var i = 0; i < d.length; ++i)\n cr = crct[(cr & 255) ^ d[i]] ^ (cr >>> 8);\n c = cr;\n },\n d: function () { return ~c; }\n };\n};\n// Adler32\nvar adler = function () {\n var a = 1, b = 0;\n return {\n p: function (d) {\n // closures have awful performance\n var n = a, m = b;\n var l = d.length | 0;\n for (var i = 0; i != l;) {\n var e = Math.min(i + 2655, l);\n for (; i < e; ++i)\n m += n += d[i];\n n = (n & 65535) + 15 * (n >> 16), m = (m & 65535) + 15 * (m >> 16);\n }\n a = n, b = m;\n },\n d: function () {\n a %= 65521, b %= 65521;\n return (a & 255) << 24 | (a & 0xFF00) << 8 | (b & 255) << 8 | (b >> 8);\n }\n };\n};\n;\n// deflate with opts\nvar dopt = function (dat, opt, pre, post, st) {\n if (!st) {\n st = { l: 1 };\n if (opt.dictionary) {\n var dict = opt.dictionary.subarray(-32768);\n var newDat = new u8(dict.length + dat.length);\n newDat.set(dict);\n newDat.set(dat, dict.length);\n dat = newDat;\n st.w = dict.length;\n }\n }\n return dflt(dat, opt.level == null ? 6 : opt.level, opt.mem == null ? (st.l ? Math.ceil(Math.max(8, Math.min(13, Math.log(dat.length))) * 1.5) : 20) : (12 + opt.mem), pre, post, st);\n};\n// Walmart object spread\nvar mrg = function (a, b) {\n var o = {};\n for (var k in a)\n o[k] = a[k];\n for (var k in b)\n o[k] = b[k];\n return o;\n};\n// worker clone\n// This is possibly the craziest part of the entire codebase, despite how simple it may seem.\n// The only parameter to this function is a closure that returns an array of variables outside of the function scope.\n// We're going to try to figure out the variable names used in the closure as strings because that is crucial for workerization.\n// We will return an object mapping of true variable name to value (basically, the current scope as a JS object).\n// The reason we can't just use the original variable names is minifiers mangling the toplevel scope.\n// This took me three weeks to figure out how to do.\nvar wcln = function (fn, fnStr, td) {\n var dt = fn();\n var st = fn.toString();\n var ks = st.slice(st.indexOf('[') + 1, st.lastIndexOf(']')).replace(/\\s+/g, '').split(',');\n for (var i = 0; i < dt.length; ++i) {\n var v = dt[i], k = ks[i];\n if (typeof v == 'function') {\n fnStr += ';' + k + '=';\n var st_1 = v.toString();\n if (v.prototype) {\n // for global objects\n if (st_1.indexOf('[native code]') != -1) {\n var spInd = st_1.indexOf(' ', 8) + 1;\n fnStr += st_1.slice(spInd, st_1.indexOf('(', spInd));\n }\n else {\n fnStr += st_1;\n for (var t in v.prototype)\n fnStr += ';' + k + '.prototype.' + t + '=' + v.prototype[t].toString();\n }\n }\n else\n fnStr += st_1;\n }\n else\n td[k] = v;\n }\n return fnStr;\n};\nvar ch = [];\n// clone bufs\nvar cbfs = function (v) {\n var tl = [];\n for (var k in v) {\n if (v[k].buffer) {\n tl.push((v[k] = new v[k].constructor(v[k])).buffer);\n }\n }\n return tl;\n};\n// use a worker to execute code\nvar wrkr = function (fns, init, id, cb) {\n if (!ch[id]) {\n var fnStr = '', td_1 = {}, m = fns.length - 1;\n for (var i = 0; i < m; ++i)\n fnStr = wcln(fns[i], fnStr, td_1);\n ch[id] = { c: wcln(fns[m], fnStr, td_1), e: td_1 };\n }\n var td = mrg({}, ch[id].e);\n return wk(ch[id].c + ';onmessage=function(e){for(var k in e.data)self[k]=e.data[k];onmessage=' + init.toString() + '}', id, td, cbfs(td), cb);\n};\n// base async inflate fn\nvar bInflt = function () { return [u8, u16, i32, fleb, fdeb, clim, fl, fd, flrm, fdrm, rev, ec, hMap, max, bits, bits16, shft, slc, err, inflt, inflateSync, pbf, gopt]; };\nvar bDflt = function () { return [u8, u16, i32, fleb, fdeb, clim, revfl, revfd, flm, flt, fdm, fdt, rev, deo, et, hMap, wbits, wbits16, hTree, ln, lc, clen, wfblk, wblk, shft, slc, dflt, dopt, deflateSync, pbf]; };\n// gzip extra\nvar gze = function () { return [gzh, gzhl, wbytes, crc, crct]; };\n// gunzip extra\nvar guze = function () { return [gzs, gzl]; };\n// zlib extra\nvar zle = function () { return [zlh, wbytes, adler]; };\n// unzlib extra\nvar zule = function () { return [zls]; };\n// post buf\nvar pbf = function (msg) { return postMessage(msg, [msg.buffer]); };\n// get opts\nvar gopt = function (o) { return o && {\n out: o.size && new u8(o.size),\n dictionary: o.dictionary\n}; };\n// async helper\nvar cbify = function (dat, opts, fns, init, id, cb) {\n var w = wrkr(fns, init, id, function (err, dat) {\n w.terminate();\n cb(err, dat);\n });\n w.postMessage([dat, opts], opts.consume ? [dat.buffer] : []);\n return function () { w.terminate(); };\n};\n// auto stream\nvar astrm = function (strm) {\n strm.ondata = function (dat, final) { return postMessage([dat, final], [dat.buffer]); };\n return function (ev) {\n if (ev.data[0]) {\n strm.push(ev.data[0], ev.data[1]);\n postMessage([ev.data[0].length]);\n }\n else\n strm.flush(ev.data[1]);\n };\n};\n// async stream attach\nvar astrmify = function (fns, strm, opts, init, id, flush, ext) {\n var t;\n var w = wrkr(fns, init, id, function (err, dat) {\n if (err)\n w.terminate(), strm.ondata.call(strm, err);\n else if (!Array.isArray(dat))\n ext(dat);\n else if (dat.length == 1) {\n strm.queuedSize -= dat[0];\n if (strm.ondrain)\n strm.ondrain(dat[0]);\n }\n else {\n if (dat[1])\n w.terminate();\n strm.ondata.call(strm, err, dat[0], dat[1]);\n }\n });\n w.postMessage(opts);\n strm.queuedSize = 0;\n strm.push = function (d, f) {\n if (!strm.ondata)\n err(5);\n if (t)\n strm.ondata(err(4, 0, 1), null, !!f);\n strm.queuedSize += d.length;\n // can fail for cross-realm Uint8Array, but ok - only a small performance penalty\n w.postMessage([d, t = f], d.buffer instanceof ArrayBuffer ? [d.buffer] : []);\n };\n strm.terminate = function () { w.terminate(); };\n if (flush) {\n strm.flush = function (sync) { w.postMessage([0, sync]); };\n }\n};\n// read 2 bytes\nvar b2 = function (d, b) { return d[b] | (d[b + 1] << 8); };\n// read 4 bytes\nvar b4 = function (d, b) { return (d[b] | (d[b + 1] << 8) | (d[b + 2] << 16) | (d[b + 3] << 24)) >>> 0; };\n// read 8 bytes\nvar b8 = function (d, b) { return b4(d, b) + (b4(d, b + 4) * 4294967296); };\n// write bytes\nvar wbytes = function (d, b, v) {\n for (; v; ++b)\n d[b] = v, v >>>= 8;\n};\n// gzip header\nvar gzh = function (c, o) {\n var fn = o.filename;\n c[0] = 31, c[1] = 139, c[2] = 8, c[8] = o.level < 2 ? 4 : o.level == 9 ? 2 : 0, c[9] = 3; // assume Unix\n if (o.mtime != 0)\n wbytes(c, 4, Math.floor(new Date(o.mtime || Date.now()) / 1000));\n if (fn) {\n c[3] = 8;\n for (var i = 0; i <= fn.length; ++i)\n c[i + 10] = fn.charCodeAt(i);\n }\n};\n// gzip footer: -8 to -4 = CRC, -4 to -0 is length\n// gzip start\nvar gzs = function (d) {\n if (d[0] != 31 || d[1] != 139 || d[2] != 8)\n err(6, 'invalid gzip data');\n var flg = d[3];\n var st = 10;\n if (flg & 4)\n st += (d[10] | d[11] << 8) + 2;\n for (var zs = (flg >> 3 & 1) + (flg >> 4 & 1); zs > 0; zs -= !d[st++])\n ;\n return st + (flg & 2);\n};\n// gzip length\nvar gzl = function (d) {\n var l = d.length;\n return (d[l - 4] | d[l - 3] << 8 | d[l - 2] << 16 | d[l - 1] << 24) >>> 0;\n};\n// gzip header length\nvar gzhl = function (o) { return 10 + (o.filename ? o.filename.length + 1 : 0); };\n// zlib header\nvar zlh = function (c, o) {\n var lv = o.level, fl = lv == 0 ? 0 : lv < 6 ? 1 : lv == 9 ? 3 : 2;\n c[0] = 120, c[1] = (fl << 6) | (o.dictionary && 32);\n c[1] |= 31 - ((c[0] << 8) | c[1]) % 31;\n if (o.dictionary) {\n var h = adler();\n h.p(o.dictionary);\n wbytes(c, 2, h.d());\n }\n};\n// zlib start\nvar zls = function (d, dict) {\n if ((d[0] & 15) != 8 || (d[0] >> 4) > 7 || ((d[0] << 8 | d[1]) % 31))\n err(6, 'invalid zlib data');\n if ((d[1] >> 5 & 1) == +!dict)\n err(6, 'invalid zlib data: ' + (d[1] & 32 ? 'need' : 'unexpected') + ' dictionary');\n return (d[1] >> 3 & 4) + 2;\n};\nfunction StrmOpt(opts, cb) {\n if (typeof opts == 'function')\n cb = opts, opts = {};\n this.ondata = cb;\n return opts;\n}\n/**\n * Streaming DEFLATE compression\n */\nvar Deflate = /*#__PURE__*/ (function () {\n function Deflate(opts, cb) {\n if (typeof opts == 'function')\n cb = opts, opts = {};\n this.ondata = cb;\n this.o = opts || {};\n this.s = { l: 0, i: 32768, w: 32768, z: 32768 };\n // Buffer length must always be 0 mod 32768 for index calculations to be correct when modifying head and prev\n // 98304 = 32768 (lookback) + 65536 (common chunk size)\n this.b = new u8(98304);\n if (this.o.dictionary) {\n var dict = this.o.dictionary.subarray(-32768);\n this.b.set(dict, 32768 - dict.length);\n this.s.i = 32768 - dict.length;\n }\n }\n Deflate.prototype.p = function (c, f) {\n this.ondata(dopt(c, this.o, 0, 0, this.s), f);\n };\n /**\n * Pushes a chunk to be deflated\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n Deflate.prototype.push = function (chunk, final) {\n if (!this.ondata)\n err(5);\n if (this.s.l)\n err(4);\n var endLen = chunk.length + this.s.z;\n if (endLen > this.b.length) {\n if (endLen > 2 * this.b.length - 32768) {\n var newBuf = new u8(endLen & -32768);\n newBuf.set(this.b.subarray(0, this.s.z));\n this.b = newBuf;\n }\n var split = this.b.length - this.s.z;\n this.b.set(chunk.subarray(0, split), this.s.z);\n this.s.z = this.b.length;\n this.p(this.b, false);\n this.b.set(this.b.subarray(-32768));\n this.b.set(chunk.subarray(split), 32768);\n this.s.z = chunk.length - split + 32768;\n this.s.i = 32766, this.s.w = 32768;\n }\n else {\n this.b.set(chunk, this.s.z);\n this.s.z += chunk.length;\n }\n this.s.l = final & 1;\n if (this.s.z > this.s.w + 8191 || final) {\n this.p(this.b, final || false);\n this.s.w = this.s.i, this.s.i -= 2;\n }\n if (final) {\n // cleanup unneeded buffers/state to reduce memory usage\n this.s = this.o = {};\n this.b = et;\n }\n };\n /**\n * Flushes buffered uncompressed data. Useful to immediately retrieve the\n * deflated output for small inputs.\n * @param sync Whether to flush to a byte boundary. A sync flush takes 4-5\n * extra bytes, but guarantees all pushed data is immediately\n * decompressible. A separate DEFLATE stream may be concatenated\n * with the current output after a sync flush.\n */\n Deflate.prototype.flush = function (sync) {\n if (!this.ondata)\n err(5);\n if (this.s.l)\n err(4);\n this.p(this.b, false);\n this.s.w = this.s.i, this.s.i -= 2;\n // could technically skip writing the type-0 block for (this.s.r & 7) == 0,\n // but the deterministic trailer (00 00 FF FF) is useful in some situations\n if (sync) {\n var c = new u8(6);\n c[0] = this.s.r >> 3;\n // write empty, non-final type-0 block\n var ep = wfblk(c, this.s.r, et);\n this.s.r = 0;\n this.ondata(c.subarray(0, ep >> 3), false);\n }\n };\n return Deflate;\n}());\nexport { Deflate };\n/**\n * Asynchronous streaming DEFLATE compression\n */\nvar AsyncDeflate = /*#__PURE__*/ (function () {\n function AsyncDeflate(opts, cb) {\n astrmify([\n bDflt,\n function () { return [astrm, Deflate]; }\n ], this, StrmOpt.call(this, opts, cb), function (ev) {\n var strm = new Deflate(ev.data);\n onmessage = astrm(strm);\n }, 6, 1);\n }\n return AsyncDeflate;\n}());\nexport { AsyncDeflate };\nexport function deflate(data, opts, cb) {\n if (!cb)\n cb = opts, opts = {};\n if (typeof cb != 'function')\n err(7);\n return cbify(data, opts, [\n bDflt,\n ], function (ev) { return pbf(deflateSync(ev.data[0], ev.data[1])); }, 0, cb);\n}\n/**\n * Compresses data with DEFLATE without any wrapper\n * @param data The data to compress\n * @param opts The compression options\n * @returns The deflated version of the data\n */\nexport function deflateSync(data, opts) {\n return dopt(data, opts || {}, 0, 0);\n}\n/**\n * Streaming DEFLATE decompression\n */\nvar Inflate = /*#__PURE__*/ (function () {\n function Inflate(opts, cb) {\n // no StrmOpt here to avoid adding to workerizer\n if (typeof opts == 'function')\n cb = opts, opts = {};\n this.ondata = cb;\n var dict = opts && opts.dictionary && opts.dictionary.subarray(-32768);\n this.s = { i: 0, b: dict ? dict.length : 0 };\n this.o = new u8(32768);\n this.p = new u8(0);\n if (dict)\n this.o.set(dict);\n }\n Inflate.prototype.e = function (c) {\n if (!this.ondata)\n err(5);\n if (this.d)\n err(4);\n if (!this.p.length)\n this.p = c;\n else if (c.length) {\n var n = new u8(this.p.length + c.length);\n n.set(this.p), n.set(c, this.p.length), this.p = n;\n }\n };\n Inflate.prototype.c = function (final) {\n this.s.i = +(this.d = final || false);\n var bts = this.s.b;\n var dt = inflt(this.p, this.s, this.o);\n this.ondata(slc(dt, bts, this.s.b), this.d);\n this.o = slc(dt, this.s.b - 32768), this.s.b = this.o.length;\n this.p = slc(this.p, (this.s.p / 8) | 0), this.s.p &= 7;\n };\n /**\n * Pushes a chunk to be inflated\n * @param chunk The chunk to push\n * @param final Whether this is the final chunk\n */\n Inflate.prototype.push = function (chunk, final) {\n this.e(chunk), this.c(final);\n };\n return Inflate;\n}());\nexport { Inflate };\n/**\n * Asynchronous streaming DEFLATE decompression\n */\nvar AsyncInflate = /*#__PURE__*/ (function () {\n function AsyncInflate(opts, cb) {\n astrmify([\n bInflt,\n function () { return [astrm, Inflate]; }\n ], this, StrmOpt.call(this, opts, cb), function (ev) {\n var strm = new Inflate(ev.data);\n onmessage = astrm(strm);\n }, 7, 0);\n }\n return AsyncInflate;\n}());\nexport { AsyncInflate };\nexport function inflate(data, opts, cb) {\n if (!cb)\n cb = opts, opts = {};\n if (typeof cb != 'function')\n err(7);\n return cbify(data, opts, [\n bInflt\n ], function (ev) { return pbf(inflateSync(ev.data[0], gopt(ev.data[1]))); }, 1, cb);\n}\nexport function inflateSync(data, opts) {\n return inflt(data, { i: 2 }, opts && opts.out, opts && opts.dictionary);\n}\n// before you yell at me for not just using extends, my reason is that TS inheritance is hard to workerize.\n/**\n * Streaming GZIP compression\n */\nvar Gzip = /*#__PURE__*/ (function () {\n function Gzip(opts, cb) {\n this.c = crc();\n this.l = 0;\n this.v = 1;\n Deflate.call(this, opts, cb);\n }\n /**\n * Pushes a chunk to be GZIPped\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n Gzip.prototype.push = function (chunk, final) {\n this.c.p(chunk);\n this.l += chunk.length;\n Deflate.prototype.push.call(this, chunk, final);\n };\n Gzip.prototype.p = function (c, f) {\n var raw = dopt(c, this.o, this.v && gzhl(this.o), f && 8, this.s);\n if (this.v)\n gzh(raw, this.o), this.v = 0;\n if (f)\n wbytes(raw, raw.length - 8, this.c.d()), wbytes(raw, raw.length - 4, this.l);\n this.ondata(raw, f);\n };\n /**\n * Flushes buffered uncompressed data. Useful to immediately retrieve the\n * GZIPped output for small inputs.\n * @param sync Whether to flush to a byte boundary. A sync flush takes 4-5\n * extra bytes, but guarantees all pushed data is immediately\n * decompressible.\n */\n Gzip.prototype.flush = function (sync) {\n Deflate.prototype.flush.call(this, sync);\n };\n return Gzip;\n}());\nexport { Gzip };\n/**\n * Asynchronous streaming GZIP compression\n */\nvar AsyncGzip = /*#__PURE__*/ (function () {\n function AsyncGzip(opts, cb) {\n astrmify([\n bDflt,\n gze,\n function () { return [astrm, Deflate, Gzip]; }\n ], this, StrmOpt.call(this, opts, cb), function (ev) {\n var strm = new Gzip(ev.data);\n onmessage = astrm(strm);\n }, 8, 1);\n }\n return AsyncGzip;\n}());\nexport { AsyncGzip };\nexport function gzip(data, opts, cb) {\n if (!cb)\n cb = opts, opts = {};\n if (typeof cb != 'function')\n err(7);\n return cbify(data, opts, [\n bDflt,\n gze,\n function () { return [gzipSync]; }\n ], function (ev) { return pbf(gzipSync(ev.data[0], ev.data[1])); }, 2, cb);\n}\n/**\n * Compresses data with GZIP\n * @param data The data to compress\n * @param opts The compression options\n * @returns The gzipped version of the data\n */\nexport function gzipSync(data, opts) {\n if (!opts)\n opts = {};\n var c = crc(), l = data.length;\n c.p(data);\n var d = dopt(data, opts, gzhl(opts), 8), s = d.length;\n return gzh(d, opts), wbytes(d, s - 8, c.d()), wbytes(d, s - 4, l), d;\n}\n/**\n * Streaming single or multi-member GZIP decompression\n */\nvar Gunzip = /*#__PURE__*/ (function () {\n function Gunzip(opts, cb) {\n this.v = 1;\n this.r = 0;\n Inflate.call(this, opts, cb);\n }\n /**\n * Pushes a chunk to be GUNZIPped\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n Gunzip.prototype.push = function (chunk, final) {\n Inflate.prototype.e.call(this, chunk);\n this.r += chunk.length;\n if (this.v) {\n var p = this.p.subarray(this.v - 1);\n var s = p.length > 3 ? gzs(p) : 4;\n if (s > p.length) {\n if (!final)\n return;\n }\n else if (this.v > 1 && this.onmember) {\n this.onmember(this.r - p.length);\n }\n this.p = p.subarray(s), this.v = 0;\n }\n // necessary to prevent TS from using the closure value\n // This allows for workerization to function correctly\n Inflate.prototype.c.call(this, 0);\n // process concatenated GZIP\n if (this.s.f && !this.s.l) {\n this.v = shft(this.s.p) + 9;\n this.s = { i: 0 };\n this.o = new u8(0);\n this.push(new u8(0), final);\n }\n else if (final) {\n Inflate.prototype.c.call(this, final);\n }\n };\n return Gunzip;\n}());\nexport { Gunzip };\n/**\n * Asynchronous streaming single or multi-member GZIP decompression\n */\nvar AsyncGunzip = /*#__PURE__*/ (function () {\n function AsyncGunzip(opts, cb) {\n var _this = this;\n astrmify([\n bInflt,\n guze,\n function () { return [astrm, Inflate, Gunzip]; }\n ], this, StrmOpt.call(this, opts, cb), function (ev) {\n var strm = new Gunzip(ev.data);\n strm.onmember = function (offset) { return postMessage(offset); };\n onmessage = astrm(strm);\n }, 9, 0, function (offset) { return _this.onmember && _this.onmember(offset); });\n }\n return AsyncGunzip;\n}());\nexport { AsyncGunzip };\nexport function gunzip(data, opts, cb) {\n if (!cb)\n cb = opts, opts = {};\n if (typeof cb != 'function')\n err(7);\n return cbify(data, opts, [\n bInflt,\n guze,\n function () { return [gunzipSync]; }\n ], function (ev) { return pbf(gunzipSync(ev.data[0], ev.data[1])); }, 3, cb);\n}\nexport function gunzipSync(data, opts) {\n var st = gzs(data);\n if (st + 8 > data.length)\n err(6, 'invalid gzip data');\n return inflt(data.subarray(st, -8), { i: 2 }, opts && opts.out || new u8(gzl(data)), opts && opts.dictionary);\n}\n/**\n * Streaming Zlib compression\n */\nvar Zlib = /*#__PURE__*/ (function () {\n function Zlib(opts, cb) {\n this.c = adler();\n this.v = 1;\n Deflate.call(this, opts, cb);\n }\n /**\n * Pushes a chunk to be zlibbed\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n Zlib.prototype.push = function (chunk, final) {\n this.c.p(chunk);\n Deflate.prototype.push.call(this, chunk, final);\n };\n Zlib.prototype.p = function (c, f) {\n var raw = dopt(c, this.o, this.v && (this.o.dictionary ? 6 : 2), f && 4, this.s);\n if (this.v)\n zlh(raw, this.o), this.v = 0;\n if (f)\n wbytes(raw, raw.length - 4, this.c.d());\n this.ondata(raw, f);\n };\n /**\n * Flushes buffered uncompressed data. Useful to immediately retrieve the\n * zlibbed output for small inputs.\n * @param sync Whether to flush to a byte boundary. A sync flush takes 4-5\n * extra bytes, but guarantees all pushed data is immediately\n * decompressible.\n */\n Zlib.prototype.flush = function (sync) {\n Deflate.prototype.flush.call(this, sync);\n };\n return Zlib;\n}());\nexport { Zlib };\n/**\n * Asynchronous streaming Zlib compression\n */\nvar AsyncZlib = /*#__PURE__*/ (function () {\n function AsyncZlib(opts, cb) {\n astrmify([\n bDflt,\n zle,\n function () { return [astrm, Deflate, Zlib]; }\n ], this, StrmOpt.call(this, opts, cb), function (ev) {\n var strm = new Zlib(ev.data);\n onmessage = astrm(strm);\n }, 10, 1);\n }\n return AsyncZlib;\n}());\nexport { AsyncZlib };\nexport function zlib(data, opts, cb) {\n if (!cb)\n cb = opts, opts = {};\n if (typeof cb != 'function')\n err(7);\n return cbify(data, opts, [\n bDflt,\n zle,\n function () { return [zlibSync]; }\n ], function (ev) { return pbf(zlibSync(ev.data[0], ev.data[1])); }, 4, cb);\n}\n/**\n * Compress data with Zlib\n * @param data The data to compress\n * @param opts The compression options\n * @returns The zlib-compressed version of the data\n */\nexport function zlibSync(data, opts) {\n if (!opts)\n opts = {};\n var a = adler();\n a.p(data);\n var d = dopt(data, opts, opts.dictionary ? 6 : 2, 4);\n return zlh(d, opts), wbytes(d, d.length - 4, a.d()), d;\n}\n/**\n * Streaming Zlib decompression\n */\nvar Unzlib = /*#__PURE__*/ (function () {\n function Unzlib(opts, cb) {\n Inflate.call(this, opts, cb);\n this.v = opts && opts.dictionary ? 2 : 1;\n }\n /**\n * Pushes a chunk to be unzlibbed\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n Unzlib.prototype.push = function (chunk, final) {\n Inflate.prototype.e.call(this, chunk);\n if (this.v) {\n if (this.p.length < 6 && !final)\n return;\n this.p = this.p.subarray(zls(this.p, this.v - 1)), this.v = 0;\n }\n if (final) {\n if (this.p.length < 4)\n err(6, 'invalid zlib data');\n this.p = this.p.subarray(0, -4);\n }\n // necessary to prevent TS from using the closure value\n // This allows for workerization to function correctly\n Inflate.prototype.c.call(this, final);\n };\n return Unzlib;\n}());\nexport { Unzlib };\n/**\n * Asynchronous streaming Zlib decompression\n */\nvar AsyncUnzlib = /*#__PURE__*/ (function () {\n function AsyncUnzlib(opts, cb) {\n astrmify([\n bInflt,\n zule,\n function () { return [astrm, Inflate, Unzlib]; }\n ], this, StrmOpt.call(this, opts, cb), function (ev) {\n var strm = new Unzlib(ev.data);\n onmessage = astrm(strm);\n }, 11, 0);\n }\n return AsyncUnzlib;\n}());\nexport { AsyncUnzlib };\nexport function unzlib(data, opts, cb) {\n if (!cb)\n cb = opts, opts = {};\n if (typeof cb != 'function')\n err(7);\n return cbify(data, opts, [\n bInflt,\n zule,\n function () { return [unzlibSync]; }\n ], function (ev) { return pbf(unzlibSync(ev.data[0], gopt(ev.data[1]))); }, 5, cb);\n}\nexport function unzlibSync(data, opts) {\n return inflt(data.subarray(zls(data, opts && opts.dictionary), -4), { i: 2 }, opts && opts.out, opts && opts.dictionary);\n}\n// Default algorithm for compression (used because having a known output size allows faster decompression)\nexport { gzip as compress, AsyncGzip as AsyncCompress };\nexport { gzipSync as compressSync, Gzip as Compress };\n/**\n * Streaming GZIP, Zlib, or raw DEFLATE decompression\n */\nvar Decompress = /*#__PURE__*/ (function () {\n function Decompress(opts, cb) {\n this.o = StrmOpt.call(this, opts, cb) || {};\n this.G = Gunzip;\n this.I = Inflate;\n this.Z = Unzlib;\n }\n // init substream\n // overriden by AsyncDecompress\n Decompress.prototype.i = function () {\n var _this = this;\n this.s.ondata = function (dat, final) {\n _this.ondata(dat, final);\n };\n };\n /**\n * Pushes a chunk to be decompressed\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n Decompress.prototype.push = function (chunk, final) {\n if (!this.ondata)\n err(5);\n if (!this.s) {\n if (this.p && this.p.length) {\n var n = new u8(this.p.length + chunk.length);\n n.set(this.p), n.set(chunk, this.p.length);\n }\n else\n this.p = chunk;\n if (this.p.length > 2) {\n this.s = (this.p[0] == 31 && this.p[1] == 139 && this.p[2] == 8)\n ? new this.G(this.o)\n : ((this.p[0] & 15) != 8 || (this.p[0] >> 4) > 7 || ((this.p[0] << 8 | this.p[1]) % 31))\n ? new this.I(this.o)\n : new this.Z(this.o);\n this.i();\n this.s.push(this.p, final);\n this.p = null;\n }\n }\n else\n this.s.push(chunk, final);\n };\n return Decompress;\n}());\nexport { Decompress };\n/**\n * Asynchronous streaming GZIP, Zlib, or raw DEFLATE decompression\n */\nvar AsyncDecompress = /*#__PURE__*/ (function () {\n function AsyncDecompress(opts, cb) {\n Decompress.call(this, opts, cb);\n this.queuedSize = 0;\n this.G = AsyncGunzip;\n this.I = AsyncInflate;\n this.Z = AsyncUnzlib;\n }\n AsyncDecompress.prototype.i = function () {\n var _this = this;\n this.s.ondata = function (err, dat, final) {\n _this.ondata(err, dat, final);\n };\n this.s.ondrain = function (size) {\n _this.queuedSize -= size;\n if (_this.ondrain)\n _this.ondrain(size);\n };\n };\n /**\n * Pushes a chunk to be decompressed\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n AsyncDecompress.prototype.push = function (chunk, final) {\n this.queuedSize += chunk.length;\n Decompress.prototype.push.call(this, chunk, final);\n };\n return AsyncDecompress;\n}());\nexport { AsyncDecompress };\nexport function decompress(data, opts, cb) {\n if (!cb)\n cb = opts, opts = {};\n if (typeof cb != 'function')\n err(7);\n return (data[0] == 31 && data[1] == 139 && data[2] == 8)\n ? gunzip(data, opts, cb)\n : ((data[0] & 15) != 8 || (data[0] >> 4) > 7 || ((data[0] << 8 | data[1]) % 31))\n ? inflate(data, opts, cb)\n : unzlib(data, opts, cb);\n}\n/**\n * Expands compressed GZIP, Zlib, or raw DEFLATE data, automatically detecting the format\n * @param data The data to decompress\n * @param opts The decompression options\n * @returns The decompressed version of the data\n */\nexport function decompressSync(data, opts) {\n return (data[0] == 31 && data[1] == 139 && data[2] == 8)\n ? gunzipSync(data, opts)\n : ((data[0] & 15) != 8 || (data[0] >> 4) > 7 || ((data[0] << 8 | data[1]) % 31))\n ? inflateSync(data, opts)\n : unzlibSync(data, opts);\n}\n// flatten a directory structure\nvar fltn = function (d, p, t, o) {\n for (var k in d) {\n var val = d[k], n = p + k, op = o;\n if (Array.isArray(val))\n op = mrg(o, val[1]), val = val[0];\n if (ArrayBuffer.isView(val))\n t[n] = [val, op];\n else {\n t[n += '/'] = [new u8(0), op];\n fltn(val, n, t, o);\n }\n }\n};\n// text encoder\nvar te = typeof TextEncoder != 'undefined' && /*#__PURE__*/ new TextEncoder();\n// text decoder\nvar td = typeof TextDecoder != 'undefined' && /*#__PURE__*/ new TextDecoder();\n// text decoder stream\nvar tds = 0;\ntry {\n td.decode(et, { stream: true });\n tds = 1;\n}\ncatch (e) { }\n// decode UTF8\nvar dutf8 = function (d) {\n for (var r = '', i = 0;;) {\n var c = d[i++];\n var eb = (c > 127) + (c > 223) + (c > 239);\n if (i + eb > d.length)\n return { s: r, r: slc(d, i - 1) };\n if (!eb)\n r += String.fromCharCode(c);\n else if (eb == 3) {\n c = ((c & 15) << 18 | (d[i++] & 63) << 12 | (d[i++] & 63) << 6 | (d[i++] & 63)) - 65536,\n r += String.fromCharCode(55296 | (c >> 10), 56320 | (c & 1023));\n }\n else if (eb & 1)\n r += String.fromCharCode((c & 31) << 6 | (d[i++] & 63));\n else\n r += String.fromCharCode((c & 15) << 12 | (d[i++] & 63) << 6 | (d[i++] & 63));\n }\n};\n/**\n * Streaming UTF-8 decoding\n */\nvar DecodeUTF8 = /*#__PURE__*/ (function () {\n /**\n * Creates a UTF-8 decoding stream\n * @param cb The callback to call whenever data is decoded\n */\n function DecodeUTF8(cb) {\n this.ondata = cb;\n if (tds)\n this.t = new TextDecoder();\n else\n this.p = et;\n }\n /**\n * Pushes a chunk to be decoded from UTF-8 binary\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n DecodeUTF8.prototype.push = function (chunk, final) {\n if (!this.ondata)\n err(5);\n final = !!final;\n if (this.t) {\n this.ondata(this.t.decode(chunk, { stream: true }), final);\n if (final) {\n if (this.t.decode().length)\n err(8);\n this.t = null;\n }\n return;\n }\n if (!this.p)\n err(4);\n var dat = new u8(this.p.length + chunk.length);\n dat.set(this.p);\n dat.set(chunk, this.p.length);\n var _a = dutf8(dat), s = _a.s, r = _a.r;\n if (final) {\n if (r.length)\n err(8);\n this.p = null;\n }\n else\n this.p = r;\n this.ondata(s, final);\n };\n return DecodeUTF8;\n}());\nexport { DecodeUTF8 };\n/**\n * Streaming UTF-8 encoding\n */\nvar EncodeUTF8 = /*#__PURE__*/ (function () {\n /**\n * Creates a UTF-8 decoding stream\n * @param cb The callback to call whenever data is encoded\n */\n function EncodeUTF8(cb) {\n this.ondata = cb;\n }\n /**\n * Pushes a chunk to be encoded to UTF-8\n * @param chunk The string data to push\n * @param final Whether this is the last chunk\n */\n EncodeUTF8.prototype.push = function (chunk, final) {\n if (!this.ondata)\n err(5);\n if (this.d)\n err(4);\n this.ondata(strToU8(chunk), this.d = final || false);\n };\n return EncodeUTF8;\n}());\nexport { EncodeUTF8 };\n/**\n * Converts a string into a Uint8Array for use with compression/decompression methods\n * @param str The string to encode\n * @param latin1 Whether or not to interpret the data as Latin-1. This should\n * not need to be true unless decoding a binary string.\n * @returns The string encoded in UTF-8/Latin-1 binary\n */\nexport function strToU8(str, latin1) {\n if (latin1) {\n var ar_1 = new u8(str.length);\n for (var i = 0; i < str.length; ++i)\n ar_1[i] = str.charCodeAt(i);\n return ar_1;\n }\n if (te)\n return te.encode(str);\n var l = str.length;\n var ar = new u8(str.length + (str.length >> 1));\n var ai = 0;\n var w = function (v) { ar[ai++] = v; };\n for (var i = 0; i < l; ++i) {\n if (ai + 5 > ar.length) {\n var n = new u8(ai + 8 + ((l - i) << 1));\n n.set(ar);\n ar = n;\n }\n var c = str.charCodeAt(i);\n if (c < 128 || latin1)\n w(c);\n else if (c < 2048)\n w(192 | (c >> 6)), w(128 | (c & 63));\n else if (c > 55295 && c < 57344)\n c = 65536 + (c & 1023 << 10) | (str.charCodeAt(++i) & 1023),\n w(240 | (c >> 18)), w(128 | ((c >> 12) & 63)), w(128 | ((c >> 6) & 63)), w(128 | (c & 63));\n else\n w(224 | (c >> 12)), w(128 | ((c >> 6) & 63)), w(128 | (c & 63));\n }\n return slc(ar, 0, ai);\n}\n/**\n * Converts a Uint8Array to a string\n * @param dat The data to decode to string\n * @param latin1 Whether or not to interpret the data as Latin-1. This should\n * not need to be true unless encoding to binary string.\n * @returns The original UTF-8/Latin-1 string\n */\nexport function strFromU8(dat, latin1) {\n if (latin1) {\n var r = '';\n for (var i = 0; i < dat.length; i += 16384)\n r += String.fromCharCode.apply(null, dat.subarray(i, i + 16384));\n return r;\n }\n else if (td) {\n return td.decode(dat);\n }\n else {\n var _a = dutf8(dat), s = _a.s, r = _a.r;\n if (r.length)\n err(8);\n return s;\n }\n}\n;\n// deflate bit flag\nvar dbf = function (l) { return l == 1 ? 3 : l < 6 ? 2 : l == 9 ? 1 : 0; };\n// skip local zip header\nvar slzh = function (d, b) { return b + 30 + b2(d, b + 26) + b2(d, b + 28); };\n// read zip header\nvar zh = function (d, b, z) {\n var fnl = b2(d, b + 28), efl = b2(d, b + 30), fn = strFromU8(d.subarray(b + 46, b + 46 + fnl), !(b2(d, b + 8) & 2048)), es = b + 46 + fnl;\n var _a = z64hs(d, es, efl, z, b4(d, b + 20), b4(d, b + 24), b4(d, b + 42)), sc = _a[0], su = _a[1], off = _a[2];\n return [b2(d, b + 10), sc, su, fn, es + efl + b2(d, b + 32), off];\n};\n// read zip64 header sizes\nvar z64hs = function (d, b, l, z, sc, su, off) {\n var nsc = sc == 4294967295, nsu = su == 4294967295, noff = off == 4294967295, e = b + l;\n var nf = nsc + nsu + noff;\n if (z && nf) {\n for (; b + 4 < e; b += 4 + b2(d, b + 2)) {\n if (b2(d, b) == 1) {\n return [\n nsc ? b8(d, b + 4 + 8 * nsu) : sc,\n nsu ? b8(d, b + 4) : su,\n noff ? b8(d, b + 4 + 8 * (nsu + nsc)) : off,\n 1\n ];\n }\n }\n // z == 2 for unknown whether or not zip64\n if (z < 2)\n err(13);\n }\n return [sc, su, off, 0];\n};\n// extra field length\nvar exfl = function (ex) {\n var le = 0;\n if (ex) {\n for (var k in ex) {\n var l = ex[k].length;\n if (l > 65535)\n err(9);\n le += l + 4;\n }\n }\n return le;\n};\n// write zip header\nvar wzh = function (d, b, f, fn, u, c, ce, co) {\n var fl = fn.length, ex = f.extra, col = co && co.length;\n var exl = exfl(ex);\n wbytes(d, b, ce != null ? 0x2014B50 : 0x4034B50), b += 4;\n if (ce != null)\n d[b++] = 20, d[b++] = f.os;\n d[b] = 20, b += 2; // spec compliance? what's that?\n d[b++] = (f.flag << 1) | (c < 0 && 8), d[b++] = u && 8;\n d[b++] = f.compression & 255, d[b++] = f.compression >> 8;\n var dt = new Date(f.mtime == null ? Date.now() : f.mtime), y = dt.getFullYear() - 1980;\n if (y < 0 || y > 119)\n err(10);\n wbytes(d, b, (y << 25) | ((dt.getMonth() + 1) << 21) | (dt.getDate() << 16) | (dt.getHours() << 11) | (dt.getMinutes() << 5) | (dt.getSeconds() >> 1)), b += 4;\n if (c != -1) {\n wbytes(d, b, f.crc);\n wbytes(d, b + 4, c < 0 ? -c - 2 : c);\n wbytes(d, b + 8, f.size);\n }\n wbytes(d, b + 12, fl);\n wbytes(d, b + 14, exl), b += 16;\n if (ce != null) {\n wbytes(d, b, col);\n wbytes(d, b + 6, f.attrs);\n wbytes(d, b + 10, ce), b += 14;\n }\n d.set(fn, b);\n b += fl;\n if (exl) {\n for (var k in ex) {\n var exf = ex[k], l = exf.length;\n wbytes(d, b, +k);\n wbytes(d, b + 2, l);\n d.set(exf, b + 4), b += 4 + l;\n }\n }\n if (col)\n d.set(co, b), b += col;\n return b;\n};\n// write zip footer (end of central directory)\nvar wzf = function (o, b, c, d, e) {\n wbytes(o, b, 0x6054B50); // skip disk\n wbytes(o, b + 8, c);\n wbytes(o, b + 10, c);\n wbytes(o, b + 12, d);\n wbytes(o, b + 16, e);\n};\n/**\n * A pass-through stream to keep data uncompressed in a ZIP archive.\n */\nvar ZipPassThrough = /*#__PURE__*/ (function () {\n /**\n * Creates a pass-through stream that can be added to ZIP archives\n * @param filename The filename to associate with this data stream\n */\n function ZipPassThrough(filename) {\n this.filename = filename;\n this.c = crc();\n this.size = 0;\n this.compression = 0;\n }\n /**\n * Processes a chunk and pushes to the output stream. You can override this\n * method in a subclass for custom behavior, but by default this passes\n * the data through. You must call this.ondata(err, chunk, final) at some\n * point in this method.\n * @param chunk The chunk to process\n * @param final Whether this is the last chunk\n */\n ZipPassThrough.prototype.process = function (chunk, final) {\n this.ondata(null, chunk, final);\n };\n /**\n * Pushes a chunk to be added. If you are subclassing this with a custom\n * compression algorithm, note that you must push data from the source\n * file only, pre-compression.\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n ZipPassThrough.prototype.push = function (chunk, final) {\n if (!this.ondata)\n err(5);\n this.c.p(chunk);\n this.size += chunk.length;\n if (final)\n this.crc = this.c.d();\n // we shouldn't really do this cast, but properly handling ArrayBufferLike\n // makes the API unergonomic with Buffer\n this.process(chunk, final || false);\n };\n return ZipPassThrough;\n}());\nexport { ZipPassThrough };\n// I don't extend because TypeScript extension adds 1kB of runtime bloat\n/**\n * Streaming DEFLATE compression for ZIP archives. Prefer using AsyncZipDeflate\n * for better performance\n */\nvar ZipDeflate = /*#__PURE__*/ (function () {\n /**\n * Creates a DEFLATE stream that can be added to ZIP archives\n * @param filename The filename to associate with this data stream\n * @param opts The compression options\n */\n function ZipDeflate(filename, opts) {\n var _this = this;\n if (!opts)\n opts = {};\n ZipPassThrough.call(this, filename);\n this.d = new Deflate(opts, function (dat, final) {\n _this.ondata(null, dat, final);\n });\n this.compression = 8;\n this.flag = dbf(opts.level);\n }\n ZipDeflate.prototype.process = function (chunk, final) {\n try {\n this.d.push(chunk, final);\n }\n catch (e) {\n this.ondata(e, null, final);\n }\n };\n /**\n * Pushes a chunk to be deflated\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n ZipDeflate.prototype.push = function (chunk, final) {\n ZipPassThrough.prototype.push.call(this, chunk, final);\n };\n return ZipDeflate;\n}());\nexport { ZipDeflate };\n/**\n * Asynchronous streaming DEFLATE compression for ZIP archives\n */\nvar AsyncZipDeflate = /*#__PURE__*/ (function () {\n /**\n * Creates an asynchronous DEFLATE stream that can be added to ZIP archives\n * @param filename The filename to associate with this data stream\n * @param opts The compression options\n */\n function AsyncZipDeflate(filename, opts) {\n var _this = this;\n if (!opts)\n opts = {};\n ZipPassThrough.call(this, filename);\n this.d = new AsyncDeflate(opts, function (err, dat, final) {\n _this.ondata(err, dat, final);\n });\n this.compression = 8;\n this.flag = dbf(opts.level);\n this.terminate = this.d.terminate;\n }\n AsyncZipDeflate.prototype.process = function (chunk, final) {\n this.d.push(chunk, final);\n };\n /**\n * Pushes a chunk to be deflated\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n AsyncZipDeflate.prototype.push = function (chunk, final) {\n ZipPassThrough.prototype.push.call(this, chunk, final);\n };\n return AsyncZipDeflate;\n}());\nexport { AsyncZipDeflate };\n// TODO: Better tree shaking\n/**\n * A zippable archive to which files can incrementally be added\n */\nvar Zip = /*#__PURE__*/ (function () {\n /**\n * Creates an empty ZIP archive to which files can be added\n * @param cb The callback to call whenever data for the generated ZIP archive\n * is available\n */\n function Zip(cb) {\n this.ondata = cb;\n this.u = [];\n this.d = 1;\n }\n /**\n * Adds a file to the ZIP archive\n * @param file The file stream to add\n */\n Zip.prototype.add = function (file) {\n var _this = this;\n if (!this.ondata)\n err(5);\n // finishing or finished\n if (this.d & 2)\n this.ondata(err(4 + (this.d & 1) * 8, 0, 1), null, false);\n else {\n var f = strToU8(file.filename), fl_1 = f.length;\n var com = file.comment, o = com && strToU8(com);\n var u = fl_1 != file.filename.length || (o && (com.length != o.length));\n var hl_1 = fl_1 + exfl(file.extra) + 30;\n if (fl_1 > 65535)\n this.ondata(err(11, 0, 1), null, false);\n var header = new u8(hl_1);\n wzh(header, 0, file, f, u, -1);\n var chks_1 = [header];\n var pAll_1 = function () {\n for (var _i = 0, chks_2 = chks_1; _i < chks_2.length; _i++) {\n var chk = chks_2[_i];\n _this.ondata(null, chk, false);\n }\n chks_1 = [];\n };\n var tr_1 = this.d;\n this.d = 0;\n var ind_1 = this.u.length;\n var uf_1 = mrg(file, {\n f: f,\n u: u,\n o: o,\n t: function () {\n if (file.terminate)\n file.terminate();\n },\n r: function () {\n pAll_1();\n if (tr_1) {\n var nxt = _this.u[ind_1 + 1];\n if (nxt)\n nxt.r();\n else\n _this.d = 1;\n }\n tr_1 = 1;\n }\n });\n var cl_1 = 0;\n file.ondata = function (err, dat, final) {\n if (err) {\n _this.ondata(err, dat, final);\n _this.terminate();\n }\n else {\n cl_1 += dat.length;\n chks_1.push(dat);\n if (final) {\n var dd = new u8(16);\n wbytes(dd, 0, 0x8074B50);\n wbytes(dd, 4, file.crc);\n wbytes(dd, 8, cl_1);\n wbytes(dd, 12, file.size);\n chks_1.push(dd);\n uf_1.c = cl_1, uf_1.b = hl_1 + cl_1 + 16, uf_1.crc = file.crc, uf_1.size = file.size;\n if (tr_1)\n uf_1.r();\n tr_1 = 1;\n }\n else if (tr_1)\n pAll_1();\n }\n };\n this.u.push(uf_1);\n }\n };\n /**\n * Ends the process of adding files and prepares to emit the final chunks.\n * This *must* be called after adding all desired files for the resulting\n * ZIP file to work properly.\n */\n Zip.prototype.end = function () {\n var _this = this;\n if (this.d & 2) {\n this.ondata(err(4 + (this.d & 1) * 8, 0, 1), null, true);\n return;\n }\n if (this.d)\n this.e();\n else\n this.u.push({\n r: function () {\n if (!(_this.d & 1))\n return;\n _this.u.splice(-1, 1);\n _this.e();\n },\n t: function () { }\n });\n this.d = 3;\n };\n Zip.prototype.e = function () {\n var bt = 0, l = 0, tl = 0;\n for (var _i = 0, _a = this.u; _i < _a.length; _i++) {\n var f = _a[_i];\n tl += 46 + f.f.length + exfl(f.extra) + (f.o ? f.o.length : 0);\n }\n var out = new u8(tl + 22);\n for (var _b = 0, _c = this.u; _b < _c.length; _b++) {\n var f = _c[_b];\n wzh(out, bt, f, f.f, f.u, -f.c - 2, l, f.o);\n bt += 46 + f.f.length + exfl(f.extra) + (f.o ? f.o.length : 0), l += f.b;\n }\n wzf(out, bt, this.u.length, tl, l);\n this.ondata(null, out, true);\n this.d = 2;\n };\n /**\n * A method to terminate any internal workers used by the stream. Subsequent\n * calls to add() will fail.\n */\n Zip.prototype.terminate = function () {\n for (var _i = 0, _a = this.u; _i < _a.length; _i++) {\n var f = _a[_i];\n f.t();\n }\n this.d = 2;\n };\n return Zip;\n}());\nexport { Zip };\nexport function zip(data, opts, cb) {\n if (!cb)\n cb = opts, opts = {};\n if (typeof cb != 'function')\n err(7);\n var r = {};\n fltn(data, '', r, opts);\n var k = Object.keys(r);\n var lft = k.length, o = 0, tot = 0;\n var slft = lft, files = new Array(lft);\n var term = [];\n var tAll = function () {\n for (var i = 0; i < term.length; ++i)\n term[i]();\n };\n var cbd = function (a, b) {\n mt(function () { cb(a, b); });\n };\n mt(function () { cbd = cb; });\n var cbf = function () {\n var out = new u8(tot + 22), oe = o, cdl = tot - o;\n tot = 0;\n for (var i = 0; i < slft; ++i) {\n var f = files[i];\n try {\n var l = f.c.length;\n wzh(out, tot, f, f.f, f.u, l);\n var badd = 30 + f.f.length + exfl(f.extra);\n var loc = tot + badd;\n out.set(f.c, loc);\n wzh(out, o, f, f.f, f.u, l, tot, f.m), o += 16 + badd + (f.m ? f.m.length : 0), tot = loc + l;\n }\n catch (e) {\n return cbd(e, null);\n }\n }\n wzf(out, o, files.length, cdl, oe);\n cbd(null, out);\n };\n if (!lft)\n cbf();\n var _loop_1 = function (i) {\n var fn = k[i];\n var _a = r[fn], file = _a[0], p = _a[1];\n var c = crc(), size = file.length;\n c.p(file);\n var f = strToU8(fn), s = f.length;\n var com = p.comment, m = com && strToU8(com), ms = m && m.length;\n var exl = exfl(p.extra);\n var compression = p.level == 0 ? 0 : 8;\n var cbl = function (e, d) {\n if (e) {\n tAll();\n cbd(e, null);\n }\n else {\n var l = d.length;\n files[i] = mrg(p, {\n size: size,\n crc: c.d(),\n c: d,\n f: f,\n m: m,\n u: s != fn.length || (m && (com.length != ms)),\n compression: compression\n });\n o += 30 + s + exl + l;\n tot += 76 + 2 * (s + exl) + (ms || 0) + l;\n if (!--lft)\n cbf();\n }\n };\n if (s > 65535)\n cbl(err(11, 0, 1), null);\n if (!compression)\n cbl(null, file);\n else if (size < 160000) {\n try {\n cbl(null, deflateSync(file, p));\n }\n catch (e) {\n cbl(e, null);\n }\n }\n else\n term.push(deflate(file, p, cbl));\n };\n // Cannot use lft because it can decrease\n for (var i = 0; i < slft; ++i) {\n _loop_1(i);\n }\n return tAll;\n}\n/**\n * Synchronously creates a ZIP file. Prefer using `zip` for better performance\n * with more than one file.\n * @param data The directory structure for the ZIP archive\n * @param opts The main options, merged with per-file options\n * @returns The generated ZIP archive\n */\nexport function zipSync(data, opts) {\n if (!opts)\n opts = {};\n var r = {};\n var files = [];\n fltn(data, '', r, opts);\n var o = 0;\n var tot = 0;\n for (var fn in r) {\n var _a = r[fn], file = _a[0], p = _a[1];\n var compression = p.level == 0 ? 0 : 8;\n var f = strToU8(fn), s = f.length;\n var com = p.comment, m = com && strToU8(com), ms = m && m.length;\n var exl = exfl(p.extra);\n if (s > 65535)\n err(11);\n var d = compression ? deflateSync(file, p) : file, l = d.length;\n var c = crc();\n c.p(file);\n files.push(mrg(p, {\n size: file.length,\n crc: c.d(),\n c: d,\n f: f,\n m: m,\n u: s != fn.length || (m && (com.length != ms)),\n o: o,\n compression: compression\n }));\n o += 30 + s + exl + l;\n tot += 76 + 2 * (s + exl) + (ms || 0) + l;\n }\n var out = new u8(tot + 22), oe = o, cdl = tot - o;\n for (var i = 0; i < files.length; ++i) {\n var f = files[i];\n wzh(out, f.o, f, f.f, f.u, f.c.length);\n var badd = 30 + f.f.length + exfl(f.extra);\n out.set(f.c, f.o + badd);\n wzh(out, o, f, f.f, f.u, f.c.length, f.o, f.m), o += 16 + badd + (f.m ? f.m.length : 0);\n }\n wzf(out, o, files.length, cdl, oe);\n return out;\n}\n/**\n * Streaming pass-through decompression for ZIP archives\n */\nvar UnzipPassThrough = /*#__PURE__*/ (function () {\n function UnzipPassThrough() {\n }\n UnzipPassThrough.prototype.push = function (chunk, final) {\n // same as ZipPassThrough: cast to retain Buffer ergonomics\n this.ondata(null, chunk, final);\n };\n UnzipPassThrough.compression = 0;\n return UnzipPassThrough;\n}());\nexport { UnzipPassThrough };\n/**\n * Streaming DEFLATE decompression for ZIP archives. Prefer AsyncZipInflate for\n * better performance.\n */\nvar UnzipInflate = /*#__PURE__*/ (function () {\n /**\n * Creates a DEFLATE decompression that can be used in ZIP archives\n */\n function UnzipInflate() {\n var _this = this;\n this.i = new Inflate(function (dat, final) {\n _this.ondata(null, dat, final);\n });\n }\n UnzipInflate.prototype.push = function (chunk, final) {\n try {\n this.i.push(chunk, final);\n }\n catch (e) {\n this.ondata(e, null, final);\n }\n };\n UnzipInflate.compression = 8;\n return UnzipInflate;\n}());\nexport { UnzipInflate };\n/**\n * Asynchronous streaming DEFLATE decompression for ZIP archives\n */\nvar AsyncUnzipInflate = /*#__PURE__*/ (function () {\n /**\n * Creates a DEFLATE decompression that can be used in ZIP archives\n */\n function AsyncUnzipInflate(_, sz) {\n var _this = this;\n if (sz < 320000) {\n this.i = new Inflate(function (dat, final) {\n _this.ondata(null, dat, final);\n });\n }\n else {\n this.i = new AsyncInflate(function (err, dat, final) {\n _this.ondata(err, dat, final);\n });\n this.terminate = this.i.terminate;\n }\n }\n AsyncUnzipInflate.prototype.push = function (chunk, final) {\n if (this.i.terminate)\n chunk = slc(chunk, 0);\n this.i.push(chunk, final);\n };\n AsyncUnzipInflate.compression = 8;\n return AsyncUnzipInflate;\n}());\nexport { AsyncUnzipInflate };\n/**\n * A ZIP archive decompression stream that emits files as they are discovered\n */\nvar Unzip = /*#__PURE__*/ (function () {\n /**\n * Creates a ZIP decompression stream\n * @param cb The callback to call whenever a file in the ZIP archive is found\n */\n function Unzip(cb) {\n this.onfile = cb;\n this.k = [];\n this.o = {\n 0: UnzipPassThrough\n };\n this.p = et;\n }\n /**\n * Pushes a chunk to be unzipped\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n Unzip.prototype.push = function (chunk, final) {\n var _this = this;\n if (!this.onfile)\n err(5);\n if (!this.p)\n err(4);\n if (this.c > 0) {\n var len = Math.min(this.c, chunk.length);\n var toAdd = chunk.subarray(0, len);\n this.c -= len;\n if (this.d)\n this.d.push(toAdd, !this.c);\n else\n this.k[0].push(toAdd);\n chunk = chunk.subarray(len);\n if (chunk.length)\n return this.push(chunk, final);\n }\n else {\n var f = 0, i = 0, is = void 0, buf = void 0;\n if (!this.p.length)\n buf = chunk;\n else if (!chunk.length)\n buf = this.p;\n else {\n buf = new u8(this.p.length + chunk.length);\n buf.set(this.p), buf.set(chunk, this.p.length);\n }\n var l = buf.length, oc = this.c, add = oc && this.d;\n var _loop_2 = function () {\n var sig = b4(buf, i);\n if (sig == 0x4034B50) {\n f = 1, is = i;\n this_1.d = null;\n this_1.c = 0;\n var bf = b2(buf, i + 6), cmp_1 = b2(buf, i + 8), u = bf & 2048, dd = bf & 8, fnl = b2(buf, i + 26), es = b2(buf, i + 28);\n if (l > i + 30 + fnl + es) {\n var chks_3 = [];\n this_1.k.unshift(chks_3);\n f = 2;\n var lsc = b4(buf, i + 18), lsu = b4(buf, i + 22);\n var fn_1 = strFromU8(buf.subarray(i + 30, i += 30 + fnl), !u);\n var _a = z64hs(buf, i, es, 2, lsc, lsu, 0), sc_1 = _a[0], su_1 = _a[1], z64 = _a[3];\n if (dd)\n sc_1 = -1 - z64;\n i += es;\n this_1.c = sc_1;\n var d_1;\n var file_1 = {\n name: fn_1,\n compression: cmp_1,\n start: function () {\n if (!file_1.ondata)\n err(5);\n if (!sc_1)\n file_1.ondata(null, et, true);\n else {\n var ctr = _this.o[cmp_1];\n if (!ctr)\n file_1.ondata(err(14, 'unknown compression type ' + cmp_1, 1), null, false);\n d_1 = sc_1 < 0 ? new ctr(fn_1) : new ctr(fn_1, sc_1, su_1);\n d_1.ondata = function (err, dat, final) { file_1.ondata(err, dat, final); };\n for (var _i = 0, chks_4 = chks_3; _i < chks_4.length; _i++) {\n var dat = chks_4[_i];\n d_1.push(dat, false);\n }\n if (_this.k[0] == chks_3 && _this.c)\n _this.d = d_1;\n else\n d_1.push(et, true);\n }\n },\n terminate: function () {\n if (d_1 && d_1.terminate)\n d_1.terminate();\n }\n };\n if (sc_1 >= 0)\n file_1.size = sc_1, file_1.originalSize = su_1;\n this_1.onfile(file_1);\n }\n return \"break\";\n }\n else if (oc) {\n if (sig == 0x8074B50) {\n is = i += 12 + (oc == -2 && 8), f = 3, this_1.c = 0;\n return \"break\";\n }\n else if (sig == 0x2014B50) {\n is = i -= 4, f = 3, this_1.c = 0;\n return \"break\";\n }\n }\n };\n var this_1 = this;\n for (; i < l - 4; ++i) {\n var state_1 = _loop_2();\n if (state_1 === \"break\")\n break;\n }\n this.p = et;\n if (oc < 0) {\n var dat = f ? buf.subarray(0, is - 12 - (oc == -2 && 8) - (b4(buf, is - 16) == 0x8074B50 && 4)) : buf.subarray(0, i);\n if (add)\n add.push(dat, !!f);\n else\n this.k[+(f == 2)].push(dat);\n }\n if (f & 2)\n return this.push(buf.subarray(i), final);\n this.p = buf.subarray(i);\n }\n if (final) {\n if (this.c)\n err(13);\n this.p = null;\n }\n };\n /**\n * Registers a decoder with the stream, allowing for files compressed with\n * the compression type provided to be expanded correctly\n * @param decoder The decoder constructor\n */\n Unzip.prototype.register = function (decoder) {\n this.o[decoder.compression] = decoder;\n };\n return Unzip;\n}());\nexport { Unzip };\nvar mt = typeof queueMicrotask == 'function' ? queueMicrotask : typeof setTimeout == 'function' ? setTimeout : function (fn) { fn(); };\nexport function unzip(data, opts, cb) {\n if (!cb)\n cb = opts, opts = {};\n if (typeof cb != 'function')\n err(7);\n var term = [];\n var tAll = function () {\n for (var i = 0; i < term.length; ++i)\n term[i]();\n };\n var files = {};\n var cbd = function (a, b) {\n mt(function () { cb(a, b); });\n };\n mt(function () { cbd = cb; });\n var e = data.length - 22;\n for (; b4(data, e) != 0x6054B50; --e) {\n if (!e || data.length - e > 65558) {\n cbd(err(13, 0, 1), null);\n return tAll;\n }\n }\n ;\n var lft = b2(data, e + 8);\n if (lft) {\n var c = lft;\n var o = b4(data, e + 16);\n var z = b4(data, e - 20) == 0x7064B50;\n if (z) {\n var ze = b4(data, e - 12);\n z = b4(data, ze) == 0x6064B50;\n if (z) {\n c = lft = b4(data, ze + 32);\n o = b4(data, ze + 48);\n }\n }\n var fltr = opts && opts.filter;\n var _loop_3 = function (i) {\n var _a = zh(data, o, z), c_1 = _a[0], sc = _a[1], su = _a[2], fn = _a[3], no = _a[4], off = _a[5], b = slzh(data, off);\n o = no;\n var cbl = function (e, d) {\n if (e) {\n tAll();\n cbd(e, null);\n }\n else {\n if (d)\n files[fn] = d;\n if (!--lft)\n cbd(null, files);\n }\n };\n if (!fltr || fltr({\n name: fn,\n size: sc,\n originalSize: su,\n compression: c_1\n })) {\n if (!c_1)\n cbl(null, slc(data, b, b + sc));\n else if (c_1 == 8) {\n var infl = data.subarray(b, b + sc);\n // Synchronously decompress under 512KB, or barely-compressed data\n if (su < 524288 || sc > 0.8 * su) {\n try {\n cbl(null, inflateSync(infl, { out: new u8(su) }));\n }\n catch (e) {\n cbl(e, null);\n }\n }\n else\n term.push(inflate(infl, { size: su }, cbl));\n }\n else\n cbl(err(14, 'unknown compression type ' + c_1, 1), null);\n }\n else\n cbl(null, null);\n };\n for (var i = 0; i < c; ++i) {\n _loop_3(i);\n }\n }\n else\n cbd(null, {});\n return tAll;\n}\n/**\n * Synchronously decompresses a ZIP archive. Prefer using `unzip` for better\n * performance with more than one file.\n * @param data The raw compressed ZIP file\n * @param opts The ZIP extraction options\n * @returns The decompressed files\n */\nexport function unzipSync(data, opts) {\n var files = {};\n var e = data.length - 22;\n for (; b4(data, e) != 0x6054B50; --e) {\n if (!e || data.length - e > 65558)\n err(13);\n }\n ;\n var c = b2(data, e + 8);\n if (!c)\n return {};\n var o = b4(data, e + 16);\n var z = b4(data, e - 20) == 0x7064B50;\n if (z) {\n var ze = b4(data, e - 12);\n z = b4(data, ze) == 0x6064B50;\n if (z) {\n c = b4(data, ze + 32);\n o = b4(data, ze + 48);\n }\n }\n var fltr = opts && opts.filter;\n for (var i = 0; i < c; ++i) {\n var _a = zh(data, o, z), c_2 = _a[0], sc = _a[1], su = _a[2], fn = _a[3], no = _a[4], off = _a[5], b = slzh(data, off);\n o = no;\n if (!fltr || fltr({\n name: fn,\n size: sc,\n originalSize: su,\n compression: c_2\n })) {\n if (!c_2)\n files[fn] = slc(data, b, b + sc);\n else if (c_2 == 8)\n files[fn] = inflateSync(data.subarray(b, b + sc), { out: new u8(su) });\n else\n err(14, 'unknown compression type ' + c_2);\n }\n }\n return files;\n}\n", "/** PDF \u5D4C\u5165\u56FE\u63D0\u53D6\uFF1AJPEG\uFF08DCTDecode \u76F4\u626B\uFF09+ PNG \u91CD\u5EFA\uFF08FlateDecode \u2192 \u89E3\u538B \u2192\n * \u53CD\u6EE4\u6CE2 \u2192 \u91CD\u7F16\u7801 PNG\uFF09\u3002\n *\n * PNG \u91CD\u5EFA\u5F62\u6001\uFF08v2 \u589E\u5F3A\uFF09\uFF1A\u5B57\u8282\u7EA7\u626B `obj \u2026 /Subtype /Image \u2026 stream\u2026endstream`\n * \u5757\uFF0C\u8BFB /Width /Height /ColorSpace /BitsPerComponent /Predictor /Colors\uFF0C\n * inflate \u540E\u6309 PNG \u884C\u6EE4\u6CE2\u5668\uFF080-4\uFF1Anone/sub/up/avg/paeth\uFF09\u53CD\u6EE4\u6CE2\uFF0CCMYK \u8F6C\n * RGB\uFF0C\u91CD\u7F16\u7801\u4E3A\u6807\u51C6 PNG\uFF08fflate deflateSync \u51FA IDAT\uFF0C\u81EA\u5B9E\u73B0 CRC32\uFF09\u3002\n */\n\nimport { inflateSync, deflateSync } from 'fflate'\n\nimport { KsrwError } from './error.ts'\n\n/** \u9875\u6570 + \u9010\u9875\u6587\u672C\uFF08Library \u9884\u89C8/\u76F8\u5173\u6027\u8BC1\u636E\u9762\uFF09\u3002 */\nexport async function pdfText(data: Uint8Array): Promise<{ pages: number; texts: string[] }> {\n try {\n const { extractText, getDocumentProxy } = await import('unpdf')\n const pdf = await getDocumentProxy(data)\n const { text, totalPages } = await extractText(pdf, { mergePages: false })\n return { pages: totalPages, texts: Array.isArray(text) ? text : [String(text)] }\n } catch (error) {\n throw new KsrwError('INVALID', `PDF \u89E3\u6790\u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}\uFF08\u786E\u8BA4\u6587\u4EF6\u672A\u52A0\u5BC6\u4E14\u4E3A\u6709\u6548 PDF\uFF09`)\n }\n}\n\n/** \u63D0\u53D6\u5D4C\u5165 JPEG\uFF08DCTDecode \u6D41\u6700\u5E38\u89C1\u5F62\u6001\uFF09\uFF1A\u5B57\u8282\u7EA7\u626B FFD8\u2026FFD9\u3002 */\nexport function extractJpegImages(data: Uint8Array, minBytes = 8_000): Uint8Array[] {\n const out: Uint8Array[] = []\n const seen = new Set<string>()\n let start = findStart(data, 0)\n while (start !== -1) {\n const end = findEnd(data, start + 2)\n if (end === -1) break\n const size = end + 2 - start\n if (size >= minBytes) {\n const slice = data.slice(start, end + 2)\n const key = `${size}:${slice[0]},${slice[4]}`\n if (!seen.has(key)) {\n seen.add(key)\n out.push(slice)\n }\n }\n start = findStart(data, end + 2)\n }\n return out\n}\n\nfunction findStart(data: Uint8Array, from: number): number {\n for (let i = from; i < data.length - 3; i++) {\n if (data[i] === 0xff && data[i + 1] === 0xd8 && data[i + 2] === 0xff) return i\n }\n return -1\n}\n\nfunction findEnd(data: Uint8Array, from: number): number {\n for (let i = from; i < data.length - 1; i++) {\n if (data[i] === 0xff && data[i + 1] === 0xd9) return i + 1\n }\n return -1\n}\n\n// \u2500\u2500 FlateDecode \u56FE\u50CF\u91CD\u5EFA\uFF08PNG \u5F62\u6001\uFF09\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface ExtractedImage {\n readonly bytes: Uint8Array\n readonly kind: 'png' | 'jpeg'\n readonly width: number\n readonly height: number\n}\n\n/** \u7EFC\u5408\u63D0\u53D6\uFF1AJPEG \u76F4\u626B + FlateDecode \u56FE\u50CF\u91CD\u5EFA\uFF0C\u6309\u51FA\u73B0\u987A\u5E8F\u53BB\u91CD\uFF08\u5C3A\u5BF8+\u5934\u5B57\u8282\uFF09\u3002 */\nexport function extractImages(data: Uint8Array, minBytes = 8_000): ExtractedImage[] {\n const out: ExtractedImage[] = []\n const seen = new Set<string>()\n const push = (img: ExtractedImage): void => {\n const key = `${img.kind}:${img.bytes.length}:${img.width}x${img.height}`\n if (seen.has(key)) return\n seen.add(key)\n out.push(img)\n }\n for (const jpeg of extractJpegImages(data, minBytes)) {\n const dim = jpegDim(jpeg)\n push({ bytes: jpeg, kind: 'jpeg', width: dim[0], height: dim[1] })\n }\n for (const png of extractFlateImages(data, minBytes)) push(png)\n return out\n}\n\nfunction jpegDim(jpeg: Uint8Array): [number, number] {\n for (let i = 2; i < jpeg.length - 9; ) {\n if (jpeg[i] !== 0xff) { i++; continue }\n const marker = jpeg[i + 1]\n if (marker === 0xc0 || marker === 0xc2) {\n const view = new DataView(jpeg.buffer, jpeg.byteOffset, jpeg.byteLength)\n return [view.getUint16(i + 7), view.getUint16(i + 5)]\n }\n const len = (jpeg[i + 2] ?? 0) * 256 + (jpeg[i + 3] ?? 0)\n i += 2 + Math.max(2, len)\n }\n return [0, 0]\n}\n\n/** \u626B PDF \u5BF9\u8C61\u6D41\uFF1A`N M obj << \u2026 /Subtype /Image \u2026 >> stream\\n<data>endstream`\u3002 */\nexport function extractFlateImages(data: Uint8Array, minBytes = 8_000): ExtractedImage[] {\n const latin1 = Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString('latin1')\n const out: ExtractedImage[] = []\n const objRe = /<<([\\s\\S]{0,1200}?)>>\\s*stream\\r?\\n/g\n for (const match of latin1.matchAll(objRe)) {\n const dict = match[1] ?? ''\n if (!/\\/Subtype\\s*\\/Image/.test(dict) || !/\\/Filter\\s*\\/FlateDecode/.test(dict)) continue\n const streamStart = (match.index ?? 0) + match[0].length\n const endIdx = latin1.indexOf('endstream', streamStart)\n if (endIdx === -1) continue\n const raw = data.slice(streamStart, endIdx)\n const img = rebuildPng(dict, trimStreamTail(raw))\n if (img !== undefined && img.bytes.length >= minBytes) out.push(img)\n }\n return out\n}\n\n/** endstream \u524D\u5E38\u6709 EOL\uFF1B\u6D41\u5C3E\u82E5\u88AB\u6CE8\u91CA/\u7A7A\u767D\u6C61\u67D3\u6309 zlib \u5BB9\u9519\u622A\u65AD\u3002 */\nfunction trimStreamTail(raw: Uint8Array): Uint8Array {\n let end = raw.length\n while (end > 0 && (raw[end - 1] === 0x0a || raw[end - 1] === 0x0d || raw[end - 1] === 0x20)) end--\n return raw.subarray(0, end)\n}\n\nfunction dictNum(dict: string, key: string): number | undefined {\n const m = dict.match(new RegExp(`/${key}\\\\s+(\\\\d+)`))\n return m !== null ? Number(m[1]) : undefined\n}\n\nfunction rebuildPng(dict: string, raw: Uint8Array): ExtractedImage | undefined {\n const width = dictNum(dict, 'Width')\n const height = dictNum(dict, 'Height')\n if (width === undefined || height === undefined || width <= 0 || height <= 0 || width > 16_384 || height > 16_384) return undefined\n const bpc = dictNum(dict, 'BitsPerComponent') ?? 8\n if (bpc !== 8) return undefined // 1/2/4/16 bpp \u4E0D\u8D70\u672C\u901A\u9053\n\n let inflated: Uint8Array\n try {\n inflated = inflateSync(raw)\n } catch {\n return undefined // \u6D41\u635F\u574F/\u5BF9\u8C61\u6D41\u5D4C\u5957\uFF1A\u8DF3\u8FC7\n }\n\n // \u901A\u9053\u6570\u5224\u5B9A\uFF1A\u663E\u5F0F /Colors > \u989C\u8272\u7A7A\u95F4\u540D > \u6309\u6570\u636E\u957F\u5EA6\u53CD\u63A8\n const colorsDecl = dictNum(dict, 'Colors')\n const predictor = dictNum(dict, 'Predictor') ?? 1\n const csMatch2 = dict.match(/\\/ColorSpace\\s*\\/(\\w+)/)\n const csName = csMatch2?.[1] ?? ''\n let colors = colorsDecl ?? (csName.includes('RGB') || csName === 'CalRGB' ? 3 : csName.includes('CMYK') ? 4 : csName.includes('Gray') || csName.includes('CalGray') || csName === 'G' ? 1 : 0)\n if (colors === 0) {\n const perRowOverhead = predictor >= 10 ? 1 : 0\n const pixelBytes = Math.max(1, width * height)\n const guess = Math.floor((inflated.length - perRowOverhead * height) / pixelBytes)\n colors = [1, 3, 4].includes(guess) ? guess : 3\n }\n\n let rows: Uint8Array\n try {\n rows = predictor >= 10 ? unfilterPngRows(inflated, width, height, colors) : inflated.subarray(0, width * height * colors)\n } catch {\n return undefined\n }\n const expected = width * height * colors\n if (rows.length < expected) return undefined\n\n // CMYK \u2192 RGB\uFF1B\u5176\u4F59\u539F\u6837\n const rgb = colors === 4 ? cmykToRgb(rows, expected) : rows.subarray(0, expected)\n const gray = colors === 1\n return { bytes: encodePng(rgb, width, height, gray), kind: 'png', width, height }\n}\n\n/** PNG \u884C\u6EE4\u6CE2\u53CD\u6F14\uFF08filter byte \u6BCF\u884C 1 \u4E2A\uFF0Cbpp = colors \u5B57\u8282\uFF09\u3002 */\nfunction unfilterPngRows(data: Uint8Array, width: number, height: number, colors: number): Uint8Array {\n const stride = width * colors\n const out = new Uint8Array(height * stride)\n let src = 0\n for (let y = 0; y < height; y++) {\n if (src >= data.length) throw new Error('data short')\n const filter = data[src++] ?? 0\n const rowStart = y * stride\n for (let x = 0; x < stride; x++) {\n const rawByte = data[src++] ?? 0\n const left = x >= colors ? (out[rowStart + x - colors] ?? 0) : 0\n const up = y > 0 ? (out[rowStart - stride + x] ?? 0) : 0\n const upLeft = y > 0 && x >= colors ? (out[rowStart - stride + x - colors] ?? 0) : 0\n let val: number\n switch (filter) {\n case 0: val = rawByte; break\n case 1: val = rawByte + left; break\n case 2: val = rawByte + up; break\n case 3: val = rawByte + ((left + up) >> 1); break\n case 4: {\n const p = left + up - upLeft\n const pa = Math.abs(p - left)\n const pb = Math.abs(p - up)\n const pc = Math.abs(p - upLeft)\n val = rawByte + (pa <= pb && pa <= pc ? left : pb <= pc ? up : upLeft)\n break\n }\n default: val = rawByte\n }\n out[rowStart + x] = val & 0xff\n }\n }\n return out\n}\n\nfunction cmykToRgb(rows: Uint8Array, expected: number): Uint8Array {\n const out = new Uint8Array(expected)\n for (let i = 0, j = 0; j < expected; i += 4, j += 3) {\n const c = rows[i] ?? 0\n const m = rows[i + 1] ?? 0\n const y = rows[i + 2] ?? 0\n const k = rows[i + 3] ?? 0\n out[j] = 255 - Math.min(255, c + k)\n out[j + 1] = 255 - Math.min(255, m + k)\n out[j + 2] = 255 - Math.min(255, y + k)\n }\n return out\n}\n\n// \u2500\u2500 PNG \u7F16\u7801\uFF08IHDR/IDAT/IEND + CRC32\uFF09\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst CRC_TABLE = (() => {\n const table = new Uint32Array(256)\n for (let n = 0; n < 256; n++) {\n let c = n\n for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1\n table[n] = c >>> 0\n }\n return table\n})()\n\nfunction crc32(buf: Uint8Array): number {\n let c = 0xffffffff\n for (let i = 0; i < buf.length; i++) c = CRC_TABLE[(c ^ (buf[i] ?? 0)) & 0xff] ?? (c >>> 8)\n return (c ^ 0xffffffff) >>> 0\n}\n\nfunction pngChunk(type: string, body: Uint8Array): Uint8Array {\n const out = new Uint8Array(12 + body.length)\n const view = new DataView(out.buffer)\n view.setUint32(0, body.length)\n for (let i = 0; i < 4; i++) out[4 + i] = type.charCodeAt(i)\n out.set(body, 8)\n const typeAndBody = out.subarray(4, 8 + body.length)\n view.setUint32(8 + body.length, crc32(typeAndBody))\n return out\n}\n\n/** PNG \u5934\u55C5\u63A2\uFF08\u7B7E\u540D + IHDR \u5C3A\u5BF8\uFF1B\u975E PNG \u8FD4\u56DE null\uFF09\u3002 */\nexport function sniffPng(data: Uint8Array): { width: number; height: number } | null {\n if (data.length < 24) return null\n const sig = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]\n for (let i = 0; i < 8; i++) {\n if ((data[i] ?? 0) !== sig[i]) return null\n }\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength)\n return { width: view.getUint32(16), height: view.getUint32(20) }\n}\n\n/** RGB/Gray \u50CF\u7D20 \u2192 \u6807\u51C6 PNG\uFF08\u6BCF\u884C\u524D\u7F6E filter 0\uFF09\u3002 */\nexport function encodePng(pixels: Uint8Array, width: number, height: number, gray: boolean): Uint8Array {\n const stride = width * (gray ? 1 : 3)\n const raw = new Uint8Array((stride + 1) * height)\n for (let y = 0; y < height; y++) {\n raw[y * (stride + 1)] = 0\n raw.set(pixels.subarray(y * stride, (y + 1) * stride), y * (stride + 1) + 1)\n }\n const ihdr = new Uint8Array(13)\n const view = new DataView(ihdr.buffer)\n view.setUint32(0, width)\n view.setUint32(4, height)\n ihdr[8] = 8\n ihdr[9] = gray ? 0 : 2\n const signature = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])\n const parts = [signature, pngChunk('IHDR', ihdr), pngChunk('IDAT', deflateSync(raw, { level: 6 })), pngChunk('IEND', new Uint8Array(0))]\n const total = parts.reduce((acc, p) => acc + p.length, 0)\n const out = new Uint8Array(total)\n let off = 0\n for (const p of parts) {\n out.set(p, off)\n off += p.length\n }\n return out\n}\n", "/** \u8BBA\u6587\u7CBE\u8BFB\uFF1A\u8BA2\u9605\u6293\u53D6 \u2192 \u4E0B\u8F7D PDF \u2192 \u539F\u6587/\u56FE\u8868\u62BD\u53D6 \u2192 \u52A9\u624B\u7CBE\u8BFB \u2192 \u6458\u8981\u4E0E\u5B9A\u4F4D\u951A\u70B9\u3002\n *\n * \u76EE\u5F55\u7EA6\u5B9A\uFF08\u9879\u76EE\u5185\uFF09\uFF1A\n * literature/reading/prefs.json \u7CBE\u8BFB\u5EFA\u8BAE\uFF08\u8BA2\u9605\u5173\u952E\u8BCD/\u6DF1\u5EA6/\u8BED\u8A00/\u56FE\u8868\u7B56\u7565/\u5B9A\u65F6\uFF09\n * literature/reading/index.json \u5F85\u8BFB/\u5DF2\u8BFB\u6E05\u5355\n * literature/reading/pdfs/<slug>.pdf \u539F\u59CB PDF\n * literature/reading/<slug>/original.md \u62BD\u597D\u7684\u539F\u6587\uFF08\u9010\u9875 + \u6BB5\u843D\u7F16\u53F7 [\u9875.\u6BB5]\uFF09\n * literature/reading/<slug>/figures/ \u62BD\u51FA\u7684\u56FE\u8868\uFF08fig-01.png \u2026\uFF09\n * literature/reading/<slug>/digest.md \u7CBE\u8BFB\u8F93\u51FA\uFF08\u52A9\u624B\u5199\uFF0C\u9010\u6BB5\u53CC\u8BED + \u89E3\u8BFB + \u56FE\u8868\u89E3\u8BFB + \u5B9A\u4F4D\uFF09\n */\nimport { spawnSync } from 'node:child_process'\nimport { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\nimport { createArxivClient, type ArxivPaper, type FetchFn } from './arxiv.ts'\nimport { KsrwError } from './error.ts'\nimport { extractImages, pdfText } from './pdf.ts'\n\nexport const READING_DIR = 'literature/reading'\n\nexport type ReadingGoal = 'method' | 'repro' | 'compare' | 'cite' | 'survey'\nexport type ReadingDepth = 'abstract' | 'section' | 'paragraph'\nexport type ReadingLanguage = 'zh-en' | 'zh' | 'en-zh'\n\nexport interface ReadingSchedule {\n readonly enabled: boolean\n /** \u6BCF N \u5C0F\u65F6\u81EA\u52A8\u6293\u53D6\u4E00\u6B21\uFF08\u6700\u5C0F 1\uFF09\u3002 */\n readonly everyHours: number\n /** \u6BCF\u6B21\u6700\u591A\u4E0B\u8F7D\u51E0\u7BC7\u3002 */\n readonly maxPerRun: number\n}\n\nexport interface ReadingPrefs {\n readonly topics: string[]\n readonly goal: ReadingGoal\n readonly depth: ReadingDepth\n readonly language: ReadingLanguage\n /** \u7CBE\u8BFB\u91CC\u5FC5\u987B\u5E26\u56FE\u8868\uFF08\u539F\u6587\u6709\u56FE\u5C31\u4E0D\u80FD\u7F3A\uFF09\u3002 */\n readonly includeFigures: boolean\n /** \u56FE\u8868\u4E5F\u8981\u7ED9\u89E3\u8BFB\u3002 */\n readonly figureNotes: boolean\n readonly focus: string[]\n readonly notes: string\n readonly schedule: ReadingSchedule\n readonly lastRunAt: number\n readonly updatedAt: number\n}\n\nexport const DEFAULT_READING_PREFS: ReadingPrefs = {\n topics: [],\n goal: 'method',\n depth: 'paragraph',\n language: 'zh-en',\n includeFigures: true,\n figureNotes: true,\n focus: ['\u65B9\u6CD5', '\u5B9E\u9A8C\u8BBE\u7F6E', '\u7ED3\u8BBA\u4E0E\u5C40\u9650'],\n notes: '',\n schedule: { enabled: false, everyHours: 24, maxPerRun: 3 },\n lastRunAt: 0,\n updatedAt: 0,\n}\n\nexport type ReadingStatus = 'downloaded' | 'extracted' | 'reading' | 'ready' | 'failed'\n\nexport interface ReadingFigure {\n readonly index: number\n readonly file: string\n readonly kind: 'png' | 'jpeg'\n readonly width: number\n readonly height: number\n /** \u6240\u5728\u9875\uFF08\u80FD\u5B9A\u4F4D\u65F6\uFF09\u3002 */\n readonly page?: number\n /** \u4ECE\u8BE5\u9875\u6587\u672C\u91CC\u627E\u5230\u7684\u56FE\u6CE8\uFF08\u5F85\u52A9\u624B\u6838\u5BF9\uFF09\u3002 */\n readonly caption?: string\n}\n\nexport interface ReadingEntry {\n readonly id: string\n readonly source: 'arxiv'\n readonly arxivId?: string\n readonly title: string\n readonly authors: string[]\n readonly abstract: string\n readonly published: string\n readonly categories: string[]\n readonly absUrl: string\n readonly pdfUrl?: string\n /** \u76F8\u5BF9\u9879\u76EE\u8DEF\u5F84\u3002 */\n readonly pdf: string\n readonly dir: string\n readonly status: ReadingStatus\n readonly pages?: number\n readonly figures?: ReadingFigure[]\n readonly digest?: string\n readonly error?: string\n readonly addedAt: number\n readonly updatedAt: number\n}\n\nexport interface ReadingContext {\n readonly entry: ReadingEntry\n readonly prefs: ReadingPrefs\n readonly original: string\n readonly captionCandidates: string[]\n}\n\nfunction root(projectPath: string): string {\n return join(projectPath, READING_DIR)\n}\n\nfunction readJson<T>(file: string): T | undefined {\n try { return JSON.parse(readFileSync(file, 'utf8')) as T } catch { return undefined }\n}\n\nfunction writeJson(file: string, value: unknown): void {\n mkdirSync(join(file, '..'), { recursive: true })\n writeFileSync(file, JSON.stringify(value, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n}\n\n/** \u7CBE\u8BFB\u5EFA\u8BAE\uFF08\u7F3A\u7701\u503C \u2192 \u5DF2\u4FDD\u5B58\u503C\uFF09\u3002 */\nexport function readReadingPrefs(projectPath: string): ReadingPrefs {\n const saved = readJson<Partial<ReadingPrefs>>(join(root(projectPath), 'prefs.json')) ?? {}\n return {\n ...DEFAULT_READING_PREFS,\n ...saved,\n topics: Array.isArray(saved.topics) ? saved.topics : DEFAULT_READING_PREFS.topics,\n focus: Array.isArray(saved.focus) ? saved.focus : DEFAULT_READING_PREFS.focus,\n schedule: { ...DEFAULT_READING_PREFS.schedule, ...(saved.schedule ?? {}) },\n }\n}\n\nexport function writeReadingPrefs(projectPath: string, patch: Partial<ReadingPrefs>): ReadingPrefs {\n const next: ReadingPrefs = {\n ...readReadingPrefs(projectPath),\n ...patch,\n schedule: { ...readReadingPrefs(projectPath).schedule, ...(patch.schedule ?? {}) },\n updatedAt: Date.now(),\n }\n mkdirSync(root(projectPath), { recursive: true })\n writeJson(join(root(projectPath), 'prefs.json'), next)\n return next\n}\n\n/** \u5F85\u8BFB\u6E05\u5355\uFF08\u6309\u52A0\u5165\u65F6\u95F4\u5012\u5E8F\uFF09\u3002 */\nexport function readReadingIndex(projectPath: string): ReadingEntry[] {\n const saved = readJson<{ entries?: ReadingEntry[] }>(join(root(projectPath), 'index.json'))\n return Array.isArray(saved?.entries) ? saved.entries : []\n}\n\nfunction writeReadingIndex(projectPath: string, entries: ReadingEntry[]): void {\n mkdirSync(root(projectPath), { recursive: true })\n writeJson(join(root(projectPath), 'index.json'), { entries })\n}\n\nfunction upsertEntry(projectPath: string, entry: ReadingEntry): ReadingEntry {\n const entries = readReadingIndex(projectPath)\n const next = [entry, ...entries.filter(item => item.id !== entry.id)]\n writeReadingIndex(projectPath, next)\n return entry\n}\n\nfunction slugOf(paper: ArxivPaper): string {\n const id = paper.arxivId.replace(/\\//g, '-')\n return 'arxiv-' + id\n}\n\n/** \u6309\u8BA2\u9605\u5173\u952E\u8BCD\u6293\u6700\u65B0\uFF08arXiv submittedDate \u5012\u5E8F\uFF09\u3002 */\nexport async function searchLatestPapers(\n prefs: ReadingPrefs,\n options: { days?: number; max?: number; fetchFn?: FetchFn } = {},\n): Promise<ArxivPaper[]> {\n const topics = prefs.topics.map(topic => topic.trim()).filter(topic => topic !== '')\n if (topics.length === 0) throw new KsrwError('INVALID', '\u5148\u5728\u7CBE\u8BFB\u5EFA\u8BAE\u91CC\u586B\u8BA2\u9605\u5173\u952E\u8BCD\uFF08topics\uFF09')\n const days = options.days ?? 30\n const max = options.max ?? 10\n const query = topics.map(topic => 'all:\"' + topic + '\"').join(' OR ')\n const client = createArxivClient(options.fetchFn)\n const papers = await client.search({ query, maxResults: Math.min(50, max * 3), sortBy: 'submittedDate', sortOrder: 'descending' })\n const cutoff = Date.now() - days * 24 * 3600 * 1000\n return papers\n .filter(paper => {\n const stamp = Date.parse(paper.published)\n return Number.isFinite(stamp) ? stamp >= cutoff : true\n })\n .slice(0, max)\n}\n\nfunction captionCandidates(text: string): string[] {\n const pattern = /(?:Figure|Fig\\.?|TABLE|Table|\u56FE|\u8868)\\s*\\d+[:.]?\\s*[^\\n]{0,180}/g\n const out: string[] = []\n const seen = new Set<string>()\n for (const match of text.matchAll(pattern)) {\n const line = (match[0] ?? '').trim()\n if (line === '' || seen.has(line)) continue\n seen.add(line)\n out.push(line)\n if (out.length >= 40) break\n }\n return out\n}\n\nfunction hasTool(tool: string): boolean {\n const probe = process.platform === 'win32' ? 'where' : 'which'\n const found = spawnSync(probe, [tool], { encoding: 'utf8' })\n return found.status === 0\n}\n\n/** \u7528 poppler \u7684 pdfimages -list \u62FF\u5230\u9875\u7801\uFF0C\u518D\u6309\u9875\u5BFC\u51FA PNG\uFF08\u77E2\u91CF\u56FE\u4E5F\u80FD\u9760\u9875\u9762\u6E32\u67D3\u7559\u6863\uFF09\u3002 */\nfunction extractFiguresWithPoppler(pdfAbs: string, dirAbs: string, pagesTexts: string[]): ReadingFigure[] {\n if (!hasTool('pdfimages')) return []\n const list = spawnSync('pdfimages', ['-list', pdfAbs], { encoding: 'utf8', maxBuffer: 8 * 1024 * 1024 })\n if (list.status !== 0) return []\n const rows = String(list.stdout ?? '').split('\\n')\n .map(line => line.trim().split(/\\s+/))\n .filter(cells => cells.length >= 6 && /^\\d+$/.test(cells[0] ?? '') && /^\\d+$/.test(cells[1] ?? ''))\n if (rows.length === 0) return []\n const prefix = join(dirAbs, 'figures', 'pdfimg')\n const dump = spawnSync('pdfimages', ['-png', pdfAbs, prefix], { encoding: 'utf8', maxBuffer: 8 * 1024 * 1024 })\n if (dump.status !== 0) return []\n const out: ReadingFigure[] = []\n rows.forEach((cells, index) => {\n const page = Number(cells[0])\n const width = Number(cells[3])\n const height = Number(cells[4])\n const base = String(index).padStart(3, '0')\n let file: string | undefined\n for (const candidate of ['pdfimg-' + base + '.png', 'pdfimg-' + base + '.jpg']) {\n const absolute = join(dirAbs, 'figures', candidate)\n if (existsSync(absolute)) { file = candidate; break }\n }\n if (file === undefined) return\n const target = 'fig-' + String(out.length + 1).padStart(2, '0') + '.png'\n try { renameSync(join(dirAbs, 'figures', file), join(dirAbs, 'figures', target)) } catch { return }\n const pageText = pagesTexts[page - 1] ?? ''\n const caption = /(?:Figure|Fig\\.?|Table|TABLE)\\s*\\d+[:.]?[^\\n]{0,180}/.exec(pageText)?.[0]?.trim()\n out.push({ index: out.length + 1, file: 'figures/' + target, kind: 'png', width, height, page, ...(caption === undefined ? {} : { caption }) })\n })\n return out\n}\n\n/** \u515C\u5E95\uFF1A\u5B57\u8282\u7EA7\u63D0\u53D6\uFF08\u6CA1\u6709 poppler \u6216 poppler \u5931\u8D25\u65F6\uFF09\u3002 */\nfunction extractFiguresFromBytes(bytes: Uint8Array, dirAbs: string): ReadingFigure[] {\n let images: ReturnType<typeof extractImages> = []\n try { images = extractImages(bytes, 12_000) } catch { images = [] }\n return images.map((image, index) => {\n const file = 'fig-' + String(index + 1).padStart(2, '0') + (image.kind === 'jpeg' ? '.jpg' : '.png')\n writeFileSync(join(dirAbs, 'figures', file), image.bytes, { mode: 0o600 })\n return { index: index + 1, file: 'figures/' + file, kind: image.kind, width: image.width, height: image.height }\n })\n}\n\n/** \u628A PDF \u62BD\u6210 original.md\uFF08\u9010\u9875 + \u6BB5\u843D\u7F16\u53F7\uFF09\u4E0E figures/\u3002 */\nexport async function extractReadingPdf(projectPath: string, entry: ReadingEntry, bytes: Uint8Array): Promise<ReadingEntry> {\n const dirAbs = join(projectPath, entry.dir)\n mkdirSync(join(dirAbs, 'figures'), { recursive: true })\n {\n const parsed = await pdfText(bytes)\n const lines: string[] = ['# ' + entry.title, '', '- arXiv: ' + (entry.arxivId ?? ''), '- \u9875\u6570: ' + String(parsed.pages), '']\n parsed.texts.forEach((text, pageIndex) => {\n lines.push('<!-- page:' + String(pageIndex + 1) + ' -->')\n const paragraphs = text.split(/\\n\\s*\\n/).map(item => item.replace(/\\s+$/g, '')).filter(item => item.trim() !== '')\n paragraphs.forEach((paragraph, paraIndex) => {\n lines.push('[' + String(pageIndex + 1) + '.' + String(paraIndex + 1) + '] ' + paragraph.replace(/\\n/g, ' ').trim())\n })\n lines.push('')\n })\n const original = lines.join('\\n')\n writeFileSync(join(dirAbs, 'original.md'), original, { encoding: 'utf8', mode: 0o600 })\n // \u4F18\u5148 poppler\uFF08\u80FD\u5E26\u9875\u7801\u4E0E\u56FE\u6CE8\uFF09\uFF0C\u5931\u8D25\u518D\u9000\u5B57\u8282\u7EA7\u63D0\u53D6\uFF1B\u56FE\u8868\u62BD\u53D6\u5931\u8D25\u4E0D\u5F71\u54CD\u539F\u6587\u3002\n let figures = extractFiguresWithPoppler(join(projectPath, entry.pdf), dirAbs, parsed.texts)\n if (figures.length === 0) figures = extractFiguresFromBytes(bytes, dirAbs)\n const next: ReadingEntry = {\n ...entry,\n status: 'extracted',\n pages: parsed.pages,\n figures,\n updatedAt: Date.now(),\n }\n return upsertEntry(projectPath, next)\n }\n}\n\n/** \u4E0B\u8F7D\u5E76\u62BD\u53D6\u4E00\u6279 arXiv \u8BBA\u6587\uFF08\u5DF2\u5B58\u5728\u7684\u8DF3\u8FC7\uFF09\u3002 */\nexport async function addReadingPapers(\n projectPath: string,\n papers: ArxivPaper[],\n options: { fetchFn?: FetchFn } = {},\n): Promise<ReadingEntry[]> {\n const existing = new Set(readReadingIndex(projectPath).map(entry => entry.id))\n const client = createArxivClient(options.fetchFn)\n const added: ReadingEntry[] = []\n for (const paper of papers) {\n const id = slugOf(paper)\n if (existing.has(id)) continue\n const pdfRel = READING_DIR + '/pdfs/' + id + '.pdf'\n const pdfAbs = join(projectPath, pdfRel)\n mkdirSync(join(projectPath, READING_DIR, 'pdfs'), { recursive: true })\n const base: ReadingEntry = {\n id,\n source: 'arxiv',\n ...(paper.arxivId === '' ? {} : { arxivId: paper.arxivId }),\n title: paper.title,\n authors: paper.authors,\n abstract: paper.abstract,\n published: paper.published,\n categories: paper.categories,\n absUrl: paper.absUrl,\n ...(paper.pdfUrl === undefined ? {} : { pdfUrl: paper.pdfUrl }),\n pdf: pdfRel,\n dir: READING_DIR + '/' + id,\n status: 'downloaded',\n addedAt: Date.now(),\n updatedAt: Date.now(),\n }\n try {\n await client.downloadPdf(paper, pdfAbs)\n const bytes = new Uint8Array(readFileSync(pdfAbs))\n const extracted = await extractReadingPdf(projectPath, base, bytes)\n added.push(extracted)\n } catch (error) {\n added.push(upsertEntry(projectPath, {\n ...base,\n status: 'failed',\n error: error instanceof Error ? error.message : String(error),\n updatedAt: Date.now(),\n }))\n }\n }\n return added\n}\n\n/** \u8BFB\u53D6\u5355\u7BC7\u7684\u539F\u6587\u4E0E\u56FE\u8868\u6E05\u5355\uFF08\u7ED9\u52A9\u624B\u505A\u7CBE\u8BFB\u7684\u4E0A\u4E0B\u6587\uFF09\u3002 */\nexport function readReadingEntry(projectPath: string, id: string): ReadingContext {\n const entry = readReadingIndex(projectPath).find(item => item.id === id)\n if (entry === undefined) throw new KsrwError('NOT_FOUND', '\u7CBE\u8BFB\u6761\u76EE\u4E0D\u5B58\u5728\uFF1A' + id)\n const dirAbs = join(projectPath, entry.dir)\n let original = ''\n try { original = readFileSync(join(dirAbs, 'original.md'), 'utf8') } catch { original = '' }\n if (original === '') throw new KsrwError('NOT_FOUND', '\u8FD8\u6CA1\u6709\u62BD\u53D6\u539F\u6587\uFF1A\u5148\u4E0B\u8F7D\u5E76\u62BD\u53D6\uFF08' + entry.id + '\uFF09')\n return { entry, prefs: readReadingPrefs(projectPath), original, captionCandidates: captionCandidates(original) }\n}\n\n/** \u52A9\u624B\u5199\u5165\u7CBE\u8BFB\u7A3F\uFF08digest.md\uFF09\uFF0C\u72B6\u6001\u8F6C ready\u3002 */\nexport function writeReadingDigest(projectPath: string, id: string, markdown: string): ReadingEntry {\n const entries = readReadingIndex(projectPath)\n const entry = entries.find(item => item.id === id)\n if (entry === undefined) throw new KsrwError('NOT_FOUND', '\u7CBE\u8BFB\u6761\u76EE\u4E0D\u5B58\u5728\uFF1A' + id)\n if (markdown.trim() === '') throw new KsrwError('INVALID', '\u7CBE\u8BFB\u7A3F\u4E0D\u80FD\u4E3A\u7A7A')\n const dirAbs = join(projectPath, entry.dir)\n mkdirSync(dirAbs, { recursive: true })\n const digestRel = entry.dir + '/digest.md'\n writeFileSync(join(projectPath, digestRel), markdown, { encoding: 'utf8', mode: 0o600 })\n return upsertEntry(projectPath, { ...entry, digest: digestRel, status: 'ready', updatedAt: Date.now() })\n}\n\n/** \u5B9A\u65F6\u6293\u53D6\uFF1A\u8FD4\u56DE\u672C\u6B21\u65B0\u589E\u6761\u76EE\uFF08\u8C03\u5EA6\u5668\u4E0E UI\u300C\u7ACB\u5373\u6293\u53D6\u300D\u5171\u7528\uFF09\u3002 */\nexport async function runReadingFetch(\n projectPath: string,\n options: { fetchFn?: FetchFn; days?: number; max?: number } = {},\n): Promise<{ papers: number; added: ReadingEntry[]; prefs: ReadingPrefs }> {\n const prefs = readReadingPrefs(projectPath)\n const max = options.max ?? prefs.schedule.maxPerRun\n const papers = await searchLatestPapers(prefs, { days: options.days ?? 30, max, ...(options.fetchFn === undefined ? {} : { fetchFn: options.fetchFn }) })\n const added = await addReadingPapers(projectPath, papers, options.fetchFn === undefined ? {} : { fetchFn: options.fetchFn })\n const next = writeReadingPrefs(projectPath, { lastRunAt: Date.now() })\n return { papers: papers.length, added, prefs: next }\n}\n\n/** \u626B\u63CF\u6240\u6709\u9879\u76EE\u7684\u5B9A\u65F6\u7CBE\u8BFB\u914D\u7F6E\uFF08\u8C03\u5EA6\u5668\u7528\uFF09\u3002 */\nexport function listScheduledReadingProjects(projectsHome: string): string[] {\n const out: string[] = []\n let dirs: string[] = []\n try { dirs = readdirSync(projectsHome) } catch { return out }\n for (const name of dirs) {\n const projectPath = join(projectsHome, name)\n const prefsFile = join(projectPath, READING_DIR, 'prefs.json')\n if (!existsSync(prefsFile)) continue\n const prefs = readJson<Partial<ReadingPrefs>>(prefsFile)\n if (prefs?.schedule?.enabled !== true) continue\n if (prefs.topics === undefined || prefs.topics.length === 0) continue\n out.push(projectPath)\n }\n return out\n}\n\n/** \u662F\u5426\u5230\u4E86\u4E0B\u6B21\u6293\u53D6\u65F6\u95F4\u3002 */\nexport function readingFetchDue(prefs: ReadingPrefs, now = Date.now()): boolean {\n if (!prefs.schedule.enabled) return false\n const everyMs = Math.max(1, prefs.schedule.everyHours) * 3600 * 1000\n return now - (prefs.lastRunAt || 0) >= everyMs\n}\n\n/** \u9879\u76EE\u5185\u9605\u8BFB\u76EE\u5F55\u662F\u5426\u5B58\u5728\uFF08UI \u5C55\u793A\u7528\uFF09\u3002 */\nexport function readingDirSize(projectPath: string): number {\n try { return statSync(root(projectPath)).size } catch { return 0 }\n}\n", "/** \u4F1A\u8BAE/\u671F\u520A\u6392\u7248\u6A21\u677F\u6CE8\u518C\u8868 + \u6392\u7248\u8981\u6C42\u63CF\u8FF0\uFF08UI \u4E0E agent \u5DE5\u5177\u5171\u7528\u540C\u4E00\u4EFD\u89C4\u683C\uFF09\u3002\n *\n * \u5355\u4E00\u4E8B\u5B9E\u6765\u6E90\uFF1Aplugins/ksrw-core/public/templates/index.json\uFF08\u7531 scripts/build-paper-templates.py\n * \u4ECE scripts/paper-templates.json \u751F\u6210\uFF09\uFF0Ccore \u542F\u52A8\u65F6\u64AD\u79CD\u5230 ~/.ksrw/assets/templates/index.json\u3002\n * \u8FD9\u91CC\u7684 VENUE_TEMPLATES \u53EA\u662F\u515C\u5E95\uFF08index.json \u7F3A\u5931\u65F6 UI \u4ECD\u80FD\u5217\u51FA\u5E38\u7528\u6A21\u677F\uFF09\u3002\n */\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { dirname, join } from 'node:path'\n\nimport { resolveKsrwHome } from './home.ts'\n\nexport interface VenuePageSpec {\n readonly size?: string\n readonly margins?: { readonly top?: number; readonly bottom?: number; readonly left?: number; readonly right?: number }\n readonly columns?: number\n readonly lineNumbers?: boolean\n}\n\nexport interface VenueTextSpec {\n readonly latin?: string\n readonly cjk?: string\n readonly size?: number\n readonly line?: number\n readonly indentChars?: number\n readonly justify?: boolean\n}\n\nexport interface VenueHeadingSpec {\n readonly size?: number\n readonly align?: string\n readonly bold?: boolean\n readonly italic?: boolean\n}\n\nexport interface VenueSpec {\n readonly id: string\n readonly label: string\n readonly page?: VenuePageSpec\n readonly body?: VenueTextSpec\n readonly title?: VenueHeadingSpec\n readonly headings?: readonly VenueHeadingSpec[]\n readonly caption?: VenueHeadingSpec\n readonly code?: { readonly size?: number; readonly latin?: string }\n}\n\nexport interface VenueTemplate {\n readonly id: string\n readonly label: string\n /** \u53CC\u680F\u6A21\u677F\uFF1Adocx/Word \u662F\u53CC\u680F\uFF0CPDF \u9884\u89C8\u7ECF LibreOffice \u4ECD\u662F\u5355\u680F\u3002 */\n readonly columns: number\n readonly note?: string\n}\n\nexport const DEFAULT_VENUE_ID = 'academic-cn'\n\nconst FALLBACK_TEMPLATES: readonly VenueTemplate[] = [\n { id: 'academic-cn', label: '\u4E2D\u6587\u901A\u7528\uFF08A4 \u00B7 \u5B8B\u4F53\u5C0F\u56DB \u00B7 1.5 \u500D\u884C\u8DDD\uFF09', columns: 1 },\n { id: 'ieee', label: 'IEEE \u4F1A\u8BAE\uFF08Letter \u00B7 \u53CC\u680F \u00B7 Times 10pt\uFF09', columns: 2, note: 'Word \u4E3A\u53CC\u680F\uFF1B\u5E94\u7528\u5185 PDF \u9884\u89C8\u6682\u4E3A\u5355\u680F' },\n { id: 'acm', label: 'ACM sigconf\uFF08Letter \u00B7 \u53CC\u680F \u00B7 Times 9pt\uFF09', columns: 2, note: 'Word \u4E3A\u53CC\u680F\uFF1B\u5E94\u7528\u5185 PDF \u9884\u89C8\u6682\u4E3A\u5355\u680F' },\n { id: 'elsevier', label: 'Elsevier \u5BA1\u7A3F\u7A3F\uFF08A4 \u00B7 \u5355\u680F \u00B7 11pt \u00B7 \u53CC\u500D\u884C\u8DDD \u00B7 \u884C\u53F7\uFF09', columns: 1 },\n { id: 'lncs', label: 'Springer LNCS\uFF08A4 \u00B7 \u5355\u680F \u00B7 Times 10pt\uFF09', columns: 1 },\n { id: 'neurips', label: 'NeurIPS \u6295\u7A3F\u7A3F\uFF08Letter \u00B7 \u5355\u680F \u00B7 10pt \u00B7 \u884C\u53F7\uFF09', columns: 1 },\n]\n\nexport interface PaperStyle { readonly venue: string }\n\nfunction ksrwHome(): string {\n return resolveKsrwHome()\n}\n\n/** \u6A21\u677F\u76EE\u5F55\uFF08core \u542F\u52A8\u65F6\u64AD\u79CD\u5230\u8FD9\u91CC\uFF09\u3002 */\nexport function venueTemplateDir(): string {\n return join(ksrwHome(), 'assets', 'templates')\n}\n\n/** \u8BFB\u53D6\u6A21\u677F\u89C4\u683C\u6E05\u5355\uFF08index.json\uFF1B\u7F3A\u5931\u65F6\u8FD4\u56DE\u7A7A\uFF09\u3002 */\nexport function readVenueSpecs(): VenueSpec[] {\n const file = join(venueTemplateDir(), 'index.json')\n try {\n const parsed = JSON.parse(readFileSync(file, 'utf8')) as { templates?: VenueSpec[] }\n return Array.isArray(parsed.templates) ? parsed.templates : []\n } catch { return [] }\n}\n\n/** UI \u7528\u7684\u6A21\u677F\u5217\u8868\uFF08\u4F18\u5148 index.json \u7684\u89C4\u683C\uFF09\u3002 */\nexport function listVenueTemplates(): VenueTemplate[] {\n const specs = readVenueSpecs()\n if (specs.length === 0) return [...FALLBACK_TEMPLATES]\n return specs.map(spec => ({\n id: spec.id,\n label: spec.label,\n columns: spec.page?.columns ?? 1,\n ...(spec.page?.columns === 2 ? { note: 'Word \u4E3A\u53CC\u680F\uFF1B\u5E94\u7528\u5185 PDF \u9884\u89C8\u6682\u4E3A\u5355\u680F' } : {}),\n }))\n}\n\n/** \u517C\u5BB9\u65E7\u8C03\u7528\uFF1A\u9759\u6001\u5217\u8868\u5728 index.json \u4E0D\u53EF\u7528\u65F6\u4ECD\u662F\u53EF\u7528\u515C\u5E95\u3002 */\nexport const VENUE_TEMPLATES: readonly VenueTemplate[] = FALLBACK_TEMPLATES\n\nfunction styleFile(projectPath: string): string {\n return join(projectPath, 'paper', 'style.json')\n}\n\n/** \u8BFB\u53D6\u9879\u76EE\u7684\u6392\u7248\u6A21\u677F\u9009\u62E9\uFF08\u7F3A\u7701 academic-cn\uFF09\u3002 */\nexport function readPaperStyle(projectPath: string): PaperStyle {\n const known = new Set(listVenueTemplates().map(item => item.id))\n try {\n const parsed = JSON.parse(readFileSync(styleFile(projectPath), 'utf8')) as Partial<PaperStyle>\n if (typeof parsed.venue === 'string' && known.has(parsed.venue)) return { venue: parsed.venue }\n } catch { /* \u672A\u9009\u62E9\u8FC7 */ }\n return { venue: DEFAULT_VENUE_ID }\n}\n\nexport function writePaperStyle(projectPath: string, venue: string): PaperStyle {\n if (!listVenueTemplates().some(item => item.id === venue)) throw new Error('\u672A\u77E5\u6392\u7248\u6A21\u677F\uFF1A' + venue)\n const file = styleFile(projectPath)\n mkdirSync(dirname(file), { recursive: true })\n writeFileSync(file, JSON.stringify({ venue }, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n return { venue }\n}\n\nexport function venueTemplate(id: string): VenueTemplate | undefined {\n return listVenueTemplates().find(item => item.id === id)\n}\n\n/** \u9879\u76EE\u5E94\u4F7F\u7528\u7684 reference.docx\uFF1A\u5B58\u5728\u624D\u8FD4\u56DE\uFF08\u5426\u5219\u9000\u56DE pandoc \u9ED8\u8BA4\u6A21\u677F\uFF09\u3002 */\nexport function paperTemplateFor(projectPath: string, env: NodeJS.ProcessEnv = process.env): string | null {\n const explicit = env['KSRW_PAPER_TEMPLATE']\n if (explicit !== undefined && explicit !== '' && existsSync(explicit)) return explicit\n const venue = readPaperStyle(projectPath).venue\n const candidate = join(venueTemplateDir(), venue + '.docx')\n if (existsSync(candidate)) return candidate\n const legacy = join(ksrwHome(), 'assets', 'reference-academic.docx')\n return existsSync(legacy) ? legacy : null\n}\n\n/** \u53D6\u67D0\u4E2A\u6A21\u677F\u7684\u5B8C\u6574\u89C4\u683C\uFF08\u7F3A\u7701\u7528\u5F53\u524D\u9879\u76EE\u7684\u6A21\u677F\uFF09\u3002 */\nexport function venueSpec(id: string): VenueSpec | undefined {\n return readVenueSpecs().find(item => item.id === id)\n}\n\nfunction lineSpacingText(line: number | undefined): string {\n if (line === undefined) return '\u672A\u6307\u5B9A'\n if (Math.abs(line - 1) < 0.01) return '\u5355\u500D\u884C\u8DDD'\n if (Math.abs(line - 1.5) < 0.01) return '1.5 \u500D\u884C\u8DDD'\n if (Math.abs(line - 2) < 0.01) return '\u53CC\u500D\u884C\u8DDD'\n return String(line) + ' \u500D\u884C\u8DDD'\n}\n\n/** \u628A\u6A21\u677F\u89C4\u683C\u7FFB\u8BD1\u6210\u53EF\u6267\u884C\u7684\u6392\u7248\u8981\u6C42\uFF08agent \u4E0E\u7528\u6237\u90FD\u80FD\u6309\u5B83\u68C0\u67E5\uFF09\u3002 */\nexport function describeVenue(spec: VenueSpec): string[] {\n const page = spec.page ?? {}\n const body = spec.body ?? {}\n const margins = page.margins ?? {}\n const lines: string[] = []\n const paperSize = (page.size ?? 'a4').toLowerCase() === 'letter' ? 'Letter\uFF088.5\u00D711 in\uFF09' : 'A4\uFF0821\u00D729.7 cm\uFF09'\n lines.push('\u9875\u9762\uFF1A' + paperSize + '\uFF0C\u9875\u8FB9\u8DDD \u4E0A\u4E0B ' + String(margins.top ?? 2.5) + 'cm / \u5DE6\u53F3 ' + String(margins.left ?? 2.5) + 'cm' + ((page.columns ?? 1) === 2 ? '\uFF0C\u53CC\u680F' : '\uFF0C\u5355\u680F'))\n if (page.lineNumbers === true) lines.push('\u884C\u53F7\uFF1A\u5F00\u542F\uFF08\u4FBF\u4E8E\u8BC4\u5BA1\u5F15\u7528\u884C\u53F7\uFF09')\n lines.push('\u6B63\u6587\uFF1A' + (body.latin ?? 'Times New Roman') + ' / ' + (body.cjk ?? '\u5B8B\u4F53') + '\uFF0C' + String(body.size ?? 12) + 'pt\uFF0C' + lineSpacingText(body.line) + ((body.indentChars ?? 0) > 0 ? '\uFF0C\u9996\u884C\u7F29\u8FDB 2 \u5B57\u7B26' : '\uFF0C\u4E0D\u7F29\u8FDB') + (body.justify === false ? '\uFF0C\u5DE6\u5BF9\u9F50' : '\uFF0C\u4E24\u7AEF\u5BF9\u9F50'))\n const title = spec.title\n if (title !== undefined) lines.push('\u6807\u9898\uFF1A' + String(title.size ?? 18) + 'pt\uFF0C\u52A0\u7C97\uFF0C' + (title.align === 'left' ? '\u5DE6\u5BF9\u9F50' : '\u5C45\u4E2D'))\n const headings = spec.headings ?? []\n if (headings.length > 0) {\n const sizes = headings.map((item, index) => 'H' + String(index + 1) + ' ' + String(item.size ?? '?') + 'pt' + (item.italic === true ? '\uFF08\u659C\u4F53\uFF09' : '') + (item.bold === false ? '' : ' \u52A0\u7C97') + (item.align === 'center' ? ' \u5C45\u4E2D' : '')).join('\uFF1B')\n lines.push('\u5206\u7EA7\u6807\u9898\uFF1A' + sizes)\n }\n const caption = spec.caption\n if (caption !== undefined) lines.push('\u56FE\u8868\u6CE8\uFF1A' + String(caption.size ?? 9) + 'pt\uFF0C' + (caption.align === 'left' ? '\u5DE6\u5BF9\u9F50\uFF0C\u7F6E\u4E8E\u56FE\u4E0B/\u8868\u4E0A' : '\u5C45\u4E2D'))\n const code = spec.code\n if (code !== undefined) lines.push('\u4EE3\u7801\uFF1A' + (code.latin ?? 'Courier New') + ' ' + String(code.size ?? 8.5) + 'pt\uFF0C\u4E0D\u8BED\u6CD5\u9AD8\u4EAE')\n lines.push('\u9875\u811A\uFF1A\u5C45\u4E2D\u9875\u7801')\n return lines\n}\n", "/** \u8BBA\u6587\u56FE\u5E93\u4E0E\u56FE\u8868\u751F\u6210\uFF08kernel \u5171\u4EAB\u5B9E\u73B0\uFF09\uFF1AUI\uFF08ksrw-core\uFF09\u4E0E agent \u5DE5\u5177\uFF08ksrw-tools\uFF09\u5171\u7528\u3002\n *\n * \u76EE\u5F55\u7EA6\u5B9A\uFF1Apaper/figures/<file> + paper/figures/index.json {figures:[{file,title,caption,source,kind,addedAt,svg?}]}\n * \u7ED8\u56FE\uFF1A\u672C\u5730 bundled Python + matplotlib\uFF1Bpython \u89E3\u6790\u987A\u5E8F\u4E0E lab \u4E00\u81F4\uFF08\u73AF\u5883\u53D8\u91CF \u2192 \u8FD0\u884C\u65F6\u76EE\u5F55 \u2192 PATH\uFF09\u3002\n */\nimport { spawnSync } from 'node:child_process'\nimport { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs'\nimport { delimiter, dirname, extname, join, resolve, sep } from 'node:path'\nimport { homedir, tmpdir } from 'node:os'\nimport AdmZip from 'adm-zip'\nimport { resolveKsrwHome } from './home.ts'\nimport { sniffPng } from './pdf.ts'\nimport { createRequire } from 'node:module'\n\nexport type FigureKind = 'result' | 'metrics' | 'architecture' | 'chart' | 'diagram'\n\nexport interface PaperFigure {\n readonly file: string\n readonly title: string\n readonly caption: string\n readonly source: string\n readonly kind: FigureKind\n readonly addedAt: number\n /** \u53EF\u7F16\u8F91\u7684\u77E2\u91CF\u6E90\u6587\u4EF6\uFF08\u6709\u7684\u8BDD\uFF09\u3002 */\n readonly svg?: string\n}\n\nexport interface FigurePoint { readonly x: number; readonly y: number }\nexport interface FigureSeries { readonly name: string; readonly points: readonly FigurePoint[] }\nexport interface FigureCategory { readonly label: string; readonly value: number }\n\nexport interface ChartSpec {\n readonly kind: 'line' | 'bar' | 'scatter'\n readonly title?: string\n readonly xlabel?: string\n readonly ylabel?: string\n readonly series?: readonly FigureSeries[]\n readonly categories?: readonly FigureCategory[]\n readonly width?: number\n readonly height?: number\n}\n\nexport interface FlowNode { readonly id: string; readonly label: string; readonly active?: boolean }\nexport interface FlowEdge { readonly from: string; readonly to: string; readonly label?: string }\nexport interface FlowSpec {\n readonly title?: string\n readonly subtitle?: string\n readonly nodes: readonly FlowNode[]\n readonly edges?: readonly FlowEdge[]\n readonly direction?: 'lr' | 'tb'\n readonly width?: number\n readonly height?: number\n /** \u6A2A\u5411\u5E03\u5C40\u6BCF\u6392\u6700\u591A\u51E0\u4E2A\u8282\u70B9\uFF08\u7F3A\u7701\u81EA\u9002\u5E94\uFF0C\u6700\u591A 5\uFF1B\u8D85\u51FA\u81EA\u52A8\u6362\u884C\u6210\u591A\u6392\uFF09\u3002 */\n readonly maxColumns?: number\n /** \u8282\u70B9\u6807\u7B7E\u5355\u884C\u5B57\u7B26\u9884\u7B97\uFF08\u82F1\u6587\u6309 1\u3001\u4E2D\u6587\u6309 2 \u8BA1\uFF1B\u7F3A\u7701 14\uFF0C\u8D85\u51FA\u6298\u884C\uFF09\u3002 */\n readonly labelWidth?: number\n}\n\nconst FIGURE_DIR = join('paper', 'figures')\nconst FIGURE_EXTS = new Set(['.png', '.jpg', '.jpeg', '.svg'])\n\nexport function figuresDir(projectPath: string): string {\n return join(projectPath, FIGURE_DIR)\n}\n\nfunction indexFile(projectPath: string): string {\n return join(figuresDir(projectPath), 'index.json')\n}\n\nfunction readIndex(projectPath: string): PaperFigure[] | undefined {\n try {\n const parsed = JSON.parse(readFileSync(indexFile(projectPath), 'utf8')) as { figures?: PaperFigure[] }\n if (Array.isArray(parsed.figures)) return parsed.figures\n } catch { /* \u65E0\u7D22\u5F15 */ }\n return undefined\n}\n\n/** \u56FE\u5E93\u6E05\u5355\uFF08\u8BFB index.json\uFF0C\u7F3A\u5931\u65F6\u6309\u6587\u4EF6\u540D\u515C\u5E95\uFF09\u3002 */\nexport function listFigures(projectPath: string): PaperFigure[] {\n const index = readIndex(projectPath)\n if (index !== undefined) return index\n const dir = figuresDir(projectPath)\n if (!existsSync(dir)) return []\n try {\n return readdirSync(dir, { withFileTypes: true })\n .filter(entry => entry.isFile() && FIGURE_EXTS.has(extname(entry.name).toLowerCase()))\n .map(entry => ({ file: entry.name, title: entry.name, caption: entry.name, source: 'manual', kind: 'result' as FigureKind, addedAt: 0 }))\n } catch { return [] }\n}\n\nexport function saveFigures(projectPath: string, figures: PaperFigure[]): void {\n mkdirSync(figuresDir(projectPath), { recursive: true })\n writeFileSync(indexFile(projectPath), JSON.stringify({ figures }, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n}\n\nexport function upsertFigure(projectPath: string, record: PaperFigure): PaperFigure {\n const figures = (readIndex(projectPath) ?? [])\n .filter(item => item.file !== record.file && item.file !== record.svg)\n figures.push(record)\n saveFigures(projectPath, figures)\n return record\n}\n\nfunction sanitize(name: string): string {\n return name.replace(/[^\\w.-]+/g, '_')\n}\n\n/** \u2500\u2500 Python \u89E3\u91CA\u5668\u89E3\u6790\uFF08\u4E0E core/lab \u7684\u5019\u9009\u987A\u5E8F\u4E00\u81F4\uFF0C\u5C3D\u91CF\u4E0D\u91CD\u590D\u5B9E\u73B0\uFF09 \u2500\u2500 */\nconst PY_NAMES = ['python3', 'python3.12', 'python3.13', 'python']\n\nfunction pythonCandidates(): string[] {\n const list: string[] = []\n const explicit = process.env['KSRW_LAB_PYTHON']?.trim()\n if (explicit !== undefined && explicit !== '') list.push(explicit)\n const runtime = process.env['KSRW_LAB_RUNTIME']?.trim()\n if (runtime !== undefined && runtime !== '') {\n for (const name of PY_NAMES) list.push(join(runtime, 'python', 'bin', name))\n }\n const home = resolveKsrwHome()\n for (const name of PY_NAMES) list.push(join(home, 'lab', 'runtime', 'python', 'bin', name))\n // \u5F00\u53D1\u6001\uFF1A\u4ECE cwd \u5411\u4E0A\u627E staging/lab-runtime/runtime\n let cursor = resolve(process.cwd())\n for (let depth = 0; depth < 5; depth += 1) {\n for (const name of PY_NAMES) list.push(join(cursor, 'staging', 'lab-runtime', 'runtime', 'python', 'bin', name))\n const parent = dirname(cursor)\n if (parent === cursor) break\n cursor = parent\n }\n list.push('python3')\n return list\n}\n\nfunction sitePackagesFor(python: string): string | undefined {\n // <runtime>/python/bin/pythonX \u2192 <runtime>/site-packages\uFF08uv \u6784\u5EFA\u7684\u8FD0\u884C\u65F6\u9760 PYTHONPATH \u6302\u8F7D\uFF09\n const candidate = python.replace(/[\\/]python[\\/]bin[\\/][^\\/]+$/, '/site-packages')\n return candidate !== python && existsSync(candidate) ? candidate : undefined\n}\n\nfunction withSiteEnv(python: string): NodeJS.ProcessEnv {\n const site = sitePackagesFor(python)\n if (site === undefined) return { ...process.env }\n return { ...process.env, PYTHONPATH: [site, process.env['PYTHONPATH']].filter(Boolean).join(delimiter) }\n}\n\nfunction pythonCanRun(python: string): boolean {\n if (python.includes('/') && !existsSync(python)) return false\n const probe = spawnSync(python, ['-c', 'import matplotlib'], { encoding: 'utf8', env: withSiteEnv(python) })\n return probe.status === 0\n}\n\nlet cachedPython: string | undefined\n\n/** \u627E\u5230\u80FD import matplotlib \u7684 python\uFF1B\u627E\u4E0D\u5230\u8FD4\u56DE null\u3002\n * P2-9\uFF1A\u53EA\u7F13\u5B58\u6210\u529F\u7ED3\u679C\u2014\u2014\u65E9\u671F\u63A2\u6D4B\u5931\u8D25\uFF08lab runtime \u672A\u5B89\u88C5\uFF09\u4E0D\u80FD\u6C38\u4E45\u9489\u6B7B\u5230\u8FDB\u7A0B\u7ED3\u675F\u3002 */\nexport function figurePython(): string | null {\n if (cachedPython !== undefined) return cachedPython\n const found = pythonCandidates().find(pythonCanRun)\n if (found !== undefined) cachedPython = found\n return found ?? null\n}\n\nfunction siteOf(python: string): string | undefined {\n const runtime = process.env['KSRW_LAB_RUNTIME']?.trim()\n if (runtime !== undefined && runtime !== '') {\n const candidate = join(runtime, 'site-packages')\n if (existsSync(candidate)) return candidate\n }\n return undefined\n}\n\nfunction runPython(script: string, args: string[]): void {\n const python = figurePython()\n if (python === null) throw new Error('\u6CA1\u6709\u53EF\u7528\u7684 Python + matplotlib\uFF08\u5148\u6784\u5EFA lab runtime \u6216\u8BBE\u7F6E KSRW_LAB_PYTHON\uFF09')\n const file = join(tmpdir(), 'ksrw-figure-' + String(Date.now()) + '-' + String(Math.random()).slice(2, 6) + '.py')\n writeFileSync(file, script, { encoding: 'utf8', mode: 0o600 })\n const result = spawnSync(python, [file, ...args], { encoding: 'utf8', timeout: 120_000, env: withSiteEnv(python) })\n if (result.status !== 0) {\n throw new Error('\u7ED8\u56FE\u5931\u8D25\uFF1A' + ((result.stderr ?? '') + (result.stdout ?? '')).trim().slice(-400))\n }\n}\n\nconst CHART_SCRIPT = `\nimport json, sys\nfrom pathlib import Path\nimport matplotlib\nmatplotlib.use('Agg')\nimport matplotlib.pyplot as plt\nspec = json.loads(Path(sys.argv[1]).read_text(encoding='utf-8'))\nout = Path(sys.argv[2])\nout.parent.mkdir(parents=True, exist_ok=True)\nfig, ax = plt.subplots(figsize=(spec.get('width', 6), spec.get('height', 3.4)), dpi=200)\nkind = spec.get('kind', 'line')\nif spec.get('categories'):\n labels = [c['label'] for c in spec['categories']]\n values = [float(c['value']) for c in spec['categories']]\n ax.bar(labels, values, color='#b85c2e')\n ax.tick_params(axis='x', rotation=20)\nelse:\n for series in spec.get('series', []):\n xs = [float(p['x']) for p in series['points']]\n ys = [float(p['y']) for p in series['points']]\n if kind == 'bar':\n ax.bar(xs, ys, label=series['name'])\n elif kind == 'scatter':\n ax.scatter(xs, ys, label=series['name'], s=18)\n else:\n ax.plot(xs, ys, label=series['name'])\n if spec.get('series'):\n ax.legend()\nif spec.get('title'):\n ax.set_title(spec['title'])\nif spec.get('xlabel'):\n ax.set_xlabel(spec['xlabel'])\nif spec.get('ylabel'):\n ax.set_ylabel(spec['ylabel'])\nfig.tight_layout()\nfig.savefig(out)\nprint('saved', out)\n`\n\nconst FLOW_SCRIPT = `\nimport json, math, sys\nfrom pathlib import Path\nimport matplotlib\nmatplotlib.use('Agg')\nimport matplotlib.pyplot as plt\nfrom matplotlib import font_manager\nfrom matplotlib.patches import FancyBboxPatch, FancyArrowPatch\n\nspec = json.loads(Path(sys.argv[1]).read_text(encoding='utf-8'))\nout = Path(sys.argv[2])\nNL = chr(10)\nnodes = spec['nodes']\nedges = spec.get('edges', [])\ndirection = spec.get('direction', 'lr')\nactive = {node['id'] for node in nodes if node.get('active')}\n\n# CJK \u5B57\u4F53\u5C3D\u529B\u800C\u4E3A\uFF1A\u547D\u4E2D\u5219\u4E2D\u6587\u6807\u7B7E\u53EF\u8BFB\uFF0C\u672A\u547D\u4E2D\u65F6\u82F1\u6587\u6807\u7B7E\u4ECD\u6B63\u5E38\uFF08\u4E2D\u6587\u663E\u793A\u65B9\u5757\uFF09\nCJK_FONTS = ['PingFang SC', 'Hiragino Sans GB', 'Songti SC', 'Noto Sans CJK SC', 'Source Han Sans SC', 'Microsoft YaHei', 'SimHei', 'WenQuanYi Micro Hei']\ntry:\n available = {face.name for face in font_manager.fontManager.ttflist}\n picked = next((name for name in CJK_FONTS if name in available), None)\n if picked is not None:\n plt.rcParams['font.sans-serif'] = [picked, 'DejaVu Sans']\n plt.rcParams['axes.unicode_minus'] = False\nexcept Exception:\n pass\n\ndef display_width(text):\n return sum(2 if ord(ch) > 0x2e80 else 1 for ch in text)\n\ndef wrap_label(text, limit):\n lines, line, width = [], '', 0\n for ch in text:\n w = 2 if ord(ch) > 0x2e80 else 1\n if width + w > limit and line != '':\n lines.append(line)\n line, width = ch, w\n else:\n line += ch\n width += w\n if line != '':\n lines.append(line)\n if len(lines) > 3:\n lines = lines[:3]\n lines[-1] = lines[-1][:-1] + '\u2026'\n return lines or ['']\n\n# \u6BCF\u884C\u5B57\u7B26\u9884\u7B97\uFF08\u82F1\u6587\u6309 1\u3001\u4E2D\u6587\u6309 2\uFF1B\u7F3A\u7701 14\uFF09\uFF0C\u8282\u70B9\u6587\u672C\u5148\u6298\u884C\u518D\u5B9A\u76D2\u5BBD/\u76D2\u9AD8\nbudget = int(spec.get('labelWidth') or 14)\nlabels = {}\nfor node in nodes:\n labels[node['id']] = wrap_label(str(node.get('label', node['id'])), budget)\nmax_lines = max([len(value) for value in labels.values()] or [1])\n\nBOX_W = 0.245 * budget + 0.70\nBOX_H = 0.55 + 0.42 * max_lines\nGAP_X = 0.90\nGAP_Y = 1.00\nPAD = 0.45\n\ncount = len(nodes)\nif direction == 'lr':\n cols = int(spec.get('maxColumns') or 0)\n if cols <= 0:\n # \u5355\u6392\u8D85\u8FC7 5 \u4E2A\u5C31\u6362\u884C\uFF1A\u65E7\u5B9E\u73B0\u5355\u6392\u5E73\u94FA\uFF0C\u8282\u70B9\u4E00\u591A\u76D2\u5B50\u5FC5\u7136\u91CD\u53E0\n cols = max(1, min(count, 5))\n rows = int(math.ceil(count / float(cols)))\nelse:\n cols = 1\n rows = count\n\nrow_counts = [min(cols, count - r * cols) for r in range(rows)]\nspan_x = cols * BOX_W + (cols - 1) * GAP_X + 2 * PAD\nspan_y = rows * BOX_H + (rows - 1) * GAP_Y + 2 * PAD\nbottom = -0.55 if spec.get('subtitle') else 0.0\n\nwidth_in = float(spec.get('width') or min(9.5, max(4.2, 0.30 * span_x)))\nheight_in = float(spec.get('height') or min(8.0, max(1.6, 0.36 * span_y)))\nfig, ax = plt.subplots(figsize=(width_in, height_in), dpi=200)\nax.axis('off')\n\npositions = {}\nfor index, node in enumerate(nodes):\n r = index // cols\n c = index % cols\n in_row = row_counts[r]\n offset = (cols - in_row) * (BOX_W + GAP_X) / 2.0\n x = PAD + offset + c * (BOX_W + GAP_X)\n y = PAD + (rows - 1 - r) * (BOX_H + GAP_Y)\n positions[node['id']] = (x, y)\n\nfor node in nodes:\n x, y = positions[node['id']]\n hot = node['id'] in active\n ax.add_patch(FancyBboxPatch((x, y), BOX_W, BOX_H,\n boxstyle='round,pad=0.02,rounding_size=0.10',\n linewidth=1.3,\n edgecolor='#b85c2e' if hot else '#b9b1a6',\n facecolor='#f6e7dc' if hot else '#fbf8f4'))\n ax.text(x + BOX_W / 2.0, y + BOX_H / 2.0, NL.join(labels[node['id']]),\n ha='center', va='center', fontsize=9.5, linespacing=1.3,\n color='#7a3d1c' if hot else '#4a443c')\n\nfor edge in edges:\n src = edge.get('from')\n dst = edge.get('to')\n if src not in positions or dst not in positions or src == dst:\n continue\n x1, y1 = positions[src]\n x2, y2 = positions[dst]\n same_row = abs(y1 - y2) < 1e-6\n if same_row:\n if x2 > x1:\n start, end = (x1 + BOX_W, y1 + BOX_H / 2.0), (x2, y2 + BOX_H / 2.0)\n else:\n start, end = (x1, y1 + BOX_H / 2.0), (x2 + BOX_W, y2 + BOX_H / 2.0)\n ax.add_patch(FancyArrowPatch(start, end, arrowstyle='-|>', mutation_scale=11,\n color='#8a8378', lw=1.1))\n mid = ((start[0] + end[0]) / 2.0, (start[1] + end[1]) / 2.0)\n label_va = 'bottom'\n else:\n if y2 < y1:\n start, end = (x1 + BOX_W / 2.0, y1), (x2 + BOX_W / 2.0, y2 + BOX_H)\n else:\n start, end = (x1 + BOX_W / 2.0, y1 + BOX_H), (x2 + BOX_W / 2.0, y2)\n rad = 0.0 if abs(x1 - x2) < 0.01 else 0.18\n ax.add_patch(FancyArrowPatch(start, end, arrowstyle='-|>', mutation_scale=11,\n color='#8a8378', lw=1.1,\n connectionstyle='arc3,rad=' + str(rad)))\n mid = ((start[0] + end[0]) / 2.0, (start[1] + end[1]) / 2.0)\n label_va = 'center'\n if edge.get('label'):\n ax.text(mid[0], mid[1], str(edge['label']), fontsize=7.5, color='#6b635a',\n ha='center', va=label_va)\n\nax.set_xlim(0, span_x)\nax.set_ylim(bottom, span_y)\nif spec.get('title'):\n ax.set_title(str(spec['title']), fontsize=12, pad=8)\nif spec.get('subtitle'):\n ax.text(PAD * 0.6, bottom + 0.10, str(spec['subtitle']), fontsize=8.5,\n color='#6b635a', ha='left', va='bottom')\nfig.tight_layout()\nfig.savefig(out, bbox_inches='tight')\nprint('saved', out)\n`\n\nfunction writeFigure(projectPath: string, spec: { file: string; title: string; caption: string; source: string; kind: FigureKind; svg?: string }): PaperFigure {\n mkdirSync(figuresDir(projectPath), { recursive: true })\n return upsertFigure(projectPath, { ...spec, addedAt: Date.now() })\n}\n\n/** \u753B\u4E00\u5F20\u6570\u636E\u56FE\uFF08\u6298\u7EBF/\u67F1/\u6563\u70B9\uFF09\uFF0C\u843D\u5230 paper/figures \u5E76\u767B\u8BB0\u3002 */\nexport function renderChart(projectPath: string, spec: ChartSpec, meta: { file?: string; title?: string; caption?: string; source?: string; kind?: PaperFigure['kind'] } = {}): PaperFigure {\n const hasSeries = (spec.series ?? []).length > 0\n const hasCategories = (spec.categories ?? []).length > 0\n if (!hasSeries && !hasCategories) throw new Error('\u56FE\u8868\u6CA1\u6709\u6570\u636E\uFF1Aseries \u6216 categories \u81F3\u5C11\u7ED9\u4E00\u4E2A')\n const file = sanitize(meta.file ?? 'chart-' + String(Date.now()).slice(-6)) + '.png'\n const out = join(figuresDir(projectPath), file)\n mkdirSync(figuresDir(projectPath), { recursive: true })\n const payload = join(tmpdir(), 'ksrw-chart-' + String(Date.now()) + '.json')\n writeFileSync(payload, JSON.stringify(spec), 'utf8')\n runPython(CHART_SCRIPT, [payload, out])\n return writeFigure(projectPath, {\n file,\n title: meta.title ?? spec.title ?? '\u6570\u636E\u56FE',\n caption: meta.caption ?? spec.title ?? '\u6570\u636E\u56FE',\n source: meta.source ?? 'agent',\n kind: meta.kind ?? 'chart',\n })\n}\n\n/** \u753B\u6D41\u7A0B\u56FE/\u67B6\u6784\u56FE\uFF08\u8282\u70B9 + \u8FB9\uFF09\uFF0C\u843D\u5230 paper/figures \u5E76\u767B\u8BB0\u3002 */\nexport function renderFlow(projectPath: string, spec: FlowSpec, meta: { file?: string; title?: string; caption?: string; source?: string; kind?: PaperFigure['kind']; svg?: string } = {}): PaperFigure {\n if (spec.nodes.length === 0) throw new Error('\u6D41\u7A0B\u56FE\u6CA1\u6709\u8282\u70B9')\n const file = sanitize(meta.file ?? 'flow-' + String(Date.now()).slice(-6)) + '.png'\n const out = join(figuresDir(projectPath), file)\n mkdirSync(figuresDir(projectPath), { recursive: true })\n const payload = join(tmpdir(), 'ksrw-flow-' + String(Date.now()) + '.json')\n writeFileSync(payload, JSON.stringify(spec), 'utf8')\n runPython(FLOW_SCRIPT, [payload, out])\n return writeFigure(projectPath, {\n file,\n title: meta.title ?? spec.title ?? '\u6D41\u7A0B\u793A\u610F\u56FE',\n caption: meta.caption ?? spec.title ?? '\u6D41\u7A0B\u793A\u610F\u56FE',\n source: meta.source ?? 'agent',\n kind: meta.kind ?? 'diagram',\n ...(meta.svg === undefined ? {} : { svg: meta.svg }),\n })\n}\n\nfunction metricsFromRun(runDir: string): Record<string, number | string> {\n try { return JSON.parse(readFileSync(join(runDir, 'metrics.json'), 'utf8')) as Record<string, number | string> } catch { return {} }\n}\n\n/** \u4ECE\u5B9E\u9A8C run \u7684 metrics.json \u751F\u6210\u67F1\u72B6\u56FE\u3002 */\nexport function renderMetricsChart(projectPath: string, runId: string, meta: { title?: string; caption?: string } = {}): PaperFigure {\n const runDir = join(projectPath, 'experiments', 'runs', runId)\n const metrics = metricsFromRun(runDir)\n const numeric = Object.entries(metrics).filter(([, value]) => typeof value === 'number')\n if (numeric.length === 0) throw new Error('\u8BE5 run \u6CA1\u6709\u6570\u503C\u6307\u6807\uFF1A' + runId)\n let script = runId\n try {\n const config = JSON.parse(readFileSync(join(runDir, 'config.json'), 'utf8')) as { script?: string }\n if (typeof config.script === 'string') script = config.script\n } catch { /* \u65E0 config */ }\n return renderChart(projectPath, {\n kind: 'bar',\n categories: numeric.map(([label, value]) => ({ label, value: Number(value) })),\n title: meta.title ?? 'Metrics \u00B7 ' + script,\n }, {\n file: 'metrics-' + runId,\n title: '\u6307\u6807\u56FE \u00B7 ' + script,\n caption: meta.caption ?? '\u5B9E\u9A8C ' + runId + ' \u7684\u6570\u503C\u6307\u6807',\n source: runId,\n kind: 'metrics',\n })\n}\n\nexport interface IngestResult { readonly file: string; readonly caption: string }\n\n/** \u5B9E\u9A8C\u811A\u672C\u5199 artifacts/figures/ \u7684\u56FE\uFF0C\u6536\u8FDB\u8BBA\u6587\u56FE\u5E93\u3002 */\nexport function ingestRunFigures(projectPath: string, runId: string): PaperFigure[] {\n const sourceDir = join(projectPath, 'experiments', 'runs', runId, 'artifacts', 'figures')\n if (!existsSync(sourceDir)) return []\n mkdirSync(figuresDir(projectPath), { recursive: true })\n const ingested: string[] = []\n let entries: string[] = []\n try { entries = readdirSync(sourceDir) } catch { return [] }\n for (const name of entries) {\n if (!FIGURE_EXTS.has(extname(name).toLowerCase())) continue\n const target = sanitize(runId + '-' + name)\n copyFileSync(join(sourceDir, name), join(figuresDir(projectPath), target))\n writeFigure(projectPath, {\n file: target,\n title: name,\n caption: '\u5B9E\u9A8C ' + runId + ' \u8F93\u51FA\uFF1A' + name.replace(/\\.[^.]+$/, ''),\n source: runId,\n kind: 'result',\n })\n ingested.push(target)\n }\n return listFigures(projectPath).filter(item => ingested.includes(item.file))\n}\n\n/** \u628A\u56FE\u63D2\u5230\u6B63\u6587\uFF1A\u7ED9\u4E86 heading \u5C31\u63D2\u5728\u8BE5\u6807\u9898\u5C0F\u8282\u672B\u5C3E\uFF0C\u5426\u5219\u8FFD\u52A0\u5230\u6587\u672B\uFF1B\u5DF2\u5B58\u5728\u5219\u4E0D\u91CD\u590D\u3002 */\nexport function insertFigureMarkdown(\n projectPath: string,\n source: string,\n file: string,\n caption: string,\n heading?: string,\n): { source: string; added: string; insertedAfter?: string } {\n const absolute = join(projectPath, source)\n if (!existsSync(absolute)) throw new Error('\u6B63\u6587\u4E0D\u5B58\u5728\uFF1A' + source)\n const text = readFileSync(absolute, 'utf8')\n const snippet = '![' + caption + '](figures/' + file + ')'\n if (text.includes(snippet)) return { source, added: snippet }\n if (heading !== undefined && heading.trim() !== '') {\n const lines = text.split('\\n')\n const targetIndex = lines.findIndex(line => line.replace(/^#+\\s*/, '').trim().startsWith(heading.trim()))\n if (targetIndex >= 0) {\n let end = lines.length\n for (let index = targetIndex + 1; index < lines.length; index += 1) {\n if (/^#{1,2}\\s+/.test(lines[index] ?? '')) { end = index; break }\n }\n while (end > targetIndex + 1 && (lines[end - 1] ?? '').trim() === '') end -= 1\n lines.splice(end, 0, '', snippet, '')\n writeFileSync(absolute, lines.join('\\n'), { encoding: 'utf8' })\n return { source, added: snippet, insertedAfter: lines[targetIndex] ?? heading }\n }\n }\n const next = text.replace(/\\s+$/, '') + '\\n\\n' + snippet + '\\n'\n writeFileSync(absolute, next, { encoding: 'utf8' })\n return { source, added: snippet }\n}\n\n/** \u8BBE\u7F6E\u56FE\u6CE8\uFF08index.json \u91CC\u66F4\u65B0\uFF09\u3002 */\nexport function setFigureCaption(projectPath: string, file: string, caption: string): PaperFigure {\n const record = listFigures(projectPath).find(item => item.file === file)\n if (record === undefined) throw new Error('\u56FE\u4E0D\u5B58\u5728\uFF1A' + file)\n return upsertFigure(projectPath, { ...record, caption })\n}\n\n/** \u5220\u9664\u4E00\u5F20\u56FE\uFF08\u6587\u4EF6 + \u7D22\u5F15\uFF09\u3002 */\nexport function removeFigure(projectPath: string, file: string): boolean {\n const figures = listFigures(projectPath)\n if (!figures.some(item => item.file === file)) return false\n saveFigures(projectPath, figures.filter(item => item.file !== file))\n try {\n const absolute = join(figuresDir(projectPath), file)\n if (existsSync(absolute)) writeFileSync(absolute, Buffer.alloc(0))\n if (existsSync(absolute) && statSync(absolute).size === 0) require('node:fs').unlinkSync(absolute)\n } catch { /* \u6587\u4EF6\u5220\u9664\u5931\u8D25\u5FFD\u7565 */ }\n return true\n}\n\n/**\n * \u4ECE DOCX\uFF08\u672C\u8D28\u662F zip\uFF09\u91CC\u62BD\u51FA\u5185\u5D4C\u56FE\u7247\u5E76\u767B\u8BB0\u8FDB\u56FE\u5E93\u3002\n *\n * \u4E8B\u6545\u80CC\u666F\uFF082026-10-30\uFF09\uFF1ADOCX \u2192 Markdown \u5BFC\u5165\u53EA\u62BD\u4E86\u6B63\u6587\u6587\u5B57\uFF0Cword/media/ \u91CC\u7684\n * \u539F\u59CB\u63D2\u56FE\u88AB\u4E22\u6389\uFF1B\u8BBA\u6587\u5DE5\u574A\u53C8\u4E0D\u5411 agent \u63D0\u4F9B shell/\u89E3\u538B\u5DE5\u5177\uFF0C\u539F\u56FE\u4ECE\u6B64\u4E0D\u53EF\u8FBE\u3002\n * \u8FD9\u91CC\u628A\u8FD9\u4E00\u6B65\u6536\u8FDB\u4EA7\u54C1\u5185\u7F6E\u80FD\u529B\uFF1A\u53EA\u8BFB zip\u3001\u8DEF\u5F84\u9650\u5B9A\u5728\u9879\u76EE\u5185\u3001\u62BD\u51FA\u7684\u56FE\u76F4\u63A5\u8FDB\n * paper/figures \u5E76\u767B\u8BB0\uFF08source=docx\uFF09\uFF0CUI \u56FE\u4EF6\u5361\u4E0E agent \u90FD\u80FD\u76F4\u63A5\u7528\u3002\n */\nexport interface DocxMediaResult {\n /** \u63D0\u53D6\u5E76\u767B\u8BB0\u8FDB\u56FE\u5E93\u7684\u56FE\u7247\u3002 */\n readonly figures: PaperFigure[]\n /** \u672A\u843D\u76D8\u7684\u6210\u5458\uFF08emf/wmf \u7B49\u975E png/jpg/svg\uFF09\u3002 */\n readonly skipped: string[]\n /** \u88AB\u8BFB\u53D6\u7684 DOCX\uFF08\u76F8\u5BF9\u9879\u76EE\uFF09\u3002 */\n readonly source: string\n}\n\n/** \u5DE5\u574A DOCX \u9644\u4EF6\u7684\u9ED8\u8BA4\u4F4D\u7F6E\uFF08\u5BFC\u5165\u6D41\u7A0B\u628A\u4E0A\u4F20\u4EF6\u56FA\u5B9A\u5199\u6210 attachment.docx\uFF09\u3002 */\nfunction defaultDocxPath(projectPath: string): string {\n for (const candidate of ['paper/attachment.docx', 'paper/source.docx']) {\n if (existsSync(join(projectPath, candidate))) return candidate\n }\n try {\n const entries = readdirSync(join(projectPath, 'paper'), { withFileTypes: true })\n const found = entries.find(entry => entry.isFile() && entry.name.toLowerCase().endsWith('.docx'))\n if (found !== undefined) return join('paper', found.name)\n } catch { /* paper/ \u53EF\u80FD\u8FD8\u4E0D\u5B58\u5728 */ }\n throw new Error('\u5DE5\u574A\u91CC\u6CA1\u6709 DOCX \u9644\u4EF6\uFF08\u5148\u4E0A\u4F20\u8BBA\u6587\uFF0C\u6216\u5728\u53C2\u6570\u91CC\u6307\u5B9A docx \u8DEF\u5F84\uFF09')\n}\n\nexport function extractDocxMedia(projectPath: string, options: { docx?: string; prefix?: string } = {}): DocxMediaResult {\n const root = resolve(projectPath)\n const docx = options.docx ?? defaultDocxPath(root)\n const absolute = resolve(root, docx)\n if (absolute !== root && !absolute.startsWith(root + sep)) throw new Error('DOCX \u8DEF\u5F84\u8D8A\u754C\uFF1A' + docx)\n if (!existsSync(absolute)) throw new Error('\u627E\u4E0D\u5230 DOCX\uFF1A' + docx)\n const zip = new AdmZip(absolute)\n const prefix = sanitize(options.prefix ?? 'docx')\n const figures: PaperFigure[] = []\n const skipped: string[] = []\n mkdirSync(figuresDir(root), { recursive: true })\n for (const entry of zip.getEntries()) {\n if (entry.isDirectory || !entry.entryName.startsWith('word/media/')) continue\n const extension = extname(entry.entryName).toLowerCase()\n if (!FIGURE_EXTS.has(extension)) { skipped.push(entry.entryName); continue }\n const base = sanitize((entry.entryName.split('/').pop() ?? 'image').replace(/.[^.]+$/, ''))\n let name = prefix + '-' + base + extension\n let counter = 2\n while (existsSync(join(figuresDir(root), name))) {\n name = prefix + '-' + base + '-' + String(counter) + extension\n counter += 1\n }\n writeFileSync(join(figuresDir(root), name), entry.getData(), { mode: 0o600 })\n figures.push(writeFigure(root, {\n file: name,\n title: '\u539F\u56FE \u00B7 ' + (entry.entryName.split('/').pop() ?? name),\n caption: '\u4ECE ' + docx + ' \u5185\u5D4C\u5A92\u4F53\u63D0\u53D6\u7684\u539F\u56FE\uFF08' + entry.entryName + '\uFF09',\n source: 'docx',\n kind: 'result',\n }))\n }\n if (figures.length === 0) throw new Error('DOCX \u91CC\u6CA1\u6709\u53EF\u63D0\u53D6\u7684\u56FE\u7247\uFF08png/jpg/svg\uFF09\uFF1A' + docx)\n return { figures, skipped, source: docx }\n}\n\n/** \u2500\u2500 \u6536\u7F16\u5DF2\u6709\u56FE\uFF08agent \u624B\u5199 SVG / \u5916\u90E8\u5BFC\u51FA PNG\uFF09\uFF1A\u767B\u8BB0 + SVG\u2192PNG 300dpi + \u6539\u5199\u6B63\u6587\u5F15\u7528 \u2500\u2500 */\n\nexport interface AdoptInput {\n readonly file: string\n readonly caption?: string\n readonly title?: string\n readonly kind?: PaperFigure['kind']\n readonly source?: string\n /** \u9ED8\u8BA4 true\uFF1A\u628A\u6B63\u6587\u91CC figures/<name>.svg \u7684\u5F15\u7528\u6539\u6210 .png\uFF08PNG \u66F4\u7A33\uFF09\u3002 */\n readonly rewrite?: boolean\n /** \u6B63\u6587\u76F8\u5BF9\u8DEF\u5F84\uFF0C\u7F3A\u7701 paper/main.md\u3002 */\n readonly draft?: string\n}\n\nfunction projectRelativeFigure(projectPath: string, file: string): string {\n const normalized = file.replace(/^\\/+/, '').replace(/^paper\\//, '')\n const relative = normalized.startsWith('figures/') ? normalized : 'figures/' + normalized\n const absolute = resolve(projectPath, 'paper', relative)\n const root = resolve(projectPath, 'paper')\n if (absolute !== root && !absolute.startsWith(root + '/')) throw new Error('\u56FE\u5FC5\u987B\u4F4D\u4E8E\u9879\u76EE paper/ \u76EE\u5F55\u5185\uFF1A' + file)\n return absolute\n}\n\nfunction svgPixelWidth(svg: string): number | undefined {\n const match = /<svg[^>]*\\swidth=[\"']([0-9.]+)(?:px)?[\"']/i.exec(svg)\n if (match === null) return undefined\n const width = Number(match[1])\n return Number.isFinite(width) && width > 0 ? width : undefined\n}\n\nfunction rasterizeSvg(svg: string, targetWidth: number): Uint8Array {\n // \u61D2\u52A0\u8F7D\u539F\u751F\u6A21\u5757\uFF1A\u53EA\u6709\u771F\u6B63\u8F6C\u56FE\u65F6\u624D\u4ED8\u52A0\u8F7D\u6210\u672C\u3002\n const req = createRequire(import.meta.url)\n const { Resvg } = req('@resvg/resvg-js') as { Resvg: new (svg: string, options: Record<string, unknown>) => { render(): { asPng(): Uint8Array } } }\n const renderer = new Resvg(svg, { fitTo: { mode: 'width', value: Math.min(6000, Math.max(300, Math.round(targetWidth))) }, background: 'white' })\n return renderer.render().asPng()\n}\n\n/** \u628A\u5DF2\u6709\u56FE\u6536\u7F16\u8FDB\u56FE\u5E93\uFF1ASVG \u989D\u5916\u751F\u6210 300dpi PNG \u5E76\u767B\u8BB0\uFF0C\u6B63\u6587\u5F15\u7528\u6539\u6210 PNG\u3002 */\nexport function adoptFigure(projectPath: string, input: AdoptInput): PaperFigure {\n const absolute = projectRelativeFigure(projectPath, input.file)\n if (!existsSync(absolute)) throw new Error('\u56FE\u4E0D\u5B58\u5728\uFF1A' + input.file)\n const extension = extname(absolute).toLowerCase()\n if (!FIGURE_EXTS.has(extension)) throw new Error('\u53EA\u652F\u6301 png/jpg/jpeg/svg\uFF1A' + input.file)\n const base = absolute.slice(0, absolute.length - extension.length).split('/').pop() ?? 'figure'\n const name = sanitize(base)\n mkdirSync(figuresDir(projectPath), { recursive: true })\n let pngName = name + '.png'\n let svgName: string | undefined\n if (extension === '.svg') {\n const svg = readFileSync(absolute, 'utf8')\n const width = svgPixelWidth(svg) ?? 900\n const png = rasterizeSvg(svg, (width / 96) * 300)\n writeFileSync(join(figuresDir(projectPath), pngName), png, { mode: 0o600 })\n svgName = name + '.svg'\n } else if (extension !== '.png') {\n pngName = name + extension\n }\n const caption = input.caption ?? input.title ?? name\n const record = upsertFigure(projectPath, {\n file: pngName,\n title: input.title ?? caption,\n caption,\n source: input.source ?? 'adopted',\n kind: input.kind ?? 'diagram',\n addedAt: Date.now(),\n ...(svgName === undefined ? {} : { svg: svgName }),\n })\n if (input.rewrite !== false) {\n const draft = input.draft ?? 'paper/main.md'\n const draftPath = join(projectPath, draft)\n if (existsSync(draftPath)) {\n const text = readFileSync(draftPath, 'utf8')\n const next = text\n .split('(figures/' + name + '.svg)').join('(figures/' + pngName + ')')\n .split('(figures/' + name + '.jpg)').join('(figures/' + pngName + ')')\n if (next !== text) writeFileSync(draftPath, next, { encoding: 'utf8' })\n }\n }\n return record\n}\n\n/** \u626B figures/ \u4E0B\u672A\u767B\u8BB0\u7684\u56FE\u5E76\u9010\u4E00\u6536\u7F16\u3002 */\nexport function adoptUnregisteredFigures(projectPath: string, options: { captionPrefix?: string } = {}): PaperFigure[] {\n const dir = figuresDir(projectPath)\n if (!existsSync(dir)) return []\n const registered = new Set<string>()\n for (const record of listFigures(projectPath)) {\n registered.add(record.file)\n if (record.svg !== undefined) registered.add(record.svg)\n }\n const adopted: PaperFigure[] = []\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (!entry.isFile()) continue\n const extension = extname(entry.name).toLowerCase()\n if (!FIGURE_EXTS.has(extension)) continue\n if (registered.has(entry.name)) continue\n try {\n adopted.push(adoptFigure(projectPath, {\n file: 'figures/' + entry.name,\n caption: (options.captionPrefix ?? '') + entry.name.replace(/\\.[^.]+$/, ''),\n kind: 'diagram',\n source: 'adopted',\n rewrite: true,\n }))\n } catch { /* \u5355\u5F20\u5931\u8D25\u4E0D\u963B\u585E\u5176\u4F59 */ }\n }\n return adopted\n}\n\nexport interface FigureIssue {\n readonly file: string\n readonly level: 'error' | 'warn'\n readonly message: string\n}\n\n/** \u56FE\u5E93\u4F53\u68C0\uFF1A\u6587\u4EF6\u5B58\u5728\u6027\u3001\u50CF\u7D20\u5BBD\u5EA6\u3001\u56FE\u6CE8\u3001\u6B63\u6587\u5F15\u7528\u3001SVG \u662F\u5426\u6709 PNG \u526F\u672C\u3002 */\nexport function validateFigures(projectPath: string, options: { file?: string; draft?: string } = {}): FigureIssue[] {\n const issues: FigureIssue[] = []\n const figures = listFigures(projectPath).filter(item => options.file === undefined || item.file === options.file)\n const draftPath = join(projectPath, options.draft ?? 'paper/main.md')\n let draft = ''\n try { draft = readFileSync(draftPath, 'utf8') } catch { issues.push({ file: options.draft ?? 'paper/main.md', level: 'warn', message: '\u6B63\u6587\u4E0D\u5B58\u5728\uFF0C\u65E0\u6CD5\u68C0\u67E5\u56FE\u7247\u5F15\u7528' }) }\n for (const record of figures) {\n const absolute = join(figuresDir(projectPath), record.file)\n if (!existsSync(absolute)) { issues.push({ file: record.file, level: 'error', message: '\u6587\u4EF6\u4E0D\u5B58\u5728' }); continue }\n if (extname(record.file).toLowerCase() === '.png') {\n const dims = sniffPng(new Uint8Array(readFileSync(absolute)))\n if (dims !== null && dims.width < 1200) issues.push({ file: record.file, level: 'warn', message: '\u5BBD\u5EA6\u4EC5 ' + String(dims.width) + 'px\uFF0C\u6295\u7A3F\u5EFA\u8BAE \u22651200px\uFF08300dpi\uFF09' })\n }\n if (record.svg === undefined && extname(record.file).toLowerCase() === '.svg') {\n issues.push({ file: record.file, level: 'warn', message: '\u53EA\u6709 SVG\uFF0C\u6CA1\u6709 PNG \u526F\u672C' })\n }\n if (record.caption.trim() === '' || record.caption === record.file) issues.push({ file: record.file, level: 'warn', message: '\u56FE\u6CE8\u7F3A\u5931\u6216\u7B49\u4E8E\u6587\u4EF6\u540D' })\n if (draft !== '' && !draft.includes('figures/' + record.file)) issues.push({ file: record.file, level: 'warn', message: '\u6B63\u6587\u6CA1\u6709\u5F15\u7528\u8FD9\u5F20\u56FE' })\n }\n return issues\n}\n", "/** \u6295\u7A3F\u8981\u6C42\u4E0E\u4FEE\u6539\u6E05\u5355\uFF1A\u628A\u51FA\u7248\u793E/\u4F1A\u8BAE\u7684\u8981\u6C42\u843D\u6210\u53EF\u52FE\u9009\u7684\u6E05\u5355\uFF0C\u5E76\u8BA9\u52A9\u624B\u6309\u6E05\u5355\u6539\u7A3F\u3002 */\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\n\nimport { KsrwError } from './error.ts'\n\nexport type RequirementStatus = 'todo' | 'done' | 'na'\n\nexport interface VenueRequirement {\n readonly id: string\n readonly text: string\n readonly status: RequirementStatus\n readonly note?: string\n}\n\nexport interface VenueProfile {\n readonly name: string\n readonly kind: string\n readonly deadline: string\n /** \u51FA\u7248\u793E/\u4F1A\u8BAE\u7684\u539F\u59CB\u8981\u6C42\u5168\u6587\uFF08\u4F5C\u8005\u987B\u77E5\u3001\u4FEE\u6539\u610F\u89C1\u7B49\uFF09\u3002 */\n readonly requirements: string\n readonly checklist: VenueRequirement[]\n readonly updatedAt: number\n}\n\nexport const DEFAULT_VENUE_PROFILE: VenueProfile = {\n name: '',\n kind: '',\n deadline: '',\n requirements: '',\n checklist: [],\n updatedAt: 0,\n}\n\nfunction venueFile(projectPath: string): string {\n return join(projectPath, 'paper', 'venue.json')\n}\n\nexport function readVenueProfile(projectPath: string): VenueProfile {\n try {\n const parsed = JSON.parse(readFileSync(venueFile(projectPath), 'utf8')) as Partial<VenueProfile>\n return {\n ...DEFAULT_VENUE_PROFILE,\n ...parsed,\n checklist: Array.isArray(parsed.checklist) ? parsed.checklist : [],\n }\n } catch { return DEFAULT_VENUE_PROFILE }\n}\n\nexport function writeVenueProfile(projectPath: string, patch: Partial<VenueProfile>): VenueProfile {\n const current = readVenueProfile(projectPath)\n const next: VenueProfile = {\n ...current,\n ...patch,\n checklist: patch.checklist !== undefined && Array.isArray(patch.checklist) ? patch.checklist : current.checklist,\n updatedAt: Date.now(),\n }\n const file = venueFile(projectPath)\n mkdirSync(dirname(file), { recursive: true })\n writeFileSync(file, JSON.stringify(next, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n return next\n}\n\n/** \u4ECE\u8981\u6C42\u5168\u6587\u91CC\u62C6\u6761\uFF08\u6309\u884C/\u5206\u53F7\uFF09\uFF0C\u751F\u6210\u521D\u59CB\u6E05\u5355\uFF1B\u5DF2\u5B58\u5728\u7684\u540C\u6587\u6761\u76EE\u4FDD\u7559\u72B6\u6001\u3002 */\nexport function seedVenueChecklist(projectPath: string, requirements: string): VenueProfile {\n const lines = requirements\n .split(/\\n+/)\n .map(line => line.replace(/^\\s*(?:[-*\u2022]|\\d+[.)\u3001])\\s*/, '').trim())\n .filter(line => line.length >= 4)\n const current = readVenueProfile(projectPath)\n const existing = new Map(current.checklist.map(item => [item.text, item]))\n const checklist = lines.map((text, index) => existing.get(text) ?? {\n id: 'req-' + String(index + 1),\n text,\n status: 'todo' as RequirementStatus,\n })\n return writeVenueProfile(projectPath, { requirements, checklist })\n}\n\nexport function setVenueRequirement(projectPath: string, id: string, status: RequirementStatus, note?: string): VenueProfile {\n const current = readVenueProfile(projectPath)\n if (!current.checklist.some(item => item.id === id)) throw new KsrwError('NOT_FOUND', '\u6E05\u5355\u9879\u4E0D\u5B58\u5728\uFF1A' + id)\n return writeVenueProfile(projectPath, {\n checklist: current.checklist.map(item => item.id === id\n ? { ...item, status, ...(note === undefined ? {} : { note }) }\n : item),\n })\n}\n\n/** \u628A\u56FE\u63D2\u5230\u6B63\u6587\u672B\u5C3E\uFF1A\u4F18\u5148\u8DDF\u5728\u540C\u540D/\u540C\u56FE\u6CE8\u7684\u63D2\u5165\u70B9\u4E4B\u540E\uFF0C\u4FDD\u8BC1\u56FE\u6CE8\u4E0E\u8DEF\u5F84\u4E00\u81F4\u3002 */\nexport function insertFigureIntoDraft(\n projectPath: string,\n source: string,\n file: string,\n caption: string,\n): { source: string; added: string } {\n const absolute = join(projectPath, source)\n if (!existsSync(absolute)) throw new KsrwError('NOT_FOUND', '\u6B63\u6587\u4E0D\u5B58\u5728\uFF1A' + source)\n const text = readFileSync(absolute, 'utf8')\n const snippet = '![' + caption + '](figures/' + file + ')'\n if (text.includes(snippet)) return { source, added: snippet }\n const next = text.replace(/\\s+$/, '') + '\\n\\n' + snippet + '\\n'\n writeFileSync(absolute, next, { encoding: 'utf8' })\n return { source, added: snippet }\n}\n", "/** KSRW \u9879\u76EE\u5DE5\u4F5C\u533A\u5185\u6838\uFF08v2\uFF09\uFF1A\u9879\u76EE\u76EE\u5F55\u5373\u9879\u76EE + \u7814\u7A76\u72B6\u6001 + \u63D0\u6848/revision\u3002\n *\n * \u7EAA\u5F8B\uFF08\u7167\u6284 KToon \u7684\u63D0\u6848\u95ED\u73AF\uFF09\uFF1A\n * - \u6743\u5A01\u6587\u4EF6\u53EA\u7531 apply \u5199\u5165\uFF1BAgent \u53EA\u80FD propose\uFF1B\n * - \u6BCF\u4E2A\u4EA7\u7269\u5E26\u5355\u8C03 revision\uFF1B\u63D0\u6848\u5E26 baseRevision\uFF0C\u5931\u914D\u8FD4\u56DE STALE_REVISION\uFF1B\n * - \u4E00\u5207\u5199\u5165 tmp+rename \u539F\u5B50\u5199\uFF1B\u9879\u76EE\u5185 `.ksrw.lock` \u8DE8\u8FDB\u7A0B\u4E32\u884C\u5316\u5199\u8DEF\u5F84\uFF08\u89C1 lock.ts\uFF09\u3002\n */\nimport { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { dirname, join, resolve } from 'node:path'\n\nimport { KsrwError } from './error.ts'\nimport { resolveKsrwHome } from './home.ts'\nimport { withProjectLock, withRegistryLock } from './lock.ts'\nimport { invalidateStalePaperFinal, paperFinalStatus, readPaperBuild } from './paper.ts'\nimport { newId } from './ids.ts'\n\nexport type ResearchStage =\n | 'idea' | 'topic' | 'proposal' | 'literature' | 'experiment'\n | 'paper' | 'submission' | 'patent' | 'done'\n\n/**\n * \u8FD0\u884C\u9636\u6BB5\u6E05\u5355\uFF08\u552F\u4E00\u771F\u6E90\uFF09\uFF1A\u7C7B\u578B\u53EA\u80FD\u4FDD\u8BC1\u7F16\u8BD1\u671F\uFF0C\u670D\u52A1\u7AEF\u6821\u9A8C\u9636\u6BB5\u9700\u8981\u8FD0\u884C\u671F\u6570\u7EC4\u3002\n * \u4EE5\u524D api.ts \u81EA\u5DF1\u5217\u4E86\u4E00\u4EFD\uFF0C\u5C11\u4E86 `submission`\uFF0C\u5BFC\u81F4\u300C\u6295\u7A3F\u300D\u8FD9\u4E2A\u5408\u6CD5\u9636\u6BB5\u65E0\u6CD5\u901A\u8FC7\n * `/ksrw/api/project/stage` \u5199\u5165\u3002\u65B0\u589E\u9636\u6BB5\u53EA\u6539\u8FD9\u91CC\u3002\n */\nexport const RESEARCH_STAGES: readonly ResearchStage[] = [\n 'idea', 'topic', 'proposal', 'literature', 'experiment', 'paper', 'submission', 'patent', 'done',\n] as const\n\n/** \u9636\u6BB5\u504F\u5E8F\u7684\u79E9\uFF1ARESEARCH_STAGES \u6570\u7EC4\u987A\u5E8F\u5373\u7814\u7A76\u4E3B\u7EBF\u7684\u5355\u8C03\u524D\u8FDB\u65B9\u5411\u3002 */\nexport function stageRank(stage: ResearchStage): number {\n return RESEARCH_STAGES.indexOf(stage)\n}\n\n/**\n * \u9636\u6BB5\u8F6C\u6362\u6821\u9A8C\uFF08P1-2\uFF09\uFF1A\u9636\u6BB5\u53EA\u5141\u8BB8\u524D\u5411\uFF08\u76F8\u7B49\u89C6\u4E3A\u5E42\u7B49\u91CD\u653E\uFF09\uFF0C\n * idea \u662F\u8D77\u70B9\u4E0D\u53EF\u4F5C\u4E3A\u63A8\u8FDB/\u95F8\u95E8\u76EE\u6807\u3002\u8FD4\u56DE null \u8868\u793A\u5141\u8BB8\uFF0C\u5426\u5219\u662F\u53EF\u8BFB\u9519\u8BEF\u6587\u6848\u3002\n */\nexport function stageTransitionError(current: ResearchStage, target: ResearchStage): string | null {\n if (target === 'idea') return 'idea \u662F\u8D77\u70B9\u9636\u6BB5\uFF0C\u4E0D\u80FD\u4F5C\u4E3A\u63A8\u8FDB/\u95F8\u95E8\u76EE\u6807\uFF1B\u5982\u9700\u91CD\u6765\u8BF7\u65B0\u5EFA\u9879\u76EE'\n if (stageRank(target) < stageRank(current)) {\n return '\u9636\u6BB5\u4E0D\u80FD\u56DE\u9000\uFF1A\u5F53\u524D ' + current + '\uFF0C\u76EE\u6807 ' + target + '\uFF08\u56DE\u9000\u8BF7\u8D70\u65B0\u5EFA\u9879\u76EE\u6216\u663E\u5F0F\u8FD0\u7EF4\u6D41\u7A0B\uFF09'\n }\n return null\n}\n\nexport interface ArtifactMeta {\n readonly revision: number\n readonly updatedAt: number\n readonly path: string\n}\n\nexport interface ProjectRecord {\n readonly id: string\n name: string\n idea: string\n stage: ResearchStage\n readonly path: string\n readonly createdAt: number\n updatedAt: number\n revision: number\n artifacts: Record<string, ArtifactMeta>\n /**\n * \u514D\u767B\u8BB0\u5DE5\u4F5C\u533A\uFF08\u514D\u9879\u76EE\u5B9E\u9A8C\u5DE5\u574A\uFF09\uFF1A\u53EF\u4EE5 open / \u63D0\u6848 / \u63A8\u8FDB\u9636\u6BB5\uFF0C\u4F46\u4E0D\u8FDB\u9879\u76EE\u5E93\u3002\n * \u7528\u6237\u4E3B\u52A8\u300C\u767B\u8BB0\u4E3A\u9879\u76EE\u300D\u65F6\u6E05\u9664\u8BE5\u6807\u8BB0\u3002\n */\n detached?: boolean\n}\n\nexport interface ResearchState {\n stage: ResearchStage\n summary: string\n next: string\n blockers: string[]\n confirmed: string[]\n updatedAt: number\n}\n\nexport interface TopicFeasibility {\n data: string\n compute: string\n time: string\n skill: string\n}\n\nexport interface TopicCard {\n question: string\n motivation: string\n gap: string\n novelty: string[]\n feasibility: TopicFeasibility\n risks: string[]\n decision: 'pending' | 'go' | 'no-go'\n revision: number\n updatedAt: number\n}\n\nexport type ProposalStatus = 'pending' | 'applied' | 'discarded'\n\nexport interface ProposalRecord {\n readonly id: string\n readonly projectId: string\n readonly artifactRef: string\n readonly baseRevision: number\n readonly summary: string\n readonly content: string\n status: ProposalStatus\n readonly authorRole?: string\n readonly createdAt: number\n appliedAt?: number\n}\n\nexport interface HandoffRecord {\n readonly id: string\n readonly from: string\n readonly to: string\n readonly artifacts: string[]\n readonly acceptance: string[]\n readonly requestedOutput: string\n note?: string\n status: 'open' | 'started' | 'accepted' | 'returned' | 'done'\n readonly createdAt: number\n startedAt?: number\n returnedAt?: number\n doneAt?: number\n}\n\nexport interface EvidenceRecord {\n readonly id: string\n readonly claimRef: string\n readonly source: { readonly kind: 'paper' | 'run' | 'patent'; readonly ref: string; readonly locator?: string }\n readonly quote: string\n readonly createdAt: number\n}\n\nexport interface ArtifactView {\n readonly ref: string\n readonly revision: number\n readonly updatedAt: number\n readonly content: string\n}\n\nexport interface ProjectView {\n readonly project: ProjectRecord\n readonly researchState: ResearchState\n readonly topic: TopicCard | null\n readonly proposals: readonly ProposalRecord[]\n /** P1-3\uFF1Aevidence.jsonl \u7684\u8BFB\u53D6\u7ED3\u679C\uFF0C\u5B57\u6BB5\u4E0E appendEvidence \u5199\u5165\u4E00\u81F4\u3002 */\n readonly evidence: readonly EvidenceRecord[]\n}\n\nexport interface GateResult {\n readonly allowed: boolean\n readonly stage: ResearchStage\n readonly blockers: string[]\n}\n\nexport interface ProjectIndexEntry {\n readonly id: string\n readonly name: string\n readonly path: string\n readonly stage: ResearchStage\n readonly createdAt: number\n readonly updatedAt: number\n}\n\ninterface Registry {\n version: 1\n projects: ProjectIndexEntry[]\n}\n\nconst REGISTRY_VERSION = 1\nexport const DEFAULT_PROJECT_DIR = 'projects'\n\nexport { resolveKsrwHome } from './home.ts'\n\nexport function resolveProjectHome(home = resolveKsrwHome()): string {\n return join(home, DEFAULT_PROJECT_DIR)\n}\n\nfunction atomicWrite(filePath: string, text: string): void {\n mkdirSync(dirname(filePath), { recursive: true, mode: 0o700 })\n const tmp = filePath + '.tmp-' + process.pid + '-' + Math.random().toString(36).slice(2, 8)\n writeFileSync(tmp, text, { encoding: 'utf8', mode: 0o600 })\n renameSync(tmp, filePath)\n}\n\nfunction readJson<T>(filePath: string): T | null {\n try {\n return JSON.parse(readFileSync(filePath, 'utf8')) as T\n } catch {\n return null\n }\n}\n\nfunction readText(filePath: string): string | null {\n try {\n return readFileSync(filePath, 'utf8')\n } catch {\n return null\n }\n}\n\nfunction writeJson(filePath: string, value: unknown): void {\n atomicWrite(filePath, JSON.stringify(value, null, 2) + '\\n')\n}\n\nfunction slugify(name: string): string {\n const slug = name.trim().toLowerCase()\n .replace(/[^\\w\\u4e00-\\u9fff-]+/g, '-')\n .replace(/^-+|-+$/g, '')\n return slug === '' ? 'project-' + Date.now().toString(36) : slug\n}\n\nexport function projectDirs(): readonly string[] {\n return ['topic', 'proposal', 'literature', 'experiments', 'paper', 'submission', 'patent', 'evidence', 'proposals']\n}\n\nconst ARTIFACT_PATHS: Record<string, string> = {\n topic: 'topic/topic.json',\n proposal: 'proposal/proposal.md',\n literature: 'literature/matrix.md',\n experiment: 'experiments/record.md',\n paper: 'paper/main.md',\n patent: 'patent/disclosure.md',\n}\n\n/** Stable artifact refs that the UI and proposals may write. */\nexport function artifactPath(ref: string): string | null {\n return ARTIFACT_PATHS[ref] ?? null\n}\n\n/** Stage gate: only artifact evidence exported by the UI/tools can pass. */\n/** \u4EA4\u63A5\u5355\u76EE\u6807\u5DE5\u4F4D \u2192 \u4E3B\u7EBF\u9636\u6BB5\uFF08\u4E13\u5229\u662F\u72EC\u7ACB\u5DE5\u4F4D\uFF0C\u4E0D\u53C2\u4E0E\u9636\u6BB5\u95F8\u95E8\uFF09\u3002 */\nconst HANDOFF_MAINLINE_STAGE: Record<string, ResearchStage> = {\n topic: 'topic',\n proposal: 'proposal',\n literature: 'literature',\n experiment: 'experiment',\n paper: 'paper',\n}\n\nexport function handoffMainlineStage(to: string): ResearchStage | null {\n const bare = to.startsWith('ksrw-') ? to.slice(5) : to\n const direct = HANDOFF_MAINLINE_STAGE[bare]\n if (direct !== undefined) return direct\n if (bare.includes('\u5F00\u9898')) return 'proposal'\n if (bare.includes('\u6587\u732E')) return 'literature'\n if (bare.includes('\u5B9E\u9A8C')) return 'experiment'\n if (bare.includes('\u8BBA\u6587')) return 'paper'\n return null\n}\n\nexport function gateBlockers(view: ProjectView, target: ResearchStage): string[] {\n const blockers: string[] = []\n const artifacts = view.project.artifacts\n // P1-2\uFF1A\u95F8\u95E8\u53EA\u5141\u8BB8\u524D\u5411\u63A8\u8FDB\uFF0Cidea \u4E0D\u53EF\u4F5C\u4E3A\u76EE\u6807\uFF08fake/legacy \u89C6\u56FE\u7F3A stage \u65F6\u8DF3\u8FC7\u8BE5\u68C0\u67E5\uFF09\u3002\n const currentStage = view.project.stage\n if (currentStage !== undefined) {\n const transition = stageTransitionError(currentStage, target)\n if (transition !== null) blockers.push(transition)\n }\n if (target === 'idea') blockers.push('idea \u662F\u8D77\u70B9\u9636\u6BB5\uFF0C\u4E0D\u80FD\u4F5C\u4E3A\u95F8\u95E8\u76EE\u6807')\n // P1-3\uFF1A\u8BC1\u636E\u8986\u76D6\u3002\u63D0\u6848\u4E0D\u643A\u5E26 claimRef\uFF08\u63D0\u6848\u8BB0\u5F55\u91CC\u6CA1\u6709 claim \u6982\u5FF5\uFF09\uFF0C\n // \u56E0\u6B64\u6309\u62A5\u544A\u56DE\u9000\u53E3\u5F84\uFF1A\u5DF2\u6709\u5F00\u9898\u4EA7\u7269\u4E14\u9636\u6BB5\u8D8A\u8FC7 literature \u4E4B\u540E\uFF0C\u81F3\u5C11\u8981\u6709\u4E00\u6761 evidence\u3002\n const viewStage = view.project.stage\n if (viewStage !== undefined\n && stageRank(viewStage) > stageRank('literature')\n && (artifacts['proposal']?.revision ?? 0) >= 1\n && (view.evidence?.length ?? 0) === 0) {\n blockers.push('\u7F3A\u5C11\u8BC1\u636E\uFF08evidence/evidence.jsonl\uFF09\uFF1A\u9636\u6BB5\u5DF2\u8D8A\u8FC7 literature \u4E14\u5F00\u9898\u4EA7\u7269\u5DF2\u5B58\u5728\uFF0C\u81F3\u5C11\u9700\u8981\u4E00\u6761\u8BC1\u636E')\n }\n if (target === 'topic') {\n if (view.topic === null) blockers.push('\u7F3A\u5C11\u9009\u9898\u5361\uFF08topic/topic.json\uFF09')\n else if (view.topic.decision !== 'go') blockers.push('\u9009\u9898\u5361 decision \u4E0D\u662F go')\n }\n if (target === 'proposal' || target === 'literature' || target === 'experiment') {\n if ((artifacts['proposal']?.revision ?? 0) < 1) blockers.push('\u7F3A\u5C11\u5DF2\u5E94\u7528\u7684\u5F00\u9898\u4EA7\u7269\uFF08proposal/proposal.md\uFF09')\n }\n if (target === 'experiment') {\n if ((artifacts['literature']?.revision ?? 0) < 1) blockers.push('\u7F3A\u5C11\u6587\u732E\u4EA7\u7269\uFF08literature\uFF09\uFF1A\u5B9E\u9A8C\u9700\u8981\u6587\u732E\u77E9\u9635\u4F5C\u4E3A\u8BC1\u636E\u57FA\u7840')\n }\n if (target === 'paper') {\n if ((artifacts['literature']?.revision ?? 0) < 1) blockers.push('\u7F3A\u5C11\u6587\u732E\u4EA7\u7269\uFF08literature\uFF09')\n if ((artifacts['experiment']?.revision ?? 0) < 1) blockers.push('\u7F3A\u5C11\u5B9E\u9A8C\u4EA7\u7269\uFF08experiment\uFF09')\n }\n if (target === 'patent') {\n if ((artifacts['paper']?.revision ?? 0) < 1) blockers.push('\u7F3A\u5C11\u8BBA\u6587\u4EA7\u7269\uFF08paper\uFF09')\n }\n if (target === 'done') {\n if ((artifacts['paper']?.revision ?? 0) < 1) blockers.push('\u7F3A\u5C11\u8BBA\u6587\u4EA7\u7269\uFF08paper\uFF09')\n }\n return blockers\n}\n\n/** One project on disk; every operation reads the manifest first. */\nexport class ProjectStore {\n private readonly home: string\n private readonly root: string\n\n constructor(options: { home?: string } = {}) {\n this.home = resolve(options.home ?? resolveKsrwHome())\n this.root = resolveProjectHome(this.home)\n mkdirSync(this.root, { recursive: true, mode: 0o700 })\n }\n\n /**\n * \u6240\u6709\u5199\u8DEF\u5F84\u90FD\u5728\u9879\u76EE\u9501\u5185\u6267\u884C\uFF08`<project>/.ksrw.lock`\uFF0C\u8DE8\u8FDB\u7A0B\uFF09\u3002\n * \u5D4C\u5957\u8C03\u7528\u7531 lock.ts \u6309\u8DEF\u5F84\u91CD\u5165\u8BA1\u6570\uFF0C\u4E0D\u9700\u8981\u8C03\u7528\u65B9\u533A\u5206\u300C\u5916\u5C42/\u5185\u5C42\u300D\u3002\n */\n private locked<T>(projectPath: string, fn: () => T): T {\n return withProjectLock(projectPath, fn)\n }\n\n /** \u767B\u8BB0\u8868\u662F home \u7EA7\u5171\u4EAB\u6587\u4EF6\uFF0C\u8BFB\u5199\u5F80\u8FD4\u4E5F\u8981\u8DE8\u8FDB\u7A0B\u4E92\u65A5\u3002 */\n private withRegistry<T>(fn: () => T): T {\n return withRegistryLock(this.home, fn)\n }\n\n private registryPath(): string {\n return join(this.home, 'projects.json')\n }\n\n private loadRegistry(): Registry {\n const parsed = readJson<Registry>(this.registryPath())\n if (parsed === null || parsed.version !== REGISTRY_VERSION || !Array.isArray(parsed.projects)) {\n return { version: REGISTRY_VERSION, projects: [] }\n }\n return parsed\n }\n\n private saveRegistry(registry: Registry): void {\n writeJson(this.registryPath(), registry)\n }\n\n list(): readonly ProjectIndexEntry[] {\n // P2-5\uFF1A\u76EE\u5F55\u5373\u9879\u76EE\u2014\u2014\u76EE\u5F55\u88AB\u5220/\u79FB\u540E\u6E05\u5355\u6761\u76EE\u6210\u4E86\u6253\u4E0D\u5F00\u7684\u5E7D\u7075\u9879\uFF0C\u5217\u8868\u5FC5\u987B\u8FC7\u6EE4\u6389\u3002\n return this.loadRegistry().projects\n .filter(item => readJson<ProjectRecord>(join(item.path, 'project.json')) !== null)\n .slice()\n .sort((a, b) => b.updatedAt - a.updatedAt)\n }\n\n create(input: { name: string; idea?: string; directory?: string; detached?: boolean }): ProjectRecord {\n const name = input.name.trim()\n if (name === '') throw new KsrwError('INVALID', '\u9879\u76EE\u540D\u4E0D\u80FD\u4E3A\u7A7A')\n const idea = (input.idea ?? '').trim()\n const target = resolve(input.directory !== undefined && input.directory.trim() !== ''\n ? input.directory\n : join(this.root, slugify(name)))\n mkdirSync(target, { recursive: true, mode: 0o700 })\n // \u68C0\u67E5\u91CD\u540D\u4E0E\u521B\u5EFA\u5FC5\u987B\u5728\u540C\u4E00\u628A\u9501\u91CC\uFF1A\u4E24\u4E2A\u8FDB\u7A0B\u540C\u65F6\u65B0\u5EFA\u540C\u4E00\u76EE\u5F55\u65F6\u4E0D\u80FD\u90FD\u6210\u529F\u3002\n return this.locked(target, () => {\n const manifestPath = join(target, 'project.json')\n if (readJson<ProjectRecord>(manifestPath) !== null) {\n throw new KsrwError('DUPLICATE', '\u8BE5\u76EE\u5F55\u5DF2\u6709 KSRW \u9879\u76EE\uFF1A' + target)\n }\n for (const dir of projectDirs()) mkdirSync(join(target, dir), { recursive: true, mode: 0o700 })\n const now = Date.now()\n const project: ProjectRecord = {\n id: newId('prj'),\n name,\n idea,\n stage: 'idea',\n path: target,\n createdAt: now,\n updatedAt: now,\n revision: 1,\n artifacts: {},\n ...(input.detached === true ? { detached: true } : {}),\n }\n this.saveProject(project)\n if (idea !== '') atomicWrite(join(target, 'topic', 'ideas.md'), '# \u60F3\u6CD5\u6536\u4EF6\u7BB1\\n\\n- ' + idea + '\\n')\n this.writeResearchState(project, {\n stage: 'idea',\n summary: idea === '' ? '\u65B0\u5EFA\u9879\u76EE\uFF0C\u7B49\u5F85\u5199\u4E0B idea\u3002' : idea,\n next: '\u5199\u6E05\u695A idea\uFF0C\u7136\u540E\u751F\u6210\u9009\u9898\u5361\u3002',\n blockers: [],\n confirmed: [],\n updatedAt: now,\n })\n this.upsertRegistry(project)\n return project\n })\n }\n\n open(projectPath: string): ProjectView {\n const project = this.readProject(projectPath)\n return {\n project,\n researchState: this.readResearchState(project),\n topic: this.readTopic(project),\n proposals: this.listProposals(project),\n evidence: this.readEvidence(projectPath),\n }\n }\n\n /** Re-register an existing project directory (the directory is the project). */\n register(directory: string): ProjectView {\n const target = resolve(directory.trim())\n return this.locked(target, () => {\n // P1-1\uFF1A\u7ECF readProject \u8BFB\u53D6\uFF0Cpath \u4EE5\u5B9E\u9645\u76EE\u5F55\u4E3A\u51C6\uFF08\u6E05\u5355\u8BB0\u5F55\u7684\u65E7\u8DEF\u5F84\u88AB\u5C31\u5730\u6821\u6B63\u5199\u56DE\uFF09\uFF0C\n // \u767B\u8BB0\u8868\u968F\u4E4B\u6307\u5411\u76EE\u5F55\u5F53\u524D\u4F4D\u7F6E\u800C\u4E0D\u662F\u6E05\u5355\u91CC\u7684\u65E7\u4F4D\u7F6E\u3002\n let project: ProjectRecord\n try {\n project = this.readProject(target)\n } catch (error) {\n if (error instanceof KsrwError && error.code === 'NOT_FOUND') {\n throw new KsrwError('NOT_FOUND', '\u8BE5\u76EE\u5F55\u6CA1\u6709 project.json\uFF0C\u4E0D\u662F KSRW \u9879\u76EE\uFF1A' + target)\n }\n throw error\n }\n // \u767B\u8BB0\u5373\u8131\u79BB\u300C\u514D\u767B\u8BB0\u300D\u72B6\u6001\uFF1A\u514D\u9879\u76EE\u5B9E\u9A8C\u5DE5\u574A\u88AB\u63A5\u6536\u4E3A\u6B63\u5F0F\u9879\u76EE\u540E\u8981\u51FA\u73B0\u5728\u9879\u76EE\u5E93\u3002\n if (project.detached === true) {\n project.detached = false\n this.saveProject(project)\n }\n this.upsertRegistry(project)\n return this.open(target)\n })\n }\n\n /** Remove from the project library. User files are kept; register it again anytime. */\n forget(projectPath: string): void {\n forgetProject(this.home, projectPath)\n }\n\n readProject(projectPath: string): ProjectRecord {\n const target = resolve(projectPath)\n const project = readJson<ProjectRecord>(join(target, 'project.json'))\n if (project === null) throw new KsrwError('NOT_FOUND', '\u4E0D\u662F KSRW \u9879\u76EE\uFF08\u7F3A project.json\uFF09\uFF1A' + target)\n // P2-6\uFF1A\u5173\u952E\u5B57\u6BB5\u7F3A\u5931\u629B\u53EF\u8BFB\u7684 INVALID\uFF0C\u800C\u4E0D\u662F\u4E0B\u6E38\u67D0\u5904\u7684 TypeError\uFF08500\uFF09\u3002\n if (typeof project.id !== 'string' || project.id === '') {\n throw new KsrwError('INVALID', 'project.json \u7F3A\u5C11 id \u5B57\u6BB5\uFF1A\u8BF7\u624B\u5DE5\u8865\u4E0A\u5B57\u7B26\u4E32 id \u540E\u91CD\u8BD5\uFF08' + target + '\uFF09')\n }\n if (typeof project.createdAt !== 'number' || !Number.isFinite(project.createdAt)) {\n throw new KsrwError('INVALID', 'project.json \u7F3A\u5C11 createdAt \u5B57\u6BB5\uFF1A\u8BF7\u624B\u5DE5\u8865\u4E0A\u6570\u5B57\u65F6\u95F4\u6233\u540E\u91CD\u8BD5\uFF08' + target + '\uFF09')\n }\n // P1-1\uFF1A\u76EE\u5F55\u5373\u9879\u76EE\uFF0Cpath \u4EE5\u300C\u5B9E\u9645\u76EE\u5F55\u300D\u4E3A\u51C6\u3002\u6E05\u5355\u91CC\u8BB0\u5F55\u7684\u65E7\u7EDD\u5BF9\u8DEF\u5F84\uFF08\u76EE\u5F55\u88AB\u79FB\u52A8/\u590D\u5236\uFF09\n // \u5C31\u5730\u6821\u6B63\u5E76\u5199\u56DE\uFF0C\u968F\u540E\u7684\u4EA7\u7269\u8BFB\u5199\u5168\u90E8\u843D\u5230\u5B9E\u9645\u76EE\u5F55\uFF0C\u4E0D\u518D\u5199\u56DE\u65E7\u76EE\u5F55\u5236\u9020\u5E7D\u7075\u76EE\u5F55\u3002\n if (project.path !== target) {\n ;(project as { path: string }).path = target\n writeJson(join(target, 'project.json'), project)\n }\n const now = Date.now()\n // A missing artifact entry is a valid legacy state; callers fall back to revision 0.\n if (typeof project.revision !== 'number') project.revision = 1\n if (project.stage === undefined) project.stage = 'idea'\n if (project.artifacts === undefined) project.artifacts = {}\n if (project.updatedAt === undefined) project.updatedAt = now\n return project\n }\n\n saveProject(project: ProjectRecord): void {\n this.locked(project.path, () => writeJson(join(project.path, 'project.json'), project))\n }\n\n touch(project: ProjectRecord): ProjectRecord {\n return this.locked(project.path, () => {\n project.updatedAt = Date.now()\n project.revision += 1\n this.saveProject(project)\n this.upsertRegistry(project)\n return project\n })\n }\n\n setStage(projectPath: string, stage: ResearchStage): ProjectRecord {\n return this.locked(projectPath, () => {\n const project = this.readProject(projectPath)\n // P1-2\uFF1A\u9636\u6BB5\u504F\u5E8F\u2014\u2014\u53EA\u5141\u8BB8\u524D\u5411\uFF08\u76F8\u7B49\u5E42\u7B49\uFF09\uFF0Cidea \u4E0D\u53EF\u4F5C\u4E3A\u76EE\u6807\uFF1B\u5426\u5219\u629B\u53EF\u8BFB INVALID\uFF08API \u5C42 400\uFF09\u3002\n const transition = stageTransitionError(project.stage, stage)\n if (transition !== null) throw new KsrwError('INVALID', transition)\n project.stage = stage\n this.touch(project)\n this.writeResearchState(project, {\n ...this.readResearchState(project),\n stage,\n updatedAt: Date.now(),\n })\n return project\n })\n }\n\n gate(projectPath: string, target: ResearchStage): GateResult {\n // \u68C0\u67E5\u4E0E\u63A8\u8FDB\u5FC5\u987B\u5728\u540C\u4E00\u628A\u9501\u91CC\uFF1A\u5426\u5219\u4E24\u4E2A\u5DE5\u4F4D\u53EF\u80FD\u5404\u81EA\u770B\u5230\u300C\u65E0 blocker\u300D\u5E76\u91CD\u590D\u63A8\u8FDB\u3002\n return this.locked(projectPath, () => {\n const view = this.open(projectPath)\n const blockers = gateBlockers(view, target)\n // P2-1\uFF1A\u4E2D\u95F4\u95F8\u95E8\u4E5F\u8981\u770B\u4EA4\u63A5\u5355\u2014\u2014\u76EE\u6807\u9636\u6BB5\u4E4B\u524D\u7684\u4E3B\u7EBF\u4EA4\u63A5\u5355\u5FC5\u987B\u6536\u5C3E\u3002\n // \u53EA\u8981\u6C42\u300C\u4E25\u683C\u65E9\u4E8E\u76EE\u6807\u9636\u6BB5\u300D\u7684\u5355\uFF1A\u76EE\u6807\u9636\u6BB5\u81EA\u5DF1\u7684\u4EA4\u63A5\u5355\u662F\u63A5\u4E0B\u6765\u8981\u505A\u7684\u5DE5\u4F5C\u3002\n const pendingEarlier = this.readHandoffs(projectPath).filter(handoff => {\n if (handoff.status !== 'open' && handoff.status !== 'started') return false\n const stage = handoffMainlineStage(handoff.to)\n return stage !== null && stageRank(stage) < stageRank(target)\n })\n if (pendingEarlier.length > 0) {\n blockers.push('\u8FD8\u6709 ' + pendingEarlier.length + ' \u5F20\u4E0A\u4E00\u9636\u6BB5\u7684\u4EA4\u63A5\u5355\u672A\u6536\u5C3E\uFF08' + pendingEarlier.map(item => item.id).join('\u3001') + '\uFF09')\n }\n // \u5B9A\u7A3F\u624D\u7B97\u5B8C\u6210\uFF1APDF \u5FC5\u987B\u5728\uFF0C\u4E14\u662F\u7531\u5F53\u524D\u8FD9\u7248\u8BBA\u6587\u6B63\u6587\u751F\u6210\u7684\u3002\n if (target === 'done') {\n invalidateStalePaperFinal(projectPath, 'paper')\n const final = paperFinalStatus(projectPath, 'paper')\n const record = readPaperBuild(projectPath, 'paper')\n if (record !== undefined && record.pdf === '' && record.invalidatedAt !== undefined) {\n blockers.push('\u8BBA\u6587\u6B63\u6587\u5DF2\u4FEE\u6539\uFF1A\u65E7\u5B9A\u7A3F PDF \u5DF2\u5220\u9664\uFF0C\u8BF7\u91CD\u65B0\u7F16\u8BD1\u5B9A\u7A3F')\n } else if (final.build === undefined) {\n blockers.push('\u8BBA\u6587\u5C1A\u672A\u5B9A\u7A3F\uFF1A\u5148\u5728\u8BBA\u6587\u5DE5\u4F4D\u300C\u7F16\u8BD1\u5B9A\u7A3F PDF\u300D')\n } else if (final.stale) {\n blockers.push('\u8BBA\u6587\u6B63\u6587\u5DF2\u4FEE\u6539\uFF1A\u5B9A\u7A3F PDF \u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u7F16\u8BD1\u5B9A\u7A3F')\n }\n }\n if (blockers.length === 0) this.setStage(projectPath, target)\n return { allowed: blockers.length === 0, stage: target, blockers }\n })\n }\n\n readTopic(project: ProjectRecord): TopicCard | null {\n return readJson<TopicCard>(join(project.path, 'topic', 'topic.json'))\n }\n\n saveTopic(projectPath: string, input: Omit<TopicCard, 'revision' | 'updatedAt'>): TopicCard {\n return this.locked(projectPath, () => {\n const project = this.readProject(projectPath)\n const previous = this.readTopic(project)\n const now = Date.now()\n const card: TopicCard = {\n ...input,\n revision: (previous?.revision ?? 0) + 1,\n updatedAt: now,\n }\n writeJson(join(project.path, 'topic', 'topic.json'), card)\n project.artifacts['topic'] = { revision: card.revision, updatedAt: now, path: 'topic/topic.json' }\n if (project.stage === 'idea') project.stage = 'topic'\n project.updatedAt = now\n project.revision += 1\n this.saveProject(project)\n this.upsertRegistry(project)\n this.writeResearchState(project, {\n ...this.readResearchState(project),\n stage: project.stage,\n summary: card.question === '' ? '\u9009\u9898\u5361\u5DF2\u5EFA\u7ACB\uFF0C\u95EE\u9898\u5F85\u8865\u3002' : card.question,\n next: card.decision === 'go' ? '\u786E\u8BA4\u9009\u9898\u540E\u8FDB\u5165\u5F00\u9898\u3002' : '\u8865\u5168\u95EE\u9898/\u5DEE\u8DDD/\u53EF\u884C\u6027\uFF0C\u7136\u540E\u786E\u8BA4\u9009\u9898\u3002',\n updatedAt: now,\n })\n return card\n })\n }\n\n readArtifact(projectPath: string, ref: string): ArtifactView | null {\n const project = this.readProject(projectPath)\n const relative = artifactPath(ref)\n if (relative === null) throw new KsrwError('INVALID', '\u672A\u77E5\u4EA7\u7269\u7C7B\u578B\uFF1A' + ref)\n const content = readText(join(project.path, relative))\n if (content === null) return null\n const meta = project.artifacts[ref]\n return {\n ref,\n revision: meta?.revision ?? 0,\n updatedAt: meta?.updatedAt ?? project.updatedAt,\n content,\n }\n }\n\n /** Direct user-authoritative write (UI edit). Agent writes still go through proposals. */\n writeArtifact(projectPath: string, ref: string, content: string): ArtifactView {\n return this.locked(projectPath, () => {\n const project = this.readProject(projectPath)\n const relative = artifactPath(ref)\n if (relative === null) throw new KsrwError('INVALID', '\u672A\u77E5\u4EA7\u7269\u7C7B\u578B\uFF1A' + ref)\n if (content.trim() === '') throw new KsrwError('INVALID', '\u4EA7\u7269\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A')\n atomicWrite(join(project.path, relative), content)\n const now = Date.now()\n const revision = (project.artifacts[ref]?.revision ?? 0) + 1\n project.artifacts[ref] = { revision, updatedAt: now, path: relative }\n project.updatedAt = now\n project.revision += 1\n this.saveProject(project)\n this.upsertRegistry(project)\n return { ref, revision, updatedAt: now, content }\n })\n }\n\n listProposals(project: ProjectRecord): ProposalRecord[] {\n const dir = join(project.path, 'proposals')\n const proposals: ProposalRecord[] = []\n let entries: string[] = []\n try { entries = readdirNames(dir) } catch { return proposals }\n for (const name of entries) {\n if (!name.endsWith('.json')) continue\n const proposal = readJson<ProposalRecord>(join(dir, name))\n if (proposal !== null) proposals.push(proposal)\n }\n return proposals.sort((a, b) => b.createdAt - a.createdAt)\n }\n\n createProposal(projectPath: string, input: {\n artifactRef: string\n baseRevision: number\n summary: string\n content: string\n authorRole?: string\n }): ProposalRecord {\n // \u57FA\u7EBF\u6821\u9A8C\u5FC5\u987B\u4E0E\u5199\u5165\u540C\u4E00\u628A\u9501\uFF1A\u5426\u5219\u4E24\u4E2A agent \u53EF\u540C\u65F6\u901A\u8FC7 STALE \u68C0\u67E5\u3002\n return this.locked(projectPath, () => {\n const project = this.readProject(projectPath)\n const current = project.artifacts[input.artifactRef]?.revision ?? 0\n if (input.baseRevision !== current) {\n throw new KsrwError('STALE_REVISION', '\u63D0\u6848\u57FA\u7EBF\u5DF2\u8FC7\u671F\uFF1Abase=' + input.baseRevision + ' current=' + current)\n }\n // topic \u63D0\u6848\u6309 TopicCard JSON \u5B58\uFF1Aapply \u65F6\u5FC5\u987B\u80FD\u76F4\u63A5\u89E3\u6790\uFF0C\u4E0D\u80FD\u7B49\u5230\u7528\u6237\u70B9\u300C\u5E94\u7528\u300D\u624D\u5931\u8D25\u3002\n if (input.artifactRef === 'topic') {\n let parsed: unknown\n try { parsed = JSON.parse(input.content) } catch {\n throw new KsrwError('INVALID', '\u9009\u9898\u63D0\u6848 content \u5FC5\u987B\u662F TopicCard JSON\uFF08question/motivation/gap/novelty/feasibility/risks/decision\uFF09\uFF0C\u4E0D\u662F Markdown')\n }\n const card = parsed as Partial<Record<string, unknown>>\n for (const field of ['question', 'motivation', 'gap', 'novelty', 'feasibility', 'risks', 'decision']) {\n if (card[field] === undefined) throw new KsrwError('INVALID', '\u9009\u9898\u63D0\u6848\u7F3A\u5C11\u5B57\u6BB5\uFF1A' + field)\n }\n if (card['decision'] !== 'pending' && card['decision'] !== 'go' && card['decision'] !== 'nogo') {\n throw new KsrwError('INVALID', '\u9009\u9898\u63D0\u6848 decision \u53EA\u80FD\u662F pending/go/nogo\uFF0C\u6536\u5230\uFF1A' + String(card['decision']))\n }\n }\n const proposal: ProposalRecord = {\n id: newId('prop'),\n projectId: project.id,\n artifactRef: input.artifactRef,\n baseRevision: input.baseRevision,\n summary: input.summary,\n content: input.content,\n status: 'pending',\n ...(input.authorRole === undefined ? {} : { authorRole: input.authorRole }),\n createdAt: Date.now(),\n }\n writeJson(join(project.path, 'proposals', proposal.id + '.json'), proposal)\n return proposal\n })\n }\n\n applyProposal(projectPath: string, proposalId: string): { proposal: ProposalRecord; revision: number } {\n // \u6821\u9A8C\u3001\u5199\u4EA7\u7269\u3001\u63A8\u8FDB revision\u3001\u56DE\u5199\u63D0\u6848\u662F\u4E00\u4E2A\u4E8B\u52A1\uFF1B\u5E76\u53D1 apply \u4E0D\u80FD\u90FD\u6210\u529F\u3002\n return this.locked(projectPath, () => {\n const project = this.readProject(projectPath)\n const proposal = readJson<ProposalRecord>(join(project.path, 'proposals', proposalId + '.json'))\n if (proposal === null) throw new KsrwError('NOT_FOUND', '\u63D0\u6848\u4E0D\u5B58\u5728\uFF1A' + proposalId)\n if (proposal.status !== 'pending') throw new KsrwError('INVALID', '\u63D0\u6848\u5DF2\u5904\u7406\uFF1A' + proposal.status)\n const current = project.artifacts[proposal.artifactRef]?.revision ?? 0\n if (proposal.baseRevision !== current) {\n throw new KsrwError('STALE_REVISION', '\u63D0\u6848\u57FA\u7EBF\u5DF2\u8FC7\u671F\uFF1Abase=' + proposal.baseRevision + ' current=' + current)\n }\n const revision = current + 1\n const now = Date.now()\n if (proposal.artifactRef === 'topic') {\n let card: TopicCard\n try { card = JSON.parse(proposal.content) as TopicCard } catch {\n throw new KsrwError('INVALID', '\u9009\u9898\u63D0\u6848\u4E0D\u662F\u5408\u6CD5 JSON')\n }\n card.revision = revision\n card.updatedAt = now\n writeJson(join(project.path, 'topic', 'topic.json'), card)\n project.artifacts['topic'] = { revision, updatedAt: now, path: 'topic/topic.json' }\n } else {\n const relative = artifactPath(proposal.artifactRef)\n if (relative === null) throw new KsrwError('INVALID', 'M0 \u6682\u4E0D\u652F\u6301\u8BE5\u4EA7\u7269\u7C7B\u578B\uFF1A' + proposal.artifactRef)\n if (proposal.content.trim() === '') throw new KsrwError('INVALID', '\u4EA7\u7269\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A')\n atomicWrite(join(project.path, relative), proposal.content)\n project.artifacts[proposal.artifactRef] = { revision, updatedAt: now, path: relative }\n }\n project.updatedAt = now\n project.revision += 1\n this.saveProject(project)\n this.upsertRegistry(project)\n proposal.status = 'applied'\n proposal.appliedAt = now\n writeJson(join(project.path, 'proposals', proposalId + '.json'), proposal)\n return { proposal, revision }\n })\n }\n\n discardProposal(projectPath: string, proposalId: string): ProposalRecord {\n return this.locked(projectPath, () => {\n const project = this.readProject(projectPath)\n const proposal = readJson<ProposalRecord>(join(project.path, 'proposals', proposalId + '.json'))\n if (proposal === null) throw new KsrwError('NOT_FOUND', '\u63D0\u6848\u4E0D\u5B58\u5728\uFF1A' + proposalId)\n // P2-4\uFF1A\u53EA\u5141\u8BB8 pending \u2192 discarded\uFF1B\u5DF2 applied \u7684\u63D0\u6848\u4E0D\u53EF\u53D8\uFF08\u64A4\u9500\u8BF7\u751F\u6210\u53CD\u5411\u63D0\u6848\uFF09\uFF0C\n // \u5DF2 discarded \u7684\u63D0\u6848\u4E0D\u53EF\u91CD\u590D\u4E22\u5F03\u3002\n if (proposal.status === 'applied') {\n throw new KsrwError('INVALID', '\u63D0\u6848\u5DF2\u5E94\u7528\uFF08applied\uFF09\uFF0C\u4E0D\u53EF\u4E22\u5F03\uFF1A\u5982\u9700\u64A4\u9500\u8BF7\u751F\u6210\u53CD\u5411\u63D0\u6848\uFF08' + proposalId + '\uFF09')\n }\n if (proposal.status !== 'pending') {\n throw new KsrwError('INVALID', '\u63D0\u6848\u5DF2\u4E22\u5F03\uFF0C\u65E0\u9700\u91CD\u590D\u64CD\u4F5C\uFF1A' + proposalId)\n }\n proposal.status = 'discarded'\n writeJson(join(project.path, 'proposals', proposalId + '.json'), proposal)\n return proposal\n })\n }\n\n appendEvidence(projectPath: string, input: Omit<EvidenceRecord, 'id' | 'createdAt'>): EvidenceRecord {\n // jsonl \u662F\u300C\u8BFB\u5168\u6587 + \u8FFD\u52A0\u300D\uFF1A\u4E24\u4E2A\u8FDB\u7A0B\u540C\u65F6\u8FFD\u52A0\u65F6\u5FC5\u987B\u4E32\u884C\uFF0C\u5426\u5219\u4E22\u8BC1\u636E\u3002\n return this.locked(projectPath, () => {\n const project = this.readProject(projectPath)\n const record: EvidenceRecord = { ...input, id: newId('ev'), createdAt: Date.now() }\n const file = join(project.path, 'evidence', 'evidence.jsonl')\n const previous = readText(file) ?? ''\n atomicWrite(file, previous + JSON.stringify(record) + '\\n')\n return record\n })\n }\n\n /** P1-3\uFF1Aevidence.jsonl \u7684\u8BFB\u7AEF\uFF1B\u5B57\u6BB5\u4E0E appendEvidence \u5199\u5165\u4E00\u81F4\u3002 */\n readEvidence(projectPath: string): EvidenceRecord[] {\n const project = this.readProject(projectPath)\n const text = readText(join(project.path, 'evidence', 'evidence.jsonl')) ?? ''\n const records: EvidenceRecord[] = []\n for (const line of text.split('\\n')) {\n if (line.trim() === '') continue\n try { records.push(JSON.parse(line) as EvidenceRecord) } catch { /* ignore a torn line */ }\n }\n return records\n }\n\n appendHandoff(projectPath: string, input: Omit<HandoffRecord, 'id' | 'createdAt' | 'status'>): HandoffRecord {\n return this.locked(projectPath, () => {\n const project = this.readProject(projectPath)\n // P2-3\uFF1A\u4E0D\u5141\u8BB8\u7ED9\u81EA\u5DF1\u6D3E\u5355\uFF1B\u540C\u4E00 (from,to,requestedOutput) \u7684\u672A\u6536\u5C3E\u5355\u5E42\u7B49\u590D\u7528\uFF0C\n // Agent \u91CD\u8BD5/\u590D\u8FF0\u4E0D\u4F1A\u5806\u51FA\u91CD\u590D\u5355\u62AC\u9AD8\u95F8\u95E8\u963B\u585E\u8BA1\u6570\u3002\n if (input.from === input.to) throw new KsrwError('INVALID', '\u4E0D\u80FD\u7ED9\u81EA\u5DF1\u6D3E\u5355\uFF1A' + input.to)\n const records = this.readHandoffs(projectPath)\n const duplicate = records.find(record =>\n (record.status === 'open' || record.status === 'started')\n && record.from === input.from\n && record.to === input.to\n && record.requestedOutput === input.requestedOutput)\n if (duplicate !== undefined) return duplicate\n const record: HandoffRecord = { ...input, id: newId('hand'), status: 'open', createdAt: Date.now() }\n const file = join(project.path, 'handoffs.jsonl')\n const previous = readText(file) ?? ''\n atomicWrite(file, previous + JSON.stringify(record) + '\\n')\n return record\n })\n }\n\n updateHandoff(\n projectPath: string,\n id: string,\n status: HandoffRecord['status'],\n note?: string,\n ): HandoffRecord {\n // jsonl \u662F\u5168\u91CF\u91CD\u5199\uFF1A\u4E24\u4E2A\u5DE5\u4F4D\u540C\u65F6\u6539\u4E0D\u540C\u4EA4\u63A5\u5355\u65F6\uFF0C\u5FC5\u987B\u4E32\u884C\u4EE5\u514D\u4E92\u76F8\u8986\u76D6\u3002\n return this.locked(projectPath, () => {\n const project = this.readProject(projectPath)\n const file = join(project.path, 'handoffs.jsonl')\n const records = this.readHandoffs(projectPath)\n const index = records.findIndex(record => record.id === id)\n if (index < 0) throw new KsrwError('NOT_FOUND', '\u4EA4\u63A5\u5355\u4E0D\u5B58\u5728\uFF1A' + id)\n const previous = records[index]!\n // \u751F\u547D\u5468\u671F\u65F6\u95F4\u6233\u6309\u76EE\u6807\u72B6\u6001\u6574\u4F53\u91CD\u7B97\uFF1A\u91CD\u6D3E/\u9000\u56DE/\u91CD\u5F00\u4E0D\u4F1A\u7559\u4E0B\u65E7 started/done \u75D5\u8FF9\u3002\n let updated: HandoffRecord = {\n ...previous,\n status,\n ...(note === undefined ? {} : { note }),\n }\n if (status === 'started') {\n updated = { ...updated, startedAt: Date.now() }\n delete updated.returnedAt\n delete updated.doneAt\n } else if (status === 'returned') {\n updated = { ...updated, returnedAt: Date.now() }\n delete updated.startedAt\n delete updated.doneAt\n } else if (status === 'open') {\n if (note === undefined) delete updated.note\n delete updated.startedAt\n delete updated.returnedAt\n delete updated.doneAt\n } else {\n updated = { ...updated, doneAt: Date.now() }\n delete updated.returnedAt\n }\n records[index] = updated\n atomicWrite(file, records.map(record => JSON.stringify(record)).join('\\n') + '\\n')\n return updated\n })\n }\n\n readHandoffs(projectPath: string): HandoffRecord[] {\n const project = this.readProject(projectPath)\n const text = readText(join(project.path, 'handoffs.jsonl')) ?? ''\n const records: HandoffRecord[] = []\n for (const line of text.split('\\n')) {\n if (line.trim() === '') continue\n try { records.push(JSON.parse(line) as HandoffRecord) } catch { /* ignore a torn line */ }\n }\n return records\n }\n\n readResearchState(project: ProjectRecord): ResearchState {\n const parsed = readJson<ResearchState>(join(project.path, 'researchState.json'))\n if (parsed !== null) return parsed\n return {\n stage: project.stage,\n summary: project.idea,\n next: '\u5199\u51FA idea \u5E76\u751F\u6210\u9009\u9898\u5361\u3002',\n blockers: [],\n confirmed: [],\n updatedAt: project.updatedAt,\n }\n }\n\n writeResearchState(project: ProjectRecord, state: ResearchState): void {\n // json/md \u4E24\u4EFD\u662F\u540C\u4E00\u72B6\u6001\u7684\u4E24\u9762\uFF0C\u5199\u4E00\u534A\u4F1A\u88AB\u8BFB\u5230\u4E0D\u4E00\u81F4\u7684\u5FEB\u7167\u3002\n this.locked(project.path, () => {\n writeJson(join(project.path, 'researchState.json'), state)\n atomicWrite(join(project.path, 'researchState.md'), renderResearchState(project, state))\n })\n }\n\n private upsertRegistry(project: ProjectRecord): void {\n // \u767B\u8BB0\u8868\u662F home \u7EA7\u5171\u4EAB\u6587\u4EF6\uFF1A\u8BFB-\u6539-\u5199\u5FC5\u987B\u8DE8\u8FDB\u7A0B\u4E92\u65A5\uFF0C\u5426\u5219\u4E24\u4E2A\u9879\u76EE\u540C\u65F6\u767B\u8BB0\u4F1A\u4E22\u4E00\u4E2A\u3002\n // \u9501\u5E8F\u56FA\u5B9A\u4E3A\u300C\u9879\u76EE\u9501 \u2192 \u767B\u8BB0\u8868\u9501\u300D\uFF0C\u4E0D\u8981\u53CD\u5411\uFF0C\u907F\u514D\u4E24\u8FDB\u7A0B\u4EA4\u53C9\u6301\u6709\u6B7B\u9501\u3002\n this.withRegistry(() => {\n // \u514D\u767B\u8BB0\u5DE5\u4F5C\u533A\u4E0D\u8FDB\u9879\u76EE\u5E93\uFF1B\u82E5\u5386\u53F2\u4E0A\u88AB\u767B\u8BB0\u8FC7\uFF08\u4F8B\u5982\u5148\u767B\u8BB0\u540E\u8F6C\u4E3A\u5DE5\u574A\uFF09\uFF0C\u987A\u624B\u6E05\u6389\u3002\n if (project.detached === true) {\n const registry = this.loadRegistry()\n if (registry.projects.some(item => item.path === project.path)) {\n this.saveRegistry({\n version: REGISTRY_VERSION,\n projects: registry.projects.filter(item => item.path !== project.path),\n })\n }\n return\n }\n const registry = this.loadRegistry()\n const entry: ProjectIndexEntry = {\n id: project.id,\n name: project.name,\n path: project.path,\n stage: project.stage,\n createdAt: project.createdAt,\n updatedAt: project.updatedAt,\n }\n const others = registry.projects.filter(item => item.path !== project.path)\n this.saveRegistry({ version: REGISTRY_VERSION, projects: [...others, entry] })\n })\n }\n}\n\nfunction readdirNames(dir: string): string[] {\n return readdirSync(dir) as string[]\n}\n\nexport function renderResearchState(project: ProjectRecord, state: ResearchState): string {\n const lines = [\n '# \u7814\u7A76\u72B6\u6001 \u00B7 ' + project.name,\n '',\n '- \u9636\u6BB5\uFF1A' + state.stage,\n '- \u66F4\u65B0\uFF1A' + new Date(state.updatedAt).toISOString(),\n '- \u6458\u8981\uFF1A' + state.summary,\n '- \u4E0B\u4E00\u6B65\uFF1A' + state.next,\n '- \u963B\u585E\uFF1A' + (state.blockers.length === 0 ? '\u65E0' : state.blockers.join('\uFF1B')),\n '- \u5DF2\u786E\u8BA4\uFF1A' + (state.confirmed.length === 0 ? '\u65E0' : state.confirmed.join('\uFF1B')),\n '',\n ]\n return lines.join('\\n')\n}\n\n/** \u4F9B\u5DE5\u5177/RPC \u590D\u7528\u7684\u9879\u76EE\u6839\u63A8\u5BFC\uFF08\u76EE\u5F55\u5373\u9879\u76EE\uFF09\u3002 */\nexport function projectManifestPath(projectPath: string): string {\n return join(resolve(projectPath), 'project.json')\n}\n\n/** \u5220\u9664\u9879\u76EE\u53EA\u5220\u767B\u8BB0\uFF0C\u4E0D\u5220\u7528\u6237\u76EE\u5F55\uFF08\u7528\u6237\u81EA\u5DF1\u7BA1\u7684\u6587\u4EF6\u5939\uFF09\u3002 */\nexport function forgetProject(home: string, projectPath: string): void {\n withRegistryLock(home, () => {\n const registryFile = join(resolve(home), 'projects.json')\n const parsed = readJson<Registry>(registryFile)\n if (parsed === null) return\n const target = resolve(projectPath)\n writeJson(registryFile, {\n version: REGISTRY_VERSION,\n projects: parsed.projects.filter(item => resolve(item.path) !== target),\n })\n // \u6CE8\u610F\uFF1A\u4E0D\u80FD\u987A\u624B\u5220 `<project>/.ksrw.lock`\u3002\u90A3\u53EF\u80FD\u662F\u522B\u7684\u8FDB\u7A0B\u6B63\u6301\u6709\u7684\u6D3B\u9501\uFF0C\n // \u5220\u6389\u4F1A\u8BA9\u4E24\u4E2A\u5199\u5165\u8005\u540C\u65F6\u8FDB\u4E34\u754C\u533A\u3002\u9501\u53EA\u5728\u6301\u6709\u8005\u91CA\u653E\u65F6\u7531 lock.ts \u81EA\u5DF1\u6E05\u7406\u3002\n })\n}\n", "/** \u8BBA\u6587/\u4E13\u5229\u6210\u679C\u7F16\u8BD1\u4E0E\u5BFC\u51FA\uFF1Apandoc\uFF08md\u2192pdf/docx\uFF09\u4E0E latexmk\uFF08tex\u2192pdf\uFF09\u3002\n *\n * \u7F16\u8BD1\u4EA7\u7269\u4E0E\u65E5\u5FD7\u5168\u90E8\u843D\u76D8\u5728\u9879\u76EE\u76EE\u5F55\u5185\uFF1B\u5DE5\u5177\u7F3A\u5931\u65F6\u7ED9\u51FA\u53EF\u6267\u884C\u7684\u9519\u8BEF\uFF0C\n * \u4E0D\u5047\u88C5\u7F16\u8BD1\u6210\u529F\u3002UI\uFF08/ksrw/api\uFF09\u4E0E agent \u5DE5\u5177\uFF08ksrw_compile\uFF09\u5171\u7528\u540C\u4E00\u5B9E\u73B0\u3002\n */\nimport { spawnSync } from 'node:child_process'\nimport { homedir } from 'node:os'\nimport { createHash } from 'node:crypto'\nimport { existsSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs'\nimport { delimiter, extname, join, resolve, sep } from 'node:path'\n\nimport AdmZip from 'adm-zip'\n\nimport { KsrwError } from './error.ts'\nimport { paperTemplateFor } from './paper-templates.ts'\n\n/** \u672C\u673A\u7F16\u8BD1\u5DE5\u5177\u53EF\u7528\u6027\u3002 */\nexport interface PaperTools {\n readonly pandoc: boolean\n readonly latexmk: boolean\n readonly xelatex: boolean\n /** LibreOffice\uFF1A\u6CA1\u6709 TeX \u65F6\u7528\u5B83\u628A pandoc \u4EA7\u51FA\u7684 docx \u8F6C\u6210 PDF\u3002 */\n readonly soffice: boolean\n}\n\n/** \u4E00\u6B21\u5916\u90E8\u5DE5\u5177\u8C03\u7528\u7684\u6700\u5C0F\u7ED3\u679C\u3002 */\nexport interface PaperRunResult {\n readonly status: number | null\n readonly stdout?: string\n readonly stderr?: string\n}\n\n/** \u5DE5\u5177\u63A2\u6D4B\uFF08\u53EF\u6CE8\u5165\u6D4B\u8BD5\uFF09\u3002 */\nexport type ToolProbe = (tool: string) => string | undefined\n/** \u7F16\u8BD1\u6267\u884C\u5668\uFF08\u53EF\u6CE8\u5165\u6D4B\u8BD5\uFF09\u3002 */\nexport type PaperRunner = (tool: string, args: readonly string[], cwd: string) => PaperRunResult\n\nexport const defaultToolProbe: ToolProbe = (tool) => {\n const probe = process.platform === 'win32' ? 'where' : 'which'\n const found = spawnSync(probe, [tool], { encoding: 'utf8' })\n if (found.status !== 0) return undefined\n return found.stdout.split('\\n').map(line => line.trim()).find(line => line !== '')\n}\n\nexport const defaultPaperRunner: PaperRunner = (tool, args, cwd) => spawnSync(tool, [...args], {\n cwd, encoding: 'utf8', timeout: 180_000, maxBuffer: 4 * 1024 * 1024,\n})\n\nexport interface PaperIo {\n readonly probe?: ToolProbe\n readonly runner?: PaperRunner\n}\n\n/** \u63A2\u6D4B\u672C\u673A\u7F16\u8BD1\u5DE5\u5177\u3002 */\nexport function paperTools(io: PaperIo = {}): PaperTools {\n const probe = io.probe ?? defaultToolProbe\n return {\n pandoc: probe('pandoc') !== undefined,\n latexmk: probe('latexmk') !== undefined,\n xelatex: probe('xelatex') !== undefined,\n soffice: probe('soffice') !== undefined,\n }\n}\n\n/**\n * \u6392\u7248\u6A21\u677F\u7684\u4E24\u6761\u751F\u4EA7\u7EBF\uFF08\u6A21\u677F\u6587\u4EF6\u672C\u8EAB\u968F\u5305\uFF0C\u751F\u6210\u5668\u53EA\u5728\u91CD\u5EFA\u6A21\u677F\u65F6\u8DD1\uFF09\uFF1A\n * \u00B7 \u4F1A\u8BAE/\u671F\u520A\u6A21\u677F `public/templates/<id>.docx` \u2190 `scripts/build-paper-templates.py`\n * \u8BFB `scripts/paper-templates.json`\uFF08\u771F\u6E90\u89C1 paper-templates.ts \u9876\u90E8\uFF09\uFF1B\n * \u00B7 \u65E7\u7248\u5355\u4E00\u6A21\u677F `public/reference-academic.docx` \u2190 `scripts/build-paper-template.py`\n * \uFF08\u5355\u6570\uFF0C\u9700\u8981 pandoc + python-docx\uFF09\uFF0C\u4EC5\u4F5C\u4E3A\u5B58\u91CF\u9879\u76EE\u7684\u9000\u56DE\u8DEF\u5F84\u3002\n */\n\n\n/**\n * pandoc \u7684 Word \u6A21\u677F\u628A\u4EE3\u7801\u5B57\u4F53\u8BBE\u6210 Consolas\uFF08Windows \u4E13\u6709\uFF09\u3002\n * \u5728 macOS \u4E0A\u9884\u89C8 docx \u4F1A\u56E0\u6B64\u62A5\u300C\u7F3A\u5B57\u4F53\u300D\uFF0C\u6240\u4EE5\u5BFC\u51FA\u540E\u628A\u4EE3\u7801\u5B57\u4F53\u6362\u6210\u672C\u673A\u7B49\u5BBD\u5B57\u4F53\u3002\n */\nexport function patchDocxFonts(docxPath: string): string[] {\n const mono = process.platform === 'darwin' ? 'Menlo' : process.platform === 'linux' ? 'DejaVu Sans Mono' : undefined\n if (mono === undefined) return []\n if (!existsSync(docxPath)) return []\n try {\n const zip = new AdmZip(docxPath)\n const changed: string[] = []\n for (const entry of zip.getEntries()) {\n if (!/^word\\/(styles|document|numbering|theme\\/theme1)\\.xml$/.test(entry.entryName)) continue\n const text = entry.getData().toString('utf8')\n if (!text.includes('Consolas')) continue\n zip.updateFile(entry.entryName, Buffer.from(text.split('Consolas').join(mono), 'utf8'))\n changed.push(entry.entryName)\n }\n if (changed.length > 0) zip.writeZip(docxPath)\n return changed\n } catch { return [] }\n}\n\n/** \u6E90\u6587\u4EF6\u76F8\u5BF9\u8DEF\u5F84\u9ED8\u8BA4\u503C\u3002 */\nexport function defaultPaperSource(dir: 'paper' | 'patent'): string {\n return dir === 'patent' ? 'patent/disclosure.md' : 'paper/main.md'\n}\n\n/** \u89E3\u6790\u5E76\u5B88\u536B\u6E90\u6587\u4EF6\u8DEF\u5F84\uFF1A\u5FC5\u987B\u5728\u9879\u76EE\u76EE\u5F55\u5185\u3002 */\nexport function resolvePaperSource(projectPath: string, source: string): string {\n const absolute = resolve(projectPath, source)\n if (absolute !== projectPath && !absolute.startsWith(projectPath + sep)) {\n throw new KsrwError('INVALID', '\u6E90\u6587\u4EF6\u5FC5\u987B\u5728\u9879\u76EE\u76EE\u5F55\u5185')\n }\n return absolute\n}\n\n/** \u6E90\u6587\u4EF6\u6240\u5728\u76EE\u5F55\u4E0E\u6587\u4EF6\u540D\uFF1Apandoc/latexmk \u4EE5\u8BE5\u76EE\u5F55\u4E3A cwd\uFF0C\u56FE\u624D\u80FD\u6309\u76F8\u5BF9\u8DEF\u5F84\u89E3\u6790\u3002 */\nfunction sourceParts(absolute: string): { dir: string; name: string } {\n const name = absolute.split(sep).pop() ?? absolute\n return { dir: absolute.slice(0, absolute.length - name.length), name }\n}\n\nfunction toolOf(source: string): 'pandoc' | 'latexmk' {\n const extension = extname(source).toLowerCase()\n if (extension === '.tex') return 'latexmk'\n if (extension === '.md' || extension === '.markdown') return 'pandoc'\n throw new KsrwError('INVALID', '\u53EA\u652F\u6301 .md \u6216 .tex\uFF1A' + source)\n}\n\n/** \u7F16\u8BD1\u72B6\u6001\uFF1A\u6E90\u6587\u4EF6\u5B58\u5728\u6027 + \u5DE5\u5177\u53EF\u7528\u6027 + \u662F\u5426\u53EF\u7F16\u8BD1\u51FA PDF\u3002 */\nexport function paperStatus(projectPath: string, source: string, io: PaperIo = {}): {\n source: string\n exists: boolean\n tool: string\n available: boolean\n ready: boolean\n pdfEngine: 'xelatex' | 'soffice' | 'none'\n tools: PaperTools\n /** \u5B66\u672F\u6392\u7248\u6A21\u677F\u662F\u5426\u5C31\u7EEA\uFF08reference-academic.docx\uFF09\u3002 */\n template: boolean\n} {\n const absolute = resolvePaperSource(projectPath, source)\n const tool = toolOf(source)\n const tools = paperTools(io)\n const available = tool === 'latexmk' ? tools.latexmk : tools.pandoc\n // Markdown can still produce a PDF without TeX: pandoc \u2192 docx \u2192 LibreOffice.\n const pdfEngine: 'xelatex' | 'soffice' | 'none' = tool === 'latexmk'\n ? (tools.latexmk ? 'xelatex' : 'none')\n : (!tools.pandoc ? 'none' : (tools.xelatex ? 'xelatex' : (tools.soffice ? 'soffice' : 'none')))\n return {\n source,\n exists: existsSync(absolute),\n tool,\n available,\n ready: pdfEngine !== 'none',\n pdfEngine,\n tools,\n template: paperTemplateFor(projectPath) !== null,\n }\n}\n\nfunction logTail(run: PaperRunResult): string {\n return ((run.stdout ?? '') + (run.stderr ?? '')).trim().slice(-4000)\n}\n\n/** \u628A Markdown \u7F16\u8BD1\u6210 Word\uFF08pandoc -o docx\uFF09\uFF1B\u8FD4\u56DE docx \u76F8\u5BF9\u8DEF\u5F84\u3002 */\nexport function exportPaperDocx(projectPath: string, source: string, io: PaperIo = {}): {\n docx: string\n bytes: number\n log: string\n} {\n const absolute = resolvePaperSource(projectPath, source)\n if (!existsSync(absolute)) throw new KsrwError('NOT_FOUND', '\u6E90\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A' + source)\n const tool = toolOf(source)\n if (tool !== 'pandoc') throw new KsrwError('INVALID', 'Word \u5BFC\u51FA\u53EA\u652F\u6301 Markdown \u6E90\uFF08.md\uFF09\uFF1BLaTeX \u8BF7\u5148\u7F16\u8BD1 PDF')\n const tools = paperTools(io)\n if (!tools.pandoc) throw new KsrwError('ENGINE_MISSING', '\u7F3A\u5C11 pandoc\uFF1B\u5148\u5B89\u88C5 pandoc \u518D\u5BFC\u51FA Word')\n const output = absolute.replace(/\\.md(own|markdown)?$/i, '.docx')\n const runner = io.runner ?? defaultPaperRunner\n const template = paperTemplateFor(projectPath)\n const styleArgs = [\n ...(template === null ? [] : ['--reference-doc=' + template]),\n '--syntax-highlighting=none',\n ]\n const run = runner('pandoc', ['--standalone', source, '-o', output, ...styleArgs], projectPath)\n const log = logTail(run)\n if (run.status !== 0) throw new KsrwError('COMPILE_FAILED', 'pandoc \u5BFC\u51FA Word \u5931\u8D25\uFF08\u9000\u51FA\u7801 ' + String(run.status) + '\uFF09\\n' + log)\n if (!existsSync(output)) throw new KsrwError('COMPILE_FAILED', '\u672A\u751F\u6210 docx\uFF1A' + output + '\\n' + log)\n patchDocxFonts(output)\n writePaperBuild(projectPath, source.startsWith('patent/') ? 'patent' : 'paper', {\n docx: output.slice(projectPath.length + 1), docxBytes: statSync(output).size,\n docxSourceHash: hashFile(absolute) ?? '', docxExportedAt: Date.now(),\n })\n return { docx: output.slice(projectPath.length + 1), bytes: statSync(output).size, log }\n}\n\n/** \u7F16\u8BD1\u8BBA\u6587/\u4EA4\u5E95\u4E66\u4E3A PDF\uFF1Amd \u8D70 pandoc+xelatex\uFF0Ctex \u8D70 latexmk\u3002 */\nexport function compilePaper(projectPath: string, source: string, io: PaperIo = {}): {\n pdf: string\n bytes: number\n tool: string\n log: string\n} {\n const absolute = resolvePaperSource(projectPath, source)\n if (!existsSync(absolute)) throw new KsrwError('NOT_FOUND', '\u6E90\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A' + source)\n const tool = toolOf(source)\n const tools = paperTools(io)\n const runner = io.runner ?? defaultPaperRunner\n if (tool === 'latexmk' && !tools.latexmk) throw new KsrwError('ENGINE_MISSING', '\u7F3A\u5C11 latexmk\uFF1B\u5B89\u88C5 TeX \u540E\u518D\u7F16\u8BD1 .tex')\n if (tool === 'pandoc') {\n if (!tools.pandoc) throw new KsrwError('ENGINE_MISSING', '\u7F3A\u5C11 pandoc\uFF1B\u5148\u5B89\u88C5 pandoc')\n // \u6CA1\u6709 TeX \u65F6\u964D\u7EA7\uFF1Apandoc \u4EA7\u51FA docx\uFF0C\u518D\u7531 LibreOffice \u8F6C PDF\uFF08\u6392\u7248\u7565\u7B80\uFF0C\u4F46\u53EF\u7528\uFF09\u3002\n if (!tools.xelatex) return compileViaOffice(projectPath, absolute, source, io, runner)\n }\n const output = absolute.replace(/\\.(md|markdown|tex)$/i, '.pdf')\n const parts = sourceParts(absolute)\n // \u4EE5\u6E90\u6587\u4EF6\u76EE\u5F55\u4E3A cwd\uFF1A\u6B63\u6587\u91CC\u7684 figures/x.png \u624D\u80FD\u88AB pandoc/latexmk \u627E\u5230\u3002\n const resourcePath = parts.dir + delimiter + projectPath\n const args = tool === 'latexmk'\n ? ['-pdf', '-interaction=nonstopmode', '-halt-on-error', parts.name]\n : ['--standalone', parts.name, '-o', output, '--pdf-engine=xelatex', '--resource-path=' + resourcePath]\n const run = runner(tool, args, parts.dir)\n const log = logTail(run)\n if (run.status !== 0) throw new KsrwError('COMPILE_FAILED', tool + ' \u9000\u51FA\u7801 ' + String(run.status) + '\\n' + log)\n if (!existsSync(output)) throw new KsrwError('COMPILE_FAILED', '\u672A\u751F\u6210 PDF\uFF1A' + output.slice(projectPath.length + 1) + '\\n' + log)\n const finalPdf = output.slice(projectPath.length + 1)\n writePaperBuild(projectPath, source.startsWith('patent/') ? 'patent' : 'paper', {\n source, sourceHash: hashFile(absolute) ?? '', pdf: finalPdf, pdfBytes: statSync(output).size,\n })\n return { pdf: finalPdf, bytes: statSync(output).size, tool, log }\n}\n\n/** \u65E0 TeX \u7684 PDF \u8DEF\u5F84\uFF1Apandoc md \u2192 docx\uFF0C\u518D soffice \u2192 pdf\u3002 */\nfunction compileViaOffice(\n projectPath: string,\n absolute: string,\n source: string,\n io: PaperIo,\n runner: PaperRunner,\n): { pdf: string; bytes: number; tool: string; log: string } {\n const probe = io.probe ?? defaultToolProbe\n const soffice = probe('soffice')\n if (soffice === undefined) {\n throw new KsrwError('ENGINE_MISSING', '\u7F3A\u5C11 PDF \u5F15\u64CE\uFF1A\u5B89\u88C5 TeX\uFF08xelatex\uFF09\u6216\u7528 pandoc + LibreOffice\uFF08soffice\uFF09')\n }\n const docx = absolute.replace(/\\.md(own|markdown)?$/i, '.docx')\n const pdf = absolute.replace(/\\.md(own|markdown)?$/i, '.pdf')\n const parts = sourceParts(absolute)\n const resourcePath = parts.dir + delimiter + projectPath\n const template = paperTemplateFor(projectPath)\n const styleArgs = [\n ...(template === null ? [] : ['--reference-doc=' + template]),\n '--syntax-highlighting=none',\n ]\n const pandoc = runner('pandoc', ['--standalone', parts.name, '-o', docx, '--resource-path=' + resourcePath, ...styleArgs], parts.dir)\n if (pandoc.status !== 0) throw new KsrwError('COMPILE_FAILED', 'pandoc \u751F\u6210 docx \u5931\u8D25\uFF08\u9000\u51FA\u7801 ' + String(pandoc.status) + '\uFF09\\n' + logTail(pandoc))\n if (!existsSync(docx)) throw new KsrwError('COMPILE_FAILED', '\u672A\u751F\u6210 docx\uFF1A' + docx.slice(projectPath.length + 1))\n patchDocxFonts(docx)\n // \u65E0\u5934\u8F6C\u6362\u7ED9\u4E00\u4E2A\u72EC\u7ACB profile\uFF1A\u907F\u514D\u4E0E\u7528\u6237\u6B63\u5728\u7528\u7684 LibreOffice \u62A2\u9501/\u6539\u914D\u7F6E\uFF0C\n // \u4E5F\u8BA9\u53D7\u9650\u73AF\u5883\uFF08HOME \u53EA\u8BFB\uFF09\u80FD\u628A profile \u6307\u5230\u53EF\u5199\u76EE\u5F55\u3002\n const profile = process.env['KSRW_SOFFICE_PROFILE']?.trim()\n const convert = runner(soffice, [\n '--headless',\n ...(profile === undefined || profile === '' ? [] : ['-env:UserInstallation=file://' + resolve(profile)]),\n '--convert-to', 'pdf', '--outdir', parts.dir, docx,\n ], projectPath)\n const log = logTail(convert)\n if (convert.status !== 0) throw new KsrwError('COMPILE_FAILED', 'LibreOffice \u8F6C PDF \u5931\u8D25\uFF08\u9000\u51FA\u7801 ' + String(convert.status) + '\uFF09\\n' + log)\n if (!existsSync(pdf)) throw new KsrwError('COMPILE_FAILED', '\u672A\u751F\u6210 PDF\uFF1A' + pdf.slice(projectPath.length + 1) + '\\n' + log)\n const finalPdf = pdf.slice(projectPath.length + 1)\n writePaperBuild(projectPath, source.startsWith('patent/') ? 'patent' : 'paper', {\n source, sourceHash: hashFile(absolute) ?? '', pdf: finalPdf, pdfBytes: statSync(pdf).size,\n })\n return { pdf: finalPdf, bytes: statSync(pdf).size, tool: 'pandoc+soffice', log }\n}\n\n/** \u5B9A\u7A3F\u5370\u8BB0\uFF1APDF \u7531\u54EA\u4E00\u7248\u6E90\u6587\u4EF6\u751F\u6210\uFF0C\u7528\u6765\u5224\u65AD\u300C\u8BBA\u6587\u6539\u8FC7\u3001PDF \u5DF2\u8FC7\u671F\u300D\u3002 */\nexport interface PaperBuildRecord {\n readonly source: string\n readonly sourceHash: string\n readonly pdf: string\n readonly pdfBytes: number\n readonly docx?: string\n readonly docxBytes?: number\n /** \u5BFC\u51FA Word \u65F6\u6B63\u6587\u7684\u6307\u7EB9\u4E0E\u65F6\u95F4\uFF1A\u7528\u6765\u63D0\u793A\u300C\u8349\u7A3F\u5DF2\u8FC7\u671F\u300D\u3002 */\n readonly docxSourceHash?: string\n readonly docxExportedAt?: number\n readonly generatedAt: number\n /** \u6B63\u6587\u6539\u8FC7\u4E4B\u540E\u65E7\u5B9A\u7A3F\u88AB\u5220\u9664\u7684\u65F6\u95F4\uFF08\u7528\u4E8E\u7ED9\u51FA\u51C6\u786E\u63D0\u793A\uFF09\u3002 */\n readonly invalidatedAt?: number\n /** \u6E90\u6587\u4EF6\u6682\u65F6\u8BFB\u4E0D\u5230\uFF08\u88AB\u79FB\u52A8/\u6743\u9650\u5F02\u5E38\uFF09\u7684\u65F6\u95F4\uFF1A\u6B64\u65F6\u4FDD\u7559 PDF\uFF0C\u53EA\u6807\u8BB0\u4E0D\u53EF\u8BFB\u3002 */\n readonly unreadableAt?: number\n}\n\nconst BUILD_FILE = '.build.json'\n\nfunction buildFile(projectPath: string, dir: 'paper' | 'patent'): string {\n return join(projectPath, dir, BUILD_FILE)\n}\n\nfunction hashFile(absolute: string): string | undefined {\n try { return createHash('sha256').update(readFileSync(absolute)).digest('hex').slice(0, 16) } catch { return undefined }\n}\n\n/** \u8BFB\u5B9A\u7A3F\u5370\u8BB0\uFF08\u6CA1\u6709\u5219 undefined\uFF09\u3002 */\nexport function readPaperBuild(projectPath: string, dir: 'paper' | 'patent'): PaperBuildRecord | undefined {\n try {\n const parsed = JSON.parse(readFileSync(buildFile(projectPath, dir), 'utf8')) as Partial<PaperBuildRecord>\n if (typeof parsed.source !== 'string' || typeof parsed.pdf !== 'string') return undefined\n return {\n source: parsed.source,\n sourceHash: typeof parsed.sourceHash === 'string' ? parsed.sourceHash : '',\n pdf: parsed.pdf,\n pdfBytes: typeof parsed.pdfBytes === 'number' ? parsed.pdfBytes : 0,\n ...(typeof parsed.docx === 'string' ? { docx: parsed.docx } : {}),\n ...(typeof parsed.docxBytes === 'number' ? { docxBytes: parsed.docxBytes } : {}),\n ...(typeof parsed.docxSourceHash === 'string' ? { docxSourceHash: parsed.docxSourceHash } : {}),\n ...(typeof parsed.docxExportedAt === 'number' ? { docxExportedAt: parsed.docxExportedAt } : {}),\n ...(typeof parsed.invalidatedAt === 'number' ? { invalidatedAt: parsed.invalidatedAt } : {}),\n ...(typeof parsed.unreadableAt === 'number' ? { unreadableAt: parsed.unreadableAt } : {}),\n generatedAt: typeof parsed.generatedAt === 'number' ? parsed.generatedAt : 0,\n }\n } catch { return undefined }\n}\n\nfunction writePaperBuild(projectPath: string, dir: 'paper' | 'patent', patch: Partial<PaperBuildRecord>): void {\n const previous = readPaperBuild(projectPath, dir) ?? { source: '', sourceHash: '', pdf: '', pdfBytes: 0, generatedAt: 0 }\n const next: PaperBuildRecord = { ...previous, ...patch, generatedAt: Date.now() } as PaperBuildRecord\n try { writeFileSync(buildFile(projectPath, dir), JSON.stringify(next, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 }) } catch { /* \u5370\u8BB0\u5199\u5931\u8D25\u4E0D\u963B\u585E\u7F16\u8BD1 */ }\n}\n\n/**\n * \u6B63\u6587\u4E00\u6539\u5C31\u628A\u65E7\u5B9A\u7A3F PDF \u5220\u6389\uFF1A\u5B9A\u7A3F\u53EA\u4EE3\u8868\u300C\u5F53\u524D\u8FD9\u4E00\u7248\u6B63\u6587\u300D\u3002\n * @returns \u662F\u5426\u5220\u9664\u4E86\u8FC7\u671F PDF\u3002\n */\nexport function invalidateStalePaperFinal(projectPath: string, dir: 'paper' | 'patent'): boolean {\n const build = readPaperBuild(projectPath, dir)\n if (build === undefined || build.pdf === '') return false\n const source = build.source === '' ? defaultPaperSource(dir) : build.source\n const current = hashFile(resolvePaperSource(projectPath, source))\n if (current !== undefined && current === build.sourceHash) return false\n if (current === undefined) {\n // P2-21\uFF1A\u6E90\u6587\u4EF6\u8BFB\u4E0D\u5230\uFF08\u4E34\u65F6\u6539\u540D/\u6743\u9650/\u76EE\u5F55\u79FB\u52A8\uFF09\u4E0D\u7B49\u4E8E\u6B63\u6587\u6539\u8FC7\u2014\u2014\u4E0D\u8981\u5220\u5DF2\u7F16\u8BD1\u7684\n // \u5B9A\u7A3F PDF\uFF0C\u53EA\u8BB0\u5F55\u4E0D\u53EF\u8BFB\u65F6\u95F4\uFF1BpaperFinalStatus \u4ECD\u4F1A\u628A\u5B83\u6807\u4E3A\u8FC7\u671F\u5E76\u63D0\u793A\u91CD\u65B0\u7F16\u8BD1\u3002\n const unreadable: PaperBuildRecord = { ...build, unreadableAt: Date.now() }\n try { writeFileSync(buildFile(projectPath, dir), JSON.stringify(unreadable, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 }) } catch { /* ignore */ }\n return true\n }\n try { rmSync(join(projectPath, build.pdf), { force: true }) } catch { /* \u5220\u4E0D\u6389\u5C31\u7559\u7740\uFF0C\u53CD\u6B63\u5370\u8BB0\u5DF2\u5931\u6548 */ }\n const next: PaperBuildRecord = {\n ...build, pdf: '', pdfBytes: 0, sourceHash: current ?? build.sourceHash, invalidatedAt: Date.now(),\n }\n try { writeFileSync(buildFile(projectPath, dir), JSON.stringify(next, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 }) } catch { /* ignore */ }\n return true\n}\n\n/** \u5B9A\u7A3F\u72B6\u6001\uFF1A\u6709\u6CA1\u6709 PDF \u5370\u8BB0\u3001\u6E90\u6587\u4EF6\u6539\u8FC7\u4E4B\u540E\u662F\u5426\u8FC7\u671F\u3002 */\nexport function paperFinalStatus(projectPath: string, dir: 'paper' | 'patent'): { build?: PaperBuildRecord; stale: boolean; draftStale: boolean } {\n const build = readPaperBuild(projectPath, dir)\n const source = build !== undefined && build.source !== '' ? build.source : defaultPaperSource(dir)\n const current = hashFile(resolvePaperSource(projectPath, source))\n const draftStale = build?.docx !== undefined && build.docxSourceHash !== current\n // \u53EA\u5BFC\u51FA\u8FC7 Word \u4E0D\u7B97\u5B9A\u7A3F\uFF1A\u5FC5\u987B\u771F\u7684\u6709 PDF\u3002\n if (build === undefined || build.pdf === '' || !existsSync(join(projectPath, build.pdf))) return { stale: true, draftStale }\n return { build, stale: current === undefined || current !== build.sourceHash, draftStale }\n}\n\nexport interface PaperOutput {\n readonly name: string\n readonly path: string\n readonly bytes: number\n readonly updatedAt: number\n}\n\nconst OUTPUT_EXTS = new Set(['.md', '.markdown', '.tex', '.pdf', '.docx', '.log'])\n\n/** \u5217\u51FA\u6210\u679C\u76EE\u5F55\u4E0B\u53EF\u9884\u89C8/\u7F16\u8BD1\u7684\u6587\u4EF6\uFF08\u5E73\u94FA\u4E00\u5C42\uFF0C\u6309\u4FEE\u6539\u65F6\u95F4\u5012\u5E8F\uFF09\u3002 */\nexport function listPaperOutputs(projectPath: string, dir: 'paper' | 'patent'): PaperOutput[] {\n const root = resolvePaperSource(projectPath, dir)\n if (!existsSync(root)) return []\n const outputs: PaperOutput[] = []\n for (const name of readdirSync(root)) {\n if (name.startsWith('.')) continue\n const absolute = join(root, name)\n let info\n try { info = statSync(absolute) } catch { continue }\n if (!info.isFile()) continue\n if (!OUTPUT_EXTS.has(extname(name).toLowerCase())) continue\n outputs.push({ name, path: dir + '/' + name, bytes: info.size, updatedAt: info.mtimeMs })\n }\n return outputs.sort((left, right) => right.updatedAt - left.updatedAt)\n}\n\n/** \u8BFB\u53D6\u9879\u76EE\u5185\u6587\u4EF6\u7684\u5B57\u8282\uFF08\u9884\u89C8\u7528\uFF09\uFF1B\u5B88\u536B\u8DEF\u5F84\u5E76\u9650\u5236\u5927\u5C0F\u3002 */\nexport function readProjectFileBytes(projectPath: string, file: string, maxBytes: number): Uint8Array {\n const absolute = resolvePaperSource(projectPath, file)\n let info\n try { info = statSync(absolute) } catch {\n throw new KsrwError('NOT_FOUND', '\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A' + file)\n }\n if (!info.isFile()) throw new KsrwError('INVALID', '\u4E0D\u662F\u5E38\u89C4\u6587\u4EF6\uFF1A' + file)\n if (info.size > maxBytes) {\n throw new KsrwError('INVALID', '\u6587\u4EF6\u8D85\u8FC7\u9884\u89C8\u5927\u5C0F\u4E0A\u9650\uFF08' + Math.round(maxBytes / 1024 / 1024) + 'MiB\uFF09\uFF1A' + file)\n }\n return new Uint8Array(readFileSync(absolute))\n}\n", "/** \u514D\u9879\u76EE\u5B9E\u9A8C\u5DE5\u574A\uFF08standalone\uFF09\uFF1A\u4E0D\u5EFA\u9879\u76EE\uFF0C\u76F4\u63A5\u4E0A\u4F20\u8BBA\u6587 \u2192 \u62BD\u53D6\u5B9E\u9A8C\u8981\u7D20 \u2192 \u8865\u5B9E\u9A8C\u8BBE\u8BA1 \u2192 \u8FD0\u884C \u2192 \u62A5\u544A\u3002\n *\n * \u843D\u76D8\uFF08<KSRW_HOME>/standalone/<id>/\uFF0C\u9ED8\u8BA4 ~/.ksrw/standalone\uFF09\uFF1A\n * standalone.json \u5DE5\u574A\u6E05\u5355\uFF08id / \u540D\u79F0 / \u8BBA\u6587\u5143\u4FE1\u606F\uFF09\n * project.json detached \u9879\u76EE\u8BB0\u5F55\uFF1A\u590D\u7528\u63D0\u6848/\u5DE5\u4EF6/\u5DE5\u5177\uFF0C\u4F46\u4E0D\u8FDB\u9879\u76EE\u5E93\n * paper/attachment.* \u539F\u59CB\u9644\u4EF6\uFF08PDF / DOCX / MD / TXT\uFF09\n * paper/paper.md \u89C4\u8303\u5316\u5168\u6587\uFF08PDF \u6309\u300C## \u7B2C N \u9875\u300D\u5206\u9875\uFF09\n * paper/analysis.json \u5B9E\u9A8C\u8981\u7D20\u62BD\u53D6\u7ED3\u679C\n * experiments/record.md experiment \u5DE5\u4EF6 = \u5B9E\u9A8C\u8BBE\u8BA1\uFF08\u63D0\u6848\u9A71\u52A8\uFF0C\u7528\u6237 apply \u540E\u6743\u5A01\uFF09\n * experiments/code/*.py \u53EF\u6267\u884C\u811A\u672C\uFF08Agent \u53EA\u7ECF ksrw_experiment(script) \u5199\u5165\uFF09\n * experiments/runs/<id>/ \u590D\u7528\u65E2\u6709 run \u843D\u76D8\u683C\u5F0F\n * experiments/report.md \u6309\u771F\u5B9E run \u751F\u6210\u7684\u62A5\u544A\uFF08\u4E0D\u7F16\u9020\u6307\u6807\uFF09\n */\nimport { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs'\nimport { join, resolve, sep } from 'node:path'\n\nimport {\n MAX_ATTACHMENT_BYTES,\n attachmentKindOf,\n cleanText,\n detectTitle,\n extractAttachment,\n looksLikeByline,\n normalizeWithPageMarkers,\n safeAttachmentName,\n splitNormalizedPages,\n type PaperKind,\n} from './attachment.ts'\nimport { KsrwError } from './error.ts'\nimport { withProjectLock } from './lock.ts'\nimport { ProjectStore, forgetProject, resolveKsrwHome, type ProjectRecord } from './workspace.ts'\n\nexport const STANDALONE_DIR = 'standalone'\nexport const MAX_PAPER_BYTES = MAX_ATTACHMENT_BYTES\nexport const MAX_SCRIPT_BYTES = 256 * 1024\nconst MAX_PAPER_TEXT = 200_000\n\nexport type StandaloneStatus = 'empty' | 'paper' | 'analyzed' | 'designed' | 'running' | 'ran' | 'failed'\nexport type { PaperKind } from './attachment.ts'\n// \u517C\u5BB9\u65E7\u8C03\u7528\uFF08\u6D4B\u8BD5/\u5185\u6838\u5916\u4F7F\u7528\uFF09\uFF1ADOCX \u89E3\u6790\u5DF2\u62BD\u5230 attachment.ts\u3002\nexport { docxToText } from './attachment.ts'\nexport type ElementKey = 'task' | 'method' | 'dataset' | 'metric' | 'baseline' | 'limitation'\n\nexport interface PaperMeta {\n fileName: string\n kind: PaperKind\n bytes: number\n uploadedAt: number\n pages: number\n chars: number\n title: string\n}\n\nexport interface ElementItem {\n text: string\n /** \u8BC1\u636E\u4F4D\u7F6E\uFF1A\u7B2C 3 \u9875 / \u6458\u8981 / \u6B63\u6587\u3002 */\n source: string\n}\n\nexport interface ExperimentElement {\n key: ElementKey\n label: string\n items: ElementItem[]\n}\n\nexport interface PaperAnalysis {\n analyzedAt: number\n title: string\n abstract: string\n keywords: string[]\n sections: { heading: string; chars: number }[]\n elements: ExperimentElement[]\n missing: { key: string; label: string; hint: string }[]\n warnings: string[]\n}\n\nexport interface StandaloneManifest {\n schemaVersion: 1\n id: string\n name: string\n createdAt: number\n paper: PaperMeta | null\n}\n\nexport interface StandaloneSummary {\n id: string\n name: string\n path: string\n createdAt: number\n updatedAt: number\n status: StandaloneStatus\n paperName: string\n pages: number\n chars: number\n runCount: number\n designRevision: number\n}\n\nexport interface StandaloneDesign {\n revision: number\n updatedAt: number\n content: string\n}\n\nexport interface StandaloneRunSummary {\n id: string\n script: string\n status: 'running' | 'succeeded' | 'failed' | 'cancelled'\n startedAt: number\n durationMs: number\n exitCode: number | null\n metrics: Record<string, number | string>\n artifacts: string[]\n python?: string\n}\n\nexport interface StandaloneScript {\n relative: string\n title: string\n mtime: number\n}\n\nexport interface StandaloneDetail {\n experiment: StandaloneSummary & {\n paper: PaperMeta | null\n analysis: PaperAnalysis | null\n designRevision: number\n projectStage: string\n }\n design: StandaloneDesign\n paperText: string\n scripts: StandaloneScript[]\n runs: StandaloneRunSummary[]\n report: string\n}\n\nexport function resolveStandaloneRoot(home: string = resolveKsrwHome()): string {\n return join(resolve(home), STANDALONE_DIR)\n}\n\nfunction readJson<T>(file: string): T | null {\n try { return JSON.parse(readFileSync(file, 'utf8')) as T } catch { return null }\n}\n\nfunction readText(file: string): string | null {\n try { return readFileSync(file, 'utf8') } catch { return null }\n}\n\nfunction writeJson(file: string, value: unknown): void {\n mkdirSync(join(file, '..'), { recursive: true, mode: 0o700 })\n writeFileSync(file, JSON.stringify(value, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n}\n\nfunction manifestPath(projectPath: string): string {\n return join(projectPath, 'standalone.json')\n}\n\nfunction paperDir(projectPath: string): string {\n return join(projectPath, 'paper')\n}\n\n/** \u4E24\u4F4D\u8865\u96F6\u7684\u672C\u5730\u65F6\u95F4\u7247\u6BB5\uFF08\u6587\u4EF6\u540D/\u5DE5\u574A\u540D\u7528\uFF09\u3002 */\nfunction two(value: number): string {\n return value < 10 ? '0' + String(value) : String(value)\n}\n\nfunction stamp(at: number): string {\n const date = new Date(at)\n return String(date.getFullYear()) + two(date.getMonth() + 1) + two(date.getDate())\n + '-' + two(date.getHours()) + two(date.getMinutes()) + two(date.getSeconds())\n}\n\nfunction shortId(): string {\n return Math.random().toString(36).slice(2, 8)\n}\n\nfunction defaultName(at: number): string {\n const date = new Date(at)\n return '\u514D\u9879\u76EE\u5B9E\u9A8C \u00B7 ' + two(date.getMonth() + 1) + '-' + two(date.getDate())\n + ' ' + two(date.getHours()) + ':' + two(date.getMinutes())\n}\n\nfunction deriveManifest(projectPath: string): StandaloneManifest | null {\n const project = readJson<ProjectRecord>(join(projectPath, 'project.json'))\n if (project === null) return null\n return { schemaVersion: 1, id: project.id, name: project.name, createdAt: project.createdAt, paper: null }\n}\n\nfunction readManifest(projectPath: string): StandaloneManifest | null {\n return readJson<StandaloneManifest>(manifestPath(projectPath)) ?? deriveManifest(projectPath)\n}\n\nfunction writeManifest(projectPath: string, manifest: StandaloneManifest): void {\n writeJson(manifestPath(projectPath), manifest)\n}\n\nfunction artifactsOf(runDir: string): string[] {\n const root = join(runDir, 'artifacts')\n if (!existsSync(root)) return []\n try {\n return readdirSync(root, { withFileTypes: true }).filter(entry => entry.isFile()).map(entry => entry.name).sort()\n } catch { return [] }\n}\n\n/** \u6301\u4E45\u5316 run \u6458\u8981\uFF08\u8FD0\u884C\u4E2D\u7684\u5B9E\u65F6\u72B6\u6001\u7531 core \u4FA7\u53E0\u52A0\uFF09\u3002 */\nexport function listStandaloneRuns(projectPath: string): StandaloneRunSummary[] {\n const root = join(projectPath, 'experiments', 'runs')\n if (!existsSync(root)) return []\n let entries: string[] = []\n try { entries = readdirSync(root) } catch { return [] }\n const runs: StandaloneRunSummary[] = []\n for (const entry of entries) {\n const dir = join(root, entry)\n const config = readJson<Record<string, unknown>>(join(dir, 'config.json'))\n if (config === null) continue\n const metrics = readJson<Record<string, number | string>>(join(dir, 'metrics.json')) ?? {}\n const status = config['status'] === 'succeeded' || config['status'] === 'failed' || config['status'] === 'cancelled'\n ? config['status'] as StandaloneRunSummary['status']\n : 'running'\n runs.push({\n id: entry,\n script: typeof config['script'] === 'string' ? config['script'] : '',\n status,\n startedAt: typeof config['startedAt'] === 'number' ? config['startedAt'] : 0,\n durationMs: typeof config['durationMs'] === 'number' ? config['durationMs'] : 0,\n exitCode: typeof config['exitCode'] === 'number' ? config['exitCode'] : null,\n metrics,\n artifacts: artifactsOf(dir),\n ...(typeof config['python'] === 'string' ? { python: config['python'] } : {}),\n })\n }\n return runs.sort((left, right) => right.startedAt - left.startedAt)\n}\n\n/** experiments/code \u4E0B\u7684\u811A\u672C\uFF08\u4E0E core \u7684 experiment runner \u540C\u4E00\u76EE\u5F55\u7EA6\u5B9A\uFF09\u3002 */\nexport function listStandaloneScripts(projectPath: string): StandaloneScript[] {\n const root = join(projectPath, 'experiments', 'code')\n if (!existsSync(root)) return []\n let entries: string[] = []\n try { entries = readdirSync(root) } catch { return [] }\n const scripts: StandaloneScript[] = []\n for (const entry of entries) {\n const full = join(root, entry)\n let stat\n try { stat = statSync(full) } catch { continue }\n if (!stat.isFile() || !entry.endsWith('.py')) continue\n scripts.push({ relative: entry, title: entry, mtime: stat.mtimeMs })\n }\n return scripts.sort((left, right) => right.mtime - left.mtime)\n}\n\nfunction statusOf(paper: PaperMeta | null, analysis: PaperAnalysis | null, designRevision: number, runs: StandaloneRunSummary[]): StandaloneStatus {\n if (runs.some(run => run.status === 'running')) return 'running'\n if (runs.some(run => run.status === 'succeeded')) return 'ran'\n if (runs.some(run => run.status === 'failed')) return 'failed'\n if (designRevision > 0) return 'designed'\n if (analysis !== null) return 'analyzed'\n if (paper !== null) return 'paper'\n return 'empty'\n}\n\n/** \u8BFB\u4E00\u4E2A\u5DE5\u574A\u7684\u5B8C\u6574\u89C6\u56FE\uFF08\u4E0D\u542B\u8FD0\u884C\u4E2D\u7684\u5B9E\u65F6\u53E0\u52A0\uFF09\u3002 */\nexport function readStandaloneExperiment(projectPath: string, options: { home?: string } = {}): StandaloneDetail {\n const directory = resolve(projectPath)\n const manifest = readManifest(directory)\n if (manifest === null) throw new KsrwError('NOT_FOUND', '\u4E0D\u662F KSRW \u5B9E\u9A8C\u5DE5\u574A\uFF08\u7F3A standalone.json / project.json\uFF09\uFF1A' + directory)\n const store = new ProjectStore(options.home === undefined ? {} : { home: options.home })\n const project = store.readProject(directory)\n const designArtifact = store.readArtifact(directory, 'experiment')\n const design: StandaloneDesign = {\n revision: designArtifact?.revision ?? 0,\n updatedAt: designArtifact?.updatedAt ?? project.updatedAt,\n content: designArtifact?.content ?? '',\n }\n const analysis = readJson<PaperAnalysis>(join(paperDir(directory), 'analysis.json'))\n const paperText = (() => {\n const text = readText(join(paperDir(directory), 'paper.md')) ?? ''\n return text.length > MAX_PAPER_TEXT ? text.slice(0, MAX_PAPER_TEXT) + '\\n\u2026\uFF08\u5168\u6587\u5DF2\u622A\u65AD\uFF09' : text\n })()\n const runs = listStandaloneRuns(directory)\n const latestActivity = Math.max(\n project.updatedAt,\n design.updatedAt,\n manifest.paper?.uploadedAt ?? 0,\n analysis?.analyzedAt ?? 0,\n runs[0]?.startedAt ?? 0,\n )\n const paper = manifest.paper\n const summary: StandaloneSummary = {\n id: manifest.id,\n name: manifest.name,\n path: directory,\n createdAt: manifest.createdAt,\n updatedAt: latestActivity,\n status: statusOf(paper, analysis, design.revision, runs),\n paperName: paper?.fileName ?? '',\n pages: paper?.pages ?? 0,\n chars: paper?.chars ?? 0,\n runCount: runs.length,\n designRevision: design.revision,\n }\n return {\n experiment: {\n ...summary,\n paper,\n analysis,\n designRevision: design.revision,\n projectStage: project.stage,\n },\n design,\n paperText,\n scripts: listStandaloneScripts(directory),\n runs,\n report: readText(join(directory, 'experiments', 'report.md')) ?? '',\n }\n}\n\n/** \u5DE5\u574A\u5217\u8868\uFF08\u6700\u8FD1\u6D3B\u8DC3\u4F18\u5148\uFF09\u3002 */\nexport function listStandaloneExperiments(options: { home?: string } = {}): StandaloneSummary[] {\n const root = resolveStandaloneRoot(options.home ?? resolveKsrwHome())\n if (!existsSync(root)) return []\n let entries: string[] = []\n try { entries = readdirSync(root) } catch { return [] }\n const items: StandaloneSummary[] = []\n for (const entry of entries) {\n if (entry.startsWith('.')) continue\n const directory = join(root, entry)\n try {\n if (!statSync(directory).isDirectory()) continue\n items.push(readStandaloneExperiment(directory, options).experiment)\n } catch { /* \u4E0D\u662F\u5DE5\u574A\u76EE\u5F55\u6216\u5DF2\u635F\u574F\uFF1A\u8DF3\u8FC7 */ }\n }\n return items.sort((left, right) => right.updatedAt - left.updatedAt)\n}\n\n/** \u65B0\u5EFA\u5DE5\u574A\uFF1Adetached \u9879\u76EE + paper \u76EE\u5F55 + \u6E05\u5355\u3002 */\nexport function createStandaloneExperiment(input: { name?: string; idea?: string; home?: string; now?: number } = {}): StandaloneDetail {\n const home = resolve(input.home ?? resolveKsrwHome())\n const root = resolveStandaloneRoot(home)\n mkdirSync(root, { recursive: true, mode: 0o700 })\n const at = input.now ?? Date.now()\n const name = (input.name ?? '').trim() === '' ? defaultName(at) : (input.name ?? '').trim()\n const id = 'exp-' + stamp(at) + '-' + shortId()\n const directory = join(root, id)\n const store = new ProjectStore({ home })\n const project = store.create({\n name,\n directory,\n detached: true,\n idea: input.idea ?? '\u514D\u9879\u76EE\u5B9E\u9A8C\u5DE5\u574A\uFF1A\u4E0A\u4F20\u8BBA\u6587\u9644\u4EF6\u540E\u8865\u5168\u5B9E\u9A8C\u8BBE\u8BA1\u5E76\u5B8C\u6210\u5B9E\u9A8C\u3002',\n })\n store.setStage(project.path, 'experiment')\n mkdirSync(paperDir(directory), { recursive: true, mode: 0o700 })\n writeManifest(directory, { schemaVersion: 1, id, name, createdAt: at, paper: null })\n return readStandaloneExperiment(directory, { home })\n}\n\n/** \u5220\u9664\u5DE5\u574A\u76EE\u5F55\uFF08\u53EA\u5141\u8BB8\u5220 standalone \u6839\u4E0B\u7684\u76EE\u5F55\uFF09+ \u6E05\u6389\u53EF\u80FD\u7684\u9879\u76EE\u5E93\u767B\u8BB0\u3002 */\nexport function deleteStandaloneExperiment(projectPath: string, options: { home?: string } = {}): boolean {\n const home = resolve(options.home ?? resolveKsrwHome())\n const root = resolveStandaloneRoot(home)\n const target = resolve(projectPath)\n if (target === root || !target.startsWith(root + sep)) {\n throw new KsrwError('PATH_DENIED', '\u53EA\u80FD\u5220\u9664 standalone \u5DE5\u574A\u76EE\u5F55\uFF1A' + target)\n }\n if (!existsSync(target)) throw new KsrwError('NOT_FOUND', '\u5DE5\u574A\u4E0D\u5B58\u5728\uFF1A' + target)\n rmSync(target, { recursive: true, force: true })\n forgetProject(home, target)\n return true\n}\n\n/** \u628A\u5DE5\u574A\u63A5\u6536\u4E3A\u6B63\u5F0F\u9879\u76EE\uFF08\u6E05\u6389 detached\uFF0C\u8FDB\u9879\u76EE\u5E93\uFF09\u3002 */\nexport function registerStandaloneExperiment(projectPath: string, options: { home?: string } = {}): StandaloneDetail {\n const store = new ProjectStore(options.home === undefined ? {} : { home: options.home })\n store.register(projectPath)\n return readStandaloneExperiment(projectPath, options)\n}\n\n\n/** \u4E0A\u4F20/\u66FF\u6362\u8BBA\u6587\u9644\u4EF6\uFF1A\u843D\u76D8\u539F\u6587 + \u89C4\u8303\u5316\u5168\u6587 + \u91CD\u65B0\u62BD\u53D6\u3002 */\nexport async function ingestStandalonePaper(\n projectPath: string,\n input: { fileName: string; bytes: Uint8Array; home?: string },\n): Promise<StandaloneDetail> {\n const directory = resolve(projectPath)\n const manifest = readManifest(directory)\n if (manifest === null) throw new KsrwError('NOT_FOUND', '\u4E0D\u662F KSRW \u5B9E\u9A8C\u5DE5\u574A\uFF1A' + directory)\n const fileName = safeAttachmentName(input.fileName)\n const kind = attachmentKindOf(fileName)\n if (kind === null) {\n const extension = fileName.slice(fileName.lastIndexOf('.'))\n throw new KsrwError('INVALID', '\u53EA\u652F\u6301 PDF / DOCX / MD / TXT / TeX \u8BBA\u6587\u9644\u4EF6\uFF0C\u6536\u5230\uFF1A' + (extension === fileName ? '\u65E0\u6269\u5C55\u540D' : extension))\n }\n const bytes = Buffer.from(input.bytes)\n if (bytes.byteLength === 0) throw new KsrwError('INVALID', '\u9644\u4EF6\u4E3A\u7A7A')\n if (bytes.byteLength > MAX_PAPER_BYTES) throw new KsrwError('INVALID', '\u9644\u4EF6\u8D85\u8FC7 48MiB \u4E0A\u9650')\n\n const dir = paperDir(directory)\n mkdirSync(dir, { recursive: true, mode: 0o700 })\n // P2-18\uFF1A\u5148\u89E3\u6790\u3001\u540E\u66FF\u6362\u3002\u89E3\u6790\u5931\u8D25\u65F6\u65E7\u9644\u4EF6\u4E0E\u65E7\u6B63\u6587\u5FC5\u987B\u539F\u6837\u4FDD\u7559\u3002\n const raw = await extractAttachment(kind, new Uint8Array(bytes))\n const title = detectTitle(raw.pages)\n const normalized = normalizeWithPageMarkers(title, raw)\n const analysis = analyzePaperText(title, normalized.pages, raw.warnings)\n\n for (const name of readdirSync(dir)) {\n if (name.startsWith('attachment.')) rmSync(join(dir, name), { force: true })\n }\n const extension = fileName.slice(fileName.lastIndexOf('.'))\n writeFileSync(join(dir, 'attachment' + extension), bytes, { mode: 0o600 })\n writeFileSync(join(dir, 'paper.md'), normalized.text, { encoding: 'utf8', mode: 0o600 })\n writeFileSync(join(dir, 'analysis.json'), JSON.stringify(analysis, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n\n const meta: PaperMeta = {\n fileName,\n kind,\n bytes: bytes.byteLength,\n uploadedAt: Date.now(),\n pages: normalized.pages.length,\n chars: normalized.text.length,\n title: analysis.title,\n }\n // \u9644\u4EF6\u62BD\u53D6\u662F async\uFF08\u53EF\u80FD\u8981\u597D\u51E0\u79D2\uFF09\uFF1A\u671F\u95F4\u53E6\u4E00\u4E2A\u7A97\u53E3\u53EF\u80FD\u6539\u8FC7\u6E05\u5355\uFF0C\u843D\u76D8\u524D\u91CD\u8BFB\uFF0C\u907F\u514D\u8986\u76D6\u5B83\u3002\n const latest = readManifest(directory) ?? manifest\n writeManifest(directory, { ...latest, paper: meta })\n try {\n const store = new ProjectStore(input.home === undefined ? {} : { home: input.home })\n store.touch(store.readProject(directory))\n } catch { /* updatedAt \u53EA\u662F\u5217\u8868\u6392\u5E8F\u7528 */ }\n return readStandaloneExperiment(directory, input.home === undefined ? {} : { home: input.home })\n}\n\n/** \u91CD\u65B0\u62BD\u53D6\uFF08\u8BBA\u6587\u6B63\u6587/\u6392\u7248\u66F4\u65B0\u540E\u624B\u52A8\u89E6\u53D1\uFF09\u3002 */\nexport function analyzeStandalonePaper(projectPath: string, options: { home?: string } = {}): StandaloneDetail {\n const directory = resolve(projectPath)\n return withProjectLock(directory, () => {\n const manifest = readManifest(directory)\n if (manifest === null) throw new KsrwError('NOT_FOUND', '\u4E0D\u662F KSRW \u5B9E\u9A8C\u5DE5\u574A\uFF1A' + directory)\n const text = readText(join(paperDir(directory), 'paper.md'))\n if (text === null) throw new KsrwError('NOT_FOUND', '\u8FD8\u6CA1\u6709\u8BBA\u6587\u5168\u6587\uFF1A\u8BF7\u5148\u4E0A\u4F20\u8BBA\u6587\u9644\u4EF6')\n const title = detectTitle([{ index: 1, text }])\n const pages = splitNormalizedPages(text)\n const analysis = analyzePaperText(title, pages, [])\n writeFileSync(join(paperDir(directory), 'analysis.json'), JSON.stringify(analysis, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n if (manifest.paper !== null) {\n writeManifest(directory, {\n ...manifest,\n paper: { ...manifest.paper, pages: pages.length, chars: text.length, title: analysis.title },\n })\n }\n return readStandaloneExperiment(directory, options)\n })\n}\n\n// \u2500\u2500 \u5B9E\u9A8C\u8981\u7D20\u62BD\u53D6 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst DATASET_LEXICON = [\n 'ImageNet', 'CIFAR-10', 'CIFAR-100', 'MNIST', 'Fashion-MNIST', 'SQuAD', 'GLUE', 'SuperGLUE', 'MS MARCO',\n 'CoNLL', 'WMT', 'IWSLT', 'LibriSpeech', 'Common Voice', 'COCO', 'ADE20K', 'Cityscapes', 'KITTI', 'VisDrone',\n 'Pascal VOC', 'Kinetics', 'UCF101', 'HMDB51', 'MovieLens', 'Amazon Reviews', 'Yelp', 'IMDB', 'WikiText',\n 'Penn Treebank', 'The Pile', 'C4', 'MMLU', 'GSM8K', 'HumanEval', 'MATH', 'Alpaca', 'OpenWebText', 'BookCorpus',\n 'LAION', 'CelebA', 'FFHQ', 'ShapeNet', 'ModelNet', 'nuScenes', 'Waymo', 'MIMIC', 'PhysioNet',\n]\nconst METRIC_LEXICON = [\n 'Accuracy', 'Top-1', 'Top-5', 'Precision', 'Recall', 'F1', 'macro-F1', 'micro-F1', 'AUC', 'AUROC', 'mAP',\n 'IoU', 'mIoU', 'PSNR', 'SSIM', 'BLEU', 'ROUGE', 'METEOR', 'CIDEr', 'WER', 'CER', 'Perplexity', 'RMSE', 'MAE',\n 'MSE', 'Exact Match', 'pass@1', 'latency', 'throughput', 'FLOPs',\n]\nconst ELEMENT_LABELS: Record<ElementKey, string> = {\n task: '\u7814\u7A76\u4EFB\u52A1', method: '\u65B9\u6CD5', dataset: '\u6570\u636E\u96C6', metric: '\u6307\u6807', baseline: '\u57FA\u7EBF/\u5BF9\u7167', limitation: '\u5C40\u9650',\n}\nconst ELEMENT_HINTS: Record<ElementKey, string> = {\n task: '\u8BBA\u6587\u6CA1\u6709\u5199\u6E05\u8981\u9A8C\u8BC1\u7684\u4EFB\u52A1\uFF1A\u5728\u8BBE\u8BA1\u91CC\u7528\u4E00\u53E5\u8BDD\u5199\u6B7B\u8F93\u5165/\u8F93\u51FA\u3002',\n method: '\u6CA1\u6709\u627E\u5230\u660E\u786E\u7684\u65B9\u6CD5\u63CF\u8FF0\uFF1A\u4ECE\u6458\u8981/\u65B9\u6CD5\u7AE0\u8282\u8865\u4E00\u6BB5\uFF0C\u518D\u62C6\u6210\u53EF\u5B9E\u73B0\u7684\u6B65\u9AA4\u3002',\n dataset: '\u8BBA\u6587\u6CA1\u70B9\u540D\u6570\u636E\u96C6\uFF1A\u8BBE\u8BA1\u91CC\u5FC5\u987B\u5199\u6E05\u6570\u636E\u6765\u6E90\u3001\u8BB8\u53EF\u3001\u5207\u5206\u4E0E\u7248\u672C\u3002',\n metric: '\u6CA1\u627E\u5230\u8BC4\u4EF7\u6307\u6807\uFF1A\u8BBE\u8BA1\u91CC\u8981\u5B9A\u4E49\u6307\u6807\u4E0E\u8BA1\u7B97\u53E3\u5F84\uFF08\u811A\u672C\u7528 KSRW_METRIC \u6253\u5370\uFF09\u3002',\n baseline: '\u6CA1\u6709\u57FA\u7EBF\uFF1A\u81F3\u5C11\u8865\u4E00\u4E2A\u6700\u7B80\u5355\u57FA\u7EBF\uFF08\u968F\u673A/\u5747\u503C/\u7EBF\u6027/\u5B98\u65B9 baseline\uFF09\uFF0C\u5426\u5219\u6536\u76CA\u65E0\u6CD5\u5224\u65AD\u3002',\n limitation: '\u6CA1\u6709\u8BA8\u8BBA\u5C40\u9650\uFF1A\u8865\u4E00\u6761\u300C\u672C\u5B9E\u9A8C\u56DE\u7B54\u4E0D\u4E86\u4EC0\u4E48\u300D\uFF0C\u907F\u514D\u8FC7\u5EA6\u5916\u63A8\u3002',\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\nfunction sentencesOf(text: string): string[] {\n return text\n .replace(/\\s+/g, ' ')\n .split(/(?<=[\u3002\uFF01\uFF1F\uFF1B;])\\s*|(?<=[.!?])\\s+(?=[A-Z0-9(\\[])/)\n .map(sentence => sentence.trim())\n .filter(sentence => sentence.length >= 10 && sentence.length <= 800)\n}\n\nfunction uniqItems(items: ElementItem[], limit: number): ElementItem[] {\n const seen = new Set<string>()\n const out: ElementItem[] = []\n for (const item of items) {\n const key = item.text.replace(/\\s+/g, ' ').slice(0, 160).toLowerCase()\n if (seen.has(key)) continue\n seen.add(key)\n out.push(item)\n if (out.length >= limit) break\n }\n return out\n}\n\nfunction pageSentences(pages: { index: number; text: string }[]): { text: string; source: string }[] {\n const out: { text: string; source: string }[] = []\n for (const page of pages) {\n const source = pages.length > 1 ? '\u7B2C ' + String(page.index) + ' \u9875' : '\u6B63\u6587'\n for (const sentence of sentencesOf(page.text)) out.push({ text: sentence, source })\n }\n return out\n}\n\nfunction extractSections(pages: { index: number; text: string }[]): { heading: string; chars: number }[] {\n const sections: { heading: string; chars: number }[] = []\n for (const page of pages) {\n const lines = page.text.split('\\n')\n let current: { heading: string; chars: number } | undefined\n for (const rawLine of lines) {\n const line = rawLine.replace(/^#+\\s*/, '').trim()\n if (line === '') continue\n if (/^\u7B2C\\s*\\d+\\s*\u9875$/.test(line)) continue\n if (looksLikeByline(line)) continue\n const numbered = /^(?:(?:\\d+(?:\\.\\d+)*)\\.?\\s+)?([A-Z\\u4e00-\\u9fff][^\u3002\uFF01\uFF1F,\uFF0C;\uFF1B]{1,38})$/.exec(line)\n const known = /^(abstract|\u6458\u8981|introduction|\u5F15\u8A00|related work|\u76F8\u5173\u5DE5\u4F5C|method|\u65B9\u6CD5|approach|experiment|\u5B9E\u9A8C|results?|\u7ED3\u679C|discussion|\u8BA8\u8BBA|conclusion|\u7ED3\u8BBA|references?|\u53C2\u8003\u6587\u732E|limitations?|\u5C40\u9650)/i.test(line)\n const looksHeading = line.length <= 42 && (known || (numbered !== null && !/[.\u3002]$/.test(line)))\n if (looksHeading) {\n current = { heading: line, chars: 0 }\n sections.push(current)\n continue\n }\n if (current !== undefined) current.chars += line.length\n }\n }\n return sections.slice(0, 40)\n}\n\nfunction extractAbstract(pages: { index: number; text: string }[]): string {\n const full = pages.map(page => page.text).join('\\n')\n // \u6458\u8981\u6807\u9898\uFF1A\u666E\u901A\u884C\u3001Markdown \u6807\u9898\uFF08#/##\uFF09\u3001\u5E26\u5192\u53F7/\u7A7A\u683C\u90FD\u63A5\u53D7\u3002\n const startRe = /(?:^|\\n)[ \\t]*#{0,6}[ \\t]*(?:abstract|\u6458\u8981)[ \\t]*[:\uFF1A]?[ \\t]*(?:\\n|$)/i\n const start = startRe.exec(full)\n if (start === null) return ''\n const rest = full.slice(start.index + start[0].length)\n const endRe = /(?:^|\\n)[ \\t]*(?:#{1,6}[ \\t]*)?(?:\\d+(?:\\.\\d+)*\\.?[ \\t]+)?(?:introduction|\u5F15\u8A00|\u4E00\u3001\u5F15\u8A00|keywords?|\u5173\u952E\u8BCD|related work|\u76F8\u5173\u5DE5\u4F5C|background|\u80CC\u666F|## \u7B2C\\s*\\d+\\s*\u9875)[ \\t]*[:\uFF1A]?[ \\t]*(?:\\n|$)/i\n const end = endRe.exec(rest)\n const body = end === null ? rest.slice(0, 2400) : rest.slice(0, end.index)\n return cleanText(body).slice(0, 2400)\n}\n\nfunction extractKeywords(pages: { index: number; text: string }[]): string[] {\n const full = pages.map(page => page.text).join('\\n')\n const match = /(?:keywords?|\u5173\u952E\u8BCD)\\s*[:\uFF1A]\\s*([^\\n]{2,240})/i.exec(full)\n if (match === null) return []\n return (match[1] ?? '')\n .split(/[,;\uFF0C\uFF1B\u3001]/)\n .map(item => item.trim())\n .filter(item => item.length >= 2 && item.length <= 40)\n .slice(0, 12)\n}\n\nfunction lexiconItems(sentences: { text: string; source: string }[], lexicon: string[], limit: number): ElementItem[] {\n const items: ElementItem[] = []\n for (const entry of sentences) {\n const hit = lexicon.find(term => new RegExp('(^|[^A-Za-z0-9])' + escapeRegExp(term) + '([^A-Za-z0-9]|$)', 'i').test(entry.text))\n if (hit === undefined) continue\n items.push({ text: entry.text, source: entry.source })\n }\n return uniqItems(items, limit)\n}\n\n/** \u4ECE\u8BBA\u6587\u5168\u6587\u62BD\u53D6\u5B9E\u9A8C\u8981\u7D20\uFF08\u786E\u5B9A\u6027\u3001\u53EF\u89E3\u91CA\uFF1B\u4E0D\u505A\u7ED3\u8BBA\u63A8\u65AD\uFF09\u3002 */\nexport function analyzePaperText(title: string, pages: { index: number; text: string }[], warnings: string[] = []): PaperAnalysis {\n const sentences = pageSentences(pages)\n const abstract = extractAbstract(pages)\n const keywords = extractKeywords(pages)\n const detectedTitle = title !== '' ? title : (pages[0]?.text.split('\\n').map(line => line.trim()).find(line => line !== '') ?? '')\n\n const taskItems: ElementItem[] = abstract === ''\n ? sentences.slice(0, 2).map(item => ({ text: item.text, source: item.source }))\n : sentencesOf(abstract).slice(0, 2).map(text => ({ text, source: '\u6458\u8981' }))\n\n const methodItems = uniqItems(sentences.filter(item =>\n /(we propose|we present|we introduce|we develop|our (?:method|approach|framework|model)|\u672C\u6587\u63D0\u51FA|\u6211\u4EEC\u63D0\u51FA|\u6240\u63D0\u65B9\u6CD5|\u672C\u6587\u65B9\u6CD5|\u8BE5\u65B9\u6CD5)/i.test(item.text),\n ), 6)\n\n const datasetItems = (() => {\n const items = lexiconItems(sentences, DATASET_LEXICON, 10)\n const explicit: ElementItem[] = []\n const re = /(?:\u6570\u636E\u96C6|dataset[s]?)\\s*[:\uFF1A]\\s*([^\\n\u3002.;]{2,120})/gi\n const full = pages.map(page => page.text).join('\\n')\n let match: RegExpExecArray | null\n while ((match = re.exec(full)) !== null) {\n const value = (match[1] ?? '').trim()\n if (value !== '') explicit.push({ text: value, source: '\u539F\u6587' })\n }\n return uniqItems([...explicit, ...items], 10)\n })()\n\n const metricItems = (() => {\n const items = lexiconItems(sentences, METRIC_LEXICON, 10)\n const chinese = uniqItems(sentences.filter(item =>\n /(\u51C6\u786E\u7387|\u7CBE\u786E\u7387|\u53EC\u56DE\u7387|F1|AUC|mAP|IoU|RMSE|MAE|BLEU|ROUGE|\u56F0\u60D1\u5EA6|\u541E\u5410|\u5EF6\u8FDF|\u8BEF\u5DEE)/.test(item.text),\n ).map(item => ({ text: item.text, source: item.source })), 10)\n return uniqItems([...items, ...chinese], 10)\n })()\n\n const baselineItems = uniqItems(sentences.filter(item =>\n /(baseline|compare (?:with|to|against)|comparison|\u5BF9\u7167|\u57FA\u7EBF|\u5BF9\u6BD4\u65B9\u6CD5|\u76F8\u6BD4)/i.test(item.text),\n ), 6)\n\n const limitationItems = uniqItems(sentences.filter(item =>\n /(limitation|\u5C40\u9650|\u4E0D\u8DB3|future work|\u672A\u6765\u5DE5\u4F5C|\u5C1A\u672A|\u5C1A\u4E0D)/i.test(item.text),\n ), 5)\n\n const elements: ExperimentElement[] = [\n { key: 'task', label: ELEMENT_LABELS.task, items: taskItems },\n { key: 'method', label: ELEMENT_LABELS.method, items: methodItems },\n { key: 'dataset', label: ELEMENT_LABELS.dataset, items: datasetItems },\n { key: 'metric', label: ELEMENT_LABELS.metric, items: metricItems },\n { key: 'baseline', label: ELEMENT_LABELS.baseline, items: baselineItems },\n { key: 'limitation', label: ELEMENT_LABELS.limitation, items: limitationItems },\n ]\n const missing = elements\n .filter(element => element.items.length === 0)\n .map(element => ({ key: element.key, label: element.label, hint: ELEMENT_HINTS[element.key] }))\n\n const merged = [...warnings]\n if (abstract === '') merged.push('\u6CA1\u627E\u5230\u6458\u8981\uFF08Abstract/\u6458\u8981\uFF09\uFF1A\u62BD\u53D6\u8D28\u91CF\u4F1A\u4E0B\u964D\uFF0C\u5EFA\u8BAE\u4EBA\u5DE5\u8865\u7814\u7A76\u95EE\u9898\u3002')\n if (detectedTitle === '') merged.push('\u6CA1\u8BC6\u522B\u51FA\u6807\u9898\uFF1A\u7B2C\u4E00\u884C\u53EF\u80FD\u662F\u9875\u7709\u6216\u671F\u520A\u4FE1\u606F\u3002')\n const totalChars = pages.map(page => page.text.length).reduce((sum, size) => sum + size, 0)\n if (pages.length > 1 && totalChars < 3000) merged.push('\u5168\u6587\u5F88\u77ED\uFF1A\u53EF\u80FD\u662F\u6458\u8981\u9875\u6216\u62BD\u53D6\u5931\u8D25\uFF0C\u8BF7\u6838\u5BF9 paper.md\u3002')\n\n return {\n analyzedAt: Date.now(),\n title: detectedTitle,\n abstract,\n keywords,\n sections: extractSections(pages),\n elements,\n missing,\n warnings: merged,\n }\n}\n\n// \u2500\u2500 \u8BBE\u8BA1\u9AA8\u67B6 / \u811A\u672C / \u62A5\u544A \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction itemsOrPlaceholder(element: ExperimentElement | undefined, placeholder: string): string[] {\n if (element === undefined || element.items.length === 0) return ['- ' + placeholder]\n return element.items.slice(0, 5).map(item => '- ' + item.text.replace(/\\s+/g, ' ') + '\uFF08' + item.source + '\uFF09')\n}\n\n/** \u786E\u5B9A\u6027\u8BBE\u8BA1\u9AA8\u67B6\uFF1A\u6CA1\u6709 Agent \u4E5F\u80FD\u8865\u51FA\u4E00\u4EFD\u53EF\u6267\u884C\u7684\u8BBE\u8BA1\u521D\u7A3F\u3002 */\nexport function renderDesignScaffold(analysis: PaperAnalysis | null, name: string): string {\n const element = (key: ElementKey): ExperimentElement | undefined => analysis?.elements.find(item => item.key === key)\n const dataset = element('dataset')\n const metric = element('metric')\n const baseline = element('baseline')\n const method = element('method')\n const limitation = element('limitation')\n const title = analysis?.title === undefined || analysis.title === '' ? name : analysis.title\n const abstract = analysis?.abstract ?? ''\n const firstSentence = abstract.split(/(?<=[\u3002\uFF01\uFF1F])\\s|(?<=[.])\\s/)[0] ?? abstract.slice(0, 200)\n const lines: string[] = [\n '# \u5B9E\u9A8C\u8BBE\u8BA1 \u00B7 ' + title,\n '',\n '> \u7531 KSRW \u4ECE\u8BBA\u6587\u9644\u4EF6\u81EA\u52A8\u62BD\u53D6\u9AA8\u67B6\uFF08\u786E\u5B9A\u6027\uFF09\uFF1B\u6807\u300C\u5F85\u8865\u300D\u7684\u6761\u76EE\u5FC5\u987B\u5728\u8FD0\u884C\u4E3B\u5B9E\u9A8C\u524D\u843D\u5B9E\u3002',\n '> \u534F\u8BAE\uFF1A\u5148\u57FA\u7EBF\u540E\u4E3B\u5B9E\u9A8C\uFF1B\u6BCF\u4E2A run \u8BB0\u5F55\u73AF\u5883\u3001\u79CD\u5B50\u3001\u6307\u6807\u4E0E\u4EA7\u7269\uFF1B\u5931\u8D25\u4E5F\u8981\u843D\u76D8\u3002',\n '',\n '## 1. \u7814\u7A76\u95EE\u9898\u4E0E\u5047\u8BBE',\n '',\n '### \u8BBA\u6587\u8981\u56DE\u7B54\u7684\u95EE\u9898',\n abstract === '' ? '- \u5F85\u8865\uFF1A\u7528\u4E00\u53E5\u8BDD\u5199\u6E05\u8BBA\u6587\u7684\u7814\u7A76\u95EE\u9898\u3002' : '- \u6309\u6458\u8981\u590D\u8FF0\uFF1A' + firstSentence,\n '',\n '### \u5F85\u9A8C\u8BC1\u5047\u8BBE\uFF08\u672C\u5B9E\u9A8C\u8981\u8865\u7684\uFF09',\n '- H1\uFF08\u4E3B\u5047\u8BBE\uFF09\uFF1A\u5728\u3010\u6570\u636E\u96C6\u3011\u4E0A\uFF0C\u3010\u65B9\u6CD5\u3011\u76F8\u5BF9\u3010\u57FA\u7EBF\u3011\u5728\u3010\u6307\u6807\u3011\u4E0A\u63D0\u5347\uFF0C\u4E14\u5DEE\u503C\u5927\u4E8E\u968F\u673A\u6CE2\u52A8\u3002',\n '- H2\uFF08\u6D88\u878D\uFF09\uFF1A\u53BB\u6389\u3010\u5173\u952E\u7EC4\u4EF6\u3011\u540E\u6307\u6807\u4E0B\u964D\uFF0C\u8BF4\u660E\u8BE5\u7EC4\u4EF6\u6709\u6548\u3002',\n '- H3\uFF08\u7A33\u5065\u6027\uFF09\uFF1A\u81F3\u5C11 3 \u4E2A\u968F\u673A\u79CD\u5B50\u4E0B\u7ED3\u8BBA\u65B9\u5411\u4E00\u81F4\u3002',\n '',\n '## 2. \u6570\u636E',\n '',\n ...itemsOrPlaceholder(dataset, '\u5F85\u8865\uFF1A\u8BBA\u6587\u6CA1\u6709\u70B9\u540D\u6570\u636E\u96C6\uFF0C\u5FC5\u987B\u5199\u6E05\u6765\u6E90/\u8BB8\u53EF/\u5207\u5206/\u7248\u672C/\u9884\u5904\u7406\u3002'),\n '- \u5207\u5206\uFF1A\u8BAD\u7EC3/\u9A8C\u8BC1/\u6D4B\u8BD5\uFF0C\u6D4B\u8BD5\u96C6\u53EA\u7528\u4E00\u6B21',\n '- \u7248\u672C\u4E0E\u6821\u9A8C\uFF1Asha256 \u8BB0\u5F55\u5230 experiments/runs/<runId>/config.json',\n '',\n '## 3. \u57FA\u7EBF',\n '',\n ...itemsOrPlaceholder(baseline, '\u5F85\u8865\uFF1A\u81F3\u5C11\u4E00\u4E2A\u6700\u7B80\u5355\u57FA\u7EBF\uFF08\u968F\u673A/\u5747\u503C/\u7EBF\u6027/\u5B98\u65B9 baseline\uFF09\u3002'),\n '- \u5BF9\u7167\u539F\u5219\uFF1A\u540C\u4E00\u6570\u636E\u5207\u5206\u3001\u540C\u4E00\u8BC4\u4F30\u811A\u672C\u3001\u540C\u4E00\u786C\u4EF6',\n '',\n '## 4. \u6307\u6807\u4E0E\u53E3\u5F84',\n '',\n ...itemsOrPlaceholder(metric, '\u5F85\u8865\uFF1A\u5B9A\u4E49\u4E3B\u6307\u6807\u4E0E\u8BA1\u7B97\u53E3\u5F84\uFF08\u811A\u672C\u7528 KSRW_METRIC key=value \u6253\u5370\uFF09\u3002'),\n '- \u62A5\u544A\u53E3\u5F84\uFF1A\u5747\u503C \u00B1 \u6807\u51C6\u5DEE\uFF083 \u4E2A\u79CD\u5B50\uFF09\uFF0C\u540C\u65F6\u4FDD\u7559\u6700\u5DEE\u79CD\u5B50',\n '',\n '## 5. \u53D8\u91CF\u4E0E\u6D88\u878D',\n '',\n ...itemsOrPlaceholder(method, '\u5F85\u8865\uFF1A\u628A\u65B9\u6CD5\u62C6\u6210\u53EF\u5F00\u5173\u7684\u7EC4\u4EF6\uFF0C\u9010\u4E2A\u6D88\u878D\u3002'),\n '- \u4E00\u6B21\u53EA\u6539\u4E00\u4E2A\u53D8\u91CF\uFF1B\u8D85\u53C2\u641C\u7D22\u4E0E\u6700\u7EC8\u8BC4\u4F30\u5206\u5F00',\n '',\n '## 6. \u8FD0\u884C\u8BA1\u5212',\n '',\n '- \u811A\u672C\uFF1Aexperiments/code/\uFF08\u7531\u5B9E\u9A8C\u5DE5\u4F4D Agent \u751F\u6210\uFF1B\u6253\u5370 KSRW_METRIC name=value\uFF09',\n '- \u73AF\u5883\uFF1APython 3.12 + \u4F9D\u8D56\u6E05\u5355\uFF08\u7248\u672C\u5199\u8FDB\u8BBE\u8BA1\uFF09',\n '- \u79CD\u5B50\uFF1A0 / 1 / 2 \u4E09\u6B21\u91CD\u590D',\n '- \u9884\u7B97\uFF1A\u3010\u5F85\u8865\uFF1A\u5355\u6B21\u9884\u8BA1\u65F6\u957F \u00D7 \u6B21\u6570 \u00D7 \u8BBE\u5907\u3011',\n '- \u8FD0\u884C\u4F4D\u7F6E\uFF1A\u672C\u5730 Python \u8FD0\u884C\u65F6 / \u8FDC\u7A0B GPU\uFF08\u4E8C\u9009\u4E00\uFF0C\u5199\u6E05\u76EE\u6807\uFF09',\n '',\n '## 7. \u505C\u6B62\u6761\u4EF6\u4E0E\u5224\u5B9A',\n '',\n '- \u9884\u7B97\u7528\u5C3D\u6216 3 \u4E2A\u79CD\u5B50\u8DD1\u5B8C\u5373\u505C\uFF1B\u4E2D\u9014\u5931\u8D25\u4FDD\u7559\u65E5\u5FD7',\n '- \u5224\u5B9A\uFF1A\u4E3B\u6307\u6807\u63D0\u5347\u4E14\u6D88\u878D\u65B9\u5411\u4E00\u81F4 \u2192 \u652F\u6301 H1/H2\uFF1B\u5426\u5219\u6309\u5931\u8D25\u5206\u6790\u5904\u7406\uFF0C\u4E0D\u6311\u6570\u636E',\n '',\n '## 8. \u98CE\u9669\u4E0E\u5C40\u9650',\n '',\n ...itemsOrPlaceholder(limitation, '\u5F85\u8865\uFF1A\u5199\u6E05\u672C\u5B9E\u9A8C\u56DE\u7B54\u4E0D\u4E86\u4EC0\u4E48\u3002'),\n '- \u6570\u636E/\u8BB8\u53EF\uFF1A\u4E0D\u53EF\u5F97\u7684\u516C\u5F00\u6570\u636E\u8981\u5199\u66FF\u4EE3\u65B9\u6848\u4E0E\u6210\u672C',\n '',\n ]\n return lines.join('\\n')\n}\n\n/** \u5199\u5165/\u8986\u76D6\u5B9E\u9A8C\u8BBE\u8BA1\uFF08\u7528\u6237\u76F4\u5199\u6743\u5A01\u6587\u4EF6\uFF1BAgent \u8D70\u63D0\u6848\uFF09\u3002 */\nexport function saveStandaloneDesign(projectPath: string, content: string, options: { home?: string } = {}): StandaloneDesign {\n if (content.trim() === '') throw new KsrwError('INVALID', '\u5B9E\u9A8C\u8BBE\u8BA1\u4E0D\u80FD\u4E3A\u7A7A')\n const store = new ProjectStore(options.home === undefined ? {} : { home: options.home })\n const view = store.writeArtifact(projectPath, 'experiment', content)\n return { revision: view.revision, updatedAt: view.updatedAt, content: view.content }\n}\n\n/** \u751F\u6210\u9AA8\u67B6\u5E76\u5199\u5165\u5B9E\u9A8C\u8BBE\u8BA1\uFF08\u9ED8\u8BA4\u53EA\u5728\u8BBE\u8BA1\u4E3A\u7A7A\u65F6\u5199\uFF0Coverwrite \u624D\u8986\u76D6\uFF09\u3002 */\nexport function writeStandaloneScaffold(projectPath: string, options: { overwrite?: boolean; home?: string } = {}): StandaloneDetail {\n const detail = readStandaloneExperiment(projectPath, options.home === undefined ? {} : { home: options.home })\n if (detail.experiment.analysis === null) throw new KsrwError('NOT_FOUND', '\u8FD8\u6CA1\u6709\u8BBA\u6587\u5206\u6790\u7ED3\u679C\uFF1A\u8BF7\u5148\u4E0A\u4F20\u8BBA\u6587\u9644\u4EF6\u5E76\u62BD\u53D6\u5B9E\u9A8C\u8981\u7D20')\n if (detail.design.revision > 0 && options.overwrite !== true) {\n throw new KsrwError('INVALID', '\u5B9E\u9A8C\u8BBE\u8BA1\u5DF2\u5B58\u5728\uFF08revision ' + detail.design.revision + '\uFF09\uFF1A\u786E\u8BA4\u8986\u76D6\u518D\u8C03\u7528')\n }\n const scaffold = renderDesignScaffold(detail.experiment.analysis, detail.experiment.name)\n mkdirSync(join(projectPath, 'experiments'), { recursive: true, mode: 0o700 })\n writeFileSync(join(projectPath, 'experiments', 'design-scaffold.md'), scaffold, { encoding: 'utf8', mode: 0o600 })\n saveStandaloneDesign(projectPath, scaffold, options.home === undefined ? {} : { home: options.home })\n return readStandaloneExperiment(projectPath, options.home === undefined ? {} : { home: options.home })\n}\n\n/** Agent \u552F\u4E00\u53EF\u5199\u811A\u672C\u5165\u53E3\uFF1A\u53EA\u5141\u8BB8 experiments/code/<name>.py\u3002 */\nexport function writeStandaloneScript(projectPath: string, name: string, content: string): StandaloneScript {\n const fileName = name.trim()\n if (!/^[A-Za-z0-9][A-Za-z0-9._-]*\\.py$/.test(fileName)) {\n throw new KsrwError('INVALID', '\u811A\u672C\u540D\u5FC5\u987B\u662F <name>.py\uFF08\u5B57\u6BCD/\u6570\u5B57/._-\uFF0C\u4E0D\u80FD\u5E26\u76EE\u5F55\uFF09\uFF1A' + name)\n }\n if (content.trim() === '') throw new KsrwError('INVALID', '\u811A\u672C\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A')\n if (Buffer.byteLength(content, 'utf8') > MAX_SCRIPT_BYTES) throw new KsrwError('INVALID', '\u811A\u672C\u8D85\u8FC7 256KiB \u4E0A\u9650')\n const dir = join(projectPath, 'experiments', 'code')\n mkdirSync(dir, { recursive: true, mode: 0o700 })\n const file = join(dir, fileName)\n writeFileSync(file, content, { encoding: 'utf8', mode: 0o600 })\n return { relative: fileName, title: fileName, mtime: statSync(file).mtimeMs }\n}\n\nexport interface ReportRun {\n id: string\n script: string\n status: string\n startedAt: number\n durationMs: number\n exitCode: number | null\n metrics: Record<string, number | string>\n artifacts?: string[]\n}\n\nfunction formatDuration(ms: number): string {\n if (ms <= 0) return '\u2014'\n if (ms < 1000) return String(ms) + ' ms'\n const seconds = Math.round(ms / 100) / 10\n if (seconds < 60) return String(seconds) + ' s'\n return String(Math.round(seconds / 60)) + ' min'\n}\n\n/** \u6309\u8BBE\u8BA1 + \u771F\u5B9E run \u751F\u6210\u62A5\u544A\uFF08\u6CA1\u6709\u6210\u529F run \u65F6\u660E\u786E\u5199\u300C\u4E0D\u5F97\u4E0B\u7ED3\u8BBA\u300D\uFF09\u3002 */\nexport function renderExperimentReport(input: { name: string; paperTitle?: string; design: string; runs: ReportRun[]; generatedAt?: number }): string {\n const generatedAt = input.generatedAt ?? Date.now()\n const succeeded = input.runs.filter(run => run.status === 'succeeded')\n const failed = input.runs.filter(run => run.status === 'failed' || run.status === 'cancelled')\n const running = input.runs.filter(run => run.status === 'running')\n const lines: string[] = [\n '# \u5B9E\u9A8C\u62A5\u544A \u00B7 ' + input.name,\n '',\n '- \u751F\u6210\u65F6\u95F4\uFF1A' + new Date(generatedAt).toISOString(),\n '- \u8BBA\u6587\uFF1A' + (input.paperTitle === undefined || input.paperTitle === '' ? '\uFF08\u672A\u4E0A\u4F20/\u672A\u8BC6\u522B\uFF09' : input.paperTitle),\n '- \u8FD0\u884C\uFF1A\u6210\u529F ' + String(succeeded.length) + ' / \u5931\u8D25 ' + String(failed.length) + ' / \u8FD0\u884C\u4E2D ' + String(running.length),\n '',\n '## 1. \u8FD0\u884C\u6982\u89C8',\n '',\n ]\n if (input.runs.length === 0) {\n lines.push('> \u8FD8\u6CA1\u6709\u4EFB\u4F55 run\uFF1A\u5148\u5728\u5B9E\u9A8C\u5DE5\u4F4D\u751F\u6210\u811A\u672C\u5E76\u8FD0\u884C\uFF0C\u6307\u6807\u5FC5\u987B\u6765\u81EA\u771F\u5B9E run\u3002', '')\n } else {\n lines.push('| run | \u811A\u672C | \u72B6\u6001 | \u8017\u65F6 | \u9000\u51FA\u7801 |', '| --- | --- | --- | --- | --- |')\n for (const run of input.runs) {\n lines.push('| ' + [run.id, run.script, run.status, formatDuration(run.durationMs), run.exitCode === null ? '\u2014' : String(run.exitCode)].join(' | ') + ' |')\n }\n lines.push('')\n }\n lines.push('## 2. \u6307\u6807', '')\n if (succeeded.length === 0) {\n lines.push('> \u6CA1\u6709\u6210\u529F\u7684 run\uFF1A\u672C\u62A5\u544A\u4E0D\u7ED9\u51FA\u4EFB\u4F55\u5B9E\u9A8C\u7ED3\u8BBA\u3002', '')\n } else {\n for (const run of succeeded) {\n const keys = Object.keys(run.metrics)\n lines.push('### ' + run.id + ' \u00B7 ' + run.script, '')\n if (keys.length === 0) {\n lines.push('- \u8BE5 run \u6CA1\u6709\u6253\u5370 KSRW_METRIC\uFF0C\u65E0\u6CD5\u91C7\u96C6\u6307\u6807\u3002', '')\n continue\n }\n for (const key of keys) lines.push('- ' + key + ' = ' + String(run.metrics[key]))\n lines.push('')\n }\n }\n lines.push('## 3. \u7ED3\u8BBA', '')\n if (succeeded.length === 0) {\n lines.push('- \u5C1A\u65E0\u6210\u529F run\uFF1A\u4E0D\u5F97\u5199\u300C\u65B9\u6CD5\u6709\u6548\u300D\u300C\u6307\u6807\u63D0\u5347\u300D\u7B49\u7ED3\u8BBA\u3002', '- \u4E0B\u4E00\u6B65\uFF1A\u6838\u5BF9\u811A\u672C\u4E0E\u65E5\u5FD7\uFF0C\u4FEE\u590D\u540E\u91CD\u8DD1\u6700\u5C0F\u57FA\u7EBF\u3002', '')\n } else {\n lines.push('- \u5DF2\u8DD1\u901A ' + String(succeeded.length) + ' \u4E2A run\uFF1B\u7ED3\u8BBA\u53EA\u8986\u76D6\u8FD9\u4E9B run \u8986\u76D6\u7684\u6570\u636E/\u6307\u6807\u3002', '- \u5931\u8D25/\u53D6\u6D88\u7684 run \u5DF2\u4FDD\u7559\u65E5\u5FD7\uFF0C\u5931\u8D25\u5206\u6790\u89C1 experiments/runs/<runId>/stdout.log\u3002', '')\n }\n lines.push('## 4. \u4EA7\u7269', '')\n const artifacts = input.runs.flatMap(run => (run.artifacts ?? []).map(file => run.id + '/' + file))\n lines.push(...(artifacts.length === 0 ? ['- \u6682\u65E0 artifacts\uFF08\u811A\u672C\u53EF\u5199 $KSRW_RUN_DIR/artifacts/\uFF09\u3002'] : artifacts.map(file => '- experiments/runs/' + file)))\n lines.push('', '## 5. \u590D\u73B0', '')\n lines.push('- \u590D\u73B0\u547D\u4EE4\u3001\u73AF\u5883\u4E0E\u79CD\u5B50\u8BB0\u5F55\u5728 experiments/runs/<runId>/config.json\uFF1B', '- \u8BBE\u8BA1\u4EE5 experiments/record.md \u4E3A\u51C6\uFF0C\u811A\u672C\u5728 experiments/code/\u3002', '')\n return lines.join('\\n')\n}\n\n/** \u751F\u6210\u5E76\u843D\u76D8\u62A5\u544A\uFF1Aexperiments/report.md\u3002 */\nexport function writeStandaloneReport(projectPath: string, options: { home?: string } = {}): { report: string; path: string } {\n const detail = readStandaloneExperiment(projectPath, options.home === undefined ? {} : { home: options.home })\n const report = renderExperimentReport({\n name: detail.experiment.name,\n paperTitle: detail.experiment.paper?.title ?? detail.experiment.analysis?.title ?? '',\n design: detail.design.content,\n runs: detail.runs,\n })\n const dir = join(projectPath, 'experiments')\n mkdirSync(dir, { recursive: true, mode: 0o700 })\n const file = join(dir, 'report.md')\n writeFileSync(file, report, { encoding: 'utf8', mode: 0o600 })\n return { report, path: file }\n}\n", "/** \u8BBA\u6587\u9644\u4EF6\uFF08PDF / DOCX / MD / TXT\uFF09\u89E3\u6790\uFF1A\u5B9E\u9A8C\u5DE5\u574A\u4E0E\u8BBA\u6587\u5DE5\u574A\u5171\u7528\u3002\n *\n * \u53EA\u505A\u300C\u5B57\u8282 \u2192 \u5206\u9875\u7EAF\u6587\u672C + \u6807\u9898\u300D\u7684\u786E\u5B9A\u6027\u89E3\u6790\uFF0C\u4E0D\u505A\u8981\u7D20\u62BD\u53D6\uFF08\u90A3\u662F\u5404\u5DE5\u574A\u81EA\u5DF1\u7684\u4E8B\uFF09\u3002\n */\nimport { unzipSync } from 'fflate'\nimport { extname } from 'node:path'\n\nimport { KsrwError } from './error.ts'\nimport { pdfText } from './pdf.ts'\n\nexport const MAX_ATTACHMENT_BYTES = 48 * 1024 * 1024\n\nexport type PaperKind = 'pdf' | 'docx' | 'text' | 'markdown'\n\nexport interface AttachmentPage {\n index: number\n text: string\n}\n\nexport interface ExtractedAttachment {\n pages: AttachmentPage[]\n warnings: string[]\n}\n\nconst PAPER_KINDS: Record<string, PaperKind> = {\n '.pdf': 'pdf', '.docx': 'docx', '.md': 'markdown', '.markdown': 'markdown', '.txt': 'text', '.tex': 'text',\n}\n\n/** \u6269\u5C55\u540D \u2192 \u89E3\u6790\u7C7B\u578B\uFF1B\u4E0D\u652F\u6301\u65F6\u8FD4\u56DE null\u3002 */\nexport function attachmentKindOf(fileName: string): PaperKind | null {\n return PAPER_KINDS[extname(fileName).toLowerCase()] ?? null\n}\n\n/** \u53EA\u53D6\u6587\u4EF6\u540D\u672C\u8EAB\uFF08\u53BB\u6389\u76EE\u5F55\u3001\u63A7\u5236\u5B57\u7B26\uFF09\uFF0C\u62D2\u7EDD\u7A7A / . / ..\u3002 */\nexport function safeAttachmentName(raw: string): string {\n const name = raw.trim().replace(/\\\\/g, '/').split('/').pop() ?? ''\n const cleaned = name.replace(/[\\u0000-\\u001f]/g, '').trim()\n if (cleaned === '' || cleaned === '.' || cleaned === '..') throw new KsrwError('INVALID', '\u9644\u4EF6\u6587\u4EF6\u540D\u4E0D\u5408\u6CD5')\n return cleaned\n}\n\nfunction decodeXml(value: string): string {\n return value\n .replace(/&#x([0-9a-fA-F]+);/g, (_all, hex: string) => String.fromCodePoint(Number.parseInt(hex, 16)))\n .replace(/&#(\\d+);/g, (_all, dec: string) => String.fromCodePoint(Number.parseInt(dec, 10)))\n .replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '\"').replace(/&apos;/g, \"'\")\n .replace(/&amp;/g, '&')\n}\n\n/** DOCX \u2192 \u7EAF\u6587\u672C\uFF08\u89E3 zip \u8BFB word/document.xml\uFF0C\u6309\u6BB5\u843D/\u5236\u8868/\u6362\u884C\u8FD8\u539F\uFF1B\u4E0D\u4F9D\u8D56\u529E\u516C\u8F6F\u4EF6\uFF09\u3002 */\nexport function docxToText(bytes: Uint8Array): string {\n let files: Record<string, Uint8Array>\n try {\n files = unzipSync(new Uint8Array(bytes))\n } catch {\n throw new KsrwError('INVALID', 'DOCX \u89E3\u538B\u5931\u8D25\uFF08\u4E0D\u662F\u6709\u6548\u7684 Word \u6587\u6863\uFF09')\n }\n const entry = files['word/document.xml']\n if (entry === undefined) throw new KsrwError('INVALID', 'DOCX \u7F3A\u5C11 word/document.xml\uFF08\u4E0D\u662F\u6709\u6548\u7684 Word \u6587\u6863\uFF09')\n const xml = new TextDecoder().decode(entry)\n const lines: string[] = []\n for (const paragraph of xml.split('</w:p>')) {\n const pieces: string[] = []\n const re = /<w:t(?:\\s[^>]*)?>([\\s\\S]*?)<\\/w:t>|<w:tab\\/>|<w:br\\/>/g\n let match: RegExpExecArray | null\n while ((match = re.exec(paragraph)) !== null) {\n if (match[1] !== undefined) pieces.push(decodeXml(match[1]))\n else if (match[0] === '<w:tab/>') pieces.push('\\t')\n else pieces.push('\\n')\n }\n const line = pieces.join('').replace(/[ \\t]+$/g, '')\n if (line.trim() !== '') lines.push(line)\n }\n return lines.join('\\n')\n}\n\n/** \u5B57\u8282 \u2192 \u5206\u9875\u6587\u672C\uFF08PDF \u9010\u9875\uFF1B\u5176\u4F59\u5355\u9875\uFF09\u3002 */\nexport async function extractAttachment(kind: PaperKind, bytes: Uint8Array): Promise<ExtractedAttachment> {\n if (kind === 'pdf') {\n // unpdf \u660E\u786E\u62D2\u7EDD Buffer\uFF1APDF \u89E3\u6790\u5FC5\u987B\u62FF\u771F\u6B63\u7684 Uint8Array\u3002\n const parsed = await pdfText(new Uint8Array(bytes))\n const warnings: string[] = []\n if (parsed.pages > 0 && parsed.texts.join('').trim().length < 600) {\n warnings.push('PDF \u6587\u672C\u5F88\u5C11\uFF08\u53EF\u80FD\u662F\u626B\u63CF\u7248/\u56FE\u7247 PDF\uFF09\uFF1A\u62BD\u53D6\u4F1A\u4E0D\u5B8C\u6574\uFF0C\u9700\u8981\u4EBA\u5DE5\u8865\u3002')\n }\n return { pages: parsed.texts.map((text, index) => ({ index: index + 1, text })), warnings }\n }\n if (kind === 'docx') return { pages: [{ index: 1, text: docxToText(bytes) }], warnings: [] }\n return { pages: [{ index: 1, text: Buffer.from(bytes).toString('utf8') }], warnings: [] }\n}\n\n/** \u7EDF\u4E00\u7A7A\u767D\uFF1ACRLF\u3001\u884C\u5C3E\u7A7A\u683C\u3001\u4E09\u8FDE\u7A7A\u884C\u3002 */\nexport function cleanText(text: string): string {\n return text\n .replace(/\\r\\n?/g, '\\n')\n .replace(/[ \\t]+\\n/g, '\\n')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim()\n}\n\n/** \u4F5C\u8005/\u673A\u6784/\u7248\u6743\u884C\uFF1A\u4E0D\u80FD\u5F53\u6807\u9898\u3002 */\nexport function looksLikeByline(line: string): boolean {\n if (/@/.test(line)) return true\n if (/[\u2217\u2020\u2021]/.test(line)) return true\n if (/(universit|institute|laborator|department|school of|inc\\.|ltd\\.|google brain|google research|google deepmind)/i.test(line)) return true\n if (/^(provided proper attribution|copyright|permission|licensed|license|doi|arxiv|https?:|\u00A9)/i.test(line)) return true\n return false\n}\n\n/** \u6807\u9898\u8BC6\u522B\uFF1A\u53D6\u6458\u8981\u524D\u6700\u50CF\u6807\u9898\u7684\u4E00\u884C\uFF08PDF \u9996\u9875\u5E38\u6709\u7248\u6743/\u4F5C\u8005\u5757\uFF0C\u4E0D\u80FD\u53D6\u7B2C\u4E00\u884C\uFF09\u3002 */\nexport function detectTitle(pages: AttachmentPage[]): string {\n const lines = (pages[0]?.text ?? '')\n .split('\\n')\n .map(line => line.replace(/^#+\\s*/, '').trim())\n .filter(line => line !== '')\n // \u4E2D\u6587\u6CA1\u6709 \\b \u8BCD\u8FB9\u754C\uFF1A\u8FD9\u91CC\u53EA\u6309\u884C\u9996\u5339\u914D\uFF0C\u6458\u8981/\u5173\u952E\u8BCD\u884C\u90FD\u7B97\u8FB9\u754C\u3002\n const abstractIndex = lines.findIndex(line => /^(abstract|\u6458\u8981|keywords?|\u5173\u952E\u8BCD)/i.test(line))\n const limit = abstractIndex >= 0 ? Math.min(abstractIndex, 40) : Math.min(lines.length, 15)\n const candidates: { line: string; index: number; score: number }[] = []\n for (let index = 0; index < limit; index += 1) {\n const line = lines[index] ?? ''\n if (line.length < 8 || line.length > 160) continue\n if (looksLikeByline(line)) continue\n if (/^(abstract|\u6458\u8981|keywords?|\u5173\u952E\u8BCD)/i.test(line)) continue\n if (/^\\d+$/.test(line)) continue\n // \u53E5\u5B50\uFF08\u5E26\u53E5\u672B\u6807\u70B9\uFF09\u4E0D\u662F\u6807\u9898\uFF1A\u6458\u8981\u53E5\u5E38\u88AB\u8BEF\u6293\u3002\n if (/[\u3002\uFF01\uFF1F\uFF1B;]$/.test(line)) continue\n if (/[.,;:]$/.test(line) && line.split(/\\s+/).length < 4) continue\n const words = line.split(/\\s+/).filter(word => word !== '').length\n const cjk = /[\\u4e00-\\u9fff]/.test(line)\n // \u82F1\u6587\u6807\u9898\u81F3\u5C11\u4E24\u4E2A\u8BCD\uFF1B\u4E2D\u6587\u6807\u9898\u53EF\u4EE5\u662F\u4E00\u884C\uFF08\u6CA1\u6709\u7A7A\u683C\u5206\u8BCD\uFF09\u3002\n if (words < 2 && !(cjk && line.length >= 6)) continue\n let score = Math.min(words, 12)\n if (/^[A-Z\\u4e00-\\u9fff]/.test(line)) score += 2\n if (cjk) score += 2\n // \u6807\u9898\u7D27\u8DDF\u7740\u4F5C\u8005\u884C\uFF08\u5E26 \u2217/\u2020 \u6216\u90AE\u7BB1\uFF09\u662F\u6700\u5F3A\u4FE1\u53F7\u3002\n const next = lines[index + 1] ?? ''\n if (/[\u2217\u2020\u2021]/.test(next) || /@/.test(next)) score += 8\n candidates.push({ line, index, score })\n }\n candidates.sort((left, right) => right.score - left.score || left.index - right.index)\n return candidates[0]?.line ?? ''\n}\n\n/** \u6807\u9898\u50CF\u4E0D\u50CF\u7AE0\u8282\u6807\u9898\uFF08\u7F16\u53F7\u3001\u5E38\u89C1\u7AE0\u8282\u540D\u3001\u77ED\u884C\uFF09\u3002 */\nexport function looksLikeHeading(line: string): boolean {\n if (line === '' || line.length > 42) return false\n if (looksLikeByline(line)) return false\n if (/^(abstract|\u6458\u8981|introduction|\u5F15\u8A00|related work|\u76F8\u5173\u5DE5\u4F5C|method|\u65B9\u6CD5|approach|experiment|\u5B9E\u9A8C|results?|\u7ED3\u679C|discussion|\u8BA8\u8BBA|conclusion|\u7ED3\u8BBA|references?|\u53C2\u8003\u6587\u732E|limitations?|\u5C40\u9650)/i.test(line)) return true\n if (/^\\d+(?:\\.\\d+)*\\.?\\s+\\S/.test(line) && !/[\u3002.]$/.test(line)) return true\n return false\n}\n\n/** \u628A\u300C## \u7B2C N \u9875\u300D\u5206\u9875\u6807\u8BB0\u8FD8\u539F\u4E3A\u9875\u6570\u7EC4\uFF1B\u6CA1\u6709\u6807\u8BB0\u65F6\u89C6\u4E3A\u5355\u9875\u3002 */\nexport function splitNormalizedPages(text: string): AttachmentPage[] {\n const parts = text.split(/^## \u7B2C (\\d+) \u9875$/m)\n if (parts.length < 3) return [{ index: 1, text }]\n const pages: AttachmentPage[] = []\n for (let index = 1; index < parts.length; index += 2) {\n pages.push({ index: Number(parts[index] ?? pages.length + 1), text: parts[index + 1] ?? '' })\n }\n return pages\n}\n\n/** \u89C4\u8303\u5316\u5168\u6587\uFF08\u5E26\u9875\u6807\u8BB0\uFF09\uFF1A\u7ED9 agent/\u9884\u89C8\u8BFB\u539F\u6587\u7528\u3002 */\nexport function normalizeWithPageMarkers(title: string, raw: ExtractedAttachment): { text: string; pages: AttachmentPage[] } {\n const pages = raw.pages.map(page => ({ index: page.index, text: cleanText(page.text) }))\n const body = pages.length > 1\n ? pages.map(page => '## \u7B2C ' + String(page.index) + ' \u9875\\n\\n' + page.text).join('\\n\\n')\n : (pages[0]?.text ?? '')\n const heading = title === '' ? '' : '# ' + title + '\\n\\n'\n return { text: cleanText(heading + body) + '\\n', pages }\n}\n\n/**\n * \u628A\u9644\u4EF6\u89E3\u6790\u7ED3\u679C\u8F6C\u6210\u300C\u53EF\u7F16\u8F91\u7684\u8BBA\u6587\u521D\u7A3F\u300D\uFF08Markdown\uFF09\uFF1A\n * - Markdown \u8F93\u5165\u539F\u6837\u4FDD\u7559\uFF1B\n * - DOCX/TXT \u6BCF\u884C\u89C6\u4E3A\u4E00\u6BB5\uFF1B\n * - PDF \u6BCF\u4E2A\u89C6\u89C9\u884C\u62FC\u56DE\u6BB5\u843D\uFF0C\u9047\u5230\u7AE0\u8282\u6807\u9898/\u5217\u8868\u9879/\u6362\u9875\u65F6\u65AD\u6BB5\u3002\n * \u4E0D\u5220\u4F5C\u8005\u4E0E\u7248\u6743\u884C\uFF08\u8BBA\u6587\u5DE5\u574A\u8981\u6539\u7684\u662F\u539F\u6587\uFF0C\u4E0D\u662F\u91CD\u5199\u6458\u8981\uFF09\u3002\n */\nexport function draftMarkdownFromAttachment(kind: PaperKind, title: string, pages: AttachmentPage[]): string {\n if (kind === 'markdown') {\n const text = pages.map(page => cleanText(page.text)).join('\\n\\n').trim()\n return text === '' ? '' : text + '\\n'\n }\n const blocks: string[] = []\n const normalizedTitle = title.replace(/\\s+/g, ' ').trim().toLowerCase()\n let skippedTitle = false\n if (title !== '') blocks.push('# ' + title)\n // PDF \u7684\u89C6\u89C9\u6362\u884C\uFF1A\u4E2D\u6587\u4E4B\u95F4\u4E0D\u52A0\u7A7A\u683C\uFF0C\u82F1\u6587\u5355\u8BCD\u4E4B\u95F4\u52A0\uFF1B\u884C\u5C3E\u8FDE\u5B57\u7B26\u8981\u63A5\u56DE\u53BB\u3002\n const joinLines = (parts: string[]): string => {\n let text = ''\n for (const part of parts) {\n if (text === '') { text = part; continue }\n const last = text.slice(-1)\n const first = part.slice(0, 1)\n if (last === '-' && /[a-z]/.test(first)) { text = text.slice(0, -1) + part; continue }\n const cjk = /[\\u4e00-\\u9fff]/.test(last) || /[\\u4e00-\\u9fff]/.test(first)\n text += (cjk ? '' : ' ') + part\n }\n return text\n }\n for (const page of pages) {\n let buffer: string[] = []\n const flush = (): void => {\n const paragraph = joinLines(buffer).replace(/\\s+/g, ' ').replace(/([\\u4e00-\\u9fff])\\s+([\\u4e00-\\u9fff])/g, '$1$2').trim()\n if (paragraph !== '') blocks.push(paragraph)\n buffer = []\n }\n for (const rawLine of page.text.split('\\n')) {\n const line = rawLine.trim()\n if (line === '') { flush(); continue }\n if (/^\u7B2C\\s*\\d+\\s*\u9875$/.test(line)) continue\n // PDF \u9996\u9875\u7684\u6807\u9898\u884C\u4F1A\u91CD\u590D\u51FA\u73B0\uFF1A\u4FDD\u7559 Markdown \u6807\u9898\uFF0C\u6B63\u6587\u91CC\u53BB\u6389\u7B2C\u4E00\u6B21\u91CD\u590D\u3002\n if (!skippedTitle && normalizedTitle !== '' && line.replace(/\\s+/g, ' ').trim().toLowerCase() === normalizedTitle) {\n skippedTitle = true\n flush()\n continue\n }\n if (looksLikeHeading(line)) {\n flush()\n blocks.push((/^\\d/.test(line) ? '## ' : '## ') + line)\n continue\n }\n if (/^([-*\u2022]|\\d+[.)])\\s+/.test(line) || looksLikeByline(line)) {\n flush()\n blocks.push(line)\n continue\n }\n if (kind === 'pdf') {\n buffer.push(line)\n // \u6781\u7AEF\u957F\u6BB5\uFF08PDF \u884C\u88AB\u6574\u9875\u62FC\u8D77\u6765\uFF09\u4E3B\u52A8\u65AD\u6BB5\uFF0C\u7F16\u8F91\u5668\u624D\u4E0D\u4F1A\u51FA\u73B0\u4E00\u5835\u5899\u3002\n if (buffer.join(' ').length > 1200) flush()\n } else { flush(); blocks.push(line) }\n }\n flush()\n }\n return blocks.join('\\n\\n').trim() + '\\n'\n}\n", "/** \u514D\u9879\u76EE\u8BBA\u6587\u5DE5\u574A\uFF08paper workshop\uFF09\uFF1A\u4E0D\u5EFA\u9879\u76EE\uFF0C\u4E0A\u4F20\u81EA\u5DF1\u7684\u5386\u53F2\u8BBA\u6587 \u2192 \u8F6C\u6210\u53EF\u7F16\u8F91 Markdown \u2192 \u4FEE\u6539 \u2192 \u7F16\u8BD1/\u5BFC\u51FA\u3002\n *\n * \u4E0E\u5B9E\u9A8C\u5DE5\u574A\uFF08standalone\uFF09\u540C\u6784\uFF1Adetached \u9879\u76EE\u76EE\u5F55\uFF0C\u53EF\u63D0\u6848\u3001\u53EF\u7F16\u8BD1\uFF0C\u4F46\u4E0D\u8FDB\u9879\u76EE\u5E93\u3002\n * \u76EE\u5F55\uFF08<KSRW_HOME>/paper-workshops/<id>/\uFF09\uFF1A\n * workshop.json \u5DE5\u574A\u6E05\u5355\uFF08\u6765\u6E90\u9644\u4EF6\u5143\u4FE1\u606F\uFF09\n * project.json detached \u9879\u76EE\u8BB0\u5F55\uFF08stage=paper\uFF09\n * paper/attachment.* \u539F\u59CB\u9644\u4EF6\uFF08PDF / DOCX / MD / TXT\uFF09\n * paper/source.md \u89C4\u8303\u5316\u5168\u6587\uFF08PDF \u6309\u300C## \u7B2C N \u9875\u300D\u5206\u9875\uFF0C\u4F9B\u5BF9\u7167/agent \u8BFB\u539F\u6587\uFF09\n * paper/original.md \u5BFC\u5165\u65F6\u7684\u521D\u7A3F\uFF08\u53EF\u4E00\u952E\u6062\u590D\uFF09\n * paper/main.md \u5F53\u524D\u8BBA\u6587\u6B63\u6587\uFF08paper \u5DE5\u4EF6\uFF0C\u63D0\u6848\u9A71\u52A8\uFF09\n * paper/*.pdf|docx \u7F16\u8BD1/\u5BFC\u51FA\u4EA7\u7269\uFF08\u590D\u7528 kernel paper \u7F16\u8BD1\u6D41\u6C34\u7EBF\uFF09\n */\nimport { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs'\nimport { join, resolve, sep } from 'node:path'\n\nimport {\n MAX_ATTACHMENT_BYTES,\n attachmentKindOf,\n cleanText,\n detectTitle,\n draftMarkdownFromAttachment,\n extractAttachment,\n normalizeWithPageMarkers,\n safeAttachmentName,\n splitNormalizedPages,\n type PaperKind,\n} from './attachment.ts'\nimport { KsrwError } from './error.ts'\nimport { extractDocxMedia } from './figures.ts'\nimport {\n listPaperOutputs,\n paperFinalStatus,\n paperStatus,\n readPaperBuild,\n type PaperBuildRecord,\n type PaperOutput,\n type PaperTools,\n} from './paper.ts'\nimport { ProjectStore, forgetProject, resolveKsrwHome, type ProjectRecord } from './workspace.ts'\n\nexport const PAPER_WORKSHOP_DIR = 'paper-workshops'\nexport const MAX_WORKSHOP_ATTACHMENT_BYTES = MAX_ATTACHMENT_BYTES\nconst MAX_SOURCE_TEXT = 200_000\n\nexport interface PaperSourceMeta {\n fileName: string\n kind: PaperKind\n bytes: number\n uploadedAt: number\n pages: number\n chars: number\n title: string\n}\n\nexport interface PaperWorkshopManifest {\n schemaVersion: 1\n id: string\n name: string\n createdAt: number\n source: PaperSourceMeta | null\n}\n\nexport type PaperWorkshopStatus = 'empty' | 'imported' | 'compiled'\n\nexport interface PaperWorkshopSummary {\n id: string\n name: string\n path: string\n createdAt: number\n updatedAt: number\n status: PaperWorkshopStatus\n title: string\n revision: number\n words: number\n source: PaperSourceMeta | null\n}\n\nexport interface PaperWorkshopCompile {\n ready: boolean\n pdfEngine: 'xelatex' | 'soffice' | 'none'\n tools: PaperTools\n outputs: PaperOutput[]\n build: PaperBuildRecord | null\n stale: boolean\n}\n\nexport interface PaperWorkshopDetail {\n workshop: PaperWorkshopSummary\n draft: { revision: number; updatedAt: number; content: string }\n original: string\n sourceText: string\n compile: PaperWorkshopCompile\n}\n\nexport function resolvePaperWorkshopRoot(home: string = resolveKsrwHome()): string {\n return join(resolve(home), PAPER_WORKSHOP_DIR)\n}\n\nfunction readJson<T>(file: string): T | null {\n try { return JSON.parse(readFileSync(file, 'utf8')) as T } catch { return null }\n}\n\nfunction readText(file: string): string | null {\n try { return readFileSync(file, 'utf8') } catch { return null }\n}\n\nfunction writeJson(file: string, value: unknown): void {\n mkdirSync(join(file, '..'), { recursive: true, mode: 0o700 })\n writeFileSync(file, JSON.stringify(value, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n}\n\nfunction paperDir(projectPath: string): string {\n return join(projectPath, 'paper')\n}\n\nfunction manifestPath(projectPath: string): string {\n return join(projectPath, 'workshop.json')\n}\n\nfunction deriveManifest(projectPath: string): PaperWorkshopManifest | null {\n const project = readJson<ProjectRecord>(join(projectPath, 'project.json'))\n if (project === null) return null\n return { schemaVersion: 1, id: project.id, name: project.name, createdAt: project.createdAt, source: null }\n}\n\nfunction readManifest(projectPath: string): PaperWorkshopManifest | null {\n return readJson<PaperWorkshopManifest>(manifestPath(projectPath)) ?? deriveManifest(projectPath)\n}\n\nfunction writeManifest(projectPath: string, manifest: PaperWorkshopManifest): void {\n writeJson(manifestPath(projectPath), manifest)\n}\n\nfunction two(value: number): string {\n return value < 10 ? '0' + String(value) : String(value)\n}\n\nfunction stamp(at: number): string {\n const date = new Date(at)\n return String(date.getFullYear()) + two(date.getMonth() + 1) + two(date.getDate())\n + '-' + two(date.getHours()) + two(date.getMinutes()) + two(date.getSeconds())\n}\n\nfunction shortId(): string {\n return Math.random().toString(36).slice(2, 8)\n}\n\nfunction defaultName(at: number): string {\n const date = new Date(at)\n return '\u8BBA\u6587\u4FEE\u6539 \u00B7 ' + two(date.getMonth() + 1) + '-' + two(date.getDate())\n + ' ' + two(date.getHours()) + ':' + two(date.getMinutes())\n}\n\n/** \u4E2D\u6587\u5B57\u7B26 + \u62C9\u4E01\u8BCD\u8BA1\u6570\uFF08\u4E0D\u542B\u7A7A\u767D\u4E0E\u6807\u70B9\uFF09\u3002 */\nexport function countPaperWords(text: string): number {\n const cjk = (text.match(/[\\u4e00-\\u9fff]/g) ?? []).length\n const latin = (text.match(/[A-Za-z0-9]+(?:['\u2019-][A-Za-z0-9]+)*/g) ?? []).length\n return cjk + latin\n}\n\nfunction compileOf(projectPath: string): PaperWorkshopCompile {\n const status = paperStatus(projectPath, 'paper/main.md')\n const final = paperFinalStatus(projectPath, 'paper')\n return {\n ready: status.ready,\n pdfEngine: status.pdfEngine,\n tools: status.tools,\n // \u9644\u4EF6\u672C\u8EAB\u4E0D\u662F\u4EA4\u4ED8\u7269\uFF1A\u5217\u8868\u91CC\u53EA\u4FDD\u7559\u6B63\u6587\u3001PDF \u4E0E\u5BFC\u51FA\u7684 Word\u3002\n outputs: listPaperOutputs(projectPath, 'paper').filter(output => !output.name.startsWith('attachment.')),\n build: readPaperBuild(projectPath, 'paper') ?? final.build ?? null,\n stale: final.stale,\n }\n}\n\nfunction statusOf(source: PaperSourceMeta | null, revision: number, compile: PaperWorkshopCompile): PaperWorkshopStatus {\n if (compile.build !== null && compile.build.pdf !== '' && !compile.stale) return 'compiled'\n if (source !== null || revision > 0) return 'imported'\n return 'empty'\n}\n\n/** \u8BFB\u5DE5\u574A\u5B8C\u6574\u89C6\u56FE\uFF08\u6B63\u6587 + \u521D\u7A3F + \u539F\u6587 + \u7F16\u8BD1\u72B6\u6001\uFF09\u3002 */\nexport function readPaperWorkshop(projectPath: string, options: { home?: string } = {}): PaperWorkshopDetail {\n const directory = resolve(projectPath)\n const manifest = readManifest(directory)\n if (manifest === null) throw new KsrwError('NOT_FOUND', '\u4E0D\u662F KSRW \u8BBA\u6587\u5DE5\u574A\uFF08\u7F3A workshop.json / project.json\uFF09\uFF1A' + directory)\n const store = new ProjectStore(options.home === undefined ? {} : { home: options.home })\n const project = store.readProject(directory)\n const artifact = store.readArtifact(directory, 'paper')\n const draft = {\n revision: artifact?.revision ?? 0,\n updatedAt: artifact?.updatedAt ?? project.updatedAt,\n content: artifact?.content ?? '',\n }\n const compile = compileOf(directory)\n const sourceTextRaw = readText(join(paperDir(directory), 'source.md')) ?? ''\n const sourceText = sourceTextRaw.length > MAX_SOURCE_TEXT\n ? sourceTextRaw.slice(0, MAX_SOURCE_TEXT) + '\\n\u2026\uFF08\u539F\u6587\u5DF2\u622A\u65AD\uFF09'\n : sourceTextRaw\n const summary: PaperWorkshopSummary = {\n id: manifest.id,\n name: manifest.name,\n path: directory,\n createdAt: manifest.createdAt,\n updatedAt: Math.max(project.updatedAt, draft.updatedAt, manifest.source?.uploadedAt ?? 0, compile.build?.generatedAt ?? 0),\n status: statusOf(manifest.source, draft.revision, compile),\n title: manifest.source?.title ?? '',\n revision: draft.revision,\n words: countPaperWords(draft.content),\n source: manifest.source,\n }\n return {\n workshop: summary,\n draft,\n original: readText(join(paperDir(directory), 'original.md')) ?? '',\n sourceText,\n compile,\n }\n}\n\n/** \u5DE5\u574A\u5217\u8868\uFF08\u6700\u8FD1\u6D3B\u8DC3\u4F18\u5148\uFF09\u3002 */\nexport function listPaperWorkshops(options: { home?: string } = {}): PaperWorkshopSummary[] {\n const root = resolvePaperWorkshopRoot(options.home ?? resolveKsrwHome())\n if (!existsSync(root)) return []\n let entries: string[] = []\n try { entries = readdirSync(root) } catch { return [] }\n const items: PaperWorkshopSummary[] = []\n for (const entry of entries) {\n if (entry.startsWith('.')) continue\n const directory = join(root, entry)\n try {\n if (!statSync(directory).isDirectory()) continue\n items.push(readPaperWorkshop(directory, options).workshop)\n } catch { /* \u4E0D\u662F\u5DE5\u574A\u76EE\u5F55\u6216\u5DF2\u635F\u574F\uFF1A\u8DF3\u8FC7 */ }\n }\n return items.sort((left, right) => right.updatedAt - left.updatedAt)\n}\n\n/** \u65B0\u5EFA\u8BBA\u6587\u5DE5\u574A\uFF08detached \u9879\u76EE + paper \u76EE\u5F55 + \u6E05\u5355\uFF09\u3002 */\nexport function createPaperWorkshop(input: { name?: string; home?: string; now?: number } = {}): PaperWorkshopDetail {\n const home = resolve(input.home ?? resolveKsrwHome())\n const root = resolvePaperWorkshopRoot(home)\n mkdirSync(root, { recursive: true, mode: 0o700 })\n const at = input.now ?? Date.now()\n const name = (input.name ?? '').trim() === '' ? defaultName(at) : (input.name ?? '').trim()\n const id = 'paper-' + stamp(at) + '-' + shortId()\n const directory = join(root, id)\n const store = new ProjectStore({ home })\n const project = store.create({\n name,\n directory,\n detached: true,\n idea: '\u514D\u9879\u76EE\u8BBA\u6587\u5DE5\u574A\uFF1A\u4E0A\u4F20\u5386\u53F2\u8BBA\u6587\u540E\u4FEE\u6539\u5E76\u91CD\u65B0\u7F16\u8BD1\u3002',\n })\n store.setStage(project.path, 'paper')\n mkdirSync(paperDir(directory), { recursive: true, mode: 0o700 })\n writeManifest(directory, { schemaVersion: 1, id, name, createdAt: at, source: null })\n return readPaperWorkshop(directory, { home })\n}\n\n/** \u4E0A\u4F20/\u66FF\u6362\u5386\u53F2\u8BBA\u6587\uFF1A\u843D\u76D8\u539F\u6587 + \u89C4\u8303\u5316\u539F\u6587 + \u53EF\u7F16\u8F91\u521D\u7A3F\uFF08\u4F5C\u4E3A paper \u5DE5\u4EF6 revision\uFF09\u3002 */\nexport async function importPaperAttachment(\n projectPath: string,\n input: { fileName: string; bytes: Uint8Array; home?: string },\n): Promise<PaperWorkshopDetail> {\n const directory = resolve(projectPath)\n const manifest = readManifest(directory)\n if (manifest === null) throw new KsrwError('NOT_FOUND', '\u4E0D\u662F KSRW \u8BBA\u6587\u5DE5\u574A\uFF1A' + directory)\n const fileName = safeAttachmentName(input.fileName)\n const kind = attachmentKindOf(fileName)\n if (kind === null) {\n const extension = fileName.slice(fileName.lastIndexOf('.'))\n throw new KsrwError('INVALID', '\u53EA\u652F\u6301 PDF / DOCX / MD / TXT / TeX \u8BBA\u6587\u9644\u4EF6\uFF0C\u6536\u5230\uFF1A' + (extension === fileName ? '\u65E0\u6269\u5C55\u540D' : extension))\n }\n const bytes = Buffer.from(input.bytes)\n if (bytes.byteLength === 0) throw new KsrwError('INVALID', '\u9644\u4EF6\u4E3A\u7A7A')\n if (bytes.byteLength > MAX_WORKSHOP_ATTACHMENT_BYTES) throw new KsrwError('INVALID', '\u9644\u4EF6\u8D85\u8FC7 48MiB \u4E0A\u9650')\n\n const dir = paperDir(directory)\n mkdirSync(dir, { recursive: true, mode: 0o700 })\n // P2-18\uFF1A\u5148\u89E3\u6790\u3001\u540E\u66FF\u6362\u3002\u89E3\u6790\u5931\u8D25\uFF08\u574F\u6587\u4EF6/\u626B\u63CF\u7248 PDF\uFF09\u65F6\u65E7\u9644\u4EF6\u4E0E\u65E7\u6B63\u6587\u539F\u6837\u4FDD\u7559\u3002\n const raw = await extractAttachment(kind, new Uint8Array(bytes))\n const title = detectTitle(raw.pages) === '' ? manifest.name : detectTitle(raw.pages)\n const normalized = normalizeWithPageMarkers(title, raw)\n const draft = draftMarkdownFromAttachment(kind, title, normalized.pages)\n if (draft.trim() === '') throw new KsrwError('INVALID', '\u6CA1\u80FD\u4ECE\u9644\u4EF6\u91CC\u62BD\u51FA\u6B63\u6587\uFF08\u53EF\u80FD\u662F\u626B\u63CF\u7248 PDF\uFF09\uFF1A\u8BF7\u6362\u4E00\u4EFD\u53EF\u590D\u5236\u6587\u672C\u7684\u6587\u4EF6')\n\n for (const name of readdirSync(dir)) {\n if (name.startsWith('attachment.')) rmSync(join(dir, name), { force: true })\n }\n const extension = fileName.slice(fileName.lastIndexOf('.'))\n writeFileSync(join(dir, 'attachment' + extension), bytes, { mode: 0o600 })\n writeFileSync(join(dir, 'source.md'), normalized.text, { encoding: 'utf8', mode: 0o600 })\n writeFileSync(join(dir, 'original.md'), draft, { encoding: 'utf8', mode: 0o600 })\n // \u4EA7\u54C1 bug \u4FEE\u590D\uFF082026-10-30\uFF09\uFF1ADOCX \u5BFC\u5165\u6B64\u524D\u53EA\u62BD\u6B63\u6587\u3001\u4E22\u6389 word/media/ \u91CC\u7684\u539F\u56FE\u3002\n // \u8FD9\u91CC\u628A\u5185\u5D4C\u56FE\u7247\u62BD\u8FDB paper/figures \u5E76\u767B\u8BB0\uFF0C\u7528\u6237\u7684\u539F\u56FE\u4ECE\u5BFC\u5165\u90A3\u4E00\u523B\u8D77\u5C31\u53EF\u8FBE\uFF1B\n // \u62BD\u53D6\u5931\u8D25\uFF08\u9644\u4EF6\u672C\u6765\u6CA1\u56FE\u3001zip \u635F\u574F\uFF09\u4E0D\u963B\u585E\u5BFC\u5165\u3002\n if (kind === 'docx') {\n try { extractDocxMedia(directory, { docx: 'paper/attachment' + extension }) } catch { /* \u65E0\u5185\u5D4C\u56FE\uFF1A\u5FFD\u7565 */ }\n }\n\n const store = new ProjectStore(input.home === undefined ? {} : { home: input.home })\n store.writeArtifact(directory, 'paper', draft)\n store.touch(store.readProject(directory))\n const meta: PaperSourceMeta = {\n fileName,\n kind,\n bytes: bytes.byteLength,\n uploadedAt: Date.now(),\n pages: splitNormalizedPages(normalized.text).length,\n chars: normalized.text.length,\n title,\n }\n writeManifest(directory, { ...manifest, source: meta })\n return readPaperWorkshop(directory, input.home === undefined ? {} : { home: input.home })\n}\n\n/** \u4FDD\u5B58\u6B63\u6587\uFF08\u7528\u6237\u76F4\u5199\u6743\u5A01\u6587\u4EF6\uFF1BAgent \u8D70\u63D0\u6848\uFF09\u3002 */\nexport function savePaperWorkshopDraft(projectPath: string, content: string, options: { home?: string } = {}): PaperWorkshopDetail['draft'] {\n if (content.trim() === '') throw new KsrwError('INVALID', '\u8BBA\u6587\u6B63\u6587\u4E0D\u80FD\u4E3A\u7A7A')\n const store = new ProjectStore(options.home === undefined ? {} : { home: options.home })\n const view = store.writeArtifact(projectPath, 'paper', content)\n return { revision: view.revision, updatedAt: view.updatedAt, content: view.content }\n}\n\n/** \u6062\u590D\u4E3A\u5BFC\u5165\u65F6\u7684\u521D\u7A3F\uFF08\u4F1A\u65B0\u589E\u4E00\u4E2A revision\uFF0C\u4E0D\u5220\u7528\u6237\u5386\u53F2\uFF09\u3002 */\nexport function restorePaperWorkshopDraft(projectPath: string, options: { home?: string } = {}): PaperWorkshopDetail['draft'] {\n const directory = resolve(projectPath)\n // \u5148\u786E\u8BA4\u662F\u5DE5\u574A\uFF1A\u5426\u5219\u4E0D\u5B58\u5728\u7684\u8DEF\u5F84\u4F1A\u4F2A\u88C5\u6210\u300C\u8FD8\u6CA1\u4E0A\u4F20\u8BBA\u6587\u300D\u3002\n if (readManifest(directory) === null) throw new KsrwError('NOT_FOUND', '\u4E0D\u662F KSRW \u8BBA\u6587\u5DE5\u574A\uFF1A' + directory)\n const original = readText(join(paperDir(directory), 'original.md'))\n if (original === null) throw new KsrwError('NOT_FOUND', '\u8FD8\u6CA1\u6709\u5BFC\u5165\u8FC7\u7684\u521D\u7A3F\uFF1A\u5148\u4E0A\u4F20\u5386\u53F2\u8BBA\u6587')\n return savePaperWorkshopDraft(projectPath, original, options)\n}\n\n/** \u5220\u9664\u5DE5\u574A\u76EE\u5F55\uFF08\u53EA\u5141\u8BB8\u5220 paper-workshops \u6839\u4E0B\u7684\u76EE\u5F55\uFF09+ \u6E05\u6389\u53EF\u80FD\u7684\u9879\u76EE\u5E93\u767B\u8BB0\u3002 */\nexport function deletePaperWorkshop(projectPath: string, options: { home?: string } = {}): boolean {\n const home = resolve(options.home ?? resolveKsrwHome())\n const root = resolvePaperWorkshopRoot(home)\n const target = resolve(projectPath)\n if (target === root || !target.startsWith(root + sep)) {\n throw new KsrwError('PATH_DENIED', '\u53EA\u80FD\u5220\u9664 paper-workshops \u5DE5\u574A\u76EE\u5F55\uFF1A' + target)\n }\n if (!existsSync(target)) throw new KsrwError('NOT_FOUND', '\u8BBA\u6587\u5DE5\u574A\u4E0D\u5B58\u5728\uFF1A' + target)\n rmSync(target, { recursive: true, force: true })\n forgetProject(home, target)\n return true\n}\n\n/** \u628A\u5DE5\u574A\u63A5\u6536\u4E3A\u6B63\u5F0F\u9879\u76EE\uFF08\u6E05\u6389 detached\uFF0C\u8FDB\u9879\u76EE\u5E93\uFF09\u3002 */\nexport function registerPaperWorkshop(projectPath: string, options: { home?: string } = {}): PaperWorkshopDetail {\n const store = new ProjectStore(options.home === undefined ? {} : { home: options.home })\n store.register(projectPath)\n return readPaperWorkshop(projectPath, options)\n}\n\n/** \u4F9B\u5DE5\u5177\u4F7F\u7528\uFF1A\u8BFB\u5DE5\u574A\u91CC\u7684\u539F\u6587\uFF08\u5206\u7247\uFF09\u3002 */\nexport function readPaperWorkshopSource(projectPath: string): { text: string; chars: number } {\n const raw = readText(join(paperDir(resolve(projectPath)), 'source.md')) ?? ''\n return { text: raw, chars: raw.length }\n}\n\n/** \u4F9B\u5DE5\u5177\u4F7F\u7528\uFF1A\u89C4\u8303\u5316\u540E\u7684\u539F\u6587\u662F\u5426\u5DF2\u5B58\u5728\u3002 */\nexport function paperWorkshopImported(projectPath: string): boolean {\n return existsSync(join(paperDir(resolve(projectPath)), 'attachment.pdf'))\n || existsSync(join(paperDir(resolve(projectPath)), 'attachment.docx'))\n || existsSync(join(paperDir(resolve(projectPath)), 'attachment.md'))\n || existsSync(join(paperDir(resolve(projectPath)), 'attachment.markdown'))\n || existsSync(join(paperDir(resolve(projectPath)), 'attachment.txt'))\n || existsSync(join(paperDir(resolve(projectPath)), 'attachment.tex'))\n}\n\n/** \u8349\u7A3F\u7AE0\u8282\uFF08\u7ED9\u5DE5\u5177/UI \u5C55\u793A\u7ED3\u6784\uFF1B\u53EA\u505A\u6807\u9898\u8BC6\u522B\uFF0C\u4E0D\u6539\u6B63\u6587\uFF09\u3002 */\nexport function paperWorkshopSections(content: string): { heading: string; chars: number }[] {\n const sections: { heading: string; chars: number }[] = []\n let current: { heading: string; chars: number } | undefined\n for (const rawLine of content.split('\\n')) {\n const line = rawLine.trim()\n if (line === '') continue\n if (/^#{1,6}\\s+/.test(line)) {\n current = { heading: line.replace(/^#{1,6}\\s+/, ''), chars: 0 }\n sections.push(current)\n continue\n }\n if (current !== undefined) current.chars += cleanText(line).length\n }\n return sections.slice(0, 80)\n}\n", "/** KSRW \u77E5\u8BC6\u5E93\uFF1A\u672C\u5730 Markdown \u7D22\u5F15 + \u4E2D\u6587 bigram \u68C0\u7D22\uFF08\u96F6\u4F9D\u8D56\uFF09\u3002\n *\n * \u77E5\u8BC6\u6761\u76EE\u662F\u65B9\u6CD5/\u672F\u8BED/\u6A21\u677F\uFF0C\u4E0D\u662F\u5F15\u7528\u6743\u5A01\uFF1B\u6BCF\u6761\u7ED3\u679C\u90FD\u5E26 path/heading \u4F9B\u56DE\u70B9\u3002\n */\nimport { readFileSync, statSync } from 'node:fs'\nimport { join } from 'node:path'\n\nexport interface KnowledgeDoc {\n readonly id: string\n readonly title: string\n readonly path: string\n readonly category: string\n readonly chars: number\n}\n\nexport interface KnowledgeChunk {\n readonly id: string\n readonly docId: string\n readonly title: string\n readonly path: string\n readonly category: string\n readonly heading: string\n readonly text: string\n}\n\nexport interface KnowledgeIndex {\n readonly version: 1\n readonly builtAt: string\n readonly source: string\n readonly docs: readonly KnowledgeDoc[]\n readonly chunks: readonly KnowledgeChunk[]\n}\n\nexport interface KnowledgeHit {\n readonly docId: string\n readonly title: string\n readonly path: string\n readonly category: string\n readonly heading: string\n readonly snippet: string\n readonly score: number\n}\n\nconst LATIN = /[a-z0-9][a-z0-9_+.-]*/gi\nconst CJK = /[\\u4e00-\\u9fff]+/g\n\n/** Query terms: Latin words + Chinese bigrams (single char for one-char runs). */\nexport function knowledgeTerms(query: string): string[] {\n const terms: string[] = []\n for (const match of query.toLowerCase().matchAll(LATIN)) terms.push(match[0])\n for (const match of query.matchAll(CJK)) {\n const run = match[0]\n if (run.length === 1) terms.push(run)\n else for (let index = 0; index < run.length - 1; index += 1) terms.push(run.slice(index, index + 2))\n }\n return [...new Set(terms.filter(term => term.length > 0))]\n}\n\nfunction occurrences(text: string, term: string): number {\n if (term === '') return 0\n let count = 0\n let index = text.indexOf(term)\n while (index >= 0) {\n count += 1\n index = text.indexOf(term, index + term.length)\n }\n return count\n}\n\nfunction snippetFor(text: string, terms: readonly string[]): string {\n const lower = text.toLowerCase()\n let at = -1\n for (const term of terms) {\n const found = lower.indexOf(term)\n if (found >= 0 && (at < 0 || found < at)) at = found\n }\n const start = at < 0 ? 0 : Math.max(0, at - 60)\n return text.slice(start, start + 220).replace(/\\s+/g, ' ').trim()\n}\n\n/** Rank chunks by term frequency, heading/title boost, and exact phrase. */\nexport function searchKnowledge(index: KnowledgeIndex, query: string, limit = 8): KnowledgeHit[] {\n const terms = knowledgeTerms(query)\n if (terms.length === 0) return []\n const phrase = query.trim().toLowerCase()\n const hits: KnowledgeHit[] = []\n for (const chunk of index.chunks) {\n const text = chunk.text.toLowerCase()\n if (text.length === 0) continue\n let score = 0\n for (const term of terms) {\n const tf = occurrences(text, term)\n if (tf === 0) continue\n score += Math.min(tf, 12) * (term.length >= 3 ? 1.2 : 1)\n if (chunk.heading.toLowerCase().includes(term)) score += 3\n if (chunk.title.toLowerCase().includes(term)) score += 2\n }\n if (score === 0) continue\n if (phrase.length >= 2 && text.includes(phrase)) score += 6\n score /= Math.sqrt(Math.max(80, text.length) / 80)\n hits.push({\n docId: chunk.docId, title: chunk.title, path: chunk.path, category: chunk.category,\n heading: chunk.heading, snippet: snippetFor(chunk.text, terms), score: Number(score.toFixed(4)),\n })\n }\n return hits.sort((left, right) => right.score - left.score).slice(0, Math.max(1, Math.min(50, limit)))\n}\n\nexport function knowledgeStats(index: KnowledgeIndex): {\n docs: number; chunks: number; chars: number; categories: Record<string, number>\n} {\n const categories: Record<string, number> = {}\n for (const doc of index.docs) categories[doc.category] = (categories[doc.category] ?? 0) + 1\n return {\n docs: index.docs.length,\n chunks: index.chunks.length,\n chars: index.docs.reduce((sum, doc) => sum + doc.chars, 0),\n categories,\n }\n}\n\nexport function loadKnowledgeIndex(directory: string): KnowledgeIndex {\n const path = join(directory, 'index.json')\n const parsed = JSON.parse(readFileSync(path, 'utf8')) as KnowledgeIndex\n if (parsed.version !== 1 || !Array.isArray(parsed.chunks) || !Array.isArray(parsed.docs)) {\n throw new Error('knowledge index is invalid: ' + path)\n }\n statSync(path)\n return parsed\n}\n", "/** KSRW \u4EA7\u54C1 API\uFF08M0\uFF09\uFF1A\u9879\u76EE\u76EE\u5F55\u3001\u7814\u7A76\u72B6\u6001\u3001\u9009\u9898\u5361\u3001\u63D0\u6848/revision\u3002\n *\n * \u8DEF\u7531\u524D\u7F00 /ksrw/api\uFF1B\u540C\u6E90 cookie + \u5F15\u64CE connection \u56F4\u680F\uFF08\u82E5\u5BBF\u4E3B\u63D0\u4F9B\uFF09\u3002\n * \u4FE1\u5C01\uFF1A{ ok: true, value } | { ok: false, error: { code, message } }\u3002\n */\nimport { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'\nimport { connect } from 'node:net'\nimport { homedir } from 'node:os'\nimport { extname, join } from 'node:path'\n\nimport { UNAUTHENTICATED_ESCAPE } from './request-guard.ts'\n\nimport {\n KsrwError,\n ProjectStore,\n compilePaper,\n defaultPaperSource,\n exportPaperDocx,\n knowledgeStats,\n ksrwEnvelope,\n listPaperOutputs,\n loadKnowledgeIndex,\n invalidateStalePaperFinal,\n paperFinalStatus,\n readPaperBuild,\n paperStatus,\n addReadingPapers,\n listVenueTemplates,\n projectLockOwner,\n readPaperStyle,\n writePaperStyle,\n insertFigureIntoDraft,\n readVenueProfile,\n seedVenueChecklist,\n setVenueRequirement,\n writeVenueProfile,\n readReadingEntry,\n readReadingIndex,\n readReadingPrefs,\n readingFetchDue,\n readProjectFileBytes,\n resolveKsrwHome,\n runReadingFetch,\n searchLatestPapers,\n writeReadingDigest,\n writeReadingPrefs,\n searchKnowledge,\n MAX_PAPER_BYTES,\n analyzeStandalonePaper,\n createStandaloneExperiment,\n deleteStandaloneExperiment,\n ingestStandalonePaper,\n listStandaloneExperiments,\n readStandaloneExperiment,\n registerStandaloneExperiment,\n saveStandaloneDesign,\n writeStandaloneReport,\n writeStandaloneScaffold,\n MAX_WORKSHOP_ATTACHMENT_BYTES,\n createPaperWorkshop,\n deletePaperWorkshop,\n importPaperAttachment,\n listPaperWorkshops,\n readPaperWorkshop,\n readPaperWorkshopSource,\n registerPaperWorkshop,\n restorePaperWorkshopDraft,\n savePaperWorkshopDraft,\n RESEARCH_STAGES,\n type KnowledgeIndex,\n type ResearchStage,\n type TopicCard,\n} from 'ksrw-kernel'\n\nimport {\n cancelExperimentRun, cancelRemoteExperiment, listExperimentRuns, listExperimentScripts,\n pollRemoteExperiment, readRemoteRunInfo, readRunLog, startExperimentRun, startRemoteExperiment,\n} from './experiment.ts'\nimport { adoptFigure, adoptUnregisteredFigures, extractDocxMedia, ingestRunFigures, listFigures, readArchitectureSource, renderArchitecture, renderMetricsChart, saveArchitectureRender, validateFigures } from './figures.ts'\nimport { execRemoteTarget, listRemoteTargets, probeRemoteTarget, removeRemoteTarget, upsertRemoteTarget } from './remote.ts'\n\n/** \u5F15\u64CE office-to-pdf \u670D\u52A1\u7684\u6700\u5C0F\u7ED3\u6784\u9762\uFF08\u6302\u8F7D\u4E8E web \u7EC4\u5408\u5C42\uFF1B\u7F3A\u5931\u65F6\u9884\u89C8\u964D\u7EA7\u62A5\u9519\uFF09\u3002 */\nexport interface OfficePreviewService {\n convert(request: Record<string, unknown>): Promise<{ pdf: Uint8Array; missingFonts?: string[] }>\n}\n\nconst PAPER_DIRS = new Set(['paper', 'patent'])\n\nfunction paperDir(value: unknown): 'paper' | 'patent' {\n if (typeof value !== 'string' || !PAPER_DIRS.has(value)) throw new KsrwError('INVALID', 'dir \u5FC5\u987B\u662F paper \u6216 patent')\n return value as 'paper' | 'patent'\n}\n\nconst IMAGE_MIME: Record<string, string> = {\n '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif',\n '.webp': 'image/webp', '.svg': 'image/svg+xml', '.bmp': 'image/bmp',\n}\nconst DOWNLOAD_MIME: Record<string, string> = {\n '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n '.doc': 'application/msword',\n '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n '.pdf': 'application/pdf',\n '.md': 'text/markdown; charset=utf-8',\n '.txt': 'text/plain; charset=utf-8',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.svg': 'image/svg+xml',\n '.tex': 'text/plain; charset=utf-8',\n}\n\nconst OFFICE_EXTS = new Set(['doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx'])\nconst PREVIEW_TEXT_MAX = 1 * 1024 * 1024\nconst PREVIEW_PDF_MAX = 40 * 1024 * 1024\nconst PREVIEW_OFFICE_MAX = 50 * 1024 * 1024\n\ninterface ReqLike {\n url?: string\n method?: string\n headers?: Record<string, string | string[] | undefined>\n [Symbol.asyncIterator]?: () => AsyncIterator<Uint8Array | string>\n /** Node IncomingMessage: SSE \u7528\u5B83\u611F\u77E5\u5BA2\u6237\u7AEF\u65AD\u5F00\u3002 */\n on?: (event: 'close', listener: () => void) => void\n}\n/** SSE \u9700\u8981\u7684 Response \u9762\uFF08writeHead + write\uFF09\u3002 */\ninterface SseResponse {\n writeHead(code: number, headers?: Record<string, string>): unknown\n write(chunk: string): boolean\n}\ninterface ResLike {\n writeHead(code: number, headers?: Record<string, string>): unknown\n end(body?: unknown): unknown\n}\n\n/** \u5F15\u64CE connection \u670D\u52A1\u7684\u7ED3\u6784\u9762\uFF08\u53EA\u53D6\u672C\u63D2\u4EF6\u9700\u8981\u7684\u90A3\u4E00\u9879\uFF09\u3002 */\nexport interface ConnectionFence {\n requestRejection?(request: unknown): number | undefined\n}\n\n/** \u4E00\u6B21\u8BF7\u6C42\u7684\u51C6\u5165\u5224\u5B9A\uFF1A\u8FD4\u56DE HTTP \u72B6\u6001\u7801\u8868\u793A\u62D2\u7EDD\uFF0Cundefined \u8868\u793A\u653E\u884C\u3002 */\nexport type RequestGuard = (req: ReqLike) => number | undefined\n\n// \u5B88\u536B\u672C\u4F53\u4E0E\u9003\u751F\u53E3\u5E38\u91CF\u79FB\u5230\u96F6\u4F9D\u8D56\u7684 request-guard.ts\uFF1A\n// \u94C1\u5F8B\uFF08\u65B9\u6CD5\u5F62\u5F0F\u8C03\u7528\u5F15\u64CE\u56F4\u680F\u3001this \u4E0D\u5F97\u4E22\u5931\uFF09\u5728\u90A3\u91CC\u6709\u9632\u56DE\u5F52\u5355\u6D4B\u76F4\u63A5\u8986\u76D6\u3002\nexport { createRequestGuard, UNAUTHENTICATED_ESCAPE } from './request-guard.ts'\n\nconst MAX_BODY = 2 * 1024 * 1024\ninterface KnowledgeCacheEntry { readonly index: KnowledgeIndex; readonly directory: string; readonly mtimeMs: number }\nlet knowledgeCache: KnowledgeCacheEntry | undefined\n\n/** P2-13\uFF1A\u6309 index.json \u7684 mtime \u5931\u6548\uFF0Cimport:knowledge \u4E4B\u540E\u4E0D\u7528\u91CD\u542F\u5BBF\u4E3B\u3002 */\nfunction knowledgeIndex(): KnowledgeIndex {\n const directory = process.env['KSRW_KNOWLEDGE_DIR']?.trim() || join(resolveKsrwHome(), 'knowledge')\n let mtimeMs = -1\n try { mtimeMs = statSync(join(directory, 'index.json')).mtimeMs } catch { /* \u7F3A\u5931\u65F6\u4EA4\u7ED9 loader \u62A5\u9519 */ }\n if (knowledgeCache !== undefined && knowledgeCache.directory === directory && knowledgeCache.mtimeMs === mtimeMs) return knowledgeCache.index\n try {\n const index = loadKnowledgeIndex(directory)\n knowledgeCache = { index, directory, mtimeMs }\n return index\n } catch (error) {\n throw new KsrwError('NOT_FOUND', '\u77E5\u8BC6\u7D22\u5F15\u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u8BFB\uFF08' + directory + '\uFF09\uFF1B\u5148\u8FD0\u884C pnpm import:knowledge')\n }\n}\n\n/** P2-8\uFF1A\u5199\u8DEF\u5F84\u5148\u770B\u6709\u6CA1\u6709\u53E6\u4E00\u4E2A\u6D3B\u8FDB\u7A0B\u6301\u9501\uFF1B\u6709\u5C31\u76F4\u63A5 409\uFF0C\u4E0D\u5728\u4E8B\u4EF6\u5FAA\u73AF\u91CC\u540C\u6B65\u7B49 5s\u3002 */\nfunction assertProjectWritable(projectPath: string): void {\n const owner = projectLockOwner(projectPath)\n if (owner === null) return\n throw new KsrwError('LOCKED', '\u9879\u76EE\u6B63\u88AB\u53E6\u4E00\u4E2A\u8FDB\u7A0B\u5199\u5165\uFF08pid ' + owner.pid + '\uFF0C\u5DF2\u6301\u6709 ' + Math.round((Date.now() - owner.at) / 1000) + 's\uFF09\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5')\n}\nconst STAGES: readonly ResearchStage[] = RESEARCH_STAGES\n\n/** \u4EA7\u54C1\u4E8B\u4EF6\u6D41\uFF1A\u5199\u64CD\u4F5C\u540E\u5411\u8BA2\u9605\u7684 UI \u63A8\u4E00\u6761\u300C\u8BE5\u9879\u76EE\u53D8\u4E86\u300D\uFF0C\u66FF\u4EE3 4s/5s \u8F6E\u8BE2\u3002 */\ninterface ProjectEventClient {\n readonly res: SseResponse\n readonly path: string\n readonly timer: ReturnType<typeof setInterval>\n}\nconst projectEventClients = new Set<ProjectEventClient>()\nfunction notifyProject(path: string): void {\n if (projectEventClients.size === 0) return\n const payload = 'data: ' + JSON.stringify({ path, at: Date.now() }) + '\\n\\n'\n for (const client of projectEventClients) {\n if (client.path !== '' && client.path !== path) continue\n try {\n client.res.write(payload)\n } catch {\n projectEventClients.delete(client)\n clearInterval(client.timer)\n }\n }\n}\n\nfunction send(res: ResLike, status: number, value: unknown): void {\n res.writeHead(status, { 'content-type': 'application/json; charset=utf-8' })\n res.end(JSON.stringify(value))\n}\nfunction ok(res: ResLike, value: unknown): void {\n send(res, 200, { ok: true, value })\n}\nfunction fail(res: ResLike, error: unknown): void {\n const envelope = ksrwEnvelope(error)\n const status = error instanceof KsrwError\n ? error.code === 'NOT_FOUND' ? 404\n : error.code === 'STALE_REVISION' ? 409\n // \u53E6\u4E00\u4E2A\u8FDB\u7A0B\u6B63\u6301\u9501\u5199\u540C\u4E00\u9879\u76EE\uFF1A409 \u8BA9 UI \u63D0\u793A\u300C\u7A0D\u540E\u91CD\u8BD5\u300D\uFF0C\u800C\u4E0D\u662F\u5F53\u6210\u53C2\u6570\u9519\u8BEF\u3002\n : error.code === 'LOCKED' ? 409\n : error.code === 'DUPLICATE' ? 409\n : error.code === 'PATH_DENIED' ? 403\n : 400\n : 500\n send(res, status, { ok: false, error: envelope })\n}\n\nasync function readBody(req: ReqLike): Promise<Record<string, unknown>> {\n if (req[Symbol.asyncIterator] === undefined) return {}\n const chunks: Buffer[] = []\n let bytes = 0\n for await (const chunk of req as AsyncIterable<Uint8Array | string>) {\n const buffer = typeof chunk === 'string' ? Buffer.from(chunk) : Buffer.from(chunk)\n bytes += buffer.byteLength\n if (bytes > MAX_BODY) throw new KsrwError('INVALID', '\u8BF7\u6C42\u4F53\u8D85\u8FC7 2MiB')\n chunks.push(buffer)\n }\n if (bytes === 0) return {}\n try {\n const parsed: unknown = JSON.parse(Buffer.concat(chunks).toString('utf8'))\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new KsrwError('INVALID', '\u8BF7\u6C42\u4F53\u5FC5\u987B\u662F JSON \u5BF9\u8C61')\n }\n return parsed as Record<string, unknown>\n } catch (error) {\n if (error instanceof KsrwError) throw error\n throw new KsrwError('INVALID', '\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5 JSON')\n }\n}\n\n/** \u539F\u59CB\u5B57\u8282\u8BF7\u6C42\u4F53\uFF08\u8BBA\u6587\u9644\u4EF6\uFF09\uFF1A\u6309\u7D2F\u8BA1\u5B57\u8282\u6570\u6D41\u5F0F\u9650\u6D41\uFF0C\u8D85\u9650\u7ACB\u523B\u4E2D\u65AD\uFF0C\u4E0D\u5148\u8BFB\u5168\u91CF\u518D\u5224\u65AD\u3002 */\nexport async function readRawBody(req: ReqLike, limit: number): Promise<Buffer> {\n if (req[Symbol.asyncIterator] === undefined) {\n throw new KsrwError('INVALID', '\u7F3A\u5C11\u8BF7\u6C42\u4F53\uFF1A\u8BF7\u9009\u62E9\u8981\u4E0A\u4F20\u7684\u8BBA\u6587\u9644\u4EF6\uFF08PDF / DOCX / MD / TXT\uFF09')\n }\n const chunks: Buffer[] = []\n let bytes = 0\n for await (const chunk of req as AsyncIterable<Uint8Array | string>) {\n const buffer = typeof chunk === 'string' ? Buffer.from(chunk) : Buffer.from(chunk)\n bytes += buffer.byteLength\n if (bytes > limit) {\n throw new KsrwError('INVALID', '\u9644\u4EF6\u8D85\u8FC7\u4E0A\u9650\uFF08' + formatBytes(limit) + '\uFF09\uFF1A\u53EA\u652F\u6301 PDF / DOCX / MD / TXT\uFF0C\u8BF7\u538B\u7F29\u6216\u62C6\u5206\u540E\u91CD\u4F20')\n }\n chunks.push(buffer)\n }\n if (bytes === 0) throw new KsrwError('INVALID', '\u9644\u4EF6\u4E3A\u7A7A\uFF1A\u8BF7\u4E0A\u4F20 PDF / DOCX / MD / TXT \u8BBA\u6587\u6587\u4EF6')\n return Buffer.concat(chunks, bytes)\n}\n\nfunction formatBytes(limit: number): string {\n if (limit >= 1024 * 1024 && limit % (1024 * 1024) === 0) return String(limit / (1024 * 1024)) + 'MiB'\n return String(limit) + ' \u5B57\u8282'\n}\n\nfunction text(value: unknown, field: string): string {\n if (typeof value !== 'string' || value.trim() === '') throw new KsrwError('INVALID', field + ' \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32')\n return value.trim()\n}\nfunction optionalText(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim() !== '' ? value.trim() : undefined\n}\nfunction stageOf(value: unknown): ResearchStage {\n if (typeof value !== 'string' || !STAGES.includes(value as ResearchStage)) {\n throw new KsrwError('INVALID', '\u672A\u77E5\u9636\u6BB5\uFF1A' + String(value))\n }\n return value as ResearchStage\n}\nfunction topicOf(value: unknown): Omit<TopicCard, 'revision' | 'updatedAt'> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new KsrwError('INVALID', 'topic \u5FC5\u987B\u662F\u5BF9\u8C61')\n }\n const card = value as Record<string, unknown>\n const feasibility = card['feasibility']\n if (typeof feasibility !== 'object' || feasibility === null) throw new KsrwError('INVALID', 'topic.feasibility \u5FC5\u987B\u662F\u5BF9\u8C61')\n const f = feasibility as Record<string, unknown>\n const decision = card['decision']\n if (decision !== 'pending' && decision !== 'go' && decision !== 'no-go') {\n throw new KsrwError('INVALID', 'topic.decision \u5FC5\u987B\u662F pending/go/no-go')\n }\n return {\n question: text(card['question'], 'question'),\n motivation: typeof card['motivation'] === 'string' ? card['motivation'] : '',\n gap: typeof card['gap'] === 'string' ? card['gap'] : '',\n novelty: Array.isArray(card['novelty']) ? card['novelty'].filter((x): x is string => typeof x === 'string') : [],\n feasibility: {\n data: typeof f['data'] === 'string' ? f['data'] : '',\n compute: typeof f['compute'] === 'string' ? f['compute'] : '',\n time: typeof f['time'] === 'string' ? f['time'] : '',\n skill: typeof f['skill'] === 'string' ? f['skill'] : '',\n },\n risks: Array.isArray(card['risks']) ? card['risks'].filter((x): x is string => typeof x === 'string') : [],\n decision,\n }\n}\n\n/** \u6210\u679C\u6587\u4EF6\u9884\u89C8\uFF1A\u6587\u672C / \u56FE\u7247 / PDF \u76F4\u8BFB\uFF1BOffice \u8D70\u5F15\u64CE office-to-pdf \u8F6C\u6362\u3002 */\nasync function previewFile(store: ProjectStore, office: OfficePreviewService | undefined, body: Record<string, unknown>): Promise<Record<string, unknown>> {\n const projectPath = text(body['path'], 'path')\n const file = text(body['file'], 'file')\n const project = store.open(projectPath).project\n const extension = file.slice(file.lastIndexOf('.')).toLowerCase()\n if (extension === '.md' || extension === '.markdown' || extension === '.txt') {\n const bytes = readProjectFileBytes(project.path, file, PREVIEW_TEXT_MAX)\n return { kind: 'text', name: file, text: Buffer.from(bytes).toString('utf8') }\n }\n const mime = IMAGE_MIME[extension]\n if (mime !== undefined) {\n const bytes = readProjectFileBytes(project.path, file, PREVIEW_PDF_MAX)\n return { kind: 'image', name: file, mime, base64: Buffer.from(bytes).toString('base64') }\n }\n if (extension === '.pdf') {\n const bytes = readProjectFileBytes(project.path, file, PREVIEW_PDF_MAX)\n return { kind: 'pdf', name: file, base64: Buffer.from(bytes).toString('base64'), missingFonts: [] }\n }\n const officeExt = extension.startsWith('.') ? extension.slice(1) : extension\n if (!OFFICE_EXTS.has(officeExt)) throw new KsrwError('INVALID', '\u4E0D\u652F\u6301\u7684\u9884\u89C8\u683C\u5F0F\uFF1A' + file)\n if (office === undefined || typeof office.convert !== 'function') {\n throw new KsrwError('ENGINE_MISSING', 'Office \u9884\u89C8\u670D\u52A1\u672A\u542F\u7528\uFF08\u5F15\u64CE office-to-pdf \u672A\u6302\u8F7D\uFF09\uFF1B\u8BF7\u6539\u7528 PDF')\n }\n const bytes = readProjectFileBytes(project.path, file, PREVIEW_OFFICE_MAX)\n const result = await office.convert({\n extension: officeExt,\n priority: 'foreground',\n source: {\n key: 'ksrw/' + file,\n version: String(Date.now()) + '-' + bytes.byteLength,\n bytes: bytes.byteLength,\n read: async () => ({ bytes, version: String(Date.now()) + '-' + bytes.byteLength }),\n },\n })\n return {\n kind: 'pdf', name: file,\n base64: Buffer.from(result.pdf).toString('base64'),\n missingFonts: result.missingFonts ?? [],\n }\n}\n\n/** \u628A\u5B88\u536B\u7ED3\u8BBA\u843D\u6210\u62D2\u7EDD\u54CD\u5E94\uFF08/ksrw/api \u4E0E /ksrw/api/lab \u5171\u7528\u540C\u4E00\u53E3\u5F84\uFF09\u3002 */\nexport function rejectRequest(res: ResLike, status: number): void {\n // 503 \u53EA\u53EF\u80FD\u6765\u81EA fail-closed\uFF08\u5BBF\u4E3B\u6CA1\u7ED9\u56F4\u680F\uFF09\uFF1B401/403 \u662F\u5F15\u64CE\u4F1A\u8BDD\u6821\u9A8C\u7684\u7ED3\u8BBA\u3002\n if (status === 503) {\n send(res, 503, {\n ok: false,\n error: {\n code: 'AUTH_UNAVAILABLE',\n message: 'API \u672A\u542F\u7528\u9274\u6743\uFF08\u5BBF\u4E3B\u672A\u6302\u8F7D connection\uFF09\uFF1A\u8BF7\u7528\u684C\u9762\u7AEF\u767B\u5F55\u540E\u4F7F\u7528\uFF1B'\n + `\u5F00\u53D1\u6001\u786E\u9700\u88F8 API \u65F6\u8BBE\u7F6E ${UNAUTHENTICATED_ESCAPE}=1`,\n },\n })\n return\n }\n send(res, status, { ok: false, error: { code: 'AUTH_MISSING', message: '\u8BF7\u5148\u767B\u5F55' } })\n}\n\n/** Handle one /ksrw/api request. */\n/** \u5F15\u64CE\u300C\u81EA\u52A8\u5316\u4EFB\u52A1\u300D\u670D\u52A1\u9762\uFF08\u53EA\u7528\u5230 create\uFF09\u3002 */\nexport interface EngineScheduleService {\n create(request: Record<string, unknown>): Promise<unknown>\n}\n\nexport async function handleApi(\n req: ReqLike,\n res: ResLike,\n store: ProjectStore,\n guard: RequestGuard,\n office?: OfficePreviewService,\n schedule?: EngineScheduleService | null,\n): Promise<void> {\n try {\n const rejection = guard(req)\n if (typeof rejection === 'number') {\n rejectRequest(res, rejection)\n return\n }\n const url = new URL(req.url ?? '/ksrw/api', 'http://localhost')\n const pathname = url.pathname\n const method = (req.method ?? 'GET').toUpperCase()\n if (pathname === '/ksrw/api/events' && method === 'GET') {\n const watch = url.searchParams.get('path') ?? ''\n const sse = res as unknown as SseResponse\n sse.writeHead(200, {\n 'content-type': 'text/event-stream; charset=utf-8',\n 'cache-control': 'no-cache, no-transform',\n connection: 'keep-alive',\n 'x-accel-buffering': 'no',\n })\n sse.write(': connected\\n\\n')\n const timer = setInterval(() => { try { sse.write(': ping\\n\\n') } catch { /* client gone */ } }, 25_000)\n timer.unref?.()\n const client: ProjectEventClient = { res: sse, path: watch, timer }\n projectEventClients.add(client)\n req.on?.('close', () => {\n projectEventClients.delete(client)\n clearInterval(timer)\n })\n return\n }\n if (pathname === '/ksrw/api/health') {\n // \u6570\u636E\u6839\u7531\u5BBF\u4E3B\u89E3\u6790\uFF08\u63D2\u4EF6\u6A21\u5F0F\u843D\u5728\u5BBF\u4E3B home\uFF0C\u72EC\u7ACB\u4EA7\u54C1\u843D ~/.ksrw\uFF09\uFF1A\u6587\u6848\u91CC\u522B\u5199\u6B7B\u8DEF\u5F84\u3002\n const home = resolveKsrwHome()\n const label = home.startsWith(homedir()) ? '~' + home.slice(homedir().length) : home\n ok(res, { product: 'KSRW', version: '0.1.1', home, homeLabel: label })\n return\n }\n if (pathname === '/ksrw/api/project/figures' && method === 'GET') {\n ok(res, listFigures(text(url.searchParams.get('path'), 'path')))\n return\n }\n if (pathname === '/ksrw/api/project/figures/ingest' && method === 'POST') {\n const body = await readBody(req)\n ok(res, ingestRunFigures(text(body['path'], 'path'), text(body['runId'], 'runId')))\n return\n }\n if (pathname === '/ksrw/api/project/figures/chart' && method === 'POST') {\n const body = await readBody(req)\n ok(res, renderMetricsChart(text(body['path'], 'path'), text(body['runId'], 'runId')))\n return\n }\n if (pathname === '/ksrw/api/project/figures/adopt' && method === 'POST') {\n const body = await readBody(req)\n const caption = optionalText(body['caption'])\n const title = optionalText(body['title'])\n ok(res, adoptFigure(text(body['path'], 'path'), {\n file: text(body['file'], 'file'),\n ...(caption === undefined ? {} : { caption }),\n ...(title === undefined ? {} : { title }),\n ...(optionalText(body['draft']) === undefined ? {} : { draft: optionalText(body['draft'])! }),\n }))\n return\n }\n if (pathname === '/ksrw/api/project/figures/adopt-all' && method === 'POST') {\n const body = await readBody(req)\n ok(res, { adopted: adoptUnregisteredFigures(text(body['path'], 'path')) })\n return\n }\n if (pathname === '/ksrw/api/project/figures/validate' && method === 'GET') {\n const projectPath = text(url.searchParams.get('path'), 'path')\n const file = optionalText(url.searchParams.get('file'))\n const issues = validateFigures(projectPath, file === undefined ? {} : { file }) as { file: string; level: 'error' | 'warn'; message: string }[]\n ok(res, { issues, ok: issues.filter(item => item.level === 'error').length === 0 })\n return\n }\n if (pathname === '/ksrw/api/project/figures/architecture/source' && method === 'GET') {\n ok(res, readArchitectureSource(text(url.searchParams.get('path'), 'path')))\n return\n }\n if (pathname === '/ksrw/api/project/figures/architecture/render' && method === 'POST') {\n const body = await readBody(req)\n ok(res, saveArchitectureRender(text(body['path'], 'path'), {\n source: text(body['source'], 'source'),\n svg: text(body['svg'], 'svg'),\n pngBase64: text(body['pngBase64'], 'pngBase64'),\n }))\n return\n }\n if (pathname === '/ksrw/api/project/figures/architecture' && method === 'POST') {\n const body = await readBody(req)\n const projectPath = text(body['path'], 'path')\n const current = text(body['current'], 'current')\n ok(res, renderArchitecture(projectPath, current, text(body['subtitle'] ?? 'KSRW \u7814\u7A76\u6D41\u6C34\u7EBF', 'subtitle')))\n return\n }\n if (pathname === '/ksrw/api/experiment/scripts') {\n ok(res, listExperimentScripts(text(url.searchParams.get('path'), 'path')))\n return\n }\n if (pathname === '/ksrw/api/experiment/runs') {\n ok(res, listExperimentRuns(text(url.searchParams.get('path'), 'path')))\n return\n }\n if (pathname === '/ksrw/api/experiment/runs/log') {\n ok(res, { log: readRunLog(text(url.searchParams.get('path'), 'path'), text(url.searchParams.get('id'), 'id')) })\n return\n }\n if (pathname === '/ksrw/api/experiment/remote/run' && method === 'POST') {\n const body = await readBody(req)\n const args = Array.isArray(body['args']) ? body['args'].map(item => String(item)) : []\n const python = optionalText(body['python']) ?? ''\n ok(res, await startRemoteExperiment(text(body['path'], 'path'), text(body['targetId'], 'targetId'), text(body['script'], 'script'), args, python))\n return\n }\n if (pathname === '/ksrw/api/experiment/remote/status') {\n const projectPath = text(url.searchParams.get('path'), 'path')\n const id = text(url.searchParams.get('id'), 'id')\n ok(res, { run: await pollRemoteExperiment(projectPath, id), remote: readRemoteRunInfo(projectPath, id) ?? null })\n return\n }\n if (pathname === '/ksrw/api/experiment/remote/cancel' && method === 'POST') {\n const body = await readBody(req)\n ok(res, { cancelled: await cancelRemoteExperiment(text(body['path'], 'path'), text(body['id'], 'id')) })\n return\n }\n if (pathname === '/ksrw/api/experiment/run' && method === 'POST') {\n const body = await readBody(req)\n const args = Array.isArray(body['args']) ? body['args'].map(item => String(item)) : []\n ok(res, startExperimentRun(text(body['path'], 'path'), text(body['script'], 'script'), args))\n return\n }\n if (pathname === '/ksrw/api/experiment/runs/cancel' && method === 'POST') {\n const body = await readBody(req)\n // P2-12\uFF1A\u5E26\u9879\u76EE\u4F5C\u7528\u57DF\uFF1B\u65E7\u8C03\u7528\u65B9\u4E0D\u4F20 path \u65F6\u4FDD\u6301\u539F\u884C\u4E3A\u3002\n const projectPath = typeof body['path'] === 'string' || body['path'] instanceof String ? String(body['path']) : undefined\n ok(res, { cancelled: cancelExperimentRun(text(body['id'], 'id'), projectPath) })\n return\n }\n if (pathname === '/ksrw/api/remote/targets' && method === 'GET') {\n ok(res, listRemoteTargets())\n return\n }\n if (pathname === '/ksrw/api/remote/targets' && method === 'POST') {\n const body = await readBody(req)\n const id = optionalText(body['id'])\n const password = optionalText(body['password'])\n const keyPath = optionalText(body['keyPath'])\n const passphrase = optionalText(body['passphrase'])\n const workdir = optionalText(body['workdir'])\n ok(res, upsertRemoteTarget({\n ...(id === undefined ? {} : { id }),\n name: text(body['name'], 'name'),\n host: text(body['host'], 'host'),\n port: Number(body['port'] ?? 22),\n user: text(body['user'], 'user'),\n ...(body['authKind'] === 'key' ? { authKind: 'key' as const }\n : body['authKind'] === 'agent' ? { authKind: 'agent' as const }\n : {}),\n ...(password === undefined ? {} : { password }),\n ...(keyPath === undefined ? {} : { keyPath }),\n ...(passphrase === undefined ? {} : { passphrase }),\n ...(workdir === undefined ? {} : { workdir }),\n ...(optionalText(body['pythonPath']) === undefined ? {} : { pythonPath: optionalText(body['pythonPath'])! }),\n ...(optionalText(body['pipIndexUrl']) === undefined ? {} : { pipIndexUrl: optionalText(body['pipIndexUrl'])! }),\n }))\n return\n }\n if (pathname === '/ksrw/api/remote/targets/delete' && method === 'POST') {\n const body = await readBody(req)\n ok(res, { removed: removeRemoteTarget(text(body['id'], 'id')) })\n return\n }\n if (pathname === '/ksrw/api/remote/test' && method === 'POST') {\n const body = await readBody(req)\n ok(res, await probeRemoteTarget(text(body['id'], 'id')))\n return\n }\n if (pathname === '/ksrw/api/remote/exec' && method === 'POST') {\n const body = await readBody(req)\n ok(res, await execRemoteTarget(text(body['id'], 'id'), text(body['command'], 'command'), Number(body['timeoutMs'] ?? 120000)))\n return\n }\n if (pathname === '/ksrw/api/remote/probe') {\n const host = text(url.searchParams.get('host'), 'host')\n const port = Number.parseInt(text(url.searchParams.get('port'), 'port'), 10)\n const started = Date.now()\n const reachable = await new Promise<boolean>((resolve) => {\n const socket = connect({ host, port })\n const finish = (value: boolean): void => { socket.destroy(); resolve(value) }\n socket.setTimeout(8000)\n socket.once('connect', () => finish(true))\n socket.once('timeout', () => finish(false))\n socket.once('error', () => finish(false))\n })\n ok(res, { host, port, reachable, latencyMs: Date.now() - started })\n return\n }\n if (pathname === '/ksrw/api/knowledge/stats') { ok(res, knowledgeStats(knowledgeIndex())); return }\n if (pathname === '/ksrw/api/knowledge/search') {\n const query = text(url.searchParams.get('q'), 'q')\n const limit = Number.parseInt(url.searchParams.get('limit') ?? '8', 10)\n ok(res, searchKnowledge(knowledgeIndex(), query, Number.isFinite(limit) ? limit : 8))\n return\n }\n\n // \u2500\u2500 \u514D\u9879\u76EE\u5B9E\u9A8C\u5DE5\u574A\uFF08standalone\uFF09\uFF1A\u4E0A\u4F20\u8BBA\u6587 \u2192 \u62BD\u53D6\u8981\u7D20 \u2192 \u8BBE\u8BA1 \u2192 \u62A5\u544A \u2500\u2500\n if (pathname === '/ksrw/api/standalone' && method === 'GET') {\n // \u65E0 path \u53C2\u6570 = \u5217\u8868\uFF1B\u5E26 path \u4F46\u4E3A\u7A7A = \u53C2\u6570\u9519\u8BEF\uFF08\u522B\u9759\u9ED8\u8FD4\u56DE\u5217\u8868\uFF09\u3002\n const projectPath = url.searchParams.get('path')\n if (projectPath === null) { ok(res, { items: listStandaloneExperiments() }); return }\n ok(res, readStandaloneExperiment(text(projectPath, 'path')))\n return\n }\n if (pathname === '/ksrw/api/standalone' && method === 'POST') {\n const body = await readBody(req)\n const name = optionalText(body['name'])\n ok(res, createStandaloneExperiment(name === undefined ? {} : { name }))\n return\n }\n if (pathname === '/ksrw/api/standalone/delete' && method === 'POST') {\n const body = await readBody(req)\n ok(res, { removed: deleteStandaloneExperiment(text(body['path'], 'path')) })\n return\n }\n if (pathname === '/ksrw/api/standalone/register' && method === 'POST') {\n const body = await readBody(req)\n ok(res, registerStandaloneExperiment(text(body['path'], 'path')))\n return\n }\n if (pathname === '/ksrw/api/standalone/paper' && method === 'POST') {\n // \u9644\u4EF6\u662F\u539F\u59CB\u5B57\u8282\uFF08content-type \u4EFB\u610F\uFF0C\u4E0D\u662F JSON\uFF09\uFF1A\u72EC\u7ACB\u9650\u6D41\uFF0C\u6269\u5C55\u540D\u767D\u540D\u5355\u4EA4\u7ED9 kernel\u3002\n const projectPath = text(url.searchParams.get('path'), 'path')\n const fileName = text(url.searchParams.get('name'), 'name')\n const bytes = await readRawBody(req, MAX_PAPER_BYTES)\n ok(res, await ingestStandalonePaper(projectPath, { fileName, bytes }))\n return\n }\n if (pathname === '/ksrw/api/standalone/paper' && method === 'GET') {\n const detail = readStandaloneExperiment(text(url.searchParams.get('path'), 'path'))\n const paper = detail.experiment.paper\n if (paper === null) {\n throw new KsrwError('NOT_FOUND', '\u8FD9\u4E2A\u5DE5\u574A\u8FD8\u6CA1\u6709\u8BBA\u6587\u9644\u4EF6\uFF1A\u5148\u4E0A\u4F20 PDF / DOCX / MD / TXT')\n }\n ok(res, {\n name: paper.fileName, kind: paper.kind, title: paper.title,\n pages: paper.pages, chars: paper.chars, text: detail.paperText,\n })\n return\n }\n if (pathname === '/ksrw/api/standalone/analyze' && method === 'POST') {\n const body = await readBody(req)\n ok(res, analyzeStandalonePaper(text(body['path'], 'path')))\n return\n }\n if (pathname === '/ksrw/api/standalone/design' && method === 'POST') {\n const body = await readBody(req)\n const design = saveStandaloneDesign(text(body['path'], 'path'), text(body['content'], 'content'))\n ok(res, { revision: design.revision, updatedAt: design.updatedAt })\n return\n }\n if (pathname === '/ksrw/api/standalone/design/scaffold' && method === 'POST') {\n const body = await readBody(req)\n const projectPath = text(body['path'], 'path')\n ok(res, writeStandaloneScaffold(projectPath, body['overwrite'] === true ? { overwrite: true } : {}))\n return\n }\n if (pathname === '/ksrw/api/standalone/report' && method === 'POST') {\n const body = await readBody(req)\n ok(res, writeStandaloneReport(text(body['path'], 'path')))\n return\n }\n\n // \u2500\u2500 \u514D\u9879\u76EE\u8BBA\u6587\u5DE5\u574A\uFF08paper workshop\uFF09\uFF1A\u4E0A\u4F20\u5386\u53F2\u8BBA\u6587 \u2192 \u6539\u6B63\u6587 \u2192 \u7F16\u8BD1/\u5BFC\u51FA \u2192 \u53EF\u767B\u8BB0\u4E3A\u9879\u76EE \u2500\u2500\n if (pathname === '/ksrw/api/paper-workshop' && method === 'GET') {\n // \u65E0 path \u53C2\u6570 = \u5217\u8868\uFF1B\u5E26 path \u4F46\u4E3A\u7A7A = \u53C2\u6570\u9519\u8BEF\uFF08\u522B\u9759\u9ED8\u8FD4\u56DE\u5217\u8868\uFF09\u3002\n const workshopPath = url.searchParams.get('path')\n if (workshopPath === null) { ok(res, { items: listPaperWorkshops() }); return }\n ok(res, readPaperWorkshop(text(workshopPath, 'path')))\n return\n }\n if (pathname === '/ksrw/api/paper-workshop' && method === 'POST') {\n const body = await readBody(req)\n const name = optionalText(body['name'])\n ok(res, createPaperWorkshop(name === undefined ? {} : { name }))\n return\n }\n if (pathname === '/ksrw/api/paper-workshop/delete' && method === 'POST') {\n const body = await readBody(req)\n ok(res, { removed: deletePaperWorkshop(text(body['path'], 'path')) })\n return\n }\n if (pathname === '/ksrw/api/paper-workshop/register' && method === 'POST') {\n const body = await readBody(req)\n ok(res, registerPaperWorkshop(text(body['path'], 'path')))\n return\n }\n if (pathname === '/ksrw/api/paper-workshop/import' && method === 'POST') {\n // \u9644\u4EF6\u662F\u539F\u59CB\u5B57\u8282\uFF08content-type \u4EFB\u610F\uFF0C\u4E0D\u662F JSON\uFF09\uFF1A\u72EC\u7ACB\u9650\u6D41\uFF0C\u6269\u5C55\u540D\u767D\u540D\u5355\u4EA4\u7ED9 kernel\u3002\n const workshopPath = text(url.searchParams.get('path'), 'path')\n const fileName = text(url.searchParams.get('name'), 'name')\n const bytes = await readRawBody(req, MAX_WORKSHOP_ATTACHMENT_BYTES)\n ok(res, await importPaperAttachment(workshopPath, { fileName, bytes }))\n return\n }\n // \u4ECE\u9644\u4EF6 DOCX \u91CC\u62BD\u5185\u5D4C\u539F\u56FE\uFF08\u5BFC\u5165\u65F6\u5DF2\u81EA\u52A8\u62BD\uFF1B\u8FD9\u91CC\u7528\u4E8E\u8865\u62BD / \u6362\u9644\u4EF6\u540E\u91CD\u62BD\uFF09\u3002\n if (pathname === '/ksrw/api/paper-workshop/docx-media' && method === 'POST') {\n const body = await readBody(req)\n const workshopPath = text(body['path'], 'path')\n const docx = optionalText(body['docx'])\n ok(res, extractDocxMedia(workshopPath, docx === undefined ? {} : { docx }))\n return\n }\n if (pathname === '/ksrw/api/paper-workshop/source' && method === 'GET') {\n const workshopPath = text(url.searchParams.get('path'), 'path')\n const detail = readPaperWorkshop(workshopPath)\n const source = detail.workshop.source\n if (source === null) {\n throw new KsrwError('NOT_FOUND', '\u8FD9\u4E2A\u8BBA\u6587\u5DE5\u574A\u8FD8\u6CA1\u6709\u4E0A\u4F20\u8FC7\u539F\u6587\uFF1A\u5148\u4E0A\u4F20 PDF / DOCX / MD / TXT \u8BBA\u6587\u9644\u4EF6')\n }\n const raw = readPaperWorkshopSource(workshopPath)\n ok(res, {\n name: source.fileName,\n kind: source.kind,\n title: source.title,\n pages: source.pages,\n chars: raw.chars,\n text: raw.text,\n })\n return\n }\n if (pathname === '/ksrw/api/paper-workshop/draft' && method === 'POST') {\n const body = await readBody(req)\n const workshopPath = text(body['path'], 'path')\n // \u6B63\u6587\u53EF\u80FD\u6709\u610F\u7684\u9996\u5C3E\u7A7A\u884C\uFF1A\u53EA\u6821\u9A8C\u7C7B\u578B\uFF0C\u4E0D trim\uFF1B\u7A7A\u6B63\u6587\u7531 kernel \u62D2\u7EDD\u3002\n const content = body['content']\n if (typeof content !== 'string') throw new KsrwError('INVALID', 'content \u5FC5\u987B\u662F\u8BBA\u6587\u6B63\u6587\u6587\u672C\uFF08Markdown\uFF09')\n const draft = savePaperWorkshopDraft(workshopPath, content)\n ok(res, { revision: draft.revision, updatedAt: draft.updatedAt })\n return\n }\n if (pathname === '/ksrw/api/paper-workshop/restore' && method === 'POST') {\n const body = await readBody(req)\n const workshopPath = text(body['path'], 'path')\n restorePaperWorkshopDraft(workshopPath)\n ok(res, readPaperWorkshop(workshopPath))\n return\n }\n\n if (pathname === '/ksrw/api/projects' && method === 'GET') { ok(res, store.list()); return }\n if (pathname === '/ksrw/api/projects' && method === 'POST') {\n const body = await readBody(req)\n const project = store.create({\n name: text(body['name'], 'name'),\n ...(optionalText(body['idea']) === undefined ? {} : { idea: optionalText(body['idea'])! }),\n ...(optionalText(body['directory']) === undefined ? {} : { directory: optionalText(body['directory'])! }),\n })\n ok(res, store.open(project.path))\n notifyProject('')\n return\n }\n if (pathname === '/ksrw/api/projects/forget' && method === 'POST') {\n const body = await readBody(req)\n store.forget(text(body['path'], 'path'))\n ok(res, { forgotten: true })\n notifyProject('')\n return\n }\n if (pathname === '/ksrw/api/projects/register' && method === 'POST') {\n const body = await readBody(req)\n const registered = store.register(text(body['directory'], 'directory'))\n ok(res, registered)\n notifyProject('')\n return\n }\n\n if (pathname === '/ksrw/api/project' && method === 'GET') {\n const projectPath = text(url.searchParams.get('path'), 'path')\n ok(res, store.open(projectPath))\n return\n }\n if (pathname === '/ksrw/api/project/artifact' && method === 'GET') {\n const projectPath = text(url.searchParams.get('path'), 'path')\n const ref = text(url.searchParams.get('ref'), 'ref')\n ok(res, store.readArtifact(projectPath, ref))\n return\n }\n if (pathname === '/ksrw/api/project/artifact' && method === 'POST') {\n const body = await readBody(req)\n const projectPath = text(body['path'], 'path')\n assertProjectWritable(projectPath)\n ok(res, store.writeArtifact(projectPath, text(body['ref'], 'ref'), text(body['content'], 'content')))\n notifyProject(projectPath)\n return\n }\n if (pathname === '/ksrw/api/project/topic' && method === 'POST') {\n const body = await readBody(req)\n const projectPath = text(body['path'], 'path')\n assertProjectWritable(projectPath)\n ok(res, store.saveTopic(projectPath, topicOf(body['topic'])))\n notifyProject(projectPath)\n return\n }\n if (pathname === '/ksrw/api/project/stage' && method === 'POST') {\n const body = await readBody(req)\n const projectPath = text(body['path'], 'path')\n assertProjectWritable(projectPath)\n ok(res, store.setStage(projectPath, stageOf(body['stage'])))\n notifyProject(projectPath)\n return\n }\n if (pathname === '/ksrw/api/project/gate' && method === 'POST') {\n const body = await readBody(req)\n const projectPath = text(body['path'], 'path')\n assertProjectWritable(projectPath)\n ok(res, store.gate(projectPath, stageOf(body['stage'])))\n notifyProject(projectPath)\n return\n }\n if (pathname === '/ksrw/api/project/handoffs' && method === 'GET') {\n const projectPath = text(url.searchParams.get('path'), 'path')\n ok(res, store.readHandoffs(projectPath))\n return\n }\n if (pathname === '/ksrw/api/project/handoffs/status' && method === 'POST') {\n const body = await readBody(req)\n const status = text(body['status'], 'status')\n if (!['open', 'started', 'accepted', 'returned', 'done'].includes(status)) {\n throw new KsrwError('INVALID', '\u672A\u77E5\u4EA4\u63A5\u72B6\u6001\uFF1A' + status)\n }\n const note = optionalText(body['note'])\n const projectPath = text(body['path'], 'path')\n assertProjectWritable(projectPath)\n ok(res, store.updateHandoff(projectPath, text(body['id'], 'id'),\n status as 'open' | 'started' | 'accepted' | 'returned' | 'done', note))\n notifyProject(projectPath)\n return\n }\n if (pathname === '/ksrw/api/project/handoffs' && method === 'POST') {\n const body = await readBody(req)\n const projectPath = text(body['path'], 'path')\n assertProjectWritable(projectPath)\n ok(res, store.appendHandoff(projectPath, {\n from: optionalText(body['from']) ?? 'user',\n to: text(body['to'], 'to'),\n artifacts: Array.isArray(body['artifacts']) ? body['artifacts'].filter((item): item is string => typeof item === 'string') : [],\n acceptance: Array.isArray(body['acceptance']) ? body['acceptance'].filter((item): item is string => typeof item === 'string') : [],\n requestedOutput: text(body['requestedOutput'], 'requestedOutput'),\n ...(optionalText(body['note']) === undefined ? {} : { note: optionalText(body['note'])! }),\n }))\n notifyProject(projectPath)\n return\n }\n if (pathname === '/ksrw/api/project/proposals' && method === 'GET') {\n ok(res, store.open(text(url.searchParams.get('path'), 'path')).proposals)\n return\n }\n if (pathname === '/ksrw/api/project/proposals' && method === 'POST') {\n const body = await readBody(req)\n const projectPath = text(body['path'], 'path')\n assertProjectWritable(projectPath)\n ok(res, store.createProposal(projectPath, {\n artifactRef: text(body['artifactRef'], 'artifactRef'),\n baseRevision: typeof body['baseRevision'] === 'number' ? body['baseRevision'] : 0,\n summary: text(body['summary'], 'summary'),\n content: text(body['content'], 'content'),\n ...(optionalText(body['authorRole']) === undefined ? {} : { authorRole: optionalText(body['authorRole'])! }),\n }))\n notifyProject(projectPath)\n return\n }\n if (pathname === '/ksrw/api/project/proposals/apply' && method === 'POST') {\n const body = await readBody(req)\n const projectPath = text(body['path'], 'path')\n assertProjectWritable(projectPath)\n ok(res, store.applyProposal(projectPath, text(body['proposalId'], 'proposalId')))\n notifyProject(projectPath)\n return\n }\n if (pathname === '/ksrw/api/project/proposals/discard' && method === 'POST') {\n const body = await readBody(req)\n const projectPath = text(body['path'], 'path')\n assertProjectWritable(projectPath)\n ok(res, store.discardProposal(projectPath, text(body['proposalId'], 'proposalId')))\n notifyProject(projectPath)\n return\n }\n if (pathname === '/ksrw/api/project/paper/style' && method === 'GET') {\n const projectPath = text(url.searchParams.get('path'), 'path')\n ok(res, { ...readPaperStyle(projectPath), templates: listVenueTemplates() })\n return\n }\n if (pathname === '/ksrw/api/project/paper/style' && method === 'POST') {\n const body = await readBody(req)\n ok(res, writePaperStyle(text(body['path'], 'path'), text(body['venue'], 'venue')))\n return\n }\n if (pathname === '/ksrw/api/project/paper/status' && method === 'GET') {\n const projectPath = text(url.searchParams.get('path'), 'path')\n const dir = paperDir(url.searchParams.get('dir') ?? 'paper')\n const source = optionalText(url.searchParams.get('source')) ?? defaultPaperSource(dir)\n invalidateStalePaperFinal(projectPath, dir)\n const final = paperFinalStatus(projectPath, dir)\n const styleId = readPaperStyle(projectPath).venue\n const venue = listVenueTemplates().find(item => item.id === styleId)\n ok(res, {\n ...paperStatus(projectPath, source),\n outputs: listPaperOutputs(projectPath, dir),\n build: final.build ?? null,\n draft: readPaperBuild(projectPath, dir) ?? null,\n stale: final.stale,\n draftStale: final.draftStale,\n style: venue ?? { id: styleId, label: styleId, columns: 1 },\n })\n return\n }\n if (pathname === '/ksrw/api/project/paper/compile' && method === 'POST') {\n const body = await readBody(req)\n const projectPath = text(body['path'], 'path')\n const dir = paperDir(body['dir'] ?? 'paper')\n const source = optionalText(body['source']) ?? defaultPaperSource(dir)\n ok(res, compilePaper(projectPath, source))\n return\n }\n if (pathname === '/ksrw/api/project/paper/export-docx' && method === 'POST') {\n const body = await readBody(req)\n const projectPath = text(body['path'], 'path')\n const dir = paperDir(body['dir'] ?? 'paper')\n const source = optionalText(body['source']) ?? defaultPaperSource(dir)\n ok(res, exportPaperDocx(projectPath, source))\n return\n }\n if (pathname === '/ksrw/api/project/file' && method === 'GET') {\n // \u539F\u59CB\u6587\u4EF6\u4E0B\u8F7D\uFF1AWord/PPT/Excel \u9884\u89C8\u4F1A\u8F6C\u6210 PDF\uFF0C\u4F46\u300C\u4E0B\u8F7D\u300D\u5FC5\u987B\u7ED9\u539F\u6587\u4EF6\u3002\n const projectPath = text(url.searchParams.get('path'), 'path')\n const file = text(url.searchParams.get('file'), 'file')\n const project = store.open(projectPath).project\n const bytes = readProjectFileBytes(project.path, file, PREVIEW_OFFICE_MAX * 4)\n const contentType = DOWNLOAD_MIME[extname(file).toLowerCase()] ?? 'application/octet-stream'\n res.writeHead(200, {\n 'content-type': contentType,\n 'content-disposition': 'attachment; filename=\"' + encodeURIComponent(file.split('/').pop() ?? file) + '\"',\n 'content-length': String(bytes.byteLength),\n })\n res.end(Buffer.from(bytes))\n return\n }\n if (pathname === '/ksrw/api/project/reading' && method === 'GET') {\n const projectPath = text(url.searchParams.get('path'), 'path')\n const prefs = readReadingPrefs(projectPath)\n ok(res, { prefs, entries: readReadingIndex(projectPath), due: readingFetchDue(prefs) })\n return\n }\n if (pathname === '/ksrw/api/project/reading/prefs' && method === 'POST') {\n const body = await readBody(req)\n ok(res, writeReadingPrefs(text(body['path'], 'path'), (body['prefs'] ?? {}) as Record<string, never>))\n return\n }\n if (pathname === '/ksrw/api/project/reading/search' && method === 'POST') {\n const body = await readBody(req)\n const projectPath = text(body['path'], 'path')\n const prefs = readReadingPrefs(projectPath)\n ok(res, await searchLatestPapers(prefs, { days: Number(body['days'] ?? 30), max: Number(body['max'] ?? 10) }))\n return\n }\n if (pathname === '/ksrw/api/project/reading/add' && method === 'POST') {\n const body = await readBody(req)\n const projectPath = text(body['path'], 'path')\n const raw = Array.isArray(body['papers']) ? body['papers'] : []\n const papers = raw.map(item => {\n const paper = (item ?? {}) as Record<string, unknown>\n return {\n arxivId: String(paper['arxivId'] ?? ''),\n title: String(paper['title'] ?? ''),\n authors: Array.isArray(paper['authors']) ? paper['authors'].map(value => String(value)) : [],\n abstract: String(paper['abstract'] ?? ''),\n categories: Array.isArray(paper['categories']) ? paper['categories'].map(value => String(value)) : [],\n published: String(paper['published'] ?? ''),\n updated: String(paper['updated'] ?? paper['published'] ?? ''),\n absUrl: String(paper['absUrl'] ?? ''),\n ...(typeof paper['pdfUrl'] === 'string' ? { pdfUrl: paper['pdfUrl'] } : {}),\n }\n })\n ok(res, await addReadingPapers(projectPath, papers))\n return\n }\n if (pathname === '/ksrw/api/project/reading/entry' && method === 'GET') {\n ok(res, readReadingEntry(text(url.searchParams.get('path'), 'path'), text(url.searchParams.get('id'), 'id')))\n return\n }\n if (pathname === '/ksrw/api/project/reading/digest' && method === 'POST') {\n const body = await readBody(req)\n ok(res, writeReadingDigest(text(body['path'], 'path'), text(body['id'], 'id'), text(body['markdown'], 'markdown')))\n return\n }\n if (pathname === '/ksrw/api/project/reading/fetch' && method === 'POST') {\n const body = await readBody(req)\n const max = optionalText(body['max'])\n ok(res, await runReadingFetch(text(body['path'], 'path'), max === undefined ? {} : { max: Number(max) }))\n return\n }\n if (pathname === '/ksrw/api/project/reading/schedule' && method === 'POST') {\n const body = await readBody(req)\n const projectPath = text(body['path'], 'path')\n const requested = text(body['time'], 'time')\n const timeZone = optionalText(body['timeZone']) ?? 'Asia/Shanghai'\n // \u5F15\u64CE\u7684 daily.time \u53EA\u8BA4 HH:MM:SS\uFF1BHH:MM \u4F1A\u88AB\u5224\u6210 invalid_rule\u3002\n const clock = /^\\d{2}:\\d{2}$/.test(requested) ? requested + ':00' : requested\n const project = store.list().find(item => item.path === projectPath)\n if (project === undefined) {\n send(res, 404, { ok: false, error: { code: 'project_not_found', message: '\u9879\u76EE\u672A\u767B\u8BB0\uFF1A' + projectPath } })\n return\n }\n if (schedule === undefined || schedule === null) {\n send(res, 503, { ok: false, error: { code: 'schedule_unavailable', message: '\u5BBF\u4E3B\u672A\u542F\u7528\u300C\u81EA\u52A8\u5316\u4EFB\u52A1\u300D\uFF0C\u65E0\u6CD5\u521B\u5EFA\u5B9A\u65F6\u6293\u53D6' } })\n return\n }\n const created = await schedule.create({\n title: ('\u6587\u732E\u6293\u53D6 \u00B7 ' + project.name).slice(0, 120),\n prompt: '\u8C03\u7528 ksrw_reading \u5DE5\u5177\uFF0Caction=fetch\uFF0Cpath=' + projectPath + '\uFF0C\u6293\u53D6\u6700\u65B0 arXiv \u5E76\u6309\u7CBE\u8BFB\u504F\u597D\u5165\u5E93\uFF1B\u5DF2\u7ECF\u5165\u5E93\u7684\u8BBA\u6587\u4E0D\u8981\u91CD\u590D\u6DFB\u52A0\u3002',\n timing: { kind: 'daily', daily: { time: clock, time_zone: timeZone } },\n target: { kind: 'new_once' },\n }) as { id?: string; scheduledAt?: string } | null\n ok(res, { id: created?.id ?? '', nextScheduledAt: created?.scheduledAt ?? '' })\n return\n }\n if (pathname === '/ksrw/api/project/venue' && method === 'GET') {\n ok(res, readVenueProfile(text(url.searchParams.get('path'), 'path')))\n return\n }\n if (pathname === '/ksrw/api/project/venue' && method === 'POST') {\n const body = await readBody(req)\n ok(res, writeVenueProfile(text(body['path'], 'path'), (body['patch'] ?? {}) as Record<string, never>))\n return\n }\n if (pathname === '/ksrw/api/project/venue/seed' && method === 'POST') {\n const body = await readBody(req)\n ok(res, seedVenueChecklist(text(body['path'], 'path'), text(body['requirements'], 'requirements')))\n return\n }\n if (pathname === '/ksrw/api/project/venue/set' && method === 'POST') {\n const body = await readBody(req)\n const status = text(body['status'], 'status')\n if (status !== 'todo' && status !== 'done' && status !== 'na') throw new KsrwError('INVALID', 'status \u53EA\u652F\u6301 todo/done/na')\n const note = optionalText(body['note'])\n ok(res, setVenueRequirement(text(body['path'], 'path'), text(body['id'], 'id'), status, note))\n return\n }\n if (pathname === '/ksrw/api/project/paper/insert-figure' && method === 'POST') {\n const body = await readBody(req)\n const dir = paperDir(body['dir'] ?? 'paper')\n const source = optionalText(body['source']) ?? defaultPaperSource(dir)\n ok(res, insertFigureIntoDraft(text(body['path'], 'path'), source, text(body['file'], 'file'), text(body['caption'], 'caption')))\n return\n }\n if (pathname === '/ksrw/api/preview' && method === 'POST') {\n const body = await readBody(req)\n ok(res, await previewFile(store, office, body))\n return\n }\n if (pathname === '/ksrw/api/project/evidence' && method === 'GET') {\n ok(res, store.readEvidence(text(url.searchParams.get('path'), 'path')))\n return\n }\n if (pathname === '/ksrw/api/project/evidence' && method === 'POST') {\n const body = await readBody(req)\n const source = body['source']\n if (typeof source !== 'object' || source === null) throw new KsrwError('INVALID', 'source \u5FC5\u987B\u662F\u5BF9\u8C61')\n const s = source as Record<string, unknown>\n if (s['kind'] !== 'paper' && s['kind'] !== 'run' && s['kind'] !== 'patent') {\n throw new KsrwError('INVALID', 'source.kind \u5FC5\u987B\u662F paper/run/patent')\n }\n const projectPath = text(body['path'], 'path')\n assertProjectWritable(projectPath)\n ok(res, store.appendEvidence(projectPath, {\n claimRef: text(body['claimRef'], 'claimRef'),\n source: {\n kind: s['kind'],\n ref: text(s['ref'], 'source.ref'),\n ...(optionalText(s['locator']) === undefined ? {} : { locator: optionalText(s['locator'])! }),\n },\n quote: text(body['quote'], 'quote'),\n }))\n notifyProject(projectPath)\n return\n }\n\n send(res, 404, { ok: false, error: { code: 'NOT_FOUND', message: 'unknown endpoint: ' + pathname } })\n } catch (error) {\n fail(res, error)\n }\n}\n", "/** /ksrw/api \u8BF7\u6C42\u5B88\u536B\uFF1A\u628A\u5F15\u64CE connection \u56F4\u680F\u9002\u914D\u6210\u5355\u4E00\u5224\u5B9A\u51FD\u6570\u3002\n *\n * \u94C1\u5F8B\uFF082026-10-30\uFF09\uFF1A\u5FC5\u987B\u4EE5**\u65B9\u6CD5\u5F62\u5F0F**\u8C03\u7528 fence.requestRejection(req)\u3002\n * \u5F15\u64CE\u4FA7 requestRejection \u5185\u90E8\u662F this.admit(request)\u2014\u2014\u628A\u65B9\u6CD5\u62C6\u6210\u88F8\u51FD\u6570\u518D\u8C03\u7528\n * \u4F1A\u4E22 this\uFF08ESM \u4E25\u683C\u6001\u4E3A undefined\uFF09\uFF0C\u6BCF\u4E2A /ksrw/api \u8BF7\u6C42\u90FD 500\n * \u300CCannot read properties of undefined (reading 'admit')\u300D\uFF0C\u9879\u76EE\u5E93\u6574\u4E2A\u4E0D\u53EF\u7528\u3002\n * tests/runtime-policy.test.mjs \u91CC\u6709\u9632\u56DE\u5F52\u7528\u4F8B\uFF1A\u62C6\u7ED1\u8C03\u7528\u5FC5\u987B\u66B4\u9732\u5931\u8D25\u3001\n * \u65B9\u6CD5\u8C03\u7528\u5FC5\u987B\u653E\u884C\u3002\n *\n * \u672C\u6A21\u5757\u4FDD\u6301\u96F6\u4F9D\u8D56\uFF1A\u5355\u6D4B\u76F4\u63A5 import \u672C .ts \u6E90\u7801\u505A\u884C\u4E3A\u65AD\u8A00\u3002\n */\n\n/** \u5F15\u64CE connection \u56F4\u680F\uFF08\u53EA\u58F0\u660E\u5B88\u536B\u9700\u8981\u7684\u4E00\u5C0F\u5757\uFF0C\u4FDD\u6301\u672C\u6A21\u5757\u96F6\u4F9D\u8D56\uFF09\u3002\n * requestRejection \u662F\u53EF\u9009\u6210\u5458\uFF08\u5F15\u64CE\u7ED3\u6784\u9762\u5141\u8BB8\u7F3A\u5E2D\uFF0C\u7F3A\u5E2D\u65F6\u5B88\u536B fail-closed\uFF09\u3002 */\nexport interface GuardFence {\n requestRejection?(request: unknown): unknown\n}\n\nexport type RequestGuard = (req: unknown) => number | undefined\n\n/** \u5BBF\u4E3B\u6CA1\u6302 connection\uFF08\u6D4F\u89C8\u5668/\u5F00\u53D1\u6001\u672A\u88C5 accounts\uFF09\u65F6\uFF0C\u6CA1\u6709\u9274\u6743\u6743\u5A01\u5C31\u62D2\u7EDD\u3002\n * \u663E\u5F0F\u653E\u884C\u53EA\u7559\u7ED9\u786E\u5B9E\u9700\u8981\u88F8 API \u7684\u5F00\u53D1\u573A\u666F\uFF0C\u4E14\u5FC5\u987B\u7531\u73AF\u5883\u53D8\u91CF\u5F00\u53E3\u5B50\u3002 */\nexport const UNAUTHENTICATED_ESCAPE = 'KSRW_API_ALLOW_UNAUTHENTICATED'\n\nexport function createRequestGuard(fence: GuardFence | null | undefined): RequestGuard {\n const requestRejection = fence?.requestRejection\n if (typeof requestRejection !== 'function') {\n if (process.env[UNAUTHENTICATED_ESCAPE] === '1') return () => undefined\n return () => 503\n }\n // \u94C1\u5F8B\uFF1A\u4EE5\u65B9\u6CD5\u5F62\u5F0F\u8C03\u7528\uFF08.call(fence, \u2026) \u4FDD\u6301 this\uFF09\u2014\u2014\u62C6\u7ED1\u4F1A\u8BA9\u5F15\u64CE\u4FA7\n // this.admit(request) \u8BFB\u5230 undefined\uFF0C/ksrw/api \u5168 500\uFF08\u89C1 tests/runtime-policy\uFF09\u3002\n return req => {\n const rejection = requestRejection.call(fence, req)\n return typeof rejection === 'number' ? rejection : undefined\n }\n}\n", "/** \u672C\u673A\u5B9E\u9A8C runner\uFF1A\u5728\u9879\u76EE\u7684 experiments/ \u4E0B\u6267\u884C\u8BBA\u6587\u5B9E\u9A8C\u811A\u672C\uFF0C\u843D\u76D8 run \u8BB0\u5F55\u3002\n *\n * \u4EA7\u7269\u5E03\u5C40\uFF08\u4E0E plans/ksrw-experiment-redesign.md \u5BF9\u9F50\uFF09\uFF1A\n * experiments/record.md \u5B9E\u9A8C\u65B9\u6848\u4E0E\u8BB0\u5F55\uFF08experiment \u5DE5\u4EF6\uFF0C\u63D0\u6848\u9A71\u52A8\uFF09\n * experiments/code/*.py \u53EF\u6267\u884C\u811A\u672C\n * experiments/runs/<runId>/ config.json / metrics.json / stdout.log\n */\nimport { spawn, type ChildProcess } from 'node:child_process'\nimport { randomUUID } from 'node:crypto'\nimport { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs'\nimport { delimiter, join, relative } from 'node:path'\n\nimport { labPython } from './lab.ts'\nimport { ingestRunFigures } from './figures.ts'\nimport { downloadDirectory, execRemoteTarget, targetPipIndex, targetPython, uploadDirectory } from './remote.ts'\n\nexport interface ExperimentScript {\n /** Path relative to the project's experiments/code directory. */\n relative: string\n title: string\n mtime: number\n}\n\nexport interface ExperimentRun {\n id: string\n script: string\n args: string[]\n status: 'running' | 'succeeded' | 'failed' | 'cancelled'\n startedAt: number\n durationMs: number\n exitCode: number | null\n metrics: Record<string, number | string>\n python?: string\n /** run \u76EE\u5F55\u4E0B artifacts/ \u91CC\u7684\u6587\u4EF6\u540D\uFF08\u672C\u5730\u4E0E\u8FDC\u7AEF\u7EDF\u4E00\uFF09\u3002 */\n artifacts?: string[]\n}\n\nconst CODE_DIR = join('experiments', 'code')\nconst RUNS_DIR = join('experiments', 'runs')\n\ninterface RunningRun {\n child: ChildProcess\n run: ExperimentRun\n metrics: Record<string, number | string>\n startedAt: number\n cancelRequested: boolean\n}\n\nconst running = new Map<string, RunningRun>()\n\n/** run id -> projectPath, so cancel can verify ownership (P2-12). */\nconst runOwners = new Map<string, string>()\n\n/**\n * P2-10: a host crash leaves runs/<id>/config.json stuck at status=running with no\n * live process in this process table. Before listing/starting, rewrite such\n * configs to interrupted so the UI stops polling runs that no longer exist.\n */\nexport function reclaimStaleRuns(projectPath: string): string[] {\n const root = join(projectPath, RUNS_DIR)\n if (!existsSync(root)) return []\n const reclaimed: string[] = []\n let entries: string[]\n try { entries = readdirSync(root) } catch { return [] }\n for (const entry of entries) {\n if (running.has(entry)) continue\n const file = join(root, entry, 'config.json')\n const config = readJson<Record<string, unknown>>(file)\n if (config === undefined || config['status'] !== 'running') continue\n try {\n writeFileSync(file, JSON.stringify({\n ...config,\n status: 'interrupted',\n exitCode: null,\n interruptedAt: Date.now(),\n interruptedReason: '\u5BBF\u4E3B\u91CD\u542F\uFF1A\u8BE5 run \u4E0D\u5C5E\u4E8E\u672C\u8FDB\u7A0B\uFF0C\u5DF2\u6807\u8BB0 interrupted',\n }, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n reclaimed.push(entry)\n } catch { /* unwritable run dir: leave it for the next pass */ }\n }\n return reclaimed\n}\n\n/** Every .py file under experiments/code (recursive, capped). */\nexport function listExperimentScripts(projectPath: string): ExperimentScript[] {\n const root = join(projectPath, CODE_DIR)\n if (!existsSync(root)) return []\n const found: ExperimentScript[] = []\n const walk = (dir: string, depth: number): void => {\n if (depth > 3 || found.length > 200) return\n let entries: string[]\n try { entries = readdirSync(dir) } catch { return }\n for (const entry of entries) {\n if (entry.startsWith('.')) continue\n const full = join(dir, entry)\n let stat\n try { stat = statSync(full) } catch { continue }\n if (stat.isDirectory()) { walk(full, depth + 1); continue }\n if (!entry.endsWith('.py')) continue\n found.push({ relative: relative(root, full), title: entry, mtime: stat.mtimeMs })\n }\n }\n walk(root, 0)\n return found.sort((left, right) => right.mtime - left.mtime)\n}\n\nfunction readJson<T>(file: string): T | undefined {\n try { return JSON.parse(readFileSync(file, 'utf8')) as T } catch { return undefined }\n}\n\n/** Persisted runs (newest first) with live status overlaid from the process table. */\nfunction localArtifacts(dir: string): string[] {\n const root = join(dir, 'artifacts')\n if (!existsSync(root)) return []\n try {\n return readdirSync(root, { withFileTypes: true })\n .filter(entry => entry.isFile()).map(entry => entry.name).sort()\n } catch { return [] }\n}\n\nexport function listExperimentRuns(projectPath: string): ExperimentRun[] {\n reclaimStaleRuns(projectPath)\n const root = join(projectPath, RUNS_DIR)\n if (!existsSync(root)) return []\n let entries: string[]\n try { entries = readdirSync(root) } catch { return [] }\n const runs: ExperimentRun[] = []\n for (const entry of entries) {\n const dir = join(root, entry)\n const config = readJson<Partial<ExperimentRun>>(join(dir, 'config.json'))\n if (config === undefined) continue\n const metrics = readJson<Record<string, number | string>>(join(dir, 'metrics.json')) ?? {}\n const live = running.get(entry)\n const run: ExperimentRun = {\n id: entry,\n script: typeof config.script === 'string' ? config.script : '',\n args: Array.isArray(config.args) ? config.args as string[] : [],\n status: live !== undefined ? 'running' : (typeof config.status === 'string' ? config.status as ExperimentRun['status'] : 'failed'),\n startedAt: typeof config.startedAt === 'number' ? config.startedAt : 0,\n durationMs: typeof config.durationMs === 'number' ? config.durationMs : 0,\n exitCode: typeof config.exitCode === 'number' ? config.exitCode : null,\n metrics: live !== undefined ? live.metrics : metrics,\n ...(typeof config.python === 'string' ? { python: config.python } : {}),\n artifacts: localArtifacts(dir),\n }\n runs.push(run)\n }\n return runs.sort((left, right) => right.startedAt - left.startedAt)\n}\n\n/** Start a run; throws when the script or the Python runtime is missing. */\nexport function startExperimentRun(projectPath: string, script: string, args: string[] = []): ExperimentRun {\n const codeRoot = join(projectPath, CODE_DIR)\n const scriptPath = join(codeRoot, script)\n if (!scriptPath.startsWith(codeRoot) || !existsSync(scriptPath)) {\n throw new Error('\u5B9E\u9A8C\u811A\u672C\u4E0D\u5B58\u5728\uFF1A' + script)\n }\n const runtime = labPython()\n if (runtime === undefined) {\n throw new Error('\u6CA1\u6709\u53EF\u7528\u7684 Python \u8FD0\u884C\u65F6\uFF1A\u5148\u6784\u5EFA lab runtime\uFF08node scripts/build-lab-runtime.mjs --profile ml\uFF09\u6216\u8BBE\u7F6E KSRW_LAB_PYTHON')\n }\n reclaimStaleRuns(projectPath)\n const id = 'run_' + randomUUID().slice(0, 12)\n const dir = join(projectPath, RUNS_DIR, id)\n mkdirSync(dir, { recursive: true })\n const stdoutFile = join(dir, 'stdout.log')\n writeFileSync(stdoutFile, '', { encoding: 'utf8', mode: 0o600 })\n const startedAt = Date.now()\n const run: ExperimentRun = {\n id, script, args, status: 'running', startedAt, durationMs: 0, exitCode: null, metrics: {}, python: runtime.python,\n }\n const pythonPath = [codeRoot, runtime.site, process.env['KSRW_LAB_PYTHONPATH']]\n .filter((item): item is string => item !== undefined && item !== '').join(delimiter)\n const child = spawn(runtime.python, [scriptPath, ...args], {\n cwd: codeRoot,\n env: {\n ...process.env,\n PYTHONUNBUFFERED: '1',\n PYTHONPATH: pythonPath,\n KSRW_PROJECT: projectPath,\n KSRW_RUN_ID: id,\n KSRW_RUN_DIR: dir,\n },\n stdio: ['ignore', 'pipe', 'pipe'],\n })\n const state: RunningRun = { child, run, metrics: {}, startedAt, cancelRequested: false }\n running.set(id, state)\n runOwners.set(id, projectPath)\n writeFileSync(join(dir, 'config.json'), JSON.stringify({\n id, script, args, startedAt, python: runtime.python, host: 'local', status: 'running',\n }, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n\n const onChunk = (chunk: Buffer): void => {\n const text = chunk.toString('utf8')\n appendFileSync(stdoutFile, text, 'utf8')\n for (const line of text.split('\\n')) {\n const match = /^KSRW_METRIC\\s+([\\w.-]+)\\s*=\\s*(.+)$/.exec(line.trim())\n if (match === null) continue\n const raw = match[2]!.trim()\n const numeric = Number(raw)\n state.metrics[match[1]!] = Number.isFinite(numeric) ? numeric : raw\n }\n }\n child.stdout?.on('data', onChunk)\n child.stderr?.on('data', onChunk)\n const finish = (exitCode: number | null, status: ExperimentRun['status']): void => {\n const durationMs = Date.now() - startedAt\n running.delete(id)\n runOwners.delete(id)\n writeFileSync(join(dir, 'metrics.json'), JSON.stringify(state.metrics, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n try { ingestRunFigures(projectPath, id) } catch { /* \u56FE\u6536\u8FDB\u8BBA\u6587\u5931\u8D25\u4E0D\u963B\u585E run */ }\n const config = readJson<Record<string, unknown>>(join(dir, 'config.json')) ?? {}\n writeFileSync(join(dir, 'config.json'), JSON.stringify({\n ...config, status, exitCode, durationMs, artifacts: localArtifacts(dir),\n }, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n }\n child.once('exit', (code) => {\n const status: ExperimentRun['status'] = state.cancelRequested ? 'cancelled' : (code === 0 ? 'succeeded' : 'failed')\n finish(code, status)\n })\n child.once('error', (error) => {\n appendFileSync(stdoutFile, '\\n[spawn error] ' + error.message + '\\n', 'utf8')\n finish(null, 'failed')\n })\n return run\n}\n\n/** Ask a running experiment to stop (SIGTERM, then SIGKILL after 3s).\n *\n * P2-12: when a projectPath is supplied, the run must belong to that project \u2014\n * a caller that only knows the run id cannot cancel another project's run.\n */\nexport function cancelExperimentRun(id: string, projectPath?: string): boolean {\n if (projectPath !== undefined) {\n const owner = runOwners.get(id)\n if (owner === undefined || owner !== projectPath) return false\n }\n const state = running.get(id)\n if (state === undefined) return false\n state.cancelRequested = true\n state.child.kill('SIGTERM')\n // child.killed \u53EA\u8868\u793A\u4FE1\u53F7\u5DF2\u53D1\u51FA\uFF1B\u7528 exitCode/signalCode \u5224\u65AD\u662F\u5426\u4ECD\u5B58\u6D3B\u3002\n setTimeout(() => {\n if (state.child.exitCode === null && state.child.signalCode === null) state.child.kill('SIGKILL')\n }, 3000).unref()\n return true\n}\n\n\n// ---- \u8FDC\u7A0B run\uFF08S3\uFF09\uFF1A\u4E0A\u4F20 experiments/code \u2192 \u8FDC\u7AEF nohup \u6267\u884C \u2192 \u8F6E\u8BE2\u65E5\u5FD7/\u9000\u51FA\u7801 ----\n\nexport interface RemoteRunInfo {\n readonly id: string\n readonly targetId: string\n readonly remoteDir: string\n readonly pid?: string\n readonly python: string\n readonly status: 'running' | 'succeeded' | 'failed' | 'cancelled'\n}\n\nfunction shellQuote(value: string): string {\n return \"'\" + value.replace(/'/g, \"'\\\\''\") + \"'\"\n}\n\nfunction readRemoteInfo(dir: string): RemoteRunInfo | undefined {\n return readJson<RemoteRunInfo>(join(dir, 'remote.json'))\n}\n\nfunction writeRemoteInfo(dir: string, info: RemoteRunInfo): void {\n writeFileSync(join(dir, 'remote.json'), JSON.stringify(info, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n}\n\nfunction metricsFromLog(text: string): Record<string, number | string> {\n const metrics: Record<string, number | string> = {}\n for (const line of text.split('\\n')) {\n const match = /^KSRW_METRIC\\s+([\\w.-]+)\\s*=\\s*(.+)$/.exec(line.trim())\n if (match === null) continue\n const numeric = Number(match[2]!.trim())\n metrics[match[1]!] = Number.isFinite(numeric) ? numeric : match[2]!.trim()\n }\n return metrics\n}\n\n/** \u5728\u8FDC\u7A0B\u76EE\u6807\u4E0A\u542F\u52A8\u4E00\u6B21\u5B9E\u9A8C\uFF08\u811A\u672C\u6765\u81EA experiments/code\uFF09\u3002 */\nexport async function startRemoteExperiment(\n projectPath: string,\n targetId: string,\n script: string,\n args: string[] = [],\n python = '',\n): Promise<ExperimentRun> {\n const codeRoot = join(projectPath, CODE_DIR)\n if (!existsSync(join(codeRoot, script))) throw new Error('\u5B9E\u9A8C\u811A\u672C\u4E0D\u5B58\u5728\uFF1A' + script)\n const id = 'run_' + randomUUID().slice(0, 12)\n const dir = join(projectPath, RUNS_DIR, id)\n mkdirSync(dir, { recursive: true })\n const remoteDir = 'ksrw-runs/' + id\n // fastPut \u8981\u6C42\u7236\u76EE\u5F55\u5B58\u5728\uFF1A\u5148\u5EFA\u597D\u8FDC\u7AEF\u76EE\u5F55\u6811\u518D\u4E0A\u4F20\u3002\n await execRemoteTarget(targetId, 'mkdir -p ' + remoteDir + '/code', 30_000)\n await uploadDirectory(targetId, codeRoot, remoteDir + '/code')\n const interpreter = python === '' ? targetPython(targetId) : python\n const index = targetPipIndex(targetId)\n const inner = 'KSRW_RUN_DIR=$PWD PIP_INDEX_URL=' + shellQuote(index) + ' UV_INDEX_URL=' + shellQuote(index) + ' ' + interpreter + ' -u code/' + script + (args.length === 0 ? '' : ' ' + args.map(shellQuote).join(' '))\n const command = [\n 'mkdir -p ' + remoteDir,\n 'cd ' + remoteDir,\n 'setsid sh -c ' + shellQuote(inner + ' > stdout.log 2>&1; echo $? > exit.code') + ' > /dev/null 2>&1 & echo $!',\n ].join(' && ')\n const started = await execRemoteTarget(targetId, command, 60_000)\n const pid = started.stdout.trim().split('\\n').filter(line => line !== '').pop() ?? ''\n const startedAt = Date.now()\n writeFileSync(join(dir, 'config.json'), JSON.stringify({\n id, script, args, startedAt, status: 'running', host: 'remote:' + targetId, python: interpreter, remoteDir, pid: Number(pid) || pid,\n }, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n writeRemoteInfo(dir, { id, targetId, remoteDir, ...(pid === '' ? {} : { pid }), python: interpreter, status: 'running' })\n return { id, script, args, status: 'running', startedAt, durationMs: 0, exitCode: null, metrics: {}, python }\n}\n\n/** \u8F6E\u8BE2\u8FDC\u7A0B run\uFF1A\u5B8C\u6210\u65F6\u628A\u65E5\u5FD7\u4E0E\u6307\u6807\u62C9\u56DE\u672C\u5730 run \u76EE\u5F55\u3002 */\nexport async function pollRemoteExperiment(projectPath: string, id: string): Promise<ExperimentRun> {\n const dir = join(projectPath, RUNS_DIR, id)\n const info = readRemoteInfo(dir)\n const config = readJson<Record<string, unknown>>(join(dir, 'config.json')) ?? {}\n const base: ExperimentRun = {\n id,\n script: typeof config['script'] === 'string' ? config['script'] : '',\n args: Array.isArray(config['args']) ? config['args'] as string[] : [],\n status: (typeof config['status'] === 'string' ? config['status'] : 'failed') as ExperimentRun['status'],\n startedAt: typeof config['startedAt'] === 'number' ? config['startedAt'] : 0,\n durationMs: typeof config['durationMs'] === 'number' ? config['durationMs'] : 0,\n exitCode: typeof config['exitCode'] === 'number' ? config['exitCode'] : null,\n metrics: readJson<Record<string, number | string>>(join(dir, 'metrics.json')) ?? {},\n }\n if (info === undefined || info.status !== 'running') return base\n const probe = await execRemoteTarget(info.targetId, 'cd ' + info.remoteDir + ' && ( if [ -f exit.code ]; then echo DONE:$(cat exit.code); else echo RUNNING; fi )', 30_000)\n const marker = probe.stdout.trim().split('\\n').pop() ?? ''\n if (!marker.startsWith('DONE:')) return base\n const exitCode = Number(marker.slice(5))\n const logResult = await execRemoteTarget(info.targetId, 'cd ' + info.remoteDir + ' && tail -c 200000 stdout.log', 60_000)\n writeFileSync(join(dir, 'stdout.log'), logResult.stdout, { encoding: 'utf8', mode: 0o600 })\n const metrics = metricsFromLog(logResult.stdout)\n writeFileSync(join(dir, 'metrics.json'), JSON.stringify(metrics, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n try { await downloadDirectory(info.targetId, info.remoteDir + '/artifacts', join(dir, 'artifacts')) } catch { /* \u6CA1\u6709\u4EA7\u7269\u76EE\u5F55\u5C31\u7B97\u4E86 */ }\n try { ingestRunFigures(projectPath, id) } catch { /* ignore */ }\n const durationMs = Date.now() - base.startedAt\n const status: ExperimentRun['status'] = exitCode === 0 ? 'succeeded' : 'failed'\n writeFileSync(join(dir, 'config.json'), JSON.stringify({ ...config, status, exitCode, durationMs, artifacts: localArtifacts(dir) }, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n writeRemoteInfo(dir, { ...info, status })\n return { ...base, status, exitCode, durationMs, metrics, artifacts: localArtifacts(dir) }\n}\n\n/** \u53D6\u6D88\u8FDC\u7A0B run\uFF1A\u7ED9\u6574\u4E2A\u8FDB\u7A0B\u7EC4\u53D1 TERM\u3002 */\nexport async function cancelRemoteExperiment(projectPath: string, id: string): Promise<boolean> {\n const dir = join(projectPath, RUNS_DIR, id)\n const info = readRemoteInfo(dir)\n if (info === undefined || info.pid === undefined || info.status !== 'running') return false\n await execRemoteTarget(info.targetId, 'kill -TERM -' + info.pid + ' 2>/dev/null || kill -TERM ' + info.pid + ' 2>/dev/null; echo killed', 30_000)\n const config = readJson<Record<string, unknown>>(join(dir, 'config.json')) ?? {}\n writeFileSync(join(dir, 'config.json'), JSON.stringify({ ...config, status: 'cancelled', durationMs: Date.now() - (typeof config['startedAt'] === 'number' ? config['startedAt'] : Date.now()) }, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n writeRemoteInfo(dir, { ...info, status: 'cancelled' })\n return true\n}\n\n/** \u5217\u51FA run \u76EE\u5F55\u91CC\u7684\u8FDC\u7AEF\u4FE1\u606F\uFF08\u4F9B UI \u5224\u65AD\u8FD0\u884C\u4F4D\u7F6E\uFF09\u3002 */\nexport function readRemoteRunInfo(projectPath: string, id: string): RemoteRunInfo | undefined {\n return readRemoteInfo(join(projectPath, RUNS_DIR, id))\n}\n/** Last N lines of a run's stdout.log. */\nexport function readRunLog(projectPath: string, id: string, tailLines = 200): string {\n const file = join(projectPath, RUNS_DIR, id, 'stdout.log')\n if (!existsSync(file)) return ''\n try {\n const lines = readFileSync(file, 'utf8').split('\\n')\n return lines.slice(Math.max(0, lines.length - tailLines)).join('\\n')\n } catch { return '' }\n}\n", "/** KSRW Lab \u5BBF\u4E3B\uFF1Aspawn Python sidecar + \u53CD\u5411\u4EE3\u7406 + \u751F\u547D\u5468\u671F\u3002 */\nimport { spawn, spawnSync, type ChildProcessByStdio } from 'node:child_process'\nimport { randomBytes } from 'node:crypto'\nimport type { Readable } from 'node:stream'\nimport { existsSync } from 'node:fs'\nimport { delimiter, dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nexport interface LabEndpoint { readonly url: string; readonly token: string }\n\ninterface ReqLike {\n url?: string\n method?: string\n [Symbol.asyncIterator]?: () => AsyncIterator<Uint8Array | string>\n}\ninterface ResLike {\n writeHead(code: number, headers?: Record<string, string>): unknown\n end(body?: unknown): unknown\n}\n\nconst DEFAULT_CWD = fileURLToPath(new URL('../../../python/ksrw-lab', import.meta.url))\n\n/** \u7B49\u5B50\u8FDB\u7A0B\u771F\u6B63\u9000\u51FA\uFF1B\u8D85\u65F6\u8FD4\u56DE false\uFF08restart \u7528\u5B83\u51B3\u5B9A\u662F\u5426\u5347\u7EA7\u5230 SIGKILL\uFF09\u3002 */\nfunction waitForExit(child: ChildProcessByStdio<null, Readable, Readable>, timeoutMs: number): Promise<boolean> {\n if (child.exitCode !== null || child.signalCode !== null) return Promise.resolve(true)\n return new Promise(resolve => {\n const done = (value: boolean): void => {\n clearTimeout(timer)\n child.off('exit', onExit)\n resolve(value)\n }\n const onExit = (): void => { done(true) }\n const timer = setTimeout(() => { done(false) }, timeoutMs)\n child.once('exit', onExit)\n })\n}\n\n/** Local venv written by the installer: ~/.ksrw/lab/venv. */\nfunction localVenvPython(): string | undefined {\n const home = process.env['KSRW_HOME']?.trim() || join(process.env['HOME'] ?? '.', '.ksrw')\n const dir = process.env['KSRW_LAB_VENV']?.trim() || join(home, 'lab', 'venv')\n return ['bin/python3', 'bin/python', 'Scripts/python.exe'].map(name => join(dir, name)).find(candidate => existsSync(candidate))\n}\n\ninterface LabCandidate { cwd: string; site?: string; python?: string }\n\n/** A directory that holds ksrw_lab, plus the bundled interpreter when it has one. */\nfunction runtimeCandidate(directory: string): LabCandidate | undefined {\n if (!existsSync(join(directory, 'ksrw_lab', '__main__.py'))) return undefined\n const site = join(directory, 'site-packages')\n const bin = join(directory, 'python', 'bin')\n const python = ['python3.12', 'python3', 'python', 'python3.12.exe', 'python.exe']\n .map(name => join(bin, name)).find(candidate => existsSync(candidate))\n return {\n cwd: directory,\n ...(existsSync(site) ? { site } : {}),\n ...(python === undefined ? {} : { python }),\n }\n}\n\n/**\n * Every place the ksrw_lab package may live, best first: explicit runtime, the\n * installed runtime under KSRW_HOME, a runtime built next to any ancestor of this\n * module (staging/lab-runtime/runtime), an explicit cwd, then bare package dirs\n * in ancestors (dev checkout, compiled default).\n */\nfunction candidateLabDirs(): LabCandidate[] {\n const list: LabCandidate[] = []\n const explicitRuntime = process.env['KSRW_LAB_RUNTIME']?.trim()\n if (explicitRuntime !== undefined && explicitRuntime !== '') {\n const candidate = runtimeCandidate(explicitRuntime)\n if (candidate !== undefined) list.push(candidate)\n }\n const home = process.env['KSRW_HOME']?.trim() || join(process.env['HOME'] ?? '.', '.ksrw')\n const installed = runtimeCandidate(join(home, 'lab', 'runtime'))\n if (installed !== undefined) list.push(installed)\n const ancestors: string[] = []\n let dir = dirname(fileURLToPath(import.meta.url))\n for (let depth = 0; depth < 7; depth += 1) { ancestors.push(dir); dir = dirname(dir) }\n for (const ancestor of ancestors) {\n for (const relative of [join('staging', 'lab-runtime', 'runtime'), join('lab-runtime', 'runtime')]) {\n const candidate = runtimeCandidate(join(ancestor, relative))\n if (candidate !== undefined) list.push(candidate)\n }\n }\n const explicitCwd = process.env['KSRW_LAB_CWD']?.trim()\n if (explicitCwd !== undefined && explicitCwd !== '') {\n list.push(runtimeCandidate(explicitCwd) ?? { cwd: explicitCwd })\n }\n for (const ancestor of ancestors) {\n for (const relative of [join('python', 'ksrw-lab'), '.']) {\n const path = join(ancestor, relative)\n if (existsSync(join(path, 'ksrw_lab', '__main__.py'))) list.push({ cwd: path })\n }\n }\n return list\n}\n\nfunction pythonCandidates(): string[] {\n const explicit = process.env['KSRW_LAB_PYTHON']?.trim()\n return explicit !== undefined && explicit !== '' ? [explicit] : ['python3', 'python']\n}\n\nfunction canRun(command: string): boolean {\n try {\n return spawnSync(command, ['--version'], { stdio: 'ignore', timeout: 5000 }).status === 0\n } catch { return false }\n}\n\n/** A build-lab-runtime archive extracted under KSRW_HOME/lab/runtime (or KSRW_LAB_RUNTIME). */\nfunction packagedRuntime(): { directory: string; python: string; site: string } | undefined {\n const home = process.env['KSRW_HOME']?.trim() || join(process.env['HOME'] ?? '.', '.ksrw')\n const directory = process.env['KSRW_LAB_RUNTIME']?.trim() || join(home, 'lab', 'runtime')\n if (!existsSync(join(directory, 'ksrw_lab', '__main__.py'))) return undefined\n const bin = join(directory, 'python', 'bin')\n const python = ['python3.12', 'python3', 'python', 'python3.12.exe', 'python.exe']\n .map(name => join(bin, name))\n .find(candidate => existsSync(candidate))\n if (python === undefined) return undefined\n return { directory, python, site: join(directory, 'site-packages') }\n}\n\nasync function readRaw(req: ReqLike): Promise<Buffer> {\n if (req[Symbol.asyncIterator] === undefined) return Buffer.alloc(0)\n const chunks: Buffer[] = []\n let bytes = 0\n for await (const chunk of req as AsyncIterable<Uint8Array | string>) {\n const buffer = typeof chunk === 'string' ? Buffer.from(chunk) : Buffer.from(chunk)\n bytes += buffer.byteLength\n if (bytes > 2 * 1024 * 1024) throw new Error('lab request body too large')\n chunks.push(buffer)\n }\n return Buffer.concat(chunks)\n}\n\n/** Managed lab sidecar. It starts once, proxies every request, and dies with the plugin. */\n/** Interpreter + site-packages from the best lab runtime (also used for experiment runs). */\nexport function labPython(): { python: string; site?: string; cwd: string } | undefined {\n const chosen = candidateLabDirs()[0]\n if (chosen === undefined) return undefined\n const python = process.env['KSRW_LAB_PYTHON']?.trim() || chosen.python || localVenvPython() || pythonCandidates().find(canRun)\n if (python === undefined) return undefined\n return { python, ...(chosen.site === undefined ? {} : { site: chosen.site }), cwd: chosen.cwd }\n}\n\nexport class LabRuntime {\n private child: ChildProcessByStdio<null, Readable, Readable> | undefined\n private endpoint: LabEndpoint | undefined\n private starting: Promise<LabEndpoint | undefined> | undefined\n private stopped = false\n private reason = ''\n private cwdUsed = ''\n private pythonUsed = ''\n\n constructor(private readonly log: (message: string) => void = () => {}) {}\n\n /** Start the sidecar, or adopt an externally managed KSRW_LAB_URL. */\n async start(): Promise<LabEndpoint | undefined> {\n if (this.endpoint !== undefined) return this.endpoint\n if (this.starting !== undefined) return this.starting\n this.starting = this.launch()\n return this.starting\n }\n\n private async launch(): Promise<LabEndpoint | undefined> {\n const external = process.env['KSRW_LAB_URL']?.trim()\n if (external !== undefined && external !== '') {\n const token = process.env['KSRW_LAB_TOKEN'] ?? ''\n this.endpoint = { url: external.replace(/\\/$/, ''), token }\n this.log('lab: using external sidecar ' + this.endpoint.url)\n return this.endpoint\n }\n const chosen = candidateLabDirs()[0]\n if (chosen === undefined) {\n this.reason = '\u627E\u4E0D\u5230 ksrw_lab Python \u5305\uFF08\u53EF\u7528 KSRW_LAB_CWD \u6307\u5B9A\uFF0C\u6216\u8FD0\u884C node scripts/build-lab-runtime.mjs --profile ml\uFF09'\n this.log('lab: python package not found (KSRW_LAB_CWD / KSRW_LAB_RUNTIME)')\n return undefined\n }\n const cwd = chosen.cwd\n const packaged = chosen.site === undefined ? undefined : { directory: cwd, python: chosen.python ?? '', site: chosen.site }\n this.cwdUsed = cwd\n const explicitPython = process.env['KSRW_LAB_PYTHON']?.trim()\n const python = explicitPython !== undefined && explicitPython !== ''\n ? explicitPython\n : chosen.python ?? localVenvPython() ?? pythonCandidates().find(canRun)\n if (python === undefined) {\n this.reason = '\u627E\u4E0D\u5230\u53EF\u7528\u7684 Python \u89E3\u91CA\u5668\uFF08\u53EF\u8BBE\u7F6E KSRW_LAB_PYTHON\uFF0C\u6216\u6784\u5EFA lab runtime\uFF09'\n this.log('lab: no python interpreter found (set KSRW_LAB_PYTHON or build:lab:ml)')\n return undefined\n }\n this.pythonUsed = python\n if (packaged !== undefined) this.log('lab: using packaged runtime ' + cwd)\n const data = process.env['KSRW_LAB_DATA']?.trim()\n || join(process.env['KSRW_HOME'] ?? join(process.env['HOME'] ?? '.', '.ksrw'), 'lab')\n const token = randomBytes(24).toString('base64url')\n const child = spawn(python, ['-m', 'ksrw_lab', '--host', '127.0.0.1', '--port', '0', '--data', data, '--token', token], {\n cwd,\n env: {\n ...process.env,\n PYTHONUNBUFFERED: '1',\n PYTHONPATH: [cwd, packaged?.site, process.env['KSRW_LAB_PYTHONPATH']?.trim()]\n .filter((item): item is string => item !== undefined && item !== '').join(delimiter),\n },\n stdio: ['ignore', 'pipe', 'pipe'],\n })\n this.child = child\n child.stderr.setEncoding('utf8')\n child.stderr.on('data', (chunk: string) => { this.log('lab: ' + chunk.trim()) })\n const endpoint = await new Promise<LabEndpoint | undefined>((resolve) => {\n let settled = false\n let buffer = ''\n const finish = (value: LabEndpoint | undefined): void => {\n if (settled) return\n settled = true\n resolve(value)\n }\n const timer = setTimeout(() => finish(undefined), 20_000)\n child.stdout.setEncoding('utf8')\n child.stdout.on('data', (chunk: string) => {\n buffer += chunk\n const line = buffer.split('\\n').find(item => item.includes('\"ready\"'))\n if (line === undefined) return\n try {\n const ready = JSON.parse(line) as { ready?: boolean; port?: number }\n if (ready.ready === true && typeof ready.port === 'number') {\n clearTimeout(timer)\n finish({ url: 'http://127.0.0.1:' + ready.port, token })\n }\n } catch { /* wait for the full line */ }\n })\n child.once('exit', (code) => {\n clearTimeout(timer)\n this.log('lab: sidecar exited (' + String(code) + ')')\n finish(undefined)\n })\n child.once('error', (error) => {\n clearTimeout(timer)\n this.log('lab: spawn failed: ' + error.message)\n finish(undefined)\n })\n })\n if (endpoint === undefined) {\n if (this.reason === '') this.reason = 'sidecar \u542F\u52A8\u5931\u8D25\uFF1A\u89E3\u91CA\u5668\u6CA1\u6709\u5728 20 \u79D2\u5185\u5C31\u7EEA\uFF08\u68C0\u67E5 Python \u4F9D\u8D56\uFF1Afastapi/uvicorn/scikit-learn\uFF09'\n this.stop()\n return undefined\n }\n this.endpoint = endpoint\n process.env['KSRW_LAB_URL'] = endpoint.url\n process.env['KSRW_LAB_TOKEN'] = endpoint.token\n this.log('lab: sidecar ready at ' + endpoint.url)\n return endpoint\n }\n\n /** Structured status for the UI: why the sidecar is down and what it resolved. */\n status(): { ok: boolean; reason: string; cwd: string; python: string; url?: string } {\n return {\n ok: this.endpoint !== undefined,\n reason: this.reason,\n cwd: this.cwdUsed,\n python: this.pythonUsed,\n ...(this.endpoint === undefined ? {} : { url: this.endpoint.url }),\n }\n }\n\n /** Kill the current child and try again (used by the UI retry button). */\n async restart(): Promise<void> {\n const child = this.child\n this.child = undefined\n this.endpoint = undefined\n this.starting = undefined\n this.stopped = false\n this.reason = ''\n // P1-4\uFF1A\u5148 SIGTERM \u5E76\u7B49\u5F85\u8FDB\u7A0B\u771F\u6B63\u9000\u51FA\uFF0C\u518D\u51B3\u5B9A\u662F\u5426 SIGKILL\uFF1B\u7ACB\u523B start \u4F1A\u5728\n // \u65E7\u8FDB\u7A0B\u8FD8\u5360\u7740\u7AEF\u53E3\u65F6\u628A\u65B0\u8FDB\u7A0B\u649E\u6B7B\uFF08\u65E7\u8FDB\u7A0B\u6210\u4E3A\u5B64\u513F\u7EE7\u7EED\u8DD1 GPU \u4EFB\u52A1\uFF09\u3002\n if (child !== undefined && child.exitCode === null && child.signalCode === null) {\n child.kill('SIGTERM')\n const exited = await waitForExit(child, 3000)\n if (!exited) {\n child.kill('SIGKILL')\n await waitForExit(child, 1000)\n }\n }\n delete process.env['KSRW_LAB_URL']\n delete process.env['KSRW_LAB_TOKEN']\n await this.start()\n }\n\n async handle(req: ReqLike, res: ResLike): Promise<void> {\n const pathname = new URL(req.url ?? '/ksrw/api/lab', 'http://localhost').pathname\n if (pathname === '/ksrw/api/lab/status') {\n res.writeHead(200, { 'content-type': 'application/json; charset=utf-8' })\n res.end(JSON.stringify({ ok: true, value: this.status() }))\n return\n }\n if (pathname === '/ksrw/api/lab/restart' && (req.method ?? 'GET') === 'POST') {\n await this.restart()\n res.writeHead(200, { 'content-type': 'application/json; charset=utf-8' })\n res.end(JSON.stringify({ ok: true, value: this.status() }))\n return\n }\n const endpoint = await this.start()\n if (endpoint === undefined) {\n res.writeHead(503, { 'content-type': 'application/json; charset=utf-8' })\n res.end(JSON.stringify({ ok: false, error: { code: 'LAB_UNAVAILABLE', message: this.reason === '' ? '\u5B9E\u9A8C sidecar \u672A\u542F\u52A8' : this.reason } }))\n return\n }\n try {\n const url = new URL(req.url ?? '/ksrw/api/lab', 'http://localhost')\n const suffix = url.pathname.startsWith('/ksrw/api/lab')\n ? url.pathname.slice('/ksrw/api/lab'.length) || '/v1/health'\n : '/v1/health'\n const target = endpoint.url + (suffix.startsWith('/v1/') ? suffix : '/v1' + suffix) + url.search\n const raw = await readRaw(req)\n const headers: Record<string, string> = { 'x-ksrw-lab-token': endpoint.token }\n if (raw.length > 0) headers['content-type'] = 'application/json'\n const response = await fetch(target, {\n method: req.method ?? 'GET',\n headers,\n ...(raw.length > 0 ? { body: raw.toString('utf8') } : {}),\n })\n const body = Buffer.from(await response.arrayBuffer())\n res.writeHead(response.status, { 'content-type': 'application/json; charset=utf-8' })\n res.end(body)\n } catch (error) {\n res.writeHead(502, { 'content-type': 'application/json; charset=utf-8' })\n res.end(JSON.stringify({ ok: false, error: { code: 'LAB_PROXY', message: error instanceof Error ? error.message : String(error) } }))\n }\n }\n\n stop(): void {\n this.stopped = true\n const child = this.child\n this.child = undefined\n this.endpoint = undefined\n if (child !== undefined && child.exitCode === null && child.signalCode === null) {\n child.kill('SIGTERM')\n // child.killed \u53EA\u8868\u793A\u4FE1\u53F7\u5DF2\u53D1\u51FA\uFF1B\u7528 exitCode/signalCode \u5224\u65AD\u662F\u5426\u4ECD\u5B58\u6D3B\u3002\n setTimeout(() => {\n if (child.exitCode === null && child.signalCode === null) child.kill('SIGKILL')\n }, 2000).unref()\n }\n }\n}\n", "/** \u56FE\u8868\u6D41\u6C34\u7EBF\uFF08core \u4FA7\u8584\u5C01\u88C5\uFF09\uFF1A\u5B9E\u9645\u5B9E\u73B0\u5728 ksrw-kernel/figures\uFF0CUI \u4E0E agent \u5DE5\u5177\u5171\u7528\u540C\u4E00\u5957\u3002\n * \u8FD9\u91CC\u53EA\u4FDD\u7559\u6D4F\u89C8\u5668\u7AEF Mermaid \u6E32\u67D3\u7ED3\u679C\u7684\u843D\u76D8\uFF08\u6E32\u67D3\u5728 renderer\uFF0C\u4FDD\u5B58\u5728\u8FD9\u91CC\uFF09\u3002\n */\nimport { mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\nimport {\n adoptFigure,\n adoptUnregisteredFigures,\n extractDocxMedia,\n validateFigures,\n ingestRunFigures,\n listFigures,\n renderFlow,\n renderMetricsChart,\n upsertFigure,\n type PaperFigure,\n} from 'ksrw-kernel'\n\nexport type { PaperFigure }\nexport { adoptFigure, adoptUnregisteredFigures, extractDocxMedia, validateFigures, ingestRunFigures, listFigures, renderMetricsChart }\n\nconst STAGES = ['\u60F3\u6CD5', '\u9009\u9898', '\u5F00\u9898', '\u6587\u732E', '\u5B9E\u9A8C', '\u8BBA\u6587', '\u5B8C\u6210']\n\n/** \u751F\u6210\u4E00\u5F20\u300C\u7814\u7A76\u6D41\u6C34\u7EBF\u73B0\u72B6\u300D\u793A\u610F\u56FE\uFF08\u6807\u6CE8\u5F53\u524D\u9636\u6BB5\uFF09\u3002 */\nexport function renderArchitecture(projectPath: string, current: string, subtitle: string): PaperFigure {\n return renderFlow(projectPath, {\n nodes: STAGES.map((label, index) => ({ id: 's' + String(index), label, active: label === current })),\n edges: STAGES.slice(0, -1).map((_, index) => ({ from: 's' + String(index), to: 's' + String(index + 1) })),\n direction: 'lr',\n subtitle,\n }, {\n file: 'architecture',\n title: '\u7CFB\u7EDF\u67B6\u6784\u56FE \u00B7 \u7814\u7A76\u6D41\u6C34\u7EBF',\n caption: '\u7814\u7A76\u6D41\u6C34\u7EBF\u73B0\u72B6\uFF08\u5F53\u524D\u9636\u6BB5\uFF1A' + current + '\uFF09',\n source: 'project',\n kind: 'architecture',\n })\n}\n\n/** \u67B6\u6784\u56FE v2\uFF1AMermaid \u6E90\u6587\u4EF6\uFF08renderer \u6E32\u67D3\uFF0Ccore \u53EA\u5B58\uFF09\u3002 */\nfunction figuresDir(projectPath: string): string {\n return join(projectPath, 'paper', 'figures')\n}\n\nfunction architectureSourceFile(projectPath: string): string {\n return join(figuresDir(projectPath), 'architecture.mmd')\n}\n\nexport const DEFAULT_ARCHITECTURE_MERMAID = [\n 'flowchart LR',\n ' A[\u60F3\u6CD5\u6C60] --> B[\u9009\u9898\u5DE5\u4F4D]',\n ' B --> C[\u5F00\u9898\u5DE5\u4F4D]',\n ' C --> D[\u6587\u732E\u5DE5\u4F4D]',\n ' D --> E[\u5B9E\u9A8C\u5DE5\u4F4D]',\n ' E --> F[\u8BBA\u6587\u5DE5\u4F4D]',\n ' F --> G[\u5B8C\u6210]',\n].join('\\n')\n\nexport function readArchitectureSource(projectPath: string): { source: string; exists: boolean } {\n const file = architectureSourceFile(projectPath)\n try { return { source: readFileSync(file, 'utf8'), exists: true } } catch { return { source: DEFAULT_ARCHITECTURE_MERMAID, exists: false } }\n}\n\nexport function saveArchitectureRender(projectPath: string, input: { source: string; svg: string; pngBase64: string }): PaperFigure {\n mkdirSync(figuresDir(projectPath), { recursive: true })\n writeFileSync(architectureSourceFile(projectPath), input.source, { encoding: 'utf8', mode: 0o600 })\n writeFileSync(join(figuresDir(projectPath), 'architecture.svg'), input.svg, { encoding: 'utf8', mode: 0o600 })\n writeFileSync(join(figuresDir(projectPath), 'architecture.png'), Buffer.from(input.pngBase64, 'base64'), { mode: 0o600 })\n return upsertFigure(projectPath, {\n file: 'architecture.png',\n title: '\u7CFB\u7EDF\u67B6\u6784\u56FE\uFF08Mermaid\uFF09',\n caption: '\u7CFB\u7EDF\u67B6\u6784\uFF1A\u7814\u7A76\u6D41\u6C34\u7EBF\u4E0E\u5DE5\u4F4D\u534F\u4F5C',\n source: 'project',\n kind: 'architecture',\n addedAt: Date.now(),\n svg: 'architecture.svg',\n })\n}\n", "/** \u8FDC\u7A0B GPU \u76EE\u6807\u4E0E SSH \u8FDE\u63A5\uFF1AKSRW \u5B9E\u9A8C\u5BA4\u8FDC\u7A0B\u6267\u884C\u7684\u57FA\u7840\u8BBE\u65BD\u3002\n *\n * \u51ED\u636E\u4F18\u5148\u7EA7\uFF1ASSH agent\uFF08\u4E0D\u8FDB\u78C1\u76D8\uFF09> \u79C1\u94A5\u6587\u4EF6 > \u5BC6\u7801\u3002\u5BC6\u7801\u4F18\u5148\u5B58\u7CFB\u7EDF\u51ED\u636E\u5B58\u50A8\n * \uFF08macOS \u94A5\u5319\u4E32 / freedesktop Secret Service\uFF09\uFF0Cremotes.json \u53EA\u8BB0\u5F15\u7528\uFF1B\n * \u540E\u7AEF\u4E0D\u53EF\u7528\u65F6\u624D\u9000\u56DE 0600 \u660E\u6587\uFF0C\u5E76\u5728\u89C6\u56FE\u91CC\u5E26 `warning` \u8BA9 UI \u660E\u793A\u3002\n * \u5B58\u91CF\u660E\u6587\u5728\u63D2\u4EF6\u542F\u52A8\u65F6\u7531 migratePlaintextSecrets() \u5E42\u7B49\u8FC1\u79FB\u3002\n */\nimport { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\n\nimport { resolveKsrwHome } from 'ksrw-kernel'\nimport type { Client as SshClient, SFTPWrapper } from 'ssh2'\n\nimport { systemSecretStore, type SecretBackend } from './secret-store.ts'\n\nexport interface RemoteAuth {\n readonly kind: 'password' | 'key' | 'agent'\n /** \u4EC5\u5728\u540E\u7AEF\u4E0D\u53EF\u7528\u7684\u964D\u7EA7\u8DEF\u5F84\u4E0A\u51FA\u73B0\uFF080600 \u660E\u6587\uFF09\u3002 */\n readonly password?: string\n readonly keyPath?: string\n readonly passphrase?: string\n /** \u5BC6\u7801\u5728\u7CFB\u7EDF\u51ED\u636E\u5B58\u50A8\u91CC\u7684\u5F15\u7528\uFF1A`<backend>|remote:<targetId>`\u3002 */\n readonly secretRef?: string\n}\n\nexport interface RemoteTarget {\n readonly id: string\n readonly name: string\n readonly host: string\n readonly port: number\n readonly user: string\n readonly auth: RemoteAuth\n readonly workdir?: string\n /** \u8FDC\u7AEF\u89E3\u91CA\u5668\u8DEF\u5F84\uFF08venv/bin/python\uFF09\uFF1B\u7F3A\u7701 python3\u3002 */\n readonly pythonPath?: string\n /** \u8FDC\u7AEF\u88C5\u5305\u7528\u7684 PyPI \u955C\u50CF\uFF1B\u7F3A\u7701\u6E05\u534E\u6E90\u3002 */\n readonly pipIndexUrl?: string\n}\n\n/** \u5BF9\u5916\u5B89\u5168\u89C6\u56FE\uFF1A\u4E0D\u542B\u5BC6\u7801/\u79C1\u94A5\u5185\u5BB9\u3002 */\nexport interface RemoteTargetView {\n readonly id: string\n readonly name: string\n readonly host: string\n readonly port: number\n readonly user: string\n readonly authKind: 'password' | 'key' | 'agent'\n readonly hasSecret: boolean\n /** \u5BC6\u7801\u6750\u6599\u5B58\u653E\u5728\u54EA\uFF1A\u7CFB\u7EDF\u51ED\u636E\u5B58\u50A8 / \u660E\u6587\u964D\u7EA7 / \u4E0D\u9002\u7528\uFF08key\u3001agent\uFF09\u3002 */\n readonly secretStorage: 'keychain' | 'secret-service' | 'plaintext' | 'none'\n /** \u79C1\u94A5\u8DEF\u5F84\uFF08\u4EC5 key \u8BA4\u8BC1\uFF1B\u6587\u4EF6\u540D\u4E0D\u662F\u5BC6\u94A5\u5185\u5BB9\uFF0C\u56DE\u586B\u7F16\u8F91\u7528\uFF09\u3002 */\n readonly keyPath?: string\n /** \u5B89\u5168\u544A\u8B66\uFF08\u660E\u6587\u5BC6\u7801\u3001\u7F3A SSH agent \u7B49\uFF09\uFF1B\u65E0\u544A\u8B66\u65F6\u4E0D\u51FA\u73B0\u3002 */\n readonly warning?: string\n readonly workdir?: string\n readonly pythonPath?: string\n readonly pipIndexUrl?: string\n}\n\nexport interface RemoteProbe {\n readonly ok: boolean\n readonly latencyMs: number\n readonly error?: string\n readonly uname?: string\n readonly gpu?: string[]\n readonly python?: string\n readonly cpus?: number\n readonly memoryGb?: number\n}\n\nexport interface RemoteExecResult {\n readonly code: number | null\n readonly stdout: string\n readonly stderr: string\n}\n\nfunction storeFile(): string {\n return join(resolveKsrwHome(), 'remotes.json')\n}\n\n/** \u51ED\u636E\u5B58\u50A8\u5728\u7CFB\u7EDF\u91CC\u7684 service \u540D\uFF08\u94A5\u5319\u4E32\u6761\u76EE / Secret Service \u5C5E\u6027\uFF09\u3002 */\nconst SECRET_SERVICE = 'ksrw-remote'\n\nfunction secretAccount(id: string): string {\n return 'remote:' + id\n}\n\nfunction secretRefOf(backend: SecretBackend, id: string): string {\n return backend + '|' + secretAccount(id)\n}\n\n/** \u89E3\u6790\u51ED\u636E\u5F15\u7528\uFF1B\u683C\u5F0F\u4E0D\u5BF9\uFF08\u65E7\u6570\u636E/\u624B\u5DE5\u6539\u574F\uFF09\u65F6\u8FD4\u56DE undefined\u3002 */\nfunction parseSecretRef(ref: string): { backend: SecretBackend; account: string } | undefined {\n const index = ref.indexOf('|')\n if (index <= 0) return undefined\n const backend = ref.slice(0, index)\n if (backend !== 'keychain' && backend !== 'secret-service') return undefined\n const account = ref.slice(index + 1)\n return account === '' ? undefined : { backend, account }\n}\n\n/** \u8BFB\u51FA\u53EF\u7528\u5BC6\u7801\uFF1A\u5F15\u7528\u8D70\u7CFB\u7EDF\u51ED\u636E\u5B58\u50A8\uFF0C\u964D\u7EA7\u8DEF\u5F84\u76F4\u63A5\u7528\u660E\u6587\u3002 */\nfunction resolvePassword(auth: RemoteAuth): string {\n const ref = auth.secretRef ?? ''\n if (ref === '') return auth.password ?? ''\n const parsed = parseSecretRef(ref)\n if (parsed === undefined) throw new Error('\u8FDC\u7A0B\u76EE\u6807\u7684\u51ED\u636E\u5F15\u7528\u65E0\u6548\uFF1A\u8BF7\u91CD\u65B0\u586B\u5199\u5BC6\u7801\u6216\u6539\u7528\u5BC6\u94A5/agent \u8BA4\u8BC1')\n const store = systemSecretStore()\n if (store.backend !== parsed.backend) {\n throw new Error(`\u8BE5\u76EE\u6807\u7684\u5BC6\u7801\u5B58\u5728 ${parsed.backend}\uFF0C\u5F53\u524D\u7CFB\u7EDF\u53EF\u7528\u7684\u51ED\u636E\u540E\u7AEF\u662F ${store.backend}\uFF1A\u8BF7\u91CD\u65B0\u586B\u5199\u5BC6\u7801`)\n }\n const secret = store.get(SECRET_SERVICE, parsed.account)\n if (secret === undefined) throw new Error('\u7CFB\u7EDF\u51ED\u636E\u5B58\u50A8\u91CC\u627E\u4E0D\u5230\u8BE5\u76EE\u6807\u7684\u5BC6\u7801\uFF1A\u8BF7\u91CD\u65B0\u586B\u5199\u5BC6\u7801')\n return secret\n}\n\nfunction readAll(): RemoteTarget[] {\n const file = storeFile()\n if (!existsSync(file)) return []\n try {\n const parsed = JSON.parse(readFileSync(file, 'utf8')) as { targets?: RemoteTarget[] }\n return Array.isArray(parsed.targets) ? parsed.targets : []\n } catch { return [] }\n}\n\nfunction writeAll(targets: RemoteTarget[]): void {\n const file = storeFile()\n mkdirSync(dirname(file), { recursive: true })\n writeFileSync(file, JSON.stringify({ targets }, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n}\n\nfunction view(target: RemoteTarget): RemoteTargetView {\n const auth = target.auth\n const agentSocket = process.env['SSH_AUTH_SOCK'] ?? ''\n const parsed = parseSecretRef(auth.secretRef ?? '')\n const hasSecret = auth.kind === 'agent'\n ? agentSocket !== ''\n : auth.kind === 'key'\n ? (auth.keyPath ?? '') !== ''\n : parsed !== undefined || (auth.password ?? '') !== ''\n const secretStorage: RemoteTargetView['secretStorage'] = auth.kind !== 'password'\n ? 'none'\n : parsed !== undefined\n ? parsed.backend\n : (auth.password ?? '') !== '' ? 'plaintext' : 'none'\n const warning = auth.kind === 'agent' && agentSocket === ''\n ? '\u672A\u627E\u5230 SSH agent\uFF08SSH_AUTH_SOCK \u672A\u8BBE\u7F6E\uFF09\uFF1A\u8BF7\u542F\u52A8 ssh-agent \u6216\u6539\u7528\u5BC6\u94A5/\u5BC6\u7801'\n : secretStorage === 'plaintext'\n ? '\u5BC6\u7801\u4EE5\u660E\u6587\u4FDD\u5B58\u5728 remotes.json\uFF08\u7CFB\u7EDF\u51ED\u636E\u5B58\u50A8\u4E0D\u53EF\u7528\uFF09\uFF1A\u5EFA\u8BAE\u6539\u7528\u5BC6\u94A5\u6216 agent \u8BA4\u8BC1'\n : undefined\n return {\n id: target.id,\n name: target.name,\n host: target.host,\n port: target.port,\n user: target.user,\n authKind: auth.kind,\n hasSecret,\n secretStorage,\n ...(warning === undefined ? {} : { warning }),\n ...(target.workdir === undefined ? {} : { workdir: target.workdir }),\n ...(target.pythonPath === undefined ? {} : { pythonPath: target.pythonPath }),\n ...(target.pipIndexUrl === undefined ? {} : { pipIndexUrl: target.pipIndexUrl }),\n ...(auth.kind === 'key' && (auth.keyPath ?? '') !== '' ? { keyPath: auth.keyPath } : {}),\n }\n}\n\n/** \u89E3\u6790\u8981\u843D\u76D8\u7684\u5BC6\u7801\u51ED\u636E\uFF1A\u4F18\u5148\u7CFB\u7EDF\u51ED\u636E\u5B58\u50A8\uFF0C\u540E\u7AEF\u4E0D\u53EF\u7528\u65F6\u624D\u660E\u6587\u964D\u7EA7\u3002 */\nfunction passwordAuth(id: string, submitted: string | undefined, existing: RemoteTarget | undefined): RemoteAuth {\n if (submitted !== undefined && submitted !== '') {\n const store = systemSecretStore()\n if (store.backend !== 'none' && store.set(SECRET_SERVICE, secretAccount(id), submitted)) {\n return { kind: 'password', secretRef: secretRefOf(store.backend, id) }\n }\n return { kind: 'password', password: submitted }\n }\n const previous = existing?.auth\n if (previous?.secretRef !== undefined && previous.secretRef !== '') {\n return { kind: 'password', secretRef: previous.secretRef }\n }\n return { kind: 'password', password: previous?.password ?? '' }\n}\n\n/** \u5217\u51FA\u5DF2\u4FDD\u5B58\u7684\u8FDC\u7A0B\u76EE\u6807\uFF08\u8131\u654F\uFF09\u3002 */\nexport function listRemoteTargets(): RemoteTargetView[] {\n return readAll().map(view)\n}\n\n/** \u65B0\u5EFA\u6216\u66F4\u65B0\u4E00\u4E2A\u8FDC\u7A0B\u76EE\u6807\uFF1B\u4E0D\u4F20\u5BC6\u7801\u65F6\u4FDD\u7559\u539F\u51ED\u636E\u3002 */\nexport function upsertRemoteTarget(input: {\n id?: string\n name: string\n host: string\n port: number\n user: string\n authKind?: 'password' | 'key' | 'agent'\n password?: string\n keyPath?: string\n passphrase?: string\n workdir?: string\n pythonPath?: string\n pipIndexUrl?: string\n}): RemoteTargetView {\n const targets = readAll()\n const existing = input.id === undefined ? undefined : targets.find(item => item.id === input.id)\n const id = existing?.id ?? input.id ?? 'rt_' + Math.random().toString(36).slice(2, 10)\n const kind = input.authKind ?? existing?.auth.kind ?? 'password'\n const auth: RemoteAuth = kind === 'key'\n ? {\n kind: 'key',\n keyPath: input.keyPath !== undefined && input.keyPath !== '' ? input.keyPath : (existing?.auth.keyPath ?? ''),\n ...(input.passphrase !== undefined && input.passphrase !== '' ? { passphrase: input.passphrase } : (existing?.auth.passphrase === undefined ? {} : { passphrase: existing.auth.passphrase })),\n }\n : kind === 'agent'\n ? { kind: 'agent' }\n : passwordAuth(id, input.password, existing)\n const next: RemoteTarget = {\n id,\n name: input.name,\n host: input.host,\n port: input.port,\n user: input.user,\n auth,\n ...(input.workdir !== undefined && input.workdir !== '' ? { workdir: input.workdir } : (existing?.workdir === undefined ? {} : { workdir: existing.workdir })),\n ...(input.pythonPath !== undefined && input.pythonPath !== '' ? { pythonPath: input.pythonPath } : (existing?.pythonPath === undefined ? {} : { pythonPath: existing.pythonPath })),\n ...(input.pipIndexUrl !== undefined && input.pipIndexUrl !== '' ? { pipIndexUrl: input.pipIndexUrl } : (existing?.pipIndexUrl === undefined ? {} : { pipIndexUrl: existing.pipIndexUrl })),\n }\n // \u6362\u4E86\u8BA4\u8BC1\u65B9\u5F0F\u6216\u5BC6\u7801\uFF1A\u65E7\u51ED\u636E\u4E0D\u80FD\u6210\u4E3A\u94A5\u5319\u4E32\u91CC\u7684\u5B64\u513F\u6761\u76EE\u3002\n const previousRef = existing?.auth.secretRef ?? ''\n const nextRef = auth.secretRef ?? ''\n if (previousRef !== '' && previousRef !== nextRef) {\n const stale = parseSecretRef(previousRef)\n if (stale !== undefined) systemSecretStore().remove(SECRET_SERVICE, stale.account)\n }\n writeAll([...targets.filter(item => item.id !== id), next])\n return view(next)\n}\n\n/**\n * \u628A remotes.json \u91CC\u7684\u660E\u6587\u5BC6\u7801\u8FC1\u8FDB\u7CFB\u7EDF\u51ED\u636E\u5B58\u50A8\uFF08\u5E42\u7B49\uFF1B\u540E\u7AEF\u4E0D\u53EF\u7528\u65F6\u4E0D\u52A8\uFF09\u3002\n * \u63D2\u4EF6\u542F\u52A8\u65F6\u8C03\u7528\u4E00\u6B21\uFF1A\u5B58\u91CF\u76EE\u6807\u81EA\u52A8\u5B8C\u6210\u5347\u7EA7\uFF0C\u4E0D\u8981\u6C42\u7528\u6237\u91CD\u586B\u5BC6\u7801\u3002\n */\nexport function migratePlaintextSecrets(): { migrated: number; remaining: number } {\n const targets = readAll()\n const store = systemSecretStore()\n const plaintextCount = (list: RemoteTarget[]): number =>\n list.filter(item => item.auth.kind === 'password' && (item.auth.password ?? '') !== '').length\n if (store.backend === 'none') return { migrated: 0, remaining: plaintextCount(targets) }\n let migrated = 0\n const next = targets.map(target => {\n const secret = target.auth.password ?? ''\n if (target.auth.kind !== 'password' || secret === '') return target\n if (!store.set(SECRET_SERVICE, secretAccount(target.id), secret)) return target\n migrated += 1\n const auth: RemoteAuth = { kind: 'password', secretRef: secretRefOf(store.backend, target.id) }\n return { ...target, auth }\n })\n if (migrated > 0) writeAll(next)\n return { migrated, remaining: plaintextCount(next) }\n}\n\n/** \u5220\u9664\u76EE\u6807\uFF1B\u540C\u65F6\u6E05\u6389\u7CFB\u7EDF\u51ED\u636E\u5B58\u50A8\u91CC\u7684\u5BC6\u7801\u3002 */\nexport function removeRemoteTarget(id: string): boolean {\n const targets = readAll()\n const next = targets.filter(item => item.id !== id)\n if (next.length === targets.length) return false\n const removed = targets.find(item => item.id === id)\n const parsed = parseSecretRef(removed?.auth.secretRef ?? '')\n if (parsed !== undefined) systemSecretStore().remove(SECRET_SERVICE, parsed.account)\n writeAll(next)\n return true\n}\n\n/** \u8FDC\u7AEF\u88C5\u5305\u955C\u50CF\uFF1A\u76EE\u6807\u914D\u7F6E\u4F18\u5148\uFF0C\u7F3A\u7701\u6E05\u534E\u6E90\uFF08\u8BE5\u7F51\u7EDC\u8BBF\u95EE\u5B98\u65B9 PyPI \u5F88\u6162\uFF09\u3002 */\nexport const DEFAULT_PIP_INDEX = 'https://pypi.tuna.tsinghua.edu.cn/simple'\n\nexport function targetPipIndex(targetId: string): string {\n const target = readAll().find(item => item.id === targetId)\n return target?.pipIndexUrl ?? DEFAULT_PIP_INDEX\n}\n\n/** \u8FDC\u7AEF\u89E3\u91CA\u5668\uFF1A\u76EE\u6807\u91CC\u914D\u7F6E\u7684 venv python\uFF0C\u7F3A\u7701 python3\u3002 */\nexport function targetPython(targetId: string): string {\n const target = readAll().find(item => item.id === targetId)\n return target?.pythonPath ?? 'python3'\n}\n\nfunction findTarget(id: string): RemoteTarget {\n const target = readAll().find(item => item.id === id)\n if (target === undefined) throw new Error('\u8FDC\u7A0B\u76EE\u6807\u4E0D\u5B58\u5728\uFF1A' + id)\n return target\n}\n\n/** \u5EFA\u7ACB SSH \u8FDE\u63A5\uFF08\u8C03\u7528\u65B9\u8D1F\u8D23 end()\uFF09\u3002 */\nlet sshModulePromise: Promise<typeof import('ssh2')> | null = null\nfunction sshModule(): Promise<typeof import('ssh2')> {\n sshModulePromise ??= import('ssh2')\n return sshModulePromise\n}\n\nasync function connect(target: RemoteTarget): Promise<SshClient> {\n const { Client } = await sshModule()\n return await new Promise((resolve, reject) => {\n const client = new Client()\n const options: Record<string, unknown> = {\n host: target.host,\n port: target.port,\n username: target.user,\n readyTimeout: 20_000,\n keepaliveInterval: 10_000,\n }\n if (target.auth.kind === 'agent') {\n const socket = process.env['SSH_AUTH_SOCK'] ?? ''\n if (socket === '') throw new Error('\u672A\u627E\u5230 SSH agent\uFF08SSH_AUTH_SOCK \u672A\u8BBE\u7F6E\uFF09\uFF1A\u8BF7\u542F\u52A8 ssh-agent \u6216\u6539\u7528\u5BC6\u94A5/\u5BC6\u7801')\n options.agent = socket\n } else if (target.auth.kind === 'password') {\n options.password = resolvePassword(target.auth)\n } else {\n const keyPath = target.auth.keyPath ?? ''\n if (!existsSync(keyPath)) throw new Error('\u79C1\u94A5\u4E0D\u5B58\u5728\uFF1A' + keyPath)\n options.privateKey = readFileSync(keyPath)\n if ((target.auth.passphrase ?? '') !== '') options.passphrase = target.auth.passphrase\n }\n client.once('ready', () => resolve(client))\n client.once('error', (error: Error) => reject(error))\n client.connect(options)\n })\n}\n\nfunction runRemote(client: SshClient, command: string, timeoutMs: number): Promise<RemoteExecResult> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => reject(new Error('\u8FDC\u7AEF\u547D\u4EE4\u8D85\u65F6')), timeoutMs)\n client.exec(command, (error: Error | undefined, stream: any) => {\n if (error) { clearTimeout(timer); reject(error); return }\n let stdout = ''\n let stderr = ''\n stream.on('data', (chunk: Buffer) => { stdout += chunk.toString('utf8') })\n stream.stderr.on('data', (chunk: Buffer) => { stderr += chunk.toString('utf8') })\n stream.on('close', (code: number | null) => {\n clearTimeout(timer)\n resolve({ code, stdout, stderr })\n })\n })\n })\n}\n\nconst PROBE_COMMAND = [\n 'echo \"#UNAME\"; uname -sr',\n 'echo \"#GPU\"; (/usr/lib/wsl/lib/nvidia-smi -L 2>/dev/null || nvidia-smi -L 2>/dev/null || echo \"no-gpu\")',\n 'echo \"#PY\"; (python3 -V 2>&1 || echo \"no-python\")',\n 'echo \"#CPU\"; nproc',\n 'echo \"#MEM\"; (free -g 2>/dev/null | awk \"/Mem:/{print \\$2}\" || echo 0)',\n].join('; ')\n\nfunction section(text: string, name: string): string {\n const start = text.indexOf('#' + name)\n if (start < 0) return ''\n const rest = text.slice(start + name.length + 1)\n const end = rest.search(/\\n#/)\n return (end < 0 ? rest : rest.slice(0, end)).trim()\n}\n\n/** \u8FDE\u63A5\u5E76\u63A2\u6D4B\uFF1A\u7CFB\u7EDF\u3001GPU\u3001Python\u3001\u6838\u6570\u3001\u5185\u5B58\u3002 */\nexport async function probeRemoteTarget(id: string): Promise<RemoteProbe> {\n const started = Date.now()\n let client: SshClient | undefined\n try {\n const target = findTarget(id)\n client = await connect(target)\n const result = await runRemote(client, PROBE_COMMAND, 30_000)\n const gpuSection = section(result.stdout, 'GPU')\n const gpus = gpuSection === '' || gpuSection.includes('no-gpu') || gpuSection.includes('not found')\n ? []\n : gpuSection.split('\\n').map(line => line.trim()).filter(line => line !== '')\n const python = section(result.stdout, 'PY')\n const cpus = Number(section(result.stdout, 'CPU'))\n const memory = Number(section(result.stdout, 'MEM'))\n return {\n ok: true,\n latencyMs: Date.now() - started,\n uname: section(result.stdout, 'UNAME'),\n gpu: gpus,\n python: python === '' ? undefined : python,\n ...(Number.isFinite(cpus) ? { cpus } : {}),\n ...(Number.isFinite(memory) ? { memoryGb: memory } : {}),\n }\n } catch (error) {\n return { ok: false, latencyMs: Date.now() - started, error: error instanceof Error ? error.message : String(error) }\n } finally {\n client?.end()\n }\n}\n\n/** \u9012\u5F52\u4E0A\u4F20\u672C\u5730\u76EE\u5F55\u5230\u8FDC\u7AEF\uFF08SFTP\uFF09\u3002 */\nexport async function uploadDirectory(targetId: string, localDir: string, remoteDir: string): Promise<{ files: number }> {\n const target = findTarget(targetId)\n const client = await connect(target)\n try {\n const files: { local: string; remote: string }[] = []\n const walk = (dir: string, prefix: string): void => {\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.name.startsWith('.')) continue\n const local = join(dir, entry.name)\n const remote = prefix + '/' + entry.name\n if (entry.isDirectory()) walk(local, remote)\n else files.push({ local, remote })\n }\n }\n walk(localDir, '')\n const sftp = await new Promise<SFTPWrapper>((resolve, reject) => {\n client.sftp((error, value) => { if (error) reject(error); else resolve(value) })\n })\n await new Promise<void>((resolve, reject) => {\n let index = 0\n const next = (): void => {\n if (index >= files.length) { resolve(); return }\n const item = files[index]!\n index += 1\n const parts = item.remote.split('/').filter(part => part !== '')\n let created = remoteDir\n for (const part of parts.slice(0, -1)) {\n created += '/' + part\n sftp.mkdir(created, () => undefined)\n }\n sftp.fastPut(item.local, remoteDir + item.remote, (error) => { if (error) reject(error); else next() })\n }\n next()\n })\n return { files: files.length }\n } finally { client.end() }\n}\n\n/** \u9012\u5F52\u4E0B\u8F7D\u8FDC\u7AEF\u76EE\u5F55\u5230\u672C\u5730\uFF08\u5931\u8D25\u65F6\u5C3D\u91CF\u4FDD\u7559\u5DF2\u4E0B\u8F7D\u90E8\u5206\uFF09\u3002 */\nexport async function downloadDirectory(targetId: string, remoteDir: string, localDir: string): Promise<{ files: number }> {\n const target = findTarget(targetId)\n const client = await connect(target)\n try {\n const sftp = await new Promise<SFTPWrapper>((resolve, reject) => {\n client.sftp((error, value) => { if (error) reject(error); else resolve(value) })\n })\n const list = (dir: string): Promise<{ name: string; dir: boolean }[]> => new Promise((resolve, reject) => {\n sftp.readdir(dir, (error, entries) => {\n if (error) { reject(error); return }\n resolve(entries.filter(entry => entry.filename !== '.' && entry.filename !== '..')\n .map(entry => ({ name: entry.filename, dir: (entry.attrs.mode & 0o040000) !== 0 })))\n })\n })\n let count = 0\n const walk = async (remote: string, local: string, depth: number): Promise<void> => {\n if (depth > 4) return\n let entries: { name: string; dir: boolean }[]\n try { entries = await list(remote) } catch { return }\n mkdirSync(local, { recursive: true })\n for (const entry of entries) {\n const remotePath = remote + '/' + entry.name\n const localPath = join(local, entry.name)\n if (entry.dir) { await walk(remotePath, localPath, depth + 1); continue }\n await new Promise<void>((resolve, reject) => {\n sftp.fastGet(remotePath, localPath, error => { if (error) reject(error); else resolve() })\n }).then(() => { count += 1 }).catch(() => undefined)\n }\n }\n await walk(remoteDir, localDir, 0)\n return { files: count }\n } finally { client.end() }\n}\n\n/** \u5728\u8FDC\u7AEF\u6267\u884C\u4E00\u6761\u547D\u4EE4\uFF08cwd \u9ED8\u8BA4\u76EE\u6807 workdir\uFF09\u3002 */\nexport async function execRemoteTarget(id: string, command: string, timeoutMs = 120_000): Promise<RemoteExecResult> {\n const target = findTarget(id)\n const client = await connect(target)\n try {\n const full = (target.workdir ?? '') === ''\n ? command\n : 'cd ' + JSON.stringify(target.workdir) + ' 2>/dev/null; ' + command\n return await runRemote(client, full, timeoutMs)\n } finally {\n client.end()\n }\n}\n", "/** \u7CFB\u7EDF\u51ED\u636E\u5B58\u50A8\uFF1A\u8BA9\u8FDC\u7A0B\u76EE\u6807\u7684\u5BC6\u7801\u4E0D\u843D JSON \u660E\u6587\u3002\n *\n * \u540E\u7AEF\u9009\u62E9\uFF1A\n * - macOS\uFF1A`security(1)` \u7684 generic password\uFF08\u767B\u5F55\u94A5\u5319\u4E32\uFF0C\u968F\u7528\u6237\u767B\u5F55\u89E3\u9501\uFF09\uFF1B\n * - Linux\uFF1Afreedesktop Secret Service \u7684 `secret-tool`\uFF1B\n * - \u5176\u5B83\u5E73\u53F0\u6216\u7F3A CLI\uFF1A`none`\uFF0C\u8C03\u7528\u65B9\u9000\u56DE 0600 \u660E\u6587\u5E76\u5728\u89C6\u56FE\u91CC\u660E\u786E\u544A\u8B66\u3002\n *\n * KSRW_SECRET_STORE \u53EF\u5F3A\u5236\u540E\u7AEF\uFF1A`keychain` / `secret-service` / `none`\n * \uFF08\u6D4B\u8BD5\u4E0E CI \u7528 `none` \u56FA\u5B9A\u8D70\u964D\u7EA7\u8DEF\u5F84\uFF09\u3002\n */\nimport { spawnSync } from 'node:child_process'\n\nexport type SecretBackend = 'keychain' | 'secret-service' | 'none'\n\nexport interface SecretStore {\n readonly backend: SecretBackend\n /** \u5199\u5165\uFF08\u8986\u76D6\uFF09\u4E00\u6761\u51ED\u636E\uFF1B\u8FD4\u56DE\u662F\u5426\u843D\u5E93\u6210\u529F\u3002 */\n set(service: string, account: string, secret: string): boolean\n /** \u8BFB\u51FA\u51ED\u636E\uFF1B\u4E0D\u5B58\u5728\u6216\u540E\u7AEF\u4E0D\u53EF\u7528\u65F6\u8FD4\u56DE undefined\u3002 */\n get(service: string, account: string): string | undefined\n /** \u5220\u9664\u51ED\u636E\uFF1B\u8FD4\u56DE\u662F\u5426\u771F\u7684\u5220\u6389\u4E86\u4E00\u6761\u3002 */\n remove(service: string, account: string): boolean\n}\n\nfunction run(command: string, args: string[], input?: string): { ok: boolean; stdout: string } {\n const result = spawnSync(command, args, {\n encoding: 'utf8',\n input,\n timeout: 10_000,\n stdio: ['pipe', 'pipe', 'ignore'],\n })\n return { ok: result.error === undefined && result.status === 0, stdout: (result.stdout ?? '').trim() }\n}\n\nfunction commandExists(command: string): boolean {\n const result = spawnSync(command, ['--help'], { stdio: 'ignore', timeout: 5_000 })\n return result.error === undefined\n}\n\nconst NONE: SecretStore = {\n backend: 'none',\n set: () => false,\n get: () => undefined,\n remove: () => false,\n}\n\n/** macOS \u767B\u5F55\u94A5\u5319\u4E32\u3002\u5BC6\u7801\u7ECF argv \u4F20\u7ED9 security(1)\uFF0C\u672C\u673A\u5355\u7528\u6237\u573A\u666F\u53EF\u63A5\u53D7\u3002 */\nfunction keychainStore(): SecretStore {\n return {\n backend: 'keychain',\n set(service, account, secret) {\n return run('security', ['add-generic-password', '-U', '-a', account, '-s', service, '-w', secret]).ok\n },\n get(service, account) {\n const result = run('security', ['find-generic-password', '-a', account, '-s', service, '-w'])\n return result.ok && result.stdout !== '' ? result.stdout : undefined\n },\n remove(service, account) {\n return run('security', ['delete-generic-password', '-a', account, '-s', service]).ok\n },\n }\n}\n\n/** freedesktop Secret Service\u3002secret \u8D70 stdin\uFF0C\u4E0D\u8FDB argv\u3002 */\nfunction secretServiceStore(): SecretStore {\n return {\n backend: 'secret-service',\n set(service, account, secret) {\n return run('secret-tool', ['store', '--label', `KSRW ${account}`, 'service', service, 'account', account], secret).ok\n },\n get(service, account) {\n const result = run('secret-tool', ['lookup', 'service', service, 'account', account])\n return result.ok && result.stdout !== '' ? result.stdout : undefined\n },\n remove(service, account) {\n return run('secret-tool', ['clear', 'service', service, 'account', account]).ok\n },\n }\n}\n\nlet cached: { key: string; store: SecretStore } | undefined\n\n/** \u89E3\u6790\u5F53\u524D\u5E73\u53F0\u7684\u51ED\u636E\u540E\u7AEF\uFF08\u6309 env + \u5E73\u53F0\u7F13\u5B58\uFF0C\u6539 env \u5373\u5931\u6548\uFF09\u3002 */\nexport function systemSecretStore(): SecretStore {\n const forced = process.env['KSRW_SECRET_STORE']?.trim() ?? ''\n const key = forced + '|' + process.platform\n if (cached?.key === key) return cached.store\n let store = NONE\n if (forced === 'none') {\n store = NONE\n } else if (forced === '' || forced === 'keychain') {\n if (process.platform === 'darwin' && commandExists('security')) store = keychainStore()\n }\n if (store.backend === 'none' && (forced === '' || forced === 'secret-service') && process.platform === 'linux' && commandExists('secret-tool')) {\n store = secretServiceStore()\n }\n cached = { key, store }\n return store\n}\n", "/** \u4F1A\u8BDD\u515C\u5E95\u5F52\u6863\uFF1Aagent \u5FD8\u4E86\u8C03 ksrw_archive \u65F6\uFF0C\u5BBF\u4E3B\u4ECD\u628A\u5DE5\u4F4D\u4F1A\u8BDD\u7684\u6210\u679C\u5F52\u6863\u8FDB\u9879\u76EE\u5E93\u3002\n *\n * \u4F9D\u636E\uFF1A\u5F15\u64CE\u628A\u6BCF\u4E2A\u4F1A\u8BDD\u5FEB\u7167\u5199\u6210 `<QILIN_HOME>/sessions/<workspace>/<session>/session.v*.jsonl.zstd`\uFF0C\n * \u9996\u884C\u662F\u4F1A\u8BDD\u5143\u6570\u636E `{ id, cwd, agentPreset, createdAt }`\u3002\u5DE1\u68C0\u53EA\u770B\u9996\u884C\uFF0C\u4E0D\u89E3\u6790\u6574\u6BB5\u5BF9\u8BDD\u3002\n *\n * \u89C4\u5219\uFF1A\n * - \u53EA\u5904\u7406 agentPreset \u4EE5 `ksrw-` \u5F00\u5934\u7684\u4F1A\u8BDD\uFF08\u5DE5\u4F4D\u9884\u8BBE agent\uFF09\uFF1B\n * - cwd \u672C\u8EAB\u662F\u5DF2\u767B\u8BB0\u7684 KSRW \u9879\u76EE \u2192 \u628A\u5F52\u6863\u5199\u8FDB\u8BE5\u9879\u76EE\uFF1Bdetached \u5DE5\u574A\u4E0D\u63D0\u5347\uFF1B\n * - cwd \u4E0D\u662F KSRW \u9879\u76EE \u2192 \u5728\u9879\u76EE\u5E93\u91CC\u5EFA\u4E00\u6761\u300C\u4F1A\u8BDD\u5F52\u6863\u300D\u5F71\u5B50\u9879\u76EE\uFF0C\u4E0D\u5F80\u7528\u6237\u5DE5\u4F5C\u76EE\u5F55\u5199\u4EFB\u4F55\u6587\u4EF6\uFF1B\n * - \u6309 (sessionId, \u5FEB\u7167 mtime) \u53BB\u91CD\uFF0C\u91CD\u542F\u4E0D\u4F1A\u91CD\u590D\u5F52\u6863\u3002\n */\nimport { createReadStream, existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { createZstdDecompress } from 'node:zlib'\n\nimport { ProjectStore, resolveKsrwHome } from 'ksrw-kernel'\n\nconst ROLE_PREFIX = 'ksrw-'\nconst TICK_MS = 60_000\nconst MAX_SNAPSHOT_BYTES = 32 * 1024 * 1024\n\nexport interface AutoArchiveLog {\n info?(message: string): void\n warn?(message: string): void\n}\n\ninterface SessionMeta {\n readonly id?: string\n readonly cwd?: string\n readonly agentPreset?: string\n}\n\n/** \u8BFB\u4F1A\u8BDD\u5FEB\u7167\u9996\u884C\u5143\u6570\u636E\uFF08\u53EA\u89E3\u538B\u5230\u7B2C\u4E00\u884C\u5C31\u65AD\u5F00\uFF09\u3002 */\nexport async function readSessionMeta(file: string): Promise<SessionMeta | undefined> {\n try {\n if (statSync(file).size > MAX_SNAPSHOT_BYTES) return undefined\n } catch { return undefined }\n return await new Promise<SessionMeta | undefined>((resolve) => {\n let settled = false\n const finish = (value?: SessionMeta): void => {\n if (settled) return\n settled = true\n try { stream.destroy() } catch { /* stream already closed */ }\n resolve(value)\n }\n let stream: ReturnType<typeof createReadStream> | undefined\n try {\n const decompress = createZstdDecompress()\n stream = createReadStream(file).pipe(decompress) as unknown as ReturnType<typeof createReadStream>\n let buffer = ''\n stream.on('data', (chunk: Buffer) => {\n buffer += chunk.toString('utf8')\n const newline = buffer.indexOf('\\n')\n if (newline < 0) {\n if (buffer.length > 262_144) finish(undefined)\n return\n }\n try { finish(JSON.parse(buffer.slice(0, newline)) as SessionMeta) } catch { finish(undefined) }\n })\n stream.on('error', () => finish(undefined))\n stream.on('end', () => finish(undefined))\n } catch {\n finish(undefined)\n }\n })\n}\n\n/** \u6536\u96C6\u6240\u6709\u53EF\u80FD\u5FEB\u7167\uFF1A<sessionsRoot>/<workspace>/<session>/session.v*.jsonl.zstd\u3002 */\nexport function collectSnapshots(sessionsRoot: string): { file: string; sessionDir: string }[] {\n const out: { file: string; sessionDir: string }[] = []\n let workspaces: string[] = []\n try { workspaces = readdirSync(sessionsRoot) } catch { return out }\n for (const workspace of workspaces) {\n const workspacePath = join(sessionsRoot, workspace)\n let sessions: string[] = []\n try { sessions = readdirSync(workspacePath) } catch { continue }\n for (const session of sessions) {\n const sessionDir = join(workspacePath, session)\n let files: string[] = []\n try { files = readdirSync(sessionDir) } catch { continue }\n for (const name of files) {\n if (!/^session\\.v\\d+\\.jsonl\\.zstd$/.test(name)) continue\n out.push({ file: join(sessionDir, name), sessionDir })\n }\n }\n }\n return out\n}\n\nfunction slugify(name: string): string {\n const slug = name.trim().toLowerCase().replace(/[^\\p{L}\\p{N}]+/gu, '-').replace(/^-+|-+$/g, '')\n return slug === '' ? 'session-archive' : slug.slice(0, 60)\n}\n\nfunction loadMarkers(file: string): Record<string, number> {\n try {\n const parsed = JSON.parse(readFileSync(file, 'utf8')) as { sessions?: Record<string, number> }\n return parsed.sessions ?? {}\n } catch { return {} }\n}\n\nfunction saveMarkers(file: string, markers: Record<string, number>): void {\n try {\n mkdirSync(dirname(file), { recursive: true, mode: 0o700 })\n writeFileSync(file, JSON.stringify({ version: 1, sessions: markers }, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n } catch { /* \u53BB\u91CD\u8BB0\u4E0D\u4E0B\u65F6\u4E0B\u6B21\u6700\u591A\u591A\u5F52\u6863\u4E00\u6B21\uFF0C\u4E0D\u5F71\u54CD\u6B63\u786E\u6027 */ }\n}\n\n/** \u5F52\u6863\u4E00\u4E2A\u5DE5\u4F4D\u4F1A\u8BDD\uFF1B\u8FD4\u56DE\u5F52\u6863\u6587\u4EF6\u8DEF\u5F84\uFF0C\u8DF3\u8FC7\u65F6\u8FD4\u56DE undefined\u3002 */\nexport async function archiveSession(\n store: ProjectStore,\n meta: SessionMeta,\n): Promise<string | undefined> {\n const cwd = meta.cwd ?? ''\n const role = meta.agentPreset ?? ''\n if (!role.startsWith(ROLE_PREFIX) || cwd === '' || !existsSync(cwd)) return undefined\n const now = Date.now()\n const stamp = new Date(now).toISOString().replace(/[:.]/g, '-')\n const sessionLabel = (meta.id ?? '').slice(-8) || 'unknown'\n let projectPath = cwd\n if (!existsSync(join(cwd, 'project.json'))) {\n // \u7528\u6237\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0D\u662F KSRW \u9879\u76EE\uFF1A\u53EA\u5728\u9879\u76EE\u5E93\u91CC\u8BB0\u4E00\u6761\u5F71\u5B50\u5F52\u6863\uFF0C\u7EDD\u4E0D\u5F80\u5DE5\u4F5C\u76EE\u5F55\u5199\u6587\u4EF6\u3002\n const name = '\u4F1A\u8BDD\u5F52\u6863 \u00B7 ' + (cwd.split(/[\\/\\\\]/).filter(Boolean).pop() ?? sessionLabel)\n projectPath = store.create({ name, idea: '\u6765\u81EA\u5DE5\u4F4D\u4F1A\u8BDD\u7684\u81EA\u52A8\u5F52\u6863\uFF1A' + cwd }).path\n } else {\n // \u5DF2\u6709\u9879\u76EE\uFF1Adetached \u5DE5\u574A\u4E0D\u63D0\u5347\u8FDB\u9879\u76EE\u5E93\uFF0C\u4F46\u4ECD\u53EF\u5199\u5165\u5F52\u6863\u7EAA\u8981\u3002\n const project = store.readProject(cwd)\n if (project.detached !== true) store.register(cwd)\n projectPath = cwd\n }\n const archiveDir = join(projectPath, 'archive')\n mkdirSync(archiveDir, { recursive: true, mode: 0o700 })\n const file = join(archiveDir, 'auto-' + stamp + '.md')\n const body = [\n '# \u81EA\u52A8\u5F52\u6863 \u00B7 ' + stamp,\n '',\n '- \u5DE5\u4F4D\u9884\u8BBE\uFF1A' + role,\n '- \u4F1A\u8BDD\uFF1A' + (meta.id ?? '(unknown)'),\n '- \u5DE5\u4F5C\u76EE\u5F55\uFF1A' + cwd,\n '- \u89E6\u53D1\uFF1A\u5BBF\u4E3B\u5DE1\u68C0\u515C\u5E95\uFF08agent \u672A\u5FC5\u8C03\u7528\u8FC7 ksrw_archive\uFF09',\n '',\n '## \u8BF4\u660E',\n '',\n '\u672C\u6B21\u4EFB\u52A1\u7684\u5F52\u6863\u7531\u5BBF\u4E3B\u81EA\u52A8\u5B8C\u6210\uFF0C\u53EA\u8BB0\u5F55\u4F1A\u8BDD\u8EAB\u4EFD\u4E0E\u5DE5\u4F5C\u76EE\u5F55\uFF1B\u5177\u4F53\u7ED3\u8BBA\u89C1\u8BE5\u4F1A\u8BDD\u7684\u5BF9\u8BDD\u8BB0\u5F55\u3002',\n '',\n ].join('\\n')\n writeFileSync(file, body, { encoding: 'utf8', mode: 0o600 })\n return file\n}\n\n/** \u542F\u52A8\u5DE1\u68C0\uFF1B\u8FD4\u56DE\u505C\u6B62\u51FD\u6570\u3002 */\nexport function startSessionAutoArchive(log?: AutoArchiveLog): () => void {\n const home = resolveKsrwHome()\n const sessionsRoot = join(dirname(home), 'sessions')\n const markerPath = join(home, 'auto-archive.json')\n const markers = loadMarkers(markerPath)\n // \u9996\u6B21\u542F\u7528\uFF08\u8FD8\u6CA1\u6709\u53BB\u91CD\u8868\uFF09\u53EA\u505A\u57FA\u7EBF\u767B\u8BB0\uFF1A\u4E0D\u628A\u5386\u53F2\u4F1A\u8BDD\u4E00\u6B21\u6027\u5012\u8FDB\u9879\u76EE\u5E93\u3002\n let baseline = !existsSync(markerPath)\n let running = false\n let dirty = false\n\n const tick = async (): Promise<void> => {\n if (running) return\n running = true\n try {\n const store = new ProjectStore({ home })\n for (const { file } of collectSnapshots(sessionsRoot)) {\n let mtime = 0\n try { mtime = statSync(file).mtimeMs } catch { continue }\n const meta = await readSessionMeta(file)\n const id = meta?.id ?? ''\n if (id === '' || meta === undefined) continue\n if (markers[id] === mtime) continue\n if (baseline) { markers[id] = mtime; dirty = true; continue }\n if (!(meta.agentPreset ?? '').startsWith(ROLE_PREFIX)) { markers[id] = mtime; dirty = true; continue }\n try {\n const archived = await archiveSession(store, meta)\n if (archived !== undefined) log?.info?.('[auto-archive] \u5DF2\u5F52\u6863\u4F1A\u8BDD ' + id + ' \u2192 ' + archived)\n markers[id] = mtime\n dirty = true\n } catch (error) {\n log?.warn?.('[auto-archive] \u5F52\u6863\u5931\u8D25\uFF1A' + (error instanceof Error ? error.message : String(error)))\n }\n }\n } catch (error) {\n // Node 22 \u9ED8\u8BA4 unhandledRejection=throw\uFF1A\u5DE1\u68C0\u5F02\u5E38\u4E0D\u80FD\u5E26\u8D70\u5BBF\u4E3B\u8FDB\u7A0B\u3002\n log?.warn?.('[auto-archive] \u5DE1\u68C0\u5931\u8D25\uFF1A' + (error instanceof Error ? error.message : String(error)))\n } finally {\n running = false\n if (baseline) {\n // \u57FA\u7EBF\u767B\u8BB0\u5B8C\u6210\u540E\u624D\u5F00\u59CB\u771F\u6B63\u5F52\u6863\u65B0\u4F1A\u8BDD\u3002\n baseline = false\n dirty = true\n log?.info?.('[auto-archive] \u5DF2\u8BB0\u5F55\u73B0\u6709\u4F1A\u8BDD\u57FA\u7EBF\uFF0C\u540E\u7EED\u5DE5\u4F4D\u4F1A\u8BDD\u5C06\u81EA\u52A8\u5F52\u6863')\n }\n if (dirty) { saveMarkers(markerPath, markers); dirty = false }\n }\n }\n\n const timer = setInterval(() => { void tick() }, TICK_MS)\n timer.unref?.()\n void tick()\n return () => clearInterval(timer)\n}\n"],
4
+ "sourcesContent": ["/** ksrw-core \u2014 KSRW v2 \u5BBF\u4E3B\u63D2\u4EF6\uFF08M0 \u9AA8\u67B6\uFF09\u3002\n *\n * \u4E09\u4EF6\u4E8B\uFF1A\n * 1. exact / \u63D0\u4F9B\u81EA\u6709 auth \u9875\uFF08\u6D4F\u89C8\u5668/\u5F00\u53D1\u6001\u56DE\u9000\uFF1B\u684C\u9762\u6001\u7531\u5F15\u64CE welcome \u7A97\u627F\u8F7D\uFF0Crenderer \u7528\u540C\u4E00\u5957\u8BBE\u8BA1\uFF09\uFF1B\n * 2. /ksrw/api \u63D0\u4F9B\u4EA7\u54C1 API\uFF08M0 \u5148 health\uFF09\uFF1B/ksrw/* \u63D0\u4F9B\u9759\u6001\u8D44\u6E90\uFF1B\n * 3. \u9886\u57DF\u5DE5\u5177\u7531 ksrw-tools \u6309\u5DE5\u4F4D\u9884\u8BBE\u6302\u8F7D\uFF08\u672C\u63D2\u4EF6\u53EA\u505A UI/API/\u9759\u6001\u8D44\u6E90\uFF09\u3002\n *\n * \u8BA4\u8BC1\u80FD\u529B\u6CBF\u7528\u5F15\u64CE\uFF1A\u684C\u9762 welcome \u7A97\u7684 preload\uFF08window.dshWelcome\uFF09\u8C03\u7528 local-auth\uFF1B\n * \u6D4F\u89C8\u5668\u56DE\u9000\u9875\u8C03 /api/auth/*\uFF08accounts-local\uFF09\u3002\u4EA7\u54C1\u4E0D\u65B0\u5199\u8D26\u6237\u7CFB\u7EDF\u3002\n */\nimport { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from 'node:fs'\nimport { readFile } from 'node:fs/promises'\nimport { dirname, extname, join } from 'node:path'\nimport { homedir } from 'node:os'\nimport { fileURLToPath } from 'node:url'\n\nimport { ProjectStore, resolveKsrwHome } from 'ksrw-kernel'\n\nimport { createRequestGuard, handleApi, rejectRequest, UNAUTHENTICATED_ESCAPE } from './api.ts'\nimport { startSessionAutoArchive } from './auto-archive.ts'\nimport type { ConnectionFence, RequestGuard } from './api.ts'\nimport { migratePlaintextSecrets } from './remote.ts'\n\nexport const name = 'ksrw-core'\nexport const inject = ['webServer'] as const\nexport const VERSION = '0.1.2'\n\nexport const PUBLIC_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', 'public')\n\ninterface ServerResponseLike {\n writeHead(code: number, headers?: Record<string, string>): unknown\n end(body?: unknown): unknown\n}\ninterface ServerRequestLike { url?: string; method?: string }\ninterface RouteLike {\n kind: 'exact' | 'prefix'\n path: string\n handler: (req: ServerRequestLike, res: ServerResponseLike) => void | Promise<void>\n}\nexport interface ContextLike {\n readonly webServer: { register(route: RouteLike): () => void }\n readonly connection?: { requestRejection?(req: unknown): number | undefined }\n readonly reflect?: { get?(name: string, strict?: boolean): unknown }\n readonly effect?: (disposer: () => unknown, label?: string) => unknown\n readonly logger?: { warn?(msg: string): void; info?(msg: string): void }\n}\n\nconst MIME: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.json': 'application/json',\n '.png': 'image/png',\n}\n\n/** \u4E00\u7EA7\u6587\u4EF6\u540D\u5B88\u536B\uFF1A\u62D2\u7EDD\u7A7A\u3001\u5206\u9694\u7B26\u3001..\uFF08\u9759\u6001\u8D44\u6E90\u53EA\u5141\u8BB8\u5E73\u94FA\uFF09\u3002 */\nexport function safeStaticName(raw: string): string | null {\n if (raw === '' || raw.includes('/') || raw.includes('\\\\') || raw.includes('..')) return null\n return raw\n}\n\nasync function serveFile(fileName: string, res: ServerResponseLike): Promise<void> {\n try {\n const body = await readFile(join(PUBLIC_ROOT, fileName))\n res.writeHead(200, { 'content-type': MIME[extname(fileName).toLowerCase()] ?? 'application/octet-stream' })\n res.end(body)\n } catch {\n res.writeHead(404)\n res.end()\n }\n}\n\n/** \u628A\u5B66\u672F\u6392\u7248\u6A21\u677F\u64AD\u79CD\u5230 KSRW home\uFF08kernel \u7684 paperTemplatePath \u4ECE\u8FD9\u91CC\u8BFB\uFF09\u3002 */\nfunction seedPaperTemplate(): void {\n try {\n const home = resolveKsrwHome()\n // 1) \u4F1A\u8BAE/\u671F\u520A\u6A21\u677F\u76EE\u5F55\n const templateRoot = join(PUBLIC_ROOT, 'templates')\n const targetDir = join(home, 'assets', 'templates')\n if (existsSync(templateRoot)) {\n mkdirSync(targetDir, { recursive: true })\n for (const fileName of readdirSync(templateRoot)) {\n if (!fileName.endsWith('.docx') && fileName !== 'index.json') continue\n const source = join(templateRoot, fileName)\n const target = join(targetDir, fileName)\n const sourceSize = statSync(source).size\n if (existsSync(target) && statSync(target).size === sourceSize) continue\n copyFileSync(source, target)\n }\n }\n // 2) \u65E7\u7248\u5355\u4E00\u6A21\u677F\uFF08\u517C\u5BB9\u5DF2\u4FDD\u5B58\u7684\u9879\u76EE\uFF09\n const legacy = join(PUBLIC_ROOT, 'reference-academic.docx')\n if (!existsSync(legacy)) return\n const legacyTarget = join(home, 'assets', 'reference-academic.docx')\n if (existsSync(legacyTarget) && statSync(legacyTarget).size === statSync(legacy).size) return\n mkdirSync(dirname(legacyTarget), { recursive: true })\n copyFileSync(legacy, legacyTarget)\n } catch { /* \u64AD\u79CD\u5931\u8D25\u65F6\u9000\u56DE pandoc \u9ED8\u8BA4\u6A21\u677F\uFF0C\u4E0D\u963B\u585E\u542F\u52A8 */ }\n}\n\nexport function apply(ctx: ContextLike): void {\n const disposers: (() => void)[] = []\n const effect = (fn: () => unknown, label: string): void => {\n if (typeof ctx.effect === 'function') ctx.effect(fn, label)\n }\n\n // \u63D2\u4EF6\u6A21\u5F0F\uFF1A/ \u7531\u5F15\u64CE Web \u5E94\u7528\u63D0\u4F9B\uFF0C\u63D2\u4EF6\u4E0D\u518D\u5360\u7528\u9996\u9875\u3001\u4E0D\u81EA\u5EFA\u767B\u5F55/\u6CE8\u518C\u3002\n // \u684C\u9762\u4EA7\u54C1\u7684 welcome \u7A97\u4ECD\u7531\u684C\u9762\u58F3\u81EA\u5DF1\u7684\u9759\u6001\u9875\u627F\u8F7D\uFF0C\u4E0E\u672C\u63D2\u4EF6\u65E0\u5173\u3002\n\n const soft = (name: string): unknown => {\n try { return ctx.reflect?.get?.(name, false) ?? null } catch { return null }\n }\n // \u9274\u6743\u6743\u5A01\u662F\u5F15\u64CE\u7684 connection \u56F4\u680F\u3002\u8BF7\u6C42\u65F6\u89E3\u6790\u800C\u4E0D\u662F apply \u65F6\u89E3\u6790\uFF1A\n // connection \u7684\u6302\u8F7D\u987A\u5E8F\u53EF\u80FD\u5728 ksrw-core \u4E4B\u540E\uFF08\u65E9\u8BFB\u4F1A\u62FF\u5230 null \u5E76\u6C38\u4E45\u6536\u7D27\uFF09\u3002\n let guardWarned = false\n const requestGuard = (): RequestGuard => {\n const fence = soft('connection') as ConnectionFence | null\n if (typeof fence?.requestRejection !== 'function' && !guardWarned) {\n guardWarned = true\n if (process.env[UNAUTHENTICATED_ESCAPE] === '1') {\n ctx.logger?.warn?.(`ksrw-core: \u5BBF\u4E3B\u672A\u6302\u8F7D connection\uFF0C${UNAUTHENTICATED_ESCAPE}=1 \u5DF2\u663E\u5F0F\u653E\u884C /ksrw/api\uFF08\u4EC5\u9650\u5F00\u53D1\u6001\uFF09`)\n } else {\n ctx.logger?.warn?.('ksrw-core: \u5BBF\u4E3B\u672A\u6302\u8F7D connection\uFF0C/ksrw/api \u5DF2 fail-closed\uFF08503\uFF09\uFF1B\u684C\u9762\u7AEF\u8BF7\u786E\u8BA4 accounts \u5DF2\u542F\u7528')\n }\n }\n return createRequestGuard(fence)\n }\n /** \u8FD4\u56DE true \u8868\u793A\u5DF2\u62D2\u7EDD\u5E76\u5199\u5B8C\u54CD\u5E94\uFF08\u8C03\u7528\u65B9\u76F4\u63A5 return\uFF09\u3002 */\n const rejectIfBlocked = (req: ServerRequestLike, res: ServerResponseLike): boolean => {\n const status = requestGuard()(req as never)\n if (typeof status !== 'number') return false\n rejectRequest(res as never, status)\n return true\n }\n\n // 3) /ksrw/api\uFF1A\u9879\u76EE\u5E93 / \u7814\u7A76\u72B6\u6001 / \u9009\u9898\u5361 / \u63D0\u6848\n const store = new ProjectStore()\n disposers.push(ctx.webServer.register({\n kind: 'prefix',\n path: '/ksrw/api',\n handler: async (req, res) => {\n // \u5F15\u64CE web \u7EC4\u5408\u5C42\u6302\u8F7D\u7684 office-to-pdf\uFF1AWord/PPT \u6210\u679C\u9884\u89C8\u8F6C PDF\u3002\u61D2\u89E3\u6790\uFF0C\n // \u56E0\u4E3A\u5B83\u7684\u6302\u8F7D\u987A\u5E8F\u53EF\u80FD\u5728 ksrw-core \u4E4B\u540E\uFF08\u7F3A\u5931\u65F6\u9884\u89C8\u7AEF\u70B9\u660E\u786E\u62A5\u9519\uFF09\u3002\n const officeToPdf = soft('officeToPdf') as\n | { convert(request: Record<string, unknown>): Promise<{ pdf: Uint8Array; missingFonts?: string[] }> }\n | undefined\n // \u5B9A\u65F6\u6293\u53D6\u4E0D\u518D\u7531\u63D2\u4EF6\u81EA\u5EFA\u8C03\u5EA6\uFF1A\u8BFB\u5199\u90FD\u4EA4\u7ED9\u5F15\u64CE\u7684 schedule \u670D\u52A1\u3002\n const schedule = soft('schedule') as\n | { create(request: Record<string, unknown>): Promise<unknown> }\n | null\n await handleApi(\n req as never,\n res as never,\n store,\n requestGuard(),\n officeToPdf,\n schedule,\n )\n },\n }))\n\n // 3) /ksrw/*\uFF1Aauth \u9875\u9759\u6001\u8D44\u6E90\uFF08\u53EA\u5141\u8BB8\u4E00\u7EA7\u6587\u4EF6\u540D\uFF09\n disposers.push(ctx.webServer.register({\n kind: 'prefix',\n path: '/ksrw',\n handler: async (req, res) => {\n const pathname = (req.url ?? '').split('?')[0] ?? ''\n const raw = pathname.startsWith('/ksrw/') ? pathname.slice('/ksrw/'.length) : ''\n let decoded = raw\n try { decoded = decodeURIComponent(raw) } catch { decoded = '\\u0000' }\n const fileName = safeStaticName(decoded)\n if (fileName === null) { res.writeHead(404); res.end(); return }\n await serveFile(fileName, res)\n },\n }))\n\n seedPaperTemplate()\n\n // 4) \u8FDC\u7A0B\u76EE\u6807\u51ED\u636E\u5347\u7EA7\uFF1A\u5B58\u91CF\u660E\u6587\u5BC6\u7801\u8FC1\u8FDB\u7CFB\u7EDF\u51ED\u636E\u5B58\u50A8\uFF08\u540E\u7AEF\u4E0D\u53EF\u7528\u65F6\u53EA\u544A\u8B66\uFF0C\u4E0D\u963B\u585E\u542F\u52A8\uFF09\u3002\n try {\n const secrets = migratePlaintextSecrets()\n if (secrets.migrated > 0) {\n ctx.logger?.info?.(`ksrw-core: \u5DF2\u628A ${secrets.migrated} \u4E2A\u8FDC\u7A0B\u76EE\u6807\u7684\u5BC6\u7801\u8FC1\u5165\u7CFB\u7EDF\u51ED\u636E\u5B58\u50A8`)\n }\n if (secrets.remaining > 0) {\n ctx.logger?.warn?.(`ksrw-core: ${secrets.remaining} \u4E2A\u8FDC\u7A0B\u76EE\u6807\u4ECD\u4EE5\u660E\u6587\u4FDD\u5B58\u5BC6\u7801\uFF08\u7CFB\u7EDF\u51ED\u636E\u5B58\u50A8\u4E0D\u53EF\u7528\uFF09\uFF1A\u5EFA\u8BAE\u6539\u7528\u5BC6\u94A5\u6216 agent \u8BA4\u8BC1`)\n }\n } catch { /* \u8FC1\u79FB\u662F\u52A0\u56FA\uFF0C\u4E0D\u662F\u542F\u52A8\u5FC5\u8981\u6B65\u9AA4 */ }\n\n // \u515C\u5E95\uFF1Aagent \u5FD8\u4E86\u8C03 ksrw_archive \u65F6\uFF0C\u4ECD\u628A\u5DE5\u4F4D\u4F1A\u8BDD\u7684\u6210\u679C\u5F52\u6863\u8FDB\u9879\u76EE\u5E93\u3002\n const stopAutoArchive = startSessionAutoArchive({\n info: message => ctx.logger?.info?.(message),\n warn: message => ctx.logger?.warn?.(message),\n })\n\n effect(() => () => {\n stopAutoArchive()\n for (const dispose of disposers.reverse()) dispose()\n }, 'ksrw-core: routes + auto-archive')\n}\n", "/** KSRW \u7EDF\u4E00\u9519\u8BEF\uFF1Acode \u7A33\u5B9A\u53EF\u5224\u522B\uFF0Cmessage \u9762\u5411\u6A21\u578B/\u7528\u6237\u53EF\u8BFB\uFF08\u53CC\u8BED\u77ED\u53E5\uFF09\u3002 */\n\nexport const ERROR_CODES = [\n 'INVALID',\n 'NOT_FOUND',\n 'DUPLICATE',\n 'LOCKED',\n 'PATH_DENIED',\n 'ENGINE_MISSING',\n 'COMPILE_FAILED',\n 'NETWORK',\n 'RATE_LIMITED',\n 'AUTH_MISSING',\n 'SSH_FAILED',\n 'NO_PROVIDER',\n 'ABORTED',\n 'STALE_REVISION',\n 'INTERNAL',\n] as const\n\nexport type ErrorCode = (typeof ERROR_CODES)[number]\n\nexport class KsrwError extends Error {\n constructor(\n readonly code: ErrorCode,\n message: string,\n readonly detail?: unknown,\n ) {\n super(message)\n this.name = 'KsrwError'\n }\n\n /** \u5DE5\u5177/RPC \u7EDF\u4E00\u4FE1\u5C01\u7684\u9519\u8BEF\u5F62\u6001\u3002 */\n envelope(): { code: ErrorCode; message: string } {\n return { code: this.code, message: this.message }\n }\n}\n\nexport function ksrwEnvelope(error: unknown): { code: string; message: string } {\n if (error instanceof KsrwError) return error.envelope()\n return { code: 'internal', message: error instanceof Error ? error.message : String(error) }\n}\n", "/** \u77ED ID\uFF1A\u65F6\u95F4\u524D\u7F00\uFF08\u6BEB\u79D236\u8FDB\u5236\uFF09+ 4 \u4F4D\u968F\u673A\uFF0C\u53EF\u8BFB\u3001\u53EF\u6392\u5E8F\u3001\u78B0\u649E\u6982\u7387\u53EF\u5FFD\u7565\u3002 */\n\nexport function newId(prefix: string): string {\n const t = Date.now().toString(36)\n const r = Math.random().toString(36).slice(2, 6)\n return `${prefix}_${t}${r}`\n}\n", "/** \u9879\u76EE\u7EA7\u8DE8\u8FDB\u7A0B\u5199\u9501\uFF08`.ksrw.lock`\uFF09\uFF1A\u72EC\u5360\u521B\u5EFA + \u9648\u65E7\u63A5\u7BA1\u3002\n *\n * \u4E3A\u4EC0\u4E48\u9700\u8981\uFF1A\u9879\u76EE\u76EE\u5F55\u662F\u7528\u6237\u53EF\u89C1\u7684\u666E\u901A\u6587\u4EF6\u5939\uFF0C\u540C\u4E00\u76EE\u5F55\u53EF\u80FD\u540C\u65F6\u88AB\u684C\u9762\u58F3\u3001CLI\n * \u4E0E\u7B2C\u4E8C\u4E2A\u7A97\u53E3\u6253\u5F00\u3002\u5185\u6838\u5199\u8DEF\u5F84\u90FD\u662F\u300C\u8BFB \u2192 \u6539 \u2192 \u5199\u300D\uFF0C\u53EA\u9760 tmp+rename \u539F\u5B50\u5199\u53EA\u80FD\n * \u4FDD\u8BC1\u5355\u6B21\u5199\u4E0D\u6495\u88C2\uFF0C\u4E0D\u80FD\u9632\u6B62\u4E24\u4E2A\u8FDB\u7A0B\u5404\u81EA\u57FA\u4E8E\u65E7\u503C\u5199\u56DE\uFF08\u4E22\u66F4\u65B0\uFF1A\u63D0\u6848 apply \u4E0E\n * agent \u5199\u4EA4\u63A5\u5355\u5E76\u53D1\u65F6\u6700\u5178\u578B\uFF09\u3002\u8FD9\u91CC\u7528\u6587\u4EF6\u72EC\u5360\u521B\u5EFA\u505A\u8DE8\u8FDB\u7A0B\u4E92\u65A5\u3002\n *\n * \u5B9E\u73B0\u8981\u70B9\uFF1A\n * - \u72EC\u5360\u521B\u5EFA\uFF08open O_EXCL\uFF09\u5728 POSIX \u4E0E Windows \u4E0A\u90FD\u662F\u539F\u5B50\u7684\uFF0C\u65E0\u9700 flock \u4F9D\u8D56\uFF1B\n * - \u9501\u5185\u5BB9 {pid, host, at, nonce}\uFF1A\u53EA\u6709 nonce \u4ECD\u5C5E\u4E8E\u81EA\u5DF1\u65F6\u624D unlink\uFF0C\u907F\u514D\u9648\u65E7\n * \u63A5\u7BA1\u540E\u628A\u65B0\u6301\u6709\u8005\u7684\u9501\u5220\u6389\uFF1B\n * - \u540C\u8FDB\u7A0B\u91CD\u5165\u6309\u8DEF\u5F84\u8BA1\u6570\uFF1A\u5185\u6838\u65B9\u6CD5\u4E92\u76F8\u8C03\u7528\uFF08gate \u2192 setStage \u2192 touch\uFF09\u4E0D\u4F1A\u81EA\u9501\uFF1B\n * - \u9648\u65E7\u5224\u5B9A\u89C1 `staleReason()`\uFF1A\u540C\u4E3B\u673A\u6B7B pid\u3001\u9501\u9F84\u8D85\u8FC7 staleMs\u3001\u6216\u8D85\u8FC7\u786C\u4E0A\u9650\u3002\n */\nimport { closeSync, existsSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeSync } from 'node:fs'\nimport { hostname } from 'node:os'\nimport { resolve } from 'node:path'\n\nimport { KsrwError } from './error.ts'\n\nexport const PROJECT_LOCK_FILE = '.ksrw.lock'\n/** \u9879\u76EE\u5E93\u767B\u8BB0\u8868\uFF08~/.ksrw/projects.json\uFF09\u7684\u8DE8\u8FDB\u7A0B\u9501\u3002 */\nexport const REGISTRY_LOCK_FILE = '.projects.lock'\n\nconst DEFAULT_TIMEOUT_MS = 5_000\nconst DEFAULT_STALE_MS = 30_000\nconst DEFAULT_POLL_MS = 20\n\nexport interface LockOptions {\n /** \u7B49\u9501\u4E0A\u9650\uFF1B\u8D85\u65F6\u629B LOCKED\u3002\u9ED8\u8BA4 5s\uFF0C\u53EF\u7528 KSRW_LOCK_TIMEOUT_MS \u8986\u76D6\u3002 */\n timeoutMs?: number\n /** \u8D85\u8FC7\u8BE5\u9501\u9F84\u89C6\u4E3A\u9648\u65E7\uFF08\u6301\u6709\u8005\u53EF\u80FD\u5DF2\u5D29\uFF09\u3002\u9ED8\u8BA4 30s\uFF0CKSRW_LOCK_STALE_MS \u8986\u76D6\u3002 */\n staleMs?: number\n /** \u8F6E\u8BE2\u95F4\u9694\u3002\u9ED8\u8BA4 20ms\u3002 */\n pollMs?: number\n /** \u62A5\u9519\u91CC\u7684\u4EBA\u7C7B\u53EF\u8BFB\u5BF9\u8C61\uFF08\u9879\u76EE/\u767B\u8BB0\u8868\uFF09\u3002 */\n label?: string\n}\n\nexport interface LockInfo {\n readonly pid: number\n readonly host: string\n readonly at: number\n readonly nonce: string\n}\n\n/** \u8FDB\u7A0B\u5185\u91CD\u5165\u8BA1\u6570\uFF1Akey \u4E3A\u89E3\u6790\u540E\u7684\u9501\u6587\u4EF6\u7EDD\u5BF9\u8DEF\u5F84\u3002 */\nconst held = new Map<string, number>()\nconst nonces = new Map<string, string>()\n\n/** \u672C\u673A pid \u662F\u5426\u4ECD\u5B58\u5728\uFF08EPERM = \u5B58\u5728\u4F46\u5C5E\u4E8E\u522B\u7684\u7528\u6237\uFF09\u3002 */\nfunction pidAlive(pid: number): boolean {\n if (!Number.isInteger(pid) || pid <= 0) return false\n try {\n process.kill(pid, 0)\n return true\n } catch (error) {\n return (error as NodeJS.ErrnoException).code === 'EPERM'\n }\n}\n\n/** \u540C\u6B65\u7761\u7720\uFF1A\u9501\u662F\u540C\u6B65 API\uFF0C\u4E0D\u80FD\u7528 setTimeout\u3002 */\nfunction sleepSync(ms: number): void {\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms)\n}\n\nfunction envMs(name: string, fallback: number): number {\n const raw = process.env[name]?.trim()\n if (raw === undefined || raw === '') return fallback\n const value = Number(raw)\n return Number.isFinite(value) && value >= 0 ? value : fallback\n}\n\nfunction readInfo(lockPath: string): LockInfo | null {\n try {\n const parsed = JSON.parse(readFileSync(lockPath, 'utf8')) as Partial<LockInfo>\n if (typeof parsed.pid !== 'number' || typeof parsed.at !== 'number') return null\n return {\n pid: parsed.pid,\n host: typeof parsed.host === 'string' ? parsed.host : '',\n at: parsed.at,\n nonce: typeof parsed.nonce === 'string' ? parsed.nonce : '',\n }\n } catch {\n return null\n }\n}\n\n/** \u9501\u6587\u4EF6\u5E74\u9F84\uFF08\u8BFB\u4E0D\u5230\u5185\u5BB9\u65F6\u9000\u56DE mtime\uFF0C\u5BB9\u5FCD\u5199\u5230\u4E00\u534A\u7684\u9501\uFF09\u3002 */\nfunction lockAge(lockPath: string, info: LockInfo | null, now: number): number {\n if (info !== null) return now - info.at\n try { return now - statSync(lockPath).mtimeMs } catch { return Number.POSITIVE_INFINITY }\n}\n\n/** \u9648\u65E7\u5224\u5B9A\uFF1A\u8FD4\u56DE\u539F\u56E0\uFF08\u7528\u4E8E\u65E5\u5FD7/\u62A5\u9519\uFF09\uFF0C\u4ECD\u6709\u6D3B\u6301\u6709\u8005\u65F6\u8FD4\u56DE null\u3002 */\nfunction staleReason(lockPath: string, staleMs: number, now: number): string | null {\n const info = readInfo(lockPath)\n const age = lockAge(lockPath, info, now)\n if (info !== null && info.host === hostname() && !pidAlive(info.pid)) {\n return '\u6301\u6709\u8FDB\u7A0B\u5DF2\u9000\u51FA\uFF08pid ' + info.pid + '\uFF09'\n }\n // \u786C\u4E0A\u9650\uFF1Apid \u88AB\u590D\u7528\u3001\u6301\u6709\u8005\u6302\u6B7B\uFF08\u8C03\u8BD5\u5668\u6682\u505C\uFF09\u6216\u5199\u5230\u4E00\u534A\u5D29\u6E83\u3002\n if (age > Math.max(staleMs * 4, staleMs + 60_000)) return '\u9501\u9F84\u8D85\u8FC7\u786C\u4E0A\u9650\uFF08' + Math.round(age / 1000) + 's\uFF09'\n if (age > staleMs && (info === null || info.host !== hostname() || !pidAlive(info.pid))) {\n return '\u9501\u9F84\u8D85\u8FC7 ' + Math.round(staleMs / 1000) + 's \u4E14\u65E0\u6CD5\u786E\u8BA4\u6301\u6709\u8005'\n }\n return null\n}\n\nfunction tryBreak(lockPath: string, staleMs: number, now: number): boolean {\n if (staleReason(lockPath, staleMs, now) === null) return false\n // P2-7\uFF1A\u9648\u65E7\u63A5\u7BA1\u4E0D\u8981\u65E0\u6761\u4EF6 unlink\u3002\u5148\u628A\u9501\u6587\u4EF6\u539F\u5B50 rename \u5230\u552F\u4E00\u9694\u79BB\u540D\uFF0C\n // \u518D\u6838\u5BF9\u5185\u5BB9\u91CC\u4ECD\u662F\u540C\u4E00\u628A\u9501\uFF1B\u671F\u95F4\u88AB\u522B\u4EBA\u6362\u6389\u5C31\u539F\u6837\u653E\u56DE\uFF0C\u7EDD\u4E0D\u5220\u6D3B\u9501\u3002\n const before = readInfo(lockPath)\n const expectedNonce = before?.nonce ?? ''\n const quarantine = lockPath + '.stale.' + process.pid.toString(36) + '.' + Math.random().toString(36).slice(2, 8)\n try { renameSync(lockPath, quarantine) } catch { return false }\n const moved = readInfo(quarantine)\n if (moved !== null && expectedNonce !== '' && moved.nonce !== expectedNonce) {\n // \u8BFB\u9648\u65E7\u4E0E rename \u4E4B\u95F4\u6709\u4EBA\u63A5\u7BA1\uFF1A\u8DEF\u5F84\u7A7A\u5219\u653E\u56DE\uFF1B\u5DF2\u88AB\u518D\u6B21\u5360\u7528\u5C31\u7559\u5728\u9694\u79BB\u540D\uFF0C\n // \u8BA9\u6301\u6709\u8005\u81EA\u5DF1 release\uFF08\u5B81\u53EF\u4E0D\u5220\uFF0C\u4E5F\u4E0D\u80FD\u5220\u6389\u6D3B\u9501\uFF09\u3002\n if (!existsSync(lockPath)) {\n try { renameSync(quarantine, lockPath) } catch { /* \u4FDD\u7559\u9694\u79BB\u6587\u4EF6 */ }\n }\n return false\n }\n try { unlinkSync(quarantine) } catch { /* \u5DF2\u88AB\u6E05\u7406 */ }\n return true\n}\n\n/** \u5F53\u524D\u9879\u76EE\u9501\u662F\u5426\u88AB\u300C\u53E6\u4E00\u4E2A\u4ECD\u6D3B\u7740\u7684\u8FDB\u7A0B\u300D\u6301\u6709\uFF08API \u5C42\u5FEB\u901F\u5931\u8D25\u7528\uFF0C\u8FD4\u56DE null \u8868\u793A\u53EF\u76F4\u63A5\u5199\uFF09\u3002 */\nexport function projectLockOwner(projectPath: string): LockInfo | null {\n const lockPath = resolve(projectPath, PROJECT_LOCK_FILE)\n const info = readInfo(lockPath)\n if (info === null) return null\n if (staleReason(lockPath, envMs('KSRW_LOCK_STALE_MS', DEFAULT_STALE_MS), Date.now()) !== null) return null\n if (info.host === hostname() && info.pid === process.pid) return null\n return info\n}\n\nfunction acquire(lockPath: string, options: LockOptions): void {\n const timeoutMs = options.timeoutMs ?? envMs('KSRW_LOCK_TIMEOUT_MS', DEFAULT_TIMEOUT_MS)\n const staleMs = options.staleMs ?? envMs('KSRW_LOCK_STALE_MS', DEFAULT_STALE_MS)\n const pollMs = options.pollMs ?? DEFAULT_POLL_MS\n const label = options.label ?? '\u9879\u76EE\u76EE\u5F55'\n const deadline = Date.now() + timeoutMs\n const nonce = process.pid.toString(36) + '-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 10)\n const payload = JSON.stringify({ pid: process.pid, host: hostname(), at: Date.now(), nonce } satisfies LockInfo)\n let explained = ''\n for (;;) {\n try {\n const fd = openSync(lockPath, 'wx', 0o600)\n try { writeSync(fd, payload) } finally { closeSync(fd) }\n nonces.set(lockPath, nonce)\n return\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code\n if (code !== 'EEXIST') {\n throw new KsrwError('INTERNAL', '\u65E0\u6CD5\u521B\u5EFA\u5199\u9501 ' + lockPath + '\uFF1A' + (error as Error).message)\n }\n }\n const now = Date.now()\n const reason = staleReason(lockPath, staleMs, now)\n if (reason !== null && tryBreak(lockPath, staleMs, now)) continue\n if (now >= deadline) {\n const info = readInfo(lockPath)\n const holder = info === null\n ? '\u672A\u77E5\u6301\u6709\u8005'\n : 'pid ' + info.pid + (info.host === hostname() ? '' : ' @ ' + info.host) + '\uFF0C\u5DF2\u6301\u6709 ' + Math.round((now - info.at) / 1000) + 's'\n throw new KsrwError(\n 'LOCKED',\n label + '\u6B63\u88AB\u5176\u4ED6\u8FDB\u7A0B\u5199\u5165\uFF08' + holder + '\uFF09\uFF0C\u7B49\u5F85 ' + timeoutMs + 'ms \u540E\u4ECD\u672A\u91CA\u653E\u3002'\n + (explained === '' ? '' : '\u4E0A\u6B21\u8BCA\u65AD\uFF1A' + explained),\n )\n }\n explained = reason ?? ''\n sleepSync(Math.min(pollMs, Math.max(1, deadline - now)))\n }\n}\n\nfunction release(lockPath: string): void {\n const depth = held.get(lockPath) ?? 0\n if (depth > 1) {\n held.set(lockPath, depth - 1)\n return\n }\n held.delete(lockPath)\n const nonce = nonces.get(lockPath)\n nonces.delete(lockPath)\n const info = readInfo(lockPath)\n // \u53EA\u6709\u9501\u4ECD\u5C5E\u4E8E\u81EA\u5DF1\u624D\u5220\uFF1A\u9648\u65E7\u63A5\u7BA1\u540E\u5220\u522B\u4EBA\u7684\u9501\u4F1A\u8BA9\u4E24\u4E2A\u5199\u5165\u8005\u540C\u65F6\u8FDB\u4E34\u754C\u533A\u3002\n if (info !== null && nonce !== undefined && info.nonce !== nonce) return\n try { unlinkSync(lockPath) } catch { /* \u5DF2\u88AB\u6E05\u7406 */ }\n}\n\n/**\n * \u5728\u8DE8\u8FDB\u7A0B\u6392\u4ED6\u9501\u5185\u6267\u884C\u4E00\u6BB5\u540C\u6B65\u903B\u8F91\u3002\n * @param lockPath - \u9501\u6587\u4EF6\u7EDD\u5BF9\u8DEF\u5F84\uFF08\u4E0D\u5B58\u5728\u65F6\u521B\u5EFA\uFF09\u3002\n * @param fn - \u4E34\u754C\u533A\uFF1B\u629B\u9519\u4E5F\u4F1A\u91CA\u653E\u9501\u3002\n * @param options - \u8D85\u65F6/\u9648\u65E7/\u6807\u7B7E\u3002\n * @returns fn \u7684\u8FD4\u56DE\u503C\u3002\n * @throws KsrwError LOCKED \u7B49\u9501\u8D85\u65F6\u3002\n */\nexport function withFileLock<T>(lockPath: string, fn: () => T, options: LockOptions = {}): T {\n const key = resolve(lockPath)\n const depth = held.get(key) ?? 0\n if (depth === 0) acquire(key, options)\n held.set(key, depth + 1)\n try {\n return fn()\n } finally {\n release(key)\n }\n}\n\n/** \u9879\u76EE\u76EE\u5F55\u5199\u9501\uFF1A`<project>/.ksrw.lock`\u3002 */\nexport function withProjectLock<T>(projectPath: string, fn: () => T, options: LockOptions = {}): T {\n return withFileLock(resolve(projectPath, PROJECT_LOCK_FILE), fn, { label: '\u9879\u76EE ' + resolve(projectPath), ...options })\n}\n\n/** \u9879\u76EE\u5E93\u767B\u8BB0\u8868\u5199\u9501\uFF1A`<home>/.projects.lock`\u3002 */\nexport function withRegistryLock<T>(home: string, fn: () => T, options: LockOptions = {}): T {\n return withFileLock(resolve(home, REGISTRY_LOCK_FILE), fn, { label: '\u9879\u76EE\u5E93\u767B\u8BB0\u8868', ...options })\n}\n\n/** \u8BCA\u65AD\u7528\uFF1A\u5F53\u524D\u9501\u6301\u6709\u8005\uFF08\u65E0\u9501\u8FD4\u56DE null\uFF09\u3002 */\nexport function readProjectLock(projectPath: string): LockInfo | null {\n return readInfo(resolve(projectPath, PROJECT_LOCK_FILE))\n}\n", "/** arXiv Atom API \u5BA2\u6237\u7AEF\uFF08docs/02-design/0203 \u00A71 `arxiv_search` \u6570\u636E\u9762\uFF09\u3002\n *\n * \u7EAA\u5F8B\uFF1A\u5BFC\u51FA API \u9650\u901F 3 \u79D2/\u6B21\uFF08\u5B98\u65B9 politeness \u7EA6\u5B9A\uFF09\uFF1B429/503 \u4F18\u5148\u9075\u5FAA\n * Retry-After\uFF1B\u5168\u7F51\u7EDC\u9762\u53EF\u6CE8\u5165 fetchFn\uFF08\u6D4B\u8BD5\u7528\u51BB\u7ED3\u6837\u672C\uFF09\u3002\n * Atom \u89E3\u6790\u7528\u5BB9\u9519\u6B63\u5219\u63D0\u53D6\uFF08\u547D\u540D\u7A7A\u95F4\u65E0\u5173\uFF09\uFF0C\u4E0D\u5F15\u5165 XML \u4F9D\u8D56\u3002\n */\n\nimport { KsrwError } from './error.ts'\n\nconst API = 'https://export.arxiv.org/api/query'\nconst MIN_INTERVAL_MS = 3_000\n\nexport interface ArxivPaper {\n readonly arxivId: string\n readonly title: string\n readonly authors: string[]\n readonly abstract: string\n readonly categories: string[]\n readonly published: string\n readonly updated: string\n readonly pdfUrl?: string\n readonly absUrl: string\n}\n\nexport interface ArxivSearchOptions {\n readonly query: string\n readonly maxResults?: number\n readonly sortBy?: 'relevance' | 'lastUpdatedDate' | 'submittedDate'\n readonly sortOrder?: 'ascending' | 'descending'\n readonly categoryId?: string\n readonly signal?: AbortSignal\n}\n\nexport type FetchFn = (url: string, init?: { signal?: AbortSignal; headers?: Record<string, string> }) => Promise<{ ok: boolean; status: number; headers: { get(name: string): string | null }; text: () => Promise<string>; arrayBuffer: () => Promise<ArrayBuffer> }>\n\nexport interface ArxivClient {\n search(options: ArxivSearchOptions): Promise<ArxivPaper[]>\n fetchByIds(ids: readonly string[], signal?: AbortSignal): Promise<ArxivPaper[]>\n downloadPdf(paper: ArxivPaper, destPath: string, signal?: AbortSignal): Promise<void>\n}\n\nexport function createArxivClient(fetchFn: FetchFn = defaultFetch): ArxivClient {\n let lastCall = 0\n\n async function throttle(signal?: AbortSignal): Promise<void> {\n const wait = lastCall + MIN_INTERVAL_MS - Date.now()\n if (wait > 0) await sleep(wait, signal)\n lastCall = Date.now()\n }\n\n async function request(url: string, signal?: AbortSignal): Promise<string> {\n await throttle(signal)\n for (let attempt = 0; ; attempt++) {\n const res = await fetchFn(url, { signal })\n if (res.ok) return await res.text()\n if (res.status === 429 || res.status === 503) {\n const retryAfter = Number(res.headers.get('retry-after'))\n const waitMs = Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter * 1000 : Math.min(30_000, 2_000 * 2 ** attempt)\n if (attempt >= 2) throw new KsrwError('RATE_LIMITED', `arXiv \u9650\u6D41\uFF08HTTP ${res.status}\uFF09\uFF0C\u5DF2\u91CD\u8BD5 2 \u6B21\u4ECD\u5931\u8D25\uFF1B\u7A0D\u540E\u518D\u8BD5`)\n await sleep(waitMs, signal)\n continue\n }\n throw new KsrwError('NETWORK', `arXiv \u8BF7\u6C42\u5931\u8D25\uFF1AHTTP ${res.status}`)\n }\n }\n\n return {\n async search(options) {\n const max = clamp(options.maxResults ?? 10, 1, 30)\n const q = buildQuery(options.query, options.categoryId)\n const url = `${API}?search_query=${encodeURIComponent(q)}&start=0&max_results=${max}` +\n `&sortBy=${options.sortBy ?? 'relevance'}&sortOrder=${options.sortOrder ?? 'descending'}`\n return parseAtom(await request(url, options.signal))\n },\n\n async fetchByIds(ids, signal) {\n if (ids.length === 0) return []\n const idList = ids.map(normalizeId).join(',')\n const url = `${API}?id_list=${encodeURIComponent(idList)}&max_results=${ids.length}`\n return parseAtom(await request(url, signal))\n },\n\n async downloadPdf(paper, destPath, signal) {\n const url = paper.pdfUrl ?? `https://arxiv.org/pdf/${paper.arxivId}`\n await throttle(signal)\n const res = await fetchFn(url, { signal })\n if (!res.ok) throw new KsrwError('NETWORK', `PDF \u4E0B\u8F7D\u5931\u8D25\uFF1AHTTP ${res.status}`)\n const { mkdirSync, writeFileSync, renameSync } = await import('node:fs')\n const { dirname } = await import('node:path')\n const buf = Buffer.from(await res.arrayBuffer())\n if (buf.subarray(0, 5).toString() !== '%PDF-') {\n throw new KsrwError('NETWORK', '\u4E0B\u8F7D\u5185\u5BB9\u4E0D\u662F PDF\uFF08\u53EF\u80FD\u88AB\u91CD\u5B9A\u5411\u5230\u9A8C\u8BC1\u9875\uFF09')\n }\n mkdirSync(dirname(destPath), { recursive: true })\n const tmp = `${destPath}.tmp-${Date.now()}`\n writeFileSync(tmp, buf, { mode: 0o600 })\n renameSync(tmp, destPath)\n },\n }\n}\n\n/** \u67E5\u8BE2\u7EC4\u88C5\uFF1A\u88F8\u8BCD\u7EC4\u8D70 all: \u524D\u7F00\uFF1B\u5DF2\u5E26\u5B57\u6BB5\u524D\u7F00\uFF08ti:/au:/abs:/cat:/all:\uFF09\u539F\u6837\u4FDD\u7559\u3002 */\nexport function buildQuery(query: string, categoryId?: string): string {\n const trimmed = query.trim()\n const terms = /(^|\\s)(ti:|au:|abs:|cat:|all:)/.test(trimmed) || trimmed === ''\n ? trimmed\n : `all:${trimmed}`\n const parts = [terms]\n if (categoryId !== undefined && categoryId.trim() !== '') parts.push(`cat:${categoryId.trim()}`)\n return parts.filter(p => p !== '').join(' AND ')\n}\n\nexport function normalizeId(raw: string): string {\n return raw.trim().replace(/^https?:\\/\\/arxiv\\.org\\/(abs|pdf)\\//, '').replace(/\\.pdf$/, '').replace(/^arXiv:/, '')\n}\n\n// \u2500\u2500 Atom \u89E3\u6790\uFF08\u5BB9\u9519\uFF1A\u5B9E\u4F53\u53CD\u8F6C\u4E49 + \u6807\u7B7E\u5185\u6587\u672C\u63D0\u53D6\uFF09\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function parseAtom(xml: string): ArxivPaper[] {\n const papers: ArxivPaper[] = []\n const entries = xml.split(/<entry[\\s>]/).slice(1)\n for (const entry of entries) {\n const end = entry.indexOf('</entry>')\n const body = end === -1 ? entry : entry.slice(0, end)\n const idUrl = extract(body, 'id') ?? ''\n const arxivId = normalizeId(idUrl)\n if (arxivId === '') continue\n const absUrl = /^https?:\\/\\/arxiv\\.org\\/abs\\//.test(idUrl) ? idUrl : `https://arxiv.org/abs/${arxivId}`\n papers.push({\n arxivId,\n title: clean(extract(body, 'title') ?? ''),\n authors: collectAll(body, 'name'),\n abstract: clean(extract(body, 'summary') ?? ''),\n categories: collectAttrAll(body, 'category', 'term'),\n published: extract(body, 'published') ?? '',\n updated: extract(body, 'updated') ?? '',\n pdfUrl: extractLink(body, 'title=\"pdf\"') ?? `https://arxiv.org/pdf/${arxivId}`,\n absUrl,\n })\n }\n return papers\n}\n\nfunction extract(haystack: string, tag: string): string | undefined {\n const m = haystack.match(new RegExp(`<${tag}(?:\\\\s[^>]*)?>([\\\\s\\\\S]*?)</${tag}>`))\n return m?.[1]\n}\n\nfunction collectAll(haystack: string, tag: string): string[] {\n return [...haystack.matchAll(new RegExp(`<${tag}(?:\\\\s[^>]*)?>([\\\\s\\\\S]*?)</${tag}>`, 'g'))]\n .map(m => clean(m[1] ?? ''))\n .filter(s => s !== '')\n}\n\nfunction collectAttrAll(haystack: string, tag: string, attr: string): string[] {\n return [...haystack.matchAll(new RegExp(`<${tag}\\\\s[^>]*?${attr}=\"([^\"]*)\"`, 'g'))]\n .map(m => m[1] ?? '')\n .filter(s => s !== '')\n}\n\nfunction extractLink(haystack: string, marker: string): string | undefined {\n const m = haystack.match(new RegExp(`<link\\\\s[^>]*?${marker}[^>]*?href=\"([^\"]+)\"`))\n return m?.[1]\n}\n\nfunction clean(s: string): string {\n return s\n .replace(/<[^>]+>/g, ' ')\n .replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&').replace(/&quot;/g, '\"').replace(/&#39;/g, \"'\")\n .replace(/\\s+/g, ' ')\n .trim()\n}\n\nfunction clamp(n: number, lo: number, hi: number): number {\n return Math.min(hi, Math.max(lo, n))\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n const t = setTimeout(resolve, ms)\n signal?.addEventListener('abort', () => {\n clearTimeout(t)\n reject(new KsrwError('ABORTED', '\u5DF2\u4E2D\u6B62'))\n }, { once: true })\n })\n}\n\nconst defaultFetch: FetchFn = (url, init) => globalThis.fetch(url, init as RequestInit) as unknown as Promise<{ ok: boolean; status: number; headers: { get(name: string): string | null }; text: () => Promise<string>; arrayBuffer: () => Promise<ArrayBuffer> }>\n", "import { createRequire } from 'module';\nvar require = createRequire('/');\n// DEFLATE is a complex format; to read this code, you should probably check the RFC first:\n// https://tools.ietf.org/html/rfc1951\n// You may also wish to take a look at the guide I made about this program:\n// https://gist.github.com/101arrowz/253f31eb5abc3d9275ab943003ffecad\n// Some of the following code is similar to that of UZIP.js:\n// https://github.com/photopea/UZIP.js\n// However, the vast majority of the codebase has diverged from UZIP.js to increase performance and reduce bundle size.\n// Sometimes 0 will appear where -1 would be more appropriate. This is because using a uint\n// is better for memory in most engines (I *think*).\nvar _a;\n// Mediocre shim\nvar Worker;\nvar isMarkedAsUntransferable;\nvar workerAdd = \";var __w=require('worker_threads');__w.parentPort.on('message',function(m){onmessage({data:m})}),postMessage=function(m,t){__w.parentPort.postMessage(m,t)},close=process.exit;self=global\";\ntry {\n (_a = require('worker_threads'), Worker = _a.Worker, isMarkedAsUntransferable = _a.isMarkedAsUntransferable);\n}\ncatch (e) {\n}\nvar wk = Worker ? function (c, _, msg, transfer, cb) {\n var done = false;\n var w = new Worker(c + workerAdd, { eval: true })\n .on('error', function (e) { return cb(e, null); })\n .on('message', function (m) { return cb(null, m); })\n .on('exit', function (c) {\n if (c && !done)\n cb(new Error('exited with code ' + c), null);\n });\n if (isMarkedAsUntransferable)\n transfer = transfer.filter(function (t) { return !isMarkedAsUntransferable(t); });\n w.postMessage(msg, transfer);\n w.terminate = function () {\n done = true;\n return Worker.prototype.terminate.call(w);\n };\n return w;\n} : function (_, __, ___, ____, cb) {\n setImmediate(function () { return cb(new Error('async operations unsupported - update to Node 12+ (or Node 10-11 with the --experimental-worker CLI flag)'), null); });\n var NOP = function () { };\n return {\n terminate: NOP,\n postMessage: NOP\n };\n};\n\n// aliases for shorter compressed code (most minifers don't do this)\nvar u8 = Uint8Array, u16 = Uint16Array, i32 = Int32Array;\n// fixed length extra bits\nvar fleb = new u8([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, /* unused */ 0, 0, /* impossible */ 0]);\n// fixed distance extra bits\nvar fdeb = new u8([0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, /* unused */ 0, 0]);\n// code length index map\nvar clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);\n// get base, reverse index map from extra bits\nvar freb = function (eb, start) {\n var b = new u16(31);\n for (var i = 0; i < 31; ++i) {\n b[i] = start += 1 << eb[i - 1];\n }\n // numbers here are at max 18 bits\n var r = new i32(b[30]);\n for (var i = 1; i < 30; ++i) {\n for (var j = b[i]; j < b[i + 1]; ++j) {\n r[j] = ((j - b[i]) << 5) | i;\n }\n }\n return { b: b, r: r };\n};\nvar _a = freb(fleb, 2), fl = _a.b, revfl = _a.r;\n// we can ignore the fact that the other numbers are wrong; they never happen anyway\nfl[28] = 258, revfl[258] = 28;\nvar _b = freb(fdeb, 0), fd = _b.b, revfd = _b.r;\n// map of value to reverse (assuming 16 bits)\nvar rev = new u16(32768);\nfor (var i = 0; i < 32768; ++i) {\n // reverse table algorithm from SO\n var x = ((i & 0xAAAA) >> 1) | ((i & 0x5555) << 1);\n x = ((x & 0xCCCC) >> 2) | ((x & 0x3333) << 2);\n x = ((x & 0xF0F0) >> 4) | ((x & 0x0F0F) << 4);\n rev[i] = (((x & 0xFF00) >> 8) | ((x & 0x00FF) << 8)) >> 1;\n}\n// create huffman tree from u8 \"map\": index -> code length for code index\n// mb (max bits) must be at most 15\n// TODO: optimize/split up?\nvar hMap = (function (cd, mb, r) {\n var s = cd.length;\n // index\n var i = 0;\n // u16 \"map\": index -> # of codes with bit length = index\n var l = new u16(mb);\n // length of cd must be 288 (total # of codes)\n for (; i < s; ++i) {\n if (cd[i])\n ++l[cd[i] - 1];\n }\n // u16 \"map\": index -> minimum code for bit length = index\n var le = new u16(mb);\n for (i = 1; i < mb; ++i) {\n le[i] = (le[i - 1] + l[i - 1]) << 1;\n }\n var co;\n if (r) {\n // u16 \"map\": index -> number of actual bits, symbol for code\n co = new u16(1 << mb);\n // bits to remove for reverser\n var rvb = 15 - mb;\n for (i = 0; i < s; ++i) {\n // ignore 0 lengths\n if (cd[i]) {\n // num encoding both symbol and bits read\n var sv = (i << 4) | cd[i];\n // free bits\n var r_1 = mb - cd[i];\n // start value\n var v = le[cd[i] - 1]++ << r_1;\n // m is end value\n for (var m = v | ((1 << r_1) - 1); v <= m; ++v) {\n // every 16 bit value starting with the code yields the same result\n co[rev[v] >> rvb] = sv;\n }\n }\n }\n }\n else {\n co = new u16(s);\n for (i = 0; i < s; ++i) {\n if (cd[i]) {\n co[i] = rev[le[cd[i] - 1]++] >> (15 - cd[i]);\n }\n }\n }\n return co;\n});\n// fixed length tree\nvar flt = new u8(288);\nfor (var i = 0; i < 144; ++i)\n flt[i] = 8;\nfor (var i = 144; i < 256; ++i)\n flt[i] = 9;\nfor (var i = 256; i < 280; ++i)\n flt[i] = 7;\nfor (var i = 280; i < 288; ++i)\n flt[i] = 8;\n// fixed distance tree\nvar fdt = new u8(32);\nfor (var i = 0; i < 32; ++i)\n fdt[i] = 5;\n// fixed length map\nvar flm = /*#__PURE__*/ hMap(flt, 9, 0), flrm = /*#__PURE__*/ hMap(flt, 9, 1);\n// fixed distance map\nvar fdm = /*#__PURE__*/ hMap(fdt, 5, 0), fdrm = /*#__PURE__*/ hMap(fdt, 5, 1);\n// find max of array\nvar max = function (a) {\n var m = a[0];\n for (var i = 1; i < a.length; ++i) {\n if (a[i] > m)\n m = a[i];\n }\n return m;\n};\n// read d, starting at bit p and mask with m\nvar bits = function (d, p, m) {\n var o = (p / 8) | 0;\n return ((d[o] | (d[o + 1] << 8)) >> (p & 7)) & m;\n};\n// read d, starting at bit p continuing for at least 16 bits\nvar bits16 = function (d, p) {\n var o = (p / 8) | 0;\n return ((d[o] | (d[o + 1] << 8) | (d[o + 2] << 16)) >> (p & 7));\n};\n// get end of byte\nvar shft = function (p) { return ((p + 7) / 8) | 0; };\n// typed array slice - allows garbage collector to free original reference,\n// while being more compatible than .slice\nvar slc = function (v, s, e) {\n if (s == null || s < 0)\n s = 0;\n if (e == null || e > v.length)\n e = v.length;\n // can't use .constructor in case user-supplied\n return new u8(v.subarray(s, e));\n};\n/**\n * Codes for errors generated within this library\n */\nexport var FlateErrorCode = {\n UnexpectedEOF: 0,\n InvalidBlockType: 1,\n InvalidLengthLiteral: 2,\n InvalidDistance: 3,\n StreamFinished: 4,\n NoStreamHandler: 5,\n InvalidHeader: 6,\n NoCallback: 7,\n InvalidUTF8: 8,\n ExtraFieldTooLong: 9,\n InvalidDate: 10,\n FilenameTooLong: 11,\n StreamFinishing: 12,\n InvalidZipData: 13,\n UnknownCompressionMethod: 14\n};\n// error codes\nvar ec = [\n 'unexpected EOF',\n 'invalid block type',\n 'invalid length/literal',\n 'invalid distance',\n 'stream finished',\n 'no stream handler',\n , // determined by compression function\n 'no callback',\n 'invalid UTF-8 data',\n 'extra field too long',\n 'date not in range 1980-2099',\n 'filename too long',\n 'stream finishing',\n 'invalid zip data'\n // determined by unknown compression method\n];\n;\nvar err = function (ind, msg, nt) {\n var e = new Error(msg || ec[ind]);\n e.code = ind;\n if (Error.captureStackTrace)\n Error.captureStackTrace(e, err);\n if (!nt)\n throw e;\n return e;\n};\n// expands raw DEFLATE data\nvar inflt = function (dat, st, buf, dict) {\n // source length dict length\n var sl = dat.length, dl = dict ? dict.length : 0;\n if (!sl || st.f && !st.l)\n return buf || new u8(0);\n var noBuf = !buf;\n // have to estimate size\n var resize = noBuf || st.i != 2;\n // no state\n var noSt = st.i;\n // Assumes roughly 33% compression ratio average\n if (noBuf)\n buf = new u8(sl * 3);\n // ensure buffer can fit at least l elements\n var cbuf = function (l) {\n var bl = buf.length;\n // need to increase size to fit\n if (l > bl) {\n // Double or set to necessary, whichever is greater\n var nbuf = new u8(Math.max(bl * 2, l));\n nbuf.set(buf);\n buf = nbuf;\n }\n };\n // last chunk bitpos bytes\n var final = st.f || 0, pos = st.p || 0, bt = st.b || 0, lm = st.l, dm = st.d, lbt = st.m, dbt = st.n;\n // total bits\n var tbts = sl * 8;\n do {\n if (!lm) {\n // BFINAL - this is only 1 when last chunk is next\n final = bits(dat, pos, 1);\n // type: 0 = no compression, 1 = fixed huffman, 2 = dynamic huffman\n var type = bits(dat, pos + 1, 3);\n pos += 3;\n if (!type) {\n // go to end of byte boundary\n var s = shft(pos) + 4, l = dat[s - 4] | (dat[s - 3] << 8), t = s + l;\n if (t > sl) {\n if (noSt)\n err(0);\n break;\n }\n // ensure size\n if (resize)\n cbuf(bt + l);\n // Copy over uncompressed data\n buf.set(dat.subarray(s, t), bt);\n // Get new bitpos, update byte count\n st.b = bt += l, st.p = pos = t * 8, st.f = final;\n continue;\n }\n else if (type == 1)\n lm = flrm, dm = fdrm, lbt = 9, dbt = 5;\n else if (type == 2) {\n // literal lengths\n var hLit = bits(dat, pos, 31) + 257, hcLen = bits(dat, pos + 10, 15) + 4;\n var tl = hLit + bits(dat, pos + 5, 31) + 1;\n pos += 14;\n // length+distance tree\n var ldt = new u8(tl);\n // code length tree\n var clt = new u8(19);\n for (var i = 0; i < hcLen; ++i) {\n // use index map to get real code\n clt[clim[i]] = bits(dat, pos + i * 3, 7);\n }\n pos += hcLen * 3;\n // code lengths bits\n var clb = max(clt), clbmsk = (1 << clb) - 1;\n // code lengths map\n var clm = hMap(clt, clb, 1);\n for (var i = 0; i < tl;) {\n var r = clm[bits(dat, pos, clbmsk)];\n // bits read\n pos += r & 15;\n // symbol\n var s = r >> 4;\n // code length to copy\n if (s < 16) {\n ldt[i++] = s;\n }\n else {\n // copy count\n var c = 0, n = 0;\n if (s == 16)\n n = 3 + bits(dat, pos, 3), pos += 2, c = ldt[i - 1];\n else if (s == 17)\n n = 3 + bits(dat, pos, 7), pos += 3;\n else if (s == 18)\n n = 11 + bits(dat, pos, 127), pos += 7;\n while (n--)\n ldt[i++] = c;\n }\n }\n // length tree distance tree\n var lt = ldt.subarray(0, hLit), dt = ldt.subarray(hLit);\n // max length bits\n lbt = max(lt);\n // max dist bits\n dbt = max(dt);\n lm = hMap(lt, lbt, 1);\n dm = hMap(dt, dbt, 1);\n }\n else\n err(1);\n if (pos > tbts) {\n if (noSt)\n err(0);\n break;\n }\n }\n // Make sure the buffer can hold this + the largest possible addition\n // Maximum chunk size (practically, theoretically infinite) is 2^17\n if (resize)\n cbuf(bt + 131072);\n var lms = (1 << lbt) - 1, dms = (1 << dbt) - 1;\n var lpos = pos;\n for (;; lpos = pos) {\n // bits read, code\n var c = lm[bits16(dat, pos) & lms], sym = c >> 4;\n pos += c & 15;\n if (pos > tbts) {\n if (noSt)\n err(0);\n break;\n }\n if (!c)\n err(2);\n if (sym < 256)\n buf[bt++] = sym;\n else if (sym == 256) {\n lpos = pos, lm = null;\n break;\n }\n else {\n var add = sym - 254;\n // no extra bits needed if less\n if (sym > 264) {\n // index\n var i = sym - 257, b = fleb[i];\n add = bits(dat, pos, (1 << b) - 1) + fl[i];\n pos += b;\n }\n // dist\n var d = dm[bits16(dat, pos) & dms], dsym = d >> 4;\n if (!d)\n err(3);\n pos += d & 15;\n var dt = fd[dsym];\n if (dsym > 3) {\n var b = fdeb[dsym];\n dt += bits16(dat, pos) & (1 << b) - 1, pos += b;\n }\n if (pos > tbts) {\n if (noSt)\n err(0);\n break;\n }\n if (resize)\n cbuf(bt + 131072);\n var end = bt + add;\n if (bt < dt) {\n var shift = dl - dt, dend = Math.min(dt, end);\n if (shift + bt < 0)\n err(3);\n for (; bt < dend; ++bt)\n buf[bt] = dict[shift + bt];\n }\n for (; bt < end; ++bt)\n buf[bt] = buf[bt - dt];\n }\n }\n st.l = lm, st.p = lpos, st.b = bt, st.f = final;\n if (lm)\n final = 1, st.m = lbt, st.d = dm, st.n = dbt;\n } while (!final);\n // don't reallocate for streams or user buffers\n return bt != buf.length && noBuf ? slc(buf, 0, bt) : buf.subarray(0, bt);\n};\n// starting at p, write the minimum number of bits that can hold v to d\nvar wbits = function (d, p, v) {\n v <<= p & 7;\n var o = (p / 8) | 0;\n d[o] |= v;\n d[o + 1] |= v >> 8;\n};\n// starting at p, write the minimum number of bits (>8) that can hold v to d\nvar wbits16 = function (d, p, v) {\n v <<= p & 7;\n var o = (p / 8) | 0;\n d[o] |= v;\n d[o + 1] |= v >> 8;\n d[o + 2] |= v >> 16;\n};\n// creates code lengths from a frequency table\nvar hTree = function (d, mb) {\n // Need extra info to make a tree\n var t = [];\n for (var i = 0; i < d.length; ++i) {\n if (d[i])\n t.push({ s: i, f: d[i] });\n }\n var s = t.length;\n var t2 = t.slice();\n if (!s)\n return { t: et, l: 0 };\n if (s == 1) {\n var v = new u8(t[0].s + 1);\n v[t[0].s] = 1;\n return { t: v, l: 1 };\n }\n t.sort(function (a, b) { return a.f - b.f; });\n // after i2 reaches last ind, will be stopped\n // freq must be greater than largest possible number of symbols\n t.push({ s: -1, f: 25001 });\n var l = t[0], r = t[1], i0 = 0, i1 = 1, i2 = 2;\n t[0] = { s: -1, f: l.f + r.f, l: l, r: r };\n // efficient algorithm from UZIP.js\n // i0 is lookbehind, i2 is lookahead - after processing two low-freq\n // symbols that combined have high freq, will start processing i2 (high-freq,\n // non-composite) symbols instead\n // see https://reddit.com/r/photopea/comments/ikekht/uzipjs_questions/\n while (i1 != s - 1) {\n l = t[t[i0].f < t[i2].f ? i0++ : i2++];\n r = t[i0 != i1 && t[i0].f < t[i2].f ? i0++ : i2++];\n t[i1++] = { s: -1, f: l.f + r.f, l: l, r: r };\n }\n var maxSym = t2[0].s;\n for (var i = 1; i < s; ++i) {\n if (t2[i].s > maxSym)\n maxSym = t2[i].s;\n }\n // code lengths\n var tr = new u16(maxSym + 1);\n // max bits in tree\n var mbt = ln(t[i1 - 1], tr, 0);\n if (mbt > mb) {\n // more algorithms from UZIP.js\n // TODO: find out how this code works (debt)\n // ind debt\n var i = 0, dt = 0;\n // left cost\n var lft = mbt - mb, cst = 1 << lft;\n t2.sort(function (a, b) { return tr[b.s] - tr[a.s] || a.f - b.f; });\n for (; i < s; ++i) {\n var i2_1 = t2[i].s;\n if (tr[i2_1] > mb) {\n dt += cst - (1 << (mbt - tr[i2_1]));\n tr[i2_1] = mb;\n }\n else\n break;\n }\n dt >>= lft;\n while (dt > 0) {\n var i2_2 = t2[i].s;\n if (tr[i2_2] < mb)\n dt -= 1 << (mb - tr[i2_2]++ - 1);\n else\n ++i;\n }\n for (; i >= 0 && dt; --i) {\n var i2_3 = t2[i].s;\n if (tr[i2_3] == mb) {\n --tr[i2_3];\n ++dt;\n }\n }\n mbt = mb;\n }\n return { t: new u8(tr), l: mbt };\n};\n// get the max length and assign length codes\nvar ln = function (n, l, d) {\n return n.s == -1\n ? Math.max(ln(n.l, l, d + 1), ln(n.r, l, d + 1))\n : (l[n.s] = d);\n};\n// length codes generation\nvar lc = function (c) {\n var s = c.length;\n // Note that the semicolon was intentional\n while (s && !c[--s])\n ;\n var cl = new u16(++s);\n // ind num streak\n var cli = 0, cln = c[0], cls = 1;\n var w = function (v) { cl[cli++] = v; };\n for (var i = 1; i <= s; ++i) {\n if (c[i] == cln && i != s)\n ++cls;\n else {\n if (!cln && cls > 2) {\n for (; cls > 138; cls -= 138)\n w(32754);\n if (cls > 2) {\n w(cls > 10 ? ((cls - 11) << 5) | 28690 : ((cls - 3) << 5) | 12305);\n cls = 0;\n }\n }\n else if (cls > 3) {\n w(cln), --cls;\n for (; cls > 6; cls -= 6)\n w(8304);\n if (cls > 2)\n w(((cls - 3) << 5) | 8208), cls = 0;\n }\n while (cls--)\n w(cln);\n cls = 1;\n cln = c[i];\n }\n }\n return { c: cl.subarray(0, cli), n: s };\n};\n// calculate the length of output from tree, code lengths\nvar clen = function (cf, cl) {\n var l = 0;\n for (var i = 0; i < cl.length; ++i)\n l += cf[i] * cl[i];\n return l;\n};\n// writes a fixed block\n// returns the new bit pos\nvar wfblk = function (out, pos, dat) {\n // no need to write 00 as type: TypedArray defaults to 0\n var s = dat.length;\n var o = shft(pos + 2);\n out[o] = s & 255;\n out[o + 1] = s >> 8;\n out[o + 2] = out[o] ^ 255;\n out[o + 3] = out[o + 1] ^ 255;\n for (var i = 0; i < s; ++i)\n out[o + i + 4] = dat[i];\n return (o + 4 + s) * 8;\n};\n// writes a block\nvar wblk = function (dat, out, final, syms, lf, df, eb, li, bs, bl, p) {\n wbits(out, p++, final);\n ++lf[256];\n var _a = hTree(lf, 15), dlt = _a.t, mlb = _a.l;\n var _b = hTree(df, 15), ddt = _b.t, mdb = _b.l;\n var _c = lc(dlt), lclt = _c.c, nlc = _c.n;\n var _d = lc(ddt), lcdt = _d.c, ndc = _d.n;\n var lcfreq = new u16(19);\n for (var i = 0; i < lclt.length; ++i)\n ++lcfreq[lclt[i] & 31];\n for (var i = 0; i < lcdt.length; ++i)\n ++lcfreq[lcdt[i] & 31];\n var _e = hTree(lcfreq, 7), lct = _e.t, mlcb = _e.l;\n var nlcc = 19;\n for (; nlcc > 4 && !lct[clim[nlcc - 1]]; --nlcc)\n ;\n var flen = (bl + 5) << 3;\n var ftlen = clen(lf, flt) + clen(df, fdt) + eb;\n var dtlen = clen(lf, dlt) + clen(df, ddt) + eb + 14 + 3 * nlcc + clen(lcfreq, lct) + 2 * lcfreq[16] + 3 * lcfreq[17] + 7 * lcfreq[18];\n if (bs >= 0 && flen <= ftlen && flen <= dtlen)\n return wfblk(out, p, dat.subarray(bs, bs + bl));\n var lm, ll, dm, dl;\n wbits(out, p, 1 + (dtlen < ftlen)), p += 2;\n if (dtlen < ftlen) {\n lm = hMap(dlt, mlb, 0), ll = dlt, dm = hMap(ddt, mdb, 0), dl = ddt;\n var llm = hMap(lct, mlcb, 0);\n wbits(out, p, nlc - 257);\n wbits(out, p + 5, ndc - 1);\n wbits(out, p + 10, nlcc - 4);\n p += 14;\n for (var i = 0; i < nlcc; ++i)\n wbits(out, p + 3 * i, lct[clim[i]]);\n p += 3 * nlcc;\n var lcts = [lclt, lcdt];\n for (var it = 0; it < 2; ++it) {\n var clct = lcts[it];\n for (var i = 0; i < clct.length; ++i) {\n var len = clct[i] & 31;\n wbits(out, p, llm[len]), p += lct[len];\n if (len > 15)\n wbits(out, p, (clct[i] >> 5) & 127), p += clct[i] >> 12;\n }\n }\n }\n else {\n lm = flm, ll = flt, dm = fdm, dl = fdt;\n }\n for (var i = 0; i < li; ++i) {\n var sym = syms[i];\n if (sym > 255) {\n var len = (sym >> 18) & 31;\n wbits16(out, p, lm[len + 257]), p += ll[len + 257];\n if (len > 7)\n wbits(out, p, (sym >> 23) & 31), p += fleb[len];\n var dst = sym & 31;\n wbits16(out, p, dm[dst]), p += dl[dst];\n if (dst > 3)\n wbits16(out, p, (sym >> 5) & 8191), p += fdeb[dst];\n }\n else {\n wbits16(out, p, lm[sym]), p += ll[sym];\n }\n }\n wbits16(out, p, lm[256]);\n return p + ll[256];\n};\n// deflate options (nice << 13) | chain\nvar deo = /*#__PURE__*/ new i32([65540, 131080, 131088, 131104, 262176, 1048704, 1048832, 2114560, 2117632]);\n// empty\nvar et = /*#__PURE__*/ new u8(0);\n// compresses data into a raw DEFLATE buffer\nvar dflt = function (dat, lvl, plvl, pre, post, st) {\n var s = st.z || dat.length;\n var o = new u8(pre + s + 5 * (1 + Math.ceil(s / 7000)) + post);\n // writing to this writes to the output buffer\n var w = o.subarray(pre, o.length - post);\n var lst = st.l;\n var pos = (st.r || 0) & 7;\n if (lvl) {\n if (pos)\n w[0] = st.r >> 3;\n var opt = deo[lvl - 1];\n var n = opt >> 13, c = opt & 8191;\n var msk_1 = (1 << plvl) - 1;\n // prev 2-byte val map curr 2-byte val map\n var prev = st.p || new u16(32768), head = st.h || new u16(msk_1 + 1);\n var bs1_1 = Math.ceil(plvl / 3), bs2_1 = 2 * bs1_1;\n var hsh = function (i) { return (dat[i] ^ (dat[i + 1] << bs1_1) ^ (dat[i + 2] << bs2_1)) & msk_1; };\n // 24576 is an arbitrary number of maximum symbols per block\n // 424 buffer for last block\n var syms = new i32(25000);\n // length/literal freq distance freq\n var lf = new u16(288), df = new u16(32);\n // l/lcnt exbits index l/lind waitdx blkpos\n var lc_1 = 0, eb = 0, i = st.i || 0, li = 0, wi = st.w || 0, bs = 0;\n for (; i + 2 < s; ++i) {\n // hash value\n var hv = hsh(i);\n // index mod 32768 previous index mod\n var imod = i & 32767, pimod = head[hv];\n prev[imod] = pimod;\n head[hv] = imod;\n // We always should modify head and prev, but only add symbols if\n // this data is not yet processed (\"wait\" for wait index)\n if (wi <= i) {\n // bytes remaining\n var rem = s - i;\n if ((lc_1 > 7000 || li > 24576) && (rem > 423 || !lst)) {\n pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs, i - bs, pos);\n li = lc_1 = eb = 0, bs = i;\n for (var j = 0; j < 286; ++j)\n lf[j] = 0;\n for (var j = 0; j < 30; ++j)\n df[j] = 0;\n }\n // len dist chain\n var l = 2, d = 0, ch_1 = c, dif = imod - pimod & 32767;\n if (rem > 2 && hv == hsh(i - dif)) {\n var maxn = Math.min(n, rem) - 1;\n var maxd = Math.min(32767, i);\n // max possible length\n // not capped at dif because decompressors implement \"rolling\" index population\n var ml = Math.min(258, rem);\n while (dif <= maxd && --ch_1 && imod != pimod) {\n if (dat[i + l] == dat[i + l - dif]) {\n var nl = 0;\n for (; nl < ml && dat[i + nl] == dat[i + nl - dif]; ++nl)\n ;\n if (nl > l) {\n l = nl, d = dif;\n // break out early when we reach \"nice\" (we are satisfied enough)\n if (nl > maxn)\n break;\n // now, find the rarest 2-byte sequence within this\n // length of literals and search for that instead.\n // Much faster than just using the start\n var mmd = Math.min(dif, nl - 2);\n var md = 0;\n for (var j = 0; j < mmd; ++j) {\n var ti = i - dif + j & 32767;\n var pti = prev[ti];\n var cd = ti - pti & 32767;\n if (cd > md)\n md = cd, pimod = ti;\n }\n }\n }\n // check the previous match\n imod = pimod, pimod = prev[imod];\n dif += imod - pimod & 32767;\n }\n }\n // d will be nonzero only when a match was found\n if (d) {\n // store both dist and len data in one int32\n // Make sure this is recognized as a len/dist with 28th bit (2^28)\n syms[li++] = 268435456 | (revfl[l] << 18) | revfd[d];\n var lin = revfl[l] & 31, din = revfd[d] & 31;\n eb += fleb[lin] + fdeb[din];\n ++lf[257 + lin];\n ++df[din];\n wi = i + l;\n ++lc_1;\n }\n else {\n syms[li++] = dat[i];\n ++lf[dat[i]];\n }\n }\n }\n for (i = Math.max(i, wi); i < s; ++i) {\n syms[li++] = dat[i];\n ++lf[dat[i]];\n }\n pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i - bs, pos);\n if (!lst) {\n st.r = (pos & 7) | w[(pos / 8) | 0] << 3;\n // shft(pos) now 1 less if pos & 7 != 0\n pos -= 7;\n st.h = head, st.p = prev, st.i = i, st.w = wi;\n }\n }\n else {\n for (var i = st.w || 0; i < s + lst; i += 65535) {\n // end\n var e = i + 65535;\n if (e >= s) {\n // write final block\n w[(pos / 8) | 0] = lst;\n e = s;\n }\n pos = wfblk(w, pos + 1, dat.subarray(i, e));\n }\n st.i = s;\n }\n return slc(o, 0, pre + shft(pos) + post);\n};\n// CRC32 table\nvar crct = /*#__PURE__*/ (function () {\n var t = new Int32Array(256);\n for (var i = 0; i < 256; ++i) {\n var c = i, k = 9;\n while (--k)\n c = ((c & 1) && -306674912) ^ (c >>> 1);\n t[i] = c;\n }\n return t;\n})();\n// CRC32\nvar crc = function () {\n var c = -1;\n return {\n p: function (d) {\n // closures have awful performance\n var cr = c;\n for (var i = 0; i < d.length; ++i)\n cr = crct[(cr & 255) ^ d[i]] ^ (cr >>> 8);\n c = cr;\n },\n d: function () { return ~c; }\n };\n};\n// Adler32\nvar adler = function () {\n var a = 1, b = 0;\n return {\n p: function (d) {\n // closures have awful performance\n var n = a, m = b;\n var l = d.length | 0;\n for (var i = 0; i != l;) {\n var e = Math.min(i + 2655, l);\n for (; i < e; ++i)\n m += n += d[i];\n n = (n & 65535) + 15 * (n >> 16), m = (m & 65535) + 15 * (m >> 16);\n }\n a = n, b = m;\n },\n d: function () {\n a %= 65521, b %= 65521;\n return (a & 255) << 24 | (a & 0xFF00) << 8 | (b & 255) << 8 | (b >> 8);\n }\n };\n};\n;\n// deflate with opts\nvar dopt = function (dat, opt, pre, post, st) {\n if (!st) {\n st = { l: 1 };\n if (opt.dictionary) {\n var dict = opt.dictionary.subarray(-32768);\n var newDat = new u8(dict.length + dat.length);\n newDat.set(dict);\n newDat.set(dat, dict.length);\n dat = newDat;\n st.w = dict.length;\n }\n }\n return dflt(dat, opt.level == null ? 6 : opt.level, opt.mem == null ? (st.l ? Math.ceil(Math.max(8, Math.min(13, Math.log(dat.length))) * 1.5) : 20) : (12 + opt.mem), pre, post, st);\n};\n// Walmart object spread\nvar mrg = function (a, b) {\n var o = {};\n for (var k in a)\n o[k] = a[k];\n for (var k in b)\n o[k] = b[k];\n return o;\n};\n// worker clone\n// This is possibly the craziest part of the entire codebase, despite how simple it may seem.\n// The only parameter to this function is a closure that returns an array of variables outside of the function scope.\n// We're going to try to figure out the variable names used in the closure as strings because that is crucial for workerization.\n// We will return an object mapping of true variable name to value (basically, the current scope as a JS object).\n// The reason we can't just use the original variable names is minifiers mangling the toplevel scope.\n// This took me three weeks to figure out how to do.\nvar wcln = function (fn, fnStr, td) {\n var dt = fn();\n var st = fn.toString();\n var ks = st.slice(st.indexOf('[') + 1, st.lastIndexOf(']')).replace(/\\s+/g, '').split(',');\n for (var i = 0; i < dt.length; ++i) {\n var v = dt[i], k = ks[i];\n if (typeof v == 'function') {\n fnStr += ';' + k + '=';\n var st_1 = v.toString();\n if (v.prototype) {\n // for global objects\n if (st_1.indexOf('[native code]') != -1) {\n var spInd = st_1.indexOf(' ', 8) + 1;\n fnStr += st_1.slice(spInd, st_1.indexOf('(', spInd));\n }\n else {\n fnStr += st_1;\n for (var t in v.prototype)\n fnStr += ';' + k + '.prototype.' + t + '=' + v.prototype[t].toString();\n }\n }\n else\n fnStr += st_1;\n }\n else\n td[k] = v;\n }\n return fnStr;\n};\nvar ch = [];\n// clone bufs\nvar cbfs = function (v) {\n var tl = [];\n for (var k in v) {\n if (v[k].buffer) {\n tl.push((v[k] = new v[k].constructor(v[k])).buffer);\n }\n }\n return tl;\n};\n// use a worker to execute code\nvar wrkr = function (fns, init, id, cb) {\n if (!ch[id]) {\n var fnStr = '', td_1 = {}, m = fns.length - 1;\n for (var i = 0; i < m; ++i)\n fnStr = wcln(fns[i], fnStr, td_1);\n ch[id] = { c: wcln(fns[m], fnStr, td_1), e: td_1 };\n }\n var td = mrg({}, ch[id].e);\n return wk(ch[id].c + ';onmessage=function(e){for(var k in e.data)self[k]=e.data[k];onmessage=' + init.toString() + '}', id, td, cbfs(td), cb);\n};\n// base async inflate fn\nvar bInflt = function () { return [u8, u16, i32, fleb, fdeb, clim, fl, fd, flrm, fdrm, rev, ec, hMap, max, bits, bits16, shft, slc, err, inflt, inflateSync, pbf, gopt]; };\nvar bDflt = function () { return [u8, u16, i32, fleb, fdeb, clim, revfl, revfd, flm, flt, fdm, fdt, rev, deo, et, hMap, wbits, wbits16, hTree, ln, lc, clen, wfblk, wblk, shft, slc, dflt, dopt, deflateSync, pbf]; };\n// gzip extra\nvar gze = function () { return [gzh, gzhl, wbytes, crc, crct]; };\n// gunzip extra\nvar guze = function () { return [gzs, gzl]; };\n// zlib extra\nvar zle = function () { return [zlh, wbytes, adler]; };\n// unzlib extra\nvar zule = function () { return [zls]; };\n// post buf\nvar pbf = function (msg) { return postMessage(msg, [msg.buffer]); };\n// get opts\nvar gopt = function (o) { return o && {\n out: o.size && new u8(o.size),\n dictionary: o.dictionary\n}; };\n// async helper\nvar cbify = function (dat, opts, fns, init, id, cb) {\n var w = wrkr(fns, init, id, function (err, dat) {\n w.terminate();\n cb(err, dat);\n });\n w.postMessage([dat, opts], opts.consume ? [dat.buffer] : []);\n return function () { w.terminate(); };\n};\n// auto stream\nvar astrm = function (strm) {\n strm.ondata = function (dat, final) { return postMessage([dat, final], [dat.buffer]); };\n return function (ev) {\n if (ev.data[0]) {\n strm.push(ev.data[0], ev.data[1]);\n postMessage([ev.data[0].length]);\n }\n else\n strm.flush(ev.data[1]);\n };\n};\n// async stream attach\nvar astrmify = function (fns, strm, opts, init, id, flush, ext) {\n var t;\n var w = wrkr(fns, init, id, function (err, dat) {\n if (err)\n w.terminate(), strm.ondata.call(strm, err);\n else if (!Array.isArray(dat))\n ext(dat);\n else if (dat.length == 1) {\n strm.queuedSize -= dat[0];\n if (strm.ondrain)\n strm.ondrain(dat[0]);\n }\n else {\n if (dat[1])\n w.terminate();\n strm.ondata.call(strm, err, dat[0], dat[1]);\n }\n });\n w.postMessage(opts);\n strm.queuedSize = 0;\n strm.push = function (d, f) {\n if (!strm.ondata)\n err(5);\n if (t)\n strm.ondata(err(4, 0, 1), null, !!f);\n strm.queuedSize += d.length;\n // can fail for cross-realm Uint8Array, but ok - only a small performance penalty\n w.postMessage([d, t = f], d.buffer instanceof ArrayBuffer ? [d.buffer] : []);\n };\n strm.terminate = function () { w.terminate(); };\n if (flush) {\n strm.flush = function (sync) { w.postMessage([0, sync]); };\n }\n};\n// read 2 bytes\nvar b2 = function (d, b) { return d[b] | (d[b + 1] << 8); };\n// read 4 bytes\nvar b4 = function (d, b) { return (d[b] | (d[b + 1] << 8) | (d[b + 2] << 16) | (d[b + 3] << 24)) >>> 0; };\n// read 8 bytes\nvar b8 = function (d, b) { return b4(d, b) + (b4(d, b + 4) * 4294967296); };\n// write bytes\nvar wbytes = function (d, b, v) {\n for (; v; ++b)\n d[b] = v, v >>>= 8;\n};\n// gzip header\nvar gzh = function (c, o) {\n var fn = o.filename;\n c[0] = 31, c[1] = 139, c[2] = 8, c[8] = o.level < 2 ? 4 : o.level == 9 ? 2 : 0, c[9] = 3; // assume Unix\n if (o.mtime != 0)\n wbytes(c, 4, Math.floor(new Date(o.mtime || Date.now()) / 1000));\n if (fn) {\n c[3] = 8;\n for (var i = 0; i <= fn.length; ++i)\n c[i + 10] = fn.charCodeAt(i);\n }\n};\n// gzip footer: -8 to -4 = CRC, -4 to -0 is length\n// gzip start\nvar gzs = function (d) {\n if (d[0] != 31 || d[1] != 139 || d[2] != 8)\n err(6, 'invalid gzip data');\n var flg = d[3];\n var st = 10;\n if (flg & 4)\n st += (d[10] | d[11] << 8) + 2;\n for (var zs = (flg >> 3 & 1) + (flg >> 4 & 1); zs > 0; zs -= !d[st++])\n ;\n return st + (flg & 2);\n};\n// gzip length\nvar gzl = function (d) {\n var l = d.length;\n return (d[l - 4] | d[l - 3] << 8 | d[l - 2] << 16 | d[l - 1] << 24) >>> 0;\n};\n// gzip header length\nvar gzhl = function (o) { return 10 + (o.filename ? o.filename.length + 1 : 0); };\n// zlib header\nvar zlh = function (c, o) {\n var lv = o.level, fl = lv == 0 ? 0 : lv < 6 ? 1 : lv == 9 ? 3 : 2;\n c[0] = 120, c[1] = (fl << 6) | (o.dictionary && 32);\n c[1] |= 31 - ((c[0] << 8) | c[1]) % 31;\n if (o.dictionary) {\n var h = adler();\n h.p(o.dictionary);\n wbytes(c, 2, h.d());\n }\n};\n// zlib start\nvar zls = function (d, dict) {\n if ((d[0] & 15) != 8 || (d[0] >> 4) > 7 || ((d[0] << 8 | d[1]) % 31))\n err(6, 'invalid zlib data');\n if ((d[1] >> 5 & 1) == +!dict)\n err(6, 'invalid zlib data: ' + (d[1] & 32 ? 'need' : 'unexpected') + ' dictionary');\n return (d[1] >> 3 & 4) + 2;\n};\nfunction StrmOpt(opts, cb) {\n if (typeof opts == 'function')\n cb = opts, opts = {};\n this.ondata = cb;\n return opts;\n}\n/**\n * Streaming DEFLATE compression\n */\nvar Deflate = /*#__PURE__*/ (function () {\n function Deflate(opts, cb) {\n if (typeof opts == 'function')\n cb = opts, opts = {};\n this.ondata = cb;\n this.o = opts || {};\n this.s = { l: 0, i: 32768, w: 32768, z: 32768 };\n // Buffer length must always be 0 mod 32768 for index calculations to be correct when modifying head and prev\n // 98304 = 32768 (lookback) + 65536 (common chunk size)\n this.b = new u8(98304);\n if (this.o.dictionary) {\n var dict = this.o.dictionary.subarray(-32768);\n this.b.set(dict, 32768 - dict.length);\n this.s.i = 32768 - dict.length;\n }\n }\n Deflate.prototype.p = function (c, f) {\n this.ondata(dopt(c, this.o, 0, 0, this.s), f);\n };\n /**\n * Pushes a chunk to be deflated\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n Deflate.prototype.push = function (chunk, final) {\n if (!this.ondata)\n err(5);\n if (this.s.l)\n err(4);\n var endLen = chunk.length + this.s.z;\n if (endLen > this.b.length) {\n if (endLen > 2 * this.b.length - 32768) {\n var newBuf = new u8(endLen & -32768);\n newBuf.set(this.b.subarray(0, this.s.z));\n this.b = newBuf;\n }\n var split = this.b.length - this.s.z;\n this.b.set(chunk.subarray(0, split), this.s.z);\n this.s.z = this.b.length;\n this.p(this.b, false);\n this.b.set(this.b.subarray(-32768));\n this.b.set(chunk.subarray(split), 32768);\n this.s.z = chunk.length - split + 32768;\n this.s.i = 32766, this.s.w = 32768;\n }\n else {\n this.b.set(chunk, this.s.z);\n this.s.z += chunk.length;\n }\n this.s.l = final & 1;\n if (this.s.z > this.s.w + 8191 || final) {\n this.p(this.b, final || false);\n this.s.w = this.s.i, this.s.i -= 2;\n }\n if (final) {\n // cleanup unneeded buffers/state to reduce memory usage\n this.s = this.o = {};\n this.b = et;\n }\n };\n /**\n * Flushes buffered uncompressed data. Useful to immediately retrieve the\n * deflated output for small inputs.\n * @param sync Whether to flush to a byte boundary. A sync flush takes 4-5\n * extra bytes, but guarantees all pushed data is immediately\n * decompressible. A separate DEFLATE stream may be concatenated\n * with the current output after a sync flush.\n */\n Deflate.prototype.flush = function (sync) {\n if (!this.ondata)\n err(5);\n if (this.s.l)\n err(4);\n this.p(this.b, false);\n this.s.w = this.s.i, this.s.i -= 2;\n // could technically skip writing the type-0 block for (this.s.r & 7) == 0,\n // but the deterministic trailer (00 00 FF FF) is useful in some situations\n if (sync) {\n var c = new u8(6);\n c[0] = this.s.r >> 3;\n // write empty, non-final type-0 block\n var ep = wfblk(c, this.s.r, et);\n this.s.r = 0;\n this.ondata(c.subarray(0, ep >> 3), false);\n }\n };\n return Deflate;\n}());\nexport { Deflate };\n/**\n * Asynchronous streaming DEFLATE compression\n */\nvar AsyncDeflate = /*#__PURE__*/ (function () {\n function AsyncDeflate(opts, cb) {\n astrmify([\n bDflt,\n function () { return [astrm, Deflate]; }\n ], this, StrmOpt.call(this, opts, cb), function (ev) {\n var strm = new Deflate(ev.data);\n onmessage = astrm(strm);\n }, 6, 1);\n }\n return AsyncDeflate;\n}());\nexport { AsyncDeflate };\nexport function deflate(data, opts, cb) {\n if (!cb)\n cb = opts, opts = {};\n if (typeof cb != 'function')\n err(7);\n return cbify(data, opts, [\n bDflt,\n ], function (ev) { return pbf(deflateSync(ev.data[0], ev.data[1])); }, 0, cb);\n}\n/**\n * Compresses data with DEFLATE without any wrapper\n * @param data The data to compress\n * @param opts The compression options\n * @returns The deflated version of the data\n */\nexport function deflateSync(data, opts) {\n return dopt(data, opts || {}, 0, 0);\n}\n/**\n * Streaming DEFLATE decompression\n */\nvar Inflate = /*#__PURE__*/ (function () {\n function Inflate(opts, cb) {\n // no StrmOpt here to avoid adding to workerizer\n if (typeof opts == 'function')\n cb = opts, opts = {};\n this.ondata = cb;\n var dict = opts && opts.dictionary && opts.dictionary.subarray(-32768);\n this.s = { i: 0, b: dict ? dict.length : 0 };\n this.o = new u8(32768);\n this.p = new u8(0);\n if (dict)\n this.o.set(dict);\n }\n Inflate.prototype.e = function (c) {\n if (!this.ondata)\n err(5);\n if (this.d)\n err(4);\n if (!this.p.length)\n this.p = c;\n else if (c.length) {\n var n = new u8(this.p.length + c.length);\n n.set(this.p), n.set(c, this.p.length), this.p = n;\n }\n };\n Inflate.prototype.c = function (final) {\n this.s.i = +(this.d = final || false);\n var bts = this.s.b;\n var dt = inflt(this.p, this.s, this.o);\n this.ondata(slc(dt, bts, this.s.b), this.d);\n this.o = slc(dt, this.s.b - 32768), this.s.b = this.o.length;\n this.p = slc(this.p, (this.s.p / 8) | 0), this.s.p &= 7;\n };\n /**\n * Pushes a chunk to be inflated\n * @param chunk The chunk to push\n * @param final Whether this is the final chunk\n */\n Inflate.prototype.push = function (chunk, final) {\n this.e(chunk), this.c(final);\n };\n return Inflate;\n}());\nexport { Inflate };\n/**\n * Asynchronous streaming DEFLATE decompression\n */\nvar AsyncInflate = /*#__PURE__*/ (function () {\n function AsyncInflate(opts, cb) {\n astrmify([\n bInflt,\n function () { return [astrm, Inflate]; }\n ], this, StrmOpt.call(this, opts, cb), function (ev) {\n var strm = new Inflate(ev.data);\n onmessage = astrm(strm);\n }, 7, 0);\n }\n return AsyncInflate;\n}());\nexport { AsyncInflate };\nexport function inflate(data, opts, cb) {\n if (!cb)\n cb = opts, opts = {};\n if (typeof cb != 'function')\n err(7);\n return cbify(data, opts, [\n bInflt\n ], function (ev) { return pbf(inflateSync(ev.data[0], gopt(ev.data[1]))); }, 1, cb);\n}\nexport function inflateSync(data, opts) {\n return inflt(data, { i: 2 }, opts && opts.out, opts && opts.dictionary);\n}\n// before you yell at me for not just using extends, my reason is that TS inheritance is hard to workerize.\n/**\n * Streaming GZIP compression\n */\nvar Gzip = /*#__PURE__*/ (function () {\n function Gzip(opts, cb) {\n this.c = crc();\n this.l = 0;\n this.v = 1;\n Deflate.call(this, opts, cb);\n }\n /**\n * Pushes a chunk to be GZIPped\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n Gzip.prototype.push = function (chunk, final) {\n this.c.p(chunk);\n this.l += chunk.length;\n Deflate.prototype.push.call(this, chunk, final);\n };\n Gzip.prototype.p = function (c, f) {\n var raw = dopt(c, this.o, this.v && gzhl(this.o), f && 8, this.s);\n if (this.v)\n gzh(raw, this.o), this.v = 0;\n if (f)\n wbytes(raw, raw.length - 8, this.c.d()), wbytes(raw, raw.length - 4, this.l);\n this.ondata(raw, f);\n };\n /**\n * Flushes buffered uncompressed data. Useful to immediately retrieve the\n * GZIPped output for small inputs.\n * @param sync Whether to flush to a byte boundary. A sync flush takes 4-5\n * extra bytes, but guarantees all pushed data is immediately\n * decompressible.\n */\n Gzip.prototype.flush = function (sync) {\n Deflate.prototype.flush.call(this, sync);\n };\n return Gzip;\n}());\nexport { Gzip };\n/**\n * Asynchronous streaming GZIP compression\n */\nvar AsyncGzip = /*#__PURE__*/ (function () {\n function AsyncGzip(opts, cb) {\n astrmify([\n bDflt,\n gze,\n function () { return [astrm, Deflate, Gzip]; }\n ], this, StrmOpt.call(this, opts, cb), function (ev) {\n var strm = new Gzip(ev.data);\n onmessage = astrm(strm);\n }, 8, 1);\n }\n return AsyncGzip;\n}());\nexport { AsyncGzip };\nexport function gzip(data, opts, cb) {\n if (!cb)\n cb = opts, opts = {};\n if (typeof cb != 'function')\n err(7);\n return cbify(data, opts, [\n bDflt,\n gze,\n function () { return [gzipSync]; }\n ], function (ev) { return pbf(gzipSync(ev.data[0], ev.data[1])); }, 2, cb);\n}\n/**\n * Compresses data with GZIP\n * @param data The data to compress\n * @param opts The compression options\n * @returns The gzipped version of the data\n */\nexport function gzipSync(data, opts) {\n if (!opts)\n opts = {};\n var c = crc(), l = data.length;\n c.p(data);\n var d = dopt(data, opts, gzhl(opts), 8), s = d.length;\n return gzh(d, opts), wbytes(d, s - 8, c.d()), wbytes(d, s - 4, l), d;\n}\n/**\n * Streaming single or multi-member GZIP decompression\n */\nvar Gunzip = /*#__PURE__*/ (function () {\n function Gunzip(opts, cb) {\n this.v = 1;\n this.r = 0;\n Inflate.call(this, opts, cb);\n }\n /**\n * Pushes a chunk to be GUNZIPped\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n Gunzip.prototype.push = function (chunk, final) {\n Inflate.prototype.e.call(this, chunk);\n this.r += chunk.length;\n if (this.v) {\n var p = this.p.subarray(this.v - 1);\n var s = p.length > 3 ? gzs(p) : 4;\n if (s > p.length) {\n if (!final)\n return;\n }\n else if (this.v > 1 && this.onmember) {\n this.onmember(this.r - p.length);\n }\n this.p = p.subarray(s), this.v = 0;\n }\n // necessary to prevent TS from using the closure value\n // This allows for workerization to function correctly\n Inflate.prototype.c.call(this, 0);\n // process concatenated GZIP\n if (this.s.f && !this.s.l) {\n this.v = shft(this.s.p) + 9;\n this.s = { i: 0 };\n this.o = new u8(0);\n this.push(new u8(0), final);\n }\n else if (final) {\n Inflate.prototype.c.call(this, final);\n }\n };\n return Gunzip;\n}());\nexport { Gunzip };\n/**\n * Asynchronous streaming single or multi-member GZIP decompression\n */\nvar AsyncGunzip = /*#__PURE__*/ (function () {\n function AsyncGunzip(opts, cb) {\n var _this = this;\n astrmify([\n bInflt,\n guze,\n function () { return [astrm, Inflate, Gunzip]; }\n ], this, StrmOpt.call(this, opts, cb), function (ev) {\n var strm = new Gunzip(ev.data);\n strm.onmember = function (offset) { return postMessage(offset); };\n onmessage = astrm(strm);\n }, 9, 0, function (offset) { return _this.onmember && _this.onmember(offset); });\n }\n return AsyncGunzip;\n}());\nexport { AsyncGunzip };\nexport function gunzip(data, opts, cb) {\n if (!cb)\n cb = opts, opts = {};\n if (typeof cb != 'function')\n err(7);\n return cbify(data, opts, [\n bInflt,\n guze,\n function () { return [gunzipSync]; }\n ], function (ev) { return pbf(gunzipSync(ev.data[0], ev.data[1])); }, 3, cb);\n}\nexport function gunzipSync(data, opts) {\n var st = gzs(data);\n if (st + 8 > data.length)\n err(6, 'invalid gzip data');\n return inflt(data.subarray(st, -8), { i: 2 }, opts && opts.out || new u8(gzl(data)), opts && opts.dictionary);\n}\n/**\n * Streaming Zlib compression\n */\nvar Zlib = /*#__PURE__*/ (function () {\n function Zlib(opts, cb) {\n this.c = adler();\n this.v = 1;\n Deflate.call(this, opts, cb);\n }\n /**\n * Pushes a chunk to be zlibbed\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n Zlib.prototype.push = function (chunk, final) {\n this.c.p(chunk);\n Deflate.prototype.push.call(this, chunk, final);\n };\n Zlib.prototype.p = function (c, f) {\n var raw = dopt(c, this.o, this.v && (this.o.dictionary ? 6 : 2), f && 4, this.s);\n if (this.v)\n zlh(raw, this.o), this.v = 0;\n if (f)\n wbytes(raw, raw.length - 4, this.c.d());\n this.ondata(raw, f);\n };\n /**\n * Flushes buffered uncompressed data. Useful to immediately retrieve the\n * zlibbed output for small inputs.\n * @param sync Whether to flush to a byte boundary. A sync flush takes 4-5\n * extra bytes, but guarantees all pushed data is immediately\n * decompressible.\n */\n Zlib.prototype.flush = function (sync) {\n Deflate.prototype.flush.call(this, sync);\n };\n return Zlib;\n}());\nexport { Zlib };\n/**\n * Asynchronous streaming Zlib compression\n */\nvar AsyncZlib = /*#__PURE__*/ (function () {\n function AsyncZlib(opts, cb) {\n astrmify([\n bDflt,\n zle,\n function () { return [astrm, Deflate, Zlib]; }\n ], this, StrmOpt.call(this, opts, cb), function (ev) {\n var strm = new Zlib(ev.data);\n onmessage = astrm(strm);\n }, 10, 1);\n }\n return AsyncZlib;\n}());\nexport { AsyncZlib };\nexport function zlib(data, opts, cb) {\n if (!cb)\n cb = opts, opts = {};\n if (typeof cb != 'function')\n err(7);\n return cbify(data, opts, [\n bDflt,\n zle,\n function () { return [zlibSync]; }\n ], function (ev) { return pbf(zlibSync(ev.data[0], ev.data[1])); }, 4, cb);\n}\n/**\n * Compress data with Zlib\n * @param data The data to compress\n * @param opts The compression options\n * @returns The zlib-compressed version of the data\n */\nexport function zlibSync(data, opts) {\n if (!opts)\n opts = {};\n var a = adler();\n a.p(data);\n var d = dopt(data, opts, opts.dictionary ? 6 : 2, 4);\n return zlh(d, opts), wbytes(d, d.length - 4, a.d()), d;\n}\n/**\n * Streaming Zlib decompression\n */\nvar Unzlib = /*#__PURE__*/ (function () {\n function Unzlib(opts, cb) {\n Inflate.call(this, opts, cb);\n this.v = opts && opts.dictionary ? 2 : 1;\n }\n /**\n * Pushes a chunk to be unzlibbed\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n Unzlib.prototype.push = function (chunk, final) {\n Inflate.prototype.e.call(this, chunk);\n if (this.v) {\n if (this.p.length < 6 && !final)\n return;\n this.p = this.p.subarray(zls(this.p, this.v - 1)), this.v = 0;\n }\n if (final) {\n if (this.p.length < 4)\n err(6, 'invalid zlib data');\n this.p = this.p.subarray(0, -4);\n }\n // necessary to prevent TS from using the closure value\n // This allows for workerization to function correctly\n Inflate.prototype.c.call(this, final);\n };\n return Unzlib;\n}());\nexport { Unzlib };\n/**\n * Asynchronous streaming Zlib decompression\n */\nvar AsyncUnzlib = /*#__PURE__*/ (function () {\n function AsyncUnzlib(opts, cb) {\n astrmify([\n bInflt,\n zule,\n function () { return [astrm, Inflate, Unzlib]; }\n ], this, StrmOpt.call(this, opts, cb), function (ev) {\n var strm = new Unzlib(ev.data);\n onmessage = astrm(strm);\n }, 11, 0);\n }\n return AsyncUnzlib;\n}());\nexport { AsyncUnzlib };\nexport function unzlib(data, opts, cb) {\n if (!cb)\n cb = opts, opts = {};\n if (typeof cb != 'function')\n err(7);\n return cbify(data, opts, [\n bInflt,\n zule,\n function () { return [unzlibSync]; }\n ], function (ev) { return pbf(unzlibSync(ev.data[0], gopt(ev.data[1]))); }, 5, cb);\n}\nexport function unzlibSync(data, opts) {\n return inflt(data.subarray(zls(data, opts && opts.dictionary), -4), { i: 2 }, opts && opts.out, opts && opts.dictionary);\n}\n// Default algorithm for compression (used because having a known output size allows faster decompression)\nexport { gzip as compress, AsyncGzip as AsyncCompress };\nexport { gzipSync as compressSync, Gzip as Compress };\n/**\n * Streaming GZIP, Zlib, or raw DEFLATE decompression\n */\nvar Decompress = /*#__PURE__*/ (function () {\n function Decompress(opts, cb) {\n this.o = StrmOpt.call(this, opts, cb) || {};\n this.G = Gunzip;\n this.I = Inflate;\n this.Z = Unzlib;\n }\n // init substream\n // overriden by AsyncDecompress\n Decompress.prototype.i = function () {\n var _this = this;\n this.s.ondata = function (dat, final) {\n _this.ondata(dat, final);\n };\n };\n /**\n * Pushes a chunk to be decompressed\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n Decompress.prototype.push = function (chunk, final) {\n if (!this.ondata)\n err(5);\n if (!this.s) {\n if (this.p && this.p.length) {\n var n = new u8(this.p.length + chunk.length);\n n.set(this.p), n.set(chunk, this.p.length);\n }\n else\n this.p = chunk;\n if (this.p.length > 2) {\n this.s = (this.p[0] == 31 && this.p[1] == 139 && this.p[2] == 8)\n ? new this.G(this.o)\n : ((this.p[0] & 15) != 8 || (this.p[0] >> 4) > 7 || ((this.p[0] << 8 | this.p[1]) % 31))\n ? new this.I(this.o)\n : new this.Z(this.o);\n this.i();\n this.s.push(this.p, final);\n this.p = null;\n }\n }\n else\n this.s.push(chunk, final);\n };\n return Decompress;\n}());\nexport { Decompress };\n/**\n * Asynchronous streaming GZIP, Zlib, or raw DEFLATE decompression\n */\nvar AsyncDecompress = /*#__PURE__*/ (function () {\n function AsyncDecompress(opts, cb) {\n Decompress.call(this, opts, cb);\n this.queuedSize = 0;\n this.G = AsyncGunzip;\n this.I = AsyncInflate;\n this.Z = AsyncUnzlib;\n }\n AsyncDecompress.prototype.i = function () {\n var _this = this;\n this.s.ondata = function (err, dat, final) {\n _this.ondata(err, dat, final);\n };\n this.s.ondrain = function (size) {\n _this.queuedSize -= size;\n if (_this.ondrain)\n _this.ondrain(size);\n };\n };\n /**\n * Pushes a chunk to be decompressed\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n AsyncDecompress.prototype.push = function (chunk, final) {\n this.queuedSize += chunk.length;\n Decompress.prototype.push.call(this, chunk, final);\n };\n return AsyncDecompress;\n}());\nexport { AsyncDecompress };\nexport function decompress(data, opts, cb) {\n if (!cb)\n cb = opts, opts = {};\n if (typeof cb != 'function')\n err(7);\n return (data[0] == 31 && data[1] == 139 && data[2] == 8)\n ? gunzip(data, opts, cb)\n : ((data[0] & 15) != 8 || (data[0] >> 4) > 7 || ((data[0] << 8 | data[1]) % 31))\n ? inflate(data, opts, cb)\n : unzlib(data, opts, cb);\n}\n/**\n * Expands compressed GZIP, Zlib, or raw DEFLATE data, automatically detecting the format\n * @param data The data to decompress\n * @param opts The decompression options\n * @returns The decompressed version of the data\n */\nexport function decompressSync(data, opts) {\n return (data[0] == 31 && data[1] == 139 && data[2] == 8)\n ? gunzipSync(data, opts)\n : ((data[0] & 15) != 8 || (data[0] >> 4) > 7 || ((data[0] << 8 | data[1]) % 31))\n ? inflateSync(data, opts)\n : unzlibSync(data, opts);\n}\n// flatten a directory structure\nvar fltn = function (d, p, t, o) {\n for (var k in d) {\n var val = d[k], n = p + k, op = o;\n if (Array.isArray(val))\n op = mrg(o, val[1]), val = val[0];\n if (ArrayBuffer.isView(val))\n t[n] = [val, op];\n else {\n t[n += '/'] = [new u8(0), op];\n fltn(val, n, t, o);\n }\n }\n};\n// text encoder\nvar te = typeof TextEncoder != 'undefined' && /*#__PURE__*/ new TextEncoder();\n// text decoder\nvar td = typeof TextDecoder != 'undefined' && /*#__PURE__*/ new TextDecoder();\n// text decoder stream\nvar tds = 0;\ntry {\n td.decode(et, { stream: true });\n tds = 1;\n}\ncatch (e) { }\n// decode UTF8\nvar dutf8 = function (d) {\n for (var r = '', i = 0;;) {\n var c = d[i++];\n var eb = (c > 127) + (c > 223) + (c > 239);\n if (i + eb > d.length)\n return { s: r, r: slc(d, i - 1) };\n if (!eb)\n r += String.fromCharCode(c);\n else if (eb == 3) {\n c = ((c & 15) << 18 | (d[i++] & 63) << 12 | (d[i++] & 63) << 6 | (d[i++] & 63)) - 65536,\n r += String.fromCharCode(55296 | (c >> 10), 56320 | (c & 1023));\n }\n else if (eb & 1)\n r += String.fromCharCode((c & 31) << 6 | (d[i++] & 63));\n else\n r += String.fromCharCode((c & 15) << 12 | (d[i++] & 63) << 6 | (d[i++] & 63));\n }\n};\n/**\n * Streaming UTF-8 decoding\n */\nvar DecodeUTF8 = /*#__PURE__*/ (function () {\n /**\n * Creates a UTF-8 decoding stream\n * @param cb The callback to call whenever data is decoded\n */\n function DecodeUTF8(cb) {\n this.ondata = cb;\n if (tds)\n this.t = new TextDecoder();\n else\n this.p = et;\n }\n /**\n * Pushes a chunk to be decoded from UTF-8 binary\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n DecodeUTF8.prototype.push = function (chunk, final) {\n if (!this.ondata)\n err(5);\n final = !!final;\n if (this.t) {\n this.ondata(this.t.decode(chunk, { stream: true }), final);\n if (final) {\n if (this.t.decode().length)\n err(8);\n this.t = null;\n }\n return;\n }\n if (!this.p)\n err(4);\n var dat = new u8(this.p.length + chunk.length);\n dat.set(this.p);\n dat.set(chunk, this.p.length);\n var _a = dutf8(dat), s = _a.s, r = _a.r;\n if (final) {\n if (r.length)\n err(8);\n this.p = null;\n }\n else\n this.p = r;\n this.ondata(s, final);\n };\n return DecodeUTF8;\n}());\nexport { DecodeUTF8 };\n/**\n * Streaming UTF-8 encoding\n */\nvar EncodeUTF8 = /*#__PURE__*/ (function () {\n /**\n * Creates a UTF-8 decoding stream\n * @param cb The callback to call whenever data is encoded\n */\n function EncodeUTF8(cb) {\n this.ondata = cb;\n }\n /**\n * Pushes a chunk to be encoded to UTF-8\n * @param chunk The string data to push\n * @param final Whether this is the last chunk\n */\n EncodeUTF8.prototype.push = function (chunk, final) {\n if (!this.ondata)\n err(5);\n if (this.d)\n err(4);\n this.ondata(strToU8(chunk), this.d = final || false);\n };\n return EncodeUTF8;\n}());\nexport { EncodeUTF8 };\n/**\n * Converts a string into a Uint8Array for use with compression/decompression methods\n * @param str The string to encode\n * @param latin1 Whether or not to interpret the data as Latin-1. This should\n * not need to be true unless decoding a binary string.\n * @returns The string encoded in UTF-8/Latin-1 binary\n */\nexport function strToU8(str, latin1) {\n if (latin1) {\n var ar_1 = new u8(str.length);\n for (var i = 0; i < str.length; ++i)\n ar_1[i] = str.charCodeAt(i);\n return ar_1;\n }\n if (te)\n return te.encode(str);\n var l = str.length;\n var ar = new u8(str.length + (str.length >> 1));\n var ai = 0;\n var w = function (v) { ar[ai++] = v; };\n for (var i = 0; i < l; ++i) {\n if (ai + 5 > ar.length) {\n var n = new u8(ai + 8 + ((l - i) << 1));\n n.set(ar);\n ar = n;\n }\n var c = str.charCodeAt(i);\n if (c < 128 || latin1)\n w(c);\n else if (c < 2048)\n w(192 | (c >> 6)), w(128 | (c & 63));\n else if (c > 55295 && c < 57344)\n c = 65536 + (c & 1023 << 10) | (str.charCodeAt(++i) & 1023),\n w(240 | (c >> 18)), w(128 | ((c >> 12) & 63)), w(128 | ((c >> 6) & 63)), w(128 | (c & 63));\n else\n w(224 | (c >> 12)), w(128 | ((c >> 6) & 63)), w(128 | (c & 63));\n }\n return slc(ar, 0, ai);\n}\n/**\n * Converts a Uint8Array to a string\n * @param dat The data to decode to string\n * @param latin1 Whether or not to interpret the data as Latin-1. This should\n * not need to be true unless encoding to binary string.\n * @returns The original UTF-8/Latin-1 string\n */\nexport function strFromU8(dat, latin1) {\n if (latin1) {\n var r = '';\n for (var i = 0; i < dat.length; i += 16384)\n r += String.fromCharCode.apply(null, dat.subarray(i, i + 16384));\n return r;\n }\n else if (td) {\n return td.decode(dat);\n }\n else {\n var _a = dutf8(dat), s = _a.s, r = _a.r;\n if (r.length)\n err(8);\n return s;\n }\n}\n;\n// deflate bit flag\nvar dbf = function (l) { return l == 1 ? 3 : l < 6 ? 2 : l == 9 ? 1 : 0; };\n// skip local zip header\nvar slzh = function (d, b) { return b + 30 + b2(d, b + 26) + b2(d, b + 28); };\n// read zip header\nvar zh = function (d, b, z) {\n var fnl = b2(d, b + 28), efl = b2(d, b + 30), fn = strFromU8(d.subarray(b + 46, b + 46 + fnl), !(b2(d, b + 8) & 2048)), es = b + 46 + fnl;\n var _a = z64hs(d, es, efl, z, b4(d, b + 20), b4(d, b + 24), b4(d, b + 42)), sc = _a[0], su = _a[1], off = _a[2];\n return [b2(d, b + 10), sc, su, fn, es + efl + b2(d, b + 32), off];\n};\n// read zip64 header sizes\nvar z64hs = function (d, b, l, z, sc, su, off) {\n var nsc = sc == 4294967295, nsu = su == 4294967295, noff = off == 4294967295, e = b + l;\n var nf = nsc + nsu + noff;\n if (z && nf) {\n for (; b + 4 < e; b += 4 + b2(d, b + 2)) {\n if (b2(d, b) == 1) {\n return [\n nsc ? b8(d, b + 4 + 8 * nsu) : sc,\n nsu ? b8(d, b + 4) : su,\n noff ? b8(d, b + 4 + 8 * (nsu + nsc)) : off,\n 1\n ];\n }\n }\n // z == 2 for unknown whether or not zip64\n if (z < 2)\n err(13);\n }\n return [sc, su, off, 0];\n};\n// extra field length\nvar exfl = function (ex) {\n var le = 0;\n if (ex) {\n for (var k in ex) {\n var l = ex[k].length;\n if (l > 65535)\n err(9);\n le += l + 4;\n }\n }\n return le;\n};\n// write zip header\nvar wzh = function (d, b, f, fn, u, c, ce, co) {\n var fl = fn.length, ex = f.extra, col = co && co.length;\n var exl = exfl(ex);\n wbytes(d, b, ce != null ? 0x2014B50 : 0x4034B50), b += 4;\n if (ce != null)\n d[b++] = 20, d[b++] = f.os;\n d[b] = 20, b += 2; // spec compliance? what's that?\n d[b++] = (f.flag << 1) | (c < 0 && 8), d[b++] = u && 8;\n d[b++] = f.compression & 255, d[b++] = f.compression >> 8;\n var dt = new Date(f.mtime == null ? Date.now() : f.mtime), y = dt.getFullYear() - 1980;\n if (y < 0 || y > 119)\n err(10);\n wbytes(d, b, (y << 25) | ((dt.getMonth() + 1) << 21) | (dt.getDate() << 16) | (dt.getHours() << 11) | (dt.getMinutes() << 5) | (dt.getSeconds() >> 1)), b += 4;\n if (c != -1) {\n wbytes(d, b, f.crc);\n wbytes(d, b + 4, c < 0 ? -c - 2 : c);\n wbytes(d, b + 8, f.size);\n }\n wbytes(d, b + 12, fl);\n wbytes(d, b + 14, exl), b += 16;\n if (ce != null) {\n wbytes(d, b, col);\n wbytes(d, b + 6, f.attrs);\n wbytes(d, b + 10, ce), b += 14;\n }\n d.set(fn, b);\n b += fl;\n if (exl) {\n for (var k in ex) {\n var exf = ex[k], l = exf.length;\n wbytes(d, b, +k);\n wbytes(d, b + 2, l);\n d.set(exf, b + 4), b += 4 + l;\n }\n }\n if (col)\n d.set(co, b), b += col;\n return b;\n};\n// write zip footer (end of central directory)\nvar wzf = function (o, b, c, d, e) {\n wbytes(o, b, 0x6054B50); // skip disk\n wbytes(o, b + 8, c);\n wbytes(o, b + 10, c);\n wbytes(o, b + 12, d);\n wbytes(o, b + 16, e);\n};\n/**\n * A pass-through stream to keep data uncompressed in a ZIP archive.\n */\nvar ZipPassThrough = /*#__PURE__*/ (function () {\n /**\n * Creates a pass-through stream that can be added to ZIP archives\n * @param filename The filename to associate with this data stream\n */\n function ZipPassThrough(filename) {\n this.filename = filename;\n this.c = crc();\n this.size = 0;\n this.compression = 0;\n }\n /**\n * Processes a chunk and pushes to the output stream. You can override this\n * method in a subclass for custom behavior, but by default this passes\n * the data through. You must call this.ondata(err, chunk, final) at some\n * point in this method.\n * @param chunk The chunk to process\n * @param final Whether this is the last chunk\n */\n ZipPassThrough.prototype.process = function (chunk, final) {\n this.ondata(null, chunk, final);\n };\n /**\n * Pushes a chunk to be added. If you are subclassing this with a custom\n * compression algorithm, note that you must push data from the source\n * file only, pre-compression.\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n ZipPassThrough.prototype.push = function (chunk, final) {\n if (!this.ondata)\n err(5);\n this.c.p(chunk);\n this.size += chunk.length;\n if (final)\n this.crc = this.c.d();\n // we shouldn't really do this cast, but properly handling ArrayBufferLike\n // makes the API unergonomic with Buffer\n this.process(chunk, final || false);\n };\n return ZipPassThrough;\n}());\nexport { ZipPassThrough };\n// I don't extend because TypeScript extension adds 1kB of runtime bloat\n/**\n * Streaming DEFLATE compression for ZIP archives. Prefer using AsyncZipDeflate\n * for better performance\n */\nvar ZipDeflate = /*#__PURE__*/ (function () {\n /**\n * Creates a DEFLATE stream that can be added to ZIP archives\n * @param filename The filename to associate with this data stream\n * @param opts The compression options\n */\n function ZipDeflate(filename, opts) {\n var _this = this;\n if (!opts)\n opts = {};\n ZipPassThrough.call(this, filename);\n this.d = new Deflate(opts, function (dat, final) {\n _this.ondata(null, dat, final);\n });\n this.compression = 8;\n this.flag = dbf(opts.level);\n }\n ZipDeflate.prototype.process = function (chunk, final) {\n try {\n this.d.push(chunk, final);\n }\n catch (e) {\n this.ondata(e, null, final);\n }\n };\n /**\n * Pushes a chunk to be deflated\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n ZipDeflate.prototype.push = function (chunk, final) {\n ZipPassThrough.prototype.push.call(this, chunk, final);\n };\n return ZipDeflate;\n}());\nexport { ZipDeflate };\n/**\n * Asynchronous streaming DEFLATE compression for ZIP archives\n */\nvar AsyncZipDeflate = /*#__PURE__*/ (function () {\n /**\n * Creates an asynchronous DEFLATE stream that can be added to ZIP archives\n * @param filename The filename to associate with this data stream\n * @param opts The compression options\n */\n function AsyncZipDeflate(filename, opts) {\n var _this = this;\n if (!opts)\n opts = {};\n ZipPassThrough.call(this, filename);\n this.d = new AsyncDeflate(opts, function (err, dat, final) {\n _this.ondata(err, dat, final);\n });\n this.compression = 8;\n this.flag = dbf(opts.level);\n this.terminate = this.d.terminate;\n }\n AsyncZipDeflate.prototype.process = function (chunk, final) {\n this.d.push(chunk, final);\n };\n /**\n * Pushes a chunk to be deflated\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n AsyncZipDeflate.prototype.push = function (chunk, final) {\n ZipPassThrough.prototype.push.call(this, chunk, final);\n };\n return AsyncZipDeflate;\n}());\nexport { AsyncZipDeflate };\n// TODO: Better tree shaking\n/**\n * A zippable archive to which files can incrementally be added\n */\nvar Zip = /*#__PURE__*/ (function () {\n /**\n * Creates an empty ZIP archive to which files can be added\n * @param cb The callback to call whenever data for the generated ZIP archive\n * is available\n */\n function Zip(cb) {\n this.ondata = cb;\n this.u = [];\n this.d = 1;\n }\n /**\n * Adds a file to the ZIP archive\n * @param file The file stream to add\n */\n Zip.prototype.add = function (file) {\n var _this = this;\n if (!this.ondata)\n err(5);\n // finishing or finished\n if (this.d & 2)\n this.ondata(err(4 + (this.d & 1) * 8, 0, 1), null, false);\n else {\n var f = strToU8(file.filename), fl_1 = f.length;\n var com = file.comment, o = com && strToU8(com);\n var u = fl_1 != file.filename.length || (o && (com.length != o.length));\n var hl_1 = fl_1 + exfl(file.extra) + 30;\n if (fl_1 > 65535)\n this.ondata(err(11, 0, 1), null, false);\n var header = new u8(hl_1);\n wzh(header, 0, file, f, u, -1);\n var chks_1 = [header];\n var pAll_1 = function () {\n for (var _i = 0, chks_2 = chks_1; _i < chks_2.length; _i++) {\n var chk = chks_2[_i];\n _this.ondata(null, chk, false);\n }\n chks_1 = [];\n };\n var tr_1 = this.d;\n this.d = 0;\n var ind_1 = this.u.length;\n var uf_1 = mrg(file, {\n f: f,\n u: u,\n o: o,\n t: function () {\n if (file.terminate)\n file.terminate();\n },\n r: function () {\n pAll_1();\n if (tr_1) {\n var nxt = _this.u[ind_1 + 1];\n if (nxt)\n nxt.r();\n else\n _this.d = 1;\n }\n tr_1 = 1;\n }\n });\n var cl_1 = 0;\n file.ondata = function (err, dat, final) {\n if (err) {\n _this.ondata(err, dat, final);\n _this.terminate();\n }\n else {\n cl_1 += dat.length;\n chks_1.push(dat);\n if (final) {\n var dd = new u8(16);\n wbytes(dd, 0, 0x8074B50);\n wbytes(dd, 4, file.crc);\n wbytes(dd, 8, cl_1);\n wbytes(dd, 12, file.size);\n chks_1.push(dd);\n uf_1.c = cl_1, uf_1.b = hl_1 + cl_1 + 16, uf_1.crc = file.crc, uf_1.size = file.size;\n if (tr_1)\n uf_1.r();\n tr_1 = 1;\n }\n else if (tr_1)\n pAll_1();\n }\n };\n this.u.push(uf_1);\n }\n };\n /**\n * Ends the process of adding files and prepares to emit the final chunks.\n * This *must* be called after adding all desired files for the resulting\n * ZIP file to work properly.\n */\n Zip.prototype.end = function () {\n var _this = this;\n if (this.d & 2) {\n this.ondata(err(4 + (this.d & 1) * 8, 0, 1), null, true);\n return;\n }\n if (this.d)\n this.e();\n else\n this.u.push({\n r: function () {\n if (!(_this.d & 1))\n return;\n _this.u.splice(-1, 1);\n _this.e();\n },\n t: function () { }\n });\n this.d = 3;\n };\n Zip.prototype.e = function () {\n var bt = 0, l = 0, tl = 0;\n for (var _i = 0, _a = this.u; _i < _a.length; _i++) {\n var f = _a[_i];\n tl += 46 + f.f.length + exfl(f.extra) + (f.o ? f.o.length : 0);\n }\n var out = new u8(tl + 22);\n for (var _b = 0, _c = this.u; _b < _c.length; _b++) {\n var f = _c[_b];\n wzh(out, bt, f, f.f, f.u, -f.c - 2, l, f.o);\n bt += 46 + f.f.length + exfl(f.extra) + (f.o ? f.o.length : 0), l += f.b;\n }\n wzf(out, bt, this.u.length, tl, l);\n this.ondata(null, out, true);\n this.d = 2;\n };\n /**\n * A method to terminate any internal workers used by the stream. Subsequent\n * calls to add() will fail.\n */\n Zip.prototype.terminate = function () {\n for (var _i = 0, _a = this.u; _i < _a.length; _i++) {\n var f = _a[_i];\n f.t();\n }\n this.d = 2;\n };\n return Zip;\n}());\nexport { Zip };\nexport function zip(data, opts, cb) {\n if (!cb)\n cb = opts, opts = {};\n if (typeof cb != 'function')\n err(7);\n var r = {};\n fltn(data, '', r, opts);\n var k = Object.keys(r);\n var lft = k.length, o = 0, tot = 0;\n var slft = lft, files = new Array(lft);\n var term = [];\n var tAll = function () {\n for (var i = 0; i < term.length; ++i)\n term[i]();\n };\n var cbd = function (a, b) {\n mt(function () { cb(a, b); });\n };\n mt(function () { cbd = cb; });\n var cbf = function () {\n var out = new u8(tot + 22), oe = o, cdl = tot - o;\n tot = 0;\n for (var i = 0; i < slft; ++i) {\n var f = files[i];\n try {\n var l = f.c.length;\n wzh(out, tot, f, f.f, f.u, l);\n var badd = 30 + f.f.length + exfl(f.extra);\n var loc = tot + badd;\n out.set(f.c, loc);\n wzh(out, o, f, f.f, f.u, l, tot, f.m), o += 16 + badd + (f.m ? f.m.length : 0), tot = loc + l;\n }\n catch (e) {\n return cbd(e, null);\n }\n }\n wzf(out, o, files.length, cdl, oe);\n cbd(null, out);\n };\n if (!lft)\n cbf();\n var _loop_1 = function (i) {\n var fn = k[i];\n var _a = r[fn], file = _a[0], p = _a[1];\n var c = crc(), size = file.length;\n c.p(file);\n var f = strToU8(fn), s = f.length;\n var com = p.comment, m = com && strToU8(com), ms = m && m.length;\n var exl = exfl(p.extra);\n var compression = p.level == 0 ? 0 : 8;\n var cbl = function (e, d) {\n if (e) {\n tAll();\n cbd(e, null);\n }\n else {\n var l = d.length;\n files[i] = mrg(p, {\n size: size,\n crc: c.d(),\n c: d,\n f: f,\n m: m,\n u: s != fn.length || (m && (com.length != ms)),\n compression: compression\n });\n o += 30 + s + exl + l;\n tot += 76 + 2 * (s + exl) + (ms || 0) + l;\n if (!--lft)\n cbf();\n }\n };\n if (s > 65535)\n cbl(err(11, 0, 1), null);\n if (!compression)\n cbl(null, file);\n else if (size < 160000) {\n try {\n cbl(null, deflateSync(file, p));\n }\n catch (e) {\n cbl(e, null);\n }\n }\n else\n term.push(deflate(file, p, cbl));\n };\n // Cannot use lft because it can decrease\n for (var i = 0; i < slft; ++i) {\n _loop_1(i);\n }\n return tAll;\n}\n/**\n * Synchronously creates a ZIP file. Prefer using `zip` for better performance\n * with more than one file.\n * @param data The directory structure for the ZIP archive\n * @param opts The main options, merged with per-file options\n * @returns The generated ZIP archive\n */\nexport function zipSync(data, opts) {\n if (!opts)\n opts = {};\n var r = {};\n var files = [];\n fltn(data, '', r, opts);\n var o = 0;\n var tot = 0;\n for (var fn in r) {\n var _a = r[fn], file = _a[0], p = _a[1];\n var compression = p.level == 0 ? 0 : 8;\n var f = strToU8(fn), s = f.length;\n var com = p.comment, m = com && strToU8(com), ms = m && m.length;\n var exl = exfl(p.extra);\n if (s > 65535)\n err(11);\n var d = compression ? deflateSync(file, p) : file, l = d.length;\n var c = crc();\n c.p(file);\n files.push(mrg(p, {\n size: file.length,\n crc: c.d(),\n c: d,\n f: f,\n m: m,\n u: s != fn.length || (m && (com.length != ms)),\n o: o,\n compression: compression\n }));\n o += 30 + s + exl + l;\n tot += 76 + 2 * (s + exl) + (ms || 0) + l;\n }\n var out = new u8(tot + 22), oe = o, cdl = tot - o;\n for (var i = 0; i < files.length; ++i) {\n var f = files[i];\n wzh(out, f.o, f, f.f, f.u, f.c.length);\n var badd = 30 + f.f.length + exfl(f.extra);\n out.set(f.c, f.o + badd);\n wzh(out, o, f, f.f, f.u, f.c.length, f.o, f.m), o += 16 + badd + (f.m ? f.m.length : 0);\n }\n wzf(out, o, files.length, cdl, oe);\n return out;\n}\n/**\n * Streaming pass-through decompression for ZIP archives\n */\nvar UnzipPassThrough = /*#__PURE__*/ (function () {\n function UnzipPassThrough() {\n }\n UnzipPassThrough.prototype.push = function (chunk, final) {\n // same as ZipPassThrough: cast to retain Buffer ergonomics\n this.ondata(null, chunk, final);\n };\n UnzipPassThrough.compression = 0;\n return UnzipPassThrough;\n}());\nexport { UnzipPassThrough };\n/**\n * Streaming DEFLATE decompression for ZIP archives. Prefer AsyncZipInflate for\n * better performance.\n */\nvar UnzipInflate = /*#__PURE__*/ (function () {\n /**\n * Creates a DEFLATE decompression that can be used in ZIP archives\n */\n function UnzipInflate() {\n var _this = this;\n this.i = new Inflate(function (dat, final) {\n _this.ondata(null, dat, final);\n });\n }\n UnzipInflate.prototype.push = function (chunk, final) {\n try {\n this.i.push(chunk, final);\n }\n catch (e) {\n this.ondata(e, null, final);\n }\n };\n UnzipInflate.compression = 8;\n return UnzipInflate;\n}());\nexport { UnzipInflate };\n/**\n * Asynchronous streaming DEFLATE decompression for ZIP archives\n */\nvar AsyncUnzipInflate = /*#__PURE__*/ (function () {\n /**\n * Creates a DEFLATE decompression that can be used in ZIP archives\n */\n function AsyncUnzipInflate(_, sz) {\n var _this = this;\n if (sz < 320000) {\n this.i = new Inflate(function (dat, final) {\n _this.ondata(null, dat, final);\n });\n }\n else {\n this.i = new AsyncInflate(function (err, dat, final) {\n _this.ondata(err, dat, final);\n });\n this.terminate = this.i.terminate;\n }\n }\n AsyncUnzipInflate.prototype.push = function (chunk, final) {\n if (this.i.terminate)\n chunk = slc(chunk, 0);\n this.i.push(chunk, final);\n };\n AsyncUnzipInflate.compression = 8;\n return AsyncUnzipInflate;\n}());\nexport { AsyncUnzipInflate };\n/**\n * A ZIP archive decompression stream that emits files as they are discovered\n */\nvar Unzip = /*#__PURE__*/ (function () {\n /**\n * Creates a ZIP decompression stream\n * @param cb The callback to call whenever a file in the ZIP archive is found\n */\n function Unzip(cb) {\n this.onfile = cb;\n this.k = [];\n this.o = {\n 0: UnzipPassThrough\n };\n this.p = et;\n }\n /**\n * Pushes a chunk to be unzipped\n * @param chunk The chunk to push\n * @param final Whether this is the last chunk\n */\n Unzip.prototype.push = function (chunk, final) {\n var _this = this;\n if (!this.onfile)\n err(5);\n if (!this.p)\n err(4);\n if (this.c > 0) {\n var len = Math.min(this.c, chunk.length);\n var toAdd = chunk.subarray(0, len);\n this.c -= len;\n if (this.d)\n this.d.push(toAdd, !this.c);\n else\n this.k[0].push(toAdd);\n chunk = chunk.subarray(len);\n if (chunk.length)\n return this.push(chunk, final);\n }\n else {\n var f = 0, i = 0, is = void 0, buf = void 0;\n if (!this.p.length)\n buf = chunk;\n else if (!chunk.length)\n buf = this.p;\n else {\n buf = new u8(this.p.length + chunk.length);\n buf.set(this.p), buf.set(chunk, this.p.length);\n }\n var l = buf.length, oc = this.c, add = oc && this.d;\n var _loop_2 = function () {\n var sig = b4(buf, i);\n if (sig == 0x4034B50) {\n f = 1, is = i;\n this_1.d = null;\n this_1.c = 0;\n var bf = b2(buf, i + 6), cmp_1 = b2(buf, i + 8), u = bf & 2048, dd = bf & 8, fnl = b2(buf, i + 26), es = b2(buf, i + 28);\n if (l > i + 30 + fnl + es) {\n var chks_3 = [];\n this_1.k.unshift(chks_3);\n f = 2;\n var lsc = b4(buf, i + 18), lsu = b4(buf, i + 22);\n var fn_1 = strFromU8(buf.subarray(i + 30, i += 30 + fnl), !u);\n var _a = z64hs(buf, i, es, 2, lsc, lsu, 0), sc_1 = _a[0], su_1 = _a[1], z64 = _a[3];\n if (dd)\n sc_1 = -1 - z64;\n i += es;\n this_1.c = sc_1;\n var d_1;\n var file_1 = {\n name: fn_1,\n compression: cmp_1,\n start: function () {\n if (!file_1.ondata)\n err(5);\n if (!sc_1)\n file_1.ondata(null, et, true);\n else {\n var ctr = _this.o[cmp_1];\n if (!ctr)\n file_1.ondata(err(14, 'unknown compression type ' + cmp_1, 1), null, false);\n d_1 = sc_1 < 0 ? new ctr(fn_1) : new ctr(fn_1, sc_1, su_1);\n d_1.ondata = function (err, dat, final) { file_1.ondata(err, dat, final); };\n for (var _i = 0, chks_4 = chks_3; _i < chks_4.length; _i++) {\n var dat = chks_4[_i];\n d_1.push(dat, false);\n }\n if (_this.k[0] == chks_3 && _this.c)\n _this.d = d_1;\n else\n d_1.push(et, true);\n }\n },\n terminate: function () {\n if (d_1 && d_1.terminate)\n d_1.terminate();\n }\n };\n if (sc_1 >= 0)\n file_1.size = sc_1, file_1.originalSize = su_1;\n this_1.onfile(file_1);\n }\n return \"break\";\n }\n else if (oc) {\n if (sig == 0x8074B50) {\n is = i += 12 + (oc == -2 && 8), f = 3, this_1.c = 0;\n return \"break\";\n }\n else if (sig == 0x2014B50) {\n is = i -= 4, f = 3, this_1.c = 0;\n return \"break\";\n }\n }\n };\n var this_1 = this;\n for (; i < l - 4; ++i) {\n var state_1 = _loop_2();\n if (state_1 === \"break\")\n break;\n }\n this.p = et;\n if (oc < 0) {\n var dat = f ? buf.subarray(0, is - 12 - (oc == -2 && 8) - (b4(buf, is - 16) == 0x8074B50 && 4)) : buf.subarray(0, i);\n if (add)\n add.push(dat, !!f);\n else\n this.k[+(f == 2)].push(dat);\n }\n if (f & 2)\n return this.push(buf.subarray(i), final);\n this.p = buf.subarray(i);\n }\n if (final) {\n if (this.c)\n err(13);\n this.p = null;\n }\n };\n /**\n * Registers a decoder with the stream, allowing for files compressed with\n * the compression type provided to be expanded correctly\n * @param decoder The decoder constructor\n */\n Unzip.prototype.register = function (decoder) {\n this.o[decoder.compression] = decoder;\n };\n return Unzip;\n}());\nexport { Unzip };\nvar mt = typeof queueMicrotask == 'function' ? queueMicrotask : typeof setTimeout == 'function' ? setTimeout : function (fn) { fn(); };\nexport function unzip(data, opts, cb) {\n if (!cb)\n cb = opts, opts = {};\n if (typeof cb != 'function')\n err(7);\n var term = [];\n var tAll = function () {\n for (var i = 0; i < term.length; ++i)\n term[i]();\n };\n var files = {};\n var cbd = function (a, b) {\n mt(function () { cb(a, b); });\n };\n mt(function () { cbd = cb; });\n var e = data.length - 22;\n for (; b4(data, e) != 0x6054B50; --e) {\n if (!e || data.length - e > 65558) {\n cbd(err(13, 0, 1), null);\n return tAll;\n }\n }\n ;\n var lft = b2(data, e + 8);\n if (lft) {\n var c = lft;\n var o = b4(data, e + 16);\n var z = b4(data, e - 20) == 0x7064B50;\n if (z) {\n var ze = b4(data, e - 12);\n z = b4(data, ze) == 0x6064B50;\n if (z) {\n c = lft = b4(data, ze + 32);\n o = b4(data, ze + 48);\n }\n }\n var fltr = opts && opts.filter;\n var _loop_3 = function (i) {\n var _a = zh(data, o, z), c_1 = _a[0], sc = _a[1], su = _a[2], fn = _a[3], no = _a[4], off = _a[5], b = slzh(data, off);\n o = no;\n var cbl = function (e, d) {\n if (e) {\n tAll();\n cbd(e, null);\n }\n else {\n if (d)\n files[fn] = d;\n if (!--lft)\n cbd(null, files);\n }\n };\n if (!fltr || fltr({\n name: fn,\n size: sc,\n originalSize: su,\n compression: c_1\n })) {\n if (!c_1)\n cbl(null, slc(data, b, b + sc));\n else if (c_1 == 8) {\n var infl = data.subarray(b, b + sc);\n // Synchronously decompress under 512KB, or barely-compressed data\n if (su < 524288 || sc > 0.8 * su) {\n try {\n cbl(null, inflateSync(infl, { out: new u8(su) }));\n }\n catch (e) {\n cbl(e, null);\n }\n }\n else\n term.push(inflate(infl, { size: su }, cbl));\n }\n else\n cbl(err(14, 'unknown compression type ' + c_1, 1), null);\n }\n else\n cbl(null, null);\n };\n for (var i = 0; i < c; ++i) {\n _loop_3(i);\n }\n }\n else\n cbd(null, {});\n return tAll;\n}\n/**\n * Synchronously decompresses a ZIP archive. Prefer using `unzip` for better\n * performance with more than one file.\n * @param data The raw compressed ZIP file\n * @param opts The ZIP extraction options\n * @returns The decompressed files\n */\nexport function unzipSync(data, opts) {\n var files = {};\n var e = data.length - 22;\n for (; b4(data, e) != 0x6054B50; --e) {\n if (!e || data.length - e > 65558)\n err(13);\n }\n ;\n var c = b2(data, e + 8);\n if (!c)\n return {};\n var o = b4(data, e + 16);\n var z = b4(data, e - 20) == 0x7064B50;\n if (z) {\n var ze = b4(data, e - 12);\n z = b4(data, ze) == 0x6064B50;\n if (z) {\n c = b4(data, ze + 32);\n o = b4(data, ze + 48);\n }\n }\n var fltr = opts && opts.filter;\n for (var i = 0; i < c; ++i) {\n var _a = zh(data, o, z), c_2 = _a[0], sc = _a[1], su = _a[2], fn = _a[3], no = _a[4], off = _a[5], b = slzh(data, off);\n o = no;\n if (!fltr || fltr({\n name: fn,\n size: sc,\n originalSize: su,\n compression: c_2\n })) {\n if (!c_2)\n files[fn] = slc(data, b, b + sc);\n else if (c_2 == 8)\n files[fn] = inflateSync(data.subarray(b, b + sc), { out: new u8(su) });\n else\n err(14, 'unknown compression type ' + c_2);\n }\n }\n return files;\n}\n", "/** PDF \u5D4C\u5165\u56FE\u63D0\u53D6\uFF1AJPEG\uFF08DCTDecode \u76F4\u626B\uFF09+ PNG \u91CD\u5EFA\uFF08FlateDecode \u2192 \u89E3\u538B \u2192\n * \u53CD\u6EE4\u6CE2 \u2192 \u91CD\u7F16\u7801 PNG\uFF09\u3002\n *\n * PNG \u91CD\u5EFA\u5F62\u6001\uFF08v2 \u589E\u5F3A\uFF09\uFF1A\u5B57\u8282\u7EA7\u626B `obj \u2026 /Subtype /Image \u2026 stream\u2026endstream`\n * \u5757\uFF0C\u8BFB /Width /Height /ColorSpace /BitsPerComponent /Predictor /Colors\uFF0C\n * inflate \u540E\u6309 PNG \u884C\u6EE4\u6CE2\u5668\uFF080-4\uFF1Anone/sub/up/avg/paeth\uFF09\u53CD\u6EE4\u6CE2\uFF0CCMYK \u8F6C\n * RGB\uFF0C\u91CD\u7F16\u7801\u4E3A\u6807\u51C6 PNG\uFF08fflate deflateSync \u51FA IDAT\uFF0C\u81EA\u5B9E\u73B0 CRC32\uFF09\u3002\n */\n\nimport { inflateSync, deflateSync } from 'fflate'\n\nimport { KsrwError } from './error.ts'\n\n/** \u9875\u6570 + \u9010\u9875\u6587\u672C\uFF08Library \u9884\u89C8/\u76F8\u5173\u6027\u8BC1\u636E\u9762\uFF09\u3002 */\nexport async function pdfText(data: Uint8Array): Promise<{ pages: number; texts: string[] }> {\n try {\n const { extractText, getDocumentProxy } = await import('unpdf')\n const pdf = await getDocumentProxy(data)\n const { text, totalPages } = await extractText(pdf, { mergePages: false })\n return { pages: totalPages, texts: Array.isArray(text) ? text : [String(text)] }\n } catch (error) {\n throw new KsrwError('INVALID', `PDF \u89E3\u6790\u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}\uFF08\u786E\u8BA4\u6587\u4EF6\u672A\u52A0\u5BC6\u4E14\u4E3A\u6709\u6548 PDF\uFF09`)\n }\n}\n\n/** \u63D0\u53D6\u5D4C\u5165 JPEG\uFF08DCTDecode \u6D41\u6700\u5E38\u89C1\u5F62\u6001\uFF09\uFF1A\u5B57\u8282\u7EA7\u626B FFD8\u2026FFD9\u3002 */\nexport function extractJpegImages(data: Uint8Array, minBytes = 8_000): Uint8Array[] {\n const out: Uint8Array[] = []\n const seen = new Set<string>()\n let start = findStart(data, 0)\n while (start !== -1) {\n const end = findEnd(data, start + 2)\n if (end === -1) break\n const size = end + 2 - start\n if (size >= minBytes) {\n const slice = data.slice(start, end + 2)\n const key = `${size}:${slice[0]},${slice[4]}`\n if (!seen.has(key)) {\n seen.add(key)\n out.push(slice)\n }\n }\n start = findStart(data, end + 2)\n }\n return out\n}\n\nfunction findStart(data: Uint8Array, from: number): number {\n for (let i = from; i < data.length - 3; i++) {\n if (data[i] === 0xff && data[i + 1] === 0xd8 && data[i + 2] === 0xff) return i\n }\n return -1\n}\n\nfunction findEnd(data: Uint8Array, from: number): number {\n for (let i = from; i < data.length - 1; i++) {\n if (data[i] === 0xff && data[i + 1] === 0xd9) return i + 1\n }\n return -1\n}\n\n// \u2500\u2500 FlateDecode \u56FE\u50CF\u91CD\u5EFA\uFF08PNG \u5F62\u6001\uFF09\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface ExtractedImage {\n readonly bytes: Uint8Array\n readonly kind: 'png' | 'jpeg'\n readonly width: number\n readonly height: number\n}\n\n/** \u7EFC\u5408\u63D0\u53D6\uFF1AJPEG \u76F4\u626B + FlateDecode \u56FE\u50CF\u91CD\u5EFA\uFF0C\u6309\u51FA\u73B0\u987A\u5E8F\u53BB\u91CD\uFF08\u5C3A\u5BF8+\u5934\u5B57\u8282\uFF09\u3002 */\nexport function extractImages(data: Uint8Array, minBytes = 8_000): ExtractedImage[] {\n const out: ExtractedImage[] = []\n const seen = new Set<string>()\n const push = (img: ExtractedImage): void => {\n const key = `${img.kind}:${img.bytes.length}:${img.width}x${img.height}`\n if (seen.has(key)) return\n seen.add(key)\n out.push(img)\n }\n for (const jpeg of extractJpegImages(data, minBytes)) {\n const dim = jpegDim(jpeg)\n push({ bytes: jpeg, kind: 'jpeg', width: dim[0], height: dim[1] })\n }\n for (const png of extractFlateImages(data, minBytes)) push(png)\n return out\n}\n\nfunction jpegDim(jpeg: Uint8Array): [number, number] {\n for (let i = 2; i < jpeg.length - 9; ) {\n if (jpeg[i] !== 0xff) { i++; continue }\n const marker = jpeg[i + 1]\n if (marker === 0xc0 || marker === 0xc2) {\n const view = new DataView(jpeg.buffer, jpeg.byteOffset, jpeg.byteLength)\n return [view.getUint16(i + 7), view.getUint16(i + 5)]\n }\n const len = (jpeg[i + 2] ?? 0) * 256 + (jpeg[i + 3] ?? 0)\n i += 2 + Math.max(2, len)\n }\n return [0, 0]\n}\n\n/** \u626B PDF \u5BF9\u8C61\u6D41\uFF1A`N M obj << \u2026 /Subtype /Image \u2026 >> stream\\n<data>endstream`\u3002 */\nexport function extractFlateImages(data: Uint8Array, minBytes = 8_000): ExtractedImage[] {\n const latin1 = Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString('latin1')\n const out: ExtractedImage[] = []\n const objRe = /<<([\\s\\S]{0,1200}?)>>\\s*stream\\r?\\n/g\n for (const match of latin1.matchAll(objRe)) {\n const dict = match[1] ?? ''\n if (!/\\/Subtype\\s*\\/Image/.test(dict) || !/\\/Filter\\s*\\/FlateDecode/.test(dict)) continue\n const streamStart = (match.index ?? 0) + match[0].length\n const endIdx = latin1.indexOf('endstream', streamStart)\n if (endIdx === -1) continue\n const raw = data.slice(streamStart, endIdx)\n const img = rebuildPng(dict, trimStreamTail(raw))\n if (img !== undefined && img.bytes.length >= minBytes) out.push(img)\n }\n return out\n}\n\n/** endstream \u524D\u5E38\u6709 EOL\uFF1B\u6D41\u5C3E\u82E5\u88AB\u6CE8\u91CA/\u7A7A\u767D\u6C61\u67D3\u6309 zlib \u5BB9\u9519\u622A\u65AD\u3002 */\nfunction trimStreamTail(raw: Uint8Array): Uint8Array {\n let end = raw.length\n while (end > 0 && (raw[end - 1] === 0x0a || raw[end - 1] === 0x0d || raw[end - 1] === 0x20)) end--\n return raw.subarray(0, end)\n}\n\nfunction dictNum(dict: string, key: string): number | undefined {\n const m = dict.match(new RegExp(`/${key}\\\\s+(\\\\d+)`))\n return m !== null ? Number(m[1]) : undefined\n}\n\nfunction rebuildPng(dict: string, raw: Uint8Array): ExtractedImage | undefined {\n const width = dictNum(dict, 'Width')\n const height = dictNum(dict, 'Height')\n if (width === undefined || height === undefined || width <= 0 || height <= 0 || width > 16_384 || height > 16_384) return undefined\n const bpc = dictNum(dict, 'BitsPerComponent') ?? 8\n if (bpc !== 8) return undefined // 1/2/4/16 bpp \u4E0D\u8D70\u672C\u901A\u9053\n\n let inflated: Uint8Array\n try {\n inflated = inflateSync(raw)\n } catch {\n return undefined // \u6D41\u635F\u574F/\u5BF9\u8C61\u6D41\u5D4C\u5957\uFF1A\u8DF3\u8FC7\n }\n\n // \u901A\u9053\u6570\u5224\u5B9A\uFF1A\u663E\u5F0F /Colors > \u989C\u8272\u7A7A\u95F4\u540D > \u6309\u6570\u636E\u957F\u5EA6\u53CD\u63A8\n const colorsDecl = dictNum(dict, 'Colors')\n const predictor = dictNum(dict, 'Predictor') ?? 1\n const csMatch2 = dict.match(/\\/ColorSpace\\s*\\/(\\w+)/)\n const csName = csMatch2?.[1] ?? ''\n let colors = colorsDecl ?? (csName.includes('RGB') || csName === 'CalRGB' ? 3 : csName.includes('CMYK') ? 4 : csName.includes('Gray') || csName.includes('CalGray') || csName === 'G' ? 1 : 0)\n if (colors === 0) {\n const perRowOverhead = predictor >= 10 ? 1 : 0\n const pixelBytes = Math.max(1, width * height)\n const guess = Math.floor((inflated.length - perRowOverhead * height) / pixelBytes)\n colors = [1, 3, 4].includes(guess) ? guess : 3\n }\n\n let rows: Uint8Array\n try {\n rows = predictor >= 10 ? unfilterPngRows(inflated, width, height, colors) : inflated.subarray(0, width * height * colors)\n } catch {\n return undefined\n }\n const expected = width * height * colors\n if (rows.length < expected) return undefined\n\n // CMYK \u2192 RGB\uFF1B\u5176\u4F59\u539F\u6837\n const rgb = colors === 4 ? cmykToRgb(rows, expected) : rows.subarray(0, expected)\n const gray = colors === 1\n return { bytes: encodePng(rgb, width, height, gray), kind: 'png', width, height }\n}\n\n/** PNG \u884C\u6EE4\u6CE2\u53CD\u6F14\uFF08filter byte \u6BCF\u884C 1 \u4E2A\uFF0Cbpp = colors \u5B57\u8282\uFF09\u3002 */\nfunction unfilterPngRows(data: Uint8Array, width: number, height: number, colors: number): Uint8Array {\n const stride = width * colors\n const out = new Uint8Array(height * stride)\n let src = 0\n for (let y = 0; y < height; y++) {\n if (src >= data.length) throw new Error('data short')\n const filter = data[src++] ?? 0\n const rowStart = y * stride\n for (let x = 0; x < stride; x++) {\n const rawByte = data[src++] ?? 0\n const left = x >= colors ? (out[rowStart + x - colors] ?? 0) : 0\n const up = y > 0 ? (out[rowStart - stride + x] ?? 0) : 0\n const upLeft = y > 0 && x >= colors ? (out[rowStart - stride + x - colors] ?? 0) : 0\n let val: number\n switch (filter) {\n case 0: val = rawByte; break\n case 1: val = rawByte + left; break\n case 2: val = rawByte + up; break\n case 3: val = rawByte + ((left + up) >> 1); break\n case 4: {\n const p = left + up - upLeft\n const pa = Math.abs(p - left)\n const pb = Math.abs(p - up)\n const pc = Math.abs(p - upLeft)\n val = rawByte + (pa <= pb && pa <= pc ? left : pb <= pc ? up : upLeft)\n break\n }\n default: val = rawByte\n }\n out[rowStart + x] = val & 0xff\n }\n }\n return out\n}\n\nfunction cmykToRgb(rows: Uint8Array, expected: number): Uint8Array {\n const out = new Uint8Array(expected)\n for (let i = 0, j = 0; j < expected; i += 4, j += 3) {\n const c = rows[i] ?? 0\n const m = rows[i + 1] ?? 0\n const y = rows[i + 2] ?? 0\n const k = rows[i + 3] ?? 0\n out[j] = 255 - Math.min(255, c + k)\n out[j + 1] = 255 - Math.min(255, m + k)\n out[j + 2] = 255 - Math.min(255, y + k)\n }\n return out\n}\n\n// \u2500\u2500 PNG \u7F16\u7801\uFF08IHDR/IDAT/IEND + CRC32\uFF09\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst CRC_TABLE = (() => {\n const table = new Uint32Array(256)\n for (let n = 0; n < 256; n++) {\n let c = n\n for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1\n table[n] = c >>> 0\n }\n return table\n})()\n\nfunction crc32(buf: Uint8Array): number {\n let c = 0xffffffff\n for (let i = 0; i < buf.length; i++) c = CRC_TABLE[(c ^ (buf[i] ?? 0)) & 0xff] ?? (c >>> 8)\n return (c ^ 0xffffffff) >>> 0\n}\n\nfunction pngChunk(type: string, body: Uint8Array): Uint8Array {\n const out = new Uint8Array(12 + body.length)\n const view = new DataView(out.buffer)\n view.setUint32(0, body.length)\n for (let i = 0; i < 4; i++) out[4 + i] = type.charCodeAt(i)\n out.set(body, 8)\n const typeAndBody = out.subarray(4, 8 + body.length)\n view.setUint32(8 + body.length, crc32(typeAndBody))\n return out\n}\n\n/** PNG \u5934\u55C5\u63A2\uFF08\u7B7E\u540D + IHDR \u5C3A\u5BF8\uFF1B\u975E PNG \u8FD4\u56DE null\uFF09\u3002 */\nexport function sniffPng(data: Uint8Array): { width: number; height: number } | null {\n if (data.length < 24) return null\n const sig = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]\n for (let i = 0; i < 8; i++) {\n if ((data[i] ?? 0) !== sig[i]) return null\n }\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength)\n return { width: view.getUint32(16), height: view.getUint32(20) }\n}\n\n/** RGB/Gray \u50CF\u7D20 \u2192 \u6807\u51C6 PNG\uFF08\u6BCF\u884C\u524D\u7F6E filter 0\uFF09\u3002 */\nexport function encodePng(pixels: Uint8Array, width: number, height: number, gray: boolean): Uint8Array {\n const stride = width * (gray ? 1 : 3)\n const raw = new Uint8Array((stride + 1) * height)\n for (let y = 0; y < height; y++) {\n raw[y * (stride + 1)] = 0\n raw.set(pixels.subarray(y * stride, (y + 1) * stride), y * (stride + 1) + 1)\n }\n const ihdr = new Uint8Array(13)\n const view = new DataView(ihdr.buffer)\n view.setUint32(0, width)\n view.setUint32(4, height)\n ihdr[8] = 8\n ihdr[9] = gray ? 0 : 2\n const signature = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])\n const parts = [signature, pngChunk('IHDR', ihdr), pngChunk('IDAT', deflateSync(raw, { level: 6 })), pngChunk('IEND', new Uint8Array(0))]\n const total = parts.reduce((acc, p) => acc + p.length, 0)\n const out = new Uint8Array(total)\n let off = 0\n for (const p of parts) {\n out.set(p, off)\n off += p.length\n }\n return out\n}\n", "/** \u8BBA\u6587\u7CBE\u8BFB\uFF1A\u8BA2\u9605\u6293\u53D6 \u2192 \u4E0B\u8F7D PDF \u2192 \u539F\u6587/\u56FE\u8868\u62BD\u53D6 \u2192 \u52A9\u624B\u7CBE\u8BFB \u2192 \u6458\u8981\u4E0E\u5B9A\u4F4D\u951A\u70B9\u3002\n *\n * \u76EE\u5F55\u7EA6\u5B9A\uFF08\u9879\u76EE\u5185\uFF09\uFF1A\n * literature/reading/prefs.json \u7CBE\u8BFB\u5EFA\u8BAE\uFF08\u8BA2\u9605\u5173\u952E\u8BCD/\u6DF1\u5EA6/\u8BED\u8A00/\u56FE\u8868\u7B56\u7565/\u5B9A\u65F6\uFF09\n * literature/reading/index.json \u5F85\u8BFB/\u5DF2\u8BFB\u6E05\u5355\n * literature/reading/pdfs/<slug>.pdf \u539F\u59CB PDF\n * literature/reading/<slug>/original.md \u62BD\u597D\u7684\u539F\u6587\uFF08\u9010\u9875 + \u6BB5\u843D\u7F16\u53F7 [\u9875.\u6BB5]\uFF09\n * literature/reading/<slug>/figures/ \u62BD\u51FA\u7684\u56FE\u8868\uFF08fig-01.png \u2026\uFF09\n * literature/reading/<slug>/digest.md \u7CBE\u8BFB\u8F93\u51FA\uFF08\u52A9\u624B\u5199\uFF0C\u9010\u6BB5\u53CC\u8BED + \u89E3\u8BFB + \u56FE\u8868\u89E3\u8BFB + \u5B9A\u4F4D\uFF09\n */\nimport { spawnSync } from 'node:child_process'\nimport { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\nimport { createArxivClient, type ArxivPaper, type FetchFn } from './arxiv.ts'\nimport { KsrwError } from './error.ts'\nimport { extractImages, pdfText } from './pdf.ts'\n\nexport const READING_DIR = 'literature/reading'\n\nexport type ReadingGoal = 'method' | 'repro' | 'compare' | 'cite' | 'survey'\nexport type ReadingDepth = 'abstract' | 'section' | 'paragraph'\nexport type ReadingLanguage = 'zh-en' | 'zh' | 'en-zh'\n\nexport interface ReadingSchedule {\n readonly enabled: boolean\n /** \u6BCF N \u5C0F\u65F6\u81EA\u52A8\u6293\u53D6\u4E00\u6B21\uFF08\u6700\u5C0F 1\uFF09\u3002 */\n readonly everyHours: number\n /** \u6BCF\u6B21\u6700\u591A\u4E0B\u8F7D\u51E0\u7BC7\u3002 */\n readonly maxPerRun: number\n}\n\nexport interface ReadingPrefs {\n readonly topics: string[]\n readonly goal: ReadingGoal\n readonly depth: ReadingDepth\n readonly language: ReadingLanguage\n /** \u7CBE\u8BFB\u91CC\u5FC5\u987B\u5E26\u56FE\u8868\uFF08\u539F\u6587\u6709\u56FE\u5C31\u4E0D\u80FD\u7F3A\uFF09\u3002 */\n readonly includeFigures: boolean\n /** \u56FE\u8868\u4E5F\u8981\u7ED9\u89E3\u8BFB\u3002 */\n readonly figureNotes: boolean\n readonly focus: string[]\n readonly notes: string\n readonly schedule: ReadingSchedule\n readonly lastRunAt: number\n readonly updatedAt: number\n}\n\nexport const DEFAULT_READING_PREFS: ReadingPrefs = {\n topics: [],\n goal: 'method',\n depth: 'paragraph',\n language: 'zh-en',\n includeFigures: true,\n figureNotes: true,\n focus: ['\u65B9\u6CD5', '\u5B9E\u9A8C\u8BBE\u7F6E', '\u7ED3\u8BBA\u4E0E\u5C40\u9650'],\n notes: '',\n schedule: { enabled: false, everyHours: 24, maxPerRun: 3 },\n lastRunAt: 0,\n updatedAt: 0,\n}\n\nexport type ReadingStatus = 'downloaded' | 'extracted' | 'reading' | 'ready' | 'failed'\n\nexport interface ReadingFigure {\n readonly index: number\n readonly file: string\n readonly kind: 'png' | 'jpeg'\n readonly width: number\n readonly height: number\n /** \u6240\u5728\u9875\uFF08\u80FD\u5B9A\u4F4D\u65F6\uFF09\u3002 */\n readonly page?: number\n /** \u4ECE\u8BE5\u9875\u6587\u672C\u91CC\u627E\u5230\u7684\u56FE\u6CE8\uFF08\u5F85\u52A9\u624B\u6838\u5BF9\uFF09\u3002 */\n readonly caption?: string\n}\n\nexport interface ReadingEntry {\n readonly id: string\n readonly source: 'arxiv'\n readonly arxivId?: string\n readonly title: string\n readonly authors: string[]\n readonly abstract: string\n readonly published: string\n readonly categories: string[]\n readonly absUrl: string\n readonly pdfUrl?: string\n /** \u76F8\u5BF9\u9879\u76EE\u8DEF\u5F84\u3002 */\n readonly pdf: string\n readonly dir: string\n readonly status: ReadingStatus\n readonly pages?: number\n readonly figures?: ReadingFigure[]\n readonly digest?: string\n readonly error?: string\n readonly addedAt: number\n readonly updatedAt: number\n}\n\nexport interface ReadingContext {\n readonly entry: ReadingEntry\n readonly prefs: ReadingPrefs\n readonly original: string\n readonly captionCandidates: string[]\n}\n\nfunction root(projectPath: string): string {\n return join(projectPath, READING_DIR)\n}\n\nfunction readJson<T>(file: string): T | undefined {\n try { return JSON.parse(readFileSync(file, 'utf8')) as T } catch { return undefined }\n}\n\nfunction writeJson(file: string, value: unknown): void {\n mkdirSync(join(file, '..'), { recursive: true })\n writeFileSync(file, JSON.stringify(value, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n}\n\n/** \u7CBE\u8BFB\u5EFA\u8BAE\uFF08\u7F3A\u7701\u503C \u2192 \u5DF2\u4FDD\u5B58\u503C\uFF09\u3002 */\nexport function readReadingPrefs(projectPath: string): ReadingPrefs {\n const saved = readJson<Partial<ReadingPrefs>>(join(root(projectPath), 'prefs.json')) ?? {}\n return {\n ...DEFAULT_READING_PREFS,\n ...saved,\n topics: Array.isArray(saved.topics) ? saved.topics : DEFAULT_READING_PREFS.topics,\n focus: Array.isArray(saved.focus) ? saved.focus : DEFAULT_READING_PREFS.focus,\n schedule: { ...DEFAULT_READING_PREFS.schedule, ...(saved.schedule ?? {}) },\n }\n}\n\nexport function writeReadingPrefs(projectPath: string, patch: Partial<ReadingPrefs>): ReadingPrefs {\n const next: ReadingPrefs = {\n ...readReadingPrefs(projectPath),\n ...patch,\n schedule: { ...readReadingPrefs(projectPath).schedule, ...(patch.schedule ?? {}) },\n updatedAt: Date.now(),\n }\n mkdirSync(root(projectPath), { recursive: true })\n writeJson(join(root(projectPath), 'prefs.json'), next)\n return next\n}\n\n/** \u5F85\u8BFB\u6E05\u5355\uFF08\u6309\u52A0\u5165\u65F6\u95F4\u5012\u5E8F\uFF09\u3002 */\nexport function readReadingIndex(projectPath: string): ReadingEntry[] {\n const saved = readJson<{ entries?: ReadingEntry[] }>(join(root(projectPath), 'index.json'))\n return Array.isArray(saved?.entries) ? saved.entries : []\n}\n\nfunction writeReadingIndex(projectPath: string, entries: ReadingEntry[]): void {\n mkdirSync(root(projectPath), { recursive: true })\n writeJson(join(root(projectPath), 'index.json'), { entries })\n}\n\nfunction upsertEntry(projectPath: string, entry: ReadingEntry): ReadingEntry {\n const entries = readReadingIndex(projectPath)\n const next = [entry, ...entries.filter(item => item.id !== entry.id)]\n writeReadingIndex(projectPath, next)\n return entry\n}\n\nfunction slugOf(paper: ArxivPaper): string {\n const id = paper.arxivId.replace(/\\//g, '-')\n return 'arxiv-' + id\n}\n\n/** \u6309\u8BA2\u9605\u5173\u952E\u8BCD\u6293\u6700\u65B0\uFF08arXiv submittedDate \u5012\u5E8F\uFF09\u3002 */\nexport async function searchLatestPapers(\n prefs: ReadingPrefs,\n options: { days?: number; max?: number; fetchFn?: FetchFn } = {},\n): Promise<ArxivPaper[]> {\n const topics = prefs.topics.map(topic => topic.trim()).filter(topic => topic !== '')\n if (topics.length === 0) throw new KsrwError('INVALID', '\u5148\u5728\u7CBE\u8BFB\u5EFA\u8BAE\u91CC\u586B\u8BA2\u9605\u5173\u952E\u8BCD\uFF08topics\uFF09')\n const days = options.days ?? 30\n const max = options.max ?? 10\n const query = topics.map(topic => 'all:\"' + topic + '\"').join(' OR ')\n const client = createArxivClient(options.fetchFn)\n const papers = await client.search({ query, maxResults: Math.min(50, max * 3), sortBy: 'submittedDate', sortOrder: 'descending' })\n const cutoff = Date.now() - days * 24 * 3600 * 1000\n return papers\n .filter(paper => {\n const stamp = Date.parse(paper.published)\n return Number.isFinite(stamp) ? stamp >= cutoff : true\n })\n .slice(0, max)\n}\n\nfunction captionCandidates(text: string): string[] {\n const pattern = /(?:Figure|Fig\\.?|TABLE|Table|\u56FE|\u8868)\\s*\\d+[:.]?\\s*[^\\n]{0,180}/g\n const out: string[] = []\n const seen = new Set<string>()\n for (const match of text.matchAll(pattern)) {\n const line = (match[0] ?? '').trim()\n if (line === '' || seen.has(line)) continue\n seen.add(line)\n out.push(line)\n if (out.length >= 40) break\n }\n return out\n}\n\nfunction hasTool(tool: string): boolean {\n const probe = process.platform === 'win32' ? 'where' : 'which'\n const found = spawnSync(probe, [tool], { encoding: 'utf8' })\n return found.status === 0\n}\n\n/** \u7528 poppler \u7684 pdfimages -list \u62FF\u5230\u9875\u7801\uFF0C\u518D\u6309\u9875\u5BFC\u51FA PNG\uFF08\u77E2\u91CF\u56FE\u4E5F\u80FD\u9760\u9875\u9762\u6E32\u67D3\u7559\u6863\uFF09\u3002 */\nfunction extractFiguresWithPoppler(pdfAbs: string, dirAbs: string, pagesTexts: string[]): ReadingFigure[] {\n if (!hasTool('pdfimages')) return []\n const list = spawnSync('pdfimages', ['-list', pdfAbs], { encoding: 'utf8', maxBuffer: 8 * 1024 * 1024 })\n if (list.status !== 0) return []\n const rows = String(list.stdout ?? '').split('\\n')\n .map(line => line.trim().split(/\\s+/))\n .filter(cells => cells.length >= 6 && /^\\d+$/.test(cells[0] ?? '') && /^\\d+$/.test(cells[1] ?? ''))\n if (rows.length === 0) return []\n const prefix = join(dirAbs, 'figures', 'pdfimg')\n const dump = spawnSync('pdfimages', ['-png', pdfAbs, prefix], { encoding: 'utf8', maxBuffer: 8 * 1024 * 1024 })\n if (dump.status !== 0) return []\n const out: ReadingFigure[] = []\n rows.forEach((cells, index) => {\n const page = Number(cells[0])\n const width = Number(cells[3])\n const height = Number(cells[4])\n const base = String(index).padStart(3, '0')\n let file: string | undefined\n for (const candidate of ['pdfimg-' + base + '.png', 'pdfimg-' + base + '.jpg']) {\n const absolute = join(dirAbs, 'figures', candidate)\n if (existsSync(absolute)) { file = candidate; break }\n }\n if (file === undefined) return\n const target = 'fig-' + String(out.length + 1).padStart(2, '0') + '.png'\n try { renameSync(join(dirAbs, 'figures', file), join(dirAbs, 'figures', target)) } catch { return }\n const pageText = pagesTexts[page - 1] ?? ''\n const caption = /(?:Figure|Fig\\.?|Table|TABLE)\\s*\\d+[:.]?[^\\n]{0,180}/.exec(pageText)?.[0]?.trim()\n out.push({ index: out.length + 1, file: 'figures/' + target, kind: 'png', width, height, page, ...(caption === undefined ? {} : { caption }) })\n })\n return out\n}\n\n/** \u515C\u5E95\uFF1A\u5B57\u8282\u7EA7\u63D0\u53D6\uFF08\u6CA1\u6709 poppler \u6216 poppler \u5931\u8D25\u65F6\uFF09\u3002 */\nfunction extractFiguresFromBytes(bytes: Uint8Array, dirAbs: string): ReadingFigure[] {\n let images: ReturnType<typeof extractImages> = []\n try { images = extractImages(bytes, 12_000) } catch { images = [] }\n return images.map((image, index) => {\n const file = 'fig-' + String(index + 1).padStart(2, '0') + (image.kind === 'jpeg' ? '.jpg' : '.png')\n writeFileSync(join(dirAbs, 'figures', file), image.bytes, { mode: 0o600 })\n return { index: index + 1, file: 'figures/' + file, kind: image.kind, width: image.width, height: image.height }\n })\n}\n\n/** \u628A PDF \u62BD\u6210 original.md\uFF08\u9010\u9875 + \u6BB5\u843D\u7F16\u53F7\uFF09\u4E0E figures/\u3002 */\nexport async function extractReadingPdf(projectPath: string, entry: ReadingEntry, bytes: Uint8Array): Promise<ReadingEntry> {\n const dirAbs = join(projectPath, entry.dir)\n mkdirSync(join(dirAbs, 'figures'), { recursive: true })\n {\n const parsed = await pdfText(bytes)\n const lines: string[] = ['# ' + entry.title, '', '- arXiv: ' + (entry.arxivId ?? ''), '- \u9875\u6570: ' + String(parsed.pages), '']\n parsed.texts.forEach((text, pageIndex) => {\n lines.push('<!-- page:' + String(pageIndex + 1) + ' -->')\n const paragraphs = text.split(/\\n\\s*\\n/).map(item => item.replace(/\\s+$/g, '')).filter(item => item.trim() !== '')\n paragraphs.forEach((paragraph, paraIndex) => {\n lines.push('[' + String(pageIndex + 1) + '.' + String(paraIndex + 1) + '] ' + paragraph.replace(/\\n/g, ' ').trim())\n })\n lines.push('')\n })\n const original = lines.join('\\n')\n writeFileSync(join(dirAbs, 'original.md'), original, { encoding: 'utf8', mode: 0o600 })\n // \u4F18\u5148 poppler\uFF08\u80FD\u5E26\u9875\u7801\u4E0E\u56FE\u6CE8\uFF09\uFF0C\u5931\u8D25\u518D\u9000\u5B57\u8282\u7EA7\u63D0\u53D6\uFF1B\u56FE\u8868\u62BD\u53D6\u5931\u8D25\u4E0D\u5F71\u54CD\u539F\u6587\u3002\n let figures = extractFiguresWithPoppler(join(projectPath, entry.pdf), dirAbs, parsed.texts)\n if (figures.length === 0) figures = extractFiguresFromBytes(bytes, dirAbs)\n const next: ReadingEntry = {\n ...entry,\n status: 'extracted',\n pages: parsed.pages,\n figures,\n updatedAt: Date.now(),\n }\n return upsertEntry(projectPath, next)\n }\n}\n\n/** \u4E0B\u8F7D\u5E76\u62BD\u53D6\u4E00\u6279 arXiv \u8BBA\u6587\uFF08\u5DF2\u5B58\u5728\u7684\u8DF3\u8FC7\uFF09\u3002 */\nexport async function addReadingPapers(\n projectPath: string,\n papers: ArxivPaper[],\n options: { fetchFn?: FetchFn } = {},\n): Promise<ReadingEntry[]> {\n const existing = new Set(readReadingIndex(projectPath).map(entry => entry.id))\n const client = createArxivClient(options.fetchFn)\n const added: ReadingEntry[] = []\n for (const paper of papers) {\n const id = slugOf(paper)\n if (existing.has(id)) continue\n const pdfRel = READING_DIR + '/pdfs/' + id + '.pdf'\n const pdfAbs = join(projectPath, pdfRel)\n mkdirSync(join(projectPath, READING_DIR, 'pdfs'), { recursive: true })\n const base: ReadingEntry = {\n id,\n source: 'arxiv',\n ...(paper.arxivId === '' ? {} : { arxivId: paper.arxivId }),\n title: paper.title,\n authors: paper.authors,\n abstract: paper.abstract,\n published: paper.published,\n categories: paper.categories,\n absUrl: paper.absUrl,\n ...(paper.pdfUrl === undefined ? {} : { pdfUrl: paper.pdfUrl }),\n pdf: pdfRel,\n dir: READING_DIR + '/' + id,\n status: 'downloaded',\n addedAt: Date.now(),\n updatedAt: Date.now(),\n }\n try {\n await client.downloadPdf(paper, pdfAbs)\n const bytes = new Uint8Array(readFileSync(pdfAbs))\n const extracted = await extractReadingPdf(projectPath, base, bytes)\n added.push(extracted)\n } catch (error) {\n added.push(upsertEntry(projectPath, {\n ...base,\n status: 'failed',\n error: error instanceof Error ? error.message : String(error),\n updatedAt: Date.now(),\n }))\n }\n }\n return added\n}\n\n/** \u8BFB\u53D6\u5355\u7BC7\u7684\u539F\u6587\u4E0E\u56FE\u8868\u6E05\u5355\uFF08\u7ED9\u52A9\u624B\u505A\u7CBE\u8BFB\u7684\u4E0A\u4E0B\u6587\uFF09\u3002 */\nexport function readReadingEntry(projectPath: string, id: string): ReadingContext {\n const entry = readReadingIndex(projectPath).find(item => item.id === id)\n if (entry === undefined) throw new KsrwError('NOT_FOUND', '\u7CBE\u8BFB\u6761\u76EE\u4E0D\u5B58\u5728\uFF1A' + id)\n const dirAbs = join(projectPath, entry.dir)\n let original = ''\n try { original = readFileSync(join(dirAbs, 'original.md'), 'utf8') } catch { original = '' }\n if (original === '') throw new KsrwError('NOT_FOUND', '\u8FD8\u6CA1\u6709\u62BD\u53D6\u539F\u6587\uFF1A\u5148\u4E0B\u8F7D\u5E76\u62BD\u53D6\uFF08' + entry.id + '\uFF09')\n return { entry, prefs: readReadingPrefs(projectPath), original, captionCandidates: captionCandidates(original) }\n}\n\n/** \u52A9\u624B\u5199\u5165\u7CBE\u8BFB\u7A3F\uFF08digest.md\uFF09\uFF0C\u72B6\u6001\u8F6C ready\u3002 */\nexport function writeReadingDigest(projectPath: string, id: string, markdown: string): ReadingEntry {\n const entries = readReadingIndex(projectPath)\n const entry = entries.find(item => item.id === id)\n if (entry === undefined) throw new KsrwError('NOT_FOUND', '\u7CBE\u8BFB\u6761\u76EE\u4E0D\u5B58\u5728\uFF1A' + id)\n if (markdown.trim() === '') throw new KsrwError('INVALID', '\u7CBE\u8BFB\u7A3F\u4E0D\u80FD\u4E3A\u7A7A')\n const dirAbs = join(projectPath, entry.dir)\n mkdirSync(dirAbs, { recursive: true })\n const digestRel = entry.dir + '/digest.md'\n writeFileSync(join(projectPath, digestRel), markdown, { encoding: 'utf8', mode: 0o600 })\n return upsertEntry(projectPath, { ...entry, digest: digestRel, status: 'ready', updatedAt: Date.now() })\n}\n\n/** \u5B9A\u65F6\u6293\u53D6\uFF1A\u8FD4\u56DE\u672C\u6B21\u65B0\u589E\u6761\u76EE\uFF08\u8C03\u5EA6\u5668\u4E0E UI\u300C\u7ACB\u5373\u6293\u53D6\u300D\u5171\u7528\uFF09\u3002 */\nexport async function runReadingFetch(\n projectPath: string,\n options: { fetchFn?: FetchFn; days?: number; max?: number } = {},\n): Promise<{ papers: number; added: ReadingEntry[]; prefs: ReadingPrefs }> {\n const prefs = readReadingPrefs(projectPath)\n const max = options.max ?? prefs.schedule.maxPerRun\n const papers = await searchLatestPapers(prefs, { days: options.days ?? 30, max, ...(options.fetchFn === undefined ? {} : { fetchFn: options.fetchFn }) })\n const added = await addReadingPapers(projectPath, papers, options.fetchFn === undefined ? {} : { fetchFn: options.fetchFn })\n const next = writeReadingPrefs(projectPath, { lastRunAt: Date.now() })\n return { papers: papers.length, added, prefs: next }\n}\n\n/** \u626B\u63CF\u6240\u6709\u9879\u76EE\u7684\u5B9A\u65F6\u7CBE\u8BFB\u914D\u7F6E\uFF08\u8C03\u5EA6\u5668\u7528\uFF09\u3002 */\nexport function listScheduledReadingProjects(projectsHome: string): string[] {\n const out: string[] = []\n let dirs: string[] = []\n try { dirs = readdirSync(projectsHome) } catch { return out }\n for (const name of dirs) {\n const projectPath = join(projectsHome, name)\n const prefsFile = join(projectPath, READING_DIR, 'prefs.json')\n if (!existsSync(prefsFile)) continue\n const prefs = readJson<Partial<ReadingPrefs>>(prefsFile)\n if (prefs?.schedule?.enabled !== true) continue\n if (prefs.topics === undefined || prefs.topics.length === 0) continue\n out.push(projectPath)\n }\n return out\n}\n\n/** \u662F\u5426\u5230\u4E86\u4E0B\u6B21\u6293\u53D6\u65F6\u95F4\u3002 */\nexport function readingFetchDue(prefs: ReadingPrefs, now = Date.now()): boolean {\n if (!prefs.schedule.enabled) return false\n const everyMs = Math.max(1, prefs.schedule.everyHours) * 3600 * 1000\n return now - (prefs.lastRunAt || 0) >= everyMs\n}\n\n/** \u9879\u76EE\u5185\u9605\u8BFB\u76EE\u5F55\u662F\u5426\u5B58\u5728\uFF08UI \u5C55\u793A\u7528\uFF09\u3002 */\nexport function readingDirSize(projectPath: string): number {\n try { return statSync(root(projectPath)).size } catch { return 0 }\n}\n", "/** \u4F1A\u8BAE/\u671F\u520A\u6392\u7248\u6A21\u677F\u6CE8\u518C\u8868 + \u6392\u7248\u8981\u6C42\u63CF\u8FF0\uFF08UI \u4E0E agent \u5DE5\u5177\u5171\u7528\u540C\u4E00\u4EFD\u89C4\u683C\uFF09\u3002\n *\n * \u5355\u4E00\u4E8B\u5B9E\u6765\u6E90\uFF1Aplugins/ksrw-core/public/templates/index.json\uFF08\u7531 scripts/build-paper-templates.py\n * \u4ECE scripts/paper-templates.json \u751F\u6210\uFF09\uFF0Ccore \u542F\u52A8\u65F6\u64AD\u79CD\u5230 ~/.ksrw/assets/templates/index.json\u3002\n * \u8FD9\u91CC\u7684 VENUE_TEMPLATES \u53EA\u662F\u515C\u5E95\uFF08index.json \u7F3A\u5931\u65F6 UI \u4ECD\u80FD\u5217\u51FA\u5E38\u7528\u6A21\u677F\uFF09\u3002\n */\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { dirname, join } from 'node:path'\n\nimport { resolveKsrwHome } from './home.ts'\n\nexport interface VenuePageSpec {\n readonly size?: string\n readonly margins?: { readonly top?: number; readonly bottom?: number; readonly left?: number; readonly right?: number }\n readonly columns?: number\n readonly lineNumbers?: boolean\n}\n\nexport interface VenueTextSpec {\n readonly latin?: string\n readonly cjk?: string\n readonly size?: number\n readonly line?: number\n readonly indentChars?: number\n readonly justify?: boolean\n}\n\nexport interface VenueHeadingSpec {\n readonly size?: number\n readonly align?: string\n readonly bold?: boolean\n readonly italic?: boolean\n}\n\nexport interface VenueSpec {\n readonly id: string\n readonly label: string\n readonly page?: VenuePageSpec\n readonly body?: VenueTextSpec\n readonly title?: VenueHeadingSpec\n readonly headings?: readonly VenueHeadingSpec[]\n readonly caption?: VenueHeadingSpec\n readonly code?: { readonly size?: number; readonly latin?: string }\n}\n\nexport interface VenueTemplate {\n readonly id: string\n readonly label: string\n /** \u53CC\u680F\u6A21\u677F\uFF1Adocx/Word \u662F\u53CC\u680F\uFF0CPDF \u9884\u89C8\u7ECF LibreOffice \u4ECD\u662F\u5355\u680F\u3002 */\n readonly columns: number\n readonly note?: string\n}\n\nexport const DEFAULT_VENUE_ID = 'academic-cn'\n\nconst FALLBACK_TEMPLATES: readonly VenueTemplate[] = [\n { id: 'academic-cn', label: '\u4E2D\u6587\u901A\u7528\uFF08A4 \u00B7 \u5B8B\u4F53\u5C0F\u56DB \u00B7 1.5 \u500D\u884C\u8DDD\uFF09', columns: 1 },\n { id: 'ieee', label: 'IEEE \u4F1A\u8BAE\uFF08Letter \u00B7 \u53CC\u680F \u00B7 Times 10pt\uFF09', columns: 2, note: 'Word \u4E3A\u53CC\u680F\uFF1B\u5E94\u7528\u5185 PDF \u9884\u89C8\u6682\u4E3A\u5355\u680F' },\n { id: 'acm', label: 'ACM sigconf\uFF08Letter \u00B7 \u53CC\u680F \u00B7 Times 9pt\uFF09', columns: 2, note: 'Word \u4E3A\u53CC\u680F\uFF1B\u5E94\u7528\u5185 PDF \u9884\u89C8\u6682\u4E3A\u5355\u680F' },\n { id: 'elsevier', label: 'Elsevier \u5BA1\u7A3F\u7A3F\uFF08A4 \u00B7 \u5355\u680F \u00B7 11pt \u00B7 \u53CC\u500D\u884C\u8DDD \u00B7 \u884C\u53F7\uFF09', columns: 1 },\n { id: 'lncs', label: 'Springer LNCS\uFF08A4 \u00B7 \u5355\u680F \u00B7 Times 10pt\uFF09', columns: 1 },\n { id: 'neurips', label: 'NeurIPS \u6295\u7A3F\u7A3F\uFF08Letter \u00B7 \u5355\u680F \u00B7 10pt \u00B7 \u884C\u53F7\uFF09', columns: 1 },\n]\n\nexport interface PaperStyle { readonly venue: string }\n\nfunction ksrwHome(): string {\n return resolveKsrwHome()\n}\n\n/** \u6A21\u677F\u76EE\u5F55\uFF08core \u542F\u52A8\u65F6\u64AD\u79CD\u5230\u8FD9\u91CC\uFF09\u3002 */\nexport function venueTemplateDir(): string {\n return join(ksrwHome(), 'assets', 'templates')\n}\n\n/** \u8BFB\u53D6\u6A21\u677F\u89C4\u683C\u6E05\u5355\uFF08index.json\uFF1B\u7F3A\u5931\u65F6\u8FD4\u56DE\u7A7A\uFF09\u3002 */\nexport function readVenueSpecs(): VenueSpec[] {\n const file = join(venueTemplateDir(), 'index.json')\n try {\n const parsed = JSON.parse(readFileSync(file, 'utf8')) as { templates?: VenueSpec[] }\n return Array.isArray(parsed.templates) ? parsed.templates : []\n } catch { return [] }\n}\n\n/** UI \u7528\u7684\u6A21\u677F\u5217\u8868\uFF08\u4F18\u5148 index.json \u7684\u89C4\u683C\uFF09\u3002 */\nexport function listVenueTemplates(): VenueTemplate[] {\n const specs = readVenueSpecs()\n if (specs.length === 0) return [...FALLBACK_TEMPLATES]\n return specs.map(spec => ({\n id: spec.id,\n label: spec.label,\n columns: spec.page?.columns ?? 1,\n ...(spec.page?.columns === 2 ? { note: 'Word \u4E3A\u53CC\u680F\uFF1B\u5E94\u7528\u5185 PDF \u9884\u89C8\u6682\u4E3A\u5355\u680F' } : {}),\n }))\n}\n\n/** \u517C\u5BB9\u65E7\u8C03\u7528\uFF1A\u9759\u6001\u5217\u8868\u5728 index.json \u4E0D\u53EF\u7528\u65F6\u4ECD\u662F\u53EF\u7528\u515C\u5E95\u3002 */\nexport const VENUE_TEMPLATES: readonly VenueTemplate[] = FALLBACK_TEMPLATES\n\nfunction styleFile(projectPath: string): string {\n return join(projectPath, 'paper', 'style.json')\n}\n\n/** \u8BFB\u53D6\u9879\u76EE\u7684\u6392\u7248\u6A21\u677F\u9009\u62E9\uFF08\u7F3A\u7701 academic-cn\uFF09\u3002 */\nexport function readPaperStyle(projectPath: string): PaperStyle {\n const known = new Set(listVenueTemplates().map(item => item.id))\n try {\n const parsed = JSON.parse(readFileSync(styleFile(projectPath), 'utf8')) as Partial<PaperStyle>\n if (typeof parsed.venue === 'string' && known.has(parsed.venue)) return { venue: parsed.venue }\n } catch { /* \u672A\u9009\u62E9\u8FC7 */ }\n return { venue: DEFAULT_VENUE_ID }\n}\n\nexport function writePaperStyle(projectPath: string, venue: string): PaperStyle {\n if (!listVenueTemplates().some(item => item.id === venue)) throw new Error('\u672A\u77E5\u6392\u7248\u6A21\u677F\uFF1A' + venue)\n const file = styleFile(projectPath)\n mkdirSync(dirname(file), { recursive: true })\n writeFileSync(file, JSON.stringify({ venue }, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n return { venue }\n}\n\nexport function venueTemplate(id: string): VenueTemplate | undefined {\n return listVenueTemplates().find(item => item.id === id)\n}\n\n/** \u9879\u76EE\u5E94\u4F7F\u7528\u7684 reference.docx\uFF1A\u5B58\u5728\u624D\u8FD4\u56DE\uFF08\u5426\u5219\u9000\u56DE pandoc \u9ED8\u8BA4\u6A21\u677F\uFF09\u3002 */\nexport function paperTemplateFor(projectPath: string, env: NodeJS.ProcessEnv = process.env): string | null {\n const explicit = env['KSRW_PAPER_TEMPLATE']\n if (explicit !== undefined && explicit !== '' && existsSync(explicit)) return explicit\n const venue = readPaperStyle(projectPath).venue\n const candidate = join(venueTemplateDir(), venue + '.docx')\n if (existsSync(candidate)) return candidate\n const legacy = join(ksrwHome(), 'assets', 'reference-academic.docx')\n return existsSync(legacy) ? legacy : null\n}\n\n/** \u53D6\u67D0\u4E2A\u6A21\u677F\u7684\u5B8C\u6574\u89C4\u683C\uFF08\u7F3A\u7701\u7528\u5F53\u524D\u9879\u76EE\u7684\u6A21\u677F\uFF09\u3002 */\nexport function venueSpec(id: string): VenueSpec | undefined {\n return readVenueSpecs().find(item => item.id === id)\n}\n\nfunction lineSpacingText(line: number | undefined): string {\n if (line === undefined) return '\u672A\u6307\u5B9A'\n if (Math.abs(line - 1) < 0.01) return '\u5355\u500D\u884C\u8DDD'\n if (Math.abs(line - 1.5) < 0.01) return '1.5 \u500D\u884C\u8DDD'\n if (Math.abs(line - 2) < 0.01) return '\u53CC\u500D\u884C\u8DDD'\n return String(line) + ' \u500D\u884C\u8DDD'\n}\n\n/** \u628A\u6A21\u677F\u89C4\u683C\u7FFB\u8BD1\u6210\u53EF\u6267\u884C\u7684\u6392\u7248\u8981\u6C42\uFF08agent \u4E0E\u7528\u6237\u90FD\u80FD\u6309\u5B83\u68C0\u67E5\uFF09\u3002 */\nexport function describeVenue(spec: VenueSpec): string[] {\n const page = spec.page ?? {}\n const body = spec.body ?? {}\n const margins = page.margins ?? {}\n const lines: string[] = []\n const paperSize = (page.size ?? 'a4').toLowerCase() === 'letter' ? 'Letter\uFF088.5\u00D711 in\uFF09' : 'A4\uFF0821\u00D729.7 cm\uFF09'\n lines.push('\u9875\u9762\uFF1A' + paperSize + '\uFF0C\u9875\u8FB9\u8DDD \u4E0A\u4E0B ' + String(margins.top ?? 2.5) + 'cm / \u5DE6\u53F3 ' + String(margins.left ?? 2.5) + 'cm' + ((page.columns ?? 1) === 2 ? '\uFF0C\u53CC\u680F' : '\uFF0C\u5355\u680F'))\n if (page.lineNumbers === true) lines.push('\u884C\u53F7\uFF1A\u5F00\u542F\uFF08\u4FBF\u4E8E\u8BC4\u5BA1\u5F15\u7528\u884C\u53F7\uFF09')\n lines.push('\u6B63\u6587\uFF1A' + (body.latin ?? 'Times New Roman') + ' / ' + (body.cjk ?? '\u5B8B\u4F53') + '\uFF0C' + String(body.size ?? 12) + 'pt\uFF0C' + lineSpacingText(body.line) + ((body.indentChars ?? 0) > 0 ? '\uFF0C\u9996\u884C\u7F29\u8FDB 2 \u5B57\u7B26' : '\uFF0C\u4E0D\u7F29\u8FDB') + (body.justify === false ? '\uFF0C\u5DE6\u5BF9\u9F50' : '\uFF0C\u4E24\u7AEF\u5BF9\u9F50'))\n const title = spec.title\n if (title !== undefined) lines.push('\u6807\u9898\uFF1A' + String(title.size ?? 18) + 'pt\uFF0C\u52A0\u7C97\uFF0C' + (title.align === 'left' ? '\u5DE6\u5BF9\u9F50' : '\u5C45\u4E2D'))\n const headings = spec.headings ?? []\n if (headings.length > 0) {\n const sizes = headings.map((item, index) => 'H' + String(index + 1) + ' ' + String(item.size ?? '?') + 'pt' + (item.italic === true ? '\uFF08\u659C\u4F53\uFF09' : '') + (item.bold === false ? '' : ' \u52A0\u7C97') + (item.align === 'center' ? ' \u5C45\u4E2D' : '')).join('\uFF1B')\n lines.push('\u5206\u7EA7\u6807\u9898\uFF1A' + sizes)\n }\n const caption = spec.caption\n if (caption !== undefined) lines.push('\u56FE\u8868\u6CE8\uFF1A' + String(caption.size ?? 9) + 'pt\uFF0C' + (caption.align === 'left' ? '\u5DE6\u5BF9\u9F50\uFF0C\u7F6E\u4E8E\u56FE\u4E0B/\u8868\u4E0A' : '\u5C45\u4E2D'))\n const code = spec.code\n if (code !== undefined) lines.push('\u4EE3\u7801\uFF1A' + (code.latin ?? 'Courier New') + ' ' + String(code.size ?? 8.5) + 'pt\uFF0C\u4E0D\u8BED\u6CD5\u9AD8\u4EAE')\n lines.push('\u9875\u811A\uFF1A\u5C45\u4E2D\u9875\u7801')\n return lines\n}\n", "/** \u8BBA\u6587\u56FE\u5E93\u4E0E\u56FE\u8868\u751F\u6210\uFF08kernel \u5171\u4EAB\u5B9E\u73B0\uFF09\uFF1AUI\uFF08ksrw-core\uFF09\u4E0E agent \u5DE5\u5177\uFF08ksrw-tools\uFF09\u5171\u7528\u3002\n *\n * \u76EE\u5F55\u7EA6\u5B9A\uFF1Apaper/figures/<file> + paper/figures/index.json {figures:[{file,title,caption,source,kind,addedAt,svg?}]}\n * \u7ED8\u56FE\uFF1A\u672C\u5730 bundled Python + matplotlib\uFF1Bpython \u89E3\u6790\u987A\u5E8F\u4E0E lab \u4E00\u81F4\uFF08\u73AF\u5883\u53D8\u91CF \u2192 \u8FD0\u884C\u65F6\u76EE\u5F55 \u2192 PATH\uFF09\u3002\n */\nimport { spawnSync } from 'node:child_process'\nimport { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs'\nimport { delimiter, dirname, extname, join, resolve, sep } from 'node:path'\nimport { homedir, tmpdir } from 'node:os'\nimport AdmZip from 'adm-zip'\nimport { resolveKsrwHome } from './home.ts'\nimport { sniffPng } from './pdf.ts'\nimport { createRequire } from 'node:module'\n\nexport type FigureKind = 'result' | 'metrics' | 'architecture' | 'chart' | 'diagram'\n\nexport interface PaperFigure {\n readonly file: string\n readonly title: string\n readonly caption: string\n readonly source: string\n readonly kind: FigureKind\n readonly addedAt: number\n /** \u53EF\u7F16\u8F91\u7684\u77E2\u91CF\u6E90\u6587\u4EF6\uFF08\u6709\u7684\u8BDD\uFF09\u3002 */\n readonly svg?: string\n}\n\nexport interface FigurePoint { readonly x: number; readonly y: number }\nexport interface FigureSeries { readonly name: string; readonly points: readonly FigurePoint[] }\nexport interface FigureCategory { readonly label: string; readonly value: number }\n\nexport interface ChartSpec {\n readonly kind: 'line' | 'bar' | 'scatter'\n readonly title?: string\n readonly xlabel?: string\n readonly ylabel?: string\n readonly series?: readonly FigureSeries[]\n readonly categories?: readonly FigureCategory[]\n readonly width?: number\n readonly height?: number\n}\n\nexport interface FlowNode { readonly id: string; readonly label: string; readonly active?: boolean }\nexport interface FlowEdge { readonly from: string; readonly to: string; readonly label?: string }\nexport interface FlowSpec {\n readonly title?: string\n readonly subtitle?: string\n readonly nodes: readonly FlowNode[]\n readonly edges?: readonly FlowEdge[]\n readonly direction?: 'lr' | 'tb'\n readonly width?: number\n readonly height?: number\n /** \u6A2A\u5411\u5E03\u5C40\u6BCF\u6392\u6700\u591A\u51E0\u4E2A\u8282\u70B9\uFF08\u7F3A\u7701\u81EA\u9002\u5E94\uFF0C\u6700\u591A 5\uFF1B\u8D85\u51FA\u81EA\u52A8\u6362\u884C\u6210\u591A\u6392\uFF09\u3002 */\n readonly maxColumns?: number\n /** \u8282\u70B9\u6807\u7B7E\u5355\u884C\u5B57\u7B26\u9884\u7B97\uFF08\u82F1\u6587\u6309 1\u3001\u4E2D\u6587\u6309 2 \u8BA1\uFF1B\u7F3A\u7701 14\uFF0C\u8D85\u51FA\u6298\u884C\uFF09\u3002 */\n readonly labelWidth?: number\n}\n\nconst FIGURE_DIR = join('paper', 'figures')\nconst FIGURE_EXTS = new Set(['.png', '.jpg', '.jpeg', '.svg'])\n\nexport function figuresDir(projectPath: string): string {\n return join(projectPath, FIGURE_DIR)\n}\n\nfunction indexFile(projectPath: string): string {\n return join(figuresDir(projectPath), 'index.json')\n}\n\nfunction readIndex(projectPath: string): PaperFigure[] | undefined {\n try {\n const parsed = JSON.parse(readFileSync(indexFile(projectPath), 'utf8')) as { figures?: PaperFigure[] }\n if (Array.isArray(parsed.figures)) return parsed.figures\n } catch { /* \u65E0\u7D22\u5F15 */ }\n return undefined\n}\n\n/** \u56FE\u5E93\u6E05\u5355\uFF08\u8BFB index.json\uFF0C\u7F3A\u5931\u65F6\u6309\u6587\u4EF6\u540D\u515C\u5E95\uFF09\u3002 */\nexport function listFigures(projectPath: string): PaperFigure[] {\n const index = readIndex(projectPath)\n if (index !== undefined) return index\n const dir = figuresDir(projectPath)\n if (!existsSync(dir)) return []\n try {\n return readdirSync(dir, { withFileTypes: true })\n .filter(entry => entry.isFile() && FIGURE_EXTS.has(extname(entry.name).toLowerCase()))\n .map(entry => ({ file: entry.name, title: entry.name, caption: entry.name, source: 'manual', kind: 'result' as FigureKind, addedAt: 0 }))\n } catch { return [] }\n}\n\nexport function saveFigures(projectPath: string, figures: PaperFigure[]): void {\n mkdirSync(figuresDir(projectPath), { recursive: true })\n writeFileSync(indexFile(projectPath), JSON.stringify({ figures }, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n}\n\nexport function upsertFigure(projectPath: string, record: PaperFigure): PaperFigure {\n const figures = (readIndex(projectPath) ?? [])\n .filter(item => item.file !== record.file && item.file !== record.svg)\n figures.push(record)\n saveFigures(projectPath, figures)\n return record\n}\n\nfunction sanitize(name: string): string {\n return name.replace(/[^\\w.-]+/g, '_')\n}\n\n/** \u2500\u2500 Python \u89E3\u91CA\u5668\u89E3\u6790\uFF08\u4E0E core/lab \u7684\u5019\u9009\u987A\u5E8F\u4E00\u81F4\uFF0C\u5C3D\u91CF\u4E0D\u91CD\u590D\u5B9E\u73B0\uFF09 \u2500\u2500 */\nconst PY_NAMES = ['python3', 'python3.12', 'python3.13', 'python']\n\nfunction pythonCandidates(): string[] {\n const list: string[] = []\n const explicit = process.env['KSRW_LAB_PYTHON']?.trim()\n if (explicit !== undefined && explicit !== '') list.push(explicit)\n const runtime = process.env['KSRW_LAB_RUNTIME']?.trim()\n if (runtime !== undefined && runtime !== '') {\n for (const name of PY_NAMES) list.push(join(runtime, 'python', 'bin', name))\n }\n const home = resolveKsrwHome()\n for (const name of PY_NAMES) list.push(join(home, 'lab', 'runtime', 'python', 'bin', name))\n // \u5F00\u53D1\u6001\uFF1A\u4ECE cwd \u5411\u4E0A\u627E staging/lab-runtime/runtime\n let cursor = resolve(process.cwd())\n for (let depth = 0; depth < 5; depth += 1) {\n for (const name of PY_NAMES) list.push(join(cursor, 'staging', 'lab-runtime', 'runtime', 'python', 'bin', name))\n const parent = dirname(cursor)\n if (parent === cursor) break\n cursor = parent\n }\n list.push('python3')\n return list\n}\n\nfunction sitePackagesFor(python: string): string | undefined {\n // <runtime>/python/bin/pythonX \u2192 <runtime>/site-packages\uFF08uv \u6784\u5EFA\u7684\u8FD0\u884C\u65F6\u9760 PYTHONPATH \u6302\u8F7D\uFF09\n const candidate = python.replace(/[\\/]python[\\/]bin[\\/][^\\/]+$/, '/site-packages')\n return candidate !== python && existsSync(candidate) ? candidate : undefined\n}\n\nfunction withSiteEnv(python: string): NodeJS.ProcessEnv {\n const site = sitePackagesFor(python)\n if (site === undefined) return { ...process.env }\n return { ...process.env, PYTHONPATH: [site, process.env['PYTHONPATH']].filter(Boolean).join(delimiter) }\n}\n\nfunction pythonCanRun(python: string): boolean {\n if (python.includes('/') && !existsSync(python)) return false\n const probe = spawnSync(python, ['-c', 'import matplotlib'], { encoding: 'utf8', env: withSiteEnv(python) })\n return probe.status === 0\n}\n\nlet cachedPython: string | undefined\n\n/** \u627E\u5230\u80FD import matplotlib \u7684 python\uFF1B\u627E\u4E0D\u5230\u8FD4\u56DE null\u3002\n * P2-9\uFF1A\u53EA\u7F13\u5B58\u6210\u529F\u7ED3\u679C\u2014\u2014\u65E9\u671F\u63A2\u6D4B\u5931\u8D25\uFF08lab runtime \u672A\u5B89\u88C5\uFF09\u4E0D\u80FD\u6C38\u4E45\u9489\u6B7B\u5230\u8FDB\u7A0B\u7ED3\u675F\u3002 */\nexport function figurePython(): string | null {\n if (cachedPython !== undefined) return cachedPython\n const found = pythonCandidates().find(pythonCanRun)\n if (found !== undefined) cachedPython = found\n return found ?? null\n}\n\nfunction siteOf(python: string): string | undefined {\n const runtime = process.env['KSRW_LAB_RUNTIME']?.trim()\n if (runtime !== undefined && runtime !== '') {\n const candidate = join(runtime, 'site-packages')\n if (existsSync(candidate)) return candidate\n }\n return undefined\n}\n\nfunction runPython(script: string, args: string[]): void {\n const python = figurePython()\n if (python === null) throw new Error('\u6CA1\u6709\u53EF\u7528\u7684 Python + matplotlib\uFF08\u5148\u6784\u5EFA lab runtime \u6216\u8BBE\u7F6E KSRW_LAB_PYTHON\uFF09')\n const file = join(tmpdir(), 'ksrw-figure-' + String(Date.now()) + '-' + String(Math.random()).slice(2, 6) + '.py')\n writeFileSync(file, script, { encoding: 'utf8', mode: 0o600 })\n const result = spawnSync(python, [file, ...args], { encoding: 'utf8', timeout: 120_000, env: withSiteEnv(python) })\n if (result.status !== 0) {\n throw new Error('\u7ED8\u56FE\u5931\u8D25\uFF1A' + ((result.stderr ?? '') + (result.stdout ?? '')).trim().slice(-400))\n }\n}\n\nconst CHART_SCRIPT = `\nimport json, sys\nfrom pathlib import Path\nimport matplotlib\nmatplotlib.use('Agg')\nimport matplotlib.pyplot as plt\nspec = json.loads(Path(sys.argv[1]).read_text(encoding='utf-8'))\nout = Path(sys.argv[2])\nout.parent.mkdir(parents=True, exist_ok=True)\nfig, ax = plt.subplots(figsize=(spec.get('width', 6), spec.get('height', 3.4)), dpi=200)\nkind = spec.get('kind', 'line')\nif spec.get('categories'):\n labels = [c['label'] for c in spec['categories']]\n values = [float(c['value']) for c in spec['categories']]\n ax.bar(labels, values, color='#b85c2e')\n ax.tick_params(axis='x', rotation=20)\nelse:\n for series in spec.get('series', []):\n xs = [float(p['x']) for p in series['points']]\n ys = [float(p['y']) for p in series['points']]\n if kind == 'bar':\n ax.bar(xs, ys, label=series['name'])\n elif kind == 'scatter':\n ax.scatter(xs, ys, label=series['name'], s=18)\n else:\n ax.plot(xs, ys, label=series['name'])\n if spec.get('series'):\n ax.legend()\nif spec.get('title'):\n ax.set_title(spec['title'])\nif spec.get('xlabel'):\n ax.set_xlabel(spec['xlabel'])\nif spec.get('ylabel'):\n ax.set_ylabel(spec['ylabel'])\nfig.tight_layout()\nfig.savefig(out)\nprint('saved', out)\n`\n\nconst FLOW_SCRIPT = `\nimport json, math, sys\nfrom pathlib import Path\nimport matplotlib\nmatplotlib.use('Agg')\nimport matplotlib.pyplot as plt\nfrom matplotlib import font_manager\nfrom matplotlib.patches import FancyBboxPatch, FancyArrowPatch\n\nspec = json.loads(Path(sys.argv[1]).read_text(encoding='utf-8'))\nout = Path(sys.argv[2])\nNL = chr(10)\nnodes = spec['nodes']\nedges = spec.get('edges', [])\ndirection = spec.get('direction', 'lr')\nactive = {node['id'] for node in nodes if node.get('active')}\n\n# CJK \u5B57\u4F53\u5C3D\u529B\u800C\u4E3A\uFF1A\u547D\u4E2D\u5219\u4E2D\u6587\u6807\u7B7E\u53EF\u8BFB\uFF0C\u672A\u547D\u4E2D\u65F6\u82F1\u6587\u6807\u7B7E\u4ECD\u6B63\u5E38\uFF08\u4E2D\u6587\u663E\u793A\u65B9\u5757\uFF09\nCJK_FONTS = ['PingFang SC', 'Hiragino Sans GB', 'Songti SC', 'Noto Sans CJK SC', 'Source Han Sans SC', 'Microsoft YaHei', 'SimHei', 'WenQuanYi Micro Hei']\ntry:\n available = {face.name for face in font_manager.fontManager.ttflist}\n picked = next((name for name in CJK_FONTS if name in available), None)\n if picked is not None:\n plt.rcParams['font.sans-serif'] = [picked, 'DejaVu Sans']\n plt.rcParams['axes.unicode_minus'] = False\nexcept Exception:\n pass\n\ndef display_width(text):\n return sum(2 if ord(ch) > 0x2e80 else 1 for ch in text)\n\ndef wrap_label(text, limit):\n lines, line, width = [], '', 0\n for ch in text:\n w = 2 if ord(ch) > 0x2e80 else 1\n if width + w > limit and line != '':\n lines.append(line)\n line, width = ch, w\n else:\n line += ch\n width += w\n if line != '':\n lines.append(line)\n if len(lines) > 3:\n lines = lines[:3]\n lines[-1] = lines[-1][:-1] + '\u2026'\n return lines or ['']\n\n# \u6BCF\u884C\u5B57\u7B26\u9884\u7B97\uFF08\u82F1\u6587\u6309 1\u3001\u4E2D\u6587\u6309 2\uFF1B\u7F3A\u7701 14\uFF09\uFF0C\u8282\u70B9\u6587\u672C\u5148\u6298\u884C\u518D\u5B9A\u76D2\u5BBD/\u76D2\u9AD8\nbudget = int(spec.get('labelWidth') or 14)\nlabels = {}\nfor node in nodes:\n labels[node['id']] = wrap_label(str(node.get('label', node['id'])), budget)\nmax_lines = max([len(value) for value in labels.values()] or [1])\n\nBOX_W = 0.245 * budget + 0.70\nBOX_H = 0.55 + 0.42 * max_lines\nGAP_X = 0.90\nGAP_Y = 1.00\nPAD = 0.45\n\ncount = len(nodes)\nif direction == 'lr':\n cols = int(spec.get('maxColumns') or 0)\n if cols <= 0:\n # \u5355\u6392\u8D85\u8FC7 5 \u4E2A\u5C31\u6362\u884C\uFF1A\u65E7\u5B9E\u73B0\u5355\u6392\u5E73\u94FA\uFF0C\u8282\u70B9\u4E00\u591A\u76D2\u5B50\u5FC5\u7136\u91CD\u53E0\n cols = max(1, min(count, 5))\n rows = int(math.ceil(count / float(cols)))\nelse:\n cols = 1\n rows = count\n\nrow_counts = [min(cols, count - r * cols) for r in range(rows)]\nspan_x = cols * BOX_W + (cols - 1) * GAP_X + 2 * PAD\nspan_y = rows * BOX_H + (rows - 1) * GAP_Y + 2 * PAD\nbottom = -0.55 if spec.get('subtitle') else 0.0\n\nwidth_in = float(spec.get('width') or min(9.5, max(4.2, 0.30 * span_x)))\nheight_in = float(spec.get('height') or min(8.0, max(1.6, 0.36 * span_y)))\nfig, ax = plt.subplots(figsize=(width_in, height_in), dpi=200)\nax.axis('off')\n\npositions = {}\nfor index, node in enumerate(nodes):\n r = index // cols\n c = index % cols\n in_row = row_counts[r]\n offset = (cols - in_row) * (BOX_W + GAP_X) / 2.0\n x = PAD + offset + c * (BOX_W + GAP_X)\n y = PAD + (rows - 1 - r) * (BOX_H + GAP_Y)\n positions[node['id']] = (x, y)\n\nfor node in nodes:\n x, y = positions[node['id']]\n hot = node['id'] in active\n ax.add_patch(FancyBboxPatch((x, y), BOX_W, BOX_H,\n boxstyle='round,pad=0.02,rounding_size=0.10',\n linewidth=1.3,\n edgecolor='#b85c2e' if hot else '#b9b1a6',\n facecolor='#f6e7dc' if hot else '#fbf8f4'))\n ax.text(x + BOX_W / 2.0, y + BOX_H / 2.0, NL.join(labels[node['id']]),\n ha='center', va='center', fontsize=9.5, linespacing=1.3,\n color='#7a3d1c' if hot else '#4a443c')\n\nfor edge in edges:\n src = edge.get('from')\n dst = edge.get('to')\n if src not in positions or dst not in positions or src == dst:\n continue\n x1, y1 = positions[src]\n x2, y2 = positions[dst]\n same_row = abs(y1 - y2) < 1e-6\n if same_row:\n if x2 > x1:\n start, end = (x1 + BOX_W, y1 + BOX_H / 2.0), (x2, y2 + BOX_H / 2.0)\n else:\n start, end = (x1, y1 + BOX_H / 2.0), (x2 + BOX_W, y2 + BOX_H / 2.0)\n ax.add_patch(FancyArrowPatch(start, end, arrowstyle='-|>', mutation_scale=11,\n color='#8a8378', lw=1.1))\n mid = ((start[0] + end[0]) / 2.0, (start[1] + end[1]) / 2.0)\n label_va = 'bottom'\n else:\n if y2 < y1:\n start, end = (x1 + BOX_W / 2.0, y1), (x2 + BOX_W / 2.0, y2 + BOX_H)\n else:\n start, end = (x1 + BOX_W / 2.0, y1 + BOX_H), (x2 + BOX_W / 2.0, y2)\n rad = 0.0 if abs(x1 - x2) < 0.01 else 0.18\n ax.add_patch(FancyArrowPatch(start, end, arrowstyle='-|>', mutation_scale=11,\n color='#8a8378', lw=1.1,\n connectionstyle='arc3,rad=' + str(rad)))\n mid = ((start[0] + end[0]) / 2.0, (start[1] + end[1]) / 2.0)\n label_va = 'center'\n if edge.get('label'):\n ax.text(mid[0], mid[1], str(edge['label']), fontsize=7.5, color='#6b635a',\n ha='center', va=label_va)\n\nax.set_xlim(0, span_x)\nax.set_ylim(bottom, span_y)\nif spec.get('title'):\n ax.set_title(str(spec['title']), fontsize=12, pad=8)\nif spec.get('subtitle'):\n ax.text(PAD * 0.6, bottom + 0.10, str(spec['subtitle']), fontsize=8.5,\n color='#6b635a', ha='left', va='bottom')\nfig.tight_layout()\nfig.savefig(out, bbox_inches='tight')\nprint('saved', out)\n`\n\nfunction writeFigure(projectPath: string, spec: { file: string; title: string; caption: string; source: string; kind: FigureKind; svg?: string }): PaperFigure {\n mkdirSync(figuresDir(projectPath), { recursive: true })\n return upsertFigure(projectPath, { ...spec, addedAt: Date.now() })\n}\n\n/** \u753B\u4E00\u5F20\u6570\u636E\u56FE\uFF08\u6298\u7EBF/\u67F1/\u6563\u70B9\uFF09\uFF0C\u843D\u5230 paper/figures \u5E76\u767B\u8BB0\u3002 */\nexport function renderChart(projectPath: string, spec: ChartSpec, meta: { file?: string; title?: string; caption?: string; source?: string; kind?: PaperFigure['kind'] } = {}): PaperFigure {\n const hasSeries = (spec.series ?? []).length > 0\n const hasCategories = (spec.categories ?? []).length > 0\n if (!hasSeries && !hasCategories) throw new Error('\u56FE\u8868\u6CA1\u6709\u6570\u636E\uFF1Aseries \u6216 categories \u81F3\u5C11\u7ED9\u4E00\u4E2A')\n const file = sanitize(meta.file ?? 'chart-' + String(Date.now()).slice(-6)) + '.png'\n const out = join(figuresDir(projectPath), file)\n mkdirSync(figuresDir(projectPath), { recursive: true })\n const payload = join(tmpdir(), 'ksrw-chart-' + String(Date.now()) + '.json')\n writeFileSync(payload, JSON.stringify(spec), 'utf8')\n runPython(CHART_SCRIPT, [payload, out])\n return writeFigure(projectPath, {\n file,\n title: meta.title ?? spec.title ?? '\u6570\u636E\u56FE',\n caption: meta.caption ?? spec.title ?? '\u6570\u636E\u56FE',\n source: meta.source ?? 'agent',\n kind: meta.kind ?? 'chart',\n })\n}\n\n/** \u753B\u6D41\u7A0B\u56FE/\u67B6\u6784\u56FE\uFF08\u8282\u70B9 + \u8FB9\uFF09\uFF0C\u843D\u5230 paper/figures \u5E76\u767B\u8BB0\u3002 */\nexport function renderFlow(projectPath: string, spec: FlowSpec, meta: { file?: string; title?: string; caption?: string; source?: string; kind?: PaperFigure['kind']; svg?: string } = {}): PaperFigure {\n if (spec.nodes.length === 0) throw new Error('\u6D41\u7A0B\u56FE\u6CA1\u6709\u8282\u70B9')\n const file = sanitize(meta.file ?? 'flow-' + String(Date.now()).slice(-6)) + '.png'\n const out = join(figuresDir(projectPath), file)\n mkdirSync(figuresDir(projectPath), { recursive: true })\n const payload = join(tmpdir(), 'ksrw-flow-' + String(Date.now()) + '.json')\n writeFileSync(payload, JSON.stringify(spec), 'utf8')\n runPython(FLOW_SCRIPT, [payload, out])\n return writeFigure(projectPath, {\n file,\n title: meta.title ?? spec.title ?? '\u6D41\u7A0B\u793A\u610F\u56FE',\n caption: meta.caption ?? spec.title ?? '\u6D41\u7A0B\u793A\u610F\u56FE',\n source: meta.source ?? 'agent',\n kind: meta.kind ?? 'diagram',\n ...(meta.svg === undefined ? {} : { svg: meta.svg }),\n })\n}\n\nfunction metricsFromRun(runDir: string): Record<string, number | string> {\n try { return JSON.parse(readFileSync(join(runDir, 'metrics.json'), 'utf8')) as Record<string, number | string> } catch { return {} }\n}\n\n/** \u4ECE\u5B9E\u9A8C run \u7684 metrics.json \u751F\u6210\u67F1\u72B6\u56FE\u3002 */\nexport function renderMetricsChart(projectPath: string, runId: string, meta: { title?: string; caption?: string } = {}): PaperFigure {\n const runDir = join(projectPath, 'experiments', 'runs', runId)\n const metrics = metricsFromRun(runDir)\n const numeric = Object.entries(metrics).filter(([, value]) => typeof value === 'number')\n if (numeric.length === 0) throw new Error('\u8BE5 run \u6CA1\u6709\u6570\u503C\u6307\u6807\uFF1A' + runId)\n let script = runId\n try {\n const config = JSON.parse(readFileSync(join(runDir, 'config.json'), 'utf8')) as { script?: string }\n if (typeof config.script === 'string') script = config.script\n } catch { /* \u65E0 config */ }\n return renderChart(projectPath, {\n kind: 'bar',\n categories: numeric.map(([label, value]) => ({ label, value: Number(value) })),\n title: meta.title ?? 'Metrics \u00B7 ' + script,\n }, {\n file: 'metrics-' + runId,\n title: '\u6307\u6807\u56FE \u00B7 ' + script,\n caption: meta.caption ?? '\u5B9E\u9A8C ' + runId + ' \u7684\u6570\u503C\u6307\u6807',\n source: runId,\n kind: 'metrics',\n })\n}\n\nexport interface IngestResult { readonly file: string; readonly caption: string }\n\n/** \u5B9E\u9A8C\u811A\u672C\u5199 artifacts/figures/ \u7684\u56FE\uFF0C\u6536\u8FDB\u8BBA\u6587\u56FE\u5E93\u3002 */\nexport function ingestRunFigures(projectPath: string, runId: string): PaperFigure[] {\n const sourceDir = join(projectPath, 'experiments', 'runs', runId, 'artifacts', 'figures')\n if (!existsSync(sourceDir)) return []\n mkdirSync(figuresDir(projectPath), { recursive: true })\n const ingested: string[] = []\n let entries: string[] = []\n try { entries = readdirSync(sourceDir) } catch { return [] }\n for (const name of entries) {\n if (!FIGURE_EXTS.has(extname(name).toLowerCase())) continue\n const target = sanitize(runId + '-' + name)\n copyFileSync(join(sourceDir, name), join(figuresDir(projectPath), target))\n writeFigure(projectPath, {\n file: target,\n title: name,\n caption: '\u5B9E\u9A8C ' + runId + ' \u8F93\u51FA\uFF1A' + name.replace(/\\.[^.]+$/, ''),\n source: runId,\n kind: 'result',\n })\n ingested.push(target)\n }\n return listFigures(projectPath).filter(item => ingested.includes(item.file))\n}\n\n/** \u628A\u56FE\u63D2\u5230\u6B63\u6587\uFF1A\u7ED9\u4E86 heading \u5C31\u63D2\u5728\u8BE5\u6807\u9898\u5C0F\u8282\u672B\u5C3E\uFF0C\u5426\u5219\u8FFD\u52A0\u5230\u6587\u672B\uFF1B\u5DF2\u5B58\u5728\u5219\u4E0D\u91CD\u590D\u3002 */\nexport function insertFigureMarkdown(\n projectPath: string,\n source: string,\n file: string,\n caption: string,\n heading?: string,\n): { source: string; added: string; insertedAfter?: string } {\n const absolute = join(projectPath, source)\n if (!existsSync(absolute)) throw new Error('\u6B63\u6587\u4E0D\u5B58\u5728\uFF1A' + source)\n const text = readFileSync(absolute, 'utf8')\n const snippet = '![' + caption + '](figures/' + file + ')'\n if (text.includes(snippet)) return { source, added: snippet }\n if (heading !== undefined && heading.trim() !== '') {\n const lines = text.split('\\n')\n const targetIndex = lines.findIndex(line => line.replace(/^#+\\s*/, '').trim().startsWith(heading.trim()))\n if (targetIndex >= 0) {\n let end = lines.length\n for (let index = targetIndex + 1; index < lines.length; index += 1) {\n if (/^#{1,2}\\s+/.test(lines[index] ?? '')) { end = index; break }\n }\n while (end > targetIndex + 1 && (lines[end - 1] ?? '').trim() === '') end -= 1\n lines.splice(end, 0, '', snippet, '')\n writeFileSync(absolute, lines.join('\\n'), { encoding: 'utf8' })\n return { source, added: snippet, insertedAfter: lines[targetIndex] ?? heading }\n }\n }\n const next = text.replace(/\\s+$/, '') + '\\n\\n' + snippet + '\\n'\n writeFileSync(absolute, next, { encoding: 'utf8' })\n return { source, added: snippet }\n}\n\n/** \u8BBE\u7F6E\u56FE\u6CE8\uFF08index.json \u91CC\u66F4\u65B0\uFF09\u3002 */\nexport function setFigureCaption(projectPath: string, file: string, caption: string): PaperFigure {\n const record = listFigures(projectPath).find(item => item.file === file)\n if (record === undefined) throw new Error('\u56FE\u4E0D\u5B58\u5728\uFF1A' + file)\n return upsertFigure(projectPath, { ...record, caption })\n}\n\n/** \u5220\u9664\u4E00\u5F20\u56FE\uFF08\u6587\u4EF6 + \u7D22\u5F15\uFF09\u3002 */\nexport function removeFigure(projectPath: string, file: string): boolean {\n const figures = listFigures(projectPath)\n if (!figures.some(item => item.file === file)) return false\n saveFigures(projectPath, figures.filter(item => item.file !== file))\n try {\n const absolute = join(figuresDir(projectPath), file)\n if (existsSync(absolute)) writeFileSync(absolute, Buffer.alloc(0))\n if (existsSync(absolute) && statSync(absolute).size === 0) require('node:fs').unlinkSync(absolute)\n } catch { /* \u6587\u4EF6\u5220\u9664\u5931\u8D25\u5FFD\u7565 */ }\n return true\n}\n\n/**\n * \u4ECE DOCX\uFF08\u672C\u8D28\u662F zip\uFF09\u91CC\u62BD\u51FA\u5185\u5D4C\u56FE\u7247\u5E76\u767B\u8BB0\u8FDB\u56FE\u5E93\u3002\n *\n * \u4E8B\u6545\u80CC\u666F\uFF082026-10-30\uFF09\uFF1ADOCX \u2192 Markdown \u5BFC\u5165\u53EA\u62BD\u4E86\u6B63\u6587\u6587\u5B57\uFF0Cword/media/ \u91CC\u7684\n * \u539F\u59CB\u63D2\u56FE\u88AB\u4E22\u6389\uFF1B\u8BBA\u6587\u5DE5\u574A\u53C8\u4E0D\u5411 agent \u63D0\u4F9B shell/\u89E3\u538B\u5DE5\u5177\uFF0C\u539F\u56FE\u4ECE\u6B64\u4E0D\u53EF\u8FBE\u3002\n * \u8FD9\u91CC\u628A\u8FD9\u4E00\u6B65\u6536\u8FDB\u4EA7\u54C1\u5185\u7F6E\u80FD\u529B\uFF1A\u53EA\u8BFB zip\u3001\u8DEF\u5F84\u9650\u5B9A\u5728\u9879\u76EE\u5185\u3001\u62BD\u51FA\u7684\u56FE\u76F4\u63A5\u8FDB\n * paper/figures \u5E76\u767B\u8BB0\uFF08source=docx\uFF09\uFF0CUI \u56FE\u4EF6\u5361\u4E0E agent \u90FD\u80FD\u76F4\u63A5\u7528\u3002\n */\nexport interface DocxMediaResult {\n /** \u63D0\u53D6\u5E76\u767B\u8BB0\u8FDB\u56FE\u5E93\u7684\u56FE\u7247\u3002 */\n readonly figures: PaperFigure[]\n /** \u672A\u843D\u76D8\u7684\u6210\u5458\uFF08emf/wmf \u7B49\u975E png/jpg/svg\uFF09\u3002 */\n readonly skipped: string[]\n /** \u88AB\u8BFB\u53D6\u7684 DOCX\uFF08\u76F8\u5BF9\u9879\u76EE\uFF09\u3002 */\n readonly source: string\n}\n\n/** \u5DE5\u574A DOCX \u9644\u4EF6\u7684\u9ED8\u8BA4\u4F4D\u7F6E\uFF08\u5BFC\u5165\u6D41\u7A0B\u628A\u4E0A\u4F20\u4EF6\u56FA\u5B9A\u5199\u6210 attachment.docx\uFF09\u3002 */\nfunction defaultDocxPath(projectPath: string): string {\n for (const candidate of ['paper/attachment.docx', 'paper/source.docx']) {\n if (existsSync(join(projectPath, candidate))) return candidate\n }\n try {\n const entries = readdirSync(join(projectPath, 'paper'), { withFileTypes: true })\n const found = entries.find(entry => entry.isFile() && entry.name.toLowerCase().endsWith('.docx'))\n if (found !== undefined) return join('paper', found.name)\n } catch { /* paper/ \u53EF\u80FD\u8FD8\u4E0D\u5B58\u5728 */ }\n throw new Error('\u5DE5\u574A\u91CC\u6CA1\u6709 DOCX \u9644\u4EF6\uFF08\u5148\u4E0A\u4F20\u8BBA\u6587\uFF0C\u6216\u5728\u53C2\u6570\u91CC\u6307\u5B9A docx \u8DEF\u5F84\uFF09')\n}\n\nexport function extractDocxMedia(projectPath: string, options: { docx?: string; prefix?: string } = {}): DocxMediaResult {\n const root = resolve(projectPath)\n const docx = options.docx ?? defaultDocxPath(root)\n const absolute = resolve(root, docx)\n if (absolute !== root && !absolute.startsWith(root + sep)) throw new Error('DOCX \u8DEF\u5F84\u8D8A\u754C\uFF1A' + docx)\n if (!existsSync(absolute)) throw new Error('\u627E\u4E0D\u5230 DOCX\uFF1A' + docx)\n const zip = new AdmZip(absolute)\n const prefix = sanitize(options.prefix ?? 'docx')\n const figures: PaperFigure[] = []\n const skipped: string[] = []\n mkdirSync(figuresDir(root), { recursive: true })\n for (const entry of zip.getEntries()) {\n if (entry.isDirectory || !entry.entryName.startsWith('word/media/')) continue\n const extension = extname(entry.entryName).toLowerCase()\n if (!FIGURE_EXTS.has(extension)) { skipped.push(entry.entryName); continue }\n const base = sanitize((entry.entryName.split('/').pop() ?? 'image').replace(/.[^.]+$/, ''))\n let name = prefix + '-' + base + extension\n let counter = 2\n while (existsSync(join(figuresDir(root), name))) {\n name = prefix + '-' + base + '-' + String(counter) + extension\n counter += 1\n }\n writeFileSync(join(figuresDir(root), name), entry.getData(), { mode: 0o600 })\n figures.push(writeFigure(root, {\n file: name,\n title: '\u539F\u56FE \u00B7 ' + (entry.entryName.split('/').pop() ?? name),\n caption: '\u4ECE ' + docx + ' \u5185\u5D4C\u5A92\u4F53\u63D0\u53D6\u7684\u539F\u56FE\uFF08' + entry.entryName + '\uFF09',\n source: 'docx',\n kind: 'result',\n }))\n }\n if (figures.length === 0) throw new Error('DOCX \u91CC\u6CA1\u6709\u53EF\u63D0\u53D6\u7684\u56FE\u7247\uFF08png/jpg/svg\uFF09\uFF1A' + docx)\n return { figures, skipped, source: docx }\n}\n\n/** \u2500\u2500 \u6536\u7F16\u5DF2\u6709\u56FE\uFF08agent \u624B\u5199 SVG / \u5916\u90E8\u5BFC\u51FA PNG\uFF09\uFF1A\u767B\u8BB0 + SVG\u2192PNG 300dpi + \u6539\u5199\u6B63\u6587\u5F15\u7528 \u2500\u2500 */\n\nexport interface AdoptInput {\n readonly file: string\n readonly caption?: string\n readonly title?: string\n readonly kind?: PaperFigure['kind']\n readonly source?: string\n /** \u9ED8\u8BA4 true\uFF1A\u628A\u6B63\u6587\u91CC figures/<name>.svg \u7684\u5F15\u7528\u6539\u6210 .png\uFF08PNG \u66F4\u7A33\uFF09\u3002 */\n readonly rewrite?: boolean\n /** \u6B63\u6587\u76F8\u5BF9\u8DEF\u5F84\uFF0C\u7F3A\u7701 paper/main.md\u3002 */\n readonly draft?: string\n}\n\nfunction projectRelativeFigure(projectPath: string, file: string): string {\n const normalized = file.replace(/^\\/+/, '').replace(/^paper\\//, '')\n const relative = normalized.startsWith('figures/') ? normalized : 'figures/' + normalized\n const absolute = resolve(projectPath, 'paper', relative)\n const root = resolve(projectPath, 'paper')\n if (absolute !== root && !absolute.startsWith(root + '/')) throw new Error('\u56FE\u5FC5\u987B\u4F4D\u4E8E\u9879\u76EE paper/ \u76EE\u5F55\u5185\uFF1A' + file)\n return absolute\n}\n\nfunction svgPixelWidth(svg: string): number | undefined {\n const match = /<svg[^>]*\\swidth=[\"']([0-9.]+)(?:px)?[\"']/i.exec(svg)\n if (match === null) return undefined\n const width = Number(match[1])\n return Number.isFinite(width) && width > 0 ? width : undefined\n}\n\nfunction rasterizeSvg(svg: string, targetWidth: number): Uint8Array {\n // \u61D2\u52A0\u8F7D\u539F\u751F\u6A21\u5757\uFF1A\u53EA\u6709\u771F\u6B63\u8F6C\u56FE\u65F6\u624D\u4ED8\u52A0\u8F7D\u6210\u672C\u3002\n const req = createRequire(import.meta.url)\n const { Resvg } = req('@resvg/resvg-js') as { Resvg: new (svg: string, options: Record<string, unknown>) => { render(): { asPng(): Uint8Array } } }\n const renderer = new Resvg(svg, { fitTo: { mode: 'width', value: Math.min(6000, Math.max(300, Math.round(targetWidth))) }, background: 'white' })\n return renderer.render().asPng()\n}\n\n/** \u628A\u5DF2\u6709\u56FE\u6536\u7F16\u8FDB\u56FE\u5E93\uFF1ASVG \u989D\u5916\u751F\u6210 300dpi PNG \u5E76\u767B\u8BB0\uFF0C\u6B63\u6587\u5F15\u7528\u6539\u6210 PNG\u3002 */\nexport function adoptFigure(projectPath: string, input: AdoptInput): PaperFigure {\n const absolute = projectRelativeFigure(projectPath, input.file)\n if (!existsSync(absolute)) throw new Error('\u56FE\u4E0D\u5B58\u5728\uFF1A' + input.file)\n const extension = extname(absolute).toLowerCase()\n if (!FIGURE_EXTS.has(extension)) throw new Error('\u53EA\u652F\u6301 png/jpg/jpeg/svg\uFF1A' + input.file)\n const base = absolute.slice(0, absolute.length - extension.length).split('/').pop() ?? 'figure'\n const name = sanitize(base)\n mkdirSync(figuresDir(projectPath), { recursive: true })\n let pngName = name + '.png'\n let svgName: string | undefined\n if (extension === '.svg') {\n const svg = readFileSync(absolute, 'utf8')\n const width = svgPixelWidth(svg) ?? 900\n const png = rasterizeSvg(svg, (width / 96) * 300)\n writeFileSync(join(figuresDir(projectPath), pngName), png, { mode: 0o600 })\n svgName = name + '.svg'\n } else if (extension !== '.png') {\n pngName = name + extension\n }\n const caption = input.caption ?? input.title ?? name\n const record = upsertFigure(projectPath, {\n file: pngName,\n title: input.title ?? caption,\n caption,\n source: input.source ?? 'adopted',\n kind: input.kind ?? 'diagram',\n addedAt: Date.now(),\n ...(svgName === undefined ? {} : { svg: svgName }),\n })\n if (input.rewrite !== false) {\n const draft = input.draft ?? 'paper/main.md'\n const draftPath = join(projectPath, draft)\n if (existsSync(draftPath)) {\n const text = readFileSync(draftPath, 'utf8')\n const next = text\n .split('(figures/' + name + '.svg)').join('(figures/' + pngName + ')')\n .split('(figures/' + name + '.jpg)').join('(figures/' + pngName + ')')\n if (next !== text) writeFileSync(draftPath, next, { encoding: 'utf8' })\n }\n }\n return record\n}\n\n/** \u626B figures/ \u4E0B\u672A\u767B\u8BB0\u7684\u56FE\u5E76\u9010\u4E00\u6536\u7F16\u3002 */\nexport function adoptUnregisteredFigures(projectPath: string, options: { captionPrefix?: string } = {}): PaperFigure[] {\n const dir = figuresDir(projectPath)\n if (!existsSync(dir)) return []\n const registered = new Set<string>()\n for (const record of listFigures(projectPath)) {\n registered.add(record.file)\n if (record.svg !== undefined) registered.add(record.svg)\n }\n const adopted: PaperFigure[] = []\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (!entry.isFile()) continue\n const extension = extname(entry.name).toLowerCase()\n if (!FIGURE_EXTS.has(extension)) continue\n if (registered.has(entry.name)) continue\n try {\n adopted.push(adoptFigure(projectPath, {\n file: 'figures/' + entry.name,\n caption: (options.captionPrefix ?? '') + entry.name.replace(/\\.[^.]+$/, ''),\n kind: 'diagram',\n source: 'adopted',\n rewrite: true,\n }))\n } catch { /* \u5355\u5F20\u5931\u8D25\u4E0D\u963B\u585E\u5176\u4F59 */ }\n }\n return adopted\n}\n\nexport interface FigureIssue {\n readonly file: string\n readonly level: 'error' | 'warn'\n readonly message: string\n}\n\n/** \u56FE\u5E93\u4F53\u68C0\uFF1A\u6587\u4EF6\u5B58\u5728\u6027\u3001\u50CF\u7D20\u5BBD\u5EA6\u3001\u56FE\u6CE8\u3001\u6B63\u6587\u5F15\u7528\u3001SVG \u662F\u5426\u6709 PNG \u526F\u672C\u3002 */\nexport function validateFigures(projectPath: string, options: { file?: string; draft?: string } = {}): FigureIssue[] {\n const issues: FigureIssue[] = []\n const figures = listFigures(projectPath).filter(item => options.file === undefined || item.file === options.file)\n const draftPath = join(projectPath, options.draft ?? 'paper/main.md')\n let draft = ''\n try { draft = readFileSync(draftPath, 'utf8') } catch { issues.push({ file: options.draft ?? 'paper/main.md', level: 'warn', message: '\u6B63\u6587\u4E0D\u5B58\u5728\uFF0C\u65E0\u6CD5\u68C0\u67E5\u56FE\u7247\u5F15\u7528' }) }\n for (const record of figures) {\n const absolute = join(figuresDir(projectPath), record.file)\n if (!existsSync(absolute)) { issues.push({ file: record.file, level: 'error', message: '\u6587\u4EF6\u4E0D\u5B58\u5728' }); continue }\n if (extname(record.file).toLowerCase() === '.png') {\n const dims = sniffPng(new Uint8Array(readFileSync(absolute)))\n if (dims !== null && dims.width < 1200) issues.push({ file: record.file, level: 'warn', message: '\u5BBD\u5EA6\u4EC5 ' + String(dims.width) + 'px\uFF0C\u6295\u7A3F\u5EFA\u8BAE \u22651200px\uFF08300dpi\uFF09' })\n }\n if (record.svg === undefined && extname(record.file).toLowerCase() === '.svg') {\n issues.push({ file: record.file, level: 'warn', message: '\u53EA\u6709 SVG\uFF0C\u6CA1\u6709 PNG \u526F\u672C' })\n }\n if (record.caption.trim() === '' || record.caption === record.file) issues.push({ file: record.file, level: 'warn', message: '\u56FE\u6CE8\u7F3A\u5931\u6216\u7B49\u4E8E\u6587\u4EF6\u540D' })\n if (draft !== '' && !draft.includes('figures/' + record.file)) issues.push({ file: record.file, level: 'warn', message: '\u6B63\u6587\u6CA1\u6709\u5F15\u7528\u8FD9\u5F20\u56FE' })\n }\n return issues\n}\n", "/** \u6295\u7A3F\u8981\u6C42\u4E0E\u4FEE\u6539\u6E05\u5355\uFF1A\u628A\u51FA\u7248\u793E/\u4F1A\u8BAE\u7684\u8981\u6C42\u843D\u6210\u53EF\u52FE\u9009\u7684\u6E05\u5355\uFF0C\u5E76\u8BA9\u52A9\u624B\u6309\u6E05\u5355\u6539\u7A3F\u3002 */\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\n\nimport { KsrwError } from './error.ts'\n\nexport type RequirementStatus = 'todo' | 'done' | 'na'\n\nexport interface VenueRequirement {\n readonly id: string\n readonly text: string\n readonly status: RequirementStatus\n readonly note?: string\n}\n\nexport interface VenueProfile {\n readonly name: string\n readonly kind: string\n readonly deadline: string\n /** \u51FA\u7248\u793E/\u4F1A\u8BAE\u7684\u539F\u59CB\u8981\u6C42\u5168\u6587\uFF08\u4F5C\u8005\u987B\u77E5\u3001\u4FEE\u6539\u610F\u89C1\u7B49\uFF09\u3002 */\n readonly requirements: string\n readonly checklist: VenueRequirement[]\n readonly updatedAt: number\n}\n\nexport const DEFAULT_VENUE_PROFILE: VenueProfile = {\n name: '',\n kind: '',\n deadline: '',\n requirements: '',\n checklist: [],\n updatedAt: 0,\n}\n\nfunction venueFile(projectPath: string): string {\n return join(projectPath, 'paper', 'venue.json')\n}\n\nexport function readVenueProfile(projectPath: string): VenueProfile {\n try {\n const parsed = JSON.parse(readFileSync(venueFile(projectPath), 'utf8')) as Partial<VenueProfile>\n return {\n ...DEFAULT_VENUE_PROFILE,\n ...parsed,\n checklist: Array.isArray(parsed.checklist) ? parsed.checklist : [],\n }\n } catch { return DEFAULT_VENUE_PROFILE }\n}\n\nexport function writeVenueProfile(projectPath: string, patch: Partial<VenueProfile>): VenueProfile {\n const current = readVenueProfile(projectPath)\n const next: VenueProfile = {\n ...current,\n ...patch,\n checklist: patch.checklist !== undefined && Array.isArray(patch.checklist) ? patch.checklist : current.checklist,\n updatedAt: Date.now(),\n }\n const file = venueFile(projectPath)\n mkdirSync(dirname(file), { recursive: true })\n writeFileSync(file, JSON.stringify(next, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n return next\n}\n\n/** \u4ECE\u8981\u6C42\u5168\u6587\u91CC\u62C6\u6761\uFF08\u6309\u884C/\u5206\u53F7\uFF09\uFF0C\u751F\u6210\u521D\u59CB\u6E05\u5355\uFF1B\u5DF2\u5B58\u5728\u7684\u540C\u6587\u6761\u76EE\u4FDD\u7559\u72B6\u6001\u3002 */\nexport function seedVenueChecklist(projectPath: string, requirements: string): VenueProfile {\n const lines = requirements\n .split(/\\n+/)\n .map(line => line.replace(/^\\s*(?:[-*\u2022]|\\d+[.)\u3001])\\s*/, '').trim())\n .filter(line => line.length >= 4)\n const current = readVenueProfile(projectPath)\n const existing = new Map(current.checklist.map(item => [item.text, item]))\n const checklist = lines.map((text, index) => existing.get(text) ?? {\n id: 'req-' + String(index + 1),\n text,\n status: 'todo' as RequirementStatus,\n })\n return writeVenueProfile(projectPath, { requirements, checklist })\n}\n\nexport function setVenueRequirement(projectPath: string, id: string, status: RequirementStatus, note?: string): VenueProfile {\n const current = readVenueProfile(projectPath)\n if (!current.checklist.some(item => item.id === id)) throw new KsrwError('NOT_FOUND', '\u6E05\u5355\u9879\u4E0D\u5B58\u5728\uFF1A' + id)\n return writeVenueProfile(projectPath, {\n checklist: current.checklist.map(item => item.id === id\n ? { ...item, status, ...(note === undefined ? {} : { note }) }\n : item),\n })\n}\n\n/** \u628A\u56FE\u63D2\u5230\u6B63\u6587\u672B\u5C3E\uFF1A\u4F18\u5148\u8DDF\u5728\u540C\u540D/\u540C\u56FE\u6CE8\u7684\u63D2\u5165\u70B9\u4E4B\u540E\uFF0C\u4FDD\u8BC1\u56FE\u6CE8\u4E0E\u8DEF\u5F84\u4E00\u81F4\u3002 */\nexport function insertFigureIntoDraft(\n projectPath: string,\n source: string,\n file: string,\n caption: string,\n): { source: string; added: string } {\n const absolute = join(projectPath, source)\n if (!existsSync(absolute)) throw new KsrwError('NOT_FOUND', '\u6B63\u6587\u4E0D\u5B58\u5728\uFF1A' + source)\n const text = readFileSync(absolute, 'utf8')\n const snippet = '![' + caption + '](figures/' + file + ')'\n if (text.includes(snippet)) return { source, added: snippet }\n const next = text.replace(/\\s+$/, '') + '\\n\\n' + snippet + '\\n'\n writeFileSync(absolute, next, { encoding: 'utf8' })\n return { source, added: snippet }\n}\n", "/** KSRW \u9879\u76EE\u5DE5\u4F5C\u533A\u5185\u6838\uFF08v2\uFF09\uFF1A\u9879\u76EE\u76EE\u5F55\u5373\u9879\u76EE + \u7814\u7A76\u72B6\u6001 + \u63D0\u6848/revision\u3002\n *\n * \u7EAA\u5F8B\uFF08\u7167\u6284 KToon \u7684\u63D0\u6848\u95ED\u73AF\uFF09\uFF1A\n * - \u6743\u5A01\u6587\u4EF6\u53EA\u7531 apply \u5199\u5165\uFF1BAgent \u53EA\u80FD propose\uFF1B\n * - \u6BCF\u4E2A\u4EA7\u7269\u5E26\u5355\u8C03 revision\uFF1B\u63D0\u6848\u5E26 baseRevision\uFF0C\u5931\u914D\u8FD4\u56DE STALE_REVISION\uFF1B\n * - \u4E00\u5207\u5199\u5165 tmp+rename \u539F\u5B50\u5199\uFF1B\u9879\u76EE\u5185 `.ksrw.lock` \u8DE8\u8FDB\u7A0B\u4E32\u884C\u5316\u5199\u8DEF\u5F84\uFF08\u89C1 lock.ts\uFF09\u3002\n */\nimport { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { dirname, join, resolve } from 'node:path'\n\nimport { KsrwError } from './error.ts'\nimport { resolveKsrwHome } from './home.ts'\nimport { withProjectLock, withRegistryLock } from './lock.ts'\nimport { invalidateStalePaperFinal, paperFinalStatus, readPaperBuild } from './paper.ts'\nimport { newId } from './ids.ts'\n\nexport type ResearchStage =\n | 'idea' | 'topic' | 'proposal' | 'literature' | 'experiment'\n | 'paper' | 'submission' | 'patent' | 'done'\n\n/**\n * \u8FD0\u884C\u9636\u6BB5\u6E05\u5355\uFF08\u552F\u4E00\u771F\u6E90\uFF09\uFF1A\u7C7B\u578B\u53EA\u80FD\u4FDD\u8BC1\u7F16\u8BD1\u671F\uFF0C\u670D\u52A1\u7AEF\u6821\u9A8C\u9636\u6BB5\u9700\u8981\u8FD0\u884C\u671F\u6570\u7EC4\u3002\n * \u4EE5\u524D api.ts \u81EA\u5DF1\u5217\u4E86\u4E00\u4EFD\uFF0C\u5C11\u4E86 `submission`\uFF0C\u5BFC\u81F4\u300C\u6295\u7A3F\u300D\u8FD9\u4E2A\u5408\u6CD5\u9636\u6BB5\u65E0\u6CD5\u901A\u8FC7\n * `/ksrw/api/project/stage` \u5199\u5165\u3002\u65B0\u589E\u9636\u6BB5\u53EA\u6539\u8FD9\u91CC\u3002\n */\nexport const RESEARCH_STAGES: readonly ResearchStage[] = [\n 'idea', 'topic', 'proposal', 'literature', 'experiment', 'paper', 'submission', 'patent', 'done',\n] as const\n\n/** \u9636\u6BB5\u504F\u5E8F\u7684\u79E9\uFF1ARESEARCH_STAGES \u6570\u7EC4\u987A\u5E8F\u5373\u7814\u7A76\u4E3B\u7EBF\u7684\u5355\u8C03\u524D\u8FDB\u65B9\u5411\u3002 */\nexport function stageRank(stage: ResearchStage): number {\n return RESEARCH_STAGES.indexOf(stage)\n}\n\n/**\n * \u9636\u6BB5\u8F6C\u6362\u6821\u9A8C\uFF08P1-2\uFF09\uFF1A\u9636\u6BB5\u53EA\u5141\u8BB8\u524D\u5411\uFF08\u76F8\u7B49\u89C6\u4E3A\u5E42\u7B49\u91CD\u653E\uFF09\uFF0C\n * idea \u662F\u8D77\u70B9\u4E0D\u53EF\u4F5C\u4E3A\u63A8\u8FDB/\u95F8\u95E8\u76EE\u6807\u3002\u8FD4\u56DE null \u8868\u793A\u5141\u8BB8\uFF0C\u5426\u5219\u662F\u53EF\u8BFB\u9519\u8BEF\u6587\u6848\u3002\n */\nexport function stageTransitionError(current: ResearchStage, target: ResearchStage): string | null {\n if (target === 'idea') return 'idea \u662F\u8D77\u70B9\u9636\u6BB5\uFF0C\u4E0D\u80FD\u4F5C\u4E3A\u63A8\u8FDB/\u95F8\u95E8\u76EE\u6807\uFF1B\u5982\u9700\u91CD\u6765\u8BF7\u65B0\u5EFA\u9879\u76EE'\n if (stageRank(target) < stageRank(current)) {\n return '\u9636\u6BB5\u4E0D\u80FD\u56DE\u9000\uFF1A\u5F53\u524D ' + current + '\uFF0C\u76EE\u6807 ' + target + '\uFF08\u56DE\u9000\u8BF7\u8D70\u65B0\u5EFA\u9879\u76EE\u6216\u663E\u5F0F\u8FD0\u7EF4\u6D41\u7A0B\uFF09'\n }\n return null\n}\n\nexport interface ArtifactMeta {\n readonly revision: number\n readonly updatedAt: number\n readonly path: string\n}\n\nexport interface ProjectRecord {\n readonly id: string\n name: string\n idea: string\n stage: ResearchStage\n readonly path: string\n readonly createdAt: number\n updatedAt: number\n revision: number\n artifacts: Record<string, ArtifactMeta>\n /**\n * \u514D\u767B\u8BB0\u5DE5\u4F5C\u533A\uFF08\u514D\u9879\u76EE\u5B9E\u9A8C\u5DE5\u574A\uFF09\uFF1A\u53EF\u4EE5 open / \u63D0\u6848 / \u63A8\u8FDB\u9636\u6BB5\uFF0C\u4F46\u4E0D\u8FDB\u9879\u76EE\u5E93\u3002\n * \u7528\u6237\u4E3B\u52A8\u300C\u767B\u8BB0\u4E3A\u9879\u76EE\u300D\u65F6\u6E05\u9664\u8BE5\u6807\u8BB0\u3002\n */\n detached?: boolean\n}\n\nexport interface ResearchState {\n stage: ResearchStage\n summary: string\n next: string\n blockers: string[]\n confirmed: string[]\n updatedAt: number\n}\n\nexport interface TopicFeasibility {\n data: string\n compute: string\n time: string\n skill: string\n}\n\nexport interface TopicCard {\n question: string\n motivation: string\n gap: string\n novelty: string[]\n feasibility: TopicFeasibility\n risks: string[]\n decision: 'pending' | 'go' | 'no-go'\n revision: number\n updatedAt: number\n}\n\nexport type ProposalStatus = 'pending' | 'applied' | 'discarded'\n\nexport interface ProposalRecord {\n readonly id: string\n readonly projectId: string\n readonly artifactRef: string\n readonly baseRevision: number\n readonly summary: string\n readonly content: string\n status: ProposalStatus\n readonly authorRole?: string\n readonly createdAt: number\n appliedAt?: number\n}\n\nexport interface HandoffRecord {\n readonly id: string\n readonly from: string\n readonly to: string\n readonly artifacts: string[]\n readonly acceptance: string[]\n readonly requestedOutput: string\n note?: string\n status: 'open' | 'started' | 'accepted' | 'returned' | 'done'\n readonly createdAt: number\n startedAt?: number\n returnedAt?: number\n doneAt?: number\n}\n\nexport interface EvidenceRecord {\n readonly id: string\n readonly claimRef: string\n readonly source: { readonly kind: 'paper' | 'run' | 'patent'; readonly ref: string; readonly locator?: string }\n readonly quote: string\n readonly createdAt: number\n}\n\nexport interface ArtifactView {\n readonly ref: string\n readonly revision: number\n readonly updatedAt: number\n readonly content: string\n}\n\nexport interface ProjectView {\n readonly project: ProjectRecord\n readonly researchState: ResearchState\n readonly topic: TopicCard | null\n readonly proposals: readonly ProposalRecord[]\n /** P1-3\uFF1Aevidence.jsonl \u7684\u8BFB\u53D6\u7ED3\u679C\uFF0C\u5B57\u6BB5\u4E0E appendEvidence \u5199\u5165\u4E00\u81F4\u3002 */\n readonly evidence: readonly EvidenceRecord[]\n}\n\nexport interface GateResult {\n readonly allowed: boolean\n readonly stage: ResearchStage\n readonly blockers: string[]\n}\n\nexport interface ProjectIndexEntry {\n readonly id: string\n readonly name: string\n readonly path: string\n readonly stage: ResearchStage\n readonly createdAt: number\n readonly updatedAt: number\n}\n\ninterface Registry {\n version: 1\n projects: ProjectIndexEntry[]\n}\n\nconst REGISTRY_VERSION = 1\nexport const DEFAULT_PROJECT_DIR = 'projects'\n\nexport { resolveKsrwHome } from './home.ts'\n\nexport function resolveProjectHome(home = resolveKsrwHome()): string {\n return join(home, DEFAULT_PROJECT_DIR)\n}\n\nfunction atomicWrite(filePath: string, text: string): void {\n mkdirSync(dirname(filePath), { recursive: true, mode: 0o700 })\n const tmp = filePath + '.tmp-' + process.pid + '-' + Math.random().toString(36).slice(2, 8)\n writeFileSync(tmp, text, { encoding: 'utf8', mode: 0o600 })\n renameSync(tmp, filePath)\n}\n\nfunction readJson<T>(filePath: string): T | null {\n try {\n return JSON.parse(readFileSync(filePath, 'utf8')) as T\n } catch {\n return null\n }\n}\n\nfunction readText(filePath: string): string | null {\n try {\n return readFileSync(filePath, 'utf8')\n } catch {\n return null\n }\n}\n\nfunction writeJson(filePath: string, value: unknown): void {\n atomicWrite(filePath, JSON.stringify(value, null, 2) + '\\n')\n}\n\nfunction slugify(name: string): string {\n const slug = name.trim().toLowerCase()\n .replace(/[^\\w\\u4e00-\\u9fff-]+/g, '-')\n .replace(/^-+|-+$/g, '')\n return slug === '' ? 'project-' + Date.now().toString(36) : slug\n}\n\nexport function projectDirs(): readonly string[] {\n return ['topic', 'proposal', 'literature', 'experiments', 'paper', 'submission', 'patent', 'evidence', 'proposals']\n}\n\nconst ARTIFACT_PATHS: Record<string, string> = {\n topic: 'topic/topic.json',\n proposal: 'proposal/proposal.md',\n literature: 'literature/matrix.md',\n experiment: 'experiments/record.md',\n paper: 'paper/main.md',\n patent: 'patent/disclosure.md',\n}\n\n/** Stable artifact refs that the UI and proposals may write. */\nexport function artifactPath(ref: string): string | null {\n return ARTIFACT_PATHS[ref] ?? null\n}\n\n/** Stage gate: only artifact evidence exported by the UI/tools can pass. */\n/** \u4EA4\u63A5\u5355\u76EE\u6807\u5DE5\u4F4D \u2192 \u4E3B\u7EBF\u9636\u6BB5\uFF08\u4E13\u5229\u662F\u72EC\u7ACB\u5DE5\u4F4D\uFF0C\u4E0D\u53C2\u4E0E\u9636\u6BB5\u95F8\u95E8\uFF09\u3002 */\nconst HANDOFF_MAINLINE_STAGE: Record<string, ResearchStage> = {\n topic: 'topic',\n proposal: 'proposal',\n literature: 'literature',\n experiment: 'experiment',\n paper: 'paper',\n}\n\nexport function handoffMainlineStage(to: string): ResearchStage | null {\n const bare = to.startsWith('ksrw-') ? to.slice(5) : to\n const direct = HANDOFF_MAINLINE_STAGE[bare]\n if (direct !== undefined) return direct\n if (bare.includes('\u5F00\u9898')) return 'proposal'\n if (bare.includes('\u6587\u732E')) return 'literature'\n if (bare.includes('\u5B9E\u9A8C')) return 'experiment'\n if (bare.includes('\u8BBA\u6587')) return 'paper'\n return null\n}\n\nexport function gateBlockers(view: ProjectView, target: ResearchStage): string[] {\n const blockers: string[] = []\n const artifacts = view.project.artifacts\n // P1-2\uFF1A\u95F8\u95E8\u53EA\u5141\u8BB8\u524D\u5411\u63A8\u8FDB\uFF0Cidea \u4E0D\u53EF\u4F5C\u4E3A\u76EE\u6807\uFF08fake/legacy \u89C6\u56FE\u7F3A stage \u65F6\u8DF3\u8FC7\u8BE5\u68C0\u67E5\uFF09\u3002\n const currentStage = view.project.stage\n if (currentStage !== undefined) {\n const transition = stageTransitionError(currentStage, target)\n if (transition !== null) blockers.push(transition)\n }\n if (target === 'idea') blockers.push('idea \u662F\u8D77\u70B9\u9636\u6BB5\uFF0C\u4E0D\u80FD\u4F5C\u4E3A\u95F8\u95E8\u76EE\u6807')\n // P1-3\uFF1A\u8BC1\u636E\u8986\u76D6\u3002\u63D0\u6848\u4E0D\u643A\u5E26 claimRef\uFF08\u63D0\u6848\u8BB0\u5F55\u91CC\u6CA1\u6709 claim \u6982\u5FF5\uFF09\uFF0C\n // \u56E0\u6B64\u6309\u62A5\u544A\u56DE\u9000\u53E3\u5F84\uFF1A\u5DF2\u6709\u5F00\u9898\u4EA7\u7269\u4E14\u9636\u6BB5\u8D8A\u8FC7 literature \u4E4B\u540E\uFF0C\u81F3\u5C11\u8981\u6709\u4E00\u6761 evidence\u3002\n const viewStage = view.project.stage\n if (viewStage !== undefined\n && stageRank(viewStage) > stageRank('literature')\n && (artifacts['proposal']?.revision ?? 0) >= 1\n && (view.evidence?.length ?? 0) === 0) {\n blockers.push('\u7F3A\u5C11\u8BC1\u636E\uFF08evidence/evidence.jsonl\uFF09\uFF1A\u9636\u6BB5\u5DF2\u8D8A\u8FC7 literature \u4E14\u5F00\u9898\u4EA7\u7269\u5DF2\u5B58\u5728\uFF0C\u81F3\u5C11\u9700\u8981\u4E00\u6761\u8BC1\u636E')\n }\n if (target === 'topic') {\n if (view.topic === null) blockers.push('\u7F3A\u5C11\u9009\u9898\u5361\uFF08topic/topic.json\uFF09')\n else if (view.topic.decision !== 'go') blockers.push('\u9009\u9898\u5361 decision \u4E0D\u662F go')\n }\n if (target === 'proposal' || target === 'literature' || target === 'experiment') {\n if ((artifacts['proposal']?.revision ?? 0) < 1) blockers.push('\u7F3A\u5C11\u5DF2\u5E94\u7528\u7684\u5F00\u9898\u4EA7\u7269\uFF08proposal/proposal.md\uFF09')\n }\n if (target === 'experiment') {\n if ((artifacts['literature']?.revision ?? 0) < 1) blockers.push('\u7F3A\u5C11\u6587\u732E\u4EA7\u7269\uFF08literature\uFF09\uFF1A\u5B9E\u9A8C\u9700\u8981\u6587\u732E\u77E9\u9635\u4F5C\u4E3A\u8BC1\u636E\u57FA\u7840')\n }\n if (target === 'paper') {\n if ((artifacts['literature']?.revision ?? 0) < 1) blockers.push('\u7F3A\u5C11\u6587\u732E\u4EA7\u7269\uFF08literature\uFF09')\n if ((artifacts['experiment']?.revision ?? 0) < 1) blockers.push('\u7F3A\u5C11\u5B9E\u9A8C\u4EA7\u7269\uFF08experiment\uFF09')\n }\n if (target === 'patent') {\n if ((artifacts['paper']?.revision ?? 0) < 1) blockers.push('\u7F3A\u5C11\u8BBA\u6587\u4EA7\u7269\uFF08paper\uFF09')\n }\n if (target === 'done') {\n if ((artifacts['paper']?.revision ?? 0) < 1) blockers.push('\u7F3A\u5C11\u8BBA\u6587\u4EA7\u7269\uFF08paper\uFF09')\n }\n return blockers\n}\n\n/** One project on disk; every operation reads the manifest first. */\nexport class ProjectStore {\n private readonly home: string\n private readonly root: string\n\n constructor(options: { home?: string } = {}) {\n this.home = resolve(options.home ?? resolveKsrwHome())\n this.root = resolveProjectHome(this.home)\n mkdirSync(this.root, { recursive: true, mode: 0o700 })\n }\n\n /**\n * \u6240\u6709\u5199\u8DEF\u5F84\u90FD\u5728\u9879\u76EE\u9501\u5185\u6267\u884C\uFF08`<project>/.ksrw.lock`\uFF0C\u8DE8\u8FDB\u7A0B\uFF09\u3002\n * \u5D4C\u5957\u8C03\u7528\u7531 lock.ts \u6309\u8DEF\u5F84\u91CD\u5165\u8BA1\u6570\uFF0C\u4E0D\u9700\u8981\u8C03\u7528\u65B9\u533A\u5206\u300C\u5916\u5C42/\u5185\u5C42\u300D\u3002\n */\n private locked<T>(projectPath: string, fn: () => T): T {\n return withProjectLock(projectPath, fn)\n }\n\n /** \u767B\u8BB0\u8868\u662F home \u7EA7\u5171\u4EAB\u6587\u4EF6\uFF0C\u8BFB\u5199\u5F80\u8FD4\u4E5F\u8981\u8DE8\u8FDB\u7A0B\u4E92\u65A5\u3002 */\n private withRegistry<T>(fn: () => T): T {\n return withRegistryLock(this.home, fn)\n }\n\n private registryPath(): string {\n return join(this.home, 'projects.json')\n }\n\n private loadRegistry(): Registry {\n const parsed = readJson<Registry>(this.registryPath())\n if (parsed === null || parsed.version !== REGISTRY_VERSION || !Array.isArray(parsed.projects)) {\n return { version: REGISTRY_VERSION, projects: [] }\n }\n return parsed\n }\n\n private saveRegistry(registry: Registry): void {\n writeJson(this.registryPath(), registry)\n }\n\n list(): readonly ProjectIndexEntry[] {\n // P2-5\uFF1A\u76EE\u5F55\u5373\u9879\u76EE\u2014\u2014\u76EE\u5F55\u88AB\u5220/\u79FB\u540E\u6E05\u5355\u6761\u76EE\u6210\u4E86\u6253\u4E0D\u5F00\u7684\u5E7D\u7075\u9879\uFF0C\u5217\u8868\u5FC5\u987B\u8FC7\u6EE4\u6389\u3002\n return this.loadRegistry().projects\n .filter(item => readJson<ProjectRecord>(join(item.path, 'project.json')) !== null)\n .slice()\n .sort((a, b) => b.updatedAt - a.updatedAt)\n }\n\n create(input: { name: string; idea?: string; directory?: string; detached?: boolean }): ProjectRecord {\n const name = input.name.trim()\n if (name === '') throw new KsrwError('INVALID', '\u9879\u76EE\u540D\u4E0D\u80FD\u4E3A\u7A7A')\n const idea = (input.idea ?? '').trim()\n const target = resolve(input.directory !== undefined && input.directory.trim() !== ''\n ? input.directory\n : join(this.root, slugify(name)))\n mkdirSync(target, { recursive: true, mode: 0o700 })\n // \u68C0\u67E5\u91CD\u540D\u4E0E\u521B\u5EFA\u5FC5\u987B\u5728\u540C\u4E00\u628A\u9501\u91CC\uFF1A\u4E24\u4E2A\u8FDB\u7A0B\u540C\u65F6\u65B0\u5EFA\u540C\u4E00\u76EE\u5F55\u65F6\u4E0D\u80FD\u90FD\u6210\u529F\u3002\n return this.locked(target, () => {\n const manifestPath = join(target, 'project.json')\n if (readJson<ProjectRecord>(manifestPath) !== null) {\n throw new KsrwError('DUPLICATE', '\u8BE5\u76EE\u5F55\u5DF2\u6709 KSRW \u9879\u76EE\uFF1A' + target)\n }\n for (const dir of projectDirs()) mkdirSync(join(target, dir), { recursive: true, mode: 0o700 })\n const now = Date.now()\n const project: ProjectRecord = {\n id: newId('prj'),\n name,\n idea,\n stage: 'idea',\n path: target,\n createdAt: now,\n updatedAt: now,\n revision: 1,\n artifacts: {},\n ...(input.detached === true ? { detached: true } : {}),\n }\n this.saveProject(project)\n if (idea !== '') atomicWrite(join(target, 'topic', 'ideas.md'), '# \u60F3\u6CD5\u6536\u4EF6\u7BB1\\n\\n- ' + idea + '\\n')\n this.writeResearchState(project, {\n stage: 'idea',\n summary: idea === '' ? '\u65B0\u5EFA\u9879\u76EE\uFF0C\u7B49\u5F85\u5199\u4E0B idea\u3002' : idea,\n next: '\u5199\u6E05\u695A idea\uFF0C\u7136\u540E\u751F\u6210\u9009\u9898\u5361\u3002',\n blockers: [],\n confirmed: [],\n updatedAt: now,\n })\n this.upsertRegistry(project)\n return project\n })\n }\n\n open(projectPath: string): ProjectView {\n const project = this.readProject(projectPath)\n return {\n project,\n researchState: this.readResearchState(project),\n topic: this.readTopic(project),\n proposals: this.listProposals(project),\n evidence: this.readEvidence(projectPath),\n }\n }\n\n /** Re-register an existing project directory (the directory is the project). */\n register(directory: string): ProjectView {\n const target = resolve(directory.trim())\n return this.locked(target, () => {\n // P1-1\uFF1A\u7ECF readProject \u8BFB\u53D6\uFF0Cpath \u4EE5\u5B9E\u9645\u76EE\u5F55\u4E3A\u51C6\uFF08\u6E05\u5355\u8BB0\u5F55\u7684\u65E7\u8DEF\u5F84\u88AB\u5C31\u5730\u6821\u6B63\u5199\u56DE\uFF09\uFF0C\n // \u767B\u8BB0\u8868\u968F\u4E4B\u6307\u5411\u76EE\u5F55\u5F53\u524D\u4F4D\u7F6E\u800C\u4E0D\u662F\u6E05\u5355\u91CC\u7684\u65E7\u4F4D\u7F6E\u3002\n let project: ProjectRecord\n try {\n project = this.readProject(target)\n } catch (error) {\n if (error instanceof KsrwError && error.code === 'NOT_FOUND') {\n throw new KsrwError('NOT_FOUND', '\u8BE5\u76EE\u5F55\u6CA1\u6709 project.json\uFF0C\u4E0D\u662F KSRW \u9879\u76EE\uFF1A' + target)\n }\n throw error\n }\n // \u767B\u8BB0\u5373\u8131\u79BB\u300C\u514D\u767B\u8BB0\u300D\u72B6\u6001\uFF1A\u514D\u9879\u76EE\u5B9E\u9A8C\u5DE5\u574A\u88AB\u63A5\u6536\u4E3A\u6B63\u5F0F\u9879\u76EE\u540E\u8981\u51FA\u73B0\u5728\u9879\u76EE\u5E93\u3002\n if (project.detached === true) {\n project.detached = false\n this.saveProject(project)\n }\n this.upsertRegistry(project)\n return this.open(target)\n })\n }\n\n /** Remove from the project library. User files are kept; register it again anytime. */\n forget(projectPath: string): void {\n forgetProject(this.home, projectPath)\n }\n\n readProject(projectPath: string): ProjectRecord {\n const target = resolve(projectPath)\n const project = readJson<ProjectRecord>(join(target, 'project.json'))\n if (project === null) throw new KsrwError('NOT_FOUND', '\u4E0D\u662F KSRW \u9879\u76EE\uFF08\u7F3A project.json\uFF09\uFF1A' + target)\n // P2-6\uFF1A\u5173\u952E\u5B57\u6BB5\u7F3A\u5931\u629B\u53EF\u8BFB\u7684 INVALID\uFF0C\u800C\u4E0D\u662F\u4E0B\u6E38\u67D0\u5904\u7684 TypeError\uFF08500\uFF09\u3002\n if (typeof project.id !== 'string' || project.id === '') {\n throw new KsrwError('INVALID', 'project.json \u7F3A\u5C11 id \u5B57\u6BB5\uFF1A\u8BF7\u624B\u5DE5\u8865\u4E0A\u5B57\u7B26\u4E32 id \u540E\u91CD\u8BD5\uFF08' + target + '\uFF09')\n }\n if (typeof project.createdAt !== 'number' || !Number.isFinite(project.createdAt)) {\n throw new KsrwError('INVALID', 'project.json \u7F3A\u5C11 createdAt \u5B57\u6BB5\uFF1A\u8BF7\u624B\u5DE5\u8865\u4E0A\u6570\u5B57\u65F6\u95F4\u6233\u540E\u91CD\u8BD5\uFF08' + target + '\uFF09')\n }\n // P1-1\uFF1A\u76EE\u5F55\u5373\u9879\u76EE\uFF0Cpath \u4EE5\u300C\u5B9E\u9645\u76EE\u5F55\u300D\u4E3A\u51C6\u3002\u6E05\u5355\u91CC\u8BB0\u5F55\u7684\u65E7\u7EDD\u5BF9\u8DEF\u5F84\uFF08\u76EE\u5F55\u88AB\u79FB\u52A8/\u590D\u5236\uFF09\n // \u5C31\u5730\u6821\u6B63\u5E76\u5199\u56DE\uFF0C\u968F\u540E\u7684\u4EA7\u7269\u8BFB\u5199\u5168\u90E8\u843D\u5230\u5B9E\u9645\u76EE\u5F55\uFF0C\u4E0D\u518D\u5199\u56DE\u65E7\u76EE\u5F55\u5236\u9020\u5E7D\u7075\u76EE\u5F55\u3002\n if (project.path !== target) {\n ;(project as { path: string }).path = target\n writeJson(join(target, 'project.json'), project)\n }\n const now = Date.now()\n // A missing artifact entry is a valid legacy state; callers fall back to revision 0.\n if (typeof project.revision !== 'number') project.revision = 1\n if (project.stage === undefined) project.stage = 'idea'\n if (project.artifacts === undefined) project.artifacts = {}\n if (project.updatedAt === undefined) project.updatedAt = now\n return project\n }\n\n saveProject(project: ProjectRecord): void {\n this.locked(project.path, () => writeJson(join(project.path, 'project.json'), project))\n }\n\n touch(project: ProjectRecord): ProjectRecord {\n return this.locked(project.path, () => {\n project.updatedAt = Date.now()\n project.revision += 1\n this.saveProject(project)\n this.upsertRegistry(project)\n return project\n })\n }\n\n setStage(projectPath: string, stage: ResearchStage): ProjectRecord {\n return this.locked(projectPath, () => {\n const project = this.readProject(projectPath)\n // P1-2\uFF1A\u9636\u6BB5\u504F\u5E8F\u2014\u2014\u53EA\u5141\u8BB8\u524D\u5411\uFF08\u76F8\u7B49\u5E42\u7B49\uFF09\uFF0Cidea \u4E0D\u53EF\u4F5C\u4E3A\u76EE\u6807\uFF1B\u5426\u5219\u629B\u53EF\u8BFB INVALID\uFF08API \u5C42 400\uFF09\u3002\n const transition = stageTransitionError(project.stage, stage)\n if (transition !== null) throw new KsrwError('INVALID', transition)\n project.stage = stage\n this.touch(project)\n this.writeResearchState(project, {\n ...this.readResearchState(project),\n stage,\n updatedAt: Date.now(),\n })\n return project\n })\n }\n\n gate(projectPath: string, target: ResearchStage): GateResult {\n // \u68C0\u67E5\u4E0E\u63A8\u8FDB\u5FC5\u987B\u5728\u540C\u4E00\u628A\u9501\u91CC\uFF1A\u5426\u5219\u4E24\u4E2A\u5DE5\u4F4D\u53EF\u80FD\u5404\u81EA\u770B\u5230\u300C\u65E0 blocker\u300D\u5E76\u91CD\u590D\u63A8\u8FDB\u3002\n return this.locked(projectPath, () => {\n const view = this.open(projectPath)\n const blockers = gateBlockers(view, target)\n // P2-1\uFF1A\u4E2D\u95F4\u95F8\u95E8\u4E5F\u8981\u770B\u4EA4\u63A5\u5355\u2014\u2014\u76EE\u6807\u9636\u6BB5\u4E4B\u524D\u7684\u4E3B\u7EBF\u4EA4\u63A5\u5355\u5FC5\u987B\u6536\u5C3E\u3002\n // \u53EA\u8981\u6C42\u300C\u4E25\u683C\u65E9\u4E8E\u76EE\u6807\u9636\u6BB5\u300D\u7684\u5355\uFF1A\u76EE\u6807\u9636\u6BB5\u81EA\u5DF1\u7684\u4EA4\u63A5\u5355\u662F\u63A5\u4E0B\u6765\u8981\u505A\u7684\u5DE5\u4F5C\u3002\n const pendingEarlier = this.readHandoffs(projectPath).filter(handoff => {\n if (handoff.status !== 'open' && handoff.status !== 'started') return false\n const stage = handoffMainlineStage(handoff.to)\n return stage !== null && stageRank(stage) < stageRank(target)\n })\n if (pendingEarlier.length > 0) {\n blockers.push('\u8FD8\u6709 ' + pendingEarlier.length + ' \u5F20\u4E0A\u4E00\u9636\u6BB5\u7684\u4EA4\u63A5\u5355\u672A\u6536\u5C3E\uFF08' + pendingEarlier.map(item => item.id).join('\u3001') + '\uFF09')\n }\n // \u5B9A\u7A3F\u624D\u7B97\u5B8C\u6210\uFF1APDF \u5FC5\u987B\u5728\uFF0C\u4E14\u662F\u7531\u5F53\u524D\u8FD9\u7248\u8BBA\u6587\u6B63\u6587\u751F\u6210\u7684\u3002\n if (target === 'done') {\n invalidateStalePaperFinal(projectPath, 'paper')\n const final = paperFinalStatus(projectPath, 'paper')\n const record = readPaperBuild(projectPath, 'paper')\n if (record !== undefined && record.pdf === '' && record.invalidatedAt !== undefined) {\n blockers.push('\u8BBA\u6587\u6B63\u6587\u5DF2\u4FEE\u6539\uFF1A\u65E7\u5B9A\u7A3F PDF \u5DF2\u5220\u9664\uFF0C\u8BF7\u91CD\u65B0\u7F16\u8BD1\u5B9A\u7A3F')\n } else if (final.build === undefined) {\n blockers.push('\u8BBA\u6587\u5C1A\u672A\u5B9A\u7A3F\uFF1A\u5148\u5728\u8BBA\u6587\u5DE5\u4F4D\u300C\u7F16\u8BD1\u5B9A\u7A3F PDF\u300D')\n } else if (final.stale) {\n blockers.push('\u8BBA\u6587\u6B63\u6587\u5DF2\u4FEE\u6539\uFF1A\u5B9A\u7A3F PDF \u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u7F16\u8BD1\u5B9A\u7A3F')\n }\n }\n if (blockers.length === 0) this.setStage(projectPath, target)\n return { allowed: blockers.length === 0, stage: target, blockers }\n })\n }\n\n readTopic(project: ProjectRecord): TopicCard | null {\n return readJson<TopicCard>(join(project.path, 'topic', 'topic.json'))\n }\n\n saveTopic(projectPath: string, input: Omit<TopicCard, 'revision' | 'updatedAt'>): TopicCard {\n return this.locked(projectPath, () => {\n const project = this.readProject(projectPath)\n const previous = this.readTopic(project)\n const now = Date.now()\n const card: TopicCard = {\n ...input,\n revision: (previous?.revision ?? 0) + 1,\n updatedAt: now,\n }\n writeJson(join(project.path, 'topic', 'topic.json'), card)\n project.artifacts['topic'] = { revision: card.revision, updatedAt: now, path: 'topic/topic.json' }\n if (project.stage === 'idea') project.stage = 'topic'\n project.updatedAt = now\n project.revision += 1\n this.saveProject(project)\n this.upsertRegistry(project)\n this.writeResearchState(project, {\n ...this.readResearchState(project),\n stage: project.stage,\n summary: card.question === '' ? '\u9009\u9898\u5361\u5DF2\u5EFA\u7ACB\uFF0C\u95EE\u9898\u5F85\u8865\u3002' : card.question,\n next: card.decision === 'go' ? '\u786E\u8BA4\u9009\u9898\u540E\u8FDB\u5165\u5F00\u9898\u3002' : '\u8865\u5168\u95EE\u9898/\u5DEE\u8DDD/\u53EF\u884C\u6027\uFF0C\u7136\u540E\u786E\u8BA4\u9009\u9898\u3002',\n updatedAt: now,\n })\n return card\n })\n }\n\n readArtifact(projectPath: string, ref: string): ArtifactView | null {\n const project = this.readProject(projectPath)\n const relative = artifactPath(ref)\n if (relative === null) throw new KsrwError('INVALID', '\u672A\u77E5\u4EA7\u7269\u7C7B\u578B\uFF1A' + ref)\n const content = readText(join(project.path, relative))\n if (content === null) return null\n const meta = project.artifacts[ref]\n return {\n ref,\n revision: meta?.revision ?? 0,\n updatedAt: meta?.updatedAt ?? project.updatedAt,\n content,\n }\n }\n\n /** Direct user-authoritative write (UI edit). Agent writes still go through proposals. */\n writeArtifact(projectPath: string, ref: string, content: string): ArtifactView {\n return this.locked(projectPath, () => {\n const project = this.readProject(projectPath)\n const relative = artifactPath(ref)\n if (relative === null) throw new KsrwError('INVALID', '\u672A\u77E5\u4EA7\u7269\u7C7B\u578B\uFF1A' + ref)\n if (content.trim() === '') throw new KsrwError('INVALID', '\u4EA7\u7269\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A')\n atomicWrite(join(project.path, relative), content)\n const now = Date.now()\n const revision = (project.artifacts[ref]?.revision ?? 0) + 1\n project.artifacts[ref] = { revision, updatedAt: now, path: relative }\n project.updatedAt = now\n project.revision += 1\n this.saveProject(project)\n this.upsertRegistry(project)\n return { ref, revision, updatedAt: now, content }\n })\n }\n\n listProposals(project: ProjectRecord): ProposalRecord[] {\n const dir = join(project.path, 'proposals')\n const proposals: ProposalRecord[] = []\n let entries: string[] = []\n try { entries = readdirNames(dir) } catch { return proposals }\n for (const name of entries) {\n if (!name.endsWith('.json')) continue\n const proposal = readJson<ProposalRecord>(join(dir, name))\n if (proposal !== null) proposals.push(proposal)\n }\n return proposals.sort((a, b) => b.createdAt - a.createdAt)\n }\n\n createProposal(projectPath: string, input: {\n artifactRef: string\n baseRevision: number\n summary: string\n content: string\n authorRole?: string\n }): ProposalRecord {\n // \u57FA\u7EBF\u6821\u9A8C\u5FC5\u987B\u4E0E\u5199\u5165\u540C\u4E00\u628A\u9501\uFF1A\u5426\u5219\u4E24\u4E2A agent \u53EF\u540C\u65F6\u901A\u8FC7 STALE \u68C0\u67E5\u3002\n return this.locked(projectPath, () => {\n const project = this.readProject(projectPath)\n const current = project.artifacts[input.artifactRef]?.revision ?? 0\n if (input.baseRevision !== current) {\n throw new KsrwError('STALE_REVISION', '\u63D0\u6848\u57FA\u7EBF\u5DF2\u8FC7\u671F\uFF1Abase=' + input.baseRevision + ' current=' + current)\n }\n // topic \u63D0\u6848\u6309 TopicCard JSON \u5B58\uFF1Aapply \u65F6\u5FC5\u987B\u80FD\u76F4\u63A5\u89E3\u6790\uFF0C\u4E0D\u80FD\u7B49\u5230\u7528\u6237\u70B9\u300C\u5E94\u7528\u300D\u624D\u5931\u8D25\u3002\n if (input.artifactRef === 'topic') {\n let parsed: unknown\n try { parsed = JSON.parse(input.content) } catch {\n throw new KsrwError('INVALID', '\u9009\u9898\u63D0\u6848 content \u5FC5\u987B\u662F TopicCard JSON\uFF08question/motivation/gap/novelty/feasibility/risks/decision\uFF09\uFF0C\u4E0D\u662F Markdown')\n }\n const card = parsed as Partial<Record<string, unknown>>\n for (const field of ['question', 'motivation', 'gap', 'novelty', 'feasibility', 'risks', 'decision']) {\n if (card[field] === undefined) throw new KsrwError('INVALID', '\u9009\u9898\u63D0\u6848\u7F3A\u5C11\u5B57\u6BB5\uFF1A' + field)\n }\n if (card['decision'] !== 'pending' && card['decision'] !== 'go' && card['decision'] !== 'nogo') {\n throw new KsrwError('INVALID', '\u9009\u9898\u63D0\u6848 decision \u53EA\u80FD\u662F pending/go/nogo\uFF0C\u6536\u5230\uFF1A' + String(card['decision']))\n }\n }\n const proposal: ProposalRecord = {\n id: newId('prop'),\n projectId: project.id,\n artifactRef: input.artifactRef,\n baseRevision: input.baseRevision,\n summary: input.summary,\n content: input.content,\n status: 'pending',\n ...(input.authorRole === undefined ? {} : { authorRole: input.authorRole }),\n createdAt: Date.now(),\n }\n writeJson(join(project.path, 'proposals', proposal.id + '.json'), proposal)\n return proposal\n })\n }\n\n applyProposal(projectPath: string, proposalId: string): { proposal: ProposalRecord; revision: number } {\n // \u6821\u9A8C\u3001\u5199\u4EA7\u7269\u3001\u63A8\u8FDB revision\u3001\u56DE\u5199\u63D0\u6848\u662F\u4E00\u4E2A\u4E8B\u52A1\uFF1B\u5E76\u53D1 apply \u4E0D\u80FD\u90FD\u6210\u529F\u3002\n return this.locked(projectPath, () => {\n const project = this.readProject(projectPath)\n const proposal = readJson<ProposalRecord>(join(project.path, 'proposals', proposalId + '.json'))\n if (proposal === null) throw new KsrwError('NOT_FOUND', '\u63D0\u6848\u4E0D\u5B58\u5728\uFF1A' + proposalId)\n if (proposal.status !== 'pending') throw new KsrwError('INVALID', '\u63D0\u6848\u5DF2\u5904\u7406\uFF1A' + proposal.status)\n const current = project.artifacts[proposal.artifactRef]?.revision ?? 0\n if (proposal.baseRevision !== current) {\n throw new KsrwError('STALE_REVISION', '\u63D0\u6848\u57FA\u7EBF\u5DF2\u8FC7\u671F\uFF1Abase=' + proposal.baseRevision + ' current=' + current)\n }\n const revision = current + 1\n const now = Date.now()\n if (proposal.artifactRef === 'topic') {\n let card: TopicCard\n try { card = JSON.parse(proposal.content) as TopicCard } catch {\n throw new KsrwError('INVALID', '\u9009\u9898\u63D0\u6848\u4E0D\u662F\u5408\u6CD5 JSON')\n }\n card.revision = revision\n card.updatedAt = now\n writeJson(join(project.path, 'topic', 'topic.json'), card)\n project.artifacts['topic'] = { revision, updatedAt: now, path: 'topic/topic.json' }\n } else {\n const relative = artifactPath(proposal.artifactRef)\n if (relative === null) throw new KsrwError('INVALID', 'M0 \u6682\u4E0D\u652F\u6301\u8BE5\u4EA7\u7269\u7C7B\u578B\uFF1A' + proposal.artifactRef)\n if (proposal.content.trim() === '') throw new KsrwError('INVALID', '\u4EA7\u7269\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A')\n atomicWrite(join(project.path, relative), proposal.content)\n project.artifacts[proposal.artifactRef] = { revision, updatedAt: now, path: relative }\n }\n project.updatedAt = now\n project.revision += 1\n this.saveProject(project)\n this.upsertRegistry(project)\n proposal.status = 'applied'\n proposal.appliedAt = now\n writeJson(join(project.path, 'proposals', proposalId + '.json'), proposal)\n return { proposal, revision }\n })\n }\n\n discardProposal(projectPath: string, proposalId: string): ProposalRecord {\n return this.locked(projectPath, () => {\n const project = this.readProject(projectPath)\n const proposal = readJson<ProposalRecord>(join(project.path, 'proposals', proposalId + '.json'))\n if (proposal === null) throw new KsrwError('NOT_FOUND', '\u63D0\u6848\u4E0D\u5B58\u5728\uFF1A' + proposalId)\n // P2-4\uFF1A\u53EA\u5141\u8BB8 pending \u2192 discarded\uFF1B\u5DF2 applied \u7684\u63D0\u6848\u4E0D\u53EF\u53D8\uFF08\u64A4\u9500\u8BF7\u751F\u6210\u53CD\u5411\u63D0\u6848\uFF09\uFF0C\n // \u5DF2 discarded \u7684\u63D0\u6848\u4E0D\u53EF\u91CD\u590D\u4E22\u5F03\u3002\n if (proposal.status === 'applied') {\n throw new KsrwError('INVALID', '\u63D0\u6848\u5DF2\u5E94\u7528\uFF08applied\uFF09\uFF0C\u4E0D\u53EF\u4E22\u5F03\uFF1A\u5982\u9700\u64A4\u9500\u8BF7\u751F\u6210\u53CD\u5411\u63D0\u6848\uFF08' + proposalId + '\uFF09')\n }\n if (proposal.status !== 'pending') {\n throw new KsrwError('INVALID', '\u63D0\u6848\u5DF2\u4E22\u5F03\uFF0C\u65E0\u9700\u91CD\u590D\u64CD\u4F5C\uFF1A' + proposalId)\n }\n proposal.status = 'discarded'\n writeJson(join(project.path, 'proposals', proposalId + '.json'), proposal)\n return proposal\n })\n }\n\n appendEvidence(projectPath: string, input: Omit<EvidenceRecord, 'id' | 'createdAt'>): EvidenceRecord {\n // jsonl \u662F\u300C\u8BFB\u5168\u6587 + \u8FFD\u52A0\u300D\uFF1A\u4E24\u4E2A\u8FDB\u7A0B\u540C\u65F6\u8FFD\u52A0\u65F6\u5FC5\u987B\u4E32\u884C\uFF0C\u5426\u5219\u4E22\u8BC1\u636E\u3002\n return this.locked(projectPath, () => {\n const project = this.readProject(projectPath)\n const record: EvidenceRecord = { ...input, id: newId('ev'), createdAt: Date.now() }\n const file = join(project.path, 'evidence', 'evidence.jsonl')\n const previous = readText(file) ?? ''\n atomicWrite(file, previous + JSON.stringify(record) + '\\n')\n return record\n })\n }\n\n /** P1-3\uFF1Aevidence.jsonl \u7684\u8BFB\u7AEF\uFF1B\u5B57\u6BB5\u4E0E appendEvidence \u5199\u5165\u4E00\u81F4\u3002 */\n readEvidence(projectPath: string): EvidenceRecord[] {\n const project = this.readProject(projectPath)\n const text = readText(join(project.path, 'evidence', 'evidence.jsonl')) ?? ''\n const records: EvidenceRecord[] = []\n for (const line of text.split('\\n')) {\n if (line.trim() === '') continue\n try { records.push(JSON.parse(line) as EvidenceRecord) } catch { /* ignore a torn line */ }\n }\n return records\n }\n\n appendHandoff(projectPath: string, input: Omit<HandoffRecord, 'id' | 'createdAt' | 'status'>): HandoffRecord {\n return this.locked(projectPath, () => {\n const project = this.readProject(projectPath)\n // P2-3\uFF1A\u4E0D\u5141\u8BB8\u7ED9\u81EA\u5DF1\u6D3E\u5355\uFF1B\u540C\u4E00 (from,to,requestedOutput) \u7684\u672A\u6536\u5C3E\u5355\u5E42\u7B49\u590D\u7528\uFF0C\n // Agent \u91CD\u8BD5/\u590D\u8FF0\u4E0D\u4F1A\u5806\u51FA\u91CD\u590D\u5355\u62AC\u9AD8\u95F8\u95E8\u963B\u585E\u8BA1\u6570\u3002\n if (input.from === input.to) throw new KsrwError('INVALID', '\u4E0D\u80FD\u7ED9\u81EA\u5DF1\u6D3E\u5355\uFF1A' + input.to)\n const records = this.readHandoffs(projectPath)\n const duplicate = records.find(record =>\n (record.status === 'open' || record.status === 'started')\n && record.from === input.from\n && record.to === input.to\n && record.requestedOutput === input.requestedOutput)\n if (duplicate !== undefined) return duplicate\n const record: HandoffRecord = { ...input, id: newId('hand'), status: 'open', createdAt: Date.now() }\n const file = join(project.path, 'handoffs.jsonl')\n const previous = readText(file) ?? ''\n atomicWrite(file, previous + JSON.stringify(record) + '\\n')\n return record\n })\n }\n\n updateHandoff(\n projectPath: string,\n id: string,\n status: HandoffRecord['status'],\n note?: string,\n ): HandoffRecord {\n // jsonl \u662F\u5168\u91CF\u91CD\u5199\uFF1A\u4E24\u4E2A\u5DE5\u4F4D\u540C\u65F6\u6539\u4E0D\u540C\u4EA4\u63A5\u5355\u65F6\uFF0C\u5FC5\u987B\u4E32\u884C\u4EE5\u514D\u4E92\u76F8\u8986\u76D6\u3002\n return this.locked(projectPath, () => {\n const project = this.readProject(projectPath)\n const file = join(project.path, 'handoffs.jsonl')\n const records = this.readHandoffs(projectPath)\n const index = records.findIndex(record => record.id === id)\n if (index < 0) throw new KsrwError('NOT_FOUND', '\u4EA4\u63A5\u5355\u4E0D\u5B58\u5728\uFF1A' + id)\n const previous = records[index]!\n // \u751F\u547D\u5468\u671F\u65F6\u95F4\u6233\u6309\u76EE\u6807\u72B6\u6001\u6574\u4F53\u91CD\u7B97\uFF1A\u91CD\u6D3E/\u9000\u56DE/\u91CD\u5F00\u4E0D\u4F1A\u7559\u4E0B\u65E7 started/done \u75D5\u8FF9\u3002\n let updated: HandoffRecord = {\n ...previous,\n status,\n ...(note === undefined ? {} : { note }),\n }\n if (status === 'started') {\n updated = { ...updated, startedAt: Date.now() }\n delete updated.returnedAt\n delete updated.doneAt\n } else if (status === 'returned') {\n updated = { ...updated, returnedAt: Date.now() }\n delete updated.startedAt\n delete updated.doneAt\n } else if (status === 'open') {\n if (note === undefined) delete updated.note\n delete updated.startedAt\n delete updated.returnedAt\n delete updated.doneAt\n } else {\n updated = { ...updated, doneAt: Date.now() }\n delete updated.returnedAt\n }\n records[index] = updated\n atomicWrite(file, records.map(record => JSON.stringify(record)).join('\\n') + '\\n')\n return updated\n })\n }\n\n readHandoffs(projectPath: string): HandoffRecord[] {\n const project = this.readProject(projectPath)\n const text = readText(join(project.path, 'handoffs.jsonl')) ?? ''\n const records: HandoffRecord[] = []\n for (const line of text.split('\\n')) {\n if (line.trim() === '') continue\n try { records.push(JSON.parse(line) as HandoffRecord) } catch { /* ignore a torn line */ }\n }\n return records\n }\n\n readResearchState(project: ProjectRecord): ResearchState {\n const parsed = readJson<ResearchState>(join(project.path, 'researchState.json'))\n if (parsed !== null) return parsed\n return {\n stage: project.stage,\n summary: project.idea,\n next: '\u5199\u51FA idea \u5E76\u751F\u6210\u9009\u9898\u5361\u3002',\n blockers: [],\n confirmed: [],\n updatedAt: project.updatedAt,\n }\n }\n\n writeResearchState(project: ProjectRecord, state: ResearchState): void {\n // json/md \u4E24\u4EFD\u662F\u540C\u4E00\u72B6\u6001\u7684\u4E24\u9762\uFF0C\u5199\u4E00\u534A\u4F1A\u88AB\u8BFB\u5230\u4E0D\u4E00\u81F4\u7684\u5FEB\u7167\u3002\n this.locked(project.path, () => {\n writeJson(join(project.path, 'researchState.json'), state)\n atomicWrite(join(project.path, 'researchState.md'), renderResearchState(project, state))\n })\n }\n\n private upsertRegistry(project: ProjectRecord): void {\n // \u767B\u8BB0\u8868\u662F home \u7EA7\u5171\u4EAB\u6587\u4EF6\uFF1A\u8BFB-\u6539-\u5199\u5FC5\u987B\u8DE8\u8FDB\u7A0B\u4E92\u65A5\uFF0C\u5426\u5219\u4E24\u4E2A\u9879\u76EE\u540C\u65F6\u767B\u8BB0\u4F1A\u4E22\u4E00\u4E2A\u3002\n // \u9501\u5E8F\u56FA\u5B9A\u4E3A\u300C\u9879\u76EE\u9501 \u2192 \u767B\u8BB0\u8868\u9501\u300D\uFF0C\u4E0D\u8981\u53CD\u5411\uFF0C\u907F\u514D\u4E24\u8FDB\u7A0B\u4EA4\u53C9\u6301\u6709\u6B7B\u9501\u3002\n this.withRegistry(() => {\n // \u514D\u767B\u8BB0\u5DE5\u4F5C\u533A\u4E0D\u8FDB\u9879\u76EE\u5E93\uFF1B\u82E5\u5386\u53F2\u4E0A\u88AB\u767B\u8BB0\u8FC7\uFF08\u4F8B\u5982\u5148\u767B\u8BB0\u540E\u8F6C\u4E3A\u5DE5\u574A\uFF09\uFF0C\u987A\u624B\u6E05\u6389\u3002\n if (project.detached === true) {\n const registry = this.loadRegistry()\n if (registry.projects.some(item => item.path === project.path)) {\n this.saveRegistry({\n version: REGISTRY_VERSION,\n projects: registry.projects.filter(item => item.path !== project.path),\n })\n }\n return\n }\n const registry = this.loadRegistry()\n const entry: ProjectIndexEntry = {\n id: project.id,\n name: project.name,\n path: project.path,\n stage: project.stage,\n createdAt: project.createdAt,\n updatedAt: project.updatedAt,\n }\n const others = registry.projects.filter(item => item.path !== project.path)\n this.saveRegistry({ version: REGISTRY_VERSION, projects: [...others, entry] })\n })\n }\n}\n\nfunction readdirNames(dir: string): string[] {\n return readdirSync(dir) as string[]\n}\n\nexport function renderResearchState(project: ProjectRecord, state: ResearchState): string {\n const lines = [\n '# \u7814\u7A76\u72B6\u6001 \u00B7 ' + project.name,\n '',\n '- \u9636\u6BB5\uFF1A' + state.stage,\n '- \u66F4\u65B0\uFF1A' + new Date(state.updatedAt).toISOString(),\n '- \u6458\u8981\uFF1A' + state.summary,\n '- \u4E0B\u4E00\u6B65\uFF1A' + state.next,\n '- \u963B\u585E\uFF1A' + (state.blockers.length === 0 ? '\u65E0' : state.blockers.join('\uFF1B')),\n '- \u5DF2\u786E\u8BA4\uFF1A' + (state.confirmed.length === 0 ? '\u65E0' : state.confirmed.join('\uFF1B')),\n '',\n ]\n return lines.join('\\n')\n}\n\n/** \u4F9B\u5DE5\u5177/RPC \u590D\u7528\u7684\u9879\u76EE\u6839\u63A8\u5BFC\uFF08\u76EE\u5F55\u5373\u9879\u76EE\uFF09\u3002 */\nexport function projectManifestPath(projectPath: string): string {\n return join(resolve(projectPath), 'project.json')\n}\n\n/** \u5220\u9664\u9879\u76EE\u53EA\u5220\u767B\u8BB0\uFF0C\u4E0D\u5220\u7528\u6237\u76EE\u5F55\uFF08\u7528\u6237\u81EA\u5DF1\u7BA1\u7684\u6587\u4EF6\u5939\uFF09\u3002 */\nexport function forgetProject(home: string, projectPath: string): void {\n withRegistryLock(home, () => {\n const registryFile = join(resolve(home), 'projects.json')\n const parsed = readJson<Registry>(registryFile)\n if (parsed === null) return\n const target = resolve(projectPath)\n writeJson(registryFile, {\n version: REGISTRY_VERSION,\n projects: parsed.projects.filter(item => resolve(item.path) !== target),\n })\n // \u6CE8\u610F\uFF1A\u4E0D\u80FD\u987A\u624B\u5220 `<project>/.ksrw.lock`\u3002\u90A3\u53EF\u80FD\u662F\u522B\u7684\u8FDB\u7A0B\u6B63\u6301\u6709\u7684\u6D3B\u9501\uFF0C\n // \u5220\u6389\u4F1A\u8BA9\u4E24\u4E2A\u5199\u5165\u8005\u540C\u65F6\u8FDB\u4E34\u754C\u533A\u3002\u9501\u53EA\u5728\u6301\u6709\u8005\u91CA\u653E\u65F6\u7531 lock.ts \u81EA\u5DF1\u6E05\u7406\u3002\n })\n}\n", "/** \u8BBA\u6587/\u4E13\u5229\u6210\u679C\u7F16\u8BD1\u4E0E\u5BFC\u51FA\uFF1Apandoc\uFF08md\u2192pdf/docx\uFF09\u4E0E latexmk\uFF08tex\u2192pdf\uFF09\u3002\n *\n * \u7F16\u8BD1\u4EA7\u7269\u4E0E\u65E5\u5FD7\u5168\u90E8\u843D\u76D8\u5728\u9879\u76EE\u76EE\u5F55\u5185\uFF1B\u5DE5\u5177\u7F3A\u5931\u65F6\u7ED9\u51FA\u53EF\u6267\u884C\u7684\u9519\u8BEF\uFF0C\n * \u4E0D\u5047\u88C5\u7F16\u8BD1\u6210\u529F\u3002UI\uFF08/ksrw/api\uFF09\u4E0E agent \u5DE5\u5177\uFF08ksrw_compile\uFF09\u5171\u7528\u540C\u4E00\u5B9E\u73B0\u3002\n */\nimport { spawnSync } from 'node:child_process'\nimport { homedir } from 'node:os'\nimport { createHash } from 'node:crypto'\nimport { existsSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs'\nimport { delimiter, extname, join, resolve, sep } from 'node:path'\n\nimport AdmZip from 'adm-zip'\n\nimport { KsrwError } from './error.ts'\nimport { paperTemplateFor } from './paper-templates.ts'\n\n/** \u672C\u673A\u7F16\u8BD1\u5DE5\u5177\u53EF\u7528\u6027\u3002 */\nexport interface PaperTools {\n readonly pandoc: boolean\n readonly latexmk: boolean\n readonly xelatex: boolean\n /** LibreOffice\uFF1A\u6CA1\u6709 TeX \u65F6\u7528\u5B83\u628A pandoc \u4EA7\u51FA\u7684 docx \u8F6C\u6210 PDF\u3002 */\n readonly soffice: boolean\n}\n\n/** \u4E00\u6B21\u5916\u90E8\u5DE5\u5177\u8C03\u7528\u7684\u6700\u5C0F\u7ED3\u679C\u3002 */\nexport interface PaperRunResult {\n readonly status: number | null\n readonly stdout?: string\n readonly stderr?: string\n}\n\n/** \u5DE5\u5177\u63A2\u6D4B\uFF08\u53EF\u6CE8\u5165\u6D4B\u8BD5\uFF09\u3002 */\nexport type ToolProbe = (tool: string) => string | undefined\n/** \u7F16\u8BD1\u6267\u884C\u5668\uFF08\u53EF\u6CE8\u5165\u6D4B\u8BD5\uFF09\u3002 */\nexport type PaperRunner = (tool: string, args: readonly string[], cwd: string) => PaperRunResult\n\nexport const defaultToolProbe: ToolProbe = (tool) => {\n const probe = process.platform === 'win32' ? 'where' : 'which'\n const found = spawnSync(probe, [tool], { encoding: 'utf8' })\n if (found.status !== 0) return undefined\n return found.stdout.split('\\n').map(line => line.trim()).find(line => line !== '')\n}\n\nexport const defaultPaperRunner: PaperRunner = (tool, args, cwd) => spawnSync(tool, [...args], {\n cwd, encoding: 'utf8', timeout: 180_000, maxBuffer: 4 * 1024 * 1024,\n})\n\nexport interface PaperIo {\n readonly probe?: ToolProbe\n readonly runner?: PaperRunner\n}\n\n/** \u63A2\u6D4B\u672C\u673A\u7F16\u8BD1\u5DE5\u5177\u3002 */\nexport function paperTools(io: PaperIo = {}): PaperTools {\n const probe = io.probe ?? defaultToolProbe\n return {\n pandoc: probe('pandoc') !== undefined,\n latexmk: probe('latexmk') !== undefined,\n xelatex: probe('xelatex') !== undefined,\n soffice: probe('soffice') !== undefined,\n }\n}\n\n/**\n * \u6392\u7248\u6A21\u677F\u7684\u4E24\u6761\u751F\u4EA7\u7EBF\uFF08\u6A21\u677F\u6587\u4EF6\u672C\u8EAB\u968F\u5305\uFF0C\u751F\u6210\u5668\u53EA\u5728\u91CD\u5EFA\u6A21\u677F\u65F6\u8DD1\uFF09\uFF1A\n * \u00B7 \u4F1A\u8BAE/\u671F\u520A\u6A21\u677F `public/templates/<id>.docx` \u2190 `scripts/build-paper-templates.py`\n * \u8BFB `scripts/paper-templates.json`\uFF08\u771F\u6E90\u89C1 paper-templates.ts \u9876\u90E8\uFF09\uFF1B\n * \u00B7 \u65E7\u7248\u5355\u4E00\u6A21\u677F `public/reference-academic.docx` \u2190 `scripts/build-paper-template.py`\n * \uFF08\u5355\u6570\uFF0C\u9700\u8981 pandoc + python-docx\uFF09\uFF0C\u4EC5\u4F5C\u4E3A\u5B58\u91CF\u9879\u76EE\u7684\u9000\u56DE\u8DEF\u5F84\u3002\n */\n\n\n/**\n * pandoc \u7684 Word \u6A21\u677F\u628A\u4EE3\u7801\u5B57\u4F53\u8BBE\u6210 Consolas\uFF08Windows \u4E13\u6709\uFF09\u3002\n * \u5728 macOS \u4E0A\u9884\u89C8 docx \u4F1A\u56E0\u6B64\u62A5\u300C\u7F3A\u5B57\u4F53\u300D\uFF0C\u6240\u4EE5\u5BFC\u51FA\u540E\u628A\u4EE3\u7801\u5B57\u4F53\u6362\u6210\u672C\u673A\u7B49\u5BBD\u5B57\u4F53\u3002\n */\nexport function patchDocxFonts(docxPath: string): string[] {\n const mono = process.platform === 'darwin' ? 'Menlo' : process.platform === 'linux' ? 'DejaVu Sans Mono' : undefined\n if (mono === undefined) return []\n if (!existsSync(docxPath)) return []\n try {\n const zip = new AdmZip(docxPath)\n const changed: string[] = []\n for (const entry of zip.getEntries()) {\n if (!/^word\\/(styles|document|numbering|theme\\/theme1)\\.xml$/.test(entry.entryName)) continue\n const text = entry.getData().toString('utf8')\n if (!text.includes('Consolas')) continue\n zip.updateFile(entry.entryName, Buffer.from(text.split('Consolas').join(mono), 'utf8'))\n changed.push(entry.entryName)\n }\n if (changed.length > 0) zip.writeZip(docxPath)\n return changed\n } catch { return [] }\n}\n\n/** \u6E90\u6587\u4EF6\u76F8\u5BF9\u8DEF\u5F84\u9ED8\u8BA4\u503C\u3002 */\nexport function defaultPaperSource(dir: 'paper' | 'patent'): string {\n return dir === 'patent' ? 'patent/disclosure.md' : 'paper/main.md'\n}\n\n/** \u89E3\u6790\u5E76\u5B88\u536B\u6E90\u6587\u4EF6\u8DEF\u5F84\uFF1A\u5FC5\u987B\u5728\u9879\u76EE\u76EE\u5F55\u5185\u3002 */\nexport function resolvePaperSource(projectPath: string, source: string): string {\n const absolute = resolve(projectPath, source)\n if (absolute !== projectPath && !absolute.startsWith(projectPath + sep)) {\n throw new KsrwError('INVALID', '\u6E90\u6587\u4EF6\u5FC5\u987B\u5728\u9879\u76EE\u76EE\u5F55\u5185')\n }\n return absolute\n}\n\n/** \u6E90\u6587\u4EF6\u6240\u5728\u76EE\u5F55\u4E0E\u6587\u4EF6\u540D\uFF1Apandoc/latexmk \u4EE5\u8BE5\u76EE\u5F55\u4E3A cwd\uFF0C\u56FE\u624D\u80FD\u6309\u76F8\u5BF9\u8DEF\u5F84\u89E3\u6790\u3002 */\nfunction sourceParts(absolute: string): { dir: string; name: string } {\n const name = absolute.split(sep).pop() ?? absolute\n return { dir: absolute.slice(0, absolute.length - name.length), name }\n}\n\nfunction toolOf(source: string): 'pandoc' | 'latexmk' {\n const extension = extname(source).toLowerCase()\n if (extension === '.tex') return 'latexmk'\n if (extension === '.md' || extension === '.markdown') return 'pandoc'\n throw new KsrwError('INVALID', '\u53EA\u652F\u6301 .md \u6216 .tex\uFF1A' + source)\n}\n\n/** \u7F16\u8BD1\u72B6\u6001\uFF1A\u6E90\u6587\u4EF6\u5B58\u5728\u6027 + \u5DE5\u5177\u53EF\u7528\u6027 + \u662F\u5426\u53EF\u7F16\u8BD1\u51FA PDF\u3002 */\nexport function paperStatus(projectPath: string, source: string, io: PaperIo = {}): {\n source: string\n exists: boolean\n tool: string\n available: boolean\n ready: boolean\n pdfEngine: 'xelatex' | 'soffice' | 'none'\n tools: PaperTools\n /** \u5B66\u672F\u6392\u7248\u6A21\u677F\u662F\u5426\u5C31\u7EEA\uFF08reference-academic.docx\uFF09\u3002 */\n template: boolean\n} {\n const absolute = resolvePaperSource(projectPath, source)\n const tool = toolOf(source)\n const tools = paperTools(io)\n const available = tool === 'latexmk' ? tools.latexmk : tools.pandoc\n // Markdown can still produce a PDF without TeX: pandoc \u2192 docx \u2192 LibreOffice.\n const pdfEngine: 'xelatex' | 'soffice' | 'none' = tool === 'latexmk'\n ? (tools.latexmk ? 'xelatex' : 'none')\n : (!tools.pandoc ? 'none' : (tools.xelatex ? 'xelatex' : (tools.soffice ? 'soffice' : 'none')))\n return {\n source,\n exists: existsSync(absolute),\n tool,\n available,\n ready: pdfEngine !== 'none',\n pdfEngine,\n tools,\n template: paperTemplateFor(projectPath) !== null,\n }\n}\n\nfunction logTail(run: PaperRunResult): string {\n return ((run.stdout ?? '') + (run.stderr ?? '')).trim().slice(-4000)\n}\n\n/** \u628A Markdown \u7F16\u8BD1\u6210 Word\uFF08pandoc -o docx\uFF09\uFF1B\u8FD4\u56DE docx \u76F8\u5BF9\u8DEF\u5F84\u3002 */\nexport function exportPaperDocx(projectPath: string, source: string, io: PaperIo = {}): {\n docx: string\n bytes: number\n log: string\n} {\n const absolute = resolvePaperSource(projectPath, source)\n if (!existsSync(absolute)) throw new KsrwError('NOT_FOUND', '\u6E90\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A' + source)\n const tool = toolOf(source)\n if (tool !== 'pandoc') throw new KsrwError('INVALID', 'Word \u5BFC\u51FA\u53EA\u652F\u6301 Markdown \u6E90\uFF08.md\uFF09\uFF1BLaTeX \u8BF7\u5148\u7F16\u8BD1 PDF')\n const tools = paperTools(io)\n if (!tools.pandoc) throw new KsrwError('ENGINE_MISSING', '\u7F3A\u5C11 pandoc\uFF1B\u5148\u5B89\u88C5 pandoc \u518D\u5BFC\u51FA Word')\n const output = absolute.replace(/\\.md(own|markdown)?$/i, '.docx')\n const runner = io.runner ?? defaultPaperRunner\n const template = paperTemplateFor(projectPath)\n const styleArgs = [\n ...(template === null ? [] : ['--reference-doc=' + template]),\n '--syntax-highlighting=none',\n ]\n const run = runner('pandoc', ['--standalone', source, '-o', output, ...styleArgs], projectPath)\n const log = logTail(run)\n if (run.status !== 0) throw new KsrwError('COMPILE_FAILED', 'pandoc \u5BFC\u51FA Word \u5931\u8D25\uFF08\u9000\u51FA\u7801 ' + String(run.status) + '\uFF09\\n' + log)\n if (!existsSync(output)) throw new KsrwError('COMPILE_FAILED', '\u672A\u751F\u6210 docx\uFF1A' + output + '\\n' + log)\n patchDocxFonts(output)\n writePaperBuild(projectPath, source.startsWith('patent/') ? 'patent' : 'paper', {\n docx: output.slice(projectPath.length + 1), docxBytes: statSync(output).size,\n docxSourceHash: hashFile(absolute) ?? '', docxExportedAt: Date.now(),\n })\n return { docx: output.slice(projectPath.length + 1), bytes: statSync(output).size, log }\n}\n\n/** \u7F16\u8BD1\u8BBA\u6587/\u4EA4\u5E95\u4E66\u4E3A PDF\uFF1Amd \u8D70 pandoc+xelatex\uFF0Ctex \u8D70 latexmk\u3002 */\nexport function compilePaper(projectPath: string, source: string, io: PaperIo = {}): {\n pdf: string\n bytes: number\n tool: string\n log: string\n} {\n const absolute = resolvePaperSource(projectPath, source)\n if (!existsSync(absolute)) throw new KsrwError('NOT_FOUND', '\u6E90\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A' + source)\n const tool = toolOf(source)\n const tools = paperTools(io)\n const runner = io.runner ?? defaultPaperRunner\n if (tool === 'latexmk' && !tools.latexmk) throw new KsrwError('ENGINE_MISSING', '\u7F3A\u5C11 latexmk\uFF1B\u5B89\u88C5 TeX \u540E\u518D\u7F16\u8BD1 .tex')\n if (tool === 'pandoc') {\n if (!tools.pandoc) throw new KsrwError('ENGINE_MISSING', '\u7F3A\u5C11 pandoc\uFF1B\u5148\u5B89\u88C5 pandoc')\n // \u6CA1\u6709 TeX \u65F6\u964D\u7EA7\uFF1Apandoc \u4EA7\u51FA docx\uFF0C\u518D\u7531 LibreOffice \u8F6C PDF\uFF08\u6392\u7248\u7565\u7B80\uFF0C\u4F46\u53EF\u7528\uFF09\u3002\n if (!tools.xelatex) return compileViaOffice(projectPath, absolute, source, io, runner)\n }\n const output = absolute.replace(/\\.(md|markdown|tex)$/i, '.pdf')\n const parts = sourceParts(absolute)\n // \u4EE5\u6E90\u6587\u4EF6\u76EE\u5F55\u4E3A cwd\uFF1A\u6B63\u6587\u91CC\u7684 figures/x.png \u624D\u80FD\u88AB pandoc/latexmk \u627E\u5230\u3002\n const resourcePath = parts.dir + delimiter + projectPath\n const args = tool === 'latexmk'\n ? ['-pdf', '-interaction=nonstopmode', '-halt-on-error', parts.name]\n : ['--standalone', parts.name, '-o', output, '--pdf-engine=xelatex', '--resource-path=' + resourcePath]\n const run = runner(tool, args, parts.dir)\n const log = logTail(run)\n if (run.status !== 0) throw new KsrwError('COMPILE_FAILED', tool + ' \u9000\u51FA\u7801 ' + String(run.status) + '\\n' + log)\n if (!existsSync(output)) throw new KsrwError('COMPILE_FAILED', '\u672A\u751F\u6210 PDF\uFF1A' + output.slice(projectPath.length + 1) + '\\n' + log)\n const finalPdf = output.slice(projectPath.length + 1)\n writePaperBuild(projectPath, source.startsWith('patent/') ? 'patent' : 'paper', {\n source, sourceHash: hashFile(absolute) ?? '', pdf: finalPdf, pdfBytes: statSync(output).size,\n })\n return { pdf: finalPdf, bytes: statSync(output).size, tool, log }\n}\n\n/** \u65E0 TeX \u7684 PDF \u8DEF\u5F84\uFF1Apandoc md \u2192 docx\uFF0C\u518D soffice \u2192 pdf\u3002 */\nfunction compileViaOffice(\n projectPath: string,\n absolute: string,\n source: string,\n io: PaperIo,\n runner: PaperRunner,\n): { pdf: string; bytes: number; tool: string; log: string } {\n const probe = io.probe ?? defaultToolProbe\n const soffice = probe('soffice')\n if (soffice === undefined) {\n throw new KsrwError('ENGINE_MISSING', '\u7F3A\u5C11 PDF \u5F15\u64CE\uFF1A\u5B89\u88C5 TeX\uFF08xelatex\uFF09\u6216\u7528 pandoc + LibreOffice\uFF08soffice\uFF09')\n }\n const docx = absolute.replace(/\\.md(own|markdown)?$/i, '.docx')\n const pdf = absolute.replace(/\\.md(own|markdown)?$/i, '.pdf')\n const parts = sourceParts(absolute)\n const resourcePath = parts.dir + delimiter + projectPath\n const template = paperTemplateFor(projectPath)\n const styleArgs = [\n ...(template === null ? [] : ['--reference-doc=' + template]),\n '--syntax-highlighting=none',\n ]\n const pandoc = runner('pandoc', ['--standalone', parts.name, '-o', docx, '--resource-path=' + resourcePath, ...styleArgs], parts.dir)\n if (pandoc.status !== 0) throw new KsrwError('COMPILE_FAILED', 'pandoc \u751F\u6210 docx \u5931\u8D25\uFF08\u9000\u51FA\u7801 ' + String(pandoc.status) + '\uFF09\\n' + logTail(pandoc))\n if (!existsSync(docx)) throw new KsrwError('COMPILE_FAILED', '\u672A\u751F\u6210 docx\uFF1A' + docx.slice(projectPath.length + 1))\n patchDocxFonts(docx)\n // \u65E0\u5934\u8F6C\u6362\u7ED9\u4E00\u4E2A\u72EC\u7ACB profile\uFF1A\u907F\u514D\u4E0E\u7528\u6237\u6B63\u5728\u7528\u7684 LibreOffice \u62A2\u9501/\u6539\u914D\u7F6E\uFF0C\n // \u4E5F\u8BA9\u53D7\u9650\u73AF\u5883\uFF08HOME \u53EA\u8BFB\uFF09\u80FD\u628A profile \u6307\u5230\u53EF\u5199\u76EE\u5F55\u3002\n const profile = process.env['KSRW_SOFFICE_PROFILE']?.trim()\n const convert = runner(soffice, [\n '--headless',\n ...(profile === undefined || profile === '' ? [] : ['-env:UserInstallation=file://' + resolve(profile)]),\n '--convert-to', 'pdf', '--outdir', parts.dir, docx,\n ], projectPath)\n const log = logTail(convert)\n if (convert.status !== 0) throw new KsrwError('COMPILE_FAILED', 'LibreOffice \u8F6C PDF \u5931\u8D25\uFF08\u9000\u51FA\u7801 ' + String(convert.status) + '\uFF09\\n' + log)\n if (!existsSync(pdf)) throw new KsrwError('COMPILE_FAILED', '\u672A\u751F\u6210 PDF\uFF1A' + pdf.slice(projectPath.length + 1) + '\\n' + log)\n const finalPdf = pdf.slice(projectPath.length + 1)\n writePaperBuild(projectPath, source.startsWith('patent/') ? 'patent' : 'paper', {\n source, sourceHash: hashFile(absolute) ?? '', pdf: finalPdf, pdfBytes: statSync(pdf).size,\n })\n return { pdf: finalPdf, bytes: statSync(pdf).size, tool: 'pandoc+soffice', log }\n}\n\n/** \u5B9A\u7A3F\u5370\u8BB0\uFF1APDF \u7531\u54EA\u4E00\u7248\u6E90\u6587\u4EF6\u751F\u6210\uFF0C\u7528\u6765\u5224\u65AD\u300C\u8BBA\u6587\u6539\u8FC7\u3001PDF \u5DF2\u8FC7\u671F\u300D\u3002 */\nexport interface PaperBuildRecord {\n readonly source: string\n readonly sourceHash: string\n readonly pdf: string\n readonly pdfBytes: number\n readonly docx?: string\n readonly docxBytes?: number\n /** \u5BFC\u51FA Word \u65F6\u6B63\u6587\u7684\u6307\u7EB9\u4E0E\u65F6\u95F4\uFF1A\u7528\u6765\u63D0\u793A\u300C\u8349\u7A3F\u5DF2\u8FC7\u671F\u300D\u3002 */\n readonly docxSourceHash?: string\n readonly docxExportedAt?: number\n readonly generatedAt: number\n /** \u6B63\u6587\u6539\u8FC7\u4E4B\u540E\u65E7\u5B9A\u7A3F\u88AB\u5220\u9664\u7684\u65F6\u95F4\uFF08\u7528\u4E8E\u7ED9\u51FA\u51C6\u786E\u63D0\u793A\uFF09\u3002 */\n readonly invalidatedAt?: number\n /** \u6E90\u6587\u4EF6\u6682\u65F6\u8BFB\u4E0D\u5230\uFF08\u88AB\u79FB\u52A8/\u6743\u9650\u5F02\u5E38\uFF09\u7684\u65F6\u95F4\uFF1A\u6B64\u65F6\u4FDD\u7559 PDF\uFF0C\u53EA\u6807\u8BB0\u4E0D\u53EF\u8BFB\u3002 */\n readonly unreadableAt?: number\n}\n\nconst BUILD_FILE = '.build.json'\n\nfunction buildFile(projectPath: string, dir: 'paper' | 'patent'): string {\n return join(projectPath, dir, BUILD_FILE)\n}\n\nfunction hashFile(absolute: string): string | undefined {\n try { return createHash('sha256').update(readFileSync(absolute)).digest('hex').slice(0, 16) } catch { return undefined }\n}\n\n/** \u8BFB\u5B9A\u7A3F\u5370\u8BB0\uFF08\u6CA1\u6709\u5219 undefined\uFF09\u3002 */\nexport function readPaperBuild(projectPath: string, dir: 'paper' | 'patent'): PaperBuildRecord | undefined {\n try {\n const parsed = JSON.parse(readFileSync(buildFile(projectPath, dir), 'utf8')) as Partial<PaperBuildRecord>\n if (typeof parsed.source !== 'string' || typeof parsed.pdf !== 'string') return undefined\n return {\n source: parsed.source,\n sourceHash: typeof parsed.sourceHash === 'string' ? parsed.sourceHash : '',\n pdf: parsed.pdf,\n pdfBytes: typeof parsed.pdfBytes === 'number' ? parsed.pdfBytes : 0,\n ...(typeof parsed.docx === 'string' ? { docx: parsed.docx } : {}),\n ...(typeof parsed.docxBytes === 'number' ? { docxBytes: parsed.docxBytes } : {}),\n ...(typeof parsed.docxSourceHash === 'string' ? { docxSourceHash: parsed.docxSourceHash } : {}),\n ...(typeof parsed.docxExportedAt === 'number' ? { docxExportedAt: parsed.docxExportedAt } : {}),\n ...(typeof parsed.invalidatedAt === 'number' ? { invalidatedAt: parsed.invalidatedAt } : {}),\n ...(typeof parsed.unreadableAt === 'number' ? { unreadableAt: parsed.unreadableAt } : {}),\n generatedAt: typeof parsed.generatedAt === 'number' ? parsed.generatedAt : 0,\n }\n } catch { return undefined }\n}\n\nfunction writePaperBuild(projectPath: string, dir: 'paper' | 'patent', patch: Partial<PaperBuildRecord>): void {\n const previous = readPaperBuild(projectPath, dir) ?? { source: '', sourceHash: '', pdf: '', pdfBytes: 0, generatedAt: 0 }\n const next: PaperBuildRecord = { ...previous, ...patch, generatedAt: Date.now() } as PaperBuildRecord\n try { writeFileSync(buildFile(projectPath, dir), JSON.stringify(next, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 }) } catch { /* \u5370\u8BB0\u5199\u5931\u8D25\u4E0D\u963B\u585E\u7F16\u8BD1 */ }\n}\n\n/**\n * \u6B63\u6587\u4E00\u6539\u5C31\u628A\u65E7\u5B9A\u7A3F PDF \u5220\u6389\uFF1A\u5B9A\u7A3F\u53EA\u4EE3\u8868\u300C\u5F53\u524D\u8FD9\u4E00\u7248\u6B63\u6587\u300D\u3002\n * @returns \u662F\u5426\u5220\u9664\u4E86\u8FC7\u671F PDF\u3002\n */\nexport function invalidateStalePaperFinal(projectPath: string, dir: 'paper' | 'patent'): boolean {\n const build = readPaperBuild(projectPath, dir)\n if (build === undefined || build.pdf === '') return false\n const source = build.source === '' ? defaultPaperSource(dir) : build.source\n const current = hashFile(resolvePaperSource(projectPath, source))\n if (current !== undefined && current === build.sourceHash) return false\n if (current === undefined) {\n // P2-21\uFF1A\u6E90\u6587\u4EF6\u8BFB\u4E0D\u5230\uFF08\u4E34\u65F6\u6539\u540D/\u6743\u9650/\u76EE\u5F55\u79FB\u52A8\uFF09\u4E0D\u7B49\u4E8E\u6B63\u6587\u6539\u8FC7\u2014\u2014\u4E0D\u8981\u5220\u5DF2\u7F16\u8BD1\u7684\n // \u5B9A\u7A3F PDF\uFF0C\u53EA\u8BB0\u5F55\u4E0D\u53EF\u8BFB\u65F6\u95F4\uFF1BpaperFinalStatus \u4ECD\u4F1A\u628A\u5B83\u6807\u4E3A\u8FC7\u671F\u5E76\u63D0\u793A\u91CD\u65B0\u7F16\u8BD1\u3002\n const unreadable: PaperBuildRecord = { ...build, unreadableAt: Date.now() }\n try { writeFileSync(buildFile(projectPath, dir), JSON.stringify(unreadable, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 }) } catch { /* ignore */ }\n return true\n }\n try { rmSync(join(projectPath, build.pdf), { force: true }) } catch { /* \u5220\u4E0D\u6389\u5C31\u7559\u7740\uFF0C\u53CD\u6B63\u5370\u8BB0\u5DF2\u5931\u6548 */ }\n const next: PaperBuildRecord = {\n ...build, pdf: '', pdfBytes: 0, sourceHash: current ?? build.sourceHash, invalidatedAt: Date.now(),\n }\n try { writeFileSync(buildFile(projectPath, dir), JSON.stringify(next, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 }) } catch { /* ignore */ }\n return true\n}\n\n/** \u5B9A\u7A3F\u72B6\u6001\uFF1A\u6709\u6CA1\u6709 PDF \u5370\u8BB0\u3001\u6E90\u6587\u4EF6\u6539\u8FC7\u4E4B\u540E\u662F\u5426\u8FC7\u671F\u3002 */\nexport function paperFinalStatus(projectPath: string, dir: 'paper' | 'patent'): { build?: PaperBuildRecord; stale: boolean; draftStale: boolean } {\n const build = readPaperBuild(projectPath, dir)\n const source = build !== undefined && build.source !== '' ? build.source : defaultPaperSource(dir)\n const current = hashFile(resolvePaperSource(projectPath, source))\n const draftStale = build?.docx !== undefined && build.docxSourceHash !== current\n // \u53EA\u5BFC\u51FA\u8FC7 Word \u4E0D\u7B97\u5B9A\u7A3F\uFF1A\u5FC5\u987B\u771F\u7684\u6709 PDF\u3002\n if (build === undefined || build.pdf === '' || !existsSync(join(projectPath, build.pdf))) return { stale: true, draftStale }\n return { build, stale: current === undefined || current !== build.sourceHash, draftStale }\n}\n\nexport interface PaperOutput {\n readonly name: string\n readonly path: string\n readonly bytes: number\n readonly updatedAt: number\n}\n\nconst OUTPUT_EXTS = new Set(['.md', '.markdown', '.tex', '.pdf', '.docx', '.log'])\n\n/** \u5217\u51FA\u6210\u679C\u76EE\u5F55\u4E0B\u53EF\u9884\u89C8/\u7F16\u8BD1\u7684\u6587\u4EF6\uFF08\u5E73\u94FA\u4E00\u5C42\uFF0C\u6309\u4FEE\u6539\u65F6\u95F4\u5012\u5E8F\uFF09\u3002 */\nexport function listPaperOutputs(projectPath: string, dir: 'paper' | 'patent'): PaperOutput[] {\n const root = resolvePaperSource(projectPath, dir)\n if (!existsSync(root)) return []\n const outputs: PaperOutput[] = []\n for (const name of readdirSync(root)) {\n if (name.startsWith('.')) continue\n const absolute = join(root, name)\n let info\n try { info = statSync(absolute) } catch { continue }\n if (!info.isFile()) continue\n if (!OUTPUT_EXTS.has(extname(name).toLowerCase())) continue\n outputs.push({ name, path: dir + '/' + name, bytes: info.size, updatedAt: info.mtimeMs })\n }\n return outputs.sort((left, right) => right.updatedAt - left.updatedAt)\n}\n\n/** \u8BFB\u53D6\u9879\u76EE\u5185\u6587\u4EF6\u7684\u5B57\u8282\uFF08\u9884\u89C8\u7528\uFF09\uFF1B\u5B88\u536B\u8DEF\u5F84\u5E76\u9650\u5236\u5927\u5C0F\u3002 */\nexport function readProjectFileBytes(projectPath: string, file: string, maxBytes: number): Uint8Array {\n const absolute = resolvePaperSource(projectPath, file)\n let info\n try { info = statSync(absolute) } catch {\n throw new KsrwError('NOT_FOUND', '\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A' + file)\n }\n if (!info.isFile()) throw new KsrwError('INVALID', '\u4E0D\u662F\u5E38\u89C4\u6587\u4EF6\uFF1A' + file)\n if (info.size > maxBytes) {\n throw new KsrwError('INVALID', '\u6587\u4EF6\u8D85\u8FC7\u9884\u89C8\u5927\u5C0F\u4E0A\u9650\uFF08' + Math.round(maxBytes / 1024 / 1024) + 'MiB\uFF09\uFF1A' + file)\n }\n return new Uint8Array(readFileSync(absolute))\n}\n", "/** \u514D\u9879\u76EE\u5B9E\u9A8C\u5DE5\u574A\uFF08standalone\uFF09\uFF1A\u4E0D\u5EFA\u9879\u76EE\uFF0C\u76F4\u63A5\u4E0A\u4F20\u8BBA\u6587 \u2192 \u62BD\u53D6\u5B9E\u9A8C\u8981\u7D20 \u2192 \u8865\u5B9E\u9A8C\u8BBE\u8BA1 \u2192 \u8FD0\u884C \u2192 \u62A5\u544A\u3002\n *\n * \u843D\u76D8\uFF08<KSRW_HOME>/standalone/<id>/\uFF0C\u9ED8\u8BA4 ~/.ksrw/standalone\uFF09\uFF1A\n * standalone.json \u5DE5\u574A\u6E05\u5355\uFF08id / \u540D\u79F0 / \u8BBA\u6587\u5143\u4FE1\u606F\uFF09\n * project.json detached \u9879\u76EE\u8BB0\u5F55\uFF1A\u590D\u7528\u63D0\u6848/\u5DE5\u4EF6/\u5DE5\u5177\uFF0C\u4F46\u4E0D\u8FDB\u9879\u76EE\u5E93\n * paper/attachment.* \u539F\u59CB\u9644\u4EF6\uFF08PDF / DOCX / MD / TXT\uFF09\n * paper/paper.md \u89C4\u8303\u5316\u5168\u6587\uFF08PDF \u6309\u300C## \u7B2C N \u9875\u300D\u5206\u9875\uFF09\n * paper/analysis.json \u5B9E\u9A8C\u8981\u7D20\u62BD\u53D6\u7ED3\u679C\n * experiments/record.md experiment \u5DE5\u4EF6 = \u5B9E\u9A8C\u8BBE\u8BA1\uFF08\u63D0\u6848\u9A71\u52A8\uFF0C\u7528\u6237 apply \u540E\u6743\u5A01\uFF09\n * experiments/code/*.py \u53EF\u6267\u884C\u811A\u672C\uFF08Agent \u53EA\u7ECF ksrw_experiment(script) \u5199\u5165\uFF09\n * experiments/runs/<id>/ \u590D\u7528\u65E2\u6709 run \u843D\u76D8\u683C\u5F0F\n * experiments/report.md \u6309\u771F\u5B9E run \u751F\u6210\u7684\u62A5\u544A\uFF08\u4E0D\u7F16\u9020\u6307\u6807\uFF09\n */\nimport { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs'\nimport { join, resolve, sep } from 'node:path'\n\nimport {\n MAX_ATTACHMENT_BYTES,\n attachmentKindOf,\n cleanText,\n detectTitle,\n extractAttachment,\n looksLikeByline,\n normalizeWithPageMarkers,\n safeAttachmentName,\n splitNormalizedPages,\n type PaperKind,\n} from './attachment.ts'\nimport { KsrwError } from './error.ts'\nimport { withProjectLock } from './lock.ts'\nimport { ProjectStore, forgetProject, resolveKsrwHome, type ProjectRecord } from './workspace.ts'\n\nexport const STANDALONE_DIR = 'standalone'\nexport const MAX_PAPER_BYTES = MAX_ATTACHMENT_BYTES\nexport const MAX_SCRIPT_BYTES = 256 * 1024\nconst MAX_PAPER_TEXT = 200_000\n\nexport type StandaloneStatus = 'empty' | 'paper' | 'analyzed' | 'designed' | 'running' | 'ran' | 'failed'\nexport type { PaperKind } from './attachment.ts'\n// \u517C\u5BB9\u65E7\u8C03\u7528\uFF08\u6D4B\u8BD5/\u5185\u6838\u5916\u4F7F\u7528\uFF09\uFF1ADOCX \u89E3\u6790\u5DF2\u62BD\u5230 attachment.ts\u3002\nexport { docxToText } from './attachment.ts'\nexport type ElementKey = 'task' | 'method' | 'dataset' | 'metric' | 'baseline' | 'limitation'\n\nexport interface PaperMeta {\n fileName: string\n kind: PaperKind\n bytes: number\n uploadedAt: number\n pages: number\n chars: number\n title: string\n}\n\nexport interface ElementItem {\n text: string\n /** \u8BC1\u636E\u4F4D\u7F6E\uFF1A\u7B2C 3 \u9875 / \u6458\u8981 / \u6B63\u6587\u3002 */\n source: string\n}\n\nexport interface ExperimentElement {\n key: ElementKey\n label: string\n items: ElementItem[]\n}\n\nexport interface PaperAnalysis {\n analyzedAt: number\n title: string\n abstract: string\n keywords: string[]\n sections: { heading: string; chars: number }[]\n elements: ExperimentElement[]\n missing: { key: string; label: string; hint: string }[]\n warnings: string[]\n}\n\nexport interface StandaloneManifest {\n schemaVersion: 1\n id: string\n name: string\n createdAt: number\n paper: PaperMeta | null\n}\n\nexport interface StandaloneSummary {\n id: string\n name: string\n path: string\n createdAt: number\n updatedAt: number\n status: StandaloneStatus\n paperName: string\n pages: number\n chars: number\n runCount: number\n designRevision: number\n}\n\nexport interface StandaloneDesign {\n revision: number\n updatedAt: number\n content: string\n}\n\nexport interface StandaloneRunSummary {\n id: string\n script: string\n status: 'running' | 'succeeded' | 'failed' | 'cancelled'\n startedAt: number\n durationMs: number\n exitCode: number | null\n metrics: Record<string, number | string>\n artifacts: string[]\n python?: string\n}\n\nexport interface StandaloneScript {\n relative: string\n title: string\n mtime: number\n}\n\nexport interface StandaloneDetail {\n experiment: StandaloneSummary & {\n paper: PaperMeta | null\n analysis: PaperAnalysis | null\n designRevision: number\n projectStage: string\n }\n design: StandaloneDesign\n paperText: string\n scripts: StandaloneScript[]\n runs: StandaloneRunSummary[]\n report: string\n}\n\nexport function resolveStandaloneRoot(home: string = resolveKsrwHome()): string {\n return join(resolve(home), STANDALONE_DIR)\n}\n\nfunction readJson<T>(file: string): T | null {\n try { return JSON.parse(readFileSync(file, 'utf8')) as T } catch { return null }\n}\n\nfunction readText(file: string): string | null {\n try { return readFileSync(file, 'utf8') } catch { return null }\n}\n\nfunction writeJson(file: string, value: unknown): void {\n mkdirSync(join(file, '..'), { recursive: true, mode: 0o700 })\n writeFileSync(file, JSON.stringify(value, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n}\n\nfunction manifestPath(projectPath: string): string {\n return join(projectPath, 'standalone.json')\n}\n\nfunction paperDir(projectPath: string): string {\n return join(projectPath, 'paper')\n}\n\n/** \u4E24\u4F4D\u8865\u96F6\u7684\u672C\u5730\u65F6\u95F4\u7247\u6BB5\uFF08\u6587\u4EF6\u540D/\u5DE5\u574A\u540D\u7528\uFF09\u3002 */\nfunction two(value: number): string {\n return value < 10 ? '0' + String(value) : String(value)\n}\n\nfunction stamp(at: number): string {\n const date = new Date(at)\n return String(date.getFullYear()) + two(date.getMonth() + 1) + two(date.getDate())\n + '-' + two(date.getHours()) + two(date.getMinutes()) + two(date.getSeconds())\n}\n\nfunction shortId(): string {\n return Math.random().toString(36).slice(2, 8)\n}\n\nfunction defaultName(at: number): string {\n const date = new Date(at)\n return '\u514D\u9879\u76EE\u5B9E\u9A8C \u00B7 ' + two(date.getMonth() + 1) + '-' + two(date.getDate())\n + ' ' + two(date.getHours()) + ':' + two(date.getMinutes())\n}\n\nfunction deriveManifest(projectPath: string): StandaloneManifest | null {\n const project = readJson<ProjectRecord>(join(projectPath, 'project.json'))\n if (project === null) return null\n return { schemaVersion: 1, id: project.id, name: project.name, createdAt: project.createdAt, paper: null }\n}\n\nfunction readManifest(projectPath: string): StandaloneManifest | null {\n return readJson<StandaloneManifest>(manifestPath(projectPath)) ?? deriveManifest(projectPath)\n}\n\nfunction writeManifest(projectPath: string, manifest: StandaloneManifest): void {\n writeJson(manifestPath(projectPath), manifest)\n}\n\nfunction artifactsOf(runDir: string): string[] {\n const root = join(runDir, 'artifacts')\n if (!existsSync(root)) return []\n try {\n return readdirSync(root, { withFileTypes: true }).filter(entry => entry.isFile()).map(entry => entry.name).sort()\n } catch { return [] }\n}\n\n/** \u6301\u4E45\u5316 run \u6458\u8981\uFF08\u8FD0\u884C\u4E2D\u7684\u5B9E\u65F6\u72B6\u6001\u7531 core \u4FA7\u53E0\u52A0\uFF09\u3002 */\nexport function listStandaloneRuns(projectPath: string): StandaloneRunSummary[] {\n const root = join(projectPath, 'experiments', 'runs')\n if (!existsSync(root)) return []\n let entries: string[] = []\n try { entries = readdirSync(root) } catch { return [] }\n const runs: StandaloneRunSummary[] = []\n for (const entry of entries) {\n const dir = join(root, entry)\n const config = readJson<Record<string, unknown>>(join(dir, 'config.json'))\n if (config === null) continue\n const metrics = readJson<Record<string, number | string>>(join(dir, 'metrics.json')) ?? {}\n const status = config['status'] === 'succeeded' || config['status'] === 'failed' || config['status'] === 'cancelled'\n ? config['status'] as StandaloneRunSummary['status']\n : 'running'\n runs.push({\n id: entry,\n script: typeof config['script'] === 'string' ? config['script'] : '',\n status,\n startedAt: typeof config['startedAt'] === 'number' ? config['startedAt'] : 0,\n durationMs: typeof config['durationMs'] === 'number' ? config['durationMs'] : 0,\n exitCode: typeof config['exitCode'] === 'number' ? config['exitCode'] : null,\n metrics,\n artifacts: artifactsOf(dir),\n ...(typeof config['python'] === 'string' ? { python: config['python'] } : {}),\n })\n }\n return runs.sort((left, right) => right.startedAt - left.startedAt)\n}\n\n/** experiments/code \u4E0B\u7684\u811A\u672C\uFF08\u4E0E core \u7684 experiment runner \u540C\u4E00\u76EE\u5F55\u7EA6\u5B9A\uFF09\u3002 */\nexport function listStandaloneScripts(projectPath: string): StandaloneScript[] {\n const root = join(projectPath, 'experiments', 'code')\n if (!existsSync(root)) return []\n let entries: string[] = []\n try { entries = readdirSync(root) } catch { return [] }\n const scripts: StandaloneScript[] = []\n for (const entry of entries) {\n const full = join(root, entry)\n let stat\n try { stat = statSync(full) } catch { continue }\n if (!stat.isFile() || !entry.endsWith('.py')) continue\n scripts.push({ relative: entry, title: entry, mtime: stat.mtimeMs })\n }\n return scripts.sort((left, right) => right.mtime - left.mtime)\n}\n\nfunction statusOf(paper: PaperMeta | null, analysis: PaperAnalysis | null, designRevision: number, runs: StandaloneRunSummary[]): StandaloneStatus {\n if (runs.some(run => run.status === 'running')) return 'running'\n if (runs.some(run => run.status === 'succeeded')) return 'ran'\n if (runs.some(run => run.status === 'failed')) return 'failed'\n if (designRevision > 0) return 'designed'\n if (analysis !== null) return 'analyzed'\n if (paper !== null) return 'paper'\n return 'empty'\n}\n\n/** \u8BFB\u4E00\u4E2A\u5DE5\u574A\u7684\u5B8C\u6574\u89C6\u56FE\uFF08\u4E0D\u542B\u8FD0\u884C\u4E2D\u7684\u5B9E\u65F6\u53E0\u52A0\uFF09\u3002 */\nexport function readStandaloneExperiment(projectPath: string, options: { home?: string } = {}): StandaloneDetail {\n const directory = resolve(projectPath)\n const manifest = readManifest(directory)\n if (manifest === null) throw new KsrwError('NOT_FOUND', '\u4E0D\u662F KSRW \u5B9E\u9A8C\u5DE5\u574A\uFF08\u7F3A standalone.json / project.json\uFF09\uFF1A' + directory)\n const store = new ProjectStore(options.home === undefined ? {} : { home: options.home })\n const project = store.readProject(directory)\n const designArtifact = store.readArtifact(directory, 'experiment')\n const design: StandaloneDesign = {\n revision: designArtifact?.revision ?? 0,\n updatedAt: designArtifact?.updatedAt ?? project.updatedAt,\n content: designArtifact?.content ?? '',\n }\n const analysis = readJson<PaperAnalysis>(join(paperDir(directory), 'analysis.json'))\n const paperText = (() => {\n const text = readText(join(paperDir(directory), 'paper.md')) ?? ''\n return text.length > MAX_PAPER_TEXT ? text.slice(0, MAX_PAPER_TEXT) + '\\n\u2026\uFF08\u5168\u6587\u5DF2\u622A\u65AD\uFF09' : text\n })()\n const runs = listStandaloneRuns(directory)\n const latestActivity = Math.max(\n project.updatedAt,\n design.updatedAt,\n manifest.paper?.uploadedAt ?? 0,\n analysis?.analyzedAt ?? 0,\n runs[0]?.startedAt ?? 0,\n )\n const paper = manifest.paper\n const summary: StandaloneSummary = {\n id: manifest.id,\n name: manifest.name,\n path: directory,\n createdAt: manifest.createdAt,\n updatedAt: latestActivity,\n status: statusOf(paper, analysis, design.revision, runs),\n paperName: paper?.fileName ?? '',\n pages: paper?.pages ?? 0,\n chars: paper?.chars ?? 0,\n runCount: runs.length,\n designRevision: design.revision,\n }\n return {\n experiment: {\n ...summary,\n paper,\n analysis,\n designRevision: design.revision,\n projectStage: project.stage,\n },\n design,\n paperText,\n scripts: listStandaloneScripts(directory),\n runs,\n report: readText(join(directory, 'experiments', 'report.md')) ?? '',\n }\n}\n\n/** \u5DE5\u574A\u5217\u8868\uFF08\u6700\u8FD1\u6D3B\u8DC3\u4F18\u5148\uFF09\u3002 */\nexport function listStandaloneExperiments(options: { home?: string } = {}): StandaloneSummary[] {\n const root = resolveStandaloneRoot(options.home ?? resolveKsrwHome())\n if (!existsSync(root)) return []\n let entries: string[] = []\n try { entries = readdirSync(root) } catch { return [] }\n const items: StandaloneSummary[] = []\n for (const entry of entries) {\n if (entry.startsWith('.')) continue\n const directory = join(root, entry)\n try {\n if (!statSync(directory).isDirectory()) continue\n items.push(readStandaloneExperiment(directory, options).experiment)\n } catch { /* \u4E0D\u662F\u5DE5\u574A\u76EE\u5F55\u6216\u5DF2\u635F\u574F\uFF1A\u8DF3\u8FC7 */ }\n }\n return items.sort((left, right) => right.updatedAt - left.updatedAt)\n}\n\n/** \u65B0\u5EFA\u5DE5\u574A\uFF1Adetached \u9879\u76EE + paper \u76EE\u5F55 + \u6E05\u5355\u3002 */\nexport function createStandaloneExperiment(input: { name?: string; idea?: string; home?: string; now?: number } = {}): StandaloneDetail {\n const home = resolve(input.home ?? resolveKsrwHome())\n const root = resolveStandaloneRoot(home)\n mkdirSync(root, { recursive: true, mode: 0o700 })\n const at = input.now ?? Date.now()\n const name = (input.name ?? '').trim() === '' ? defaultName(at) : (input.name ?? '').trim()\n const id = 'exp-' + stamp(at) + '-' + shortId()\n const directory = join(root, id)\n const store = new ProjectStore({ home })\n const project = store.create({\n name,\n directory,\n detached: true,\n idea: input.idea ?? '\u514D\u9879\u76EE\u5B9E\u9A8C\u5DE5\u574A\uFF1A\u4E0A\u4F20\u8BBA\u6587\u9644\u4EF6\u540E\u8865\u5168\u5B9E\u9A8C\u8BBE\u8BA1\u5E76\u5B8C\u6210\u5B9E\u9A8C\u3002',\n })\n store.setStage(project.path, 'experiment')\n mkdirSync(paperDir(directory), { recursive: true, mode: 0o700 })\n writeManifest(directory, { schemaVersion: 1, id, name, createdAt: at, paper: null })\n return readStandaloneExperiment(directory, { home })\n}\n\n/** \u5220\u9664\u5DE5\u574A\u76EE\u5F55\uFF08\u53EA\u5141\u8BB8\u5220 standalone \u6839\u4E0B\u7684\u76EE\u5F55\uFF09+ \u6E05\u6389\u53EF\u80FD\u7684\u9879\u76EE\u5E93\u767B\u8BB0\u3002 */\nexport function deleteStandaloneExperiment(projectPath: string, options: { home?: string } = {}): boolean {\n const home = resolve(options.home ?? resolveKsrwHome())\n const root = resolveStandaloneRoot(home)\n const target = resolve(projectPath)\n if (target === root || !target.startsWith(root + sep)) {\n throw new KsrwError('PATH_DENIED', '\u53EA\u80FD\u5220\u9664 standalone \u5DE5\u574A\u76EE\u5F55\uFF1A' + target)\n }\n if (!existsSync(target)) throw new KsrwError('NOT_FOUND', '\u5DE5\u574A\u4E0D\u5B58\u5728\uFF1A' + target)\n rmSync(target, { recursive: true, force: true })\n forgetProject(home, target)\n return true\n}\n\n/** \u628A\u5DE5\u574A\u63A5\u6536\u4E3A\u6B63\u5F0F\u9879\u76EE\uFF08\u6E05\u6389 detached\uFF0C\u8FDB\u9879\u76EE\u5E93\uFF09\u3002 */\nexport function registerStandaloneExperiment(projectPath: string, options: { home?: string } = {}): StandaloneDetail {\n const store = new ProjectStore(options.home === undefined ? {} : { home: options.home })\n store.register(projectPath)\n return readStandaloneExperiment(projectPath, options)\n}\n\n\n/** \u4E0A\u4F20/\u66FF\u6362\u8BBA\u6587\u9644\u4EF6\uFF1A\u843D\u76D8\u539F\u6587 + \u89C4\u8303\u5316\u5168\u6587 + \u91CD\u65B0\u62BD\u53D6\u3002 */\nexport async function ingestStandalonePaper(\n projectPath: string,\n input: { fileName: string; bytes: Uint8Array; home?: string },\n): Promise<StandaloneDetail> {\n const directory = resolve(projectPath)\n const manifest = readManifest(directory)\n if (manifest === null) throw new KsrwError('NOT_FOUND', '\u4E0D\u662F KSRW \u5B9E\u9A8C\u5DE5\u574A\uFF1A' + directory)\n const fileName = safeAttachmentName(input.fileName)\n const kind = attachmentKindOf(fileName)\n if (kind === null) {\n const extension = fileName.slice(fileName.lastIndexOf('.'))\n throw new KsrwError('INVALID', '\u53EA\u652F\u6301 PDF / DOCX / MD / TXT / TeX \u8BBA\u6587\u9644\u4EF6\uFF0C\u6536\u5230\uFF1A' + (extension === fileName ? '\u65E0\u6269\u5C55\u540D' : extension))\n }\n const bytes = Buffer.from(input.bytes)\n if (bytes.byteLength === 0) throw new KsrwError('INVALID', '\u9644\u4EF6\u4E3A\u7A7A')\n if (bytes.byteLength > MAX_PAPER_BYTES) throw new KsrwError('INVALID', '\u9644\u4EF6\u8D85\u8FC7 48MiB \u4E0A\u9650')\n\n const dir = paperDir(directory)\n mkdirSync(dir, { recursive: true, mode: 0o700 })\n // P2-18\uFF1A\u5148\u89E3\u6790\u3001\u540E\u66FF\u6362\u3002\u89E3\u6790\u5931\u8D25\u65F6\u65E7\u9644\u4EF6\u4E0E\u65E7\u6B63\u6587\u5FC5\u987B\u539F\u6837\u4FDD\u7559\u3002\n const raw = await extractAttachment(kind, new Uint8Array(bytes))\n const title = detectTitle(raw.pages)\n const normalized = normalizeWithPageMarkers(title, raw)\n const analysis = analyzePaperText(title, normalized.pages, raw.warnings)\n\n for (const name of readdirSync(dir)) {\n if (name.startsWith('attachment.')) rmSync(join(dir, name), { force: true })\n }\n const extension = fileName.slice(fileName.lastIndexOf('.'))\n writeFileSync(join(dir, 'attachment' + extension), bytes, { mode: 0o600 })\n writeFileSync(join(dir, 'paper.md'), normalized.text, { encoding: 'utf8', mode: 0o600 })\n writeFileSync(join(dir, 'analysis.json'), JSON.stringify(analysis, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n\n const meta: PaperMeta = {\n fileName,\n kind,\n bytes: bytes.byteLength,\n uploadedAt: Date.now(),\n pages: normalized.pages.length,\n chars: normalized.text.length,\n title: analysis.title,\n }\n // \u9644\u4EF6\u62BD\u53D6\u662F async\uFF08\u53EF\u80FD\u8981\u597D\u51E0\u79D2\uFF09\uFF1A\u671F\u95F4\u53E6\u4E00\u4E2A\u7A97\u53E3\u53EF\u80FD\u6539\u8FC7\u6E05\u5355\uFF0C\u843D\u76D8\u524D\u91CD\u8BFB\uFF0C\u907F\u514D\u8986\u76D6\u5B83\u3002\n const latest = readManifest(directory) ?? manifest\n writeManifest(directory, { ...latest, paper: meta })\n try {\n const store = new ProjectStore(input.home === undefined ? {} : { home: input.home })\n store.touch(store.readProject(directory))\n } catch { /* updatedAt \u53EA\u662F\u5217\u8868\u6392\u5E8F\u7528 */ }\n return readStandaloneExperiment(directory, input.home === undefined ? {} : { home: input.home })\n}\n\n/** \u91CD\u65B0\u62BD\u53D6\uFF08\u8BBA\u6587\u6B63\u6587/\u6392\u7248\u66F4\u65B0\u540E\u624B\u52A8\u89E6\u53D1\uFF09\u3002 */\nexport function analyzeStandalonePaper(projectPath: string, options: { home?: string } = {}): StandaloneDetail {\n const directory = resolve(projectPath)\n return withProjectLock(directory, () => {\n const manifest = readManifest(directory)\n if (manifest === null) throw new KsrwError('NOT_FOUND', '\u4E0D\u662F KSRW \u5B9E\u9A8C\u5DE5\u574A\uFF1A' + directory)\n const text = readText(join(paperDir(directory), 'paper.md'))\n if (text === null) throw new KsrwError('NOT_FOUND', '\u8FD8\u6CA1\u6709\u8BBA\u6587\u5168\u6587\uFF1A\u8BF7\u5148\u4E0A\u4F20\u8BBA\u6587\u9644\u4EF6')\n const title = detectTitle([{ index: 1, text }])\n const pages = splitNormalizedPages(text)\n const analysis = analyzePaperText(title, pages, [])\n writeFileSync(join(paperDir(directory), 'analysis.json'), JSON.stringify(analysis, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n if (manifest.paper !== null) {\n writeManifest(directory, {\n ...manifest,\n paper: { ...manifest.paper, pages: pages.length, chars: text.length, title: analysis.title },\n })\n }\n return readStandaloneExperiment(directory, options)\n })\n}\n\n// \u2500\u2500 \u5B9E\u9A8C\u8981\u7D20\u62BD\u53D6 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst DATASET_LEXICON = [\n 'ImageNet', 'CIFAR-10', 'CIFAR-100', 'MNIST', 'Fashion-MNIST', 'SQuAD', 'GLUE', 'SuperGLUE', 'MS MARCO',\n 'CoNLL', 'WMT', 'IWSLT', 'LibriSpeech', 'Common Voice', 'COCO', 'ADE20K', 'Cityscapes', 'KITTI', 'VisDrone',\n 'Pascal VOC', 'Kinetics', 'UCF101', 'HMDB51', 'MovieLens', 'Amazon Reviews', 'Yelp', 'IMDB', 'WikiText',\n 'Penn Treebank', 'The Pile', 'C4', 'MMLU', 'GSM8K', 'HumanEval', 'MATH', 'Alpaca', 'OpenWebText', 'BookCorpus',\n 'LAION', 'CelebA', 'FFHQ', 'ShapeNet', 'ModelNet', 'nuScenes', 'Waymo', 'MIMIC', 'PhysioNet',\n]\nconst METRIC_LEXICON = [\n 'Accuracy', 'Top-1', 'Top-5', 'Precision', 'Recall', 'F1', 'macro-F1', 'micro-F1', 'AUC', 'AUROC', 'mAP',\n 'IoU', 'mIoU', 'PSNR', 'SSIM', 'BLEU', 'ROUGE', 'METEOR', 'CIDEr', 'WER', 'CER', 'Perplexity', 'RMSE', 'MAE',\n 'MSE', 'Exact Match', 'pass@1', 'latency', 'throughput', 'FLOPs',\n]\nconst ELEMENT_LABELS: Record<ElementKey, string> = {\n task: '\u7814\u7A76\u4EFB\u52A1', method: '\u65B9\u6CD5', dataset: '\u6570\u636E\u96C6', metric: '\u6307\u6807', baseline: '\u57FA\u7EBF/\u5BF9\u7167', limitation: '\u5C40\u9650',\n}\nconst ELEMENT_HINTS: Record<ElementKey, string> = {\n task: '\u8BBA\u6587\u6CA1\u6709\u5199\u6E05\u8981\u9A8C\u8BC1\u7684\u4EFB\u52A1\uFF1A\u5728\u8BBE\u8BA1\u91CC\u7528\u4E00\u53E5\u8BDD\u5199\u6B7B\u8F93\u5165/\u8F93\u51FA\u3002',\n method: '\u6CA1\u6709\u627E\u5230\u660E\u786E\u7684\u65B9\u6CD5\u63CF\u8FF0\uFF1A\u4ECE\u6458\u8981/\u65B9\u6CD5\u7AE0\u8282\u8865\u4E00\u6BB5\uFF0C\u518D\u62C6\u6210\u53EF\u5B9E\u73B0\u7684\u6B65\u9AA4\u3002',\n dataset: '\u8BBA\u6587\u6CA1\u70B9\u540D\u6570\u636E\u96C6\uFF1A\u8BBE\u8BA1\u91CC\u5FC5\u987B\u5199\u6E05\u6570\u636E\u6765\u6E90\u3001\u8BB8\u53EF\u3001\u5207\u5206\u4E0E\u7248\u672C\u3002',\n metric: '\u6CA1\u627E\u5230\u8BC4\u4EF7\u6307\u6807\uFF1A\u8BBE\u8BA1\u91CC\u8981\u5B9A\u4E49\u6307\u6807\u4E0E\u8BA1\u7B97\u53E3\u5F84\uFF08\u811A\u672C\u7528 KSRW_METRIC \u6253\u5370\uFF09\u3002',\n baseline: '\u6CA1\u6709\u57FA\u7EBF\uFF1A\u81F3\u5C11\u8865\u4E00\u4E2A\u6700\u7B80\u5355\u57FA\u7EBF\uFF08\u968F\u673A/\u5747\u503C/\u7EBF\u6027/\u5B98\u65B9 baseline\uFF09\uFF0C\u5426\u5219\u6536\u76CA\u65E0\u6CD5\u5224\u65AD\u3002',\n limitation: '\u6CA1\u6709\u8BA8\u8BBA\u5C40\u9650\uFF1A\u8865\u4E00\u6761\u300C\u672C\u5B9E\u9A8C\u56DE\u7B54\u4E0D\u4E86\u4EC0\u4E48\u300D\uFF0C\u907F\u514D\u8FC7\u5EA6\u5916\u63A8\u3002',\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\nfunction sentencesOf(text: string): string[] {\n return text\n .replace(/\\s+/g, ' ')\n .split(/(?<=[\u3002\uFF01\uFF1F\uFF1B;])\\s*|(?<=[.!?])\\s+(?=[A-Z0-9(\\[])/)\n .map(sentence => sentence.trim())\n .filter(sentence => sentence.length >= 10 && sentence.length <= 800)\n}\n\nfunction uniqItems(items: ElementItem[], limit: number): ElementItem[] {\n const seen = new Set<string>()\n const out: ElementItem[] = []\n for (const item of items) {\n const key = item.text.replace(/\\s+/g, ' ').slice(0, 160).toLowerCase()\n if (seen.has(key)) continue\n seen.add(key)\n out.push(item)\n if (out.length >= limit) break\n }\n return out\n}\n\nfunction pageSentences(pages: { index: number; text: string }[]): { text: string; source: string }[] {\n const out: { text: string; source: string }[] = []\n for (const page of pages) {\n const source = pages.length > 1 ? '\u7B2C ' + String(page.index) + ' \u9875' : '\u6B63\u6587'\n for (const sentence of sentencesOf(page.text)) out.push({ text: sentence, source })\n }\n return out\n}\n\nfunction extractSections(pages: { index: number; text: string }[]): { heading: string; chars: number }[] {\n const sections: { heading: string; chars: number }[] = []\n for (const page of pages) {\n const lines = page.text.split('\\n')\n let current: { heading: string; chars: number } | undefined\n for (const rawLine of lines) {\n const line = rawLine.replace(/^#+\\s*/, '').trim()\n if (line === '') continue\n if (/^\u7B2C\\s*\\d+\\s*\u9875$/.test(line)) continue\n if (looksLikeByline(line)) continue\n const numbered = /^(?:(?:\\d+(?:\\.\\d+)*)\\.?\\s+)?([A-Z\\u4e00-\\u9fff][^\u3002\uFF01\uFF1F,\uFF0C;\uFF1B]{1,38})$/.exec(line)\n const known = /^(abstract|\u6458\u8981|introduction|\u5F15\u8A00|related work|\u76F8\u5173\u5DE5\u4F5C|method|\u65B9\u6CD5|approach|experiment|\u5B9E\u9A8C|results?|\u7ED3\u679C|discussion|\u8BA8\u8BBA|conclusion|\u7ED3\u8BBA|references?|\u53C2\u8003\u6587\u732E|limitations?|\u5C40\u9650)/i.test(line)\n const looksHeading = line.length <= 42 && (known || (numbered !== null && !/[.\u3002]$/.test(line)))\n if (looksHeading) {\n current = { heading: line, chars: 0 }\n sections.push(current)\n continue\n }\n if (current !== undefined) current.chars += line.length\n }\n }\n return sections.slice(0, 40)\n}\n\nfunction extractAbstract(pages: { index: number; text: string }[]): string {\n const full = pages.map(page => page.text).join('\\n')\n // \u6458\u8981\u6807\u9898\uFF1A\u666E\u901A\u884C\u3001Markdown \u6807\u9898\uFF08#/##\uFF09\u3001\u5E26\u5192\u53F7/\u7A7A\u683C\u90FD\u63A5\u53D7\u3002\n const startRe = /(?:^|\\n)[ \\t]*#{0,6}[ \\t]*(?:abstract|\u6458\u8981)[ \\t]*[:\uFF1A]?[ \\t]*(?:\\n|$)/i\n const start = startRe.exec(full)\n if (start === null) return ''\n const rest = full.slice(start.index + start[0].length)\n const endRe = /(?:^|\\n)[ \\t]*(?:#{1,6}[ \\t]*)?(?:\\d+(?:\\.\\d+)*\\.?[ \\t]+)?(?:introduction|\u5F15\u8A00|\u4E00\u3001\u5F15\u8A00|keywords?|\u5173\u952E\u8BCD|related work|\u76F8\u5173\u5DE5\u4F5C|background|\u80CC\u666F|## \u7B2C\\s*\\d+\\s*\u9875)[ \\t]*[:\uFF1A]?[ \\t]*(?:\\n|$)/i\n const end = endRe.exec(rest)\n const body = end === null ? rest.slice(0, 2400) : rest.slice(0, end.index)\n return cleanText(body).slice(0, 2400)\n}\n\nfunction extractKeywords(pages: { index: number; text: string }[]): string[] {\n const full = pages.map(page => page.text).join('\\n')\n const match = /(?:keywords?|\u5173\u952E\u8BCD)\\s*[:\uFF1A]\\s*([^\\n]{2,240})/i.exec(full)\n if (match === null) return []\n return (match[1] ?? '')\n .split(/[,;\uFF0C\uFF1B\u3001]/)\n .map(item => item.trim())\n .filter(item => item.length >= 2 && item.length <= 40)\n .slice(0, 12)\n}\n\nfunction lexiconItems(sentences: { text: string; source: string }[], lexicon: string[], limit: number): ElementItem[] {\n const items: ElementItem[] = []\n for (const entry of sentences) {\n const hit = lexicon.find(term => new RegExp('(^|[^A-Za-z0-9])' + escapeRegExp(term) + '([^A-Za-z0-9]|$)', 'i').test(entry.text))\n if (hit === undefined) continue\n items.push({ text: entry.text, source: entry.source })\n }\n return uniqItems(items, limit)\n}\n\n/** \u4ECE\u8BBA\u6587\u5168\u6587\u62BD\u53D6\u5B9E\u9A8C\u8981\u7D20\uFF08\u786E\u5B9A\u6027\u3001\u53EF\u89E3\u91CA\uFF1B\u4E0D\u505A\u7ED3\u8BBA\u63A8\u65AD\uFF09\u3002 */\nexport function analyzePaperText(title: string, pages: { index: number; text: string }[], warnings: string[] = []): PaperAnalysis {\n const sentences = pageSentences(pages)\n const abstract = extractAbstract(pages)\n const keywords = extractKeywords(pages)\n const detectedTitle = title !== '' ? title : (pages[0]?.text.split('\\n').map(line => line.trim()).find(line => line !== '') ?? '')\n\n const taskItems: ElementItem[] = abstract === ''\n ? sentences.slice(0, 2).map(item => ({ text: item.text, source: item.source }))\n : sentencesOf(abstract).slice(0, 2).map(text => ({ text, source: '\u6458\u8981' }))\n\n const methodItems = uniqItems(sentences.filter(item =>\n /(we propose|we present|we introduce|we develop|our (?:method|approach|framework|model)|\u672C\u6587\u63D0\u51FA|\u6211\u4EEC\u63D0\u51FA|\u6240\u63D0\u65B9\u6CD5|\u672C\u6587\u65B9\u6CD5|\u8BE5\u65B9\u6CD5)/i.test(item.text),\n ), 6)\n\n const datasetItems = (() => {\n const items = lexiconItems(sentences, DATASET_LEXICON, 10)\n const explicit: ElementItem[] = []\n const re = /(?:\u6570\u636E\u96C6|dataset[s]?)\\s*[:\uFF1A]\\s*([^\\n\u3002.;]{2,120})/gi\n const full = pages.map(page => page.text).join('\\n')\n let match: RegExpExecArray | null\n while ((match = re.exec(full)) !== null) {\n const value = (match[1] ?? '').trim()\n if (value !== '') explicit.push({ text: value, source: '\u539F\u6587' })\n }\n return uniqItems([...explicit, ...items], 10)\n })()\n\n const metricItems = (() => {\n const items = lexiconItems(sentences, METRIC_LEXICON, 10)\n const chinese = uniqItems(sentences.filter(item =>\n /(\u51C6\u786E\u7387|\u7CBE\u786E\u7387|\u53EC\u56DE\u7387|F1|AUC|mAP|IoU|RMSE|MAE|BLEU|ROUGE|\u56F0\u60D1\u5EA6|\u541E\u5410|\u5EF6\u8FDF|\u8BEF\u5DEE)/.test(item.text),\n ).map(item => ({ text: item.text, source: item.source })), 10)\n return uniqItems([...items, ...chinese], 10)\n })()\n\n const baselineItems = uniqItems(sentences.filter(item =>\n /(baseline|compare (?:with|to|against)|comparison|\u5BF9\u7167|\u57FA\u7EBF|\u5BF9\u6BD4\u65B9\u6CD5|\u76F8\u6BD4)/i.test(item.text),\n ), 6)\n\n const limitationItems = uniqItems(sentences.filter(item =>\n /(limitation|\u5C40\u9650|\u4E0D\u8DB3|future work|\u672A\u6765\u5DE5\u4F5C|\u5C1A\u672A|\u5C1A\u4E0D)/i.test(item.text),\n ), 5)\n\n const elements: ExperimentElement[] = [\n { key: 'task', label: ELEMENT_LABELS.task, items: taskItems },\n { key: 'method', label: ELEMENT_LABELS.method, items: methodItems },\n { key: 'dataset', label: ELEMENT_LABELS.dataset, items: datasetItems },\n { key: 'metric', label: ELEMENT_LABELS.metric, items: metricItems },\n { key: 'baseline', label: ELEMENT_LABELS.baseline, items: baselineItems },\n { key: 'limitation', label: ELEMENT_LABELS.limitation, items: limitationItems },\n ]\n const missing = elements\n .filter(element => element.items.length === 0)\n .map(element => ({ key: element.key, label: element.label, hint: ELEMENT_HINTS[element.key] }))\n\n const merged = [...warnings]\n if (abstract === '') merged.push('\u6CA1\u627E\u5230\u6458\u8981\uFF08Abstract/\u6458\u8981\uFF09\uFF1A\u62BD\u53D6\u8D28\u91CF\u4F1A\u4E0B\u964D\uFF0C\u5EFA\u8BAE\u4EBA\u5DE5\u8865\u7814\u7A76\u95EE\u9898\u3002')\n if (detectedTitle === '') merged.push('\u6CA1\u8BC6\u522B\u51FA\u6807\u9898\uFF1A\u7B2C\u4E00\u884C\u53EF\u80FD\u662F\u9875\u7709\u6216\u671F\u520A\u4FE1\u606F\u3002')\n const totalChars = pages.map(page => page.text.length).reduce((sum, size) => sum + size, 0)\n if (pages.length > 1 && totalChars < 3000) merged.push('\u5168\u6587\u5F88\u77ED\uFF1A\u53EF\u80FD\u662F\u6458\u8981\u9875\u6216\u62BD\u53D6\u5931\u8D25\uFF0C\u8BF7\u6838\u5BF9 paper.md\u3002')\n\n return {\n analyzedAt: Date.now(),\n title: detectedTitle,\n abstract,\n keywords,\n sections: extractSections(pages),\n elements,\n missing,\n warnings: merged,\n }\n}\n\n// \u2500\u2500 \u8BBE\u8BA1\u9AA8\u67B6 / \u811A\u672C / \u62A5\u544A \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction itemsOrPlaceholder(element: ExperimentElement | undefined, placeholder: string): string[] {\n if (element === undefined || element.items.length === 0) return ['- ' + placeholder]\n return element.items.slice(0, 5).map(item => '- ' + item.text.replace(/\\s+/g, ' ') + '\uFF08' + item.source + '\uFF09')\n}\n\n/** \u786E\u5B9A\u6027\u8BBE\u8BA1\u9AA8\u67B6\uFF1A\u6CA1\u6709 Agent \u4E5F\u80FD\u8865\u51FA\u4E00\u4EFD\u53EF\u6267\u884C\u7684\u8BBE\u8BA1\u521D\u7A3F\u3002 */\nexport function renderDesignScaffold(analysis: PaperAnalysis | null, name: string): string {\n const element = (key: ElementKey): ExperimentElement | undefined => analysis?.elements.find(item => item.key === key)\n const dataset = element('dataset')\n const metric = element('metric')\n const baseline = element('baseline')\n const method = element('method')\n const limitation = element('limitation')\n const title = analysis?.title === undefined || analysis.title === '' ? name : analysis.title\n const abstract = analysis?.abstract ?? ''\n const firstSentence = abstract.split(/(?<=[\u3002\uFF01\uFF1F])\\s|(?<=[.])\\s/)[0] ?? abstract.slice(0, 200)\n const lines: string[] = [\n '# \u5B9E\u9A8C\u8BBE\u8BA1 \u00B7 ' + title,\n '',\n '> \u7531 KSRW \u4ECE\u8BBA\u6587\u9644\u4EF6\u81EA\u52A8\u62BD\u53D6\u9AA8\u67B6\uFF08\u786E\u5B9A\u6027\uFF09\uFF1B\u6807\u300C\u5F85\u8865\u300D\u7684\u6761\u76EE\u5FC5\u987B\u5728\u8FD0\u884C\u4E3B\u5B9E\u9A8C\u524D\u843D\u5B9E\u3002',\n '> \u534F\u8BAE\uFF1A\u5148\u57FA\u7EBF\u540E\u4E3B\u5B9E\u9A8C\uFF1B\u6BCF\u4E2A run \u8BB0\u5F55\u73AF\u5883\u3001\u79CD\u5B50\u3001\u6307\u6807\u4E0E\u4EA7\u7269\uFF1B\u5931\u8D25\u4E5F\u8981\u843D\u76D8\u3002',\n '',\n '## 1. \u7814\u7A76\u95EE\u9898\u4E0E\u5047\u8BBE',\n '',\n '### \u8BBA\u6587\u8981\u56DE\u7B54\u7684\u95EE\u9898',\n abstract === '' ? '- \u5F85\u8865\uFF1A\u7528\u4E00\u53E5\u8BDD\u5199\u6E05\u8BBA\u6587\u7684\u7814\u7A76\u95EE\u9898\u3002' : '- \u6309\u6458\u8981\u590D\u8FF0\uFF1A' + firstSentence,\n '',\n '### \u5F85\u9A8C\u8BC1\u5047\u8BBE\uFF08\u672C\u5B9E\u9A8C\u8981\u8865\u7684\uFF09',\n '- H1\uFF08\u4E3B\u5047\u8BBE\uFF09\uFF1A\u5728\u3010\u6570\u636E\u96C6\u3011\u4E0A\uFF0C\u3010\u65B9\u6CD5\u3011\u76F8\u5BF9\u3010\u57FA\u7EBF\u3011\u5728\u3010\u6307\u6807\u3011\u4E0A\u63D0\u5347\uFF0C\u4E14\u5DEE\u503C\u5927\u4E8E\u968F\u673A\u6CE2\u52A8\u3002',\n '- H2\uFF08\u6D88\u878D\uFF09\uFF1A\u53BB\u6389\u3010\u5173\u952E\u7EC4\u4EF6\u3011\u540E\u6307\u6807\u4E0B\u964D\uFF0C\u8BF4\u660E\u8BE5\u7EC4\u4EF6\u6709\u6548\u3002',\n '- H3\uFF08\u7A33\u5065\u6027\uFF09\uFF1A\u81F3\u5C11 3 \u4E2A\u968F\u673A\u79CD\u5B50\u4E0B\u7ED3\u8BBA\u65B9\u5411\u4E00\u81F4\u3002',\n '',\n '## 2. \u6570\u636E',\n '',\n ...itemsOrPlaceholder(dataset, '\u5F85\u8865\uFF1A\u8BBA\u6587\u6CA1\u6709\u70B9\u540D\u6570\u636E\u96C6\uFF0C\u5FC5\u987B\u5199\u6E05\u6765\u6E90/\u8BB8\u53EF/\u5207\u5206/\u7248\u672C/\u9884\u5904\u7406\u3002'),\n '- \u5207\u5206\uFF1A\u8BAD\u7EC3/\u9A8C\u8BC1/\u6D4B\u8BD5\uFF0C\u6D4B\u8BD5\u96C6\u53EA\u7528\u4E00\u6B21',\n '- \u7248\u672C\u4E0E\u6821\u9A8C\uFF1Asha256 \u8BB0\u5F55\u5230 experiments/runs/<runId>/config.json',\n '',\n '## 3. \u57FA\u7EBF',\n '',\n ...itemsOrPlaceholder(baseline, '\u5F85\u8865\uFF1A\u81F3\u5C11\u4E00\u4E2A\u6700\u7B80\u5355\u57FA\u7EBF\uFF08\u968F\u673A/\u5747\u503C/\u7EBF\u6027/\u5B98\u65B9 baseline\uFF09\u3002'),\n '- \u5BF9\u7167\u539F\u5219\uFF1A\u540C\u4E00\u6570\u636E\u5207\u5206\u3001\u540C\u4E00\u8BC4\u4F30\u811A\u672C\u3001\u540C\u4E00\u786C\u4EF6',\n '',\n '## 4. \u6307\u6807\u4E0E\u53E3\u5F84',\n '',\n ...itemsOrPlaceholder(metric, '\u5F85\u8865\uFF1A\u5B9A\u4E49\u4E3B\u6307\u6807\u4E0E\u8BA1\u7B97\u53E3\u5F84\uFF08\u811A\u672C\u7528 KSRW_METRIC key=value \u6253\u5370\uFF09\u3002'),\n '- \u62A5\u544A\u53E3\u5F84\uFF1A\u5747\u503C \u00B1 \u6807\u51C6\u5DEE\uFF083 \u4E2A\u79CD\u5B50\uFF09\uFF0C\u540C\u65F6\u4FDD\u7559\u6700\u5DEE\u79CD\u5B50',\n '',\n '## 5. \u53D8\u91CF\u4E0E\u6D88\u878D',\n '',\n ...itemsOrPlaceholder(method, '\u5F85\u8865\uFF1A\u628A\u65B9\u6CD5\u62C6\u6210\u53EF\u5F00\u5173\u7684\u7EC4\u4EF6\uFF0C\u9010\u4E2A\u6D88\u878D\u3002'),\n '- \u4E00\u6B21\u53EA\u6539\u4E00\u4E2A\u53D8\u91CF\uFF1B\u8D85\u53C2\u641C\u7D22\u4E0E\u6700\u7EC8\u8BC4\u4F30\u5206\u5F00',\n '',\n '## 6. \u8FD0\u884C\u8BA1\u5212',\n '',\n '- \u811A\u672C\uFF1Aexperiments/code/\uFF08\u7531\u5B9E\u9A8C\u5DE5\u4F4D Agent \u751F\u6210\uFF1B\u6253\u5370 KSRW_METRIC name=value\uFF09',\n '- \u73AF\u5883\uFF1APython 3.12 + \u4F9D\u8D56\u6E05\u5355\uFF08\u7248\u672C\u5199\u8FDB\u8BBE\u8BA1\uFF09',\n '- \u79CD\u5B50\uFF1A0 / 1 / 2 \u4E09\u6B21\u91CD\u590D',\n '- \u9884\u7B97\uFF1A\u3010\u5F85\u8865\uFF1A\u5355\u6B21\u9884\u8BA1\u65F6\u957F \u00D7 \u6B21\u6570 \u00D7 \u8BBE\u5907\u3011',\n '- \u8FD0\u884C\u4F4D\u7F6E\uFF1A\u672C\u5730 Python \u8FD0\u884C\u65F6 / \u8FDC\u7A0B GPU\uFF08\u4E8C\u9009\u4E00\uFF0C\u5199\u6E05\u76EE\u6807\uFF09',\n '',\n '## 7. \u505C\u6B62\u6761\u4EF6\u4E0E\u5224\u5B9A',\n '',\n '- \u9884\u7B97\u7528\u5C3D\u6216 3 \u4E2A\u79CD\u5B50\u8DD1\u5B8C\u5373\u505C\uFF1B\u4E2D\u9014\u5931\u8D25\u4FDD\u7559\u65E5\u5FD7',\n '- \u5224\u5B9A\uFF1A\u4E3B\u6307\u6807\u63D0\u5347\u4E14\u6D88\u878D\u65B9\u5411\u4E00\u81F4 \u2192 \u652F\u6301 H1/H2\uFF1B\u5426\u5219\u6309\u5931\u8D25\u5206\u6790\u5904\u7406\uFF0C\u4E0D\u6311\u6570\u636E',\n '',\n '## 8. \u98CE\u9669\u4E0E\u5C40\u9650',\n '',\n ...itemsOrPlaceholder(limitation, '\u5F85\u8865\uFF1A\u5199\u6E05\u672C\u5B9E\u9A8C\u56DE\u7B54\u4E0D\u4E86\u4EC0\u4E48\u3002'),\n '- \u6570\u636E/\u8BB8\u53EF\uFF1A\u4E0D\u53EF\u5F97\u7684\u516C\u5F00\u6570\u636E\u8981\u5199\u66FF\u4EE3\u65B9\u6848\u4E0E\u6210\u672C',\n '',\n ]\n return lines.join('\\n')\n}\n\n/** \u5199\u5165/\u8986\u76D6\u5B9E\u9A8C\u8BBE\u8BA1\uFF08\u7528\u6237\u76F4\u5199\u6743\u5A01\u6587\u4EF6\uFF1BAgent \u8D70\u63D0\u6848\uFF09\u3002 */\nexport function saveStandaloneDesign(projectPath: string, content: string, options: { home?: string } = {}): StandaloneDesign {\n if (content.trim() === '') throw new KsrwError('INVALID', '\u5B9E\u9A8C\u8BBE\u8BA1\u4E0D\u80FD\u4E3A\u7A7A')\n const store = new ProjectStore(options.home === undefined ? {} : { home: options.home })\n const view = store.writeArtifact(projectPath, 'experiment', content)\n return { revision: view.revision, updatedAt: view.updatedAt, content: view.content }\n}\n\n/** \u751F\u6210\u9AA8\u67B6\u5E76\u5199\u5165\u5B9E\u9A8C\u8BBE\u8BA1\uFF08\u9ED8\u8BA4\u53EA\u5728\u8BBE\u8BA1\u4E3A\u7A7A\u65F6\u5199\uFF0Coverwrite \u624D\u8986\u76D6\uFF09\u3002 */\nexport function writeStandaloneScaffold(projectPath: string, options: { overwrite?: boolean; home?: string } = {}): StandaloneDetail {\n const detail = readStandaloneExperiment(projectPath, options.home === undefined ? {} : { home: options.home })\n if (detail.experiment.analysis === null) throw new KsrwError('NOT_FOUND', '\u8FD8\u6CA1\u6709\u8BBA\u6587\u5206\u6790\u7ED3\u679C\uFF1A\u8BF7\u5148\u4E0A\u4F20\u8BBA\u6587\u9644\u4EF6\u5E76\u62BD\u53D6\u5B9E\u9A8C\u8981\u7D20')\n if (detail.design.revision > 0 && options.overwrite !== true) {\n throw new KsrwError('INVALID', '\u5B9E\u9A8C\u8BBE\u8BA1\u5DF2\u5B58\u5728\uFF08revision ' + detail.design.revision + '\uFF09\uFF1A\u786E\u8BA4\u8986\u76D6\u518D\u8C03\u7528')\n }\n const scaffold = renderDesignScaffold(detail.experiment.analysis, detail.experiment.name)\n mkdirSync(join(projectPath, 'experiments'), { recursive: true, mode: 0o700 })\n writeFileSync(join(projectPath, 'experiments', 'design-scaffold.md'), scaffold, { encoding: 'utf8', mode: 0o600 })\n saveStandaloneDesign(projectPath, scaffold, options.home === undefined ? {} : { home: options.home })\n return readStandaloneExperiment(projectPath, options.home === undefined ? {} : { home: options.home })\n}\n\n/** Agent \u552F\u4E00\u53EF\u5199\u811A\u672C\u5165\u53E3\uFF1A\u53EA\u5141\u8BB8 experiments/code/<name>.py\u3002 */\nexport function writeStandaloneScript(projectPath: string, name: string, content: string): StandaloneScript {\n const fileName = name.trim()\n if (!/^[A-Za-z0-9][A-Za-z0-9._-]*\\.py$/.test(fileName)) {\n throw new KsrwError('INVALID', '\u811A\u672C\u540D\u5FC5\u987B\u662F <name>.py\uFF08\u5B57\u6BCD/\u6570\u5B57/._-\uFF0C\u4E0D\u80FD\u5E26\u76EE\u5F55\uFF09\uFF1A' + name)\n }\n if (content.trim() === '') throw new KsrwError('INVALID', '\u811A\u672C\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A')\n if (Buffer.byteLength(content, 'utf8') > MAX_SCRIPT_BYTES) throw new KsrwError('INVALID', '\u811A\u672C\u8D85\u8FC7 256KiB \u4E0A\u9650')\n const dir = join(projectPath, 'experiments', 'code')\n mkdirSync(dir, { recursive: true, mode: 0o700 })\n const file = join(dir, fileName)\n writeFileSync(file, content, { encoding: 'utf8', mode: 0o600 })\n return { relative: fileName, title: fileName, mtime: statSync(file).mtimeMs }\n}\n\nexport interface ReportRun {\n id: string\n script: string\n status: string\n startedAt: number\n durationMs: number\n exitCode: number | null\n metrics: Record<string, number | string>\n artifacts?: string[]\n}\n\nfunction formatDuration(ms: number): string {\n if (ms <= 0) return '\u2014'\n if (ms < 1000) return String(ms) + ' ms'\n const seconds = Math.round(ms / 100) / 10\n if (seconds < 60) return String(seconds) + ' s'\n return String(Math.round(seconds / 60)) + ' min'\n}\n\n/** \u6309\u8BBE\u8BA1 + \u771F\u5B9E run \u751F\u6210\u62A5\u544A\uFF08\u6CA1\u6709\u6210\u529F run \u65F6\u660E\u786E\u5199\u300C\u4E0D\u5F97\u4E0B\u7ED3\u8BBA\u300D\uFF09\u3002 */\nexport function renderExperimentReport(input: { name: string; paperTitle?: string; design: string; runs: ReportRun[]; generatedAt?: number }): string {\n const generatedAt = input.generatedAt ?? Date.now()\n const succeeded = input.runs.filter(run => run.status === 'succeeded')\n const failed = input.runs.filter(run => run.status === 'failed' || run.status === 'cancelled')\n const running = input.runs.filter(run => run.status === 'running')\n const lines: string[] = [\n '# \u5B9E\u9A8C\u62A5\u544A \u00B7 ' + input.name,\n '',\n '- \u751F\u6210\u65F6\u95F4\uFF1A' + new Date(generatedAt).toISOString(),\n '- \u8BBA\u6587\uFF1A' + (input.paperTitle === undefined || input.paperTitle === '' ? '\uFF08\u672A\u4E0A\u4F20/\u672A\u8BC6\u522B\uFF09' : input.paperTitle),\n '- \u8FD0\u884C\uFF1A\u6210\u529F ' + String(succeeded.length) + ' / \u5931\u8D25 ' + String(failed.length) + ' / \u8FD0\u884C\u4E2D ' + String(running.length),\n '',\n '## 1. \u8FD0\u884C\u6982\u89C8',\n '',\n ]\n if (input.runs.length === 0) {\n lines.push('> \u8FD8\u6CA1\u6709\u4EFB\u4F55 run\uFF1A\u5148\u5728\u5B9E\u9A8C\u5DE5\u4F4D\u751F\u6210\u811A\u672C\u5E76\u8FD0\u884C\uFF0C\u6307\u6807\u5FC5\u987B\u6765\u81EA\u771F\u5B9E run\u3002', '')\n } else {\n lines.push('| run | \u811A\u672C | \u72B6\u6001 | \u8017\u65F6 | \u9000\u51FA\u7801 |', '| --- | --- | --- | --- | --- |')\n for (const run of input.runs) {\n lines.push('| ' + [run.id, run.script, run.status, formatDuration(run.durationMs), run.exitCode === null ? '\u2014' : String(run.exitCode)].join(' | ') + ' |')\n }\n lines.push('')\n }\n lines.push('## 2. \u6307\u6807', '')\n if (succeeded.length === 0) {\n lines.push('> \u6CA1\u6709\u6210\u529F\u7684 run\uFF1A\u672C\u62A5\u544A\u4E0D\u7ED9\u51FA\u4EFB\u4F55\u5B9E\u9A8C\u7ED3\u8BBA\u3002', '')\n } else {\n for (const run of succeeded) {\n const keys = Object.keys(run.metrics)\n lines.push('### ' + run.id + ' \u00B7 ' + run.script, '')\n if (keys.length === 0) {\n lines.push('- \u8BE5 run \u6CA1\u6709\u6253\u5370 KSRW_METRIC\uFF0C\u65E0\u6CD5\u91C7\u96C6\u6307\u6807\u3002', '')\n continue\n }\n for (const key of keys) lines.push('- ' + key + ' = ' + String(run.metrics[key]))\n lines.push('')\n }\n }\n lines.push('## 3. \u7ED3\u8BBA', '')\n if (succeeded.length === 0) {\n lines.push('- \u5C1A\u65E0\u6210\u529F run\uFF1A\u4E0D\u5F97\u5199\u300C\u65B9\u6CD5\u6709\u6548\u300D\u300C\u6307\u6807\u63D0\u5347\u300D\u7B49\u7ED3\u8BBA\u3002', '- \u4E0B\u4E00\u6B65\uFF1A\u6838\u5BF9\u811A\u672C\u4E0E\u65E5\u5FD7\uFF0C\u4FEE\u590D\u540E\u91CD\u8DD1\u6700\u5C0F\u57FA\u7EBF\u3002', '')\n } else {\n lines.push('- \u5DF2\u8DD1\u901A ' + String(succeeded.length) + ' \u4E2A run\uFF1B\u7ED3\u8BBA\u53EA\u8986\u76D6\u8FD9\u4E9B run \u8986\u76D6\u7684\u6570\u636E/\u6307\u6807\u3002', '- \u5931\u8D25/\u53D6\u6D88\u7684 run \u5DF2\u4FDD\u7559\u65E5\u5FD7\uFF0C\u5931\u8D25\u5206\u6790\u89C1 experiments/runs/<runId>/stdout.log\u3002', '')\n }\n lines.push('## 4. \u4EA7\u7269', '')\n const artifacts = input.runs.flatMap(run => (run.artifacts ?? []).map(file => run.id + '/' + file))\n lines.push(...(artifacts.length === 0 ? ['- \u6682\u65E0 artifacts\uFF08\u811A\u672C\u53EF\u5199 $KSRW_RUN_DIR/artifacts/\uFF09\u3002'] : artifacts.map(file => '- experiments/runs/' + file)))\n lines.push('', '## 5. \u590D\u73B0', '')\n lines.push('- \u590D\u73B0\u547D\u4EE4\u3001\u73AF\u5883\u4E0E\u79CD\u5B50\u8BB0\u5F55\u5728 experiments/runs/<runId>/config.json\uFF1B', '- \u8BBE\u8BA1\u4EE5 experiments/record.md \u4E3A\u51C6\uFF0C\u811A\u672C\u5728 experiments/code/\u3002', '')\n return lines.join('\\n')\n}\n\n/** \u751F\u6210\u5E76\u843D\u76D8\u62A5\u544A\uFF1Aexperiments/report.md\u3002 */\nexport function writeStandaloneReport(projectPath: string, options: { home?: string } = {}): { report: string; path: string } {\n const detail = readStandaloneExperiment(projectPath, options.home === undefined ? {} : { home: options.home })\n const report = renderExperimentReport({\n name: detail.experiment.name,\n paperTitle: detail.experiment.paper?.title ?? detail.experiment.analysis?.title ?? '',\n design: detail.design.content,\n runs: detail.runs,\n })\n const dir = join(projectPath, 'experiments')\n mkdirSync(dir, { recursive: true, mode: 0o700 })\n const file = join(dir, 'report.md')\n writeFileSync(file, report, { encoding: 'utf8', mode: 0o600 })\n return { report, path: file }\n}\n", "/** \u8BBA\u6587\u9644\u4EF6\uFF08PDF / DOCX / MD / TXT\uFF09\u89E3\u6790\uFF1A\u5B9E\u9A8C\u5DE5\u574A\u4E0E\u8BBA\u6587\u5DE5\u574A\u5171\u7528\u3002\n *\n * \u53EA\u505A\u300C\u5B57\u8282 \u2192 \u5206\u9875\u7EAF\u6587\u672C + \u6807\u9898\u300D\u7684\u786E\u5B9A\u6027\u89E3\u6790\uFF0C\u4E0D\u505A\u8981\u7D20\u62BD\u53D6\uFF08\u90A3\u662F\u5404\u5DE5\u574A\u81EA\u5DF1\u7684\u4E8B\uFF09\u3002\n */\nimport { unzipSync } from 'fflate'\nimport { extname } from 'node:path'\n\nimport { KsrwError } from './error.ts'\nimport { pdfText } from './pdf.ts'\n\nexport const MAX_ATTACHMENT_BYTES = 48 * 1024 * 1024\n\nexport type PaperKind = 'pdf' | 'docx' | 'text' | 'markdown'\n\nexport interface AttachmentPage {\n index: number\n text: string\n}\n\nexport interface ExtractedAttachment {\n pages: AttachmentPage[]\n warnings: string[]\n}\n\nconst PAPER_KINDS: Record<string, PaperKind> = {\n '.pdf': 'pdf', '.docx': 'docx', '.md': 'markdown', '.markdown': 'markdown', '.txt': 'text', '.tex': 'text',\n}\n\n/** \u6269\u5C55\u540D \u2192 \u89E3\u6790\u7C7B\u578B\uFF1B\u4E0D\u652F\u6301\u65F6\u8FD4\u56DE null\u3002 */\nexport function attachmentKindOf(fileName: string): PaperKind | null {\n return PAPER_KINDS[extname(fileName).toLowerCase()] ?? null\n}\n\n/** \u53EA\u53D6\u6587\u4EF6\u540D\u672C\u8EAB\uFF08\u53BB\u6389\u76EE\u5F55\u3001\u63A7\u5236\u5B57\u7B26\uFF09\uFF0C\u62D2\u7EDD\u7A7A / . / ..\u3002 */\nexport function safeAttachmentName(raw: string): string {\n const name = raw.trim().replace(/\\\\/g, '/').split('/').pop() ?? ''\n const cleaned = name.replace(/[\\u0000-\\u001f]/g, '').trim()\n if (cleaned === '' || cleaned === '.' || cleaned === '..') throw new KsrwError('INVALID', '\u9644\u4EF6\u6587\u4EF6\u540D\u4E0D\u5408\u6CD5')\n return cleaned\n}\n\nfunction decodeXml(value: string): string {\n return value\n .replace(/&#x([0-9a-fA-F]+);/g, (_all, hex: string) => String.fromCodePoint(Number.parseInt(hex, 16)))\n .replace(/&#(\\d+);/g, (_all, dec: string) => String.fromCodePoint(Number.parseInt(dec, 10)))\n .replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '\"').replace(/&apos;/g, \"'\")\n .replace(/&amp;/g, '&')\n}\n\n/** DOCX \u2192 \u7EAF\u6587\u672C\uFF08\u89E3 zip \u8BFB word/document.xml\uFF0C\u6309\u6BB5\u843D/\u5236\u8868/\u6362\u884C\u8FD8\u539F\uFF1B\u4E0D\u4F9D\u8D56\u529E\u516C\u8F6F\u4EF6\uFF09\u3002 */\nexport function docxToText(bytes: Uint8Array): string {\n let files: Record<string, Uint8Array>\n try {\n files = unzipSync(new Uint8Array(bytes))\n } catch {\n throw new KsrwError('INVALID', 'DOCX \u89E3\u538B\u5931\u8D25\uFF08\u4E0D\u662F\u6709\u6548\u7684 Word \u6587\u6863\uFF09')\n }\n const entry = files['word/document.xml']\n if (entry === undefined) throw new KsrwError('INVALID', 'DOCX \u7F3A\u5C11 word/document.xml\uFF08\u4E0D\u662F\u6709\u6548\u7684 Word \u6587\u6863\uFF09')\n const xml = new TextDecoder().decode(entry)\n const lines: string[] = []\n for (const paragraph of xml.split('</w:p>')) {\n const pieces: string[] = []\n const re = /<w:t(?:\\s[^>]*)?>([\\s\\S]*?)<\\/w:t>|<w:tab\\/>|<w:br\\/>/g\n let match: RegExpExecArray | null\n while ((match = re.exec(paragraph)) !== null) {\n if (match[1] !== undefined) pieces.push(decodeXml(match[1]))\n else if (match[0] === '<w:tab/>') pieces.push('\\t')\n else pieces.push('\\n')\n }\n const line = pieces.join('').replace(/[ \\t]+$/g, '')\n if (line.trim() !== '') lines.push(line)\n }\n return lines.join('\\n')\n}\n\n/** \u5B57\u8282 \u2192 \u5206\u9875\u6587\u672C\uFF08PDF \u9010\u9875\uFF1B\u5176\u4F59\u5355\u9875\uFF09\u3002 */\nexport async function extractAttachment(kind: PaperKind, bytes: Uint8Array): Promise<ExtractedAttachment> {\n if (kind === 'pdf') {\n // unpdf \u660E\u786E\u62D2\u7EDD Buffer\uFF1APDF \u89E3\u6790\u5FC5\u987B\u62FF\u771F\u6B63\u7684 Uint8Array\u3002\n const parsed = await pdfText(new Uint8Array(bytes))\n const warnings: string[] = []\n if (parsed.pages > 0 && parsed.texts.join('').trim().length < 600) {\n warnings.push('PDF \u6587\u672C\u5F88\u5C11\uFF08\u53EF\u80FD\u662F\u626B\u63CF\u7248/\u56FE\u7247 PDF\uFF09\uFF1A\u62BD\u53D6\u4F1A\u4E0D\u5B8C\u6574\uFF0C\u9700\u8981\u4EBA\u5DE5\u8865\u3002')\n }\n return { pages: parsed.texts.map((text, index) => ({ index: index + 1, text })), warnings }\n }\n if (kind === 'docx') return { pages: [{ index: 1, text: docxToText(bytes) }], warnings: [] }\n return { pages: [{ index: 1, text: Buffer.from(bytes).toString('utf8') }], warnings: [] }\n}\n\n/** \u7EDF\u4E00\u7A7A\u767D\uFF1ACRLF\u3001\u884C\u5C3E\u7A7A\u683C\u3001\u4E09\u8FDE\u7A7A\u884C\u3002 */\nexport function cleanText(text: string): string {\n return text\n .replace(/\\r\\n?/g, '\\n')\n .replace(/[ \\t]+\\n/g, '\\n')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim()\n}\n\n/** \u4F5C\u8005/\u673A\u6784/\u7248\u6743\u884C\uFF1A\u4E0D\u80FD\u5F53\u6807\u9898\u3002 */\nexport function looksLikeByline(line: string): boolean {\n if (/@/.test(line)) return true\n if (/[\u2217\u2020\u2021]/.test(line)) return true\n if (/(universit|institute|laborator|department|school of|inc\\.|ltd\\.|google brain|google research|google deepmind)/i.test(line)) return true\n if (/^(provided proper attribution|copyright|permission|licensed|license|doi|arxiv|https?:|\u00A9)/i.test(line)) return true\n return false\n}\n\n/** \u6807\u9898\u8BC6\u522B\uFF1A\u53D6\u6458\u8981\u524D\u6700\u50CF\u6807\u9898\u7684\u4E00\u884C\uFF08PDF \u9996\u9875\u5E38\u6709\u7248\u6743/\u4F5C\u8005\u5757\uFF0C\u4E0D\u80FD\u53D6\u7B2C\u4E00\u884C\uFF09\u3002 */\nexport function detectTitle(pages: AttachmentPage[]): string {\n const lines = (pages[0]?.text ?? '')\n .split('\\n')\n .map(line => line.replace(/^#+\\s*/, '').trim())\n .filter(line => line !== '')\n // \u4E2D\u6587\u6CA1\u6709 \\b \u8BCD\u8FB9\u754C\uFF1A\u8FD9\u91CC\u53EA\u6309\u884C\u9996\u5339\u914D\uFF0C\u6458\u8981/\u5173\u952E\u8BCD\u884C\u90FD\u7B97\u8FB9\u754C\u3002\n const abstractIndex = lines.findIndex(line => /^(abstract|\u6458\u8981|keywords?|\u5173\u952E\u8BCD)/i.test(line))\n const limit = abstractIndex >= 0 ? Math.min(abstractIndex, 40) : Math.min(lines.length, 15)\n const candidates: { line: string; index: number; score: number }[] = []\n for (let index = 0; index < limit; index += 1) {\n const line = lines[index] ?? ''\n if (line.length < 8 || line.length > 160) continue\n if (looksLikeByline(line)) continue\n if (/^(abstract|\u6458\u8981|keywords?|\u5173\u952E\u8BCD)/i.test(line)) continue\n if (/^\\d+$/.test(line)) continue\n // \u53E5\u5B50\uFF08\u5E26\u53E5\u672B\u6807\u70B9\uFF09\u4E0D\u662F\u6807\u9898\uFF1A\u6458\u8981\u53E5\u5E38\u88AB\u8BEF\u6293\u3002\n if (/[\u3002\uFF01\uFF1F\uFF1B;]$/.test(line)) continue\n if (/[.,;:]$/.test(line) && line.split(/\\s+/).length < 4) continue\n const words = line.split(/\\s+/).filter(word => word !== '').length\n const cjk = /[\\u4e00-\\u9fff]/.test(line)\n // \u82F1\u6587\u6807\u9898\u81F3\u5C11\u4E24\u4E2A\u8BCD\uFF1B\u4E2D\u6587\u6807\u9898\u53EF\u4EE5\u662F\u4E00\u884C\uFF08\u6CA1\u6709\u7A7A\u683C\u5206\u8BCD\uFF09\u3002\n if (words < 2 && !(cjk && line.length >= 6)) continue\n let score = Math.min(words, 12)\n if (/^[A-Z\\u4e00-\\u9fff]/.test(line)) score += 2\n if (cjk) score += 2\n // \u6807\u9898\u7D27\u8DDF\u7740\u4F5C\u8005\u884C\uFF08\u5E26 \u2217/\u2020 \u6216\u90AE\u7BB1\uFF09\u662F\u6700\u5F3A\u4FE1\u53F7\u3002\n const next = lines[index + 1] ?? ''\n if (/[\u2217\u2020\u2021]/.test(next) || /@/.test(next)) score += 8\n candidates.push({ line, index, score })\n }\n candidates.sort((left, right) => right.score - left.score || left.index - right.index)\n return candidates[0]?.line ?? ''\n}\n\n/** \u6807\u9898\u50CF\u4E0D\u50CF\u7AE0\u8282\u6807\u9898\uFF08\u7F16\u53F7\u3001\u5E38\u89C1\u7AE0\u8282\u540D\u3001\u77ED\u884C\uFF09\u3002 */\nexport function looksLikeHeading(line: string): boolean {\n if (line === '' || line.length > 42) return false\n if (looksLikeByline(line)) return false\n if (/^(abstract|\u6458\u8981|introduction|\u5F15\u8A00|related work|\u76F8\u5173\u5DE5\u4F5C|method|\u65B9\u6CD5|approach|experiment|\u5B9E\u9A8C|results?|\u7ED3\u679C|discussion|\u8BA8\u8BBA|conclusion|\u7ED3\u8BBA|references?|\u53C2\u8003\u6587\u732E|limitations?|\u5C40\u9650)/i.test(line)) return true\n if (/^\\d+(?:\\.\\d+)*\\.?\\s+\\S/.test(line) && !/[\u3002.]$/.test(line)) return true\n return false\n}\n\n/** \u628A\u300C## \u7B2C N \u9875\u300D\u5206\u9875\u6807\u8BB0\u8FD8\u539F\u4E3A\u9875\u6570\u7EC4\uFF1B\u6CA1\u6709\u6807\u8BB0\u65F6\u89C6\u4E3A\u5355\u9875\u3002 */\nexport function splitNormalizedPages(text: string): AttachmentPage[] {\n const parts = text.split(/^## \u7B2C (\\d+) \u9875$/m)\n if (parts.length < 3) return [{ index: 1, text }]\n const pages: AttachmentPage[] = []\n for (let index = 1; index < parts.length; index += 2) {\n pages.push({ index: Number(parts[index] ?? pages.length + 1), text: parts[index + 1] ?? '' })\n }\n return pages\n}\n\n/** \u89C4\u8303\u5316\u5168\u6587\uFF08\u5E26\u9875\u6807\u8BB0\uFF09\uFF1A\u7ED9 agent/\u9884\u89C8\u8BFB\u539F\u6587\u7528\u3002 */\nexport function normalizeWithPageMarkers(title: string, raw: ExtractedAttachment): { text: string; pages: AttachmentPage[] } {\n const pages = raw.pages.map(page => ({ index: page.index, text: cleanText(page.text) }))\n const body = pages.length > 1\n ? pages.map(page => '## \u7B2C ' + String(page.index) + ' \u9875\\n\\n' + page.text).join('\\n\\n')\n : (pages[0]?.text ?? '')\n const heading = title === '' ? '' : '# ' + title + '\\n\\n'\n return { text: cleanText(heading + body) + '\\n', pages }\n}\n\n/**\n * \u628A\u9644\u4EF6\u89E3\u6790\u7ED3\u679C\u8F6C\u6210\u300C\u53EF\u7F16\u8F91\u7684\u8BBA\u6587\u521D\u7A3F\u300D\uFF08Markdown\uFF09\uFF1A\n * - Markdown \u8F93\u5165\u539F\u6837\u4FDD\u7559\uFF1B\n * - DOCX/TXT \u6BCF\u884C\u89C6\u4E3A\u4E00\u6BB5\uFF1B\n * - PDF \u6BCF\u4E2A\u89C6\u89C9\u884C\u62FC\u56DE\u6BB5\u843D\uFF0C\u9047\u5230\u7AE0\u8282\u6807\u9898/\u5217\u8868\u9879/\u6362\u9875\u65F6\u65AD\u6BB5\u3002\n * \u4E0D\u5220\u4F5C\u8005\u4E0E\u7248\u6743\u884C\uFF08\u8BBA\u6587\u5DE5\u574A\u8981\u6539\u7684\u662F\u539F\u6587\uFF0C\u4E0D\u662F\u91CD\u5199\u6458\u8981\uFF09\u3002\n */\nexport function draftMarkdownFromAttachment(kind: PaperKind, title: string, pages: AttachmentPage[]): string {\n if (kind === 'markdown') {\n const text = pages.map(page => cleanText(page.text)).join('\\n\\n').trim()\n return text === '' ? '' : text + '\\n'\n }\n const blocks: string[] = []\n const normalizedTitle = title.replace(/\\s+/g, ' ').trim().toLowerCase()\n let skippedTitle = false\n if (title !== '') blocks.push('# ' + title)\n // PDF \u7684\u89C6\u89C9\u6362\u884C\uFF1A\u4E2D\u6587\u4E4B\u95F4\u4E0D\u52A0\u7A7A\u683C\uFF0C\u82F1\u6587\u5355\u8BCD\u4E4B\u95F4\u52A0\uFF1B\u884C\u5C3E\u8FDE\u5B57\u7B26\u8981\u63A5\u56DE\u53BB\u3002\n const joinLines = (parts: string[]): string => {\n let text = ''\n for (const part of parts) {\n if (text === '') { text = part; continue }\n const last = text.slice(-1)\n const first = part.slice(0, 1)\n if (last === '-' && /[a-z]/.test(first)) { text = text.slice(0, -1) + part; continue }\n const cjk = /[\\u4e00-\\u9fff]/.test(last) || /[\\u4e00-\\u9fff]/.test(first)\n text += (cjk ? '' : ' ') + part\n }\n return text\n }\n for (const page of pages) {\n let buffer: string[] = []\n const flush = (): void => {\n const paragraph = joinLines(buffer).replace(/\\s+/g, ' ').replace(/([\\u4e00-\\u9fff])\\s+([\\u4e00-\\u9fff])/g, '$1$2').trim()\n if (paragraph !== '') blocks.push(paragraph)\n buffer = []\n }\n for (const rawLine of page.text.split('\\n')) {\n const line = rawLine.trim()\n if (line === '') { flush(); continue }\n if (/^\u7B2C\\s*\\d+\\s*\u9875$/.test(line)) continue\n // PDF \u9996\u9875\u7684\u6807\u9898\u884C\u4F1A\u91CD\u590D\u51FA\u73B0\uFF1A\u4FDD\u7559 Markdown \u6807\u9898\uFF0C\u6B63\u6587\u91CC\u53BB\u6389\u7B2C\u4E00\u6B21\u91CD\u590D\u3002\n if (!skippedTitle && normalizedTitle !== '' && line.replace(/\\s+/g, ' ').trim().toLowerCase() === normalizedTitle) {\n skippedTitle = true\n flush()\n continue\n }\n if (looksLikeHeading(line)) {\n flush()\n blocks.push((/^\\d/.test(line) ? '## ' : '## ') + line)\n continue\n }\n if (/^([-*\u2022]|\\d+[.)])\\s+/.test(line) || looksLikeByline(line)) {\n flush()\n blocks.push(line)\n continue\n }\n if (kind === 'pdf') {\n buffer.push(line)\n // \u6781\u7AEF\u957F\u6BB5\uFF08PDF \u884C\u88AB\u6574\u9875\u62FC\u8D77\u6765\uFF09\u4E3B\u52A8\u65AD\u6BB5\uFF0C\u7F16\u8F91\u5668\u624D\u4E0D\u4F1A\u51FA\u73B0\u4E00\u5835\u5899\u3002\n if (buffer.join(' ').length > 1200) flush()\n } else { flush(); blocks.push(line) }\n }\n flush()\n }\n return blocks.join('\\n\\n').trim() + '\\n'\n}\n", "/** \u514D\u9879\u76EE\u8BBA\u6587\u5DE5\u574A\uFF08paper workshop\uFF09\uFF1A\u4E0D\u5EFA\u9879\u76EE\uFF0C\u4E0A\u4F20\u81EA\u5DF1\u7684\u5386\u53F2\u8BBA\u6587 \u2192 \u8F6C\u6210\u53EF\u7F16\u8F91 Markdown \u2192 \u4FEE\u6539 \u2192 \u7F16\u8BD1/\u5BFC\u51FA\u3002\n *\n * \u4E0E\u5B9E\u9A8C\u5DE5\u574A\uFF08standalone\uFF09\u540C\u6784\uFF1Adetached \u9879\u76EE\u76EE\u5F55\uFF0C\u53EF\u63D0\u6848\u3001\u53EF\u7F16\u8BD1\uFF0C\u4F46\u4E0D\u8FDB\u9879\u76EE\u5E93\u3002\n * \u76EE\u5F55\uFF08<KSRW_HOME>/paper-workshops/<id>/\uFF09\uFF1A\n * workshop.json \u5DE5\u574A\u6E05\u5355\uFF08\u6765\u6E90\u9644\u4EF6\u5143\u4FE1\u606F\uFF09\n * project.json detached \u9879\u76EE\u8BB0\u5F55\uFF08stage=paper\uFF09\n * paper/attachment.* \u539F\u59CB\u9644\u4EF6\uFF08PDF / DOCX / MD / TXT\uFF09\n * paper/source.md \u89C4\u8303\u5316\u5168\u6587\uFF08PDF \u6309\u300C## \u7B2C N \u9875\u300D\u5206\u9875\uFF0C\u4F9B\u5BF9\u7167/agent \u8BFB\u539F\u6587\uFF09\n * paper/original.md \u5BFC\u5165\u65F6\u7684\u521D\u7A3F\uFF08\u53EF\u4E00\u952E\u6062\u590D\uFF09\n * paper/main.md \u5F53\u524D\u8BBA\u6587\u6B63\u6587\uFF08paper \u5DE5\u4EF6\uFF0C\u63D0\u6848\u9A71\u52A8\uFF09\n * paper/*.pdf|docx \u7F16\u8BD1/\u5BFC\u51FA\u4EA7\u7269\uFF08\u590D\u7528 kernel paper \u7F16\u8BD1\u6D41\u6C34\u7EBF\uFF09\n */\nimport { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs'\nimport { join, resolve, sep } from 'node:path'\n\nimport {\n MAX_ATTACHMENT_BYTES,\n attachmentKindOf,\n cleanText,\n detectTitle,\n draftMarkdownFromAttachment,\n extractAttachment,\n normalizeWithPageMarkers,\n safeAttachmentName,\n splitNormalizedPages,\n type PaperKind,\n} from './attachment.ts'\nimport { KsrwError } from './error.ts'\nimport { extractDocxMedia } from './figures.ts'\nimport {\n listPaperOutputs,\n paperFinalStatus,\n paperStatus,\n readPaperBuild,\n type PaperBuildRecord,\n type PaperOutput,\n type PaperTools,\n} from './paper.ts'\nimport { ProjectStore, forgetProject, resolveKsrwHome, type ProjectRecord } from './workspace.ts'\n\nexport const PAPER_WORKSHOP_DIR = 'paper-workshops'\nexport const MAX_WORKSHOP_ATTACHMENT_BYTES = MAX_ATTACHMENT_BYTES\nconst MAX_SOURCE_TEXT = 200_000\n\nexport interface PaperSourceMeta {\n fileName: string\n kind: PaperKind\n bytes: number\n uploadedAt: number\n pages: number\n chars: number\n title: string\n}\n\nexport interface PaperWorkshopManifest {\n schemaVersion: 1\n id: string\n name: string\n createdAt: number\n source: PaperSourceMeta | null\n}\n\nexport type PaperWorkshopStatus = 'empty' | 'imported' | 'compiled'\n\nexport interface PaperWorkshopSummary {\n id: string\n name: string\n path: string\n createdAt: number\n updatedAt: number\n status: PaperWorkshopStatus\n title: string\n revision: number\n words: number\n source: PaperSourceMeta | null\n}\n\nexport interface PaperWorkshopCompile {\n ready: boolean\n pdfEngine: 'xelatex' | 'soffice' | 'none'\n tools: PaperTools\n outputs: PaperOutput[]\n build: PaperBuildRecord | null\n stale: boolean\n}\n\nexport interface PaperWorkshopDetail {\n workshop: PaperWorkshopSummary\n draft: { revision: number; updatedAt: number; content: string }\n original: string\n sourceText: string\n compile: PaperWorkshopCompile\n}\n\nexport function resolvePaperWorkshopRoot(home: string = resolveKsrwHome()): string {\n return join(resolve(home), PAPER_WORKSHOP_DIR)\n}\n\nfunction readJson<T>(file: string): T | null {\n try { return JSON.parse(readFileSync(file, 'utf8')) as T } catch { return null }\n}\n\nfunction readText(file: string): string | null {\n try { return readFileSync(file, 'utf8') } catch { return null }\n}\n\nfunction writeJson(file: string, value: unknown): void {\n mkdirSync(join(file, '..'), { recursive: true, mode: 0o700 })\n writeFileSync(file, JSON.stringify(value, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n}\n\nfunction paperDir(projectPath: string): string {\n return join(projectPath, 'paper')\n}\n\nfunction manifestPath(projectPath: string): string {\n return join(projectPath, 'workshop.json')\n}\n\nfunction deriveManifest(projectPath: string): PaperWorkshopManifest | null {\n const project = readJson<ProjectRecord>(join(projectPath, 'project.json'))\n if (project === null) return null\n return { schemaVersion: 1, id: project.id, name: project.name, createdAt: project.createdAt, source: null }\n}\n\nfunction readManifest(projectPath: string): PaperWorkshopManifest | null {\n return readJson<PaperWorkshopManifest>(manifestPath(projectPath)) ?? deriveManifest(projectPath)\n}\n\nfunction writeManifest(projectPath: string, manifest: PaperWorkshopManifest): void {\n writeJson(manifestPath(projectPath), manifest)\n}\n\nfunction two(value: number): string {\n return value < 10 ? '0' + String(value) : String(value)\n}\n\nfunction stamp(at: number): string {\n const date = new Date(at)\n return String(date.getFullYear()) + two(date.getMonth() + 1) + two(date.getDate())\n + '-' + two(date.getHours()) + two(date.getMinutes()) + two(date.getSeconds())\n}\n\nfunction shortId(): string {\n return Math.random().toString(36).slice(2, 8)\n}\n\nfunction defaultName(at: number): string {\n const date = new Date(at)\n return '\u8BBA\u6587\u4FEE\u6539 \u00B7 ' + two(date.getMonth() + 1) + '-' + two(date.getDate())\n + ' ' + two(date.getHours()) + ':' + two(date.getMinutes())\n}\n\n/** \u4E2D\u6587\u5B57\u7B26 + \u62C9\u4E01\u8BCD\u8BA1\u6570\uFF08\u4E0D\u542B\u7A7A\u767D\u4E0E\u6807\u70B9\uFF09\u3002 */\nexport function countPaperWords(text: string): number {\n const cjk = (text.match(/[\\u4e00-\\u9fff]/g) ?? []).length\n const latin = (text.match(/[A-Za-z0-9]+(?:['\u2019-][A-Za-z0-9]+)*/g) ?? []).length\n return cjk + latin\n}\n\nfunction compileOf(projectPath: string): PaperWorkshopCompile {\n const status = paperStatus(projectPath, 'paper/main.md')\n const final = paperFinalStatus(projectPath, 'paper')\n return {\n ready: status.ready,\n pdfEngine: status.pdfEngine,\n tools: status.tools,\n // \u9644\u4EF6\u672C\u8EAB\u4E0D\u662F\u4EA4\u4ED8\u7269\uFF1A\u5217\u8868\u91CC\u53EA\u4FDD\u7559\u6B63\u6587\u3001PDF \u4E0E\u5BFC\u51FA\u7684 Word\u3002\n outputs: listPaperOutputs(projectPath, 'paper').filter(output => !output.name.startsWith('attachment.')),\n build: readPaperBuild(projectPath, 'paper') ?? final.build ?? null,\n stale: final.stale,\n }\n}\n\nfunction statusOf(source: PaperSourceMeta | null, revision: number, compile: PaperWorkshopCompile): PaperWorkshopStatus {\n if (compile.build !== null && compile.build.pdf !== '' && !compile.stale) return 'compiled'\n if (source !== null || revision > 0) return 'imported'\n return 'empty'\n}\n\n/** \u8BFB\u5DE5\u574A\u5B8C\u6574\u89C6\u56FE\uFF08\u6B63\u6587 + \u521D\u7A3F + \u539F\u6587 + \u7F16\u8BD1\u72B6\u6001\uFF09\u3002 */\nexport function readPaperWorkshop(projectPath: string, options: { home?: string } = {}): PaperWorkshopDetail {\n const directory = resolve(projectPath)\n const manifest = readManifest(directory)\n if (manifest === null) throw new KsrwError('NOT_FOUND', '\u4E0D\u662F KSRW \u8BBA\u6587\u5DE5\u574A\uFF08\u7F3A workshop.json / project.json\uFF09\uFF1A' + directory)\n const store = new ProjectStore(options.home === undefined ? {} : { home: options.home })\n const project = store.readProject(directory)\n const artifact = store.readArtifact(directory, 'paper')\n const draft = {\n revision: artifact?.revision ?? 0,\n updatedAt: artifact?.updatedAt ?? project.updatedAt,\n content: artifact?.content ?? '',\n }\n const compile = compileOf(directory)\n const sourceTextRaw = readText(join(paperDir(directory), 'source.md')) ?? ''\n const sourceText = sourceTextRaw.length > MAX_SOURCE_TEXT\n ? sourceTextRaw.slice(0, MAX_SOURCE_TEXT) + '\\n\u2026\uFF08\u539F\u6587\u5DF2\u622A\u65AD\uFF09'\n : sourceTextRaw\n const summary: PaperWorkshopSummary = {\n id: manifest.id,\n name: manifest.name,\n path: directory,\n createdAt: manifest.createdAt,\n updatedAt: Math.max(project.updatedAt, draft.updatedAt, manifest.source?.uploadedAt ?? 0, compile.build?.generatedAt ?? 0),\n status: statusOf(manifest.source, draft.revision, compile),\n title: manifest.source?.title ?? '',\n revision: draft.revision,\n words: countPaperWords(draft.content),\n source: manifest.source,\n }\n return {\n workshop: summary,\n draft,\n original: readText(join(paperDir(directory), 'original.md')) ?? '',\n sourceText,\n compile,\n }\n}\n\n/** \u5DE5\u574A\u5217\u8868\uFF08\u6700\u8FD1\u6D3B\u8DC3\u4F18\u5148\uFF09\u3002 */\nexport function listPaperWorkshops(options: { home?: string } = {}): PaperWorkshopSummary[] {\n const root = resolvePaperWorkshopRoot(options.home ?? resolveKsrwHome())\n if (!existsSync(root)) return []\n let entries: string[] = []\n try { entries = readdirSync(root) } catch { return [] }\n const items: PaperWorkshopSummary[] = []\n for (const entry of entries) {\n if (entry.startsWith('.')) continue\n const directory = join(root, entry)\n try {\n if (!statSync(directory).isDirectory()) continue\n items.push(readPaperWorkshop(directory, options).workshop)\n } catch { /* \u4E0D\u662F\u5DE5\u574A\u76EE\u5F55\u6216\u5DF2\u635F\u574F\uFF1A\u8DF3\u8FC7 */ }\n }\n return items.sort((left, right) => right.updatedAt - left.updatedAt)\n}\n\n/** \u65B0\u5EFA\u8BBA\u6587\u5DE5\u574A\uFF08detached \u9879\u76EE + paper \u76EE\u5F55 + \u6E05\u5355\uFF09\u3002 */\nexport function createPaperWorkshop(input: { name?: string; home?: string; now?: number } = {}): PaperWorkshopDetail {\n const home = resolve(input.home ?? resolveKsrwHome())\n const root = resolvePaperWorkshopRoot(home)\n mkdirSync(root, { recursive: true, mode: 0o700 })\n const at = input.now ?? Date.now()\n const name = (input.name ?? '').trim() === '' ? defaultName(at) : (input.name ?? '').trim()\n const id = 'paper-' + stamp(at) + '-' + shortId()\n const directory = join(root, id)\n const store = new ProjectStore({ home })\n const project = store.create({\n name,\n directory,\n detached: true,\n idea: '\u514D\u9879\u76EE\u8BBA\u6587\u5DE5\u574A\uFF1A\u4E0A\u4F20\u5386\u53F2\u8BBA\u6587\u540E\u4FEE\u6539\u5E76\u91CD\u65B0\u7F16\u8BD1\u3002',\n })\n store.setStage(project.path, 'paper')\n mkdirSync(paperDir(directory), { recursive: true, mode: 0o700 })\n writeManifest(directory, { schemaVersion: 1, id, name, createdAt: at, source: null })\n return readPaperWorkshop(directory, { home })\n}\n\n/** \u4E0A\u4F20/\u66FF\u6362\u5386\u53F2\u8BBA\u6587\uFF1A\u843D\u76D8\u539F\u6587 + \u89C4\u8303\u5316\u539F\u6587 + \u53EF\u7F16\u8F91\u521D\u7A3F\uFF08\u4F5C\u4E3A paper \u5DE5\u4EF6 revision\uFF09\u3002 */\nexport async function importPaperAttachment(\n projectPath: string,\n input: { fileName: string; bytes: Uint8Array; home?: string },\n): Promise<PaperWorkshopDetail> {\n const directory = resolve(projectPath)\n const manifest = readManifest(directory)\n if (manifest === null) throw new KsrwError('NOT_FOUND', '\u4E0D\u662F KSRW \u8BBA\u6587\u5DE5\u574A\uFF1A' + directory)\n const fileName = safeAttachmentName(input.fileName)\n const kind = attachmentKindOf(fileName)\n if (kind === null) {\n const extension = fileName.slice(fileName.lastIndexOf('.'))\n throw new KsrwError('INVALID', '\u53EA\u652F\u6301 PDF / DOCX / MD / TXT / TeX \u8BBA\u6587\u9644\u4EF6\uFF0C\u6536\u5230\uFF1A' + (extension === fileName ? '\u65E0\u6269\u5C55\u540D' : extension))\n }\n const bytes = Buffer.from(input.bytes)\n if (bytes.byteLength === 0) throw new KsrwError('INVALID', '\u9644\u4EF6\u4E3A\u7A7A')\n if (bytes.byteLength > MAX_WORKSHOP_ATTACHMENT_BYTES) throw new KsrwError('INVALID', '\u9644\u4EF6\u8D85\u8FC7 48MiB \u4E0A\u9650')\n\n const dir = paperDir(directory)\n mkdirSync(dir, { recursive: true, mode: 0o700 })\n // P2-18\uFF1A\u5148\u89E3\u6790\u3001\u540E\u66FF\u6362\u3002\u89E3\u6790\u5931\u8D25\uFF08\u574F\u6587\u4EF6/\u626B\u63CF\u7248 PDF\uFF09\u65F6\u65E7\u9644\u4EF6\u4E0E\u65E7\u6B63\u6587\u539F\u6837\u4FDD\u7559\u3002\n const raw = await extractAttachment(kind, new Uint8Array(bytes))\n const title = detectTitle(raw.pages) === '' ? manifest.name : detectTitle(raw.pages)\n const normalized = normalizeWithPageMarkers(title, raw)\n const draft = draftMarkdownFromAttachment(kind, title, normalized.pages)\n if (draft.trim() === '') throw new KsrwError('INVALID', '\u6CA1\u80FD\u4ECE\u9644\u4EF6\u91CC\u62BD\u51FA\u6B63\u6587\uFF08\u53EF\u80FD\u662F\u626B\u63CF\u7248 PDF\uFF09\uFF1A\u8BF7\u6362\u4E00\u4EFD\u53EF\u590D\u5236\u6587\u672C\u7684\u6587\u4EF6')\n\n for (const name of readdirSync(dir)) {\n if (name.startsWith('attachment.')) rmSync(join(dir, name), { force: true })\n }\n const extension = fileName.slice(fileName.lastIndexOf('.'))\n writeFileSync(join(dir, 'attachment' + extension), bytes, { mode: 0o600 })\n writeFileSync(join(dir, 'source.md'), normalized.text, { encoding: 'utf8', mode: 0o600 })\n writeFileSync(join(dir, 'original.md'), draft, { encoding: 'utf8', mode: 0o600 })\n // \u4EA7\u54C1 bug \u4FEE\u590D\uFF082026-10-30\uFF09\uFF1ADOCX \u5BFC\u5165\u6B64\u524D\u53EA\u62BD\u6B63\u6587\u3001\u4E22\u6389 word/media/ \u91CC\u7684\u539F\u56FE\u3002\n // \u8FD9\u91CC\u628A\u5185\u5D4C\u56FE\u7247\u62BD\u8FDB paper/figures \u5E76\u767B\u8BB0\uFF0C\u7528\u6237\u7684\u539F\u56FE\u4ECE\u5BFC\u5165\u90A3\u4E00\u523B\u8D77\u5C31\u53EF\u8FBE\uFF1B\n // \u62BD\u53D6\u5931\u8D25\uFF08\u9644\u4EF6\u672C\u6765\u6CA1\u56FE\u3001zip \u635F\u574F\uFF09\u4E0D\u963B\u585E\u5BFC\u5165\u3002\n if (kind === 'docx') {\n try { extractDocxMedia(directory, { docx: 'paper/attachment' + extension }) } catch { /* \u65E0\u5185\u5D4C\u56FE\uFF1A\u5FFD\u7565 */ }\n }\n\n const store = new ProjectStore(input.home === undefined ? {} : { home: input.home })\n store.writeArtifact(directory, 'paper', draft)\n store.touch(store.readProject(directory))\n const meta: PaperSourceMeta = {\n fileName,\n kind,\n bytes: bytes.byteLength,\n uploadedAt: Date.now(),\n pages: splitNormalizedPages(normalized.text).length,\n chars: normalized.text.length,\n title,\n }\n writeManifest(directory, { ...manifest, source: meta })\n return readPaperWorkshop(directory, input.home === undefined ? {} : { home: input.home })\n}\n\n/** \u4FDD\u5B58\u6B63\u6587\uFF08\u7528\u6237\u76F4\u5199\u6743\u5A01\u6587\u4EF6\uFF1BAgent \u8D70\u63D0\u6848\uFF09\u3002 */\nexport function savePaperWorkshopDraft(projectPath: string, content: string, options: { home?: string } = {}): PaperWorkshopDetail['draft'] {\n if (content.trim() === '') throw new KsrwError('INVALID', '\u8BBA\u6587\u6B63\u6587\u4E0D\u80FD\u4E3A\u7A7A')\n const store = new ProjectStore(options.home === undefined ? {} : { home: options.home })\n const view = store.writeArtifact(projectPath, 'paper', content)\n return { revision: view.revision, updatedAt: view.updatedAt, content: view.content }\n}\n\n/** \u6062\u590D\u4E3A\u5BFC\u5165\u65F6\u7684\u521D\u7A3F\uFF08\u4F1A\u65B0\u589E\u4E00\u4E2A revision\uFF0C\u4E0D\u5220\u7528\u6237\u5386\u53F2\uFF09\u3002 */\nexport function restorePaperWorkshopDraft(projectPath: string, options: { home?: string } = {}): PaperWorkshopDetail['draft'] {\n const directory = resolve(projectPath)\n // \u5148\u786E\u8BA4\u662F\u5DE5\u574A\uFF1A\u5426\u5219\u4E0D\u5B58\u5728\u7684\u8DEF\u5F84\u4F1A\u4F2A\u88C5\u6210\u300C\u8FD8\u6CA1\u4E0A\u4F20\u8BBA\u6587\u300D\u3002\n if (readManifest(directory) === null) throw new KsrwError('NOT_FOUND', '\u4E0D\u662F KSRW \u8BBA\u6587\u5DE5\u574A\uFF1A' + directory)\n const original = readText(join(paperDir(directory), 'original.md'))\n if (original === null) throw new KsrwError('NOT_FOUND', '\u8FD8\u6CA1\u6709\u5BFC\u5165\u8FC7\u7684\u521D\u7A3F\uFF1A\u5148\u4E0A\u4F20\u5386\u53F2\u8BBA\u6587')\n return savePaperWorkshopDraft(projectPath, original, options)\n}\n\n/** \u5220\u9664\u5DE5\u574A\u76EE\u5F55\uFF08\u53EA\u5141\u8BB8\u5220 paper-workshops \u6839\u4E0B\u7684\u76EE\u5F55\uFF09+ \u6E05\u6389\u53EF\u80FD\u7684\u9879\u76EE\u5E93\u767B\u8BB0\u3002 */\nexport function deletePaperWorkshop(projectPath: string, options: { home?: string } = {}): boolean {\n const home = resolve(options.home ?? resolveKsrwHome())\n const root = resolvePaperWorkshopRoot(home)\n const target = resolve(projectPath)\n if (target === root || !target.startsWith(root + sep)) {\n throw new KsrwError('PATH_DENIED', '\u53EA\u80FD\u5220\u9664 paper-workshops \u5DE5\u574A\u76EE\u5F55\uFF1A' + target)\n }\n if (!existsSync(target)) throw new KsrwError('NOT_FOUND', '\u8BBA\u6587\u5DE5\u574A\u4E0D\u5B58\u5728\uFF1A' + target)\n rmSync(target, { recursive: true, force: true })\n forgetProject(home, target)\n return true\n}\n\n/** \u628A\u5DE5\u574A\u63A5\u6536\u4E3A\u6B63\u5F0F\u9879\u76EE\uFF08\u6E05\u6389 detached\uFF0C\u8FDB\u9879\u76EE\u5E93\uFF09\u3002 */\nexport function registerPaperWorkshop(projectPath: string, options: { home?: string } = {}): PaperWorkshopDetail {\n const store = new ProjectStore(options.home === undefined ? {} : { home: options.home })\n store.register(projectPath)\n return readPaperWorkshop(projectPath, options)\n}\n\n/** \u4F9B\u5DE5\u5177\u4F7F\u7528\uFF1A\u8BFB\u5DE5\u574A\u91CC\u7684\u539F\u6587\uFF08\u5206\u7247\uFF09\u3002 */\nexport function readPaperWorkshopSource(projectPath: string): { text: string; chars: number } {\n const raw = readText(join(paperDir(resolve(projectPath)), 'source.md')) ?? ''\n return { text: raw, chars: raw.length }\n}\n\n/** \u4F9B\u5DE5\u5177\u4F7F\u7528\uFF1A\u89C4\u8303\u5316\u540E\u7684\u539F\u6587\u662F\u5426\u5DF2\u5B58\u5728\u3002 */\nexport function paperWorkshopImported(projectPath: string): boolean {\n return existsSync(join(paperDir(resolve(projectPath)), 'attachment.pdf'))\n || existsSync(join(paperDir(resolve(projectPath)), 'attachment.docx'))\n || existsSync(join(paperDir(resolve(projectPath)), 'attachment.md'))\n || existsSync(join(paperDir(resolve(projectPath)), 'attachment.markdown'))\n || existsSync(join(paperDir(resolve(projectPath)), 'attachment.txt'))\n || existsSync(join(paperDir(resolve(projectPath)), 'attachment.tex'))\n}\n\n/** \u8349\u7A3F\u7AE0\u8282\uFF08\u7ED9\u5DE5\u5177/UI \u5C55\u793A\u7ED3\u6784\uFF1B\u53EA\u505A\u6807\u9898\u8BC6\u522B\uFF0C\u4E0D\u6539\u6B63\u6587\uFF09\u3002 */\nexport function paperWorkshopSections(content: string): { heading: string; chars: number }[] {\n const sections: { heading: string; chars: number }[] = []\n let current: { heading: string; chars: number } | undefined\n for (const rawLine of content.split('\\n')) {\n const line = rawLine.trim()\n if (line === '') continue\n if (/^#{1,6}\\s+/.test(line)) {\n current = { heading: line.replace(/^#{1,6}\\s+/, ''), chars: 0 }\n sections.push(current)\n continue\n }\n if (current !== undefined) current.chars += cleanText(line).length\n }\n return sections.slice(0, 80)\n}\n", "/** KSRW \u77E5\u8BC6\u5E93\uFF1A\u672C\u5730 Markdown \u7D22\u5F15 + \u4E2D\u6587 bigram \u68C0\u7D22\uFF08\u96F6\u4F9D\u8D56\uFF09\u3002\n *\n * \u77E5\u8BC6\u6761\u76EE\u662F\u65B9\u6CD5/\u672F\u8BED/\u6A21\u677F\uFF0C\u4E0D\u662F\u5F15\u7528\u6743\u5A01\uFF1B\u6BCF\u6761\u7ED3\u679C\u90FD\u5E26 path/heading \u4F9B\u56DE\u70B9\u3002\n */\nimport { readFileSync, statSync } from 'node:fs'\nimport { join } from 'node:path'\n\nexport interface KnowledgeDoc {\n readonly id: string\n readonly title: string\n readonly path: string\n readonly category: string\n readonly chars: number\n}\n\nexport interface KnowledgeChunk {\n readonly id: string\n readonly docId: string\n readonly title: string\n readonly path: string\n readonly category: string\n readonly heading: string\n readonly text: string\n}\n\nexport interface KnowledgeIndex {\n readonly version: 1\n readonly builtAt: string\n readonly source: string\n readonly docs: readonly KnowledgeDoc[]\n readonly chunks: readonly KnowledgeChunk[]\n}\n\nexport interface KnowledgeHit {\n readonly docId: string\n readonly title: string\n readonly path: string\n readonly category: string\n readonly heading: string\n readonly snippet: string\n readonly score: number\n}\n\nconst LATIN = /[a-z0-9][a-z0-9_+.-]*/gi\nconst CJK = /[\\u4e00-\\u9fff]+/g\n\n/** Query terms: Latin words + Chinese bigrams (single char for one-char runs). */\nexport function knowledgeTerms(query: string): string[] {\n const terms: string[] = []\n for (const match of query.toLowerCase().matchAll(LATIN)) terms.push(match[0])\n for (const match of query.matchAll(CJK)) {\n const run = match[0]\n if (run.length === 1) terms.push(run)\n else for (let index = 0; index < run.length - 1; index += 1) terms.push(run.slice(index, index + 2))\n }\n return [...new Set(terms.filter(term => term.length > 0))]\n}\n\nfunction occurrences(text: string, term: string): number {\n if (term === '') return 0\n let count = 0\n let index = text.indexOf(term)\n while (index >= 0) {\n count += 1\n index = text.indexOf(term, index + term.length)\n }\n return count\n}\n\nfunction snippetFor(text: string, terms: readonly string[]): string {\n const lower = text.toLowerCase()\n let at = -1\n for (const term of terms) {\n const found = lower.indexOf(term)\n if (found >= 0 && (at < 0 || found < at)) at = found\n }\n const start = at < 0 ? 0 : Math.max(0, at - 60)\n return text.slice(start, start + 220).replace(/\\s+/g, ' ').trim()\n}\n\n/** Rank chunks by term frequency, heading/title boost, and exact phrase. */\nexport function searchKnowledge(index: KnowledgeIndex, query: string, limit = 8): KnowledgeHit[] {\n const terms = knowledgeTerms(query)\n if (terms.length === 0) return []\n const phrase = query.trim().toLowerCase()\n const hits: KnowledgeHit[] = []\n for (const chunk of index.chunks) {\n const text = chunk.text.toLowerCase()\n if (text.length === 0) continue\n let score = 0\n for (const term of terms) {\n const tf = occurrences(text, term)\n if (tf === 0) continue\n score += Math.min(tf, 12) * (term.length >= 3 ? 1.2 : 1)\n if (chunk.heading.toLowerCase().includes(term)) score += 3\n if (chunk.title.toLowerCase().includes(term)) score += 2\n }\n if (score === 0) continue\n if (phrase.length >= 2 && text.includes(phrase)) score += 6\n score /= Math.sqrt(Math.max(80, text.length) / 80)\n hits.push({\n docId: chunk.docId, title: chunk.title, path: chunk.path, category: chunk.category,\n heading: chunk.heading, snippet: snippetFor(chunk.text, terms), score: Number(score.toFixed(4)),\n })\n }\n return hits.sort((left, right) => right.score - left.score).slice(0, Math.max(1, Math.min(50, limit)))\n}\n\nexport function knowledgeStats(index: KnowledgeIndex): {\n docs: number; chunks: number; chars: number; categories: Record<string, number>\n} {\n const categories: Record<string, number> = {}\n for (const doc of index.docs) categories[doc.category] = (categories[doc.category] ?? 0) + 1\n return {\n docs: index.docs.length,\n chunks: index.chunks.length,\n chars: index.docs.reduce((sum, doc) => sum + doc.chars, 0),\n categories,\n }\n}\n\nexport function loadKnowledgeIndex(directory: string): KnowledgeIndex {\n const path = join(directory, 'index.json')\n const parsed = JSON.parse(readFileSync(path, 'utf8')) as KnowledgeIndex\n if (parsed.version !== 1 || !Array.isArray(parsed.chunks) || !Array.isArray(parsed.docs)) {\n throw new Error('knowledge index is invalid: ' + path)\n }\n statSync(path)\n return parsed\n}\n", "/** KSRW \u4EA7\u54C1 API\uFF08M0\uFF09\uFF1A\u9879\u76EE\u76EE\u5F55\u3001\u7814\u7A76\u72B6\u6001\u3001\u9009\u9898\u5361\u3001\u63D0\u6848/revision\u3002\n *\n * \u8DEF\u7531\u524D\u7F00 /ksrw/api\uFF1B\u540C\u6E90 cookie + \u5F15\u64CE connection \u56F4\u680F\uFF08\u82E5\u5BBF\u4E3B\u63D0\u4F9B\uFF09\u3002\n * \u4FE1\u5C01\uFF1A{ ok: true, value } | { ok: false, error: { code, message } }\u3002\n */\nimport { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'\nimport { connect } from 'node:net'\nimport { homedir } from 'node:os'\nimport { extname, join } from 'node:path'\n\nimport { UNAUTHENTICATED_ESCAPE } from './request-guard.ts'\n\nimport {\n KsrwError,\n ProjectStore,\n compilePaper,\n defaultPaperSource,\n exportPaperDocx,\n knowledgeStats,\n ksrwEnvelope,\n listPaperOutputs,\n loadKnowledgeIndex,\n invalidateStalePaperFinal,\n paperFinalStatus,\n readPaperBuild,\n paperStatus,\n addReadingPapers,\n listVenueTemplates,\n projectLockOwner,\n readPaperStyle,\n writePaperStyle,\n insertFigureIntoDraft,\n readVenueProfile,\n seedVenueChecklist,\n setVenueRequirement,\n writeVenueProfile,\n readReadingEntry,\n readReadingIndex,\n readReadingPrefs,\n readingFetchDue,\n readProjectFileBytes,\n resolveKsrwHome,\n runReadingFetch,\n searchLatestPapers,\n writeReadingDigest,\n writeReadingPrefs,\n searchKnowledge,\n MAX_PAPER_BYTES,\n analyzeStandalonePaper,\n createStandaloneExperiment,\n deleteStandaloneExperiment,\n ingestStandalonePaper,\n listStandaloneExperiments,\n readStandaloneExperiment,\n registerStandaloneExperiment,\n saveStandaloneDesign,\n writeStandaloneReport,\n writeStandaloneScaffold,\n MAX_WORKSHOP_ATTACHMENT_BYTES,\n createPaperWorkshop,\n deletePaperWorkshop,\n importPaperAttachment,\n listPaperWorkshops,\n readPaperWorkshop,\n readPaperWorkshopSource,\n registerPaperWorkshop,\n restorePaperWorkshopDraft,\n savePaperWorkshopDraft,\n RESEARCH_STAGES,\n type KnowledgeIndex,\n type ResearchStage,\n type TopicCard,\n} from 'ksrw-kernel'\n\nimport {\n cancelExperimentRun, cancelRemoteExperiment, listExperimentRuns, listExperimentScripts,\n pollRemoteExperiment, readRemoteRunInfo, readRunLog, startExperimentRun, startRemoteExperiment,\n} from './experiment.ts'\nimport { adoptFigure, adoptUnregisteredFigures, extractDocxMedia, ingestRunFigures, listFigures, readArchitectureSource, renderArchitecture, renderMetricsChart, saveArchitectureRender, validateFigures } from './figures.ts'\nimport { execRemoteTarget, listRemoteTargets, probeRemoteTarget, removeRemoteTarget, upsertRemoteTarget } from './remote.ts'\n\n/** \u5F15\u64CE office-to-pdf \u670D\u52A1\u7684\u6700\u5C0F\u7ED3\u6784\u9762\uFF08\u6302\u8F7D\u4E8E web \u7EC4\u5408\u5C42\uFF1B\u7F3A\u5931\u65F6\u9884\u89C8\u964D\u7EA7\u62A5\u9519\uFF09\u3002 */\nexport interface OfficePreviewService {\n convert(request: Record<string, unknown>): Promise<{ pdf: Uint8Array; missingFonts?: string[] }>\n}\n\nconst PAPER_DIRS = new Set(['paper', 'patent'])\n\nfunction paperDir(value: unknown): 'paper' | 'patent' {\n if (typeof value !== 'string' || !PAPER_DIRS.has(value)) throw new KsrwError('INVALID', 'dir \u5FC5\u987B\u662F paper \u6216 patent')\n return value as 'paper' | 'patent'\n}\n\nconst IMAGE_MIME: Record<string, string> = {\n '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif',\n '.webp': 'image/webp', '.svg': 'image/svg+xml', '.bmp': 'image/bmp',\n}\nconst DOWNLOAD_MIME: Record<string, string> = {\n '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n '.doc': 'application/msword',\n '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n '.pdf': 'application/pdf',\n '.md': 'text/markdown; charset=utf-8',\n '.txt': 'text/plain; charset=utf-8',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.svg': 'image/svg+xml',\n '.tex': 'text/plain; charset=utf-8',\n}\n\nconst OFFICE_EXTS = new Set(['doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx'])\nconst PREVIEW_TEXT_MAX = 1 * 1024 * 1024\nconst PREVIEW_PDF_MAX = 40 * 1024 * 1024\nconst PREVIEW_OFFICE_MAX = 50 * 1024 * 1024\n\ninterface ReqLike {\n url?: string\n method?: string\n headers?: Record<string, string | string[] | undefined>\n [Symbol.asyncIterator]?: () => AsyncIterator<Uint8Array | string>\n /** Node IncomingMessage: SSE \u7528\u5B83\u611F\u77E5\u5BA2\u6237\u7AEF\u65AD\u5F00\u3002 */\n on?: (event: 'close', listener: () => void) => void\n}\n/** SSE \u9700\u8981\u7684 Response \u9762\uFF08writeHead + write\uFF09\u3002 */\ninterface SseResponse {\n writeHead(code: number, headers?: Record<string, string>): unknown\n write(chunk: string): boolean\n}\ninterface ResLike {\n writeHead(code: number, headers?: Record<string, string>): unknown\n end(body?: unknown): unknown\n}\n\n/** \u5F15\u64CE connection \u670D\u52A1\u7684\u7ED3\u6784\u9762\uFF08\u53EA\u53D6\u672C\u63D2\u4EF6\u9700\u8981\u7684\u90A3\u4E00\u9879\uFF09\u3002 */\nexport interface ConnectionFence {\n requestRejection?(request: unknown): number | undefined\n}\n\n/** \u4E00\u6B21\u8BF7\u6C42\u7684\u51C6\u5165\u5224\u5B9A\uFF1A\u8FD4\u56DE HTTP \u72B6\u6001\u7801\u8868\u793A\u62D2\u7EDD\uFF0Cundefined \u8868\u793A\u653E\u884C\u3002 */\nexport type RequestGuard = (req: ReqLike) => number | undefined\n\n// \u5B88\u536B\u672C\u4F53\u4E0E\u9003\u751F\u53E3\u5E38\u91CF\u79FB\u5230\u96F6\u4F9D\u8D56\u7684 request-guard.ts\uFF1A\n// \u94C1\u5F8B\uFF08\u65B9\u6CD5\u5F62\u5F0F\u8C03\u7528\u5F15\u64CE\u56F4\u680F\u3001this \u4E0D\u5F97\u4E22\u5931\uFF09\u5728\u90A3\u91CC\u6709\u9632\u56DE\u5F52\u5355\u6D4B\u76F4\u63A5\u8986\u76D6\u3002\nexport { createRequestGuard, UNAUTHENTICATED_ESCAPE } from './request-guard.ts'\n\nconst MAX_BODY = 2 * 1024 * 1024\ninterface KnowledgeCacheEntry { readonly index: KnowledgeIndex; readonly directory: string; readonly mtimeMs: number }\nlet knowledgeCache: KnowledgeCacheEntry | undefined\n\n/** P2-13\uFF1A\u6309 index.json \u7684 mtime \u5931\u6548\uFF0Cimport:knowledge \u4E4B\u540E\u4E0D\u7528\u91CD\u542F\u5BBF\u4E3B\u3002 */\nfunction knowledgeIndex(): KnowledgeIndex {\n const directory = process.env['KSRW_KNOWLEDGE_DIR']?.trim() || join(resolveKsrwHome(), 'knowledge')\n let mtimeMs = -1\n try { mtimeMs = statSync(join(directory, 'index.json')).mtimeMs } catch { /* \u7F3A\u5931\u65F6\u4EA4\u7ED9 loader \u62A5\u9519 */ }\n if (knowledgeCache !== undefined && knowledgeCache.directory === directory && knowledgeCache.mtimeMs === mtimeMs) return knowledgeCache.index\n try {\n const index = loadKnowledgeIndex(directory)\n knowledgeCache = { index, directory, mtimeMs }\n return index\n } catch (error) {\n throw new KsrwError('NOT_FOUND', '\u77E5\u8BC6\u7D22\u5F15\u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u8BFB\uFF08' + directory + '\uFF09\uFF1B\u5148\u8FD0\u884C pnpm import:knowledge')\n }\n}\n\n/** P2-8\uFF1A\u5199\u8DEF\u5F84\u5148\u770B\u6709\u6CA1\u6709\u53E6\u4E00\u4E2A\u6D3B\u8FDB\u7A0B\u6301\u9501\uFF1B\u6709\u5C31\u76F4\u63A5 409\uFF0C\u4E0D\u5728\u4E8B\u4EF6\u5FAA\u73AF\u91CC\u540C\u6B65\u7B49 5s\u3002 */\nfunction assertProjectWritable(projectPath: string): void {\n const owner = projectLockOwner(projectPath)\n if (owner === null) return\n throw new KsrwError('LOCKED', '\u9879\u76EE\u6B63\u88AB\u53E6\u4E00\u4E2A\u8FDB\u7A0B\u5199\u5165\uFF08pid ' + owner.pid + '\uFF0C\u5DF2\u6301\u6709 ' + Math.round((Date.now() - owner.at) / 1000) + 's\uFF09\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5')\n}\nconst STAGES: readonly ResearchStage[] = RESEARCH_STAGES\n\n/** \u4EA7\u54C1\u4E8B\u4EF6\u6D41\uFF1A\u5199\u64CD\u4F5C\u540E\u5411\u8BA2\u9605\u7684 UI \u63A8\u4E00\u6761\u300C\u8BE5\u9879\u76EE\u53D8\u4E86\u300D\uFF0C\u66FF\u4EE3 4s/5s \u8F6E\u8BE2\u3002 */\ninterface ProjectEventClient {\n readonly res: SseResponse\n readonly path: string\n readonly timer: ReturnType<typeof setInterval>\n}\nconst projectEventClients = new Set<ProjectEventClient>()\nfunction notifyProject(path: string): void {\n if (projectEventClients.size === 0) return\n const payload = 'data: ' + JSON.stringify({ path, at: Date.now() }) + '\\n\\n'\n for (const client of projectEventClients) {\n if (client.path !== '' && client.path !== path) continue\n try {\n client.res.write(payload)\n } catch {\n projectEventClients.delete(client)\n clearInterval(client.timer)\n }\n }\n}\n\nfunction send(res: ResLike, status: number, value: unknown): void {\n res.writeHead(status, { 'content-type': 'application/json; charset=utf-8' })\n res.end(JSON.stringify(value))\n}\nfunction ok(res: ResLike, value: unknown): void {\n send(res, 200, { ok: true, value })\n}\nfunction fail(res: ResLike, error: unknown): void {\n const envelope = ksrwEnvelope(error)\n const status = error instanceof KsrwError\n ? error.code === 'NOT_FOUND' ? 404\n : error.code === 'STALE_REVISION' ? 409\n // \u53E6\u4E00\u4E2A\u8FDB\u7A0B\u6B63\u6301\u9501\u5199\u540C\u4E00\u9879\u76EE\uFF1A409 \u8BA9 UI \u63D0\u793A\u300C\u7A0D\u540E\u91CD\u8BD5\u300D\uFF0C\u800C\u4E0D\u662F\u5F53\u6210\u53C2\u6570\u9519\u8BEF\u3002\n : error.code === 'LOCKED' ? 409\n : error.code === 'DUPLICATE' ? 409\n : error.code === 'PATH_DENIED' ? 403\n : 400\n : 500\n send(res, status, { ok: false, error: envelope })\n}\n\nasync function readBody(req: ReqLike): Promise<Record<string, unknown>> {\n if (req[Symbol.asyncIterator] === undefined) return {}\n const chunks: Buffer[] = []\n let bytes = 0\n for await (const chunk of req as AsyncIterable<Uint8Array | string>) {\n const buffer = typeof chunk === 'string' ? Buffer.from(chunk) : Buffer.from(chunk)\n bytes += buffer.byteLength\n if (bytes > MAX_BODY) throw new KsrwError('INVALID', '\u8BF7\u6C42\u4F53\u8D85\u8FC7 2MiB')\n chunks.push(buffer)\n }\n if (bytes === 0) return {}\n try {\n const parsed: unknown = JSON.parse(Buffer.concat(chunks).toString('utf8'))\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new KsrwError('INVALID', '\u8BF7\u6C42\u4F53\u5FC5\u987B\u662F JSON \u5BF9\u8C61')\n }\n return parsed as Record<string, unknown>\n } catch (error) {\n if (error instanceof KsrwError) throw error\n throw new KsrwError('INVALID', '\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5 JSON')\n }\n}\n\n/** \u539F\u59CB\u5B57\u8282\u8BF7\u6C42\u4F53\uFF08\u8BBA\u6587\u9644\u4EF6\uFF09\uFF1A\u6309\u7D2F\u8BA1\u5B57\u8282\u6570\u6D41\u5F0F\u9650\u6D41\uFF0C\u8D85\u9650\u7ACB\u523B\u4E2D\u65AD\uFF0C\u4E0D\u5148\u8BFB\u5168\u91CF\u518D\u5224\u65AD\u3002 */\nexport async function readRawBody(req: ReqLike, limit: number): Promise<Buffer> {\n if (req[Symbol.asyncIterator] === undefined) {\n throw new KsrwError('INVALID', '\u7F3A\u5C11\u8BF7\u6C42\u4F53\uFF1A\u8BF7\u9009\u62E9\u8981\u4E0A\u4F20\u7684\u8BBA\u6587\u9644\u4EF6\uFF08PDF / DOCX / MD / TXT\uFF09')\n }\n const chunks: Buffer[] = []\n let bytes = 0\n for await (const chunk of req as AsyncIterable<Uint8Array | string>) {\n const buffer = typeof chunk === 'string' ? Buffer.from(chunk) : Buffer.from(chunk)\n bytes += buffer.byteLength\n if (bytes > limit) {\n throw new KsrwError('INVALID', '\u9644\u4EF6\u8D85\u8FC7\u4E0A\u9650\uFF08' + formatBytes(limit) + '\uFF09\uFF1A\u53EA\u652F\u6301 PDF / DOCX / MD / TXT\uFF0C\u8BF7\u538B\u7F29\u6216\u62C6\u5206\u540E\u91CD\u4F20')\n }\n chunks.push(buffer)\n }\n if (bytes === 0) throw new KsrwError('INVALID', '\u9644\u4EF6\u4E3A\u7A7A\uFF1A\u8BF7\u4E0A\u4F20 PDF / DOCX / MD / TXT \u8BBA\u6587\u6587\u4EF6')\n return Buffer.concat(chunks, bytes)\n}\n\nfunction formatBytes(limit: number): string {\n if (limit >= 1024 * 1024 && limit % (1024 * 1024) === 0) return String(limit / (1024 * 1024)) + 'MiB'\n return String(limit) + ' \u5B57\u8282'\n}\n\nfunction text(value: unknown, field: string): string {\n if (typeof value !== 'string' || value.trim() === '') throw new KsrwError('INVALID', field + ' \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32')\n return value.trim()\n}\nfunction optionalText(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim() !== '' ? value.trim() : undefined\n}\nfunction stageOf(value: unknown): ResearchStage {\n if (typeof value !== 'string' || !STAGES.includes(value as ResearchStage)) {\n throw new KsrwError('INVALID', '\u672A\u77E5\u9636\u6BB5\uFF1A' + String(value))\n }\n return value as ResearchStage\n}\nfunction topicOf(value: unknown): Omit<TopicCard, 'revision' | 'updatedAt'> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new KsrwError('INVALID', 'topic \u5FC5\u987B\u662F\u5BF9\u8C61')\n }\n const card = value as Record<string, unknown>\n const feasibility = card['feasibility']\n if (typeof feasibility !== 'object' || feasibility === null) throw new KsrwError('INVALID', 'topic.feasibility \u5FC5\u987B\u662F\u5BF9\u8C61')\n const f = feasibility as Record<string, unknown>\n const decision = card['decision']\n if (decision !== 'pending' && decision !== 'go' && decision !== 'no-go') {\n throw new KsrwError('INVALID', 'topic.decision \u5FC5\u987B\u662F pending/go/no-go')\n }\n return {\n question: text(card['question'], 'question'),\n motivation: typeof card['motivation'] === 'string' ? card['motivation'] : '',\n gap: typeof card['gap'] === 'string' ? card['gap'] : '',\n novelty: Array.isArray(card['novelty']) ? card['novelty'].filter((x): x is string => typeof x === 'string') : [],\n feasibility: {\n data: typeof f['data'] === 'string' ? f['data'] : '',\n compute: typeof f['compute'] === 'string' ? f['compute'] : '',\n time: typeof f['time'] === 'string' ? f['time'] : '',\n skill: typeof f['skill'] === 'string' ? f['skill'] : '',\n },\n risks: Array.isArray(card['risks']) ? card['risks'].filter((x): x is string => typeof x === 'string') : [],\n decision,\n }\n}\n\n/** \u6210\u679C\u6587\u4EF6\u9884\u89C8\uFF1A\u6587\u672C / \u56FE\u7247 / PDF \u76F4\u8BFB\uFF1BOffice \u8D70\u5F15\u64CE office-to-pdf \u8F6C\u6362\u3002 */\nasync function previewFile(store: ProjectStore, office: OfficePreviewService | undefined, body: Record<string, unknown>): Promise<Record<string, unknown>> {\n const projectPath = text(body['path'], 'path')\n const file = text(body['file'], 'file')\n const project = store.open(projectPath).project\n const extension = file.slice(file.lastIndexOf('.')).toLowerCase()\n if (extension === '.md' || extension === '.markdown' || extension === '.txt') {\n const bytes = readProjectFileBytes(project.path, file, PREVIEW_TEXT_MAX)\n return { kind: 'text', name: file, text: Buffer.from(bytes).toString('utf8') }\n }\n const mime = IMAGE_MIME[extension]\n if (mime !== undefined) {\n const bytes = readProjectFileBytes(project.path, file, PREVIEW_PDF_MAX)\n return { kind: 'image', name: file, mime, base64: Buffer.from(bytes).toString('base64') }\n }\n if (extension === '.pdf') {\n const bytes = readProjectFileBytes(project.path, file, PREVIEW_PDF_MAX)\n return { kind: 'pdf', name: file, base64: Buffer.from(bytes).toString('base64'), missingFonts: [] }\n }\n const officeExt = extension.startsWith('.') ? extension.slice(1) : extension\n if (!OFFICE_EXTS.has(officeExt)) throw new KsrwError('INVALID', '\u4E0D\u652F\u6301\u7684\u9884\u89C8\u683C\u5F0F\uFF1A' + file)\n if (office === undefined || typeof office.convert !== 'function') {\n throw new KsrwError('ENGINE_MISSING', 'Office \u9884\u89C8\u670D\u52A1\u672A\u542F\u7528\uFF08\u5F15\u64CE office-to-pdf \u672A\u6302\u8F7D\uFF09\uFF1B\u8BF7\u6539\u7528 PDF')\n }\n const bytes = readProjectFileBytes(project.path, file, PREVIEW_OFFICE_MAX)\n const result = await office.convert({\n extension: officeExt,\n priority: 'foreground',\n source: {\n key: 'ksrw/' + file,\n version: String(Date.now()) + '-' + bytes.byteLength,\n bytes: bytes.byteLength,\n read: async () => ({ bytes, version: String(Date.now()) + '-' + bytes.byteLength }),\n },\n })\n return {\n kind: 'pdf', name: file,\n base64: Buffer.from(result.pdf).toString('base64'),\n missingFonts: result.missingFonts ?? [],\n }\n}\n\n/** \u628A\u5B88\u536B\u7ED3\u8BBA\u843D\u6210\u62D2\u7EDD\u54CD\u5E94\uFF08/ksrw/api \u4E0E /ksrw/api/lab \u5171\u7528\u540C\u4E00\u53E3\u5F84\uFF09\u3002 */\nexport function rejectRequest(res: ResLike, status: number): void {\n // 503 \u53EA\u53EF\u80FD\u6765\u81EA fail-closed\uFF08\u5BBF\u4E3B\u6CA1\u7ED9\u56F4\u680F\uFF09\uFF1B401/403 \u662F\u5F15\u64CE\u4F1A\u8BDD\u6821\u9A8C\u7684\u7ED3\u8BBA\u3002\n if (status === 503) {\n send(res, 503, {\n ok: false,\n error: {\n code: 'AUTH_UNAVAILABLE',\n message: 'API \u672A\u542F\u7528\u9274\u6743\uFF08\u5BBF\u4E3B\u672A\u6302\u8F7D connection\uFF09\uFF1A\u8BF7\u7528\u684C\u9762\u7AEF\u767B\u5F55\u540E\u4F7F\u7528\uFF1B'\n + `\u5F00\u53D1\u6001\u786E\u9700\u88F8 API \u65F6\u8BBE\u7F6E ${UNAUTHENTICATED_ESCAPE}=1`,\n },\n })\n return\n }\n send(res, status, { ok: false, error: { code: 'AUTH_MISSING', message: '\u8BF7\u5148\u767B\u5F55' } })\n}\n\n/** Handle one /ksrw/api request. */\n/** \u5F15\u64CE\u300C\u81EA\u52A8\u5316\u4EFB\u52A1\u300D\u670D\u52A1\u9762\uFF08\u53EA\u7528\u5230 create\uFF09\u3002 */\nexport interface EngineScheduleService {\n create(request: Record<string, unknown>): Promise<unknown>\n}\n\nexport async function handleApi(\n req: ReqLike,\n res: ResLike,\n store: ProjectStore,\n guard: RequestGuard,\n office?: OfficePreviewService,\n schedule?: EngineScheduleService | null,\n): Promise<void> {\n try {\n const rejection = guard(req)\n if (typeof rejection === 'number') {\n rejectRequest(res, rejection)\n return\n }\n const url = new URL(req.url ?? '/ksrw/api', 'http://localhost')\n const pathname = url.pathname\n const method = (req.method ?? 'GET').toUpperCase()\n if (pathname === '/ksrw/api/events' && method === 'GET') {\n const watch = url.searchParams.get('path') ?? ''\n const sse = res as unknown as SseResponse\n sse.writeHead(200, {\n 'content-type': 'text/event-stream; charset=utf-8',\n 'cache-control': 'no-cache, no-transform',\n connection: 'keep-alive',\n 'x-accel-buffering': 'no',\n })\n sse.write(': connected\\n\\n')\n const timer = setInterval(() => { try { sse.write(': ping\\n\\n') } catch { /* client gone */ } }, 25_000)\n timer.unref?.()\n const client: ProjectEventClient = { res: sse, path: watch, timer }\n projectEventClients.add(client)\n req.on?.('close', () => {\n projectEventClients.delete(client)\n clearInterval(timer)\n })\n return\n }\n if (pathname === '/ksrw/api/health') {\n // \u6570\u636E\u6839\u7531\u5BBF\u4E3B\u89E3\u6790\uFF08\u63D2\u4EF6\u6A21\u5F0F\u843D\u5728\u5BBF\u4E3B home\uFF0C\u72EC\u7ACB\u4EA7\u54C1\u843D ~/.ksrw\uFF09\uFF1A\u6587\u6848\u91CC\u522B\u5199\u6B7B\u8DEF\u5F84\u3002\n const home = resolveKsrwHome()\n const label = home.startsWith(homedir()) ? '~' + home.slice(homedir().length) : home\n ok(res, { product: 'KSRW', version: '0.1.2', home, homeLabel: label })\n return\n }\n if (pathname === '/ksrw/api/project/figures' && method === 'GET') {\n ok(res, listFigures(text(url.searchParams.get('path'), 'path')))\n return\n }\n if (pathname === '/ksrw/api/project/figures/ingest' && method === 'POST') {\n const body = await readBody(req)\n ok(res, ingestRunFigures(text(body['path'], 'path'), text(body['runId'], 'runId')))\n return\n }\n if (pathname === '/ksrw/api/project/figures/chart' && method === 'POST') {\n const body = await readBody(req)\n ok(res, renderMetricsChart(text(body['path'], 'path'), text(body['runId'], 'runId')))\n return\n }\n if (pathname === '/ksrw/api/project/figures/adopt' && method === 'POST') {\n const body = await readBody(req)\n const caption = optionalText(body['caption'])\n const title = optionalText(body['title'])\n ok(res, adoptFigure(text(body['path'], 'path'), {\n file: text(body['file'], 'file'),\n ...(caption === undefined ? {} : { caption }),\n ...(title === undefined ? {} : { title }),\n ...(optionalText(body['draft']) === undefined ? {} : { draft: optionalText(body['draft'])! }),\n }))\n return\n }\n if (pathname === '/ksrw/api/project/figures/adopt-all' && method === 'POST') {\n const body = await readBody(req)\n ok(res, { adopted: adoptUnregisteredFigures(text(body['path'], 'path')) })\n return\n }\n if (pathname === '/ksrw/api/project/figures/validate' && method === 'GET') {\n const projectPath = text(url.searchParams.get('path'), 'path')\n const file = optionalText(url.searchParams.get('file'))\n const issues = validateFigures(projectPath, file === undefined ? {} : { file }) as { file: string; level: 'error' | 'warn'; message: string }[]\n ok(res, { issues, ok: issues.filter(item => item.level === 'error').length === 0 })\n return\n }\n if (pathname === '/ksrw/api/project/figures/architecture/source' && method === 'GET') {\n ok(res, readArchitectureSource(text(url.searchParams.get('path'), 'path')))\n return\n }\n if (pathname === '/ksrw/api/project/figures/architecture/render' && method === 'POST') {\n const body = await readBody(req)\n ok(res, saveArchitectureRender(text(body['path'], 'path'), {\n source: text(body['source'], 'source'),\n svg: text(body['svg'], 'svg'),\n pngBase64: text(body['pngBase64'], 'pngBase64'),\n }))\n return\n }\n if (pathname === '/ksrw/api/project/figures/architecture' && method === 'POST') {\n const body = await readBody(req)\n const projectPath = text(body['path'], 'path')\n const current = text(body['current'], 'current')\n ok(res, renderArchitecture(projectPath, current, text(body['subtitle'] ?? 'KSRW \u7814\u7A76\u6D41\u6C34\u7EBF', 'subtitle')))\n return\n }\n if (pathname === '/ksrw/api/experiment/scripts') {\n ok(res, listExperimentScripts(text(url.searchParams.get('path'), 'path')))\n return\n }\n if (pathname === '/ksrw/api/experiment/runs') {\n ok(res, listExperimentRuns(text(url.searchParams.get('path'), 'path')))\n return\n }\n if (pathname === '/ksrw/api/experiment/runs/log') {\n ok(res, { log: readRunLog(text(url.searchParams.get('path'), 'path'), text(url.searchParams.get('id'), 'id')) })\n return\n }\n if (pathname === '/ksrw/api/experiment/remote/run' && method === 'POST') {\n const body = await readBody(req)\n const args = Array.isArray(body['args']) ? body['args'].map(item => String(item)) : []\n const python = optionalText(body['python']) ?? ''\n ok(res, await startRemoteExperiment(text(body['path'], 'path'), text(body['targetId'], 'targetId'), text(body['script'], 'script'), args, python))\n return\n }\n if (pathname === '/ksrw/api/experiment/remote/status') {\n const projectPath = text(url.searchParams.get('path'), 'path')\n const id = text(url.searchParams.get('id'), 'id')\n ok(res, { run: await pollRemoteExperiment(projectPath, id), remote: readRemoteRunInfo(projectPath, id) ?? null })\n return\n }\n if (pathname === '/ksrw/api/experiment/remote/cancel' && method === 'POST') {\n const body = await readBody(req)\n ok(res, { cancelled: await cancelRemoteExperiment(text(body['path'], 'path'), text(body['id'], 'id')) })\n return\n }\n if (pathname === '/ksrw/api/experiment/run' && method === 'POST') {\n const body = await readBody(req)\n const args = Array.isArray(body['args']) ? body['args'].map(item => String(item)) : []\n ok(res, startExperimentRun(text(body['path'], 'path'), text(body['script'], 'script'), args))\n return\n }\n if (pathname === '/ksrw/api/experiment/runs/cancel' && method === 'POST') {\n const body = await readBody(req)\n // P2-12\uFF1A\u5E26\u9879\u76EE\u4F5C\u7528\u57DF\uFF1B\u65E7\u8C03\u7528\u65B9\u4E0D\u4F20 path \u65F6\u4FDD\u6301\u539F\u884C\u4E3A\u3002\n const projectPath = typeof body['path'] === 'string' || body['path'] instanceof String ? String(body['path']) : undefined\n ok(res, { cancelled: cancelExperimentRun(text(body['id'], 'id'), projectPath) })\n return\n }\n if (pathname === '/ksrw/api/remote/targets' && method === 'GET') {\n ok(res, listRemoteTargets())\n return\n }\n if (pathname === '/ksrw/api/remote/targets' && method === 'POST') {\n const body = await readBody(req)\n const id = optionalText(body['id'])\n const password = optionalText(body['password'])\n const keyPath = optionalText(body['keyPath'])\n const passphrase = optionalText(body['passphrase'])\n const workdir = optionalText(body['workdir'])\n ok(res, upsertRemoteTarget({\n ...(id === undefined ? {} : { id }),\n name: text(body['name'], 'name'),\n host: text(body['host'], 'host'),\n port: Number(body['port'] ?? 22),\n user: text(body['user'], 'user'),\n ...(body['authKind'] === 'key' ? { authKind: 'key' as const }\n : body['authKind'] === 'agent' ? { authKind: 'agent' as const }\n : {}),\n ...(password === undefined ? {} : { password }),\n ...(keyPath === undefined ? {} : { keyPath }),\n ...(passphrase === undefined ? {} : { passphrase }),\n ...(workdir === undefined ? {} : { workdir }),\n ...(optionalText(body['pythonPath']) === undefined ? {} : { pythonPath: optionalText(body['pythonPath'])! }),\n ...(optionalText(body['pipIndexUrl']) === undefined ? {} : { pipIndexUrl: optionalText(body['pipIndexUrl'])! }),\n }))\n return\n }\n if (pathname === '/ksrw/api/remote/targets/delete' && method === 'POST') {\n const body = await readBody(req)\n ok(res, { removed: removeRemoteTarget(text(body['id'], 'id')) })\n return\n }\n if (pathname === '/ksrw/api/remote/test' && method === 'POST') {\n const body = await readBody(req)\n ok(res, await probeRemoteTarget(text(body['id'], 'id')))\n return\n }\n if (pathname === '/ksrw/api/remote/exec' && method === 'POST') {\n const body = await readBody(req)\n ok(res, await execRemoteTarget(text(body['id'], 'id'), text(body['command'], 'command'), Number(body['timeoutMs'] ?? 120000)))\n return\n }\n if (pathname === '/ksrw/api/remote/probe') {\n const host = text(url.searchParams.get('host'), 'host')\n const port = Number.parseInt(text(url.searchParams.get('port'), 'port'), 10)\n const started = Date.now()\n const reachable = await new Promise<boolean>((resolve) => {\n const socket = connect({ host, port })\n const finish = (value: boolean): void => { socket.destroy(); resolve(value) }\n socket.setTimeout(8000)\n socket.once('connect', () => finish(true))\n socket.once('timeout', () => finish(false))\n socket.once('error', () => finish(false))\n })\n ok(res, { host, port, reachable, latencyMs: Date.now() - started })\n return\n }\n if (pathname === '/ksrw/api/knowledge/stats') { ok(res, knowledgeStats(knowledgeIndex())); return }\n if (pathname === '/ksrw/api/knowledge/search') {\n const query = text(url.searchParams.get('q'), 'q')\n const limit = Number.parseInt(url.searchParams.get('limit') ?? '8', 10)\n ok(res, searchKnowledge(knowledgeIndex(), query, Number.isFinite(limit) ? limit : 8))\n return\n }\n\n // \u2500\u2500 \u514D\u9879\u76EE\u5B9E\u9A8C\u5DE5\u574A\uFF08standalone\uFF09\uFF1A\u4E0A\u4F20\u8BBA\u6587 \u2192 \u62BD\u53D6\u8981\u7D20 \u2192 \u8BBE\u8BA1 \u2192 \u62A5\u544A \u2500\u2500\n if (pathname === '/ksrw/api/standalone' && method === 'GET') {\n // \u65E0 path \u53C2\u6570 = \u5217\u8868\uFF1B\u5E26 path \u4F46\u4E3A\u7A7A = \u53C2\u6570\u9519\u8BEF\uFF08\u522B\u9759\u9ED8\u8FD4\u56DE\u5217\u8868\uFF09\u3002\n const projectPath = url.searchParams.get('path')\n if (projectPath === null) { ok(res, { items: listStandaloneExperiments() }); return }\n ok(res, readStandaloneExperiment(text(projectPath, 'path')))\n return\n }\n if (pathname === '/ksrw/api/standalone' && method === 'POST') {\n const body = await readBody(req)\n const name = optionalText(body['name'])\n ok(res, createStandaloneExperiment(name === undefined ? {} : { name }))\n return\n }\n if (pathname === '/ksrw/api/standalone/delete' && method === 'POST') {\n const body = await readBody(req)\n ok(res, { removed: deleteStandaloneExperiment(text(body['path'], 'path')) })\n return\n }\n if (pathname === '/ksrw/api/standalone/register' && method === 'POST') {\n const body = await readBody(req)\n ok(res, registerStandaloneExperiment(text(body['path'], 'path')))\n return\n }\n if (pathname === '/ksrw/api/standalone/paper' && method === 'POST') {\n // \u9644\u4EF6\u662F\u539F\u59CB\u5B57\u8282\uFF08content-type \u4EFB\u610F\uFF0C\u4E0D\u662F JSON\uFF09\uFF1A\u72EC\u7ACB\u9650\u6D41\uFF0C\u6269\u5C55\u540D\u767D\u540D\u5355\u4EA4\u7ED9 kernel\u3002\n const projectPath = text(url.searchParams.get('path'), 'path')\n const fileName = text(url.searchParams.get('name'), 'name')\n const bytes = await readRawBody(req, MAX_PAPER_BYTES)\n ok(res, await ingestStandalonePaper(projectPath, { fileName, bytes }))\n return\n }\n if (pathname === '/ksrw/api/standalone/paper' && method === 'GET') {\n const detail = readStandaloneExperiment(text(url.searchParams.get('path'), 'path'))\n const paper = detail.experiment.paper\n if (paper === null) {\n throw new KsrwError('NOT_FOUND', '\u8FD9\u4E2A\u5DE5\u574A\u8FD8\u6CA1\u6709\u8BBA\u6587\u9644\u4EF6\uFF1A\u5148\u4E0A\u4F20 PDF / DOCX / MD / TXT')\n }\n ok(res, {\n name: paper.fileName, kind: paper.kind, title: paper.title,\n pages: paper.pages, chars: paper.chars, text: detail.paperText,\n })\n return\n }\n if (pathname === '/ksrw/api/standalone/analyze' && method === 'POST') {\n const body = await readBody(req)\n ok(res, analyzeStandalonePaper(text(body['path'], 'path')))\n return\n }\n if (pathname === '/ksrw/api/standalone/design' && method === 'POST') {\n const body = await readBody(req)\n const design = saveStandaloneDesign(text(body['path'], 'path'), text(body['content'], 'content'))\n ok(res, { revision: design.revision, updatedAt: design.updatedAt })\n return\n }\n if (pathname === '/ksrw/api/standalone/design/scaffold' && method === 'POST') {\n const body = await readBody(req)\n const projectPath = text(body['path'], 'path')\n ok(res, writeStandaloneScaffold(projectPath, body['overwrite'] === true ? { overwrite: true } : {}))\n return\n }\n if (pathname === '/ksrw/api/standalone/report' && method === 'POST') {\n const body = await readBody(req)\n ok(res, writeStandaloneReport(text(body['path'], 'path')))\n return\n }\n\n // \u2500\u2500 \u514D\u9879\u76EE\u8BBA\u6587\u5DE5\u574A\uFF08paper workshop\uFF09\uFF1A\u4E0A\u4F20\u5386\u53F2\u8BBA\u6587 \u2192 \u6539\u6B63\u6587 \u2192 \u7F16\u8BD1/\u5BFC\u51FA \u2192 \u53EF\u767B\u8BB0\u4E3A\u9879\u76EE \u2500\u2500\n if (pathname === '/ksrw/api/paper-workshop' && method === 'GET') {\n // \u65E0 path \u53C2\u6570 = \u5217\u8868\uFF1B\u5E26 path \u4F46\u4E3A\u7A7A = \u53C2\u6570\u9519\u8BEF\uFF08\u522B\u9759\u9ED8\u8FD4\u56DE\u5217\u8868\uFF09\u3002\n const workshopPath = url.searchParams.get('path')\n if (workshopPath === null) { ok(res, { items: listPaperWorkshops() }); return }\n ok(res, readPaperWorkshop(text(workshopPath, 'path')))\n return\n }\n if (pathname === '/ksrw/api/paper-workshop' && method === 'POST') {\n const body = await readBody(req)\n const name = optionalText(body['name'])\n ok(res, createPaperWorkshop(name === undefined ? {} : { name }))\n return\n }\n if (pathname === '/ksrw/api/paper-workshop/delete' && method === 'POST') {\n const body = await readBody(req)\n ok(res, { removed: deletePaperWorkshop(text(body['path'], 'path')) })\n return\n }\n if (pathname === '/ksrw/api/paper-workshop/register' && method === 'POST') {\n const body = await readBody(req)\n ok(res, registerPaperWorkshop(text(body['path'], 'path')))\n return\n }\n if (pathname === '/ksrw/api/paper-workshop/import' && method === 'POST') {\n // \u9644\u4EF6\u662F\u539F\u59CB\u5B57\u8282\uFF08content-type \u4EFB\u610F\uFF0C\u4E0D\u662F JSON\uFF09\uFF1A\u72EC\u7ACB\u9650\u6D41\uFF0C\u6269\u5C55\u540D\u767D\u540D\u5355\u4EA4\u7ED9 kernel\u3002\n const workshopPath = text(url.searchParams.get('path'), 'path')\n const fileName = text(url.searchParams.get('name'), 'name')\n const bytes = await readRawBody(req, MAX_WORKSHOP_ATTACHMENT_BYTES)\n ok(res, await importPaperAttachment(workshopPath, { fileName, bytes }))\n return\n }\n // \u4ECE\u9644\u4EF6 DOCX \u91CC\u62BD\u5185\u5D4C\u539F\u56FE\uFF08\u5BFC\u5165\u65F6\u5DF2\u81EA\u52A8\u62BD\uFF1B\u8FD9\u91CC\u7528\u4E8E\u8865\u62BD / \u6362\u9644\u4EF6\u540E\u91CD\u62BD\uFF09\u3002\n if (pathname === '/ksrw/api/paper-workshop/docx-media' && method === 'POST') {\n const body = await readBody(req)\n const workshopPath = text(body['path'], 'path')\n const docx = optionalText(body['docx'])\n ok(res, extractDocxMedia(workshopPath, docx === undefined ? {} : { docx }))\n return\n }\n if (pathname === '/ksrw/api/paper-workshop/source' && method === 'GET') {\n const workshopPath = text(url.searchParams.get('path'), 'path')\n const detail = readPaperWorkshop(workshopPath)\n const source = detail.workshop.source\n if (source === null) {\n throw new KsrwError('NOT_FOUND', '\u8FD9\u4E2A\u8BBA\u6587\u5DE5\u574A\u8FD8\u6CA1\u6709\u4E0A\u4F20\u8FC7\u539F\u6587\uFF1A\u5148\u4E0A\u4F20 PDF / DOCX / MD / TXT \u8BBA\u6587\u9644\u4EF6')\n }\n const raw = readPaperWorkshopSource(workshopPath)\n ok(res, {\n name: source.fileName,\n kind: source.kind,\n title: source.title,\n pages: source.pages,\n chars: raw.chars,\n text: raw.text,\n })\n return\n }\n if (pathname === '/ksrw/api/paper-workshop/draft' && method === 'POST') {\n const body = await readBody(req)\n const workshopPath = text(body['path'], 'path')\n // \u6B63\u6587\u53EF\u80FD\u6709\u610F\u7684\u9996\u5C3E\u7A7A\u884C\uFF1A\u53EA\u6821\u9A8C\u7C7B\u578B\uFF0C\u4E0D trim\uFF1B\u7A7A\u6B63\u6587\u7531 kernel \u62D2\u7EDD\u3002\n const content = body['content']\n if (typeof content !== 'string') throw new KsrwError('INVALID', 'content \u5FC5\u987B\u662F\u8BBA\u6587\u6B63\u6587\u6587\u672C\uFF08Markdown\uFF09')\n const draft = savePaperWorkshopDraft(workshopPath, content)\n ok(res, { revision: draft.revision, updatedAt: draft.updatedAt })\n return\n }\n if (pathname === '/ksrw/api/paper-workshop/restore' && method === 'POST') {\n const body = await readBody(req)\n const workshopPath = text(body['path'], 'path')\n restorePaperWorkshopDraft(workshopPath)\n ok(res, readPaperWorkshop(workshopPath))\n return\n }\n\n if (pathname === '/ksrw/api/projects' && method === 'GET') { ok(res, store.list()); return }\n if (pathname === '/ksrw/api/projects' && method === 'POST') {\n const body = await readBody(req)\n const project = store.create({\n name: text(body['name'], 'name'),\n ...(optionalText(body['idea']) === undefined ? {} : { idea: optionalText(body['idea'])! }),\n ...(optionalText(body['directory']) === undefined ? {} : { directory: optionalText(body['directory'])! }),\n })\n ok(res, store.open(project.path))\n notifyProject('')\n return\n }\n if (pathname === '/ksrw/api/projects/forget' && method === 'POST') {\n const body = await readBody(req)\n store.forget(text(body['path'], 'path'))\n ok(res, { forgotten: true })\n notifyProject('')\n return\n }\n if (pathname === '/ksrw/api/projects/register' && method === 'POST') {\n const body = await readBody(req)\n const registered = store.register(text(body['directory'], 'directory'))\n ok(res, registered)\n notifyProject('')\n return\n }\n\n if (pathname === '/ksrw/api/project' && method === 'GET') {\n const projectPath = text(url.searchParams.get('path'), 'path')\n ok(res, store.open(projectPath))\n return\n }\n if (pathname === '/ksrw/api/project/artifact' && method === 'GET') {\n const projectPath = text(url.searchParams.get('path'), 'path')\n const ref = text(url.searchParams.get('ref'), 'ref')\n ok(res, store.readArtifact(projectPath, ref))\n return\n }\n if (pathname === '/ksrw/api/project/artifact' && method === 'POST') {\n const body = await readBody(req)\n const projectPath = text(body['path'], 'path')\n assertProjectWritable(projectPath)\n ok(res, store.writeArtifact(projectPath, text(body['ref'], 'ref'), text(body['content'], 'content')))\n notifyProject(projectPath)\n return\n }\n if (pathname === '/ksrw/api/project/topic' && method === 'POST') {\n const body = await readBody(req)\n const projectPath = text(body['path'], 'path')\n assertProjectWritable(projectPath)\n ok(res, store.saveTopic(projectPath, topicOf(body['topic'])))\n notifyProject(projectPath)\n return\n }\n if (pathname === '/ksrw/api/project/stage' && method === 'POST') {\n const body = await readBody(req)\n const projectPath = text(body['path'], 'path')\n assertProjectWritable(projectPath)\n ok(res, store.setStage(projectPath, stageOf(body['stage'])))\n notifyProject(projectPath)\n return\n }\n if (pathname === '/ksrw/api/project/gate' && method === 'POST') {\n const body = await readBody(req)\n const projectPath = text(body['path'], 'path')\n assertProjectWritable(projectPath)\n ok(res, store.gate(projectPath, stageOf(body['stage'])))\n notifyProject(projectPath)\n return\n }\n if (pathname === '/ksrw/api/project/handoffs' && method === 'GET') {\n const projectPath = text(url.searchParams.get('path'), 'path')\n ok(res, store.readHandoffs(projectPath))\n return\n }\n if (pathname === '/ksrw/api/project/handoffs/status' && method === 'POST') {\n const body = await readBody(req)\n const status = text(body['status'], 'status')\n if (!['open', 'started', 'accepted', 'returned', 'done'].includes(status)) {\n throw new KsrwError('INVALID', '\u672A\u77E5\u4EA4\u63A5\u72B6\u6001\uFF1A' + status)\n }\n const note = optionalText(body['note'])\n const projectPath = text(body['path'], 'path')\n assertProjectWritable(projectPath)\n ok(res, store.updateHandoff(projectPath, text(body['id'], 'id'),\n status as 'open' | 'started' | 'accepted' | 'returned' | 'done', note))\n notifyProject(projectPath)\n return\n }\n if (pathname === '/ksrw/api/project/handoffs' && method === 'POST') {\n const body = await readBody(req)\n const projectPath = text(body['path'], 'path')\n assertProjectWritable(projectPath)\n ok(res, store.appendHandoff(projectPath, {\n from: optionalText(body['from']) ?? 'user',\n to: text(body['to'], 'to'),\n artifacts: Array.isArray(body['artifacts']) ? body['artifacts'].filter((item): item is string => typeof item === 'string') : [],\n acceptance: Array.isArray(body['acceptance']) ? body['acceptance'].filter((item): item is string => typeof item === 'string') : [],\n requestedOutput: text(body['requestedOutput'], 'requestedOutput'),\n ...(optionalText(body['note']) === undefined ? {} : { note: optionalText(body['note'])! }),\n }))\n notifyProject(projectPath)\n return\n }\n if (pathname === '/ksrw/api/project/proposals' && method === 'GET') {\n ok(res, store.open(text(url.searchParams.get('path'), 'path')).proposals)\n return\n }\n if (pathname === '/ksrw/api/project/proposals' && method === 'POST') {\n const body = await readBody(req)\n const projectPath = text(body['path'], 'path')\n assertProjectWritable(projectPath)\n ok(res, store.createProposal(projectPath, {\n artifactRef: text(body['artifactRef'], 'artifactRef'),\n baseRevision: typeof body['baseRevision'] === 'number' ? body['baseRevision'] : 0,\n summary: text(body['summary'], 'summary'),\n content: text(body['content'], 'content'),\n ...(optionalText(body['authorRole']) === undefined ? {} : { authorRole: optionalText(body['authorRole'])! }),\n }))\n notifyProject(projectPath)\n return\n }\n if (pathname === '/ksrw/api/project/proposals/apply' && method === 'POST') {\n const body = await readBody(req)\n const projectPath = text(body['path'], 'path')\n assertProjectWritable(projectPath)\n ok(res, store.applyProposal(projectPath, text(body['proposalId'], 'proposalId')))\n notifyProject(projectPath)\n return\n }\n if (pathname === '/ksrw/api/project/proposals/discard' && method === 'POST') {\n const body = await readBody(req)\n const projectPath = text(body['path'], 'path')\n assertProjectWritable(projectPath)\n ok(res, store.discardProposal(projectPath, text(body['proposalId'], 'proposalId')))\n notifyProject(projectPath)\n return\n }\n if (pathname === '/ksrw/api/project/paper/style' && method === 'GET') {\n const projectPath = text(url.searchParams.get('path'), 'path')\n ok(res, { ...readPaperStyle(projectPath), templates: listVenueTemplates() })\n return\n }\n if (pathname === '/ksrw/api/project/paper/style' && method === 'POST') {\n const body = await readBody(req)\n ok(res, writePaperStyle(text(body['path'], 'path'), text(body['venue'], 'venue')))\n return\n }\n if (pathname === '/ksrw/api/project/paper/status' && method === 'GET') {\n const projectPath = text(url.searchParams.get('path'), 'path')\n const dir = paperDir(url.searchParams.get('dir') ?? 'paper')\n const source = optionalText(url.searchParams.get('source')) ?? defaultPaperSource(dir)\n invalidateStalePaperFinal(projectPath, dir)\n const final = paperFinalStatus(projectPath, dir)\n const styleId = readPaperStyle(projectPath).venue\n const venue = listVenueTemplates().find(item => item.id === styleId)\n ok(res, {\n ...paperStatus(projectPath, source),\n outputs: listPaperOutputs(projectPath, dir),\n build: final.build ?? null,\n draft: readPaperBuild(projectPath, dir) ?? null,\n stale: final.stale,\n draftStale: final.draftStale,\n style: venue ?? { id: styleId, label: styleId, columns: 1 },\n })\n return\n }\n if (pathname === '/ksrw/api/project/paper/compile' && method === 'POST') {\n const body = await readBody(req)\n const projectPath = text(body['path'], 'path')\n const dir = paperDir(body['dir'] ?? 'paper')\n const source = optionalText(body['source']) ?? defaultPaperSource(dir)\n ok(res, compilePaper(projectPath, source))\n return\n }\n if (pathname === '/ksrw/api/project/paper/export-docx' && method === 'POST') {\n const body = await readBody(req)\n const projectPath = text(body['path'], 'path')\n const dir = paperDir(body['dir'] ?? 'paper')\n const source = optionalText(body['source']) ?? defaultPaperSource(dir)\n ok(res, exportPaperDocx(projectPath, source))\n return\n }\n if (pathname === '/ksrw/api/project/file' && method === 'GET') {\n // \u539F\u59CB\u6587\u4EF6\u4E0B\u8F7D\uFF1AWord/PPT/Excel \u9884\u89C8\u4F1A\u8F6C\u6210 PDF\uFF0C\u4F46\u300C\u4E0B\u8F7D\u300D\u5FC5\u987B\u7ED9\u539F\u6587\u4EF6\u3002\n const projectPath = text(url.searchParams.get('path'), 'path')\n const file = text(url.searchParams.get('file'), 'file')\n const project = store.open(projectPath).project\n const bytes = readProjectFileBytes(project.path, file, PREVIEW_OFFICE_MAX * 4)\n const contentType = DOWNLOAD_MIME[extname(file).toLowerCase()] ?? 'application/octet-stream'\n res.writeHead(200, {\n 'content-type': contentType,\n 'content-disposition': 'attachment; filename=\"' + encodeURIComponent(file.split('/').pop() ?? file) + '\"',\n 'content-length': String(bytes.byteLength),\n })\n res.end(Buffer.from(bytes))\n return\n }\n if (pathname === '/ksrw/api/project/reading' && method === 'GET') {\n const projectPath = text(url.searchParams.get('path'), 'path')\n const prefs = readReadingPrefs(projectPath)\n ok(res, { prefs, entries: readReadingIndex(projectPath), due: readingFetchDue(prefs) })\n return\n }\n if (pathname === '/ksrw/api/project/reading/prefs' && method === 'POST') {\n const body = await readBody(req)\n ok(res, writeReadingPrefs(text(body['path'], 'path'), (body['prefs'] ?? {}) as Record<string, never>))\n return\n }\n if (pathname === '/ksrw/api/project/reading/search' && method === 'POST') {\n const body = await readBody(req)\n const projectPath = text(body['path'], 'path')\n const prefs = readReadingPrefs(projectPath)\n ok(res, await searchLatestPapers(prefs, { days: Number(body['days'] ?? 30), max: Number(body['max'] ?? 10) }))\n return\n }\n if (pathname === '/ksrw/api/project/reading/add' && method === 'POST') {\n const body = await readBody(req)\n const projectPath = text(body['path'], 'path')\n const raw = Array.isArray(body['papers']) ? body['papers'] : []\n const papers = raw.map(item => {\n const paper = (item ?? {}) as Record<string, unknown>\n return {\n arxivId: String(paper['arxivId'] ?? ''),\n title: String(paper['title'] ?? ''),\n authors: Array.isArray(paper['authors']) ? paper['authors'].map(value => String(value)) : [],\n abstract: String(paper['abstract'] ?? ''),\n categories: Array.isArray(paper['categories']) ? paper['categories'].map(value => String(value)) : [],\n published: String(paper['published'] ?? ''),\n updated: String(paper['updated'] ?? paper['published'] ?? ''),\n absUrl: String(paper['absUrl'] ?? ''),\n ...(typeof paper['pdfUrl'] === 'string' ? { pdfUrl: paper['pdfUrl'] } : {}),\n }\n })\n ok(res, await addReadingPapers(projectPath, papers))\n return\n }\n if (pathname === '/ksrw/api/project/reading/entry' && method === 'GET') {\n ok(res, readReadingEntry(text(url.searchParams.get('path'), 'path'), text(url.searchParams.get('id'), 'id')))\n return\n }\n if (pathname === '/ksrw/api/project/reading/digest' && method === 'POST') {\n const body = await readBody(req)\n ok(res, writeReadingDigest(text(body['path'], 'path'), text(body['id'], 'id'), text(body['markdown'], 'markdown')))\n return\n }\n if (pathname === '/ksrw/api/project/reading/fetch' && method === 'POST') {\n const body = await readBody(req)\n const max = optionalText(body['max'])\n ok(res, await runReadingFetch(text(body['path'], 'path'), max === undefined ? {} : { max: Number(max) }))\n return\n }\n if (pathname === '/ksrw/api/project/reading/schedule' && method === 'POST') {\n const body = await readBody(req)\n const projectPath = text(body['path'], 'path')\n const requested = text(body['time'], 'time')\n const timeZone = optionalText(body['timeZone']) ?? 'Asia/Shanghai'\n // \u5F15\u64CE\u7684 daily.time \u53EA\u8BA4 HH:MM:SS\uFF1BHH:MM \u4F1A\u88AB\u5224\u6210 invalid_rule\u3002\n const clock = /^\\d{2}:\\d{2}$/.test(requested) ? requested + ':00' : requested\n const project = store.list().find(item => item.path === projectPath)\n if (project === undefined) {\n send(res, 404, { ok: false, error: { code: 'project_not_found', message: '\u9879\u76EE\u672A\u767B\u8BB0\uFF1A' + projectPath } })\n return\n }\n if (schedule === undefined || schedule === null) {\n send(res, 503, { ok: false, error: { code: 'schedule_unavailable', message: '\u5BBF\u4E3B\u672A\u542F\u7528\u300C\u81EA\u52A8\u5316\u4EFB\u52A1\u300D\uFF0C\u65E0\u6CD5\u521B\u5EFA\u5B9A\u65F6\u6293\u53D6' } })\n return\n }\n const created = await schedule.create({\n title: ('\u6587\u732E\u6293\u53D6 \u00B7 ' + project.name).slice(0, 120),\n prompt: '\u8C03\u7528 ksrw_reading \u5DE5\u5177\uFF0Caction=fetch\uFF0Cpath=' + projectPath + '\uFF0C\u6293\u53D6\u6700\u65B0 arXiv \u5E76\u6309\u7CBE\u8BFB\u504F\u597D\u5165\u5E93\uFF1B\u5DF2\u7ECF\u5165\u5E93\u7684\u8BBA\u6587\u4E0D\u8981\u91CD\u590D\u6DFB\u52A0\u3002',\n timing: { kind: 'daily', daily: { time: clock, time_zone: timeZone } },\n target: { kind: 'new_once' },\n }) as { id?: string; scheduledAt?: string } | null\n ok(res, { id: created?.id ?? '', nextScheduledAt: created?.scheduledAt ?? '' })\n return\n }\n if (pathname === '/ksrw/api/project/venue' && method === 'GET') {\n ok(res, readVenueProfile(text(url.searchParams.get('path'), 'path')))\n return\n }\n if (pathname === '/ksrw/api/project/venue' && method === 'POST') {\n const body = await readBody(req)\n ok(res, writeVenueProfile(text(body['path'], 'path'), (body['patch'] ?? {}) as Record<string, never>))\n return\n }\n if (pathname === '/ksrw/api/project/venue/seed' && method === 'POST') {\n const body = await readBody(req)\n ok(res, seedVenueChecklist(text(body['path'], 'path'), text(body['requirements'], 'requirements')))\n return\n }\n if (pathname === '/ksrw/api/project/venue/set' && method === 'POST') {\n const body = await readBody(req)\n const status = text(body['status'], 'status')\n if (status !== 'todo' && status !== 'done' && status !== 'na') throw new KsrwError('INVALID', 'status \u53EA\u652F\u6301 todo/done/na')\n const note = optionalText(body['note'])\n ok(res, setVenueRequirement(text(body['path'], 'path'), text(body['id'], 'id'), status, note))\n return\n }\n if (pathname === '/ksrw/api/project/paper/insert-figure' && method === 'POST') {\n const body = await readBody(req)\n const dir = paperDir(body['dir'] ?? 'paper')\n const source = optionalText(body['source']) ?? defaultPaperSource(dir)\n ok(res, insertFigureIntoDraft(text(body['path'], 'path'), source, text(body['file'], 'file'), text(body['caption'], 'caption')))\n return\n }\n if (pathname === '/ksrw/api/preview' && method === 'POST') {\n const body = await readBody(req)\n ok(res, await previewFile(store, office, body))\n return\n }\n if (pathname === '/ksrw/api/project/evidence' && method === 'GET') {\n ok(res, store.readEvidence(text(url.searchParams.get('path'), 'path')))\n return\n }\n if (pathname === '/ksrw/api/project/evidence' && method === 'POST') {\n const body = await readBody(req)\n const source = body['source']\n if (typeof source !== 'object' || source === null) throw new KsrwError('INVALID', 'source \u5FC5\u987B\u662F\u5BF9\u8C61')\n const s = source as Record<string, unknown>\n if (s['kind'] !== 'paper' && s['kind'] !== 'run' && s['kind'] !== 'patent') {\n throw new KsrwError('INVALID', 'source.kind \u5FC5\u987B\u662F paper/run/patent')\n }\n const projectPath = text(body['path'], 'path')\n assertProjectWritable(projectPath)\n ok(res, store.appendEvidence(projectPath, {\n claimRef: text(body['claimRef'], 'claimRef'),\n source: {\n kind: s['kind'],\n ref: text(s['ref'], 'source.ref'),\n ...(optionalText(s['locator']) === undefined ? {} : { locator: optionalText(s['locator'])! }),\n },\n quote: text(body['quote'], 'quote'),\n }))\n notifyProject(projectPath)\n return\n }\n\n send(res, 404, { ok: false, error: { code: 'NOT_FOUND', message: 'unknown endpoint: ' + pathname } })\n } catch (error) {\n fail(res, error)\n }\n}\n", "/** /ksrw/api \u8BF7\u6C42\u5B88\u536B\uFF1A\u628A\u5F15\u64CE connection \u56F4\u680F\u9002\u914D\u6210\u5355\u4E00\u5224\u5B9A\u51FD\u6570\u3002\n *\n * \u94C1\u5F8B\uFF082026-10-30\uFF09\uFF1A\u5FC5\u987B\u4EE5**\u65B9\u6CD5\u5F62\u5F0F**\u8C03\u7528 fence.requestRejection(req)\u3002\n * \u5F15\u64CE\u4FA7 requestRejection \u5185\u90E8\u662F this.admit(request)\u2014\u2014\u628A\u65B9\u6CD5\u62C6\u6210\u88F8\u51FD\u6570\u518D\u8C03\u7528\n * \u4F1A\u4E22 this\uFF08ESM \u4E25\u683C\u6001\u4E3A undefined\uFF09\uFF0C\u6BCF\u4E2A /ksrw/api \u8BF7\u6C42\u90FD 500\n * \u300CCannot read properties of undefined (reading 'admit')\u300D\uFF0C\u9879\u76EE\u5E93\u6574\u4E2A\u4E0D\u53EF\u7528\u3002\n * tests/runtime-policy.test.mjs \u91CC\u6709\u9632\u56DE\u5F52\u7528\u4F8B\uFF1A\u62C6\u7ED1\u8C03\u7528\u5FC5\u987B\u66B4\u9732\u5931\u8D25\u3001\n * \u65B9\u6CD5\u8C03\u7528\u5FC5\u987B\u653E\u884C\u3002\n *\n * \u672C\u6A21\u5757\u4FDD\u6301\u96F6\u4F9D\u8D56\uFF1A\u5355\u6D4B\u76F4\u63A5 import \u672C .ts \u6E90\u7801\u505A\u884C\u4E3A\u65AD\u8A00\u3002\n */\n\n/** \u5F15\u64CE connection \u56F4\u680F\uFF08\u53EA\u58F0\u660E\u5B88\u536B\u9700\u8981\u7684\u4E00\u5C0F\u5757\uFF0C\u4FDD\u6301\u672C\u6A21\u5757\u96F6\u4F9D\u8D56\uFF09\u3002\n * requestRejection \u662F\u53EF\u9009\u6210\u5458\uFF08\u5F15\u64CE\u7ED3\u6784\u9762\u5141\u8BB8\u7F3A\u5E2D\uFF0C\u7F3A\u5E2D\u65F6\u5B88\u536B fail-closed\uFF09\u3002 */\nexport interface GuardFence {\n requestRejection?(request: unknown): unknown\n}\n\nexport type RequestGuard = (req: unknown) => number | undefined\n\n/** \u5BBF\u4E3B\u6CA1\u6302 connection\uFF08\u6D4F\u89C8\u5668/\u5F00\u53D1\u6001\u672A\u88C5 accounts\uFF09\u65F6\uFF0C\u6CA1\u6709\u9274\u6743\u6743\u5A01\u5C31\u62D2\u7EDD\u3002\n * \u663E\u5F0F\u653E\u884C\u53EA\u7559\u7ED9\u786E\u5B9E\u9700\u8981\u88F8 API \u7684\u5F00\u53D1\u573A\u666F\uFF0C\u4E14\u5FC5\u987B\u7531\u73AF\u5883\u53D8\u91CF\u5F00\u53E3\u5B50\u3002 */\nexport const UNAUTHENTICATED_ESCAPE = 'KSRW_API_ALLOW_UNAUTHENTICATED'\n\nexport function createRequestGuard(fence: GuardFence | null | undefined): RequestGuard {\n const requestRejection = fence?.requestRejection\n if (typeof requestRejection !== 'function') {\n if (process.env[UNAUTHENTICATED_ESCAPE] === '1') return () => undefined\n return () => 503\n }\n // \u94C1\u5F8B\uFF1A\u4EE5\u65B9\u6CD5\u5F62\u5F0F\u8C03\u7528\uFF08.call(fence, \u2026) \u4FDD\u6301 this\uFF09\u2014\u2014\u62C6\u7ED1\u4F1A\u8BA9\u5F15\u64CE\u4FA7\n // this.admit(request) \u8BFB\u5230 undefined\uFF0C/ksrw/api \u5168 500\uFF08\u89C1 tests/runtime-policy\uFF09\u3002\n return req => {\n const rejection = requestRejection.call(fence, req)\n return typeof rejection === 'number' ? rejection : undefined\n }\n}\n", "/** \u672C\u673A\u5B9E\u9A8C runner\uFF1A\u5728\u9879\u76EE\u7684 experiments/ \u4E0B\u6267\u884C\u8BBA\u6587\u5B9E\u9A8C\u811A\u672C\uFF0C\u843D\u76D8 run \u8BB0\u5F55\u3002\n *\n * \u4EA7\u7269\u5E03\u5C40\uFF08\u4E0E plans/ksrw-experiment-redesign.md \u5BF9\u9F50\uFF09\uFF1A\n * experiments/record.md \u5B9E\u9A8C\u65B9\u6848\u4E0E\u8BB0\u5F55\uFF08experiment \u5DE5\u4EF6\uFF0C\u63D0\u6848\u9A71\u52A8\uFF09\n * experiments/code/*.py \u53EF\u6267\u884C\u811A\u672C\n * experiments/runs/<runId>/ config.json / metrics.json / stdout.log\n */\nimport { spawn, type ChildProcess } from 'node:child_process'\nimport { randomUUID } from 'node:crypto'\nimport { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs'\nimport { delimiter, join, relative } from 'node:path'\n\nimport { labPython } from './lab.ts'\nimport { ingestRunFigures } from './figures.ts'\nimport { downloadDirectory, execRemoteTarget, targetPipIndex, targetPython, uploadDirectory } from './remote.ts'\n\nexport interface ExperimentScript {\n /** Path relative to the project's experiments/code directory. */\n relative: string\n title: string\n mtime: number\n}\n\nexport interface ExperimentRun {\n id: string\n script: string\n args: string[]\n status: 'running' | 'succeeded' | 'failed' | 'cancelled'\n startedAt: number\n durationMs: number\n exitCode: number | null\n metrics: Record<string, number | string>\n python?: string\n /** run \u76EE\u5F55\u4E0B artifacts/ \u91CC\u7684\u6587\u4EF6\u540D\uFF08\u672C\u5730\u4E0E\u8FDC\u7AEF\u7EDF\u4E00\uFF09\u3002 */\n artifacts?: string[]\n}\n\nconst CODE_DIR = join('experiments', 'code')\nconst RUNS_DIR = join('experiments', 'runs')\n\ninterface RunningRun {\n child: ChildProcess\n run: ExperimentRun\n metrics: Record<string, number | string>\n startedAt: number\n cancelRequested: boolean\n}\n\nconst running = new Map<string, RunningRun>()\n\n/** run id -> projectPath, so cancel can verify ownership (P2-12). */\nconst runOwners = new Map<string, string>()\n\n/**\n * P2-10: a host crash leaves runs/<id>/config.json stuck at status=running with no\n * live process in this process table. Before listing/starting, rewrite such\n * configs to interrupted so the UI stops polling runs that no longer exist.\n */\nexport function reclaimStaleRuns(projectPath: string): string[] {\n const root = join(projectPath, RUNS_DIR)\n if (!existsSync(root)) return []\n const reclaimed: string[] = []\n let entries: string[]\n try { entries = readdirSync(root) } catch { return [] }\n for (const entry of entries) {\n if (running.has(entry)) continue\n const file = join(root, entry, 'config.json')\n const config = readJson<Record<string, unknown>>(file)\n if (config === undefined || config['status'] !== 'running') continue\n try {\n writeFileSync(file, JSON.stringify({\n ...config,\n status: 'interrupted',\n exitCode: null,\n interruptedAt: Date.now(),\n interruptedReason: '\u5BBF\u4E3B\u91CD\u542F\uFF1A\u8BE5 run \u4E0D\u5C5E\u4E8E\u672C\u8FDB\u7A0B\uFF0C\u5DF2\u6807\u8BB0 interrupted',\n }, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n reclaimed.push(entry)\n } catch { /* unwritable run dir: leave it for the next pass */ }\n }\n return reclaimed\n}\n\n/** Every .py file under experiments/code (recursive, capped). */\nexport function listExperimentScripts(projectPath: string): ExperimentScript[] {\n const root = join(projectPath, CODE_DIR)\n if (!existsSync(root)) return []\n const found: ExperimentScript[] = []\n const walk = (dir: string, depth: number): void => {\n if (depth > 3 || found.length > 200) return\n let entries: string[]\n try { entries = readdirSync(dir) } catch { return }\n for (const entry of entries) {\n if (entry.startsWith('.')) continue\n const full = join(dir, entry)\n let stat\n try { stat = statSync(full) } catch { continue }\n if (stat.isDirectory()) { walk(full, depth + 1); continue }\n if (!entry.endsWith('.py')) continue\n found.push({ relative: relative(root, full), title: entry, mtime: stat.mtimeMs })\n }\n }\n walk(root, 0)\n return found.sort((left, right) => right.mtime - left.mtime)\n}\n\nfunction readJson<T>(file: string): T | undefined {\n try { return JSON.parse(readFileSync(file, 'utf8')) as T } catch { return undefined }\n}\n\n/** Persisted runs (newest first) with live status overlaid from the process table. */\nfunction localArtifacts(dir: string): string[] {\n const root = join(dir, 'artifacts')\n if (!existsSync(root)) return []\n try {\n return readdirSync(root, { withFileTypes: true })\n .filter(entry => entry.isFile()).map(entry => entry.name).sort()\n } catch { return [] }\n}\n\nexport function listExperimentRuns(projectPath: string): ExperimentRun[] {\n reclaimStaleRuns(projectPath)\n const root = join(projectPath, RUNS_DIR)\n if (!existsSync(root)) return []\n let entries: string[]\n try { entries = readdirSync(root) } catch { return [] }\n const runs: ExperimentRun[] = []\n for (const entry of entries) {\n const dir = join(root, entry)\n const config = readJson<Partial<ExperimentRun>>(join(dir, 'config.json'))\n if (config === undefined) continue\n const metrics = readJson<Record<string, number | string>>(join(dir, 'metrics.json')) ?? {}\n const live = running.get(entry)\n const run: ExperimentRun = {\n id: entry,\n script: typeof config.script === 'string' ? config.script : '',\n args: Array.isArray(config.args) ? config.args as string[] : [],\n status: live !== undefined ? 'running' : (typeof config.status === 'string' ? config.status as ExperimentRun['status'] : 'failed'),\n startedAt: typeof config.startedAt === 'number' ? config.startedAt : 0,\n durationMs: typeof config.durationMs === 'number' ? config.durationMs : 0,\n exitCode: typeof config.exitCode === 'number' ? config.exitCode : null,\n metrics: live !== undefined ? live.metrics : metrics,\n ...(typeof config.python === 'string' ? { python: config.python } : {}),\n artifacts: localArtifacts(dir),\n }\n runs.push(run)\n }\n return runs.sort((left, right) => right.startedAt - left.startedAt)\n}\n\n/** Start a run; throws when the script or the Python runtime is missing. */\nexport function startExperimentRun(projectPath: string, script: string, args: string[] = []): ExperimentRun {\n const codeRoot = join(projectPath, CODE_DIR)\n const scriptPath = join(codeRoot, script)\n if (!scriptPath.startsWith(codeRoot) || !existsSync(scriptPath)) {\n throw new Error('\u5B9E\u9A8C\u811A\u672C\u4E0D\u5B58\u5728\uFF1A' + script)\n }\n const runtime = labPython()\n if (runtime === undefined) {\n throw new Error('\u6CA1\u6709\u53EF\u7528\u7684 Python \u8FD0\u884C\u65F6\uFF1A\u5148\u6784\u5EFA lab runtime\uFF08node scripts/build-lab-runtime.mjs --profile ml\uFF09\u6216\u8BBE\u7F6E KSRW_LAB_PYTHON')\n }\n reclaimStaleRuns(projectPath)\n const id = 'run_' + randomUUID().slice(0, 12)\n const dir = join(projectPath, RUNS_DIR, id)\n mkdirSync(dir, { recursive: true })\n const stdoutFile = join(dir, 'stdout.log')\n writeFileSync(stdoutFile, '', { encoding: 'utf8', mode: 0o600 })\n const startedAt = Date.now()\n const run: ExperimentRun = {\n id, script, args, status: 'running', startedAt, durationMs: 0, exitCode: null, metrics: {}, python: runtime.python,\n }\n const pythonPath = [codeRoot, runtime.site, process.env['KSRW_LAB_PYTHONPATH']]\n .filter((item): item is string => item !== undefined && item !== '').join(delimiter)\n const child = spawn(runtime.python, [scriptPath, ...args], {\n cwd: codeRoot,\n env: {\n ...process.env,\n PYTHONUNBUFFERED: '1',\n PYTHONPATH: pythonPath,\n KSRW_PROJECT: projectPath,\n KSRW_RUN_ID: id,\n KSRW_RUN_DIR: dir,\n },\n stdio: ['ignore', 'pipe', 'pipe'],\n })\n const state: RunningRun = { child, run, metrics: {}, startedAt, cancelRequested: false }\n running.set(id, state)\n runOwners.set(id, projectPath)\n writeFileSync(join(dir, 'config.json'), JSON.stringify({\n id, script, args, startedAt, python: runtime.python, host: 'local', status: 'running',\n }, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n\n const onChunk = (chunk: Buffer): void => {\n const text = chunk.toString('utf8')\n appendFileSync(stdoutFile, text, 'utf8')\n for (const line of text.split('\\n')) {\n const match = /^KSRW_METRIC\\s+([\\w.-]+)\\s*=\\s*(.+)$/.exec(line.trim())\n if (match === null) continue\n const raw = match[2]!.trim()\n const numeric = Number(raw)\n state.metrics[match[1]!] = Number.isFinite(numeric) ? numeric : raw\n }\n }\n child.stdout?.on('data', onChunk)\n child.stderr?.on('data', onChunk)\n const finish = (exitCode: number | null, status: ExperimentRun['status']): void => {\n const durationMs = Date.now() - startedAt\n running.delete(id)\n runOwners.delete(id)\n writeFileSync(join(dir, 'metrics.json'), JSON.stringify(state.metrics, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n try { ingestRunFigures(projectPath, id) } catch { /* \u56FE\u6536\u8FDB\u8BBA\u6587\u5931\u8D25\u4E0D\u963B\u585E run */ }\n const config = readJson<Record<string, unknown>>(join(dir, 'config.json')) ?? {}\n writeFileSync(join(dir, 'config.json'), JSON.stringify({\n ...config, status, exitCode, durationMs, artifacts: localArtifacts(dir),\n }, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n }\n child.once('exit', (code) => {\n const status: ExperimentRun['status'] = state.cancelRequested ? 'cancelled' : (code === 0 ? 'succeeded' : 'failed')\n finish(code, status)\n })\n child.once('error', (error) => {\n appendFileSync(stdoutFile, '\\n[spawn error] ' + error.message + '\\n', 'utf8')\n finish(null, 'failed')\n })\n return run\n}\n\n/** Ask a running experiment to stop (SIGTERM, then SIGKILL after 3s).\n *\n * P2-12: when a projectPath is supplied, the run must belong to that project \u2014\n * a caller that only knows the run id cannot cancel another project's run.\n */\nexport function cancelExperimentRun(id: string, projectPath?: string): boolean {\n if (projectPath !== undefined) {\n const owner = runOwners.get(id)\n if (owner === undefined || owner !== projectPath) return false\n }\n const state = running.get(id)\n if (state === undefined) return false\n state.cancelRequested = true\n state.child.kill('SIGTERM')\n // child.killed \u53EA\u8868\u793A\u4FE1\u53F7\u5DF2\u53D1\u51FA\uFF1B\u7528 exitCode/signalCode \u5224\u65AD\u662F\u5426\u4ECD\u5B58\u6D3B\u3002\n setTimeout(() => {\n if (state.child.exitCode === null && state.child.signalCode === null) state.child.kill('SIGKILL')\n }, 3000).unref()\n return true\n}\n\n\n// ---- \u8FDC\u7A0B run\uFF08S3\uFF09\uFF1A\u4E0A\u4F20 experiments/code \u2192 \u8FDC\u7AEF nohup \u6267\u884C \u2192 \u8F6E\u8BE2\u65E5\u5FD7/\u9000\u51FA\u7801 ----\n\nexport interface RemoteRunInfo {\n readonly id: string\n readonly targetId: string\n readonly remoteDir: string\n readonly pid?: string\n readonly python: string\n readonly status: 'running' | 'succeeded' | 'failed' | 'cancelled'\n}\n\nfunction shellQuote(value: string): string {\n return \"'\" + value.replace(/'/g, \"'\\\\''\") + \"'\"\n}\n\nfunction readRemoteInfo(dir: string): RemoteRunInfo | undefined {\n return readJson<RemoteRunInfo>(join(dir, 'remote.json'))\n}\n\nfunction writeRemoteInfo(dir: string, info: RemoteRunInfo): void {\n writeFileSync(join(dir, 'remote.json'), JSON.stringify(info, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n}\n\nfunction metricsFromLog(text: string): Record<string, number | string> {\n const metrics: Record<string, number | string> = {}\n for (const line of text.split('\\n')) {\n const match = /^KSRW_METRIC\\s+([\\w.-]+)\\s*=\\s*(.+)$/.exec(line.trim())\n if (match === null) continue\n const numeric = Number(match[2]!.trim())\n metrics[match[1]!] = Number.isFinite(numeric) ? numeric : match[2]!.trim()\n }\n return metrics\n}\n\n/** \u5728\u8FDC\u7A0B\u76EE\u6807\u4E0A\u542F\u52A8\u4E00\u6B21\u5B9E\u9A8C\uFF08\u811A\u672C\u6765\u81EA experiments/code\uFF09\u3002 */\nexport async function startRemoteExperiment(\n projectPath: string,\n targetId: string,\n script: string,\n args: string[] = [],\n python = '',\n): Promise<ExperimentRun> {\n const codeRoot = join(projectPath, CODE_DIR)\n if (!existsSync(join(codeRoot, script))) throw new Error('\u5B9E\u9A8C\u811A\u672C\u4E0D\u5B58\u5728\uFF1A' + script)\n const id = 'run_' + randomUUID().slice(0, 12)\n const dir = join(projectPath, RUNS_DIR, id)\n mkdirSync(dir, { recursive: true })\n const remoteDir = 'ksrw-runs/' + id\n // fastPut \u8981\u6C42\u7236\u76EE\u5F55\u5B58\u5728\uFF1A\u5148\u5EFA\u597D\u8FDC\u7AEF\u76EE\u5F55\u6811\u518D\u4E0A\u4F20\u3002\n await execRemoteTarget(targetId, 'mkdir -p ' + remoteDir + '/code', 30_000)\n await uploadDirectory(targetId, codeRoot, remoteDir + '/code')\n const interpreter = python === '' ? targetPython(targetId) : python\n const index = targetPipIndex(targetId)\n const inner = 'KSRW_RUN_DIR=$PWD PIP_INDEX_URL=' + shellQuote(index) + ' UV_INDEX_URL=' + shellQuote(index) + ' ' + interpreter + ' -u code/' + script + (args.length === 0 ? '' : ' ' + args.map(shellQuote).join(' '))\n const command = [\n 'mkdir -p ' + remoteDir,\n 'cd ' + remoteDir,\n 'setsid sh -c ' + shellQuote(inner + ' > stdout.log 2>&1; echo $? > exit.code') + ' > /dev/null 2>&1 & echo $!',\n ].join(' && ')\n const started = await execRemoteTarget(targetId, command, 60_000)\n const pid = started.stdout.trim().split('\\n').filter(line => line !== '').pop() ?? ''\n const startedAt = Date.now()\n writeFileSync(join(dir, 'config.json'), JSON.stringify({\n id, script, args, startedAt, status: 'running', host: 'remote:' + targetId, python: interpreter, remoteDir, pid: Number(pid) || pid,\n }, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n writeRemoteInfo(dir, { id, targetId, remoteDir, ...(pid === '' ? {} : { pid }), python: interpreter, status: 'running' })\n return { id, script, args, status: 'running', startedAt, durationMs: 0, exitCode: null, metrics: {}, python }\n}\n\n/** \u8F6E\u8BE2\u8FDC\u7A0B run\uFF1A\u5B8C\u6210\u65F6\u628A\u65E5\u5FD7\u4E0E\u6307\u6807\u62C9\u56DE\u672C\u5730 run \u76EE\u5F55\u3002 */\nexport async function pollRemoteExperiment(projectPath: string, id: string): Promise<ExperimentRun> {\n const dir = join(projectPath, RUNS_DIR, id)\n const info = readRemoteInfo(dir)\n const config = readJson<Record<string, unknown>>(join(dir, 'config.json')) ?? {}\n const base: ExperimentRun = {\n id,\n script: typeof config['script'] === 'string' ? config['script'] : '',\n args: Array.isArray(config['args']) ? config['args'] as string[] : [],\n status: (typeof config['status'] === 'string' ? config['status'] : 'failed') as ExperimentRun['status'],\n startedAt: typeof config['startedAt'] === 'number' ? config['startedAt'] : 0,\n durationMs: typeof config['durationMs'] === 'number' ? config['durationMs'] : 0,\n exitCode: typeof config['exitCode'] === 'number' ? config['exitCode'] : null,\n metrics: readJson<Record<string, number | string>>(join(dir, 'metrics.json')) ?? {},\n }\n if (info === undefined || info.status !== 'running') return base\n const probe = await execRemoteTarget(info.targetId, 'cd ' + info.remoteDir + ' && ( if [ -f exit.code ]; then echo DONE:$(cat exit.code); else echo RUNNING; fi )', 30_000)\n const marker = probe.stdout.trim().split('\\n').pop() ?? ''\n if (!marker.startsWith('DONE:')) return base\n const exitCode = Number(marker.slice(5))\n const logResult = await execRemoteTarget(info.targetId, 'cd ' + info.remoteDir + ' && tail -c 200000 stdout.log', 60_000)\n writeFileSync(join(dir, 'stdout.log'), logResult.stdout, { encoding: 'utf8', mode: 0o600 })\n const metrics = metricsFromLog(logResult.stdout)\n writeFileSync(join(dir, 'metrics.json'), JSON.stringify(metrics, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n try { await downloadDirectory(info.targetId, info.remoteDir + '/artifacts', join(dir, 'artifacts')) } catch { /* \u6CA1\u6709\u4EA7\u7269\u76EE\u5F55\u5C31\u7B97\u4E86 */ }\n try { ingestRunFigures(projectPath, id) } catch { /* ignore */ }\n const durationMs = Date.now() - base.startedAt\n const status: ExperimentRun['status'] = exitCode === 0 ? 'succeeded' : 'failed'\n writeFileSync(join(dir, 'config.json'), JSON.stringify({ ...config, status, exitCode, durationMs, artifacts: localArtifacts(dir) }, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n writeRemoteInfo(dir, { ...info, status })\n return { ...base, status, exitCode, durationMs, metrics, artifacts: localArtifacts(dir) }\n}\n\n/** \u53D6\u6D88\u8FDC\u7A0B run\uFF1A\u7ED9\u6574\u4E2A\u8FDB\u7A0B\u7EC4\u53D1 TERM\u3002 */\nexport async function cancelRemoteExperiment(projectPath: string, id: string): Promise<boolean> {\n const dir = join(projectPath, RUNS_DIR, id)\n const info = readRemoteInfo(dir)\n if (info === undefined || info.pid === undefined || info.status !== 'running') return false\n await execRemoteTarget(info.targetId, 'kill -TERM -' + info.pid + ' 2>/dev/null || kill -TERM ' + info.pid + ' 2>/dev/null; echo killed', 30_000)\n const config = readJson<Record<string, unknown>>(join(dir, 'config.json')) ?? {}\n writeFileSync(join(dir, 'config.json'), JSON.stringify({ ...config, status: 'cancelled', durationMs: Date.now() - (typeof config['startedAt'] === 'number' ? config['startedAt'] : Date.now()) }, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n writeRemoteInfo(dir, { ...info, status: 'cancelled' })\n return true\n}\n\n/** \u5217\u51FA run \u76EE\u5F55\u91CC\u7684\u8FDC\u7AEF\u4FE1\u606F\uFF08\u4F9B UI \u5224\u65AD\u8FD0\u884C\u4F4D\u7F6E\uFF09\u3002 */\nexport function readRemoteRunInfo(projectPath: string, id: string): RemoteRunInfo | undefined {\n return readRemoteInfo(join(projectPath, RUNS_DIR, id))\n}\n/** Last N lines of a run's stdout.log. */\nexport function readRunLog(projectPath: string, id: string, tailLines = 200): string {\n const file = join(projectPath, RUNS_DIR, id, 'stdout.log')\n if (!existsSync(file)) return ''\n try {\n const lines = readFileSync(file, 'utf8').split('\\n')\n return lines.slice(Math.max(0, lines.length - tailLines)).join('\\n')\n } catch { return '' }\n}\n", "/** KSRW Lab \u5BBF\u4E3B\uFF1Aspawn Python sidecar + \u53CD\u5411\u4EE3\u7406 + \u751F\u547D\u5468\u671F\u3002 */\nimport { spawn, spawnSync, type ChildProcessByStdio } from 'node:child_process'\nimport { randomBytes } from 'node:crypto'\nimport type { Readable } from 'node:stream'\nimport { existsSync } from 'node:fs'\nimport { delimiter, dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nexport interface LabEndpoint { readonly url: string; readonly token: string }\n\ninterface ReqLike {\n url?: string\n method?: string\n [Symbol.asyncIterator]?: () => AsyncIterator<Uint8Array | string>\n}\ninterface ResLike {\n writeHead(code: number, headers?: Record<string, string>): unknown\n end(body?: unknown): unknown\n}\n\nconst DEFAULT_CWD = fileURLToPath(new URL('../../../python/ksrw-lab', import.meta.url))\n\n/** \u7B49\u5B50\u8FDB\u7A0B\u771F\u6B63\u9000\u51FA\uFF1B\u8D85\u65F6\u8FD4\u56DE false\uFF08restart \u7528\u5B83\u51B3\u5B9A\u662F\u5426\u5347\u7EA7\u5230 SIGKILL\uFF09\u3002 */\nfunction waitForExit(child: ChildProcessByStdio<null, Readable, Readable>, timeoutMs: number): Promise<boolean> {\n if (child.exitCode !== null || child.signalCode !== null) return Promise.resolve(true)\n return new Promise(resolve => {\n const done = (value: boolean): void => {\n clearTimeout(timer)\n child.off('exit', onExit)\n resolve(value)\n }\n const onExit = (): void => { done(true) }\n const timer = setTimeout(() => { done(false) }, timeoutMs)\n child.once('exit', onExit)\n })\n}\n\n/** Local venv written by the installer: ~/.ksrw/lab/venv. */\nfunction localVenvPython(): string | undefined {\n const home = process.env['KSRW_HOME']?.trim() || join(process.env['HOME'] ?? '.', '.ksrw')\n const dir = process.env['KSRW_LAB_VENV']?.trim() || join(home, 'lab', 'venv')\n return ['bin/python3', 'bin/python', 'Scripts/python.exe'].map(name => join(dir, name)).find(candidate => existsSync(candidate))\n}\n\ninterface LabCandidate { cwd: string; site?: string; python?: string }\n\n/** A directory that holds ksrw_lab, plus the bundled interpreter when it has one. */\nfunction runtimeCandidate(directory: string): LabCandidate | undefined {\n if (!existsSync(join(directory, 'ksrw_lab', '__main__.py'))) return undefined\n const site = join(directory, 'site-packages')\n const bin = join(directory, 'python', 'bin')\n const python = ['python3.12', 'python3', 'python', 'python3.12.exe', 'python.exe']\n .map(name => join(bin, name)).find(candidate => existsSync(candidate))\n return {\n cwd: directory,\n ...(existsSync(site) ? { site } : {}),\n ...(python === undefined ? {} : { python }),\n }\n}\n\n/**\n * Every place the ksrw_lab package may live, best first: explicit runtime, the\n * installed runtime under KSRW_HOME, a runtime built next to any ancestor of this\n * module (staging/lab-runtime/runtime), an explicit cwd, then bare package dirs\n * in ancestors (dev checkout, compiled default).\n */\nfunction candidateLabDirs(): LabCandidate[] {\n const list: LabCandidate[] = []\n const explicitRuntime = process.env['KSRW_LAB_RUNTIME']?.trim()\n if (explicitRuntime !== undefined && explicitRuntime !== '') {\n const candidate = runtimeCandidate(explicitRuntime)\n if (candidate !== undefined) list.push(candidate)\n }\n const home = process.env['KSRW_HOME']?.trim() || join(process.env['HOME'] ?? '.', '.ksrw')\n const installed = runtimeCandidate(join(home, 'lab', 'runtime'))\n if (installed !== undefined) list.push(installed)\n const ancestors: string[] = []\n let dir = dirname(fileURLToPath(import.meta.url))\n for (let depth = 0; depth < 7; depth += 1) { ancestors.push(dir); dir = dirname(dir) }\n for (const ancestor of ancestors) {\n for (const relative of [join('staging', 'lab-runtime', 'runtime'), join('lab-runtime', 'runtime')]) {\n const candidate = runtimeCandidate(join(ancestor, relative))\n if (candidate !== undefined) list.push(candidate)\n }\n }\n const explicitCwd = process.env['KSRW_LAB_CWD']?.trim()\n if (explicitCwd !== undefined && explicitCwd !== '') {\n list.push(runtimeCandidate(explicitCwd) ?? { cwd: explicitCwd })\n }\n for (const ancestor of ancestors) {\n for (const relative of [join('python', 'ksrw-lab'), '.']) {\n const path = join(ancestor, relative)\n if (existsSync(join(path, 'ksrw_lab', '__main__.py'))) list.push({ cwd: path })\n }\n }\n return list\n}\n\nfunction pythonCandidates(): string[] {\n const explicit = process.env['KSRW_LAB_PYTHON']?.trim()\n return explicit !== undefined && explicit !== '' ? [explicit] : ['python3', 'python']\n}\n\nfunction canRun(command: string): boolean {\n try {\n return spawnSync(command, ['--version'], { stdio: 'ignore', timeout: 5000 }).status === 0\n } catch { return false }\n}\n\n/** A build-lab-runtime archive extracted under KSRW_HOME/lab/runtime (or KSRW_LAB_RUNTIME). */\nfunction packagedRuntime(): { directory: string; python: string; site: string } | undefined {\n const home = process.env['KSRW_HOME']?.trim() || join(process.env['HOME'] ?? '.', '.ksrw')\n const directory = process.env['KSRW_LAB_RUNTIME']?.trim() || join(home, 'lab', 'runtime')\n if (!existsSync(join(directory, 'ksrw_lab', '__main__.py'))) return undefined\n const bin = join(directory, 'python', 'bin')\n const python = ['python3.12', 'python3', 'python', 'python3.12.exe', 'python.exe']\n .map(name => join(bin, name))\n .find(candidate => existsSync(candidate))\n if (python === undefined) return undefined\n return { directory, python, site: join(directory, 'site-packages') }\n}\n\nasync function readRaw(req: ReqLike): Promise<Buffer> {\n if (req[Symbol.asyncIterator] === undefined) return Buffer.alloc(0)\n const chunks: Buffer[] = []\n let bytes = 0\n for await (const chunk of req as AsyncIterable<Uint8Array | string>) {\n const buffer = typeof chunk === 'string' ? Buffer.from(chunk) : Buffer.from(chunk)\n bytes += buffer.byteLength\n if (bytes > 2 * 1024 * 1024) throw new Error('lab request body too large')\n chunks.push(buffer)\n }\n return Buffer.concat(chunks)\n}\n\n/** Managed lab sidecar. It starts once, proxies every request, and dies with the plugin. */\n/** Interpreter + site-packages from the best lab runtime (also used for experiment runs). */\nexport function labPython(): { python: string; site?: string; cwd: string } | undefined {\n const chosen = candidateLabDirs()[0]\n if (chosen === undefined) return undefined\n const python = process.env['KSRW_LAB_PYTHON']?.trim() || chosen.python || localVenvPython() || pythonCandidates().find(canRun)\n if (python === undefined) return undefined\n return { python, ...(chosen.site === undefined ? {} : { site: chosen.site }), cwd: chosen.cwd }\n}\n\nexport class LabRuntime {\n private child: ChildProcessByStdio<null, Readable, Readable> | undefined\n private endpoint: LabEndpoint | undefined\n private starting: Promise<LabEndpoint | undefined> | undefined\n private stopped = false\n private reason = ''\n private cwdUsed = ''\n private pythonUsed = ''\n\n constructor(private readonly log: (message: string) => void = () => {}) {}\n\n /** Start the sidecar, or adopt an externally managed KSRW_LAB_URL. */\n async start(): Promise<LabEndpoint | undefined> {\n if (this.endpoint !== undefined) return this.endpoint\n if (this.starting !== undefined) return this.starting\n this.starting = this.launch()\n return this.starting\n }\n\n private async launch(): Promise<LabEndpoint | undefined> {\n const external = process.env['KSRW_LAB_URL']?.trim()\n if (external !== undefined && external !== '') {\n const token = process.env['KSRW_LAB_TOKEN'] ?? ''\n this.endpoint = { url: external.replace(/\\/$/, ''), token }\n this.log('lab: using external sidecar ' + this.endpoint.url)\n return this.endpoint\n }\n const chosen = candidateLabDirs()[0]\n if (chosen === undefined) {\n this.reason = '\u627E\u4E0D\u5230 ksrw_lab Python \u5305\uFF08\u53EF\u7528 KSRW_LAB_CWD \u6307\u5B9A\uFF0C\u6216\u8FD0\u884C node scripts/build-lab-runtime.mjs --profile ml\uFF09'\n this.log('lab: python package not found (KSRW_LAB_CWD / KSRW_LAB_RUNTIME)')\n return undefined\n }\n const cwd = chosen.cwd\n const packaged = chosen.site === undefined ? undefined : { directory: cwd, python: chosen.python ?? '', site: chosen.site }\n this.cwdUsed = cwd\n const explicitPython = process.env['KSRW_LAB_PYTHON']?.trim()\n const python = explicitPython !== undefined && explicitPython !== ''\n ? explicitPython\n : chosen.python ?? localVenvPython() ?? pythonCandidates().find(canRun)\n if (python === undefined) {\n this.reason = '\u627E\u4E0D\u5230\u53EF\u7528\u7684 Python \u89E3\u91CA\u5668\uFF08\u53EF\u8BBE\u7F6E KSRW_LAB_PYTHON\uFF0C\u6216\u6784\u5EFA lab runtime\uFF09'\n this.log('lab: no python interpreter found (set KSRW_LAB_PYTHON or build:lab:ml)')\n return undefined\n }\n this.pythonUsed = python\n if (packaged !== undefined) this.log('lab: using packaged runtime ' + cwd)\n const data = process.env['KSRW_LAB_DATA']?.trim()\n || join(process.env['KSRW_HOME'] ?? join(process.env['HOME'] ?? '.', '.ksrw'), 'lab')\n const token = randomBytes(24).toString('base64url')\n const child = spawn(python, ['-m', 'ksrw_lab', '--host', '127.0.0.1', '--port', '0', '--data', data, '--token', token], {\n cwd,\n env: {\n ...process.env,\n PYTHONUNBUFFERED: '1',\n PYTHONPATH: [cwd, packaged?.site, process.env['KSRW_LAB_PYTHONPATH']?.trim()]\n .filter((item): item is string => item !== undefined && item !== '').join(delimiter),\n },\n stdio: ['ignore', 'pipe', 'pipe'],\n })\n this.child = child\n child.stderr.setEncoding('utf8')\n child.stderr.on('data', (chunk: string) => { this.log('lab: ' + chunk.trim()) })\n const endpoint = await new Promise<LabEndpoint | undefined>((resolve) => {\n let settled = false\n let buffer = ''\n const finish = (value: LabEndpoint | undefined): void => {\n if (settled) return\n settled = true\n resolve(value)\n }\n const timer = setTimeout(() => finish(undefined), 20_000)\n child.stdout.setEncoding('utf8')\n child.stdout.on('data', (chunk: string) => {\n buffer += chunk\n const line = buffer.split('\\n').find(item => item.includes('\"ready\"'))\n if (line === undefined) return\n try {\n const ready = JSON.parse(line) as { ready?: boolean; port?: number }\n if (ready.ready === true && typeof ready.port === 'number') {\n clearTimeout(timer)\n finish({ url: 'http://127.0.0.1:' + ready.port, token })\n }\n } catch { /* wait for the full line */ }\n })\n child.once('exit', (code) => {\n clearTimeout(timer)\n this.log('lab: sidecar exited (' + String(code) + ')')\n finish(undefined)\n })\n child.once('error', (error) => {\n clearTimeout(timer)\n this.log('lab: spawn failed: ' + error.message)\n finish(undefined)\n })\n })\n if (endpoint === undefined) {\n if (this.reason === '') this.reason = 'sidecar \u542F\u52A8\u5931\u8D25\uFF1A\u89E3\u91CA\u5668\u6CA1\u6709\u5728 20 \u79D2\u5185\u5C31\u7EEA\uFF08\u68C0\u67E5 Python \u4F9D\u8D56\uFF1Afastapi/uvicorn/scikit-learn\uFF09'\n this.stop()\n return undefined\n }\n this.endpoint = endpoint\n process.env['KSRW_LAB_URL'] = endpoint.url\n process.env['KSRW_LAB_TOKEN'] = endpoint.token\n this.log('lab: sidecar ready at ' + endpoint.url)\n return endpoint\n }\n\n /** Structured status for the UI: why the sidecar is down and what it resolved. */\n status(): { ok: boolean; reason: string; cwd: string; python: string; url?: string } {\n return {\n ok: this.endpoint !== undefined,\n reason: this.reason,\n cwd: this.cwdUsed,\n python: this.pythonUsed,\n ...(this.endpoint === undefined ? {} : { url: this.endpoint.url }),\n }\n }\n\n /** Kill the current child and try again (used by the UI retry button). */\n async restart(): Promise<void> {\n const child = this.child\n this.child = undefined\n this.endpoint = undefined\n this.starting = undefined\n this.stopped = false\n this.reason = ''\n // P1-4\uFF1A\u5148 SIGTERM \u5E76\u7B49\u5F85\u8FDB\u7A0B\u771F\u6B63\u9000\u51FA\uFF0C\u518D\u51B3\u5B9A\u662F\u5426 SIGKILL\uFF1B\u7ACB\u523B start \u4F1A\u5728\n // \u65E7\u8FDB\u7A0B\u8FD8\u5360\u7740\u7AEF\u53E3\u65F6\u628A\u65B0\u8FDB\u7A0B\u649E\u6B7B\uFF08\u65E7\u8FDB\u7A0B\u6210\u4E3A\u5B64\u513F\u7EE7\u7EED\u8DD1 GPU \u4EFB\u52A1\uFF09\u3002\n if (child !== undefined && child.exitCode === null && child.signalCode === null) {\n child.kill('SIGTERM')\n const exited = await waitForExit(child, 3000)\n if (!exited) {\n child.kill('SIGKILL')\n await waitForExit(child, 1000)\n }\n }\n delete process.env['KSRW_LAB_URL']\n delete process.env['KSRW_LAB_TOKEN']\n await this.start()\n }\n\n async handle(req: ReqLike, res: ResLike): Promise<void> {\n const pathname = new URL(req.url ?? '/ksrw/api/lab', 'http://localhost').pathname\n if (pathname === '/ksrw/api/lab/status') {\n res.writeHead(200, { 'content-type': 'application/json; charset=utf-8' })\n res.end(JSON.stringify({ ok: true, value: this.status() }))\n return\n }\n if (pathname === '/ksrw/api/lab/restart' && (req.method ?? 'GET') === 'POST') {\n await this.restart()\n res.writeHead(200, { 'content-type': 'application/json; charset=utf-8' })\n res.end(JSON.stringify({ ok: true, value: this.status() }))\n return\n }\n const endpoint = await this.start()\n if (endpoint === undefined) {\n res.writeHead(503, { 'content-type': 'application/json; charset=utf-8' })\n res.end(JSON.stringify({ ok: false, error: { code: 'LAB_UNAVAILABLE', message: this.reason === '' ? '\u5B9E\u9A8C sidecar \u672A\u542F\u52A8' : this.reason } }))\n return\n }\n try {\n const url = new URL(req.url ?? '/ksrw/api/lab', 'http://localhost')\n const suffix = url.pathname.startsWith('/ksrw/api/lab')\n ? url.pathname.slice('/ksrw/api/lab'.length) || '/v1/health'\n : '/v1/health'\n const target = endpoint.url + (suffix.startsWith('/v1/') ? suffix : '/v1' + suffix) + url.search\n const raw = await readRaw(req)\n const headers: Record<string, string> = { 'x-ksrw-lab-token': endpoint.token }\n if (raw.length > 0) headers['content-type'] = 'application/json'\n const response = await fetch(target, {\n method: req.method ?? 'GET',\n headers,\n ...(raw.length > 0 ? { body: raw.toString('utf8') } : {}),\n })\n const body = Buffer.from(await response.arrayBuffer())\n res.writeHead(response.status, { 'content-type': 'application/json; charset=utf-8' })\n res.end(body)\n } catch (error) {\n res.writeHead(502, { 'content-type': 'application/json; charset=utf-8' })\n res.end(JSON.stringify({ ok: false, error: { code: 'LAB_PROXY', message: error instanceof Error ? error.message : String(error) } }))\n }\n }\n\n stop(): void {\n this.stopped = true\n const child = this.child\n this.child = undefined\n this.endpoint = undefined\n if (child !== undefined && child.exitCode === null && child.signalCode === null) {\n child.kill('SIGTERM')\n // child.killed \u53EA\u8868\u793A\u4FE1\u53F7\u5DF2\u53D1\u51FA\uFF1B\u7528 exitCode/signalCode \u5224\u65AD\u662F\u5426\u4ECD\u5B58\u6D3B\u3002\n setTimeout(() => {\n if (child.exitCode === null && child.signalCode === null) child.kill('SIGKILL')\n }, 2000).unref()\n }\n }\n}\n", "/** \u56FE\u8868\u6D41\u6C34\u7EBF\uFF08core \u4FA7\u8584\u5C01\u88C5\uFF09\uFF1A\u5B9E\u9645\u5B9E\u73B0\u5728 ksrw-kernel/figures\uFF0CUI \u4E0E agent \u5DE5\u5177\u5171\u7528\u540C\u4E00\u5957\u3002\n * \u8FD9\u91CC\u53EA\u4FDD\u7559\u6D4F\u89C8\u5668\u7AEF Mermaid \u6E32\u67D3\u7ED3\u679C\u7684\u843D\u76D8\uFF08\u6E32\u67D3\u5728 renderer\uFF0C\u4FDD\u5B58\u5728\u8FD9\u91CC\uFF09\u3002\n */\nimport { mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\nimport {\n adoptFigure,\n adoptUnregisteredFigures,\n extractDocxMedia,\n validateFigures,\n ingestRunFigures,\n listFigures,\n renderFlow,\n renderMetricsChart,\n upsertFigure,\n type PaperFigure,\n} from 'ksrw-kernel'\n\nexport type { PaperFigure }\nexport { adoptFigure, adoptUnregisteredFigures, extractDocxMedia, validateFigures, ingestRunFigures, listFigures, renderMetricsChart }\n\nconst STAGES = ['\u60F3\u6CD5', '\u9009\u9898', '\u5F00\u9898', '\u6587\u732E', '\u5B9E\u9A8C', '\u8BBA\u6587', '\u5B8C\u6210']\n\n/** \u751F\u6210\u4E00\u5F20\u300C\u7814\u7A76\u6D41\u6C34\u7EBF\u73B0\u72B6\u300D\u793A\u610F\u56FE\uFF08\u6807\u6CE8\u5F53\u524D\u9636\u6BB5\uFF09\u3002 */\nexport function renderArchitecture(projectPath: string, current: string, subtitle: string): PaperFigure {\n return renderFlow(projectPath, {\n nodes: STAGES.map((label, index) => ({ id: 's' + String(index), label, active: label === current })),\n edges: STAGES.slice(0, -1).map((_, index) => ({ from: 's' + String(index), to: 's' + String(index + 1) })),\n direction: 'lr',\n subtitle,\n }, {\n file: 'architecture',\n title: '\u7CFB\u7EDF\u67B6\u6784\u56FE \u00B7 \u7814\u7A76\u6D41\u6C34\u7EBF',\n caption: '\u7814\u7A76\u6D41\u6C34\u7EBF\u73B0\u72B6\uFF08\u5F53\u524D\u9636\u6BB5\uFF1A' + current + '\uFF09',\n source: 'project',\n kind: 'architecture',\n })\n}\n\n/** \u67B6\u6784\u56FE v2\uFF1AMermaid \u6E90\u6587\u4EF6\uFF08renderer \u6E32\u67D3\uFF0Ccore \u53EA\u5B58\uFF09\u3002 */\nfunction figuresDir(projectPath: string): string {\n return join(projectPath, 'paper', 'figures')\n}\n\nfunction architectureSourceFile(projectPath: string): string {\n return join(figuresDir(projectPath), 'architecture.mmd')\n}\n\nexport const DEFAULT_ARCHITECTURE_MERMAID = [\n 'flowchart LR',\n ' A[\u60F3\u6CD5\u6C60] --> B[\u9009\u9898\u5DE5\u4F4D]',\n ' B --> C[\u5F00\u9898\u5DE5\u4F4D]',\n ' C --> D[\u6587\u732E\u5DE5\u4F4D]',\n ' D --> E[\u5B9E\u9A8C\u5DE5\u4F4D]',\n ' E --> F[\u8BBA\u6587\u5DE5\u4F4D]',\n ' F --> G[\u5B8C\u6210]',\n].join('\\n')\n\nexport function readArchitectureSource(projectPath: string): { source: string; exists: boolean } {\n const file = architectureSourceFile(projectPath)\n try { return { source: readFileSync(file, 'utf8'), exists: true } } catch { return { source: DEFAULT_ARCHITECTURE_MERMAID, exists: false } }\n}\n\nexport function saveArchitectureRender(projectPath: string, input: { source: string; svg: string; pngBase64: string }): PaperFigure {\n mkdirSync(figuresDir(projectPath), { recursive: true })\n writeFileSync(architectureSourceFile(projectPath), input.source, { encoding: 'utf8', mode: 0o600 })\n writeFileSync(join(figuresDir(projectPath), 'architecture.svg'), input.svg, { encoding: 'utf8', mode: 0o600 })\n writeFileSync(join(figuresDir(projectPath), 'architecture.png'), Buffer.from(input.pngBase64, 'base64'), { mode: 0o600 })\n return upsertFigure(projectPath, {\n file: 'architecture.png',\n title: '\u7CFB\u7EDF\u67B6\u6784\u56FE\uFF08Mermaid\uFF09',\n caption: '\u7CFB\u7EDF\u67B6\u6784\uFF1A\u7814\u7A76\u6D41\u6C34\u7EBF\u4E0E\u5DE5\u4F4D\u534F\u4F5C',\n source: 'project',\n kind: 'architecture',\n addedAt: Date.now(),\n svg: 'architecture.svg',\n })\n}\n", "/** \u8FDC\u7A0B GPU \u76EE\u6807\u4E0E SSH \u8FDE\u63A5\uFF1AKSRW \u5B9E\u9A8C\u5BA4\u8FDC\u7A0B\u6267\u884C\u7684\u57FA\u7840\u8BBE\u65BD\u3002\n *\n * \u51ED\u636E\u4F18\u5148\u7EA7\uFF1ASSH agent\uFF08\u4E0D\u8FDB\u78C1\u76D8\uFF09> \u79C1\u94A5\u6587\u4EF6 > \u5BC6\u7801\u3002\u5BC6\u7801\u4F18\u5148\u5B58\u7CFB\u7EDF\u51ED\u636E\u5B58\u50A8\n * \uFF08macOS \u94A5\u5319\u4E32 / freedesktop Secret Service\uFF09\uFF0Cremotes.json \u53EA\u8BB0\u5F15\u7528\uFF1B\n * \u540E\u7AEF\u4E0D\u53EF\u7528\u65F6\u624D\u9000\u56DE 0600 \u660E\u6587\uFF0C\u5E76\u5728\u89C6\u56FE\u91CC\u5E26 `warning` \u8BA9 UI \u660E\u793A\u3002\n * \u5B58\u91CF\u660E\u6587\u5728\u63D2\u4EF6\u542F\u52A8\u65F6\u7531 migratePlaintextSecrets() \u5E42\u7B49\u8FC1\u79FB\u3002\n */\nimport { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\n\nimport { resolveKsrwHome } from 'ksrw-kernel'\nimport type { Client as SshClient, SFTPWrapper } from 'ssh2'\n\nimport { systemSecretStore, type SecretBackend } from './secret-store.ts'\n\nexport interface RemoteAuth {\n readonly kind: 'password' | 'key' | 'agent'\n /** \u4EC5\u5728\u540E\u7AEF\u4E0D\u53EF\u7528\u7684\u964D\u7EA7\u8DEF\u5F84\u4E0A\u51FA\u73B0\uFF080600 \u660E\u6587\uFF09\u3002 */\n readonly password?: string\n readonly keyPath?: string\n readonly passphrase?: string\n /** \u5BC6\u7801\u5728\u7CFB\u7EDF\u51ED\u636E\u5B58\u50A8\u91CC\u7684\u5F15\u7528\uFF1A`<backend>|remote:<targetId>`\u3002 */\n readonly secretRef?: string\n}\n\nexport interface RemoteTarget {\n readonly id: string\n readonly name: string\n readonly host: string\n readonly port: number\n readonly user: string\n readonly auth: RemoteAuth\n readonly workdir?: string\n /** \u8FDC\u7AEF\u89E3\u91CA\u5668\u8DEF\u5F84\uFF08venv/bin/python\uFF09\uFF1B\u7F3A\u7701 python3\u3002 */\n readonly pythonPath?: string\n /** \u8FDC\u7AEF\u88C5\u5305\u7528\u7684 PyPI \u955C\u50CF\uFF1B\u7F3A\u7701\u6E05\u534E\u6E90\u3002 */\n readonly pipIndexUrl?: string\n}\n\n/** \u5BF9\u5916\u5B89\u5168\u89C6\u56FE\uFF1A\u4E0D\u542B\u5BC6\u7801/\u79C1\u94A5\u5185\u5BB9\u3002 */\nexport interface RemoteTargetView {\n readonly id: string\n readonly name: string\n readonly host: string\n readonly port: number\n readonly user: string\n readonly authKind: 'password' | 'key' | 'agent'\n readonly hasSecret: boolean\n /** \u5BC6\u7801\u6750\u6599\u5B58\u653E\u5728\u54EA\uFF1A\u7CFB\u7EDF\u51ED\u636E\u5B58\u50A8 / \u660E\u6587\u964D\u7EA7 / \u4E0D\u9002\u7528\uFF08key\u3001agent\uFF09\u3002 */\n readonly secretStorage: 'keychain' | 'secret-service' | 'plaintext' | 'none'\n /** \u79C1\u94A5\u8DEF\u5F84\uFF08\u4EC5 key \u8BA4\u8BC1\uFF1B\u6587\u4EF6\u540D\u4E0D\u662F\u5BC6\u94A5\u5185\u5BB9\uFF0C\u56DE\u586B\u7F16\u8F91\u7528\uFF09\u3002 */\n readonly keyPath?: string\n /** \u5B89\u5168\u544A\u8B66\uFF08\u660E\u6587\u5BC6\u7801\u3001\u7F3A SSH agent \u7B49\uFF09\uFF1B\u65E0\u544A\u8B66\u65F6\u4E0D\u51FA\u73B0\u3002 */\n readonly warning?: string\n readonly workdir?: string\n readonly pythonPath?: string\n readonly pipIndexUrl?: string\n}\n\nexport interface RemoteProbe {\n readonly ok: boolean\n readonly latencyMs: number\n readonly error?: string\n readonly uname?: string\n readonly gpu?: string[]\n readonly python?: string\n readonly cpus?: number\n readonly memoryGb?: number\n}\n\nexport interface RemoteExecResult {\n readonly code: number | null\n readonly stdout: string\n readonly stderr: string\n}\n\nfunction storeFile(): string {\n return join(resolveKsrwHome(), 'remotes.json')\n}\n\n/** \u51ED\u636E\u5B58\u50A8\u5728\u7CFB\u7EDF\u91CC\u7684 service \u540D\uFF08\u94A5\u5319\u4E32\u6761\u76EE / Secret Service \u5C5E\u6027\uFF09\u3002 */\nconst SECRET_SERVICE = 'ksrw-remote'\n\nfunction secretAccount(id: string): string {\n return 'remote:' + id\n}\n\nfunction secretRefOf(backend: SecretBackend, id: string): string {\n return backend + '|' + secretAccount(id)\n}\n\n/** \u89E3\u6790\u51ED\u636E\u5F15\u7528\uFF1B\u683C\u5F0F\u4E0D\u5BF9\uFF08\u65E7\u6570\u636E/\u624B\u5DE5\u6539\u574F\uFF09\u65F6\u8FD4\u56DE undefined\u3002 */\nfunction parseSecretRef(ref: string): { backend: SecretBackend; account: string } | undefined {\n const index = ref.indexOf('|')\n if (index <= 0) return undefined\n const backend = ref.slice(0, index)\n if (backend !== 'keychain' && backend !== 'secret-service') return undefined\n const account = ref.slice(index + 1)\n return account === '' ? undefined : { backend, account }\n}\n\n/** \u8BFB\u51FA\u53EF\u7528\u5BC6\u7801\uFF1A\u5F15\u7528\u8D70\u7CFB\u7EDF\u51ED\u636E\u5B58\u50A8\uFF0C\u964D\u7EA7\u8DEF\u5F84\u76F4\u63A5\u7528\u660E\u6587\u3002 */\nfunction resolvePassword(auth: RemoteAuth): string {\n const ref = auth.secretRef ?? ''\n if (ref === '') return auth.password ?? ''\n const parsed = parseSecretRef(ref)\n if (parsed === undefined) throw new Error('\u8FDC\u7A0B\u76EE\u6807\u7684\u51ED\u636E\u5F15\u7528\u65E0\u6548\uFF1A\u8BF7\u91CD\u65B0\u586B\u5199\u5BC6\u7801\u6216\u6539\u7528\u5BC6\u94A5/agent \u8BA4\u8BC1')\n const store = systemSecretStore()\n if (store.backend !== parsed.backend) {\n throw new Error(`\u8BE5\u76EE\u6807\u7684\u5BC6\u7801\u5B58\u5728 ${parsed.backend}\uFF0C\u5F53\u524D\u7CFB\u7EDF\u53EF\u7528\u7684\u51ED\u636E\u540E\u7AEF\u662F ${store.backend}\uFF1A\u8BF7\u91CD\u65B0\u586B\u5199\u5BC6\u7801`)\n }\n const secret = store.get(SECRET_SERVICE, parsed.account)\n if (secret === undefined) throw new Error('\u7CFB\u7EDF\u51ED\u636E\u5B58\u50A8\u91CC\u627E\u4E0D\u5230\u8BE5\u76EE\u6807\u7684\u5BC6\u7801\uFF1A\u8BF7\u91CD\u65B0\u586B\u5199\u5BC6\u7801')\n return secret\n}\n\nfunction readAll(): RemoteTarget[] {\n const file = storeFile()\n if (!existsSync(file)) return []\n try {\n const parsed = JSON.parse(readFileSync(file, 'utf8')) as { targets?: RemoteTarget[] }\n return Array.isArray(parsed.targets) ? parsed.targets : []\n } catch { return [] }\n}\n\nfunction writeAll(targets: RemoteTarget[]): void {\n const file = storeFile()\n mkdirSync(dirname(file), { recursive: true })\n writeFileSync(file, JSON.stringify({ targets }, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n}\n\nfunction view(target: RemoteTarget): RemoteTargetView {\n const auth = target.auth\n const agentSocket = process.env['SSH_AUTH_SOCK'] ?? ''\n const parsed = parseSecretRef(auth.secretRef ?? '')\n const hasSecret = auth.kind === 'agent'\n ? agentSocket !== ''\n : auth.kind === 'key'\n ? (auth.keyPath ?? '') !== ''\n : parsed !== undefined || (auth.password ?? '') !== ''\n const secretStorage: RemoteTargetView['secretStorage'] = auth.kind !== 'password'\n ? 'none'\n : parsed !== undefined\n ? parsed.backend\n : (auth.password ?? '') !== '' ? 'plaintext' : 'none'\n const warning = auth.kind === 'agent' && agentSocket === ''\n ? '\u672A\u627E\u5230 SSH agent\uFF08SSH_AUTH_SOCK \u672A\u8BBE\u7F6E\uFF09\uFF1A\u8BF7\u542F\u52A8 ssh-agent \u6216\u6539\u7528\u5BC6\u94A5/\u5BC6\u7801'\n : secretStorage === 'plaintext'\n ? '\u5BC6\u7801\u4EE5\u660E\u6587\u4FDD\u5B58\u5728 remotes.json\uFF08\u7CFB\u7EDF\u51ED\u636E\u5B58\u50A8\u4E0D\u53EF\u7528\uFF09\uFF1A\u5EFA\u8BAE\u6539\u7528\u5BC6\u94A5\u6216 agent \u8BA4\u8BC1'\n : undefined\n return {\n id: target.id,\n name: target.name,\n host: target.host,\n port: target.port,\n user: target.user,\n authKind: auth.kind,\n hasSecret,\n secretStorage,\n ...(warning === undefined ? {} : { warning }),\n ...(target.workdir === undefined ? {} : { workdir: target.workdir }),\n ...(target.pythonPath === undefined ? {} : { pythonPath: target.pythonPath }),\n ...(target.pipIndexUrl === undefined ? {} : { pipIndexUrl: target.pipIndexUrl }),\n ...(auth.kind === 'key' && (auth.keyPath ?? '') !== '' ? { keyPath: auth.keyPath } : {}),\n }\n}\n\n/** \u89E3\u6790\u8981\u843D\u76D8\u7684\u5BC6\u7801\u51ED\u636E\uFF1A\u4F18\u5148\u7CFB\u7EDF\u51ED\u636E\u5B58\u50A8\uFF0C\u540E\u7AEF\u4E0D\u53EF\u7528\u65F6\u624D\u660E\u6587\u964D\u7EA7\u3002 */\nfunction passwordAuth(id: string, submitted: string | undefined, existing: RemoteTarget | undefined): RemoteAuth {\n if (submitted !== undefined && submitted !== '') {\n const store = systemSecretStore()\n if (store.backend !== 'none' && store.set(SECRET_SERVICE, secretAccount(id), submitted)) {\n return { kind: 'password', secretRef: secretRefOf(store.backend, id) }\n }\n return { kind: 'password', password: submitted }\n }\n const previous = existing?.auth\n if (previous?.secretRef !== undefined && previous.secretRef !== '') {\n return { kind: 'password', secretRef: previous.secretRef }\n }\n return { kind: 'password', password: previous?.password ?? '' }\n}\n\n/** \u5217\u51FA\u5DF2\u4FDD\u5B58\u7684\u8FDC\u7A0B\u76EE\u6807\uFF08\u8131\u654F\uFF09\u3002 */\nexport function listRemoteTargets(): RemoteTargetView[] {\n return readAll().map(view)\n}\n\n/** \u65B0\u5EFA\u6216\u66F4\u65B0\u4E00\u4E2A\u8FDC\u7A0B\u76EE\u6807\uFF1B\u4E0D\u4F20\u5BC6\u7801\u65F6\u4FDD\u7559\u539F\u51ED\u636E\u3002 */\nexport function upsertRemoteTarget(input: {\n id?: string\n name: string\n host: string\n port: number\n user: string\n authKind?: 'password' | 'key' | 'agent'\n password?: string\n keyPath?: string\n passphrase?: string\n workdir?: string\n pythonPath?: string\n pipIndexUrl?: string\n}): RemoteTargetView {\n const targets = readAll()\n const existing = input.id === undefined ? undefined : targets.find(item => item.id === input.id)\n const id = existing?.id ?? input.id ?? 'rt_' + Math.random().toString(36).slice(2, 10)\n const kind = input.authKind ?? existing?.auth.kind ?? 'password'\n const auth: RemoteAuth = kind === 'key'\n ? {\n kind: 'key',\n keyPath: input.keyPath !== undefined && input.keyPath !== '' ? input.keyPath : (existing?.auth.keyPath ?? ''),\n ...(input.passphrase !== undefined && input.passphrase !== '' ? { passphrase: input.passphrase } : (existing?.auth.passphrase === undefined ? {} : { passphrase: existing.auth.passphrase })),\n }\n : kind === 'agent'\n ? { kind: 'agent' }\n : passwordAuth(id, input.password, existing)\n const next: RemoteTarget = {\n id,\n name: input.name,\n host: input.host,\n port: input.port,\n user: input.user,\n auth,\n ...(input.workdir !== undefined && input.workdir !== '' ? { workdir: input.workdir } : (existing?.workdir === undefined ? {} : { workdir: existing.workdir })),\n ...(input.pythonPath !== undefined && input.pythonPath !== '' ? { pythonPath: input.pythonPath } : (existing?.pythonPath === undefined ? {} : { pythonPath: existing.pythonPath })),\n ...(input.pipIndexUrl !== undefined && input.pipIndexUrl !== '' ? { pipIndexUrl: input.pipIndexUrl } : (existing?.pipIndexUrl === undefined ? {} : { pipIndexUrl: existing.pipIndexUrl })),\n }\n // \u6362\u4E86\u8BA4\u8BC1\u65B9\u5F0F\u6216\u5BC6\u7801\uFF1A\u65E7\u51ED\u636E\u4E0D\u80FD\u6210\u4E3A\u94A5\u5319\u4E32\u91CC\u7684\u5B64\u513F\u6761\u76EE\u3002\n const previousRef = existing?.auth.secretRef ?? ''\n const nextRef = auth.secretRef ?? ''\n if (previousRef !== '' && previousRef !== nextRef) {\n const stale = parseSecretRef(previousRef)\n if (stale !== undefined) systemSecretStore().remove(SECRET_SERVICE, stale.account)\n }\n writeAll([...targets.filter(item => item.id !== id), next])\n return view(next)\n}\n\n/**\n * \u628A remotes.json \u91CC\u7684\u660E\u6587\u5BC6\u7801\u8FC1\u8FDB\u7CFB\u7EDF\u51ED\u636E\u5B58\u50A8\uFF08\u5E42\u7B49\uFF1B\u540E\u7AEF\u4E0D\u53EF\u7528\u65F6\u4E0D\u52A8\uFF09\u3002\n * \u63D2\u4EF6\u542F\u52A8\u65F6\u8C03\u7528\u4E00\u6B21\uFF1A\u5B58\u91CF\u76EE\u6807\u81EA\u52A8\u5B8C\u6210\u5347\u7EA7\uFF0C\u4E0D\u8981\u6C42\u7528\u6237\u91CD\u586B\u5BC6\u7801\u3002\n */\nexport function migratePlaintextSecrets(): { migrated: number; remaining: number } {\n const targets = readAll()\n const store = systemSecretStore()\n const plaintextCount = (list: RemoteTarget[]): number =>\n list.filter(item => item.auth.kind === 'password' && (item.auth.password ?? '') !== '').length\n if (store.backend === 'none') return { migrated: 0, remaining: plaintextCount(targets) }\n let migrated = 0\n const next = targets.map(target => {\n const secret = target.auth.password ?? ''\n if (target.auth.kind !== 'password' || secret === '') return target\n if (!store.set(SECRET_SERVICE, secretAccount(target.id), secret)) return target\n migrated += 1\n const auth: RemoteAuth = { kind: 'password', secretRef: secretRefOf(store.backend, target.id) }\n return { ...target, auth }\n })\n if (migrated > 0) writeAll(next)\n return { migrated, remaining: plaintextCount(next) }\n}\n\n/** \u5220\u9664\u76EE\u6807\uFF1B\u540C\u65F6\u6E05\u6389\u7CFB\u7EDF\u51ED\u636E\u5B58\u50A8\u91CC\u7684\u5BC6\u7801\u3002 */\nexport function removeRemoteTarget(id: string): boolean {\n const targets = readAll()\n const next = targets.filter(item => item.id !== id)\n if (next.length === targets.length) return false\n const removed = targets.find(item => item.id === id)\n const parsed = parseSecretRef(removed?.auth.secretRef ?? '')\n if (parsed !== undefined) systemSecretStore().remove(SECRET_SERVICE, parsed.account)\n writeAll(next)\n return true\n}\n\n/** \u8FDC\u7AEF\u88C5\u5305\u955C\u50CF\uFF1A\u76EE\u6807\u914D\u7F6E\u4F18\u5148\uFF0C\u7F3A\u7701\u6E05\u534E\u6E90\uFF08\u8BE5\u7F51\u7EDC\u8BBF\u95EE\u5B98\u65B9 PyPI \u5F88\u6162\uFF09\u3002 */\nexport const DEFAULT_PIP_INDEX = 'https://pypi.tuna.tsinghua.edu.cn/simple'\n\nexport function targetPipIndex(targetId: string): string {\n const target = readAll().find(item => item.id === targetId)\n return target?.pipIndexUrl ?? DEFAULT_PIP_INDEX\n}\n\n/** \u8FDC\u7AEF\u89E3\u91CA\u5668\uFF1A\u76EE\u6807\u91CC\u914D\u7F6E\u7684 venv python\uFF0C\u7F3A\u7701 python3\u3002 */\nexport function targetPython(targetId: string): string {\n const target = readAll().find(item => item.id === targetId)\n return target?.pythonPath ?? 'python3'\n}\n\nfunction findTarget(id: string): RemoteTarget {\n const target = readAll().find(item => item.id === id)\n if (target === undefined) throw new Error('\u8FDC\u7A0B\u76EE\u6807\u4E0D\u5B58\u5728\uFF1A' + id)\n return target\n}\n\n/** \u5EFA\u7ACB SSH \u8FDE\u63A5\uFF08\u8C03\u7528\u65B9\u8D1F\u8D23 end()\uFF09\u3002 */\nlet sshModulePromise: Promise<typeof import('ssh2')> | null = null\nfunction sshModule(): Promise<typeof import('ssh2')> {\n sshModulePromise ??= import('ssh2')\n return sshModulePromise\n}\n\nasync function connect(target: RemoteTarget): Promise<SshClient> {\n const { Client } = await sshModule()\n return await new Promise((resolve, reject) => {\n const client = new Client()\n const options: Record<string, unknown> = {\n host: target.host,\n port: target.port,\n username: target.user,\n readyTimeout: 20_000,\n keepaliveInterval: 10_000,\n }\n if (target.auth.kind === 'agent') {\n const socket = process.env['SSH_AUTH_SOCK'] ?? ''\n if (socket === '') throw new Error('\u672A\u627E\u5230 SSH agent\uFF08SSH_AUTH_SOCK \u672A\u8BBE\u7F6E\uFF09\uFF1A\u8BF7\u542F\u52A8 ssh-agent \u6216\u6539\u7528\u5BC6\u94A5/\u5BC6\u7801')\n options.agent = socket\n } else if (target.auth.kind === 'password') {\n options.password = resolvePassword(target.auth)\n } else {\n const keyPath = target.auth.keyPath ?? ''\n if (!existsSync(keyPath)) throw new Error('\u79C1\u94A5\u4E0D\u5B58\u5728\uFF1A' + keyPath)\n options.privateKey = readFileSync(keyPath)\n if ((target.auth.passphrase ?? '') !== '') options.passphrase = target.auth.passphrase\n }\n client.once('ready', () => resolve(client))\n client.once('error', (error: Error) => reject(error))\n client.connect(options)\n })\n}\n\nfunction runRemote(client: SshClient, command: string, timeoutMs: number): Promise<RemoteExecResult> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => reject(new Error('\u8FDC\u7AEF\u547D\u4EE4\u8D85\u65F6')), timeoutMs)\n client.exec(command, (error: Error | undefined, stream: any) => {\n if (error) { clearTimeout(timer); reject(error); return }\n let stdout = ''\n let stderr = ''\n stream.on('data', (chunk: Buffer) => { stdout += chunk.toString('utf8') })\n stream.stderr.on('data', (chunk: Buffer) => { stderr += chunk.toString('utf8') })\n stream.on('close', (code: number | null) => {\n clearTimeout(timer)\n resolve({ code, stdout, stderr })\n })\n })\n })\n}\n\nconst PROBE_COMMAND = [\n 'echo \"#UNAME\"; uname -sr',\n 'echo \"#GPU\"; (/usr/lib/wsl/lib/nvidia-smi -L 2>/dev/null || nvidia-smi -L 2>/dev/null || echo \"no-gpu\")',\n 'echo \"#PY\"; (python3 -V 2>&1 || echo \"no-python\")',\n 'echo \"#CPU\"; nproc',\n 'echo \"#MEM\"; (free -g 2>/dev/null | awk \"/Mem:/{print \\$2}\" || echo 0)',\n].join('; ')\n\nfunction section(text: string, name: string): string {\n const start = text.indexOf('#' + name)\n if (start < 0) return ''\n const rest = text.slice(start + name.length + 1)\n const end = rest.search(/\\n#/)\n return (end < 0 ? rest : rest.slice(0, end)).trim()\n}\n\n/** \u8FDE\u63A5\u5E76\u63A2\u6D4B\uFF1A\u7CFB\u7EDF\u3001GPU\u3001Python\u3001\u6838\u6570\u3001\u5185\u5B58\u3002 */\nexport async function probeRemoteTarget(id: string): Promise<RemoteProbe> {\n const started = Date.now()\n let client: SshClient | undefined\n try {\n const target = findTarget(id)\n client = await connect(target)\n const result = await runRemote(client, PROBE_COMMAND, 30_000)\n const gpuSection = section(result.stdout, 'GPU')\n const gpus = gpuSection === '' || gpuSection.includes('no-gpu') || gpuSection.includes('not found')\n ? []\n : gpuSection.split('\\n').map(line => line.trim()).filter(line => line !== '')\n const python = section(result.stdout, 'PY')\n const cpus = Number(section(result.stdout, 'CPU'))\n const memory = Number(section(result.stdout, 'MEM'))\n return {\n ok: true,\n latencyMs: Date.now() - started,\n uname: section(result.stdout, 'UNAME'),\n gpu: gpus,\n python: python === '' ? undefined : python,\n ...(Number.isFinite(cpus) ? { cpus } : {}),\n ...(Number.isFinite(memory) ? { memoryGb: memory } : {}),\n }\n } catch (error) {\n return { ok: false, latencyMs: Date.now() - started, error: error instanceof Error ? error.message : String(error) }\n } finally {\n client?.end()\n }\n}\n\n/** \u9012\u5F52\u4E0A\u4F20\u672C\u5730\u76EE\u5F55\u5230\u8FDC\u7AEF\uFF08SFTP\uFF09\u3002 */\nexport async function uploadDirectory(targetId: string, localDir: string, remoteDir: string): Promise<{ files: number }> {\n const target = findTarget(targetId)\n const client = await connect(target)\n try {\n const files: { local: string; remote: string }[] = []\n const walk = (dir: string, prefix: string): void => {\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.name.startsWith('.')) continue\n const local = join(dir, entry.name)\n const remote = prefix + '/' + entry.name\n if (entry.isDirectory()) walk(local, remote)\n else files.push({ local, remote })\n }\n }\n walk(localDir, '')\n const sftp = await new Promise<SFTPWrapper>((resolve, reject) => {\n client.sftp((error, value) => { if (error) reject(error); else resolve(value) })\n })\n await new Promise<void>((resolve, reject) => {\n let index = 0\n const next = (): void => {\n if (index >= files.length) { resolve(); return }\n const item = files[index]!\n index += 1\n const parts = item.remote.split('/').filter(part => part !== '')\n let created = remoteDir\n for (const part of parts.slice(0, -1)) {\n created += '/' + part\n sftp.mkdir(created, () => undefined)\n }\n sftp.fastPut(item.local, remoteDir + item.remote, (error) => { if (error) reject(error); else next() })\n }\n next()\n })\n return { files: files.length }\n } finally { client.end() }\n}\n\n/** \u9012\u5F52\u4E0B\u8F7D\u8FDC\u7AEF\u76EE\u5F55\u5230\u672C\u5730\uFF08\u5931\u8D25\u65F6\u5C3D\u91CF\u4FDD\u7559\u5DF2\u4E0B\u8F7D\u90E8\u5206\uFF09\u3002 */\nexport async function downloadDirectory(targetId: string, remoteDir: string, localDir: string): Promise<{ files: number }> {\n const target = findTarget(targetId)\n const client = await connect(target)\n try {\n const sftp = await new Promise<SFTPWrapper>((resolve, reject) => {\n client.sftp((error, value) => { if (error) reject(error); else resolve(value) })\n })\n const list = (dir: string): Promise<{ name: string; dir: boolean }[]> => new Promise((resolve, reject) => {\n sftp.readdir(dir, (error, entries) => {\n if (error) { reject(error); return }\n resolve(entries.filter(entry => entry.filename !== '.' && entry.filename !== '..')\n .map(entry => ({ name: entry.filename, dir: (entry.attrs.mode & 0o040000) !== 0 })))\n })\n })\n let count = 0\n const walk = async (remote: string, local: string, depth: number): Promise<void> => {\n if (depth > 4) return\n let entries: { name: string; dir: boolean }[]\n try { entries = await list(remote) } catch { return }\n mkdirSync(local, { recursive: true })\n for (const entry of entries) {\n const remotePath = remote + '/' + entry.name\n const localPath = join(local, entry.name)\n if (entry.dir) { await walk(remotePath, localPath, depth + 1); continue }\n await new Promise<void>((resolve, reject) => {\n sftp.fastGet(remotePath, localPath, error => { if (error) reject(error); else resolve() })\n }).then(() => { count += 1 }).catch(() => undefined)\n }\n }\n await walk(remoteDir, localDir, 0)\n return { files: count }\n } finally { client.end() }\n}\n\n/** \u5728\u8FDC\u7AEF\u6267\u884C\u4E00\u6761\u547D\u4EE4\uFF08cwd \u9ED8\u8BA4\u76EE\u6807 workdir\uFF09\u3002 */\nexport async function execRemoteTarget(id: string, command: string, timeoutMs = 120_000): Promise<RemoteExecResult> {\n const target = findTarget(id)\n const client = await connect(target)\n try {\n const full = (target.workdir ?? '') === ''\n ? command\n : 'cd ' + JSON.stringify(target.workdir) + ' 2>/dev/null; ' + command\n return await runRemote(client, full, timeoutMs)\n } finally {\n client.end()\n }\n}\n", "/** \u7CFB\u7EDF\u51ED\u636E\u5B58\u50A8\uFF1A\u8BA9\u8FDC\u7A0B\u76EE\u6807\u7684\u5BC6\u7801\u4E0D\u843D JSON \u660E\u6587\u3002\n *\n * \u540E\u7AEF\u9009\u62E9\uFF1A\n * - macOS\uFF1A`security(1)` \u7684 generic password\uFF08\u767B\u5F55\u94A5\u5319\u4E32\uFF0C\u968F\u7528\u6237\u767B\u5F55\u89E3\u9501\uFF09\uFF1B\n * - Linux\uFF1Afreedesktop Secret Service \u7684 `secret-tool`\uFF1B\n * - \u5176\u5B83\u5E73\u53F0\u6216\u7F3A CLI\uFF1A`none`\uFF0C\u8C03\u7528\u65B9\u9000\u56DE 0600 \u660E\u6587\u5E76\u5728\u89C6\u56FE\u91CC\u660E\u786E\u544A\u8B66\u3002\n *\n * KSRW_SECRET_STORE \u53EF\u5F3A\u5236\u540E\u7AEF\uFF1A`keychain` / `secret-service` / `none`\n * \uFF08\u6D4B\u8BD5\u4E0E CI \u7528 `none` \u56FA\u5B9A\u8D70\u964D\u7EA7\u8DEF\u5F84\uFF09\u3002\n */\nimport { spawnSync } from 'node:child_process'\n\nexport type SecretBackend = 'keychain' | 'secret-service' | 'none'\n\nexport interface SecretStore {\n readonly backend: SecretBackend\n /** \u5199\u5165\uFF08\u8986\u76D6\uFF09\u4E00\u6761\u51ED\u636E\uFF1B\u8FD4\u56DE\u662F\u5426\u843D\u5E93\u6210\u529F\u3002 */\n set(service: string, account: string, secret: string): boolean\n /** \u8BFB\u51FA\u51ED\u636E\uFF1B\u4E0D\u5B58\u5728\u6216\u540E\u7AEF\u4E0D\u53EF\u7528\u65F6\u8FD4\u56DE undefined\u3002 */\n get(service: string, account: string): string | undefined\n /** \u5220\u9664\u51ED\u636E\uFF1B\u8FD4\u56DE\u662F\u5426\u771F\u7684\u5220\u6389\u4E86\u4E00\u6761\u3002 */\n remove(service: string, account: string): boolean\n}\n\nfunction run(command: string, args: string[], input?: string): { ok: boolean; stdout: string } {\n const result = spawnSync(command, args, {\n encoding: 'utf8',\n input,\n timeout: 10_000,\n stdio: ['pipe', 'pipe', 'ignore'],\n })\n return { ok: result.error === undefined && result.status === 0, stdout: (result.stdout ?? '').trim() }\n}\n\nfunction commandExists(command: string): boolean {\n const result = spawnSync(command, ['--help'], { stdio: 'ignore', timeout: 5_000 })\n return result.error === undefined\n}\n\nconst NONE: SecretStore = {\n backend: 'none',\n set: () => false,\n get: () => undefined,\n remove: () => false,\n}\n\n/** macOS \u767B\u5F55\u94A5\u5319\u4E32\u3002\u5BC6\u7801\u7ECF argv \u4F20\u7ED9 security(1)\uFF0C\u672C\u673A\u5355\u7528\u6237\u573A\u666F\u53EF\u63A5\u53D7\u3002 */\nfunction keychainStore(): SecretStore {\n return {\n backend: 'keychain',\n set(service, account, secret) {\n return run('security', ['add-generic-password', '-U', '-a', account, '-s', service, '-w', secret]).ok\n },\n get(service, account) {\n const result = run('security', ['find-generic-password', '-a', account, '-s', service, '-w'])\n return result.ok && result.stdout !== '' ? result.stdout : undefined\n },\n remove(service, account) {\n return run('security', ['delete-generic-password', '-a', account, '-s', service]).ok\n },\n }\n}\n\n/** freedesktop Secret Service\u3002secret \u8D70 stdin\uFF0C\u4E0D\u8FDB argv\u3002 */\nfunction secretServiceStore(): SecretStore {\n return {\n backend: 'secret-service',\n set(service, account, secret) {\n return run('secret-tool', ['store', '--label', `KSRW ${account}`, 'service', service, 'account', account], secret).ok\n },\n get(service, account) {\n const result = run('secret-tool', ['lookup', 'service', service, 'account', account])\n return result.ok && result.stdout !== '' ? result.stdout : undefined\n },\n remove(service, account) {\n return run('secret-tool', ['clear', 'service', service, 'account', account]).ok\n },\n }\n}\n\nlet cached: { key: string; store: SecretStore } | undefined\n\n/** \u89E3\u6790\u5F53\u524D\u5E73\u53F0\u7684\u51ED\u636E\u540E\u7AEF\uFF08\u6309 env + \u5E73\u53F0\u7F13\u5B58\uFF0C\u6539 env \u5373\u5931\u6548\uFF09\u3002 */\nexport function systemSecretStore(): SecretStore {\n const forced = process.env['KSRW_SECRET_STORE']?.trim() ?? ''\n const key = forced + '|' + process.platform\n if (cached?.key === key) return cached.store\n let store = NONE\n if (forced === 'none') {\n store = NONE\n } else if (forced === '' || forced === 'keychain') {\n if (process.platform === 'darwin' && commandExists('security')) store = keychainStore()\n }\n if (store.backend === 'none' && (forced === '' || forced === 'secret-service') && process.platform === 'linux' && commandExists('secret-tool')) {\n store = secretServiceStore()\n }\n cached = { key, store }\n return store\n}\n", "/** \u4F1A\u8BDD\u515C\u5E95\u5F52\u6863\uFF1Aagent \u5FD8\u4E86\u8C03 ksrw_archive \u65F6\uFF0C\u5BBF\u4E3B\u4ECD\u628A\u5DE5\u4F4D\u4F1A\u8BDD\u7684\u6210\u679C\u5F52\u6863\u8FDB\u9879\u76EE\u5E93\u3002\n *\n * \u4F9D\u636E\uFF1A\u5F15\u64CE\u628A\u6BCF\u4E2A\u4F1A\u8BDD\u5FEB\u7167\u5199\u6210 `<QILIN_HOME>/sessions/<workspace>/<session>/session.v*.jsonl.zstd`\uFF0C\n * \u9996\u884C\u662F\u4F1A\u8BDD\u5143\u6570\u636E `{ id, cwd, agentPreset, createdAt }`\u3002\u5DE1\u68C0\u53EA\u770B\u9996\u884C\uFF0C\u4E0D\u89E3\u6790\u6574\u6BB5\u5BF9\u8BDD\u3002\n *\n * \u89C4\u5219\uFF1A\n * - \u53EA\u5904\u7406 agentPreset \u4EE5 `ksrw-` \u5F00\u5934\u7684\u4F1A\u8BDD\uFF08\u5DE5\u4F4D\u9884\u8BBE agent\uFF09\uFF1B\n * - cwd \u672C\u8EAB\u662F\u5DF2\u767B\u8BB0\u7684 KSRW \u9879\u76EE \u2192 \u628A\u5F52\u6863\u5199\u8FDB\u8BE5\u9879\u76EE\uFF1Bdetached \u5DE5\u574A\u4E0D\u63D0\u5347\uFF1B\n * - cwd \u4E0D\u662F KSRW \u9879\u76EE \u2192 \u5728\u9879\u76EE\u5E93\u91CC\u5EFA\u4E00\u6761\u300C\u4F1A\u8BDD\u5F52\u6863\u300D\u5F71\u5B50\u9879\u76EE\uFF0C\u4E0D\u5F80\u7528\u6237\u5DE5\u4F5C\u76EE\u5F55\u5199\u4EFB\u4F55\u6587\u4EF6\uFF1B\n * - \u6309 (sessionId, \u5FEB\u7167 mtime) \u53BB\u91CD\uFF0C\u91CD\u542F\u4E0D\u4F1A\u91CD\u590D\u5F52\u6863\u3002\n */\nimport { createReadStream, existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { createZstdDecompress } from 'node:zlib'\n\nimport { ProjectStore, resolveKsrwHome } from 'ksrw-kernel'\n\nconst ROLE_PREFIX = 'ksrw-'\nconst TICK_MS = 60_000\nconst MAX_SNAPSHOT_BYTES = 32 * 1024 * 1024\n\nexport interface AutoArchiveLog {\n info?(message: string): void\n warn?(message: string): void\n}\n\ninterface SessionMeta {\n readonly id?: string\n readonly cwd?: string\n readonly agentPreset?: string\n}\n\n/** \u8BFB\u4F1A\u8BDD\u5FEB\u7167\u9996\u884C\u5143\u6570\u636E\uFF08\u53EA\u89E3\u538B\u5230\u7B2C\u4E00\u884C\u5C31\u65AD\u5F00\uFF09\u3002 */\nexport async function readSessionMeta(file: string): Promise<SessionMeta | undefined> {\n try {\n if (statSync(file).size > MAX_SNAPSHOT_BYTES) return undefined\n } catch { return undefined }\n return await new Promise<SessionMeta | undefined>((resolve) => {\n let settled = false\n const finish = (value?: SessionMeta): void => {\n if (settled) return\n settled = true\n try { stream.destroy() } catch { /* stream already closed */ }\n resolve(value)\n }\n let stream: ReturnType<typeof createReadStream> | undefined\n try {\n const decompress = createZstdDecompress()\n stream = createReadStream(file).pipe(decompress) as unknown as ReturnType<typeof createReadStream>\n let buffer = ''\n stream.on('data', (chunk: Buffer) => {\n buffer += chunk.toString('utf8')\n const newline = buffer.indexOf('\\n')\n if (newline < 0) {\n if (buffer.length > 262_144) finish(undefined)\n return\n }\n try { finish(JSON.parse(buffer.slice(0, newline)) as SessionMeta) } catch { finish(undefined) }\n })\n stream.on('error', () => finish(undefined))\n stream.on('end', () => finish(undefined))\n } catch {\n finish(undefined)\n }\n })\n}\n\n/** \u6536\u96C6\u6240\u6709\u53EF\u80FD\u5FEB\u7167\uFF1A<sessionsRoot>/<workspace>/<session>/session.v*.jsonl.zstd\u3002 */\nexport function collectSnapshots(sessionsRoot: string): { file: string; sessionDir: string }[] {\n const out: { file: string; sessionDir: string }[] = []\n let workspaces: string[] = []\n try { workspaces = readdirSync(sessionsRoot) } catch { return out }\n for (const workspace of workspaces) {\n const workspacePath = join(sessionsRoot, workspace)\n let sessions: string[] = []\n try { sessions = readdirSync(workspacePath) } catch { continue }\n for (const session of sessions) {\n const sessionDir = join(workspacePath, session)\n let files: string[] = []\n try { files = readdirSync(sessionDir) } catch { continue }\n for (const name of files) {\n if (!/^session\\.v\\d+\\.jsonl\\.zstd$/.test(name)) continue\n out.push({ file: join(sessionDir, name), sessionDir })\n }\n }\n }\n return out\n}\n\nfunction slugify(name: string): string {\n const slug = name.trim().toLowerCase().replace(/[^\\p{L}\\p{N}]+/gu, '-').replace(/^-+|-+$/g, '')\n return slug === '' ? 'session-archive' : slug.slice(0, 60)\n}\n\nfunction loadMarkers(file: string): Record<string, number> {\n try {\n const parsed = JSON.parse(readFileSync(file, 'utf8')) as { sessions?: Record<string, number> }\n return parsed.sessions ?? {}\n } catch { return {} }\n}\n\nfunction saveMarkers(file: string, markers: Record<string, number>): void {\n try {\n mkdirSync(dirname(file), { recursive: true, mode: 0o700 })\n writeFileSync(file, JSON.stringify({ version: 1, sessions: markers }, undefined, 2) + '\\n', { encoding: 'utf8', mode: 0o600 })\n } catch { /* \u53BB\u91CD\u8BB0\u4E0D\u4E0B\u65F6\u4E0B\u6B21\u6700\u591A\u591A\u5F52\u6863\u4E00\u6B21\uFF0C\u4E0D\u5F71\u54CD\u6B63\u786E\u6027 */ }\n}\n\n/** \u5F52\u6863\u4E00\u4E2A\u5DE5\u4F4D\u4F1A\u8BDD\uFF1B\u8FD4\u56DE\u5F52\u6863\u6587\u4EF6\u8DEF\u5F84\uFF0C\u8DF3\u8FC7\u65F6\u8FD4\u56DE undefined\u3002 */\nexport async function archiveSession(\n store: ProjectStore,\n meta: SessionMeta,\n): Promise<string | undefined> {\n const cwd = meta.cwd ?? ''\n const role = meta.agentPreset ?? ''\n if (!role.startsWith(ROLE_PREFIX) || cwd === '' || !existsSync(cwd)) return undefined\n const now = Date.now()\n const stamp = new Date(now).toISOString().replace(/[:.]/g, '-')\n const sessionLabel = (meta.id ?? '').slice(-8) || 'unknown'\n let projectPath = cwd\n if (!existsSync(join(cwd, 'project.json'))) {\n // \u7528\u6237\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0D\u662F KSRW \u9879\u76EE\uFF1A\u53EA\u5728\u9879\u76EE\u5E93\u91CC\u8BB0\u4E00\u6761\u5F71\u5B50\u5F52\u6863\uFF0C\u7EDD\u4E0D\u5F80\u5DE5\u4F5C\u76EE\u5F55\u5199\u6587\u4EF6\u3002\n const name = '\u4F1A\u8BDD\u5F52\u6863 \u00B7 ' + (cwd.split(/[\\/\\\\]/).filter(Boolean).pop() ?? sessionLabel)\n projectPath = store.create({ name, idea: '\u6765\u81EA\u5DE5\u4F4D\u4F1A\u8BDD\u7684\u81EA\u52A8\u5F52\u6863\uFF1A' + cwd }).path\n } else {\n // \u5DF2\u6709\u9879\u76EE\uFF1Adetached \u5DE5\u574A\u4E0D\u63D0\u5347\u8FDB\u9879\u76EE\u5E93\uFF0C\u4F46\u4ECD\u53EF\u5199\u5165\u5F52\u6863\u7EAA\u8981\u3002\n const project = store.readProject(cwd)\n if (project.detached !== true) store.register(cwd)\n projectPath = cwd\n }\n const archiveDir = join(projectPath, 'archive')\n mkdirSync(archiveDir, { recursive: true, mode: 0o700 })\n const file = join(archiveDir, 'auto-' + stamp + '.md')\n const body = [\n '# \u81EA\u52A8\u5F52\u6863 \u00B7 ' + stamp,\n '',\n '- \u5DE5\u4F4D\u9884\u8BBE\uFF1A' + role,\n '- \u4F1A\u8BDD\uFF1A' + (meta.id ?? '(unknown)'),\n '- \u5DE5\u4F5C\u76EE\u5F55\uFF1A' + cwd,\n '- \u89E6\u53D1\uFF1A\u5BBF\u4E3B\u5DE1\u68C0\u515C\u5E95\uFF08agent \u672A\u5FC5\u8C03\u7528\u8FC7 ksrw_archive\uFF09',\n '',\n '## \u8BF4\u660E',\n '',\n '\u672C\u6B21\u4EFB\u52A1\u7684\u5F52\u6863\u7531\u5BBF\u4E3B\u81EA\u52A8\u5B8C\u6210\uFF0C\u53EA\u8BB0\u5F55\u4F1A\u8BDD\u8EAB\u4EFD\u4E0E\u5DE5\u4F5C\u76EE\u5F55\uFF1B\u5177\u4F53\u7ED3\u8BBA\u89C1\u8BE5\u4F1A\u8BDD\u7684\u5BF9\u8BDD\u8BB0\u5F55\u3002',\n '',\n ].join('\\n')\n writeFileSync(file, body, { encoding: 'utf8', mode: 0o600 })\n return file\n}\n\n/** \u542F\u52A8\u5DE1\u68C0\uFF1B\u8FD4\u56DE\u505C\u6B62\u51FD\u6570\u3002 */\nexport function startSessionAutoArchive(log?: AutoArchiveLog): () => void {\n const home = resolveKsrwHome()\n const sessionsRoot = join(dirname(home), 'sessions')\n const markerPath = join(home, 'auto-archive.json')\n const markers = loadMarkers(markerPath)\n // \u9996\u6B21\u542F\u7528\uFF08\u8FD8\u6CA1\u6709\u53BB\u91CD\u8868\uFF09\u53EA\u505A\u57FA\u7EBF\u767B\u8BB0\uFF1A\u4E0D\u628A\u5386\u53F2\u4F1A\u8BDD\u4E00\u6B21\u6027\u5012\u8FDB\u9879\u76EE\u5E93\u3002\n let baseline = !existsSync(markerPath)\n let running = false\n let dirty = false\n\n const tick = async (): Promise<void> => {\n if (running) return\n running = true\n try {\n const store = new ProjectStore({ home })\n for (const { file } of collectSnapshots(sessionsRoot)) {\n let mtime = 0\n try { mtime = statSync(file).mtimeMs } catch { continue }\n const meta = await readSessionMeta(file)\n const id = meta?.id ?? ''\n if (id === '' || meta === undefined) continue\n if (markers[id] === mtime) continue\n if (baseline) { markers[id] = mtime; dirty = true; continue }\n if (!(meta.agentPreset ?? '').startsWith(ROLE_PREFIX)) { markers[id] = mtime; dirty = true; continue }\n try {\n const archived = await archiveSession(store, meta)\n if (archived !== undefined) log?.info?.('[auto-archive] \u5DF2\u5F52\u6863\u4F1A\u8BDD ' + id + ' \u2192 ' + archived)\n markers[id] = mtime\n dirty = true\n } catch (error) {\n log?.warn?.('[auto-archive] \u5F52\u6863\u5931\u8D25\uFF1A' + (error instanceof Error ? error.message : String(error)))\n }\n }\n } catch (error) {\n // Node 22 \u9ED8\u8BA4 unhandledRejection=throw\uFF1A\u5DE1\u68C0\u5F02\u5E38\u4E0D\u80FD\u5E26\u8D70\u5BBF\u4E3B\u8FDB\u7A0B\u3002\n log?.warn?.('[auto-archive] \u5DE1\u68C0\u5931\u8D25\uFF1A' + (error instanceof Error ? error.message : String(error)))\n } finally {\n running = false\n if (baseline) {\n // \u57FA\u7EBF\u767B\u8BB0\u5B8C\u6210\u540E\u624D\u5F00\u59CB\u771F\u6B63\u5F52\u6863\u65B0\u4F1A\u8BDD\u3002\n baseline = false\n dirty = true\n log?.info?.('[auto-archive] \u5DF2\u8BB0\u5F55\u73B0\u6709\u4F1A\u8BDD\u57FA\u7EBF\uFF0C\u540E\u7EED\u5DE5\u4F4D\u4F1A\u8BDD\u5C06\u81EA\u52A8\u5F52\u6863')\n }\n if (dirty) { saveMarkers(markerPath, markers); dirty = false }\n }\n }\n\n const timer = setInterval(() => { void tick() }, TICK_MS)\n timer.unref?.()\n void tick()\n return () => clearInterval(timer)\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": ["copyFileSync", "existsSync", "mkdirSync", "readdirSync", "statSync", "dirname", "extname", "join", "fileURLToPath", "name", "fd", "max", "mkdirSync", "writeFileSync", "renameSync", "dirname", "resolve", "require", "require", "i", "l", "i", "i2", "_a", "_b", "i", "i", "_a", "_a", "i", "_a", "text", "i", "view", "x", "existsSync", "readFileSync", "renameSync", "statSync", "readFileSync", "max", "stamp", "text", "existsSync", "renameSync", "existsSync", "mkdirSync", "readFileSync", "writeFileSync", "join", "join", "readFileSync", "mkdirSync", "writeFileSync", "existsSync", "join", "spawnSync", "existsSync", "mkdirSync", "readFileSync", "readdirSync", "statSync", "writeFileSync", "dirname", "join", "resolve", "homedir", "createRequire", "join", "readFileSync", "existsSync", "readdirSync", "mkdirSync", "writeFileSync", "name", "homedir", "resolve", "dirname", "spawnSync", "join", "writeFileSync", "spawnSync", "mkdirSync", "readFileSync", "existsSync", "readdirSync", "name", "existsSync", "join", "readdirSync", "root", "resolve", "mkdirSync", "name", "writeFileSync", "relative", "createRequire", "readFileSync", "text", "existsSync", "mkdirSync", "readFileSync", "writeFileSync", "dirname", "join", "join", "readFileSync", "mkdirSync", "dirname", "writeFileSync", "text", "existsSync", "existsSync", "mkdirSync", "readdirSync", "readFileSync", "renameSync", "writeFileSync", "homedir", "dirname", "join", "resolve", "spawnSync", "existsSync", "readdirSync", "readFileSync", "statSync", "writeFileSync", "delimiter", "extname", "join", "resolve", "sep", "AdmZip", "spawnSync", "existsSync", "AdmZip", "text", "resolve", "sep", "name", "extname", "run", "statSync", "delimiter", "join", "readFileSync", "writeFileSync", "root", "readdirSync", "resolve", "join", "existsSync", "homedir", "text", "mkdirSync", "dirname", "writeFileSync", "renameSync", "readJson", "readFileSync", "writeJson", "name", "view", "manifestPath", "relative", "record", "registry", "readdirSync", "join", "resolve", "readJson", "writeJson", "existsSync", "mkdirSync", "readFileSync", "readdirSync", "rmSync", "statSync", "writeFileSync", "join", "resolve", "sep", "extname", "extname", "name", "text", "join", "resolve", "readJson", "readFileSync", "readText", "writeJson", "mkdirSync", "writeFileSync", "root", "existsSync", "readdirSync", "statSync", "run", "text", "name", "sep", "rmSync", "extension", "view", "run", "running", "join", "mkdirSync", "writeFileSync", "existsSync", "mkdirSync", "readFileSync", "readdirSync", "rmSync", "statSync", "writeFileSync", "join", "resolve", "sep", "join", "resolve", "readJson", "readFileSync", "readText", "writeJson", "mkdirSync", "writeFileSync", "paperDir", "manifestPath", "deriveManifest", "readManifest", "writeManifest", "two", "stamp", "shortId", "defaultName", "text", "statusOf", "root", "existsSync", "readdirSync", "statSync", "name", "extension", "rmSync", "view", "sep", "readFileSync", "statSync", "join", "run", "text", "statSync", "connect", "homedir", "extname", "join", "spawn", "existsSync", "mkdirSync", "readFileSync", "readdirSync", "statSync", "writeFileSync", "delimiter", "join", "spawnSync", "existsSync", "delimiter", "dirname", "join", "join", "name", "existsSync", "dirname", "relative", "pythonCandidates", "spawnSync", "pythonCandidates", "mkdirSync", "readFileSync", "writeFileSync", "join", "figuresDir", "join", "readFileSync", "mkdirSync", "writeFileSync", "existsSync", "mkdirSync", "readFileSync", "readdirSync", "writeFileSync", "dirname", "join", "spawnSync", "join", "existsSync", "readFileSync", "mkdirSync", "dirname", "writeFileSync", "resolve", "text", "name", "readdirSync", "join", "root", "existsSync", "readdirSync", "readJson", "writeFileSync", "statSync", "readFileSync", "run", "mkdirSync", "delimiter", "spawn", "text", "paperDir", "join", "statSync", "STAGES", "x", "bytes", "homedir", "resolve", "connect", "name", "extname", "max", "existsSync", "mkdirSync", "readdirSync", "readFileSync", "statSync", "writeFileSync", "dirname", "join", "statSync", "resolve", "readdirSync", "join", "name", "readFileSync", "mkdirSync", "dirname", "writeFileSync", "existsSync", "stamp", "join", "name", "running", "statSync", "join", "dirname", "fileURLToPath", "extname", "existsSync", "mkdirSync", "readdirSync", "statSync", "copyFileSync", "name"]
7
7
  }