dsh-taskboard 0.6.3 → 0.6.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.
- package/README.md +4 -0
- package/lib/client.js +4 -4
- package/lib/host/routes.js +4 -2
- package/lib/host/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/client/i18n/runtime.ts +71 -5
- package/src/client/index.ts +5 -2
- package/src/shared/version.ts +1 -1
package/lib/host/routes.js
CHANGED
|
@@ -198,8 +198,10 @@ function registerTaskboardRoutes(ctx, options) {
|
|
|
198
198
|
*/
|
|
199
199
|
const rootIsRepo = async (path) => {
|
|
200
200
|
try {
|
|
201
|
-
return await options.git?.detect(path)
|
|
202
|
-
} catch {
|
|
201
|
+
return await options.git?.detect(path) === true;
|
|
202
|
+
} catch {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
203
205
|
};
|
|
204
206
|
/**
|
|
205
207
|
* Workspace repo facts for the form (0.6.3): `gitAvailable` gates the
|
package/lib/host/routes.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.js","names":[],"sources":["../../src/host/routes.ts"],"sourcesContent":["/**\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} 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 type { CatalogModelItem, CatalogPresetItem, MergeRepoResult, TaskTemplate } from '../shared/api.ts'\nimport type { TemplateStore } from './templates.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-]+)$`)\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 /** 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/** 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) ?? false\n } catch { /* fail-soft → false */ }\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 if (rootRepo && !gitHinted.has(path)) {\n gitHinted.add(path)\n if (await gitignoreMissing(path)) {\n console.info(`[dsh-taskboard] 建议在 ${path}/.gitignore 加入一行 ${WORKTREE_DIR}/ 以隐藏任务 worktree 目录(不会自动修改)`)\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}/state`) {\n await store.load()\n json(res, { ok: true, value: store.snapshot() })\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 // 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 // ------------------------------------------------- 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 === '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 if (ifVersion === undefined || ifVersion === null) throw new Error('Error: version_conflict: ifVersion required')\n const to = asStatus(status)\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, to)) throw new Error(`Error: invalid_transition: illegal transition ${task.status} → ${to}`)\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 json(res, { ok: true, value: summarize(next!) })\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): 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({ id: newCommentId(), body: normalizeBody(body), version: 1, createdAt: options.now() })\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)。`)\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 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":["/**\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} 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 type { CatalogModelItem, CatalogPresetItem, MergeRepoResult, TaskTemplate } from '../shared/api.ts'\nimport type { TemplateStore } from './templates.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-]+)$`)\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 /** 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/** 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 if (rootRepo && !gitHinted.has(path)) {\n gitHinted.add(path)\n if (await gitignoreMissing(path)) {\n console.info(`[dsh-taskboard] 建议在 ${path}/.gitignore 加入一行 ${WORKTREE_DIR}/ 以隐藏任务 worktree 目录(不会自动修改)`)\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}/state`) {\n await store.load()\n json(res, { ok: true, value: store.snapshot() })\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 // 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 // ------------------------------------------------- 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 === '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 if (ifVersion === undefined || ifVersion === null) throw new Error('Error: version_conflict: ifVersion required')\n const to = asStatus(status)\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, to)) throw new Error(`Error: invalid_transition: illegal transition ${task.status} → ${to}`)\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 json(res, { ok: true, value: summarize(next!) })\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): 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({ id: newCommentId(), body: normalizeBody(body), version: 1, createdAt: options.now() })\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)。`)\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 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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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-taskboard",
|
|
3
3
|
"description": "Agent-first task board for the DSH web GUI: host-authoritative task ledger with taskboard_* agent tools, project (= workspace) claim boundaries, per-task model execution in fresh sessions, optional per-task git-worktree isolation (dedicated task branches, commit evidence, one-click merge), host-side cron scheduling, and a live SSE kanban view. Mounts via the official dsh plugin system — no DSH source changes.",
|
|
4
|
-
"version": "0.6.
|
|
4
|
+
"version": "0.6.4",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -55,6 +55,24 @@ let unsubscribeService: (() => void) | undefined
|
|
|
55
55
|
let snapshot: TaskboardLocaleSnapshot = { active: detectFallbackLocale(), revision: 0 }
|
|
56
56
|
const listeners = new Set<() => void>()
|
|
57
57
|
|
|
58
|
+
// Late attach (issue #16): the taskboard client injects NOTHING, so it can
|
|
59
|
+
// activate BEFORE the locale service provides — and before the locale
|
|
60
|
+
// runtime syncs <html lang> (the server-rendered static value is "en").
|
|
61
|
+
// Until a real service attaches, a MutationObserver re-detects on every
|
|
62
|
+
// <html lang> change and a short poll re-tries ctx.get('locale').
|
|
63
|
+
let langObserver: MutationObserver | undefined
|
|
64
|
+
let retryTimer: ReturnType<typeof setInterval> | undefined
|
|
65
|
+
|
|
66
|
+
/** Stop every late-attach mechanism (a service took over, or dispose). */
|
|
67
|
+
function clearLateAttach(): void {
|
|
68
|
+
if (retryTimer !== undefined) {
|
|
69
|
+
clearInterval(retryTimer)
|
|
70
|
+
retryTimer = undefined
|
|
71
|
+
}
|
|
72
|
+
try { langObserver?.disconnect() } catch { /* observer already dead */ }
|
|
73
|
+
langObserver = undefined
|
|
74
|
+
}
|
|
75
|
+
|
|
58
76
|
function isLocaleId(value: string): value is LocaleId {
|
|
59
77
|
return value === 'zh' || value === 'en'
|
|
60
78
|
}
|
|
@@ -80,6 +98,45 @@ function detectFallbackLocale(): LocaleId {
|
|
|
80
98
|
return 'en'
|
|
81
99
|
}
|
|
82
100
|
|
|
101
|
+
/** Coerce an unknown ctx value into a usable service face, else undefined. */
|
|
102
|
+
function asLocaleService(value: unknown): LocaleServiceFace | undefined {
|
|
103
|
+
const face = value as LocaleServiceFace | null | undefined
|
|
104
|
+
if (face === null || face === undefined || typeof face.getSnapshot !== 'function' || typeof face.subscribe !== 'function') return undefined
|
|
105
|
+
return face
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Watch for the late locale activation while no service is attached
|
|
110
|
+
* (issue #16): re-detect on <html lang> changes (the locale runtime syncs
|
|
111
|
+
* it once up) and poll ctx.get('locale') briefly (inject ordering — the
|
|
112
|
+
* service may provide a moment after our apply).
|
|
113
|
+
*/
|
|
114
|
+
function startLateAttach(retryGetLocale: (() => unknown) | undefined): void {
|
|
115
|
+
clearLateAttach()
|
|
116
|
+
try {
|
|
117
|
+
if (typeof MutationObserver !== 'undefined' && typeof document !== 'undefined') {
|
|
118
|
+
langObserver = new MutationObserver(() => {
|
|
119
|
+
if (service !== undefined) return // a real service owns the state
|
|
120
|
+
const next = detectFallbackLocale()
|
|
121
|
+
if (next !== snapshot.active) publish({ active: next, revision: snapshot.revision + 1 })
|
|
122
|
+
})
|
|
123
|
+
langObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['lang'] })
|
|
124
|
+
}
|
|
125
|
+
} catch { /* no DOM — the poll below still covers service-side activation */ }
|
|
126
|
+
if (retryGetLocale === undefined) return
|
|
127
|
+
let tries = 0
|
|
128
|
+
retryTimer = setInterval(() => {
|
|
129
|
+
tries += 1
|
|
130
|
+
let face: LocaleServiceFace | undefined
|
|
131
|
+
try { face = asLocaleService(retryGetLocale()) } catch { face = undefined }
|
|
132
|
+
if (face !== undefined) {
|
|
133
|
+
initI18n(face) // attaches the service and tears the late attach down
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
if (tries >= 8) clearLateAttach() // ~2s at 250ms — enough for boot ordering
|
|
137
|
+
}, 250)
|
|
138
|
+
}
|
|
139
|
+
|
|
83
140
|
function publish(next: TaskboardLocaleSnapshot): void {
|
|
84
141
|
if (next.active === snapshot.active && next.revision === snapshot.revision) return
|
|
85
142
|
snapshot = next
|
|
@@ -89,18 +146,26 @@ function publish(next: TaskboardLocaleSnapshot): void {
|
|
|
89
146
|
/**
|
|
90
147
|
* Attach the DSH locale service (call from the client entry's apply with
|
|
91
148
|
* ctx.get('locale')). Absent/malformed services are ignored — the fallback
|
|
92
|
-
* detection stays in charge
|
|
149
|
+
* detection stays in charge, and (issue #16) a late attach keeps watching:
|
|
150
|
+
* the locale service can provide AFTER our activation (inject ordering), so
|
|
151
|
+
* pass `retryGetLocale` to re-poll ctx.get('locale') for ~2s and pick the
|
|
152
|
+
* service up the moment it exists; <html lang> changes re-run detection in
|
|
153
|
+
* the meantime.
|
|
93
154
|
* @param localeService - the ctx 'locale' service, when provided.
|
|
155
|
+
* @param retryGetLocale - re-lookup for the service (the client entry passes
|
|
156
|
+
* `() => ctx.get?.('locale')`); polled only while no service is attached.
|
|
94
157
|
*/
|
|
95
|
-
export function initI18n(localeService: unknown): void {
|
|
96
|
-
const face = localeService
|
|
97
|
-
if (face ===
|
|
158
|
+
export function initI18n(localeService: unknown, retryGetLocale?: () => unknown): void {
|
|
159
|
+
const face = asLocaleService(localeService)
|
|
160
|
+
if (face === undefined) {
|
|
98
161
|
// No usable service: resolve the fallback NOW so a caller that inits
|
|
99
162
|
// after the DOM is up gets the current detection, never a stale
|
|
100
|
-
// module-load snapshot.
|
|
163
|
+
// module-load snapshot — then keep watching for the late activation.
|
|
101
164
|
publish({ active: detectFallbackLocale(), revision: 0 })
|
|
165
|
+
startLateAttach(retryGetLocale)
|
|
102
166
|
return
|
|
103
167
|
}
|
|
168
|
+
clearLateAttach()
|
|
104
169
|
service = face
|
|
105
170
|
const sync = (): void => {
|
|
106
171
|
try {
|
|
@@ -115,6 +180,7 @@ export function initI18n(localeService: unknown): void {
|
|
|
115
180
|
|
|
116
181
|
/** Detach the service and return to fallback detection (tests, dispose). */
|
|
117
182
|
export function disposeI18n(): void {
|
|
183
|
+
clearLateAttach()
|
|
118
184
|
try { unsubscribeService?.() } catch { /* source already gone */ }
|
|
119
185
|
unsubscribeService = undefined
|
|
120
186
|
service = undefined
|
package/src/client/index.ts
CHANGED
|
@@ -70,8 +70,11 @@ export function apply(ctx: ClientContextFace): void {
|
|
|
70
70
|
injectStyles()
|
|
71
71
|
// Locale source (设置 → 通用设置 → 语言): soft-attached — absent on
|
|
72
72
|
// compositions without the DSH locale plugin, where the fallback
|
|
73
|
-
// (<html lang> / navigator) takes over. Never a hard inject.
|
|
74
|
-
|
|
73
|
+
// (<html lang> / navigator) takes over. Never a hard inject. The getter
|
|
74
|
+
// rides along (issue #16): our client bundle activates with zero service
|
|
75
|
+
// deps, potentially BEFORE the locale service provides — the runtime
|
|
76
|
+
// re-polls it for ~2s and watches <html lang> until it does.
|
|
77
|
+
initI18n(ctx.get?.('locale'), () => ctx.get?.('locale'))
|
|
75
78
|
const client = createClient()
|
|
76
79
|
const controller = new BoardController(client)
|
|
77
80
|
|
package/src/shared/version.ts
CHANGED