dsh-taskboard 0.5.3 → 0.5.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 +11 -1
- package/lib/client.js +253 -26
- package/lib/host/execution.js +2 -1
- package/lib/host/execution.js.map +1 -1
- package/lib/host/tools.js +6 -2
- package/lib/host/tools.js.map +1 -1
- package/lib/shared/api.js.map +1 -1
- package/lib/shared/protocol.js +6 -4
- package/lib/shared/protocol.js.map +1 -1
- package/package.json +1 -1
- package/src/client/board/TaskBoard.tsx +2 -0
- package/src/client/board/TaskCard.tsx +43 -1
- package/src/client/board/TaskDetail.tsx +32 -6
- package/src/client/board/TaskFormModal.tsx +114 -6
- package/src/client/board/TemplateManager.tsx +4 -1
- package/src/client/controller.ts +29 -5
- package/src/client/index.ts +36 -5
- package/src/client/styles.ts +86 -8
- package/src/host/execution.ts +10 -3
- package/src/host/tools.ts +3 -2
- package/src/shared/api.ts +5 -5
- package/src/shared/protocol.ts +7 -4
- package/src/shared/version.ts +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"execution.js","names":[],"sources":["../../src/host/execution.ts"],"sourcesContent":["/**\n * Host execution service: runs a task through dsh's REAL session machinery —\n * a fresh agent+session inside the task's project workspace (creation carries\n * the pinned model when the task has one), the session is attached to the\n * workspace so it appears in the GUI's project session list, the effective\n * prompt is submitted as an ordinary user message, and the turn settlement\n * (turn/end reason) is folded back into the task's execution record.\n *\n * Every execution is a NEW session: clean context, no reuse of previous runs.\n *\n * @module dsh-taskboard/host/execution\n */\nimport {\n effectiveIsolation,\n effectivePrompt,\n newCommentId,\n newExecutionId,\n normalizeBody,\n type ExecutionRecord,\n type IsolationMode,\n type TaskRecord,\n} from '../shared/protocol.ts'\nimport { sanitizeBranchName, worktreePathOf, type GitFace, type SettlementFacts } from './git.ts'\nimport { MessageId } from './sdk.ts'\nimport type { TaskStore } from './store.ts'\n\n/** Default cap on concurrently running executions (env-overridable). */\nexport const DEFAULT_MAX_CONCURRENT = 3\n\n/** Narrow agents face (the registry's create, structurally). */\nexport interface AgentsFace {\n create(options: {\n sessionId: string\n meta?: { cwd?: string; agentPreset?: string }\n agentOptions?: { provider?: string; model?: string }\n /** Preset composition callback: mounts tools/persona into the agent's scoped context. */\n setup?: (agentCtx: unknown) => Promise<void> | void\n }): Promise<{\n agent: {\n id: string\n followup(message: unknown): void\n inject(message: unknown): void\n whenIdle(): Promise<void>\n }\n dispose(): Promise<void>\n }>\n}\n\n/**\n * The preset composition an execution session is built from — the shape\n * apiproxy's ensureSession produces: resolve → record on the session header,\n * mount → inside agents.create's setup callback.\n */\nexport interface AgentComposition {\n /** The resolved preset id recorded on the session header. */\n agentPreset: string\n /** Mounts the preset's plugins (tools, persona) into the agent's scope. */\n setup: (agentCtx: unknown) => Promise<void> | void\n}\n\n/** Narrow workspaces face for execution. */\nexport interface ExecutionWorkspaceFace {\n get(id: string): { id: string; path: string } | undefined\n attach(workspaceId: string, sessionId: string): Promise<void>\n}\n\n/** Narrow event-bus face for settlement listening. */\nexport interface EventsFace {\n onSessionEvent(listener: (sessionId: string, event: { type: string; data?: unknown }) => void): () => void\n}\n\n/** Everything the execution service needs. */\nexport interface ExecutionDeps {\n store: TaskStore\n agents: AgentsFace\n workspaces: ExecutionWorkspaceFace\n events: EventsFace\n now: () => number\n /** The deployment default model (fills sessions of unpinned tasks). */\n defaultModel?: () => { provider: string; model: string } | undefined\n /** Mint session ids (injectable for tests). */\n mintSessionId?: () => string\n /** Mint message ids (injectable for tests). */\n mintMessageId?: () => string\n /** Best-effort session rename (pins the session list title to the task title). */\n renameSession?: (sessionId: string, title: string) => void\n /** Max concurrently running executions across all tasks (default 3). */\n maxConcurrent?: number\n /**\n * Git face for worktree isolation (0.3.0). Absent → every worktree-mode\n * task degrades to the original directory with an isolationNote.\n */\n git?: GitFace\n /**\n * Resolve the preset composition for an execution session (0.3.3): hands\n * the session its tool set. Absent → sessions run on the bare host\n * composition (pre-preset behavior). A rejection fails the run through\n * the existing failure path — a broken preset never yields a half-composed\n * session (same rollback semantics as apiproxy).\n */\n composeAgent?: (presetId?: string) => Promise<AgentComposition | undefined>\n}\n\n/** Outcome of a run request (immediate; the run settles asynchronously). */\nexport type RunRequestResult =\n | { ok: true; executionId: string; sessionId: string }\n | { ok: false; error: string }\n\n/** Outcome of a cancel request. */\nexport type CancelRequestResult =\n | { ok: true; executionId: string }\n | { ok: false; error: string }\n\n/** Whether a turn/end payload closed with an error reason. */\nfunction isErrorTurnEnd(data: unknown): { message: string } | undefined {\n if (typeof data !== 'object' || data === null) return undefined\n const reason = (data as { reason?: unknown }).reason\n if (typeof reason !== 'object' || reason === null) return undefined\n const kind = (reason as { kind?: unknown }).kind\n if (kind !== 'error') return undefined\n const error = (reason as { error?: { message?: unknown } }).error\n const message = typeof error?.message === 'string' ? error.message : 'turn failed'\n console.error('[dsh-taskboard] turn error detail:', JSON.stringify(error)?.slice(0, 2000) ?? '')\n return { message }\n}\n\n/** Prepared worktree facts threaded through a live run (settlement evidence). */\nexport interface PreparedWorktree {\n branch: string\n worktreePath: string\n baseCommit: string\n /** True when an existing live worktree was kept as-is (续跑). */\n reused?: boolean\n}\n\n/** Per-run options. */\nexport interface RunOptions {\n /**\n * 续跑: keep a live worktree/branch exactly as-is (the previous agent's\n * commits and uncommitted changes survive) instead of resetting to the\n * main HEAD. Falls back to a fresh preparation when none is alive.\n */\n reuseWorktree?: boolean\n}\n\n/** One live execution tracked for settlement and cancellation. */\ninterface RunEntry {\n sessionId: string\n /** Worktree prepared for this run (evidence collection at ANY settlement). */\n prepared?: PreparedWorktree\n settle: () => void\n dispose: () => Promise<void>\n}\n\n/**\n * The execution service.\n */\nexport class ExecutionService {\n /** Live executions by execution id (settles and cancels remove entries). */\n private readonly runs = new Map<string, RunEntry>()\n\n /** Detaches the turn/end listener (plugin teardown — review P1). */\n private readonly unsubscribeEvents: () => void\n\n /** @param deps - store + agents + workspaces + events + clock. */\n constructor(private readonly deps: ExecutionDeps) {\n this.unsubscribeEvents = deps.events.onSessionEvent((sessionId, event) => {\n if (event.type !== 'turn/end') return\n // S7 (open question): ANY turn/end with an error reason fails the whole\n // execution and hands the task back. Whether the DSH session loop can\n // produce recoverable per-turn errors (and keep the session alive) needs\n // host-side confirmation; if it can, this should count consecutive\n // errors or wait for an explicit termination signal instead.\n const failure = isErrorTurnEnd(event.data)\n if (failure !== undefined) {\n this.noteFailure(sessionId, failure.message).catch(error => {\n console.error('[dsh-taskboard] failure settlement error:', error)\n })\n }\n })\n }\n\n /** Detach the settlement listener; safe to call once at plugin teardown. */\n dispose(): void {\n this.unsubscribeEvents()\n }\n\n /**\n * Best-effort evidence collection for a prepared run (fail-soft: undefined\n * on any git problem — settlement NEVER blocks on git).\n */\n private async collectEvidence(prepared: PreparedWorktree | undefined): Promise<SettlementFacts | undefined> {\n if (prepared === undefined || this.deps.git === undefined) return undefined\n try {\n return await this.deps.git.collect(prepared.worktreePath, prepared.baseCommit)\n } catch {\n return undefined\n }\n }\n\n /** Copy collected facts onto an execution record (in place). */\n private applyFacts(execution: ExecutionRecord, facts: SettlementFacts | undefined): void {\n if (facts === undefined) return\n if (facts.headCommit !== undefined) execution.headCommit = facts.headCommit\n execution.commits = facts.commits\n execution.commitsTotal = facts.commitsTotal\n execution.dirtyFiles = facts.dirtyFiles\n execution.dirtyFilesTotal = facts.dirtyFilesTotal\n execution.changedFiles = facts.changedFiles\n if (facts.diffStat !== undefined) execution.diffStat = facts.diffStat\n }\n\n /**\n * Record a turn failure against the running execution of that session and\n * give the task back. Resolves once the failure settlement has COMMITTED —\n * R2: the whenIdle rejection path awaits this (and only this) before\n * releasing its run entry, so a success settlement can never race it into\n * the ledger and record a failed run as succeeded.\n */\n private noteFailure(sessionId: string, message: string): Promise<void> {\n // The failed session may already have committed work — collect the\n // evidence (best effort) BEFORE marking the execution failed (0.3.1).\n const entry = [...this.runs.values()].find(e => e.sessionId === sessionId)\n return this.collectEvidence(entry?.prepared).then(facts =>\n this.deps.store.mutate('execution-recorded', (ledger) => {\n for (const task of ledger.tasks) {\n for (const execution of task.executions) {\n if (execution.sessionId === sessionId && execution.outcome === 'running') {\n execution.outcome = 'failed'\n execution.error = message.slice(0, 500)\n execution.endedAt = this.deps.now()\n this.applyFacts(execution, facts)\n // The failed session will not finish the work: hand the task back\n // instead of leaving it stuck in in_progress forever — and leave a\n // system comment so the GUI shows why.\n if (task.status === 'in_progress' && task.claimedBy === sessionId) {\n task.status = 'todo'\n task.updatedAt = this.deps.now()\n delete task.claimedBy\n delete task.claimedAt\n task.comments.push({\n id: newCommentId(),\n body: normalizeBody(`[系统] 执行失败:${message.slice(0, 300)};任务已退回待办。`),\n version: 1,\n createdAt: this.deps.now(),\n })\n }\n return [task]\n }\n }\n }\n return undefined\n }),\n ).then(() => { /* failure settlement committed */ })\n }\n\n /**\n * Patch one task's execution record in the ledger. R3 depth: a record that\n * already settled (cancelled/failed/succeeded) is never resurrected — the\n * startup path patches sessionId long after the gate opened, and a cancel\n * may have committed in between.\n */\n private async patchExecution(executionId: string, patch: Partial<ExecutionRecord>): Promise<void> {\n await this.deps.store.mutate('execution-recorded', (ledger) => {\n for (const task of ledger.tasks) {\n const execution = task.executions.find(e => e.id === executionId)\n if (execution !== undefined) {\n if (execution.outcome !== 'running') return undefined\n Object.assign(execution, patch)\n return [task]\n }\n }\n return undefined\n })\n }\n\n /**\n * Run one task now (manual button or scheduler tick).\n *\n * The in-progress gate and the execution-open write happen inside ONE\n * serial-queue mutation, so two overlapping run() calls (double click,\n * overlapping scheduler ticks) can never both pass — exactly one session\n * is opened per task.\n * @param taskId - the task to run.\n * @param trigger - what started it.\n * @param options - per-run options (`reuseWorktree` = 续跑).\n * @returns the immediate result; settlement lands in the ledger.\n */\n async run(taskId: string, trigger: ExecutionRecord['trigger'], options?: RunOptions): Promise<RunRequestResult> {\n const max = this.deps.maxConcurrent ?? DEFAULT_MAX_CONCURRENT\n if (this.runs.size >= max) {\n return { ok: false, error: `execution concurrency limit reached (${this.runs.size}/${max} running)` }\n }\n const task = this.deps.store.get(taskId)\n if (task === undefined || task.trashedAt !== undefined) {\n return { ok: false, error: `no task ${taskId}` }\n }\n const workspace = this.deps.workspaces.get(task.workspaceId)\n if (workspace === undefined) {\n return { ok: false, error: `unknown workspace ${task.workspaceId}` }\n }\n\n const executionId = newExecutionId()\n const sessionId = this.deps.mintSessionId?.() ?? `session-taskboard-${crypto.randomUUID()}`\n\n // 0. Resolve code isolation (plan §3.2): explicit 'none' → zero git calls;\n // 'worktree' (also the omitted default) → prepare below, degrading to\n // the original directory fail-soft on any git problem.\n const isolation: IsolationMode = effectiveIsolation(task)\n const branch = task.branch ?? sanitizeBranchName(task.title, task.id)\n const worktreePath = worktreePathOf(workspace.path, task.id)\n\n // 1. Open the execution record, flip the card to in_progress, and record\n // the executing session as the claim holder — atomically.\n let gate: string | undefined\n await this.deps.store.mutate('execution-recorded', (ledger) => {\n const target = ledger.tasks.find(t => t.id === taskId)\n if (target === undefined || target.trashedAt !== undefined) {\n gate = `no task ${taskId}`\n return undefined\n }\n if (target.status === 'in_progress') {\n gate = 'task is already in progress'\n return undefined\n }\n // S4: authoritative capacity check INSIDE the gate — counts ledger-wide\n // running executions, immune to the startup window (`runs` registers\n // only after agent creation, seconds later).\n const running = ledger.tasks.reduce((n, t) => n + t.executions.filter(e => e.outcome === 'running').length, 0)\n if (running >= max) {\n gate = `execution concurrency limit reached (${running}/${max} running)`\n return undefined\n }\n target.executions.push({\n id: executionId,\n trigger,\n startedAt: this.deps.now(),\n outcome: 'running',\n ...(isolation === 'none' ? { isolation: 'none' as const } : { isolation: 'worktree' as const, branch }),\n })\n target.status = 'in_progress'\n target.updatedAt = this.deps.now()\n target.updatedBy = { kind: 'system' }\n target.claimedBy = sessionId\n target.claimedAt = this.deps.now()\n return [target]\n })\n if (gate !== undefined) return { ok: false, error: gate }\n\n // 1b. Worktree preparation (fail-soft): any failure degrades this run to\n // the original directory with an isolationNote — the ledger and the\n // execution pipeline itself never fail over git.\n let isolationNote: string | undefined\n let prepared: PreparedWorktree | undefined\n if (isolation === 'worktree') {\n const outcome = await this.prepareIsolation(task, workspace.path, worktreePath, branch, options?.reuseWorktree === true)\n if (outcome.prepared !== undefined) {\n prepared = outcome.prepared\n // Persist the isolation facts of the run (branch is already on the\n // record from the gate mutation).\n await this.patchExecution(executionId, {\n worktreePath: outcome.prepared.worktreePath,\n baseCommit: outcome.prepared.baseCommit,\n })\n } else {\n isolationNote = outcome.note\n // Degraded run: clear the optimistic worktree markers.\n await this.patchExecution(executionId, { isolation: 'none', isolationNote, branch: undefined, worktreePath: undefined, baseCommit: undefined })\n }\n }\n\n // 2. Create the fresh agent+session inside the task's project, carrying\n // the pinned model — or the deployment default when unpinned (the\n // persona template renders {{model}}, so the session always needs one).\n // The session cwd is ALWAYS the project root: DSH's session model\n // requires cwd === the workspace path EXACTLY (attachSession validates\n // it, the sidebar groups by it, and the file sandbox takes it as the\n // workspace-write boundary) — a subdirectory cwd (the worktree) breaks\n // all three. The worktree is instead handed to the agent explicitly in\n // the framing line below.\n // Preset composition (0.3.3): resolve BEFORE creation so the header\n // snapshots `agentPreset` and the setup callback mounts the preset's\n // tools/persona into the agent's scope. undefined composeAgent (or an\n // absent preset roster) keeps the bare host composition.\n let composition: AgentComposition | undefined\n try {\n composition = this.deps.composeAgent === undefined ? undefined : await this.deps.composeAgent(task.presetId)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n await this.patchExecution(executionId, { outcome: 'failed', error: `preset 组合失败:${message.slice(0, 400)}`, endedAt: this.deps.now() })\n await this.revertProgress(taskId)\n // S1: a run that never started must not leave its worktree behind.\n if (prepared !== undefined && this.deps.git !== undefined) {\n try { await this.deps.git.removeWorktree(workspace.path, prepared.worktreePath) } catch { /* best effort (dirty worktrees are kept) */ }\n }\n return { ok: false, error: `preset composition failed: ${message}` }\n }\n let handle: Awaited<ReturnType<AgentsFace['create']>>\n try {\n const model = task.model ?? this.deps.defaultModel?.()\n handle = await this.deps.agents.create({\n sessionId,\n meta: {\n cwd: workspace.path,\n ...(composition !== undefined ? { agentPreset: composition.agentPreset } : {}),\n },\n ...(model !== undefined ? { agentOptions: { provider: model.provider, model: model.model } } : {}),\n ...(composition !== undefined ? { setup: composition.setup } : {}),\n })\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n await this.patchExecution(executionId, { outcome: 'failed', error: message.slice(0, 500), endedAt: this.deps.now() })\n await this.revertProgress(taskId)\n // S1: a run that never started must not leave its worktree behind.\n if (prepared !== undefined && this.deps.git !== undefined) {\n try { await this.deps.git.removeWorktree(workspace.path, prepared.worktreePath) } catch { /* best effort (dirty worktrees are kept) */ }\n }\n return { ok: false, error: message }\n }\n\n // R3: the startup path above awaited seconds of git + agent work. A\n // cancel() that landed inside that window already settled the execution\n // (cancelled + task back to todo) — with nothing registered in `runs`,\n // it could not dispose the agent this path was about to create. Re-verify\n // INSIDE the queue (after any enqueued cancel committed) BEFORE injecting:\n // a cancelled card must not gain a zombie session that burns tokens and\n // edits files while the task sits in todo, re-runnable by anyone.\n const stillRunning = await this.deps.store.read(ledger =>\n ledger.tasks.some(t => t.executions.some(e => e.id === executionId && e.outcome === 'running')))\n if (!stillRunning) {\n await handle.dispose().catch(() => { /* best effort */ })\n // S1: do not leave the startup artifacts behind a cancelled run either.\n if (prepared !== undefined && this.deps.git !== undefined) {\n try { await this.deps.git.removeWorktree(workspace.path, prepared.worktreePath) } catch { /* best effort */ }\n }\n return { ok: false, error: 'cancelled during startup' }\n }\n\n // 3. Attach the session to the workspace (GUI project session list).\n await this.deps.workspaces.attach(task.workspaceId, sessionId).catch(() => { /* cosmetic */ })\n\n // 3b. Best-effort rename: pin the session title to the task title so the\n // session list shows the task name (a user-sourced title also stops\n // automatic first-prompt retitling).\n try {\n this.deps.renameSession?.(sessionId, task.title)\n } catch { /* cosmetic */ }\n\n // 4. Record the session id (execution is really started now).\n await this.patchExecution(executionId, { sessionId })\n\n // 5. Submit the opening pair and settle on quiescence (turn/end errors\n // were already folded by the listener). Two messages, ONE turn:\n // - inject() queues the plugin framing line (next-step, no wake); it\n // renders as a plugin context row in the conversation.\n // - followup() queues the card body as a normal user message\n // (next-turn, wakes the driver). At claim time the loop drains ALL\n // next-step messages plus the one next-turn message into a single\n // turn — framing first, then the user bubble.\n handle.agent.inject({\n id: this.deps.mintMessageId?.() ?? MessageId(`msg-taskboard-${crypto.randomUUID()}`),\n role: 'user' as const,\n content: [{ type: 'text' as const, text: this.pluginFraming(task, prepared, isolationNote) }],\n source: { kind: 'plugin' as const, plugin: 'dsh-taskboard' },\n })\n handle.agent.followup({\n id: this.deps.mintMessageId?.() ?? MessageId(`msg-taskboard-${crypto.randomUUID()}`),\n role: 'user' as const,\n content: [{ type: 'text' as const, text: this.userBody(task) }],\n source: { kind: 'user' as const },\n })\n\n // 6. Settlement watcher: mark succeeded, release the executing session's\n // hold, collect the worktree evidence (commits / dirty / diff), and —\n // when the session did NOT follow the handoff protocol — auto-move the\n // card to in_review with a system comment.\n const settle = (): void => {\n this.runs.delete(executionId)\n void this.settleExecution(executionId, sessionId, prepared)\n }\n this.runs.set(executionId, { sessionId, ...(prepared !== undefined ? { prepared } : {}), settle, dispose: () => handle.dispose() })\n // R2: the rejection path owns its state transition EXCLUSIVELY — the old\n // code also called settle() here, racing two evidence collections whose\n // mutations both checked outcome === 'running': whoever committed first\n // won, so a run that never reached quiescence could be recorded as\n // succeeded (and auto-moved to in_review). Now only the failure\n // settlement writes, and the run entry is released after it commits.\n void handle.agent.whenIdle().then(settle, () => {\n this.noteFailure(sessionId, 'agent did not reach quiescence')\n .then(() => { this.runs.delete(executionId) })\n .catch(() => { this.runs.delete(executionId) })\n })\n\n return { ok: true, executionId, sessionId }\n }\n\n /**\n * Settle one execution: collect worktree facts first (fail-soft — git\n * problems never block settlement), then commit outcome + release + the\n * protocol-auto-review move in ONE ledger mutation.\n */\n private async settleExecution(\n executionId: string,\n sessionId: string,\n prepared: PreparedWorktree | undefined,\n ): Promise<void> {\n const facts = await this.collectEvidence(prepared)\n await this.deps.store.mutate('execution-recorded', (ledger) => {\n for (const t of ledger.tasks) {\n const execution = t.executions.find(e => e.id === executionId)\n if (execution !== undefined && execution.outcome === 'running') {\n const now = this.deps.now()\n execution.outcome = 'succeeded'\n execution.endedAt = now\n this.applyFacts(execution, facts)\n if (t.status === 'in_progress' && t.claimedBy === sessionId) {\n delete t.claimedBy\n delete t.claimedAt\n }\n if (t.status === 'in_progress') {\n const commented = t.comments.some(c => c.threadId === sessionId)\n t.comments.push({\n id: newCommentId(),\n body: normalizeBody(commented\n ? '[系统] 执行会话已结束并留有评论,但未移至待验收;系统自动移入待验收。'\n : '[系统] 执行会话已结束,但未按协议交接(无评论、未移至待验收);系统自动移入待验收,请审查后退回或验收。'),\n version: 1,\n createdAt: now,\n })\n t.status = 'in_review'\n t.updatedAt = now\n t.updatedBy = { kind: 'system' }\n }\n return [t]\n }\n }\n return undefined\n })\n }\n\n /**\n * Prepare the dedicated worktree for a run (fail-soft): detect git, then\n * create/reset the fixed task branch — fresh baseline, or keep a live\n * worktree as-is for 续跑. On success the branch name is pinned onto the\n * task once (renames never change it); on any failure the run degrades\n * with a human-readable note.\n */\n private async prepareIsolation(\n task: TaskRecord,\n workspacePath: string,\n worktreePath: string,\n branch: string,\n reuse: boolean,\n ): Promise<{ prepared?: PreparedWorktree; note?: string }> {\n const git = this.deps.git\n if (git === undefined) return { note: 'git 集成不可用,已在原目录执行' }\n let inside = false\n try {\n inside = await git.detect(workspacePath)\n } catch { /* fail-soft */ }\n if (!inside) {\n // Distinguish 未装 git from 非 git 仓库 (0.3.1): probe the binary so the\n // degradation note names the real cause.\n let hasBinary = true\n try {\n hasBinary = await git.binaryAvailable()\n } catch { /* fail-soft → treat as repo-side */ }\n return { note: hasBinary ? '当前项目不是 git 仓库,已在原目录执行' : 'git 不可用(未安装或不在 PATH),已在原目录执行' }\n }\n let info\n try {\n info = await git.prepareWorktree(workspacePath, worktreePath, branch, reuse ? 'reuse' : 'fresh')\n } catch { /* fail-soft */ }\n if (info === undefined) return { note: 'worktree 准备失败(git 报错或目录被占用),已在原目录执行' }\n // Pin the branch name at first SUCCESSFUL creation (§9: 改名不改分支).\n if (task.branch === undefined) {\n await this.deps.store.mutate('task-updated', (ledger) => {\n const target = ledger.tasks.find(t => t.id === task.id)\n if (target !== undefined && target.branch === undefined) {\n target.branch = branch\n return [target]\n }\n return undefined\n })\n }\n return {\n prepared: {\n branch: info.branch,\n worktreePath: info.path,\n baseCommit: info.baseCommit,\n ...(info.reused === true ? { reused: true } : {}),\n },\n }\n }\n\n /** How many executions are currently running (for the concurrency cap). */\n inFlight(): number {\n return this.runs.size\n }\n\n /**\n * Cancel the running execution of a task (user action): stop the agent\n * session, mark the execution cancelled, and hand the task back to todo.\n * @param taskId - the task whose execution should be stopped.\n * @returns the immediate result.\n */\n async cancel(taskId: string): Promise<CancelRequestResult> {\n const task = this.deps.store.get(taskId)\n if (task === undefined) return { ok: false, error: `no task ${taskId}` }\n const running = [...task.executions].reverse().find(e => e.outcome === 'running')\n if (running === undefined) return { ok: false, error: 'no running execution' }\n\n const entry = this.runs.get(running.id)\n this.runs.delete(running.id)\n // Stop the agent first (best effort): dispose stops the loop, unregisters\n // the agent, and removes its session. A late whenIdle settlement no-ops —\n // the record is no longer 'running'.\n try {\n await entry?.dispose()\n } catch { /* already gone */ }\n\n // The cancelled session may already have committed work — keep the\n // evidence (best effort) so the user can inspect or 续跑 (0.3.1).\n const facts = await this.collectEvidence(entry?.prepared)\n let settled = false\n await this.deps.store.mutate('execution-recorded', (ledger) => {\n const target = ledger.tasks.find(t => t.id === taskId)\n if (target === undefined) return undefined\n const execution = target.executions.find(e => e.id === running.id)\n if (execution === undefined || execution.outcome !== 'running') return undefined\n settled = true\n execution.outcome = 'cancelled'\n execution.endedAt = this.deps.now()\n this.applyFacts(execution, facts)\n if (target.status === 'in_progress') {\n target.status = 'todo'\n target.updatedAt = this.deps.now()\n delete target.claimedBy\n delete target.claimedAt\n }\n return [target]\n })\n // The execution may have settled (succeeded/failed) between the stale\n // read above and this mutation — a no-op cancel must NOT report success\n // (the GUI used to show 取消成功 for an already-succeeded run, review P1).\n if (!settled) return { ok: false, error: 'execution already settled' }\n return { ok: true, executionId: running.id }\n }\n\n /**\n * Startup reconciliation after a host restart: executions left `running`\n * by the previous process can never settle here (their settlement watchers\n * died with it), so mark them failed and hand their tasks back to todo.\n */\n async reconcile(): Promise<void> {\n await this.deps.store.mutate('execution-recorded', (ledger) => {\n const now = this.deps.now()\n const touched: TaskRecord[] = []\n for (const task of ledger.tasks) {\n let dirty = false\n for (const execution of task.executions) {\n if (execution.outcome === 'running') {\n execution.outcome = 'failed'\n execution.error = 'interrupted by host restart'\n execution.endedAt = now\n dirty = true\n }\n }\n if (!dirty) continue\n if (task.status === 'in_progress') {\n task.status = 'todo'\n task.updatedAt = now\n delete task.claimedBy\n delete task.claimedAt\n }\n touched.push(task)\n }\n return touched.length > 0 ? touched : undefined\n })\n }\n\n /**\n * The plugin framing line (rendered as a plugin context row): task head,\n * already-claimed state, and the handoff protocol — everything the session\n * must know about the board. The task id appears exactly once (here); the\n * protocol steps below refer to it as 本任务. Isolated runs add one line\n * steering the session onto its dedicated branch (commits are the evidence\n * the user reviews at merge time); 续跑 and degraded runs each add their\n * own steering line (0.3.1).\n * @param task - the task.\n * @param prepared - worktree facts when this run is isolated.\n * @param degradeNote - why a worktree task degraded to the main directory.\n */\n private pluginFraming(task: TaskRecord, prepared?: PreparedWorktree, degradeNote?: string): string {\n let text = `【任务看板】${task.title}(ID: ${task.id})\\n`\n + `本会话由任务看板执行服务启动,任务已置为进行中——无需认领;「已完成」仅限用户在界面操作(代码已限制,移了会被拒)。\\n`\n + `完成后按序交接:\\n`\n + `1. taskboard_get 读取本任务,取得最新 version\\n`\n + `2. taskboard_execution_report 提交结构化执行报告(做了什么/改了哪些文件/如何验证/剩余风险;提交与评论不冲突,都会展示给验收人)\\n`\n + `3. taskboard_comment_add 留评论:做了什么改动 / 如何验证 / 剩余风险\\n`\n + `4. taskboard_move 将本任务移至待验收 in_review(带 ifVersion)\\n`\n + `若无法完成:留评论说明原因,将任务移回待办 todo。`\n if (task.checklist !== undefined && task.checklist.length > 0) {\n const items = task.checklist\n .map((item, index) => `${item.checked ? '☑' : '☐'} ${index + 1}. ${item.text}${item.note !== undefined ? `(证据: ${item.note})` : ''}`)\n .join('\\n')\n const done = task.checklist.filter(i => i.checked).length\n text += `\\n本任务有验收清单(DoD,${done}/${task.checklist.length} 已完成)——按清单干活:\\n${items}\\n完成一项就用 taskboard_checklist(action=check,附 note 证据)勾选;未完成项会在验收时高亮,全部完成再移待验收。需要补充验收项也可用 action=add 追加。`\n }\n if (prepared !== undefined) {\n if (prepared.reused === true) {\n text += `\\n本任务启用了 Git Worktree 隔离,且本次为续跑:任务工作目录是独立分支 ${prepared.branch} 的 worktree——\\n${prepared.worktreePath}\\n上一次执行的改动与提交都保留在原处——请先查看已有改动(git status / git log)再继续,避免重复劳动,并把新完成的工作提交到该分支。`\n } else {\n text += `\\n本任务启用了 Git Worktree 隔离:任务工作目录是独立分支 ${prepared.branch} 的全新 worktree——\\n${prepared.worktreePath}\\n(全新检出,不含 node_modules/构建产物,构建或测试前可能需要先安装依赖)。\\n⚠ 边界纪律:你的会话根目录是整个项目,但本任务的全部改动必须只发生在上述 worktree 目录内——命令用 workdir 指向它、文件读写用它的绝对路径;不要改动主工作区的任何其它文件;把完成的工作提交(git commit)到该分支,验收将基于该分支的提交记录合并。`\n }\n } else if (degradeNote !== undefined) {\n text += `\\n⚠ 本次执行未能建立隔离,正在主项目目录中工作(原因:${degradeNote})。该目录可能有他人未提交的改动:动手前先 git status 检查现状,改动尽量集中,结束时在评论中说明动了哪些文件;避免把未经验证的改动直接提交到主分支。`\n }\n return text\n }\n\n /**\n * The card body as a normal user bubble: the effective prompt (title+\n * description, with the explicit prompt appended when set) with template\n * variables resolved from\n * the task's own history at submit time (valuable for recurring patrols):\n * `{{lastExecution}}` → the previous execution's trigger/outcome/error;\n * `{{lastComments}}` → the last three comments (who + body).\n */\n private userBody(task: TaskRecord): string {\n const lastExec = [...task.executions].reverse().find(e => e.outcome !== 'running')\n const lastExecText = lastExec === undefined\n ? '(无)'\n : `${lastExec.trigger} · ${lastExec.outcome}${lastExec.error !== undefined ? ` · ${lastExec.error.slice(0, 200)}` : ''} · ${lastExec.startedAt !== undefined ? new Date(lastExec.startedAt).toISOString() : '?'}`\n const lastCommentsText = task.comments.slice(-3)\n .map(c => `[${c.threadId !== undefined ? 'agent' : 'user'}] ${c.body}`)\n .join('\\n') || '(无)'\n return effectivePrompt(task)\n .replace(/\\{\\{lastExecution\\}\\}/g, lastExecText)\n .replace(/\\{\\{lastComments\\}\\}/g, lastCommentsText)\n }\n\n /** Move a task back out of in_progress (and release its hold) after a failed start. */\n private async revertProgress(taskId: string): Promise<void> {\n await this.deps.store.mutate('execution-recorded', (ledger) => {\n const target = ledger.tasks.find(t => t.id === taskId)\n if (target !== undefined && target.status === 'in_progress') {\n target.status = 'todo'\n target.updatedAt = this.deps.now()\n delete target.claimedBy\n delete target.claimedAt\n return [target]\n }\n return undefined\n })\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"execution.js","names":[],"sources":["../../src/host/execution.ts"],"sourcesContent":["/**\n * Host execution service: runs a task through dsh's REAL session machinery —\n * a fresh agent+session inside the task's project workspace (creation carries\n * the pinned model when the task has one), the session is attached to the\n * workspace so it appears in the GUI's project session list, the effective\n * prompt is submitted as an ordinary user message, and the turn settlement\n * (turn/end reason) is folded back into the task's execution record.\n *\n * Every execution is a NEW session: clean context, no reuse of previous runs.\n *\n * @module dsh-taskboard/host/execution\n */\nimport {\n effectiveIsolation,\n effectivePrompt,\n newCommentId,\n newExecutionId,\n normalizeBody,\n type ExecutionRecord,\n type IsolationMode,\n type TaskModel,\n type TaskRecord,\n} from '../shared/protocol.ts'\nimport { sanitizeBranchName, worktreePathOf, type GitFace, type SettlementFacts } from './git.ts'\nimport { MessageId } from './sdk.ts'\nimport type { TaskStore } from './store.ts'\n\n/** Default cap on concurrently running executions (env-overridable). */\nexport const DEFAULT_MAX_CONCURRENT = 3\n\n/** Narrow agents face (the registry's create, structurally). */\nexport interface AgentsFace {\n create(options: {\n sessionId: string\n meta?: { cwd?: string; agentPreset?: string }\n agentOptions?: { provider?: string; model?: string; reasoningEffort?: string }\n /** Preset composition callback: mounts tools/persona into the agent's scoped context. */\n setup?: (agentCtx: unknown) => Promise<void> | void\n }): Promise<{\n agent: {\n id: string\n followup(message: unknown): void\n inject(message: unknown): void\n whenIdle(): Promise<void>\n }\n dispose(): Promise<void>\n }>\n}\n\n/**\n * The preset composition an execution session is built from — the shape\n * apiproxy's ensureSession produces: resolve → record on the session header,\n * mount → inside agents.create's setup callback.\n */\nexport interface AgentComposition {\n /** The resolved preset id recorded on the session header. */\n agentPreset: string\n /** Mounts the preset's plugins (tools, persona) into the agent's scope. */\n setup: (agentCtx: unknown) => Promise<void> | void\n}\n\n/** Narrow workspaces face for execution. */\nexport interface ExecutionWorkspaceFace {\n get(id: string): { id: string; path: string } | undefined\n attach(workspaceId: string, sessionId: string): Promise<void>\n}\n\n/** Narrow event-bus face for settlement listening. */\nexport interface EventsFace {\n onSessionEvent(listener: (sessionId: string, event: { type: string; data?: unknown }) => void): () => void\n}\n\n/** Everything the execution service needs. */\nexport interface ExecutionDeps {\n store: TaskStore\n agents: AgentsFace\n workspaces: ExecutionWorkspaceFace\n events: EventsFace\n now: () => number\n /** The deployment default model (fills sessions of unpinned tasks). */\n defaultModel?: () => TaskModel | undefined\n /** Mint session ids (injectable for tests). */\n mintSessionId?: () => string\n /** Mint message ids (injectable for tests). */\n mintMessageId?: () => string\n /** Best-effort session rename (pins the session list title to the task title). */\n renameSession?: (sessionId: string, title: string) => void\n /** Max concurrently running executions across all tasks (default 3). */\n maxConcurrent?: number\n /**\n * Git face for worktree isolation (0.3.0). Absent → every worktree-mode\n * task degrades to the original directory with an isolationNote.\n */\n git?: GitFace\n /**\n * Resolve the preset composition for an execution session (0.3.3): hands\n * the session its tool set. Absent → sessions run on the bare host\n * composition (pre-preset behavior). A rejection fails the run through\n * the existing failure path — a broken preset never yields a half-composed\n * session (same rollback semantics as apiproxy).\n */\n composeAgent?: (presetId?: string) => Promise<AgentComposition | undefined>\n}\n\n/** Outcome of a run request (immediate; the run settles asynchronously). */\nexport type RunRequestResult =\n | { ok: true; executionId: string; sessionId: string }\n | { ok: false; error: string }\n\n/** Outcome of a cancel request. */\nexport type CancelRequestResult =\n | { ok: true; executionId: string }\n | { ok: false; error: string }\n\n/** Whether a turn/end payload closed with an error reason. */\nfunction isErrorTurnEnd(data: unknown): { message: string } | undefined {\n if (typeof data !== 'object' || data === null) return undefined\n const reason = (data as { reason?: unknown }).reason\n if (typeof reason !== 'object' || reason === null) return undefined\n const kind = (reason as { kind?: unknown }).kind\n if (kind !== 'error') return undefined\n const error = (reason as { error?: { message?: unknown } }).error\n const message = typeof error?.message === 'string' ? error.message : 'turn failed'\n console.error('[dsh-taskboard] turn error detail:', JSON.stringify(error)?.slice(0, 2000) ?? '')\n return { message }\n}\n\n/** Prepared worktree facts threaded through a live run (settlement evidence). */\nexport interface PreparedWorktree {\n branch: string\n worktreePath: string\n baseCommit: string\n /** True when an existing live worktree was kept as-is (续跑). */\n reused?: boolean\n}\n\n/** Per-run options. */\nexport interface RunOptions {\n /**\n * 续跑: keep a live worktree/branch exactly as-is (the previous agent's\n * commits and uncommitted changes survive) instead of resetting to the\n * main HEAD. Falls back to a fresh preparation when none is alive.\n */\n reuseWorktree?: boolean\n}\n\n/** One live execution tracked for settlement and cancellation. */\ninterface RunEntry {\n sessionId: string\n /** Worktree prepared for this run (evidence collection at ANY settlement). */\n prepared?: PreparedWorktree\n settle: () => void\n dispose: () => Promise<void>\n}\n\n/**\n * The execution service.\n */\nexport class ExecutionService {\n /** Live executions by execution id (settles and cancels remove entries). */\n private readonly runs = new Map<string, RunEntry>()\n\n /** Detaches the turn/end listener (plugin teardown — review P1). */\n private readonly unsubscribeEvents: () => void\n\n /** @param deps - store + agents + workspaces + events + clock. */\n constructor(private readonly deps: ExecutionDeps) {\n this.unsubscribeEvents = deps.events.onSessionEvent((sessionId, event) => {\n if (event.type !== 'turn/end') return\n // S7 (open question): ANY turn/end with an error reason fails the whole\n // execution and hands the task back. Whether the DSH session loop can\n // produce recoverable per-turn errors (and keep the session alive) needs\n // host-side confirmation; if it can, this should count consecutive\n // errors or wait for an explicit termination signal instead.\n const failure = isErrorTurnEnd(event.data)\n if (failure !== undefined) {\n this.noteFailure(sessionId, failure.message).catch(error => {\n console.error('[dsh-taskboard] failure settlement error:', error)\n })\n }\n })\n }\n\n /** Detach the settlement listener; safe to call once at plugin teardown. */\n dispose(): void {\n this.unsubscribeEvents()\n }\n\n /**\n * Best-effort evidence collection for a prepared run (fail-soft: undefined\n * on any git problem — settlement NEVER blocks on git).\n */\n private async collectEvidence(prepared: PreparedWorktree | undefined): Promise<SettlementFacts | undefined> {\n if (prepared === undefined || this.deps.git === undefined) return undefined\n try {\n return await this.deps.git.collect(prepared.worktreePath, prepared.baseCommit)\n } catch {\n return undefined\n }\n }\n\n /** Copy collected facts onto an execution record (in place). */\n private applyFacts(execution: ExecutionRecord, facts: SettlementFacts | undefined): void {\n if (facts === undefined) return\n if (facts.headCommit !== undefined) execution.headCommit = facts.headCommit\n execution.commits = facts.commits\n execution.commitsTotal = facts.commitsTotal\n execution.dirtyFiles = facts.dirtyFiles\n execution.dirtyFilesTotal = facts.dirtyFilesTotal\n execution.changedFiles = facts.changedFiles\n if (facts.diffStat !== undefined) execution.diffStat = facts.diffStat\n }\n\n /**\n * Record a turn failure against the running execution of that session and\n * give the task back. Resolves once the failure settlement has COMMITTED —\n * R2: the whenIdle rejection path awaits this (and only this) before\n * releasing its run entry, so a success settlement can never race it into\n * the ledger and record a failed run as succeeded.\n */\n private noteFailure(sessionId: string, message: string): Promise<void> {\n // The failed session may already have committed work — collect the\n // evidence (best effort) BEFORE marking the execution failed (0.3.1).\n const entry = [...this.runs.values()].find(e => e.sessionId === sessionId)\n return this.collectEvidence(entry?.prepared).then(facts =>\n this.deps.store.mutate('execution-recorded', (ledger) => {\n for (const task of ledger.tasks) {\n for (const execution of task.executions) {\n if (execution.sessionId === sessionId && execution.outcome === 'running') {\n execution.outcome = 'failed'\n execution.error = message.slice(0, 500)\n execution.endedAt = this.deps.now()\n this.applyFacts(execution, facts)\n // The failed session will not finish the work: hand the task back\n // instead of leaving it stuck in in_progress forever — and leave a\n // system comment so the GUI shows why.\n if (task.status === 'in_progress' && task.claimedBy === sessionId) {\n task.status = 'todo'\n task.updatedAt = this.deps.now()\n delete task.claimedBy\n delete task.claimedAt\n task.comments.push({\n id: newCommentId(),\n body: normalizeBody(`[系统] 执行失败:${message.slice(0, 300)};任务已退回待办。`),\n version: 1,\n createdAt: this.deps.now(),\n })\n }\n return [task]\n }\n }\n }\n return undefined\n }),\n ).then(() => { /* failure settlement committed */ })\n }\n\n /**\n * Patch one task's execution record in the ledger. R3 depth: a record that\n * already settled (cancelled/failed/succeeded) is never resurrected — the\n * startup path patches sessionId long after the gate opened, and a cancel\n * may have committed in between.\n */\n private async patchExecution(executionId: string, patch: Partial<ExecutionRecord>): Promise<void> {\n await this.deps.store.mutate('execution-recorded', (ledger) => {\n for (const task of ledger.tasks) {\n const execution = task.executions.find(e => e.id === executionId)\n if (execution !== undefined) {\n if (execution.outcome !== 'running') return undefined\n Object.assign(execution, patch)\n return [task]\n }\n }\n return undefined\n })\n }\n\n /**\n * Run one task now (manual button or scheduler tick).\n *\n * The in-progress gate and the execution-open write happen inside ONE\n * serial-queue mutation, so two overlapping run() calls (double click,\n * overlapping scheduler ticks) can never both pass — exactly one session\n * is opened per task.\n * @param taskId - the task to run.\n * @param trigger - what started it.\n * @param options - per-run options (`reuseWorktree` = 续跑).\n * @returns the immediate result; settlement lands in the ledger.\n */\n async run(taskId: string, trigger: ExecutionRecord['trigger'], options?: RunOptions): Promise<RunRequestResult> {\n const max = this.deps.maxConcurrent ?? DEFAULT_MAX_CONCURRENT\n if (this.runs.size >= max) {\n return { ok: false, error: `execution concurrency limit reached (${this.runs.size}/${max} running)` }\n }\n const task = this.deps.store.get(taskId)\n if (task === undefined || task.trashedAt !== undefined) {\n return { ok: false, error: `no task ${taskId}` }\n }\n const workspace = this.deps.workspaces.get(task.workspaceId)\n if (workspace === undefined) {\n return { ok: false, error: `unknown workspace ${task.workspaceId}` }\n }\n\n const executionId = newExecutionId()\n const sessionId = this.deps.mintSessionId?.() ?? `session-taskboard-${crypto.randomUUID()}`\n\n // 0. Resolve code isolation (plan §3.2): explicit 'none' → zero git calls;\n // 'worktree' (also the omitted default) → prepare below, degrading to\n // the original directory fail-soft on any git problem.\n const isolation: IsolationMode = effectiveIsolation(task)\n const branch = task.branch ?? sanitizeBranchName(task.title, task.id)\n const worktreePath = worktreePathOf(workspace.path, task.id)\n\n // 1. Open the execution record, flip the card to in_progress, and record\n // the executing session as the claim holder — atomically.\n let gate: string | undefined\n await this.deps.store.mutate('execution-recorded', (ledger) => {\n const target = ledger.tasks.find(t => t.id === taskId)\n if (target === undefined || target.trashedAt !== undefined) {\n gate = `no task ${taskId}`\n return undefined\n }\n if (target.status === 'in_progress') {\n gate = 'task is already in progress'\n return undefined\n }\n // S4: authoritative capacity check INSIDE the gate — counts ledger-wide\n // running executions, immune to the startup window (`runs` registers\n // only after agent creation, seconds later).\n const running = ledger.tasks.reduce((n, t) => n + t.executions.filter(e => e.outcome === 'running').length, 0)\n if (running >= max) {\n gate = `execution concurrency limit reached (${running}/${max} running)`\n return undefined\n }\n target.executions.push({\n id: executionId,\n trigger,\n startedAt: this.deps.now(),\n outcome: 'running',\n ...(isolation === 'none' ? { isolation: 'none' as const } : { isolation: 'worktree' as const, branch }),\n })\n target.status = 'in_progress'\n target.updatedAt = this.deps.now()\n target.updatedBy = { kind: 'system' }\n target.claimedBy = sessionId\n target.claimedAt = this.deps.now()\n return [target]\n })\n if (gate !== undefined) return { ok: false, error: gate }\n\n // 1b. Worktree preparation (fail-soft): any failure degrades this run to\n // the original directory with an isolationNote — the ledger and the\n // execution pipeline itself never fail over git.\n let isolationNote: string | undefined\n let prepared: PreparedWorktree | undefined\n if (isolation === 'worktree') {\n const outcome = await this.prepareIsolation(task, workspace.path, worktreePath, branch, options?.reuseWorktree === true)\n if (outcome.prepared !== undefined) {\n prepared = outcome.prepared\n // Persist the isolation facts of the run (branch is already on the\n // record from the gate mutation).\n await this.patchExecution(executionId, {\n worktreePath: outcome.prepared.worktreePath,\n baseCommit: outcome.prepared.baseCommit,\n })\n } else {\n isolationNote = outcome.note\n // Degraded run: clear the optimistic worktree markers.\n await this.patchExecution(executionId, { isolation: 'none', isolationNote, branch: undefined, worktreePath: undefined, baseCommit: undefined })\n }\n }\n\n // 2. Create the fresh agent+session inside the task's project, carrying\n // the pinned model — or the deployment default when unpinned (the\n // persona template renders {{model}}, so the session always needs one).\n // The session cwd is ALWAYS the project root: DSH's session model\n // requires cwd === the workspace path EXACTLY (attachSession validates\n // it, the sidebar groups by it, and the file sandbox takes it as the\n // workspace-write boundary) — a subdirectory cwd (the worktree) breaks\n // all three. The worktree is instead handed to the agent explicitly in\n // the framing line below.\n // Preset composition (0.3.3): resolve BEFORE creation so the header\n // snapshots `agentPreset` and the setup callback mounts the preset's\n // tools/persona into the agent's scope. undefined composeAgent (or an\n // absent preset roster) keeps the bare host composition.\n let composition: AgentComposition | undefined\n try {\n composition = this.deps.composeAgent === undefined ? undefined : await this.deps.composeAgent(task.presetId)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n await this.patchExecution(executionId, { outcome: 'failed', error: `preset 组合失败:${message.slice(0, 400)}`, endedAt: this.deps.now() })\n await this.revertProgress(taskId)\n // S1: a run that never started must not leave its worktree behind.\n if (prepared !== undefined && this.deps.git !== undefined) {\n try { await this.deps.git.removeWorktree(workspace.path, prepared.worktreePath) } catch { /* best effort (dirty worktrees are kept) */ }\n }\n return { ok: false, error: `preset composition failed: ${message}` }\n }\n let handle: Awaited<ReturnType<AgentsFace['create']>>\n try {\n const model = task.model ?? this.deps.defaultModel?.()\n handle = await this.deps.agents.create({\n sessionId,\n meta: {\n cwd: workspace.path,\n ...(composition !== undefined ? { agentPreset: composition.agentPreset } : {}),\n },\n ...(model !== undefined ? {\n agentOptions: {\n provider: model.provider,\n model: model.model,\n ...(model.reasoningEffort !== undefined ? { reasoningEffort: model.reasoningEffort } : {}),\n },\n } : {}),\n ...(composition !== undefined ? { setup: composition.setup } : {}),\n })\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n await this.patchExecution(executionId, { outcome: 'failed', error: message.slice(0, 500), endedAt: this.deps.now() })\n await this.revertProgress(taskId)\n // S1: a run that never started must not leave its worktree behind.\n if (prepared !== undefined && this.deps.git !== undefined) {\n try { await this.deps.git.removeWorktree(workspace.path, prepared.worktreePath) } catch { /* best effort (dirty worktrees are kept) */ }\n }\n return { ok: false, error: message }\n }\n\n // R3: the startup path above awaited seconds of git + agent work. A\n // cancel() that landed inside that window already settled the execution\n // (cancelled + task back to todo) — with nothing registered in `runs`,\n // it could not dispose the agent this path was about to create. Re-verify\n // INSIDE the queue (after any enqueued cancel committed) BEFORE injecting:\n // a cancelled card must not gain a zombie session that burns tokens and\n // edits files while the task sits in todo, re-runnable by anyone.\n const stillRunning = await this.deps.store.read(ledger =>\n ledger.tasks.some(t => t.executions.some(e => e.id === executionId && e.outcome === 'running')))\n if (!stillRunning) {\n await handle.dispose().catch(() => { /* best effort */ })\n // S1: do not leave the startup artifacts behind a cancelled run either.\n if (prepared !== undefined && this.deps.git !== undefined) {\n try { await this.deps.git.removeWorktree(workspace.path, prepared.worktreePath) } catch { /* best effort */ }\n }\n return { ok: false, error: 'cancelled during startup' }\n }\n\n // 3. Attach the session to the workspace (GUI project session list).\n await this.deps.workspaces.attach(task.workspaceId, sessionId).catch(() => { /* cosmetic */ })\n\n // 3b. Best-effort rename: pin the session title to the task title so the\n // session list shows the task name (a user-sourced title also stops\n // automatic first-prompt retitling).\n try {\n this.deps.renameSession?.(sessionId, task.title)\n } catch { /* cosmetic */ }\n\n // 4. Record the session id (execution is really started now).\n await this.patchExecution(executionId, { sessionId })\n\n // 5. Submit the opening pair and settle on quiescence (turn/end errors\n // were already folded by the listener). Two messages, ONE turn:\n // - inject() queues the plugin framing line (next-step, no wake); it\n // renders as a plugin context row in the conversation.\n // - followup() queues the card body as a normal user message\n // (next-turn, wakes the driver). At claim time the loop drains ALL\n // next-step messages plus the one next-turn message into a single\n // turn — framing first, then the user bubble.\n handle.agent.inject({\n id: this.deps.mintMessageId?.() ?? MessageId(`msg-taskboard-${crypto.randomUUID()}`),\n role: 'user' as const,\n content: [{ type: 'text' as const, text: this.pluginFraming(task, prepared, isolationNote) }],\n source: { kind: 'plugin' as const, plugin: 'dsh-taskboard' },\n })\n handle.agent.followup({\n id: this.deps.mintMessageId?.() ?? MessageId(`msg-taskboard-${crypto.randomUUID()}`),\n role: 'user' as const,\n content: [{ type: 'text' as const, text: this.userBody(task) }],\n source: { kind: 'user' as const },\n })\n\n // 6. Settlement watcher: mark succeeded, release the executing session's\n // hold, collect the worktree evidence (commits / dirty / diff), and —\n // when the session did NOT follow the handoff protocol — auto-move the\n // card to in_review with a system comment.\n const settle = (): void => {\n this.runs.delete(executionId)\n void this.settleExecution(executionId, sessionId, prepared)\n }\n this.runs.set(executionId, { sessionId, ...(prepared !== undefined ? { prepared } : {}), settle, dispose: () => handle.dispose() })\n // R2: the rejection path owns its state transition EXCLUSIVELY — the old\n // code also called settle() here, racing two evidence collections whose\n // mutations both checked outcome === 'running': whoever committed first\n // won, so a run that never reached quiescence could be recorded as\n // succeeded (and auto-moved to in_review). Now only the failure\n // settlement writes, and the run entry is released after it commits.\n void handle.agent.whenIdle().then(settle, () => {\n this.noteFailure(sessionId, 'agent did not reach quiescence')\n .then(() => { this.runs.delete(executionId) })\n .catch(() => { this.runs.delete(executionId) })\n })\n\n return { ok: true, executionId, sessionId }\n }\n\n /**\n * Settle one execution: collect worktree facts first (fail-soft — git\n * problems never block settlement), then commit outcome + release + the\n * protocol-auto-review move in ONE ledger mutation.\n */\n private async settleExecution(\n executionId: string,\n sessionId: string,\n prepared: PreparedWorktree | undefined,\n ): Promise<void> {\n const facts = await this.collectEvidence(prepared)\n await this.deps.store.mutate('execution-recorded', (ledger) => {\n for (const t of ledger.tasks) {\n const execution = t.executions.find(e => e.id === executionId)\n if (execution !== undefined && execution.outcome === 'running') {\n const now = this.deps.now()\n execution.outcome = 'succeeded'\n execution.endedAt = now\n this.applyFacts(execution, facts)\n if (t.status === 'in_progress' && t.claimedBy === sessionId) {\n delete t.claimedBy\n delete t.claimedAt\n }\n if (t.status === 'in_progress') {\n const commented = t.comments.some(c => c.threadId === sessionId)\n t.comments.push({\n id: newCommentId(),\n body: normalizeBody(commented\n ? '[系统] 执行会话已结束并留有评论,但未移至待验收;系统自动移入待验收。'\n : '[系统] 执行会话已结束,但未按协议交接(无评论、未移至待验收);系统自动移入待验收,请审查后退回或验收。'),\n version: 1,\n createdAt: now,\n })\n t.status = 'in_review'\n t.updatedAt = now\n t.updatedBy = { kind: 'system' }\n }\n return [t]\n }\n }\n return undefined\n })\n }\n\n /**\n * Prepare the dedicated worktree for a run (fail-soft): detect git, then\n * create/reset the fixed task branch — fresh baseline, or keep a live\n * worktree as-is for 续跑. On success the branch name is pinned onto the\n * task once (renames never change it); on any failure the run degrades\n * with a human-readable note.\n */\n private async prepareIsolation(\n task: TaskRecord,\n workspacePath: string,\n worktreePath: string,\n branch: string,\n reuse: boolean,\n ): Promise<{ prepared?: PreparedWorktree; note?: string }> {\n const git = this.deps.git\n if (git === undefined) return { note: 'git 集成不可用,已在原目录执行' }\n let inside = false\n try {\n inside = await git.detect(workspacePath)\n } catch { /* fail-soft */ }\n if (!inside) {\n // Distinguish 未装 git from 非 git 仓库 (0.3.1): probe the binary so the\n // degradation note names the real cause.\n let hasBinary = true\n try {\n hasBinary = await git.binaryAvailable()\n } catch { /* fail-soft → treat as repo-side */ }\n return { note: hasBinary ? '当前项目不是 git 仓库,已在原目录执行' : 'git 不可用(未安装或不在 PATH),已在原目录执行' }\n }\n let info\n try {\n info = await git.prepareWorktree(workspacePath, worktreePath, branch, reuse ? 'reuse' : 'fresh')\n } catch { /* fail-soft */ }\n if (info === undefined) return { note: 'worktree 准备失败(git 报错或目录被占用),已在原目录执行' }\n // Pin the branch name at first SUCCESSFUL creation (§9: 改名不改分支).\n if (task.branch === undefined) {\n await this.deps.store.mutate('task-updated', (ledger) => {\n const target = ledger.tasks.find(t => t.id === task.id)\n if (target !== undefined && target.branch === undefined) {\n target.branch = branch\n return [target]\n }\n return undefined\n })\n }\n return {\n prepared: {\n branch: info.branch,\n worktreePath: info.path,\n baseCommit: info.baseCommit,\n ...(info.reused === true ? { reused: true } : {}),\n },\n }\n }\n\n /** How many executions are currently running (for the concurrency cap). */\n inFlight(): number {\n return this.runs.size\n }\n\n /**\n * Cancel the running execution of a task (user action): stop the agent\n * session, mark the execution cancelled, and hand the task back to todo.\n * @param taskId - the task whose execution should be stopped.\n * @returns the immediate result.\n */\n async cancel(taskId: string): Promise<CancelRequestResult> {\n const task = this.deps.store.get(taskId)\n if (task === undefined) return { ok: false, error: `no task ${taskId}` }\n const running = [...task.executions].reverse().find(e => e.outcome === 'running')\n if (running === undefined) return { ok: false, error: 'no running execution' }\n\n const entry = this.runs.get(running.id)\n this.runs.delete(running.id)\n // Stop the agent first (best effort): dispose stops the loop, unregisters\n // the agent, and removes its session. A late whenIdle settlement no-ops —\n // the record is no longer 'running'.\n try {\n await entry?.dispose()\n } catch { /* already gone */ }\n\n // The cancelled session may already have committed work — keep the\n // evidence (best effort) so the user can inspect or 续跑 (0.3.1).\n const facts = await this.collectEvidence(entry?.prepared)\n let settled = false\n await this.deps.store.mutate('execution-recorded', (ledger) => {\n const target = ledger.tasks.find(t => t.id === taskId)\n if (target === undefined) return undefined\n const execution = target.executions.find(e => e.id === running.id)\n if (execution === undefined || execution.outcome !== 'running') return undefined\n settled = true\n execution.outcome = 'cancelled'\n execution.endedAt = this.deps.now()\n this.applyFacts(execution, facts)\n if (target.status === 'in_progress') {\n target.status = 'todo'\n target.updatedAt = this.deps.now()\n delete target.claimedBy\n delete target.claimedAt\n }\n return [target]\n })\n // The execution may have settled (succeeded/failed) between the stale\n // read above and this mutation — a no-op cancel must NOT report success\n // (the GUI used to show 取消成功 for an already-succeeded run, review P1).\n if (!settled) return { ok: false, error: 'execution already settled' }\n return { ok: true, executionId: running.id }\n }\n\n /**\n * Startup reconciliation after a host restart: executions left `running`\n * by the previous process can never settle here (their settlement watchers\n * died with it), so mark them failed and hand their tasks back to todo.\n */\n async reconcile(): Promise<void> {\n await this.deps.store.mutate('execution-recorded', (ledger) => {\n const now = this.deps.now()\n const touched: TaskRecord[] = []\n for (const task of ledger.tasks) {\n let dirty = false\n for (const execution of task.executions) {\n if (execution.outcome === 'running') {\n execution.outcome = 'failed'\n execution.error = 'interrupted by host restart'\n execution.endedAt = now\n dirty = true\n }\n }\n if (!dirty) continue\n if (task.status === 'in_progress') {\n task.status = 'todo'\n task.updatedAt = now\n delete task.claimedBy\n delete task.claimedAt\n }\n touched.push(task)\n }\n return touched.length > 0 ? touched : undefined\n })\n }\n\n /**\n * The plugin framing line (rendered as a plugin context row): task head,\n * already-claimed state, and the handoff protocol — everything the session\n * must know about the board. The task id appears exactly once (here); the\n * protocol steps below refer to it as 本任务. Isolated runs add one line\n * steering the session onto its dedicated branch (commits are the evidence\n * the user reviews at merge time); 续跑 and degraded runs each add their\n * own steering line (0.3.1).\n * @param task - the task.\n * @param prepared - worktree facts when this run is isolated.\n * @param degradeNote - why a worktree task degraded to the main directory.\n */\n private pluginFraming(task: TaskRecord, prepared?: PreparedWorktree, degradeNote?: string): string {\n let text = `【任务看板】${task.title}(ID: ${task.id})\\n`\n + `本会话由任务看板执行服务启动,任务已置为进行中——无需认领;「已完成」仅限用户在界面操作(代码已限制,移了会被拒)。\\n`\n + `完成后按序交接:\\n`\n + `1. taskboard_get 读取本任务,取得最新 version\\n`\n + `2. taskboard_execution_report 提交结构化执行报告(做了什么/改了哪些文件/如何验证/剩余风险;提交与评论不冲突,都会展示给验收人)\\n`\n + `3. taskboard_comment_add 留评论:做了什么改动 / 如何验证 / 剩余风险\\n`\n + `4. taskboard_move 将本任务移至待验收 in_review(带 ifVersion)\\n`\n + `若无法完成:留评论说明原因,将任务移回待办 todo。`\n if (task.checklist !== undefined && task.checklist.length > 0) {\n const items = task.checklist\n .map((item, index) => `${item.checked ? '☑' : '☐'} ${index + 1}. ${item.text}${item.note !== undefined ? `(证据: ${item.note})` : ''}`)\n .join('\\n')\n const done = task.checklist.filter(i => i.checked).length\n text += `\\n本任务有验收清单(DoD,${done}/${task.checklist.length} 已完成)——按清单干活:\\n${items}\\n完成一项就用 taskboard_checklist(action=check,附 note 证据)勾选;未完成项会在验收时高亮,全部完成再移待验收。需要补充验收项也可用 action=add 追加。`\n }\n if (prepared !== undefined) {\n if (prepared.reused === true) {\n text += `\\n本任务启用了 Git Worktree 隔离,且本次为续跑:任务工作目录是独立分支 ${prepared.branch} 的 worktree——\\n${prepared.worktreePath}\\n上一次执行的改动与提交都保留在原处——请先查看已有改动(git status / git log)再继续,避免重复劳动,并把新完成的工作提交到该分支。`\n } else {\n text += `\\n本任务启用了 Git Worktree 隔离:任务工作目录是独立分支 ${prepared.branch} 的全新 worktree——\\n${prepared.worktreePath}\\n(全新检出,不含 node_modules/构建产物,构建或测试前可能需要先安装依赖)。\\n⚠ 边界纪律:你的会话根目录是整个项目,但本任务的全部改动必须只发生在上述 worktree 目录内——命令用 workdir 指向它、文件读写用它的绝对路径;不要改动主工作区的任何其它文件;把完成的工作提交(git commit)到该分支,验收将基于该分支的提交记录合并。`\n }\n } else if (degradeNote !== undefined) {\n text += `\\n⚠ 本次执行未能建立隔离,正在主项目目录中工作(原因:${degradeNote})。该目录可能有他人未提交的改动:动手前先 git status 检查现状,改动尽量集中,结束时在评论中说明动了哪些文件;避免把未经验证的改动直接提交到主分支。`\n }\n return text\n }\n\n /**\n * The card body as a normal user bubble: the effective prompt (title+\n * description, with the explicit prompt appended when set) with template\n * variables resolved from\n * the task's own history at submit time (valuable for recurring patrols):\n * `{{lastExecution}}` → the previous execution's trigger/outcome/error;\n * `{{lastComments}}` → the last three comments (who + body).\n */\n private userBody(task: TaskRecord): string {\n const lastExec = [...task.executions].reverse().find(e => e.outcome !== 'running')\n const lastExecText = lastExec === undefined\n ? '(无)'\n : `${lastExec.trigger} · ${lastExec.outcome}${lastExec.error !== undefined ? ` · ${lastExec.error.slice(0, 200)}` : ''} · ${lastExec.startedAt !== undefined ? new Date(lastExec.startedAt).toISOString() : '?'}`\n const lastCommentsText = task.comments.slice(-3)\n .map(c => `[${c.threadId !== undefined ? 'agent' : 'user'}] ${c.body}`)\n .join('\\n') || '(无)'\n return effectivePrompt(task)\n .replace(/\\{\\{lastExecution\\}\\}/g, lastExecText)\n .replace(/\\{\\{lastComments\\}\\}/g, lastCommentsText)\n }\n\n /** Move a task back out of in_progress (and release its hold) after a failed start. */\n private async revertProgress(taskId: string): Promise<void> {\n await this.deps.store.mutate('execution-recorded', (ledger) => {\n const target = ledger.tasks.find(t => t.id === taskId)\n if (target !== undefined && target.status === 'in_progress') {\n target.status = 'todo'\n target.updatedAt = this.deps.now()\n delete target.claimedBy\n delete target.claimedAt\n return [target]\n }\n return undefined\n })\n }\n}\n"],"mappings":"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"}
|
package/lib/host/tools.js
CHANGED
|
@@ -23,7 +23,7 @@ function taskDetail(t) {
|
|
|
23
23
|
const holder = isClaimedBy(t);
|
|
24
24
|
if (holder !== void 0) lines.push(`认领: agent ${String(holder).slice(0, 24)}(持有期间其他会话不可移动)`);
|
|
25
25
|
if (t.execution.nextRunAt !== void 0) lines.push(`下次触发: ${new Date(t.execution.nextRunAt).toISOString()}`);
|
|
26
|
-
if (t.model !== void 0) lines.push(`固定模型: ${t.model.provider}/${t.model.model}`);
|
|
26
|
+
if (t.model !== void 0) lines.push(`固定模型: ${t.model.provider}/${t.model.model}${t.model.reasoningEffort !== void 0 ? ` (思考强度: ${t.model.reasoningEffort})` : ""}`);
|
|
27
27
|
if (t.presetId !== void 0) lines.push(`执行模式: ${t.presetId}(未指定时为部署默认 preset)`);
|
|
28
28
|
lines.push(`描述: ${t.description.length > 0 ? t.description : "(无)"}`);
|
|
29
29
|
lines.push(`执行 Prompt: ${t.effectivePrompt ?? effectivePrompt(t)}`);
|
|
@@ -314,7 +314,7 @@ function registerTaskboardTools(ctx, deps) {
|
|
|
314
314
|
model: {
|
|
315
315
|
type: "object",
|
|
316
316
|
additionalProperties: false,
|
|
317
|
-
description: "Pin executions to one configured model: { provider, model }. Omit to use the default model.",
|
|
317
|
+
description: "Pin executions to one configured model: { provider, model, reasoningEffort? }. Omit to use the default model.",
|
|
318
318
|
properties: {
|
|
319
319
|
provider: {
|
|
320
320
|
type: "string",
|
|
@@ -323,6 +323,10 @@ function registerTaskboardTools(ctx, deps) {
|
|
|
323
323
|
model: {
|
|
324
324
|
type: "string",
|
|
325
325
|
description: "Provider-owned model id."
|
|
326
|
+
},
|
|
327
|
+
reasoningEffort: {
|
|
328
|
+
type: "string",
|
|
329
|
+
description: "Optional thinking intensity / reasoning effort (e.g. low, medium, high)."
|
|
326
330
|
}
|
|
327
331
|
}
|
|
328
332
|
},
|
package/lib/host/tools.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.js","names":["v"],"sources":["../../src/host/tools.ts"],"sourcesContent":["/**\n * The ten `taskboard_*` agent tools. All writes require a calling agent\n * session (ownership audit), carry optimistic-version checks, and enforce\n * the protocol gates in CODE, not in prompt text:\n *\n * - `move → done` is rejected for agent callers (user confirmation only)\n * - `move todo → in_progress` requires the calling session's workspace to\n * match the task's project (claim boundary)\n * - taking over a task held by another session is rejected\n * - `delete` for agent callers only sets the soft-delete marker\n * - checklist items may be added/checked by agents, but checking never\n * completes the task (done stays user-only)\n *\n * OUTPUT CONTRACT (lesson: registry `createSuccessResult` renders\n * `output.render(args, value)` into `result.content`, and the loop feeds\n * exactly that content to the model — the raw JSON `value` never reaches\n * the model): render() IS the model-facing tool result. Every render must\n * carry the complete facts an agent needs to act (ids, versions, statuses);\n * a \"terse UI summary\" here starves the agent.\n *\n * @module dsh-taskboard/host/tools\n */\nimport type { WorkspaceRegistry } from '@deepseek-ai/dsh-workspace'\nimport { defineTool } from './sdk.ts'\nimport {\n MAX_CHECKLIST_ITEMS,\n asIsolation,\n asStatus,\n asUrgency,\n canTransition,\n checklistFromTexts,\n defaultIsolationOf,\n effectivePrompt,\n isClaim,\n isClaimedBy,\n newChecklistItemId,\n newCommentId,\n newTaskId,\n normalizeBody,\n normalizeExecution,\n normalizeExecutionReport,\n normalizeModel,\n normalizePrompt,\n normalizeTitle,\n summarize,\n syncClaim,\n type Actor,\n type ChecklistItem,\n type TaskLedger,\n type TaskModel,\n type TaskRecord,\n} from '../shared/protocol.ts'\nimport type { TaskStore } from './store.ts'\n\n/** Render side: one compact task line (id/status/version are load-bearing). */\nfunction taskLine(t: {\n id: string\n title: string\n status: string\n urgency: string\n version: number\n workspaceId: string\n blocked: boolean\n executionMode: string\n commentCount?: number\n lastExecutionOutcome?: string\n checklist?: { done: number; total: number }\n trashed?: boolean\n}): string {\n const parts = [\n `- ${t.id} [${t.status}] v${t.version} · ${t.urgency} · 项目 ${t.workspaceId}`,\n `「${t.title}」`,\n ]\n if (t.blocked) parts.push('·受阻')\n if (t.executionMode === 'scheduled') parts.push('·定时')\n if (t.commentCount !== undefined && t.commentCount > 0) parts.push(`·评论${t.commentCount}`)\n if (t.checklist !== undefined && t.checklist.total > 0) parts.push(`·清单${t.checklist.done}/${t.checklist.total}`)\n if (t.lastExecutionOutcome !== undefined) parts.push(`·上次执行${t.lastExecutionOutcome}`)\n if (t.trashed === true) parts.push('·已删')\n return parts.join(' ')\n}\n\n/** Render side: the full task detail block (everything an executor needs). */\nfunction taskDetail(t: TaskRecord & { effectivePrompt?: string }): string {\n const lines: string[] = [\n `任务 ${t.id} 「${t.title}」`,\n `状态: ${t.status} (v${t.version}) · 紧急度: ${t.urgency} · 项目: ${t.workspaceId}${t.blocked ? ' · 受阻' : ''}`,\n `执行方式: ${t.execution.mode}${t.execution.cron !== undefined ? ` cron=${t.execution.cron}` : ''}`,\n `隔离: ${t.isolation === 'none' ? '关闭(原目录执行)' : 'Git Worktree'}${t.branch !== undefined ? `(分支 ${t.branch})` : ''}`,\n ]\n const holder = isClaimedBy(t)\n if (holder !== undefined) lines.push(`认领: agent ${String(holder).slice(0, 24)}(持有期间其他会话不可移动)`)\n if (t.execution.nextRunAt !== undefined) lines.push(`下次触发: ${new Date(t.execution.nextRunAt).toISOString()}`)\n if (t.model !== undefined) lines.push(`固定模型: ${t.model.provider}/${t.model.model}`)\n if (t.presetId !== undefined) lines.push(`执行模式: ${t.presetId}(未指定时为部署默认 preset)`)\n lines.push(`描述: ${t.description.length > 0 ? t.description : '(无)'}`)\n lines.push(`执行 Prompt: ${t.effectivePrompt ?? effectivePrompt(t)}`)\n if (t.checklist !== undefined && t.checklist.length > 0) {\n const done = t.checklist.filter(i => i.checked).length\n lines.push(`验收清单 (${done}/${t.checklist.length}):`)\n for (const item of t.checklist) {\n const mark = item.checked ? '☑' : '☐'\n const who = item.checkedBy === undefined ? '' : item.checkedBy === 'user' ? ' ·用户勾选' : ` ·agent ${String(item.checkedBy).slice(0, 24)}勾选`\n const note = item.note !== undefined ? ` ·证据: ${item.note}` : ''\n lines.push(` ${mark} ${item.text}${who}${note}`)\n }\n }\n if (t.comments.length > 0) {\n lines.push(`评论 (${t.comments.length}):`)\n for (const c of t.comments) {\n const who = c.threadId !== undefined ? `agent ${String(c.threadId).slice(0, 24)}` : 'user'\n lines.push(` - [${who} ${new Date(c.createdAt).toISOString()}] ${c.body}`)\n }\n } else {\n lines.push('评论: 无')\n }\n if (t.executions.length > 0) {\n lines.push(`执行记录 (${t.executions.length}):`)\n for (const e of t.executions) {\n const at = e.startedAt !== undefined ? new Date(e.startedAt).toISOString() : '?'\n const err = e.error !== undefined ? ` 错误: ${e.error}` : ''\n const report = e.report !== undefined ? ' [已交报告]' : ''\n lines.push(` - [${e.trigger} ${at}] ${e.outcome}${report}${err}`)\n }\n } else {\n lines.push('执行记录: 无')\n }\n const updatedBy = t.updatedBy.kind === 'agent' ? `agent ${String(t.updatedBy.sessionId).slice(0, 24)}` : t.updatedBy.kind === 'system' ? 'system' : 'user'\n lines.push(`更新: ${new Date(t.updatedAt).toISOString()} 由 ${updatedBy}`)\n return lines.join('\\n')\n}\n\n/** Stable error codes surfaced at the head of tool error messages. */\nexport const ERR = {\n notFound: 'not_found',\n versionConflict: 'version_conflict',\n workspaceMismatch: 'workspace_mismatch',\n invalidTransition: 'invalid_transition',\n forbidden: 'forbidden',\n requiresAgent: 'unauthorized_actor',\n invalidInput: 'invalid_input',\n} as const\n\n/** Tool failure: an Error whose message starts with a stable code. The code\n * is also carried structurally so the routes layer can map failures without\n * re-parsing messages (review P2). */\nexport class ToolError extends Error {\n constructor(readonly code: string, detail: string) {\n super(`Error: ${code}: ${detail}`)\n }\n}\n\n/** The workspace face the tools need (narrow for tests). */\nexport interface WorkspaceFace {\n /** Resolve the workspace owning a canonical cwd, if any. */\n resolveByPath(path: string): Promise<{ id: string } | undefined>\n /** Get a workspace by id. */\n get(id: string): { id: string; path: string; title: string } | undefined\n /** List all workspaces. */\n list(): Array<{ id: string; path: string; title: string }>\n}\n\n/** Adapt the real registry to the narrow face. */\nexport function workspaceFace(registry: WorkspaceRegistry): WorkspaceFace {\n // Explicit field mapping: Workspace entities expose path/title as prototype\n // getters, which JSON.stringify skips (own enumerable properties only).\n return {\n resolveByPath: async (path) => {\n const ws = await registry.resolveByPath(path as never)\n return ws === undefined ? undefined : { id: ws.id }\n },\n get: id => {\n const ws = registry.get(id as never)\n return ws === undefined ? undefined : { id: ws.id, path: ws.path, title: ws.title }\n },\n list: () => registry.list().map(ws => ({ id: ws.id, path: ws.path, title: ws.title })),\n }\n}\n\n/** Everything the tool set needs. */\nexport interface ToolDeps {\n store: TaskStore\n workspaces: WorkspaceFace\n /** Current epoch ms (injectable for tests). */\n now: () => number\n /**\n * Registered model provider routes (from the host llm runtime), for\n * advisory validation of pinned models; undefined = runtime unavailable,\n * in which case only the structural check applies.\n */\n modelProviders?: () => string[] | undefined\n}\n\n/** Validate a pinned model: structural check always, provider route when known. */\nfunction checkModel(deps: ToolDeps, raw: unknown): TaskModel {\n const model = normalizeModel(raw)\n const providers = deps.modelProviders?.()\n if (providers !== undefined && !providers.includes(model.provider)) {\n throw new ToolError(ERR.invalidInput, `model provider \"${model.provider}\" has no registered route (available: ${providers.join(', ')})`)\n }\n return model\n}\n\n/** Resolve the calling agent's actor and session id. */\nfunction caller(exec: ToolRunContext): { actor: Actor & { kind: 'agent' }; sessionId: string } {\n if (!exec.agent) throw new ToolError(ERR.requiresAgent, 'taskboard tools require a calling agent session')\n const sessionId = exec.agent.id\n return { actor: { kind: 'agent', sessionId }, sessionId }\n}\n\n/** The calling session's workspace id (undefined when unaffiliated). */\nasync function callerWorkspace(deps: ToolDeps, exec: ToolRunContext): Promise<string | undefined> {\n const cwd = exec.agent?.session.header.cwd\n if (typeof cwd !== 'string' || cwd.length === 0) return undefined\n const ws = await deps.workspaces.resolveByPath(cwd)\n return ws?.id\n}\n\n/** Guard: version match. */\nfunction versionGuard(task: TaskRecord, ifVersion: number | undefined): void {\n if (ifVersion === undefined) {\n throw new ToolError(ERR.versionConflict, 'this write requires ifVersion; read the task first')\n }\n if (ifVersion !== task.version) {\n throw new ToolError(ERR.versionConflict, `stale version ${ifVersion} (current ${task.version}); re-read the task and retry once`)\n }\n}\n\n/**\n * Find a live (non-trashed) task INSIDE a mutator (R1: every guard must run\n * on the fresh draft the serial queue hands us, never on a pre-read clone —\n * a pre-read can pass its version check and then blind-overwrite a task that\n * changed while the caller awaited). Throws not_found for missing/trashed.\n */\nfunction liveTaskAt(ledger: TaskLedger, id: string): { index: number; task: TaskRecord } {\n const index = ledger.tasks.findIndex(t => t.id === id)\n if (index < 0) throw new ToolError(ERR.notFound, `no task ${id}`)\n const task = ledger.tasks[index]!\n if (task.trashedAt !== undefined) throw new ToolError(ERR.notFound, `no task ${id}`)\n return { index, task }\n}\n\n/** Re-throw with a stable code; non-ToolErrors become invalid_input. */\nfunction fail(error: unknown): never {\n if (error instanceof ToolError) throw error\n const message = error instanceof Error ? error.message : String(error)\n throw new ToolError(ERR.invalidInput, message)\n}\n\n/** Loose json output schema shared by every taskboard tool. */\nconst JSON_OUT = { type: 'json' } as const\n\n/** Deep-JSON a value for a json-rooted tool output (spread results lose implicit index signatures). */\nfunction json<T>(value: T): Record<string, unknown> {\n return JSON.parse(JSON.stringify(value)) as Record<string, unknown>\n}\n\n/** The exec context face the tools read (agent identity + session cwd). */\nexport interface ToolRunContext {\n agent?: { id: string; session: { header: { cwd?: string } } }\n}\n\n/** Registry-like context face (tests stub this). */\nexport interface ToolContextFace {\n tools: { register(tool: { name: string }): unknown }\n}\n\n/**\n * Register all ten tools.\n * @param ctx - a context exposing `tools.register`.\n * @param deps - store + workspaces + clock.\n * @returns dispose functions, one per tool.\n */\nexport function registerTaskboardTools(ctx: ToolContextFace, deps: ToolDeps): Array<() => void> {\n const disposers: Array<() => void> = []\n const { store, workspaces } = deps\n\n // Env-gated tool-call tracing (ATB_TRACE=1) — evidence for protocol E2E.\n const register = (tool: { name: string; execute?: unknown }) => {\n if (process.env.ATB_TRACE === '1' && typeof tool.execute === 'function') {\n const orig = tool.execute as (args: unknown, exec: unknown) => Promise<unknown>\n tool.execute = async (args: unknown, exec: unknown) => {\n console.error(`[atb ▶] ${tool.name}`, JSON.stringify(args).slice(0, 300))\n try {\n const result = await orig(args, exec)\n console.error(`[atb ✓] ${tool.name}`, JSON.stringify(result).slice(0, 300))\n return result\n } catch (error) {\n console.error(`[atb ✗] ${tool.name}`, String(error).slice(0, 400))\n throw error\n }\n }\n }\n return ctx.tools.register(tool as { name: string })\n }\n\n // ------------------------------------------------------------------ list\n disposers.push(register(defineTool({\n name: 'taskboard_list',\n description:\n 'List task-board tasks. Filter by project (workspaceId), status, or urgency. '\n + 'Returns compact summaries (id, title, status, urgency, version, claim owner). '\n + 'Check this before starting work to find claimable todo tasks in your project.',\n parameters: {\n workspaceId: { type: 'string', description: 'Filter by project (DSH workspace id).' },\n status: { type: 'string', description: 'Filter by exact status (backlog/todo/in_progress/in_review/done/canceled/archived).' },\n urgency: { type: 'string', description: 'Filter by urgency (urgent/normal/relaxed).' },\n includeTrashed: { type: 'boolean', description: 'Include soft-deleted tasks (default false).' },\n },\n output: {\n schema: JSON_OUT,\n render: (_args, value) => {\n const v = value as { revision?: number; tasks?: Array<Record<string, unknown>> }\n const tasks = v.tasks ?? []\n const head = `任务 ${tasks.length} 条(台账 rev ${v.revision ?? '?'})`\n if (tasks.length === 0) return [{ type: 'text', text: `${head}:无匹配任务。` }]\n return [{ type: 'text', text: [head, ...tasks.map(t => taskLine(t as never))].join('\\n') }]\n },\n },\n async execute(args) {\n try {\n const a = args as { workspaceId?: string; status?: string; urgency?: string; includeTrashed?: boolean }\n const tasks = store.snapshot().tasks.filter(t =>\n (a.workspaceId === undefined || t.workspaceId === a.workspaceId)\n && (a.status === undefined || t.status === a.status)\n && (a.urgency === undefined || t.urgency === a.urgency)\n && (a.includeTrashed === true || t.trashedAt === undefined))\n return json({ revision: store.snapshot().revision, tasks: tasks.map(summarize) })\n } catch (error) { fail(error) }\n },\n })) as () => void)\n\n // ------------------------------------------------------------------- get\n disposers.push(register(defineTool({\n name: 'taskboard_get',\n description:\n 'Read one task in full: description, prompt, project, urgency, status, comments, executions, version. '\n + 'Read this (and the comments) BEFORE claiming or starting work on a task.',\n parameters: {\n id: { type: 'string', required: true, description: 'Task id from the board.' },\n },\n output: {\n schema: JSON_OUT,\n render: (_args, value) => {\n const v = value as { task?: TaskRecord & { effectivePrompt?: string } }\n return [{ type: 'text', text: v.task === undefined ? '任务不存在。' : taskDetail(v.task) }]\n },\n },\n async execute(args: { id: string }) {\n try {\n const { id } = args\n const task = store.get(id)\n if (task === undefined || task.trashedAt !== undefined) throw new ToolError(ERR.notFound, `no task ${id}`)\n return json({ task: { ...task, effectivePrompt: effectivePrompt(task) } })\n } catch (error) { fail(error) }\n },\n })) as () => void)\n\n // ---------------------------------------------------------------- create\n disposers.push(register(defineTool({\n name: 'taskboard_create',\n description:\n 'Create a task on the board. Required: title, workspaceId (project), urgency (urgent/normal/relaxed). '\n + 'Optional: description, prompt (sent to a fresh session on execution), status (default todo), '\n + 'execution mode (claim|scheduled + cron), model {provider, model} to pin executions to a model. '\n + 'Do not track trivial requests as tasks.',\n parameters: {\n title: { type: 'string', required: true, description: 'Short imperative line (1..200 chars).' },\n workspaceId: { type: 'string', required: true, description: 'Project (DSH workspace id) this task belongs to.' },\n urgency: { type: 'string', required: true, description: 'urgent (red) | normal (purple) | relaxed (blue).' },\n description: { type: 'string', description: 'What the task involves (plain text).' },\n prompt: { type: 'string', description: 'Extra execution instructions; the session receives title+description+this prompt.' },\n status: { type: 'string', description: 'Initial status; default todo. backlog = not approved for execution.' },\n execution: {\n type: 'object',\n additionalProperties: false,\n description: 'Execution config: { mode: \"claim\" } (default) or { mode: \"scheduled\", cron: \"m h dom mon dow\" }.',\n properties: {\n mode: { type: 'string', description: 'claim | scheduled.' },\n cron: { type: 'string', description: 'Five-field cron expression (scheduled only).' },\n },\n },\n model: {\n type: 'object',\n additionalProperties: false,\n description: 'Pin executions to one configured model: { provider, model }. Omit to use the default model.',\n properties: {\n provider: { type: 'string', description: 'Provider route id.' },\n model: { type: 'string', description: 'Provider-owned model id.' },\n },\n },\n isolation: {\n type: 'string',\n description: 'Code isolation for executions: \"worktree\" (each run gets a fresh git worktree on branch task/<标题>+<taskId>) or \"none\" (run in the project directory, zero git interaction). Omitted → the board default (看板设置 → 默认执行隔离; factory default \"none\").',\n },\n presetId: {\n type: 'string',\n description: 'Agent preset the execution session is composed from (its tool set / persona); default = the deployment default preset. Optional.',\n },\n checklist: {\n type: 'array',\n description: `Acceptance checklist (DoD) item texts (≤${MAX_CHECKLIST_ITEMS} × 200 chars); agents check them off at handoff, the user reviews.`,\n items: { type: 'string' },\n },\n },\n output: {\n schema: JSON_OUT,\n render: (_args, value) => {\n const v = value as { task?: { id?: string; status?: string; version?: number } }\n const t = v.task\n return [{ type: 'text', text: t === undefined ? '创建失败。' : `已创建任务 ${t.id} [${t.status}] v${t.version}。写入前先 taskboard_get 读取。` }]\n },\n },\n async execute(args: {\n title: string\n workspaceId: string\n urgency: string\n status?: string\n description?: string\n prompt?: string\n execution?: { mode?: string; cron?: string }\n model?: { provider?: string; model?: string }\n isolation?: string\n presetId?: string\n checklist?: string[]\n }, exec: unknown) {\n try {\n const { actor } = caller(exec as ToolRunContext)\n const title = normalizeTitle(args.title)\n if (workspaces.get(args.workspaceId) === undefined) {\n throw new ToolError(ERR.notFound, `unknown workspaceId ${args.workspaceId}`)\n }\n const urgency = asUrgency(args.urgency)\n const status = args.status === undefined ? 'todo' as const : asStatus(args.status)\n if (status !== 'backlog' && status !== 'todo') {\n throw new ToolError(ERR.invalidTransition, 'a new task must start as backlog or todo (in_progress requires claiming the task)')\n }\n const execution = normalizeExecution(args.execution ?? {}, deps.now())\n const model = args.model !== undefined ? checkModel(deps, args.model) : undefined\n // 0.5.0: an omitted isolation is MATERIALIZED from the board setting\n // (看板设置) at creation, so later setting changes never rewrite\n // existing tasks.\n const isolation = args.isolation === undefined ? defaultIsolationOf(store.snapshot().settings) : asIsolation(args.isolation)\n const presetId = args.presetId?.trim() || undefined\n // T9: match the GUI create route — trim and drop blank lines instead\n // of failing the whole call over one empty string.\n const checklistTexts = args.checklist?.map(c => c.trim()).filter(c => c.length > 0)\n const checklist = checklistTexts !== undefined && checklistTexts.length > 0 ? checklistFromTexts(checklistTexts) : undefined\n const now = deps.now()\n const task: TaskRecord = {\n id: newTaskId(),\n title,\n description: (args.description ?? '').trim(),\n prompt: normalizePrompt(args.prompt),\n workspaceId: args.workspaceId,\n urgency,\n status,\n blocked: false,\n execution,\n model,\n isolation,\n ...(presetId !== undefined ? { presetId } : {}),\n ...(checklist !== undefined ? { checklist } : {}),\n version: 1,\n createdAt: now,\n updatedAt: now,\n createdBy: actor,\n updatedBy: actor,\n comments: [],\n executions: [],\n }\n await store.mutate('task-created', ledger => {\n ledger.tasks.push(task)\n return [task]\n })\n return json({ task: summarize(task) })\n } catch (error) { fail(error) }\n },\n })) as () => void)\n\n // ---------------------------------------------------------------- update\n disposers.push(register(defineTool({\n name: 'taskboard_update',\n description:\n 'Update a task\\'s title/description/prompt/urgency/blocked. Requires ifVersion (read first). '\n + 'The model and execution config are read-only through this tool (they belong to the task owner/user).',\n parameters: {\n id: { type: 'string', required: true, description: 'Task id.' },\n ifVersion: { type: 'number', required: true, description: 'The task version you read; the write fails on mismatch.' },\n title: { type: 'string', description: 'New title.' },\n description: { type: 'string', description: 'New description.' },\n prompt: { type: 'string', description: 'New execution prompt.' },\n urgency: { type: 'string', description: 'urgent | normal | relaxed.' },\n blocked: { type: 'boolean', description: 'Blocked marker (work cannot continue right now).' },\n },\n output: {\n schema: JSON_OUT,\n render: (_args, value) => {\n const v = value as { task?: { id?: string; status?: string; version?: number } }\n const t = v.task\n return [{ type: 'text', text: t === undefined ? '更新失败。' : `已更新任务 ${t.id},当前 v${t.version} [${t.status}]。` }]\n },\n },\n async execute(args: {\n id: string\n ifVersion: number\n title?: string\n description?: string\n prompt?: string\n urgency?: string\n blocked?: boolean\n }, exec: unknown) {\n try {\n const { actor } = caller(exec as ToolRunContext)\n // R1: lookup + version guard + write run inside the serial-queue\n // mutation, on the fresh draft — a pre-read clone could pass its\n // version check and then blind-overwrite a concurrent writer.\n let next: TaskRecord | undefined\n await store.mutate('task-updated', ledger => {\n const { index, task } = liveTaskAt(ledger, args.id)\n versionGuard(task, args.ifVersion)\n if (task.status === 'archived') throw new ToolError(ERR.invalidTransition, 'archived tasks are immutable')\n next = structuredClone(task)\n if (args.title !== undefined) next.title = normalizeTitle(args.title)\n if (args.description !== undefined) next.description = args.description.trim()\n if (args.prompt !== undefined) next.prompt = normalizePrompt(args.prompt)\n if (args.urgency !== undefined) next.urgency = asUrgency(args.urgency)\n if (args.blocked !== undefined) next.blocked = args.blocked\n next.version = task.version + 1\n next.updatedAt = deps.now()\n next.updatedBy = actor\n ledger.tasks[index] = next\n return [next]\n })\n return json({ task: summarize(next!) })\n } catch (error) { fail(error) }\n },\n })) as () => void)\n\n // ------------------------------------------------------------------ move\n disposers.push(register(defineTool({\n name: 'taskboard_move',\n description:\n 'Move a task between statuses (requires ifVersion). Claim = todo→in_progress (only a session '\n + 'inside the task\\'s project may claim; never take over a task held by another session). '\n + 'After implementing and self-verifying: comment, then in_progress→in_review. '\n + 'You can NEVER move a task to done — that requires explicit user confirmation.',\n parameters: {\n id: { type: 'string', required: true, description: 'Task id.' },\n status: { type: 'string', required: true, description: 'Target status.' },\n ifVersion: { type: 'number', required: true, description: 'Task version you read; fails on mismatch.' },\n },\n output: {\n schema: JSON_OUT,\n render: (_args, value) => {\n const v = value as { task?: { id?: string; status?: string; version?: number } }\n const t = v.task\n return [{ type: 'text', text: t === undefined ? '移动失败。' : `任务 ${t.id} 已移到 ${t.status},当前 v${t.version}。` }]\n },\n },\n async execute(args: { id: string; status: string; ifVersion: number }, exec: unknown) {\n try {\n const { actor } = caller(exec as ToolRunContext)\n const to = asStatus(args.status)\n // Claim boundary (policy gate): resolving the caller's workspace is\n // async, so it happens BEFORE the mutation — the comparison runs INSIDE\n // against the FRESH task (stronger than the old stale-clone compare).\n const callerWsId = to === 'in_progress'\n ? await callerWorkspace(deps, exec as ToolRunContext)\n : undefined\n // R1: every state guard + the write itself run inside the mutation.\n let next: TaskRecord | undefined\n await store.mutate('task-moved', ledger => {\n const { index, task } = liveTaskAt(ledger, args.id)\n versionGuard(task, args.ifVersion)\n\n // Code-level gate: agents never complete a task.\n if (to === 'done') {\n throw new ToolError(ERR.forbidden, 'moving a task to done requires explicit user confirmation (GUI); agents cannot do it')\n }\n if (!canTransition(task.status, to)) {\n throw new ToolError(ERR.invalidTransition, `illegal transition ${task.status} → ${to}`)\n }\n // Exclusive hold: while a task is in_progress under a session\n // (explicit claimedBy — an agent claim or a live execution), no other\n // session may move it (that would be a takeover).\n if (task.status === 'in_progress' && task.claimedBy !== undefined && task.claimedBy !== actor.sessionId) {\n throw new ToolError(ERR.forbidden, `task is held by session ${task.claimedBy}; never take over another session's claim`)\n }\n // Claim boundary: the calling session must belong to the task's project.\n if (isClaim(task.status, to) && callerWsId !== task.workspaceId) {\n throw new ToolError(ERR.workspaceMismatch, 'only a session inside this task\\'s project may claim it')\n }\n next = structuredClone(task)\n next.status = to\n next.version = task.version + 1\n next.updatedAt = deps.now()\n next.updatedBy = actor\n if (isClaim(task.status, to)) next.blocked = false\n // Record the holder on a claim; every move out of in_progress releases it.\n syncClaim(next, to, deps.now(), isClaim(task.status, to) ? actor.sessionId : undefined)\n ledger.tasks[index] = next\n return [next]\n })\n return json({ task: summarize(next!) })\n } catch (error) { fail(error) }\n },\n })) as () => void)\n\n // ----------------------------------------------------------- comment_add\n disposers.push(register(defineTool({\n name: 'taskboard_comment_add',\n description:\n 'Append a progress/report comment to a task. When handing off to review, the comment should cover: '\n + 'what changed, how it was verified, outcome, and remaining risks.',\n parameters: {\n id: { type: 'string', required: true, description: 'Task id.' },\n body: { type: 'string', required: true, description: 'Comment text (1..4000 chars).' },\n },\n output: {\n schema: JSON_OUT,\n render: (_args, value) => {\n const v = value as { comment?: { id?: string }; task?: { id?: string; version?: number; status?: string } }\n const c = v.comment\n const t = v.task\n if (c === undefined || t === undefined) return [{ type: 'text', text: '评论失败。' }]\n // The comment bumped the version — echo it so the agent can chain the\n // next write (e.g. move → in_review) WITHOUT re-reading.\n return [{\n type: 'text',\n text: `评论 ${c.id} 已添加;任务 ${t.id} 当前 v${t.version} [${t.status}](后续写操作用此版本号).`,\n }]\n },\n },\n async execute(args: { id: string; body: string }, exec: unknown) {\n try {\n const { sessionId } = caller(exec as ToolRunContext)\n const comment = {\n id: newCommentId(),\n body: normalizeBody(args.body),\n version: 1,\n createdAt: deps.now(),\n threadId: sessionId,\n }\n // R1: find + append inside the mutation (no ifVersion by design —\n // comments are append-only — but the write must not clobber a task\n // that changed while we were queued).\n let next: TaskRecord | undefined\n await store.mutate('comment-added', ledger => {\n const { index, task } = liveTaskAt(ledger, args.id)\n if (task.status === 'archived') throw new ToolError(ERR.invalidTransition, 'archived tasks are immutable')\n next = structuredClone(task)\n next.comments.push(comment)\n next.version = task.version + 1\n next.updatedAt = deps.now()\n ledger.tasks[index] = next\n return [next]\n })\n return json({ comment, task: { id: next!.id, version: next!.version, status: next!.status } })\n } catch (error) { fail(error) }\n },\n })) as () => void)\n\n // -------------------------------------------------------------- comments\n disposers.push(register(defineTool({\n name: 'taskboard_comments',\n description: 'List a task\\'s comments, oldest first. Read them before deciding to start work.',\n parameters: {\n id: { type: 'string', required: true, description: 'Task id.' },\n },\n output: {\n schema: JSON_OUT,\n render: (_args, value) => {\n const v = value as { comments?: unknown[] }\n const list = v.comments as Array<{ body: string; createdAt: number; threadId?: string }> | undefined\n if (list === undefined || list.length === 0) return [{ type: 'text', text: '无评论。' }]\n const lines = list.map(c => {\n const who = c.threadId !== undefined ? `agent ${String(c.threadId).slice(0, 24)}` : 'user'\n return `- [${who} ${new Date(c.createdAt).toISOString()}] ${c.body}`\n })\n return [{ type: 'text', text: `评论 ${list.length} 条:\\n${lines.join('\\n')}` }]\n },\n },\n async execute(args: { id: string }) {\n try {\n const task = store.get(args.id)\n if (task === undefined || task.trashedAt !== undefined) throw new ToolError(ERR.notFound, `no task ${args.id}`)\n return json({ comments: task.comments })\n } catch (error) { fail(error) }\n },\n })) as () => void)\n\n // ---------------------------------------------------------------- delete\n disposers.push(register(defineTool({\n name: 'taskboard_delete',\n description:\n 'Soft-delete a task (marks it trashed; the user confirms the purge in the GUI). '\n + 'Requires ifVersion. Prefer canceled/archived over delete unless the task was a mistake.',\n parameters: {\n id: { type: 'string', required: true, description: 'Task id.' },\n ifVersion: { type: 'number', required: true, description: 'Task version you read.' },\n },\n output: {\n schema: JSON_OUT,\n render: (_args, value) => {\n const v = value as { trashed?: boolean }\n return [{ type: 'text', text: v.trashed === true ? '任务已标记删除(等待用户在 GUI 清除)。' : '删除失败。' }]\n },\n },\n async execute(args: { id: string; ifVersion: number }, exec: unknown) {\n try {\n caller(exec as ToolRunContext)\n // R1: guards inside the mutation. S5: a running execution keeps\n // writing to the task (report, settlement) — refuse the soft-delete\n // until it is cancelled or settled. T8: clear claim residue.\n let next: TaskRecord | undefined\n await store.mutate('task-deleted', ledger => {\n const { index, task } = liveTaskAt(ledger, args.id)\n versionGuard(task, args.ifVersion)\n if (task.executions.some(e => e.outcome === 'running')) {\n throw new ToolError(ERR.invalidInput, '任务有正在运行的执行(先在 GUI 取消或等它结束再删除)')\n }\n next = structuredClone(task)\n next.trashedAt = deps.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 return { trashed: true }\n } catch (error) { fail(error) }\n },\n })) as () => void)\n\n // -------------------------------------------------------------- checklist\n disposers.push(register(defineTool({\n name: 'taskboard_checklist',\n description:\n `Manage the task's acceptance checklist (DoD). Actions: \"add\" (append item texts, ≤10 per call), `\n + '\"check\" (mark an item done, with an optional evidence note), \"uncheck\" (reopen an item). '\n + 'Checking items NEVER completes the task — done stays a user-only action. Requires ifVersion.',\n parameters: {\n id: { type: 'string', required: true, description: 'Task id.' },\n action: { type: 'string', required: true, description: 'add | check | uncheck.' },\n ifVersion: { type: 'number', required: true, description: 'Task version you read; fails on mismatch.' },\n items: {\n type: 'array',\n description: 'Item texts to append (action=add only; 1..10 per call, 200 chars each).',\n items: { type: 'string' },\n },\n itemId: { type: 'string', description: 'The checklist item id (action=check/uncheck).' },\n note: { type: 'string', description: 'Evidence note recorded with the check (≤400 chars).' },\n },\n output: {\n schema: JSON_OUT,\n render: (_args, value) => {\n const v = value as { task?: { id?: string; version?: number }; checklist?: Array<Record<string, unknown>>; done?: number; total?: number }\n if (v.task === undefined || v.checklist === undefined) return [{ type: 'text', text: '清单操作失败。' }]\n const lines = v.checklist.map((i, index) => `${i.checked === true ? '☑' : '☐'} [${index + 1}] ${String(i.text)}${i.note !== undefined ? `(证据: ${String(i.note)})` : ''} id=${String(i.id)}`)\n return [{\n type: 'text',\n text: `任务 ${v.task.id} 验收清单 ${v.done ?? 0}/${v.total ?? 0} 已完成,当前 v${v.task.version}:\\n${lines.join('\\n')}`,\n }]\n },\n },\n async execute(args: {\n id: string\n action: string\n ifVersion: number\n items?: string[]\n itemId?: string\n note?: string\n }, exec: unknown) {\n try {\n const { actor } = caller(exec as ToolRunContext)\n // R1: guards + the checklist edit itself run inside the mutation.\n let next: TaskRecord | undefined\n await store.mutate('task-updated', ledger => {\n const { index, task } = liveTaskAt(ledger, args.id)\n versionGuard(task, args.ifVersion)\n if (task.status === 'archived') throw new ToolError(ERR.invalidTransition, 'archived tasks are immutable')\n next = structuredClone(task)\n const checklist: ChecklistItem[] = next.checklist === undefined ? [] : [...next.checklist]\n\n if (args.action === 'add') {\n const texts = args.items ?? []\n if (texts.length === 0 || texts.length > 10) {\n throw new ToolError(ERR.invalidInput, 'items must carry 1..10 texts per add call')\n }\n if (checklist.length + texts.length > MAX_CHECKLIST_ITEMS) {\n throw new ToolError(ERR.invalidInput, `checklist may hold at most ${MAX_CHECKLIST_ITEMS} items (currently ${checklist.length})`)\n }\n checklist.push(...checklistFromTexts(texts))\n } else if (args.action === 'check') {\n if (args.itemId === undefined) throw new ToolError(ERR.invalidInput, 'itemId is required for check')\n const item = checklist.find(i => i.id === args.itemId)\n if (item === undefined) throw new ToolError(ERR.notFound, `no checklist item ${args.itemId} (task ${args.id})`)\n const note = args.note !== undefined && args.note.trim().length > 0 ? args.note.trim().slice(0, 400) : undefined\n item.checked = true\n item.checkedBy = actor.sessionId\n item.checkedAt = deps.now()\n if (note !== undefined) item.note = note\n } else if (args.action === 'uncheck') {\n if (args.itemId === undefined) throw new ToolError(ERR.invalidInput, 'itemId is required for uncheck')\n const item = checklist.find(i => i.id === args.itemId)\n if (item === undefined) throw new ToolError(ERR.notFound, `no checklist item ${args.itemId} (task ${args.id})`)\n item.checked = false\n delete item.checkedBy\n delete item.checkedAt\n delete item.note\n } else {\n throw new ToolError(ERR.invalidInput, `action must be add | check | uncheck (got \"${args.action}\")`)\n }\n\n if (checklist.length > 0) next.checklist = checklist\n else delete next.checklist\n next.version = task.version + 1\n next.updatedAt = deps.now()\n next.updatedBy = actor\n ledger.tasks[index] = next\n return [next]\n })\n const progress = next!.checklist !== undefined\n ? { done: next!.checklist.filter(i => i.checked).length, total: next!.checklist.length }\n : { done: 0, total: 0 }\n return json({ task: { id: next!.id, version: next!.version }, checklist: next!.checklist ?? [], ...progress })\n } catch (error) { fail(error) }\n },\n })) as () => void)\n\n // ------------------------------------------------------- execution report\n disposers.push(register(defineTool({\n name: 'taskboard_execution_report',\n description:\n 'Submit the structured execution report for the task you are currently executing (summary / changed '\n + 'files / how you verified / artifacts / remaining risk). Submit BEFORE moving the task to in_review; '\n + 'a later submission overwrites the previous report. If your run already settled, you may back-submit '\n + 'onto your latest succeeded execution while you still hold the task. Commits and diffs are host-collected.',\n parameters: {\n summary: { type: 'string', required: true, description: 'What was done (1..2000 chars).' },\n changedFiles: {\n type: 'array',\n description: 'Files you changed (paths, ≤50 × 300 chars).',\n items: { type: 'string' },\n },\n checks: {\n type: 'array',\n description: 'How the work was verified (e.g. test commands + outcomes, ≤50 entries).',\n items: { type: 'string' },\n },\n artifacts: {\n type: 'array',\n description: 'Artifacts worth reviewing (build outputs, screenshots, docs, ≤30 entries).',\n items: { type: 'string' },\n },\n risk: { type: 'string', description: 'Known remaining risks or follow-ups (≤2000 chars, optional).' },\n },\n output: {\n schema: JSON_OUT,\n render: (_args, value) => {\n const v = value as { taskId?: string; executionId?: string; report?: { summary?: string } }\n if (v.taskId === undefined || v.report === undefined) return [{ type: 'text', text: '报告提交失败。' }]\n return [{\n type: 'text',\n text: `执行报告已记录到任务 ${v.taskId}(执行 ${v.executionId}):${v.report.summary?.slice(0, 120) ?? ''}\\n`\n + '接下来:taskboard_comment_add 留交接评论,然后 taskboard_move 移至待验收 in_review。',\n }]\n },\n },\n async execute(args: {\n summary: string\n changedFiles?: string[]\n checks?: string[]\n artifacts?: string[]\n risk?: string\n }, exec: unknown) {\n try {\n const { sessionId } = caller(exec as ToolRunContext)\n const report = normalizeExecutionReport(args)\n // Path 1 (unchanged): attach to the RUNNING execution this session\n // owns — reports ride the live run, so the agent never needs ids.\n let taskId: string | undefined\n let executionId: string | undefined\n await store.mutate('execution-recorded', ledger => {\n for (const task of ledger.tasks) {\n const execution = task.executions.find(e => e.sessionId === sessionId && e.outcome === 'running')\n if (execution !== undefined) {\n execution.report = report\n taskId = task.id\n executionId = execution.id\n return [task]\n }\n }\n return undefined\n })\n // Path 2 (review follow-up, P2): back-submit onto a session-owned\n // SETTLED execution — the main conversation claims tasks directly and\n // has no live run. Allowed when the session holds the task or owns\n // its latest successful execution; never touches anyone else's runs.\n if (taskId === undefined || executionId === undefined) {\n await store.mutate('execution-recorded', ledger => {\n for (const task of ledger.tasks) {\n if (task.trashedAt !== undefined || task.status === 'archived') continue\n const last = task.executions[task.executions.length - 1]\n const owned = last !== undefined && last.sessionId === sessionId && last.outcome === 'succeeded'\n const holds = task.claimedBy === sessionId && last !== undefined && last.sessionId === sessionId\n if (!owned && !holds) continue\n last!.report = report\n taskId = task.id\n executionId = last!.id\n return [task]\n }\n return undefined\n })\n }\n if (taskId === undefined || executionId === undefined) {\n throw new ToolError(ERR.forbidden, 'no running execution and no settled execution of yours to report on — reports attach to your running execution, or back-submit onto your latest succeeded one while you hold the task')\n }\n return json({ taskId, executionId, report })\n } catch (error) { fail(error) }\n },\n })) as () => void)\n\n return disposers\n}\n"],"mappings":";;;;AAuDA,SAAS,SAAS,GAaP;CACT,MAAM,QAAQ,CACZ,KAAK,EAAE,GAAG,IAAI,EAAE,OAAO,KAAK,EAAE,QAAQ,KAAK,EAAE,QAAQ,QAAQ,EAAE,eAC/D,IAAI,EAAE,MAAM,EACd;CACA,IAAI,EAAE,SAAS,MAAM,KAAK,KAAK;CAC/B,IAAI,EAAE,kBAAkB,aAAa,MAAM,KAAK,KAAK;CACrD,IAAI,EAAE,iBAAiB,KAAA,KAAa,EAAE,eAAe,GAAG,MAAM,KAAK,MAAM,EAAE,cAAc;CACzF,IAAI,EAAE,cAAc,KAAA,KAAa,EAAE,UAAU,QAAQ,GAAG,MAAM,KAAK,MAAM,EAAE,UAAU,KAAK,GAAG,EAAE,UAAU,OAAO;CAChH,IAAI,EAAE,yBAAyB,KAAA,GAAW,MAAM,KAAK,QAAQ,EAAE,sBAAsB;CACrF,IAAI,EAAE,YAAY,MAAM,MAAM,KAAK,KAAK;CACxC,OAAO,MAAM,KAAK,GAAG;AACvB;;AAGA,SAAS,WAAW,GAAsD;CACxE,MAAM,QAAkB;EACtB,MAAM,EAAE,GAAG,IAAI,EAAE,MAAM;EACvB,OAAO,EAAE,OAAO,KAAK,EAAE,QAAQ,WAAW,EAAE,QAAQ,SAAS,EAAE,cAAc,EAAE,UAAU,UAAU;EACnG,SAAS,EAAE,UAAU,OAAO,EAAE,UAAU,SAAS,KAAA,IAAY,SAAS,EAAE,UAAU,SAAS;EAC3F,OAAO,EAAE,cAAc,SAAS,cAAc,iBAAiB,EAAE,WAAW,KAAA,IAAY,OAAO,EAAE,OAAO,KAAK;CAC/G;CACA,MAAM,SAAS,YAAY,CAAC;CAC5B,IAAI,WAAW,KAAA,GAAW,MAAM,KAAK,aAAa,OAAO,MAAM,CAAC,CAAC,MAAM,GAAG,EAAE,EAAE,eAAe;CAC7F,IAAI,EAAE,UAAU,cAAc,KAAA,GAAW,MAAM,KAAK,SAAS,IAAI,KAAK,EAAE,UAAU,SAAS,CAAC,CAAC,YAAY,GAAG;CAC5G,IAAI,EAAE,UAAU,KAAA,GAAW,MAAM,KAAK,SAAS,EAAE,MAAM,SAAS,GAAG,EAAE,MAAM,OAAO;CAClF,IAAI,EAAE,aAAa,KAAA,GAAW,MAAM,KAAK,SAAS,EAAE,SAAS,mBAAmB;CAChF,MAAM,KAAK,OAAO,EAAE,YAAY,SAAS,IAAI,EAAE,cAAc,OAAO;CACpE,MAAM,KAAK,cAAc,EAAE,mBAAmB,gBAAgB,CAAC,GAAG;CAClE,IAAI,EAAE,cAAc,KAAA,KAAa,EAAE,UAAU,SAAS,GAAG;EACvD,MAAM,OAAO,EAAE,UAAU,QAAO,MAAK,EAAE,OAAO,CAAC,CAAC;EAChD,MAAM,KAAK,SAAS,KAAK,GAAG,EAAE,UAAU,OAAO,GAAG;EAClD,KAAK,MAAM,QAAQ,EAAE,WAAW;GAC9B,MAAM,OAAO,KAAK,UAAU,MAAM;GAClC,MAAM,MAAM,KAAK,cAAc,KAAA,IAAY,KAAK,KAAK,cAAc,SAAS,WAAW,WAAW,OAAO,KAAK,SAAS,CAAC,CAAC,MAAM,GAAG,EAAE,EAAE;GACtI,MAAM,OAAO,KAAK,SAAS,KAAA,IAAY,SAAS,KAAK,SAAS;GAC9D,MAAM,KAAK,KAAK,KAAK,GAAG,KAAK,OAAO,MAAM,MAAM;EAClD;CACF;CACA,IAAI,EAAE,SAAS,SAAS,GAAG;EACzB,MAAM,KAAK,OAAO,EAAE,SAAS,OAAO,GAAG;EACvC,KAAK,MAAM,KAAK,EAAE,UAAU;GAC1B,MAAM,MAAM,EAAE,aAAa,KAAA,IAAY,SAAS,OAAO,EAAE,QAAQ,CAAC,CAAC,MAAM,GAAG,EAAE,MAAM;GACpF,MAAM,KAAK,QAAQ,IAAI,GAAG,IAAI,KAAK,EAAE,SAAS,CAAC,CAAC,YAAY,EAAE,IAAI,EAAE,MAAM;EAC5E;CACF,OACE,MAAM,KAAK,OAAO;CAEpB,IAAI,EAAE,WAAW,SAAS,GAAG;EAC3B,MAAM,KAAK,SAAS,EAAE,WAAW,OAAO,GAAG;EAC3C,KAAK,MAAM,KAAK,EAAE,YAAY;GAC5B,MAAM,KAAK,EAAE,cAAc,KAAA,IAAY,IAAI,KAAK,EAAE,SAAS,CAAC,CAAC,YAAY,IAAI;GAC7E,MAAM,MAAM,EAAE,UAAU,KAAA,IAAY,QAAQ,EAAE,UAAU;GACxD,MAAM,SAAS,EAAE,WAAW,KAAA,IAAY,YAAY;GACpD,MAAM,KAAK,QAAQ,EAAE,QAAQ,GAAG,GAAG,IAAI,EAAE,UAAU,SAAS,KAAK;EACnE;CACF,OACE,MAAM,KAAK,SAAS;CAEtB,MAAM,YAAY,EAAE,UAAU,SAAS,UAAU,SAAS,OAAO,EAAE,UAAU,SAAS,CAAC,CAAC,MAAM,GAAG,EAAE,MAAM,EAAE,UAAU,SAAS,WAAW,WAAW;CACpJ,MAAM,KAAK,OAAO,IAAI,KAAK,EAAE,SAAS,CAAC,CAAC,YAAY,EAAE,KAAK,WAAW;CACtE,OAAO,MAAM,KAAK,IAAI;AACxB;;AAGA,MAAa,MAAM;CACjB,UAAU;CACV,iBAAiB;CACjB,mBAAmB;CACnB,mBAAmB;CACnB,WAAW;CACX,eAAe;CACf,cAAc;AAChB;;;;AAKA,IAAa,YAAb,cAA+B,MAAM;CACd;CAArB,YAAY,MAAuB,QAAgB;EACjD,MAAM,UAAU,KAAK,IAAI,QAAQ;EADd,KAAA,OAAA;CAErB;AACF;;AAaA,SAAgB,cAAc,UAA4C;CAGxE,OAAO;EACL,eAAe,OAAO,SAAS;GAC7B,MAAM,KAAK,MAAM,SAAS,cAAc,IAAa;GACrD,OAAO,OAAO,KAAA,IAAY,KAAA,IAAY,EAAE,IAAI,GAAG,GAAG;EACpD;EACA,MAAK,OAAM;GACT,MAAM,KAAK,SAAS,IAAI,EAAW;GACnC,OAAO,OAAO,KAAA,IAAY,KAAA,IAAY;IAAE,IAAI,GAAG;IAAI,MAAM,GAAG;IAAM,OAAO,GAAG;GAAM;EACpF;EACA,YAAY,SAAS,KAAK,CAAC,CAAC,KAAI,QAAO;GAAE,IAAI,GAAG;GAAI,MAAM,GAAG;GAAM,OAAO,GAAG;EAAM,EAAE;CACvF;AACF;;AAiBA,SAAS,WAAW,MAAgB,KAAyB;CAC3D,MAAM,QAAQ,eAAe,GAAG;CAChC,MAAM,YAAY,KAAK,iBAAiB;CACxC,IAAI,cAAc,KAAA,KAAa,CAAC,UAAU,SAAS,MAAM,QAAQ,GAC/D,MAAM,IAAI,UAAU,IAAI,cAAc,mBAAmB,MAAM,SAAS,wCAAwC,UAAU,KAAK,IAAI,EAAE,EAAE;CAEzI,OAAO;AACT;;AAGA,SAAS,OAAO,MAA+E;CAC7F,IAAI,CAAC,KAAK,OAAO,MAAM,IAAI,UAAU,IAAI,eAAe,iDAAiD;CACzG,MAAM,YAAY,KAAK,MAAM;CAC7B,OAAO;EAAE,OAAO;GAAE,MAAM;GAAS;EAAU;EAAG;CAAU;AAC1D;;AAGA,eAAe,gBAAgB,MAAgB,MAAmD;CAChG,MAAM,MAAM,KAAK,OAAO,QAAQ,OAAO;CACvC,IAAI,OAAO,QAAQ,YAAY,IAAI,WAAW,GAAG,OAAO,KAAA;CAExD,QAAO,MADU,KAAK,WAAW,cAAc,GAAG,EAAA,EACvC;AACb;;AAGA,SAAS,aAAa,MAAkB,WAAqC;CAC3E,IAAI,cAAc,KAAA,GAChB,MAAM,IAAI,UAAU,IAAI,iBAAiB,oDAAoD;CAE/F,IAAI,cAAc,KAAK,SACrB,MAAM,IAAI,UAAU,IAAI,iBAAiB,iBAAiB,UAAU,YAAY,KAAK,QAAQ,mCAAmC;AAEpI;;;;;;;AAQA,SAAS,WAAW,QAAoB,IAAiD;CACvF,MAAM,QAAQ,OAAO,MAAM,WAAU,MAAK,EAAE,OAAO,EAAE;CACrD,IAAI,QAAQ,GAAG,MAAM,IAAI,UAAU,IAAI,UAAU,WAAW,IAAI;CAChE,MAAM,OAAO,OAAO,MAAM;CAC1B,IAAI,KAAK,cAAc,KAAA,GAAW,MAAM,IAAI,UAAU,IAAI,UAAU,WAAW,IAAI;CACnF,OAAO;EAAE;EAAO;CAAK;AACvB;;AAGA,SAAS,KAAK,OAAuB;CACnC,IAAI,iBAAiB,WAAW,MAAM;CACtC,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;CACrE,MAAM,IAAI,UAAU,IAAI,cAAc,OAAO;AAC/C;;AAGA,MAAM,WAAW,EAAE,MAAM,OAAO;;AAGhC,SAAS,KAAQ,OAAmC;CAClD,OAAO,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC;AACzC;;;;;;;AAkBA,SAAgB,uBAAuB,KAAsB,MAAmC;CAC9F,MAAM,YAA+B,CAAC;CACtC,MAAM,EAAE,OAAO,eAAe;CAG9B,MAAM,YAAY,SAA8C;EAC9D,IAAI,QAAQ,IAAI,cAAc,OAAO,OAAO,KAAK,YAAY,YAAY;GACvE,MAAM,OAAO,KAAK;GAClB,KAAK,UAAU,OAAO,MAAe,SAAkB;IACrD,QAAQ,MAAM,WAAW,KAAK,QAAQ,KAAK,UAAU,IAAI,CAAC,CAAC,MAAM,GAAG,GAAG,CAAC;IACxE,IAAI;KACF,MAAM,SAAS,MAAM,KAAK,MAAM,IAAI;KACpC,QAAQ,MAAM,WAAW,KAAK,QAAQ,KAAK,UAAU,MAAM,CAAC,CAAC,MAAM,GAAG,GAAG,CAAC;KAC1E,OAAO;IACT,SAAS,OAAO;KACd,QAAQ,MAAM,WAAW,KAAK,QAAQ,OAAO,KAAK,CAAC,CAAC,MAAM,GAAG,GAAG,CAAC;KACjE,MAAM;IACR;GACF;EACF;EACA,OAAO,IAAI,MAAM,SAAS,IAAwB;CACpD;CAGA,UAAU,KAAK,SAAS,WAAW;EACjC,MAAM;EACN,aACE;EAGF,YAAY;GACV,aAAa;IAAE,MAAM;IAAU,aAAa;GAAwC;GACpF,QAAQ;IAAE,MAAM;IAAU,aAAa;GAAsF;GAC7H,SAAS;IAAE,MAAM;IAAU,aAAa;GAA6C;GACrF,gBAAgB;IAAE,MAAM;IAAW,aAAa;GAA8C;EAChG;EACA,QAAQ;GACN,QAAQ;GACR,SAAS,OAAO,UAAU;IACxB,MAAM,IAAI;IACV,MAAM,QAAQ,EAAE,SAAS,CAAC;IAC1B,MAAM,OAAO,MAAM,MAAM,OAAO,YAAY,EAAE,YAAY,IAAI;IAC9D,IAAI,MAAM,WAAW,GAAG,OAAO,CAAC;KAAE,MAAM;KAAQ,MAAM,GAAG,KAAK;IAAS,CAAC;IACxE,OAAO,CAAC;KAAE,MAAM;KAAQ,MAAM,CAAC,MAAM,GAAG,MAAM,KAAI,MAAK,SAAS,CAAU,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI;IAAE,CAAC;GAC5F;EACF;EACA,MAAM,QAAQ,MAAM;GAClB,IAAI;IACF,MAAM,IAAI;IACV,MAAM,QAAQ,MAAM,SAAS,CAAC,CAAC,MAAM,QAAO,OACzC,EAAE,gBAAgB,KAAA,KAAa,EAAE,gBAAgB,EAAE,iBAChD,EAAE,WAAW,KAAA,KAAa,EAAE,WAAW,EAAE,YACzC,EAAE,YAAY,KAAA,KAAa,EAAE,YAAY,EAAE,aAC3C,EAAE,mBAAmB,QAAQ,EAAE,cAAc,KAAA,EAAU;IAC7D,OAAO,KAAK;KAAE,UAAU,MAAM,SAAS,CAAC,CAAC;KAAU,OAAO,MAAM,IAAI,SAAS;IAAE,CAAC;GAClF,SAAS,OAAO;IAAE,KAAK,KAAK;GAAE;EAChC;CACF,CAAC,CAAC,CAAe;CAGjB,UAAU,KAAK,SAAS,WAAW;EACjC,MAAM;EACN,aACE;EAEF,YAAY,EACV,IAAI;GAAE,MAAM;GAAU,UAAU;GAAM,aAAa;EAA0B,EAC/E;EACA,QAAQ;GACN,QAAQ;GACR,SAAS,OAAO,UAAU;IACxB,MAAM,IAAI;IACV,OAAO,CAAC;KAAE,MAAM;KAAQ,MAAM,EAAE,SAAS,KAAA,IAAY,WAAW,WAAW,EAAE,IAAI;IAAE,CAAC;GACtF;EACF;EACA,MAAM,QAAQ,MAAsB;GAClC,IAAI;IACF,MAAM,EAAE,OAAO;IACf,MAAM,OAAO,MAAM,IAAI,EAAE;IACzB,IAAI,SAAS,KAAA,KAAa,KAAK,cAAc,KAAA,GAAW,MAAM,IAAI,UAAU,IAAI,UAAU,WAAW,IAAI;IACzG,OAAO,KAAK,EAAE,MAAM;KAAE,GAAG;KAAM,iBAAiB,gBAAgB,IAAI;IAAE,EAAE,CAAC;GAC3E,SAAS,OAAO;IAAE,KAAK,KAAK;GAAE;EAChC;CACF,CAAC,CAAC,CAAe;CAGjB,UAAU,KAAK,SAAS,WAAW;EACjC,MAAM;EACN,aACE;EAIF,YAAY;GACV,OAAO;IAAE,MAAM;IAAU,UAAU;IAAM,aAAa;GAAwC;GAC9F,aAAa;IAAE,MAAM;IAAU,UAAU;IAAM,aAAa;GAAmD;GAC/G,SAAS;IAAE,MAAM;IAAU,UAAU;IAAM,aAAa;GAAmD;GAC3G,aAAa;IAAE,MAAM;IAAU,aAAa;GAAuC;GACnF,QAAQ;IAAE,MAAM;IAAU,aAAa;GAAoF;GAC3H,QAAQ;IAAE,MAAM;IAAU,aAAa;GAAsE;GAC7G,WAAW;IACT,MAAM;IACN,sBAAsB;IACtB,aAAa;IACb,YAAY;KACV,MAAM;MAAE,MAAM;MAAU,aAAa;KAAqB;KAC1D,MAAM;MAAE,MAAM;MAAU,aAAa;KAA+C;IACtF;GACF;GACA,OAAO;IACL,MAAM;IACN,sBAAsB;IACtB,aAAa;IACb,YAAY;KACV,UAAU;MAAE,MAAM;MAAU,aAAa;KAAqB;KAC9D,OAAO;MAAE,MAAM;MAAU,aAAa;KAA2B;IACnE;GACF;GACA,WAAW;IACT,MAAM;IACN,aAAa;GACf;GACA,UAAU;IACR,MAAM;IACN,aAAa;GACf;GACA,WAAW;IACT,MAAM;IACN,aAAa;IACb,OAAO,EAAE,MAAM,SAAS;GAC1B;EACF;EACA,QAAQ;GACN,QAAQ;GACR,SAAS,OAAO,UAAU;IAExB,MAAM,IAAIA,MAAE;IACZ,OAAO,CAAC;KAAE,MAAM;KAAQ,MAAM,MAAM,KAAA,IAAY,UAAU,SAAS,EAAE,GAAG,IAAI,EAAE,OAAO,KAAK,EAAE,QAAQ;IAAyB,CAAC;GAChI;EACF;EACA,MAAM,QAAQ,MAYX,MAAe;GAChB,IAAI;IACF,MAAM,EAAE,UAAU,OAAO,IAAsB;IAC/C,MAAM,QAAQ,eAAe,KAAK,KAAK;IACvC,IAAI,WAAW,IAAI,KAAK,WAAW,MAAM,KAAA,GACvC,MAAM,IAAI,UAAU,IAAI,UAAU,uBAAuB,KAAK,aAAa;IAE7E,MAAM,UAAU,UAAU,KAAK,OAAO;IACtC,MAAM,SAAS,KAAK,WAAW,KAAA,IAAY,SAAkB,SAAS,KAAK,MAAM;IACjF,IAAI,WAAW,aAAa,WAAW,QACrC,MAAM,IAAI,UAAU,IAAI,mBAAmB,mFAAmF;IAEhI,MAAM,YAAY,mBAAmB,KAAK,aAAa,CAAC,GAAG,KAAK,IAAI,CAAC;IACrE,MAAM,QAAQ,KAAK,UAAU,KAAA,IAAY,WAAW,MAAM,KAAK,KAAK,IAAI,KAAA;IAIxE,MAAM,YAAY,KAAK,cAAc,KAAA,IAAY,mBAAmB,MAAM,SAAS,CAAC,CAAC,QAAQ,IAAI,YAAY,KAAK,SAAS;IAC3H,MAAM,WAAW,KAAK,UAAU,KAAK,KAAK,KAAA;IAG1C,MAAM,iBAAiB,KAAK,WAAW,KAAI,MAAK,EAAE,KAAK,CAAC,CAAC,CAAC,QAAO,MAAK,EAAE,SAAS,CAAC;IAClF,MAAM,YAAY,mBAAmB,KAAA,KAAa,eAAe,SAAS,IAAI,mBAAmB,cAAc,IAAI,KAAA;IACnH,MAAM,MAAM,KAAK,IAAI;IACrB,MAAM,OAAmB;KACvB,IAAI,UAAU;KACd;KACA,cAAc,KAAK,eAAe,GAAA,CAAI,KAAK;KAC3C,QAAQ,gBAAgB,KAAK,MAAM;KACnC,aAAa,KAAK;KAClB;KACA;KACA,SAAS;KACT;KACA;KACA;KACA,GAAI,aAAa,KAAA,IAAY,EAAE,SAAS,IAAI,CAAC;KAC7C,GAAI,cAAc,KAAA,IAAY,EAAE,UAAU,IAAI,CAAC;KAC/C,SAAS;KACT,WAAW;KACX,WAAW;KACX,WAAW;KACX,WAAW;KACX,UAAU,CAAC;KACX,YAAY,CAAC;IACf;IACA,MAAM,MAAM,OAAO,iBAAgB,WAAU;KAC3C,OAAO,MAAM,KAAK,IAAI;KACtB,OAAO,CAAC,IAAI;IACd,CAAC;IACD,OAAO,KAAK,EAAE,MAAM,UAAU,IAAI,EAAE,CAAC;GACvC,SAAS,OAAO;IAAE,KAAK,KAAK;GAAE;EAChC;CACF,CAAC,CAAC,CAAe;CAGjB,UAAU,KAAK,SAAS,WAAW;EACjC,MAAM;EACN,aACE;EAEF,YAAY;GACV,IAAI;IAAE,MAAM;IAAU,UAAU;IAAM,aAAa;GAAW;GAC9D,WAAW;IAAE,MAAM;IAAU,UAAU;IAAM,aAAa;GAA0D;GACpH,OAAO;IAAE,MAAM;IAAU,aAAa;GAAa;GACnD,aAAa;IAAE,MAAM;IAAU,aAAa;GAAmB;GAC/D,QAAQ;IAAE,MAAM;IAAU,aAAa;GAAwB;GAC/D,SAAS;IAAE,MAAM;IAAU,aAAa;GAA6B;GACrE,SAAS;IAAE,MAAM;IAAW,aAAa;GAAmD;EAC9F;EACA,QAAQ;GACN,QAAQ;GACR,SAAS,OAAO,UAAU;IAExB,MAAM,IAAIA,MAAE;IACZ,OAAO,CAAC;KAAE,MAAM;KAAQ,MAAM,MAAM,KAAA,IAAY,UAAU,SAAS,EAAE,GAAG,OAAO,EAAE,QAAQ,IAAI,EAAE,OAAO;IAAI,CAAC;GAC7G;EACF;EACA,MAAM,QAAQ,MAQX,MAAe;GAChB,IAAI;IACF,MAAM,EAAE,UAAU,OAAO,IAAsB;IAI/C,IAAI;IACJ,MAAM,MAAM,OAAO,iBAAgB,WAAU;KAC3C,MAAM,EAAE,OAAO,SAAS,WAAW,QAAQ,KAAK,EAAE;KAClD,aAAa,MAAM,KAAK,SAAS;KACjC,IAAI,KAAK,WAAW,YAAY,MAAM,IAAI,UAAU,IAAI,mBAAmB,8BAA8B;KACzG,OAAO,gBAAgB,IAAI;KAC3B,IAAI,KAAK,UAAU,KAAA,GAAW,KAAK,QAAQ,eAAe,KAAK,KAAK;KACpE,IAAI,KAAK,gBAAgB,KAAA,GAAW,KAAK,cAAc,KAAK,YAAY,KAAK;KAC7E,IAAI,KAAK,WAAW,KAAA,GAAW,KAAK,SAAS,gBAAgB,KAAK,MAAM;KACxE,IAAI,KAAK,YAAY,KAAA,GAAW,KAAK,UAAU,UAAU,KAAK,OAAO;KACrE,IAAI,KAAK,YAAY,KAAA,GAAW,KAAK,UAAU,KAAK;KACpD,KAAK,UAAU,KAAK,UAAU;KAC9B,KAAK,YAAY,KAAK,IAAI;KAC1B,KAAK,YAAY;KACjB,OAAO,MAAM,SAAS;KACtB,OAAO,CAAC,IAAI;IACd,CAAC;IACD,OAAO,KAAK,EAAE,MAAM,UAAU,IAAK,EAAE,CAAC;GACxC,SAAS,OAAO;IAAE,KAAK,KAAK;GAAE;EAChC;CACF,CAAC,CAAC,CAAe;CAGjB,UAAU,KAAK,SAAS,WAAW;EACjC,MAAM;EACN,aACE;EAIF,YAAY;GACV,IAAI;IAAE,MAAM;IAAU,UAAU;IAAM,aAAa;GAAW;GAC9D,QAAQ;IAAE,MAAM;IAAU,UAAU;IAAM,aAAa;GAAiB;GACxE,WAAW;IAAE,MAAM;IAAU,UAAU;IAAM,aAAa;GAA4C;EACxG;EACA,QAAQ;GACN,QAAQ;GACR,SAAS,OAAO,UAAU;IAExB,MAAM,IAAIA,MAAE;IACZ,OAAO,CAAC;KAAE,MAAM;KAAQ,MAAM,MAAM,KAAA,IAAY,UAAU,MAAM,EAAE,GAAG,OAAO,EAAE,OAAO,OAAO,EAAE,QAAQ;IAAG,CAAC;GAC5G;EACF;EACA,MAAM,QAAQ,MAAyD,MAAe;GACpF,IAAI;IACF,MAAM,EAAE,UAAU,OAAO,IAAsB;IAC/C,MAAM,KAAK,SAAS,KAAK,MAAM;IAI/B,MAAM,aAAa,OAAO,gBACtB,MAAM,gBAAgB,MAAM,IAAsB,IAClD,KAAA;IAEJ,IAAI;IACJ,MAAM,MAAM,OAAO,eAAc,WAAU;KACzC,MAAM,EAAE,OAAO,SAAS,WAAW,QAAQ,KAAK,EAAE;KAClD,aAAa,MAAM,KAAK,SAAS;KAGjC,IAAI,OAAO,QACT,MAAM,IAAI,UAAU,IAAI,WAAW,sFAAsF;KAE3H,IAAI,CAAC,cAAc,KAAK,QAAQ,EAAE,GAChC,MAAM,IAAI,UAAU,IAAI,mBAAmB,sBAAsB,KAAK,OAAO,KAAK,IAAI;KAKxF,IAAI,KAAK,WAAW,iBAAiB,KAAK,cAAc,KAAA,KAAa,KAAK,cAAc,MAAM,WAC5F,MAAM,IAAI,UAAU,IAAI,WAAW,2BAA2B,KAAK,UAAU,0CAA0C;KAGzH,IAAI,QAAQ,KAAK,QAAQ,EAAE,KAAK,eAAe,KAAK,aAClD,MAAM,IAAI,UAAU,IAAI,mBAAmB,wDAAyD;KAEtG,OAAO,gBAAgB,IAAI;KAC3B,KAAK,SAAS;KACd,KAAK,UAAU,KAAK,UAAU;KAC9B,KAAK,YAAY,KAAK,IAAI;KAC1B,KAAK,YAAY;KACjB,IAAI,QAAQ,KAAK,QAAQ,EAAE,GAAG,KAAK,UAAU;KAE7C,UAAU,MAAM,IAAI,KAAK,IAAI,GAAG,QAAQ,KAAK,QAAQ,EAAE,IAAI,MAAM,YAAY,KAAA,CAAS;KACtF,OAAO,MAAM,SAAS;KACtB,OAAO,CAAC,IAAI;IACd,CAAC;IACD,OAAO,KAAK,EAAE,MAAM,UAAU,IAAK,EAAE,CAAC;GACxC,SAAS,OAAO;IAAE,KAAK,KAAK;GAAE;EAChC;CACF,CAAC,CAAC,CAAe;CAGjB,UAAU,KAAK,SAAS,WAAW;EACjC,MAAM;EACN,aACE;EAEF,YAAY;GACV,IAAI;IAAE,MAAM;IAAU,UAAU;IAAM,aAAa;GAAW;GAC9D,MAAM;IAAE,MAAM;IAAU,UAAU;IAAM,aAAa;GAAgC;EACvF;EACA,QAAQ;GACN,QAAQ;GACR,SAAS,OAAO,UAAU;IACxB,MAAM,IAAI;IACV,MAAM,IAAI,EAAE;IACZ,MAAM,IAAI,EAAE;IACZ,IAAI,MAAM,KAAA,KAAa,MAAM,KAAA,GAAW,OAAO,CAAC;KAAE,MAAM;KAAQ,MAAM;IAAQ,CAAC;IAG/E,OAAO,CAAC;KACN,MAAM;KACN,MAAM,MAAM,EAAE,GAAG,UAAU,EAAE,GAAG,OAAO,EAAE,QAAQ,IAAI,EAAE,OAAO;IAChE,CAAC;GACH;EACF;EACA,MAAM,QAAQ,MAAoC,MAAe;GAC/D,IAAI;IACF,MAAM,EAAE,cAAc,OAAO,IAAsB;IACnD,MAAM,UAAU;KACd,IAAI,aAAa;KACjB,MAAM,cAAc,KAAK,IAAI;KAC7B,SAAS;KACT,WAAW,KAAK,IAAI;KACpB,UAAU;IACZ;IAIA,IAAI;IACJ,MAAM,MAAM,OAAO,kBAAiB,WAAU;KAC5C,MAAM,EAAE,OAAO,SAAS,WAAW,QAAQ,KAAK,EAAE;KAClD,IAAI,KAAK,WAAW,YAAY,MAAM,IAAI,UAAU,IAAI,mBAAmB,8BAA8B;KACzG,OAAO,gBAAgB,IAAI;KAC3B,KAAK,SAAS,KAAK,OAAO;KAC1B,KAAK,UAAU,KAAK,UAAU;KAC9B,KAAK,YAAY,KAAK,IAAI;KAC1B,OAAO,MAAM,SAAS;KACtB,OAAO,CAAC,IAAI;IACd,CAAC;IACD,OAAO,KAAK;KAAE;KAAS,MAAM;MAAE,IAAI,KAAM;MAAI,SAAS,KAAM;MAAS,QAAQ,KAAM;KAAO;IAAE,CAAC;GAC/F,SAAS,OAAO;IAAE,KAAK,KAAK;GAAE;EAChC;CACF,CAAC,CAAC,CAAe;CAGjB,UAAU,KAAK,SAAS,WAAW;EACjC,MAAM;EACN,aAAa;EACb,YAAY,EACV,IAAI;GAAE,MAAM;GAAU,UAAU;GAAM,aAAa;EAAW,EAChE;EACA,QAAQ;GACN,QAAQ;GACR,SAAS,OAAO,UAAU;IAExB,MAAM,OAAOA,MAAE;IACf,IAAI,SAAS,KAAA,KAAa,KAAK,WAAW,GAAG,OAAO,CAAC;KAAE,MAAM;KAAQ,MAAM;IAAO,CAAC;IACnF,MAAM,QAAQ,KAAK,KAAI,MAAK;KAE1B,OAAO,MADK,EAAE,aAAa,KAAA,IAAY,SAAS,OAAO,EAAE,QAAQ,CAAC,CAAC,MAAM,GAAG,EAAE,MAAM,OACnE,GAAG,IAAI,KAAK,EAAE,SAAS,CAAC,CAAC,YAAY,EAAE,IAAI,EAAE;IAChE,CAAC;IACD,OAAO,CAAC;KAAE,MAAM;KAAQ,MAAM,MAAM,KAAK,OAAO,OAAO,MAAM,KAAK,IAAI;IAAI,CAAC;GAC7E;EACF;EACA,MAAM,QAAQ,MAAsB;GAClC,IAAI;IACF,MAAM,OAAO,MAAM,IAAI,KAAK,EAAE;IAC9B,IAAI,SAAS,KAAA,KAAa,KAAK,cAAc,KAAA,GAAW,MAAM,IAAI,UAAU,IAAI,UAAU,WAAW,KAAK,IAAI;IAC9G,OAAO,KAAK,EAAE,UAAU,KAAK,SAAS,CAAC;GACzC,SAAS,OAAO;IAAE,KAAK,KAAK;GAAE;EAChC;CACF,CAAC,CAAC,CAAe;CAGjB,UAAU,KAAK,SAAS,WAAW;EACjC,MAAM;EACN,aACE;EAEF,YAAY;GACV,IAAI;IAAE,MAAM;IAAU,UAAU;IAAM,aAAa;GAAW;GAC9D,WAAW;IAAE,MAAM;IAAU,UAAU;IAAM,aAAa;GAAyB;EACrF;EACA,QAAQ;GACN,QAAQ;GACR,SAAS,OAAO,UAAU;IAExB,OAAO,CAAC;KAAE,MAAM;KAAQ,MAAMA,MAAE,YAAY,OAAO,2BAA2B;IAAQ,CAAC;GACzF;EACF;EACA,MAAM,QAAQ,MAAyC,MAAe;GACpE,IAAI;IACF,OAAO,IAAsB;IAI7B,IAAI;IACJ,MAAM,MAAM,OAAO,iBAAgB,WAAU;KAC3C,MAAM,EAAE,OAAO,SAAS,WAAW,QAAQ,KAAK,EAAE;KAClD,aAAa,MAAM,KAAK,SAAS;KACjC,IAAI,KAAK,WAAW,MAAK,MAAK,EAAE,YAAY,SAAS,GACnD,MAAM,IAAI,UAAU,IAAI,cAAc,+BAA+B;KAEvE,OAAO,gBAAgB,IAAI;KAC3B,KAAK,YAAY,KAAK,IAAI;KAC1B,KAAK,UAAU,KAAK,UAAU;KAC9B,OAAO,KAAK;KACZ,OAAO,KAAK;KACZ,KAAK,UAAU;KACf,OAAO,MAAM,SAAS;KACtB,OAAO,CAAC,IAAI;IACd,CAAC;IACD,OAAO,EAAE,SAAS,KAAK;GACzB,SAAS,OAAO;IAAE,KAAK,KAAK;GAAE;EAChC;CACF,CAAC,CAAC,CAAe;CAGjB,UAAU,KAAK,SAAS,WAAW;EACjC,MAAM;EACN,aACE;EAGF,YAAY;GACV,IAAI;IAAE,MAAM;IAAU,UAAU;IAAM,aAAa;GAAW;GAC9D,QAAQ;IAAE,MAAM;IAAU,UAAU;IAAM,aAAa;GAAyB;GAChF,WAAW;IAAE,MAAM;IAAU,UAAU;IAAM,aAAa;GAA4C;GACtG,OAAO;IACL,MAAM;IACN,aAAa;IACb,OAAO,EAAE,MAAM,SAAS;GAC1B;GACA,QAAQ;IAAE,MAAM;IAAU,aAAa;GAAgD;GACvF,MAAM;IAAE,MAAM;IAAU,aAAa;GAAsD;EAC7F;EACA,QAAQ;GACN,QAAQ;GACR,SAAS,OAAO,UAAU;IACxB,MAAM,IAAI;IACV,IAAI,EAAE,SAAS,KAAA,KAAa,EAAE,cAAc,KAAA,GAAW,OAAO,CAAC;KAAE,MAAM;KAAQ,MAAM;IAAU,CAAC;IAChG,MAAM,QAAQ,EAAE,UAAU,KAAK,GAAG,UAAU,GAAG,EAAE,YAAY,OAAO,MAAM,IAAI,IAAI,QAAQ,EAAE,IAAI,OAAO,EAAE,IAAI,IAAI,EAAE,SAAS,KAAA,IAAY,QAAQ,OAAO,EAAE,IAAI,EAAE,KAAK,GAAG,MAAM,OAAO,EAAE,EAAE,GAAG;IAC3L,OAAO,CAAC;KACN,MAAM;KACN,MAAM,MAAM,EAAE,KAAK,GAAG,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,SAAS,EAAE,WAAW,EAAE,KAAK,QAAQ,KAAK,MAAM,KAAK,IAAI;IAC1G,CAAC;GACH;EACF;EACA,MAAM,QAAQ,MAOX,MAAe;GAChB,IAAI;IACF,MAAM,EAAE,UAAU,OAAO,IAAsB;IAE/C,IAAI;IACJ,MAAM,MAAM,OAAO,iBAAgB,WAAU;KAC3C,MAAM,EAAE,OAAO,SAAS,WAAW,QAAQ,KAAK,EAAE;KAClD,aAAa,MAAM,KAAK,SAAS;KACjC,IAAI,KAAK,WAAW,YAAY,MAAM,IAAI,UAAU,IAAI,mBAAmB,8BAA8B;KACzG,OAAO,gBAAgB,IAAI;KAC3B,MAAM,YAA6B,KAAK,cAAc,KAAA,IAAY,CAAC,IAAI,CAAC,GAAG,KAAK,SAAS;KAEzF,IAAI,KAAK,WAAW,OAAO;MACzB,MAAM,QAAQ,KAAK,SAAS,CAAC;MAC7B,IAAI,MAAM,WAAW,KAAK,MAAM,SAAS,IACvC,MAAM,IAAI,UAAU,IAAI,cAAc,2CAA2C;MAEnF,IAAI,UAAU,SAAS,MAAM,SAAA,IAC3B,MAAM,IAAI,UAAU,IAAI,cAAc,kDAAsE,UAAU,OAAO,EAAE;MAEjI,UAAU,KAAK,GAAG,mBAAmB,KAAK,CAAC;KAC7C,OAAO,IAAI,KAAK,WAAW,SAAS;MAClC,IAAI,KAAK,WAAW,KAAA,GAAW,MAAM,IAAI,UAAU,IAAI,cAAc,8BAA8B;MACnG,MAAM,OAAO,UAAU,MAAK,MAAK,EAAE,OAAO,KAAK,MAAM;MACrD,IAAI,SAAS,KAAA,GAAW,MAAM,IAAI,UAAU,IAAI,UAAU,qBAAqB,KAAK,OAAO,SAAS,KAAK,GAAG,EAAE;MAC9G,MAAM,OAAO,KAAK,SAAS,KAAA,KAAa,KAAK,KAAK,KAAK,CAAC,CAAC,SAAS,IAAI,KAAK,KAAK,KAAK,CAAC,CAAC,MAAM,GAAG,GAAG,IAAI,KAAA;MACvG,KAAK,UAAU;MACf,KAAK,YAAY,MAAM;MACvB,KAAK,YAAY,KAAK,IAAI;MAC1B,IAAI,SAAS,KAAA,GAAW,KAAK,OAAO;KACtC,OAAO,IAAI,KAAK,WAAW,WAAW;MACpC,IAAI,KAAK,WAAW,KAAA,GAAW,MAAM,IAAI,UAAU,IAAI,cAAc,gCAAgC;MACrG,MAAM,OAAO,UAAU,MAAK,MAAK,EAAE,OAAO,KAAK,MAAM;MACrD,IAAI,SAAS,KAAA,GAAW,MAAM,IAAI,UAAU,IAAI,UAAU,qBAAqB,KAAK,OAAO,SAAS,KAAK,GAAG,EAAE;MAC9G,KAAK,UAAU;MACf,OAAO,KAAK;MACZ,OAAO,KAAK;MACZ,OAAO,KAAK;KACd,OACE,MAAM,IAAI,UAAU,IAAI,cAAc,8CAA8C,KAAK,OAAO,GAAG;KAGrG,IAAI,UAAU,SAAS,GAAG,KAAK,YAAY;UACtC,OAAO,KAAK;KACjB,KAAK,UAAU,KAAK,UAAU;KAC9B,KAAK,YAAY,KAAK,IAAI;KAC1B,KAAK,YAAY;KACjB,OAAO,MAAM,SAAS;KACtB,OAAO,CAAC,IAAI;IACd,CAAC;IACD,MAAM,WAAW,KAAM,cAAc,KAAA,IACjC;KAAE,MAAM,KAAM,UAAU,QAAO,MAAK,EAAE,OAAO,CAAC,CAAC;KAAQ,OAAO,KAAM,UAAU;IAAO,IACrF;KAAE,MAAM;KAAG,OAAO;IAAE;IACxB,OAAO,KAAK;KAAE,MAAM;MAAE,IAAI,KAAM;MAAI,SAAS,KAAM;KAAQ;KAAG,WAAW,KAAM,aAAa,CAAC;KAAG,GAAG;IAAS,CAAC;GAC/G,SAAS,OAAO;IAAE,KAAK,KAAK;GAAE;EAChC;CACF,CAAC,CAAC,CAAe;CAGjB,UAAU,KAAK,SAAS,WAAW;EACjC,MAAM;EACN,aACE;EAIF,YAAY;GACV,SAAS;IAAE,MAAM;IAAU,UAAU;IAAM,aAAa;GAAiC;GACzF,cAAc;IACZ,MAAM;IACN,aAAa;IACb,OAAO,EAAE,MAAM,SAAS;GAC1B;GACA,QAAQ;IACN,MAAM;IACN,aAAa;IACb,OAAO,EAAE,MAAM,SAAS;GAC1B;GACA,WAAW;IACT,MAAM;IACN,aAAa;IACb,OAAO,EAAE,MAAM,SAAS;GAC1B;GACA,MAAM;IAAE,MAAM;IAAU,aAAa;GAA+D;EACtG;EACA,QAAQ;GACN,QAAQ;GACR,SAAS,OAAO,UAAU;IACxB,MAAM,IAAI;IACV,IAAI,EAAE,WAAW,KAAA,KAAa,EAAE,WAAW,KAAA,GAAW,OAAO,CAAC;KAAE,MAAM;KAAQ,MAAM;IAAU,CAAC;IAC/F,OAAO,CAAC;KACN,MAAM;KACN,MAAM,cAAc,EAAE,OAAO,MAAM,EAAE,YAAY,IAAI,EAAE,OAAO,SAAS,MAAM,GAAG,GAAG,KAAK,GAAG;IAE7F,CAAC;GACH;EACF;EACA,MAAM,QAAQ,MAMX,MAAe;GAChB,IAAI;IACF,MAAM,EAAE,cAAc,OAAO,IAAsB;IACnD,MAAM,SAAS,yBAAyB,IAAI;IAG5C,IAAI;IACJ,IAAI;IACJ,MAAM,MAAM,OAAO,uBAAsB,WAAU;KACjD,KAAK,MAAM,QAAQ,OAAO,OAAO;MAC/B,MAAM,YAAY,KAAK,WAAW,MAAK,MAAK,EAAE,cAAc,aAAa,EAAE,YAAY,SAAS;MAChG,IAAI,cAAc,KAAA,GAAW;OAC3B,UAAU,SAAS;OACnB,SAAS,KAAK;OACd,cAAc,UAAU;OACxB,OAAO,CAAC,IAAI;MACd;KACF;IAEF,CAAC;IAKD,IAAI,WAAW,KAAA,KAAa,gBAAgB,KAAA,GAC1C,MAAM,MAAM,OAAO,uBAAsB,WAAU;KACjD,KAAK,MAAM,QAAQ,OAAO,OAAO;MAC/B,IAAI,KAAK,cAAc,KAAA,KAAa,KAAK,WAAW,YAAY;MAChE,MAAM,OAAO,KAAK,WAAW,KAAK,WAAW,SAAS;MACtD,MAAM,QAAQ,SAAS,KAAA,KAAa,KAAK,cAAc,aAAa,KAAK,YAAY;MACrF,MAAM,QAAQ,KAAK,cAAc,aAAa,SAAS,KAAA,KAAa,KAAK,cAAc;MACvF,IAAI,CAAC,SAAS,CAAC,OAAO;MACtB,KAAM,SAAS;MACf,SAAS,KAAK;MACd,cAAc,KAAM;MACpB,OAAO,CAAC,IAAI;KACd;IAEF,CAAC;IAEH,IAAI,WAAW,KAAA,KAAa,gBAAgB,KAAA,GAC1C,MAAM,IAAI,UAAU,IAAI,WAAW,uLAAuL;IAE5N,OAAO,KAAK;KAAE;KAAQ;KAAa;IAAO,CAAC;GAC7C,SAAS,OAAO;IAAE,KAAK,KAAK;GAAE;EAChC;CACF,CAAC,CAAC,CAAe;CAEjB,OAAO;AACT"}
|
|
1
|
+
{"version":3,"file":"tools.js","names":["v"],"sources":["../../src/host/tools.ts"],"sourcesContent":["/**\n * The ten `taskboard_*` agent tools. All writes require a calling agent\n * session (ownership audit), carry optimistic-version checks, and enforce\n * the protocol gates in CODE, not in prompt text:\n *\n * - `move → done` is rejected for agent callers (user confirmation only)\n * - `move todo → in_progress` requires the calling session's workspace to\n * match the task's project (claim boundary)\n * - taking over a task held by another session is rejected\n * - `delete` for agent callers only sets the soft-delete marker\n * - checklist items may be added/checked by agents, but checking never\n * completes the task (done stays user-only)\n *\n * OUTPUT CONTRACT (lesson: registry `createSuccessResult` renders\n * `output.render(args, value)` into `result.content`, and the loop feeds\n * exactly that content to the model — the raw JSON `value` never reaches\n * the model): render() IS the model-facing tool result. Every render must\n * carry the complete facts an agent needs to act (ids, versions, statuses);\n * a \"terse UI summary\" here starves the agent.\n *\n * @module dsh-taskboard/host/tools\n */\nimport type { WorkspaceRegistry } from '@deepseek-ai/dsh-workspace'\nimport { defineTool } from './sdk.ts'\nimport {\n MAX_CHECKLIST_ITEMS,\n asIsolation,\n asStatus,\n asUrgency,\n canTransition,\n checklistFromTexts,\n defaultIsolationOf,\n effectivePrompt,\n isClaim,\n isClaimedBy,\n newChecklistItemId,\n newCommentId,\n newTaskId,\n normalizeBody,\n normalizeExecution,\n normalizeExecutionReport,\n normalizeModel,\n normalizePrompt,\n normalizeTitle,\n summarize,\n syncClaim,\n type Actor,\n type ChecklistItem,\n type TaskLedger,\n type TaskModel,\n type TaskRecord,\n} from '../shared/protocol.ts'\nimport type { TaskStore } from './store.ts'\n\n/** Render side: one compact task line (id/status/version are load-bearing). */\nfunction taskLine(t: {\n id: string\n title: string\n status: string\n urgency: string\n version: number\n workspaceId: string\n blocked: boolean\n executionMode: string\n commentCount?: number\n lastExecutionOutcome?: string\n checklist?: { done: number; total: number }\n trashed?: boolean\n}): string {\n const parts = [\n `- ${t.id} [${t.status}] v${t.version} · ${t.urgency} · 项目 ${t.workspaceId}`,\n `「${t.title}」`,\n ]\n if (t.blocked) parts.push('·受阻')\n if (t.executionMode === 'scheduled') parts.push('·定时')\n if (t.commentCount !== undefined && t.commentCount > 0) parts.push(`·评论${t.commentCount}`)\n if (t.checklist !== undefined && t.checklist.total > 0) parts.push(`·清单${t.checklist.done}/${t.checklist.total}`)\n if (t.lastExecutionOutcome !== undefined) parts.push(`·上次执行${t.lastExecutionOutcome}`)\n if (t.trashed === true) parts.push('·已删')\n return parts.join(' ')\n}\n\n/** Render side: the full task detail block (everything an executor needs). */\nfunction taskDetail(t: TaskRecord & { effectivePrompt?: string }): string {\n const lines: string[] = [\n `任务 ${t.id} 「${t.title}」`,\n `状态: ${t.status} (v${t.version}) · 紧急度: ${t.urgency} · 项目: ${t.workspaceId}${t.blocked ? ' · 受阻' : ''}`,\n `执行方式: ${t.execution.mode}${t.execution.cron !== undefined ? ` cron=${t.execution.cron}` : ''}`,\n `隔离: ${t.isolation === 'none' ? '关闭(原目录执行)' : 'Git Worktree'}${t.branch !== undefined ? `(分支 ${t.branch})` : ''}`,\n ]\n const holder = isClaimedBy(t)\n if (holder !== undefined) lines.push(`认领: agent ${String(holder).slice(0, 24)}(持有期间其他会话不可移动)`)\n if (t.execution.nextRunAt !== undefined) lines.push(`下次触发: ${new Date(t.execution.nextRunAt).toISOString()}`)\n if (t.model !== undefined) lines.push(`固定模型: ${t.model.provider}/${t.model.model}${t.model.reasoningEffort !== undefined ? ` (思考强度: ${t.model.reasoningEffort})` : ''}`)\n if (t.presetId !== undefined) lines.push(`执行模式: ${t.presetId}(未指定时为部署默认 preset)`)\n lines.push(`描述: ${t.description.length > 0 ? t.description : '(无)'}`)\n lines.push(`执行 Prompt: ${t.effectivePrompt ?? effectivePrompt(t)}`)\n if (t.checklist !== undefined && t.checklist.length > 0) {\n const done = t.checklist.filter(i => i.checked).length\n lines.push(`验收清单 (${done}/${t.checklist.length}):`)\n for (const item of t.checklist) {\n const mark = item.checked ? '☑' : '☐'\n const who = item.checkedBy === undefined ? '' : item.checkedBy === 'user' ? ' ·用户勾选' : ` ·agent ${String(item.checkedBy).slice(0, 24)}勾选`\n const note = item.note !== undefined ? ` ·证据: ${item.note}` : ''\n lines.push(` ${mark} ${item.text}${who}${note}`)\n }\n }\n if (t.comments.length > 0) {\n lines.push(`评论 (${t.comments.length}):`)\n for (const c of t.comments) {\n const who = c.threadId !== undefined ? `agent ${String(c.threadId).slice(0, 24)}` : 'user'\n lines.push(` - [${who} ${new Date(c.createdAt).toISOString()}] ${c.body}`)\n }\n } else {\n lines.push('评论: 无')\n }\n if (t.executions.length > 0) {\n lines.push(`执行记录 (${t.executions.length}):`)\n for (const e of t.executions) {\n const at = e.startedAt !== undefined ? new Date(e.startedAt).toISOString() : '?'\n const err = e.error !== undefined ? ` 错误: ${e.error}` : ''\n const report = e.report !== undefined ? ' [已交报告]' : ''\n lines.push(` - [${e.trigger} ${at}] ${e.outcome}${report}${err}`)\n }\n } else {\n lines.push('执行记录: 无')\n }\n const updatedBy = t.updatedBy.kind === 'agent' ? `agent ${String(t.updatedBy.sessionId).slice(0, 24)}` : t.updatedBy.kind === 'system' ? 'system' : 'user'\n lines.push(`更新: ${new Date(t.updatedAt).toISOString()} 由 ${updatedBy}`)\n return lines.join('\\n')\n}\n\n/** Stable error codes surfaced at the head of tool error messages. */\nexport const ERR = {\n notFound: 'not_found',\n versionConflict: 'version_conflict',\n workspaceMismatch: 'workspace_mismatch',\n invalidTransition: 'invalid_transition',\n forbidden: 'forbidden',\n requiresAgent: 'unauthorized_actor',\n invalidInput: 'invalid_input',\n} as const\n\n/** Tool failure: an Error whose message starts with a stable code. The code\n * is also carried structurally so the routes layer can map failures without\n * re-parsing messages (review P2). */\nexport class ToolError extends Error {\n constructor(readonly code: string, detail: string) {\n super(`Error: ${code}: ${detail}`)\n }\n}\n\n/** The workspace face the tools need (narrow for tests). */\nexport interface WorkspaceFace {\n /** Resolve the workspace owning a canonical cwd, if any. */\n resolveByPath(path: string): Promise<{ id: string } | undefined>\n /** Get a workspace by id. */\n get(id: string): { id: string; path: string; title: string } | undefined\n /** List all workspaces. */\n list(): Array<{ id: string; path: string; title: string }>\n}\n\n/** Adapt the real registry to the narrow face. */\nexport function workspaceFace(registry: WorkspaceRegistry): WorkspaceFace {\n // Explicit field mapping: Workspace entities expose path/title as prototype\n // getters, which JSON.stringify skips (own enumerable properties only).\n return {\n resolveByPath: async (path) => {\n const ws = await registry.resolveByPath(path as never)\n return ws === undefined ? undefined : { id: ws.id }\n },\n get: id => {\n const ws = registry.get(id as never)\n return ws === undefined ? undefined : { id: ws.id, path: ws.path, title: ws.title }\n },\n list: () => registry.list().map(ws => ({ id: ws.id, path: ws.path, title: ws.title })),\n }\n}\n\n/** Everything the tool set needs. */\nexport interface ToolDeps {\n store: TaskStore\n workspaces: WorkspaceFace\n /** Current epoch ms (injectable for tests). */\n now: () => number\n /**\n * Registered model provider routes (from the host llm runtime), for\n * advisory validation of pinned models; undefined = runtime unavailable,\n * in which case only the structural check applies.\n */\n modelProviders?: () => string[] | undefined\n}\n\n/** Validate a pinned model: structural check always, provider route when known. */\nfunction checkModel(deps: ToolDeps, raw: unknown): TaskModel {\n const model = normalizeModel(raw)\n const providers = deps.modelProviders?.()\n if (providers !== undefined && !providers.includes(model.provider)) {\n throw new ToolError(ERR.invalidInput, `model provider \"${model.provider}\" has no registered route (available: ${providers.join(', ')})`)\n }\n return model\n}\n\n/** Resolve the calling agent's actor and session id. */\nfunction caller(exec: ToolRunContext): { actor: Actor & { kind: 'agent' }; sessionId: string } {\n if (!exec.agent) throw new ToolError(ERR.requiresAgent, 'taskboard tools require a calling agent session')\n const sessionId = exec.agent.id\n return { actor: { kind: 'agent', sessionId }, sessionId }\n}\n\n/** The calling session's workspace id (undefined when unaffiliated). */\nasync function callerWorkspace(deps: ToolDeps, exec: ToolRunContext): Promise<string | undefined> {\n const cwd = exec.agent?.session.header.cwd\n if (typeof cwd !== 'string' || cwd.length === 0) return undefined\n const ws = await deps.workspaces.resolveByPath(cwd)\n return ws?.id\n}\n\n/** Guard: version match. */\nfunction versionGuard(task: TaskRecord, ifVersion: number | undefined): void {\n if (ifVersion === undefined) {\n throw new ToolError(ERR.versionConflict, 'this write requires ifVersion; read the task first')\n }\n if (ifVersion !== task.version) {\n throw new ToolError(ERR.versionConflict, `stale version ${ifVersion} (current ${task.version}); re-read the task and retry once`)\n }\n}\n\n/**\n * Find a live (non-trashed) task INSIDE a mutator (R1: every guard must run\n * on the fresh draft the serial queue hands us, never on a pre-read clone —\n * a pre-read can pass its version check and then blind-overwrite a task that\n * changed while the caller awaited). Throws not_found for missing/trashed.\n */\nfunction liveTaskAt(ledger: TaskLedger, id: string): { index: number; task: TaskRecord } {\n const index = ledger.tasks.findIndex(t => t.id === id)\n if (index < 0) throw new ToolError(ERR.notFound, `no task ${id}`)\n const task = ledger.tasks[index]!\n if (task.trashedAt !== undefined) throw new ToolError(ERR.notFound, `no task ${id}`)\n return { index, task }\n}\n\n/** Re-throw with a stable code; non-ToolErrors become invalid_input. */\nfunction fail(error: unknown): never {\n if (error instanceof ToolError) throw error\n const message = error instanceof Error ? error.message : String(error)\n throw new ToolError(ERR.invalidInput, message)\n}\n\n/** Loose json output schema shared by every taskboard tool. */\nconst JSON_OUT = { type: 'json' } as const\n\n/** Deep-JSON a value for a json-rooted tool output (spread results lose implicit index signatures). */\nfunction json<T>(value: T): Record<string, unknown> {\n return JSON.parse(JSON.stringify(value)) as Record<string, unknown>\n}\n\n/** The exec context face the tools read (agent identity + session cwd). */\nexport interface ToolRunContext {\n agent?: { id: string; session: { header: { cwd?: string } } }\n}\n\n/** Registry-like context face (tests stub this). */\nexport interface ToolContextFace {\n tools: { register(tool: { name: string }): unknown }\n}\n\n/**\n * Register all ten tools.\n * @param ctx - a context exposing `tools.register`.\n * @param deps - store + workspaces + clock.\n * @returns dispose functions, one per tool.\n */\nexport function registerTaskboardTools(ctx: ToolContextFace, deps: ToolDeps): Array<() => void> {\n const disposers: Array<() => void> = []\n const { store, workspaces } = deps\n\n // Env-gated tool-call tracing (ATB_TRACE=1) — evidence for protocol E2E.\n const register = (tool: { name: string; execute?: unknown }) => {\n if (process.env.ATB_TRACE === '1' && typeof tool.execute === 'function') {\n const orig = tool.execute as (args: unknown, exec: unknown) => Promise<unknown>\n tool.execute = async (args: unknown, exec: unknown) => {\n console.error(`[atb ▶] ${tool.name}`, JSON.stringify(args).slice(0, 300))\n try {\n const result = await orig(args, exec)\n console.error(`[atb ✓] ${tool.name}`, JSON.stringify(result).slice(0, 300))\n return result\n } catch (error) {\n console.error(`[atb ✗] ${tool.name}`, String(error).slice(0, 400))\n throw error\n }\n }\n }\n return ctx.tools.register(tool as { name: string })\n }\n\n // ------------------------------------------------------------------ list\n disposers.push(register(defineTool({\n name: 'taskboard_list',\n description:\n 'List task-board tasks. Filter by project (workspaceId), status, or urgency. '\n + 'Returns compact summaries (id, title, status, urgency, version, claim owner). '\n + 'Check this before starting work to find claimable todo tasks in your project.',\n parameters: {\n workspaceId: { type: 'string', description: 'Filter by project (DSH workspace id).' },\n status: { type: 'string', description: 'Filter by exact status (backlog/todo/in_progress/in_review/done/canceled/archived).' },\n urgency: { type: 'string', description: 'Filter by urgency (urgent/normal/relaxed).' },\n includeTrashed: { type: 'boolean', description: 'Include soft-deleted tasks (default false).' },\n },\n output: {\n schema: JSON_OUT,\n render: (_args, value) => {\n const v = value as { revision?: number; tasks?: Array<Record<string, unknown>> }\n const tasks = v.tasks ?? []\n const head = `任务 ${tasks.length} 条(台账 rev ${v.revision ?? '?'})`\n if (tasks.length === 0) return [{ type: 'text', text: `${head}:无匹配任务。` }]\n return [{ type: 'text', text: [head, ...tasks.map(t => taskLine(t as never))].join('\\n') }]\n },\n },\n async execute(args) {\n try {\n const a = args as { workspaceId?: string; status?: string; urgency?: string; includeTrashed?: boolean }\n const tasks = store.snapshot().tasks.filter(t =>\n (a.workspaceId === undefined || t.workspaceId === a.workspaceId)\n && (a.status === undefined || t.status === a.status)\n && (a.urgency === undefined || t.urgency === a.urgency)\n && (a.includeTrashed === true || t.trashedAt === undefined))\n return json({ revision: store.snapshot().revision, tasks: tasks.map(summarize) })\n } catch (error) { fail(error) }\n },\n })) as () => void)\n\n // ------------------------------------------------------------------- get\n disposers.push(register(defineTool({\n name: 'taskboard_get',\n description:\n 'Read one task in full: description, prompt, project, urgency, status, comments, executions, version. '\n + 'Read this (and the comments) BEFORE claiming or starting work on a task.',\n parameters: {\n id: { type: 'string', required: true, description: 'Task id from the board.' },\n },\n output: {\n schema: JSON_OUT,\n render: (_args, value) => {\n const v = value as { task?: TaskRecord & { effectivePrompt?: string } }\n return [{ type: 'text', text: v.task === undefined ? '任务不存在。' : taskDetail(v.task) }]\n },\n },\n async execute(args: { id: string }) {\n try {\n const { id } = args\n const task = store.get(id)\n if (task === undefined || task.trashedAt !== undefined) throw new ToolError(ERR.notFound, `no task ${id}`)\n return json({ task: { ...task, effectivePrompt: effectivePrompt(task) } })\n } catch (error) { fail(error) }\n },\n })) as () => void)\n\n // ---------------------------------------------------------------- create\n disposers.push(register(defineTool({\n name: 'taskboard_create',\n description:\n 'Create a task on the board. Required: title, workspaceId (project), urgency (urgent/normal/relaxed). '\n + 'Optional: description, prompt (sent to a fresh session on execution), status (default todo), '\n + 'execution mode (claim|scheduled + cron), model {provider, model} to pin executions to a model. '\n + 'Do not track trivial requests as tasks.',\n parameters: {\n title: { type: 'string', required: true, description: 'Short imperative line (1..200 chars).' },\n workspaceId: { type: 'string', required: true, description: 'Project (DSH workspace id) this task belongs to.' },\n urgency: { type: 'string', required: true, description: 'urgent (red) | normal (purple) | relaxed (blue).' },\n description: { type: 'string', description: 'What the task involves (plain text).' },\n prompt: { type: 'string', description: 'Extra execution instructions; the session receives title+description+this prompt.' },\n status: { type: 'string', description: 'Initial status; default todo. backlog = not approved for execution.' },\n execution: {\n type: 'object',\n additionalProperties: false,\n description: 'Execution config: { mode: \"claim\" } (default) or { mode: \"scheduled\", cron: \"m h dom mon dow\" }.',\n properties: {\n mode: { type: 'string', description: 'claim | scheduled.' },\n cron: { type: 'string', description: 'Five-field cron expression (scheduled only).' },\n },\n },\n model: {\n type: 'object',\n additionalProperties: false,\n description: 'Pin executions to one configured model: { provider, model, reasoningEffort? }. Omit to use the default model.',\n properties: {\n provider: { type: 'string', description: 'Provider route id.' },\n model: { type: 'string', description: 'Provider-owned model id.' },\n reasoningEffort: { type: 'string', description: 'Optional thinking intensity / reasoning effort (e.g. low, medium, high).' },\n },\n },\n isolation: {\n type: 'string',\n description: 'Code isolation for executions: \"worktree\" (each run gets a fresh git worktree on branch task/<标题>+<taskId>) or \"none\" (run in the project directory, zero git interaction). Omitted → the board default (看板设置 → 默认执行隔离; factory default \"none\").',\n },\n presetId: {\n type: 'string',\n description: 'Agent preset the execution session is composed from (its tool set / persona); default = the deployment default preset. Optional.',\n },\n checklist: {\n type: 'array',\n description: `Acceptance checklist (DoD) item texts (≤${MAX_CHECKLIST_ITEMS} × 200 chars); agents check them off at handoff, the user reviews.`,\n items: { type: 'string' },\n },\n },\n output: {\n schema: JSON_OUT,\n render: (_args, value) => {\n const v = value as { task?: { id?: string; status?: string; version?: number } }\n const t = v.task\n return [{ type: 'text', text: t === undefined ? '创建失败。' : `已创建任务 ${t.id} [${t.status}] v${t.version}。写入前先 taskboard_get 读取。` }]\n },\n },\n async execute(args: {\n title: string\n workspaceId: string\n urgency: string\n status?: string\n description?: string\n prompt?: string\n execution?: { mode?: string; cron?: string }\n model?: { provider?: string; model?: string }\n isolation?: string\n presetId?: string\n checklist?: string[]\n }, exec: unknown) {\n try {\n const { actor } = caller(exec as ToolRunContext)\n const title = normalizeTitle(args.title)\n if (workspaces.get(args.workspaceId) === undefined) {\n throw new ToolError(ERR.notFound, `unknown workspaceId ${args.workspaceId}`)\n }\n const urgency = asUrgency(args.urgency)\n const status = args.status === undefined ? 'todo' as const : asStatus(args.status)\n if (status !== 'backlog' && status !== 'todo') {\n throw new ToolError(ERR.invalidTransition, 'a new task must start as backlog or todo (in_progress requires claiming the task)')\n }\n const execution = normalizeExecution(args.execution ?? {}, deps.now())\n const model = args.model !== undefined ? checkModel(deps, args.model) : undefined\n // 0.5.0: an omitted isolation is MATERIALIZED from the board setting\n // (看板设置) at creation, so later setting changes never rewrite\n // existing tasks.\n const isolation = args.isolation === undefined ? defaultIsolationOf(store.snapshot().settings) : asIsolation(args.isolation)\n const presetId = args.presetId?.trim() || undefined\n // T9: match the GUI create route — trim and drop blank lines instead\n // of failing the whole call over one empty string.\n const checklistTexts = args.checklist?.map(c => c.trim()).filter(c => c.length > 0)\n const checklist = checklistTexts !== undefined && checklistTexts.length > 0 ? checklistFromTexts(checklistTexts) : undefined\n const now = deps.now()\n const task: TaskRecord = {\n id: newTaskId(),\n title,\n description: (args.description ?? '').trim(),\n prompt: normalizePrompt(args.prompt),\n workspaceId: args.workspaceId,\n urgency,\n status,\n blocked: false,\n execution,\n model,\n isolation,\n ...(presetId !== undefined ? { presetId } : {}),\n ...(checklist !== undefined ? { checklist } : {}),\n version: 1,\n createdAt: now,\n updatedAt: now,\n createdBy: actor,\n updatedBy: actor,\n comments: [],\n executions: [],\n }\n await store.mutate('task-created', ledger => {\n ledger.tasks.push(task)\n return [task]\n })\n return json({ task: summarize(task) })\n } catch (error) { fail(error) }\n },\n })) as () => void)\n\n // ---------------------------------------------------------------- update\n disposers.push(register(defineTool({\n name: 'taskboard_update',\n description:\n 'Update a task\\'s title/description/prompt/urgency/blocked. Requires ifVersion (read first). '\n + 'The model and execution config are read-only through this tool (they belong to the task owner/user).',\n parameters: {\n id: { type: 'string', required: true, description: 'Task id.' },\n ifVersion: { type: 'number', required: true, description: 'The task version you read; the write fails on mismatch.' },\n title: { type: 'string', description: 'New title.' },\n description: { type: 'string', description: 'New description.' },\n prompt: { type: 'string', description: 'New execution prompt.' },\n urgency: { type: 'string', description: 'urgent | normal | relaxed.' },\n blocked: { type: 'boolean', description: 'Blocked marker (work cannot continue right now).' },\n },\n output: {\n schema: JSON_OUT,\n render: (_args, value) => {\n const v = value as { task?: { id?: string; status?: string; version?: number } }\n const t = v.task\n return [{ type: 'text', text: t === undefined ? '更新失败。' : `已更新任务 ${t.id},当前 v${t.version} [${t.status}]。` }]\n },\n },\n async execute(args: {\n id: string\n ifVersion: number\n title?: string\n description?: string\n prompt?: string\n urgency?: string\n blocked?: boolean\n }, exec: unknown) {\n try {\n const { actor } = caller(exec as ToolRunContext)\n // R1: lookup + version guard + write run inside the serial-queue\n // mutation, on the fresh draft — a pre-read clone could pass its\n // version check and then blind-overwrite a concurrent writer.\n let next: TaskRecord | undefined\n await store.mutate('task-updated', ledger => {\n const { index, task } = liveTaskAt(ledger, args.id)\n versionGuard(task, args.ifVersion)\n if (task.status === 'archived') throw new ToolError(ERR.invalidTransition, 'archived tasks are immutable')\n next = structuredClone(task)\n if (args.title !== undefined) next.title = normalizeTitle(args.title)\n if (args.description !== undefined) next.description = args.description.trim()\n if (args.prompt !== undefined) next.prompt = normalizePrompt(args.prompt)\n if (args.urgency !== undefined) next.urgency = asUrgency(args.urgency)\n if (args.blocked !== undefined) next.blocked = args.blocked\n next.version = task.version + 1\n next.updatedAt = deps.now()\n next.updatedBy = actor\n ledger.tasks[index] = next\n return [next]\n })\n return json({ task: summarize(next!) })\n } catch (error) { fail(error) }\n },\n })) as () => void)\n\n // ------------------------------------------------------------------ move\n disposers.push(register(defineTool({\n name: 'taskboard_move',\n description:\n 'Move a task between statuses (requires ifVersion). Claim = todo→in_progress (only a session '\n + 'inside the task\\'s project may claim; never take over a task held by another session). '\n + 'After implementing and self-verifying: comment, then in_progress→in_review. '\n + 'You can NEVER move a task to done — that requires explicit user confirmation.',\n parameters: {\n id: { type: 'string', required: true, description: 'Task id.' },\n status: { type: 'string', required: true, description: 'Target status.' },\n ifVersion: { type: 'number', required: true, description: 'Task version you read; fails on mismatch.' },\n },\n output: {\n schema: JSON_OUT,\n render: (_args, value) => {\n const v = value as { task?: { id?: string; status?: string; version?: number } }\n const t = v.task\n return [{ type: 'text', text: t === undefined ? '移动失败。' : `任务 ${t.id} 已移到 ${t.status},当前 v${t.version}。` }]\n },\n },\n async execute(args: { id: string; status: string; ifVersion: number }, exec: unknown) {\n try {\n const { actor } = caller(exec as ToolRunContext)\n const to = asStatus(args.status)\n // Claim boundary (policy gate): resolving the caller's workspace is\n // async, so it happens BEFORE the mutation — the comparison runs INSIDE\n // against the FRESH task (stronger than the old stale-clone compare).\n const callerWsId = to === 'in_progress'\n ? await callerWorkspace(deps, exec as ToolRunContext)\n : undefined\n // R1: every state guard + the write itself run inside the mutation.\n let next: TaskRecord | undefined\n await store.mutate('task-moved', ledger => {\n const { index, task } = liveTaskAt(ledger, args.id)\n versionGuard(task, args.ifVersion)\n\n // Code-level gate: agents never complete a task.\n if (to === 'done') {\n throw new ToolError(ERR.forbidden, 'moving a task to done requires explicit user confirmation (GUI); agents cannot do it')\n }\n if (!canTransition(task.status, to)) {\n throw new ToolError(ERR.invalidTransition, `illegal transition ${task.status} → ${to}`)\n }\n // Exclusive hold: while a task is in_progress under a session\n // (explicit claimedBy — an agent claim or a live execution), no other\n // session may move it (that would be a takeover).\n if (task.status === 'in_progress' && task.claimedBy !== undefined && task.claimedBy !== actor.sessionId) {\n throw new ToolError(ERR.forbidden, `task is held by session ${task.claimedBy}; never take over another session's claim`)\n }\n // Claim boundary: the calling session must belong to the task's project.\n if (isClaim(task.status, to) && callerWsId !== task.workspaceId) {\n throw new ToolError(ERR.workspaceMismatch, 'only a session inside this task\\'s project may claim it')\n }\n next = structuredClone(task)\n next.status = to\n next.version = task.version + 1\n next.updatedAt = deps.now()\n next.updatedBy = actor\n if (isClaim(task.status, to)) next.blocked = false\n // Record the holder on a claim; every move out of in_progress releases it.\n syncClaim(next, to, deps.now(), isClaim(task.status, to) ? actor.sessionId : undefined)\n ledger.tasks[index] = next\n return [next]\n })\n return json({ task: summarize(next!) })\n } catch (error) { fail(error) }\n },\n })) as () => void)\n\n // ----------------------------------------------------------- comment_add\n disposers.push(register(defineTool({\n name: 'taskboard_comment_add',\n description:\n 'Append a progress/report comment to a task. When handing off to review, the comment should cover: '\n + 'what changed, how it was verified, outcome, and remaining risks.',\n parameters: {\n id: { type: 'string', required: true, description: 'Task id.' },\n body: { type: 'string', required: true, description: 'Comment text (1..4000 chars).' },\n },\n output: {\n schema: JSON_OUT,\n render: (_args, value) => {\n const v = value as { comment?: { id?: string }; task?: { id?: string; version?: number; status?: string } }\n const c = v.comment\n const t = v.task\n if (c === undefined || t === undefined) return [{ type: 'text', text: '评论失败。' }]\n // The comment bumped the version — echo it so the agent can chain the\n // next write (e.g. move → in_review) WITHOUT re-reading.\n return [{\n type: 'text',\n text: `评论 ${c.id} 已添加;任务 ${t.id} 当前 v${t.version} [${t.status}](后续写操作用此版本号).`,\n }]\n },\n },\n async execute(args: { id: string; body: string }, exec: unknown) {\n try {\n const { sessionId } = caller(exec as ToolRunContext)\n const comment = {\n id: newCommentId(),\n body: normalizeBody(args.body),\n version: 1,\n createdAt: deps.now(),\n threadId: sessionId,\n }\n // R1: find + append inside the mutation (no ifVersion by design —\n // comments are append-only — but the write must not clobber a task\n // that changed while we were queued).\n let next: TaskRecord | undefined\n await store.mutate('comment-added', ledger => {\n const { index, task } = liveTaskAt(ledger, args.id)\n if (task.status === 'archived') throw new ToolError(ERR.invalidTransition, 'archived tasks are immutable')\n next = structuredClone(task)\n next.comments.push(comment)\n next.version = task.version + 1\n next.updatedAt = deps.now()\n ledger.tasks[index] = next\n return [next]\n })\n return json({ comment, task: { id: next!.id, version: next!.version, status: next!.status } })\n } catch (error) { fail(error) }\n },\n })) as () => void)\n\n // -------------------------------------------------------------- comments\n disposers.push(register(defineTool({\n name: 'taskboard_comments',\n description: 'List a task\\'s comments, oldest first. Read them before deciding to start work.',\n parameters: {\n id: { type: 'string', required: true, description: 'Task id.' },\n },\n output: {\n schema: JSON_OUT,\n render: (_args, value) => {\n const v = value as { comments?: unknown[] }\n const list = v.comments as Array<{ body: string; createdAt: number; threadId?: string }> | undefined\n if (list === undefined || list.length === 0) return [{ type: 'text', text: '无评论。' }]\n const lines = list.map(c => {\n const who = c.threadId !== undefined ? `agent ${String(c.threadId).slice(0, 24)}` : 'user'\n return `- [${who} ${new Date(c.createdAt).toISOString()}] ${c.body}`\n })\n return [{ type: 'text', text: `评论 ${list.length} 条:\\n${lines.join('\\n')}` }]\n },\n },\n async execute(args: { id: string }) {\n try {\n const task = store.get(args.id)\n if (task === undefined || task.trashedAt !== undefined) throw new ToolError(ERR.notFound, `no task ${args.id}`)\n return json({ comments: task.comments })\n } catch (error) { fail(error) }\n },\n })) as () => void)\n\n // ---------------------------------------------------------------- delete\n disposers.push(register(defineTool({\n name: 'taskboard_delete',\n description:\n 'Soft-delete a task (marks it trashed; the user confirms the purge in the GUI). '\n + 'Requires ifVersion. Prefer canceled/archived over delete unless the task was a mistake.',\n parameters: {\n id: { type: 'string', required: true, description: 'Task id.' },\n ifVersion: { type: 'number', required: true, description: 'Task version you read.' },\n },\n output: {\n schema: JSON_OUT,\n render: (_args, value) => {\n const v = value as { trashed?: boolean }\n return [{ type: 'text', text: v.trashed === true ? '任务已标记删除(等待用户在 GUI 清除)。' : '删除失败。' }]\n },\n },\n async execute(args: { id: string; ifVersion: number }, exec: unknown) {\n try {\n caller(exec as ToolRunContext)\n // R1: guards inside the mutation. S5: a running execution keeps\n // writing to the task (report, settlement) — refuse the soft-delete\n // until it is cancelled or settled. T8: clear claim residue.\n let next: TaskRecord | undefined\n await store.mutate('task-deleted', ledger => {\n const { index, task } = liveTaskAt(ledger, args.id)\n versionGuard(task, args.ifVersion)\n if (task.executions.some(e => e.outcome === 'running')) {\n throw new ToolError(ERR.invalidInput, '任务有正在运行的执行(先在 GUI 取消或等它结束再删除)')\n }\n next = structuredClone(task)\n next.trashedAt = deps.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 return { trashed: true }\n } catch (error) { fail(error) }\n },\n })) as () => void)\n\n // -------------------------------------------------------------- checklist\n disposers.push(register(defineTool({\n name: 'taskboard_checklist',\n description:\n `Manage the task's acceptance checklist (DoD). Actions: \"add\" (append item texts, ≤10 per call), `\n + '\"check\" (mark an item done, with an optional evidence note), \"uncheck\" (reopen an item). '\n + 'Checking items NEVER completes the task — done stays a user-only action. Requires ifVersion.',\n parameters: {\n id: { type: 'string', required: true, description: 'Task id.' },\n action: { type: 'string', required: true, description: 'add | check | uncheck.' },\n ifVersion: { type: 'number', required: true, description: 'Task version you read; fails on mismatch.' },\n items: {\n type: 'array',\n description: 'Item texts to append (action=add only; 1..10 per call, 200 chars each).',\n items: { type: 'string' },\n },\n itemId: { type: 'string', description: 'The checklist item id (action=check/uncheck).' },\n note: { type: 'string', description: 'Evidence note recorded with the check (≤400 chars).' },\n },\n output: {\n schema: JSON_OUT,\n render: (_args, value) => {\n const v = value as { task?: { id?: string; version?: number }; checklist?: Array<Record<string, unknown>>; done?: number; total?: number }\n if (v.task === undefined || v.checklist === undefined) return [{ type: 'text', text: '清单操作失败。' }]\n const lines = v.checklist.map((i, index) => `${i.checked === true ? '☑' : '☐'} [${index + 1}] ${String(i.text)}${i.note !== undefined ? `(证据: ${String(i.note)})` : ''} id=${String(i.id)}`)\n return [{\n type: 'text',\n text: `任务 ${v.task.id} 验收清单 ${v.done ?? 0}/${v.total ?? 0} 已完成,当前 v${v.task.version}:\\n${lines.join('\\n')}`,\n }]\n },\n },\n async execute(args: {\n id: string\n action: string\n ifVersion: number\n items?: string[]\n itemId?: string\n note?: string\n }, exec: unknown) {\n try {\n const { actor } = caller(exec as ToolRunContext)\n // R1: guards + the checklist edit itself run inside the mutation.\n let next: TaskRecord | undefined\n await store.mutate('task-updated', ledger => {\n const { index, task } = liveTaskAt(ledger, args.id)\n versionGuard(task, args.ifVersion)\n if (task.status === 'archived') throw new ToolError(ERR.invalidTransition, 'archived tasks are immutable')\n next = structuredClone(task)\n const checklist: ChecklistItem[] = next.checklist === undefined ? [] : [...next.checklist]\n\n if (args.action === 'add') {\n const texts = args.items ?? []\n if (texts.length === 0 || texts.length > 10) {\n throw new ToolError(ERR.invalidInput, 'items must carry 1..10 texts per add call')\n }\n if (checklist.length + texts.length > MAX_CHECKLIST_ITEMS) {\n throw new ToolError(ERR.invalidInput, `checklist may hold at most ${MAX_CHECKLIST_ITEMS} items (currently ${checklist.length})`)\n }\n checklist.push(...checklistFromTexts(texts))\n } else if (args.action === 'check') {\n if (args.itemId === undefined) throw new ToolError(ERR.invalidInput, 'itemId is required for check')\n const item = checklist.find(i => i.id === args.itemId)\n if (item === undefined) throw new ToolError(ERR.notFound, `no checklist item ${args.itemId} (task ${args.id})`)\n const note = args.note !== undefined && args.note.trim().length > 0 ? args.note.trim().slice(0, 400) : undefined\n item.checked = true\n item.checkedBy = actor.sessionId\n item.checkedAt = deps.now()\n if (note !== undefined) item.note = note\n } else if (args.action === 'uncheck') {\n if (args.itemId === undefined) throw new ToolError(ERR.invalidInput, 'itemId is required for uncheck')\n const item = checklist.find(i => i.id === args.itemId)\n if (item === undefined) throw new ToolError(ERR.notFound, `no checklist item ${args.itemId} (task ${args.id})`)\n item.checked = false\n delete item.checkedBy\n delete item.checkedAt\n delete item.note\n } else {\n throw new ToolError(ERR.invalidInput, `action must be add | check | uncheck (got \"${args.action}\")`)\n }\n\n if (checklist.length > 0) next.checklist = checklist\n else delete next.checklist\n next.version = task.version + 1\n next.updatedAt = deps.now()\n next.updatedBy = actor\n ledger.tasks[index] = next\n return [next]\n })\n const progress = next!.checklist !== undefined\n ? { done: next!.checklist.filter(i => i.checked).length, total: next!.checklist.length }\n : { done: 0, total: 0 }\n return json({ task: { id: next!.id, version: next!.version }, checklist: next!.checklist ?? [], ...progress })\n } catch (error) { fail(error) }\n },\n })) as () => void)\n\n // ------------------------------------------------------- execution report\n disposers.push(register(defineTool({\n name: 'taskboard_execution_report',\n description:\n 'Submit the structured execution report for the task you are currently executing (summary / changed '\n + 'files / how you verified / artifacts / remaining risk). Submit BEFORE moving the task to in_review; '\n + 'a later submission overwrites the previous report. If your run already settled, you may back-submit '\n + 'onto your latest succeeded execution while you still hold the task. Commits and diffs are host-collected.',\n parameters: {\n summary: { type: 'string', required: true, description: 'What was done (1..2000 chars).' },\n changedFiles: {\n type: 'array',\n description: 'Files you changed (paths, ≤50 × 300 chars).',\n items: { type: 'string' },\n },\n checks: {\n type: 'array',\n description: 'How the work was verified (e.g. test commands + outcomes, ≤50 entries).',\n items: { type: 'string' },\n },\n artifacts: {\n type: 'array',\n description: 'Artifacts worth reviewing (build outputs, screenshots, docs, ≤30 entries).',\n items: { type: 'string' },\n },\n risk: { type: 'string', description: 'Known remaining risks or follow-ups (≤2000 chars, optional).' },\n },\n output: {\n schema: JSON_OUT,\n render: (_args, value) => {\n const v = value as { taskId?: string; executionId?: string; report?: { summary?: string } }\n if (v.taskId === undefined || v.report === undefined) return [{ type: 'text', text: '报告提交失败。' }]\n return [{\n type: 'text',\n text: `执行报告已记录到任务 ${v.taskId}(执行 ${v.executionId}):${v.report.summary?.slice(0, 120) ?? ''}\\n`\n + '接下来:taskboard_comment_add 留交接评论,然后 taskboard_move 移至待验收 in_review。',\n }]\n },\n },\n async execute(args: {\n summary: string\n changedFiles?: string[]\n checks?: string[]\n artifacts?: string[]\n risk?: string\n }, exec: unknown) {\n try {\n const { sessionId } = caller(exec as ToolRunContext)\n const report = normalizeExecutionReport(args)\n // Path 1 (unchanged): attach to the RUNNING execution this session\n // owns — reports ride the live run, so the agent never needs ids.\n let taskId: string | undefined\n let executionId: string | undefined\n await store.mutate('execution-recorded', ledger => {\n for (const task of ledger.tasks) {\n const execution = task.executions.find(e => e.sessionId === sessionId && e.outcome === 'running')\n if (execution !== undefined) {\n execution.report = report\n taskId = task.id\n executionId = execution.id\n return [task]\n }\n }\n return undefined\n })\n // Path 2 (review follow-up, P2): back-submit onto a session-owned\n // SETTLED execution — the main conversation claims tasks directly and\n // has no live run. Allowed when the session holds the task or owns\n // its latest successful execution; never touches anyone else's runs.\n if (taskId === undefined || executionId === undefined) {\n await store.mutate('execution-recorded', ledger => {\n for (const task of ledger.tasks) {\n if (task.trashedAt !== undefined || task.status === 'archived') continue\n const last = task.executions[task.executions.length - 1]\n const owned = last !== undefined && last.sessionId === sessionId && last.outcome === 'succeeded'\n const holds = task.claimedBy === sessionId && last !== undefined && last.sessionId === sessionId\n if (!owned && !holds) continue\n last!.report = report\n taskId = task.id\n executionId = last!.id\n return [task]\n }\n return undefined\n })\n }\n if (taskId === undefined || executionId === undefined) {\n throw new ToolError(ERR.forbidden, 'no running execution and no settled execution of yours to report on — reports attach to your running execution, or back-submit onto your latest succeeded one while you hold the task')\n }\n return json({ taskId, executionId, report })\n } catch (error) { fail(error) }\n },\n })) as () => void)\n\n return disposers\n}\n"],"mappings":"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"}
|