dsh-taskboard 0.7.1 → 0.7.4

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.
@@ -1 +1 @@
1
- {"version":3,"file":"routes.js","names":[],"sources":["../../src/host/routes.ts"],"sourcesContent":["import { archiveTaskSessions } from './archive-sessions.ts'\n/**\n * /dsh-taskboard routes on the shared DSH webserver: a JSON API for the\n * GUI's human operations (create/update/move/comment/delete — actor `user`,\n * the done move IS allowed here) plus an SSE stream mirroring every\n * committed ledger mutation.\n *\n * All domain validation goes through the shared protocol pure functions; the\n * route layer only maps transport to envelope.\n *\n * @module dsh-taskboard/host/routes\n */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { readdir, rm } from 'node:fs/promises'\nimport { join, resolve, sep } from 'node:path'\nimport type { Context } from '@deepseek-ai/cordis'\n// Type-only: pulls the webServer Context merge (ctx.webServer).\nimport type {} from '@deepseek-ai/dsh-host-webserver'\nimport {\n asBoardSettings,\n asIsolation,\n asPermission,\n asStatus,\n asUrgency,\n canTransition,\n checklistFromTexts,\n defaultIsolationOf,\n defaultPermissionOf,\n isValidRelRepoPath,\n newCommentId,\n newTaskId,\n normalizeBody,\n normalizeChecklist,\n normalizeExecution,\n normalizeModel,\n normalizePrompt,\n normalizeTitle,\n summarize,\n syncClaim,\n validateLedgerImport,\n type TaskLedger,\n type TaskModel,\n type TaskRecord,\n type SystemCommentRow,\n} from '../shared/protocol.ts'\nimport { WORKTREE_DIR, worktreePathOf, type GitFace } from './git.ts'\nimport { removeMirror, repoMainPath } from './isolation.ts'\nimport { createRepoScanner, type RepoScanner } from './repos.ts'\nimport { activeHostLocale } from './locale.ts'\nimport type { CatalogModelItem, CatalogPresetItem, MergeRepoResult, TaskTemplate } from '../shared/api.ts'\nimport type { TemplateStore } from './templates.ts'\nimport { MAX_ASSET_BYTES, type AssetStore } from './assets.ts'\nimport type { StorageCoordinator } from './storage.ts'\nimport { ROUTE_PREFIX, SSE_PATH, type ApiFail, type ApiResult } from '../shared/api.ts'\nimport type { TaskStore } from './store.ts'\nimport { ERR, ToolError } from './tools.ts'\nimport type { WorkspaceFace } from './tools.ts'\n\n/** Heartbeat cadence for the SSE stream. */\nconst HEARTBEAT_MS = 20_000\n\n/** Max accepted JSON body bytes (S8: unbounded buffering is a local OOM vector). */\nconst MAX_BODY_BYTES = 5 * 1024 * 1024\n\n/** Route shapes (T2: compiled once at module load, not on every request). */\nconst TASK_DIFF_RE = new RegExp(`^${ROUTE_PREFIX}/tasks/([^/]+)/diff$`)\nconst TASK_RE = new RegExp(`^${ROUTE_PREFIX}/tasks/([^/]+)$`)\nconst TASK_ACTION_RE = new RegExp(`^${ROUTE_PREFIX}/tasks/([^/]+)/([\\\\w-]+)$`)\nconst ASSET_RE = new RegExp(`^${ROUTE_PREFIX}/assets/([a-f0-9]{64}\\\\.(?:png|jpg|gif|webp))$`)\n\n/** How long a workspace git-detection result stays cached (fail-soft). */\nconst GIT_DETECT_TTL_MS = 60_000\n\n/** The workspaces face routes need (same narrow shape as tools). */\nexport type RoutesWorkspaceFace = WorkspaceFace\n\n/** Options. */\nexport interface TaskboardRoutesOptions {\n store: TaskStore\n workspaces: RoutesWorkspaceFace\n now: () => number\n /** Manual-run hook (the execution service); absent → 501. Options carry `reuseWorktree` (续跑). */\n run?: (taskId: string, options?: { reuseWorktree?: boolean }) => Promise<{ ok: true; executionId: string; sessionId: string } | { ok: false; error: string }>\n /** Cancel hook (the execution service); absent → 501. */\n cancel?: (taskId: string) => Promise<{ ok: true; executionId: string } | { ok: false; error: string }>\n /**\n * Registered model provider routes (from the host llm runtime), for\n * advisory validation of pinned models; undefined = runtime unavailable.\n */\n modelProviders?: () => string[] | undefined\n /** Git face for worktree actions + workspace git detection; absent → 501 on git actions. */\n git?: GitFace\n /** Nested-repo scanner for mirror removal; absent → a default one is built on first use. */\n scanner?: RepoScanner\n /** Task-template store (0.4.0); absent → 501 on template actions. */\n templates?: TemplateStore\n /** Durable image attachment store (0.7.0); absent → attachment routes unavailable. */\n assets?: AssetStore\n /** Configurable data-directory coordinator (0.7.0). */\n storage?: StorageCoordinator\n /** Initial storage/ledger readiness barrier. */\n ready?: () => Promise<void>\n /** Prompt completions face (0.5.5; dynamically discovers skills & commands). */\n promptCompletions?: () => Promise<{\n skills?: Array<{ name: string; description?: string }>\n commands?: Array<{ name: string; description?: string; hint?: string }>\n }>\n /** Model and preset catalog face (0.5.5; dynamically discovers models & presets). */\n modelCatalog?: () => Promise<{\n models?: CatalogModelItem[]\n presets?: CatalogPresetItem[]\n defaultPresetId?: string\n }>\n}\n\n/** Validate a template's task spec (routes-side, unknown → invalid_input). */\nfunction normalizeTemplateSpec(raw: unknown, now: number): TaskTemplate['task'] {\n if (typeof raw !== 'object' || raw === null) throw new Error('Error: invalid_input: task must be an object')\n const e = raw as Record<string, unknown>\n const spec: TaskTemplate['task'] = {}\n const str = (key: string): string | undefined => {\n const v = e[key]\n if (v === undefined) return undefined\n if (typeof v !== 'string') throw new Error(`Error: invalid_input: task.${key} must be a string`)\n return v\n }\n const title = str('title')\n const description = str('description')\n const prompt = str('prompt')\n const urgency = str('urgency')\n const isolation = str('isolation')\n const presetId = str('presetId')\n const permission = str('permission')\n if (title !== undefined) spec.title = normalizeTitle(title)\n if (description !== undefined) spec.description = description\n if (prompt !== undefined) spec.prompt = normalizePrompt(prompt)\n if (urgency !== undefined) spec.urgency = asUrgency(urgency)\n if (isolation !== undefined) spec.isolation = asIsolation(isolation)\n if (presetId !== undefined && presetId.trim().length > 0) spec.presetId = presetId.trim()\n if (permission !== undefined && permission.trim().length > 0) spec.permission = asPermission(permission)\n if (e.execution !== undefined) {\n spec.execution = normalizeExecution(e.execution as { mode?: string; cron?: string }, now)\n }\n if (e.model !== undefined) spec.model = normalizeModel(e.model)\n if (e.checklist !== undefined) {\n if (!Array.isArray(e.checklist) || e.checklist.some(c => typeof c !== 'string')) {\n throw new Error('Error: invalid_input: task.checklist must be an array of strings')\n }\n checklistFromTexts(e.checklist as string[]) // validates count + texts\n spec.checklist = e.checklist as string[]\n }\n return spec\n}\n\n/** Validate a pinned model: structural check always, provider route when known. */\nfunction checkModel(raw: unknown, modelProviders?: () => string[] | undefined): TaskModel {\n const model = normalizeModel(raw)\n const providers = modelProviders?.()\n if (providers !== undefined && !providers.includes(model.provider)) {\n throw new Error(`Error: invalid_input: model provider \"${model.provider}\" has no registered route (available: ${providers.join(', ')})`)\n }\n return model\n}\n\n/** JSON-envelope writer. */\nfunction json(res: ServerResponse, payload: ApiResult<unknown>, status = 200): void {\n const body = JSON.stringify(payload)\n res.writeHead(status, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' })\n res.end(body)\n}\n\n/** Domain failure → envelope + HTTP status. */\nfunction fail(code: ApiFail['error']['code'], message: string): { res: ApiFail; status: number } {\n const status = code === 'invalid_input' || code === 'invalid_transition' ? 400\n : code === 'not_found' ? 404\n : code === 'version_conflict' ? 409\n : code === 'forbidden' ? 403\n : 500\n return { res: { ok: false, error: { code, message } }, status }\n}\n\n/**\n * Read one JSON body (null on parse failure). S8: rejects bodies over\n * MAX_BODY_BYTES by throwing — the local, unauthenticated HTTP surface must\n * not be an unbounded memory sink.\n */\nasync function readBody(req: IncomingMessage): Promise<Record<string, unknown> | null> {\n const chunks: Buffer[] = []\n let total = 0\n for await (const chunk of req) {\n total += (chunk as Buffer).length\n if (total > MAX_BODY_BYTES) throw new Error('body too large')\n chunks.push(chunk as Buffer)\n }\n if (chunks.length === 0) return {}\n try {\n const parsed = JSON.parse(Buffer.concat(chunks).toString('utf8'))\n return typeof parsed === 'object' && parsed !== null ? parsed as Record<string, unknown> : null\n } catch {\n return null\n }\n}\n\n/** Read one bounded binary upload without ever buffering beyond the file cap. */\nasync function readBytes(req: IncomingMessage, limit: number): Promise<Buffer> {\n const chunks: Buffer[] = []\n let total = 0\n for await (const chunk of req) {\n const bytes = chunk as Buffer\n total += bytes.length\n if (total > limit) throw new Error('body too large')\n chunks.push(bytes)\n }\n return Buffer.concat(chunks)\n}\n\n/** String field accessor (null when absent/not a string). */\nfunction str(body: Record<string, unknown>, key: string): string | null {\n const v = body[key]\n return typeof v === 'string' ? v : null\n}\n\n/** Number field accessor (undefined when absent; null when present but not a number). */\nfunction num(body: Record<string, unknown>, key: string): number | undefined | null {\n const v = body[key]\n if (v === undefined) return undefined\n return typeof v === 'number' && Number.isFinite(v) ? v : null\n}\n\n/** Find a live task INSIDE a mutator (R1: guards run on the fresh draft). */\nfunction liveTaskAt(ledger: TaskLedger, id: string): { index: number; task: TaskRecord } {\n const index = ledger.tasks.findIndex(t => t.id === id)\n if (index < 0 || ledger.tasks[index]!.trashedAt !== undefined) throw new Error('Error: not_found: no such task')\n return { index, task: ledger.tasks[index]! }\n}\n\n/** Normalize an agent preset id: trimmed, non-empty; empty string → undefined. */\nfunction normalizePresetId(raw: string | null): string | undefined {\n const t = (raw ?? '').trim()\n return t.length === 0 ? undefined : t\n}\n\n/** Map a thrown domain error to the envelope. */\nfunction toFail(error: unknown): { res: ApiFail; status: number } {\n const message = error instanceof Error ? error.message : String(error)\n // Structured path first (review P2): ToolError carries its code — no need\n // to parse the 'Error: <code>: …' prefix it also renders into the message.\n if (error instanceof ToolError) {\n const mapped = error.code === ERR.workspaceMismatch ? 'forbidden' : error.code\n const known: ApiFail['error']['code'][] = ['invalid_input', 'not_found', 'version_conflict', 'invalid_transition', 'forbidden', 'internal']\n if ((known as string[]).includes(mapped)) {\n return fail(mapped as ApiFail['error']['code'], message.slice(7 + error.code.length + 2))\n }\n }\n const code = message.startsWith('Error: ') ? message.slice(7).split(':')[0] : undefined\n const known2: ApiFail['error']['code'][] = ['invalid_input', 'not_found', 'version_conflict', 'invalid_transition', 'forbidden', 'internal']\n if (code !== undefined && (known2 as string[]).includes(code)) {\n return fail(code as ApiFail['error']['code'], message.slice(7 + code.length + 2))\n }\n if (code === 'workspace_mismatch') return fail('forbidden', message.slice(7 + code.length + 2))\n return fail('invalid_input', message)\n}\n\n/**\n * Register the taskboard routes.\n * @param ctx - context carrying the webServer service.\n * @param options - store + workspaces + clock.\n * @returns the disposer.\n */\nexport function registerTaskboardRoutes(ctx: Context, options: TaskboardRoutesOptions): () => void {\n const { store, workspaces } = options\n const subscribers = new Set<ServerResponse>()\n let heartbeat: NodeJS.Timeout | undefined\n\n /** R4③: a cleanup/purge target must resolve INSIDE <ws>/.dsh-worktrees — string joining alone is never trusted with an rm. */\n const insideWorktreeScope = (wsPath: string, target: string): boolean => {\n const scope = resolve(wsPath, WORKTREE_DIR)\n const resolved = resolve(target)\n return resolved === scope || resolved.startsWith(scope + sep)\n }\n\n const broadcast = (change: { revision: number; kind: string; tasks: readonly TaskRecord[] }): void => {\n const frame = `event: change\\ndata: ${JSON.stringify({ revision: change.revision, kind: change.kind, tasks: change.tasks.map(summarize) })}\\n\\n`\n for (const res of subscribers) res.write(frame)\n }\n const unsubscribeBroadcast = store.subscribe(broadcast)\n\n // Workspace git detection, TTL-cached and fail-soft (false on any error):\n // feeds the create-form isolation toggle and the diagnostics panel.\n const gitCache = new Map<string, { value: boolean; at: number; repoCount?: number }>()\n const gitHinted = new Set<string>()\n\n /** Whether <root>/.gitignore (missing file counts as missing) ignores our worktree dir. */\n const gitignoreMissing = async (path: string): Promise<boolean> => {\n try {\n const { readFile } = await import('node:fs/promises')\n const ignore = await readFile(join(path, '.gitignore'), 'utf8')\n return !ignore.split('\\n').some(l => {\n const t = l.trim().replace(/\\/+$/, '')\n return t === WORKTREE_DIR || t === `/${WORKTREE_DIR}`\n })\n } catch {\n return true // no .gitignore at all (or unreadable) → suggest creating one\n }\n }\n\n // One scanner for every route-side discovery (the injected one or a\n // shared real-IO fallback whose TTL cache actually helps — 0.6.3).\n const sharedScanner: RepoScanner = options.scanner ?? createRepoScanner()\n\n /**\n * Whether the workspace root itself is a git repo (the .gitignore-suggestion\n * gate — a plain container has no repo that could ignore anything).\n */\n const rootIsRepo = async (path: string): Promise<boolean> => {\n try {\n return (await options.git?.detect(path)) === true\n } catch {\n return false // fail-soft\n }\n }\n\n /**\n * Workspace repo facts for the form (0.6.3): `gitAvailable` gates the\n * worktree option, `repoCount` feeds the mirror badge. Availability now\n * covers PARALLEL MULTI-REPO workspaces too: a root repo qualifies as\n * before, and a workspace whose root is NOT a repo still qualifies when\n * the scanner finds nested repos — prepareMirror isolates exactly that\n * container shape (mirror root = plain dir, one worktree per nested repo),\n * so the form must not lock the capability away.\n */\n const workspaceRepos = async (path: string): Promise<{ gitAvailable: boolean; repoCount: number }> => {\n if (options.git === undefined) return { gitAvailable: false, repoCount: 0 }\n const hit = gitCache.get(path)\n if (hit !== undefined && options.now() - hit.at < GIT_DETECT_TTL_MS) {\n return { gitAvailable: hit.value, repoCount: hit.repoCount ?? (hit.value ? 1 : 0) }\n }\n let rootRepo = false\n try {\n rootRepo = await options.git.detect(path)\n } catch { /* fail-soft → false */ }\n // gitignore 建议 (plan §3.2): suggest (never write) ignoring our\n // worktree directory, once per workspace per host run. Root repos only.\n // The line is localized from the DSH locale preference (see host/locale.ts).\n if (rootRepo && !gitHinted.has(path)) {\n gitHinted.add(path)\n if (await gitignoreMissing(path)) {\n const file = `${path}/.gitignore`\n const hint = activeHostLocale(ctx) === 'zh'\n ? `建议在 ${file} 加入一行 ${WORKTREE_DIR}/ 以隐藏任务 worktree 目录(不会自动修改)`\n : `suggests adding one line to ${file}: ${WORKTREE_DIR}/ to hide the task worktree directory (no automatic edits)`\n console.info(`[dsh-taskboard] ${hint}`)\n }\n }\n // The nested scan always runs: repoCount needs it even when the root\n // is a repo (root repo + nested repos = the mirror shape). The scanner's\n // own TTL cache keeps repeated workspace polls cheap.\n let nestedCount = 0\n try {\n nestedCount = (await sharedScanner.findNestedRepos(path)).length\n } catch { /* fail-soft → 0 */ }\n const value = rootRepo || nestedCount > 0\n const repoCount = (rootRepo ? 1 : 0) + nestedCount\n gitCache.set(path, { value, at: options.now(), repoCount })\n return { gitAvailable: value, repoCount }\n }\n\n /** List orphan worktree dirs: entries under <ws>/.dsh-worktrees owned by no ledger task. */\n const listOrphanWorktrees = async (): Promise<Array<{ workspaceId: string; workspacePath: string; taskId: string; path: string }>> => {\n const orphans: Array<{ workspaceId: string; workspacePath: string; taskId: string; path: string }> = []\n const known = new Set(store.snapshot().tasks.map(t => t.id))\n for (const ws of workspaces.list()) {\n let entries: string[] = []\n try {\n const dirents = await readdir(join(ws.path, WORKTREE_DIR), { withFileTypes: true })\n entries = dirents.filter(e => e.isDirectory()).map(e => e.name)\n } catch { /* no worktrees dir → nothing to do */ }\n for (const taskId of entries) {\n if (!known.has(taskId)) orphans.push({ workspaceId: ws.id, workspacePath: ws.path, taskId, path: worktreePathOf(ws.path, taskId) })\n }\n }\n return orphans\n }\n\n /** Git-enabled workspaces whose .gitignore does not cover the worktree dir. */\n const listGitignoreSuggestions = async (): Promise<Array<{ workspaceId: string; workspacePath: string }>> => {\n const suggestions: Array<{ workspaceId: string; workspacePath: string }> = []\n for (const ws of workspaces.list()) {\n // Root repos only: a plain container has no .gitignore any repo reads.\n if (!(await rootIsRepo(ws.path))) continue\n if (await gitignoreMissing(ws.path)) suggestions.push({ workspaceId: ws.id, workspacePath: ws.path })\n }\n return suggestions\n }\n\n const handler = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n const url = new URL(req.url ?? '/', 'http://x')\n const pathname = url.pathname\n\n // ---------------------------------------------------------------- GET\n if (req.method === 'GET') {\n if (pathname === `${ROUTE_PREFIX}/storage`) {\n if (options.storage === undefined) { res.writeHead(501); res.end(); return }\n json(res, { ok: true, value: await options.storage.status() })\n return\n }\n await options.ready?.()\n const assetMatch = pathname.match(ASSET_RE)\n if (assetMatch !== null) {\n const asset = await options.assets?.read(assetMatch[1]!)\n if (asset === undefined) { res.writeHead(404); res.end(); return }\n res.writeHead(200, {\n 'content-type': asset.mime,\n 'content-length': asset.bytes.length,\n 'cache-control': 'public, max-age=31536000, immutable',\n 'x-content-type-options': 'nosniff',\n })\n res.end(asset.bytes)\n return\n }\n if (pathname === `${ROUTE_PREFIX}/state`) {\n await store.load()\n json(res, { ok: true, value: { ...store.snapshot(), capabilities: { archiveSessions: typeof workspaces.archiveSession === 'function' } } })\n return\n }\n if (pathname === `${ROUTE_PREFIX}/workspaces`) {\n const list = workspaces.list()\n const info = await Promise.all(list.map(ws => workspaceRepos(ws.path)))\n json(res, {\n ok: true,\n value: list.map((ws, i) => ({ ...ws, sessionCount: 0, gitAvailable: info[i]!.gitAvailable, repoCount: info[i]!.repoCount })),\n })\n return\n }\n if (pathname === `${ROUTE_PREFIX}/diagnostics`) {\n const ledger = store.snapshot()\n let staleRunning = 0\n for (const t of ledger.tasks) {\n for (const e of t.executions) if (e.outcome === 'running') staleRunning += 1\n }\n json(res, {\n ok: true,\n value: {\n revision: ledger.revision,\n tasks: ledger.tasks.length,\n staleRunning,\n orphanWorktrees: await listOrphanWorktrees(),\n gitIgnoreSuggestions: await listGitignoreSuggestions(),\n },\n })\n return\n }\n // Diff viewer (0.4.0): read-only git show/diff for one execution's\n // commit or changed path. Prefers the live worktree (uncommitted\n // view), falls back to the main repo.\n const diffMatch = pathname.match(TASK_DIFF_RE)\n if (diffMatch !== null) {\n try {\n if (options.git === undefined) {\n const f = fail('invalid_input', 'git integration unavailable')\n json(res, f.res, 501)\n return\n }\n const task = store.get(diffMatch[1]!)\n if (task === undefined) throw new Error('Error: not_found: no such task')\n const execution = task.executions.find(e => e.id === url.searchParams.get('execution'))\n if (execution === undefined) throw new Error('Error: not_found: no such execution')\n const commit = url.searchParams.get('commit')\n const filePath = url.searchParams.get('path')\n const ws = workspaces.get(task.workspaceId)\n if (ws === undefined) throw new Error('Error: not_found: unknown workspace')\n // 0.6.3: `?repo=` picks one repo of a multi-repo mirror ('' = the\n // root); without it the legacy flat fields decide, byte-identical.\n const repoParam = url.searchParams.get('repo')\n let cwd = execution.worktreePath ?? ws.path\n let mainRepo = ws.path\n let baseCommit = execution.baseCommit\n if (repoParam !== null) {\n const entry = execution.repos?.find(r => r.repo === repoParam)\n if (entry === undefined) throw new Error('Error: invalid_input: 该执行没有此仓库的镜像记录')\n cwd = entry.worktreePath\n mainRepo = repoMainPath(ws.path, entry.repo)\n baseCommit = entry.baseCommit\n }\n let result = commit !== null\n ? await options.git.showCommit(cwd, commit)\n : filePath !== null ? await options.git.showPathDiff(cwd, filePath, baseCommit) : undefined\n // Fallback: the worktree may be gone — commits and committed\n // ranges still resolve in the main repo.\n if (result === undefined && cwd !== mainRepo) {\n result = commit !== null\n ? await options.git.showCommit(mainRepo, commit)\n : filePath !== null && baseCommit !== undefined\n ? await options.git.showPathDiff(mainRepo, filePath, baseCommit)\n : undefined\n }\n if (result === undefined) {\n throw new Error('Error: invalid_input: 无法获取 diff(git 报错、对象不存在,或仅存于已删除的 worktree 且无基线)')\n }\n json(res, { ok: true, value: { diff: result.text, truncated: result.truncated } })\n } catch (error) {\n const f = toFail(error)\n json(res, f.res, f.status)\n }\n return\n }\n\n // Templates listing (0.4.0).\n if (pathname === `${ROUTE_PREFIX}/templates`) {\n if (options.templates === undefined) {\n const f = fail('invalid_input', 'template store unavailable')\n json(res, f.res, 501)\n return\n }\n json(res, { ok: true, value: { templates: await options.templates.list() } })\n return\n }\n\n // Board settings (0.5.0): absent fields follow factory defaults.\n if (pathname === `${ROUTE_PREFIX}/settings`) {\n await store.load()\n json(res, { ok: true, value: store.snapshot().settings ?? {} })\n return\n }\n\n // Prompt completions (0.5.5; dynamically discovers skills & commands).\n if (pathname === `${ROUTE_PREFIX}/prompt-completions`) {\n const completions = await options.promptCompletions?.().catch(() => undefined)\n json(res, {\n ok: true,\n value: {\n commands: completions?.commands ?? [],\n skills: completions?.skills ?? [],\n },\n })\n return\n }\n\n // Model catalog (0.5.5; dynamically discovers models & presets from runtime).\n if (pathname === `${ROUTE_PREFIX}/model-catalog`) {\n const catalog = await options.modelCatalog?.().catch(() => undefined)\n json(res, {\n ok: true,\n value: {\n models: catalog?.models ?? [],\n presets: catalog?.presets ?? [],\n ...(catalog?.defaultPresetId !== undefined ? { defaultPresetId: catalog.defaultPresetId } : {}),\n },\n })\n return\n }\n\n const taskMatch = pathname.match(TASK_RE)\n if (taskMatch !== null) {\n const task = store.get(taskMatch[1]!)\n if (task === undefined) { const f = fail('not_found', 'no such task'); json(res, f.res, f.status); return }\n json(res, { ok: true, value: task })\n return\n }\n res.writeHead(404)\n res.end()\n return\n }\n\n if (req.method !== 'POST') {\n res.writeHead(405, { allow: 'GET, POST' })\n res.end()\n return\n }\n // Content-addressed image upload. The custom header makes this a\n // non-simple cross-origin request, preserving the JSON routes' CSRF fence.\n if (pathname === `${ROUTE_PREFIX}/assets`) {\n await options.ready?.()\n if (options.assets === undefined) {\n const f = fail('invalid_input', 'image attachments unavailable')\n json(res, f.res, 501)\n return\n }\n if (req.headers['x-dsh-taskboard-upload'] !== '1') {\n const f = fail('forbidden', 'missing upload header')\n json(res, f.res, 403)\n return\n }\n const declaredMime = String(req.headers['content-type'] ?? '').split(';', 1)[0]!.trim().toLowerCase()\n try {\n const bytes = await readBytes(req, MAX_ASSET_BYTES)\n await options.assets.cleanup(JSON.stringify(store.snapshot()))\n const asset = await options.assets.put(bytes, declaredMime)\n json(res, { ok: true, value: asset }, 201)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n const status = message.includes('1..') ? 413 : message.includes('quota') ? 507 : 400\n const f = fail('invalid_input', message)\n json(res, f.res, status)\n }\n return\n }\n // CSRF fence: cross-site simple requests cannot set application/json.\n const contentType = req.headers['content-type'] ?? ''\n if (!contentType.toLowerCase().startsWith('application/json')) {\n const f = fail('invalid_input', 'content-type must be application/json')\n json(res, f.res, 415)\n return\n }\n let body: Record<string, unknown> | null\n try {\n body = await readBody(req)\n } catch {\n const f = fail('invalid_input', `request body exceeds ${MAX_BODY_BYTES} bytes`)\n json(res, f.res, 413)\n return\n }\n if (body === null) {\n const f = fail('invalid_input', 'body is not a JSON object')\n json(res, f.res, 400)\n return\n }\n\n // Storage location is bootstrap metadata outside the ledger. Keep it on\n // separate routes so JSON imports and whole-ledger replacement cannot\n // redirect host filesystem writes.\n if (pathname === `${ROUTE_PREFIX}/storage/check` || pathname === `${ROUTE_PREFIX}/storage/migrate`) {\n if (options.storage === undefined) {\n const f = fail('invalid_input', 'storage configuration unavailable')\n json(res, f.res, 501)\n return\n }\n try {\n const directory = str(body, 'directory') ?? ''\n const value = pathname.endsWith('/check')\n ? await options.storage.check(directory)\n : await options.storage.migrate(directory)\n json(res, { ok: true, value })\n } catch (error) {\n const f = fail('invalid_input', error instanceof Error ? error.message : String(error))\n json(res, f.res, f.status)\n }\n return\n }\n await options.ready?.()\n\n // ------------------------------------------------- POST /tasks (create)\n if (pathname === `${ROUTE_PREFIX}/tasks`) {\n try {\n const title = normalizeTitle(str(body, 'title') ?? '')\n const workspaceId = str(body, 'workspaceId') ?? ''\n if (workspaces.get(workspaceId) === undefined) throw new Error('Error: not_found: unknown workspace')\n const urgency = asUrgency(str(body, 'urgency') ?? '')\n const status = str(body, 'status') === null ? 'todo' as const : asStatus(str(body, 'status')!)\n if (status !== 'backlog' && status !== 'todo') {\n throw new Error('Error: invalid_transition: a new task must start as backlog or todo')\n }\n const execution = normalizeExecution((body.execution as { mode?: string; cron?: string } | undefined) ?? {}, options.now())\n const model = body.model === undefined ? undefined : checkModel(body.model, options.modelProviders)\n const isolationRaw = str(body, 'isolation')\n // 0.5.0: an omitted isolation is MATERIALIZED from the board\n // setting (看板设置) at creation, so later setting changes never\n // rewrite existing tasks.\n const isolation = isolationRaw === null ? defaultIsolationOf(store.snapshot().settings) : asIsolation(isolationRaw)\n const presetId = normalizePresetId(str(body, 'presetId'))\n const permissionRaw = str(body, 'permission')\n const permission = permissionRaw === null ? defaultPermissionOf(store.snapshot().settings) : asPermission(permissionRaw)\n let checklist: TaskRecord['checklist'] = undefined\n if (body.checklist !== undefined) {\n if (!Array.isArray(body.checklist) || body.checklist.some(c => typeof c !== 'string')) {\n throw new Error('Error: invalid_input: checklist must be an array of strings')\n }\n const texts = (body.checklist as string[]).map(c => c.trim()).filter(c => c.length > 0)\n if (texts.length > 0) checklist = checklistFromTexts(texts)\n }\n const now = options.now()\n const task: TaskRecord = {\n id: newTaskId(),\n title,\n description: (str(body, 'description') ?? '').trim(),\n prompt: normalizePrompt(str(body, 'prompt') ?? undefined),\n workspaceId,\n urgency,\n status,\n blocked: false,\n execution,\n model,\n isolation,\n ...(presetId !== undefined ? { presetId } : {}),\n permission,\n ...(checklist !== undefined ? { checklist } : {}),\n version: 1,\n createdAt: now,\n updatedAt: now,\n createdBy: { kind: 'user' },\n updatedBy: { kind: 'user' },\n comments: [],\n executions: [],\n }\n await store.mutate('task-created', ledger => {\n ledger.tasks.push(task)\n return [task]\n })\n json(res, { ok: true, value: summarize(task) }, 201)\n } catch (error) {\n const f = toFail(error)\n json(res, f.res, f.status)\n }\n return\n }\n\n // ------------------------------------------- POST /tasks/:id/{action}\n // (\\w+ after the id would not match hyphenated actions like\n // worktree-remove, hence the explicit class in the hoisted pattern.)\n const actionMatch = pathname.match(TASK_ACTION_RE)\n if (actionMatch !== null) {\n const id = actionMatch[1]!\n const action = actionMatch[2]!\n try {\n const task = store.get(id)\n if (task === undefined) throw new Error('Error: not_found: no such task')\n if (action === 'archive-sessions') {\n if (task.trashedAt !== undefined || task.status !== 'archived') throw new Error('Error: invalid_transition: only archived live tasks can retry session archiving')\n const result = await archiveTaskSessions(task, workspaces.archiveSession)\n json(res, { ok: true, value: result })\n return\n }\n if (action === 'update') {\n const ifVersion = num(body, 'ifVersion')\n if (ifVersion === undefined || ifVersion === null) throw new Error('Error: version_conflict: ifVersion required')\n // R1: version guard + write inside the mutation, on the fresh draft.\n let next: TaskRecord | undefined\n await store.mutate('task-updated', ledger => {\n const { index, task } = liveTaskAt(ledger, id)\n if (ifVersion !== task.version) throw new Error(`Error: version_conflict: stale version ${ifVersion} (current ${task.version})`)\n if (task.status === 'archived') throw new Error('Error: invalid_transition: archived tasks are immutable')\n next = structuredClone(task)\n const title = str(body, 'title')\n if (title !== null) next.title = normalizeTitle(title)\n const description = str(body, 'description')\n if (description !== null) next.description = description.trim()\n const prompt = str(body, 'prompt')\n if (prompt !== null) next.prompt = normalizePrompt(prompt)\n const urgency = str(body, 'urgency')\n if (urgency !== null) next.urgency = asUrgency(urgency)\n // GUI-only rebind to another project; validated against the workspace registry.\n const workspaceId = str(body, 'workspaceId')\n if (workspaceId !== null) {\n if (workspaces.get(workspaceId) === undefined) throw new Error('Error: not_found: unknown workspace')\n next.workspaceId = workspaceId\n }\n if (typeof body.blocked === 'boolean') next.blocked = body.blocked\n // The GUI (task owner surface) may edit model/execution; null clears the model.\n if (body.execution !== undefined) next.execution = normalizeExecution(body.execution as { mode?: string; cron?: string }, options.now())\n if (body.model === null) next.model = undefined\n else if (body.model !== undefined) next.model = checkModel(body.model, options.modelProviders)\n // Isolation may change only before the first execution (分支与基线\n // 取决于该选择 — plan §3.1: 执行开始后锁定).\n const isolationRaw = str(body, 'isolation')\n if (isolationRaw !== null) {\n if (task.executions.length > 0 || task.status === 'in_progress') {\n throw new Error('Error: invalid_input: isolation 已锁定(任务已有执行记录),不可修改')\n }\n next.isolation = asIsolation(isolationRaw)\n }\n // Preset may change any time: each run composes fresh.\n if (body.presetId === null) delete next.presetId\n else if (body.presetId !== undefined) next.presetId = normalizePresetId(str(body, 'presetId'))!\n // Permission (0.5.5): 'workspace-write' | 'read-only' | 'danger-full-access'\n if (body.permission === null) delete next.permission\n else if (body.permission !== undefined) next.permission = asPermission(body.permission)\n // Checklist (0.4.0): the GUI replaces the whole list; null clears.\n if (body.checklist === null) delete next.checklist\n else if (body.checklist !== undefined) {\n const items = normalizeChecklist(body.checklist)\n if (items.length > 0) next.checklist = items\n else delete next.checklist\n }\n next.version = task.version + 1\n next.updatedAt = options.now()\n next.updatedBy = { kind: 'user' }\n ledger.tasks[index] = next\n return [next]\n })\n json(res, { ok: true, value: summarize(next!) })\n return\n }\n if (action === 'move') {\n const ifVersion = num(body, 'ifVersion')\n const status = str(body, 'status') ?? ''\n const archiveSessions = body.archiveSessions === true\n if (ifVersion === undefined || ifVersion === null) throw new Error('Error: version_conflict: ifVersion required')\n const to = asStatus(status)\n let next: TaskRecord | undefined\n let beforeTask: TaskRecord | undefined\n await store.mutate('task-moved', ledger => {\n const { index, task } = liveTaskAt(ledger, id)\n if (ifVersion !== task.version) throw new Error(`Error: version_conflict: stale version ${ifVersion} (current ${task.version})`)\n if (!canTransition(task.status, to)) throw new Error(`Error: invalid_transition: illegal transition ${task.status} → ${to}`)\n beforeTask = task\n next = structuredClone(task)\n next.status = to\n next.version = task.version + 1\n next.updatedAt = options.now()\n next.updatedBy = { kind: 'user' }\n if (task.status === 'todo' && to === 'in_progress') next.blocked = false\n // A user move records no holder; leaving in_progress releases any hold.\n syncClaim(next, to, options.now())\n ledger.tasks[index] = next\n return [next]\n })\n const sessionArchive = to === 'archived' && archiveSessions\n ? await archiveTaskSessions(beforeTask ?? next!, workspaces.archiveSession)\n : undefined\n json(res, { ok: true, value: { ...summarize(next!), ...(sessionArchive !== undefined ? { sessionArchive } : {}) } })\n return\n }\n if (action === 'reject') {\n // Card quick-reject: back to todo + optional user comment in one\n // atomic mutation (a failed move never strands an orphan comment).\n const ifVersion = num(body, 'ifVersion')\n if (ifVersion === undefined || ifVersion === null) throw new Error('Error: version_conflict: ifVersion required')\n const commentText = str(body, 'body') ?? ''\n let next: TaskRecord | undefined\n await store.mutate('task-moved', ledger => {\n const { index, task } = liveTaskAt(ledger, id)\n if (ifVersion !== task.version) throw new Error(`Error: version_conflict: stale version ${ifVersion} (current ${task.version})`)\n if (!canTransition(task.status, 'todo')) throw new Error(`Error: invalid_transition: illegal transition ${task.status} → todo`)\n next = structuredClone(task)\n next.status = 'todo'\n next.version = task.version + 1\n next.updatedAt = options.now()\n next.updatedBy = { kind: 'user' }\n syncClaim(next, 'todo', options.now())\n if (commentText.trim().length > 0) {\n next.comments.push({ id: newCommentId(), body: normalizeBody(commentText), version: 1, createdAt: options.now() })\n }\n ledger.tasks[index] = next\n return [next]\n })\n json(res, { ok: true, value: summarize(next!) })\n return\n }\n if (action === 'comment') {\n const bodyText = str(body, 'body') ?? ''\n const comment = { id: newCommentId(), body: normalizeBody(bodyText), version: 1, createdAt: options.now() }\n await store.mutate('comment-added', ledger => {\n const { index, task } = liveTaskAt(ledger, id)\n if (task.status === 'archived') throw new Error('Error: invalid_transition: archived tasks are immutable')\n const next = structuredClone(task)\n next.comments.push(comment)\n next.version = task.version + 1\n next.updatedAt = options.now()\n ledger.tasks[index] = next\n return [next]\n })\n json(res, { ok: true, value: comment }, 201)\n return\n }\n if (action === 'delete') {\n const purge = body.purge === true\n if (purge) {\n if (task.trashedAt === undefined) throw new Error('Error: invalid_input: purge requires a trashed task (soft-delete first)')\n // Worktree safety before purge (plan §3.3, 0.3.1): refuse while\n // uncommitted work remains; otherwise clean the worktree and\n // the task branch along with the ledger entry.\n if (options.git !== undefined) {\n const ws = workspaces.get(task.workspaceId)\n if (ws !== undefined) {\n const path = worktreePathOf(ws.path, id)\n if (!insideWorktreeScope(ws.path, path)) {\n throw new Error('Error: invalid_input: 非法的清除路径(不在任务工作目录范围内)')\n }\n try {\n // 0.6.3: the whole mirror (every repo worktree) goes\n // through the aggregated children-first removal — one\n // dirty repo refuses BEFORE anything is deleted.\n await removeMirror(\n { git: options.git, scanner: options.scanner ?? createRepoScanner() },\n { workspacePath: ws.path, taskId: id },\n )\n // Leftovers unknown to git ('unregistered' worktrees, or a\n // plain mirror dir when the workspace root is no repo).\n await rm(path, { recursive: true, force: true })\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n // Structured classification (review P2): git tags its dirty\n // rejections with a code; the keyword stays as a fallback.\n const dirty = (error as { code?: string }).code === 'dirty-worktree'\n || (error as { code?: string }).code === 'dirty-mirror'\n || message.includes('未提交修改')\n if (dirty) {\n throw new Error(`Error: invalid_input: ${message};请先处理这些改动(提交、续跑或手动保存)再物理清除任务`)\n }\n throw new Error(`Error: invalid_input: ${message}`)\n }\n // Branches die with the task (best effort, per repo).\n const branchTargets: Array<{ repo: string; branch: string }> = []\n if (task.branches !== undefined) {\n for (const [repo, branch] of Object.entries(task.branches)) branchTargets.push({ repo, branch })\n }\n if (task.branch !== undefined && !branchTargets.some(t => t.repo === '')) branchTargets.push({ repo: '', branch: task.branch })\n for (const target of branchTargets) {\n try {\n await options.git.deleteBranch(repoMainPath(ws.path, target.repo), target.branch)\n } catch { /* best effort: the branch may outlive the task */ }\n }\n }\n }\n await store.mutate('task-deleted', ledger => {\n ledger.tasks = ledger.tasks.filter(t => t.id !== id)\n return []\n })\n json(res, { ok: true, value: { purged: true } })\n return\n }\n const ifVersion = num(body, 'ifVersion')\n if (ifVersion === undefined || ifVersion === null) throw new Error('Error: version_conflict: ifVersion required')\n await store.mutate('task-deleted', ledger => {\n const { index, task } = liveTaskAt(ledger, id)\n if (ifVersion !== task.version) throw new Error(`Error: version_conflict: stale version ${ifVersion} (current ${task.version})`)\n // S5: a running execution keeps writing to the task — refuse the\n // soft-delete until it is cancelled or settled. T8: clear residue.\n if (task.executions.some(e => e.outcome === 'running')) {\n throw new Error('Error: invalid_input: 任务有正在运行的执行,请先取消或等它结束再删除')\n }\n const next = structuredClone(task)\n next.trashedAt = options.now()\n next.version = task.version + 1\n delete next.claimedBy\n delete next.claimedAt\n next.blocked = false\n ledger.tasks[index] = next\n return [next]\n })\n json(res, { ok: true, value: { trashed: true } })\n return\n }\n if (action === 'run') {\n if (options.run === undefined) {\n const f = fail('invalid_input', 'execution service unavailable')\n json(res, f.res, 501)\n return\n }\n // `reuse: true` = 续跑: keep a live worktree/branch as-is instead\n // of resetting to a fresh baseline (0.3.1).\n const runOptions = body.reuse === true ? { reuseWorktree: true } : undefined\n const result = await options.run(id, runOptions)\n if (result.ok) json(res, { ok: true, value: result }, 202)\n else {\n const f = fail('invalid_input', result.error)\n json(res, f.res, f.status)\n }\n return\n }\n if (action === 'cancel') {\n if (options.cancel === undefined) {\n const f = fail('invalid_input', 'execution service unavailable')\n json(res, f.res, 501)\n return\n }\n const result = await options.cancel(id)\n if (result.ok) json(res, { ok: true, value: { cancelled: true, executionId: result.executionId } }, 202)\n else {\n const f = fail('invalid_input', result.error)\n json(res, f.res, f.status)\n }\n return\n }\n if (action === 'merge') {\n // ⇥ 合并 (detail page, user-only): merge the task branch(es) into\n // the main worktree(s) with --no-ff; conflicts are reported\n // verbatim. 0.6.3: multi-repo mirror tasks merge PER REPO —\n // sequentially, a failed repo never blocking the others.\n if (options.git === undefined) {\n const f = fail('invalid_input', 'git integration unavailable')\n json(res, f.res, 501)\n return\n }\n if (task.status === 'in_progress') throw new Error('Error: invalid_input: 任务执行中,不能合并')\n if (task.executions.some(e => e.outcome === 'running')) throw new Error('Error: invalid_input: 任务执行中,不能合并')\n const ws = workspaces.get(task.workspaceId)\n if (ws === undefined) throw new Error('Error: not_found: unknown workspace')\n const targets: Array<{ repo: string; branch: string }> = []\n if (task.branches !== undefined) {\n for (const [repo, branch] of Object.entries(task.branches)) targets.push({ repo, branch })\n }\n if (task.branch !== undefined && !targets.some(t => t.repo === '')) targets.unshift({ repo: '', branch: task.branch })\n if (targets.length === 0) throw new Error('Error: invalid_input: 该任务还没有 worktree 分支(未隔离执行过)')\n const multi = task.branches !== undefined\n const results: MergeRepoResult[] = []\n for (const target of targets) {\n if (!isValidRelRepoPath(target.repo)) throw new Error('Error: invalid_input: 非法的仓库路径')\n const repoRoot = repoMainPath(ws.path, target.repo)\n // No-op detection (0.3.1): a branch with no commits over HEAD\n // merges as \"already up to date\" — report that instead of landing\n // a bogus 已合并 comment.\n let noop = false\n try {\n noop = await options.git.isAncestor(repoRoot, target.branch)\n } catch { /* fail-soft: proceed to the real merge */ }\n if (noop) {\n results.push({ repo: target.repo, branch: target.branch, outcome: 'noop' })\n continue\n }\n try {\n // 0.6.3 review fix: parallel repos are self-governing — their\n // content AND their gitlink entries in the ROOT main checkout\n // (e.g. a container repo tracking `dsh-taskboard` as an embedded\n // repo) don't gate the root repo's merge. Child repos merge with\n // no extra exemption (their own .dsh-worktrees rule suffices).\n const exempt = target.repo === ''\n ? await (options.scanner ?? createRepoScanner()).findNestedRepos(ws.path).then(rs => rs.map(r => r.relPath))\n : undefined\n await options.git.merge(repoRoot, target.branch, exempt)\n results.push({ repo: target.repo, branch: target.branch, outcome: 'merged' })\n } catch (error) {\n results.push({\n repo: target.repo,\n branch: target.branch,\n outcome: 'failed',\n error: error instanceof Error ? error.message : String(error),\n })\n }\n }\n // R1: the git merges above are slow — re-find the FRESH task inside\n // the mutation so a concurrent comment is never overwritten.\n const pushComment = (body: string, system?: { key: string; params?: Record<string, string>; rows?: SystemCommentRow[] }): Promise<void> =>\n store.mutate('comment-added', ledger => {\n const { index, task: fresh } = liveTaskAt(ledger, id)\n const next = structuredClone(fresh)\n next.comments.push({\n id: newCommentId(),\n body: normalizeBody(body),\n ...(system !== undefined ? {\n systemKey: system.key,\n ...(system.params !== undefined ? { systemParams: system.params } : {}),\n ...(system.rows !== undefined ? { systemRows: system.rows } : {}),\n } : {}),\n version: 1,\n createdAt: options.now(),\n })\n next.version = fresh.version + 1\n next.updatedAt = options.now()\n ledger.tasks[index] = next\n return [next]\n }).then(() => undefined)\n if (!multi) {\n // Legacy single-repo shape, byte-identical to 0.3.x–0.6.x.\n const root = results.find(r => r.repo === '')\n if (root === undefined) throw new Error('Error: invalid_input: 该任务还没有 worktree 分支(未隔离执行过)')\n if (root.outcome === 'noop') {\n json(res, { ok: true, value: { merged: false, noop: true, branch: root.branch } })\n return\n }\n if (root.outcome === 'failed') {\n throw new Error(`Error: invalid_input: ${root.error ?? '合并失败'}`)\n }\n await pushComment(`[系统] 分支 ${root.branch} 已合并到主工作区(--no-ff)。`, { key: 'sys.mergeSingle', params: { branch: root.branch } })\n json(res, { ok: true, value: { merged: true, branch: root.branch } })\n return\n }\n const labelOf = (repo: string): string => repo === '' ? '根仓库' : repo\n const mergedCount = results.filter(r => r.outcome === 'merged').length\n const failedCount = results.filter(r => r.outcome === 'failed').length\n const summary = results\n .map(r => r.outcome === 'merged'\n ? `${labelOf(r.repo)} ✓ 已合并`\n : r.outcome === 'noop' ? `${labelOf(r.repo)} ⟲ 无新提交` : `${labelOf(r.repo)} ✗ ${(r.error ?? '合并失败').slice(0, 150)}`)\n .join(';')\n await pushComment(`[系统] 分支已按仓库合并(--no-ff):${summary}`, {\n key: 'sys.mergeMulti',\n rows: results.map(r => ({ repo: r.repo, outcome: r.outcome, ...(r.error !== undefined ? { error: r.error.slice(0, 150) } : {}) })),\n })\n json(res, {\n ok: true,\n value: {\n merged: mergedCount > 0,\n ...(mergedCount === 0 && failedCount === 0 ? { noop: true } : {}),\n results,\n },\n })\n return\n }\n if (action === 'worktree-remove') {\n // 🗑 删除 worktree (detail page): refuses uncommitted changes;\n // optionally deletes the task branch after the worktree is gone.\n if (options.git === undefined) {\n const f = fail('invalid_input', 'git integration unavailable')\n json(res, f.res, 501)\n return\n }\n if (task.executions.some(e => e.outcome === 'running')) throw new Error('Error: invalid_input: 任务执行中,不能删除 worktree')\n const ws = workspaces.get(task.workspaceId)\n if (ws === undefined) throw new Error('Error: not_found: unknown workspace')\n const path = worktreePathOf(ws.path, id)\n if (!insideWorktreeScope(ws.path, path)) {\n throw new Error('Error: invalid_input: 非法的清除路径(不在任务工作目录范围内)')\n }\n try {\n // 0.6.3: aggregated children-first mirror removal (see purge).\n await removeMirror(\n { git: options.git, scanner: options.scanner ?? createRepoScanner() },\n { workspacePath: ws.path, taskId: id },\n )\n await rm(path, { recursive: true, force: true })\n } catch (error) {\n throw new Error(`Error: invalid_input: ${error instanceof Error ? error.message : String(error)}`)\n }\n let branchDeleted = false\n let branchError: string | undefined\n if (body.deleteBranch === true) {\n const branchTargets: Array<{ repo: string; branch: string }> = []\n if (task.branches !== undefined) {\n for (const [repo, branch] of Object.entries(task.branches)) branchTargets.push({ repo, branch })\n }\n if (task.branch !== undefined && !branchTargets.some(t => t.repo === '')) branchTargets.push({ repo: '', branch: task.branch })\n let failures = 0\n for (const target of branchTargets) {\n try {\n await options.git.deleteBranch(repoMainPath(ws.path, target.repo), target.branch)\n } catch (error) {\n failures += 1\n const label = target.repo === '' ? '根仓库' : target.repo\n branchError = `${label}:${error instanceof Error ? error.message : String(error)}`\n }\n }\n branchDeleted = failures === 0\n }\n json(res, { ok: true, value: { removed: true, branchDeleted, ...(branchError !== undefined ? { branchError } : {}) } })\n return\n }\n const f = fail('not_found', `unknown action ${action}`)\n json(res, f.res, f.status)\n } catch (error) {\n const f = toFail(error)\n json(res, f.res, f.status)\n }\n return\n }\n\n // -------------------------------------- POST /worktree-cleanup (⚙ 诊断)\n if (pathname === `${ROUTE_PREFIX}/worktree-cleanup`) {\n try {\n if (options.git === undefined) {\n const f = fail('invalid_input', 'git integration unavailable')\n json(res, f.res, 501)\n return\n }\n const workspaceId = str(body, 'workspaceId') ?? ''\n const taskId = str(body, 'taskId') ?? ''\n const ws = workspaces.get(workspaceId)\n if (ws === undefined) throw new Error('Error: not_found: unknown workspace')\n // Only dirs owned by NO ledger task may be cleaned here; live tasks\n // remove their worktree from the detail page.\n if (store.get(taskId) !== undefined) throw new Error('Error: invalid_input: 任务仍在看板中,请从任务详情页删除其 worktree')\n // worktreePathOf throws on an illegal id charset (R4②); the resolved\n // target must additionally stay inside the plugin's own worktree\n // scope — the taskId is fully attacker-controlled body input (R4③).\n const path = worktreePathOf(ws.path, taskId)\n if (!insideWorktreeScope(ws.path, path)) {\n throw new Error('Error: invalid_input: 非法的清除路径(不在任务工作目录范围内)')\n }\n try {\n // 0.6.3: an orphan mirror may hold several repo worktrees —\n // aggregated children-first removal, then the leftover fs rm\n // (scope-verified above).\n await removeMirror(\n { git: options.git, scanner: options.scanner ?? createRepoScanner() },\n { workspacePath: ws.path, taskId },\n )\n await rm(path, { recursive: true, force: true })\n } catch (error) {\n throw new Error(`Error: invalid_input: ${error instanceof Error ? error.message : String(error)}`)\n }\n json(res, { ok: true, value: { cleaned: true, path } })\n } catch (error) {\n const f = toFail(error)\n json(res, f.res, f.status)\n }\n return\n }\n\n // ---------------------------------------------- POST /import/preview\n // (0.4.0) Dry-run: classify every task in the uploaded ledger file\n // against the live one; nothing is written.\n if (pathname === `${ROUTE_PREFIX}/import/preview`) {\n try {\n const known = new Set(store.snapshot().tasks.map(t => t.id))\n const plan = validateLedgerImport(body, known, options.now())\n json(res, {\n ok: true,\n value: {\n plan: {\n create: plan.create.map(t => ({ id: t.id, title: t.title, status: t.status })),\n overwrite: plan.overwrite.map(t => ({ id: t.id, title: t.title, status: t.status })),\n invalid: plan.invalid,\n },\n },\n })\n } catch (error) {\n const f = toFail(error)\n json(res, f.res, f.status)\n }\n return\n }\n\n // ------------------------------------------------------ POST /import\n // (0.4.0) Commit an import. mode=merge upserts (create + overwrite by\n // id); mode=replace swaps the WHOLE ledger (invalid entries dropped)\n // after writing a timestamped backup of the current one.\n if (pathname === `${ROUTE_PREFIX}/import`) {\n try {\n const mode = str(body, 'mode') === 'replace' ? 'replace' as const : 'merge' as const\n const raw = body.ledger\n const known = new Set(store.snapshot().tasks.map(t => t.id))\n const plan = validateLedgerImport(raw, known, options.now())\n const imported = [...plan.create, ...plan.overwrite]\n if (mode === 'replace' && imported.length === 0) {\n throw new Error('Error: invalid_input: 导入文件没有可导入的任务,已拒绝整册替换')\n }\n let backupFile: string | undefined\n if (mode === 'replace' && store.snapshot().tasks.length > 0) {\n backupFile = await store.backup()\n }\n let replacedTotal: number | undefined\n await store.mutate('ledger-replaced', ledger => {\n if (mode === 'replace') {\n // S6: a whole-ledger swap must not strand live executions — the\n // running sessions keep working with no task to settle into.\n if (ledger.tasks.some(t => t.executions.some(e => e.outcome === 'running'))) {\n throw new Error('Error: invalid_input: 有任务正在执行,不能整册替换(请先取消或等待结束)')\n }\n replacedTotal = ledger.tasks.length\n ledger.tasks = structuredClone(imported)\n // Replace is a whole-ledger swap (0.5.0): board settings ride\n // along when the file carries them; merge keeps the live ones.\n if (plan.settings !== undefined) ledger.settings = structuredClone(plan.settings)\n else delete ledger.settings\n return ledger.tasks\n }\n const byId = new Map(ledger.tasks.map(t => [t.id, t]))\n for (const task of imported) {\n // S6: merging over a task whose execution is live would orphan\n // that run the same way — refuse the overwrite.\n const existing = byId.get(task.id)\n if (existing !== undefined && existing.executions.some(e => e.outcome === 'running')) {\n throw new Error(`Error: invalid_input: 任务 ${task.id} 正在执行,不能被导入覆盖`)\n }\n byId.set(task.id, structuredClone(task))\n }\n ledger.tasks = [...byId.values()]\n return structuredClone(imported)\n })\n json(res, {\n ok: true,\n value: {\n mode,\n created: plan.create.length,\n overwritten: plan.overwrite.length,\n ...(mode === 'replace' ? { replacedTotal } : {}),\n ...(backupFile !== undefined ? { backupFile } : {}),\n },\n })\n } catch (error) {\n const f = toFail(error)\n json(res, f.res, f.status)\n }\n return\n }\n\n // ------------------------------------------- POST /templates (+delete)\n if (pathname === `${ROUTE_PREFIX}/templates` || pathname === `${ROUTE_PREFIX}/templates/delete`) {\n try {\n if (options.templates === undefined) {\n const f = fail('invalid_input', 'template store unavailable')\n json(res, f.res, 501)\n return\n }\n if (pathname.endsWith('/delete')) {\n const id = str(body, 'id') ?? ''\n if (id.length === 0) throw new Error('Error: invalid_input: id required')\n const deleted = await options.templates.remove(id)\n json(res, { ok: true, value: { deleted } })\n return\n }\n const name = str(body, 'name') ?? ''\n if (name.trim().length === 0) throw new Error('Error: invalid_input: name required')\n const template = await options.templates.upsert({\n id: str(body, 'id') ?? undefined,\n name,\n task: normalizeTemplateSpec(body.task, options.now()),\n })\n json(res, { ok: true, value: template }, 201)\n } catch (error) {\n const f = toFail(error)\n json(res, f.res, f.status)\n }\n return\n }\n\n // ------------------------------------------ POST /settings/update\n // (0.5.0) Whole-object replace semantics: omitted fields fall back to\n // their factory defaults. Affects only tasks created AFTER the change.\n if (pathname === `${ROUTE_PREFIX}/settings/update`) {\n try {\n const next = asBoardSettings(body)\n await store.mutate('settings-updated', ledger => {\n ledger.settings = next\n return []\n })\n json(res, { ok: true, value: next })\n } catch (error) {\n const f = toFail(error)\n json(res, f.res, f.status)\n }\n return\n }\n\n res.writeHead(404)\n res.end()\n } catch (error) {\n const f = fail('internal', error instanceof Error ? error.message : String(error))\n json(res, f.res, f.status)\n }\n }\n\n const sse = (req: IncomingMessage, res: ServerResponse): void => {\n res.writeHead(200, {\n 'content-type': 'text/event-stream; charset=utf-8',\n 'cache-control': 'no-cache',\n connection: 'keep-alive',\n })\n res.write('retry: 2000\\n\\n')\n // Baseline frame: the client reconciles by revision and refetches state on gaps.\n res.write(`event: hello\\ndata: ${JSON.stringify({ revision: store.snapshot().revision })}\\n\\n`)\n subscribers.add(res)\n // S2: a socket that dies between 'close' detection and the next write\n // would emit 'error' on the response — unhandled, that escalates to an\n // uncaughtException. Drop the subscriber instead.\n res.on('error', () => { subscribers.delete(res) })\n if (heartbeat === undefined) {\n heartbeat = setInterval(() => {\n for (const current of subscribers) current.write(': ping\\n\\n')\n }, HEARTBEAT_MS)\n }\n req.on('close', () => {\n subscribers.delete(res)\n if (subscribers.size === 0 && heartbeat !== undefined) {\n clearInterval(heartbeat)\n heartbeat = undefined\n }\n })\n }\n\n const disposers = [\n ctx.webServer.register({ kind: 'prefix', path: ROUTE_PREFIX, handler }),\n ctx.webServer.register({ kind: 'exact', path: SSE_PATH, handler: sse }),\n ]\n return () => {\n unsubscribeBroadcast()\n for (const dispose of disposers) dispose()\n if (heartbeat !== undefined) clearInterval(heartbeat)\n for (const res of subscribers) res.end()\n subscribers.clear()\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"routes.js","names":[],"sources":["../../src/host/routes.ts"],"sourcesContent":["import { archiveTaskSessions } from './archive-sessions.ts'\n/**\n * /dsh-taskboard routes on the shared DSH webserver: a JSON API for the\n * GUI's human operations (create/update/move/comment/delete — actor `user`,\n * the done move IS allowed here) plus an SSE stream mirroring every\n * committed ledger mutation.\n *\n * All domain validation goes through the shared protocol pure functions; the\n * route layer only maps transport to envelope.\n *\n * @module dsh-taskboard/host/routes\n */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { readdir, rm } from 'node:fs/promises'\nimport { join, resolve, sep } from 'node:path'\nimport type { Context } from '@deepseek-ai/cordis'\n// Type-only: pulls the webServer Context merge (ctx.webServer).\nimport type {} from '@deepseek-ai/dsh-host-webserver'\nimport {\n asBoardSettings,\n asIsolation,\n asPermission,\n asStatus,\n asUrgency,\n canTransition,\n checklistFromTexts,\n defaultIsolationOf,\n defaultPermissionOf,\n isValidRelRepoPath,\n newCommentId,\n newTaskId,\n normalizeBody,\n normalizeChecklist,\n normalizeExecution,\n normalizeModel,\n normalizePrompt,\n normalizeTitle,\n summarize,\n syncClaim,\n validateLedgerImport,\n type TaskLedger,\n type TaskModel,\n type TaskRecord,\n type SystemCommentRow,\n} from '../shared/protocol.ts'\nimport { WORKTREE_DIR, worktreePathOf, type GitFace } from './git.ts'\nimport { removeMirror, repoMainPath } from './isolation.ts'\nimport { createRepoScanner, type RepoScanner } from './repos.ts'\nimport { activeHostLocale } from './locale.ts'\nimport type { CatalogModelItem, CatalogPresetItem, MergeRepoResult, TaskTemplate } from '../shared/api.ts'\nimport type { TemplateStore } from './templates.ts'\nimport { MAX_ASSET_BYTES, type AssetStore } from './assets.ts'\nimport type { StorageCoordinator } from './storage.ts'\nimport { ROUTE_PREFIX, SSE_PATH, type ApiFail, type ApiResult } from '../shared/api.ts'\nimport type { TaskStore } from './store.ts'\nimport { ERR, ToolError } from './tools.ts'\nimport type { WorkspaceFace } from './tools.ts'\n\n/** Heartbeat cadence for the SSE stream. */\nconst HEARTBEAT_MS = 20_000\n\n/** Max accepted JSON body bytes (S8: unbounded buffering is a local OOM vector). */\nconst MAX_BODY_BYTES = 5 * 1024 * 1024\n\n/** Route shapes (T2: compiled once at module load, not on every request). */\nconst TASK_DIFF_RE = new RegExp(`^${ROUTE_PREFIX}/tasks/([^/]+)/diff$`)\nconst TASK_RE = new RegExp(`^${ROUTE_PREFIX}/tasks/([^/]+)$`)\nconst TASK_ACTION_RE = new RegExp(`^${ROUTE_PREFIX}/tasks/([^/]+)/([\\\\w-]+)$`)\nconst ASSET_RE = new RegExp(`^${ROUTE_PREFIX}/assets/([a-f0-9]{64}\\\\.(?:png|jpg|gif|webp))$`)\n\n/** How long a workspace git-detection result stays cached (fail-soft). */\nconst GIT_DETECT_TTL_MS = 60_000\n\n/** The workspaces face routes need (same narrow shape as tools). */\nexport type RoutesWorkspaceFace = WorkspaceFace\n\n/** Options. */\nexport interface TaskboardRoutesOptions {\n store: TaskStore\n workspaces: RoutesWorkspaceFace\n now: () => number\n /** Manual-run hook (the execution service); absent → 501. Options carry `reuseWorktree` (续跑). */\n run?: (taskId: string, options?: { reuseWorktree?: boolean }) => Promise<{ ok: true; executionId: string; sessionId: string } | { ok: false; error: string }>\n /** Cancel hook (the execution service); absent → 501. */\n cancel?: (taskId: string) => Promise<{ ok: true; executionId: string } | { ok: false; error: string }>\n /**\n * Registered model provider routes (from the host llm runtime), for\n * advisory validation of pinned models; undefined = runtime unavailable.\n */\n modelProviders?: () => string[] | undefined\n /** Git face for worktree actions + workspace git detection; absent → 501 on git actions. */\n git?: GitFace\n /** Nested-repo scanner for mirror removal; absent → a default one is built on first use. */\n scanner?: RepoScanner\n /** Task-template store (0.4.0); absent → 501 on template actions. */\n templates?: TemplateStore\n /** Durable image attachment store (0.7.0); absent → attachment routes unavailable. */\n assets?: AssetStore\n /** Configurable data-directory coordinator (0.7.0). */\n storage?: StorageCoordinator\n /** Initial storage/ledger readiness barrier. */\n ready?: () => Promise<void>\n /** Prompt completions face (0.5.5; dynamically discovers skills & commands). */\n promptCompletions?: () => Promise<{\n skills?: Array<{ name: string; description?: string }>\n commands?: Array<{ name: string; description?: string; hint?: string }>\n }>\n /** Model and preset catalog face (0.5.5; dynamically discovers models & presets). */\n modelCatalog?: () => Promise<{\n models?: CatalogModelItem[]\n presets?: CatalogPresetItem[]\n defaultPresetId?: string\n }>\n}\n\n/** Validate a template's task spec (routes-side, unknown → invalid_input). */\nfunction normalizeTemplateSpec(raw: unknown, now: number): TaskTemplate['task'] {\n if (typeof raw !== 'object' || raw === null) throw new Error('Error: invalid_input: task must be an object')\n const e = raw as Record<string, unknown>\n const spec: TaskTemplate['task'] = {}\n const str = (key: string): string | undefined => {\n const v = e[key]\n if (v === undefined) return undefined\n if (typeof v !== 'string') throw new Error(`Error: invalid_input: task.${key} must be a string`)\n return v\n }\n const title = str('title')\n const description = str('description')\n const prompt = str('prompt')\n const urgency = str('urgency')\n const isolation = str('isolation')\n const presetId = str('presetId')\n const permission = str('permission')\n if (title !== undefined) spec.title = normalizeTitle(title)\n if (description !== undefined) spec.description = description\n if (prompt !== undefined) spec.prompt = normalizePrompt(prompt)\n if (urgency !== undefined) spec.urgency = asUrgency(urgency)\n if (isolation !== undefined) spec.isolation = asIsolation(isolation)\n if (presetId !== undefined && presetId.trim().length > 0) spec.presetId = presetId.trim()\n if (permission !== undefined && permission.trim().length > 0) spec.permission = asPermission(permission)\n if (e.execution !== undefined) {\n spec.execution = normalizeExecution(e.execution as { mode?: string; cron?: string; runAt?: unknown }, now)\n }\n if (e.model !== undefined) spec.model = normalizeModel(e.model)\n if (e.checklist !== undefined) {\n if (!Array.isArray(e.checklist) || e.checklist.some(c => typeof c !== 'string')) {\n throw new Error('Error: invalid_input: task.checklist must be an array of strings')\n }\n checklistFromTexts(e.checklist as string[]) // validates count + texts\n spec.checklist = e.checklist as string[]\n }\n return spec\n}\n\n/** Validate a pinned model: structural check always, provider route when known. */\nfunction checkModel(raw: unknown, modelProviders?: () => string[] | undefined): TaskModel {\n const model = normalizeModel(raw)\n const providers = modelProviders?.()\n if (providers !== undefined && !providers.includes(model.provider)) {\n throw new Error(`Error: invalid_input: model provider \"${model.provider}\" has no registered route (available: ${providers.join(', ')})`)\n }\n return model\n}\n\n/** JSON-envelope writer. */\nfunction json(res: ServerResponse, payload: ApiResult<unknown>, status = 200): void {\n const body = JSON.stringify(payload)\n res.writeHead(status, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' })\n res.end(body)\n}\n\n/** Domain failure → envelope + HTTP status. */\nfunction fail(code: ApiFail['error']['code'], message: string): { res: ApiFail; status: number } {\n const status = code === 'invalid_input' || code === 'invalid_transition' ? 400\n : code === 'not_found' ? 404\n : code === 'version_conflict' ? 409\n : code === 'forbidden' ? 403\n : 500\n return { res: { ok: false, error: { code, message } }, status }\n}\n\n/**\n * Read one JSON body (null on parse failure). S8: rejects bodies over\n * MAX_BODY_BYTES by throwing — the local, unauthenticated HTTP surface must\n * not be an unbounded memory sink.\n */\nasync function readBody(req: IncomingMessage): Promise<Record<string, unknown> | null> {\n const chunks: Buffer[] = []\n let total = 0\n for await (const chunk of req) {\n total += (chunk as Buffer).length\n if (total > MAX_BODY_BYTES) throw new Error('body too large')\n chunks.push(chunk as Buffer)\n }\n if (chunks.length === 0) return {}\n try {\n const parsed = JSON.parse(Buffer.concat(chunks).toString('utf8'))\n return typeof parsed === 'object' && parsed !== null ? parsed as Record<string, unknown> : null\n } catch {\n return null\n }\n}\n\n/** Read one bounded binary upload without ever buffering beyond the file cap. */\nasync function readBytes(req: IncomingMessage, limit: number): Promise<Buffer> {\n const chunks: Buffer[] = []\n let total = 0\n for await (const chunk of req) {\n const bytes = chunk as Buffer\n total += bytes.length\n if (total > limit) throw new Error('body too large')\n chunks.push(bytes)\n }\n return Buffer.concat(chunks)\n}\n\n/** String field accessor (null when absent/not a string). */\nfunction str(body: Record<string, unknown>, key: string): string | null {\n const v = body[key]\n return typeof v === 'string' ? v : null\n}\n\n/** Number field accessor (undefined when absent; null when present but not a number). */\nfunction num(body: Record<string, unknown>, key: string): number | undefined | null {\n const v = body[key]\n if (v === undefined) return undefined\n return typeof v === 'number' && Number.isFinite(v) ? v : null\n}\n\n/** Find a live task INSIDE a mutator (R1: guards run on the fresh draft). */\nfunction liveTaskAt(ledger: TaskLedger, id: string): { index: number; task: TaskRecord } {\n const index = ledger.tasks.findIndex(t => t.id === id)\n if (index < 0 || ledger.tasks[index]!.trashedAt !== undefined) throw new Error('Error: not_found: no such task')\n return { index, task: ledger.tasks[index]! }\n}\n\n/** Normalize an agent preset id: trimmed, non-empty; empty string → undefined. */\nfunction normalizePresetId(raw: string | null): string | undefined {\n const t = (raw ?? '').trim()\n return t.length === 0 ? undefined : t\n}\n\n/** Map a thrown domain error to the envelope. */\nfunction toFail(error: unknown): { res: ApiFail; status: number } {\n const message = error instanceof Error ? error.message : String(error)\n // Structured path first (review P2): ToolError carries its code — no need\n // to parse the 'Error: <code>: …' prefix it also renders into the message.\n if (error instanceof ToolError) {\n const mapped = error.code === ERR.workspaceMismatch ? 'forbidden' : error.code\n const known: ApiFail['error']['code'][] = ['invalid_input', 'not_found', 'version_conflict', 'invalid_transition', 'forbidden', 'internal']\n if ((known as string[]).includes(mapped)) {\n return fail(mapped as ApiFail['error']['code'], message.slice(7 + error.code.length + 2))\n }\n }\n const code = message.startsWith('Error: ') ? message.slice(7).split(':')[0] : undefined\n const known2: ApiFail['error']['code'][] = ['invalid_input', 'not_found', 'version_conflict', 'invalid_transition', 'forbidden', 'internal']\n if (code !== undefined && (known2 as string[]).includes(code)) {\n return fail(code as ApiFail['error']['code'], message.slice(7 + code.length + 2))\n }\n if (code === 'workspace_mismatch') return fail('forbidden', message.slice(7 + code.length + 2))\n return fail('invalid_input', message)\n}\n\n/**\n * Register the taskboard routes.\n * @param ctx - context carrying the webServer service.\n * @param options - store + workspaces + clock.\n * @returns the disposer.\n */\nexport function registerTaskboardRoutes(ctx: Context, options: TaskboardRoutesOptions): () => void {\n const { store, workspaces } = options\n const subscribers = new Set<ServerResponse>()\n let heartbeat: NodeJS.Timeout | undefined\n\n /** R4③: a cleanup/purge target must resolve INSIDE <ws>/.dsh-worktrees — string joining alone is never trusted with an rm. */\n const insideWorktreeScope = (wsPath: string, target: string): boolean => {\n const scope = resolve(wsPath, WORKTREE_DIR)\n const resolved = resolve(target)\n return resolved === scope || resolved.startsWith(scope + sep)\n }\n\n const broadcast = (change: { revision: number; kind: string; tasks: readonly TaskRecord[] }): void => {\n const frame = `event: change\\ndata: ${JSON.stringify({ revision: change.revision, kind: change.kind, tasks: change.tasks.map(summarize) })}\\n\\n`\n for (const res of subscribers) res.write(frame)\n }\n const unsubscribeBroadcast = store.subscribe(broadcast)\n\n // Workspace git detection, TTL-cached and fail-soft (false on any error):\n // feeds the create-form isolation toggle and the diagnostics panel.\n const gitCache = new Map<string, { value: boolean; at: number; repoCount?: number }>()\n const gitHinted = new Set<string>()\n\n /** Whether <root>/.gitignore (missing file counts as missing) ignores our worktree dir. */\n const gitignoreMissing = async (path: string): Promise<boolean> => {\n try {\n const { readFile } = await import('node:fs/promises')\n const ignore = await readFile(join(path, '.gitignore'), 'utf8')\n return !ignore.split('\\n').some(l => {\n const t = l.trim().replace(/\\/+$/, '')\n return t === WORKTREE_DIR || t === `/${WORKTREE_DIR}`\n })\n } catch {\n return true // no .gitignore at all (or unreadable) → suggest creating one\n }\n }\n\n // One scanner for every route-side discovery (the injected one or a\n // shared real-IO fallback whose TTL cache actually helps — 0.6.3).\n const sharedScanner: RepoScanner = options.scanner ?? createRepoScanner()\n\n /**\n * Whether the workspace root itself is a git repo (the .gitignore-suggestion\n * gate — a plain container has no repo that could ignore anything).\n */\n const rootIsRepo = async (path: string): Promise<boolean> => {\n try {\n return (await options.git?.detect(path)) === true\n } catch {\n return false // fail-soft\n }\n }\n\n /**\n * Workspace repo facts for the form (0.6.3): `gitAvailable` gates the\n * worktree option, `repoCount` feeds the mirror badge. Availability now\n * covers PARALLEL MULTI-REPO workspaces too: a root repo qualifies as\n * before, and a workspace whose root is NOT a repo still qualifies when\n * the scanner finds nested repos — prepareMirror isolates exactly that\n * container shape (mirror root = plain dir, one worktree per nested repo),\n * so the form must not lock the capability away.\n */\n const workspaceRepos = async (path: string): Promise<{ gitAvailable: boolean; repoCount: number }> => {\n if (options.git === undefined) return { gitAvailable: false, repoCount: 0 }\n const hit = gitCache.get(path)\n if (hit !== undefined && options.now() - hit.at < GIT_DETECT_TTL_MS) {\n return { gitAvailable: hit.value, repoCount: hit.repoCount ?? (hit.value ? 1 : 0) }\n }\n let rootRepo = false\n try {\n rootRepo = await options.git.detect(path)\n } catch { /* fail-soft → false */ }\n // gitignore 建议 (plan §3.2): suggest (never write) ignoring our\n // worktree directory, once per workspace per host run. Root repos only.\n // The line is localized from the DSH locale preference (see host/locale.ts).\n if (rootRepo && !gitHinted.has(path)) {\n gitHinted.add(path)\n if (await gitignoreMissing(path)) {\n const file = `${path}/.gitignore`\n const hint = activeHostLocale(ctx) === 'zh'\n ? `建议在 ${file} 加入一行 ${WORKTREE_DIR}/ 以隐藏任务 worktree 目录(不会自动修改)`\n : `suggests adding one line to ${file}: ${WORKTREE_DIR}/ to hide the task worktree directory (no automatic edits)`\n console.info(`[dsh-taskboard] ${hint}`)\n }\n }\n // The nested scan always runs: repoCount needs it even when the root\n // is a repo (root repo + nested repos = the mirror shape). The scanner's\n // own TTL cache keeps repeated workspace polls cheap.\n let nestedCount = 0\n try {\n nestedCount = (await sharedScanner.findNestedRepos(path)).length\n } catch { /* fail-soft → 0 */ }\n const value = rootRepo || nestedCount > 0\n const repoCount = (rootRepo ? 1 : 0) + nestedCount\n gitCache.set(path, { value, at: options.now(), repoCount })\n return { gitAvailable: value, repoCount }\n }\n\n /** List orphan worktree dirs: entries under <ws>/.dsh-worktrees owned by no ledger task. */\n const listOrphanWorktrees = async (): Promise<Array<{ workspaceId: string; workspacePath: string; taskId: string; path: string }>> => {\n const orphans: Array<{ workspaceId: string; workspacePath: string; taskId: string; path: string }> = []\n const known = new Set(store.snapshot().tasks.map(t => t.id))\n for (const ws of workspaces.list()) {\n let entries: string[] = []\n try {\n const dirents = await readdir(join(ws.path, WORKTREE_DIR), { withFileTypes: true })\n entries = dirents.filter(e => e.isDirectory()).map(e => e.name)\n } catch { /* no worktrees dir → nothing to do */ }\n for (const taskId of entries) {\n if (!known.has(taskId)) orphans.push({ workspaceId: ws.id, workspacePath: ws.path, taskId, path: worktreePathOf(ws.path, taskId) })\n }\n }\n return orphans\n }\n\n /** Git-enabled workspaces whose .gitignore does not cover the worktree dir. */\n const listGitignoreSuggestions = async (): Promise<Array<{ workspaceId: string; workspacePath: string }>> => {\n const suggestions: Array<{ workspaceId: string; workspacePath: string }> = []\n for (const ws of workspaces.list()) {\n // Root repos only: a plain container has no .gitignore any repo reads.\n if (!(await rootIsRepo(ws.path))) continue\n if (await gitignoreMissing(ws.path)) suggestions.push({ workspaceId: ws.id, workspacePath: ws.path })\n }\n return suggestions\n }\n\n const handler = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n const url = new URL(req.url ?? '/', 'http://x')\n const pathname = url.pathname\n\n // ---------------------------------------------------------------- GET\n if (req.method === 'GET') {\n if (pathname === `${ROUTE_PREFIX}/storage`) {\n if (options.storage === undefined) { res.writeHead(501); res.end(); return }\n json(res, { ok: true, value: await options.storage.status() })\n return\n }\n await options.ready?.()\n const assetMatch = pathname.match(ASSET_RE)\n if (assetMatch !== null) {\n const asset = await options.assets?.read(assetMatch[1]!)\n if (asset === undefined) { res.writeHead(404); res.end(); return }\n res.writeHead(200, {\n 'content-type': asset.mime,\n 'content-length': asset.bytes.length,\n 'cache-control': 'public, max-age=31536000, immutable',\n 'x-content-type-options': 'nosniff',\n })\n res.end(asset.bytes)\n return\n }\n if (pathname === `${ROUTE_PREFIX}/state`) {\n await store.load()\n json(res, { ok: true, value: { ...store.snapshot(), capabilities: { archiveSessions: typeof workspaces.archiveSession === 'function' } } })\n return\n }\n if (pathname === `${ROUTE_PREFIX}/workspaces`) {\n const list = workspaces.list()\n const info = await Promise.all(list.map(ws => workspaceRepos(ws.path)))\n json(res, {\n ok: true,\n value: list.map((ws, i) => ({ ...ws, sessionCount: 0, gitAvailable: info[i]!.gitAvailable, repoCount: info[i]!.repoCount })),\n })\n return\n }\n if (pathname === `${ROUTE_PREFIX}/diagnostics`) {\n const ledger = store.snapshot()\n let staleRunning = 0\n for (const t of ledger.tasks) {\n for (const e of t.executions) if (e.outcome === 'running') staleRunning += 1\n }\n json(res, {\n ok: true,\n value: {\n revision: ledger.revision,\n tasks: ledger.tasks.length,\n staleRunning,\n orphanWorktrees: await listOrphanWorktrees(),\n gitIgnoreSuggestions: await listGitignoreSuggestions(),\n },\n })\n return\n }\n // Diff viewer (0.4.0): read-only git show/diff for one execution's\n // commit or changed path. Prefers the live worktree (uncommitted\n // view), falls back to the main repo.\n const diffMatch = pathname.match(TASK_DIFF_RE)\n if (diffMatch !== null) {\n try {\n if (options.git === undefined) {\n const f = fail('invalid_input', 'git integration unavailable')\n json(res, f.res, 501)\n return\n }\n const task = store.get(diffMatch[1]!)\n if (task === undefined) throw new Error('Error: not_found: no such task')\n const execution = task.executions.find(e => e.id === url.searchParams.get('execution'))\n if (execution === undefined) throw new Error('Error: not_found: no such execution')\n const commit = url.searchParams.get('commit')\n const filePath = url.searchParams.get('path')\n const ws = workspaces.get(task.workspaceId)\n if (ws === undefined) throw new Error('Error: not_found: unknown workspace')\n // 0.6.3: `?repo=` picks one repo of a multi-repo mirror ('' = the\n // root); without it the legacy flat fields decide, byte-identical.\n const repoParam = url.searchParams.get('repo')\n let cwd = execution.worktreePath ?? ws.path\n let mainRepo = ws.path\n let baseCommit = execution.baseCommit\n if (repoParam !== null) {\n const entry = execution.repos?.find(r => r.repo === repoParam)\n if (entry === undefined) throw new Error('Error: invalid_input: 该执行没有此仓库的镜像记录')\n cwd = entry.worktreePath\n mainRepo = repoMainPath(ws.path, entry.repo)\n baseCommit = entry.baseCommit\n }\n let result = commit !== null\n ? await options.git.showCommit(cwd, commit)\n : filePath !== null ? await options.git.showPathDiff(cwd, filePath, baseCommit) : undefined\n // Fallback: the worktree may be gone — commits and committed\n // ranges still resolve in the main repo.\n if (result === undefined && cwd !== mainRepo) {\n result = commit !== null\n ? await options.git.showCommit(mainRepo, commit)\n : filePath !== null && baseCommit !== undefined\n ? await options.git.showPathDiff(mainRepo, filePath, baseCommit)\n : undefined\n }\n if (result === undefined) {\n throw new Error('Error: invalid_input: 无法获取 diff(git 报错、对象不存在,或仅存于已删除的 worktree 且无基线)')\n }\n json(res, { ok: true, value: { diff: result.text, truncated: result.truncated } })\n } catch (error) {\n const f = toFail(error)\n json(res, f.res, f.status)\n }\n return\n }\n\n // Templates listing (0.4.0).\n if (pathname === `${ROUTE_PREFIX}/templates`) {\n if (options.templates === undefined) {\n const f = fail('invalid_input', 'template store unavailable')\n json(res, f.res, 501)\n return\n }\n json(res, { ok: true, value: { templates: await options.templates.list() } })\n return\n }\n\n // Board settings (0.5.0): absent fields follow factory defaults.\n if (pathname === `${ROUTE_PREFIX}/settings`) {\n await store.load()\n json(res, { ok: true, value: store.snapshot().settings ?? {} })\n return\n }\n\n // Prompt completions (0.5.5; dynamically discovers skills & commands).\n if (pathname === `${ROUTE_PREFIX}/prompt-completions`) {\n const completions = await options.promptCompletions?.().catch(() => undefined)\n json(res, {\n ok: true,\n value: {\n commands: completions?.commands ?? [],\n skills: completions?.skills ?? [],\n },\n })\n return\n }\n\n // Model catalog (0.5.5; dynamically discovers models & presets from runtime).\n if (pathname === `${ROUTE_PREFIX}/model-catalog`) {\n const catalog = await options.modelCatalog?.().catch(() => undefined)\n json(res, {\n ok: true,\n value: {\n models: catalog?.models ?? [],\n presets: catalog?.presets ?? [],\n ...(catalog?.defaultPresetId !== undefined ? { defaultPresetId: catalog.defaultPresetId } : {}),\n },\n })\n return\n }\n\n const taskMatch = pathname.match(TASK_RE)\n if (taskMatch !== null) {\n const task = store.get(taskMatch[1]!)\n if (task === undefined) { const f = fail('not_found', 'no such task'); json(res, f.res, f.status); return }\n json(res, { ok: true, value: task })\n return\n }\n res.writeHead(404)\n res.end()\n return\n }\n\n if (req.method !== 'POST') {\n res.writeHead(405, { allow: 'GET, POST' })\n res.end()\n return\n }\n // Content-addressed image upload. The custom header makes this a\n // non-simple cross-origin request, preserving the JSON routes' CSRF fence.\n if (pathname === `${ROUTE_PREFIX}/assets`) {\n await options.ready?.()\n if (options.assets === undefined) {\n const f = fail('invalid_input', 'image attachments unavailable')\n json(res, f.res, 501)\n return\n }\n if (req.headers['x-dsh-taskboard-upload'] !== '1') {\n const f = fail('forbidden', 'missing upload header')\n json(res, f.res, 403)\n return\n }\n const declaredMime = String(req.headers['content-type'] ?? '').split(';', 1)[0]!.trim().toLowerCase()\n try {\n const bytes = await readBytes(req, MAX_ASSET_BYTES)\n await options.assets.cleanup(JSON.stringify(store.snapshot()))\n const asset = await options.assets.put(bytes, declaredMime)\n json(res, { ok: true, value: asset }, 201)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n const status = message.includes('1..') ? 413 : message.includes('quota') ? 507 : 400\n const f = fail('invalid_input', message)\n json(res, f.res, status)\n }\n return\n }\n // CSRF fence: cross-site simple requests cannot set application/json.\n const contentType = req.headers['content-type'] ?? ''\n if (!contentType.toLowerCase().startsWith('application/json')) {\n const f = fail('invalid_input', 'content-type must be application/json')\n json(res, f.res, 415)\n return\n }\n let body: Record<string, unknown> | null\n try {\n body = await readBody(req)\n } catch {\n const f = fail('invalid_input', `request body exceeds ${MAX_BODY_BYTES} bytes`)\n json(res, f.res, 413)\n return\n }\n if (body === null) {\n const f = fail('invalid_input', 'body is not a JSON object')\n json(res, f.res, 400)\n return\n }\n\n // Storage location is bootstrap metadata outside the ledger. Keep it on\n // separate routes so JSON imports and whole-ledger replacement cannot\n // redirect host filesystem writes.\n if (pathname === `${ROUTE_PREFIX}/storage/check` || pathname === `${ROUTE_PREFIX}/storage/migrate`) {\n if (options.storage === undefined) {\n const f = fail('invalid_input', 'storage configuration unavailable')\n json(res, f.res, 501)\n return\n }\n try {\n const directory = str(body, 'directory') ?? ''\n const value = pathname.endsWith('/check')\n ? await options.storage.check(directory)\n : await options.storage.migrate(directory)\n json(res, { ok: true, value })\n } catch (error) {\n const f = fail('invalid_input', error instanceof Error ? error.message : String(error))\n json(res, f.res, f.status)\n }\n return\n }\n await options.ready?.()\n\n // ------------------------------------------------- POST /tasks (create)\n if (pathname === `${ROUTE_PREFIX}/tasks`) {\n try {\n const title = normalizeTitle(str(body, 'title') ?? '')\n const workspaceId = str(body, 'workspaceId') ?? ''\n if (workspaces.get(workspaceId) === undefined) throw new Error('Error: not_found: unknown workspace')\n const urgency = asUrgency(str(body, 'urgency') ?? '')\n const status = str(body, 'status') === null ? 'todo' as const : asStatus(str(body, 'status')!)\n if (status !== 'backlog' && status !== 'todo') {\n throw new Error('Error: invalid_transition: a new task must start as backlog or todo')\n }\n const execution = normalizeExecution((body.execution as { mode?: string; cron?: string; runAt?: unknown } | undefined) ?? {}, options.now())\n const model = body.model === undefined ? undefined : checkModel(body.model, options.modelProviders)\n const isolationRaw = str(body, 'isolation')\n // 0.5.0: an omitted isolation is MATERIALIZED from the board\n // setting (看板设置) at creation, so later setting changes never\n // rewrite existing tasks.\n const isolation = isolationRaw === null ? defaultIsolationOf(store.snapshot().settings) : asIsolation(isolationRaw)\n const presetId = normalizePresetId(str(body, 'presetId'))\n const permissionRaw = str(body, 'permission')\n const permission = permissionRaw === null ? defaultPermissionOf(store.snapshot().settings) : asPermission(permissionRaw)\n let checklist: TaskRecord['checklist'] = undefined\n if (body.checklist !== undefined) {\n if (!Array.isArray(body.checklist) || body.checklist.some(c => typeof c !== 'string')) {\n throw new Error('Error: invalid_input: checklist must be an array of strings')\n }\n const texts = (body.checklist as string[]).map(c => c.trim()).filter(c => c.length > 0)\n if (texts.length > 0) checklist = checklistFromTexts(texts)\n }\n const now = options.now()\n const task: TaskRecord = {\n id: newTaskId(),\n title,\n description: (str(body, 'description') ?? '').trim(),\n prompt: normalizePrompt(str(body, 'prompt') ?? undefined),\n workspaceId,\n urgency,\n status,\n blocked: false,\n execution,\n model,\n isolation,\n ...(presetId !== undefined ? { presetId } : {}),\n permission,\n ...(checklist !== undefined ? { checklist } : {}),\n version: 1,\n createdAt: now,\n updatedAt: now,\n createdBy: { kind: 'user' },\n updatedBy: { kind: 'user' },\n comments: [],\n executions: [],\n }\n await store.mutate('task-created', ledger => {\n ledger.tasks.push(task)\n return [task]\n })\n json(res, { ok: true, value: summarize(task) }, 201)\n } catch (error) {\n const f = toFail(error)\n json(res, f.res, f.status)\n }\n return\n }\n\n // ------------------------------------------- POST /tasks/:id/{action}\n // (\\w+ after the id would not match hyphenated actions like\n // worktree-remove, hence the explicit class in the hoisted pattern.)\n const actionMatch = pathname.match(TASK_ACTION_RE)\n if (actionMatch !== null) {\n const id = actionMatch[1]!\n const action = actionMatch[2]!\n try {\n const task = store.get(id)\n if (task === undefined) throw new Error('Error: not_found: no such task')\n if (action === 'archive-sessions') {\n if (task.trashedAt !== undefined || task.status !== 'archived') throw new Error('Error: invalid_transition: only archived live tasks can retry session archiving')\n const result = await archiveTaskSessions(task, workspaces.archiveSession)\n json(res, { ok: true, value: result })\n return\n }\n if (action === 'update') {\n const ifVersion = num(body, 'ifVersion')\n if (ifVersion === undefined || ifVersion === null) throw new Error('Error: version_conflict: ifVersion required')\n // R1: version guard + write inside the mutation, on the fresh draft.\n let next: TaskRecord | undefined\n await store.mutate('task-updated', ledger => {\n const { index, task } = liveTaskAt(ledger, id)\n if (ifVersion !== task.version) throw new Error(`Error: version_conflict: stale version ${ifVersion} (current ${task.version})`)\n if (task.status === 'archived') throw new Error('Error: invalid_transition: archived tasks are immutable')\n next = structuredClone(task)\n const title = str(body, 'title')\n if (title !== null) next.title = normalizeTitle(title)\n const description = str(body, 'description')\n if (description !== null) next.description = description.trim()\n const prompt = str(body, 'prompt')\n if (prompt !== null) next.prompt = normalizePrompt(prompt)\n const urgency = str(body, 'urgency')\n if (urgency !== null) next.urgency = asUrgency(urgency)\n // GUI-only rebind to another project; validated against the workspace registry.\n const workspaceId = str(body, 'workspaceId')\n if (workspaceId !== null) {\n if (workspaces.get(workspaceId) === undefined) throw new Error('Error: not_found: unknown workspace')\n next.workspaceId = workspaceId\n }\n if (typeof body.blocked === 'boolean') next.blocked = body.blocked\n // The GUI (task owner surface) may edit model/execution; null clears the model.\n if (body.execution !== undefined) next.execution = normalizeExecution(body.execution as { mode?: string; cron?: string; runAt?: unknown }, options.now())\n if (body.model === null) next.model = undefined\n else if (body.model !== undefined) next.model = checkModel(body.model, options.modelProviders)\n // Isolation may change only before the first execution (分支与基线\n // 取决于该选择 — plan §3.1: 执行开始后锁定).\n const isolationRaw = str(body, 'isolation')\n if (isolationRaw !== null) {\n if (task.executions.length > 0 || task.status === 'in_progress') {\n throw new Error('Error: invalid_input: isolation 已锁定(任务已有执行记录),不可修改')\n }\n next.isolation = asIsolation(isolationRaw)\n }\n // Preset may change any time: each run composes fresh.\n if (body.presetId === null) delete next.presetId\n else if (body.presetId !== undefined) next.presetId = normalizePresetId(str(body, 'presetId'))!\n // Permission (0.5.5): 'workspace-write' | 'read-only' | 'danger-full-access'\n if (body.permission === null) delete next.permission\n else if (body.permission !== undefined) next.permission = asPermission(body.permission)\n // Checklist (0.4.0): the GUI replaces the whole list; null clears.\n if (body.checklist === null) delete next.checklist\n else if (body.checklist !== undefined) {\n const items = normalizeChecklist(body.checklist)\n if (items.length > 0) next.checklist = items\n else delete next.checklist\n }\n next.version = task.version + 1\n next.updatedAt = options.now()\n next.updatedBy = { kind: 'user' }\n ledger.tasks[index] = next\n return [next]\n })\n json(res, { ok: true, value: summarize(next!) })\n return\n }\n if (action === 'move') {\n const ifVersion = num(body, 'ifVersion')\n const status = str(body, 'status') ?? ''\n const archiveSessions = body.archiveSessions === true\n if (ifVersion === undefined || ifVersion === null) throw new Error('Error: version_conflict: ifVersion required')\n const to = asStatus(status)\n let next: TaskRecord | undefined\n let beforeTask: TaskRecord | undefined\n await store.mutate('task-moved', ledger => {\n const { index, task } = liveTaskAt(ledger, id)\n if (ifVersion !== task.version) throw new Error(`Error: version_conflict: stale version ${ifVersion} (current ${task.version})`)\n if (!canTransition(task.status, to)) throw new Error(`Error: invalid_transition: illegal transition ${task.status} → ${to}`)\n beforeTask = task\n next = structuredClone(task)\n next.status = to\n next.version = task.version + 1\n next.updatedAt = options.now()\n next.updatedBy = { kind: 'user' }\n if (task.status === 'todo' && to === 'in_progress') next.blocked = false\n // A user move records no holder; leaving in_progress releases any hold.\n syncClaim(next, to, options.now())\n ledger.tasks[index] = next\n return [next]\n })\n const sessionArchive = to === 'archived' && archiveSessions\n ? await archiveTaskSessions(beforeTask ?? next!, workspaces.archiveSession)\n : undefined\n json(res, { ok: true, value: { ...summarize(next!), ...(sessionArchive !== undefined ? { sessionArchive } : {}) } })\n return\n }\n if (action === 'reject') {\n // Card quick-reject: back to todo + optional user comment in one\n // atomic mutation (a failed move never strands an orphan comment).\n const ifVersion = num(body, 'ifVersion')\n if (ifVersion === undefined || ifVersion === null) throw new Error('Error: version_conflict: ifVersion required')\n const commentText = str(body, 'body') ?? ''\n let next: TaskRecord | undefined\n await store.mutate('task-moved', ledger => {\n const { index, task } = liveTaskAt(ledger, id)\n if (ifVersion !== task.version) throw new Error(`Error: version_conflict: stale version ${ifVersion} (current ${task.version})`)\n if (task.status !== 'in_review') throw new Error(`Error: invalid_transition: reject requires in_review (current ${task.status})`)\n if (!canTransition(task.status, 'todo')) throw new Error(`Error: invalid_transition: illegal transition ${task.status} → todo`)\n next = structuredClone(task)\n next.status = 'todo'\n next.version = task.version + 1\n next.updatedAt = options.now()\n next.updatedBy = { kind: 'user' }\n syncClaim(next, 'todo', options.now())\n if (commentText.trim().length > 0) {\n next.comments.push({ id: newCommentId(), body: normalizeBody(commentText), version: 1, createdAt: options.now() })\n }\n ledger.tasks[index] = next\n return [next]\n })\n json(res, { ok: true, value: summarize(next!) })\n return\n }\n if (action === 'comment') {\n const bodyText = str(body, 'body') ?? ''\n const comment = { id: newCommentId(), body: normalizeBody(bodyText), version: 1, createdAt: options.now() }\n await store.mutate('comment-added', ledger => {\n const { index, task } = liveTaskAt(ledger, id)\n if (task.status === 'archived') throw new Error('Error: invalid_transition: archived tasks are immutable')\n const next = structuredClone(task)\n next.comments.push(comment)\n next.version = task.version + 1\n next.updatedAt = options.now()\n ledger.tasks[index] = next\n return [next]\n })\n json(res, { ok: true, value: comment }, 201)\n return\n }\n if (action === 'delete') {\n const purge = body.purge === true\n if (purge) {\n if (task.trashedAt === undefined) throw new Error('Error: invalid_input: purge requires a trashed task (soft-delete first)')\n // Worktree safety before purge (plan §3.3, 0.3.1): refuse while\n // uncommitted work remains; otherwise clean the worktree and\n // the task branch along with the ledger entry.\n if (options.git !== undefined) {\n const ws = workspaces.get(task.workspaceId)\n if (ws !== undefined) {\n const path = worktreePathOf(ws.path, id)\n if (!insideWorktreeScope(ws.path, path)) {\n throw new Error('Error: invalid_input: 非法的清除路径(不在任务工作目录范围内)')\n }\n try {\n // 0.6.3: the whole mirror (every repo worktree) goes\n // through the aggregated children-first removal — one\n // dirty repo refuses BEFORE anything is deleted.\n await removeMirror(\n { git: options.git, scanner: options.scanner ?? createRepoScanner() },\n { workspacePath: ws.path, taskId: id },\n )\n // Leftovers unknown to git ('unregistered' worktrees, or a\n // plain mirror dir when the workspace root is no repo).\n await rm(path, { recursive: true, force: true })\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n // Structured classification (review P2): git tags its dirty\n // rejections with a code; the keyword stays as a fallback.\n const dirty = (error as { code?: string }).code === 'dirty-worktree'\n || (error as { code?: string }).code === 'dirty-mirror'\n || message.includes('未提交修改')\n if (dirty) {\n throw new Error(`Error: invalid_input: ${message};请先处理这些改动(提交、续跑或手动保存)再物理清除任务`)\n }\n throw new Error(`Error: invalid_input: ${message}`)\n }\n // Branches die with the task (best effort, per repo).\n const branchTargets: Array<{ repo: string; branch: string }> = []\n if (task.branches !== undefined) {\n for (const [repo, branch] of Object.entries(task.branches)) branchTargets.push({ repo, branch })\n }\n if (task.branch !== undefined && !branchTargets.some(t => t.repo === '')) branchTargets.push({ repo: '', branch: task.branch })\n for (const target of branchTargets) {\n try {\n await options.git.deleteBranch(repoMainPath(ws.path, target.repo), target.branch)\n } catch { /* best effort: the branch may outlive the task */ }\n }\n }\n }\n await store.mutate('task-deleted', ledger => {\n ledger.tasks = ledger.tasks.filter(t => t.id !== id)\n return []\n })\n json(res, { ok: true, value: { purged: true } })\n return\n }\n const ifVersion = num(body, 'ifVersion')\n if (ifVersion === undefined || ifVersion === null) throw new Error('Error: version_conflict: ifVersion required')\n await store.mutate('task-deleted', ledger => {\n const { index, task } = liveTaskAt(ledger, id)\n if (ifVersion !== task.version) throw new Error(`Error: version_conflict: stale version ${ifVersion} (current ${task.version})`)\n // S5: a running execution keeps writing to the task — refuse the\n // soft-delete until it is cancelled or settled. T8: clear residue.\n if (task.executions.some(e => e.outcome === 'running')) {\n throw new Error('Error: invalid_input: 任务有正在运行的执行,请先取消或等它结束再删除')\n }\n const next = structuredClone(task)\n next.trashedAt = options.now()\n next.version = task.version + 1\n delete next.claimedBy\n delete next.claimedAt\n next.blocked = false\n ledger.tasks[index] = next\n return [next]\n })\n json(res, { ok: true, value: { trashed: true } })\n return\n }\n if (action === 'run') {\n if (options.run === undefined) {\n const f = fail('invalid_input', 'execution service unavailable')\n json(res, f.res, 501)\n return\n }\n // `reuse: true` = 续跑: keep a live worktree/branch as-is instead\n // of resetting to a fresh baseline (0.3.1).\n const runOptions = body.reuse === true ? { reuseWorktree: true } : undefined\n const result = await options.run(id, runOptions)\n if (result.ok) json(res, { ok: true, value: result }, 202)\n else {\n const f = fail('invalid_input', result.error)\n json(res, f.res, f.status)\n }\n return\n }\n if (action === 'cancel') {\n if (options.cancel === undefined) {\n const f = fail('invalid_input', 'execution service unavailable')\n json(res, f.res, 501)\n return\n }\n const result = await options.cancel(id)\n if (result.ok) json(res, { ok: true, value: { cancelled: true, executionId: result.executionId } }, 202)\n else {\n const f = fail('invalid_input', result.error)\n json(res, f.res, f.status)\n }\n return\n }\n if (action === 'merge') {\n // ⇥ 合并 (detail page, user-only): merge the task branch(es) into\n // the main worktree(s) with --no-ff; conflicts are reported\n // verbatim. 0.6.3: multi-repo mirror tasks merge PER REPO —\n // sequentially, a failed repo never blocking the others.\n if (options.git === undefined) {\n const f = fail('invalid_input', 'git integration unavailable')\n json(res, f.res, 501)\n return\n }\n if (task.status === 'in_progress') throw new Error('Error: invalid_input: 任务执行中,不能合并')\n if (task.executions.some(e => e.outcome === 'running')) throw new Error('Error: invalid_input: 任务执行中,不能合并')\n const ws = workspaces.get(task.workspaceId)\n if (ws === undefined) throw new Error('Error: not_found: unknown workspace')\n const targets: Array<{ repo: string; branch: string }> = []\n if (task.branches !== undefined) {\n for (const [repo, branch] of Object.entries(task.branches)) targets.push({ repo, branch })\n }\n if (task.branch !== undefined && !targets.some(t => t.repo === '')) targets.unshift({ repo: '', branch: task.branch })\n if (targets.length === 0) throw new Error('Error: invalid_input: 该任务还没有 worktree 分支(未隔离执行过)')\n const multi = task.branches !== undefined\n const results: MergeRepoResult[] = []\n for (const target of targets) {\n if (!isValidRelRepoPath(target.repo)) throw new Error('Error: invalid_input: 非法的仓库路径')\n const repoRoot = repoMainPath(ws.path, target.repo)\n // No-op detection (0.3.1): a branch with no commits over HEAD\n // merges as \"already up to date\" — report that instead of landing\n // a bogus 已合并 comment.\n let noop = false\n try {\n noop = await options.git.isAncestor(repoRoot, target.branch)\n } catch { /* fail-soft: proceed to the real merge */ }\n if (noop) {\n results.push({ repo: target.repo, branch: target.branch, outcome: 'noop' })\n continue\n }\n try {\n // 0.6.3 review fix: parallel repos are self-governing — their\n // content AND their gitlink entries in the ROOT main checkout\n // (e.g. a container repo tracking `dsh-taskboard` as an embedded\n // repo) don't gate the root repo's merge. Child repos merge with\n // no extra exemption (their own .dsh-worktrees rule suffices).\n const exempt = target.repo === ''\n ? await (options.scanner ?? createRepoScanner()).findNestedRepos(ws.path).then(rs => rs.map(r => r.relPath))\n : undefined\n await options.git.merge(repoRoot, target.branch, exempt)\n results.push({ repo: target.repo, branch: target.branch, outcome: 'merged' })\n } catch (error) {\n results.push({\n repo: target.repo,\n branch: target.branch,\n outcome: 'failed',\n error: error instanceof Error ? error.message : String(error),\n })\n }\n }\n // R1: the git merges above are slow — re-find the FRESH task inside\n // the mutation so a concurrent comment is never overwritten.\n const pushComment = (body: string, system?: { key: string; params?: Record<string, string>; rows?: SystemCommentRow[] }): Promise<void> =>\n store.mutate('comment-added', ledger => {\n const { index, task: fresh } = liveTaskAt(ledger, id)\n const next = structuredClone(fresh)\n next.comments.push({\n id: newCommentId(),\n body: normalizeBody(body),\n ...(system !== undefined ? {\n systemKey: system.key,\n ...(system.params !== undefined ? { systemParams: system.params } : {}),\n ...(system.rows !== undefined ? { systemRows: system.rows } : {}),\n } : {}),\n version: 1,\n createdAt: options.now(),\n })\n next.version = fresh.version + 1\n next.updatedAt = options.now()\n ledger.tasks[index] = next\n return [next]\n }).then(() => undefined)\n if (!multi) {\n // Legacy single-repo shape, byte-identical to 0.3.x–0.6.x.\n const root = results.find(r => r.repo === '')\n if (root === undefined) throw new Error('Error: invalid_input: 该任务还没有 worktree 分支(未隔离执行过)')\n if (root.outcome === 'noop') {\n json(res, { ok: true, value: { merged: false, noop: true, branch: root.branch } })\n return\n }\n if (root.outcome === 'failed') {\n throw new Error(`Error: invalid_input: ${root.error ?? '合并失败'}`)\n }\n await pushComment(`[系统] 分支 ${root.branch} 已合并到主工作区(--no-ff)。`, { key: 'sys.mergeSingle', params: { branch: root.branch } })\n json(res, { ok: true, value: { merged: true, branch: root.branch } })\n return\n }\n const labelOf = (repo: string): string => repo === '' ? '根仓库' : repo\n const mergedCount = results.filter(r => r.outcome === 'merged').length\n const failedCount = results.filter(r => r.outcome === 'failed').length\n const summary = results\n .map(r => r.outcome === 'merged'\n ? `${labelOf(r.repo)} ✓ 已合并`\n : r.outcome === 'noop' ? `${labelOf(r.repo)} ⟲ 无新提交` : `${labelOf(r.repo)} ✗ ${(r.error ?? '合并失败').slice(0, 150)}`)\n .join(';')\n await pushComment(`[系统] 分支已按仓库合并(--no-ff):${summary}`, {\n key: 'sys.mergeMulti',\n rows: results.map(r => ({ repo: r.repo, outcome: r.outcome, ...(r.error !== undefined ? { error: r.error.slice(0, 150) } : {}) })),\n })\n json(res, {\n ok: true,\n value: {\n merged: mergedCount > 0,\n ...(mergedCount === 0 && failedCount === 0 ? { noop: true } : {}),\n results,\n },\n })\n return\n }\n if (action === 'worktree-remove') {\n // 🗑 删除 worktree (detail page): refuses uncommitted changes;\n // optionally deletes the task branch after the worktree is gone.\n if (options.git === undefined) {\n const f = fail('invalid_input', 'git integration unavailable')\n json(res, f.res, 501)\n return\n }\n if (task.executions.some(e => e.outcome === 'running')) throw new Error('Error: invalid_input: 任务执行中,不能删除 worktree')\n const ws = workspaces.get(task.workspaceId)\n if (ws === undefined) throw new Error('Error: not_found: unknown workspace')\n const path = worktreePathOf(ws.path, id)\n if (!insideWorktreeScope(ws.path, path)) {\n throw new Error('Error: invalid_input: 非法的清除路径(不在任务工作目录范围内)')\n }\n try {\n // 0.6.3: aggregated children-first mirror removal (see purge).\n await removeMirror(\n { git: options.git, scanner: options.scanner ?? createRepoScanner() },\n { workspacePath: ws.path, taskId: id },\n )\n await rm(path, { recursive: true, force: true })\n } catch (error) {\n throw new Error(`Error: invalid_input: ${error instanceof Error ? error.message : String(error)}`)\n }\n let branchDeleted = false\n let branchError: string | undefined\n if (body.deleteBranch === true) {\n const branchTargets: Array<{ repo: string; branch: string }> = []\n if (task.branches !== undefined) {\n for (const [repo, branch] of Object.entries(task.branches)) branchTargets.push({ repo, branch })\n }\n if (task.branch !== undefined && !branchTargets.some(t => t.repo === '')) branchTargets.push({ repo: '', branch: task.branch })\n let failures = 0\n for (const target of branchTargets) {\n try {\n await options.git.deleteBranch(repoMainPath(ws.path, target.repo), target.branch)\n } catch (error) {\n failures += 1\n const label = target.repo === '' ? '根仓库' : target.repo\n branchError = `${label}:${error instanceof Error ? error.message : String(error)}`\n }\n }\n branchDeleted = failures === 0\n }\n json(res, { ok: true, value: { removed: true, branchDeleted, ...(branchError !== undefined ? { branchError } : {}) } })\n return\n }\n const f = fail('not_found', `unknown action ${action}`)\n json(res, f.res, f.status)\n } catch (error) {\n const f = toFail(error)\n json(res, f.res, f.status)\n }\n return\n }\n\n // -------------------------------------- POST /worktree-cleanup (⚙ 诊断)\n if (pathname === `${ROUTE_PREFIX}/worktree-cleanup`) {\n try {\n if (options.git === undefined) {\n const f = fail('invalid_input', 'git integration unavailable')\n json(res, f.res, 501)\n return\n }\n const workspaceId = str(body, 'workspaceId') ?? ''\n const taskId = str(body, 'taskId') ?? ''\n const ws = workspaces.get(workspaceId)\n if (ws === undefined) throw new Error('Error: not_found: unknown workspace')\n // Only dirs owned by NO ledger task may be cleaned here; live tasks\n // remove their worktree from the detail page.\n if (store.get(taskId) !== undefined) throw new Error('Error: invalid_input: 任务仍在看板中,请从任务详情页删除其 worktree')\n // worktreePathOf throws on an illegal id charset (R4②); the resolved\n // target must additionally stay inside the plugin's own worktree\n // scope — the taskId is fully attacker-controlled body input (R4③).\n const path = worktreePathOf(ws.path, taskId)\n if (!insideWorktreeScope(ws.path, path)) {\n throw new Error('Error: invalid_input: 非法的清除路径(不在任务工作目录范围内)')\n }\n try {\n // 0.6.3: an orphan mirror may hold several repo worktrees —\n // aggregated children-first removal, then the leftover fs rm\n // (scope-verified above).\n await removeMirror(\n { git: options.git, scanner: options.scanner ?? createRepoScanner() },\n { workspacePath: ws.path, taskId },\n )\n await rm(path, { recursive: true, force: true })\n } catch (error) {\n throw new Error(`Error: invalid_input: ${error instanceof Error ? error.message : String(error)}`)\n }\n json(res, { ok: true, value: { cleaned: true, path } })\n } catch (error) {\n const f = toFail(error)\n json(res, f.res, f.status)\n }\n return\n }\n\n // ---------------------------------------------- POST /import/preview\n // (0.4.0) Dry-run: classify every task in the uploaded ledger file\n // against the live one; nothing is written.\n if (pathname === `${ROUTE_PREFIX}/import/preview`) {\n try {\n const known = new Set(store.snapshot().tasks.map(t => t.id))\n const plan = validateLedgerImport(body, known, options.now())\n json(res, {\n ok: true,\n value: {\n plan: {\n create: plan.create.map(t => ({ id: t.id, title: t.title, status: t.status })),\n overwrite: plan.overwrite.map(t => ({ id: t.id, title: t.title, status: t.status })),\n invalid: plan.invalid,\n },\n },\n })\n } catch (error) {\n const f = toFail(error)\n json(res, f.res, f.status)\n }\n return\n }\n\n // ------------------------------------------------------ POST /import\n // (0.4.0) Commit an import. mode=merge upserts (create + overwrite by\n // id); mode=replace swaps the WHOLE ledger (invalid entries dropped)\n // after writing a timestamped backup of the current one.\n if (pathname === `${ROUTE_PREFIX}/import`) {\n try {\n const mode = str(body, 'mode') === 'replace' ? 'replace' as const : 'merge' as const\n const raw = body.ledger\n const known = new Set(store.snapshot().tasks.map(t => t.id))\n const plan = validateLedgerImport(raw, known, options.now())\n const imported = [...plan.create, ...plan.overwrite]\n if (mode === 'replace' && imported.length === 0) {\n throw new Error('Error: invalid_input: 导入文件没有可导入的任务,已拒绝整册替换')\n }\n let backupFile: string | undefined\n if (mode === 'replace' && store.snapshot().tasks.length > 0) {\n backupFile = await store.backup()\n }\n let replacedTotal: number | undefined\n await store.mutate('ledger-replaced', ledger => {\n if (mode === 'replace') {\n // S6: a whole-ledger swap must not strand live executions — the\n // running sessions keep working with no task to settle into.\n if (ledger.tasks.some(t => t.executions.some(e => e.outcome === 'running'))) {\n throw new Error('Error: invalid_input: 有任务正在执行,不能整册替换(请先取消或等待结束)')\n }\n replacedTotal = ledger.tasks.length\n ledger.tasks = structuredClone(imported)\n // Replace is a whole-ledger swap (0.5.0): board settings ride\n // along when the file carries them; merge keeps the live ones.\n if (plan.settings !== undefined) ledger.settings = structuredClone(plan.settings)\n else delete ledger.settings\n return ledger.tasks\n }\n const byId = new Map(ledger.tasks.map(t => [t.id, t]))\n for (const task of imported) {\n // S6: merging over a task whose execution is live would orphan\n // that run the same way — refuse the overwrite.\n const existing = byId.get(task.id)\n if (existing !== undefined && existing.executions.some(e => e.outcome === 'running')) {\n throw new Error(`Error: invalid_input: 任务 ${task.id} 正在执行,不能被导入覆盖`)\n }\n byId.set(task.id, structuredClone(task))\n }\n ledger.tasks = [...byId.values()]\n return structuredClone(imported)\n })\n json(res, {\n ok: true,\n value: {\n mode,\n created: plan.create.length,\n overwritten: plan.overwrite.length,\n ...(mode === 'replace' ? { replacedTotal } : {}),\n ...(backupFile !== undefined ? { backupFile } : {}),\n },\n })\n } catch (error) {\n const f = toFail(error)\n json(res, f.res, f.status)\n }\n return\n }\n\n // ------------------------------------------- POST /templates (+delete)\n if (pathname === `${ROUTE_PREFIX}/templates` || pathname === `${ROUTE_PREFIX}/templates/delete`) {\n try {\n if (options.templates === undefined) {\n const f = fail('invalid_input', 'template store unavailable')\n json(res, f.res, 501)\n return\n }\n if (pathname.endsWith('/delete')) {\n const id = str(body, 'id') ?? ''\n if (id.length === 0) throw new Error('Error: invalid_input: id required')\n const deleted = await options.templates.remove(id)\n json(res, { ok: true, value: { deleted } })\n return\n }\n const name = str(body, 'name') ?? ''\n if (name.trim().length === 0) throw new Error('Error: invalid_input: name required')\n const template = await options.templates.upsert({\n id: str(body, 'id') ?? undefined,\n name,\n task: normalizeTemplateSpec(body.task, options.now()),\n })\n json(res, { ok: true, value: template }, 201)\n } catch (error) {\n const f = toFail(error)\n json(res, f.res, f.status)\n }\n return\n }\n\n // ------------------------------------------ POST /settings/update\n // (0.5.0) Whole-object replace semantics: omitted fields fall back to\n // their factory defaults. Affects only tasks created AFTER the change.\n if (pathname === `${ROUTE_PREFIX}/settings/update`) {\n try {\n const next = asBoardSettings(body)\n await store.mutate('settings-updated', ledger => {\n ledger.settings = next\n return []\n })\n json(res, { ok: true, value: next })\n } catch (error) {\n const f = toFail(error)\n json(res, f.res, f.status)\n }\n return\n }\n\n res.writeHead(404)\n res.end()\n } catch (error) {\n const f = fail('internal', error instanceof Error ? error.message : String(error))\n json(res, f.res, f.status)\n }\n }\n\n const sse = (req: IncomingMessage, res: ServerResponse): void => {\n res.writeHead(200, {\n 'content-type': 'text/event-stream; charset=utf-8',\n 'cache-control': 'no-cache',\n connection: 'keep-alive',\n })\n res.write('retry: 2000\\n\\n')\n // Baseline frame: the client reconciles by revision and refetches state on gaps.\n res.write(`event: hello\\ndata: ${JSON.stringify({ revision: store.snapshot().revision })}\\n\\n`)\n subscribers.add(res)\n // S2: a socket that dies between 'close' detection and the next write\n // would emit 'error' on the response — unhandled, that escalates to an\n // uncaughtException. Drop the subscriber instead.\n res.on('error', () => { subscribers.delete(res) })\n if (heartbeat === undefined) {\n heartbeat = setInterval(() => {\n for (const current of subscribers) current.write(': ping\\n\\n')\n }, HEARTBEAT_MS)\n }\n req.on('close', () => {\n subscribers.delete(res)\n if (subscribers.size === 0 && heartbeat !== undefined) {\n clearInterval(heartbeat)\n heartbeat = undefined\n }\n })\n }\n\n const disposers = [\n ctx.webServer.register({ kind: 'prefix', path: ROUTE_PREFIX, handler }),\n ctx.webServer.register({ kind: 'exact', path: SSE_PATH, handler: sse }),\n ]\n return () => {\n unsubscribeBroadcast()\n for (const dispose of disposers) dispose()\n if (heartbeat !== undefined) clearInterval(heartbeat)\n for (const res of subscribers) res.end()\n subscribers.clear()\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ //#region src/host/scheduled-session.ts
2
+ /**
3
+ * Only confirmed absence, archival or incompatible configuration permits a
4
+ * replacement session... and so does any resume obstacle: busy, locked or
5
+ * corrupt sessions degrade to a brand-new conversation (issue #26 policy),
6
+ * trading history continuity for guaranteed execution progress.
7
+ */
8
+ function scheduledSessionResumer(deps) {
9
+ return async (sessionId, options) => {
10
+ if (deps.isArchived(sessionId)) return void 0;
11
+ const compatible = (header) => header.cwd === options.meta?.cwd && header.agentPreset === options.meta?.agentPreset;
12
+ const live = deps.agents.get(sessionId);
13
+ if (live !== void 0) {
14
+ if (live.status !== "idle") return void 0;
15
+ if (!compatible(live.session.header)) return void 0;
16
+ const model = options.agentOptions;
17
+ if (live.options?.provider !== model?.provider || live.options?.model !== model?.model || live.options?.reasoningEffort !== model?.reasoningEffort) return void 0;
18
+ return {
19
+ agent: live,
20
+ borrowed: true,
21
+ dispose: async () => {
22
+ live.cancel({ kind: "user" });
23
+ await live.whenIdle();
24
+ }
25
+ };
26
+ }
27
+ let stored;
28
+ try {
29
+ stored = await deps.persistence()?.stat(sessionId);
30
+ } catch {
31
+ return;
32
+ }
33
+ if (stored === void 0 || !compatible(stored.header)) return void 0;
34
+ if (deps.isArchived(sessionId)) return void 0;
35
+ if (deps.agents.get(sessionId) !== void 0) return void 0;
36
+ try {
37
+ return await deps.agents.resume({
38
+ resumeSessionId: sessionId,
39
+ ...options.agentOptions !== void 0 ? { agentOptions: options.agentOptions } : {},
40
+ ...options.setup !== void 0 ? { setup: options.setup } : {}
41
+ });
42
+ } catch {
43
+ return;
44
+ }
45
+ };
46
+ }
47
+ //#endregion
48
+ export { scheduledSessionResumer };
49
+
50
+ //# sourceMappingURL=scheduled-session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduled-session.js","names":[],"sources":["../../src/host/scheduled-session.ts"],"sourcesContent":["/** Adapt DSH's live-agent and durable-session APIs for recurring executions. */\nimport type { AgentsFace } from './execution.ts'\n\ntype CreateOptions = Parameters<AgentsFace['create']>[0]\ntype Handle = Awaited<ReturnType<AgentsFace['create']>>\ntype Header = { cwd?: string; agentPreset?: string }\n\nexport interface ScheduledSessionDeps {\n agents: {\n get(id: string): (Handle['agent'] & {\n session: { header: Header }\n options: CreateOptions['agentOptions']\n cancel(cause: { kind: 'user' }): void\n }) | undefined\n resume(options: { resumeSessionId: string; agentOptions?: CreateOptions['agentOptions']; setup?: CreateOptions['setup'] }): Promise<Handle>\n }\n persistence(): { stat(id: string): Promise<{ header: Header } | undefined> } | undefined\n isArchived(id: string): boolean\n}\n\n/**\n * Only confirmed absence, archival or incompatible configuration permits a\n * replacement session... and so does any resume obstacle: busy, locked or\n * corrupt sessions degrade to a brand-new conversation (issue #26 policy),\n * trading history continuity for guaranteed execution progress.\n */\nexport function scheduledSessionResumer(deps: ScheduledSessionDeps): NonNullable<AgentsFace['resumeScheduled']> {\n return async (sessionId, options) => {\n if (deps.isArchived(sessionId)) return undefined\n const compatible = (header: Header): boolean => header.cwd === options.meta?.cwd\n && header.agentPreset === options.meta?.agentPreset\n const live = deps.agents.get(sessionId)\n if (live !== undefined) {\n // Busy: never queue behind the running conversation — fall back to a\n // fresh session so the scheduled trigger still makes progress.\n if (live.status !== 'idle') return undefined\n if (!compatible(live.session.header)) return undefined\n const model = options.agentOptions\n if (live.options?.provider !== model?.provider || live.options?.model !== model?.model\n || live.options?.reasoningEffort !== model?.reasoningEffort) return undefined\n return {\n agent: live,\n borrowed: true,\n // A registry lookup grants no ownership. Cancel this activity without\n // disposing the agent owned by another service (for example the UI).\n dispose: async () => { live.cancel({ kind: 'user' }); await live.whenIdle() },\n }\n }\n // Unreadable metadata (locked/corrupt/disk trouble) degrades to a fresh\n // session rather than failing the whole scheduled run.\n let stored: { header: Header } | undefined\n try {\n const persistence = deps.persistence()\n stored = await persistence?.stat(sessionId)\n } catch { return undefined }\n if (stored === undefined || !compatible(stored.header)) return undefined\n // Recheck after the asynchronous metadata read, including a GUI resume.\n if (deps.isArchived(sessionId)) return undefined\n if (deps.agents.get(sessionId) !== undefined) return undefined\n try {\n return await deps.agents.resume({\n resumeSessionId: sessionId,\n ...(options.agentOptions !== undefined ? { agentOptions: options.agentOptions } : {}),\n ...(options.setup !== undefined ? { setup: options.setup } : {}),\n })\n } catch { return undefined }\n }\n}\n"],"mappings":";;;;;;;AA0BA,SAAgB,wBAAwB,MAAwE;CAC9G,OAAO,OAAO,WAAW,YAAY;EACnC,IAAI,KAAK,WAAW,SAAS,GAAG,OAAO,KAAA;EACvC,MAAM,cAAc,WAA4B,OAAO,QAAQ,QAAQ,MAAM,OACxE,OAAO,gBAAgB,QAAQ,MAAM;EAC1C,MAAM,OAAO,KAAK,OAAO,IAAI,SAAS;EACtC,IAAI,SAAS,KAAA,GAAW;GAGtB,IAAI,KAAK,WAAW,QAAQ,OAAO,KAAA;GACnC,IAAI,CAAC,WAAW,KAAK,QAAQ,MAAM,GAAG,OAAO,KAAA;GAC7C,MAAM,QAAQ,QAAQ;GACtB,IAAI,KAAK,SAAS,aAAa,OAAO,YAAY,KAAK,SAAS,UAAU,OAAO,SAC5E,KAAK,SAAS,oBAAoB,OAAO,iBAAiB,OAAO,KAAA;GACtE,OAAO;IACL,OAAO;IACP,UAAU;IAGV,SAAS,YAAY;KAAE,KAAK,OAAO,EAAE,MAAM,OAAO,CAAC;KAAG,MAAM,KAAK,SAAS;IAAE;GAC9E;EACF;EAGA,IAAI;EACJ,IAAI;GAEF,SAAS,MADW,KAAK,YACA,CAAC,EAAE,KAAK,SAAS;EAC5C,QAAQ;GAAE;EAAiB;EAC3B,IAAI,WAAW,KAAA,KAAa,CAAC,WAAW,OAAO,MAAM,GAAG,OAAO,KAAA;EAE/D,IAAI,KAAK,WAAW,SAAS,GAAG,OAAO,KAAA;EACvC,IAAI,KAAK,OAAO,IAAI,SAAS,MAAM,KAAA,GAAW,OAAO,KAAA;EACrD,IAAI;GACF,OAAO,MAAM,KAAK,OAAO,OAAO;IAC9B,iBAAiB;IACjB,GAAI,QAAQ,iBAAiB,KAAA,IAAY,EAAE,cAAc,QAAQ,aAAa,IAAI,CAAC;IACnF,GAAI,QAAQ,UAAU,KAAA,IAAY,EAAE,OAAO,QAAQ,MAAM,IAAI,CAAC;GAChE,CAAC;EACH,QAAQ;GAAE;EAAiB;CAC7B;AACF"}