@linxin666/dsh-client-ui-task-board 0.1.13 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -953,6 +953,23 @@ window.__ModuleLoader__.load({
953
953
  }
954
954
  };
955
955
  //#endregion
956
+ //#region src/client/apply-guard.ts
957
+ /** Claims the plugin apply slot. Returns true when this call won the slot. */
958
+ function claimTaskboardApply() {
959
+ if (globalThis.__dshTaskboardApplied === true) return false;
960
+ globalThis.__dshTaskboardApplied = true;
961
+ return true;
962
+ }
963
+ /**
964
+ * Releases the claim. Called from the client fiber cleanup so that a
965
+ * hot-reloaded bundle (the loader unloads the old plugin fiber and invokes
966
+ * the rebuilt one in the same page) can claim again instead of being
967
+ * silently dropped.
968
+ */
969
+ function releaseTaskboardApply() {
970
+ globalThis.__dshTaskboardApplied = void 0;
971
+ }
972
+ //#endregion
956
973
  //#region src/client/locales.ts
957
974
  /**
958
975
  * Task-board copy: zh-first dictionaries with an English fallback, selected
@@ -2045,6 +2062,7 @@ window.__ModuleLoader__.load({
2045
2062
  * @returns disposer removing the entry and its observers.
2046
2063
  */
2047
2064
  function mountSidebarEntry(controller) {
2065
+ if (typeof document !== "undefined" && document.querySelector("[data-dsh-taskboard-entry]") !== null) return () => {};
2048
2066
  const entry = createEntry(controller);
2049
2067
  let root;
2050
2068
  let placed = false;
@@ -2631,6 +2649,8 @@ window.__ModuleLoader__.load({
2631
2649
  * @param ctx - client root context (services: sessions, workspaces).
2632
2650
  */
2633
2651
  function apply(ctx) {
2652
+ if (!claimTaskboardApply()) return;
2653
+ ctx.effect(() => releaseTaskboardApply, "task-board: apply claim");
2634
2654
  ctx.effect(() => ctx.locale.register(NS, {
2635
2655
  zh,
2636
2656
  en
package/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["css","css","css","STATUS_KEY","css","css","css","css","css"],"sources":["../../../packages/dsh-task-board/src/core/schedule.ts","../../../packages/dsh-task-board/src/core/tasks.ts","../../../packages/dsh-task-board/src/core/controller.ts","../../../packages/dsh-task-board/src/core/execution.ts","../../../packages/dsh-task-board/src/core/scheduler.ts","../../../packages/dsh-task-board/src/core/store.ts","../../../packages/dsh-task-board/src/client/locales.ts","../../../packages/dsh-task-board/src/client/board/NewTaskModal.tsx","../../../packages/dsh-task-board/src/client/board/TaskCard.tsx","../../../packages/dsh-task-board/src/client/board/ConfirmDialog.tsx","../../../packages/dsh-task-board/src/client/board/TaskDetail.tsx","../../../packages/dsh-task-board/src/client/board/TaskBoard.tsx","../../../packages/dsh-task-board/src/client/board-mount.tsx","../../../packages/dsh-task-board/src/client/sidebar-entry.ts","../../../packages/dsh-task-board/src/client/PluginSettingsCard.tsx","../../../packages/dsh-task-board/src/client/settings-form.ts","../../../packages/dsh-task-board/src/client/TaskBoardSettingsCard.tsx","../../../packages/dsh-task-board/src/client/index.ts"],"sourcesContent":["/**\n * Minimal 5-field cron parsing and next-run computation for scheduled task\n * runs. Framework-free and dependency-free so the scheduler and controller\n * share one tiny pure module.\n *\n * Grammar: five whitespace-separated fields, 分 时 日 月 周. Every field\n * supports the wildcard, step (wildcard or range + \"/n\"), single value,\n * inclusive range a-b, and comma lists mixing any of those. Ranges: minutes\n * 0-59, hours 0-23, days 1-31, months 1-12, weekdays 0-7 (0 and 7 both mean\n * Sunday). When both the day and weekday fields are restricted they combine\n * with OR semantics (standard cron). Invalid expressions parse to null and\n * are rejected by the UI/controller.\n */\n\n/** The parsed match sets of one cron expression. */\nexport interface CronSchedule {\n minutes: ReadonlySet<number>\n hours: ReadonlySet<number>\n days: ReadonlySet<number>\n months: ReadonlySet<number>\n /** Weekdays 0-6, 0 = Sunday (input 7 normalized to 0). */\n weekdays: ReadonlySet<number>\n /** Whether the day-of-month field was the literal '*' (unrestricted). */\n dayWildcard: boolean\n /** Whether the weekday field was the literal '*' (unrestricted). */\n weekdayWildcard: boolean\n}\n\n/** Inclusive ranges per field, in cron order. */\nconst FIELD_RANGES: ReadonlyArray<readonly [number, number]> = [\n [0, 59], // minutes\n [0, 23], // hours\n [1, 31], // days\n [1, 12], // months\n [0, 7], // weekdays (7 = Sunday, normalized below)\n]\n\n/**\n * Parse a 5-field cron expression.\n * @returns the match sets, or null when the expression is invalid.\n */\nexport function parseCron(expr: string): CronSchedule | null {\n const fields = expr.trim().split(/\\s+/)\n if (fields.length !== 5) return null\n const sets: Set<number>[] = []\n for (let index = 0; index < 5; index++) {\n const [min, max] = FIELD_RANGES[index]\n const set = new Set<number>()\n if (!parseField(fields[index], min, max, set)) return null\n sets.push(set)\n }\n const weekdays = new Set<number>()\n for (const day of sets[4]) weekdays.add(day === 7 ? 0 : day)\n return {\n minutes: sets[0],\n hours: sets[1],\n days: sets[2],\n months: sets[3],\n weekdays,\n // Only the literal '*' marks a field unrestricted: an explicit full\n // enumeration such as '1-31' is a restricted field and must not collapse\n // into the wildcard (it participates in day/weekday OR semantics).\n dayWildcard: fields[2] === '*',\n weekdayWildcard: fields[4] === '*',\n }\n}\n\n/** Whether the expression parses. */\nexport function isValidCron(expr: string): boolean {\n return parseCron(expr) !== null\n}\n\n/**\n * Compute the next matching instant after `fromMs` (ms epoch), in local time,\n * at minute granularity, strictly greater than `fromMs`. Returns the ms epoch\n * of the matching minute's start, or undefined when nothing matches within\n * 366 days (e.g. `0 0 30 2 *`).\n */\nexport function nextRunAtMs(expr: string, fromMs: number): number | undefined {\n const schedule = parseCron(expr)\n if (schedule === null) return undefined\n const from = new Date(fromMs)\n // Scan from the next minute; Date rolls overflow (Feb 30 → Mar 2) for free.\n const scan = new Date(from.getFullYear(), from.getMonth(), from.getDate(), from.getHours(), from.getMinutes() + 1, 0, 0)\n const limitMs = fromMs + 366 * 24 * 60 * 60 * 1000\n while (scan.getTime() <= limitMs) {\n if (matches(schedule, scan)) return scan.getTime()\n scan.setMinutes(scan.getMinutes() + 1)\n }\n return undefined\n}\n\n/** Parse one comma-list field into the match set. */\nfunction parseField(field: string, min: number, max: number, out: Set<number>): boolean {\n if (field === '*') {\n for (let value = min; value <= max; value++) out.add(value)\n return true\n }\n for (const part of field.split(',')) {\n if (part === '') return false\n const [range, stepRaw] = part.split('/')\n let low: number\n let high: number\n if (range === '*') {\n low = min\n high = max\n } else if (range.includes('-')) {\n const [a, b] = range.split('-')\n if (a === '' || b === '' || !isDigits(a) || !isDigits(b)) return false\n low = Number(a)\n high = Number(b)\n } else if (isDigits(range)) {\n low = Number(range)\n high = Number(range)\n } else {\n return false\n }\n if (low < min || high > max || low > high) return false\n const step = stepRaw === undefined ? 1 : isDigits(stepRaw) ? Number(stepRaw) : NaN\n if (!Number.isInteger(step) || step < 1) return false\n for (let value = low; value <= high; value += step) out.add(value)\n }\n return true\n}\n\n/** Day/weekday OR semantics: a restricted day field alone gates, and vice versa. */\nfunction matches(schedule: CronSchedule, date: Date): boolean {\n if (!schedule.minutes.has(date.getMinutes())) return false\n if (!schedule.hours.has(date.getHours())) return false\n if (!schedule.months.has(date.getMonth() + 1)) return false\n const dayMatches = schedule.days.has(date.getDate())\n const weekdayMatches = schedule.weekdays.has(date.getDay())\n if (schedule.dayWildcard) return weekdayMatches\n if (schedule.weekdayWildcard) return dayMatches\n return dayMatches || weekdayMatches\n}\n\nfunction isDigits(value: string): boolean {\n return /^\\d+$/.test(value)\n}\n","/**\n * Task board domain model: task lifecycle statuses, the task record shape,\n * and the pure transition functions the controller and tests share.\n * Framework-free (no cordis, no runtime imports) so the state machine is\n * unit-testable in isolation.\n */\n\n/** Task lifecycle status, one per kanban column. */\nexport type TaskStatus = 'backlog' | 'todo' | 'running' | 'done' | 'failed'\n\n/**\n * One real execution attempt: the run's own id, the dsh session that ran it\n * (filled once the session is created), and the settled outcome once the\n * session's turn ended.\n */\nexport interface ExecutionRecord {\n /** Execution attempt id (uuid). */\n id: string\n /** The dsh session that ran this attempt; absent until creation resolves. */\n sessionId: string | undefined\n /** When the run started (ms epoch). */\n startedAt: number\n /** When the run settled; absent while still running. */\n endedAt: number | undefined\n /** Outcome once settled. */\n result: 'succeeded' | 'failed' | 'cancelled' | undefined\n /** Human failure text when the run failed (prompt rejection or agent error). */\n error: string | undefined\n}\n\n/**\n * A scheduled-run rule attached to a task. The browser-side scheduler ticks\n * every minute and triggers the task when `nextRunAt` is due; the rule is\n * persisted with the task (localStorage), so scheduling survives refreshes.\n */\nexport interface ScheduleRule {\n /** Whether the schedule is armed. */\n enabled: boolean\n /** 5-field cron expression: `分 时 日 月 周`. */\n cron: string\n /** Next due instant (ms epoch); maintained by the scheduler/controller. */\n nextRunAt: number | undefined\n /** Instant of the latest scheduled trigger (ms epoch). */\n lastTriggeredAt: number | undefined\n}\n\n/** One task on the board. */\nexport interface TaskRecord {\n /** Stable task id (uuid). */\n id: string\n /** Short display title. */\n title: string\n /** Longer human description shown in the detail view. */\n description: string\n /** The prompt sent to dsh when this task is executed. */\n prompt: string\n /** Current column. */\n status: TaskStatus\n /** Creation instant (ms epoch). */\n createdAt: number\n /** Last mutation instant (ms epoch). */\n updatedAt: number\n /** Every execution attempt, most recent last. */\n executions: ExecutionRecord[]\n /** Optional scheduled-run rule (absent on tasks without a schedule). */\n schedule?: ScheduleRule\n}\n\n/** Input for creating a task. */\nexport interface NewTaskInput {\n title: string\n description: string\n prompt: string\n}\n\n/** The five kanban columns, in display order. */\nexport const COLUMNS: readonly { status: TaskStatus; label: string }[] = [\n { status: 'backlog', label: '待规划' },\n { status: 'todo', label: '待办' },\n { status: 'running', label: '进行中' },\n { status: 'done', label: '已完成' },\n { status: 'failed', label: '已失败' },\n]\n\n/** Statuses a user may move a card to manually (execution states are owned by the runner). */\nexport const MANUAL_STATUSES: readonly TaskStatus[] = ['backlog', 'todo']\n\n/** Statuses the runner may move a card to from 'running'. */\nexport const RUNNER_SETTLE_STATUSES: readonly TaskStatus[] = ['done', 'failed']\n\n/** All valid statuses (closed union guard). */\nexport const ALL_STATUSES: readonly TaskStatus[] = [\n 'backlog', 'todo', 'running', 'done', 'failed',\n]\n\n/** Brand an unknown string as a status; undefined when it is not one. */\nexport function isTaskStatus(value: unknown): value is TaskStatus {\n return typeof value === 'string' && (ALL_STATUSES as readonly string[]).includes(value)\n}\n\n/** Whether a manual move target is allowed from the given status. */\nexport function canMoveManually(_from: TaskStatus, to: TaskStatus): boolean {\n return (MANUAL_STATUSES as readonly TaskStatus[]).includes(to)\n}\n\n/** Create a task from user input. */\nexport function createTask(input: NewTaskInput, now: number, id: string): TaskRecord {\n return {\n id,\n title: input.title.trim(),\n description: input.description.trim(),\n prompt: input.prompt.trim(),\n status: 'todo',\n createdAt: now,\n updatedAt: now,\n executions: [],\n }\n}\n\n/** Clone a task with an updated status and a fresh updatedAt. */\nexport function withStatus(task: TaskRecord, status: TaskStatus, now: number): TaskRecord {\n return { ...task, status, updatedAt: now }\n}\n\n/**\n * Merge a schedule patch into a task's schedule rule (creating it when\n * absent), with a fresh updatedAt. Keys present in the patch overwrite the\n * current value — including explicit `undefined`, which clears a field (used\n * to disarm `nextRunAt`); absent keys keep their current value.\n */\nexport function withSchedule(\n task: TaskRecord,\n patch: Partial<ScheduleRule>,\n now: number,\n): TaskRecord {\n const current = task.schedule\n const schedule: ScheduleRule = {\n enabled: current?.enabled ?? false,\n cron: current?.cron ?? '',\n nextRunAt: current?.nextRunAt,\n lastTriggeredAt: current?.lastTriggeredAt,\n }\n if ('enabled' in patch) schedule.enabled = patch.enabled ?? false\n if ('cron' in patch) schedule.cron = patch.cron ?? ''\n if ('nextRunAt' in patch) schedule.nextRunAt = patch.nextRunAt\n if ('lastTriggeredAt' in patch) schedule.lastTriggeredAt = patch.lastTriggeredAt\n return { ...task, updatedAt: now, schedule }\n}\n\n/**\n * Open a fresh execution on a task: move it to 'running' and append a\n * running execution record. Returns the new task and the new execution.\n */\nexport function startExecution(\n task: TaskRecord,\n now: number,\n executionId: string,\n): { task: TaskRecord; execution: ExecutionRecord } {\n const execution: ExecutionRecord = {\n id: executionId,\n sessionId: undefined,\n startedAt: now,\n endedAt: undefined,\n result: undefined,\n error: undefined,\n }\n return {\n task: { ...task, status: 'running', updatedAt: now, executions: [...task.executions, execution] },\n execution,\n }\n}\n\n/**\n * Settle a running execution: record the outcome and move the task into the\n * matching column. No-op (returns the input task) when the execution is not\n * the task's latest or is already settled.\n */\nexport function settleExecution(\n task: TaskRecord,\n executionId: string,\n outcome: 'succeeded' | 'failed' | 'cancelled',\n now: number,\n error: string | undefined,\n): TaskRecord {\n const index = task.executions.findIndex(execution => execution.id === executionId)\n if (index === -1) return task\n const execution = task.executions[index]\n if (execution.endedAt !== undefined) return task\n const settled: ExecutionRecord = { ...execution, endedAt: now, result: outcome, error }\n const executions = [...task.executions]\n executions[index] = settled\n const status: TaskStatus = outcome === 'succeeded' ? 'done'\n : outcome === 'failed' ? 'failed'\n : task.status === 'running' ? 'todo' : task.status\n return { ...task, status, updatedAt: now, executions }\n}\n\n/** A settled-execution summary string for the detail view. */\nexport function executionLabel(execution: ExecutionRecord): string {\n if (execution.result === 'succeeded') return 'succeeded'\n if (execution.result === 'failed') return 'failed'\n if (execution.result === 'cancelled') return 'cancelled'\n return 'running'\n}\n","/**\n * Board controller: the single owner of task-ledger state and view state.\n *\n * It keeps the ledger in memory, persists every mutation through the\n * {@link TaskStore}, drives real executions through the\n * {@link ExecutionService}, and closes the board view whenever the user\n * navigates to a session (the sessions-list `current` selection changes).\n * Framework-free (structural runtime faces) so the whole orchestration is\n * unit-testable with fakes.\n */\nimport { ExecutionService, type ExecutionEvent } from './execution.ts'\nimport { isValidCron, nextRunAtMs } from './schedule.ts'\nimport type { TaskStore } from './store.ts'\nimport {\n createTask, settleExecution, startExecution, withSchedule, withStatus,\n type NewTaskInput, type TaskRecord, type TaskStatus,\n} from './tasks.ts'\n\n/** The sessions face the controller needs for navigation awareness. */\nexport interface SessionsControllerFace {\n list: {\n getSnapshot(): { current: string | undefined }\n subscribe(fn: () => void): () => void\n }\n /** Select a session as current (navigates the conversation view). */\n open(id: string): void\n}\n\n/** Controller dependencies (all swappable in tests). */\nexport interface ControllerDeps {\n store: TaskStore\n exec: ExecutionService\n sessions: SessionsControllerFace\n /** Clock; defaults to Date.now. */\n now?: () => number\n /** Id minting; defaults to a random-uuid. */\n uuid?: () => string\n /** Debounce (ms) for session-list-changed reconciles; defaults to 350. */\n reconcileDebounceMs?: number\n}\n\n/** Immutable controller snapshot for UI subscriptions. */\nexport interface ControllerSnapshot {\n tasks: readonly TaskRecord[]\n boardOpen: boolean\n selectedTaskId: string | undefined\n}\n\n/** The selected task (resolved from the ledger), or undefined. */\nexport function selectedTaskOf(snapshot: ControllerSnapshot): TaskRecord | undefined {\n if (snapshot.selectedTaskId === undefined) return undefined\n return snapshot.tasks.find(task => task.id === snapshot.selectedTaskId)\n}\n\nfunction randomUuid(): string {\n const bytes = globalThis.crypto?.getRandomValues(new Uint8Array(16))\n if (bytes === undefined) {\n // Non-secure fallback (tests, odd environments).\n return `t-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`\n }\n bytes[6] = (bytes[6] & 0x0f) | 0x40\n bytes[8] = (bytes[8] & 0x3f) | 0x80\n const hex = Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('')\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`\n}\n\n/** Read the current selection off a session-list snapshot (structural). */\nfunction currentOf(sessions: SessionsControllerFace): string | undefined {\n return sessions.list.getSnapshot().current\n}\n\n/**\n * Board controller (see module doc). All mutations bump the snapshot and\n * persist through the store; UI and DOM mounts subscribe and re-render.\n */\nexport class BoardController {\n private tasks: TaskRecord[] = []\n private boardOpen = false\n private selectedTaskId: string | undefined\n private listeners = new Set<() => void>()\n private disposers: Array<() => void> = []\n private readonly now: () => number\n private readonly uuid: () => string\n\n /** @param deps - store, execution service, and the sessions navigation face. */\n constructor(private readonly deps: ControllerDeps) {\n this.now = deps.now ?? (() => Date.now())\n this.uuid = deps.uuid ?? randomUuid\n }\n\n // --- lifecycle -------------------------------------------------------------\n\n /** Load the persisted ledger and start the navigation/status subscriptions. */\n start(): void {\n this.tasks = this.deps.store.load()\n void this.reconcileRunningTasks()\n // A sibling tab may have edited or deleted the ledger (same origin,\n // storage events). Reload on external change so a task deleted in\n // another tab stops firing here — and is never written back by this\n // tab's stale copy (scheduler roll-forward, execution settlement).\n const unsubscribeExternal = this.deps.store.subscribeExternal?.(() => {\n this.tasks = this.deps.store.load()\n this.notify()\n })\n if (unsubscribeExternal !== undefined) this.disposers.push(unsubscribeExternal)\n this.disposers.push(this.deps.sessions.list.subscribe(() => {\n this.onSessionsChanged()\n }))\n this.notify()\n }\n\n /** Stop all subscriptions and drop retained state (idempotent). */\n dispose(): void {\n for (const dispose of this.disposers.splice(0)) dispose()\n this.listeners.clear()\n if (this.reconcileTimer !== undefined) clearTimeout(this.reconcileTimer)\n this.reconcileTimer = undefined\n }\n\n // --- snapshot / subscription ------------------------------------------------\n\n getSnapshot(): ControllerSnapshot {\n return {\n tasks: this.tasks,\n boardOpen: this.boardOpen,\n selectedTaskId: this.selectedTaskId,\n }\n }\n\n subscribe(fn: () => void): () => void {\n this.listeners.add(fn)\n return () => { this.listeners.delete(fn) }\n }\n\n // --- view state -------------------------------------------------------------\n\n openBoard(): void {\n if (this.boardOpen) return\n // Baseline the selection the board opened against: the board stays open\n // until the user navigates (selection changes), never on mere status\n // updates of the already-selected session.\n this.lastCurrent = currentOf(this.deps.sessions)\n this.boardOpen = true\n this.notify()\n }\n\n closeBoard(): void {\n if (!this.boardOpen) return\n this.boardOpen = false\n this.notify()\n }\n\n toggleBoard(): void {\n if (this.boardOpen) this.closeBoard()\n else this.openBoard()\n }\n\n openTask(id: string): void {\n if (this.tasks.some(task => task.id === id)) {\n this.selectedTaskId = id\n this.notify()\n }\n }\n\n closeTask(): void {\n if (this.selectedTaskId === undefined) return\n this.selectedTaskId = undefined\n this.notify()\n }\n\n // --- task mutations ---------------------------------------------------------\n\n createTask(input: NewTaskInput): TaskRecord | undefined {\n const title = input.title.trim()\n if (title === '') return undefined\n const task = createTask(input, this.now(), this.uuid())\n this.tasks = [...this.tasks, task]\n this.persistAndNotify()\n return task\n }\n\n updateTask(id: string, patch: Partial<Pick<TaskRecord, 'title' | 'description' | 'prompt'>>): void {\n this.tasks = this.tasks.map(task => task.id === id\n ? { ...task, ...patch, updatedAt: this.now() }\n : task)\n this.persistAndNotify()\n }\n\n moveTask(id: string, status: TaskStatus): void {\n this.tasks = this.tasks.map(task => task.id === id ? withStatus(task, status, this.now()) : task)\n this.persistAndNotify()\n }\n\n deleteTask(id: string): void {\n this.tasks = this.tasks.filter(task => task.id !== id)\n if (this.selectedTaskId === id) this.selectedTaskId = undefined\n this.persistAndNotify()\n }\n\n // --- scheduling ---------------------------------------------------------------\n\n /**\n * Update a task's schedule rule. A blank or invalid cron expression is\n * rejected (returns false, state untouched). When the rule ends up enabled\n * the next run instant is computed immediately; a disabled rule carries no\n * next-run instant.\n * @param id - the task to schedule.\n * @param patch - fields to change (absent fields keep their current value).\n * @returns true when applied, false when rejected (invalid cron / unknown task).\n */\n setSchedule(id: string, patch: { enabled?: boolean; cron?: string }): boolean {\n const task = this.tasks.find(candidate => candidate.id === id)\n if (task === undefined) return false\n const current = task.schedule\n const cron = (patch.cron ?? current?.cron ?? '').trim()\n if (cron === '' || !isValidCron(cron)) return false\n const enabled = patch.enabled ?? current?.enabled ?? false\n const nextRunAt = enabled ? nextRunAtMs(cron, this.now()) : undefined\n this.tasks = this.tasks.map(candidate =>\n candidate.id === id ? withSchedule(candidate, { enabled, cron, nextRunAt }, this.now()) : candidate)\n this.persistAndNotify()\n return true\n }\n\n /**\n * Roll a task's schedule forward (scheduler callback): persist the next due\n * instant and the trigger instant of this run. No-op when the task has no\n * schedule rule (it was deleted mid-tick, for example).\n */\n applyScheduleNextRun(id: string, nextRunAt: number | undefined, lastTriggeredAt: number | undefined): void {\n this.tasks = this.tasks.map(task =>\n task.id === id && task.schedule !== undefined\n ? withSchedule(task, { nextRunAt, lastTriggeredAt }, this.now())\n : task)\n this.persistAndNotify()\n }\n\n /**\n * Jump to an execution's session transcript. Selecting the session changes\n * `current`, which closes the board (the conversation view takes over).\n * @param sessionId - the execution session to open.\n */\n openSession(sessionId: string): void {\n this.deps.sessions.open(sessionId)\n }\n\n // --- execution ---------------------------------------------------------------\n\n /**\n * Execute a task for real: move it to 'running', open an execution record,\n * and hand off to the ExecutionService. A second call while the task is\n * already running is ignored.\n */\n async runTask(id: string): Promise<boolean> {\n const task = this.tasks.find(candidate => candidate.id === id)\n if (task === undefined || task.status === 'running') return false\n const { task: next, execution } = startExecution(task, this.now(), this.uuid())\n this.tasks = this.tasks.map(candidate => candidate.id === id ? next : candidate)\n this.persistAndNotify()\n // This page owns the settlement of its own launches: the live watch\n // (ExecutionService.run) settles on the turn boundary, and list\n // reconciliation must not pre-empt it with a session that has not\n // started a turn yet (its list row is idle, not completed).\n this.activeExecutionIds.add(execution.id)\n await this.deps.exec.run(next, execution, (event) => { this.handleExecutionEvent(event) })\n return true\n }\n\n /** Re-run a settled task: move it back to 'todo' first, then execute. */\n async rerunTask(id: string): Promise<void> {\n const task = this.tasks.find(candidate => candidate.id === id)\n if (task === undefined) return\n if (task.status !== 'running') {\n this.tasks = this.tasks.map(candidate => candidate.id === id ? withStatus(candidate, 'todo', this.now()) : candidate)\n this.persistAndNotify()\n }\n await this.runTask(id)\n }\n\n private handleExecutionEvent(event: ExecutionEvent): void {\n if (event.kind === 'started') {\n this.tasks = this.tasks.map(task => task.id === event.taskId\n ? attachSessionId(task, event.executionId, event.sessionId, this.now())\n : task)\n this.persistAndNotify()\n return\n }\n this.activeExecutionIds.delete(event.executionId)\n this.tasks = this.tasks.map(task => task.id === event.taskId\n ? settleExecution(task, event.executionId, event.outcome, this.now(), event.error)\n : task)\n this.persistAndNotify()\n }\n\n // --- internals ---------------------------------------------------------------\n\n /** Reconcile running tasks and close the board when the user navigates. */\n private onSessionsChanged(): void {\n // Background/leftover executions settle through the session list (their\n // conversation snapshots stay cold until opened). Coalesce the burst of\n // list notifications into one reconcile pass instead of fanning out a\n // history read per notification; see scheduleReconcile.\n this.scheduleReconcile()\n if (!this.boardOpen) return\n const current = currentOf(this.deps.sessions)\n if (current !== this.lastCurrent) this.closeBoard()\n this.lastCurrent = current\n }\n\n private lastCurrent: string | undefined = undefined\n\n /** Execution ids launched on this page; they settle via their live watch, never list reconciliation. */\n private readonly activeExecutionIds = new Set<string>()\n\n /** Debounce timer for {@link reconcileRunningTasks}. */\n private reconcileTimer: ReturnType<typeof setTimeout> | undefined = undefined\n\n /** Whether a reconcile pass is underway (single-flight guard). */\n private reconcileInFlight = false\n\n /**\n * Debounce + single-flight trigger for the running-task reconciliation.\n * Session-list notifications arrive in bursts (one per session status\n * change); both guards together keep a burst from reading the history API\n * once per running task.\n */\n private scheduleReconcile(): void {\n if (this.reconcileTimer !== undefined) return\n this.reconcileTimer = setTimeout(() => {\n this.reconcileTimer = undefined\n void this.reconcileRunningTasks()\n }, this.deps.reconcileDebounceMs ?? 350)\n }\n\n /** Settle tasks left 'running' whose sessions already finished. */\n private async reconcileRunningTasks(): Promise<void> {\n if (this.reconcileInFlight) return\n this.reconcileInFlight = true\n try {\n type Settled = Extract<ExecutionEvent, { kind: 'settled' }>\n const events: Array<{ taskId: string; event: Settled }> = []\n for (const task of this.tasks) {\n if (task.status !== 'running') continue\n const execution = task.executions[task.executions.length - 1]\n // Runs launched on this page settle through their live watch (turn\n // boundary); reconciliation exists for background/leftover runs.\n if (execution !== undefined && this.activeExecutionIds.has(execution.id)) continue\n const event = await this.deps.exec.reconcile(task)\n if (event !== undefined && event.kind === 'settled') events.push({ taskId: task.id, event })\n }\n if (events.length === 0) return\n let changed = false\n for (const { taskId, event } of events) {\n // The reconcile call above awaited: a sibling tab may have rewritten\n // the ledger (storage event reload) meanwhile. Re-read the freshest\n // record now so the stale task captured before the await can never\n // overwrite fields the sibling wrote.\n const task = this.tasks.find(candidate => candidate.id === taskId)\n if (task === undefined) continue\n const next = settleExecution(task, event.executionId, event.outcome, this.now(), event.error)\n if (next === task) continue\n this.tasks = this.tasks.map(candidate => candidate.id === taskId ? next : candidate)\n changed = true\n }\n if (changed) this.persistAndNotify()\n } finally {\n this.reconcileInFlight = false\n }\n }\n\n private persistAndNotify(): void {\n this.deps.store.save(this.tasks)\n this.notify()\n }\n\n private notify(): void {\n for (const fn of [...this.listeners]) fn()\n }\n}\n\n/** Record which session ran an execution (once the execution service reports it). */\nfunction attachSessionId(\n task: TaskRecord,\n executionId: string,\n sessionId: string,\n now: number,\n): TaskRecord {\n return {\n ...task,\n updatedAt: now,\n executions: task.executions.map(execution =>\n execution.id === executionId ? { ...execution, sessionId } : execution),\n }\n}\n","/**\n * Execution service: runs a task through dsh's real session machinery.\n *\n * The board's \"run\" button must make dsh actually work, not fake a status:\n * the service connects a real session (workspace blank-session reuse or\n * `session.create` on the host via the workspaces service), renames it to\n * the task title, sends the task prompt with `session.prompt`, and then\n * watches the session's conversation snapshot until its turn settles. The\n * task board controller consumes {@link ExecutionEvent}s to move the card\n * running → done/failed and to keep the execution record.\n *\n * Deliberately framework-free: the runtime faces are declared structurally\n * (a narrow slice of the real `ctx.sessions` / `ctx.workspaces` contracts)\n * so tests drive it with plain fakes.\n */\nimport type { ExecutionRecord, TaskRecord } from './tasks.ts'\n\n/** The narrow sessions face the service needs. */\nexport interface SessionsExecutionFace {\n list: {\n getSnapshot(): {\n /** Baseline arrival lifecycle — 'pending' until the host list has loaded. */\n phase: 'pending' | 'ready'\n byId: Record<string, { running: boolean; completed?: boolean }>\n }\n subscribe(fn: () => void): () => void\n }\n binding(id: string): { session: SessionDriver } | undefined\n}\n\n/** The narrow workspaces face the service needs. */\nexport interface WorkspacesExecutionFace {\n list: {\n getSnapshot(): {\n items: readonly { workspaceId: string }[]\n recentWorkspaceId: string | undefined\n }\n }\n connectWorkspace(workspaceId: string): Promise<string>\n}\n\n/** One raw session-history event narrowed to the failure signal reconcile needs. */\nexport interface ExecutionHistoryEvent {\n type: string\n data?: unknown\n}\n\n/** Optional raw-history face used to detect failures of never-opened sessions. */\nexport interface HistoryExecutionFace {\n loadTail(sessionId: string): Promise<{ events: readonly ExecutionHistoryEvent[] } | undefined>\n}\n\n/** The behavior verbs the service invokes on an execution session. */\nexport interface SessionDriver {\n rename(title: string): Promise<unknown>\n prompt(\n content: readonly unknown[],\n mode: 'queue',\n ): Promise<{ ok: true } | { ok: false; error: unknown }>\n getSnapshot(): { running: boolean; lastAgentError: string | null; turnEnds: ReadonlyMap<number, number> }\n subscribe(fn: () => void): () => void\n}\n\n/** Everything the service needs from the runtime. */\nexport interface ExecutionEnvironment {\n sessions: SessionsExecutionFace\n workspaces: WorkspacesExecutionFace\n /** Raw-history reader for failure detection of never-opened sessions. */\n history?: HistoryExecutionFace\n}\n\n/** Outcome events the service emits to the controller. */\nexport type ExecutionEvent =\n | { kind: 'started'; taskId: string; executionId: string; sessionId: string }\n | { kind: 'settled'; taskId: string; executionId: string; outcome: 'succeeded' | 'failed' | 'cancelled'; error?: string }\n\n/** Human copy for a run failure. */\nfunction messageOf(error: unknown): string {\n if (error instanceof Error) return error.message\n return String(error)\n}\n\n/** Whether a `turn/end` payload closed the turn with an error reason. */\nfunction isErrorTurnEnd(data: unknown): boolean {\n if (typeof data !== 'object' || data === null) return false\n const reason = (data as { reason?: unknown }).reason\n return typeof reason === 'object' && reason !== null\n && (reason as { kind?: unknown }).kind === 'error'\n}\n\n/**\n * Run one task to completion (or to a settled failure).\n *\n * @param task - the task being executed.\n * @param execution - the freshly opened execution record (id + start time).\n * @param onEvent - callback for started/settled events.\n * @returns resolves when the run settles (or fails to start); never rejects —\n * every failure path is reported as a settled event.\n */\nexport class ExecutionService {\n /** @param env - the runtime faces (real or fake). */\n constructor(private readonly env: ExecutionEnvironment) {}\n\n async run(\n task: TaskRecord,\n execution: ExecutionRecord,\n onEvent: (event: ExecutionEvent) => void,\n ): Promise<void> {\n try {\n const sessionId = await this.connectSession()\n onEvent({ kind: 'started', taskId: task.id, executionId: execution.id, sessionId })\n const driver = this.driverOf(sessionId)\n if (driver === undefined) {\n onEvent({ kind: 'settled', taskId: task.id, executionId: execution.id, outcome: 'failed', error: 'execution session is not ready' })\n return\n }\n // Best-effort rename so the execution is recognizable in the session list.\n await driver.rename(task.title).catch(() => { /* rename is cosmetic */ })\n // Baseline the turn counter BEFORE the prompt round-trip: a turn that\n // completes while prompt is in flight must still advance past this\n // baseline, or the watch below would never observe it settle.\n const baseline = driver.getSnapshot().turnEnds.size\n const accepted = await this.sendPrompt(driver, task)\n if (!accepted.ok) {\n onEvent({\n kind: 'settled', taskId: task.id, executionId: execution.id, outcome: 'failed',\n error: messageOf(accepted.error),\n })\n return\n }\n this.watchForSettlement(driver, task.id, execution.id, onEvent, baseline)\n } catch (error) {\n onEvent({\n kind: 'settled', taskId: task.id, executionId: execution.id, outcome: 'failed',\n error: messageOf(error),\n })\n }\n }\n\n /**\n * Inspect a reloaded/background task that was left 'running' and emit a\n * settled event when its session already finished.\n *\n * A session that was never opened keeps a cold conversation snapshot (the\n * runtime only maintains the window for the staged/current session), so the\n * settled outcome is decided by the strongest available signal, in order:\n * 1. the list summary — missing session → cancelled; still running → pending;\n * 2. a warm conversation snapshot → `lastAgentError` decides failed/succeeded;\n * 3. the raw history tail (when a history face is wired) — a `turn/end`\n * error reason proves failure;\n * 4. otherwise a finished session counts as succeeded.\n *\n * @param task - a task whose latest execution has no endedAt.\n * @returns a settled event when the session state proves completion, else undefined.\n */\n async reconcile(task: TaskRecord): Promise<ExecutionEvent | undefined> {\n const execution = task.executions[task.executions.length - 1]\n if (execution === undefined || execution.sessionId === undefined || execution.endedAt !== undefined) return undefined\n const list = this.env.sessions.list.getSnapshot()\n // The host list baseline has not arrived yet (page load): a session \"not\n // found\" now would be a false cancel. Wait for a later list change.\n if (list.phase !== 'ready') return undefined\n const summary = list.byId[execution.sessionId]\n if (summary === undefined) {\n return { kind: 'settled', taskId: task.id, executionId: execution.id, outcome: 'cancelled', error: 'execution session no longer exists' }\n }\n if (summary.running) return undefined\n const driver = this.driverOf(execution.sessionId)\n if (driver !== undefined) {\n const snapshot = driver.getSnapshot()\n if (snapshot.turnEnds.size > 0) {\n const outcome = snapshot.lastAgentError !== null ? 'failed' : 'succeeded'\n return {\n kind: 'settled', taskId: task.id, executionId: execution.id, outcome,\n error: snapshot.lastAgentError ?? undefined,\n }\n }\n }\n const failed = await this.historyShowsFailure(execution.sessionId)\n if (failed) {\n return { kind: 'settled', taskId: task.id, executionId: execution.id, outcome: 'failed', error: 'agent turn failed' }\n }\n return { kind: 'settled', taskId: task.id, executionId: execution.id, outcome: 'succeeded' }\n }\n\n /** Best-effort failure probe over the raw history tail (false when unavailable). */\n private async historyShowsFailure(sessionId: string): Promise<boolean> {\n const history = this.env.history\n if (history === undefined) return false\n try {\n const tail = await history.loadTail(sessionId)\n if (tail === undefined) return false\n return tail.events.some(event => event.type === 'turn/end' && isErrorTurnEnd(event.data))\n } catch (error) {\n // A failed history read must not block settlement; fall back to success.\n console.error('[dsh-task-board] history failure probe failed', error)\n return false\n }\n }\n\n private async connectSession(): Promise<string> {\n const workspace = this.env.workspaces.list.getSnapshot()\n const workspaceId = workspace.recentWorkspaceId ?? workspace.items[0]?.workspaceId\n if (workspaceId === undefined) {\n throw new Error('no workspace available to run the task in')\n }\n return this.env.workspaces.connectWorkspace(workspaceId)\n }\n\n private driverOf(sessionId: string): SessionDriver | undefined {\n return this.env.sessions.binding(sessionId)?.session\n }\n\n private async sendPrompt(\n driver: SessionDriver,\n task: TaskRecord,\n ): Promise<{ ok: true } | { ok: false; error: unknown }> {\n const text = task.prompt.trim() !== '' ? task.prompt : task.title\n try {\n const result = await driver.prompt([{ type: 'text', text }], 'queue')\n return result\n } catch (error) {\n return { ok: false, error }\n }\n }\n\n /**\n * Subscribe to the execution session and settle the run once the accepted\n * turn completes (turn counter advanced past the acceptance baseline and\n * the session is no longer running). Never settles while the session is\n * still running; unsubscribes on settle.\n */\n private watchForSettlement(\n driver: SessionDriver,\n taskId: string,\n executionId: string,\n onEvent: (event: ExecutionEvent) => void,\n baseline: number,\n ): void {\n let settled = false\n let unsubscribe: () => void = () => {}\n const check = (): void => {\n if (settled) return\n const snapshot = driver.getSnapshot()\n if (snapshot.running || snapshot.turnEnds.size <= baseline) return\n settled = true\n unsubscribe()\n onEvent({\n kind: 'settled', taskId, executionId,\n outcome: snapshot.lastAgentError !== null ? 'failed' : 'succeeded',\n error: snapshot.lastAgentError ?? undefined,\n })\n }\n unsubscribe = driver.subscribe(check)\n // A turn can complete during the prompt round-trip (before subscribe):\n // re-check immediately so a fast turn is never missed.\n check()\n }\n}\n","/**\n * Browser-side scheduler: the heartbeat behind scheduled task runs.\n *\n * The board is a pure client plugin with no server channel, so \"定时任务\"\n * lives in the tab: a timer ticks every minute (plus immediately on tab\n * visibility recovery) and triggers any task whose `schedule.nextRunAt` is\n * due, rolling the schedule forward to the next cron match before triggering\n * so the same tick never double-fires. Missed runs are skipped, never queued:\n * a task still running at its due instant is skipped by the controller's\n * runTask guard and simply waits for the next cron match.\n *\n * Framework-free: all runtime access flows through the injected deps\n * (structural faces), so tests drive ticks directly without timers.\n */\nimport { nextRunAtMs } from './schedule.ts'\nimport type { TaskRecord } from './tasks.ts'\n\n/** Everything the scheduler needs from its host (the board controller). */\nexport interface SchedulerDeps {\n /** Read the current task ledger (the controller snapshot). */\n tasks(): readonly TaskRecord[]\n /** Clock; defaults to Date.now in the controller wiring. */\n now(): number\n /** Trigger one task's real execution (the controller's runTask); resolves\n * true when the run was accepted, false when rejected (e.g. the task is\n * already running). */\n runTask(id: string): Promise<boolean>\n /** Persist a rolled-forward schedule (next due + this trigger instant). */\n applySchedule(id: string, nextRunAt: number | undefined, lastTriggeredAt: number | undefined): void\n /** Tick cadence; defaults to 60_000 ms. */\n tickMs?: number\n /**\n * Gate: while false the tick no-ops (e.g. the session list baseline has not\n * arrived on page load, so executions would fail). Defaults to always ready.\n */\n ready?: () => boolean\n /** Environment listeners for tab-visibility recovery (browser only). */\n environment?: {\n addEventListener(type: 'visibilitychange', listener: () => void): void\n removeEventListener(type: 'visibilitychange', listener: () => void): void\n }\n}\n\n/**\n * The schedule heartbeat (see module doc). `tick` is public so tests and\n * callers can drive a check without waiting for the interval.\n */\nexport class SchedulerService {\n private timer: ReturnType<typeof setInterval> | undefined\n private disposed = false\n\n /** @param deps - tasks/clock/trigger/apply faces (see {@link SchedulerDeps}). */\n constructor(private readonly deps: SchedulerDeps) {}\n\n /** Start ticking: one immediate check (catch-up after reload) + the interval. */\n start(): void {\n if (this.disposed) return\n // Immediate catch-up tick: schedules whose due instant passed while the\n // tab was closed are triggered as soon as the runtime is ready.\n this.tick()\n this.timer = setInterval(() => { this.tick() }, this.deps.tickMs ?? 60_000)\n this.deps.environment?.addEventListener('visibilitychange', this.onVisibility)\n }\n\n /** Stop ticking and drop listeners (idempotent). */\n dispose(): void {\n this.disposed = true\n if (this.timer !== undefined) clearInterval(this.timer)\n this.timer = undefined\n this.deps.environment?.removeEventListener('visibilitychange', this.onVisibility)\n }\n\n /**\n * Check every enabled schedule and trigger the due ones. Idempotent per\n * task per tick: the schedule is rolled forward only after runTask accepts\n * the run, so a rejected run keeps its due slot and is retried on the next\n * tick instead of being silently dropped.\n */\n async tick(): Promise<void> {\n if (this.disposed) return\n if (this.deps.ready !== undefined && !this.deps.ready()) return\n const now = this.deps.now()\n for (const task of this.deps.tasks()) {\n const schedule = task.schedule\n if (schedule === undefined || !schedule.enabled) continue\n if (schedule.nextRunAt === undefined) {\n // Missing next-run instant (repaired/legacy data): recompute from the\n // cron expression and wait; an unparseable expression is skipped.\n const repaired = nextRunAtMs(schedule.cron, now)\n if (repaired === undefined) continue\n this.deps.applySchedule(task.id, repaired, undefined)\n continue\n }\n if (schedule.nextRunAt > now) continue\n // Advance from the due instant (not this tick's wall-clock) and only\n // after the run is accepted: a rejected run keeps its due slot.\n const next = nextRunAtMs(schedule.cron, schedule.nextRunAt)\n const accepted = await this.deps.runTask(task.id)\n if (accepted) this.deps.applySchedule(task.id, next, now)\n }\n }\n\n private readonly onVisibility = (): void => { this.tick() }\n}\n","/**\n * Task persistence: a small storage seam with a localStorage backend.\n *\n * The task-board client plugin runs in the browser, and dsh exposes no\n * browser-writable file channel (same conclusion the skin-center research\n * reached for cordis.patch.yml), so tasks persist in the browser's\n * localStorage under a versioned key — the same persistence mechanism dsh's\n * own client snapshot stores use (`createSnapshotStore` persist). Data\n * survives page refreshes and dsh restarts (same origin), and survives\n * plugin uninstall (the key is simply left in place).\n *\n * The seam keeps the backend swappable (e.g. an IndexedDB or a host-file\n * channel later); tests run against the in-memory backend and a jsdom\n * localStorage backend.\n */\nimport { isValidCron } from './schedule.ts'\nimport type { ScheduleRule, TaskRecord, TaskStatus } from './tasks.ts'\nimport { isTaskStatus } from './tasks.ts'\n\n/** Persistence seam for the task ledger. */\nexport interface TaskStore {\n /** Read the persisted ledger (empty when nothing is stored yet). */\n load(): TaskRecord[]\n /** Persist the whole ledger (replaces the stored document). */\n save(tasks: readonly TaskRecord[]): void\n /** Drop the persisted ledger (leaves the in-memory state alone). */\n clear(): void\n /**\n * Subscribe to ledger changes written by ANOTHER tab of the same origin\n * (browser storage events). The board controller reloads the ledger on\n * such a change, so a task deleted in one tab cannot keep firing (or be\n * written back) from the stale in-memory copy of another tab. No-op when\n * the backend has no cross-instance channel (in-memory store).\n */\n subscribeExternal?(listener: () => void): () => void\n}\n\n/** Storage key for the task ledger document. */\nexport const DEFAULT_STORAGE_KEY = 'dsh.taskBoard.v1'\n\n/** Structural shape of the storage event fired in sibling tabs (DOM-free). */\nexport interface StorageChangeEvent {\n key: string | null\n}\n\n/** The event-target face the store needs for cross-tab notifications. */\nexport interface StorageEvents {\n addEventListener(type: 'storage', listener: (event: StorageChangeEvent) => void): void\n removeEventListener(type: 'storage', listener: (event: StorageChangeEvent) => void): void\n}\n\n/**\n * Structural row check with the status left unvalidated (see {@link parseLedger}).\n * The `schedule` field is deliberately NOT checked here: a malformed schedule\n * never drops the task row — {@link normalizeSchedule} repairs or drops the\n * schedule alone.\n */\nfunction isTaskRecordShape(value: unknown): value is Omit<TaskRecord, 'status'> & { status: unknown } {\n if (typeof value !== 'object' || value === null) return false\n const record = value as Record<string, unknown>\n if (typeof record.id !== 'string' || record.id === '') return false\n if (typeof record.title !== 'string') return false\n if (typeof record.description !== 'string') return false\n if (typeof record.prompt !== 'string') return false\n if (typeof record.createdAt !== 'number') return false\n if (typeof record.updatedAt !== 'number') return false\n if (!Array.isArray(record.executions)) return false\n for (const execution of record.executions) {\n if (typeof execution !== 'object' || execution === null) return false\n const entry = execution as Record<string, unknown>\n if (typeof entry.id !== 'string') return false\n if (entry.sessionId !== undefined && typeof entry.sessionId !== 'string') return false\n if (typeof entry.startedAt !== 'number') return false\n if (entry.endedAt !== undefined && typeof entry.endedAt !== 'number') return false\n if (entry.result !== undefined && entry.result !== 'succeeded' && entry.result !== 'failed' && entry.result !== 'cancelled') return false\n if (entry.error !== undefined && typeof entry.error !== 'string') return false\n }\n return true\n}\n\n/** A task record is structurally valid if it round-trips through the UI. */\nexport function isTaskRecord(value: unknown): value is TaskRecord {\n return isTaskRecordShape(value) && isTaskStatus(value.status)\n}\n\n/** Normalize an unknown persisted status back into the closed status union. */\nfunction normalizeStatus(status: unknown): TaskStatus {\n return isTaskStatus(status) ? status : 'todo'\n}\n\n/**\n * Repair a persisted schedule rule: drop rules without a usable cron string,\n * coerce booleans/numbers, and leave `nextRunAt`/`lastTriggeredAt` undefined\n * when missing (a fresh recompute or the next tick fixes them).\n */\nfunction normalizeSchedule(schedule: unknown): ScheduleRule | undefined {\n if (typeof schedule !== 'object' || schedule === null) return undefined\n const rule = schedule as Record<string, unknown>\n // Reject (drop) a schedule whose cron is not a well-formed 5-field\n // expression: a malformed rule would otherwise linger as a never-firing\n // schedule instead of being dropped for later repair.\n if (typeof rule.cron !== 'string') return undefined\n if (rule.cron.trim() === '' || !isValidCron(rule.cron)) return undefined\n return {\n enabled: rule.enabled === true,\n cron: rule.cron,\n nextRunAt: typeof rule.nextRunAt === 'number' ? rule.nextRunAt : undefined,\n lastTriggeredAt: typeof rule.lastTriggeredAt === 'number' ? rule.lastTriggeredAt : undefined,\n }\n}\n\n/** Parse + validate a persisted ledger document; invalid rows are dropped. */\nexport function parseLedger(raw: string | null): TaskRecord[] {\n if (raw === null) return []\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch (error) {\n console.error('[dsh-task-board] persisted task ledger is not valid JSON; starting empty', error)\n return []\n }\n if (!Array.isArray(parsed)) {\n console.error('[dsh-task-board] persisted task ledger is not an array; starting empty')\n return []\n }\n const tasks: TaskRecord[] = []\n for (const row of parsed) {\n // Status is normalized (an unknown status from a future version lands in\n // todo instead of dropping the row); the schedule is repaired field by\n // field; every other field must be valid.\n if (!isTaskRecordShape(row)) {\n console.warn('[dsh-task-board] dropping invalid task row from persisted ledger', row)\n continue\n }\n // Always (re)assign the schedule: a repair that returns undefined must\n // clear a malformed persisted rule rather than leave it in the row.\n const task: TaskRecord = { ...row, status: normalizeStatus(row.status) }\n task.schedule = normalizeSchedule(row.schedule)\n tasks.push(task)\n }\n return tasks\n}\n\n/** localStorage-backed store (the browser backend). */\nexport class LocalStorageTaskStore implements TaskStore {\n /**\n * @param key - storage key for the ledger document.\n * @param storage - storage backend (defaults to the global localStorage; tests inject fakes).\n * @param events - storage-event target for cross-tab notifications (defaults\n * to the browser global; undefined in non-browser runtimes, where the\n * subscription becomes a no-op).\n */\n constructor(\n private readonly key: string = DEFAULT_STORAGE_KEY,\n private readonly storage: Pick<Storage, 'getItem' | 'setItem' | 'removeItem'> | undefined = globalThis.localStorage,\n private readonly events: StorageEvents | undefined = typeof (globalThis as { addEventListener?: unknown }).addEventListener === 'function'\n ? (globalThis as unknown as StorageEvents)\n : undefined,\n ) {}\n\n load(): TaskRecord[] {\n if (this.storage === undefined) return []\n try {\n return parseLedger(this.storage.getItem(this.key))\n } catch (error) {\n // Storage read failures (private mode, quota) degrade to an empty ledger,\n // never break the board.\n console.error('[dsh-task-board] task ledger read failed; starting empty', error)\n return []\n }\n }\n\n save(tasks: readonly TaskRecord[]): void {\n if (this.storage === undefined) return\n try {\n this.storage.setItem(this.key, JSON.stringify(tasks))\n } catch (error) {\n // Write failures only skip persistence; in-memory state stays live.\n console.error('[dsh-task-board] task ledger write failed (persistence skipped)', error)\n }\n }\n\n clear(): void {\n if (this.storage === undefined) return\n try {\n this.storage.removeItem(this.key)\n } catch (error) {\n console.error('[dsh-task-board] task ledger clear failed', error)\n }\n }\n\n /**\n * Cross-tab change subscription (see {@link TaskStore.subscribeExternal}).\n * The browser fires the storage event in every OTHER tab of the same origin\n * when one tab writes; a null key means the whole storage was cleared. Both\n * cases reload the ledger here; unrelated keys are ignored.\n */\n subscribeExternal(listener: () => void): () => void {\n if (this.events === undefined) return () => {}\n const onStorage = (event: StorageChangeEvent): void => {\n if (event.key !== null && event.key !== this.key) return\n listener()\n }\n this.events.addEventListener('storage', onStorage)\n return () => { this.events?.removeEventListener('storage', onStorage) }\n }\n}\n\n/** In-memory backend (tests, and a fallback when storage is unavailable). */\nexport class InMemoryTaskStore implements TaskStore {\n private ledger: TaskRecord[] = []\n\n load(): TaskRecord[] {\n return this.ledger.map(task => ({ ...task, executions: [...task.executions] }))\n }\n\n save(tasks: readonly TaskRecord[]): void {\n this.ledger = tasks.map(task => ({ ...task, executions: [...task.executions] }))\n }\n\n clear(): void {\n this.ledger = []\n }\n}\n","/**\n * Task-board copy: zh-first dictionaries with an English fallback, selected\n * by the document language. Kept dependency-free (no dsh locale service) so\n * the DOM-injected entry row and the standalone board tree share one tiny\n * lookup.\n */\n\n/** zh dictionary (key-set source of truth). */\nexport const zh = {\n 'entry.label': '任务看板',\n 'board.title': '任务看板',\n 'board.close': '返回对话',\n 'board.new': '新建任务',\n 'board.search': '筛选任务…',\n 'board.empty': '这个状态还没有任务',\n 'board.filterAll': '全部',\n 'board.status': '状态',\n 'board.status.backlog': '待规划',\n 'board.status.todo': '待办',\n 'board.status.running': '进行中',\n 'board.status.done': '已完成',\n 'board.status.failed': '已失败',\n 'board.runs': '次执行',\n 'board.updated': '更新于',\n 'board.created': '创建于',\n 'new.title': '标题',\n 'new.titlePlaceholder': '一句话描述要做什么',\n 'new.description': '描述',\n 'new.descriptionPlaceholder': '补充背景、范围与验收(可选)',\n 'new.prompt': '执行 Prompt',\n 'new.promptPlaceholder': '发给 agent 的完整指令(留空则使用标题)',\n 'new.submit': '创建',\n 'new.cancel': '取消',\n 'new.required': '标题不能为空',\n 'detail.title': '任务详情',\n 'detail.close': '关闭',\n 'detail.prompt': '执行 Prompt',\n 'detail.description': '描述',\n 'detail.execution': '执行记录',\n 'detail.noExecution': '尚未执行',\n 'detail.run': '执行',\n 'detail.rerun': '重新执行',\n 'detail.delete': '删除',\n 'detail.viewSession': '查看会话',\n 'detail.noSession': '暂无会话',\n 'detail.executionStarted': '已启动',\n 'detail.executionEnded': '已结束',\n 'detail.result.succeeded': '成功',\n 'detail.result.failed': '失败',\n 'detail.result.cancelled': '已取消',\n 'detail.result.running': '进行中',\n 'delete.title': '删除任务',\n 'delete.confirm': '确定删除「{name}」吗?删除后不可恢复。',\n 'delete.ok': '删除',\n 'delete.cancel': '取消',\n 'status.move.backlog': '移到待规划',\n 'status.move.todo': '移到待办',\n 'exec.error.noWorkspace': '没有可用工作区,无法执行任务',\n 'exec.error.promptRejected': 'Prompt 被拒绝',\n 'run.failed': '执行失败:{error}',\n 'time.justNow': '刚刚',\n 'detail.schedule': '定时运行',\n 'detail.schedule.enable': '启用定时执行',\n 'detail.schedule.cron': 'Cron 表达式',\n 'detail.schedule.presets': '预设',\n 'detail.schedule.preset.daily9': '每天 09:00',\n 'detail.schedule.preset.hourly': '每小时',\n 'detail.schedule.preset.tenMin': '每 10 分钟',\n 'detail.schedule.preset.weeklyMon9': '每周一 09:00',\n 'detail.schedule.nextRun': '下次运行',\n 'detail.schedule.lastTriggered': '上次触发',\n 'detail.schedule.invalid': 'Cron 表达式无效',\n 'detail.schedule.notScheduled': '尚未排程',\n 'detail.schedule.dueSoon': '即将运行',\n 'card.scheduled': '定时',\n // 插件设置卡片(settings.plugin.item 席位)。\n 'settings.title': '任务看板',\n 'settings.description': '控制看板在 agent 系统提示中的播报行为。',\n 'settings.enabled': '启用任务看板',\n 'settings.enabledHint': '关闭后隐藏侧边栏入口与看板视图。',\n 'settings.announceToAgent': '向 agent 播报任务看板',\n 'settings.announceToAgentHint': '开启:每条 agent 系统提示都会包含本看板的说明;关闭:不播报,agent 仅在用户主动提及时了解看板。',\n 'settings.inherit': '继承',\n 'settings.on': '开',\n 'settings.off': '关',\n 'settings.overridden': '已覆盖',\n 'settings.reset': '恢复默认',\n 'settings.notExposed': '当前 DSH 版本未向设置页暴露本插件的配置命名空间,表单不可用。可编辑 ~/.dsh/settings.yaml 直接配置,或为 dsh-host-apiproxy 的 WEB_SETTINGS_NAMESPACES 白名单补充本命名空间后重启。',\n 'settings.readOnly': '当前部署的设置只读。',\n 'settings.expand': '展开设置',\n 'settings.collapse': '收起设置',\n 'settings.save': '保存',\n 'settings.saving': '保存中…',\n 'settings.discard': '放弃',\n 'settings.unsaved': '未保存',\n 'settings.saveFailed': '部署未接受这些值,已保留供你修改。',\n 'settings.invalidNumber': '请输入数字,留空则使用默认值。',\n} satisfies Record<string, string>\n\n/** en dictionary, complete against the zh key set. */\nexport const en: Record<keyof typeof zh, string> = {\n 'entry.label': 'Task Board',\n 'board.title': 'Task Board',\n 'board.close': 'Back to chat',\n 'board.new': 'New Task',\n 'board.search': 'Filter tasks…',\n 'board.empty': 'No tasks in this column',\n 'board.filterAll': 'All',\n 'board.status': 'Status',\n 'board.status.backlog': 'Backlog',\n 'board.status.todo': 'To Do',\n 'board.status.running': 'In Progress',\n 'board.status.done': 'Done',\n 'board.status.failed': 'Failed',\n 'board.runs': 'runs',\n 'board.updated': 'Updated',\n 'board.created': 'Created',\n 'new.title': 'Title',\n 'new.titlePlaceholder': 'What should be done, in one line',\n 'new.description': 'Description',\n 'new.descriptionPlaceholder': 'Background, scope, acceptance criteria (optional)',\n 'new.prompt': 'Run Prompt',\n 'new.promptPlaceholder': 'The full instruction sent to the agent (title is used when blank)',\n 'new.submit': 'Create',\n 'new.cancel': 'Cancel',\n 'new.required': 'Title is required',\n 'detail.title': 'Task Detail',\n 'detail.close': 'Close',\n 'detail.prompt': 'Run Prompt',\n 'detail.description': 'Description',\n 'detail.execution': 'Execution History',\n 'detail.noExecution': 'Not executed yet',\n 'detail.run': 'Run',\n 'detail.rerun': 'Run Again',\n 'detail.delete': 'Delete',\n 'detail.viewSession': 'View Session',\n 'detail.noSession': 'No session',\n 'detail.executionStarted': 'Started',\n 'detail.executionEnded': 'Ended',\n 'detail.result.succeeded': 'Succeeded',\n 'detail.result.failed': 'Failed',\n 'detail.result.cancelled': 'Cancelled',\n 'detail.result.running': 'Running',\n 'delete.title': 'Delete Task',\n 'delete.confirm': 'Delete \"{name}\"? This cannot be undone.',\n 'delete.ok': 'Delete',\n 'delete.cancel': 'Cancel',\n 'status.move.backlog': 'Move to Backlog',\n 'status.move.todo': 'Move to To Do',\n 'exec.error.noWorkspace': 'No workspace is available to run the task',\n 'exec.error.promptRejected': 'Prompt rejected',\n 'run.failed': 'Run failed: {error}',\n 'time.justNow': 'just now',\n 'detail.schedule': 'Scheduled Runs',\n 'detail.schedule.enable': 'Enable scheduled runs',\n 'detail.schedule.cron': 'Cron expression',\n 'detail.schedule.presets': 'Presets',\n 'detail.schedule.preset.daily9': 'Every day 09:00',\n 'detail.schedule.preset.hourly': 'Every hour',\n 'detail.schedule.preset.tenMin': 'Every 10 minutes',\n 'detail.schedule.preset.weeklyMon9': 'Every Monday 09:00',\n 'detail.schedule.nextRun': 'Next run',\n 'detail.schedule.lastTriggered': 'Last triggered',\n 'detail.schedule.invalid': 'Invalid cron expression',\n 'detail.schedule.notScheduled': 'Not scheduled yet',\n 'detail.schedule.dueSoon': 'Due soon',\n 'card.scheduled': 'scheduled',\n // Plugin settings card (the `settings.plugin.item` seat).\n 'settings.title': 'Task Board',\n 'settings.description': 'How the board announces itself in each agent system prompt.',\n 'settings.enabled': 'Enable the task board',\n 'settings.enabledHint': 'When off, the sidebar entry and board view are hidden.',\n 'settings.announceToAgent': 'Announce the task board to agents',\n 'settings.announceToAgentHint': 'On: every agent system prompt includes a note about this board. Off: no announcement; agents learn about the board only when you mention it.',\n 'settings.inherit': 'Inherit',\n 'settings.on': 'On',\n 'settings.off': 'Off',\n 'settings.overridden': 'Overridden',\n 'settings.reset': 'Reset to default',\n 'settings.notExposed': 'This DSH version does not expose this plugin\\'s settings namespace to the configuration page, so the form is unavailable. Edit ~/.dsh/settings.yaml directly, or add the namespace to dsh-host-apiproxy\\'s WEB_SETTINGS_NAMESPACES allowlist and restart.',\n 'settings.readOnly': 'This deployment stores settings read-only.',\n 'settings.expand': 'Show settings',\n 'settings.collapse': 'Hide settings',\n 'settings.save': 'Save',\n 'settings.saving': 'Saving…',\n 'settings.discard': 'Discard',\n 'settings.unsaved': 'Unsaved',\n 'settings.saveFailed': 'The deployment did not accept these values; they were left for you to correct.',\n 'settings.invalidNumber': 'Enter a number, or leave blank to use the default.',\n}\n\n/** The dictionary key union. */\nexport type TaskBoardKey = keyof typeof zh\n\n/** The settings-card slice of the task-board dictionary. */\nexport type SettingsCardKey = TaskBoardKey\n\n/** Active dictionary, picked by the document language at call time. */\nexport function dictionary(): Record<TaskBoardKey, string> {\n const lang = typeof document !== 'undefined' ? document.documentElement.lang : 'zh'\n return lang.toLowerCase().startsWith('en') ? en : zh\n}\n\n/** Translate a key with optional {name} template params. */\nexport function t(key: TaskBoardKey, params?: Record<string, string>): string {\n let text: string = dictionary()[key]\n if (params !== undefined) {\n for (const [name, value] of Object.entries(params)) {\n text = text.replaceAll(`{${name}}`, value)\n }\n }\n return text\n}\n","/**\n * New-task modal: title + description + the prompt that execution will send.\n * Creates through the controller (which persists immediately).\n */\nimport { useState } from 'react'\nimport type { BoardController } from '../../core/controller.ts'\nimport { t } from '../locales.ts'\nimport css from '../board.module.css'\n\n/** New-task form overlay. */\nexport function NewTaskModal({ controller, onClose }: { controller: BoardController; onClose: () => void }) {\n const [title, setTitle] = useState('')\n const [description, setDescription] = useState('')\n const [prompt, setPrompt] = useState('')\n const [error, setError] = useState<string | undefined>(undefined)\n\n const submit = (): void => {\n const task = controller.createTask({ title, description, prompt })\n if (task === undefined) {\n setError(t('new.required'))\n return\n }\n onClose()\n }\n\n return (\n <div className={css.modalBackdrop} onMouseDown={event => { if (event.target === event.currentTarget) onClose() }}>\n <form\n className={css.modal}\n role=\"dialog\"\n aria-label={t('board.new')}\n onSubmit={event => { event.preventDefault(); submit() }}\n >\n <h2 className={css.modalTitle}>{t('board.new')}</h2>\n\n <label className={css.field}>\n <span className={css.fieldLabel}>{t('new.title')}</span>\n <input\n className={css.input}\n value={title}\n autoFocus\n placeholder={t('new.titlePlaceholder')}\n onChange={event => { setTitle(event.target.value); setError(undefined) }}\n />\n </label>\n\n <label className={css.field}>\n <span className={css.fieldLabel}>{t('new.description')}</span>\n <textarea\n className={css.input}\n rows={3}\n value={description}\n placeholder={t('new.descriptionPlaceholder')}\n onChange={event => { setDescription(event.target.value) }}\n />\n </label>\n\n <label className={css.field}>\n <span className={css.fieldLabel}>{t('new.prompt')}</span>\n <textarea\n className={css.input}\n rows={4}\n value={prompt}\n placeholder={t('new.promptPlaceholder')}\n onChange={event => { setPrompt(event.target.value) }}\n />\n </label>\n\n {error !== undefined && <p className={css.formError}>{error}</p>}\n\n <footer className={css.modalFooter}>\n <button type=\"button\" className={css.ghostButton} onClick={onClose}>\n {t('new.cancel')}\n </button>\n <button type=\"submit\" className={css.primaryButton}>\n {t('new.submit')}\n </button>\n </footer>\n </form>\n </div>\n )\n}\n","/**\n * Task card: the board's column item. Clicking opens the task detail — it\n * never executes anything directly (detail holds the Run button).\n */\nimport type { TaskRecord } from '../../core/tasks.ts'\nimport { executionLabel } from '../../core/tasks.ts'\nimport { t } from '../locales.ts'\nimport css from '../board.module.css'\n\n/** Compact relative/absolute time label. */\nexport function formatTime(ms: number): string {\n const date = new Date(ms)\n const now = Date.now()\n const minutes = Math.floor((now - ms) / 60000)\n if (minutes < 1) return t('time.justNow')\n if (minutes < 60) return `${minutes}m`\n if (minutes < 60 * 24) return `${Math.floor(minutes / 60)}h`\n return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`\n}\n\n/** One card in a column. */\nexport function TaskCard({ task, onClick }: { task: TaskRecord; onClick: () => void }) {\n const latest = task.executions[task.executions.length - 1]\n const runs = task.executions.length\n return (\n <button\n type=\"button\"\n className={css.card}\n data-status={task.status}\n onClick={onClick}\n title={task.description !== '' ? task.description : task.title}\n >\n <span className={css.cardTitle}>{task.title}</span>\n {task.description !== '' && <span className={css.cardExcerpt}>{task.description}</span>}\n <span className={css.cardMeta}>\n <span className={css.cardTime}>{t('board.updated')} {formatTime(task.updatedAt)}</span>\n {task.schedule?.enabled === true && (\n <span\n className={css.cardSchedule}\n title={task.schedule.nextRunAt !== undefined\n ? `${t('card.scheduled')} · ${new Date(task.schedule.nextRunAt).toLocaleString()}`\n : t('card.scheduled')}\n >\n {t('card.scheduled')}\n </span>\n )}\n {latest !== undefined && (\n <span className={css.cardRun} data-result={latest.result}>\n {runs} {t('board.runs')}\n </span>\n )}\n {latest?.sessionId !== undefined && (\n <span className={css.cardSession} title={latest.sessionId}>⌁</span>\n )}\n {task.status === 'running' && <span className={css.cardSpinner} aria-hidden=\"true\" />}\n </span>\n {latest !== undefined && executionLabel(latest) === 'running' && (\n <span className={css.cardRunningLabel}>{t('detail.result.running')}…</span>\n )}\n </button>\n )\n}\n","/**\n * Generic confirm dialog used by destructive actions (task delete).\n */\nimport { t } from '../locales.ts'\nimport css from '../board.module.css'\n\n/** Confirm overlay props. */\nexport interface ConfirmDialogProps {\n title: string\n message: string\n confirmLabel: string\n /** Render the confirm button in the danger style. */\n danger?: boolean\n onCancel: () => void\n onConfirm: () => void\n}\n\n/** Small confirm overlay. */\nexport function ConfirmDialog({ title, message, confirmLabel, danger, onCancel, onConfirm }: ConfirmDialogProps) {\n return (\n <div className={css.modalBackdrop} onMouseDown={event => { if (event.target === event.currentTarget) onCancel() }}>\n <div className={css.modal} role=\"alertdialog\" aria-label={title}>\n <h2 className={css.modalTitle}>{title}</h2>\n <p className={css.confirmMessage}>{message}</p>\n <footer className={css.modalFooter}>\n <button type=\"button\" className={css.ghostButton} onClick={onCancel}>\n {t('delete.cancel')}\n </button>\n <button\n type=\"button\"\n className={danger ? css.dangerButton : css.primaryButton}\n onClick={onConfirm}\n >\n {confirmLabel}\n </button>\n </footer>\n </div>\n </div>\n )\n}\n","/**\n * Task detail: the full view of one task — content, prompt, execution\n * history — and the only place execution can be triggered. Also offers\n * delete (with confirmation), manual status moves, and a jump to the\n * execution's session transcript.\n */\nimport { useEffect, useState } from 'react'\nimport type { BoardController } from '../../core/controller.ts'\nimport { isValidCron } from '../../core/schedule.ts'\nimport { MANUAL_STATUSES, type ExecutionRecord, type TaskRecord, type TaskStatus } from '../../core/tasks.ts'\nimport { t, type TaskBoardKey } from '../locales.ts'\nimport css from '../board.module.css'\nimport { ConfirmDialog } from './ConfirmDialog.tsx'\nimport { formatTime } from './TaskCard.tsx'\n\n/** Execution outcome → locale key. */\nconst RESULT_KEY: Record<NonNullable<ExecutionRecord['result']>, TaskBoardKey> = {\n succeeded: 'detail.result.succeeded',\n failed: 'detail.result.failed',\n cancelled: 'detail.result.cancelled',\n}\n\n/** Status → locale key (detail badge). */\nconst STATUS_KEY: Record<TaskStatus, TaskBoardKey> = {\n backlog: 'board.status.backlog',\n todo: 'board.status.todo',\n running: 'board.status.running',\n done: 'board.status.done',\n failed: 'board.status.failed',\n}\n\n/** One execution-history row. */\nfunction ExecutionRow({ execution, onOpen }: { execution: ExecutionRecord; onOpen: (sessionId: string) => void }) {\n const result = execution.result\n return (\n <li className={css.executionRow} data-result={result}>\n <span className={css.executionBadge} data-result={result}>\n {result === undefined ? t('detail.result.running') : t(RESULT_KEY[result])}\n </span>\n <span className={css.executionTimes}>\n {t('detail.executionStarted')} {formatTime(execution.startedAt)}\n {execution.endedAt !== undefined && ` · ${t('detail.executionEnded')} ${formatTime(execution.endedAt)}`}\n </span>\n {execution.sessionId !== undefined && (\n <button\n type=\"button\"\n className={css.linkButton}\n onClick={() => { onOpen(execution.sessionId as string) }}\n title={execution.sessionId}\n >\n {t('detail.viewSession')} ⌁\n </button>\n )}\n {execution.error !== undefined && execution.error !== '' && (\n <span className={css.executionError}>{execution.error}</span>\n )}\n </li>\n )\n}\n\n/** Common scheduled-run presets (cron → locale label). */\nconst SCHEDULE_PRESETS: ReadonlyArray<{ cron: string; label: TaskBoardKey }> = [\n { cron: '0 9 * * *', label: 'detail.schedule.preset.daily9' },\n { cron: '0 * * * *', label: 'detail.schedule.preset.hourly' },\n { cron: '*/10 * * * *', label: 'detail.schedule.preset.tenMin' },\n { cron: '0 9 * * 1', label: 'detail.schedule.preset.weeklyMon9' },\n]\n\n/** The scheduled-runs editor: enable toggle, cron input + presets, next-run info. */\nfunction ScheduleSection({ controller, task }: { controller: BoardController; task: TaskRecord }) {\n const schedule = task.schedule\n const [cron, setCron] = useState(schedule?.cron ?? '0 9 * * *')\n const [enabled, setEnabled] = useState(schedule?.enabled ?? false)\n const [nextRunAt, setNextRunAt] = useState<number | undefined>(schedule?.nextRunAt)\n const [lastTriggeredAt, setLastTriggeredAt] = useState<number | undefined>(schedule?.lastTriggeredAt)\n const [error, setError] = useState<string | undefined>(undefined)\n\n // Keep the editor in sync when the task record changes underneath (the\n // schedule rolls forward as runs trigger).\n useEffect(() => {\n setCron(schedule?.cron ?? '0 9 * * *')\n setEnabled(schedule?.enabled ?? false)\n setNextRunAt(schedule?.nextRunAt)\n setLastTriggeredAt(schedule?.lastTriggeredAt)\n setError(undefined)\n }, [task.id, schedule?.enabled, schedule?.cron, schedule?.nextRunAt, schedule?.lastTriggeredAt])\n\n /** Validate + persist the current cron text (Enter or blur). */\n const saveCron = (value: string): void => {\n const trimmed = value.trim()\n setCron(trimmed)\n if (trimmed === '' || !isValidCron(trimmed)) {\n setError(t('detail.schedule.invalid'))\n return\n }\n setError(undefined)\n controller.setSchedule(task.id, { cron: trimmed })\n }\n\n /** Arm/disarm the schedule (arming first persists the edited cron). */\n const toggleEnabled = (next: boolean): void => {\n const trimmed = cron.trim()\n if (next && (trimmed === '' || !isValidCron(trimmed))) {\n setError(t('detail.schedule.invalid'))\n return\n }\n setError(undefined)\n if (next && trimmed !== schedule?.cron) controller.setSchedule(task.id, { cron: trimmed })\n if (controller.setSchedule(task.id, { enabled: next })) setEnabled(next)\n }\n\n const applyPreset = (preset: string): void => {\n if (preset === '') return\n setCron(preset)\n setError(undefined)\n controller.setSchedule(task.id, { cron: preset })\n }\n\n const nextLabel = !enabled || nextRunAt === undefined\n ? t('detail.schedule.notScheduled')\n : nextRunAt <= Date.now()\n ? t('detail.schedule.dueSoon')\n : new Date(nextRunAt).toLocaleString()\n const lastLabel = lastTriggeredAt === undefined ? '—' : new Date(lastTriggeredAt).toLocaleString()\n\n return (\n <section className={css.detailSection}>\n <h4>{t('detail.schedule')}</h4>\n <label className={css.scheduleToggle}>\n <input\n type=\"checkbox\"\n checked={enabled}\n onChange={event => { toggleEnabled(event.target.checked) }}\n />\n <span>{t('detail.schedule.enable')}</span>\n </label>\n <div className={css.scheduleRow}>\n <input\n className={`${css.input} ${css.scheduleInput}${error !== undefined ? ` ${css.scheduleInputInvalid}` : ''}`}\n value={cron}\n placeholder=\"0 9 * * *\"\n spellCheck={false}\n aria-label={t('detail.schedule.cron')}\n onChange={event => { setCron(event.target.value); setError(undefined) }}\n onBlur={() => { saveCron(cron) }}\n onKeyDown={event => { if (event.key === 'Enter') saveCron(cron) }}\n />\n <select\n className={css.schedulePreset}\n value=\"\"\n aria-label={t('detail.schedule.presets')}\n onChange={event => { applyPreset(event.target.value) }}\n >\n <option value=\"\">{t('detail.schedule.presets')}…</option>\n {SCHEDULE_PRESETS.map(preset => (\n <option key={preset.cron} value={preset.cron}>{t(preset.label)}</option>\n ))}\n </select>\n </div>\n {error !== undefined && <p className={css.formError}>{error}</p>}\n <p className={css.scheduleMeta}>\n {t('detail.schedule.nextRun')} {nextLabel}\n {' · '}{t('detail.schedule.lastTriggered')} {lastLabel}\n </p>\n </section>\n )\n}\n\n/** Task detail overlay. */\nexport function TaskDetail({ controller, task }: { controller: BoardController; task: TaskRecord }) {\n const [confirmDelete, setConfirmDelete] = useState(false)\n const running = task.status === 'running'\n\n // Keep the overlay in sync if the task record changes underneath.\n const [latest, setLatest] = useState(task)\n useEffect(() => { setLatest(task) }, [task])\n const current = latest\n\n return (\n <div className={css.modalBackdrop} onMouseDown={event => { if (event.target === event.currentTarget) controller.closeTask() }}>\n <div className={css.detail} role=\"dialog\" aria-label={t('detail.title')}>\n <header className={css.detailHeader}>\n <h2 className={css.detailTitle}>{current.title}</h2>\n <span className={css.statusBadge} data-status={current.status}>{t(STATUS_KEY[current.status])}</span>\n <button\n type=\"button\"\n className={css.iconButton}\n aria-label={t('detail.close')}\n onClick={() => { controller.closeTask() }}\n >\n ×\n </button>\n </header>\n\n <div className={css.detailBody}>\n <section className={css.detailSection}>\n <h4>{t('detail.description')}</h4>\n <p className={css.detailText}>{current.description !== '' ? current.description : '—'}</p>\n </section>\n\n <section className={css.detailSection}>\n <h4>{t('detail.prompt')}</h4>\n <pre className={css.promptBlock}>{current.prompt !== '' ? current.prompt : current.title}</pre>\n </section>\n\n <ScheduleSection controller={controller} task={current} />\n\n <section className={css.detailSection}>\n <h4>{t('detail.execution')}</h4>\n {current.executions.length === 0 ? (\n <p className={css.detailText}>{t('detail.noExecution')}</p>\n ) : (\n <ul className={css.executionList}>\n {[...current.executions].reverse().map(execution => (\n <ExecutionRow\n key={execution.id}\n execution={execution}\n onOpen={sessionId => { controller.openSession(sessionId) }}\n />\n ))}\n </ul>\n )}\n </section>\n\n <section className={css.detailSection}>\n <h4>{t('board.status')}</h4>\n <div className={css.moveRow}>\n {MANUAL_STATUSES.map(status => (\n <button\n key={status}\n type=\"button\"\n className={css.ghostButton}\n disabled={current.status === status || running}\n onClick={() => { controller.moveTask(current.id, status) }}\n >\n {t(`status.move.${status}` as TaskBoardKey)}\n </button>\n ))}\n </div>\n </section>\n </div>\n\n <footer className={css.detailFooter}>\n <button\n type=\"button\"\n className={css.primaryButton}\n disabled={running}\n onClick={() => {\n // Running kicks off a real agent session; close the detail so\n // the whole board stays visible while the task executes.\n controller.closeTask()\n void controller.rerunTask(current.id)\n }}\n >\n {current.executions.length === 0 ? t('detail.run') : t('detail.rerun')}\n </button>\n <button\n type=\"button\"\n className={css.dangerButton}\n onClick={() => { setConfirmDelete(true) }}\n >\n {t('detail.delete')}\n </button>\n <span className={css.detailMeta}>\n {t('board.created')} {formatTime(current.createdAt)}\n </span>\n </footer>\n </div>\n\n {confirmDelete && (\n <ConfirmDialog\n title={t('delete.title')}\n message={t('delete.confirm', { name: current.title })}\n confirmLabel={t('delete.ok')}\n danger\n onCancel={() => { setConfirmDelete(false) }}\n onConfirm={() => {\n setConfirmDelete(false)\n controller.deleteTask(current.id)\n controller.closeTask()\n }}\n />\n )}\n </div>\n )\n}\n","/**\n * Board view: the multi-column kanban that replaces the middle column while\n * active. Cards open the task detail (never execute directly); the header\n * offers filter, new-task, and a back-to-chat escape.\n */\nimport { useEffect, useState } from 'react'\nimport { selectedTaskOf, type BoardController } from '../../core/controller.ts'\nimport { COLUMNS, type TaskRecord, type TaskStatus } from '../../core/tasks.ts'\nimport { t, type TaskBoardKey } from '../locales.ts'\nimport css from '../board.module.css'\nimport { NewTaskModal } from './NewTaskModal.tsx'\nimport { TaskCard } from './TaskCard.tsx'\nimport { TaskDetail } from './TaskDetail.tsx'\n\n/** Column status → locale key. */\nconst STATUS_KEY: Record<TaskStatus, TaskBoardKey> = {\n backlog: 'board.status.backlog',\n todo: 'board.status.todo',\n running: 'board.status.running',\n done: 'board.status.done',\n failed: 'board.status.failed',\n}\n\n/** Case-insensitive title/description match. */\nfunction matchesFilter(task: TaskRecord, filter: string): boolean {\n if (filter.trim() === '') return true\n const needle = filter.trim().toLowerCase()\n return task.title.toLowerCase().includes(needle) || task.description.toLowerCase().includes(needle)\n}\n\n/** Board component; subscribes to the controller snapshot. */\nexport function TaskBoard({ controller }: { controller: BoardController }) {\n const [snapshot, setSnapshot] = useState(controller.getSnapshot())\n useEffect(\n () => controller.subscribe(() => setSnapshot(controller.getSnapshot())),\n [controller],\n )\n const [filter, setFilter] = useState('')\n const [showNew, setShowNew] = useState(false)\n const selected = selectedTaskOf(snapshot)\n const visible = snapshot.tasks.filter(task => matchesFilter(task, filter))\n\n return (\n <div className={css.board} data-dsh-taskboard-board=\"\">\n <header className={css.boardHeader}>\n <h2 className={css.boardTitle}>{t('board.title')}</h2>\n <input\n className={css.search}\n type=\"search\"\n placeholder={t('board.search')}\n value={filter}\n onChange={event => { setFilter(event.target.value) }}\n aria-label={t('board.search')}\n />\n <button\n type=\"button\"\n className={css.primaryButton}\n onClick={() => { setShowNew(true) }}\n >\n + {t('board.new')}\n </button>\n <button\n type=\"button\"\n className={css.ghostButton}\n onClick={() => { controller.closeBoard() }}\n >\n {t('board.close')}\n </button>\n </header>\n\n <div className={css.columns}>\n {COLUMNS.map(column => {\n const tasks = visible.filter(task => task.status === column.status)\n return (\n <section key={column.status} className={css.column} data-status={column.status}>\n <header className={css.columnHeader}>\n <span className={css.statusDot} data-status={column.status} aria-hidden=\"true\" />\n <h3 className={css.columnTitle}>{t(STATUS_KEY[column.status])}</h3>\n <span className={css.columnCount}>{tasks.length}</span>\n </header>\n <div className={css.cards}>\n {tasks.map(task => (\n <TaskCard\n key={task.id}\n task={task}\n onClick={() => { controller.openTask(task.id) }}\n />\n ))}\n {tasks.length === 0 && <div className={css.columnEmpty}>{t('board.empty')}</div>}\n </div>\n </section>\n )\n })}\n </div>\n\n {selected !== undefined && (\n <TaskDetail controller={controller} task={selected} />\n )}\n {showNew && (\n <NewTaskModal\n controller={controller}\n onClose={() => { setShowNew(false) }}\n />\n )}\n </div>\n )\n}\n","/**\n * Board view mounting.\n *\n * The `conversation` slot is single-occupant (ui-conversation) and external\n * plugins cannot declare slots, so the board takes over the center column at\n * the DOM level: a container is appended inside the center column\n * (`[class*=\"centerCol\"]`, the dsh 0.1.0-rc.6 AppFrame layout; previously\n * `[data-pane=\"conversation\"]` on older shells — the mount selector keeps both)\n * as an extra trailing child\n * React never manages, and a stylesheet\n * rule hides the conversation content while the board is active. Toggling is\n * a data attribute on <html> — no React involvement, so the conversation\n * subtree underneath stays mounted and stateful.\n */\nimport { createRoot, type Root } from 'react-dom/client'\nimport type { BoardController } from '../core/controller.ts'\nimport { TaskBoard } from './board/TaskBoard.tsx'\nimport css from './board.module.css'\n\n/** The injected board container (kept in the DOM, hidden when inactive). */\nexport const BOARD_VIEW_SELECTOR = '[data-dsh-taskboard-view]'\n\nconst CONVERSATION_COLUMN_SELECTOR = '[data-pane=\"conversation\"], [class*=\"centerCol\"]'\nconst ACTIVE_ATTR = 'data-dsh-taskboard-active'\n/** The sibling panel's activation attribute (ssh), removed when this panel opens. */\nconst OTHER_ACTIVE_ATTR = 'data-dsh-ssh-active'\n/** Cross-plugin activation event; detail is the activating panel name. */\nconst ACTIVATE_EVENT = 'dsh-panel-activate'\nconst PANEL_NAME = 'taskboard'\n\n/** Find the center column, or undefined while the frame is not mounted. */\nfunction conversationColumn(): HTMLElement | undefined {\n return document.querySelector<HTMLElement>(CONVERSATION_COLUMN_SELECTOR) ?? undefined\n}\n\n/**\n * Mount the board React tree into the center column and bind its visibility\n * to the controller's boardOpen state.\n * @param controller - the board controller driving the view.\n * @returns disposer unmounting the tree and restoring the column.\n */\nexport function mountBoard(controller: BoardController): () => void {\n let root: Root | undefined\n let container: HTMLDivElement | undefined\n\n const ensure = (): void => {\n if (container !== undefined) return\n const column = conversationColumn()\n if (column === undefined) return\n container = document.createElement('div')\n container.dataset.dshTaskboardView = ''\n container.className = css.boardView\n column.appendChild(container)\n root = createRoot(container)\n root.render(<TaskBoard controller={controller} />)\n }\n\n // The frame mounts after boot settlement; watch for the column's arrival.\n const waitObserver = new MutationObserver(() => { ensure() })\n waitObserver.observe(document.body, { childList: true, subtree: true })\n\n const applyActive = (): void => {\n if (controller.getSnapshot().boardOpen) {\n // Single-occupant center column: opening this panel must evict the\n // sibling panel (ssh), both its html attribute and its controller\n // state, otherwise the two panels' visibility rules fight and the\n // second click appears dead.\n document.documentElement.removeAttribute(OTHER_ACTIVE_ATTR)\n document.documentElement.setAttribute(ACTIVE_ATTR, '')\n document.dispatchEvent(new CustomEvent(ACTIVATE_EVENT, { detail: PANEL_NAME }))\n } else {\n document.documentElement.removeAttribute(ACTIVE_ATTR)\n }\n }\n const onOtherActivate = (event: Event): void => {\n if ((event as CustomEvent).detail === 'ssh' && controller.getSnapshot().boardOpen) {\n controller.closeBoard()\n }\n }\n // Jump out on sidebar context clicks: clicking a session/workspace row\n // (including the already-current one, which produces no session-change\n // event) hands the center column back to the conversation. Capture phase,\n // so the panel closes before the shell processes the click.\n const SIDEBAR_ROW_SELECTOR = '[class*=\"sessionRow\"], [class*=\"projectRow\"], [class*=\"searchResultRow\"], [class*=\"searchResultWorkspace\"], [class*=\"newSession\"]'\n const onClickSidebarRow = (event: MouseEvent): void => {\n if (!controller.getSnapshot().boardOpen) return\n const target = event.target as HTMLElement | null\n if (target === null) return\n if (target.closest(SIDEBAR_ROW_SELECTOR) !== null) controller.closeBoard()\n }\n document.addEventListener('click', onClickSidebarRow, true)\n document.addEventListener(ACTIVATE_EVENT, onOtherActivate)\n const unsubscribe = controller.subscribe(applyActive)\n applyActive()\n ensure()\n\n return () => {\n document.removeEventListener('click', onClickSidebarRow, true)\n document.removeEventListener(ACTIVATE_EVENT, onOtherActivate)\n waitObserver.disconnect()\n unsubscribe()\n document.documentElement.removeAttribute(ACTIVE_ATTR)\n root?.unmount()\n root = undefined\n container?.remove()\n container = undefined\n }\n}\n","/**\n * Sidebar entry injection.\n *\n * dsh's sidebar shell exposes no slot an external plugin can register into\n * (`sidebar.workspaces` / `sidebar.settings` are single-occupant and already\n * taken), so — following the skin precedent of DOM-level extension — the\n * entry row is injected between the shell's New Session button and the\n * workspace browser. The injection self-heals: a MutationObserver watches the\n * sidebar root and re-inserts the row whenever a React re-render displaces it\n * (re-insertion happens in the same frame, before paint, so no flicker).\n *\n * The row is plain DOM (no React tree) so it can never disturb the shell's\n * reconciliation; the board view it toggles is a separate React root mounted\n * in the center column (see board-mount.ts).\n */\nimport type { BoardController } from '../core/controller.ts'\nimport { t } from './locales.ts'\nimport css from './board.module.css'\n\n/** Stable data attribute identifying the injected entry row. */\nexport const ENTRY_SELECTOR = '[data-dsh-taskboard-entry]'\n\n/** Inline icon (matches the shell's 16px nav-icon look). */\nconst ICON = `<svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"2\" y=\"2.5\" width=\"12\" height=\"11\" rx=\"1.5\"/><path d=\"M2 6.5h12M6.5 6.5v7\"/></svg>`\n\n/** Find the sidebar shell root element, or undefined while not yet mounted. */\nfunction sidebarRoot(): HTMLElement | undefined {\n const column = document.querySelector<HTMLElement>('[data-pane=\"sidebar\"], [class*=\"sidebarCol\"]')\n if (column === null) return undefined\n // Current shells wrap the sidebar UI: column > wrapper > root(logoRow owner).\n // Prefer the element that owns the logo row — the real sidebar UI root —\n // and fall back to the column's first child for legacy shells.\n const logoOwner = column.querySelector<HTMLElement>('[class*=\"logoRow\"]')?.parentElement\n return logoOwner ?? (column.firstElementChild as HTMLElement | undefined)\n}\n\n/** The New Session button: nested in the logo row on current shells, a direct child on legacy shells. */\nfunction newSessionButton(root: HTMLElement): HTMLButtonElement | undefined {\n const nested = root.querySelector<HTMLButtonElement>('button[class*=\"newSession\"]')\n if (nested !== null) return nested\n for (const child of root.children) {\n if (child.tagName === 'BUTTON') return child as HTMLButtonElement\n }\n return undefined\n}\n\n/** Build the entry row (a detached button; insert once the shell is up). */\nfunction createEntry(controller: BoardController): HTMLButtonElement {\n const entry = document.createElement('button')\n entry.type = 'button'\n entry.dataset.dshTaskboardEntry = ''\n entry.className = css.entry\n entry.setAttribute('aria-label', t('entry.label'))\n entry.innerHTML = `<span class=\"${css.entryIcon}\">${ICON}</span><span class=\"${css.entryLabel}\">${t('entry.label')}</span>`\n entry.addEventListener('click', () => { controller.toggleBoard() })\n return entry\n}\n\n/** Re-insert the entry after the New Session row (before the browser region). */\nfunction placeEntry(root: HTMLElement, entry: HTMLButtonElement): boolean {\n const button = newSessionButton(root)\n if (button === undefined) return false\n if (entry.parentElement !== root) {\n // Position relative to the family block (entries injected by sibling\n // plugins), never relative to transient logoRow geometry: every family\n // plugin that self-heals during a re-render then lands in the same\n // relative order, so the entries cannot swap positions regardless of\n // observer callback order or of shell wrapper changes. There is no\n // append-to-end fallback: appending at the end would randomly reorder\n // the block after a shell re-render.\n const row = button.closest('[class*=\"logoRow\"]')\n const base = (row !== null && row.parentElement === root) ? row : button\n const family = Array.from(root.children).filter(\n (el): el is HTMLElement => el instanceof HTMLElement && el.matches('[data-dsh-taskboard-entry], [data-dsh-ssh-entry]'),\n )\n // task board sits before the whole family block.\n const anchor = family.length > 0 ? family[0] : base.nextElementSibling\n root.insertBefore(entry, anchor)\n }\n return true\n}\n\n/**\n * Mount the sidebar entry, waiting for the shell to render and self-healing\n * on later React re-renders.\n * @param controller - the board controller the entry toggles.\n * @returns disposer removing the entry and its observers.\n */\nexport function mountSidebarEntry(controller: BoardController): () => void {\n const entry = createEntry(controller)\n let root: HTMLElement | undefined\n let placed = false\n\n const tryPlace = (): void => {\n if (root !== undefined && !root.isConnected) {\n // The shell rebuilt the sidebar pane (whole-tree teardown); the root\n // observer is gone with the old tree, so detach it and re-query from\n // scratch. The new pane is later noticed by the body-level watcher.\n rootObserver.disconnect()\n root = undefined\n placed = false\n }\n if (placed) {\n // Cheap short-circuit: entry still lives in a mountable subtree.\n if (document.body.contains(entry)) return\n // Entry was torn down together with the old tree; reset and re-place.\n rootObserver.disconnect()\n root = undefined\n placed = false\n }\n root ??= sidebarRoot()\n if (root === undefined) return\n placed = placeEntry(root, entry)\n if (placed) {\n rootObserver.observe(root, { childList: true, subtree: true })\n }\n }\n\n // Body-level watcher retained as the \"whole rebuild\" fallback: when the shell\n // tears down the whole sidebar pane, the root observer is gone with it and\n // only this body observation can notice the new pane mounting. It is no\n // longer disconnected after placement; the placed-and-still-mounted case\n // short-circuits through the cheap document.body.contains(entry) check, so\n // unrelated app mutations (e.g. chat streaming) cost one contains check\n // instead of churning the full re-query.\n const waitObserver = new MutationObserver(() => { tryPlace() })\n waitObserver.observe(document.body, { childList: true, subtree: true })\n\n // Self-heal: if a React re-render displaces the row, re-insert it in the\n // same frame (microtask before paint → no visible flicker).\n const rootObserver = new MutationObserver(() => {\n if (root === undefined || !root.isConnected) {\n placed = false\n tryPlace()\n return\n }\n if (!root.contains(entry)) {\n placed = placeEntry(root, entry)\n }\n })\n\n // Reflect the board's open state on the row (active highlight). Note: assigning\n // undefined to dataset.active materializes data-active=\"undefined\" and keeps the\n // row permanently highlighted — delete the attribute instead.\n const syncActive = () => {\n if (controller.getSnapshot().boardOpen) entry.dataset.active = 'true'\n else delete entry.dataset.active\n }\n const unsubscribe = controller.subscribe(syncActive)\n syncActive()\n\n tryPlace()\n\n return () => {\n waitObserver.disconnect()\n rootObserver.disconnect()\n unsubscribe()\n entry.remove()\n }\n}\n","/**\n * Shared chrome for the plugin settings card: a disclosure header naming the\n * plugin and what its settings govern, the controls inside, and the save that\n * writes them. Renders nothing while the namespace is unavailable — a\n * deployment that does not compose the owning plugin should show no trace of\n * it. Mirrors the official ui-plugin-config PluginCard in a self-contained\n * slice (this package must not depend on a sibling UI package).\n */\n\nimport { useState, type ReactNode } from 'react'\nimport type { CardShell } from './settings-form.ts'\nimport type { SettingsCardKey } from './locales.ts'\nimport css from './settings-card.module.css'\n\n/** Card chrome shared by every plugin settings card. */\nexport interface PluginSettingsCardProps {\n /** Locale reader for this card's copy. */\n t: (key: SettingsCardKey) => string\n /** Locale key of the plugin's name. */\n titleKey: SettingsCardKey\n /** Locale key of the line describing what this plugin's settings govern. */\n descriptionKey: SettingsCardKey\n /** The card's form state: availability, writability, and what a save would do. */\n state: CardShell\n /** Write every staged edit. */\n onSave: () => void\n /** Drop every staged edit. */\n onDiscard: () => void\n /** The plugin's controls. */\n children: ReactNode\n}\n\n/**\n * Render one plugin settings card.\n * @param props - the plugin's copy keys, its form state, and its controls.\n * @returns the card, or nothing while the namespace is still loading.\n */\nexport function PluginSettingsCard(props: PluginSettingsCardProps) {\n const [open, setOpen] = useState(false)\n const { state } = props\n if (!state.available) return null\n const title = props.t(props.titleKey)\n const blocked = !state.dirty || state.invalid || state.saving\n const cardClass = open ? `${css.cardOpen} ${css.card}` : css.card\n // The namespace exists but the Host does not serve it to this client (the\n // official settings allowlist omits third-party namespaces): show a card\n // that explains the gap instead of vanishing, so a missing card never\n // reads as a missing plugin.\n if (!state.exposed) {\n return (\n <li className={cardClass}>\n <button\n type=\"button\"\n className={css.header}\n aria-expanded={open}\n aria-label={`${props.t(open ? 'settings.collapse' : 'settings.expand')}: ${title}`}\n onClick={() => { setOpen(!open) }}\n >\n <span className={css.headText}>\n <span className={css.name}>{title}</span>\n <span className={css.description}>{props.t(props.descriptionKey)}</span>\n </span>\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 14 14\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n className={open ? `${css.chevron} ${css.chevronOpen}` : css.chevron}\n >\n <path\n d=\"M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z\"\n fill=\"currentColor\"\n />\n </svg>\n </button>\n {open\n ? (\n <div className={css.body}>\n <p className={css.notExposed} role=\"status\">{props.t('settings.notExposed')}</p>\n </div>\n )\n : null}\n </li>\n )\n }\n return (\n <li className={cardClass}>\n <button\n type=\"button\"\n className={css.header}\n aria-expanded={open}\n aria-label={`${props.t(open ? 'settings.collapse' : 'settings.expand')}: ${title}`}\n onClick={() => { setOpen(!open) }}\n >\n <span className={css.headText}>\n <span className={css.name}>{title}</span>\n <span className={css.description}>{props.t(props.descriptionKey)}</span>\n </span>\n {state.dirty ? <span className={css.pending}>{props.t('settings.unsaved')}</span> : null}\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 14 14\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n className={open ? `${css.chevron} ${css.chevronOpen}` : css.chevron}\n >\n <path\n d=\"M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z\"\n fill=\"currentColor\"\n />\n </svg>\n </button>\n {open\n ? (\n <div className={css.body}>\n {!state.writable ? <p className={css.readOnly} role=\"status\">{props.t('settings.readOnly')}</p> : null}\n {props.children}\n <div className={css.footer}>\n {state.failed ? <p className={css.failed} role=\"status\">{props.t('settings.saveFailed')}</p> : null}\n <button\n type=\"button\"\n className={css.discard}\n disabled={!state.dirty || state.saving}\n onClick={props.onDiscard}\n >\n {props.t('settings.discard')}\n </button>\n <button\n type=\"button\"\n className={css.save}\n disabled={blocked}\n onClick={props.onSave}\n >\n {props.t(!state.saving ? 'settings.save' : 'settings.saving')}\n </button>\n </div>\n </div>\n )\n : null}\n </li>\n )\n}\n\n/** Props every field control needs regardless of its value type. */\nexport interface FieldProps {\n /** Stable id associating the label with its control. */\n id: string\n /** Visible label. */\n label: string\n /** One-line explanation rendered under the control. */\n hint: string\n /** Draft text this control renders. */\n text: string\n /** True when saving would leave a user-layer entry for this field. */\n overridden: boolean\n /** True when the draft is not a value this field accepts. */\n invalid: boolean\n /** Copy for the overridden badge. */\n overriddenLabel: string\n /** Copy for the reset control. */\n resetLabel: string\n /** Copy shown in place of the hint while the draft is invalid. */\n invalidLabel: string\n /** Disables every control (read-only document, or an unavailable namespace). */\n disabled: boolean\n /** Stage draft text. */\n onEdit: (text: string) => void\n /** Stage a clear so the field re-inherits the composition layer. */\n onReset: () => void\n}\n\n/** A staged value field. `numeric` only hints the keypad: which drafts a field accepts is decided by its spec. */\nexport function ValueField(props: FieldProps & {\n /** Hints a numeric keypad without narrowing what the control accepts. */\n numeric?: boolean\n /** Placeholder shown while the draft is empty. */\n placeholder?: string\n}) {\n return (\n <div className={css.field}>\n <div className={css.head}>\n <label className={css.label} htmlFor={props.id}>{props.label}</label>\n {props.overridden\n ? (\n <span className={css.badges}>\n <span className={css.badge}>{props.overriddenLabel}</span>\n <button\n type=\"button\"\n className={css.reset}\n disabled={props.disabled}\n onClick={props.onReset}\n >\n {props.resetLabel}\n </button>\n </span>\n )\n : null}\n </div>\n <input\n id={props.id}\n className={props.invalid ? css.inputInvalid : css.input}\n type=\"text\"\n {...props.numeric === true ? { inputMode: 'numeric' as const } : {}}\n {...props.invalid ? { 'aria-invalid': true } : {}}\n value={props.text}\n placeholder={props.placeholder ?? ''}\n disabled={props.disabled}\n onChange={(event) => { props.onEdit(event.target.value) }}\n />\n <p className={props.invalid ? css.invalid : css.hint}>\n {props.invalid ? props.invalidLabel : props.hint}\n </p>\n </div>\n )\n}\n\n/** A staged boolean field: 继承 / 开 / 关. */\nexport function BooleanField(props: FieldProps & {\n /** Copy for the inherit option. */\n inheritLabel: string\n /** Copy for the on option. */\n onLabel: string\n /** Copy for the off option. */\n offLabel: string\n}) {\n return (\n <div className={css.field}>\n <div className={css.head}>\n <label className={css.label} htmlFor={props.id}>{props.label}</label>\n {props.overridden\n ? (\n <span className={css.badges}>\n <span className={css.badge}>{props.overriddenLabel}</span>\n <button\n type=\"button\"\n className={css.reset}\n disabled={props.disabled}\n onClick={props.onReset}\n >\n {props.resetLabel}\n </button>\n </span>\n )\n : null}\n </div>\n <select\n id={props.id}\n className={css.select}\n value={props.text}\n disabled={props.disabled}\n onChange={(event) => { props.onEdit(event.target.value) }}\n >\n <option value=\"\">{props.inheritLabel}</option>\n <option value=\"true\">{props.onLabel}</option>\n <option value=\"false\">{props.offLabel}</option>\n </select>\n <p className={css.hint}>{props.hint}</p>\n </div>\n )\n}","/**\n * Staged form model behind the plugin settings card. A card stages what the\n * user types and writes it only when they save — the settings write is a\n * durable, revision-fenced document mutation, so staging keeps what is on\n * screen exactly what a save would store. Mirrors the official\n * ui-plugin-config card-store pattern in a self-contained slice: this\n * package must not depend on a sibling UI package.\n */\n\nimport type { SettingsScope, SettingsScopeSnapshot, SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'\nimport { createSnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'\n\n/** The write one field's staged text performs when the card is saved. */\nexport type FieldWrite =\n | { kind: 'set'; value: unknown }\n | { kind: 'clear' }\n\n/** How one field converts between its stored value and its draft text. */\nexport interface FieldSpec {\n /** Field name inside the namespace section. */\n field: string\n /** Render a stored value as draft text; the empty string when the section carries none. */\n format: (value: unknown) => string\n /**\n * The write this draft text stages, or undefined when the text is not a\n * value this field accepts — which blocks the save rather than discarding it.\n */\n parse: (text: string) => FieldWrite | undefined\n}\n\n/** One field as the card renders it. */\nexport interface FieldState {\n /** Draft text the control renders. */\n text: string\n /** Whether saving would leave a user-layer entry for this field. */\n overridden: boolean\n /** Whether the draft is not a value this field accepts, which blocks saving. */\n invalid: boolean\n}\n\n/** Form state every plugin settings card shares. */\nexport interface CardShell {\n /** False while the namespace is still loading; the card renders nothing. */\n available: boolean\n /**\n * Whether the namespace is actually served to this client. False when the\n * Host deployment does not expose it (e.g. the official apiproxy settings\n * allowlist omits third-party namespaces): the card renders an explanation\n * instead of its form, so a missing namespace never looks like a missing\n * plugin.\n */\n exposed: boolean\n /** Whether the Host document accepts writes. */\n writable: boolean\n /** Whether the form holds edits that a save would write. */\n dirty: boolean\n /** Whether any staged draft is invalid, which blocks the save. */\n invalid: boolean\n /** Whether a save is crossing the wire. */\n saving: boolean\n /** Whether the last save did not land as staged; cleared by the next edit or save. */\n failed: boolean\n}\n\n/** The write actions the card's slot entry injects. */\nexport interface CardActions {\n /** Stage draft text for one field. */\n edit: (field: string, text: string) => void\n /** Stage a clear, so saving lets the field re-inherit the composition layer. */\n resetField: (field: string) => void\n /** Write every staged edit, then re-seed from what the Host accepted. */\n save: () => void\n /** Drop every staged edit. */\n discard: () => void\n}\n\n/** One field's staged edit. */\ninterface StagedEdit {\n /** Draft text the control renders. */\n text: string\n /** True when this edit clears the field whatever text it shows. */\n clear: boolean\n}\n\n/** One staged edit resolved into the write a save performs. */\ninterface PlannedWrite {\n /** Field this entry writes. */\n field: string\n /** Perform the write and report whether the Host holds the staged value afterwards. */\n run: (() => Promise<boolean>) | undefined\n}\n\n/** A whole-number field. An empty draft clears the field; any other draft that is not a finite number blocks the save. */\nexport function numberField(field: string): FieldSpec {\n return {\n field,\n format: value => typeof value === 'number' ? String(value) : '',\n parse: (text) => {\n const trimmed = text.trim()\n if (trimmed === '') return { kind: 'clear' }\n const parsed = Number(trimmed)\n return Number.isFinite(parsed) ? { kind: 'set', value: parsed } : undefined\n },\n }\n}\n\n/** A free-text field. An empty draft clears the field. */\nexport function textField(field: string): FieldSpec {\n return {\n field,\n format: value => typeof value === 'string' ? value : '',\n parse: (text) => {\n const trimmed = text.trim()\n return trimmed === '' ? { kind: 'clear' } : { kind: 'set', value: trimmed }\n },\n }\n}\n\n/** A boolean field, edited through true/false draft text. */\nexport function booleanField(field: string): FieldSpec {\n return {\n field,\n format: value => typeof value === 'boolean' ? String(value) : '',\n parse: (text) => {\n if (text === 'true') return { kind: 'set', value: true }\n if (text === 'false') return { kind: 'set', value: false }\n return undefined\n },\n }\n}\n\n/**\n * Stages one card's edits over one settings namespace and writes them on save.\n *\n * The Host is the only authority on whether a value was accepted — its\n * validators own the constraints no schema can express — so the outcome is\n * read back from the section rather than predicted here. A save that did not\n * land keeps its drafts, so the user can correct them instead of retyping.\n */\nexport class CardForm<T> {\n private readonly specs: Map<string, FieldSpec>\n private readonly staged = new Map<string, StagedEdit>()\n private readonly listeners = new Set<() => void>()\n private saving = false\n private failed = false\n\n /** @param scope - the bound settings scope for this card's namespace. */\n constructor(\n private readonly scope: SettingsScope<T>,\n specs: FieldSpec[],\n ) {\n this.specs = new Map(specs.map(spec => [spec.field, spec]))\n scope.subscribe(() => { this.publish() })\n }\n\n /** Publish a projection of this form, rebuilt whenever the scope or a draft changes. */\n bind<S>(project: () => S): SnapshotStore<S> {\n const store = createSnapshotStore(project())\n this.listeners.add(() => { store.set(project()) })\n return store\n }\n\n /** Read the card-level state: what the Host serves, and what a save would do. */\n shell(): CardShell {\n const snapshot = this.scope.getSnapshot()\n const plan = this.plan()\n return {\n available: snapshot.status !== 'loading',\n exposed: snapshot.status === 'ready',\n writable: snapshot.writable,\n dirty: plan.length > 0,\n invalid: plan.some(item => item.run === undefined),\n saving: this.saving,\n failed: this.failed,\n }\n }\n\n /** Read one field's state from the effective section and its staged draft. */\n field(field: string): FieldState {\n const spec = this.specOf(field)\n const staged = this.staged.get(field)\n if (staged === undefined) {\n return { text: spec.format(this.sectionValue(field)), overridden: this.stored(field), invalid: false }\n }\n const write = staged.clear ? { kind: 'clear' as const } : spec.parse(staged.text)\n return {\n text: staged.text,\n overridden: write?.kind === 'set',\n invalid: write === undefined,\n }\n }\n\n /** The actions the card's slot registration injects. */\n actions(): CardActions {\n return {\n edit: (field, text) => { this.stage(field, { text, clear: false }) },\n resetField: (field) => {\n this.stage(field, { text: this.specOf(field).format(this.baseValue(field)), clear: true })\n },\n save: () => { void this.save() },\n discard: () => {\n if (this.staged.size === 0 && !this.failed) return\n this.staged.clear()\n this.failed = false\n this.publish()\n },\n }\n }\n\n /**\n * Write every staged edit, then re-seed from what the Host accepted.\n * @returns settlement after every write and the read-back.\n */\n async save(): Promise<void> {\n const plan = this.plan()\n const writes = plan.flatMap(item => item.run === undefined ? [] : [item.run])\n if (plan.length === 0 || this.saving || writes.length !== plan.length) return\n this.saving = true\n this.failed = false\n this.publish()\n let landed = true\n for (const write of writes) {\n landed = await write() && landed\n }\n if (landed) this.staged.clear()\n this.saving = false\n this.failed = !landed\n this.publish()\n }\n\n /**\n * Every staged edit a save would write. An entry whose draft is not a value\n * its field accepts carries no write: the form is still dirty, and the save\n * refuses rather than dropping the edit. A staged edit that matches the\n * effective section is not a write at all.\n * @returns the planned writes, in the order the fields were staged.\n */\n private plan(): PlannedWrite[] {\n const plan: PlannedWrite[] = []\n for (const [field, staged] of this.staged) {\n const spec = this.specOf(field)\n if (staged.clear) {\n if (this.stored(field)) plan.push({ field, run: () => this.clear(field) })\n continue\n }\n if (staged.text === spec.format(this.sectionValue(field))) continue\n const write = spec.parse(staged.text)\n if (write === undefined) plan.push({ field, run: undefined })\n else if (write.kind === 'clear') plan.push({ field, run: () => this.clear(field) })\n else plan.push({ field, run: () => this.store(field, write.value) })\n }\n return plan\n }\n\n private async clear(field: string): Promise<boolean> {\n await this.scope.unset(field)\n return !this.stored(field)\n }\n\n private async store(field: string, value: unknown): Promise<boolean> {\n await this.scope.set(field, value)\n return this.userLayer()?.[field] === value\n }\n\n private stage(field: string, edit: StagedEdit): void {\n this.staged.set(field, edit)\n this.failed = false\n this.publish()\n }\n\n private specOf(field: string): FieldSpec {\n const spec = this.specs.get(field)\n // Every call site names a field this card declared; a missing one is a\n // wiring mistake that must not degrade into a silently inert control.\n if (spec === undefined) throw new Error(`settings card has no field ${field}`)\n return spec\n }\n\n private snapshotOf(): SettingsScopeSnapshot<T> {\n return this.scope.getSnapshot()\n }\n\n private sectionValue(field: string): unknown {\n return (this.snapshotOf().value as Record<string, unknown> | undefined)?.[field]\n }\n\n private baseValue(field: string): unknown {\n return (this.snapshotOf().base as Record<string, unknown> | undefined)?.[field]\n }\n\n private userLayer(): Record<string, unknown> | undefined {\n return this.snapshotOf().user as Record<string, unknown> | undefined\n }\n\n private stored(field: string): boolean {\n const user = this.userLayer()\n return user !== undefined && Object.hasOwn(user, field)\n }\n\n private publish(): void {\n for (const listener of this.listeners) listener()\n }\n}\n","/**\n * The task-board settings card: whether the board announces itself in every\n * agent's system prompt. Registers into the `settings.plugin.item` slot the\n * plugin-configuration section renders, bound to the `task-board` settings\n * namespace.\n */\n\nimport type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { SettingsScope, SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'\nimport { PluginSettingsCard, BooleanField } from './PluginSettingsCard.tsx'\nimport { CardForm, booleanField, type CardActions, type CardShell, type FieldState as CardFieldState } from './settings-form.ts'\n\n/** The task-board fields this card edits (the namespace's full schema). */\nexport interface TaskBoardSettings {\n /** Master switch for the plugin. */\n enabled?: boolean\n /** Whether the board announces itself in every agent's system prompt. */\n announceToAgent?: boolean\n}\n\n/** What the task-board card renders. */\nexport interface TaskBoardSettingsCardState extends CardShell {\n /** Master switch. */\n enabled: CardFieldState\n /** System-prompt announcement flag. */\n announceToAgent: CardFieldState\n}\n\n/** The registration-side face the card's slot entry injects. */\nexport interface TaskBoardSettingsCardFace extends CardActions {\n hooks: {\n /** Card snapshot bound by the renderer as useTaskBoardSettingsCard. */\n taskBoardSettingsCard: SnapshotStore<TaskBoardSettingsCardState>\n }\n}\n\n/** Bridges the `task-board` scope onto the card's staged form. */\nexport class TaskBoardSettingsCardController {\n private readonly form: CardForm<TaskBoardSettings>\n private readonly store: SnapshotStore<TaskBoardSettingsCardState>\n\n /** @param scope - the bound settings scope for the `task-board` namespace. */\n constructor(scope: SettingsScope<TaskBoardSettings>) {\n this.form = new CardForm(scope, [\n booleanField('enabled'),\n booleanField('announceToAgent'),\n ])\n this.store = this.form.bind(() => this.projection())\n }\n\n private projection(): TaskBoardSettingsCardState {\n return {\n ...this.form.shell(),\n enabled: this.form.field('enabled'),\n announceToAgent: this.form.field('announceToAgent'),\n }\n }\n\n /**\n * Build the face the card's slot registration injects.\n * @returns the card's snapshot and its form actions.\n */\n inject(): TaskBoardSettingsCardFace {\n return { hooks: { taskBoardSettingsCard: this.store }, ...this.form.actions() }\n }\n}\n\n/** Props the renderer binds for the task-board card. */\nexport type TaskBoardSettingsCardProps =\n PropsRuntime<'web-ui.plugin.item'>\n & PropsLocale<'task-board'>\n & InjectFace<TaskBoardSettingsCardFace>\n\n/**\n * Render the task-board card.\n * @param props - locale copy, the card snapshot, and its form actions.\n * @returns the card.\n */\nexport function TaskBoardSettingsCard(props: TaskBoardSettingsCardProps) {\n const { t } = props\n const state = props.useTaskBoardSettingsCard(snapshot => snapshot)\n const disabled = !state.writable\n const fieldProps = {\n overriddenLabel: t('settings.overridden'),\n resetLabel: t('settings.reset'),\n invalidLabel: t('settings.invalidNumber'),\n disabled,\n }\n return (\n <PluginSettingsCard\n t={t}\n titleKey=\"settings.title\"\n descriptionKey=\"settings.description\"\n state={state}\n onSave={props.save}\n onDiscard={props.discard}\n >\n <BooleanField\n id=\"settings-task-board-enabled\"\n label={t('settings.enabled')}\n hint={t('settings.enabledHint')}\n inheritLabel={t('settings.inherit')}\n onLabel={t('settings.on')}\n offLabel={t('settings.off')}\n {...fieldProps}\n {...state.enabled}\n onEdit={(text) => { props.edit('enabled', text) }}\n onReset={() => { props.resetField('enabled') }}\n />\n <BooleanField\n id=\"settings-task-board-announce\"\n label={t('settings.announceToAgent')}\n hint={t('settings.announceToAgentHint')}\n inheritLabel={t('settings.inherit')}\n onLabel={t('settings.on')}\n offLabel={t('settings.off')}\n {...fieldProps}\n {...state.announceToAgent}\n onEdit={(text) => { props.edit('announceToAgent', text) }}\n onReset={() => { props.resetField('announceToAgent') }}\n />\n </PluginSettingsCard>\n )\n}\n","/**\n * Task-board client plugin: wires the framework-free core (controller,\n * execution service, store) to the real client runtime and mounts the two\n * DOM surfaces — the sidebar entry row and the board view in the center\n * column.\n *\n * Failure policy: DOM mounting problems are logged, never thrown — the web\n * shell fails the whole boot when a plugin apply throws, and an external\n * plugin must not take the GUI down.\n */\nimport type { ClientContext, SessionId, SettingsScope, SettingsScopeSpec, WorkspaceId } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-slots'\n// Type-only: pulls the locale plugin's Context merge (ctx.locale) and its\n// LocaleNamespaceMap merge table.\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\n// Type-only: pulls the settings-surface Context merge (ctx.settingsScope).\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport { BoardController } from '../core/controller.ts'\nimport { ExecutionService } from '../core/execution.ts'\nimport { SchedulerService } from '../core/scheduler.ts'\nimport { LocalStorageTaskStore } from '../core/store.ts'\nimport { mountBoard } from './board-mount.tsx'\nimport { mountSidebarEntry } from './sidebar-entry.ts'\nimport { TaskBoardSettingsCard, TaskBoardSettingsCardController, type TaskBoardSettings } from './TaskBoardSettingsCard.tsx'\nimport { en, zh, type TaskBoardKey } from './locales.ts'\n\n/** Locale namespace this plugin owns. */\nconst NS = 'task-board'\n\n/** Settings namespace the settings card edits (the Host plugin registers it). */\nconst TASK_BOARD_NS = 'task-board'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** Task-board surface copy. */\n 'task-board': TaskBoardKey\n }\n\n interface SlotMap {\n /**\n * The child slot the Web UI plugin group declares; this card registers\n * into the group instead of the top-level `settings.plugin.item` list.\n * Spelled here with the same shape so this package can register without\n * depending on the sibling UI package.\n */\n 'web-ui.plugin.item': { kind: 'list'; scope: 'root'; owner: SettingsPluginItemOwnerProps }\n }\n}\n\n/** Owner share of a plugin card (the section supplies nothing). */\nexport interface SettingsPluginItemOwnerProps {\n /** Marker field: card owner props are intentionally empty. */\n children?: never\n}\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n /**\n * Optional rc.6 compatibility binder provided by dsh-web-ui-settings;\n * absent when that group plugin is not installed, so callers fall back to\n * the official settings scope.\n */\n webUiSettings?: { bind<S>(spec: SettingsScopeSpec<S>): SettingsScope<S> }\n }\n}\n\n\n/** Required services (fiber inject waiting — the runtime must be up first). */\nexport const inject = ['slots', 'sessions', 'workspaces', 'connection', 'settingsScope', 'locale', 'remote']\n\n/**\n * Mount the task board.\n * @param ctx - client root context (services: sessions, workspaces).\n */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'task-board: dictionaries')\n\n // Plugin configuration card: one staged form over the `task-board` settings\n // namespace, contributed to the Web UI plugin group.\n const binder = ctx.get('webUiSettings') ?? ctx.settingsScope\n const settingsScope = binder.bind<TaskBoardSettings>({ namespace: TASK_BOARD_NS })\n const settingsCard = new TaskBoardSettingsCardController(settingsScope)\n ctx.slots.inject('web-ui.plugin.item', () => ctx.slots.register({\n name: 'web-ui.plugin.item',\n id: 'task-board',\n order: 110,\n locale: NS,\n inject: () => settingsCard.inject(),\n }, TaskBoardSettingsCard))\n\n // The sidebar entry and board view mount once the settings scope settles;\n // while the scope is still loading, the composition default is unknown, so\n // nothing mounts yet. Only an unavailable scope (no settings surface served)\n // falls back to the composition default (enabled).\n let uiDisposer: (() => void) | undefined\n const mountUi = (): void => {\n if (uiDisposer !== undefined) return\n const sessions = ctx.sessions\n const workspaces = ctx.workspaces\n const connection = ctx.get('connection') as ConnectionHandle\n\n // Core wiring: real runtime faces into the framework-free services.\n const store = new LocalStorageTaskStore()\n const exec = new ExecutionService({\n sessions: {\n list: sessions.list,\n binding: id => sessions.binding(id as SessionId),\n },\n workspaces: {\n list: workspaces.list,\n connectWorkspace: id => workspaces.connectWorkspace(id as WorkspaceId),\n },\n history: {\n loadTail: async sessionId => {\n const response = await connection.api.sessions.history({\n sessionId: sessionId as SessionId,\n maxMessages: 20,\n })\n return response.result.ok\n ? { events: response.result.value.events.map(entry => entry.event) }\n : undefined\n },\n },\n })\n const controller = new BoardController({\n store,\n exec,\n sessions: {\n list: sessions.list,\n open: id => sessions.open(id as SessionId),\n },\n })\n controller.start()\n\n // Scheduled runs: a browser-side heartbeat that triggers due tasks through\n // the same run path as the manual Run button. The first tick is gated on\n // the session list baseline so a page-load catch-up never fires into a\n // not-yet-ready runtime; tab visibility recovery ticks immediately.\n const scheduler = new SchedulerService({\n tasks: () => controller.getSnapshot().tasks,\n now: () => Date.now(),\n runTask: id => controller.runTask(id),\n applySchedule: (id, nextRunAt, lastTriggeredAt) =>\n controller.applyScheduleNextRun(id, nextRunAt, lastTriggeredAt),\n ready: () => sessions.list.getSnapshot().phase === 'ready',\n environment: {\n addEventListener: (type, listener) => document.addEventListener(type, listener),\n removeEventListener: (type, listener) => document.removeEventListener(type, listener),\n },\n })\n scheduler.start()\n\n const disposers: Array<() => void> = []\n try {\n disposers.push(mountSidebarEntry(controller))\n disposers.push(mountBoard(controller))\n } catch (error) {\n // DOM failures degrade the board, never the GUI.\n console.error('[dsh-task-board] mount failed:', error)\n }\n\n uiDisposer = () => {\n for (const dispose of disposers.splice(0)) dispose()\n scheduler.dispose()\n controller.dispose()\n uiDisposer = undefined\n }\n }\n const syncEnabled = (): void => {\n const snapshot = settingsScope.getSnapshot()\n const enabled = snapshot.status === 'ready'\n ? snapshot.value?.enabled ?? true\n : snapshot.status === 'unavailable'\n if (enabled) mountUi()\n else uiDisposer?.()\n }\n settingsScope.subscribe(syncEnabled)\n syncEnabled()\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["css","css","css","STATUS_KEY","css","css","css","css","css"],"sources":["../../../packages/dsh-task-board/src/core/schedule.ts","../../../packages/dsh-task-board/src/core/tasks.ts","../../../packages/dsh-task-board/src/core/controller.ts","../../../packages/dsh-task-board/src/core/execution.ts","../../../packages/dsh-task-board/src/core/scheduler.ts","../../../packages/dsh-task-board/src/core/store.ts","../../../packages/dsh-task-board/src/client/apply-guard.ts","../../../packages/dsh-task-board/src/client/locales.ts","../../../packages/dsh-task-board/src/client/board/NewTaskModal.tsx","../../../packages/dsh-task-board/src/client/board/TaskCard.tsx","../../../packages/dsh-task-board/src/client/board/ConfirmDialog.tsx","../../../packages/dsh-task-board/src/client/board/TaskDetail.tsx","../../../packages/dsh-task-board/src/client/board/TaskBoard.tsx","../../../packages/dsh-task-board/src/client/board-mount.tsx","../../../packages/dsh-task-board/src/client/sidebar-entry.ts","../../../packages/dsh-task-board/src/client/PluginSettingsCard.tsx","../../../packages/dsh-task-board/src/client/settings-form.ts","../../../packages/dsh-task-board/src/client/TaskBoardSettingsCard.tsx","../../../packages/dsh-task-board/src/client/index.ts"],"sourcesContent":["/**\n * Minimal 5-field cron parsing and next-run computation for scheduled task\n * runs. Framework-free and dependency-free so the scheduler and controller\n * share one tiny pure module.\n *\n * Grammar: five whitespace-separated fields, 分 时 日 月 周. Every field\n * supports the wildcard, step (wildcard or range + \"/n\"), single value,\n * inclusive range a-b, and comma lists mixing any of those. Ranges: minutes\n * 0-59, hours 0-23, days 1-31, months 1-12, weekdays 0-7 (0 and 7 both mean\n * Sunday). When both the day and weekday fields are restricted they combine\n * with OR semantics (standard cron). Invalid expressions parse to null and\n * are rejected by the UI/controller.\n */\n\n/** The parsed match sets of one cron expression. */\nexport interface CronSchedule {\n minutes: ReadonlySet<number>\n hours: ReadonlySet<number>\n days: ReadonlySet<number>\n months: ReadonlySet<number>\n /** Weekdays 0-6, 0 = Sunday (input 7 normalized to 0). */\n weekdays: ReadonlySet<number>\n /** Whether the day-of-month field was the literal '*' (unrestricted). */\n dayWildcard: boolean\n /** Whether the weekday field was the literal '*' (unrestricted). */\n weekdayWildcard: boolean\n}\n\n/** Inclusive ranges per field, in cron order. */\nconst FIELD_RANGES: ReadonlyArray<readonly [number, number]> = [\n [0, 59], // minutes\n [0, 23], // hours\n [1, 31], // days\n [1, 12], // months\n [0, 7], // weekdays (7 = Sunday, normalized below)\n]\n\n/**\n * Parse a 5-field cron expression.\n * @returns the match sets, or null when the expression is invalid.\n */\nexport function parseCron(expr: string): CronSchedule | null {\n const fields = expr.trim().split(/\\s+/)\n if (fields.length !== 5) return null\n const sets: Set<number>[] = []\n for (let index = 0; index < 5; index++) {\n const [min, max] = FIELD_RANGES[index]\n const set = new Set<number>()\n if (!parseField(fields[index], min, max, set)) return null\n sets.push(set)\n }\n const weekdays = new Set<number>()\n for (const day of sets[4]) weekdays.add(day === 7 ? 0 : day)\n return {\n minutes: sets[0],\n hours: sets[1],\n days: sets[2],\n months: sets[3],\n weekdays,\n // Only the literal '*' marks a field unrestricted: an explicit full\n // enumeration such as '1-31' is a restricted field and must not collapse\n // into the wildcard (it participates in day/weekday OR semantics).\n dayWildcard: fields[2] === '*',\n weekdayWildcard: fields[4] === '*',\n }\n}\n\n/** Whether the expression parses. */\nexport function isValidCron(expr: string): boolean {\n return parseCron(expr) !== null\n}\n\n/**\n * Compute the next matching instant after `fromMs` (ms epoch), in local time,\n * at minute granularity, strictly greater than `fromMs`. Returns the ms epoch\n * of the matching minute's start, or undefined when nothing matches within\n * 366 days (e.g. `0 0 30 2 *`).\n */\nexport function nextRunAtMs(expr: string, fromMs: number): number | undefined {\n const schedule = parseCron(expr)\n if (schedule === null) return undefined\n const from = new Date(fromMs)\n // Scan from the next minute; Date rolls overflow (Feb 30 → Mar 2) for free.\n const scan = new Date(from.getFullYear(), from.getMonth(), from.getDate(), from.getHours(), from.getMinutes() + 1, 0, 0)\n const limitMs = fromMs + 366 * 24 * 60 * 60 * 1000\n while (scan.getTime() <= limitMs) {\n if (matches(schedule, scan)) return scan.getTime()\n scan.setMinutes(scan.getMinutes() + 1)\n }\n return undefined\n}\n\n/** Parse one comma-list field into the match set. */\nfunction parseField(field: string, min: number, max: number, out: Set<number>): boolean {\n if (field === '*') {\n for (let value = min; value <= max; value++) out.add(value)\n return true\n }\n for (const part of field.split(',')) {\n if (part === '') return false\n const [range, stepRaw] = part.split('/')\n let low: number\n let high: number\n if (range === '*') {\n low = min\n high = max\n } else if (range.includes('-')) {\n const [a, b] = range.split('-')\n if (a === '' || b === '' || !isDigits(a) || !isDigits(b)) return false\n low = Number(a)\n high = Number(b)\n } else if (isDigits(range)) {\n low = Number(range)\n high = Number(range)\n } else {\n return false\n }\n if (low < min || high > max || low > high) return false\n const step = stepRaw === undefined ? 1 : isDigits(stepRaw) ? Number(stepRaw) : NaN\n if (!Number.isInteger(step) || step < 1) return false\n for (let value = low; value <= high; value += step) out.add(value)\n }\n return true\n}\n\n/** Day/weekday OR semantics: a restricted day field alone gates, and vice versa. */\nfunction matches(schedule: CronSchedule, date: Date): boolean {\n if (!schedule.minutes.has(date.getMinutes())) return false\n if (!schedule.hours.has(date.getHours())) return false\n if (!schedule.months.has(date.getMonth() + 1)) return false\n const dayMatches = schedule.days.has(date.getDate())\n const weekdayMatches = schedule.weekdays.has(date.getDay())\n if (schedule.dayWildcard) return weekdayMatches\n if (schedule.weekdayWildcard) return dayMatches\n return dayMatches || weekdayMatches\n}\n\nfunction isDigits(value: string): boolean {\n return /^\\d+$/.test(value)\n}\n","/**\n * Task board domain model: task lifecycle statuses, the task record shape,\n * and the pure transition functions the controller and tests share.\n * Framework-free (no cordis, no runtime imports) so the state machine is\n * unit-testable in isolation.\n */\n\n/** Task lifecycle status, one per kanban column. */\nexport type TaskStatus = 'backlog' | 'todo' | 'running' | 'done' | 'failed'\n\n/**\n * One real execution attempt: the run's own id, the dsh session that ran it\n * (filled once the session is created), and the settled outcome once the\n * session's turn ended.\n */\nexport interface ExecutionRecord {\n /** Execution attempt id (uuid). */\n id: string\n /** The dsh session that ran this attempt; absent until creation resolves. */\n sessionId: string | undefined\n /** When the run started (ms epoch). */\n startedAt: number\n /** When the run settled; absent while still running. */\n endedAt: number | undefined\n /** Outcome once settled. */\n result: 'succeeded' | 'failed' | 'cancelled' | undefined\n /** Human failure text when the run failed (prompt rejection or agent error). */\n error: string | undefined\n}\n\n/**\n * A scheduled-run rule attached to a task. The browser-side scheduler ticks\n * every minute and triggers the task when `nextRunAt` is due; the rule is\n * persisted with the task (localStorage), so scheduling survives refreshes.\n */\nexport interface ScheduleRule {\n /** Whether the schedule is armed. */\n enabled: boolean\n /** 5-field cron expression: `分 时 日 月 周`. */\n cron: string\n /** Next due instant (ms epoch); maintained by the scheduler/controller. */\n nextRunAt: number | undefined\n /** Instant of the latest scheduled trigger (ms epoch). */\n lastTriggeredAt: number | undefined\n}\n\n/** One task on the board. */\nexport interface TaskRecord {\n /** Stable task id (uuid). */\n id: string\n /** Short display title. */\n title: string\n /** Longer human description shown in the detail view. */\n description: string\n /** The prompt sent to dsh when this task is executed. */\n prompt: string\n /** Current column. */\n status: TaskStatus\n /** Creation instant (ms epoch). */\n createdAt: number\n /** Last mutation instant (ms epoch). */\n updatedAt: number\n /** Every execution attempt, most recent last. */\n executions: ExecutionRecord[]\n /** Optional scheduled-run rule (absent on tasks without a schedule). */\n schedule?: ScheduleRule\n}\n\n/** Input for creating a task. */\nexport interface NewTaskInput {\n title: string\n description: string\n prompt: string\n}\n\n/** The five kanban columns, in display order. */\nexport const COLUMNS: readonly { status: TaskStatus; label: string }[] = [\n { status: 'backlog', label: '待规划' },\n { status: 'todo', label: '待办' },\n { status: 'running', label: '进行中' },\n { status: 'done', label: '已完成' },\n { status: 'failed', label: '已失败' },\n]\n\n/** Statuses a user may move a card to manually (execution states are owned by the runner). */\nexport const MANUAL_STATUSES: readonly TaskStatus[] = ['backlog', 'todo']\n\n/** Statuses the runner may move a card to from 'running'. */\nexport const RUNNER_SETTLE_STATUSES: readonly TaskStatus[] = ['done', 'failed']\n\n/** All valid statuses (closed union guard). */\nexport const ALL_STATUSES: readonly TaskStatus[] = [\n 'backlog', 'todo', 'running', 'done', 'failed',\n]\n\n/** Brand an unknown string as a status; undefined when it is not one. */\nexport function isTaskStatus(value: unknown): value is TaskStatus {\n return typeof value === 'string' && (ALL_STATUSES as readonly string[]).includes(value)\n}\n\n/** Whether a manual move target is allowed from the given status. */\nexport function canMoveManually(_from: TaskStatus, to: TaskStatus): boolean {\n return (MANUAL_STATUSES as readonly TaskStatus[]).includes(to)\n}\n\n/** Create a task from user input. */\nexport function createTask(input: NewTaskInput, now: number, id: string): TaskRecord {\n return {\n id,\n title: input.title.trim(),\n description: input.description.trim(),\n prompt: input.prompt.trim(),\n status: 'todo',\n createdAt: now,\n updatedAt: now,\n executions: [],\n }\n}\n\n/** Clone a task with an updated status and a fresh updatedAt. */\nexport function withStatus(task: TaskRecord, status: TaskStatus, now: number): TaskRecord {\n return { ...task, status, updatedAt: now }\n}\n\n/**\n * Merge a schedule patch into a task's schedule rule (creating it when\n * absent), with a fresh updatedAt. Keys present in the patch overwrite the\n * current value — including explicit `undefined`, which clears a field (used\n * to disarm `nextRunAt`); absent keys keep their current value.\n */\nexport function withSchedule(\n task: TaskRecord,\n patch: Partial<ScheduleRule>,\n now: number,\n): TaskRecord {\n const current = task.schedule\n const schedule: ScheduleRule = {\n enabled: current?.enabled ?? false,\n cron: current?.cron ?? '',\n nextRunAt: current?.nextRunAt,\n lastTriggeredAt: current?.lastTriggeredAt,\n }\n if ('enabled' in patch) schedule.enabled = patch.enabled ?? false\n if ('cron' in patch) schedule.cron = patch.cron ?? ''\n if ('nextRunAt' in patch) schedule.nextRunAt = patch.nextRunAt\n if ('lastTriggeredAt' in patch) schedule.lastTriggeredAt = patch.lastTriggeredAt\n return { ...task, updatedAt: now, schedule }\n}\n\n/**\n * Open a fresh execution on a task: move it to 'running' and append a\n * running execution record. Returns the new task and the new execution.\n */\nexport function startExecution(\n task: TaskRecord,\n now: number,\n executionId: string,\n): { task: TaskRecord; execution: ExecutionRecord } {\n const execution: ExecutionRecord = {\n id: executionId,\n sessionId: undefined,\n startedAt: now,\n endedAt: undefined,\n result: undefined,\n error: undefined,\n }\n return {\n task: { ...task, status: 'running', updatedAt: now, executions: [...task.executions, execution] },\n execution,\n }\n}\n\n/**\n * Settle a running execution: record the outcome and move the task into the\n * matching column. No-op (returns the input task) when the execution is not\n * the task's latest or is already settled.\n */\nexport function settleExecution(\n task: TaskRecord,\n executionId: string,\n outcome: 'succeeded' | 'failed' | 'cancelled',\n now: number,\n error: string | undefined,\n): TaskRecord {\n const index = task.executions.findIndex(execution => execution.id === executionId)\n if (index === -1) return task\n const execution = task.executions[index]\n if (execution.endedAt !== undefined) return task\n const settled: ExecutionRecord = { ...execution, endedAt: now, result: outcome, error }\n const executions = [...task.executions]\n executions[index] = settled\n const status: TaskStatus = outcome === 'succeeded' ? 'done'\n : outcome === 'failed' ? 'failed'\n : task.status === 'running' ? 'todo' : task.status\n return { ...task, status, updatedAt: now, executions }\n}\n\n/** A settled-execution summary string for the detail view. */\nexport function executionLabel(execution: ExecutionRecord): string {\n if (execution.result === 'succeeded') return 'succeeded'\n if (execution.result === 'failed') return 'failed'\n if (execution.result === 'cancelled') return 'cancelled'\n return 'running'\n}\n","/**\n * Board controller: the single owner of task-ledger state and view state.\n *\n * It keeps the ledger in memory, persists every mutation through the\n * {@link TaskStore}, drives real executions through the\n * {@link ExecutionService}, and closes the board view whenever the user\n * navigates to a session (the sessions-list `current` selection changes).\n * Framework-free (structural runtime faces) so the whole orchestration is\n * unit-testable with fakes.\n */\nimport { ExecutionService, type ExecutionEvent } from './execution.ts'\nimport { isValidCron, nextRunAtMs } from './schedule.ts'\nimport type { TaskStore } from './store.ts'\nimport {\n createTask, settleExecution, startExecution, withSchedule, withStatus,\n type NewTaskInput, type TaskRecord, type TaskStatus,\n} from './tasks.ts'\n\n/** The sessions face the controller needs for navigation awareness. */\nexport interface SessionsControllerFace {\n list: {\n getSnapshot(): { current: string | undefined }\n subscribe(fn: () => void): () => void\n }\n /** Select a session as current (navigates the conversation view). */\n open(id: string): void\n}\n\n/** Controller dependencies (all swappable in tests). */\nexport interface ControllerDeps {\n store: TaskStore\n exec: ExecutionService\n sessions: SessionsControllerFace\n /** Clock; defaults to Date.now. */\n now?: () => number\n /** Id minting; defaults to a random-uuid. */\n uuid?: () => string\n /** Debounce (ms) for session-list-changed reconciles; defaults to 350. */\n reconcileDebounceMs?: number\n}\n\n/** Immutable controller snapshot for UI subscriptions. */\nexport interface ControllerSnapshot {\n tasks: readonly TaskRecord[]\n boardOpen: boolean\n selectedTaskId: string | undefined\n}\n\n/** The selected task (resolved from the ledger), or undefined. */\nexport function selectedTaskOf(snapshot: ControllerSnapshot): TaskRecord | undefined {\n if (snapshot.selectedTaskId === undefined) return undefined\n return snapshot.tasks.find(task => task.id === snapshot.selectedTaskId)\n}\n\nfunction randomUuid(): string {\n const bytes = globalThis.crypto?.getRandomValues(new Uint8Array(16))\n if (bytes === undefined) {\n // Non-secure fallback (tests, odd environments).\n return `t-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`\n }\n bytes[6] = (bytes[6] & 0x0f) | 0x40\n bytes[8] = (bytes[8] & 0x3f) | 0x80\n const hex = Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('')\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`\n}\n\n/** Read the current selection off a session-list snapshot (structural). */\nfunction currentOf(sessions: SessionsControllerFace): string | undefined {\n return sessions.list.getSnapshot().current\n}\n\n/**\n * Board controller (see module doc). All mutations bump the snapshot and\n * persist through the store; UI and DOM mounts subscribe and re-render.\n */\nexport class BoardController {\n private tasks: TaskRecord[] = []\n private boardOpen = false\n private selectedTaskId: string | undefined\n private listeners = new Set<() => void>()\n private disposers: Array<() => void> = []\n private readonly now: () => number\n private readonly uuid: () => string\n\n /** @param deps - store, execution service, and the sessions navigation face. */\n constructor(private readonly deps: ControllerDeps) {\n this.now = deps.now ?? (() => Date.now())\n this.uuid = deps.uuid ?? randomUuid\n }\n\n // --- lifecycle -------------------------------------------------------------\n\n /** Load the persisted ledger and start the navigation/status subscriptions. */\n start(): void {\n this.tasks = this.deps.store.load()\n void this.reconcileRunningTasks()\n // A sibling tab may have edited or deleted the ledger (same origin,\n // storage events). Reload on external change so a task deleted in\n // another tab stops firing here — and is never written back by this\n // tab's stale copy (scheduler roll-forward, execution settlement).\n const unsubscribeExternal = this.deps.store.subscribeExternal?.(() => {\n this.tasks = this.deps.store.load()\n this.notify()\n })\n if (unsubscribeExternal !== undefined) this.disposers.push(unsubscribeExternal)\n this.disposers.push(this.deps.sessions.list.subscribe(() => {\n this.onSessionsChanged()\n }))\n this.notify()\n }\n\n /** Stop all subscriptions and drop retained state (idempotent). */\n dispose(): void {\n for (const dispose of this.disposers.splice(0)) dispose()\n this.listeners.clear()\n if (this.reconcileTimer !== undefined) clearTimeout(this.reconcileTimer)\n this.reconcileTimer = undefined\n }\n\n // --- snapshot / subscription ------------------------------------------------\n\n getSnapshot(): ControllerSnapshot {\n return {\n tasks: this.tasks,\n boardOpen: this.boardOpen,\n selectedTaskId: this.selectedTaskId,\n }\n }\n\n subscribe(fn: () => void): () => void {\n this.listeners.add(fn)\n return () => { this.listeners.delete(fn) }\n }\n\n // --- view state -------------------------------------------------------------\n\n openBoard(): void {\n if (this.boardOpen) return\n // Baseline the selection the board opened against: the board stays open\n // until the user navigates (selection changes), never on mere status\n // updates of the already-selected session.\n this.lastCurrent = currentOf(this.deps.sessions)\n this.boardOpen = true\n this.notify()\n }\n\n closeBoard(): void {\n if (!this.boardOpen) return\n this.boardOpen = false\n this.notify()\n }\n\n toggleBoard(): void {\n if (this.boardOpen) this.closeBoard()\n else this.openBoard()\n }\n\n openTask(id: string): void {\n if (this.tasks.some(task => task.id === id)) {\n this.selectedTaskId = id\n this.notify()\n }\n }\n\n closeTask(): void {\n if (this.selectedTaskId === undefined) return\n this.selectedTaskId = undefined\n this.notify()\n }\n\n // --- task mutations ---------------------------------------------------------\n\n createTask(input: NewTaskInput): TaskRecord | undefined {\n const title = input.title.trim()\n if (title === '') return undefined\n const task = createTask(input, this.now(), this.uuid())\n this.tasks = [...this.tasks, task]\n this.persistAndNotify()\n return task\n }\n\n updateTask(id: string, patch: Partial<Pick<TaskRecord, 'title' | 'description' | 'prompt'>>): void {\n this.tasks = this.tasks.map(task => task.id === id\n ? { ...task, ...patch, updatedAt: this.now() }\n : task)\n this.persistAndNotify()\n }\n\n moveTask(id: string, status: TaskStatus): void {\n this.tasks = this.tasks.map(task => task.id === id ? withStatus(task, status, this.now()) : task)\n this.persistAndNotify()\n }\n\n deleteTask(id: string): void {\n this.tasks = this.tasks.filter(task => task.id !== id)\n if (this.selectedTaskId === id) this.selectedTaskId = undefined\n this.persistAndNotify()\n }\n\n // --- scheduling ---------------------------------------------------------------\n\n /**\n * Update a task's schedule rule. A blank or invalid cron expression is\n * rejected (returns false, state untouched). When the rule ends up enabled\n * the next run instant is computed immediately; a disabled rule carries no\n * next-run instant.\n * @param id - the task to schedule.\n * @param patch - fields to change (absent fields keep their current value).\n * @returns true when applied, false when rejected (invalid cron / unknown task).\n */\n setSchedule(id: string, patch: { enabled?: boolean; cron?: string }): boolean {\n const task = this.tasks.find(candidate => candidate.id === id)\n if (task === undefined) return false\n const current = task.schedule\n const cron = (patch.cron ?? current?.cron ?? '').trim()\n if (cron === '' || !isValidCron(cron)) return false\n const enabled = patch.enabled ?? current?.enabled ?? false\n const nextRunAt = enabled ? nextRunAtMs(cron, this.now()) : undefined\n this.tasks = this.tasks.map(candidate =>\n candidate.id === id ? withSchedule(candidate, { enabled, cron, nextRunAt }, this.now()) : candidate)\n this.persistAndNotify()\n return true\n }\n\n /**\n * Roll a task's schedule forward (scheduler callback): persist the next due\n * instant and the trigger instant of this run. No-op when the task has no\n * schedule rule (it was deleted mid-tick, for example).\n */\n applyScheduleNextRun(id: string, nextRunAt: number | undefined, lastTriggeredAt: number | undefined): void {\n this.tasks = this.tasks.map(task =>\n task.id === id && task.schedule !== undefined\n ? withSchedule(task, { nextRunAt, lastTriggeredAt }, this.now())\n : task)\n this.persistAndNotify()\n }\n\n /**\n * Jump to an execution's session transcript. Selecting the session changes\n * `current`, which closes the board (the conversation view takes over).\n * @param sessionId - the execution session to open.\n */\n openSession(sessionId: string): void {\n this.deps.sessions.open(sessionId)\n }\n\n // --- execution ---------------------------------------------------------------\n\n /**\n * Execute a task for real: move it to 'running', open an execution record,\n * and hand off to the ExecutionService. A second call while the task is\n * already running is ignored.\n */\n async runTask(id: string): Promise<boolean> {\n const task = this.tasks.find(candidate => candidate.id === id)\n if (task === undefined || task.status === 'running') return false\n const { task: next, execution } = startExecution(task, this.now(), this.uuid())\n this.tasks = this.tasks.map(candidate => candidate.id === id ? next : candidate)\n this.persistAndNotify()\n // This page owns the settlement of its own launches: the live watch\n // (ExecutionService.run) settles on the turn boundary, and list\n // reconciliation must not pre-empt it with a session that has not\n // started a turn yet (its list row is idle, not completed).\n this.activeExecutionIds.add(execution.id)\n await this.deps.exec.run(next, execution, (event) => { this.handleExecutionEvent(event) })\n return true\n }\n\n /** Re-run a settled task: move it back to 'todo' first, then execute. */\n async rerunTask(id: string): Promise<void> {\n const task = this.tasks.find(candidate => candidate.id === id)\n if (task === undefined) return\n if (task.status !== 'running') {\n this.tasks = this.tasks.map(candidate => candidate.id === id ? withStatus(candidate, 'todo', this.now()) : candidate)\n this.persistAndNotify()\n }\n await this.runTask(id)\n }\n\n private handleExecutionEvent(event: ExecutionEvent): void {\n if (event.kind === 'started') {\n this.tasks = this.tasks.map(task => task.id === event.taskId\n ? attachSessionId(task, event.executionId, event.sessionId, this.now())\n : task)\n this.persistAndNotify()\n return\n }\n this.activeExecutionIds.delete(event.executionId)\n this.tasks = this.tasks.map(task => task.id === event.taskId\n ? settleExecution(task, event.executionId, event.outcome, this.now(), event.error)\n : task)\n this.persistAndNotify()\n }\n\n // --- internals ---------------------------------------------------------------\n\n /** Reconcile running tasks and close the board when the user navigates. */\n private onSessionsChanged(): void {\n // Background/leftover executions settle through the session list (their\n // conversation snapshots stay cold until opened). Coalesce the burst of\n // list notifications into one reconcile pass instead of fanning out a\n // history read per notification; see scheduleReconcile.\n this.scheduleReconcile()\n if (!this.boardOpen) return\n const current = currentOf(this.deps.sessions)\n if (current !== this.lastCurrent) this.closeBoard()\n this.lastCurrent = current\n }\n\n private lastCurrent: string | undefined = undefined\n\n /** Execution ids launched on this page; they settle via their live watch, never list reconciliation. */\n private readonly activeExecutionIds = new Set<string>()\n\n /** Debounce timer for {@link reconcileRunningTasks}. */\n private reconcileTimer: ReturnType<typeof setTimeout> | undefined = undefined\n\n /** Whether a reconcile pass is underway (single-flight guard). */\n private reconcileInFlight = false\n\n /**\n * Debounce + single-flight trigger for the running-task reconciliation.\n * Session-list notifications arrive in bursts (one per session status\n * change); both guards together keep a burst from reading the history API\n * once per running task.\n */\n private scheduleReconcile(): void {\n if (this.reconcileTimer !== undefined) return\n this.reconcileTimer = setTimeout(() => {\n this.reconcileTimer = undefined\n void this.reconcileRunningTasks()\n }, this.deps.reconcileDebounceMs ?? 350)\n }\n\n /** Settle tasks left 'running' whose sessions already finished. */\n private async reconcileRunningTasks(): Promise<void> {\n if (this.reconcileInFlight) return\n this.reconcileInFlight = true\n try {\n type Settled = Extract<ExecutionEvent, { kind: 'settled' }>\n const events: Array<{ taskId: string; event: Settled }> = []\n for (const task of this.tasks) {\n if (task.status !== 'running') continue\n const execution = task.executions[task.executions.length - 1]\n // Runs launched on this page settle through their live watch (turn\n // boundary); reconciliation exists for background/leftover runs.\n if (execution !== undefined && this.activeExecutionIds.has(execution.id)) continue\n const event = await this.deps.exec.reconcile(task)\n if (event !== undefined && event.kind === 'settled') events.push({ taskId: task.id, event })\n }\n if (events.length === 0) return\n let changed = false\n for (const { taskId, event } of events) {\n // The reconcile call above awaited: a sibling tab may have rewritten\n // the ledger (storage event reload) meanwhile. Re-read the freshest\n // record now so the stale task captured before the await can never\n // overwrite fields the sibling wrote.\n const task = this.tasks.find(candidate => candidate.id === taskId)\n if (task === undefined) continue\n const next = settleExecution(task, event.executionId, event.outcome, this.now(), event.error)\n if (next === task) continue\n this.tasks = this.tasks.map(candidate => candidate.id === taskId ? next : candidate)\n changed = true\n }\n if (changed) this.persistAndNotify()\n } finally {\n this.reconcileInFlight = false\n }\n }\n\n private persistAndNotify(): void {\n this.deps.store.save(this.tasks)\n this.notify()\n }\n\n private notify(): void {\n for (const fn of [...this.listeners]) fn()\n }\n}\n\n/** Record which session ran an execution (once the execution service reports it). */\nfunction attachSessionId(\n task: TaskRecord,\n executionId: string,\n sessionId: string,\n now: number,\n): TaskRecord {\n return {\n ...task,\n updatedAt: now,\n executions: task.executions.map(execution =>\n execution.id === executionId ? { ...execution, sessionId } : execution),\n }\n}\n","/**\n * Execution service: runs a task through dsh's real session machinery.\n *\n * The board's \"run\" button must make dsh actually work, not fake a status:\n * the service connects a real session (workspace blank-session reuse or\n * `session.create` on the host via the workspaces service), renames it to\n * the task title, sends the task prompt with `session.prompt`, and then\n * watches the session's conversation snapshot until its turn settles. The\n * task board controller consumes {@link ExecutionEvent}s to move the card\n * running → done/failed and to keep the execution record.\n *\n * Deliberately framework-free: the runtime faces are declared structurally\n * (a narrow slice of the real `ctx.sessions` / `ctx.workspaces` contracts)\n * so tests drive it with plain fakes.\n */\nimport type { ExecutionRecord, TaskRecord } from './tasks.ts'\n\n/** The narrow sessions face the service needs. */\nexport interface SessionsExecutionFace {\n list: {\n getSnapshot(): {\n /** Baseline arrival lifecycle — 'pending' until the host list has loaded. */\n phase: 'pending' | 'ready'\n byId: Record<string, { running: boolean; completed?: boolean }>\n }\n subscribe(fn: () => void): () => void\n }\n binding(id: string): { session: SessionDriver } | undefined\n}\n\n/** The narrow workspaces face the service needs. */\nexport interface WorkspacesExecutionFace {\n list: {\n getSnapshot(): {\n items: readonly { workspaceId: string }[]\n recentWorkspaceId: string | undefined\n }\n }\n connectWorkspace(workspaceId: string): Promise<string>\n}\n\n/** One raw session-history event narrowed to the failure signal reconcile needs. */\nexport interface ExecutionHistoryEvent {\n type: string\n data?: unknown\n}\n\n/** Optional raw-history face used to detect failures of never-opened sessions. */\nexport interface HistoryExecutionFace {\n loadTail(sessionId: string): Promise<{ events: readonly ExecutionHistoryEvent[] } | undefined>\n}\n\n/** The behavior verbs the service invokes on an execution session. */\nexport interface SessionDriver {\n rename(title: string): Promise<unknown>\n prompt(\n content: readonly unknown[],\n mode: 'queue',\n ): Promise<{ ok: true } | { ok: false; error: unknown }>\n getSnapshot(): { running: boolean; lastAgentError: string | null; turnEnds: ReadonlyMap<number, number> }\n subscribe(fn: () => void): () => void\n}\n\n/** Everything the service needs from the runtime. */\nexport interface ExecutionEnvironment {\n sessions: SessionsExecutionFace\n workspaces: WorkspacesExecutionFace\n /** Raw-history reader for failure detection of never-opened sessions. */\n history?: HistoryExecutionFace\n}\n\n/** Outcome events the service emits to the controller. */\nexport type ExecutionEvent =\n | { kind: 'started'; taskId: string; executionId: string; sessionId: string }\n | { kind: 'settled'; taskId: string; executionId: string; outcome: 'succeeded' | 'failed' | 'cancelled'; error?: string }\n\n/** Human copy for a run failure. */\nfunction messageOf(error: unknown): string {\n if (error instanceof Error) return error.message\n return String(error)\n}\n\n/** Whether a `turn/end` payload closed the turn with an error reason. */\nfunction isErrorTurnEnd(data: unknown): boolean {\n if (typeof data !== 'object' || data === null) return false\n const reason = (data as { reason?: unknown }).reason\n return typeof reason === 'object' && reason !== null\n && (reason as { kind?: unknown }).kind === 'error'\n}\n\n/**\n * Run one task to completion (or to a settled failure).\n *\n * @param task - the task being executed.\n * @param execution - the freshly opened execution record (id + start time).\n * @param onEvent - callback for started/settled events.\n * @returns resolves when the run settles (or fails to start); never rejects —\n * every failure path is reported as a settled event.\n */\nexport class ExecutionService {\n /** @param env - the runtime faces (real or fake). */\n constructor(private readonly env: ExecutionEnvironment) {}\n\n async run(\n task: TaskRecord,\n execution: ExecutionRecord,\n onEvent: (event: ExecutionEvent) => void,\n ): Promise<void> {\n try {\n const sessionId = await this.connectSession()\n onEvent({ kind: 'started', taskId: task.id, executionId: execution.id, sessionId })\n const driver = this.driverOf(sessionId)\n if (driver === undefined) {\n onEvent({ kind: 'settled', taskId: task.id, executionId: execution.id, outcome: 'failed', error: 'execution session is not ready' })\n return\n }\n // Best-effort rename so the execution is recognizable in the session list.\n await driver.rename(task.title).catch(() => { /* rename is cosmetic */ })\n // Baseline the turn counter BEFORE the prompt round-trip: a turn that\n // completes while prompt is in flight must still advance past this\n // baseline, or the watch below would never observe it settle.\n const baseline = driver.getSnapshot().turnEnds.size\n const accepted = await this.sendPrompt(driver, task)\n if (!accepted.ok) {\n onEvent({\n kind: 'settled', taskId: task.id, executionId: execution.id, outcome: 'failed',\n error: messageOf(accepted.error),\n })\n return\n }\n this.watchForSettlement(driver, task.id, execution.id, onEvent, baseline)\n } catch (error) {\n onEvent({\n kind: 'settled', taskId: task.id, executionId: execution.id, outcome: 'failed',\n error: messageOf(error),\n })\n }\n }\n\n /**\n * Inspect a reloaded/background task that was left 'running' and emit a\n * settled event when its session already finished.\n *\n * A session that was never opened keeps a cold conversation snapshot (the\n * runtime only maintains the window for the staged/current session), so the\n * settled outcome is decided by the strongest available signal, in order:\n * 1. the list summary — missing session → cancelled; still running → pending;\n * 2. a warm conversation snapshot → `lastAgentError` decides failed/succeeded;\n * 3. the raw history tail (when a history face is wired) — a `turn/end`\n * error reason proves failure;\n * 4. otherwise a finished session counts as succeeded.\n *\n * @param task - a task whose latest execution has no endedAt.\n * @returns a settled event when the session state proves completion, else undefined.\n */\n async reconcile(task: TaskRecord): Promise<ExecutionEvent | undefined> {\n const execution = task.executions[task.executions.length - 1]\n if (execution === undefined || execution.sessionId === undefined || execution.endedAt !== undefined) return undefined\n const list = this.env.sessions.list.getSnapshot()\n // The host list baseline has not arrived yet (page load): a session \"not\n // found\" now would be a false cancel. Wait for a later list change.\n if (list.phase !== 'ready') return undefined\n const summary = list.byId[execution.sessionId]\n if (summary === undefined) {\n return { kind: 'settled', taskId: task.id, executionId: execution.id, outcome: 'cancelled', error: 'execution session no longer exists' }\n }\n if (summary.running) return undefined\n const driver = this.driverOf(execution.sessionId)\n if (driver !== undefined) {\n const snapshot = driver.getSnapshot()\n if (snapshot.turnEnds.size > 0) {\n const outcome = snapshot.lastAgentError !== null ? 'failed' : 'succeeded'\n return {\n kind: 'settled', taskId: task.id, executionId: execution.id, outcome,\n error: snapshot.lastAgentError ?? undefined,\n }\n }\n }\n const failed = await this.historyShowsFailure(execution.sessionId)\n if (failed) {\n return { kind: 'settled', taskId: task.id, executionId: execution.id, outcome: 'failed', error: 'agent turn failed' }\n }\n return { kind: 'settled', taskId: task.id, executionId: execution.id, outcome: 'succeeded' }\n }\n\n /** Best-effort failure probe over the raw history tail (false when unavailable). */\n private async historyShowsFailure(sessionId: string): Promise<boolean> {\n const history = this.env.history\n if (history === undefined) return false\n try {\n const tail = await history.loadTail(sessionId)\n if (tail === undefined) return false\n return tail.events.some(event => event.type === 'turn/end' && isErrorTurnEnd(event.data))\n } catch (error) {\n // A failed history read must not block settlement; fall back to success.\n console.error('[dsh-task-board] history failure probe failed', error)\n return false\n }\n }\n\n private async connectSession(): Promise<string> {\n const workspace = this.env.workspaces.list.getSnapshot()\n const workspaceId = workspace.recentWorkspaceId ?? workspace.items[0]?.workspaceId\n if (workspaceId === undefined) {\n throw new Error('no workspace available to run the task in')\n }\n return this.env.workspaces.connectWorkspace(workspaceId)\n }\n\n private driverOf(sessionId: string): SessionDriver | undefined {\n return this.env.sessions.binding(sessionId)?.session\n }\n\n private async sendPrompt(\n driver: SessionDriver,\n task: TaskRecord,\n ): Promise<{ ok: true } | { ok: false; error: unknown }> {\n const text = task.prompt.trim() !== '' ? task.prompt : task.title\n try {\n const result = await driver.prompt([{ type: 'text', text }], 'queue')\n return result\n } catch (error) {\n return { ok: false, error }\n }\n }\n\n /**\n * Subscribe to the execution session and settle the run once the accepted\n * turn completes (turn counter advanced past the acceptance baseline and\n * the session is no longer running). Never settles while the session is\n * still running; unsubscribes on settle.\n */\n private watchForSettlement(\n driver: SessionDriver,\n taskId: string,\n executionId: string,\n onEvent: (event: ExecutionEvent) => void,\n baseline: number,\n ): void {\n let settled = false\n let unsubscribe: () => void = () => {}\n const check = (): void => {\n if (settled) return\n const snapshot = driver.getSnapshot()\n if (snapshot.running || snapshot.turnEnds.size <= baseline) return\n settled = true\n unsubscribe()\n onEvent({\n kind: 'settled', taskId, executionId,\n outcome: snapshot.lastAgentError !== null ? 'failed' : 'succeeded',\n error: snapshot.lastAgentError ?? undefined,\n })\n }\n unsubscribe = driver.subscribe(check)\n // A turn can complete during the prompt round-trip (before subscribe):\n // re-check immediately so a fast turn is never missed.\n check()\n }\n}\n","/**\n * Browser-side scheduler: the heartbeat behind scheduled task runs.\n *\n * The board is a pure client plugin with no server channel, so \"定时任务\"\n * lives in the tab: a timer ticks every minute (plus immediately on tab\n * visibility recovery) and triggers any task whose `schedule.nextRunAt` is\n * due, rolling the schedule forward to the next cron match before triggering\n * so the same tick never double-fires. Missed runs are skipped, never queued:\n * a task still running at its due instant is skipped by the controller's\n * runTask guard and simply waits for the next cron match.\n *\n * Framework-free: all runtime access flows through the injected deps\n * (structural faces), so tests drive ticks directly without timers.\n */\nimport { nextRunAtMs } from './schedule.ts'\nimport type { TaskRecord } from './tasks.ts'\n\n/** Everything the scheduler needs from its host (the board controller). */\nexport interface SchedulerDeps {\n /** Read the current task ledger (the controller snapshot). */\n tasks(): readonly TaskRecord[]\n /** Clock; defaults to Date.now in the controller wiring. */\n now(): number\n /** Trigger one task's real execution (the controller's runTask); resolves\n * true when the run was accepted, false when rejected (e.g. the task is\n * already running). */\n runTask(id: string): Promise<boolean>\n /** Persist a rolled-forward schedule (next due + this trigger instant). */\n applySchedule(id: string, nextRunAt: number | undefined, lastTriggeredAt: number | undefined): void\n /** Tick cadence; defaults to 60_000 ms. */\n tickMs?: number\n /**\n * Gate: while false the tick no-ops (e.g. the session list baseline has not\n * arrived on page load, so executions would fail). Defaults to always ready.\n */\n ready?: () => boolean\n /** Environment listeners for tab-visibility recovery (browser only). */\n environment?: {\n addEventListener(type: 'visibilitychange', listener: () => void): void\n removeEventListener(type: 'visibilitychange', listener: () => void): void\n }\n}\n\n/**\n * The schedule heartbeat (see module doc). `tick` is public so tests and\n * callers can drive a check without waiting for the interval.\n */\nexport class SchedulerService {\n private timer: ReturnType<typeof setInterval> | undefined\n private disposed = false\n\n /** @param deps - tasks/clock/trigger/apply faces (see {@link SchedulerDeps}). */\n constructor(private readonly deps: SchedulerDeps) {}\n\n /** Start ticking: one immediate check (catch-up after reload) + the interval. */\n start(): void {\n if (this.disposed) return\n // Immediate catch-up tick: schedules whose due instant passed while the\n // tab was closed are triggered as soon as the runtime is ready.\n this.tick()\n this.timer = setInterval(() => { this.tick() }, this.deps.tickMs ?? 60_000)\n this.deps.environment?.addEventListener('visibilitychange', this.onVisibility)\n }\n\n /** Stop ticking and drop listeners (idempotent). */\n dispose(): void {\n this.disposed = true\n if (this.timer !== undefined) clearInterval(this.timer)\n this.timer = undefined\n this.deps.environment?.removeEventListener('visibilitychange', this.onVisibility)\n }\n\n /**\n * Check every enabled schedule and trigger the due ones. Idempotent per\n * task per tick: the schedule is rolled forward only after runTask accepts\n * the run, so a rejected run keeps its due slot and is retried on the next\n * tick instead of being silently dropped.\n */\n async tick(): Promise<void> {\n if (this.disposed) return\n if (this.deps.ready !== undefined && !this.deps.ready()) return\n const now = this.deps.now()\n for (const task of this.deps.tasks()) {\n const schedule = task.schedule\n if (schedule === undefined || !schedule.enabled) continue\n if (schedule.nextRunAt === undefined) {\n // Missing next-run instant (repaired/legacy data): recompute from the\n // cron expression and wait; an unparseable expression is skipped.\n const repaired = nextRunAtMs(schedule.cron, now)\n if (repaired === undefined) continue\n this.deps.applySchedule(task.id, repaired, undefined)\n continue\n }\n if (schedule.nextRunAt > now) continue\n // Advance from the due instant (not this tick's wall-clock) and only\n // after the run is accepted: a rejected run keeps its due slot.\n const next = nextRunAtMs(schedule.cron, schedule.nextRunAt)\n const accepted = await this.deps.runTask(task.id)\n if (accepted) this.deps.applySchedule(task.id, next, now)\n }\n }\n\n private readonly onVisibility = (): void => { this.tick() }\n}\n","/**\n * Task persistence: a small storage seam with a localStorage backend.\n *\n * The task-board client plugin runs in the browser, and dsh exposes no\n * browser-writable file channel (same conclusion the skin-center research\n * reached for cordis.patch.yml), so tasks persist in the browser's\n * localStorage under a versioned key — the same persistence mechanism dsh's\n * own client snapshot stores use (`createSnapshotStore` persist). Data\n * survives page refreshes and dsh restarts (same origin), and survives\n * plugin uninstall (the key is simply left in place).\n *\n * The seam keeps the backend swappable (e.g. an IndexedDB or a host-file\n * channel later); tests run against the in-memory backend and a jsdom\n * localStorage backend.\n */\nimport { isValidCron } from './schedule.ts'\nimport type { ScheduleRule, TaskRecord, TaskStatus } from './tasks.ts'\nimport { isTaskStatus } from './tasks.ts'\n\n/** Persistence seam for the task ledger. */\nexport interface TaskStore {\n /** Read the persisted ledger (empty when nothing is stored yet). */\n load(): TaskRecord[]\n /** Persist the whole ledger (replaces the stored document). */\n save(tasks: readonly TaskRecord[]): void\n /** Drop the persisted ledger (leaves the in-memory state alone). */\n clear(): void\n /**\n * Subscribe to ledger changes written by ANOTHER tab of the same origin\n * (browser storage events). The board controller reloads the ledger on\n * such a change, so a task deleted in one tab cannot keep firing (or be\n * written back) from the stale in-memory copy of another tab. No-op when\n * the backend has no cross-instance channel (in-memory store).\n */\n subscribeExternal?(listener: () => void): () => void\n}\n\n/** Storage key for the task ledger document. */\nexport const DEFAULT_STORAGE_KEY = 'dsh.taskBoard.v1'\n\n/** Structural shape of the storage event fired in sibling tabs (DOM-free). */\nexport interface StorageChangeEvent {\n key: string | null\n}\n\n/** The event-target face the store needs for cross-tab notifications. */\nexport interface StorageEvents {\n addEventListener(type: 'storage', listener: (event: StorageChangeEvent) => void): void\n removeEventListener(type: 'storage', listener: (event: StorageChangeEvent) => void): void\n}\n\n/**\n * Structural row check with the status left unvalidated (see {@link parseLedger}).\n * The `schedule` field is deliberately NOT checked here: a malformed schedule\n * never drops the task row — {@link normalizeSchedule} repairs or drops the\n * schedule alone.\n */\nfunction isTaskRecordShape(value: unknown): value is Omit<TaskRecord, 'status'> & { status: unknown } {\n if (typeof value !== 'object' || value === null) return false\n const record = value as Record<string, unknown>\n if (typeof record.id !== 'string' || record.id === '') return false\n if (typeof record.title !== 'string') return false\n if (typeof record.description !== 'string') return false\n if (typeof record.prompt !== 'string') return false\n if (typeof record.createdAt !== 'number') return false\n if (typeof record.updatedAt !== 'number') return false\n if (!Array.isArray(record.executions)) return false\n for (const execution of record.executions) {\n if (typeof execution !== 'object' || execution === null) return false\n const entry = execution as Record<string, unknown>\n if (typeof entry.id !== 'string') return false\n if (entry.sessionId !== undefined && typeof entry.sessionId !== 'string') return false\n if (typeof entry.startedAt !== 'number') return false\n if (entry.endedAt !== undefined && typeof entry.endedAt !== 'number') return false\n if (entry.result !== undefined && entry.result !== 'succeeded' && entry.result !== 'failed' && entry.result !== 'cancelled') return false\n if (entry.error !== undefined && typeof entry.error !== 'string') return false\n }\n return true\n}\n\n/** A task record is structurally valid if it round-trips through the UI. */\nexport function isTaskRecord(value: unknown): value is TaskRecord {\n return isTaskRecordShape(value) && isTaskStatus(value.status)\n}\n\n/** Normalize an unknown persisted status back into the closed status union. */\nfunction normalizeStatus(status: unknown): TaskStatus {\n return isTaskStatus(status) ? status : 'todo'\n}\n\n/**\n * Repair a persisted schedule rule: drop rules without a usable cron string,\n * coerce booleans/numbers, and leave `nextRunAt`/`lastTriggeredAt` undefined\n * when missing (a fresh recompute or the next tick fixes them).\n */\nfunction normalizeSchedule(schedule: unknown): ScheduleRule | undefined {\n if (typeof schedule !== 'object' || schedule === null) return undefined\n const rule = schedule as Record<string, unknown>\n // Reject (drop) a schedule whose cron is not a well-formed 5-field\n // expression: a malformed rule would otherwise linger as a never-firing\n // schedule instead of being dropped for later repair.\n if (typeof rule.cron !== 'string') return undefined\n if (rule.cron.trim() === '' || !isValidCron(rule.cron)) return undefined\n return {\n enabled: rule.enabled === true,\n cron: rule.cron,\n nextRunAt: typeof rule.nextRunAt === 'number' ? rule.nextRunAt : undefined,\n lastTriggeredAt: typeof rule.lastTriggeredAt === 'number' ? rule.lastTriggeredAt : undefined,\n }\n}\n\n/** Parse + validate a persisted ledger document; invalid rows are dropped. */\nexport function parseLedger(raw: string | null): TaskRecord[] {\n if (raw === null) return []\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch (error) {\n console.error('[dsh-task-board] persisted task ledger is not valid JSON; starting empty', error)\n return []\n }\n if (!Array.isArray(parsed)) {\n console.error('[dsh-task-board] persisted task ledger is not an array; starting empty')\n return []\n }\n const tasks: TaskRecord[] = []\n for (const row of parsed) {\n // Status is normalized (an unknown status from a future version lands in\n // todo instead of dropping the row); the schedule is repaired field by\n // field; every other field must be valid.\n if (!isTaskRecordShape(row)) {\n console.warn('[dsh-task-board] dropping invalid task row from persisted ledger', row)\n continue\n }\n // Always (re)assign the schedule: a repair that returns undefined must\n // clear a malformed persisted rule rather than leave it in the row.\n const task: TaskRecord = { ...row, status: normalizeStatus(row.status) }\n task.schedule = normalizeSchedule(row.schedule)\n tasks.push(task)\n }\n return tasks\n}\n\n/** localStorage-backed store (the browser backend). */\nexport class LocalStorageTaskStore implements TaskStore {\n /**\n * @param key - storage key for the ledger document.\n * @param storage - storage backend (defaults to the global localStorage; tests inject fakes).\n * @param events - storage-event target for cross-tab notifications (defaults\n * to the browser global; undefined in non-browser runtimes, where the\n * subscription becomes a no-op).\n */\n constructor(\n private readonly key: string = DEFAULT_STORAGE_KEY,\n private readonly storage: Pick<Storage, 'getItem' | 'setItem' | 'removeItem'> | undefined = globalThis.localStorage,\n private readonly events: StorageEvents | undefined = typeof (globalThis as { addEventListener?: unknown }).addEventListener === 'function'\n ? (globalThis as unknown as StorageEvents)\n : undefined,\n ) {}\n\n load(): TaskRecord[] {\n if (this.storage === undefined) return []\n try {\n return parseLedger(this.storage.getItem(this.key))\n } catch (error) {\n // Storage read failures (private mode, quota) degrade to an empty ledger,\n // never break the board.\n console.error('[dsh-task-board] task ledger read failed; starting empty', error)\n return []\n }\n }\n\n save(tasks: readonly TaskRecord[]): void {\n if (this.storage === undefined) return\n try {\n this.storage.setItem(this.key, JSON.stringify(tasks))\n } catch (error) {\n // Write failures only skip persistence; in-memory state stays live.\n console.error('[dsh-task-board] task ledger write failed (persistence skipped)', error)\n }\n }\n\n clear(): void {\n if (this.storage === undefined) return\n try {\n this.storage.removeItem(this.key)\n } catch (error) {\n console.error('[dsh-task-board] task ledger clear failed', error)\n }\n }\n\n /**\n * Cross-tab change subscription (see {@link TaskStore.subscribeExternal}).\n * The browser fires the storage event in every OTHER tab of the same origin\n * when one tab writes; a null key means the whole storage was cleared. Both\n * cases reload the ledger here; unrelated keys are ignored.\n */\n subscribeExternal(listener: () => void): () => void {\n if (this.events === undefined) return () => {}\n const onStorage = (event: StorageChangeEvent): void => {\n if (event.key !== null && event.key !== this.key) return\n listener()\n }\n this.events.addEventListener('storage', onStorage)\n return () => { this.events?.removeEventListener('storage', onStorage) }\n }\n}\n\n/** In-memory backend (tests, and a fallback when storage is unavailable). */\nexport class InMemoryTaskStore implements TaskStore {\n private ledger: TaskRecord[] = []\n\n load(): TaskRecord[] {\n return this.ledger.map(task => ({ ...task, executions: [...task.executions] }))\n }\n\n save(tasks: readonly TaskRecord[]): void {\n this.ledger = tasks.map(task => ({ ...task, executions: [...task.executions] }))\n }\n\n clear(): void {\n this.ledger = []\n }\n}\n","/**\n * Cross-module-instance apply guard for the task-board client bundle.\n *\n * The client factory can run more than once in a single page lifetime (for\n * example when a stale bundle is mixed with a rebuilt one while `dsh web` is\n * restarted). Without a guard, every factory run mounts its own sidebar entry\n * and board view, so the shell ends up showing two \"task board\" rows.\n *\n * The flag lives on globalThis so separate module instances (independent\n * factory runs) still share one guard. First claim wins; later claims become\n * no-ops until the claim is released (fiber unload / hot-reload) or the page\n * reloads.\n */\n\ndeclare global {\n // eslint-disable-next-line no-var\n var __dshTaskboardApplied: boolean | undefined\n}\n\n/** Claims the plugin apply slot. Returns true when this call won the slot. */\nexport function claimTaskboardApply(): boolean {\n if (globalThis.__dshTaskboardApplied === true) return false\n globalThis.__dshTaskboardApplied = true\n return true\n}\n\n/**\n * Releases the claim. Called from the client fiber cleanup so that a\n * hot-reloaded bundle (the loader unloads the old plugin fiber and invokes\n * the rebuilt one in the same page) can claim again instead of being\n * silently dropped.\n */\nexport function releaseTaskboardApply(): void {\n globalThis.__dshTaskboardApplied = undefined\n}\n","/**\n * Task-board copy: zh-first dictionaries with an English fallback, selected\n * by the document language. Kept dependency-free (no dsh locale service) so\n * the DOM-injected entry row and the standalone board tree share one tiny\n * lookup.\n */\n\n/** zh dictionary (key-set source of truth). */\nexport const zh = {\n 'entry.label': '任务看板',\n 'board.title': '任务看板',\n 'board.close': '返回对话',\n 'board.new': '新建任务',\n 'board.search': '筛选任务…',\n 'board.empty': '这个状态还没有任务',\n 'board.filterAll': '全部',\n 'board.status': '状态',\n 'board.status.backlog': '待规划',\n 'board.status.todo': '待办',\n 'board.status.running': '进行中',\n 'board.status.done': '已完成',\n 'board.status.failed': '已失败',\n 'board.runs': '次执行',\n 'board.updated': '更新于',\n 'board.created': '创建于',\n 'new.title': '标题',\n 'new.titlePlaceholder': '一句话描述要做什么',\n 'new.description': '描述',\n 'new.descriptionPlaceholder': '补充背景、范围与验收(可选)',\n 'new.prompt': '执行 Prompt',\n 'new.promptPlaceholder': '发给 agent 的完整指令(留空则使用标题)',\n 'new.submit': '创建',\n 'new.cancel': '取消',\n 'new.required': '标题不能为空',\n 'detail.title': '任务详情',\n 'detail.close': '关闭',\n 'detail.prompt': '执行 Prompt',\n 'detail.description': '描述',\n 'detail.execution': '执行记录',\n 'detail.noExecution': '尚未执行',\n 'detail.run': '执行',\n 'detail.rerun': '重新执行',\n 'detail.delete': '删除',\n 'detail.viewSession': '查看会话',\n 'detail.noSession': '暂无会话',\n 'detail.executionStarted': '已启动',\n 'detail.executionEnded': '已结束',\n 'detail.result.succeeded': '成功',\n 'detail.result.failed': '失败',\n 'detail.result.cancelled': '已取消',\n 'detail.result.running': '进行中',\n 'delete.title': '删除任务',\n 'delete.confirm': '确定删除「{name}」吗?删除后不可恢复。',\n 'delete.ok': '删除',\n 'delete.cancel': '取消',\n 'status.move.backlog': '移到待规划',\n 'status.move.todo': '移到待办',\n 'exec.error.noWorkspace': '没有可用工作区,无法执行任务',\n 'exec.error.promptRejected': 'Prompt 被拒绝',\n 'run.failed': '执行失败:{error}',\n 'time.justNow': '刚刚',\n 'detail.schedule': '定时运行',\n 'detail.schedule.enable': '启用定时执行',\n 'detail.schedule.cron': 'Cron 表达式',\n 'detail.schedule.presets': '预设',\n 'detail.schedule.preset.daily9': '每天 09:00',\n 'detail.schedule.preset.hourly': '每小时',\n 'detail.schedule.preset.tenMin': '每 10 分钟',\n 'detail.schedule.preset.weeklyMon9': '每周一 09:00',\n 'detail.schedule.nextRun': '下次运行',\n 'detail.schedule.lastTriggered': '上次触发',\n 'detail.schedule.invalid': 'Cron 表达式无效',\n 'detail.schedule.notScheduled': '尚未排程',\n 'detail.schedule.dueSoon': '即将运行',\n 'card.scheduled': '定时',\n // 插件设置卡片(settings.plugin.item 席位)。\n 'settings.title': '任务看板',\n 'settings.description': '控制看板在 agent 系统提示中的播报行为。',\n 'settings.enabled': '启用任务看板',\n 'settings.enabledHint': '关闭后隐藏侧边栏入口与看板视图。',\n 'settings.announceToAgent': '向 agent 播报任务看板',\n 'settings.announceToAgentHint': '开启:每条 agent 系统提示都会包含本看板的说明;关闭:不播报,agent 仅在用户主动提及时了解看板。',\n 'settings.inherit': '继承',\n 'settings.on': '开',\n 'settings.off': '关',\n 'settings.overridden': '已覆盖',\n 'settings.reset': '恢复默认',\n 'settings.notExposed': '当前 DSH 版本未向设置页暴露本插件的配置命名空间,表单不可用。可编辑 ~/.dsh/settings.yaml 直接配置,或为 dsh-host-apiproxy 的 WEB_SETTINGS_NAMESPACES 白名单补充本命名空间后重启。',\n 'settings.readOnly': '当前部署的设置只读。',\n 'settings.expand': '展开设置',\n 'settings.collapse': '收起设置',\n 'settings.save': '保存',\n 'settings.saving': '保存中…',\n 'settings.discard': '放弃',\n 'settings.unsaved': '未保存',\n 'settings.saveFailed': '部署未接受这些值,已保留供你修改。',\n 'settings.invalidNumber': '请输入数字,留空则使用默认值。',\n} satisfies Record<string, string>\n\n/** en dictionary, complete against the zh key set. */\nexport const en: Record<keyof typeof zh, string> = {\n 'entry.label': 'Task Board',\n 'board.title': 'Task Board',\n 'board.close': 'Back to chat',\n 'board.new': 'New Task',\n 'board.search': 'Filter tasks…',\n 'board.empty': 'No tasks in this column',\n 'board.filterAll': 'All',\n 'board.status': 'Status',\n 'board.status.backlog': 'Backlog',\n 'board.status.todo': 'To Do',\n 'board.status.running': 'In Progress',\n 'board.status.done': 'Done',\n 'board.status.failed': 'Failed',\n 'board.runs': 'runs',\n 'board.updated': 'Updated',\n 'board.created': 'Created',\n 'new.title': 'Title',\n 'new.titlePlaceholder': 'What should be done, in one line',\n 'new.description': 'Description',\n 'new.descriptionPlaceholder': 'Background, scope, acceptance criteria (optional)',\n 'new.prompt': 'Run Prompt',\n 'new.promptPlaceholder': 'The full instruction sent to the agent (title is used when blank)',\n 'new.submit': 'Create',\n 'new.cancel': 'Cancel',\n 'new.required': 'Title is required',\n 'detail.title': 'Task Detail',\n 'detail.close': 'Close',\n 'detail.prompt': 'Run Prompt',\n 'detail.description': 'Description',\n 'detail.execution': 'Execution History',\n 'detail.noExecution': 'Not executed yet',\n 'detail.run': 'Run',\n 'detail.rerun': 'Run Again',\n 'detail.delete': 'Delete',\n 'detail.viewSession': 'View Session',\n 'detail.noSession': 'No session',\n 'detail.executionStarted': 'Started',\n 'detail.executionEnded': 'Ended',\n 'detail.result.succeeded': 'Succeeded',\n 'detail.result.failed': 'Failed',\n 'detail.result.cancelled': 'Cancelled',\n 'detail.result.running': 'Running',\n 'delete.title': 'Delete Task',\n 'delete.confirm': 'Delete \"{name}\"? This cannot be undone.',\n 'delete.ok': 'Delete',\n 'delete.cancel': 'Cancel',\n 'status.move.backlog': 'Move to Backlog',\n 'status.move.todo': 'Move to To Do',\n 'exec.error.noWorkspace': 'No workspace is available to run the task',\n 'exec.error.promptRejected': 'Prompt rejected',\n 'run.failed': 'Run failed: {error}',\n 'time.justNow': 'just now',\n 'detail.schedule': 'Scheduled Runs',\n 'detail.schedule.enable': 'Enable scheduled runs',\n 'detail.schedule.cron': 'Cron expression',\n 'detail.schedule.presets': 'Presets',\n 'detail.schedule.preset.daily9': 'Every day 09:00',\n 'detail.schedule.preset.hourly': 'Every hour',\n 'detail.schedule.preset.tenMin': 'Every 10 minutes',\n 'detail.schedule.preset.weeklyMon9': 'Every Monday 09:00',\n 'detail.schedule.nextRun': 'Next run',\n 'detail.schedule.lastTriggered': 'Last triggered',\n 'detail.schedule.invalid': 'Invalid cron expression',\n 'detail.schedule.notScheduled': 'Not scheduled yet',\n 'detail.schedule.dueSoon': 'Due soon',\n 'card.scheduled': 'scheduled',\n // Plugin settings card (the `settings.plugin.item` seat).\n 'settings.title': 'Task Board',\n 'settings.description': 'How the board announces itself in each agent system prompt.',\n 'settings.enabled': 'Enable the task board',\n 'settings.enabledHint': 'When off, the sidebar entry and board view are hidden.',\n 'settings.announceToAgent': 'Announce the task board to agents',\n 'settings.announceToAgentHint': 'On: every agent system prompt includes a note about this board. Off: no announcement; agents learn about the board only when you mention it.',\n 'settings.inherit': 'Inherit',\n 'settings.on': 'On',\n 'settings.off': 'Off',\n 'settings.overridden': 'Overridden',\n 'settings.reset': 'Reset to default',\n 'settings.notExposed': 'This DSH version does not expose this plugin\\'s settings namespace to the configuration page, so the form is unavailable. Edit ~/.dsh/settings.yaml directly, or add the namespace to dsh-host-apiproxy\\'s WEB_SETTINGS_NAMESPACES allowlist and restart.',\n 'settings.readOnly': 'This deployment stores settings read-only.',\n 'settings.expand': 'Show settings',\n 'settings.collapse': 'Hide settings',\n 'settings.save': 'Save',\n 'settings.saving': 'Saving…',\n 'settings.discard': 'Discard',\n 'settings.unsaved': 'Unsaved',\n 'settings.saveFailed': 'The deployment did not accept these values; they were left for you to correct.',\n 'settings.invalidNumber': 'Enter a number, or leave blank to use the default.',\n}\n\n/** The dictionary key union. */\nexport type TaskBoardKey = keyof typeof zh\n\n/** The settings-card slice of the task-board dictionary. */\nexport type SettingsCardKey = TaskBoardKey\n\n/** Active dictionary, picked by the document language at call time. */\nexport function dictionary(): Record<TaskBoardKey, string> {\n const lang = typeof document !== 'undefined' ? document.documentElement.lang : 'zh'\n return lang.toLowerCase().startsWith('en') ? en : zh\n}\n\n/** Translate a key with optional {name} template params. */\nexport function t(key: TaskBoardKey, params?: Record<string, string>): string {\n let text: string = dictionary()[key]\n if (params !== undefined) {\n for (const [name, value] of Object.entries(params)) {\n text = text.replaceAll(`{${name}}`, value)\n }\n }\n return text\n}\n","/**\n * New-task modal: title + description + the prompt that execution will send.\n * Creates through the controller (which persists immediately).\n */\nimport { useState } from 'react'\nimport type { BoardController } from '../../core/controller.ts'\nimport { t } from '../locales.ts'\nimport css from '../board.module.css'\n\n/** New-task form overlay. */\nexport function NewTaskModal({ controller, onClose }: { controller: BoardController; onClose: () => void }) {\n const [title, setTitle] = useState('')\n const [description, setDescription] = useState('')\n const [prompt, setPrompt] = useState('')\n const [error, setError] = useState<string | undefined>(undefined)\n\n const submit = (): void => {\n const task = controller.createTask({ title, description, prompt })\n if (task === undefined) {\n setError(t('new.required'))\n return\n }\n onClose()\n }\n\n return (\n <div className={css.modalBackdrop} onMouseDown={event => { if (event.target === event.currentTarget) onClose() }}>\n <form\n className={css.modal}\n role=\"dialog\"\n aria-label={t('board.new')}\n onSubmit={event => { event.preventDefault(); submit() }}\n >\n <h2 className={css.modalTitle}>{t('board.new')}</h2>\n\n <label className={css.field}>\n <span className={css.fieldLabel}>{t('new.title')}</span>\n <input\n className={css.input}\n value={title}\n autoFocus\n placeholder={t('new.titlePlaceholder')}\n onChange={event => { setTitle(event.target.value); setError(undefined) }}\n />\n </label>\n\n <label className={css.field}>\n <span className={css.fieldLabel}>{t('new.description')}</span>\n <textarea\n className={css.input}\n rows={3}\n value={description}\n placeholder={t('new.descriptionPlaceholder')}\n onChange={event => { setDescription(event.target.value) }}\n />\n </label>\n\n <label className={css.field}>\n <span className={css.fieldLabel}>{t('new.prompt')}</span>\n <textarea\n className={css.input}\n rows={4}\n value={prompt}\n placeholder={t('new.promptPlaceholder')}\n onChange={event => { setPrompt(event.target.value) }}\n />\n </label>\n\n {error !== undefined && <p className={css.formError}>{error}</p>}\n\n <footer className={css.modalFooter}>\n <button type=\"button\" className={css.ghostButton} onClick={onClose}>\n {t('new.cancel')}\n </button>\n <button type=\"submit\" className={css.primaryButton}>\n {t('new.submit')}\n </button>\n </footer>\n </form>\n </div>\n )\n}\n","/**\n * Task card: the board's column item. Clicking opens the task detail — it\n * never executes anything directly (detail holds the Run button).\n */\nimport type { TaskRecord } from '../../core/tasks.ts'\nimport { executionLabel } from '../../core/tasks.ts'\nimport { t } from '../locales.ts'\nimport css from '../board.module.css'\n\n/** Compact relative/absolute time label. */\nexport function formatTime(ms: number): string {\n const date = new Date(ms)\n const now = Date.now()\n const minutes = Math.floor((now - ms) / 60000)\n if (minutes < 1) return t('time.justNow')\n if (minutes < 60) return `${minutes}m`\n if (minutes < 60 * 24) return `${Math.floor(minutes / 60)}h`\n return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`\n}\n\n/** One card in a column. */\nexport function TaskCard({ task, onClick }: { task: TaskRecord; onClick: () => void }) {\n const latest = task.executions[task.executions.length - 1]\n const runs = task.executions.length\n return (\n <button\n type=\"button\"\n className={css.card}\n data-status={task.status}\n onClick={onClick}\n title={task.description !== '' ? task.description : task.title}\n >\n <span className={css.cardTitle}>{task.title}</span>\n {task.description !== '' && <span className={css.cardExcerpt}>{task.description}</span>}\n <span className={css.cardMeta}>\n <span className={css.cardTime}>{t('board.updated')} {formatTime(task.updatedAt)}</span>\n {task.schedule?.enabled === true && (\n <span\n className={css.cardSchedule}\n title={task.schedule.nextRunAt !== undefined\n ? `${t('card.scheduled')} · ${new Date(task.schedule.nextRunAt).toLocaleString()}`\n : t('card.scheduled')}\n >\n {t('card.scheduled')}\n </span>\n )}\n {latest !== undefined && (\n <span className={css.cardRun} data-result={latest.result}>\n {runs} {t('board.runs')}\n </span>\n )}\n {latest?.sessionId !== undefined && (\n <span className={css.cardSession} title={latest.sessionId}>⌁</span>\n )}\n {task.status === 'running' && <span className={css.cardSpinner} aria-hidden=\"true\" />}\n </span>\n {latest !== undefined && executionLabel(latest) === 'running' && (\n <span className={css.cardRunningLabel}>{t('detail.result.running')}…</span>\n )}\n </button>\n )\n}\n","/**\n * Generic confirm dialog used by destructive actions (task delete).\n */\nimport { t } from '../locales.ts'\nimport css from '../board.module.css'\n\n/** Confirm overlay props. */\nexport interface ConfirmDialogProps {\n title: string\n message: string\n confirmLabel: string\n /** Render the confirm button in the danger style. */\n danger?: boolean\n onCancel: () => void\n onConfirm: () => void\n}\n\n/** Small confirm overlay. */\nexport function ConfirmDialog({ title, message, confirmLabel, danger, onCancel, onConfirm }: ConfirmDialogProps) {\n return (\n <div className={css.modalBackdrop} onMouseDown={event => { if (event.target === event.currentTarget) onCancel() }}>\n <div className={css.modal} role=\"alertdialog\" aria-label={title}>\n <h2 className={css.modalTitle}>{title}</h2>\n <p className={css.confirmMessage}>{message}</p>\n <footer className={css.modalFooter}>\n <button type=\"button\" className={css.ghostButton} onClick={onCancel}>\n {t('delete.cancel')}\n </button>\n <button\n type=\"button\"\n className={danger ? css.dangerButton : css.primaryButton}\n onClick={onConfirm}\n >\n {confirmLabel}\n </button>\n </footer>\n </div>\n </div>\n )\n}\n","/**\n * Task detail: the full view of one task — content, prompt, execution\n * history — and the only place execution can be triggered. Also offers\n * delete (with confirmation), manual status moves, and a jump to the\n * execution's session transcript.\n */\nimport { useEffect, useState } from 'react'\nimport type { BoardController } from '../../core/controller.ts'\nimport { isValidCron } from '../../core/schedule.ts'\nimport { MANUAL_STATUSES, type ExecutionRecord, type TaskRecord, type TaskStatus } from '../../core/tasks.ts'\nimport { t, type TaskBoardKey } from '../locales.ts'\nimport css from '../board.module.css'\nimport { ConfirmDialog } from './ConfirmDialog.tsx'\nimport { formatTime } from './TaskCard.tsx'\n\n/** Execution outcome → locale key. */\nconst RESULT_KEY: Record<NonNullable<ExecutionRecord['result']>, TaskBoardKey> = {\n succeeded: 'detail.result.succeeded',\n failed: 'detail.result.failed',\n cancelled: 'detail.result.cancelled',\n}\n\n/** Status → locale key (detail badge). */\nconst STATUS_KEY: Record<TaskStatus, TaskBoardKey> = {\n backlog: 'board.status.backlog',\n todo: 'board.status.todo',\n running: 'board.status.running',\n done: 'board.status.done',\n failed: 'board.status.failed',\n}\n\n/** One execution-history row. */\nfunction ExecutionRow({ execution, onOpen }: { execution: ExecutionRecord; onOpen: (sessionId: string) => void }) {\n const result = execution.result\n return (\n <li className={css.executionRow} data-result={result}>\n <span className={css.executionBadge} data-result={result}>\n {result === undefined ? t('detail.result.running') : t(RESULT_KEY[result])}\n </span>\n <span className={css.executionTimes}>\n {t('detail.executionStarted')} {formatTime(execution.startedAt)}\n {execution.endedAt !== undefined && ` · ${t('detail.executionEnded')} ${formatTime(execution.endedAt)}`}\n </span>\n {execution.sessionId !== undefined && (\n <button\n type=\"button\"\n className={css.linkButton}\n onClick={() => { onOpen(execution.sessionId as string) }}\n title={execution.sessionId}\n >\n {t('detail.viewSession')} ⌁\n </button>\n )}\n {execution.error !== undefined && execution.error !== '' && (\n <span className={css.executionError}>{execution.error}</span>\n )}\n </li>\n )\n}\n\n/** Common scheduled-run presets (cron → locale label). */\nconst SCHEDULE_PRESETS: ReadonlyArray<{ cron: string; label: TaskBoardKey }> = [\n { cron: '0 9 * * *', label: 'detail.schedule.preset.daily9' },\n { cron: '0 * * * *', label: 'detail.schedule.preset.hourly' },\n { cron: '*/10 * * * *', label: 'detail.schedule.preset.tenMin' },\n { cron: '0 9 * * 1', label: 'detail.schedule.preset.weeklyMon9' },\n]\n\n/** The scheduled-runs editor: enable toggle, cron input + presets, next-run info. */\nfunction ScheduleSection({ controller, task }: { controller: BoardController; task: TaskRecord }) {\n const schedule = task.schedule\n const [cron, setCron] = useState(schedule?.cron ?? '0 9 * * *')\n const [enabled, setEnabled] = useState(schedule?.enabled ?? false)\n const [nextRunAt, setNextRunAt] = useState<number | undefined>(schedule?.nextRunAt)\n const [lastTriggeredAt, setLastTriggeredAt] = useState<number | undefined>(schedule?.lastTriggeredAt)\n const [error, setError] = useState<string | undefined>(undefined)\n\n // Keep the editor in sync when the task record changes underneath (the\n // schedule rolls forward as runs trigger).\n useEffect(() => {\n setCron(schedule?.cron ?? '0 9 * * *')\n setEnabled(schedule?.enabled ?? false)\n setNextRunAt(schedule?.nextRunAt)\n setLastTriggeredAt(schedule?.lastTriggeredAt)\n setError(undefined)\n }, [task.id, schedule?.enabled, schedule?.cron, schedule?.nextRunAt, schedule?.lastTriggeredAt])\n\n /** Validate + persist the current cron text (Enter or blur). */\n const saveCron = (value: string): void => {\n const trimmed = value.trim()\n setCron(trimmed)\n if (trimmed === '' || !isValidCron(trimmed)) {\n setError(t('detail.schedule.invalid'))\n return\n }\n setError(undefined)\n controller.setSchedule(task.id, { cron: trimmed })\n }\n\n /** Arm/disarm the schedule (arming first persists the edited cron). */\n const toggleEnabled = (next: boolean): void => {\n const trimmed = cron.trim()\n if (next && (trimmed === '' || !isValidCron(trimmed))) {\n setError(t('detail.schedule.invalid'))\n return\n }\n setError(undefined)\n if (next && trimmed !== schedule?.cron) controller.setSchedule(task.id, { cron: trimmed })\n if (controller.setSchedule(task.id, { enabled: next })) setEnabled(next)\n }\n\n const applyPreset = (preset: string): void => {\n if (preset === '') return\n setCron(preset)\n setError(undefined)\n controller.setSchedule(task.id, { cron: preset })\n }\n\n const nextLabel = !enabled || nextRunAt === undefined\n ? t('detail.schedule.notScheduled')\n : nextRunAt <= Date.now()\n ? t('detail.schedule.dueSoon')\n : new Date(nextRunAt).toLocaleString()\n const lastLabel = lastTriggeredAt === undefined ? '—' : new Date(lastTriggeredAt).toLocaleString()\n\n return (\n <section className={css.detailSection}>\n <h4>{t('detail.schedule')}</h4>\n <label className={css.scheduleToggle}>\n <input\n type=\"checkbox\"\n checked={enabled}\n onChange={event => { toggleEnabled(event.target.checked) }}\n />\n <span>{t('detail.schedule.enable')}</span>\n </label>\n <div className={css.scheduleRow}>\n <input\n className={`${css.input} ${css.scheduleInput}${error !== undefined ? ` ${css.scheduleInputInvalid}` : ''}`}\n value={cron}\n placeholder=\"0 9 * * *\"\n spellCheck={false}\n aria-label={t('detail.schedule.cron')}\n onChange={event => { setCron(event.target.value); setError(undefined) }}\n onBlur={() => { saveCron(cron) }}\n onKeyDown={event => { if (event.key === 'Enter') saveCron(cron) }}\n />\n <select\n className={css.schedulePreset}\n value=\"\"\n aria-label={t('detail.schedule.presets')}\n onChange={event => { applyPreset(event.target.value) }}\n >\n <option value=\"\">{t('detail.schedule.presets')}…</option>\n {SCHEDULE_PRESETS.map(preset => (\n <option key={preset.cron} value={preset.cron}>{t(preset.label)}</option>\n ))}\n </select>\n </div>\n {error !== undefined && <p className={css.formError}>{error}</p>}\n <p className={css.scheduleMeta}>\n {t('detail.schedule.nextRun')} {nextLabel}\n {' · '}{t('detail.schedule.lastTriggered')} {lastLabel}\n </p>\n </section>\n )\n}\n\n/** Task detail overlay. */\nexport function TaskDetail({ controller, task }: { controller: BoardController; task: TaskRecord }) {\n const [confirmDelete, setConfirmDelete] = useState(false)\n const running = task.status === 'running'\n\n // Keep the overlay in sync if the task record changes underneath.\n const [latest, setLatest] = useState(task)\n useEffect(() => { setLatest(task) }, [task])\n const current = latest\n\n return (\n <div className={css.modalBackdrop} onMouseDown={event => { if (event.target === event.currentTarget) controller.closeTask() }}>\n <div className={css.detail} role=\"dialog\" aria-label={t('detail.title')}>\n <header className={css.detailHeader}>\n <h2 className={css.detailTitle}>{current.title}</h2>\n <span className={css.statusBadge} data-status={current.status}>{t(STATUS_KEY[current.status])}</span>\n <button\n type=\"button\"\n className={css.iconButton}\n aria-label={t('detail.close')}\n onClick={() => { controller.closeTask() }}\n >\n ×\n </button>\n </header>\n\n <div className={css.detailBody}>\n <section className={css.detailSection}>\n <h4>{t('detail.description')}</h4>\n <p className={css.detailText}>{current.description !== '' ? current.description : '—'}</p>\n </section>\n\n <section className={css.detailSection}>\n <h4>{t('detail.prompt')}</h4>\n <pre className={css.promptBlock}>{current.prompt !== '' ? current.prompt : current.title}</pre>\n </section>\n\n <ScheduleSection controller={controller} task={current} />\n\n <section className={css.detailSection}>\n <h4>{t('detail.execution')}</h4>\n {current.executions.length === 0 ? (\n <p className={css.detailText}>{t('detail.noExecution')}</p>\n ) : (\n <ul className={css.executionList}>\n {[...current.executions].reverse().map(execution => (\n <ExecutionRow\n key={execution.id}\n execution={execution}\n onOpen={sessionId => { controller.openSession(sessionId) }}\n />\n ))}\n </ul>\n )}\n </section>\n\n <section className={css.detailSection}>\n <h4>{t('board.status')}</h4>\n <div className={css.moveRow}>\n {MANUAL_STATUSES.map(status => (\n <button\n key={status}\n type=\"button\"\n className={css.ghostButton}\n disabled={current.status === status || running}\n onClick={() => { controller.moveTask(current.id, status) }}\n >\n {t(`status.move.${status}` as TaskBoardKey)}\n </button>\n ))}\n </div>\n </section>\n </div>\n\n <footer className={css.detailFooter}>\n <button\n type=\"button\"\n className={css.primaryButton}\n disabled={running}\n onClick={() => {\n // Running kicks off a real agent session; close the detail so\n // the whole board stays visible while the task executes.\n controller.closeTask()\n void controller.rerunTask(current.id)\n }}\n >\n {current.executions.length === 0 ? t('detail.run') : t('detail.rerun')}\n </button>\n <button\n type=\"button\"\n className={css.dangerButton}\n onClick={() => { setConfirmDelete(true) }}\n >\n {t('detail.delete')}\n </button>\n <span className={css.detailMeta}>\n {t('board.created')} {formatTime(current.createdAt)}\n </span>\n </footer>\n </div>\n\n {confirmDelete && (\n <ConfirmDialog\n title={t('delete.title')}\n message={t('delete.confirm', { name: current.title })}\n confirmLabel={t('delete.ok')}\n danger\n onCancel={() => { setConfirmDelete(false) }}\n onConfirm={() => {\n setConfirmDelete(false)\n controller.deleteTask(current.id)\n controller.closeTask()\n }}\n />\n )}\n </div>\n )\n}\n","/**\n * Board view: the multi-column kanban that replaces the middle column while\n * active. Cards open the task detail (never execute directly); the header\n * offers filter, new-task, and a back-to-chat escape.\n */\nimport { useEffect, useState } from 'react'\nimport { selectedTaskOf, type BoardController } from '../../core/controller.ts'\nimport { COLUMNS, type TaskRecord, type TaskStatus } from '../../core/tasks.ts'\nimport { t, type TaskBoardKey } from '../locales.ts'\nimport css from '../board.module.css'\nimport { NewTaskModal } from './NewTaskModal.tsx'\nimport { TaskCard } from './TaskCard.tsx'\nimport { TaskDetail } from './TaskDetail.tsx'\n\n/** Column status → locale key. */\nconst STATUS_KEY: Record<TaskStatus, TaskBoardKey> = {\n backlog: 'board.status.backlog',\n todo: 'board.status.todo',\n running: 'board.status.running',\n done: 'board.status.done',\n failed: 'board.status.failed',\n}\n\n/** Case-insensitive title/description match. */\nfunction matchesFilter(task: TaskRecord, filter: string): boolean {\n if (filter.trim() === '') return true\n const needle = filter.trim().toLowerCase()\n return task.title.toLowerCase().includes(needle) || task.description.toLowerCase().includes(needle)\n}\n\n/** Board component; subscribes to the controller snapshot. */\nexport function TaskBoard({ controller }: { controller: BoardController }) {\n const [snapshot, setSnapshot] = useState(controller.getSnapshot())\n useEffect(\n () => controller.subscribe(() => setSnapshot(controller.getSnapshot())),\n [controller],\n )\n const [filter, setFilter] = useState('')\n const [showNew, setShowNew] = useState(false)\n const selected = selectedTaskOf(snapshot)\n const visible = snapshot.tasks.filter(task => matchesFilter(task, filter))\n\n return (\n <div className={css.board} data-dsh-taskboard-board=\"\">\n <header className={css.boardHeader}>\n <h2 className={css.boardTitle}>{t('board.title')}</h2>\n <input\n className={css.search}\n type=\"search\"\n placeholder={t('board.search')}\n value={filter}\n onChange={event => { setFilter(event.target.value) }}\n aria-label={t('board.search')}\n />\n <button\n type=\"button\"\n className={css.primaryButton}\n onClick={() => { setShowNew(true) }}\n >\n + {t('board.new')}\n </button>\n <button\n type=\"button\"\n className={css.ghostButton}\n onClick={() => { controller.closeBoard() }}\n >\n {t('board.close')}\n </button>\n </header>\n\n <div className={css.columns}>\n {COLUMNS.map(column => {\n const tasks = visible.filter(task => task.status === column.status)\n return (\n <section key={column.status} className={css.column} data-status={column.status}>\n <header className={css.columnHeader}>\n <span className={css.statusDot} data-status={column.status} aria-hidden=\"true\" />\n <h3 className={css.columnTitle}>{t(STATUS_KEY[column.status])}</h3>\n <span className={css.columnCount}>{tasks.length}</span>\n </header>\n <div className={css.cards}>\n {tasks.map(task => (\n <TaskCard\n key={task.id}\n task={task}\n onClick={() => { controller.openTask(task.id) }}\n />\n ))}\n {tasks.length === 0 && <div className={css.columnEmpty}>{t('board.empty')}</div>}\n </div>\n </section>\n )\n })}\n </div>\n\n {selected !== undefined && (\n <TaskDetail controller={controller} task={selected} />\n )}\n {showNew && (\n <NewTaskModal\n controller={controller}\n onClose={() => { setShowNew(false) }}\n />\n )}\n </div>\n )\n}\n","/**\n * Board view mounting.\n *\n * The `conversation` slot is single-occupant (ui-conversation) and external\n * plugins cannot declare slots, so the board takes over the center column at\n * the DOM level: a container is appended inside the center column\n * (`[class*=\"centerCol\"]`, the dsh 0.1.0-rc.6 AppFrame layout; previously\n * `[data-pane=\"conversation\"]` on older shells — the mount selector keeps both)\n * as an extra trailing child\n * React never manages, and a stylesheet\n * rule hides the conversation content while the board is active. Toggling is\n * a data attribute on <html> — no React involvement, so the conversation\n * subtree underneath stays mounted and stateful.\n */\nimport { createRoot, type Root } from 'react-dom/client'\nimport type { BoardController } from '../core/controller.ts'\nimport { TaskBoard } from './board/TaskBoard.tsx'\nimport css from './board.module.css'\n\n/** The injected board container (kept in the DOM, hidden when inactive). */\nexport const BOARD_VIEW_SELECTOR = '[data-dsh-taskboard-view]'\n\nconst CONVERSATION_COLUMN_SELECTOR = '[data-pane=\"conversation\"], [class*=\"centerCol\"]'\nconst ACTIVE_ATTR = 'data-dsh-taskboard-active'\n/** The sibling panel's activation attribute (ssh), removed when this panel opens. */\nconst OTHER_ACTIVE_ATTR = 'data-dsh-ssh-active'\n/** Cross-plugin activation event; detail is the activating panel name. */\nconst ACTIVATE_EVENT = 'dsh-panel-activate'\nconst PANEL_NAME = 'taskboard'\n\n/** Find the center column, or undefined while the frame is not mounted. */\nfunction conversationColumn(): HTMLElement | undefined {\n return document.querySelector<HTMLElement>(CONVERSATION_COLUMN_SELECTOR) ?? undefined\n}\n\n/**\n * Mount the board React tree into the center column and bind its visibility\n * to the controller's boardOpen state.\n * @param controller - the board controller driving the view.\n * @returns disposer unmounting the tree and restoring the column.\n */\nexport function mountBoard(controller: BoardController): () => void {\n let root: Root | undefined\n let container: HTMLDivElement | undefined\n\n const ensure = (): void => {\n if (container !== undefined) return\n const column = conversationColumn()\n if (column === undefined) return\n container = document.createElement('div')\n container.dataset.dshTaskboardView = ''\n container.className = css.boardView\n column.appendChild(container)\n root = createRoot(container)\n root.render(<TaskBoard controller={controller} />)\n }\n\n // The frame mounts after boot settlement; watch for the column's arrival.\n const waitObserver = new MutationObserver(() => { ensure() })\n waitObserver.observe(document.body, { childList: true, subtree: true })\n\n const applyActive = (): void => {\n if (controller.getSnapshot().boardOpen) {\n // Single-occupant center column: opening this panel must evict the\n // sibling panel (ssh), both its html attribute and its controller\n // state, otherwise the two panels' visibility rules fight and the\n // second click appears dead.\n document.documentElement.removeAttribute(OTHER_ACTIVE_ATTR)\n document.documentElement.setAttribute(ACTIVE_ATTR, '')\n document.dispatchEvent(new CustomEvent(ACTIVATE_EVENT, { detail: PANEL_NAME }))\n } else {\n document.documentElement.removeAttribute(ACTIVE_ATTR)\n }\n }\n const onOtherActivate = (event: Event): void => {\n if ((event as CustomEvent).detail === 'ssh' && controller.getSnapshot().boardOpen) {\n controller.closeBoard()\n }\n }\n // Jump out on sidebar context clicks: clicking a session/workspace row\n // (including the already-current one, which produces no session-change\n // event) hands the center column back to the conversation. Capture phase,\n // so the panel closes before the shell processes the click.\n const SIDEBAR_ROW_SELECTOR = '[class*=\"sessionRow\"], [class*=\"projectRow\"], [class*=\"searchResultRow\"], [class*=\"searchResultWorkspace\"], [class*=\"newSession\"]'\n const onClickSidebarRow = (event: MouseEvent): void => {\n if (!controller.getSnapshot().boardOpen) return\n const target = event.target as HTMLElement | null\n if (target === null) return\n if (target.closest(SIDEBAR_ROW_SELECTOR) !== null) controller.closeBoard()\n }\n document.addEventListener('click', onClickSidebarRow, true)\n document.addEventListener(ACTIVATE_EVENT, onOtherActivate)\n const unsubscribe = controller.subscribe(applyActive)\n applyActive()\n ensure()\n\n return () => {\n document.removeEventListener('click', onClickSidebarRow, true)\n document.removeEventListener(ACTIVATE_EVENT, onOtherActivate)\n waitObserver.disconnect()\n unsubscribe()\n document.documentElement.removeAttribute(ACTIVE_ATTR)\n root?.unmount()\n root = undefined\n container?.remove()\n container = undefined\n }\n}\n","/**\n * Sidebar entry injection.\n *\n * dsh's sidebar shell exposes no slot an external plugin can register into\n * (`sidebar.workspaces` / `sidebar.settings` are single-occupant and already\n * taken), so — following the skin precedent of DOM-level extension — the\n * entry row is injected between the shell's New Session button and the\n * workspace browser. The injection self-heals: a MutationObserver watches the\n * sidebar root and re-inserts the row whenever a React re-render displaces it\n * (re-insertion happens in the same frame, before paint, so no flicker).\n *\n * The row is plain DOM (no React tree) so it can never disturb the shell's\n * reconciliation; the board view it toggles is a separate React root mounted\n * in the center column (see board-mount.ts).\n */\nimport type { BoardController } from '../core/controller.ts'\nimport { t } from './locales.ts'\nimport css from './board.module.css'\n\n/** Stable data attribute identifying the injected entry row. */\nexport const ENTRY_SELECTOR = '[data-dsh-taskboard-entry]'\n\n/** Inline icon (matches the shell's 16px nav-icon look). */\nconst ICON = `<svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"2\" y=\"2.5\" width=\"12\" height=\"11\" rx=\"1.5\"/><path d=\"M2 6.5h12M6.5 6.5v7\"/></svg>`\n\n/** Find the sidebar shell root element, or undefined while not yet mounted. */\nfunction sidebarRoot(): HTMLElement | undefined {\n const column = document.querySelector<HTMLElement>('[data-pane=\"sidebar\"], [class*=\"sidebarCol\"]')\n if (column === null) return undefined\n // Current shells wrap the sidebar UI: column > wrapper > root(logoRow owner).\n // Prefer the element that owns the logo row — the real sidebar UI root —\n // and fall back to the column's first child for legacy shells.\n const logoOwner = column.querySelector<HTMLElement>('[class*=\"logoRow\"]')?.parentElement\n return logoOwner ?? (column.firstElementChild as HTMLElement | undefined)\n}\n\n/** The New Session button: nested in the logo row on current shells, a direct child on legacy shells. */\nfunction newSessionButton(root: HTMLElement): HTMLButtonElement | undefined {\n const nested = root.querySelector<HTMLButtonElement>('button[class*=\"newSession\"]')\n if (nested !== null) return nested\n for (const child of root.children) {\n if (child.tagName === 'BUTTON') return child as HTMLButtonElement\n }\n return undefined\n}\n\n/** Build the entry row (a detached button; insert once the shell is up). */\nfunction createEntry(controller: BoardController): HTMLButtonElement {\n const entry = document.createElement('button')\n entry.type = 'button'\n entry.dataset.dshTaskboardEntry = ''\n entry.className = css.entry\n entry.setAttribute('aria-label', t('entry.label'))\n entry.innerHTML = `<span class=\"${css.entryIcon}\">${ICON}</span><span class=\"${css.entryLabel}\">${t('entry.label')}</span>`\n entry.addEventListener('click', () => { controller.toggleBoard() })\n return entry\n}\n\n/** Re-insert the entry after the New Session row (before the browser region). */\nfunction placeEntry(root: HTMLElement, entry: HTMLButtonElement): boolean {\n const button = newSessionButton(root)\n if (button === undefined) return false\n if (entry.parentElement !== root) {\n // Position relative to the family block (entries injected by sibling\n // plugins), never relative to transient logoRow geometry: every family\n // plugin that self-heals during a re-render then lands in the same\n // relative order, so the entries cannot swap positions regardless of\n // observer callback order or of shell wrapper changes. There is no\n // append-to-end fallback: appending at the end would randomly reorder\n // the block after a shell re-render.\n const row = button.closest('[class*=\"logoRow\"]')\n const base = (row !== null && row.parentElement === root) ? row : button\n const family = Array.from(root.children).filter(\n (el): el is HTMLElement => el instanceof HTMLElement && el.matches('[data-dsh-taskboard-entry], [data-dsh-ssh-entry]'),\n )\n // task board sits before the whole family block.\n const anchor = family.length > 0 ? family[0] : base.nextElementSibling\n root.insertBefore(entry, anchor)\n }\n return true\n}\n\n/**\n * Mount the sidebar entry, waiting for the shell to render and self-healing\n * on later React re-renders.\n * @param controller - the board controller the entry toggles.\n * @returns disposer removing the entry and its observers.\n */\nexport function mountSidebarEntry(controller: BoardController): () => void {\n // DOM-level idempotency: whatever path mounted an entry row before this\n // call (a duplicated apply, an HMR re-injection, a stale module still\n // alive), never mount a second one. The existing row keeps working; a full\n // page reload is the ultimate reset.\n if (typeof document !== 'undefined' && document.querySelector('[data-dsh-taskboard-entry]') !== null) {\n return () => {}\n }\n const entry = createEntry(controller)\n let root: HTMLElement | undefined\n let placed = false\n\n const tryPlace = (): void => {\n if (root !== undefined && !root.isConnected) {\n // The shell rebuilt the sidebar pane (whole-tree teardown); the root\n // observer is gone with the old tree, so detach it and re-query from\n // scratch. The new pane is later noticed by the body-level watcher.\n rootObserver.disconnect()\n root = undefined\n placed = false\n }\n if (placed) {\n // Cheap short-circuit: entry still lives in a mountable subtree.\n if (document.body.contains(entry)) return\n // Entry was torn down together with the old tree; reset and re-place.\n rootObserver.disconnect()\n root = undefined\n placed = false\n }\n root ??= sidebarRoot()\n if (root === undefined) return\n placed = placeEntry(root, entry)\n if (placed) {\n rootObserver.observe(root, { childList: true, subtree: true })\n }\n }\n\n // Body-level watcher retained as the \"whole rebuild\" fallback: when the shell\n // tears down the whole sidebar pane, the root observer is gone with it and\n // only this body observation can notice the new pane mounting. It is no\n // longer disconnected after placement; the placed-and-still-mounted case\n // short-circuits through the cheap document.body.contains(entry) check, so\n // unrelated app mutations (e.g. chat streaming) cost one contains check\n // instead of churning the full re-query.\n const waitObserver = new MutationObserver(() => { tryPlace() })\n waitObserver.observe(document.body, { childList: true, subtree: true })\n\n // Self-heal: if a React re-render displaces the row, re-insert it in the\n // same frame (microtask before paint → no visible flicker).\n const rootObserver = new MutationObserver(() => {\n if (root === undefined || !root.isConnected) {\n placed = false\n tryPlace()\n return\n }\n if (!root.contains(entry)) {\n placed = placeEntry(root, entry)\n }\n })\n\n // Reflect the board's open state on the row (active highlight). Note: assigning\n // undefined to dataset.active materializes data-active=\"undefined\" and keeps the\n // row permanently highlighted — delete the attribute instead.\n const syncActive = () => {\n if (controller.getSnapshot().boardOpen) entry.dataset.active = 'true'\n else delete entry.dataset.active\n }\n const unsubscribe = controller.subscribe(syncActive)\n syncActive()\n\n tryPlace()\n\n return () => {\n waitObserver.disconnect()\n rootObserver.disconnect()\n unsubscribe()\n entry.remove()\n }\n}\n","/**\n * Shared chrome for the plugin settings card: a disclosure header naming the\n * plugin and what its settings govern, the controls inside, and the save that\n * writes them. Renders nothing while the namespace is unavailable — a\n * deployment that does not compose the owning plugin should show no trace of\n * it. Mirrors the official ui-plugin-config PluginCard in a self-contained\n * slice (this package must not depend on a sibling UI package).\n */\n\nimport { useState, type ReactNode } from 'react'\nimport type { CardShell } from './settings-form.ts'\nimport type { SettingsCardKey } from './locales.ts'\nimport css from './settings-card.module.css'\n\n/** Card chrome shared by every plugin settings card. */\nexport interface PluginSettingsCardProps {\n /** Locale reader for this card's copy. */\n t: (key: SettingsCardKey) => string\n /** Locale key of the plugin's name. */\n titleKey: SettingsCardKey\n /** Locale key of the line describing what this plugin's settings govern. */\n descriptionKey: SettingsCardKey\n /** The card's form state: availability, writability, and what a save would do. */\n state: CardShell\n /** Write every staged edit. */\n onSave: () => void\n /** Drop every staged edit. */\n onDiscard: () => void\n /** The plugin's controls. */\n children: ReactNode\n}\n\n/**\n * Render one plugin settings card.\n * @param props - the plugin's copy keys, its form state, and its controls.\n * @returns the card, or nothing while the namespace is still loading.\n */\nexport function PluginSettingsCard(props: PluginSettingsCardProps) {\n const [open, setOpen] = useState(false)\n const { state } = props\n if (!state.available) return null\n const title = props.t(props.titleKey)\n const blocked = !state.dirty || state.invalid || state.saving\n const cardClass = open ? `${css.cardOpen} ${css.card}` : css.card\n // The namespace exists but the Host does not serve it to this client (the\n // official settings allowlist omits third-party namespaces): show a card\n // that explains the gap instead of vanishing, so a missing card never\n // reads as a missing plugin.\n if (!state.exposed) {\n return (\n <li className={cardClass}>\n <button\n type=\"button\"\n className={css.header}\n aria-expanded={open}\n aria-label={`${props.t(open ? 'settings.collapse' : 'settings.expand')}: ${title}`}\n onClick={() => { setOpen(!open) }}\n >\n <span className={css.headText}>\n <span className={css.name}>{title}</span>\n <span className={css.description}>{props.t(props.descriptionKey)}</span>\n </span>\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 14 14\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n className={open ? `${css.chevron} ${css.chevronOpen}` : css.chevron}\n >\n <path\n d=\"M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z\"\n fill=\"currentColor\"\n />\n </svg>\n </button>\n {open\n ? (\n <div className={css.body}>\n <p className={css.notExposed} role=\"status\">{props.t('settings.notExposed')}</p>\n </div>\n )\n : null}\n </li>\n )\n }\n return (\n <li className={cardClass}>\n <button\n type=\"button\"\n className={css.header}\n aria-expanded={open}\n aria-label={`${props.t(open ? 'settings.collapse' : 'settings.expand')}: ${title}`}\n onClick={() => { setOpen(!open) }}\n >\n <span className={css.headText}>\n <span className={css.name}>{title}</span>\n <span className={css.description}>{props.t(props.descriptionKey)}</span>\n </span>\n {state.dirty ? <span className={css.pending}>{props.t('settings.unsaved')}</span> : null}\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 14 14\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n className={open ? `${css.chevron} ${css.chevronOpen}` : css.chevron}\n >\n <path\n d=\"M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z\"\n fill=\"currentColor\"\n />\n </svg>\n </button>\n {open\n ? (\n <div className={css.body}>\n {!state.writable ? <p className={css.readOnly} role=\"status\">{props.t('settings.readOnly')}</p> : null}\n {props.children}\n <div className={css.footer}>\n {state.failed ? <p className={css.failed} role=\"status\">{props.t('settings.saveFailed')}</p> : null}\n <button\n type=\"button\"\n className={css.discard}\n disabled={!state.dirty || state.saving}\n onClick={props.onDiscard}\n >\n {props.t('settings.discard')}\n </button>\n <button\n type=\"button\"\n className={css.save}\n disabled={blocked}\n onClick={props.onSave}\n >\n {props.t(!state.saving ? 'settings.save' : 'settings.saving')}\n </button>\n </div>\n </div>\n )\n : null}\n </li>\n )\n}\n\n/** Props every field control needs regardless of its value type. */\nexport interface FieldProps {\n /** Stable id associating the label with its control. */\n id: string\n /** Visible label. */\n label: string\n /** One-line explanation rendered under the control. */\n hint: string\n /** Draft text this control renders. */\n text: string\n /** True when saving would leave a user-layer entry for this field. */\n overridden: boolean\n /** True when the draft is not a value this field accepts. */\n invalid: boolean\n /** Copy for the overridden badge. */\n overriddenLabel: string\n /** Copy for the reset control. */\n resetLabel: string\n /** Copy shown in place of the hint while the draft is invalid. */\n invalidLabel: string\n /** Disables every control (read-only document, or an unavailable namespace). */\n disabled: boolean\n /** Stage draft text. */\n onEdit: (text: string) => void\n /** Stage a clear so the field re-inherits the composition layer. */\n onReset: () => void\n}\n\n/** A staged value field. `numeric` only hints the keypad: which drafts a field accepts is decided by its spec. */\nexport function ValueField(props: FieldProps & {\n /** Hints a numeric keypad without narrowing what the control accepts. */\n numeric?: boolean\n /** Placeholder shown while the draft is empty. */\n placeholder?: string\n}) {\n return (\n <div className={css.field}>\n <div className={css.head}>\n <label className={css.label} htmlFor={props.id}>{props.label}</label>\n {props.overridden\n ? (\n <span className={css.badges}>\n <span className={css.badge}>{props.overriddenLabel}</span>\n <button\n type=\"button\"\n className={css.reset}\n disabled={props.disabled}\n onClick={props.onReset}\n >\n {props.resetLabel}\n </button>\n </span>\n )\n : null}\n </div>\n <input\n id={props.id}\n className={props.invalid ? css.inputInvalid : css.input}\n type=\"text\"\n {...props.numeric === true ? { inputMode: 'numeric' as const } : {}}\n {...props.invalid ? { 'aria-invalid': true } : {}}\n value={props.text}\n placeholder={props.placeholder ?? ''}\n disabled={props.disabled}\n onChange={(event) => { props.onEdit(event.target.value) }}\n />\n <p className={props.invalid ? css.invalid : css.hint}>\n {props.invalid ? props.invalidLabel : props.hint}\n </p>\n </div>\n )\n}\n\n/** A staged boolean field: 继承 / 开 / 关. */\nexport function BooleanField(props: FieldProps & {\n /** Copy for the inherit option. */\n inheritLabel: string\n /** Copy for the on option. */\n onLabel: string\n /** Copy for the off option. */\n offLabel: string\n}) {\n return (\n <div className={css.field}>\n <div className={css.head}>\n <label className={css.label} htmlFor={props.id}>{props.label}</label>\n {props.overridden\n ? (\n <span className={css.badges}>\n <span className={css.badge}>{props.overriddenLabel}</span>\n <button\n type=\"button\"\n className={css.reset}\n disabled={props.disabled}\n onClick={props.onReset}\n >\n {props.resetLabel}\n </button>\n </span>\n )\n : null}\n </div>\n <select\n id={props.id}\n className={css.select}\n value={props.text}\n disabled={props.disabled}\n onChange={(event) => { props.onEdit(event.target.value) }}\n >\n <option value=\"\">{props.inheritLabel}</option>\n <option value=\"true\">{props.onLabel}</option>\n <option value=\"false\">{props.offLabel}</option>\n </select>\n <p className={css.hint}>{props.hint}</p>\n </div>\n )\n}","/**\n * Staged form model behind the plugin settings card. A card stages what the\n * user types and writes it only when they save — the settings write is a\n * durable, revision-fenced document mutation, so staging keeps what is on\n * screen exactly what a save would store. Mirrors the official\n * ui-plugin-config card-store pattern in a self-contained slice: this\n * package must not depend on a sibling UI package.\n */\n\nimport type { SettingsScope, SettingsScopeSnapshot, SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'\nimport { createSnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'\n\n/** The write one field's staged text performs when the card is saved. */\nexport type FieldWrite =\n | { kind: 'set'; value: unknown }\n | { kind: 'clear' }\n\n/** How one field converts between its stored value and its draft text. */\nexport interface FieldSpec {\n /** Field name inside the namespace section. */\n field: string\n /** Render a stored value as draft text; the empty string when the section carries none. */\n format: (value: unknown) => string\n /**\n * The write this draft text stages, or undefined when the text is not a\n * value this field accepts — which blocks the save rather than discarding it.\n */\n parse: (text: string) => FieldWrite | undefined\n}\n\n/** One field as the card renders it. */\nexport interface FieldState {\n /** Draft text the control renders. */\n text: string\n /** Whether saving would leave a user-layer entry for this field. */\n overridden: boolean\n /** Whether the draft is not a value this field accepts, which blocks saving. */\n invalid: boolean\n}\n\n/** Form state every plugin settings card shares. */\nexport interface CardShell {\n /** False while the namespace is still loading; the card renders nothing. */\n available: boolean\n /**\n * Whether the namespace is actually served to this client. False when the\n * Host deployment does not expose it (e.g. the official apiproxy settings\n * allowlist omits third-party namespaces): the card renders an explanation\n * instead of its form, so a missing namespace never looks like a missing\n * plugin.\n */\n exposed: boolean\n /** Whether the Host document accepts writes. */\n writable: boolean\n /** Whether the form holds edits that a save would write. */\n dirty: boolean\n /** Whether any staged draft is invalid, which blocks the save. */\n invalid: boolean\n /** Whether a save is crossing the wire. */\n saving: boolean\n /** Whether the last save did not land as staged; cleared by the next edit or save. */\n failed: boolean\n}\n\n/** The write actions the card's slot entry injects. */\nexport interface CardActions {\n /** Stage draft text for one field. */\n edit: (field: string, text: string) => void\n /** Stage a clear, so saving lets the field re-inherit the composition layer. */\n resetField: (field: string) => void\n /** Write every staged edit, then re-seed from what the Host accepted. */\n save: () => void\n /** Drop every staged edit. */\n discard: () => void\n}\n\n/** One field's staged edit. */\ninterface StagedEdit {\n /** Draft text the control renders. */\n text: string\n /** True when this edit clears the field whatever text it shows. */\n clear: boolean\n}\n\n/** One staged edit resolved into the write a save performs. */\ninterface PlannedWrite {\n /** Field this entry writes. */\n field: string\n /** Perform the write and report whether the Host holds the staged value afterwards. */\n run: (() => Promise<boolean>) | undefined\n}\n\n/** A whole-number field. An empty draft clears the field; any other draft that is not a finite number blocks the save. */\nexport function numberField(field: string): FieldSpec {\n return {\n field,\n format: value => typeof value === 'number' ? String(value) : '',\n parse: (text) => {\n const trimmed = text.trim()\n if (trimmed === '') return { kind: 'clear' }\n const parsed = Number(trimmed)\n return Number.isFinite(parsed) ? { kind: 'set', value: parsed } : undefined\n },\n }\n}\n\n/** A free-text field. An empty draft clears the field. */\nexport function textField(field: string): FieldSpec {\n return {\n field,\n format: value => typeof value === 'string' ? value : '',\n parse: (text) => {\n const trimmed = text.trim()\n return trimmed === '' ? { kind: 'clear' } : { kind: 'set', value: trimmed }\n },\n }\n}\n\n/** A boolean field, edited through true/false draft text. */\nexport function booleanField(field: string): FieldSpec {\n return {\n field,\n format: value => typeof value === 'boolean' ? String(value) : '',\n parse: (text) => {\n if (text === 'true') return { kind: 'set', value: true }\n if (text === 'false') return { kind: 'set', value: false }\n return undefined\n },\n }\n}\n\n/**\n * Stages one card's edits over one settings namespace and writes them on save.\n *\n * The Host is the only authority on whether a value was accepted — its\n * validators own the constraints no schema can express — so the outcome is\n * read back from the section rather than predicted here. A save that did not\n * land keeps its drafts, so the user can correct them instead of retyping.\n */\nexport class CardForm<T> {\n private readonly specs: Map<string, FieldSpec>\n private readonly staged = new Map<string, StagedEdit>()\n private readonly listeners = new Set<() => void>()\n private saving = false\n private failed = false\n\n /** @param scope - the bound settings scope for this card's namespace. */\n constructor(\n private readonly scope: SettingsScope<T>,\n specs: FieldSpec[],\n ) {\n this.specs = new Map(specs.map(spec => [spec.field, spec]))\n scope.subscribe(() => { this.publish() })\n }\n\n /** Publish a projection of this form, rebuilt whenever the scope or a draft changes. */\n bind<S>(project: () => S): SnapshotStore<S> {\n const store = createSnapshotStore(project())\n this.listeners.add(() => { store.set(project()) })\n return store\n }\n\n /** Read the card-level state: what the Host serves, and what a save would do. */\n shell(): CardShell {\n const snapshot = this.scope.getSnapshot()\n const plan = this.plan()\n return {\n available: snapshot.status !== 'loading',\n exposed: snapshot.status === 'ready',\n writable: snapshot.writable,\n dirty: plan.length > 0,\n invalid: plan.some(item => item.run === undefined),\n saving: this.saving,\n failed: this.failed,\n }\n }\n\n /** Read one field's state from the effective section and its staged draft. */\n field(field: string): FieldState {\n const spec = this.specOf(field)\n const staged = this.staged.get(field)\n if (staged === undefined) {\n return { text: spec.format(this.sectionValue(field)), overridden: this.stored(field), invalid: false }\n }\n const write = staged.clear ? { kind: 'clear' as const } : spec.parse(staged.text)\n return {\n text: staged.text,\n overridden: write?.kind === 'set',\n invalid: write === undefined,\n }\n }\n\n /** The actions the card's slot registration injects. */\n actions(): CardActions {\n return {\n edit: (field, text) => { this.stage(field, { text, clear: false }) },\n resetField: (field) => {\n this.stage(field, { text: this.specOf(field).format(this.baseValue(field)), clear: true })\n },\n save: () => { void this.save() },\n discard: () => {\n if (this.staged.size === 0 && !this.failed) return\n this.staged.clear()\n this.failed = false\n this.publish()\n },\n }\n }\n\n /**\n * Write every staged edit, then re-seed from what the Host accepted.\n * @returns settlement after every write and the read-back.\n */\n async save(): Promise<void> {\n const plan = this.plan()\n const writes = plan.flatMap(item => item.run === undefined ? [] : [item.run])\n if (plan.length === 0 || this.saving || writes.length !== plan.length) return\n this.saving = true\n this.failed = false\n this.publish()\n let landed = true\n for (const write of writes) {\n landed = await write() && landed\n }\n if (landed) this.staged.clear()\n this.saving = false\n this.failed = !landed\n this.publish()\n }\n\n /**\n * Every staged edit a save would write. An entry whose draft is not a value\n * its field accepts carries no write: the form is still dirty, and the save\n * refuses rather than dropping the edit. A staged edit that matches the\n * effective section is not a write at all.\n * @returns the planned writes, in the order the fields were staged.\n */\n private plan(): PlannedWrite[] {\n const plan: PlannedWrite[] = []\n for (const [field, staged] of this.staged) {\n const spec = this.specOf(field)\n if (staged.clear) {\n if (this.stored(field)) plan.push({ field, run: () => this.clear(field) })\n continue\n }\n if (staged.text === spec.format(this.sectionValue(field))) continue\n const write = spec.parse(staged.text)\n if (write === undefined) plan.push({ field, run: undefined })\n else if (write.kind === 'clear') plan.push({ field, run: () => this.clear(field) })\n else plan.push({ field, run: () => this.store(field, write.value) })\n }\n return plan\n }\n\n private async clear(field: string): Promise<boolean> {\n await this.scope.unset(field)\n return !this.stored(field)\n }\n\n private async store(field: string, value: unknown): Promise<boolean> {\n await this.scope.set(field, value)\n return this.userLayer()?.[field] === value\n }\n\n private stage(field: string, edit: StagedEdit): void {\n this.staged.set(field, edit)\n this.failed = false\n this.publish()\n }\n\n private specOf(field: string): FieldSpec {\n const spec = this.specs.get(field)\n // Every call site names a field this card declared; a missing one is a\n // wiring mistake that must not degrade into a silently inert control.\n if (spec === undefined) throw new Error(`settings card has no field ${field}`)\n return spec\n }\n\n private snapshotOf(): SettingsScopeSnapshot<T> {\n return this.scope.getSnapshot()\n }\n\n private sectionValue(field: string): unknown {\n return (this.snapshotOf().value as Record<string, unknown> | undefined)?.[field]\n }\n\n private baseValue(field: string): unknown {\n return (this.snapshotOf().base as Record<string, unknown> | undefined)?.[field]\n }\n\n private userLayer(): Record<string, unknown> | undefined {\n return this.snapshotOf().user as Record<string, unknown> | undefined\n }\n\n private stored(field: string): boolean {\n const user = this.userLayer()\n return user !== undefined && Object.hasOwn(user, field)\n }\n\n private publish(): void {\n for (const listener of this.listeners) listener()\n }\n}\n","/**\n * The task-board settings card: whether the board announces itself in every\n * agent's system prompt. Registers into the `settings.plugin.item` slot the\n * plugin-configuration section renders, bound to the `task-board` settings\n * namespace.\n */\n\nimport type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { SettingsScope, SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'\nimport { PluginSettingsCard, BooleanField } from './PluginSettingsCard.tsx'\nimport { CardForm, booleanField, type CardActions, type CardShell, type FieldState as CardFieldState } from './settings-form.ts'\n\n/** The task-board fields this card edits (the namespace's full schema). */\nexport interface TaskBoardSettings {\n /** Master switch for the plugin. */\n enabled?: boolean\n /** Whether the board announces itself in every agent's system prompt. */\n announceToAgent?: boolean\n}\n\n/** What the task-board card renders. */\nexport interface TaskBoardSettingsCardState extends CardShell {\n /** Master switch. */\n enabled: CardFieldState\n /** System-prompt announcement flag. */\n announceToAgent: CardFieldState\n}\n\n/** The registration-side face the card's slot entry injects. */\nexport interface TaskBoardSettingsCardFace extends CardActions {\n hooks: {\n /** Card snapshot bound by the renderer as useTaskBoardSettingsCard. */\n taskBoardSettingsCard: SnapshotStore<TaskBoardSettingsCardState>\n }\n}\n\n/** Bridges the `task-board` scope onto the card's staged form. */\nexport class TaskBoardSettingsCardController {\n private readonly form: CardForm<TaskBoardSettings>\n private readonly store: SnapshotStore<TaskBoardSettingsCardState>\n\n /** @param scope - the bound settings scope for the `task-board` namespace. */\n constructor(scope: SettingsScope<TaskBoardSettings>) {\n this.form = new CardForm(scope, [\n booleanField('enabled'),\n booleanField('announceToAgent'),\n ])\n this.store = this.form.bind(() => this.projection())\n }\n\n private projection(): TaskBoardSettingsCardState {\n return {\n ...this.form.shell(),\n enabled: this.form.field('enabled'),\n announceToAgent: this.form.field('announceToAgent'),\n }\n }\n\n /**\n * Build the face the card's slot registration injects.\n * @returns the card's snapshot and its form actions.\n */\n inject(): TaskBoardSettingsCardFace {\n return { hooks: { taskBoardSettingsCard: this.store }, ...this.form.actions() }\n }\n}\n\n/** Props the renderer binds for the task-board card. */\nexport type TaskBoardSettingsCardProps =\n PropsRuntime<'web-ui.plugin.item'>\n & PropsLocale<'task-board'>\n & InjectFace<TaskBoardSettingsCardFace>\n\n/**\n * Render the task-board card.\n * @param props - locale copy, the card snapshot, and its form actions.\n * @returns the card.\n */\nexport function TaskBoardSettingsCard(props: TaskBoardSettingsCardProps) {\n const { t } = props\n const state = props.useTaskBoardSettingsCard(snapshot => snapshot)\n const disabled = !state.writable\n const fieldProps = {\n overriddenLabel: t('settings.overridden'),\n resetLabel: t('settings.reset'),\n invalidLabel: t('settings.invalidNumber'),\n disabled,\n }\n return (\n <PluginSettingsCard\n t={t}\n titleKey=\"settings.title\"\n descriptionKey=\"settings.description\"\n state={state}\n onSave={props.save}\n onDiscard={props.discard}\n >\n <BooleanField\n id=\"settings-task-board-enabled\"\n label={t('settings.enabled')}\n hint={t('settings.enabledHint')}\n inheritLabel={t('settings.inherit')}\n onLabel={t('settings.on')}\n offLabel={t('settings.off')}\n {...fieldProps}\n {...state.enabled}\n onEdit={(text) => { props.edit('enabled', text) }}\n onReset={() => { props.resetField('enabled') }}\n />\n <BooleanField\n id=\"settings-task-board-announce\"\n label={t('settings.announceToAgent')}\n hint={t('settings.announceToAgentHint')}\n inheritLabel={t('settings.inherit')}\n onLabel={t('settings.on')}\n offLabel={t('settings.off')}\n {...fieldProps}\n {...state.announceToAgent}\n onEdit={(text) => { props.edit('announceToAgent', text) }}\n onReset={() => { props.resetField('announceToAgent') }}\n />\n </PluginSettingsCard>\n )\n}\n","/**\n * Task-board client plugin: wires the framework-free core (controller,\n * execution service, store) to the real client runtime and mounts the two\n * DOM surfaces — the sidebar entry row and the board view in the center\n * column.\n *\n * Failure policy: DOM mounting problems are logged, never thrown — the web\n * shell fails the whole boot when a plugin apply throws, and an external\n * plugin must not take the GUI down.\n */\nimport type { ClientContext, SessionId, SettingsScope, SettingsScopeSpec, WorkspaceId } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-slots'\n// Type-only: pulls the locale plugin's Context merge (ctx.locale) and its\n// LocaleNamespaceMap merge table.\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\n// Type-only: pulls the settings-surface Context merge (ctx.settingsScope).\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport { BoardController } from '../core/controller.ts'\nimport { ExecutionService } from '../core/execution.ts'\nimport { SchedulerService } from '../core/scheduler.ts'\nimport { LocalStorageTaskStore } from '../core/store.ts'\nimport { claimTaskboardApply, releaseTaskboardApply } from './apply-guard.ts'\nimport { mountBoard } from './board-mount.tsx'\nimport { mountSidebarEntry } from './sidebar-entry.ts'\nimport { TaskBoardSettingsCard, TaskBoardSettingsCardController, type TaskBoardSettings } from './TaskBoardSettingsCard.tsx'\nimport { en, zh, type TaskBoardKey } from './locales.ts'\n\n/** Locale namespace this plugin owns. */\nconst NS = 'task-board'\n\n/** Settings namespace the settings card edits (the Host plugin registers it). */\nconst TASK_BOARD_NS = 'task-board'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** Task-board surface copy. */\n 'task-board': TaskBoardKey\n }\n\n interface SlotMap {\n /**\n * The child slot the Web UI plugin group declares; this card registers\n * into the group instead of the top-level `settings.plugin.item` list.\n * Spelled here with the same shape so this package can register without\n * depending on the sibling UI package.\n */\n 'web-ui.plugin.item': { kind: 'list'; scope: 'root'; owner: SettingsPluginItemOwnerProps }\n }\n}\n\n/** Owner share of a plugin card (the section supplies nothing). */\nexport interface SettingsPluginItemOwnerProps {\n /** Marker field: card owner props are intentionally empty. */\n children?: never\n}\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n /**\n * Optional rc.6 compatibility binder provided by dsh-web-ui-settings;\n * absent when that group plugin is not installed, so callers fall back to\n * the official settings scope.\n */\n webUiSettings?: { bind<S>(spec: SettingsScopeSpec<S>): SettingsScope<S> }\n }\n}\n\n\n/** Required services (fiber inject waiting — the runtime must be up first). */\nexport const inject = ['slots', 'sessions', 'workspaces', 'connection', 'settingsScope', 'locale', 'remote']\n\n/**\n * Mount the task board.\n * @param ctx - client root context (services: sessions, workspaces).\n */\nexport function apply(ctx: ClientContext): void {\n // A duplicated client injection (module factory executed twice in one page\n // lifetime) would otherwise mount a second sidebar entry and board view.\n // First application wins; later calls become no-ops (see apply-guard.ts).\n if (!claimTaskboardApply()) return\n\n // Release the claim when this fiber unloads (the loader supports plugin\n // unloads / hot-reloads), so a rebuilt bundle can claim again in the same\n // page instead of being silently dropped.\n ctx.effect(() => releaseTaskboardApply, 'task-board: apply claim')\n\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'task-board: dictionaries')\n\n // Plugin configuration card: one staged form over the `task-board` settings\n // namespace, contributed to the Web UI plugin group.\n const binder = ctx.get('webUiSettings') ?? ctx.settingsScope\n const settingsScope = binder.bind<TaskBoardSettings>({ namespace: TASK_BOARD_NS })\n const settingsCard = new TaskBoardSettingsCardController(settingsScope)\n ctx.slots.inject('web-ui.plugin.item', () => ctx.slots.register({\n name: 'web-ui.plugin.item',\n id: 'task-board',\n order: 110,\n locale: NS,\n inject: () => settingsCard.inject(),\n }, TaskBoardSettingsCard))\n\n // The sidebar entry and board view mount once the settings scope settles;\n // while the scope is still loading, the composition default is unknown, so\n // nothing mounts yet. Only an unavailable scope (no settings surface served)\n // falls back to the composition default (enabled).\n let uiDisposer: (() => void) | undefined\n const mountUi = (): void => {\n if (uiDisposer !== undefined) return\n const sessions = ctx.sessions\n const workspaces = ctx.workspaces\n const connection = ctx.get('connection') as ConnectionHandle\n\n // Core wiring: real runtime faces into the framework-free services.\n const store = new LocalStorageTaskStore()\n const exec = new ExecutionService({\n sessions: {\n list: sessions.list,\n binding: id => sessions.binding(id as SessionId),\n },\n workspaces: {\n list: workspaces.list,\n connectWorkspace: id => workspaces.connectWorkspace(id as WorkspaceId),\n },\n history: {\n loadTail: async sessionId => {\n const response = await connection.api.sessions.history({\n sessionId: sessionId as SessionId,\n maxMessages: 20,\n })\n return response.result.ok\n ? { events: response.result.value.events.map(entry => entry.event) }\n : undefined\n },\n },\n })\n const controller = new BoardController({\n store,\n exec,\n sessions: {\n list: sessions.list,\n open: id => sessions.open(id as SessionId),\n },\n })\n controller.start()\n\n // Scheduled runs: a browser-side heartbeat that triggers due tasks through\n // the same run path as the manual Run button. The first tick is gated on\n // the session list baseline so a page-load catch-up never fires into a\n // not-yet-ready runtime; tab visibility recovery ticks immediately.\n const scheduler = new SchedulerService({\n tasks: () => controller.getSnapshot().tasks,\n now: () => Date.now(),\n runTask: id => controller.runTask(id),\n applySchedule: (id, nextRunAt, lastTriggeredAt) =>\n controller.applyScheduleNextRun(id, nextRunAt, lastTriggeredAt),\n ready: () => sessions.list.getSnapshot().phase === 'ready',\n environment: {\n addEventListener: (type, listener) => document.addEventListener(type, listener),\n removeEventListener: (type, listener) => document.removeEventListener(type, listener),\n },\n })\n scheduler.start()\n\n const disposers: Array<() => void> = []\n try {\n disposers.push(mountSidebarEntry(controller))\n disposers.push(mountBoard(controller))\n } catch (error) {\n // DOM failures degrade the board, never the GUI.\n console.error('[dsh-task-board] mount failed:', error)\n }\n\n uiDisposer = () => {\n for (const dispose of disposers.splice(0)) dispose()\n scheduler.dispose()\n controller.dispose()\n uiDisposer = undefined\n }\n }\n const syncEnabled = (): void => {\n const snapshot = settingsScope.getSnapshot()\n const enabled = snapshot.status === 'ready'\n ? snapshot.value?.enabled ?? true\n : snapshot.status === 'unavailable'\n if (enabled) mountUi()\n else uiDisposer?.()\n }\n settingsScope.subscribe(syncEnabled)\n syncEnabled()\n}\n"],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Cross-module-instance apply guard for the task-board client bundle.
3
+ *
4
+ * The client factory can run more than once in a single page lifetime (for
5
+ * example when a stale bundle is mixed with a rebuilt one while `dsh web` is
6
+ * restarted). Without a guard, every factory run mounts its own sidebar entry
7
+ * and board view, so the shell ends up showing two "task board" rows.
8
+ *
9
+ * The flag lives on globalThis so separate module instances (independent
10
+ * factory runs) still share one guard. First claim wins; later claims become
11
+ * no-ops until the claim is released (fiber unload / hot-reload) or the page
12
+ * reloads.
13
+ */
14
+ declare global {
15
+ var __dshTaskboardApplied: boolean | undefined;
16
+ }
17
+ /** Claims the plugin apply slot. Returns true when this call won the slot. */
18
+ export declare function claimTaskboardApply(): boolean;
19
+ /**
20
+ * Releases the claim. Called from the client fiber cleanup so that a
21
+ * hot-reloaded bundle (the loader unloads the old plugin fiber and invokes
22
+ * the rebuilt one in the same page) can claim again instead of being
23
+ * silently dropped.
24
+ */
25
+ export declare function releaseTaskboardApply(): void;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@linxin666/dsh-client-ui-task-board",
3
3
  "description": "Task board for the dsh web GUI: a sidebar entry plus a multi-column kanban view with local persistence, task details, and real execution through dsh sessions (session.prompt). Hot-pluggable — mounted via ~/.dsh/cordis.patch.yml + a profile node_modules symlink, no dsh source changes.",
4
- "version": "0.1.13",
4
+ "version": "0.1.14",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/types/index.d.ts",
@@ -55,7 +55,6 @@
55
55
  "lightningcss": "1.32.0",
56
56
  "react": "^18.3.1",
57
57
  "react-dom": "^18.3.1",
58
- "schemastery": "^3.18.0",
59
58
  "tsdown": "0.22.2",
60
59
  "typescript": "~5.7.2",
61
60
  "vitest": "^3.0.0"
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Cross-module-instance apply guard for the task-board client bundle.
3
+ *
4
+ * The client factory can run more than once in a single page lifetime (for
5
+ * example when a stale bundle is mixed with a rebuilt one while `dsh web` is
6
+ * restarted). Without a guard, every factory run mounts its own sidebar entry
7
+ * and board view, so the shell ends up showing two "task board" rows.
8
+ *
9
+ * The flag lives on globalThis so separate module instances (independent
10
+ * factory runs) still share one guard. First claim wins; later claims become
11
+ * no-ops until the claim is released (fiber unload / hot-reload) or the page
12
+ * reloads.
13
+ */
14
+
15
+ declare global {
16
+ // eslint-disable-next-line no-var
17
+ var __dshTaskboardApplied: boolean | undefined
18
+ }
19
+
20
+ /** Claims the plugin apply slot. Returns true when this call won the slot. */
21
+ export function claimTaskboardApply(): boolean {
22
+ if (globalThis.__dshTaskboardApplied === true) return false
23
+ globalThis.__dshTaskboardApplied = true
24
+ return true
25
+ }
26
+
27
+ /**
28
+ * Releases the claim. Called from the client fiber cleanup so that a
29
+ * hot-reloaded bundle (the loader unloads the old plugin fiber and invokes
30
+ * the rebuilt one in the same page) can claim again instead of being
31
+ * silently dropped.
32
+ */
33
+ export function releaseTaskboardApply(): void {
34
+ globalThis.__dshTaskboardApplied = undefined
35
+ }
@@ -20,6 +20,7 @@ import { BoardController } from '../core/controller.ts'
20
20
  import { ExecutionService } from '../core/execution.ts'
21
21
  import { SchedulerService } from '../core/scheduler.ts'
22
22
  import { LocalStorageTaskStore } from '../core/store.ts'
23
+ import { claimTaskboardApply, releaseTaskboardApply } from './apply-guard.ts'
23
24
  import { mountBoard } from './board-mount.tsx'
24
25
  import { mountSidebarEntry } from './sidebar-entry.ts'
25
26
  import { TaskBoardSettingsCard, TaskBoardSettingsCardController, type TaskBoardSettings } from './TaskBoardSettingsCard.tsx'
@@ -74,6 +75,16 @@ export const inject = ['slots', 'sessions', 'workspaces', 'connection', 'setting
74
75
  * @param ctx - client root context (services: sessions, workspaces).
75
76
  */
76
77
  export function apply(ctx: ClientContext): void {
78
+ // A duplicated client injection (module factory executed twice in one page
79
+ // lifetime) would otherwise mount a second sidebar entry and board view.
80
+ // First application wins; later calls become no-ops (see apply-guard.ts).
81
+ if (!claimTaskboardApply()) return
82
+
83
+ // Release the claim when this fiber unloads (the loader supports plugin
84
+ // unloads / hot-reloads), so a rebuilt bundle can claim again in the same
85
+ // page instead of being silently dropped.
86
+ ctx.effect(() => releaseTaskboardApply, 'task-board: apply claim')
87
+
77
88
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'task-board: dictionaries')
78
89
 
79
90
  // Plugin configuration card: one staged form over the `task-board` settings
@@ -87,6 +87,13 @@ function placeEntry(root: HTMLElement, entry: HTMLButtonElement): boolean {
87
87
  * @returns disposer removing the entry and its observers.
88
88
  */
89
89
  export function mountSidebarEntry(controller: BoardController): () => void {
90
+ // DOM-level idempotency: whatever path mounted an entry row before this
91
+ // call (a duplicated apply, an HMR re-injection, a stale module still
92
+ // alive), never mount a second one. The existing row keeps working; a full
93
+ // page reload is the ultimate reset.
94
+ if (typeof document !== 'undefined' && document.querySelector('[data-dsh-taskboard-entry]') !== null) {
95
+ return () => {}
96
+ }
90
97
  const entry = createEntry(controller)
91
98
  let root: HTMLElement | undefined
92
99
  let placed = false