@fonlan/dsh-task-kanban 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,17 @@
1
- # @fonlan/dsh-task-kanban
1
+ # dsh-task-kanban
2
2
 
3
3
  **@fonlan/dsh-task-kanban** 是一个 [DSH](https://github.com/deepseek-ai/dsh) web 插件,为 web 主界面新增"任务看板":把需求细化成多 phase 实现计划,并在 git worktree 中串行实现、全部 phase 完成后自动合入主分支,全流程状态在"需求 / 队列 / 运行中 / 已完成 / 已合并"五条泳道上可视化。
4
4
 
5
+ ## 界面预览
6
+
7
+ ![任务看板与 plan 详情](docs/screenshots/kanban-plan-detail.png)
8
+
9
+ 任务看板把卡片按「需求 / 队列 / 运行中 / 已完成 / 已合并」五条泳道可视化,卡片显示标题、需求摘要、模型、细化 Skill、plan 状态徽标与所属阶段;点击任意卡片,页面下方会展开 plan 详情面板:需求、实现方案摘要,以及按 phase 卡片化展示的目标 / 会话 / 结论,并支持停止 / 重试 / 删除等操作。
10
+
11
+ ![设置界面](docs/screenshots/kanban-settings.png)
12
+
13
+ 侧边栏设置 → 插件配置 →「任务看板」卡片可配置全局并行 worker 数,以及细化需求会话与 Phase 实现会话各自的默认模型、推理强度与 Agent 预设。
14
+
5
15
  ## 功能
6
16
 
7
17
  - **需求 → 计划**:新建任务后自动进入细化会话,把需求拆解为串行执行的 phase 计划(plan)。
@@ -13,7 +23,7 @@
13
23
 
14
24
  ## 设置
15
25
 
16
- 侧边栏设置中的「任务看板」分节(`settings.section` 插槽)支持:
26
+ 侧边栏设置 → 插件配置中的「任务看板」可展开卡片(`settings.plugin.item` 插槽)支持:
17
27
 
18
28
  - **全局并行 worker 数**:跨工作区同时执行的卡片数上限。
19
29
  - **细化需求会话默认 / Phase 实现会话默认**:分别配置两类会话的默认值:
@@ -29,13 +39,13 @@
29
39
 
30
40
  ### 方式一:从 npm 发布包安装
31
41
 
32
- 当前发布版本:`0.1.0`。
42
+ 当前发布版本:`0.2.4`(适配 DSH `0.1.5-rc.2`)。
33
43
 
34
44
  ```sh
35
45
  dsh plugin --profile web add @fonlan/dsh-task-kanban
36
46
  ```
37
47
 
38
- > 如需固定版本,可显式指定版本号:`dsh plugin --profile web add @fonlan/dsh-task-kanban@0.1.0`。
48
+ > 如需固定版本,可显式指定版本号:`dsh plugin --profile web add @fonlan/dsh-task-kanban@0.2.4`。
39
49
 
40
50
  ### 方式二:本地源码链接安装
41
51
 
@@ -46,7 +56,7 @@ pnpm build
46
56
  dsh plugin --profile web add .
47
57
  ```
48
58
 
49
- 安装后插件即通过 `cordis.patch.yml` 自动挂载到 web profile。web profile 会读取该 patch 并把插件注册进 `sidebar.footer.action`(看板入口)、`conversation`(看板视图)与 `settings.section`(插件设置)等插槽,通常重启 web profile 后即可在侧边栏底部看到"任务看板"入口。
59
+ 安装后插件即通过 `cordis.patch.yml` 自动挂载到 web profile。web profile 会读取该 patch 并把插件注册进 `sidebar.footer.action`(看板入口)、`main.conversation`(看板视图)与 `settings.plugin.item`(插件设置)等插槽,通常重启 web profile 后即可在侧边栏底部看到"任务看板"入口。
50
60
 
51
61
  ## 开发
52
62
 
package/lib/client.js CHANGED
@@ -176,11 +176,11 @@ window.__ModuleLoader__.load({
176
176
  const root = state.boardRoot;
177
177
  if (ctx === null || root === null || state.boardDisposer !== null) return;
178
178
  try {
179
- state.boardDisposer = ctx.slots.register({
180
- name: "conversation",
179
+ state.boardDisposer = ctx.slots.inject("main.conversation", () => ctx.slots.register({
180
+ name: "main.conversation",
181
181
  priority: -1,
182
182
  locale: "task-kanban"
183
- }, root);
183
+ }, root));
184
184
  state.boardOpen = true;
185
185
  notify();
186
186
  } catch (error) {
@@ -281,6 +281,7 @@ window.__ModuleLoader__.load({
281
281
  phaseGoal: "目标",
282
282
  phaseSessions: "会话",
283
283
  phaseConclusion: "结论",
284
+ phaseNum: "phase {n}",
284
285
  error: "错误",
285
286
  requirementLabel: "需求文本",
286
287
  requirementPlaceholder: "描述你要实现的需求……",
@@ -355,6 +356,7 @@ window.__ModuleLoader__.load({
355
356
  phaseGoal: "Goal",
356
357
  phaseSessions: "Sessions",
357
358
  phaseConclusion: "Conclusion",
359
+ phaseNum: "Phase {n}",
358
360
  error: "Error",
359
361
  requirementLabel: "Requirement",
360
362
  requirementPlaceholder: "Describe what you want to build…",
@@ -1085,8 +1087,10 @@ window.__ModuleLoader__.load({
1085
1087
  const summaries = attempts.filter((a) => a.summary !== void 0 && a.summary.trim() !== "").map((a) => a.summary);
1086
1088
  const isCurrent = phaseIndex === card.currentPhase && card.status === "running";
1087
1089
  const isCompleted = summaries.length > 0;
1090
+ const cls = "kb-plan-phase" + (isCurrent ? " kb-plan-phase-current" : "") + (isCompleted ? " kb-plan-phase-completed" : "");
1091
+ const planTitle = card.plan?.title ?? "";
1088
1092
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
1089
- className: "kb-plan-phase" + (isCurrent ? " kb-plan-phase-current" : "") + (isCompleted ? " kb-plan-phase-completed" : ""),
1093
+ className: cls,
1090
1094
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1091
1095
  className: "kb-plan-phase-head",
1092
1096
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -1116,6 +1120,8 @@ window.__ModuleLoader__.load({
1116
1120
  children: t("phaseSessions")
1117
1121
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PhaseSessions, {
1118
1122
  attempts,
1123
+ planTitle,
1124
+ phaseIndex,
1119
1125
  t,
1120
1126
  onOpenSession
1121
1127
  })]
@@ -1137,18 +1143,24 @@ window.__ModuleLoader__.load({
1137
1143
  })]
1138
1144
  });
1139
1145
  }
1140
- /** Open-session buttons for one phase's attempts (retries append more). */
1141
- function PhaseSessions({ attempts, t, onOpenSession }) {
1146
+ /**
1147
+ * Open-session buttons for one phase's attempts (retries append more). The
1148
+ * button labels the session with the deterministic "{plan title} · phase N"
1149
+ * format (the requested fallback when the host never auto-generated a title);
1150
+ * the full session id stays available as the native tooltip.
1151
+ */
1152
+ function PhaseSessions({ attempts, planTitle, phaseIndex, t, onOpenSession }) {
1142
1153
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1143
1154
  className: "kb-plan-phase-sessions",
1144
1155
  children: attempts.map((a) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1145
1156
  type: "button",
1146
1157
  className: "kb-btn kb-btn-small",
1158
+ title: a.sessionId,
1147
1159
  onClick: () => onOpenSession(a.sessionId),
1148
1160
  children: [
1149
1161
  t("openSession"),
1150
1162
  " · ",
1151
- a.sessionId.slice(0, 13)
1163
+ planTitle !== "" ? `${planTitle} · ${t("phaseNum", { n: phaseIndex + 1 })}` : a.sessionId.slice(0, 13)
1152
1164
  ]
1153
1165
  }, a.sessionId))
1154
1166
  });
package/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["useSyncExternalStore","useState","useCallback","useMemo","useRef","useRef","IconChecklistOutline14","useState","useCallback"],"sources":["../../../src/shared/lanes.ts","../../../src/client/api.ts","../../../src/client/kanban-state.ts","../../../src/client/locales.ts","../../../src/client/board.tsx","../../../src/client/sections.tsx","../../../src/client/index.tsx"],"sourcesContent":["/**\n * Lane mapping and drag rules (shared by the board UI and tests).\n */\nimport type { KanbanCard, Lane } from './card.js'\n\n/** Which lane a card renders in, based on status + error stage. */\nexport function laneOf(card: KanbanCard): Lane {\n if (card.status === 'error') {\n if (card.error?.stage !== undefined) return card.error.stage\n const kind = card.error?.kind\n if (kind === 'phase_failed' || kind === 'no_base_branch' || kind === 'worktree_failed') return 'running'\n if (kind === 'merge_failed') return 'completed'\n return 'demand'\n }\n switch (card.status) {\n case 'draft':\n case 'refining':\n case 'planned':\n return 'demand'\n case 'queued':\n return 'queue'\n case 'running':\n return 'running'\n case 'completed':\n case 'merging':\n return 'completed'\n case 'merged':\n return 'merged'\n }\n}\n\n/** Lanes a drop onto `to` may come from. */\nexport const DROP_RULES: Record<Lane, Lane[]> = {\n demand: ['queue'],\n queue: ['demand', 'running'],\n running: [],\n completed: [],\n merged: [],\n}\n\nexport function canDrop(from: Lane, to: Lane): boolean {\n return DROP_RULES[to].includes(from)\n}\n","import type { KanbanCard, KanbanSettingsShape, Lane, ModelOption } from '../shared/card'\n\nexport class KanbanApiError extends Error {\n constructor(readonly code: string, message: string) {\n super(message)\n }\n}\n\nasync function call<T>(method: string, payload: Record<string, unknown> = {}): Promise<T> {\n let response: Response\n try {\n response = await fetch(`/plugins/@fonlan/dsh-task-kanban/api/${method}`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n })\n } catch (error) {\n throw new KanbanApiError('network', error instanceof Error ? error.message : String(error))\n }\n const parsed: { ok?: boolean; value?: unknown; error?: { code?: string; message?: string } } | null\n = await response.json().catch(() => null)\n if (!response.ok || parsed === null || parsed.ok !== true || parsed.value === undefined) {\n throw new KanbanApiError(\n parsed?.error?.code ?? 'http',\n parsed?.error?.message ?? `HTTP ${response.status}`,\n )\n }\n return parsed.value as T\n}\n\n/** One row of the host `skill.list` catalog (same shape DSH's chat autocomplete uses). */\nexport interface SkillOption {\n name: string\n description: string\n whenToUse?: string\n modelInvocable: boolean\n}\n\n/**\n * Call the host gateway's `skill.list` unary RPC over the same-origin HTTP\n * bridge (the exact wire shape DSH's chat input uses for its /-autocomplete).\n * The requested `sessionId` scopes the catalog to that session's agent\n * preset, which is where the filesystem skill roots are registered.\n */\nexport async function gatewaySkillList(sessionId: string): Promise<SkillOption[]> {\n let response: Response\n try {\n response = await fetch('/api/skill.list', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n type: 'client-request',\n rpcId: `task-kanban-${Math.random().toString(36).slice(2)}`,\n method: 'skill.list',\n payload: { sessionId },\n }),\n })\n } catch (error) {\n throw new KanbanApiError('network', error instanceof Error ? error.message : String(error))\n }\n const parsed: { type?: string; result?: { ok?: boolean; value?: { skills?: SkillOption[] }; error?: { code?: string; message?: string } } } | null\n = await response.json().catch(() => null)\n if (!response.ok || parsed === null || parsed.type !== 'server-response' || parsed.result?.ok !== true) {\n throw new KanbanApiError(\n parsed?.result?.error?.code ?? 'http',\n parsed?.result?.error?.message ?? `HTTP ${response.status}`,\n )\n }\n return parsed.result.value?.skills ?? []\n}\n\n/** One row of the host `agentPresets.list()` roster. */\nexport interface PresetOption {\n id: string\n name?: string\n description?: string\n}\n\n/** Reasoning efforts of one provider/model route (`reasoning.options`). */\nexport interface ReasoningOptions {\n efforts: Array<{ id: string; name: string; description?: string }>\n defaultEffort?: string\n /** Client-side marker: the fetch failed; the stored effort must be kept. */\n failed?: boolean\n}\n\nexport const api = {\n list: (workspacePath: string) => call<KanbanCard[]>('list', { workspacePath }),\n create: (workspacePath: string, requirement: string, model: string, provider?: string, skill?: string) =>\n call<KanbanCard>('create', { workspacePath, requirement, model, provider, ...(skill !== undefined && skill !== '' ? { skill } : {}) }),\n move: (cardId: string, toLane: Lane) => call<{ ok: boolean; message?: string }>('move', { cardId, toLane }),\n stop: (cardId: string) => call<{ ok: boolean; message?: string }>('stop', { cardId }),\n retry: (cardId: string) => call<{ ok: boolean; message?: string }>('retry', { cardId }),\n remove: (cardId: string) => call<{ ok: boolean; message?: string }>('remove', { cardId }),\n settingsGet: () => call<KanbanSettingsShape>('settings.get'),\n settingsSet: (patch: Partial<KanbanSettingsShape>) => call<KanbanSettingsShape>('settings.set', patch),\n models: () => call<ModelOption[]>('models.list'),\n presets: () => call<PresetOption[]>('presets.list'),\n reasoningOptions: (provider: string, model: string) =>\n call<ReasoningOptions>('reasoning.options', { provider, model }),\n}\n","import { useSyncExternalStore } from 'react'\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\n\n/**\n * HMR-safe shared state store keyed on the global symbol registry.\n *\n * The client-hmr hot swap re-evaluates this module (a fresh `lib/client.js`\n * bundle) WITHOUT reloading the page. A module-level singleton would then be\n * duplicated per evaluation: the session-navigation wrapper installed by the\n * OLD module copy keeps calling the OLD copy's `exitBoard`, which reads the\n * OLD copy's `boardOpen`/`boardDisposer` — while a board opened after the swap\n * writes the NEW copy's state cell. The wrapper would then no-op and clicking\n * a sidebar session would never leave the board.\n *\n * Keeping every field on one `globalThis`-backed record (via `Symbol.for`, so\n * all bundle copies resolve the same key) makes every copy read/write the same\n * state cell, so exit works regardless of which module copy performed the\n * enter.\n */\nconst STATE_KEY = Symbol.for('@fonlan/dsh-task-kanban/state')\n\ninterface KanbanState {\n client: ClientContext | null\n boardRoot: ((props: Record<string, unknown>) => React.ReactNode) | null\n boardDisposer: (() => void) | null\n boardOpen: boolean\n listeners: Set<() => void>\n}\n\nfunction getState(): KanbanState {\n const g = globalThis as unknown as Record<symbol, KanbanState | undefined>\n let state = g[STATE_KEY]\n if (state === undefined) {\n state = {\n client: null,\n boardRoot: null,\n boardDisposer: null,\n boardOpen: false,\n listeners: new Set(),\n }\n g[STATE_KEY] = state\n }\n return state\n}\n\nexport function setClient(ctx: ClientContext | null): void {\n getState().client = ctx\n}\nexport function getClient(): ClientContext | null {\n return getState().client\n}\n\nexport function setBoardRoot(component: (props: Record<string, unknown>) => React.ReactNode): void {\n getState().boardRoot = component\n}\nexport function getBoardRoot(): ((props: Record<string, unknown>) => React.ReactNode) | null {\n return getState().boardRoot\n}\n\nfunction notify(): void {\n for (const listener of getState().listeners) listener()\n}\nexport function isBoardOpen(): boolean {\n return getState().boardOpen\n}\nfunction subscribe(cb: () => void): () => void {\n const listeners = getState().listeners\n listeners.add(cb)\n return () => { listeners.delete(cb) }\n}\nexport function useBoardOpen(): boolean {\n return useSyncExternalStore(subscribe, isBoardOpen)\n}\n\nexport function enterBoard(): void {\n const state = getState()\n const ctx = state.client\n const root = state.boardRoot\n if (ctx === null || root === null || state.boardDisposer !== null) return\n try {\n state.boardDisposer = ctx.slots.register({ name: 'conversation', priority: -1, locale: 'task-kanban' } as never, root as never)\n state.boardOpen = true\n notify()\n } catch (error) {\n console.error('[@fonlan/dsh-task-kanban] cannot open the board:', error)\n }\n}\n\nexport function exitBoard(): void {\n const state = getState()\n if (state.boardDisposer !== null) {\n state.boardDisposer()\n state.boardDisposer = null\n }\n if (state.boardOpen) {\n state.boardOpen = false\n notify()\n }\n}\n\nexport function toggleBoard(): void {\n if (isBoardOpen()) exitBoard()\n else enterBoard()\n}\n\n/**\n * Route session navigation through the board: opening any session while the\n * board is open leaves the board first, so the conversation view can render\n * for the newly selected session.\n *\n * Every sidebar path that selects a session funnels through `sessions.open`\n * (session rows, search results, fork results, New Session) or\n * `sessions.openSubagent` (catalog children) — including re-clicking the\n * already-current session, which never changes `list.current` and therefore\n * cannot be caught by a list-store subscription. Wrapping the two entry\n * points covers all of them in one place.\n *\n * The wrapper is idempotent: a `kbBound` marker on the wrapper prevents a\n * second bind (plugin re-apply / HMR) from stacking another layer. Because the\n * wrapper's exit goes through the shared state store, a wrapper left over from\n * a previous (HMR-replaced) module copy still closes the board correctly.\n */\nexport function bindSessionNavigation(ctx: ClientContext): void {\n const sessions = ctx.sessions\n if (sessions === undefined) return\n\n const open = sessions.open\n if (typeof open === 'function' && (open as unknown as { kbBound?: boolean }).kbBound !== true) {\n const bound = ((id: Parameters<typeof open>[0]) => {\n exitBoard()\n return open.call(sessions, id)\n }) as typeof open\n ;(bound as unknown as { kbBound?: boolean }).kbBound = true\n sessions.open = bound\n }\n\n const openSubagent = sessions.openSubagent\n if (typeof openSubagent === 'function' && (openSubagent as unknown as { kbBound?: boolean }).kbBound !== true) {\n const bound = ((address: Parameters<typeof openSubagent>[0]) => {\n exitBoard()\n return openSubagent.call(sessions, address)\n }) as typeof openSubagent\n ;(bound as unknown as { kbBound?: boolean }).kbBound = true\n sessions.openSubagent = bound\n }\n}","// Locale namespace = the plugin's cordis id (kebab). DSH flat namespace keys\n// (settings + locale) use the internal id `task-kanban`; the scoped npm package\n// name is reserved for package-bound identifiers (exported `name`, module-loader\n// id, the /plugins/<pkg>/… route base).\nexport const LOCALE_NS = 'task-kanban'\n\nexport const zh = {\n kanban: '任务看板',\n openBoard: '打开任务看板',\n closeBoard: '返回对话',\n newTask: '新建任务',\n requirement: '需求',\n project: '所属项目',\n model: '模型',\n cancel: '取消',\n close: '关闭',\n addAndRefine: '添加并细化',\n laneDemand: '需求',\n laneQueue: '队列',\n laneRunning: '运行中',\n laneCompleted: '已完成',\n laneMerged: '已合并',\n statusDraft: '未细化',\n statusRefining: '细化中',\n statusPlanned: '已规划',\n statusQueued: '排队中',\n statusRunning: '实现中',\n statusCompleted: '已完成',\n statusMerging: '合并中',\n statusMerged: '已合并',\n statusError: '出错',\n phaseProgress: '{current}/{total}',\n retry: '重试',\n stop: '停止',\n delete: '删除',\n deleteConfirm: '确定删除该任务?其 worktree 会被清理。',\n openSession: '打开会话',\n plan: '实现方案',\n summary: '摘要',\n phases: '阶段',\n phaseGoal: '目标',\n phaseSessions: '会话',\n phaseConclusion: '结论',\n error: '错误',\n requirementLabel: '需求文本',\n requirementPlaceholder: '描述你要实现的需求……',\n skillGestureHint: '开头输入 /skill-name 可指定细化时使用的 Skill(如 /grill-me)',\n skillHint: '细化时将使用 Skill「{skill}」,并可与该会话交流打磨需求',\n skillMenuUserOnly: '仅用户可调',\n skillBadge: '细化 Skill:/{skill}',\n modelPlaceholder: '(使用默认模型)',\n noWorkspace: '没有可用工作区,请先在侧边栏添加',\n refresh: '刷新',\n settingsTitle: '任务看板',\n settingsDescription: '全局并行 worker 数与各会话类型的默认模型、推理强度、Agent 预设',\n settingsExpand: '展开设置',\n settingsCollapse: '收起设置',\n maxParallelWorkers: '全局并行 worker 数',\n settingsRefineGroup: '细化需求会话默认',\n settingsPhaseGroup: 'Phase 实现会话默认',\n reasoningEffort: '推理强度',\n agentPreset: 'Agent 预设',\n defaultOption: '(默认)',\n saved: '已保存',\n loadError: '加载任务失败',\n noPlan: '该卡片还没有实现计划,请先点击\"添加并细化\"',\n dragNoPlan: '还没有实现计划的卡片不能进入队列',\n actionFailed: '操作失败:{message}',\n sessionNotListed: '该会话不在当前列表,可能来自其他工作区',\n interrupted: '已中断',\n noBaseBranch: '项目没有 main/master 分支',\n worktreeFailed: 'worktree 创建失败',\n refineFailed: '细化失败',\n phaseFailed: '阶段执行失败',\n mergeFailed: '合并失败',\n createFailed: '创建失败',\n refinementLabel: '需求细化',\n mergeLabel: '合并',\n commitLabel: '提交',\n}\n\nexport const en = {\n kanban: 'Task Kanban',\n openBoard: 'Open Task Kanban',\n closeBoard: 'Back to chat',\n newTask: 'New Task',\n requirement: 'Requirement',\n project: 'Project',\n model: 'Model',\n cancel: 'Cancel',\n close: 'Close',\n addAndRefine: 'Add & Refine',\n laneDemand: 'Demands',\n laneQueue: 'Queue',\n laneRunning: 'Running',\n laneCompleted: 'Completed',\n laneMerged: 'Merged',\n statusDraft: 'Unrefined',\n statusRefining: 'Refining',\n statusPlanned: 'Planned',\n statusQueued: 'Queued',\n statusRunning: 'Running',\n statusCompleted: 'Completed',\n statusMerging: 'Merging',\n statusMerged: 'Merged',\n statusError: 'Error',\n phaseProgress: '{current}/{total}',\n retry: 'Retry',\n stop: 'Stop',\n delete: 'Delete',\n deleteConfirm: 'Delete this task? Its worktree will be cleaned up.',\n openSession: 'Open session',\n plan: 'Plan',\n summary: 'Summary',\n phases: 'Phases',\n phaseGoal: 'Goal',\n phaseSessions: 'Sessions',\n phaseConclusion: 'Conclusion',\n error: 'Error',\n requirementLabel: 'Requirement',\n requirementPlaceholder: 'Describe what you want to build…',\n skillGestureHint: 'Start with /skill-name to choose the refinement skill (e.g. /grill-me)',\n skillHint: 'Refinement will use Skill “{skill}” — talk to this session to refine the requirement',\n skillMenuUserOnly: 'User-only',\n skillBadge: 'Refine skill: /{skill}',\n modelPlaceholder: '(use default model)',\n noWorkspace: 'No workspaces available — add one in the sidebar first',\n refresh: 'Refresh',\n settingsTitle: 'Task Kanban',\n settingsDescription: 'Global parallel workers and per-session-type defaults for model, reasoning effort, and agent preset',\n settingsExpand: 'Show settings',\n settingsCollapse: 'Hide settings',\n maxParallelWorkers: 'Global parallel workers',\n settingsRefineGroup: 'Refinement session defaults',\n settingsPhaseGroup: 'Phase implementation defaults',\n reasoningEffort: 'Reasoning effort',\n agentPreset: 'Agent preset',\n defaultOption: '(default)',\n saved: 'Saved',\n loadError: 'Failed to load tasks',\n noPlan: 'This card has no plan yet — click \"Add & Refine\" first',\n dragNoPlan: 'Cards without a plan cannot enter the queue',\n actionFailed: 'Action failed: {message}',\n sessionNotListed: 'Session is not listed here; it may belong to another workspace',\n interrupted: 'Interrupted',\n noBaseBranch: 'Project has no main/master branch',\n worktreeFailed: 'Worktree creation failed',\n refineFailed: 'Refinement failed',\n phaseFailed: 'Phase failed',\n mergeFailed: 'Merge failed',\n createFailed: 'Creation failed',\n refinementLabel: 'Refinement',\n mergeLabel: 'Merge',\n commitLabel: 'Commit',\n}","import { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport type { KanbanCard, Lane, PhaseAttempt, PlanPhase } from '../shared/card'\nimport { DROP_RULES, laneOf } from '../shared/lanes'\nimport { api, gatewaySkillList, type SkillOption } from './api'\nimport { exitBoard, getClient } from './kanban-state'\nimport { en, zh } from './locales'\nimport './board.css'\n\ninterface BoardProps {\n t?: (key: string, params?: Record<string, unknown>) => string\n useWorkspaces?: (selector: (state: unknown) => unknown) => unknown\n useSessions?: (selector: (state: unknown) => unknown) => unknown\n}\n\n/** Session-list snapshot shape the standard `useSessions` feed carries. */\ninterface SessionListStateLike {\n current?: string\n}\n\nfunction firstLine(text: string): string {\n return text.split('\\n').find((l) => l.trim() !== '')?.trim() ?? ''\n}\n\n/** The leading `/skill-name` gesture at the start of the requirement input. */\nconst SKILL_GESTURE = /^\\s*\\/([a-z0-9]+(?:-[a-z0-9]+)*)(?=\\s|$)/\n\n/** Extract a leading `/skill-name` token plus the cleaned remainder. */\nfunction parseSkillGesture(raw: string): { skill?: string; requirement: string } {\n const m = SKILL_GESTURE.exec(raw)\n if (m === null) return { requirement: raw.trim() }\n return { skill: m[1], requirement: raw.slice(m[0].length).trim() }\n}\n\nfunction statusLabel(card: KanbanCard, t: (key: string) => string): string {\n if (card.status === 'error') {\n switch (card.error?.kind) {\n case 'interrupted': return t('interrupted')\n case 'no_base_branch': return t('noBaseBranch')\n case 'worktree_failed': return t('worktreeFailed')\n case 'refine_failed': return t('refineFailed')\n case 'phase_failed': return t('phaseFailed')\n case 'merge_failed': return t('mergeFailed')\n default: return t('createFailed')\n }\n }\n switch (card.status) {\n case 'draft': return t('statusDraft')\n case 'refining': return t('statusRefining')\n case 'planned': return t('statusPlanned')\n case 'queued': return t('statusQueued')\n case 'running': return t('statusRunning')\n case 'completed': return t('statusCompleted')\n case 'merging': return t('statusMerging')\n case 'merged': return t('statusMerged')\n }\n}\n\ninterface WorkspaceRow {\n workspaceId: string\n path: string\n title: string\n}\n\nconst LANE_ORDER: Array<{ key: Lane; labelKey: string }> = [\n { key: 'demand', labelKey: 'laneDemand' },\n { key: 'queue', labelKey: 'laneQueue' },\n { key: 'running', labelKey: 'laneRunning' },\n { key: 'completed', labelKey: 'laneCompleted' },\n { key: 'merged', labelKey: 'laneMerged' },\n]\n\nconst WS_STORAGE_KEY = '@fonlan/dsh-task-kanban:workspace'\n\nexport function BoardRoot(props: BoardProps): JSX.Element {\n const t = props.t ?? ((key: string, params?: Record<string, unknown>) => {\n const dict = (zh as Record<string, string>)[key] ?? (en as Record<string, string>)[key]\n if (dict === undefined || params === undefined) return dict ?? key\n return dict.replace(/\\{(\\w+)\\}/g, (m, name: string) => (params[name] as string) ?? m)\n })\n const useWorkspaces = props.useWorkspaces\n const wsState = useWorkspaces !== undefined ? (useWorkspaces((s: unknown) => s) as { items?: WorkspaceRow[] } | undefined) : undefined\n const items: WorkspaceRow[] = wsState?.items ?? []\n const useSessions = props.useSessions\n const sessionState = useSessions !== undefined ? (useSessions((s: unknown) => s) as SessionListStateLike | undefined) : undefined\n const currentSessionId = sessionState?.current\n\n const [selectedPath, setSelectedPath] = useState<string>(() => {\n try {\n return localStorage.getItem(WS_STORAGE_KEY) ?? ''\n } catch {\n return ''\n }\n })\n const [cards, setCards] = useState<KanbanCard[]>([])\n const [toast, setToast] = useState<string | null>(null)\n const [detailId, setDetailId] = useState<string | null>(null)\n const [newTaskOpen, setNewTaskOpen] = useState(false)\n\n useEffect(() => {\n if (selectedPath === '' && items.length > 0) {\n setSelectedPath(items[0].path)\n }\n }, [selectedPath, items])\n\n const showToast = useCallback((message: string) => {\n setToast(message)\n window.setTimeout(() => setToast(null), 3500)\n }, [])\n\n const refresh = useCallback(async () => {\n if (selectedPath === '') return\n try {\n setCards(await api.list(selectedPath))\n } catch (error) {\n showToast(error instanceof Error ? error.message : String(error))\n }\n }, [selectedPath, showToast])\n\n useEffect(() => {\n void refresh()\n const timer = window.setInterval(() => { void refresh() }, 2500)\n return () => window.clearInterval(timer)\n }, [refresh])\n\n const byLane = useMemo(() => {\n const map: Record<Lane, KanbanCard[]> = { demand: [], queue: [], running: [], completed: [], merged: [] }\n for (const card of cards) map[laneOf(card)].push(card)\n for (const lane of LANE_ORDER) map[lane.key].sort((a, b) => a.createdAt - b.createdAt)\n return map\n }, [cards])\n\n const handleDrop = useCallback(async (target: Lane, event: React.DragEvent) => {\n event.preventDefault()\n const cardId = event.dataTransfer.getData('text/plain')\n if (cardId === '') return\n const card = cards.find((c) => c.id === cardId)\n if (card === undefined) return\n if (target === 'queue' && card.status !== 'running' && card.plan === undefined) {\n showToast(t('dragNoPlan'))\n return\n }\n try {\n const result = await api.move(cardId, target)\n if (!result.ok) {\n showToast(t('actionFailed', { message: result.message ?? '' }))\n }\n void refresh()\n } catch (error) {\n showToast(error instanceof Error ? error.message : String(error))\n }\n }, [cards, refresh, showToast, t])\n\n const openSession = useCallback((sessionId: string) => {\n const ctx = getClient()\n exitBoard()\n if (ctx === null) return\n try {\n ;(ctx.sessions as { open(id: string): void }).open(sessionId)\n } catch {\n showToast(t('sessionNotListed'))\n }\n }, [showToast, t])\n\n if (items.length === 0) {\n return (\n <div className=\"kb-board kb-empty\">\n <div className=\"kb-empty-text\">{t('noWorkspace')}</div>\n </div>\n )\n }\n\n return (\n <div className=\"kb-board\">\n <div className=\"kb-board-header\">\n <div className=\"kb-board-title\">\n <select\n className=\"kb-ws-select\"\n value={selectedPath}\n onChange={(e) => {\n setSelectedPath(e.target.value)\n try {\n localStorage.setItem(WS_STORAGE_KEY, e.target.value)\n } catch {\n // storage unavailable\n }\n }}\n >\n {items.map((w) => (\n <option key={w.workspaceId} value={w.path}>{w.title}</option>\n ))}\n </select>\n <button type=\"button\" className=\"kb-btn kb-btn-primary\" onClick={() => setNewTaskOpen(true)}>{t('newTask')}</button>\n </div>\n </div>\n\n <div className=\"kb-lanes\">\n {LANE_ORDER.map((lane) => (\n <div\n key={lane.key}\n className={'kb-lane' + (DROP_RULES[lane.key].length > 0 ? ' kb-lane-drop' : '')}\n onDragOver={(e) => {\n if (DROP_RULES[lane.key].length > 0) e.preventDefault()\n }}\n onDrop={(e) => { void handleDrop(lane.key, e) }}\n >\n <div className=\"kb-lane-head\">\n <span className=\"kb-lane-title\">{t(lane.labelKey)}</span>\n <span className=\"kb-lane-count\">{byLane[lane.key].length}</span>\n </div>\n <div className=\"kb-lane-body\">\n {byLane[lane.key].map((card) => (\n <CardView\n key={card.id}\n card={card}\n t={t}\n onToggle={() => setDetailId(detailId === card.id ? null : card.id)}\n />\n ))}\n </div>\n </div>\n ))}\n </div>\n\n {toast !== null && <div className=\"kb-toast\">{toast}</div>}\n\n {newTaskOpen && (\n <NewTaskModal\n t={t}\n workspaces={items}\n selectedPath={selectedPath}\n sessionId={currentSessionId}\n onClose={() => setNewTaskOpen(false)}\n onCreated={() => {\n setNewTaskOpen(false)\n void refresh()\n }}\n onError={(message) => showToast(message)}\n />\n )}\n\n {detailId !== null && (\n <DetailPanel\n card={cards.find((c) => c.id === detailId) ?? null}\n t={t}\n onClose={() => setDetailId(null)}\n onChanged={() => {\n setDetailId(null)\n void refresh()\n }}\n onToast={showToast}\n onOpenSession={openSession}\n />\n )}\n </div>\n )\n}\n\ninterface CardViewProps {\n card: KanbanCard\n t: (key: string, params?: Record<string, unknown>) => string\n onToggle: () => void\n}\n\nfunction CardView({ card, t, onToggle }: CardViewProps): JSX.Element {\n const draggable = card.status === 'planned' || card.status === 'queued' || card.status === 'running'\n const title = card.plan !== undefined && card.plan.title !== '' ? card.plan.title : firstLine(card.requirement)\n const badge = statusLabel(card, t)\n const phaseInfo = card.status === 'running' || card.status === 'error'\n ? card.currentPhase < card.phaseCount ? t('phaseProgress', { current: card.currentPhase + 1, total: card.phaseCount }) : ''\n : ''\n return (\n <div\n className={'kb-card' + (card.status === 'error' ? ' kb-card-error' : '') + (card.status === 'running' ? ' kb-card-running' : '')}\n draggable={draggable}\n onDragStart={(e) => {\n e.dataTransfer.setData('text/plain', card.id)\n e.dataTransfer.effectAllowed = 'move'\n }}\n onClick={onToggle}\n >\n <div className=\"kb-card-title\">{title}</div>\n <div className=\"kb-card-meta\">\n <span className=\"kb-card-badge\">{badge}</span>\n {phaseInfo !== '' && <span className=\"kb-card-phase\">{phaseInfo}</span>}\n {card.skill !== undefined && card.skill !== '' && <span className=\"kb-card-skill\">/ {card.skill}</span>}\n {card.model !== '' && <span className=\"kb-card-model\">{card.model}</span>}\n </div>\n {card.status === 'error' && card.error !== undefined && (\n <div className=\"kb-card-error-text\">{card.error.message}</div>\n )}\n {card.status === 'running' && card.gitMode === true && (\n <div className=\"kb-card-worktree\">{card.worktreePath ?? ''}</div>\n )}\n </div>\n )\n}\n\ninterface NewTaskModalProps {\n t: (key: string, params?: Record<string, unknown>) => string\n workspaces: WorkspaceRow[]\n selectedPath: string\n /** Current session id, scoping the host skill.list catalog lookup. */\n sessionId?: string\n onClose: () => void\n onCreated: () => void\n onError: (message: string) => void\n}\n\n/** An in-progress leading `/query` token (menu-open state). */\nconst SKILL_MENU_RE = /^\\s*\\/([a-z0-9-]*)$/\n\nfunction NewTaskModal({ t, workspaces, selectedPath, sessionId, onClose, onCreated, onError }: NewTaskModalProps): JSX.Element {\n const [requirement, setRequirement] = useState('')\n const [project, setProject] = useState(selectedPath)\n const [model, setModel] = useState('')\n const [modelProvider, setModelProvider] = useState('')\n const [models, setModels] = useState<Array<{ provider: string; id: string; name?: string }>>([])\n const [skills, setSkills] = useState<SkillOption[]>([])\n const [menuQuery, setMenuQuery] = useState<string | null>(null)\n const [menuIndex, setMenuIndex] = useState(0)\n const [busy, setBusy] = useState(false)\n const textareaRef = useRef<HTMLTextAreaElement | null>(null)\n\n useEffect(() => {\n let alive = true\n api.models()\n .then((list) => { if (alive) setModels(list) })\n .catch(() => { if (alive) setModels([]) })\n return () => { alive = false }\n }, [])\n\n // Load the host skill catalog once (scoped to the current session), the\n // same feed DSH's chat input uses for its /-autocomplete.\n useEffect(() => {\n if (sessionId === undefined) return\n let alive = true\n gatewaySkillList(sessionId)\n .then((list) => { if (alive) setSkills(list) })\n .catch(() => { if (alive) setSkills([]) })\n return () => { alive = false }\n }, [sessionId])\n\n const matches = skills.filter((s) => s.name.startsWith(menuQuery ?? ''))\n const menuVisible = menuQuery !== null && matches.length > 0\n const selected = menuVisible ? matches[Math.min(menuIndex, matches.length - 1)] : undefined\n\n /** Re-evaluate the leading-token menu state from the current value + caret. */\n const syncMenu = (value: string, caret: number): void => {\n const head = value.slice(0, caret)\n const m = SKILL_MENU_RE.exec(head)\n if (m !== null) {\n setMenuQuery(m[1])\n setMenuIndex(0)\n return\n }\n setMenuQuery(null)\n }\n\n const selectSkill = (name: string): void => {\n const el = textareaRef.current\n const caret = el?.selectionStart ?? requirement.length\n const head = requirement.slice(0, caret)\n const m = SKILL_MENU_RE.exec(head)\n // Replace the leading `/query` token with `/name `.\n const inserted = `/${name} `\n const newValue = m !== null ? inserted + requirement.slice(caret) : inserted + requirement\n setRequirement(newValue)\n setMenuQuery(null)\n requestAnimationFrame(() => {\n const next = textareaRef.current\n if (next !== null) {\n next.focus()\n const pos = inserted.length\n next.setSelectionRange(pos, pos)\n }\n })\n }\n\n const onKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>): void => {\n if (!menuVisible) return\n if (e.key === 'ArrowDown') {\n e.preventDefault()\n setMenuIndex((i) => (i + 1) % matches.length)\n } else if (e.key === 'ArrowUp') {\n e.preventDefault()\n setMenuIndex((i) => (i - 1 + matches.length) % matches.length)\n } else if (e.key === 'Enter') {\n e.preventDefault()\n if (selected !== undefined) selectSkill(selected.name)\n } else if (e.key === 'Escape') {\n e.preventDefault()\n setMenuQuery(null)\n } else if (e.key === 'Tab') {\n e.preventDefault()\n if (selected !== undefined) selectSkill(selected.name)\n }\n }\n\n const submit = async (): Promise<void> => {\n if (requirement.trim() === '') {\n onError(t('requirementLabel') + ' ' + t('error'))\n return\n }\n if (project === '') {\n onError(t('noWorkspace'))\n return\n }\n const parsed = parseSkillGesture(requirement)\n setBusy(true)\n try {\n await api.create(project, parsed.requirement, model, modelProvider, parsed.skill)\n onCreated()\n } catch (error) {\n onError(error instanceof Error ? error.message : String(error))\n setBusy(false)\n }\n }\n\n const activeSkill = parseSkillGesture(requirement).skill\n\n return (\n <div className=\"kb-modal-backdrop\" onClick={busy ? undefined : onClose}>\n <div className=\"kb-modal\" onClick={(e) => e.stopPropagation()}>\n <div className=\"kb-modal-title\">{t('newTask')}</div>\n <label className=\"kb-field\">\n <span>{t('requirementLabel')}</span>\n <div className=\"kb-skill-menu-wrap\">\n <textarea\n ref={textareaRef}\n className=\"kb-input kb-textarea\"\n value={requirement}\n placeholder={t('requirementPlaceholder')}\n onChange={(e) => {\n const value = e.target.value\n setRequirement(value)\n syncMenu(value, e.target.selectionStart ?? value.length)\n }}\n onKeyDown={onKeyDown}\n />\n {menuVisible && (\n <ul className=\"kb-skill-menu\" role=\"listbox\">\n {matches.map((s, i) => (\n <li\n key={s.name}\n role=\"option\"\n aria-selected={i === menuIndex}\n className={'kb-skill-menu-item' + (i === menuIndex ? ' kb-skill-menu-item-active' : '')}\n onMouseDown={(e) => { e.preventDefault(); selectSkill(s.name) }}\n onMouseEnter={() => setMenuIndex(i)}\n >\n <span className=\"kb-skill-menu-name\">/{s.name}</span>\n <span className=\"kb-skill-menu-desc\">\n {s.modelInvocable ? s.description : `${t('skillMenuUserOnly')} · ${s.description}`}\n </span>\n </li>\n ))}\n </ul>\n )}\n </div>\n {activeSkill !== undefined ? (\n <span className=\"kb-field-hint kb-field-hint-skill\">{t('skillHint', { skill: activeSkill })}</span>\n ) : (\n <span className=\"kb-field-hint\">{t('skillGestureHint')}</span>\n )}\n </label>\n <label className=\"kb-field\">\n <span>{t('project')}</span>\n <select className=\"kb-input\" value={project} onChange={(e) => setProject(e.target.value)}>\n {workspaces.map((w) => (\n <option key={w.workspaceId} value={w.path}>{w.title}</option>\n ))}\n </select>\n </label>\n <label className=\"kb-field\">\n <span>{t('model')}</span>\n <select\n className=\"kb-input\"\n value={model}\n onChange={(e) => {\n const id = e.target.value\n setModel(id)\n const found = models.find((m) => m.id === id)\n setModelProvider(found?.provider ?? '')\n }}\n >\n <option value=\"\">{t('modelPlaceholder')}</option>\n {models.map((m) => (\n <option key={m.provider + '/' + m.id} value={m.id}>{m.name ?? m.id}</option>\n ))}\n </select>\n </label>\n <div className=\"kb-modal-actions\">\n <button type=\"button\" className=\"kb-btn\" onClick={onClose} disabled={busy}>{t('cancel')}</button>\n <button type=\"button\" className=\"kb-btn kb-btn-primary\" onClick={() => { void submit() }} disabled={busy}>\n {t('addAndRefine')}\n </button>\n </div>\n </div>\n </div>\n )\n}\n\ninterface DetailPanelProps {\n card: KanbanCard | null\n t: (key: string, params?: Record<string, unknown>) => string\n onClose: () => void\n onChanged: () => void\n onToast: (message: string) => void\n onOpenSession: (sessionId: string) => void\n}\n\nfunction DetailPanel({ card, t, onClose, onChanged, onToast, onOpenSession }: DetailPanelProps): JSX.Element | null {\n const [busy, setBusy] = useState(false)\n if (card === null) return null\n\n const act = async (fn: () => Promise<unknown>): Promise<void> => {\n setBusy(true)\n try {\n await fn()\n onChanged()\n } catch (error) {\n onToast(error instanceof Error ? error.message : String(error))\n setBusy(false)\n }\n }\n\n const remove = async (): Promise<void> => {\n if (!window.confirm(t('deleteConfirm'))) return\n await act(() => api.remove(card!.id))\n }\n\n return (\n <div className=\"kb-detail-panel\">\n <div className=\"kb-detail-panel-head\">\n <div className=\"kb-modal-title\">\n {card.plan !== undefined && card.plan.title !== '' ? card.plan.title : firstLine(card.requirement)}\n <span className={'kb-detail-status kb-status-' + card.status}>{statusLabel(card, t)}</span>\n </div>\n <button type=\"button\" className=\"kb-detail-close\" aria-label={t('close')} title={t('close')} onClick={onClose}>✕</button>\n </div>\n\n <div className=\"kb-detail-panel-body\">\n <div className=\"kb-detail-section\">\n <div className=\"kb-detail-label\">{t('requirement')}</div>\n {card.skill !== undefined && card.skill !== '' && (\n <div className=\"kb-detail-skill\">{t('skillBadge', { skill: card.skill })}</div>\n )}\n <div className=\"kb-detail-text\">{card.requirement}</div>\n </div>\n\n {card.plan !== undefined && (\n <div className=\"kb-detail-section\">\n <div className=\"kb-detail-label\">{t('plan')}</div>\n <div className=\"kb-detail-plan\">\n <div className=\"kb-plan-summary\">{card.plan.summary}</div>\n <ol className=\"kb-plan-phases\">\n {card.plan.phases.map((phase, i) => (\n <PhaseCard\n key={phase.id}\n phase={phase}\n phaseIndex={i}\n card={card}\n t={t}\n onOpenSession={onOpenSession}\n />\n ))}\n </ol>\n </div>\n </div>\n )}\n\n {card.sessions.refinement.length > 0 && (\n <div className=\"kb-detail-section\">\n <div className=\"kb-detail-label\">{t('refinementLabel')}</div>\n <div className=\"kb-session-links\">\n {card.sessions.refinement.map((sid) => (\n <button key={sid} type=\"button\" className=\"kb-btn kb-btn-small\" onClick={() => onOpenSession(sid)}>\n {t('openSession')} · {sid.slice(0, 13)}\n </button>\n ))}\n </div>\n </div>\n )}\n\n {card.status === 'error' && card.error !== undefined && (\n <div className=\"kb-detail-section kb-detail-error\">\n <div className=\"kb-detail-label\">{t('error')}</div>\n <div className=\"kb-detail-text\">{card.error.message}</div>\n </div>\n )}\n\n {card.merge?.mergeCommit !== undefined && (\n <div className=\"kb-detail-section\">\n <div className=\"kb-detail-label\">{t('mergeLabel')}</div>\n <div className=\"kb-detail-text\">{t('commitLabel')} {card.merge.mergeCommit.slice(0, 12)}</div>\n </div>\n )}\n\n <div className=\"kb-modal-actions\">\n {card.status === 'error' && (\n <button type=\"button\" className=\"kb-btn kb-btn-primary\" disabled={busy} onClick={() => { void act(() => api.retry(card!.id)) }}>\n {t('retry')}\n </button>\n )}\n {(card.status === 'running' || card.status === 'merging') && (\n <button type=\"button\" className=\"kb-btn\" disabled={busy} onClick={() => { void act(() => api.stop(card!.id)) }}>\n {t('stop')}\n </button>\n )}\n {card.status !== 'running' && card.status !== 'refining' && card.status !== 'merging' && (\n <button type=\"button\" className=\"kb-btn kb-btn-danger\" disabled={busy} onClick={() => { void remove() }}>\n {t('delete')}\n </button>\n )}\n <button type=\"button\" className=\"kb-btn\" onClick={onClose}>{t('cancel')}</button>\n </div>\n </div>\n </div>\n )\n}\n\n/** One plan phase rendered as a card with goal / sessions / conclusion sections. */\nfunction PhaseCard({ phase, phaseIndex, card, t, onOpenSession }: {\n phase: PlanPhase\n phaseIndex: number\n card: KanbanCard\n t: (key: string, params?: Record<string, unknown>) => string\n onOpenSession: (sessionId: string) => void\n}): JSX.Element {\n const attempts = card.sessions.phases.filter((a) => a.phaseIndex === phaseIndex)\n const summaries = attempts\n .filter((a) => a.summary !== undefined && a.summary.trim() !== '')\n .map((a) => a.summary as string)\n const isCurrent = phaseIndex === card.currentPhase && card.status === 'running'\n // A phase with at least one concluded attempt counts as completed.\n const isCompleted = summaries.length > 0\n const cls = 'kb-plan-phase'\n + (isCurrent ? ' kb-plan-phase-current' : '')\n + (isCompleted ? ' kb-plan-phase-completed' : '')\n return (\n <li className={cls}>\n <div className=\"kb-plan-phase-head\">\n <span className=\"kb-plan-phase-title\">{phase.title}</span>\n <span className=\"kb-plan-phase-id\">{phase.id}</span>\n </div>\n <div className=\"kb-plan-phase-body\">\n <div className=\"kb-plan-phase-section\">\n <div className=\"kb-plan-phase-section-title\">{t('phaseGoal')}</div>\n <div className=\"kb-plan-phase-goal\">{phase.goal}</div>\n </div>\n {attempts.length > 0 && (\n <div className=\"kb-plan-phase-section\">\n <div className=\"kb-plan-phase-section-title\">{t('phaseSessions')}</div>\n <PhaseSessions attempts={attempts} t={t} onOpenSession={onOpenSession} />\n </div>\n )}\n {summaries.length > 0 && (\n <div className=\"kb-plan-phase-section\">\n <div className=\"kb-plan-phase-section-title\">{t('phaseConclusion')}</div>\n <div className=\"kb-plan-phase-conclusion\">\n {summaries.map((s, idx) => (\n <div key={idx} className=\"kb-plan-phase-conclusion-item\">{s}</div>\n ))}\n </div>\n </div>\n )}\n </div>\n </li>\n )\n}\n\n/** Open-session buttons for one phase's attempts (retries append more). */\nfunction PhaseSessions({ attempts, t, onOpenSession }: {\n attempts: PhaseAttempt[]\n t: (key: string) => string\n onOpenSession: (sessionId: string) => void\n}): JSX.Element {\n return (\n <div className=\"kb-plan-phase-sessions\">\n {attempts.map((a) => (\n <button key={a.sessionId} type=\"button\" className=\"kb-btn kb-btn-small\" onClick={() => onOpenSession(a.sessionId)}>\n {t('openSession')} · {a.sessionId.slice(0, 13)}\n </button>\n ))}\n </div>\n )\n}","import { useCallback, useEffect, useRef, useState } from 'react'\nimport { IconChecklistOutline14 } from '@deepseek-ai/dsh-client-ui-primitives'\nimport { api, type PresetOption, type ReasoningOptions } from './api'\nimport { toggleBoard, useBoardOpen } from './kanban-state'\nimport type { KanbanSettingsShape, ModelOption } from '../shared/card'\n\ninterface FooterActionProps {\n wide?: boolean\n t?: (key: string) => string\n}\n\n/** Sidebar footer entry: toggles the kanban board main view.\n * The shell renders footer actions on their own row ABOVE the settings row,\n * so the button is reparented into the settings area to sit on the same\n * line, right of the Settings trigger (see board.css .kb-settings-row). */\nexport function KanbanFooterButton({ wide, t }: FooterActionProps): JSX.Element {\n const open = useBoardOpen()\n const ref = useRef<HTMLButtonElement | null>(null)\n const label = t !== undefined ? t(open ? 'closeBoard' : 'openBoard') : 'Task Kanban'\n\n useEffect(() => {\n const btn = ref.current\n if (btn === null) return\n let foot: Element | null = null\n let disposed = false\n\n // Locate the sidebar foot area WITHOUT moving any DOM: it is the ancestor\n // of the button whose LAST child contains the settings trigger and which\n // is itself the last child of its parent (root > footArea > settingsArea).\n // The slot machinery wraps entries in an anonymous div, so the search\n // tolerates extra wrapper levels. Styling is pure CSS (see board.css\n // .kb-foot-row), so a failed lookup only leaves the button in its natural\n // slot row — it can never vanish.\n const findFoot = (): Element | null => {\n let el: Element | null = btn.parentElement\n while (el !== null && el !== document.body && el.parentElement !== null) {\n const last = el.lastElementChild\n const hasTrigger =\n last !== null &&\n (last.querySelector?.('button[aria-haspopup=\"dialog\"]') !== null ||\n last.querySelector?.('button[aria-haspopup=\"menu\"]') !== null)\n const isLastChild = el.parentElement.lastElementChild === el\n if (hasTrigger && isLastChild) return el\n el = el.parentElement\n }\n return null\n }\n\n const tryApply = (): boolean => {\n if (foot === null) {\n try {\n foot = findFoot()\n } catch {\n foot = null\n }\n }\n if (foot === null) return false\n try {\n foot.classList.add('kb-foot-row')\n foot.classList.toggle('kb-foot-rail', wide !== true)\n } catch {\n // element already gone; retry will re-locate\n }\n return true\n }\n\n let applied = false\n try {\n applied = tryApply()\n } catch (error) {\n console.error('[@fonlan/dsh-task-kanban] foot placement error:', error)\n }\n if (!applied) {\n // The settings entry may mount later than this entry (plugin load\n // order); retry on DOM mutations AND on a short interval until the\n // foot area is found (give up after 15s).\n const observer = new MutationObserver(() => {\n if (disposed) return\n if (tryApply()) observer.disconnect()\n })\n observer.observe(document.body, { childList: true, subtree: true })\n const timer = window.setInterval(() => {\n if (disposed) {\n window.clearInterval(timer)\n return\n }\n if (tryApply()) {\n observer.disconnect()\n window.clearInterval(timer)\n }\n }, 1000)\n window.setTimeout(() => {\n observer.disconnect()\n window.clearInterval(timer)\n if (foot === null) {\n // report the DOM chain so the next report carries evidence\n const chain: string[] = []\n let el: Element | null = btn.parentElement\n while (el !== null && chain.length < 8) {\n chain.push(el.tagName + '.' + String(el.className).slice(0, 60))\n el = el.parentElement\n }\n console.warn('[@fonlan/dsh-task-kanban] could not locate the sidebar foot area:', chain)\n }\n }, 15000)\n return () => {\n disposed = true\n observer.disconnect()\n window.clearInterval(timer)\n try {\n foot?.classList.remove('kb-foot-row')\n foot?.classList.remove('kb-foot-rail')\n } catch {\n // already gone\n }\n }\n }\n return () => {\n try {\n foot?.classList.remove('kb-foot-row')\n foot?.classList.remove('kb-foot-rail')\n } catch {\n // already gone\n }\n }\n }, [wide])\n\n return (\n <button\n ref={ref}\n type=\"button\"\n className={'kb-footer-action' + (wide === true ? ' kb-footer-action-wide' : '') + (open ? ' kb-footer-action-active' : '')}\n aria-label={label}\n title={label}\n onClick={() => toggleBoard()}\n >\n <IconChecklistOutline14 size={wide === true ? 16 : 18} />\n {wide === true && <span className=\"kb-footer-label\">{t !== undefined ? t('kanban') : 'Kanban'}</span>}\n </button>\n )\n}\n\ninterface SettingsCardProps {\n t?: (key: string) => string\n}\n\n/** Fallback shape while settings load (server always resolves the full set). */\nconst EMPTY_SETTINGS: KanbanSettingsShape = {\n maxParallelWorkers: 1,\n refinementModel: '',\n refinementProvider: '',\n refinementReasoningEffort: '',\n refinementPreset: '',\n phaseModel: '',\n phaseProvider: '',\n phaseReasoningEffort: '',\n phasePreset: '',\n}\n\n/** The setting field names of one session-type group. */\nconst GROUP_FIELDS = {\n refinement: {\n model: 'refinementModel',\n provider: 'refinementProvider',\n effort: 'refinementReasoningEffort',\n preset: 'refinementPreset',\n },\n phase: {\n model: 'phaseModel',\n provider: 'phaseProvider',\n effort: 'phaseReasoningEffort',\n preset: 'phasePreset',\n },\n} as const\n\ntype SessionGroupKey = keyof typeof GROUP_FIELDS\n\ninterface SessionDefaultsGroupProps {\n kind: SessionGroupKey\n settings: KanbanSettingsShape\n models: ModelOption[]\n presets: PresetOption[]\n /** Reasoning efforts already fetched, keyed `provider/model`. */\n efforts: Record<string, ReasoningOptions>\n tr: (key: string) => string\n onCommit: (patch: Partial<KanbanSettingsShape>) => Promise<void>\n loadEfforts: (provider: string, model: string) => void\n}\n\n/** Model / reasoning effort / agent preset defaults for one session type. */\nfunction SessionDefaultsGroup({ kind, settings, models, presets, efforts, tr, onCommit, loadEfforts }: SessionDefaultsGroupProps): JSX.Element {\n const fields = GROUP_FIELDS[kind]\n const model = settings[fields.model]\n const provider = settings[fields.provider]\n const effort = settings[fields.effort]\n const preset = settings[fields.preset]\n const effortKey = model !== '' && provider !== '' ? `${provider}/${model}` : ''\n const effortOptions = effortKey !== '' ? (efforts[effortKey]?.efforts ?? []) : []\n\n const handleModel = (id: string): void => {\n const found = models.find((m) => m.id === id)\n const nextProvider = found?.provider ?? ''\n void onCommit({ [fields.model]: id, [fields.provider]: nextProvider } as Partial<KanbanSettingsShape>)\n if (id !== '' && nextProvider !== '') loadEfforts(nextProvider, id)\n }\n\n return (\n <div className=\"kb-settings-group\">\n <div className=\"kb-settings-group-title\">{tr(kind === 'refinement' ? 'settingsRefineGroup' : 'settingsPhaseGroup')}</div>\n <label className=\"kb-field kb-field-row\">\n <span>{tr('model')}</span>\n <select className=\"kb-input\" value={model} onChange={(e) => handleModel(e.target.value)}>\n <option value=\"\">{tr('defaultOption')}</option>\n {models.map((m) => (\n <option key={m.provider + '/' + m.id} value={m.id}>{m.name ?? m.id}</option>\n ))}\n </select>\n </label>\n <label className=\"kb-field kb-field-row\">\n <span>{tr('reasoningEffort')}</span>\n <select\n className=\"kb-input\"\n value={effort}\n disabled={model === ''}\n onChange={(e) => void onCommit({ [fields.effort]: e.target.value } as Partial<KanbanSettingsShape>)}\n >\n <option value=\"\">{tr('defaultOption')}</option>\n {effortOptions.map((o) => (\n <option key={o.id} value={o.id}>{o.name}</option>\n ))}\n </select>\n </label>\n <label className=\"kb-field kb-field-row\">\n <span>{tr('agentPreset')}</span>\n <select\n className=\"kb-input\"\n value={preset}\n onChange={(e) => void onCommit({ [fields.preset]: e.target.value } as Partial<KanbanSettingsShape>)}\n >\n <option value=\"\">{tr('defaultOption')}</option>\n {presets.map((p) => (\n <option key={p.id} value={p.id}>{p.name ?? p.id}</option>\n ))}\n </select>\n </label>\n </div>\n )\n}\n\n/** Plugin settings card (设置 → 插件配置): workers + default model + per-session-type defaults. */\nexport function KanbanSettingsCard({ t }: SettingsCardProps): JSX.Element {\n const tr = t ?? ((key: string) => key)\n // Card-local disclosure: collapsed by default, like the built-in plugin cards.\n const [open, setOpen] = useState(false)\n const [settings, setSettings] = useState<KanbanSettingsShape>(EMPTY_SETTINGS)\n const [models, setModels] = useState<ModelOption[]>([])\n const [presets, setPresets] = useState<PresetOption[]>([])\n const [efforts, setEfforts] = useState<Record<string, ReasoningOptions>>({})\n const [saved, setSaved] = useState(false)\n\n const commit = useCallback(async (patch: Partial<KanbanSettingsShape>): Promise<void> => {\n try {\n const next = await api.settingsSet(patch)\n setSettings(next)\n setSaved(true)\n window.setTimeout(() => setSaved(false), 1500)\n } catch {\n // keep the previous value\n }\n }, [])\n\n // Refs so async callbacks (effort fetch) read the freshest settings/commit.\n const settingsRef = useRef(settings)\n settingsRef.current = settings\n const commitRef = useRef(commit)\n commitRef.current = commit\n\n useEffect(() => {\n let alive = true\n api.settingsGet()\n .then((s) => { if (alive) setSettings(s) })\n .catch(() => undefined)\n api.models()\n .then((list) => { if (alive) setModels(list) })\n .catch(() => undefined)\n api.presets()\n .then((list) => { if (alive) setPresets(list) })\n .catch(() => undefined)\n return () => { alive = false }\n }, [])\n\n /** Reasoning routes already requested (dedupe across mount + model changes). */\n const effortFetched = useRef<Set<string>>(new Set())\n\n /**\n * Fetch the reasoning efforts of one model route (once per route). A failed\n * fetch is marked `failed` so the stored effort is preserved (see the reset\n * effect below) instead of being wiped by a transient error.\n */\n const loadEfforts = useCallback((provider: string, model: string): void => {\n const key = `${provider}/${model}`\n if (effortFetched.current.has(key)) return\n effortFetched.current.add(key)\n api.reasoningOptions(provider, model)\n .then((opts) => setEfforts((prev) => ({ ...prev, [key]: opts })))\n .catch(() => setEfforts((prev) => ({ ...prev, [key]: { efforts: [], failed: true } })))\n }, [])\n\n // Fetch efforts for the models already configured in settings, so the\n // effort selects show their levels (and validate the stored effort) as soon\n // as settings load — the fetch previously ran only on model change.\n useEffect(() => {\n for (const kind of ['refinement', 'phase'] as const) {\n const fields = GROUP_FIELDS[kind]\n const model = settings[fields.model]\n const provider = settings[fields.provider]\n if (model !== '' && provider !== '') loadEfforts(provider, model)\n }\n }, [settings.refinementModel, settings.refinementProvider, settings.phaseModel, settings.phaseProvider, loadEfforts])\n\n // When the efforts of the selected model arrive, drop a stored effort the\n // model no longer offers (e.g. the user switched the model). A failed fetch\n // (`failed: true`) keeps the stored effort: the model may just be\n // temporarily unresolvable, and wiping the user's choice would be worse.\n useEffect(() => {\n const s = settingsRef.current\n for (const kind of ['refinement', 'phase'] as const) {\n const fields = GROUP_FIELDS[kind]\n const model = s[fields.model]\n const provider = s[fields.provider]\n const effort = s[fields.effort]\n if (effort === '' || model === '' || provider === '') continue\n const opts = efforts[`${provider}/${model}`]\n if (opts === undefined || opts.failed === true) continue\n if (!opts.efforts.some((o) => o.id === effort)) {\n void commitRef.current({ [fields.effort]: '' } as Partial<KanbanSettingsShape>)\n }\n }\n }, [efforts])\n\n return (\n <li className=\"kb-settings-card\" data-open={open ? '' : undefined}>\n <button\n type=\"button\"\n className=\"kb-settings-card-head\"\n aria-expanded={open}\n aria-label={(open ? tr('settingsCollapse') : tr('settingsExpand')) + ':' + tr('settingsTitle')}\n onClick={() => setOpen(!open)}\n >\n <span className=\"kb-settings-card-headText\">\n <span className=\"kb-settings-card-title\">{tr('settingsTitle')}</span>\n <span className=\"kb-settings-card-sub\">{tr('settingsDescription')}</span>\n </span>\n <span className=\"kb-settings-card-chevron\" data-open={open ? '' : undefined} aria-hidden=\"true\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\">\n <path d=\"M3.5 5.25L7 8.75L10.5 5.25\" stroke=\"currentColor\" strokeWidth=\"1.5\"\n strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n </span>\n </button>\n {open && (\n <div className=\"kb-settings-card-body\">\n <div className=\"kb-settings\">\n <label className=\"kb-field kb-field-row\">\n <span>{tr('maxParallelWorkers')}</span>\n <input\n className=\"kb-input kb-input-number\"\n type=\"number\"\n min={1}\n step={1}\n value={settings.maxParallelWorkers}\n onChange={(e) => {\n const value = Math.max(1, Math.floor(Number(e.target.value) || 1))\n setSettings((s) => ({ ...s, maxParallelWorkers: value }))\n void commit({ maxParallelWorkers: value })\n }}\n />\n </label>\n <SessionDefaultsGroup\n kind=\"refinement\"\n settings={settings}\n models={models}\n presets={presets}\n efforts={efforts}\n tr={tr}\n onCommit={commit}\n loadEfforts={loadEfforts}\n />\n <SessionDefaultsGroup\n kind=\"phase\"\n settings={settings}\n models={models}\n presets={presets}\n efforts={efforts}\n tr={tr}\n onCommit={commit}\n loadEfforts={loadEfforts}\n />\n {saved && <div className=\"kb-settings-saved\">{tr('saved')}</div>}\n </div>\n </div>\n )}\n </li>\n )\n}","import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-sidebar/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-layout/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-settings-plugins/client'\nimport { BoardRoot } from './board'\nimport { KanbanFooterButton, KanbanSettingsCard } from './sections'\nimport { LOCALE_NS, zh, en } from './locales'\nimport { setClient, setBoardRoot, bindSessionNavigation } from './kanban-state'\n\n/** Services required before mounting (provided by the client runtime). */\nexport const inject = ['slots', 'sessions', 'workspaces', 'locale']\n\n/** Client plugin body. */\nexport function apply(ctx: ClientContext): void {\n setClient(ctx)\n setBoardRoot(BoardRoot as unknown as (props: Record<string, unknown>) => React.ReactNode)\n // Selecting a session while the board is open must leave the board first.\n bindSessionNavigation(ctx)\n\n // Live translate bound to the active locale (labels re-read it per call).\n const t = ctx.locale.bind(LOCALE_NS)\n ctx.effect(() => {\n const off = ctx.locale.register(LOCALE_NS, { zh, en })\n return () => off()\n }, 'task-kanban: dictionaries')\n\n ctx.slots.inject('sidebar.footer.action', () =>\n ctx.slots.register({\n name: 'sidebar.footer.action',\n id: 'task-kanban',\n order: 60,\n label: () => t('kanban'),\n locale: LOCALE_NS,\n }, KanbanFooterButton as never),\n )\n\n // The plugin's own Settings Card (设置 → 插件配置) rides the `task-kanban`\n // settings namespace: registering into the keyed `settings.plugin.item` slot\n // with the namespace string makes the configurable-plugins tab dispatch the\n // card next to the built-in ones (bash / agent loop / web search).\n ctx.slots.inject('settings.plugin.item', () =>\n ctx.slots.register({\n name: 'settings.plugin.item',\n key: 'task-kanban',\n locale: LOCALE_NS,\n }, KanbanSettingsCard as never),\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["useSyncExternalStore","useState","useCallback","useMemo","useRef","useRef","IconChecklistOutline14","useState","useCallback"],"sources":["../../../src/shared/lanes.ts","../../../src/client/api.ts","../../../src/client/kanban-state.ts","../../../src/client/locales.ts","../../../src/client/board.tsx","../../../src/client/sections.tsx","../../../src/client/index.tsx"],"sourcesContent":["/**\n * Lane mapping and drag rules (shared by the board UI and tests).\n */\nimport type { KanbanCard, Lane } from './card.js'\n\n/** Which lane a card renders in, based on status + error stage. */\nexport function laneOf(card: KanbanCard): Lane {\n if (card.status === 'error') {\n if (card.error?.stage !== undefined) return card.error.stage\n const kind = card.error?.kind\n if (kind === 'phase_failed' || kind === 'no_base_branch' || kind === 'worktree_failed') return 'running'\n if (kind === 'merge_failed') return 'completed'\n return 'demand'\n }\n switch (card.status) {\n case 'draft':\n case 'refining':\n case 'planned':\n return 'demand'\n case 'queued':\n return 'queue'\n case 'running':\n return 'running'\n case 'completed':\n case 'merging':\n return 'completed'\n case 'merged':\n return 'merged'\n }\n}\n\n/** Lanes a drop onto `to` may come from. */\nexport const DROP_RULES: Record<Lane, Lane[]> = {\n demand: ['queue'],\n queue: ['demand', 'running'],\n running: [],\n completed: [],\n merged: [],\n}\n\nexport function canDrop(from: Lane, to: Lane): boolean {\n return DROP_RULES[to].includes(from)\n}\n","import type { KanbanCard, KanbanSettingsShape, Lane, ModelOption } from '../shared/card'\n\nexport class KanbanApiError extends Error {\n constructor(readonly code: string, message: string) {\n super(message)\n }\n}\n\nasync function call<T>(method: string, payload: Record<string, unknown> = {}): Promise<T> {\n let response: Response\n try {\n response = await fetch(`/plugins/@fonlan/dsh-task-kanban/api/${method}`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n })\n } catch (error) {\n throw new KanbanApiError('network', error instanceof Error ? error.message : String(error))\n }\n const parsed: { ok?: boolean; value?: unknown; error?: { code?: string; message?: string } } | null\n = await response.json().catch(() => null)\n if (!response.ok || parsed === null || parsed.ok !== true || parsed.value === undefined) {\n throw new KanbanApiError(\n parsed?.error?.code ?? 'http',\n parsed?.error?.message ?? `HTTP ${response.status}`,\n )\n }\n return parsed.value as T\n}\n\n/** One row of the host `skill.list` catalog (same shape DSH's chat autocomplete uses). */\nexport interface SkillOption {\n name: string\n description: string\n whenToUse?: string\n modelInvocable: boolean\n}\n\n/**\n * Call the host gateway's `skill.list` unary RPC over the same-origin HTTP\n * bridge (the exact wire shape DSH's chat input uses for its /-autocomplete).\n * The requested `sessionId` scopes the catalog to that session's agent\n * preset, which is where the filesystem skill roots are registered.\n */\nexport async function gatewaySkillList(sessionId: string): Promise<SkillOption[]> {\n let response: Response\n try {\n response = await fetch('/api/skill.list', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n type: 'client-request',\n rpcId: `task-kanban-${Math.random().toString(36).slice(2)}`,\n method: 'skill.list',\n payload: { sessionId },\n }),\n })\n } catch (error) {\n throw new KanbanApiError('network', error instanceof Error ? error.message : String(error))\n }\n const parsed: { type?: string; result?: { ok?: boolean; value?: { skills?: SkillOption[] }; error?: { code?: string; message?: string } } } | null\n = await response.json().catch(() => null)\n if (!response.ok || parsed === null || parsed.type !== 'server-response' || parsed.result?.ok !== true) {\n throw new KanbanApiError(\n parsed?.result?.error?.code ?? 'http',\n parsed?.result?.error?.message ?? `HTTP ${response.status}`,\n )\n }\n return parsed.result.value?.skills ?? []\n}\n\n/** One row of the host `agentPresets.list()` roster. */\nexport interface PresetOption {\n id: string\n name?: string\n description?: string\n}\n\n/** Reasoning efforts of one provider/model route (`reasoning.options`). */\nexport interface ReasoningOptions {\n efforts: Array<{ id: string; name: string; description?: string }>\n defaultEffort?: string\n /** Client-side marker: the fetch failed; the stored effort must be kept. */\n failed?: boolean\n}\n\nexport const api = {\n list: (workspacePath: string) => call<KanbanCard[]>('list', { workspacePath }),\n create: (workspacePath: string, requirement: string, model: string, provider?: string, skill?: string) =>\n call<KanbanCard>('create', { workspacePath, requirement, model, provider, ...(skill !== undefined && skill !== '' ? { skill } : {}) }),\n move: (cardId: string, toLane: Lane) => call<{ ok: boolean; message?: string }>('move', { cardId, toLane }),\n stop: (cardId: string) => call<{ ok: boolean; message?: string }>('stop', { cardId }),\n retry: (cardId: string) => call<{ ok: boolean; message?: string }>('retry', { cardId }),\n remove: (cardId: string) => call<{ ok: boolean; message?: string }>('remove', { cardId }),\n settingsGet: () => call<KanbanSettingsShape>('settings.get'),\n settingsSet: (patch: Partial<KanbanSettingsShape>) => call<KanbanSettingsShape>('settings.set', patch),\n models: () => call<ModelOption[]>('models.list'),\n presets: () => call<PresetOption[]>('presets.list'),\n reasoningOptions: (provider: string, model: string) =>\n call<ReasoningOptions>('reasoning.options', { provider, model }),\n}\n","import { useSyncExternalStore } from 'react'\nimport type { Context } from '@deepseek-ai/cordis'\ntype ClientContext = Context\n\n/**\n * HMR-safe shared state store keyed on the global symbol registry.\n *\n * The client-hmr hot swap re-evaluates this module (a fresh `lib/client.js`\n * bundle) WITHOUT reloading the page. A module-level singleton would then be\n * duplicated per evaluation: the session-navigation wrapper installed by the\n * OLD module copy keeps calling the OLD copy's `exitBoard`, which reads the\n * OLD copy's `boardOpen`/`boardDisposer` — while a board opened after the swap\n * writes the NEW copy's state cell. The wrapper would then no-op and clicking\n * a sidebar session would never leave the board.\n *\n * Keeping every field on one `globalThis`-backed record (via `Symbol.for`, so\n * all bundle copies resolve the same key) makes every copy read/write the same\n * state cell, so exit works regardless of which module copy performed the\n * enter.\n */\nconst STATE_KEY = Symbol.for('@fonlan/dsh-task-kanban/state')\n\ninterface KanbanState {\n client: ClientContext | null\n boardRoot: ((props: Record<string, unknown>) => React.ReactNode) | null\n boardDisposer: (() => void) | null\n boardOpen: boolean\n listeners: Set<() => void>\n}\n\nfunction getState(): KanbanState {\n const g = globalThis as unknown as Record<symbol, KanbanState | undefined>\n let state = g[STATE_KEY]\n if (state === undefined) {\n state = {\n client: null,\n boardRoot: null,\n boardDisposer: null,\n boardOpen: false,\n listeners: new Set(),\n }\n g[STATE_KEY] = state\n }\n return state\n}\n\nexport function setClient(ctx: ClientContext | null): void {\n getState().client = ctx\n}\nexport function getClient(): ClientContext | null {\n return getState().client\n}\n\nexport function setBoardRoot(component: (props: Record<string, unknown>) => React.ReactNode): void {\n getState().boardRoot = component\n}\nexport function getBoardRoot(): ((props: Record<string, unknown>) => React.ReactNode) | null {\n return getState().boardRoot\n}\n\nfunction notify(): void {\n for (const listener of getState().listeners) listener()\n}\nexport function isBoardOpen(): boolean {\n return getState().boardOpen\n}\nfunction subscribe(cb: () => void): () => void {\n const listeners = getState().listeners\n listeners.add(cb)\n return () => { listeners.delete(cb) }\n}\nexport function useBoardOpen(): boolean {\n return useSyncExternalStore(subscribe, isBoardOpen)\n}\n\nexport function enterBoard(): void {\n const state = getState()\n const ctx = state.client\n const root = state.boardRoot\n if (ctx === null || root === null || state.boardDisposer !== null) return\n try {\n // DSH 0.1.5 moved the replaceable shell from the legacy `conversation`\n // seat to `main.conversation`. Register through `inject` so the board\n // waits for that owner if the client plugin load order is reversed.\n state.boardDisposer = ctx.slots.inject('main.conversation', () =>\n ctx.slots.register({ name: 'main.conversation', priority: -1, locale: 'task-kanban' } as never, root as never),\n )\n state.boardOpen = true\n notify()\n } catch (error) {\n console.error('[@fonlan/dsh-task-kanban] cannot open the board:', error)\n }\n}\n\nexport function exitBoard(): void {\n const state = getState()\n if (state.boardDisposer !== null) {\n state.boardDisposer()\n state.boardDisposer = null\n }\n if (state.boardOpen) {\n state.boardOpen = false\n notify()\n }\n}\n\nexport function toggleBoard(): void {\n if (isBoardOpen()) exitBoard()\n else enterBoard()\n}\n\n/**\n * Route session navigation through the board: opening any session while the\n * board is open leaves the board first, so the conversation view can render\n * for the newly selected session.\n *\n * Every sidebar path that selects a session funnels through `sessions.open`\n * (session rows, search results, fork results, New Session) or\n * `sessions.openSubagent` (catalog children) — including re-clicking the\n * already-current session, which never changes `list.current` and therefore\n * cannot be caught by a list-store subscription. Wrapping the two entry\n * points covers all of them in one place.\n *\n * The wrapper is idempotent: a `kbBound` marker on the wrapper prevents a\n * second bind (plugin re-apply / HMR) from stacking another layer. Because the\n * wrapper's exit goes through the shared state store, a wrapper left over from\n * a previous (HMR-replaced) module copy still closes the board correctly.\n */\nexport function bindSessionNavigation(ctx: ClientContext): void {\n const sessions = ctx.sessions\n if (sessions === undefined) return\n\n const open = sessions.open\n if (typeof open === 'function' && (open as unknown as { kbBound?: boolean }).kbBound !== true) {\n const bound = ((id: Parameters<typeof open>[0]) => {\n exitBoard()\n return open.call(sessions, id)\n }) as typeof open\n ;(bound as unknown as { kbBound?: boolean }).kbBound = true\n sessions.open = bound\n }\n\n const openSubagent = sessions.openSubagent\n if (typeof openSubagent === 'function' && (openSubagent as unknown as { kbBound?: boolean }).kbBound !== true) {\n const bound = ((address: Parameters<typeof openSubagent>[0]) => {\n exitBoard()\n return openSubagent.call(sessions, address)\n }) as typeof openSubagent\n ;(bound as unknown as { kbBound?: boolean }).kbBound = true\n sessions.openSubagent = bound\n }\n}","// Locale namespace = the plugin's cordis id (kebab). DSH flat namespace keys\n// (settings + locale) use the internal id `task-kanban`; the scoped npm package\n// name is reserved for package-bound identifiers (exported `name`, module-loader\n// id, the /plugins/<pkg>/… route base).\nexport const LOCALE_NS = 'task-kanban'\n\nexport const zh = {\n kanban: '任务看板',\n openBoard: '打开任务看板',\n closeBoard: '返回对话',\n newTask: '新建任务',\n requirement: '需求',\n project: '所属项目',\n model: '模型',\n cancel: '取消',\n close: '关闭',\n addAndRefine: '添加并细化',\n laneDemand: '需求',\n laneQueue: '队列',\n laneRunning: '运行中',\n laneCompleted: '已完成',\n laneMerged: '已合并',\n statusDraft: '未细化',\n statusRefining: '细化中',\n statusPlanned: '已规划',\n statusQueued: '排队中',\n statusRunning: '实现中',\n statusCompleted: '已完成',\n statusMerging: '合并中',\n statusMerged: '已合并',\n statusError: '出错',\n phaseProgress: '{current}/{total}',\n retry: '重试',\n stop: '停止',\n delete: '删除',\n deleteConfirm: '确定删除该任务?其 worktree 会被清理。',\n openSession: '打开会话',\n plan: '实现方案',\n summary: '摘要',\n phases: '阶段',\n phaseGoal: '目标',\n phaseSessions: '会话',\n phaseConclusion: '结论',\n phaseNum: 'phase {n}',\n error: '错误',\n requirementLabel: '需求文本',\n requirementPlaceholder: '描述你要实现的需求……',\n skillGestureHint: '开头输入 /skill-name 可指定细化时使用的 Skill(如 /grill-me)',\n skillHint: '细化时将使用 Skill「{skill}」,并可与该会话交流打磨需求',\n skillMenuUserOnly: '仅用户可调',\n skillBadge: '细化 Skill:/{skill}',\n modelPlaceholder: '(使用默认模型)',\n noWorkspace: '没有可用工作区,请先在侧边栏添加',\n refresh: '刷新',\n settingsTitle: '任务看板',\n settingsDescription: '全局并行 worker 数与各会话类型的默认模型、推理强度、Agent 预设',\n settingsExpand: '展开设置',\n settingsCollapse: '收起设置',\n maxParallelWorkers: '全局并行 worker 数',\n settingsRefineGroup: '细化需求会话默认',\n settingsPhaseGroup: 'Phase 实现会话默认',\n reasoningEffort: '推理强度',\n agentPreset: 'Agent 预设',\n defaultOption: '(默认)',\n saved: '已保存',\n loadError: '加载任务失败',\n noPlan: '该卡片还没有实现计划,请先点击\"添加并细化\"',\n dragNoPlan: '还没有实现计划的卡片不能进入队列',\n actionFailed: '操作失败:{message}',\n sessionNotListed: '该会话不在当前列表,可能来自其他工作区',\n interrupted: '已中断',\n noBaseBranch: '项目没有 main/master 分支',\n worktreeFailed: 'worktree 创建失败',\n refineFailed: '细化失败',\n phaseFailed: '阶段执行失败',\n mergeFailed: '合并失败',\n createFailed: '创建失败',\n refinementLabel: '需求细化',\n mergeLabel: '合并',\n commitLabel: '提交',\n}\n\nexport const en = {\n kanban: 'Task Kanban',\n openBoard: 'Open Task Kanban',\n closeBoard: 'Back to chat',\n newTask: 'New Task',\n requirement: 'Requirement',\n project: 'Project',\n model: 'Model',\n cancel: 'Cancel',\n close: 'Close',\n addAndRefine: 'Add & Refine',\n laneDemand: 'Demands',\n laneQueue: 'Queue',\n laneRunning: 'Running',\n laneCompleted: 'Completed',\n laneMerged: 'Merged',\n statusDraft: 'Unrefined',\n statusRefining: 'Refining',\n statusPlanned: 'Planned',\n statusQueued: 'Queued',\n statusRunning: 'Running',\n statusCompleted: 'Completed',\n statusMerging: 'Merging',\n statusMerged: 'Merged',\n statusError: 'Error',\n phaseProgress: '{current}/{total}',\n retry: 'Retry',\n stop: 'Stop',\n delete: 'Delete',\n deleteConfirm: 'Delete this task? Its worktree will be cleaned up.',\n openSession: 'Open session',\n plan: 'Plan',\n summary: 'Summary',\n phases: 'Phases',\n phaseGoal: 'Goal',\n phaseSessions: 'Sessions',\n phaseConclusion: 'Conclusion',\n phaseNum: 'Phase {n}',\n error: 'Error',\n requirementLabel: 'Requirement',\n requirementPlaceholder: 'Describe what you want to build…',\n skillGestureHint: 'Start with /skill-name to choose the refinement skill (e.g. /grill-me)',\n skillHint: 'Refinement will use Skill “{skill}” — talk to this session to refine the requirement',\n skillMenuUserOnly: 'User-only',\n skillBadge: 'Refine skill: /{skill}',\n modelPlaceholder: '(use default model)',\n noWorkspace: 'No workspaces available — add one in the sidebar first',\n refresh: 'Refresh',\n settingsTitle: 'Task Kanban',\n settingsDescription: 'Global parallel workers and per-session-type defaults for model, reasoning effort, and agent preset',\n settingsExpand: 'Show settings',\n settingsCollapse: 'Hide settings',\n maxParallelWorkers: 'Global parallel workers',\n settingsRefineGroup: 'Refinement session defaults',\n settingsPhaseGroup: 'Phase implementation defaults',\n reasoningEffort: 'Reasoning effort',\n agentPreset: 'Agent preset',\n defaultOption: '(default)',\n saved: 'Saved',\n loadError: 'Failed to load tasks',\n noPlan: 'This card has no plan yet — click \"Add & Refine\" first',\n dragNoPlan: 'Cards without a plan cannot enter the queue',\n actionFailed: 'Action failed: {message}',\n sessionNotListed: 'Session is not listed here; it may belong to another workspace',\n interrupted: 'Interrupted',\n noBaseBranch: 'Project has no main/master branch',\n worktreeFailed: 'Worktree creation failed',\n refineFailed: 'Refinement failed',\n phaseFailed: 'Phase failed',\n mergeFailed: 'Merge failed',\n createFailed: 'Creation failed',\n refinementLabel: 'Refinement',\n mergeLabel: 'Merge',\n commitLabel: 'Commit',\n}","import { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport type { KanbanCard, Lane, PhaseAttempt, PlanPhase } from '../shared/card'\nimport { DROP_RULES, laneOf } from '../shared/lanes'\nimport { api, gatewaySkillList, type SkillOption } from './api'\nimport { exitBoard, getClient } from './kanban-state'\nimport { en, zh } from './locales'\nimport './board.css'\n\ninterface BoardProps {\n t?: (key: string, params?: Record<string, unknown>) => string\n useWorkspaces?: (selector: (state: unknown) => unknown) => unknown\n useSessions?: (selector: (state: unknown) => unknown) => unknown\n}\n\n/** Session-list snapshot shape the standard `useSessions` feed carries. */\ninterface SessionListStateLike {\n current?: string\n}\n\nfunction firstLine(text: string): string {\n return text.split('\\n').find((l) => l.trim() !== '')?.trim() ?? ''\n}\n\n/** The leading `/skill-name` gesture at the start of the requirement input. */\nconst SKILL_GESTURE = /^\\s*\\/([a-z0-9]+(?:-[a-z0-9]+)*)(?=\\s|$)/\n\n/** Extract a leading `/skill-name` token plus the cleaned remainder. */\nfunction parseSkillGesture(raw: string): { skill?: string; requirement: string } {\n const m = SKILL_GESTURE.exec(raw)\n if (m === null) return { requirement: raw.trim() }\n return { skill: m[1], requirement: raw.slice(m[0].length).trim() }\n}\n\nfunction statusLabel(card: KanbanCard, t: (key: string) => string): string {\n if (card.status === 'error') {\n switch (card.error?.kind) {\n case 'interrupted': return t('interrupted')\n case 'no_base_branch': return t('noBaseBranch')\n case 'worktree_failed': return t('worktreeFailed')\n case 'refine_failed': return t('refineFailed')\n case 'phase_failed': return t('phaseFailed')\n case 'merge_failed': return t('mergeFailed')\n default: return t('createFailed')\n }\n }\n switch (card.status) {\n case 'draft': return t('statusDraft')\n case 'refining': return t('statusRefining')\n case 'planned': return t('statusPlanned')\n case 'queued': return t('statusQueued')\n case 'running': return t('statusRunning')\n case 'completed': return t('statusCompleted')\n case 'merging': return t('statusMerging')\n case 'merged': return t('statusMerged')\n }\n}\n\ninterface WorkspaceRow {\n workspaceId: string\n path: string\n title: string\n}\n\nconst LANE_ORDER: Array<{ key: Lane; labelKey: string }> = [\n { key: 'demand', labelKey: 'laneDemand' },\n { key: 'queue', labelKey: 'laneQueue' },\n { key: 'running', labelKey: 'laneRunning' },\n { key: 'completed', labelKey: 'laneCompleted' },\n { key: 'merged', labelKey: 'laneMerged' },\n]\n\nconst WS_STORAGE_KEY = '@fonlan/dsh-task-kanban:workspace'\n\nexport function BoardRoot(props: BoardProps): JSX.Element {\n const t = props.t ?? ((key: string, params?: Record<string, unknown>) => {\n const dict = (zh as Record<string, string>)[key] ?? (en as Record<string, string>)[key]\n if (dict === undefined || params === undefined) return dict ?? key\n return dict.replace(/\\{(\\w+)\\}/g, (m, name: string) => (params[name] as string) ?? m)\n })\n const useWorkspaces = props.useWorkspaces\n const wsState = useWorkspaces !== undefined ? (useWorkspaces((s: unknown) => s) as { items?: WorkspaceRow[] } | undefined) : undefined\n const items: WorkspaceRow[] = wsState?.items ?? []\n const useSessions = props.useSessions\n const sessionState = useSessions !== undefined ? (useSessions((s: unknown) => s) as SessionListStateLike | undefined) : undefined\n const currentSessionId = sessionState?.current\n\n const [selectedPath, setSelectedPath] = useState<string>(() => {\n try {\n return localStorage.getItem(WS_STORAGE_KEY) ?? ''\n } catch {\n return ''\n }\n })\n const [cards, setCards] = useState<KanbanCard[]>([])\n const [toast, setToast] = useState<string | null>(null)\n const [detailId, setDetailId] = useState<string | null>(null)\n const [newTaskOpen, setNewTaskOpen] = useState(false)\n\n useEffect(() => {\n if (selectedPath === '' && items.length > 0) {\n setSelectedPath(items[0].path)\n }\n }, [selectedPath, items])\n\n const showToast = useCallback((message: string) => {\n setToast(message)\n window.setTimeout(() => setToast(null), 3500)\n }, [])\n\n const refresh = useCallback(async () => {\n if (selectedPath === '') return\n try {\n setCards(await api.list(selectedPath))\n } catch (error) {\n showToast(error instanceof Error ? error.message : String(error))\n }\n }, [selectedPath, showToast])\n\n useEffect(() => {\n void refresh()\n const timer = window.setInterval(() => { void refresh() }, 2500)\n return () => window.clearInterval(timer)\n }, [refresh])\n\n const byLane = useMemo(() => {\n const map: Record<Lane, KanbanCard[]> = { demand: [], queue: [], running: [], completed: [], merged: [] }\n for (const card of cards) map[laneOf(card)].push(card)\n for (const lane of LANE_ORDER) map[lane.key].sort((a, b) => a.createdAt - b.createdAt)\n return map\n }, [cards])\n\n const handleDrop = useCallback(async (target: Lane, event: React.DragEvent) => {\n event.preventDefault()\n const cardId = event.dataTransfer.getData('text/plain')\n if (cardId === '') return\n const card = cards.find((c) => c.id === cardId)\n if (card === undefined) return\n if (target === 'queue' && card.status !== 'running' && card.plan === undefined) {\n showToast(t('dragNoPlan'))\n return\n }\n try {\n const result = await api.move(cardId, target)\n if (!result.ok) {\n showToast(t('actionFailed', { message: result.message ?? '' }))\n }\n void refresh()\n } catch (error) {\n showToast(error instanceof Error ? error.message : String(error))\n }\n }, [cards, refresh, showToast, t])\n\n const openSession = useCallback((sessionId: string) => {\n const ctx = getClient()\n exitBoard()\n if (ctx === null) return\n try {\n ;(ctx.sessions as { open(id: string): void }).open(sessionId)\n } catch {\n showToast(t('sessionNotListed'))\n }\n }, [showToast, t])\n\n if (items.length === 0) {\n return (\n <div className=\"kb-board kb-empty\">\n <div className=\"kb-empty-text\">{t('noWorkspace')}</div>\n </div>\n )\n }\n\n return (\n <div className=\"kb-board\">\n <div className=\"kb-board-header\">\n <div className=\"kb-board-title\">\n <select\n className=\"kb-ws-select\"\n value={selectedPath}\n onChange={(e) => {\n setSelectedPath(e.target.value)\n try {\n localStorage.setItem(WS_STORAGE_KEY, e.target.value)\n } catch {\n // storage unavailable\n }\n }}\n >\n {items.map((w) => (\n <option key={w.workspaceId} value={w.path}>{w.title}</option>\n ))}\n </select>\n <button type=\"button\" className=\"kb-btn kb-btn-primary\" onClick={() => setNewTaskOpen(true)}>{t('newTask')}</button>\n </div>\n </div>\n\n <div className=\"kb-lanes\">\n {LANE_ORDER.map((lane) => (\n <div\n key={lane.key}\n className={'kb-lane' + (DROP_RULES[lane.key].length > 0 ? ' kb-lane-drop' : '')}\n onDragOver={(e) => {\n if (DROP_RULES[lane.key].length > 0) e.preventDefault()\n }}\n onDrop={(e) => { void handleDrop(lane.key, e) }}\n >\n <div className=\"kb-lane-head\">\n <span className=\"kb-lane-title\">{t(lane.labelKey)}</span>\n <span className=\"kb-lane-count\">{byLane[lane.key].length}</span>\n </div>\n <div className=\"kb-lane-body\">\n {byLane[lane.key].map((card) => (\n <CardView\n key={card.id}\n card={card}\n t={t}\n onToggle={() => setDetailId(detailId === card.id ? null : card.id)}\n />\n ))}\n </div>\n </div>\n ))}\n </div>\n\n {toast !== null && <div className=\"kb-toast\">{toast}</div>}\n\n {newTaskOpen && (\n <NewTaskModal\n t={t}\n workspaces={items}\n selectedPath={selectedPath}\n sessionId={currentSessionId}\n onClose={() => setNewTaskOpen(false)}\n onCreated={() => {\n setNewTaskOpen(false)\n void refresh()\n }}\n onError={(message) => showToast(message)}\n />\n )}\n\n {detailId !== null && (\n <DetailPanel\n card={cards.find((c) => c.id === detailId) ?? null}\n t={t}\n onClose={() => setDetailId(null)}\n onChanged={() => {\n setDetailId(null)\n void refresh()\n }}\n onToast={showToast}\n onOpenSession={openSession}\n />\n )}\n </div>\n )\n}\n\ninterface CardViewProps {\n card: KanbanCard\n t: (key: string, params?: Record<string, unknown>) => string\n onToggle: () => void\n}\n\nfunction CardView({ card, t, onToggle }: CardViewProps): JSX.Element {\n const draggable = card.status === 'planned' || card.status === 'queued' || card.status === 'running'\n const title = card.plan !== undefined && card.plan.title !== '' ? card.plan.title : firstLine(card.requirement)\n const badge = statusLabel(card, t)\n const phaseInfo = card.status === 'running' || card.status === 'error'\n ? card.currentPhase < card.phaseCount ? t('phaseProgress', { current: card.currentPhase + 1, total: card.phaseCount }) : ''\n : ''\n return (\n <div\n className={'kb-card' + (card.status === 'error' ? ' kb-card-error' : '') + (card.status === 'running' ? ' kb-card-running' : '')}\n draggable={draggable}\n onDragStart={(e) => {\n e.dataTransfer.setData('text/plain', card.id)\n e.dataTransfer.effectAllowed = 'move'\n }}\n onClick={onToggle}\n >\n <div className=\"kb-card-title\">{title}</div>\n <div className=\"kb-card-meta\">\n <span className=\"kb-card-badge\">{badge}</span>\n {phaseInfo !== '' && <span className=\"kb-card-phase\">{phaseInfo}</span>}\n {card.skill !== undefined && card.skill !== '' && <span className=\"kb-card-skill\">/ {card.skill}</span>}\n {card.model !== '' && <span className=\"kb-card-model\">{card.model}</span>}\n </div>\n {card.status === 'error' && card.error !== undefined && (\n <div className=\"kb-card-error-text\">{card.error.message}</div>\n )}\n {card.status === 'running' && card.gitMode === true && (\n <div className=\"kb-card-worktree\">{card.worktreePath ?? ''}</div>\n )}\n </div>\n )\n}\n\ninterface NewTaskModalProps {\n t: (key: string, params?: Record<string, unknown>) => string\n workspaces: WorkspaceRow[]\n selectedPath: string\n /** Current session id, scoping the host skill.list catalog lookup. */\n sessionId?: string\n onClose: () => void\n onCreated: () => void\n onError: (message: string) => void\n}\n\n/** An in-progress leading `/query` token (menu-open state). */\nconst SKILL_MENU_RE = /^\\s*\\/([a-z0-9-]*)$/\n\nfunction NewTaskModal({ t, workspaces, selectedPath, sessionId, onClose, onCreated, onError }: NewTaskModalProps): JSX.Element {\n const [requirement, setRequirement] = useState('')\n const [project, setProject] = useState(selectedPath)\n const [model, setModel] = useState('')\n const [modelProvider, setModelProvider] = useState('')\n const [models, setModels] = useState<Array<{ provider: string; id: string; name?: string }>>([])\n const [skills, setSkills] = useState<SkillOption[]>([])\n const [menuQuery, setMenuQuery] = useState<string | null>(null)\n const [menuIndex, setMenuIndex] = useState(0)\n const [busy, setBusy] = useState(false)\n const textareaRef = useRef<HTMLTextAreaElement | null>(null)\n\n useEffect(() => {\n let alive = true\n api.models()\n .then((list) => { if (alive) setModels(list) })\n .catch(() => { if (alive) setModels([]) })\n return () => { alive = false }\n }, [])\n\n // Load the host skill catalog once (scoped to the current session), the\n // same feed DSH's chat input uses for its /-autocomplete.\n useEffect(() => {\n if (sessionId === undefined) return\n let alive = true\n gatewaySkillList(sessionId)\n .then((list) => { if (alive) setSkills(list) })\n .catch(() => { if (alive) setSkills([]) })\n return () => { alive = false }\n }, [sessionId])\n\n const matches = skills.filter((s) => s.name.startsWith(menuQuery ?? ''))\n const menuVisible = menuQuery !== null && matches.length > 0\n const selected = menuVisible ? matches[Math.min(menuIndex, matches.length - 1)] : undefined\n\n /** Re-evaluate the leading-token menu state from the current value + caret. */\n const syncMenu = (value: string, caret: number): void => {\n const head = value.slice(0, caret)\n const m = SKILL_MENU_RE.exec(head)\n if (m !== null) {\n setMenuQuery(m[1])\n setMenuIndex(0)\n return\n }\n setMenuQuery(null)\n }\n\n const selectSkill = (name: string): void => {\n const el = textareaRef.current\n const caret = el?.selectionStart ?? requirement.length\n const head = requirement.slice(0, caret)\n const m = SKILL_MENU_RE.exec(head)\n // Replace the leading `/query` token with `/name `.\n const inserted = `/${name} `\n const newValue = m !== null ? inserted + requirement.slice(caret) : inserted + requirement\n setRequirement(newValue)\n setMenuQuery(null)\n requestAnimationFrame(() => {\n const next = textareaRef.current\n if (next !== null) {\n next.focus()\n const pos = inserted.length\n next.setSelectionRange(pos, pos)\n }\n })\n }\n\n const onKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>): void => {\n if (!menuVisible) return\n if (e.key === 'ArrowDown') {\n e.preventDefault()\n setMenuIndex((i) => (i + 1) % matches.length)\n } else if (e.key === 'ArrowUp') {\n e.preventDefault()\n setMenuIndex((i) => (i - 1 + matches.length) % matches.length)\n } else if (e.key === 'Enter') {\n e.preventDefault()\n if (selected !== undefined) selectSkill(selected.name)\n } else if (e.key === 'Escape') {\n e.preventDefault()\n setMenuQuery(null)\n } else if (e.key === 'Tab') {\n e.preventDefault()\n if (selected !== undefined) selectSkill(selected.name)\n }\n }\n\n const submit = async (): Promise<void> => {\n if (requirement.trim() === '') {\n onError(t('requirementLabel') + ' ' + t('error'))\n return\n }\n if (project === '') {\n onError(t('noWorkspace'))\n return\n }\n const parsed = parseSkillGesture(requirement)\n setBusy(true)\n try {\n await api.create(project, parsed.requirement, model, modelProvider, parsed.skill)\n onCreated()\n } catch (error) {\n onError(error instanceof Error ? error.message : String(error))\n setBusy(false)\n }\n }\n\n const activeSkill = parseSkillGesture(requirement).skill\n\n return (\n <div className=\"kb-modal-backdrop\" onClick={busy ? undefined : onClose}>\n <div className=\"kb-modal\" onClick={(e) => e.stopPropagation()}>\n <div className=\"kb-modal-title\">{t('newTask')}</div>\n <label className=\"kb-field\">\n <span>{t('requirementLabel')}</span>\n <div className=\"kb-skill-menu-wrap\">\n <textarea\n ref={textareaRef}\n className=\"kb-input kb-textarea\"\n value={requirement}\n placeholder={t('requirementPlaceholder')}\n onChange={(e) => {\n const value = e.target.value\n setRequirement(value)\n syncMenu(value, e.target.selectionStart ?? value.length)\n }}\n onKeyDown={onKeyDown}\n />\n {menuVisible && (\n <ul className=\"kb-skill-menu\" role=\"listbox\">\n {matches.map((s, i) => (\n <li\n key={s.name}\n role=\"option\"\n aria-selected={i === menuIndex}\n className={'kb-skill-menu-item' + (i === menuIndex ? ' kb-skill-menu-item-active' : '')}\n onMouseDown={(e) => { e.preventDefault(); selectSkill(s.name) }}\n onMouseEnter={() => setMenuIndex(i)}\n >\n <span className=\"kb-skill-menu-name\">/{s.name}</span>\n <span className=\"kb-skill-menu-desc\">\n {s.modelInvocable ? s.description : `${t('skillMenuUserOnly')} · ${s.description}`}\n </span>\n </li>\n ))}\n </ul>\n )}\n </div>\n {activeSkill !== undefined ? (\n <span className=\"kb-field-hint kb-field-hint-skill\">{t('skillHint', { skill: activeSkill })}</span>\n ) : (\n <span className=\"kb-field-hint\">{t('skillGestureHint')}</span>\n )}\n </label>\n <label className=\"kb-field\">\n <span>{t('project')}</span>\n <select className=\"kb-input\" value={project} onChange={(e) => setProject(e.target.value)}>\n {workspaces.map((w) => (\n <option key={w.workspaceId} value={w.path}>{w.title}</option>\n ))}\n </select>\n </label>\n <label className=\"kb-field\">\n <span>{t('model')}</span>\n <select\n className=\"kb-input\"\n value={model}\n onChange={(e) => {\n const id = e.target.value\n setModel(id)\n const found = models.find((m) => m.id === id)\n setModelProvider(found?.provider ?? '')\n }}\n >\n <option value=\"\">{t('modelPlaceholder')}</option>\n {models.map((m) => (\n <option key={m.provider + '/' + m.id} value={m.id}>{m.name ?? m.id}</option>\n ))}\n </select>\n </label>\n <div className=\"kb-modal-actions\">\n <button type=\"button\" className=\"kb-btn\" onClick={onClose} disabled={busy}>{t('cancel')}</button>\n <button type=\"button\" className=\"kb-btn kb-btn-primary\" onClick={() => { void submit() }} disabled={busy}>\n {t('addAndRefine')}\n </button>\n </div>\n </div>\n </div>\n )\n}\n\ninterface DetailPanelProps {\n card: KanbanCard | null\n t: (key: string, params?: Record<string, unknown>) => string\n onClose: () => void\n onChanged: () => void\n onToast: (message: string) => void\n onOpenSession: (sessionId: string) => void\n}\n\nfunction DetailPanel({ card, t, onClose, onChanged, onToast, onOpenSession }: DetailPanelProps): JSX.Element | null {\n const [busy, setBusy] = useState(false)\n if (card === null) return null\n\n const act = async (fn: () => Promise<unknown>): Promise<void> => {\n setBusy(true)\n try {\n await fn()\n onChanged()\n } catch (error) {\n onToast(error instanceof Error ? error.message : String(error))\n setBusy(false)\n }\n }\n\n const remove = async (): Promise<void> => {\n if (!window.confirm(t('deleteConfirm'))) return\n await act(() => api.remove(card!.id))\n }\n\n return (\n <div className=\"kb-detail-panel\">\n <div className=\"kb-detail-panel-head\">\n <div className=\"kb-modal-title\">\n {card.plan !== undefined && card.plan.title !== '' ? card.plan.title : firstLine(card.requirement)}\n <span className={'kb-detail-status kb-status-' + card.status}>{statusLabel(card, t)}</span>\n </div>\n <button type=\"button\" className=\"kb-detail-close\" aria-label={t('close')} title={t('close')} onClick={onClose}>✕</button>\n </div>\n\n <div className=\"kb-detail-panel-body\">\n <div className=\"kb-detail-section\">\n <div className=\"kb-detail-label\">{t('requirement')}</div>\n {card.skill !== undefined && card.skill !== '' && (\n <div className=\"kb-detail-skill\">{t('skillBadge', { skill: card.skill })}</div>\n )}\n <div className=\"kb-detail-text\">{card.requirement}</div>\n </div>\n\n {card.plan !== undefined && (\n <div className=\"kb-detail-section\">\n <div className=\"kb-detail-label\">{t('plan')}</div>\n <div className=\"kb-detail-plan\">\n <div className=\"kb-plan-summary\">{card.plan.summary}</div>\n <ol className=\"kb-plan-phases\">\n {card.plan.phases.map((phase, i) => (\n <PhaseCard\n key={phase.id}\n phase={phase}\n phaseIndex={i}\n card={card}\n t={t}\n onOpenSession={onOpenSession}\n />\n ))}\n </ol>\n </div>\n </div>\n )}\n\n {card.sessions.refinement.length > 0 && (\n <div className=\"kb-detail-section\">\n <div className=\"kb-detail-label\">{t('refinementLabel')}</div>\n <div className=\"kb-session-links\">\n {card.sessions.refinement.map((sid) => (\n <button key={sid} type=\"button\" className=\"kb-btn kb-btn-small\" onClick={() => onOpenSession(sid)}>\n {t('openSession')} · {sid.slice(0, 13)}\n </button>\n ))}\n </div>\n </div>\n )}\n\n {card.status === 'error' && card.error !== undefined && (\n <div className=\"kb-detail-section kb-detail-error\">\n <div className=\"kb-detail-label\">{t('error')}</div>\n <div className=\"kb-detail-text\">{card.error.message}</div>\n </div>\n )}\n\n {card.merge?.mergeCommit !== undefined && (\n <div className=\"kb-detail-section\">\n <div className=\"kb-detail-label\">{t('mergeLabel')}</div>\n <div className=\"kb-detail-text\">{t('commitLabel')} {card.merge.mergeCommit.slice(0, 12)}</div>\n </div>\n )}\n\n <div className=\"kb-modal-actions\">\n {card.status === 'error' && (\n <button type=\"button\" className=\"kb-btn kb-btn-primary\" disabled={busy} onClick={() => { void act(() => api.retry(card!.id)) }}>\n {t('retry')}\n </button>\n )}\n {(card.status === 'running' || card.status === 'merging') && (\n <button type=\"button\" className=\"kb-btn\" disabled={busy} onClick={() => { void act(() => api.stop(card!.id)) }}>\n {t('stop')}\n </button>\n )}\n {card.status !== 'running' && card.status !== 'refining' && card.status !== 'merging' && (\n <button type=\"button\" className=\"kb-btn kb-btn-danger\" disabled={busy} onClick={() => { void remove() }}>\n {t('delete')}\n </button>\n )}\n <button type=\"button\" className=\"kb-btn\" onClick={onClose}>{t('cancel')}</button>\n </div>\n </div>\n </div>\n )\n}\n\n/** One plan phase rendered as a card with goal / sessions / conclusion sections. */\nfunction PhaseCard({ phase, phaseIndex, card, t, onOpenSession }: {\n phase: PlanPhase\n phaseIndex: number\n card: KanbanCard\n t: (key: string, params?: Record<string, unknown>) => string\n onOpenSession: (sessionId: string) => void\n}): JSX.Element {\n const attempts = card.sessions.phases.filter((a) => a.phaseIndex === phaseIndex)\n const summaries = attempts\n .filter((a) => a.summary !== undefined && a.summary.trim() !== '')\n .map((a) => a.summary as string)\n const isCurrent = phaseIndex === card.currentPhase && card.status === 'running'\n // A phase with at least one concluded attempt counts as completed.\n const isCompleted = summaries.length > 0\n const cls = 'kb-plan-phase'\n + (isCurrent ? ' kb-plan-phase-current' : '')\n + (isCompleted ? ' kb-plan-phase-completed' : '')\n const planTitle = card.plan?.title ?? ''\n return (\n <li className={cls}>\n <div className=\"kb-plan-phase-head\">\n <span className=\"kb-plan-phase-title\">{phase.title}</span>\n <span className=\"kb-plan-phase-id\">{phase.id}</span>\n </div>\n <div className=\"kb-plan-phase-body\">\n <div className=\"kb-plan-phase-section\">\n <div className=\"kb-plan-phase-section-title\">{t('phaseGoal')}</div>\n <div className=\"kb-plan-phase-goal\">{phase.goal}</div>\n </div>\n {attempts.length > 0 && (\n <div className=\"kb-plan-phase-section\">\n <div className=\"kb-plan-phase-section-title\">{t('phaseSessions')}</div>\n <PhaseSessions attempts={attempts} planTitle={planTitle} phaseIndex={phaseIndex} t={t} onOpenSession={onOpenSession} />\n </div>\n )}\n {summaries.length > 0 && (\n <div className=\"kb-plan-phase-section\">\n <div className=\"kb-plan-phase-section-title\">{t('phaseConclusion')}</div>\n <div className=\"kb-plan-phase-conclusion\">\n {summaries.map((s, idx) => (\n <div key={idx} className=\"kb-plan-phase-conclusion-item\">{s}</div>\n ))}\n </div>\n </div>\n )}\n </div>\n </li>\n )\n}\n\n/**\n * Open-session buttons for one phase's attempts (retries append more). The\n * button labels the session with the deterministic \"{plan title} · phase N\"\n * format (the requested fallback when the host never auto-generated a title);\n * the full session id stays available as the native tooltip.\n */\nfunction PhaseSessions({ attempts, planTitle, phaseIndex, t, onOpenSession }: {\n attempts: PhaseAttempt[]\n planTitle: string\n phaseIndex: number\n t: (key: string, params?: Record<string, unknown>) => string\n onOpenSession: (sessionId: string) => void\n}): JSX.Element {\n return (\n <div className=\"kb-plan-phase-sessions\">\n {attempts.map((a) => (\n <button key={a.sessionId} type=\"button\" className=\"kb-btn kb-btn-small\" title={a.sessionId} onClick={() => onOpenSession(a.sessionId)}>\n {t('openSession')} · {planTitle !== '' ? `${planTitle} · ${t('phaseNum', { n: phaseIndex + 1 })}` : a.sessionId.slice(0, 13)}\n </button>\n ))}\n </div>\n )\n}","import { useCallback, useEffect, useRef, useState } from 'react'\nimport { IconChecklistOutline14 } from '@deepseek-ai/dsh-client-ui-primitives'\nimport { api, type PresetOption, type ReasoningOptions } from './api'\nimport { toggleBoard, useBoardOpen } from './kanban-state'\nimport type { KanbanSettingsShape, ModelOption } from '../shared/card'\n\ninterface FooterActionProps {\n wide?: boolean\n t?: (key: string) => string\n}\n\n/** Sidebar footer entry: toggles the kanban board main view.\n * The shell renders footer actions on their own row ABOVE the settings row,\n * so the button is reparented into the settings area to sit on the same\n * line, right of the Settings trigger (see board.css .kb-settings-row). */\nexport function KanbanFooterButton({ wide, t }: FooterActionProps): JSX.Element {\n const open = useBoardOpen()\n const ref = useRef<HTMLButtonElement | null>(null)\n const label = t !== undefined ? t(open ? 'closeBoard' : 'openBoard') : 'Task Kanban'\n\n useEffect(() => {\n const btn = ref.current\n if (btn === null) return\n let foot: Element | null = null\n let disposed = false\n\n // Locate the sidebar foot area WITHOUT moving any DOM: it is the ancestor\n // of the button whose LAST child contains the settings trigger and which\n // is itself the last child of its parent (root > footArea > settingsArea).\n // The slot machinery wraps entries in an anonymous div, so the search\n // tolerates extra wrapper levels. Styling is pure CSS (see board.css\n // .kb-foot-row), so a failed lookup only leaves the button in its natural\n // slot row — it can never vanish.\n const findFoot = (): Element | null => {\n let el: Element | null = btn.parentElement\n while (el !== null && el !== document.body && el.parentElement !== null) {\n const last = el.lastElementChild\n const hasTrigger =\n last !== null &&\n (last.querySelector?.('button[aria-haspopup=\"dialog\"]') !== null ||\n last.querySelector?.('button[aria-haspopup=\"menu\"]') !== null)\n const isLastChild = el.parentElement.lastElementChild === el\n if (hasTrigger && isLastChild) return el\n el = el.parentElement\n }\n return null\n }\n\n const tryApply = (): boolean => {\n if (foot === null) {\n try {\n foot = findFoot()\n } catch {\n foot = null\n }\n }\n if (foot === null) return false\n try {\n foot.classList.add('kb-foot-row')\n foot.classList.toggle('kb-foot-rail', wide !== true)\n } catch {\n // element already gone; retry will re-locate\n }\n return true\n }\n\n let applied = false\n try {\n applied = tryApply()\n } catch (error) {\n console.error('[@fonlan/dsh-task-kanban] foot placement error:', error)\n }\n if (!applied) {\n // The settings entry may mount later than this entry (plugin load\n // order); retry on DOM mutations AND on a short interval until the\n // foot area is found (give up after 15s).\n const observer = new MutationObserver(() => {\n if (disposed) return\n if (tryApply()) observer.disconnect()\n })\n observer.observe(document.body, { childList: true, subtree: true })\n const timer = window.setInterval(() => {\n if (disposed) {\n window.clearInterval(timer)\n return\n }\n if (tryApply()) {\n observer.disconnect()\n window.clearInterval(timer)\n }\n }, 1000)\n window.setTimeout(() => {\n observer.disconnect()\n window.clearInterval(timer)\n if (foot === null) {\n // report the DOM chain so the next report carries evidence\n const chain: string[] = []\n let el: Element | null = btn.parentElement\n while (el !== null && chain.length < 8) {\n chain.push(el.tagName + '.' + String(el.className).slice(0, 60))\n el = el.parentElement\n }\n console.warn('[@fonlan/dsh-task-kanban] could not locate the sidebar foot area:', chain)\n }\n }, 15000)\n return () => {\n disposed = true\n observer.disconnect()\n window.clearInterval(timer)\n try {\n foot?.classList.remove('kb-foot-row')\n foot?.classList.remove('kb-foot-rail')\n } catch {\n // already gone\n }\n }\n }\n return () => {\n try {\n foot?.classList.remove('kb-foot-row')\n foot?.classList.remove('kb-foot-rail')\n } catch {\n // already gone\n }\n }\n }, [wide])\n\n return (\n <button\n ref={ref}\n type=\"button\"\n className={'kb-footer-action' + (wide === true ? ' kb-footer-action-wide' : '') + (open ? ' kb-footer-action-active' : '')}\n aria-label={label}\n title={label}\n onClick={() => toggleBoard()}\n >\n <IconChecklistOutline14 size={wide === true ? 16 : 18} />\n {wide === true && <span className=\"kb-footer-label\">{t !== undefined ? t('kanban') : 'Kanban'}</span>}\n </button>\n )\n}\n\ninterface SettingsCardProps {\n t?: (key: string) => string\n}\n\n/** Fallback shape while settings load (server always resolves the full set). */\nconst EMPTY_SETTINGS: KanbanSettingsShape = {\n maxParallelWorkers: 1,\n refinementModel: '',\n refinementProvider: '',\n refinementReasoningEffort: '',\n refinementPreset: '',\n phaseModel: '',\n phaseProvider: '',\n phaseReasoningEffort: '',\n phasePreset: '',\n}\n\n/** The setting field names of one session-type group. */\nconst GROUP_FIELDS = {\n refinement: {\n model: 'refinementModel',\n provider: 'refinementProvider',\n effort: 'refinementReasoningEffort',\n preset: 'refinementPreset',\n },\n phase: {\n model: 'phaseModel',\n provider: 'phaseProvider',\n effort: 'phaseReasoningEffort',\n preset: 'phasePreset',\n },\n} as const\n\ntype SessionGroupKey = keyof typeof GROUP_FIELDS\n\ninterface SessionDefaultsGroupProps {\n kind: SessionGroupKey\n settings: KanbanSettingsShape\n models: ModelOption[]\n presets: PresetOption[]\n /** Reasoning efforts already fetched, keyed `provider/model`. */\n efforts: Record<string, ReasoningOptions>\n tr: (key: string) => string\n onCommit: (patch: Partial<KanbanSettingsShape>) => Promise<void>\n loadEfforts: (provider: string, model: string) => void\n}\n\n/** Model / reasoning effort / agent preset defaults for one session type. */\nfunction SessionDefaultsGroup({ kind, settings, models, presets, efforts, tr, onCommit, loadEfforts }: SessionDefaultsGroupProps): JSX.Element {\n const fields = GROUP_FIELDS[kind]\n const model = settings[fields.model]\n const provider = settings[fields.provider]\n const effort = settings[fields.effort]\n const preset = settings[fields.preset]\n const effortKey = model !== '' && provider !== '' ? `${provider}/${model}` : ''\n const effortOptions = effortKey !== '' ? (efforts[effortKey]?.efforts ?? []) : []\n\n const handleModel = (id: string): void => {\n const found = models.find((m) => m.id === id)\n const nextProvider = found?.provider ?? ''\n void onCommit({ [fields.model]: id, [fields.provider]: nextProvider } as Partial<KanbanSettingsShape>)\n if (id !== '' && nextProvider !== '') loadEfforts(nextProvider, id)\n }\n\n return (\n <div className=\"kb-settings-group\">\n <div className=\"kb-settings-group-title\">{tr(kind === 'refinement' ? 'settingsRefineGroup' : 'settingsPhaseGroup')}</div>\n <label className=\"kb-field kb-field-row\">\n <span>{tr('model')}</span>\n <select className=\"kb-input\" value={model} onChange={(e) => handleModel(e.target.value)}>\n <option value=\"\">{tr('defaultOption')}</option>\n {models.map((m) => (\n <option key={m.provider + '/' + m.id} value={m.id}>{m.name ?? m.id}</option>\n ))}\n </select>\n </label>\n <label className=\"kb-field kb-field-row\">\n <span>{tr('reasoningEffort')}</span>\n <select\n className=\"kb-input\"\n value={effort}\n disabled={model === ''}\n onChange={(e) => void onCommit({ [fields.effort]: e.target.value } as Partial<KanbanSettingsShape>)}\n >\n <option value=\"\">{tr('defaultOption')}</option>\n {effortOptions.map((o) => (\n <option key={o.id} value={o.id}>{o.name}</option>\n ))}\n </select>\n </label>\n <label className=\"kb-field kb-field-row\">\n <span>{tr('agentPreset')}</span>\n <select\n className=\"kb-input\"\n value={preset}\n onChange={(e) => void onCommit({ [fields.preset]: e.target.value } as Partial<KanbanSettingsShape>)}\n >\n <option value=\"\">{tr('defaultOption')}</option>\n {presets.map((p) => (\n <option key={p.id} value={p.id}>{p.name ?? p.id}</option>\n ))}\n </select>\n </label>\n </div>\n )\n}\n\n/** Plugin settings card (设置 → 插件配置): workers + default model + per-session-type defaults. */\nexport function KanbanSettingsCard({ t }: SettingsCardProps): JSX.Element {\n const tr = t ?? ((key: string) => key)\n // Card-local disclosure: collapsed by default, like the built-in plugin cards.\n const [open, setOpen] = useState(false)\n const [settings, setSettings] = useState<KanbanSettingsShape>(EMPTY_SETTINGS)\n const [models, setModels] = useState<ModelOption[]>([])\n const [presets, setPresets] = useState<PresetOption[]>([])\n const [efforts, setEfforts] = useState<Record<string, ReasoningOptions>>({})\n const [saved, setSaved] = useState(false)\n\n const commit = useCallback(async (patch: Partial<KanbanSettingsShape>): Promise<void> => {\n try {\n const next = await api.settingsSet(patch)\n setSettings(next)\n setSaved(true)\n window.setTimeout(() => setSaved(false), 1500)\n } catch {\n // keep the previous value\n }\n }, [])\n\n // Refs so async callbacks (effort fetch) read the freshest settings/commit.\n const settingsRef = useRef(settings)\n settingsRef.current = settings\n const commitRef = useRef(commit)\n commitRef.current = commit\n\n useEffect(() => {\n let alive = true\n api.settingsGet()\n .then((s) => { if (alive) setSettings(s) })\n .catch(() => undefined)\n api.models()\n .then((list) => { if (alive) setModels(list) })\n .catch(() => undefined)\n api.presets()\n .then((list) => { if (alive) setPresets(list) })\n .catch(() => undefined)\n return () => { alive = false }\n }, [])\n\n /** Reasoning routes already requested (dedupe across mount + model changes). */\n const effortFetched = useRef<Set<string>>(new Set())\n\n /**\n * Fetch the reasoning efforts of one model route (once per route). A failed\n * fetch is marked `failed` so the stored effort is preserved (see the reset\n * effect below) instead of being wiped by a transient error.\n */\n const loadEfforts = useCallback((provider: string, model: string): void => {\n const key = `${provider}/${model}`\n if (effortFetched.current.has(key)) return\n effortFetched.current.add(key)\n api.reasoningOptions(provider, model)\n .then((opts) => setEfforts((prev) => ({ ...prev, [key]: opts })))\n .catch(() => setEfforts((prev) => ({ ...prev, [key]: { efforts: [], failed: true } })))\n }, [])\n\n // Fetch efforts for the models already configured in settings, so the\n // effort selects show their levels (and validate the stored effort) as soon\n // as settings load — the fetch previously ran only on model change.\n useEffect(() => {\n for (const kind of ['refinement', 'phase'] as const) {\n const fields = GROUP_FIELDS[kind]\n const model = settings[fields.model]\n const provider = settings[fields.provider]\n if (model !== '' && provider !== '') loadEfforts(provider, model)\n }\n }, [settings.refinementModel, settings.refinementProvider, settings.phaseModel, settings.phaseProvider, loadEfforts])\n\n // When the efforts of the selected model arrive, drop a stored effort the\n // model no longer offers (e.g. the user switched the model). A failed fetch\n // (`failed: true`) keeps the stored effort: the model may just be\n // temporarily unresolvable, and wiping the user's choice would be worse.\n useEffect(() => {\n const s = settingsRef.current\n for (const kind of ['refinement', 'phase'] as const) {\n const fields = GROUP_FIELDS[kind]\n const model = s[fields.model]\n const provider = s[fields.provider]\n const effort = s[fields.effort]\n if (effort === '' || model === '' || provider === '') continue\n const opts = efforts[`${provider}/${model}`]\n if (opts === undefined || opts.failed === true) continue\n if (!opts.efforts.some((o) => o.id === effort)) {\n void commitRef.current({ [fields.effort]: '' } as Partial<KanbanSettingsShape>)\n }\n }\n }, [efforts])\n\n return (\n <li className=\"kb-settings-card\" data-open={open ? '' : undefined}>\n <button\n type=\"button\"\n className=\"kb-settings-card-head\"\n aria-expanded={open}\n aria-label={(open ? tr('settingsCollapse') : tr('settingsExpand')) + ':' + tr('settingsTitle')}\n onClick={() => setOpen(!open)}\n >\n <span className=\"kb-settings-card-headText\">\n <span className=\"kb-settings-card-title\">{tr('settingsTitle')}</span>\n <span className=\"kb-settings-card-sub\">{tr('settingsDescription')}</span>\n </span>\n <span className=\"kb-settings-card-chevron\" data-open={open ? '' : undefined} aria-hidden=\"true\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\">\n <path d=\"M3.5 5.25L7 8.75L10.5 5.25\" stroke=\"currentColor\" strokeWidth=\"1.5\"\n strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n </span>\n </button>\n {open && (\n <div className=\"kb-settings-card-body\">\n <div className=\"kb-settings\">\n <label className=\"kb-field kb-field-row\">\n <span>{tr('maxParallelWorkers')}</span>\n <input\n className=\"kb-input kb-input-number\"\n type=\"number\"\n min={1}\n step={1}\n value={settings.maxParallelWorkers}\n onChange={(e) => {\n const value = Math.max(1, Math.floor(Number(e.target.value) || 1))\n setSettings((s) => ({ ...s, maxParallelWorkers: value }))\n void commit({ maxParallelWorkers: value })\n }}\n />\n </label>\n <SessionDefaultsGroup\n kind=\"refinement\"\n settings={settings}\n models={models}\n presets={presets}\n efforts={efforts}\n tr={tr}\n onCommit={commit}\n loadEfforts={loadEfforts}\n />\n <SessionDefaultsGroup\n kind=\"phase\"\n settings={settings}\n models={models}\n presets={presets}\n efforts={efforts}\n tr={tr}\n onCommit={commit}\n loadEfforts={loadEfforts}\n />\n {saved && <div className=\"kb-settings-saved\">{tr('saved')}</div>}\n </div>\n </div>\n )}\n </li>\n )\n}","import type { Context } from '@deepseek-ai/cordis'\ntype ClientContext = Context\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-sidebar/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-layout/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-settings-plugins/client'\nimport { BoardRoot } from './board'\nimport { KanbanFooterButton, KanbanSettingsCard } from './sections'\nimport { LOCALE_NS, zh, en } from './locales'\nimport { setClient, setBoardRoot, bindSessionNavigation } from './kanban-state'\n\n/** Services required before mounting (provided by the client runtime). */\nexport const inject = ['slots', 'sessions', 'workspaces', 'locale']\n\n/** Client plugin body. */\nexport function apply(ctx: ClientContext): void {\n setClient(ctx)\n setBoardRoot(BoardRoot as unknown as (props: Record<string, unknown>) => React.ReactNode)\n // Selecting a session while the board is open must leave the board first.\n bindSessionNavigation(ctx)\n\n // Live translate bound to the active locale (labels re-read it per call).\n const t = ctx.locale.bind(LOCALE_NS)\n ctx.effect(() => {\n const off = ctx.locale.register(LOCALE_NS, { zh, en })\n return () => off()\n }, 'task-kanban: dictionaries')\n\n ctx.slots.inject('sidebar.footer.action', () =>\n ctx.slots.register({\n name: 'sidebar.footer.action',\n id: 'task-kanban',\n order: 60,\n label: () => t('kanban'),\n locale: LOCALE_NS,\n }, KanbanFooterButton as never),\n )\n\n // The plugin's own Settings Card (设置 → 插件配置) rides the `task-kanban`\n // settings namespace: registering into the keyed `settings.plugin.item` slot\n // with the namespace string makes the configurable-plugins tab dispatch the\n // card next to the built-in ones (bash / agent loop / web search).\n ctx.slots.inject('settings.plugin.item', () =>\n ctx.slots.register({\n name: 'settings.plugin.item',\n key: 'task-kanban',\n locale: LOCALE_NS,\n }, KanbanSettingsCard as never),\n )\n}\n"],"mappings":"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"}
package/lib/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import z from "@deepseek-ai/schemastery";
2
- import { settingsNamespace } from "@deepseek-ai/dsh-settings";
3
2
  import { randomUUID } from "node:crypto";
4
3
  import { appendFile, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
5
4
  import { join } from "node:path";
@@ -10,7 +9,7 @@ import { promisify } from "node:util";
10
9
  import { isSkillName, isUserInvocable, renderSkillContent } from "@deepseek-ai/dsh-skill";
11
10
  import { defineTool } from "@deepseek-ai/dsh-tools";
12
11
  //#region src/server/settings.ts
13
- const KANBAN_SETTINGS_NS = settingsNamespace("task-kanban");
12
+ const KANBAN_SETTINGS_NS = "task-kanban";
14
13
  const KanbanSettingsSchema = z.object({
15
14
  maxParallelWorkers: z.natural().min(1).default(1),
16
15
  refinementModel: z.string().default(""),
@@ -800,6 +799,11 @@ var KanbanRunner = class {
800
799
  } catch (error) {
801
800
  console.error("[task-kanban] register kanban tools for " + agent.id + " failed:", String(error));
802
801
  }
802
+ try {
803
+ await this.settleResumedPhaseTitle(agent.id, cardId);
804
+ } catch (error) {
805
+ console.error("[task-kanban] settle resumed phase title failed:", String(error));
806
+ }
803
807
  }
804
808
  async listCards(workspacePath) {
805
809
  return this.store.list(workspacePath);
@@ -1064,15 +1068,6 @@ var KanbanRunner = class {
1064
1068
  if (!interactive) {
1065
1069
  reply.dispose();
1066
1070
  settlement.dispose();
1067
- try {
1068
- const tail = handle.agent.session.events.slice(-10).map((e) => ({
1069
- type: e.type,
1070
- ...e.data !== void 0 ? { data: JSON.stringify(e.data).slice(0, 600) } : {}
1071
- }));
1072
- console.error("[task-kanban] refinement session tail:", JSON.stringify(tail, null, 1));
1073
- } catch (error) {
1074
- console.error("[task-kanban] could not read session events:", String(error));
1075
- }
1076
1071
  await this.fail(cardId, "refine_failed", "细化会话结束但未通过 kanban_write_plan 写回计划", "demand");
1077
1072
  return;
1078
1073
  }
@@ -1334,6 +1329,7 @@ var KanbanRunner = class {
1334
1329
  plan
1335
1330
  }, phaseIndex, workdir)));
1336
1331
  await handle.agent.whenIdle();
1332
+ await this.settlePhaseTitle(sessionId, plan, phaseIndex);
1337
1333
  if (entry.stopped) {
1338
1334
  await this.store.mutate(cardId, (c) => {
1339
1335
  c.status = "queued";
@@ -1359,6 +1355,48 @@ var KanbanRunner = class {
1359
1355
  return false;
1360
1356
  }
1361
1357
  }
1358
+ /**
1359
+ * Deterministic title fallback for phase implementation sessions. The host
1360
+ * session-title service (`dsh-session-title-first-prompt-llm`) generates an
1361
+ * LLM title for every fresh non-fork session whose first user message is
1362
+ * human-kind; when it lands, `session/title` carries a `provider` source and
1363
+ * we keep it. Kanban sessions are routed through the `model-router` provider
1364
+ * by default and their LLM title call never settles there (observed: only
1365
+ * the deterministic prompt-prefix fallback "你是一个软件实现工程师。请…"
1366
+ * ever lands). Once the phase session is over — or a persisted session is
1367
+ * reopened — no LLM title is coming, so a still-`fallback` title is renamed
1368
+ * to "{plan title} · phase N": the display format requested as the fallback
1369
+ * when auto-generation is unavailable. Best-effort only: a missing title
1370
+ * service, session, or a rename failure must never break the card flow.
1371
+ */
1372
+ async settlePhaseTitle(sessionId, plan, phaseIndex) {
1373
+ const sessionTitle = this.ctx.get("sessionTitle");
1374
+ if (sessionTitle === void 0) return;
1375
+ const session = this.ctx.get("sessions")?.get(sessionId);
1376
+ if (session === void 0) return;
1377
+ const snapshot = sessionTitle.get(session);
1378
+ if (snapshot === void 0 || snapshot.source.kind !== "fallback") return;
1379
+ const phase = plan.phases[phaseIndex];
1380
+ const title = `${plan.title.trim() !== "" ? plan.title : phase?.title ?? ""} · phase ${phaseIndex + 1}`;
1381
+ try {
1382
+ sessionTitle.rename(session, title);
1383
+ } catch (error) {
1384
+ console.error("[task-kanban] settle phase session title failed:", String(error));
1385
+ }
1386
+ }
1387
+ /**
1388
+ * Title fallback for phase sessions that are reopened after their work
1389
+ * ended (completion, interruption, or a host restart). A live phase card is
1390
+ * skipped: its session may still be generating the LLM title.
1391
+ */
1392
+ async settleResumedPhaseTitle(sessionId, cardId) {
1393
+ const card = await this.findCard(cardId);
1394
+ if (card === void 0 || card.plan === void 0) return;
1395
+ if (card.status === "running" || card.status === "refining" || card.status === "merging") return;
1396
+ const attempt = card.sessions.phases.find((a) => a.sessionId === sessionId);
1397
+ if (attempt === void 0) return;
1398
+ await this.settlePhaseTitle(sessionId, card.plan, attempt.phaseIndex);
1399
+ }
1362
1400
  async resolveConflictsIn(cardId, wsPath, workdir, opts = {}) {
1363
1401
  const conflicts = await unmergedPaths(workdir);
1364
1402
  if (conflicts === "") return true;
@@ -1626,18 +1664,7 @@ var KanbanRunner = class {
1626
1664
  await handle.agent.whenIdle();
1627
1665
  const done = this.completed.get(sessionId) === true;
1628
1666
  this.completed.delete(sessionId);
1629
- if (!done) {
1630
- try {
1631
- const tail = handle.agent.session.events.slice(-6).map((e) => ({
1632
- type: e.type,
1633
- ...e.data !== void 0 ? { data: JSON.stringify(e.data).slice(0, 500) } : {}
1634
- }));
1635
- console.error("[task-kanban] merge session tail:", JSON.stringify(tail, null, 1));
1636
- } catch (error) {
1637
- console.error("[task-kanban] merge session events unreadable:", String(error));
1638
- }
1639
- await this.fail(cardId, "merge_failed", "合并会话结束但未调用 kanban_merge_resolved 声明解决", "completed");
1640
- }
1667
+ if (!done) await this.fail(cardId, "merge_failed", "合并会话结束但未调用 kanban_merge_resolved 声明解决", "completed");
1641
1668
  return done;
1642
1669
  } catch (error) {
1643
1670
  await this.fail(cardId, "merge_failed", `合并会话失败: ${String(error)}`, "completed");
@@ -135,6 +135,27 @@ export declare class KanbanRunner implements KanbanToolResolver {
135
135
  private modelRoute;
136
136
  private runCard;
137
137
  private runPhase;
138
+ /**
139
+ * Deterministic title fallback for phase implementation sessions. The host
140
+ * session-title service (`dsh-session-title-first-prompt-llm`) generates an
141
+ * LLM title for every fresh non-fork session whose first user message is
142
+ * human-kind; when it lands, `session/title` carries a `provider` source and
143
+ * we keep it. Kanban sessions are routed through the `model-router` provider
144
+ * by default and their LLM title call never settles there (observed: only
145
+ * the deterministic prompt-prefix fallback "你是一个软件实现工程师。请…"
146
+ * ever lands). Once the phase session is over — or a persisted session is
147
+ * reopened — no LLM title is coming, so a still-`fallback` title is renamed
148
+ * to "{plan title} · phase N": the display format requested as the fallback
149
+ * when auto-generation is unavailable. Best-effort only: a missing title
150
+ * service, session, or a rename failure must never break the card flow.
151
+ */
152
+ private settlePhaseTitle;
153
+ /**
154
+ * Title fallback for phase sessions that are reopened after their work
155
+ * ended (completion, interruption, or a host restart). A live phase card is
156
+ * skipped: its session may still be generating the LLM title.
157
+ */
158
+ private settleResumedPhaseTitle;
138
159
  private resolveConflictsIn;
139
160
  /** Check resolved files for leftover conflict markers. */
140
161
  private verifyConflictMarkers;
@@ -8,7 +8,7 @@
8
8
  import type { Context } from '@deepseek-ai/cordis';
9
9
  import z from '@deepseek-ai/schemastery';
10
10
  import type { KanbanSessionKind, KanbanSettingsShape } from '../shared/card.js';
11
- export declare const KANBAN_SETTINGS_NS: import("@deepseek-ai/dsh-settings").SettingsNamespace;
11
+ export declare const KANBAN_SETTINGS_NS = "task-kanban";
12
12
  export declare const KanbanSettingsSchema: z<Schemastery.ObjectS<{
13
13
  maxParallelWorkers: z<number, number>;
14
14
  refinementModel: z<string, string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fonlan/dsh-task-kanban",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "DSH web plugin: a task kanban that refines requirements into phased plans, auto-executes them in git worktrees (one session per phase), and auto-merges into the main branch on completion.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -37,7 +37,8 @@
37
37
  "src",
38
38
  "cordis.patch.yml",
39
39
  "README.md",
40
- "LICENSE"
40
+ "LICENSE",
41
+ "docs/screenshots"
41
42
  ],
42
43
  "publishConfig": {
43
44
  "access": "public"
@@ -60,34 +61,41 @@
60
61
  "client": {
61
62
  "platform": "web",
62
63
  "inject": [
63
- "@deepseek-ai/dsh-client-runtime",
64
+ "@deepseek-ai/dsh-api-session-controller",
64
65
  "@deepseek-ai/dsh-client-locale",
65
66
  "@deepseek-ai/dsh-client-ui-slots",
66
67
  "@deepseek-ai/dsh-client-ui-primitives",
68
+ "@deepseek-ai/dsh-client-ui-renderer",
69
+ "@deepseek-ai/dsh-client-ui-session",
67
70
  "@deepseek-ai/dsh-client-ui-sidebar",
68
71
  "@deepseek-ai/dsh-client-ui-layout",
72
+ "@deepseek-ai/dsh-client-ui-workspace",
69
73
  "@deepseek-ai/dsh-client-ui-conversation",
70
74
  "@deepseek-ai/dsh-client-ui-settings",
71
- "@deepseek-ai/dsh-client-ui-settings-plugins",
72
- "@deepseek-ai/dsh-client-web-react"
75
+ "@deepseek-ai/dsh-client-ui-settings-plugins"
73
76
  ]
74
77
  }
75
78
  },
76
79
  "peerDependencies": {
77
- "@deepseek-ai/cordis": "^4.0.1",
78
- "@deepseek-ai/dsh-agent": "^0.1.0-rc.6",
79
- "@deepseek-ai/dsh-agent-presets": "^0.1.0-rc.6",
80
- "@deepseek-ai/dsh-session": "^0.1.0-rc.6",
81
- "@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
82
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
83
- "@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
84
- "@deepseek-ai/dsh-skill": "^0.1.0-rc.6",
85
- "@deepseek-ai/schemastery": "^3.18.1",
86
- "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
87
- "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6",
88
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
89
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.6",
90
- "@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.0-rc.6",
80
+ "@deepseek-ai/cordis": "^4.0.2",
81
+ "@deepseek-ai/dsh-agent": "^0.1.5-rc.2",
82
+ "@deepseek-ai/dsh-agent-presets": "^0.1.5-rc.2",
83
+ "@deepseek-ai/dsh-session": "^0.1.5-rc.2",
84
+ "@deepseek-ai/dsh-tools": "^0.1.5-rc.2",
85
+ "@deepseek-ai/dsh-llm": "^0.1.5-rc.2",
86
+ "@deepseek-ai/dsh-settings": "^0.1.5-rc.2",
87
+ "@deepseek-ai/dsh-skill": "^0.1.5-rc.2",
88
+ "@deepseek-ai/schemastery": "^3.18.2",
89
+ "@deepseek-ai/dsh-api-session-controller": "^0.1.5-rc.2",
90
+ "@deepseek-ai/dsh-client-locale": "^0.1.5-rc.2",
91
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-rc.2",
92
+ "@deepseek-ai/dsh-client-ui-layout": "^0.1.5-rc.2",
93
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-rc.2",
94
+ "@deepseek-ai/dsh-client-ui-session": "^0.1.5-rc.2",
95
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.2",
96
+ "@deepseek-ai/dsh-client-ui-workspace": "^0.1.5-rc.2",
97
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-rc.2",
98
+ "@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.5-rc.2",
91
99
  "react": "^18.2.0",
92
100
  "react-dom": "^18.2.0"
93
101
  },
@@ -97,20 +105,27 @@
97
105
  "@types/react-dom": "~18.3.0",
98
106
  "tsdown": "^0.22.14",
99
107
  "typescript": "^5.9.0",
100
- "@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.6",
101
- "@deepseek-ai/dsh-agent-presets": "^0.1.0-rc.6",
102
- "@deepseek-ai/dsh-agent": "^0.1.0-rc.6",
103
- "@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
104
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
105
- "@deepseek-ai/dsh-session": "^0.1.0-rc.6",
106
- "@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
107
- "@deepseek-ai/dsh-skill": "^0.1.0-rc.6",
108
- "@deepseek-ai/schemastery": "^3.18.1",
109
- "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6",
110
- "@deepseek-ai/dsh-client-ui-sidebar": "^0.1.0-rc.6",
111
- "@deepseek-ai/dsh-client-ui-layout": "^0.1.0-rc.6",
112
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.6",
113
- "@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.0-rc.6",
108
+ "@deepseek-ai/dsh-host-webserver": "^0.1.5-rc.2",
109
+ "@deepseek-ai/dsh-agent-presets": "^0.1.5-rc.2",
110
+ "@deepseek-ai/dsh-agent": "^0.1.5-rc.2",
111
+ "@deepseek-ai/dsh-tools": "^0.1.5-rc.2",
112
+ "@deepseek-ai/dsh-llm": "^0.1.5-rc.2",
113
+ "@deepseek-ai/dsh-session": "^0.1.5-rc.2",
114
+ "@deepseek-ai/dsh-settings": "^0.1.5-rc.2",
115
+ "@deepseek-ai/dsh-skill": "^0.1.5-rc.2",
116
+ "@deepseek-ai/schemastery": "^3.18.2",
117
+ "@deepseek-ai/dsh-api-session-controller": "^0.1.5-rc.2",
118
+ "@deepseek-ai/dsh-client-locale": "^0.1.5-rc.2",
119
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-rc.2",
120
+ "@deepseek-ai/dsh-client-ui-layout": "^0.1.5-rc.2",
121
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-rc.2",
122
+ "@deepseek-ai/dsh-client-ui-session": "^0.1.5-rc.2",
123
+ "@deepseek-ai/dsh-client-ui-sidebar": "^0.1.5-rc.2",
124
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.2",
125
+ "@deepseek-ai/dsh-client-ui-workspace": "^0.1.5-rc.2",
126
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-rc.2",
127
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.5-rc.2",
128
+ "@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.5-rc.2",
114
129
  "vitest": "^3.2.0"
115
130
  },
116
131
  "packageManager": "pnpm@11.21.0"
@@ -637,6 +637,7 @@ function PhaseCard({ phase, phaseIndex, card, t, onOpenSession }: {
637
637
  const cls = 'kb-plan-phase'
638
638
  + (isCurrent ? ' kb-plan-phase-current' : '')
639
639
  + (isCompleted ? ' kb-plan-phase-completed' : '')
640
+ const planTitle = card.plan?.title ?? ''
640
641
  return (
641
642
  <li className={cls}>
642
643
  <div className="kb-plan-phase-head">
@@ -651,7 +652,7 @@ function PhaseCard({ phase, phaseIndex, card, t, onOpenSession }: {
651
652
  {attempts.length > 0 && (
652
653
  <div className="kb-plan-phase-section">
653
654
  <div className="kb-plan-phase-section-title">{t('phaseSessions')}</div>
654
- <PhaseSessions attempts={attempts} t={t} onOpenSession={onOpenSession} />
655
+ <PhaseSessions attempts={attempts} planTitle={planTitle} phaseIndex={phaseIndex} t={t} onOpenSession={onOpenSession} />
655
656
  </div>
656
657
  )}
657
658
  {summaries.length > 0 && (
@@ -669,17 +670,24 @@ function PhaseCard({ phase, phaseIndex, card, t, onOpenSession }: {
669
670
  )
670
671
  }
671
672
 
672
- /** Open-session buttons for one phase's attempts (retries append more). */
673
- function PhaseSessions({ attempts, t, onOpenSession }: {
673
+ /**
674
+ * Open-session buttons for one phase's attempts (retries append more). The
675
+ * button labels the session with the deterministic "{plan title} · phase N"
676
+ * format (the requested fallback when the host never auto-generated a title);
677
+ * the full session id stays available as the native tooltip.
678
+ */
679
+ function PhaseSessions({ attempts, planTitle, phaseIndex, t, onOpenSession }: {
674
680
  attempts: PhaseAttempt[]
675
- t: (key: string) => string
681
+ planTitle: string
682
+ phaseIndex: number
683
+ t: (key: string, params?: Record<string, unknown>) => string
676
684
  onOpenSession: (sessionId: string) => void
677
685
  }): JSX.Element {
678
686
  return (
679
687
  <div className="kb-plan-phase-sessions">
680
688
  {attempts.map((a) => (
681
- <button key={a.sessionId} type="button" className="kb-btn kb-btn-small" onClick={() => onOpenSession(a.sessionId)}>
682
- {t('openSession')} · {a.sessionId.slice(0, 13)}
689
+ <button key={a.sessionId} type="button" className="kb-btn kb-btn-small" title={a.sessionId} onClick={() => onOpenSession(a.sessionId)}>
690
+ {t('openSession')} · {planTitle !== '' ? `${planTitle} · ${t('phaseNum', { n: phaseIndex + 1 })}` : a.sessionId.slice(0, 13)}
683
691
  </button>
684
692
  ))}
685
693
  </div>
@@ -1,7 +1,9 @@
1
- import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
1
+ import type { Context } from '@deepseek-ai/cordis'
2
+ type ClientContext = Context
2
3
  import type {} from '@deepseek-ai/dsh-client-locale/client'
3
4
  import type {} from '@deepseek-ai/dsh-client-ui-sidebar/client'
4
5
  import type {} from '@deepseek-ai/dsh-client-ui-layout/client'
6
+ import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
5
7
  import type {} from '@deepseek-ai/dsh-client-ui-settings-plugins/client'
6
8
  import { BoardRoot } from './board'
7
9
  import { KanbanFooterButton, KanbanSettingsCard } from './sections'
@@ -1,5 +1,6 @@
1
1
  import { useSyncExternalStore } from 'react'
2
- import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
2
+ import type { Context } from '@deepseek-ai/cordis'
3
+ type ClientContext = Context
3
4
 
4
5
  /**
5
6
  * HMR-safe shared state store keyed on the global symbol registry.
@@ -78,7 +79,12 @@ export function enterBoard(): void {
78
79
  const root = state.boardRoot
79
80
  if (ctx === null || root === null || state.boardDisposer !== null) return
80
81
  try {
81
- state.boardDisposer = ctx.slots.register({ name: 'conversation', priority: -1, locale: 'task-kanban' } as never, root as never)
82
+ // DSH 0.1.5 moved the replaceable shell from the legacy `conversation`
83
+ // seat to `main.conversation`. Register through `inject` so the board
84
+ // waits for that owner if the client plugin load order is reversed.
85
+ state.boardDisposer = ctx.slots.inject('main.conversation', () =>
86
+ ctx.slots.register({ name: 'main.conversation', priority: -1, locale: 'task-kanban' } as never, root as never),
87
+ )
82
88
  state.boardOpen = true
83
89
  notify()
84
90
  } catch (error) {
@@ -41,6 +41,7 @@ export const zh = {
41
41
  phaseGoal: '目标',
42
42
  phaseSessions: '会话',
43
43
  phaseConclusion: '结论',
44
+ phaseNum: 'phase {n}',
44
45
  error: '错误',
45
46
  requirementLabel: '需求文本',
46
47
  requirementPlaceholder: '描述你要实现的需求……',
@@ -116,6 +117,7 @@ export const en = {
116
117
  phaseGoal: 'Goal',
117
118
  phaseSessions: 'Sessions',
118
119
  phaseConclusion: 'Conclusion',
120
+ phaseNum: 'Phase {n}',
119
121
  error: 'Error',
120
122
  requirementLabel: 'Requirement',
121
123
  requirementPlaceholder: 'Describe what you want to build…',
@@ -4,6 +4,9 @@
4
4
  * of truth; en must carry the exact same key set (checked at runtime by the
5
5
  * locale registry).
6
6
  */
7
+ import type {} from '@deepseek-ai/dsh-api-session-controller/client'
8
+ import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
9
+ import type {} from '@deepseek-ai/dsh-client-ui-renderer/client'
7
10
  import type { zh } from './locales'
8
11
 
9
12
  declare module '@deepseek-ai/dsh-client-ui-slots' {
@@ -28,6 +28,12 @@ interface WorkspaceEntry {
28
28
  path: string
29
29
  }
30
30
 
31
+ /** Minimal structural face of the optional host `sessionTitle` service. */
32
+ interface SessionTitleFace {
33
+ get(session: Session): { source: { kind: string } } | undefined
34
+ rename(session: Session, title: string): unknown
35
+ }
36
+
31
37
  interface RunningEntry {
32
38
  cardId: string
33
39
  workspacePath: string
@@ -229,6 +235,15 @@ export class KanbanRunner implements KanbanToolResolver {
229
235
  } catch (error) {
230
236
  console.error('[task-kanban] register kanban tools for ' + agent.id + ' failed:', String(error))
231
237
  }
238
+ // Historical phase sessions reopened after their work ended (completion,
239
+ // interruption, host restart) keep a deterministic "plan · phase N" title
240
+ // when the host LLM title never landed. Fresh sessions are skipped here:
241
+ // their card is live ('running') and the LLM title may still be pending.
242
+ try {
243
+ await this.settleResumedPhaseTitle(agent.id, cardId)
244
+ } catch (error) {
245
+ console.error('[task-kanban] settle resumed phase title failed:', String(error))
246
+ }
232
247
  }
233
248
 
234
249
  // ── Card operations (RPC entry points) ────────────────────────────────────
@@ -493,17 +508,8 @@ export class KanbanRunner implements KanbanToolResolver {
493
508
  if (!interactive) {
494
509
  reply.dispose()
495
510
  settlement.dispose()
496
- // debug: log the session tail regardless
497
- try {
498
- const events = handle.agent.session.events
499
- const tail = events.slice(-10).map((e) => ({
500
- type: e.type,
501
- ...(e.data !== undefined ? { data: JSON.stringify(e.data).slice(0, 600) } : {}),
502
- }))
503
- console.error('[task-kanban] refinement session tail:', JSON.stringify(tail, null, 1))
504
- } catch (error) {
505
- console.error('[task-kanban] could not read session events:', String(error))
506
- }
511
+ // Session event history is no longer exposed by DSH 0.1.5. The card
512
+ // state remains the authoritative diagnostic surface for this failure.
507
513
  await this.fail(cardId, 'refine_failed', '细化会话结束但未通过 kanban_write_plan 写回计划', 'demand')
508
514
  return
509
515
  }
@@ -791,6 +797,10 @@ export class KanbanRunner implements KanbanToolResolver {
791
797
  entry.cancel = () => handle.agent.cancel({ kind: 'user' })
792
798
  handle.agent.followup(this.asUserMessage(phasePrompt({ ...(await this.store.get(wsPath, cardId)) ?? { plan } as KanbanCard, plan } as KanbanCard, phaseIndex, workdir)))
793
799
  await handle.agent.whenIdle()
800
+ // Deterministic title fallback: keep the host LLM title when it landed
801
+ // (provider-kind); otherwise rename to "{plan title} · phase N" so the
802
+ // session never shows the raw prompt prefix in the sidebar.
803
+ await this.settlePhaseTitle(sessionId, plan, phaseIndex)
794
804
  if (entry.stopped) {
795
805
  await this.store.mutate(cardId, (c) => {
796
806
  c.status = 'queued'
@@ -815,6 +825,52 @@ export class KanbanRunner implements KanbanToolResolver {
815
825
  }
816
826
  }
817
827
 
828
+ /**
829
+ * Deterministic title fallback for phase implementation sessions. The host
830
+ * session-title service (`dsh-session-title-first-prompt-llm`) generates an
831
+ * LLM title for every fresh non-fork session whose first user message is
832
+ * human-kind; when it lands, `session/title` carries a `provider` source and
833
+ * we keep it. Kanban sessions are routed through the `model-router` provider
834
+ * by default and their LLM title call never settles there (observed: only
835
+ * the deterministic prompt-prefix fallback "你是一个软件实现工程师。请…"
836
+ * ever lands). Once the phase session is over — or a persisted session is
837
+ * reopened — no LLM title is coming, so a still-`fallback` title is renamed
838
+ * to "{plan title} · phase N": the display format requested as the fallback
839
+ * when auto-generation is unavailable. Best-effort only: a missing title
840
+ * service, session, or a rename failure must never break the card flow.
841
+ */
842
+ private async settlePhaseTitle(sessionId: SessionId, plan: Plan, phaseIndex: number): Promise<void> {
843
+ const sessionTitle = this.ctx.get('sessionTitle') as SessionTitleFace | undefined
844
+ if (sessionTitle === undefined) return
845
+ const sessions = this.ctx.get('sessions') as { get(id: string): Session | undefined } | undefined
846
+ const session = sessions?.get(sessionId)
847
+ if (session === undefined) return
848
+ const snapshot = sessionTitle.get(session)
849
+ if (snapshot === undefined || snapshot.source.kind !== 'fallback') return
850
+ const phase = plan.phases[phaseIndex]
851
+ const base = plan.title.trim() !== '' ? plan.title : phase?.title ?? ''
852
+ const title = `${base} · phase ${phaseIndex + 1}`
853
+ try {
854
+ sessionTitle.rename(session, title)
855
+ } catch (error) {
856
+ console.error('[task-kanban] settle phase session title failed:', String(error))
857
+ }
858
+ }
859
+
860
+ /**
861
+ * Title fallback for phase sessions that are reopened after their work
862
+ * ended (completion, interruption, or a host restart). A live phase card is
863
+ * skipped: its session may still be generating the LLM title.
864
+ */
865
+ private async settleResumedPhaseTitle(sessionId: string, cardId: string): Promise<void> {
866
+ const card = await this.findCard(cardId)
867
+ if (card === undefined || card.plan === undefined) return
868
+ if (card.status === 'running' || card.status === 'refining' || card.status === 'merging') return
869
+ const attempt = card.sessions.phases.find((a) => a.sessionId === sessionId)
870
+ if (attempt === undefined) return
871
+ await this.settlePhaseTitle(sessionId as SessionId, card.plan, attempt.phaseIndex)
872
+ }
873
+
818
874
  private async resolveConflictsIn(
819
875
  cardId: string,
820
876
  wsPath: string,
@@ -1046,16 +1102,8 @@ export class KanbanRunner implements KanbanToolResolver {
1046
1102
  const done = this.completed.get(sessionId) === true
1047
1103
  this.completed.delete(sessionId)
1048
1104
  if (!done) {
1049
- try {
1050
- const events = handle.agent.session.events
1051
- const tail = events.slice(-6).map((e) => ({
1052
- type: e.type,
1053
- ...(e.data !== undefined ? { data: JSON.stringify(e.data).slice(0, 500) } : {}),
1054
- }))
1055
- console.error('[task-kanban] merge session tail:', JSON.stringify(tail, null, 1))
1056
- } catch (error) {
1057
- console.error('[task-kanban] merge session events unreadable:', String(error))
1058
- }
1105
+ // DSH 0.1.5 no longer exposes session event history; persist the
1106
+ // actionable failure on the card instead of relying on a debug tail.
1059
1107
  await this.fail(cardId, 'merge_failed', '合并会话结束但未调用 kanban_merge_resolved 声明解决', 'completed')
1060
1108
  }
1061
1109
  return done
@@ -7,10 +7,10 @@
7
7
  */
8
8
  import type { Context } from '@deepseek-ai/cordis'
9
9
  import z from '@deepseek-ai/schemastery'
10
- import { settingsNamespace } from '@deepseek-ai/dsh-settings'
11
10
  import type { KanbanSessionKind, KanbanSettingsShape } from '../shared/card.js'
11
+ import type {} from '@deepseek-ai/dsh-settings'
12
12
 
13
- export const KANBAN_SETTINGS_NS = settingsNamespace('task-kanban')
13
+ export const KANBAN_SETTINGS_NS = 'task-kanban'
14
14
 
15
15
  export const KanbanSettingsSchema = z.object({
16
16
  maxParallelWorkers: z.natural().min(1).default(1),