@linxin666/dsh-client-ui-task-board 0.3.22 → 0.3.24

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.
Files changed (42) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +2 -0
  3. package/README.zh.md +2 -0
  4. package/lib/client.js +726 -71
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +2619 -175
  7. package/lib/types/client/board/EditTaskModal.d.ts +6 -0
  8. package/lib/types/client/board/NewTaskModal.d.ts +6 -1
  9. package/lib/types/client/board/TaskBoard.d.ts +2 -0
  10. package/lib/types/client/board/TaskForm.d.ts +8 -3
  11. package/lib/types/client/board/parse-model-pref.d.ts +4 -0
  12. package/lib/types/client/host-api.d.ts +23 -1
  13. package/lib/types/client/locales.d.ts +26 -0
  14. package/lib/types/client/plugin-card-seat.d.ts +96 -0
  15. package/lib/types/client/sidebar-entry-core.d.ts +32 -0
  16. package/lib/types/core/controller.d.ts +35 -1
  17. package/lib/types/core/session-reuse.d.ts +4 -2
  18. package/lib/types/host-ai.d.ts +51 -0
  19. package/lib/types/host-ledger.d.ts +15 -0
  20. package/lib/types/host-routes.d.ts +11 -1
  21. package/lib/types/index.d.ts +9 -0
  22. package/lib/types/protocol.d.ts +19 -0
  23. package/package.json +2 -1
  24. package/src/client/board/EditTaskModal.tsx +36 -0
  25. package/src/client/board/NewTaskModal.tsx +119 -5
  26. package/src/client/board/TaskBoard.tsx +89 -0
  27. package/src/client/board/TaskDetail.tsx +38 -2
  28. package/src/client/board/TaskForm.tsx +17 -11
  29. package/src/client/board/parse-model-pref.ts +35 -0
  30. package/src/client/board.module.css +61 -0
  31. package/src/client/host-api.ts +124 -5
  32. package/src/client/index.ts +18 -17
  33. package/src/client/locales.ts +52 -0
  34. package/src/client/plugin-card-seat.ts +185 -0
  35. package/src/client/sidebar-entry-core.ts +96 -12
  36. package/src/core/controller.ts +57 -1
  37. package/src/core/session-reuse.ts +14 -5
  38. package/src/host-ai.ts +152 -0
  39. package/src/host-ledger.ts +84 -14
  40. package/src/host-routes.ts +68 -3
  41. package/src/index.ts +26 -1
  42. package/src/protocol.ts +34 -0
package/lib/client.js CHANGED
@@ -644,6 +644,7 @@ window.__ModuleLoader__.load({
644
644
  presets: [],
645
645
  models: []
646
646
  };
647
+ workspaceCreator;
647
648
  listeners = /* @__PURE__ */ new Set();
648
649
  disposers = [];
649
650
  now;
@@ -687,6 +688,8 @@ window.__ModuleLoader__.load({
687
688
  selectedTaskId: this.selectedTaskId,
688
689
  executionOptions: this.executionOptions,
689
690
  pendingTaskIds: [...this.pendingTaskIds],
691
+ ...this.workspaceCreator === void 0 ? {} : { canCreateWorkspace: true },
692
+ ...typeof this.deps.transport?.parseDraft === "function" ? { canParseTask: true } : {},
690
693
  ...this.transportError === void 0 ? {} : { transportError: this.transportError },
691
694
  ...this.hostState === void 0 ? {} : { host: this.hostState }
692
695
  };
@@ -789,6 +792,33 @@ window.__ModuleLoader__.load({
789
792
  };
790
793
  this.notify();
791
794
  }
795
+ /** Wire (or clear) the runtime's project registration face (issue #1536). */
796
+ setWorkspaceCreator(creator) {
797
+ this.workspaceCreator = creator;
798
+ this.notify();
799
+ }
800
+ /**
801
+ * Register an existing host directory as a DSH project, exactly as the GUI's
802
+ * own "add project" does; the runtime's failure message surfaces unchanged.
803
+ */
804
+ async createWorkspace(path) {
805
+ if (this.workspaceCreator === void 0) throw new Error("workspace creation is unavailable");
806
+ return await this.workspaceCreator(path);
807
+ }
808
+ /** Whether this deployment can parse pasted text into task fields (issue #1540). */
809
+ canParseTask() {
810
+ return typeof this.deps.transport?.parseDraft === "function";
811
+ }
812
+ /**
813
+ * Parse pasted text into task fields through the Host. The transport already
814
+ * phrases every failure for the user, so its message surfaces unchanged.
815
+ */
816
+ async parseTaskDraft(request, signal) {
817
+ const transport = this.deps.transport;
818
+ const parse = transport?.parseDraft;
819
+ if (transport === void 0 || parse === void 0) throw new Error("task parsing is unavailable");
820
+ return await parse.call(transport, request, signal);
821
+ }
792
822
  moveTask(id, status) {
793
823
  if (this.deps.transport !== void 0) {
794
824
  this.commitRemote({
@@ -1573,6 +1603,30 @@ window.__ModuleLoader__.load({
1573
1603
  "board.updated": "更新于",
1574
1604
  "board.created": "创建于",
1575
1605
  "board.hostError": "Host 操作失败:{error}",
1606
+ "board.hostError.notMounted": "任务看板的后台接口没有挂载:宿主可能没有加载该插件,也可能后台数据正被另一个 DSH 实例占用。重启 DSH 服务后再试",
1607
+ "board.hostError.unauthorized": "登录状态已失效,请刷新页面后重试",
1608
+ "board.hostError.locked": "任务看板的后台数据被占用:{detail}",
1609
+ "board.hostError.timeout": "后台 {seconds} 秒内没有响应,请稍后重试",
1610
+ "board.hostError.unreachable": "连接不上本机 DSH 服务,请确认它仍在运行",
1611
+ "board.hostError.unexpected": "后台返回了无法识别的响应(HTTP {status})",
1612
+ "board.project": "项目",
1613
+ "board.projectAll": "全部项目",
1614
+ "board.projectNew": "新建项目…",
1615
+ "board.projectNewPath": "项目目录(宿主上的绝对路径)",
1616
+ "board.projectNewPathPlaceholder": "例如 C:\\work\\my-project 或 /home/me/project",
1617
+ "board.projectCreate": "创建项目",
1618
+ "board.projectCreateFailed": "创建项目失败:{error}",
1619
+ "new.aiParse": "AI 解析",
1620
+ "new.aiParseHint": "把从别处复制的一段话粘进来,让模型整理成标题、描述和执行 Prompt;解析结果只填进下面的表单,你可以先改再创建。",
1621
+ "new.aiParsePlaceholder": "粘贴要变成任务的内容…",
1622
+ "new.aiParseModel": "解析用模型",
1623
+ "new.aiParseRun": "解析并填入",
1624
+ "new.aiParseCancel": "取消解析",
1625
+ "new.aiParseEmpty": "先粘贴一段内容再解析",
1626
+ "new.aiParseNoModel": "这个部署还没有可用的模型,请先在 DSH 里配置模型提供商,或换一个模型再试",
1627
+ "new.aiParseUnavailable": "后台没有解析接口可用,重启 DSH 服务后再试",
1628
+ "new.aiParseTimeout": "模型 {seconds} 秒内没有返回结果,请重试或换一个更快的模型",
1629
+ "new.aiParseFailed": "解析失败:{error}",
1576
1630
  "board.retryHost": "重试连接 Host",
1577
1631
  "board.hostMeta": "Host 时区 {timeZone} · revision {revision}",
1578
1632
  "new.title": "标题",
@@ -1582,6 +1636,7 @@ window.__ModuleLoader__.load({
1582
1636
  "new.prompt": "执行 Prompt",
1583
1637
  "new.promptPlaceholder": "发给 agent 的完整指令(留空则使用标题)",
1584
1638
  "new.submit": "创建",
1639
+ "new.createAndRun": "创建并执行",
1585
1640
  "new.cancel": "取消",
1586
1641
  "new.required": "标题不能为空",
1587
1642
  "new.freeze": "冻结快照(可选)",
@@ -1615,6 +1670,7 @@ window.__ModuleLoader__.load({
1615
1670
  "detail.permissionConfirmed": "已确认权限绑定 · {time}",
1616
1671
  "detail.title": "任务详情",
1617
1672
  "detail.edit": "编辑",
1673
+ "detail.editTags": "编辑标签",
1618
1674
  "detail.duplicate": "复制为新任务",
1619
1675
  "detail.duplicateAndEdit": "修改并新建副本",
1620
1676
  "detail.close": "关闭",
@@ -1736,6 +1792,30 @@ window.__ModuleLoader__.load({
1736
1792
  "board.updated": "Updated",
1737
1793
  "board.created": "Created",
1738
1794
  "board.hostError": "Host action failed: {error}",
1795
+ "board.hostError.notMounted": "The task board Host API is not mounted: the Host may not have loaded the plugin, or another DSH instance holds the ledger. Restart the DSH service and try again",
1796
+ "board.hostError.unauthorized": "This session is no longer signed in; reload the page and try again",
1797
+ "board.hostError.locked": "The task board ledger is locked: {detail}",
1798
+ "board.hostError.timeout": "The Host did not answer within {seconds}s; try again shortly",
1799
+ "board.hostError.unreachable": "Cannot reach the local DSH service; make sure it is still running",
1800
+ "board.hostError.unexpected": "The Host returned an unrecognized response (HTTP {status})",
1801
+ "board.project": "Project",
1802
+ "board.projectAll": "All projects",
1803
+ "board.projectNew": "New project…",
1804
+ "board.projectNewPath": "Project directory (absolute path on the Host)",
1805
+ "board.projectNewPathPlaceholder": "e.g. C:\\work\\my-project or /home/me/project",
1806
+ "board.projectCreate": "Create project",
1807
+ "board.projectCreateFailed": "Could not create the project: {error}",
1808
+ "new.aiParse": "Parse with AI",
1809
+ "new.aiParseHint": "Paste text copied from somewhere else and let a model turn it into a title, description, and run prompt; the result only fills the form below, so you can edit it before creating the task.",
1810
+ "new.aiParsePlaceholder": "Paste the content to turn into a task…",
1811
+ "new.aiParseModel": "Model used for parsing",
1812
+ "new.aiParseRun": "Parse and fill",
1813
+ "new.aiParseCancel": "Cancel parsing",
1814
+ "new.aiParseEmpty": "Paste something to parse first",
1815
+ "new.aiParseNoModel": "This deployment has no model available; configure a model provider in DSH or pick another model",
1816
+ "new.aiParseUnavailable": "The Host has no parse endpoint; restart the DSH service and try again",
1817
+ "new.aiParseTimeout": "The model did not answer within {seconds}s; try again or pick a faster model",
1818
+ "new.aiParseFailed": "Parsing failed: {error}",
1739
1819
  "board.retryHost": "Retry Host connection",
1740
1820
  "board.hostMeta": "Host time zone {timeZone} · revision {revision}",
1741
1821
  "new.title": "Title",
@@ -1745,6 +1825,7 @@ window.__ModuleLoader__.load({
1745
1825
  "new.prompt": "Run Prompt",
1746
1826
  "new.promptPlaceholder": "The full instruction sent to the agent (title is used when blank)",
1747
1827
  "new.submit": "Create",
1828
+ "new.createAndRun": "Create and run",
1748
1829
  "new.cancel": "Cancel",
1749
1830
  "new.required": "Title is required",
1750
1831
  "new.freeze": "Frozen snapshot (optional)",
@@ -1778,6 +1859,7 @@ window.__ModuleLoader__.load({
1778
1859
  "detail.permissionConfirmed": "Permission confirmed · {time}",
1779
1860
  "detail.title": "Task Detail",
1780
1861
  "detail.edit": "Edit",
1862
+ "detail.editTags": "Edit tags",
1781
1863
  "detail.duplicate": "Duplicate Task",
1782
1864
  "detail.duplicateAndEdit": "Edit as New Copy",
1783
1865
  "detail.close": "Close",
@@ -1900,7 +1982,7 @@ window.__ModuleLoader__.load({
1900
1982
  }
1901
1983
  //#endregion
1902
1984
  //#region \0dsh-css:packages/dsh-task-board/src/client/board.module.css.mjs
1903
- const css$1 = "[data-pane=conversation],[class*=centerCol]{position:relative}[data-dsh-taskboard-view]{z-index:60;background:var(--dsw-alias-bg-base);display:none;position:absolute;inset:0;container:_7D6uKa_task-board-view/inline-size}html[data-dsh-taskboard-active]:not([data-dsh-ssh-active]) [data-dsh-taskboard-view]{display:block}html[data-dsh-taskboard-active]:not([data-dsh-ssh-active]) [data-pane=conversation]>:not([data-dsh-taskboard-view]),html[data-dsh-taskboard-active]:not([data-dsh-ssh-active]) [class*=centerCol]>:not([data-dsh-taskboard-view]){display:none!important}._7D6uKa_entry{box-sizing:border-box;width:100%;height:36px;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-radius:8px;align-items:center;gap:8px;padding:0 10px;font-size:13px;display:flex}._7D6uKa_entry:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}._7D6uKa_entry[data-active]{background:var(--dsw-alias-interactive-bg-active);color:var(--dsw-alias-label-primary);font-weight:600}._7D6uKa_entryIcon{flex:none;justify-content:center;align-items:center;width:24px;height:24px;display:inline-flex}._7D6uKa_entryIcon svg{width:18px;height:18px;display:block}._7D6uKa_entryLabel{text-overflow:ellipsis;overflow:hidden}[data-dsh-frame][data-sidebar-collapsed] ._7D6uKa_entry,[data-sidebar-collapsed] ._7D6uKa_entry{border-radius:50%;justify-content:center;width:36px;height:36px;margin:0 auto 12px;padding:0}[data-dsh-frame][data-sidebar-collapsed] ._7D6uKa_entryLabel,[data-sidebar-collapsed] ._7D6uKa_entryLabel{display:none}._7D6uKa_board{box-sizing:border-box;background:var(--dsw-alias-bg-base);min-width:0;height:100%;min-height:0;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);flex-direction:column;gap:12px;padding:14px 16px 16px;display:flex}._7D6uKa_boardHeader{flex:none;align-items:center;gap:10px;display:flex}._7D6uKa_boardTitle{color:var(--dsw-alias-label-primary);white-space:nowrap;margin:0;font-size:16px;font-weight:700}._7D6uKa_backButton{align-items:center;gap:4px;display:inline-flex}._7D6uKa_search{min-width:120px;color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;flex:0 260px;padding:6px 10px;font-size:13px}._7D6uKa_search::placeholder{color:var(--dsw-alias-label-tertiary)}._7D6uKa_columns{overscroll-behavior-inline:contain;scrollbar-color:var(--dsw-alias-border-l3) var(--dsw-alias-interactive-bg-hover);scrollbar-width:thin;flex:1;grid-auto-columns:minmax(220px,1fr);grid-auto-flow:column;gap:12px;min-height:0;padding-bottom:6px;display:grid;overflow:auto hidden}._7D6uKa_columns::-webkit-scrollbar{height:10px}._7D6uKa_columns::-webkit-scrollbar-track{background:var(--dsw-alias-interactive-bg-hover);border-radius:999px}._7D6uKa_columns::-webkit-scrollbar-thumb{background:var(--dsw-alias-border-l3);background-clip:content-box;border:2px solid #0000;border-radius:999px}._7D6uKa_columns::-webkit-scrollbar-thumb:hover{background:var(--dsw-alias-border-l4);background-clip:content-box}._7D6uKa_column{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:12px;flex-direction:column;min-height:0;display:flex;overflow:hidden}._7D6uKa_columnHeader{flex:none;align-items:center;gap:6px;padding:10px 12px;display:flex}._7D6uKa_columnTitle{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;flex:1;margin:0;font-size:13px;font-weight:700;overflow:hidden}._7D6uKa_columnCount{min-width:0;color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-interactive-bg-hover);border-radius:999px;flex:none;padding:1px 8px;font-size:12px}._7D6uKa_statusDot{border-radius:50%;flex:none;width:8px;height:8px}._7D6uKa_statusDot[data-status=backlog]{background:var(--dsw-alias-label-tertiary)}._7D6uKa_statusDot[data-status=todo]{background:var(--dsw-alias-state-business-primary)}._7D6uKa_statusDot[data-status=running]{background:var(--dsw-alias-state-warn-primary)}._7D6uKa_statusDot[data-status=done]{background:var(--dsw-alias-state-success-primary)}._7D6uKa_statusDot[data-status=failed]{background:var(--dsw-alias-state-error-primary)}._7D6uKa_cards{flex-direction:column;flex:1;gap:8px;min-height:0;padding:2px 8px 10px;display:flex;overflow-y:auto}._7D6uKa_columnEmpty{text-align:center;color:var(--dsw-alias-label-tertiary);padding:24px 8px;font-size:12px}._7D6uKa_card{text-align:left;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;color:var(--dsw-alias-label-primary);border-radius:10px;flex-direction:column;gap:6px;padding:10px 12px;font-family:inherit;transition:box-shadow .12s,border-color .12s,transform .12s;display:flex}._7D6uKa_card:hover{box-shadow:var(--dsw-shadow-lv2);border-color:var(--dsw-alias-border-l3);transform:translateY(-1px)}._7D6uKa_card[data-status=running]{border-color:var(--dsw-alias-state-warn-primary)}._7D6uKa_cardTitle{-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:13px;font-weight:600;line-height:1.35;display:-webkit-box;overflow:hidden}._7D6uKa_cardExcerpt{color:var(--dsw-alias-label-secondary);-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:12px;line-height:1.4;display:-webkit-box;overflow:hidden}._7D6uKa_cardMeta{color:var(--dsw-alias-label-tertiary);align-items:center;gap:8px;font-size:11px;display:flex}._7D6uKa_cardTime{text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}._7D6uKa_cardSchedule{white-space:nowrap;min-width:0;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover);border-radius:999px;flex:none;padding:2px 6px;font-size:12px;line-height:1}._7D6uKa_cardRun{flex:none}._7D6uKa_cardRun[data-result=failed]{color:var(--dsw-alias-state-error-primary)}._7D6uKa_cardRun[data-result=succeeded]{color:var(--dsw-alias-state-success-primary)}._7D6uKa_cardSession{color:var(--dsw-alias-state-business-primary);flex:none}._7D6uKa_cardRunningLabel{color:var(--dsw-alias-state-warn-primary);font-size:11px}._7D6uKa_cardSpinner{border:2px solid var(--dsw-alias-state-warn-primary);border-top-color:#0000;border-radius:50%;flex:none;width:10px;height:10px;animation:.8s linear infinite _7D6uKa_dshTbSpin}@keyframes _7D6uKa_dshTbSpin{to{transform:rotate(360deg)}}._7D6uKa_primaryButton{color:var(--dsw-alias-label-primary-foreground);background:var(--dsw-alias-button-info-fill);cursor:pointer;white-space:nowrap;border:none;border-radius:8px;padding:6px 14px;font-size:13px;font-weight:600}._7D6uKa_primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-info-hover)}._7D6uKa_primaryButton:disabled{opacity:.5;cursor:default}._7D6uKa_ghostButton{color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;white-space:nowrap;background:0 0;border-radius:8px;padding:5px 12px;font-size:12px}._7D6uKa_ghostButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}._7D6uKa_ghostButton:disabled{opacity:.45;cursor:default}._7D6uKa_dangerButton{color:#fff;background:var(--dsw-alias-state-error-primary);cursor:pointer;white-space:nowrap;border:none;border-radius:8px;padding:6px 14px;font-size:13px;font-weight:600}._7D6uKa_dangerButton:hover:not(:disabled){filter:brightness(1.08)}._7D6uKa_dangerButton:active:not(:disabled){filter:brightness(.94)}._7D6uKa_dangerButton:disabled{opacity:.5;cursor:default}._7D6uKa_iconButton{width:26px;height:26px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;padding:0;font-size:13px;display:inline-flex}._7D6uKa_iconButton:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}._7D6uKa_linkButton{color:var(--dsw-alias-state-business-primary);cursor:pointer;white-space:nowrap;background:0 0;border:none;padding:0;font-size:12px}._7D6uKa_linkButton:hover{text-decoration:underline}._7D6uKa_modalBackdrop{z-index:1300;background:var(--dsw-alias-bg-mask-1);justify-content:center;align-items:center;display:flex;position:fixed;inset:0}._7D6uKa_modal{background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);width:min(520px,100vw - 48px);max-height:calc(100vh - 96px);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);border-radius:14px;flex-direction:column;gap:12px;padding:18px;display:flex;overflow-y:auto}._7D6uKa_modalTitle{margin:0;font-size:15px;font-weight:700}._7D6uKa_confirmMessage{color:var(--dsw-alias-label-secondary);white-space:pre-wrap;overflow-wrap:anywhere;margin:0;font-size:13px;line-height:1.5}._7D6uKa_modalFooter{justify-content:flex-end;gap:10px;margin-top:4px;display:flex}._7D6uKa_field{flex-direction:column;gap:5px;display:flex}._7D6uKa_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600}._7D6uKa_input{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);resize:vertical;border-radius:8px;outline:none;padding:7px 10px;font-family:inherit;font-size:13px}._7D6uKa_input:focus{border-color:var(--dsw-alias-state-business-primary)}._7D6uKa_select{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;max-width:100%;padding:7px 10px;font-family:inherit;font-size:13px}._7D6uKa_input::placeholder{color:var(--dsw-alias-label-tertiary)}._7D6uKa_formError{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px}._7D6uKa_detail{background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);width:min(640px,100vw - 48px);max-height:calc(100vh - 80px);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);border-radius:14px;flex-direction:column;display:flex;overflow:hidden}._7D6uKa_detailHeader{border-bottom:1px solid var(--dsw-alias-separator-primary);flex:none;align-items:center;gap:10px;padding:14px 18px;display:flex}._7D6uKa_detailTitle{overflow-wrap:anywhere;flex:1;margin:0;font-size:15px;font-weight:700}._7D6uKa_statusBadge{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:2px 10px;font-size:12px}._7D6uKa_statusBadge[data-status=running]{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}._7D6uKa_statusBadge[data-status=done]{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}._7D6uKa_statusBadge[data-status=failed]{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}._7D6uKa_detailBody{flex-direction:column;flex:1;gap:16px;padding:14px 18px;display:flex;overflow-y:auto}._7D6uKa_detailSection{flex-direction:column;gap:6px;display:flex}._7D6uKa_detailSection h4{color:var(--dsw-alias-label-tertiary);text-transform:none;margin:0;font-size:12px;font-weight:700}._7D6uKa_detailText{color:var(--dsw-alias-label-primary);white-space:pre-wrap;overflow-wrap:anywhere;margin:0;font-size:13px;line-height:1.55}._7D6uKa_scheduleToggle{color:var(--dsw-alias-label-primary);cursor:pointer;user-select:none;align-items:center;gap:8px;font-size:13px;display:flex}._7D6uKa_scheduleToggle input{accent-color:var(--dsw-alias-state-business-primary)}._7D6uKa_scheduleRow{align-items:center;gap:8px;display:flex}._7D6uKa_scheduleInput{min-width:0;font-family:var(--dsw-font-markdown-code-block-small);flex:1;font-size:12.5px}._7D6uKa_scheduleInputInvalid,._7D6uKa_scheduleInputInvalid:focus{border-color:var(--dsw-alias-state-error-primary)}._7D6uKa_schedulePreset{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;flex:none;padding:7px 8px;font-size:12.5px}._7D6uKa_scheduleMeta{color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere;margin:0;font-size:12px}._7D6uKa_promptBlock{font-size:12.5px;line-height:1.5;font-family:var(--dsw-font-markdown-code-block-small);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-markdown-code-block);border:1px solid var(--dsw-alias-border-l1);white-space:pre-wrap;overflow-wrap:anywhere;border-radius:8px;max-height:240px;margin:0;padding:10px 12px;overflow-y:auto}._7D6uKa_executionList{flex-direction:column;gap:8px;margin:0;padding:0;list-style:none;display:flex}._7D6uKa_executionRow{border:1px solid var(--dsw-alias-border-l1);border-radius:8px;flex-wrap:wrap;align-items:center;gap:10px;padding:8px 10px;display:flex}._7D6uKa_executionBadge{color:var(--dsw-alias-state-warn-primary);background:var(--dsw-alias-state-warn-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:600}._7D6uKa_executionBadge[data-result=succeeded]{color:var(--dsw-alias-state-success-primary);background:0 0}._7D6uKa_executionBadge[data-result=failed]{color:var(--dsw-alias-state-error-primary);background:0 0}._7D6uKa_executionBadge[data-result=cancelled]{color:var(--dsw-alias-label-tertiary);background:0 0}._7D6uKa_executionTimes{color:var(--dsw-alias-label-secondary);font-size:12px}._7D6uKa_executionError{width:100%;color:var(--dsw-alias-state-error-primary);overflow-wrap:anywhere;font-size:12px}._7D6uKa_moveRow{flex-wrap:wrap;gap:8px;display:flex}._7D6uKa_detailFooter{border-top:1px solid var(--dsw-alias-separator-primary);flex:none;align-items:center;gap:10px;padding:12px 18px;display:flex}._7D6uKa_detailMeta{color:var(--dsw-alias-label-tertiary);margin-left:auto;font-size:11px}@container _7D6uKa_task-board-view (width<=768px){._7D6uKa_board{gap:10px;padding:10px}._7D6uKa_boardHeader{flex-wrap:wrap;align-items:center;gap:8px}._7D6uKa_backButton{flex:none;order:1}._7D6uKa_boardTitle{flex:auto;order:2}._7D6uKa_boardHeader>._7D6uKa_detailMeta{flex:1 0 100%;order:3;margin-left:0}._7D6uKa_search{flex:1 0 100%;order:4;min-width:0}._7D6uKa_boardHeader>button:not(._7D6uKa_backButton){flex:1 1 0;order:5;min-width:0}._7D6uKa_columns{scroll-snap-type:inline mandatory;scrollbar-width:none;-webkit-overflow-scrolling:touch;grid-auto-columns:86cqw;gap:10px;padding-inline:2px 14cqw;scroll-padding-inline:2px}._7D6uKa_columns::-webkit-scrollbar{display:none}._7D6uKa_column{scroll-snap-align:start;scroll-snap-stop:always}}@container _7D6uKa_task-board-view (width<=720px){._7D6uKa_boardHeader>._7D6uKa_detailMeta{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}}@container _7D6uKa_task-board-view (width<=600px){._7D6uKa_board{padding-inline:8px}}@media (width<=768px){[data-dsh-taskboard-view]{height:100dvh}._7D6uKa_entry,._7D6uKa_card,._7D6uKa_primaryButton,._7D6uKa_ghostButton,._7D6uKa_dangerButton,._7D6uKa_iconButton,._7D6uKa_linkButton,._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset,._7D6uKa_scheduleToggle{min-height:44px}._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset{box-sizing:border-box;font-size:16px}._7D6uKa_modalBackdrop{justify-content:stretch;align-items:stretch;width:100vw;height:100dvh}._7D6uKa_modal,._7D6uKa_detail{box-sizing:border-box;border:0;border-radius:0;width:100vw;height:100dvh;max-height:none}._7D6uKa_modal{padding-top:max(16px, env(safe-area-inset-top));padding-right:max(16px, env(safe-area-inset-right));padding-bottom:max(16px, env(safe-area-inset-bottom));padding-left:max(16px, env(safe-area-inset-left))}._7D6uKa_modalFooter{z-index:1;background:var(--dsw-alias-bg-base);flex-wrap:wrap;padding-top:8px;position:sticky;bottom:0}._7D6uKa_modalFooter>button{flex:120px}._7D6uKa_detailHeader{padding-top:max(12px, env(safe-area-inset-top));padding-right:max(14px, env(safe-area-inset-right));padding-left:max(14px, env(safe-area-inset-left));flex-wrap:wrap}._7D6uKa_detailTitle{min-width:0}._7D6uKa_detailBody{overscroll-behavior-y:contain;padding-right:max(14px, env(safe-area-inset-right));padding-left:max(14px, env(safe-area-inset-left))}._7D6uKa_detailFooter{padding-right:max(14px, env(safe-area-inset-right));padding-bottom:max(12px, env(safe-area-inset-bottom));padding-left:max(14px, env(safe-area-inset-left));flex-wrap:wrap}._7D6uKa_detailFooter>button{flex:96px}._7D6uKa_detailFooter>._7D6uKa_detailMeta{text-align:end;flex:1 0 100%;margin-left:0}._7D6uKa_scheduleRow{flex-direction:column;align-items:stretch}._7D6uKa_schedulePreset{width:100%}}._7D6uKa_entry:focus-visible,._7D6uKa_card:focus-visible,._7D6uKa_primaryButton:focus-visible,._7D6uKa_ghostButton:focus-visible,._7D6uKa_dangerButton:focus-visible,._7D6uKa_iconButton:focus-visible,._7D6uKa_linkButton:focus-visible,._7D6uKa_search:focus-visible,._7D6uKa_input:focus-visible,._7D6uKa_select:focus-visible,._7D6uKa_schedulePreset:focus-visible,._7D6uKa_scheduleToggle input:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}._7D6uKa_entry,._7D6uKa_primaryButton,._7D6uKa_ghostButton,._7D6uKa_dangerButton,._7D6uKa_iconButton,._7D6uKa_linkButton,._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset,._7D6uKa_scheduleToggle input{transition:background-color .12s,color .12s,border-color .12s,outline-color .12s,box-shadow .12s,transform .12s}._7D6uKa_card:active{box-shadow:var(--dsw-shadow-lv1);transform:translateY(0)}._7D6uKa_entry:active,._7D6uKa_primaryButton:active:not(:disabled),._7D6uKa_ghostButton:active:not(:disabled),._7D6uKa_dangerButton:active:not(:disabled),._7D6uKa_iconButton:active:not(:disabled),._7D6uKa_linkButton:active:not(:disabled){transform:translateY(1px)}._7D6uKa_entry[data-active]:hover{background:var(--dsw-specific-sidebar-nav-item-active)}._7D6uKa_iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}._7D6uKa_linkButton:hover:not(:disabled){text-decoration:underline}._7D6uKa_iconButton:disabled,._7D6uKa_linkButton:disabled{opacity:.45;cursor:default}._7D6uKa_search:focus,._7D6uKa_select:focus,._7D6uKa_schedulePreset:focus{border-color:var(--dsw-alias-state-business-primary)}._7D6uKa_scheduleToggle input{margin:0}@media (prefers-reduced-motion:reduce){._7D6uKa_entry,._7D6uKa_card,._7D6uKa_primaryButton,._7D6uKa_ghostButton,._7D6uKa_dangerButton,._7D6uKa_iconButton,._7D6uKa_linkButton,._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset,._7D6uKa_scheduleToggle input{transition:none}._7D6uKa_cardSpinner{animation:none}}._7D6uKa_cardTags{flex-wrap:wrap;gap:4px;display:flex}._7D6uKa_cardTag{border:1px solid var(--dsh-task-tag-border);background:var(--dsh-task-tag-fill);max-width:100%;color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;border-radius:999px;padding:0 7px;font-size:10px;line-height:16px;overflow:hidden}._7D6uKa_tagFilter{flex-wrap:wrap;align-items:center;gap:6px;margin:0 0 10px;display:flex}._7D6uKa_tagFilterLabel{color:var(--dsw-alias-label-tertiary);font-size:11px}._7D6uKa_tagChip{border:1px solid var(--dsh-task-tag-border);color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border-radius:999px;padding:1px 9px;font-family:inherit;font-size:11px;line-height:18px}._7D6uKa_tagChip[data-active=true]{background:var(--dsh-task-tag-fill);color:var(--dsw-alias-label-primary)}._7D6uKa_cardTag[data-tag-tone=\"0\"],._7D6uKa_tagChip[data-tag-tone=\"0\"]{--dsh-task-tag-fill:#4e93e82e;--dsh-task-tag-border:#4e93e866}._7D6uKa_cardTag[data-tag-tone=\"1\"],._7D6uKa_tagChip[data-tag-tone=\"1\"]{--dsh-task-tag-fill:#2ea36a2e;--dsh-task-tag-border:#2ea36a66}._7D6uKa_cardTag[data-tag-tone=\"2\"],._7D6uKa_tagChip[data-tag-tone=\"2\"]{--dsh-task-tag-fill:#d08a2a2e;--dsh-task-tag-border:#d08a2a66}._7D6uKa_cardTag[data-tag-tone=\"3\"],._7D6uKa_tagChip[data-tag-tone=\"3\"]{--dsh-task-tag-fill:#b456c82e;--dsh-task-tag-border:#b456c866}._7D6uKa_cardTag[data-tag-tone=\"4\"],._7D6uKa_tagChip[data-tag-tone=\"4\"]{--dsh-task-tag-fill:#cf5f7a2e;--dsh-task-tag-border:#cf5f7a66}._7D6uKa_cardTag[data-tag-tone=\"5\"],._7D6uKa_tagChip[data-tag-tone=\"5\"]{--dsh-task-tag-fill:#4a9fb52e;--dsh-task-tag-border:#4a9fb566}._7D6uKa_fieldHint{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.4}._7D6uKa_tagRow{align-items:center;gap:6px;display:flex}._7D6uKa_tagRow ._7D6uKa_input{flex:1 1 0;min-width:0}._7D6uKa_tagRow ._7D6uKa_ghostButton{flex:none}._7D6uKa_tagAddButton{align-self:flex-start}";
1985
+ const css$1 = "[data-pane=conversation],[class*=centerCol]{position:relative}[data-dsh-taskboard-view]{z-index:60;background:var(--dsw-alias-bg-base);display:none;position:absolute;inset:0;container:_7D6uKa_task-board-view/inline-size}html[data-dsh-taskboard-active]:not([data-dsh-ssh-active]) [data-dsh-taskboard-view]{display:block}html[data-dsh-taskboard-active]:not([data-dsh-ssh-active]) [data-pane=conversation]>:not([data-dsh-taskboard-view]),html[data-dsh-taskboard-active]:not([data-dsh-ssh-active]) [class*=centerCol]>:not([data-dsh-taskboard-view]){display:none!important}._7D6uKa_entry{box-sizing:border-box;width:100%;height:36px;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-radius:8px;align-items:center;gap:8px;padding:0 10px;font-size:13px;display:flex}._7D6uKa_entry:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}._7D6uKa_entry[data-active]{background:var(--dsw-alias-interactive-bg-active);color:var(--dsw-alias-label-primary);font-weight:600}._7D6uKa_entryIcon{flex:none;justify-content:center;align-items:center;width:24px;height:24px;display:inline-flex}._7D6uKa_entryIcon svg{width:18px;height:18px;display:block}._7D6uKa_entryLabel{text-overflow:ellipsis;overflow:hidden}[data-dsh-frame][data-sidebar-collapsed] ._7D6uKa_entry,[data-sidebar-collapsed] ._7D6uKa_entry{border-radius:50%;justify-content:center;width:36px;height:36px;margin:0 auto 12px;padding:0}[data-dsh-frame][data-sidebar-collapsed] ._7D6uKa_entryLabel,[data-sidebar-collapsed] ._7D6uKa_entryLabel{display:none}._7D6uKa_board{box-sizing:border-box;background:var(--dsw-alias-bg-base);min-width:0;height:100%;min-height:0;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);flex-direction:column;gap:12px;padding:14px 16px 16px;display:flex}._7D6uKa_boardHeader{flex:none;align-items:center;gap:10px;display:flex}._7D6uKa_boardTitle{color:var(--dsw-alias-label-primary);white-space:nowrap;margin:0;font-size:16px;font-weight:700}._7D6uKa_backButton{align-items:center;gap:4px;display:inline-flex}._7D6uKa_search{min-width:120px;color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;flex:0 260px;padding:6px 10px;font-size:13px}._7D6uKa_search::placeholder{color:var(--dsw-alias-label-tertiary)}._7D6uKa_columns{overscroll-behavior-inline:contain;scrollbar-color:var(--dsw-alias-border-l3) var(--dsw-alias-interactive-bg-hover);scrollbar-width:thin;flex:1;grid-auto-columns:minmax(220px,1fr);grid-auto-flow:column;gap:12px;min-height:0;padding-bottom:6px;display:grid;overflow:auto hidden}._7D6uKa_columns::-webkit-scrollbar{height:10px}._7D6uKa_columns::-webkit-scrollbar-track{background:var(--dsw-alias-interactive-bg-hover);border-radius:999px}._7D6uKa_columns::-webkit-scrollbar-thumb{background:var(--dsw-alias-border-l3);background-clip:content-box;border:2px solid #0000;border-radius:999px}._7D6uKa_columns::-webkit-scrollbar-thumb:hover{background:var(--dsw-alias-border-l4);background-clip:content-box}._7D6uKa_column{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:12px;flex-direction:column;min-height:0;display:flex;overflow:hidden}._7D6uKa_columnHeader{flex:none;align-items:center;gap:6px;padding:10px 12px;display:flex}._7D6uKa_columnTitle{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;flex:1;margin:0;font-size:13px;font-weight:700;overflow:hidden}._7D6uKa_columnCount{min-width:0;color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-interactive-bg-hover);border-radius:999px;flex:none;padding:1px 8px;font-size:12px}._7D6uKa_statusDot{border-radius:50%;flex:none;width:8px;height:8px}._7D6uKa_statusDot[data-status=backlog]{background:var(--dsw-alias-label-tertiary)}._7D6uKa_statusDot[data-status=todo]{background:var(--dsw-alias-state-business-primary)}._7D6uKa_statusDot[data-status=running]{background:var(--dsw-alias-state-warn-primary)}._7D6uKa_statusDot[data-status=done]{background:var(--dsw-alias-state-success-primary)}._7D6uKa_statusDot[data-status=failed]{background:var(--dsw-alias-state-error-primary)}._7D6uKa_cards{flex-direction:column;flex:1;gap:8px;min-height:0;padding:2px 8px 10px;display:flex;overflow-y:auto}._7D6uKa_columnEmpty{text-align:center;color:var(--dsw-alias-label-tertiary);padding:24px 8px;font-size:12px}._7D6uKa_card{text-align:left;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;color:var(--dsw-alias-label-primary);border-radius:10px;flex-direction:column;gap:6px;padding:10px 12px;font-family:inherit;transition:box-shadow .12s,border-color .12s,transform .12s;display:flex}._7D6uKa_card:hover{box-shadow:var(--dsw-shadow-lv2);border-color:var(--dsw-alias-border-l3);transform:translateY(-1px)}._7D6uKa_card[data-status=running]{border-color:var(--dsw-alias-state-warn-primary)}._7D6uKa_cardTitle{-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:13px;font-weight:600;line-height:1.35;display:-webkit-box;overflow:hidden}._7D6uKa_cardExcerpt{color:var(--dsw-alias-label-secondary);-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:12px;line-height:1.4;display:-webkit-box;overflow:hidden}._7D6uKa_cardMeta{color:var(--dsw-alias-label-tertiary);align-items:center;gap:8px;font-size:11px;display:flex}._7D6uKa_cardTime{text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}._7D6uKa_cardSchedule{white-space:nowrap;min-width:0;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover);border-radius:999px;flex:none;padding:2px 6px;font-size:12px;line-height:1}._7D6uKa_cardRun{flex:none}._7D6uKa_cardRun[data-result=failed]{color:var(--dsw-alias-state-error-primary)}._7D6uKa_cardRun[data-result=succeeded]{color:var(--dsw-alias-state-success-primary)}._7D6uKa_cardSession{color:var(--dsw-alias-state-business-primary);flex:none}._7D6uKa_cardRunningLabel{color:var(--dsw-alias-state-warn-primary);font-size:11px}._7D6uKa_cardSpinner{border:2px solid var(--dsw-alias-state-warn-primary);border-top-color:#0000;border-radius:50%;flex:none;width:10px;height:10px;animation:.8s linear infinite _7D6uKa_dshTbSpin}@keyframes _7D6uKa_dshTbSpin{to{transform:rotate(360deg)}}._7D6uKa_primaryButton{color:var(--dsw-alias-label-primary-foreground);background:var(--dsw-alias-button-info-fill);cursor:pointer;white-space:nowrap;border:none;border-radius:8px;padding:6px 14px;font-size:13px;font-weight:600}._7D6uKa_primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-info-hover)}._7D6uKa_primaryButton:disabled{opacity:.5;cursor:default}._7D6uKa_ghostButton{color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;white-space:nowrap;background:0 0;border-radius:8px;padding:5px 12px;font-size:12px}._7D6uKa_ghostButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}._7D6uKa_ghostButton:disabled{opacity:.45;cursor:default}._7D6uKa_dangerButton{color:#fff;background:var(--dsw-alias-state-error-primary);cursor:pointer;white-space:nowrap;border:none;border-radius:8px;padding:6px 14px;font-size:13px;font-weight:600}._7D6uKa_dangerButton:hover:not(:disabled){filter:brightness(1.08)}._7D6uKa_dangerButton:active:not(:disabled){filter:brightness(.94)}._7D6uKa_dangerButton:disabled{opacity:.5;cursor:default}._7D6uKa_iconButton{width:26px;height:26px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;padding:0;font-size:13px;display:inline-flex}._7D6uKa_iconButton:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}._7D6uKa_linkButton{color:var(--dsw-alias-state-business-primary);cursor:pointer;white-space:nowrap;background:0 0;border:none;padding:0;font-size:12px}._7D6uKa_linkButton:hover{text-decoration:underline}._7D6uKa_modalBackdrop{z-index:1300;background:var(--dsw-alias-bg-mask-1);justify-content:center;align-items:center;display:flex;position:fixed;inset:0}._7D6uKa_modal{background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);width:min(520px,100vw - 48px);max-height:calc(100vh - 96px);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);border-radius:14px;flex-direction:column;gap:12px;padding:18px;display:flex;overflow-y:auto}._7D6uKa_modalTitle{margin:0;font-size:15px;font-weight:700}._7D6uKa_confirmMessage{color:var(--dsw-alias-label-secondary);white-space:pre-wrap;overflow-wrap:anywhere;margin:0;font-size:13px;line-height:1.5}._7D6uKa_modalFooter{justify-content:flex-end;gap:10px;margin-top:4px;display:flex}._7D6uKa_field{flex-direction:column;gap:5px;display:flex}._7D6uKa_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600}._7D6uKa_input{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);resize:vertical;border-radius:8px;outline:none;padding:7px 10px;font-family:inherit;font-size:13px}._7D6uKa_input:focus{border-color:var(--dsw-alias-state-business-primary)}._7D6uKa_select{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;max-width:100%;padding:7px 10px;font-family:inherit;font-size:13px}._7D6uKa_input::placeholder{color:var(--dsw-alias-label-tertiary)}._7D6uKa_formError{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px}._7D6uKa_detail{background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);width:min(640px,100vw - 48px);max-height:calc(100vh - 80px);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);border-radius:14px;flex-direction:column;display:flex;overflow:hidden}._7D6uKa_detailHeader{border-bottom:1px solid var(--dsw-alias-separator-primary);flex:none;align-items:center;gap:10px;padding:14px 18px;display:flex}._7D6uKa_detailTitle{overflow-wrap:anywhere;flex:1;margin:0;font-size:15px;font-weight:700}._7D6uKa_statusBadge{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:2px 10px;font-size:12px}._7D6uKa_statusBadge[data-status=running]{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}._7D6uKa_statusBadge[data-status=done]{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}._7D6uKa_statusBadge[data-status=failed]{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}._7D6uKa_detailBody{flex-direction:column;flex:1;gap:16px;padding:14px 18px;display:flex;overflow-y:auto}._7D6uKa_detailSection{flex-direction:column;gap:6px;display:flex}._7D6uKa_detailSection h4{color:var(--dsw-alias-label-tertiary);text-transform:none;margin:0;font-size:12px;font-weight:700}._7D6uKa_detailText{color:var(--dsw-alias-label-primary);white-space:pre-wrap;overflow-wrap:anywhere;margin:0;font-size:13px;line-height:1.55}._7D6uKa_scheduleToggle{color:var(--dsw-alias-label-primary);cursor:pointer;user-select:none;align-items:center;gap:8px;font-size:13px;display:flex}._7D6uKa_scheduleToggle input{accent-color:var(--dsw-alias-state-business-primary)}._7D6uKa_scheduleRow{align-items:center;gap:8px;display:flex}._7D6uKa_scheduleInput{min-width:0;font-family:var(--dsw-font-markdown-code-block-small);flex:1;font-size:12.5px}._7D6uKa_scheduleInputInvalid,._7D6uKa_scheduleInputInvalid:focus{border-color:var(--dsw-alias-state-error-primary)}._7D6uKa_schedulePreset{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;flex:none;padding:7px 8px;font-size:12.5px}._7D6uKa_scheduleMeta{color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere;margin:0;font-size:12px}._7D6uKa_promptBlock{font-size:12.5px;line-height:1.5;font-family:var(--dsw-font-markdown-code-block-small);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-markdown-code-block);border:1px solid var(--dsw-alias-border-l1);white-space:pre-wrap;overflow-wrap:anywhere;border-radius:8px;max-height:240px;margin:0;padding:10px 12px;overflow-y:auto}._7D6uKa_executionList{flex-direction:column;gap:8px;margin:0;padding:0;list-style:none;display:flex}._7D6uKa_executionRow{border:1px solid var(--dsw-alias-border-l1);border-radius:8px;flex-wrap:wrap;align-items:center;gap:10px;padding:8px 10px;display:flex}._7D6uKa_executionBadge{color:var(--dsw-alias-state-warn-primary);background:var(--dsw-alias-state-warn-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:600}._7D6uKa_executionBadge[data-result=succeeded]{color:var(--dsw-alias-state-success-primary);background:0 0}._7D6uKa_executionBadge[data-result=failed]{color:var(--dsw-alias-state-error-primary);background:0 0}._7D6uKa_executionBadge[data-result=cancelled]{color:var(--dsw-alias-label-tertiary);background:0 0}._7D6uKa_executionTimes{color:var(--dsw-alias-label-secondary);font-size:12px}._7D6uKa_executionError{width:100%;color:var(--dsw-alias-state-error-primary);overflow-wrap:anywhere;font-size:12px}._7D6uKa_moveRow{flex-wrap:wrap;gap:8px;display:flex}._7D6uKa_detailFooter{border-top:1px solid var(--dsw-alias-separator-primary);flex:none;align-items:center;gap:10px;padding:12px 18px;display:flex}._7D6uKa_detailMeta{color:var(--dsw-alias-label-tertiary);margin-left:auto;font-size:11px}@container _7D6uKa_task-board-view (width<=768px){._7D6uKa_board{gap:10px;padding:10px}._7D6uKa_boardHeader{flex-wrap:wrap;align-items:center;gap:8px}._7D6uKa_backButton{flex:none;order:1}._7D6uKa_boardTitle{flex:auto;order:2}._7D6uKa_boardHeader>._7D6uKa_detailMeta{flex:1 0 100%;order:3;margin-left:0}._7D6uKa_search{flex:1 0 100%;order:4;min-width:0}._7D6uKa_boardHeader>button:not(._7D6uKa_backButton){flex:1 1 0;order:5;min-width:0}._7D6uKa_columns{scroll-snap-type:inline mandatory;scrollbar-width:none;-webkit-overflow-scrolling:touch;grid-auto-columns:86cqw;gap:10px;padding-inline:2px 14cqw;scroll-padding-inline:2px}._7D6uKa_columns::-webkit-scrollbar{display:none}._7D6uKa_column{scroll-snap-align:start;scroll-snap-stop:always}}@container _7D6uKa_task-board-view (width<=720px){._7D6uKa_boardHeader>._7D6uKa_detailMeta{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}}@container _7D6uKa_task-board-view (width<=600px){._7D6uKa_board{padding-inline:8px}}@media (width<=768px){[data-dsh-taskboard-view]{height:100dvh}._7D6uKa_entry,._7D6uKa_card,._7D6uKa_primaryButton,._7D6uKa_ghostButton,._7D6uKa_dangerButton,._7D6uKa_iconButton,._7D6uKa_linkButton,._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset,._7D6uKa_scheduleToggle{min-height:44px}._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset{box-sizing:border-box;font-size:16px}._7D6uKa_modalBackdrop{justify-content:stretch;align-items:stretch;width:100vw;height:100dvh}._7D6uKa_modal,._7D6uKa_detail{box-sizing:border-box;border:0;border-radius:0;width:100vw;height:100dvh;max-height:none}._7D6uKa_modal{padding-top:max(16px, env(safe-area-inset-top));padding-right:max(16px, env(safe-area-inset-right));padding-bottom:max(16px, env(safe-area-inset-bottom));padding-left:max(16px, env(safe-area-inset-left))}._7D6uKa_modalFooter{z-index:1;background:var(--dsw-alias-bg-base);flex-wrap:wrap;padding-top:8px;position:sticky;bottom:0}._7D6uKa_modalFooter>button{flex:120px}._7D6uKa_detailHeader{padding-top:max(12px, env(safe-area-inset-top));padding-right:max(14px, env(safe-area-inset-right));padding-left:max(14px, env(safe-area-inset-left));flex-wrap:wrap}._7D6uKa_detailTitle{min-width:0}._7D6uKa_detailBody{overscroll-behavior-y:contain;padding-right:max(14px, env(safe-area-inset-right));padding-left:max(14px, env(safe-area-inset-left))}._7D6uKa_detailFooter{padding-right:max(14px, env(safe-area-inset-right));padding-bottom:max(12px, env(safe-area-inset-bottom));padding-left:max(14px, env(safe-area-inset-left));flex-wrap:wrap}._7D6uKa_detailFooter>button{flex:96px}._7D6uKa_detailFooter>._7D6uKa_detailMeta{text-align:end;flex:1 0 100%;margin-left:0}._7D6uKa_scheduleRow{flex-direction:column;align-items:stretch}._7D6uKa_schedulePreset{width:100%}}._7D6uKa_entry:focus-visible,._7D6uKa_card:focus-visible,._7D6uKa_primaryButton:focus-visible,._7D6uKa_ghostButton:focus-visible,._7D6uKa_dangerButton:focus-visible,._7D6uKa_iconButton:focus-visible,._7D6uKa_linkButton:focus-visible,._7D6uKa_search:focus-visible,._7D6uKa_input:focus-visible,._7D6uKa_select:focus-visible,._7D6uKa_schedulePreset:focus-visible,._7D6uKa_scheduleToggle input:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}._7D6uKa_entry,._7D6uKa_primaryButton,._7D6uKa_ghostButton,._7D6uKa_dangerButton,._7D6uKa_iconButton,._7D6uKa_linkButton,._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset,._7D6uKa_scheduleToggle input{transition:background-color .12s,color .12s,border-color .12s,outline-color .12s,box-shadow .12s,transform .12s}._7D6uKa_card:active{box-shadow:var(--dsw-shadow-lv1);transform:translateY(0)}._7D6uKa_entry:active,._7D6uKa_primaryButton:active:not(:disabled),._7D6uKa_ghostButton:active:not(:disabled),._7D6uKa_dangerButton:active:not(:disabled),._7D6uKa_iconButton:active:not(:disabled),._7D6uKa_linkButton:active:not(:disabled){transform:translateY(1px)}._7D6uKa_entry[data-active]:hover{background:var(--dsw-specific-sidebar-nav-item-active)}._7D6uKa_iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}._7D6uKa_linkButton:hover:not(:disabled){text-decoration:underline}._7D6uKa_iconButton:disabled,._7D6uKa_linkButton:disabled{opacity:.45;cursor:default}._7D6uKa_search:focus,._7D6uKa_select:focus,._7D6uKa_schedulePreset:focus{border-color:var(--dsw-alias-state-business-primary)}._7D6uKa_scheduleToggle input{margin:0}@media (prefers-reduced-motion:reduce){._7D6uKa_entry,._7D6uKa_card,._7D6uKa_primaryButton,._7D6uKa_ghostButton,._7D6uKa_dangerButton,._7D6uKa_iconButton,._7D6uKa_linkButton,._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset,._7D6uKa_scheduleToggle input{transition:none}._7D6uKa_cardSpinner{animation:none}}._7D6uKa_cardTags{flex-wrap:wrap;gap:4px;display:flex}._7D6uKa_cardTag{border:1px solid var(--dsh-task-tag-border);background:var(--dsh-task-tag-fill);max-width:100%;color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;border-radius:999px;padding:0 7px;font-size:10px;line-height:16px;overflow:hidden}._7D6uKa_tagFilter{flex-wrap:wrap;align-items:center;gap:6px;margin:0 0 10px;display:flex}._7D6uKa_tagFilterLabel{color:var(--dsw-alias-label-tertiary);font-size:11px}._7D6uKa_tagChip{border:1px solid var(--dsh-task-tag-border);color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border-radius:999px;padding:1px 9px;font-family:inherit;font-size:11px;line-height:18px}._7D6uKa_tagChip[data-active=true]{background:var(--dsh-task-tag-fill);color:var(--dsw-alias-label-primary)}._7D6uKa_cardTag[data-tag-tone=\"0\"],._7D6uKa_tagChip[data-tag-tone=\"0\"]{--dsh-task-tag-fill:#4e93e82e;--dsh-task-tag-border:#4e93e866}._7D6uKa_cardTag[data-tag-tone=\"1\"],._7D6uKa_tagChip[data-tag-tone=\"1\"]{--dsh-task-tag-fill:#2ea36a2e;--dsh-task-tag-border:#2ea36a66}._7D6uKa_cardTag[data-tag-tone=\"2\"],._7D6uKa_tagChip[data-tag-tone=\"2\"]{--dsh-task-tag-fill:#d08a2a2e;--dsh-task-tag-border:#d08a2a66}._7D6uKa_cardTag[data-tag-tone=\"3\"],._7D6uKa_tagChip[data-tag-tone=\"3\"]{--dsh-task-tag-fill:#b456c82e;--dsh-task-tag-border:#b456c866}._7D6uKa_cardTag[data-tag-tone=\"4\"],._7D6uKa_tagChip[data-tag-tone=\"4\"]{--dsh-task-tag-fill:#cf5f7a2e;--dsh-task-tag-border:#cf5f7a66}._7D6uKa_cardTag[data-tag-tone=\"5\"],._7D6uKa_tagChip[data-tag-tone=\"5\"]{--dsh-task-tag-fill:#4a9fb52e;--dsh-task-tag-border:#4a9fb566}._7D6uKa_fieldHint{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.4}._7D6uKa_tagRow{align-items:center;gap:6px;display:flex}._7D6uKa_tagRow ._7D6uKa_input{flex:1 1 0;min-width:0}._7D6uKa_tagRow ._7D6uKa_ghostButton{flex:none}._7D6uKa_tagAddButton{align-self:flex-start}._7D6uKa_projectFilter{flex:none;align-items:center;gap:6px;display:flex}._7D6uKa_projectFilterLabel{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:12px}._7D6uKa_projectDialog{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;flex-direction:column;flex:none;gap:8px;margin-bottom:8px;padding:10px 12px;display:flex}._7D6uKa_projectDialogActions{justify-content:flex-end;gap:8px;display:flex}._7D6uKa_aiParse{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;flex-direction:column;gap:6px;padding:10px 12px;display:flex}._7D6uKa_aiParseRow{align-items:center;gap:8px;display:flex}._7D6uKa_aiParseRow ._7D6uKa_select{flex:1 1 0;min-width:0}._7D6uKa_aiParseRow ._7D6uKa_ghostButton,._7D6uKa_aiParseRow ._7D6uKa_primaryButton{flex:none}";
1904
1986
  const tagId$1 = "@linxin666/dsh-client-ui-task-board/packages/dsh-task-board/src/client/board.module.css";
1905
1987
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
1906
1988
  const tag = document.createElement("style");
@@ -1910,6 +1992,8 @@ window.__ModuleLoader__.load({
1910
1992
  document.head.appendChild(tag);
1911
1993
  }
1912
1994
  var board_module_css_default = {
1995
+ "aiParse": "_7D6uKa_aiParse",
1996
+ "aiParseRow": "_7D6uKa_aiParseRow",
1913
1997
  "backButton": "_7D6uKa_backButton",
1914
1998
  "board": "_7D6uKa_board",
1915
1999
  "boardHeader": "_7D6uKa_boardHeader",
@@ -1966,6 +2050,10 @@ window.__ModuleLoader__.load({
1966
2050
  "modalTitle": "_7D6uKa_modalTitle",
1967
2051
  "moveRow": "_7D6uKa_moveRow",
1968
2052
  "primaryButton": "_7D6uKa_primaryButton",
2053
+ "projectDialog": "_7D6uKa_projectDialog",
2054
+ "projectDialogActions": "_7D6uKa_projectDialogActions",
2055
+ "projectFilter": "_7D6uKa_projectFilter",
2056
+ "projectFilterLabel": "_7D6uKa_projectFilterLabel",
1969
2057
  "promptBlock": "_7D6uKa_promptBlock",
1970
2058
  "scheduleInput": "_7D6uKa_scheduleInput",
1971
2059
  "scheduleInputInvalid": "_7D6uKa_scheduleInputInvalid",
@@ -2010,7 +2098,7 @@ window.__ModuleLoader__.load({
2010
2098
  /** DOM id shared by the tag-name inputs and their datalist (one board at a time). */
2011
2099
  const TAG_NAME_LIST_ID = "dsh-task-board-tag-names";
2012
2100
  /** Modal overlay: closes on backdrop press, submits through the form. */
2013
- function ModalShell({ ariaLabel, title, error, pending, submitLabel, onSubmit, onClose, children }) {
2101
+ function ModalShell({ ariaLabel, title, error, pending, submitLabel, onSubmit, onClose, secondaryAction, children }) {
2014
2102
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2015
2103
  className: board_module_css_default.modalBackdrop,
2016
2104
  onMouseDown: (event) => {
@@ -2036,17 +2124,27 @@ window.__ModuleLoader__.load({
2036
2124
  }),
2037
2125
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
2038
2126
  className: board_module_css_default.modalFooter,
2039
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2040
- type: "button",
2041
- className: board_module_css_default.ghostButton,
2042
- onClick: onClose,
2043
- children: t("new.cancel")
2044
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2045
- type: "submit",
2046
- className: board_module_css_default.primaryButton,
2047
- disabled: pending,
2048
- children: submitLabel
2049
- })]
2127
+ children: [
2128
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2129
+ type: "button",
2130
+ className: board_module_css_default.ghostButton,
2131
+ onClick: onClose,
2132
+ children: t("new.cancel")
2133
+ }),
2134
+ secondaryAction !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2135
+ type: "button",
2136
+ className: board_module_css_default.ghostButton,
2137
+ disabled: pending,
2138
+ onClick: secondaryAction.onSubmit,
2139
+ children: secondaryAction.label
2140
+ }),
2141
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2142
+ type: "submit",
2143
+ className: board_module_css_default.primaryButton,
2144
+ disabled: pending,
2145
+ children: submitLabel
2146
+ })
2147
+ ]
2050
2148
  })
2051
2149
  ]
2052
2150
  })
@@ -2178,21 +2276,42 @@ window.__ModuleLoader__.load({
2178
2276
  });
2179
2277
  }
2180
2278
  /**
2181
- * Drop blank rows and trim what survives, so the wire always carries a valid
2182
- * tag list (the protocol rejects a list with a blank name or an empty array).
2279
+ * Clean a tag list via normalizeTags: trim, drop blanks and duplicates, cap
2280
+ * lengths and count, so the wire always carries a valid tag list.
2183
2281
  */
2184
2282
  function cleanTags(tags) {
2185
- const cleaned = [];
2186
- for (const tag of tags) {
2187
- const name = tag.name.trim();
2188
- if (name === "") continue;
2189
- const promptPrefix = tag.promptPrefix?.trim();
2190
- cleaned.push(promptPrefix === void 0 || promptPrefix === "" ? { name } : {
2191
- name,
2192
- promptPrefix
2193
- });
2283
+ return normalizeTags(tags) ?? [];
2284
+ }
2285
+ //#endregion
2286
+ //#region src/client/board/parse-model-pref.ts
2287
+ /**
2288
+ * The model the user last picked for AI parsing, remembered per browser so the
2289
+ * next new-task modal starts from it instead of the roster's first entry
2290
+ * (issue #1621). An empty value is a real choice — "let the Host use its
2291
+ * default model" — and is remembered as such.
2292
+ */
2293
+ const PARSE_MODEL_KEY = "dsh-task-board.parse-model";
2294
+ /** The nearest browser storage, or undefined when the global itself throws. */
2295
+ function defaultStorage() {
2296
+ try {
2297
+ return globalThis.localStorage;
2298
+ } catch {
2299
+ return;
2300
+ }
2301
+ }
2302
+ /** The remembered parse model; '' means the Host default (or no preference). */
2303
+ function readParseModelPreference(storage = defaultStorage()) {
2304
+ try {
2305
+ return storage?.getItem(PARSE_MODEL_KEY) ?? "";
2306
+ } catch {
2307
+ return "";
2194
2308
  }
2195
- return cleaned;
2309
+ }
2310
+ /** Remember the picked parse model; '' records the Host-default choice. */
2311
+ function writeParseModelPreference(model, storage = defaultStorage()) {
2312
+ try {
2313
+ storage?.setItem(PARSE_MODEL_KEY, model);
2314
+ } catch {}
2196
2315
  }
2197
2316
  //#endregion
2198
2317
  //#region src/client/board/NewTaskModal.tsx
@@ -2201,12 +2320,12 @@ window.__ModuleLoader__.load({
2201
2320
  * Creates through the Host and closes only after the Host confirms it.
2202
2321
  */
2203
2322
  /** New-task form overlay. */
2204
- function NewTaskModal({ controller, onClose, initialTask, onDuplicateSuccess }) {
2323
+ function NewTaskModal({ controller, onClose, initialTask, defaultWorkspaceId, onDuplicateSuccess }) {
2205
2324
  const isDuplicate = initialTask !== void 0;
2206
2325
  const [title, setTitle] = (0, react.useState)(initialTask?.title ?? "");
2207
2326
  const [description, setDescription] = (0, react.useState)(initialTask?.description ?? "");
2208
2327
  const [prompt, setPrompt] = (0, react.useState)(initialTask?.prompt ?? "");
2209
- const [workspaceId, setWorkspaceId] = (0, react.useState)(initialTask?.workspaceId ?? "");
2328
+ const [workspaceId, setWorkspaceId] = (0, react.useState)(initialTask?.workspaceId ?? defaultWorkspaceId ?? "");
2210
2329
  const [mode, setMode] = (0, react.useState)(initialTask?.mode ?? "");
2211
2330
  const [permission, setPermission] = (0, react.useState)(initialTask?.permission ?? "");
2212
2331
  const [model, setModel] = (0, react.useState)(initialTask?.model ?? "");
@@ -2222,8 +2341,52 @@ window.__ModuleLoader__.load({
2222
2341
  const [error, setError] = (0, react.useState)(void 0);
2223
2342
  const [pending, setPending] = (0, react.useState)(false);
2224
2343
  const [options, setOptions] = (0, react.useState)(controller.getSnapshot().executionOptions);
2344
+ const [canParse] = (0, react.useState)(controller.getSnapshot().canParseTask === true);
2345
+ const [parseText, setParseText] = (0, react.useState)("");
2346
+ const [parseModel, setParseModel] = (0, react.useState)(() => readParseModelPreference());
2347
+ const [parsePending, setParsePending] = (0, react.useState)(false);
2348
+ const [parseError, setParseError] = (0, react.useState)(void 0);
2349
+ const parseAbort = (0, react.useRef)(void 0);
2350
+ const parseModels = options.models ?? [];
2225
2351
  (0, react.useEffect)(() => controller.subscribe(() => setOptions(controller.getSnapshot().executionOptions)), [controller]);
2226
- const submit = async () => {
2352
+ (0, react.useEffect)(() => {
2353
+ if (parseModel === "" || parseModels.length === 0) return;
2354
+ if (parseModels.some((option) => option.id === parseModel)) return;
2355
+ setParseModel("");
2356
+ writeParseModelPreference("");
2357
+ }, [parseModel, options.models]);
2358
+ const runParse = async () => {
2359
+ const text = parseText.trim();
2360
+ if (text === "") {
2361
+ setParseError(t("new.aiParseEmpty"));
2362
+ return;
2363
+ }
2364
+ const abort = new AbortController();
2365
+ parseAbort.current = abort;
2366
+ setParsePending(true);
2367
+ setParseError(void 0);
2368
+ try {
2369
+ const draft = await controller.parseTaskDraft({
2370
+ text,
2371
+ ...parseModel === "" ? {} : { model: parseModel }
2372
+ }, abort.signal);
2373
+ setTitle(draft.title);
2374
+ setDescription(draft.description);
2375
+ setPrompt(draft.prompt);
2376
+ } catch (parseFailure) {
2377
+ if (!abort.signal.aborted) setParseError(parseFailure instanceof Error ? parseFailure.message : String(parseFailure));
2378
+ } finally {
2379
+ parseAbort.current = void 0;
2380
+ setParsePending(false);
2381
+ }
2382
+ };
2383
+ /**
2384
+ * Create the task through the Host, then optionally start it.
2385
+ * @param runAfterCreate - true for the "create and run" action: the task is
2386
+ * committed either way, and a refused start opens the task instead of
2387
+ * reporting the creation as failed.
2388
+ */
2389
+ const submit = async (runAfterCreate) => {
2227
2390
  if (scheduleEnabled) {
2228
2391
  const cron = scheduleCron.trim();
2229
2392
  if (cron === "" || !isValidCron(cron)) {
@@ -2252,7 +2415,7 @@ window.__ModuleLoader__.load({
2252
2415
  };
2253
2416
  const tagList = cleanTags(tags);
2254
2417
  setPending(true);
2255
- if (await controller.createTaskConfirmed({
2418
+ const task = await controller.createTaskConfirmed({
2256
2419
  title,
2257
2420
  description,
2258
2421
  prompt,
@@ -2268,13 +2431,17 @@ window.__ModuleLoader__.load({
2268
2431
  enabled: true,
2269
2432
  cron: scheduleCron.trim()
2270
2433
  } : void 0
2271
- }) === void 0) {
2434
+ });
2435
+ if (task === void 0) {
2272
2436
  setPending(false);
2273
2437
  setError(controller.getSnapshot().transportError ?? t("new.required"));
2274
2438
  return;
2275
2439
  }
2276
2440
  if (isDuplicate && archiveOriginal && initialTask !== void 0) if (onDuplicateSuccess !== void 0) await onDuplicateSuccess(initialTask.id);
2277
2441
  else await controller.archiveTask(initialTask.id);
2442
+ if (runAfterCreate) {
2443
+ if (!await controller.runTask(task.id)) controller.openTask(task.id);
2444
+ }
2278
2445
  onClose();
2279
2446
  };
2280
2447
  /** Next-run preview for a valid armed cron (creation-time only). */
@@ -2287,10 +2454,79 @@ window.__ModuleLoader__.load({
2287
2454
  pending,
2288
2455
  submitLabel: t("new.submit"),
2289
2456
  onSubmit: () => {
2290
- submit();
2457
+ submit(false);
2291
2458
  },
2292
2459
  onClose,
2460
+ secondaryAction: {
2461
+ label: t("new.createAndRun"),
2462
+ onSubmit: () => {
2463
+ submit(true);
2464
+ }
2465
+ },
2293
2466
  children: [
2467
+ canParse && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2468
+ className: board_module_css_default.aiParse,
2469
+ "data-dsh-part": "ai-parse",
2470
+ children: [
2471
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2472
+ className: board_module_css_default.fieldLabel,
2473
+ children: t("new.aiParse")
2474
+ }),
2475
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2476
+ className: board_module_css_default.fieldHint,
2477
+ children: t("new.aiParseHint")
2478
+ }),
2479
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
2480
+ className: board_module_css_default.input,
2481
+ rows: 3,
2482
+ value: parseText,
2483
+ placeholder: t("new.aiParsePlaceholder"),
2484
+ spellCheck: false,
2485
+ onChange: (event) => {
2486
+ setParseText(event.target.value);
2487
+ setParseError(void 0);
2488
+ }
2489
+ }),
2490
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2491
+ className: board_module_css_default.aiParseRow,
2492
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
2493
+ className: board_module_css_default.select,
2494
+ value: parseModel,
2495
+ "aria-label": t("new.aiParseModel"),
2496
+ onChange: (event) => {
2497
+ setParseModel(event.target.value);
2498
+ writeParseModelPreference(event.target.value);
2499
+ },
2500
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2501
+ value: "",
2502
+ children: t("exec.model.default")
2503
+ }), parseModels.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2504
+ value: option.id,
2505
+ children: option.name ?? option.id
2506
+ }, option.id))]
2507
+ }), parsePending ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2508
+ type: "button",
2509
+ className: board_module_css_default.ghostButton,
2510
+ onClick: () => {
2511
+ parseAbort.current?.abort();
2512
+ },
2513
+ children: t("new.aiParseCancel")
2514
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2515
+ type: "button",
2516
+ className: board_module_css_default.primaryButton,
2517
+ disabled: parseText.trim() === "",
2518
+ onClick: () => {
2519
+ runParse();
2520
+ },
2521
+ children: t("new.aiParseRun")
2522
+ })]
2523
+ }),
2524
+ parseError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2525
+ className: board_module_css_default.formError,
2526
+ children: parseError
2527
+ })
2528
+ ]
2529
+ }),
2294
2530
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskContentFields, {
2295
2531
  title,
2296
2532
  description,
@@ -2759,6 +2995,39 @@ window.__ModuleLoader__.load({
2759
2995
  })]
2760
2996
  });
2761
2997
  }
2998
+ /** Edit-tags modal: edit labels only, shown for tasks after first execution. */
2999
+ function EditTagsModal({ controller, task, onClose }) {
3000
+ const [tags, setTags] = (0, react.useState)(task.tags ?? []);
3001
+ const [error, setError] = (0, react.useState)(void 0);
3002
+ const [pending, setPending] = (0, react.useState)(false);
3003
+ const submit = async () => {
3004
+ setPending(true);
3005
+ const tagList = cleanTags(tags);
3006
+ const patch = { tags: tagList.length > 0 ? tagList : null };
3007
+ if (await controller.updateTask(task.id, patch)) {
3008
+ onClose();
3009
+ return;
3010
+ }
3011
+ setPending(false);
3012
+ setError(controller.getSnapshot().transportError ?? t("new.required"));
3013
+ };
3014
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModalShell, {
3015
+ ariaLabel: t("detail.editTags"),
3016
+ title: t("detail.editTags"),
3017
+ error,
3018
+ pending,
3019
+ submitLabel: t("edit.save"),
3020
+ onSubmit: () => {
3021
+ submit();
3022
+ },
3023
+ onClose,
3024
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskTagFields, {
3025
+ tags,
3026
+ knownTags: collectKnownTags(controller.getSnapshot().tasks),
3027
+ onChange: setTags
3028
+ })
3029
+ });
3030
+ }
2762
3031
  //#endregion
2763
3032
  //#region src/client/board/TaskDetail.tsx
2764
3033
  /**
@@ -3090,6 +3359,7 @@ window.__ModuleLoader__.load({
3090
3359
  function TaskDetail({ controller, task }) {
3091
3360
  const [confirmDelete, setConfirmDelete] = (0, react.useState)(false);
3092
3361
  const [showEdit, setShowEdit] = (0, react.useState)(false);
3362
+ const [showEditTags, setShowEditTags] = (0, react.useState)(false);
3093
3363
  const [showDuplicate, setShowDuplicate] = (0, react.useState)(false);
3094
3364
  const [latest, setLatest] = (0, react.useState)(task);
3095
3365
  (0, react.useEffect)(() => {
@@ -3097,6 +3367,7 @@ window.__ModuleLoader__.load({
3097
3367
  }, [task]);
3098
3368
  (0, react.useEffect)(() => {
3099
3369
  setShowEdit(false);
3370
+ setShowEditTags(false);
3100
3371
  setShowDuplicate(false);
3101
3372
  }, [task.id]);
3102
3373
  const current = latest;
@@ -3166,6 +3437,21 @@ window.__ModuleLoader__.load({
3166
3437
  children: current.description !== "" ? current.description : "—"
3167
3438
  })]
3168
3439
  }),
3440
+ current.tags !== void 0 && current.tags.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3441
+ className: board_module_css_default.detailSection,
3442
+ "data-dsh-part": "tags",
3443
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("new.tags") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3444
+ className: board_module_css_default.cardTags,
3445
+ children: current.tags.map((tag) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3446
+ className: board_module_css_default.cardTag,
3447
+ "data-tag-tone": tagTone(tag.name),
3448
+ "data-dsh-part": "tag-badge",
3449
+ "data-tag-hint": tag.promptPrefix === void 0 ? void 0 : tag.promptPrefix,
3450
+ title: tag.promptPrefix === void 0 ? tag.name : tag.promptPrefix,
3451
+ children: tag.name
3452
+ }, tag.name))
3453
+ })]
3454
+ }),
3169
3455
  current.freeze !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3170
3456
  className: board_module_css_default.detailSection,
3171
3457
  "data-dsh-part": "freeze",
@@ -3332,6 +3618,15 @@ window.__ModuleLoader__.load({
3332
3618
  },
3333
3619
  children: t("detail.edit")
3334
3620
  }),
3621
+ !archived && !canEditTaskContent(current) && current.status !== "running" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3622
+ type: "button",
3623
+ className: board_module_css_default.ghostButton,
3624
+ disabled: pending,
3625
+ onClick: () => {
3626
+ setShowEditTags(true);
3627
+ },
3628
+ children: t("detail.editTags")
3629
+ }),
3335
3630
  !archived && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3336
3631
  type: "button",
3337
3632
  className: board_module_css_default.ghostButton,
@@ -3412,6 +3707,13 @@ window.__ModuleLoader__.load({
3412
3707
  setShowEdit(false);
3413
3708
  }
3414
3709
  }),
3710
+ showEditTags && !archived && current.status !== "running" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EditTagsModal, {
3711
+ controller,
3712
+ task: current,
3713
+ onClose: () => {
3714
+ setShowEditTags(false);
3715
+ }
3716
+ }),
3415
3717
  showDuplicate && !archived && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NewTaskModal, {
3416
3718
  controller,
3417
3719
  initialTask: current,
@@ -3426,13 +3728,6 @@ window.__ModuleLoader__.load({
3426
3728
  ]
3427
3729
  });
3428
3730
  }
3429
- //#endregion
3430
- //#region src/client/board/TaskBoard.tsx
3431
- /**
3432
- * Board view: the multi-column kanban that replaces the middle column while
3433
- * active. Cards open the task detail (never execute directly); the header
3434
- * offers filter, new-task, and a back-to-chat escape.
3435
- */
3436
3731
  /** Case-insensitive title/description/tag/freeze-snapshot match. */
3437
3732
  function matchesFilter(task, filter) {
3438
3733
  if (filter.trim() === "") return true;
@@ -3479,10 +3774,33 @@ window.__ModuleLoader__.load({
3479
3774
  const [filter, setFilter] = (0, react.useState)("");
3480
3775
  const [tagFilter, setTagFilter] = (0, react.useState)([]);
3481
3776
  const [showNew, setShowNew] = (0, react.useState)(false);
3777
+ const [projectId, setProjectId] = (0, react.useState)("");
3778
+ const [showNewProject, setShowNewProject] = (0, react.useState)(false);
3779
+ const [newProjectPath, setNewProjectPath] = (0, react.useState)("");
3780
+ const [newProjectError, setNewProjectError] = (0, react.useState)(void 0);
3781
+ const [newProjectPending, setNewProjectPending] = (0, react.useState)(false);
3482
3782
  const selected = selectedTaskOf(snapshot);
3483
3783
  const archiveView = snapshot.archiveView;
3484
3784
  const knownTags = collectKnownTags(snapshot.tasks);
3485
- const visible = snapshot.tasks.filter((task) => (archiveView ? task.archivedAt !== void 0 : task.archivedAt === void 0) && matchesFilter(task, filter) && matchesTagFilter(task, tagFilter));
3785
+ const visible = snapshot.tasks.filter((task) => (archiveView ? task.archivedAt !== void 0 : task.archivedAt === void 0) && (projectId === "" || task.workspaceId === projectId) && matchesFilter(task, filter) && matchesTagFilter(task, tagFilter));
3786
+ const projects = snapshot.executionOptions.workspaces;
3787
+ const canCreateProject = snapshot.canCreateWorkspace === true;
3788
+ const submitNewProject = async () => {
3789
+ const path = newProjectPath.trim();
3790
+ if (path === "") return;
3791
+ setNewProjectPending(true);
3792
+ setNewProjectError(void 0);
3793
+ try {
3794
+ const created = await controller.createWorkspace(path);
3795
+ setProjectId(created.workspaceId);
3796
+ setShowNewProject(false);
3797
+ setNewProjectPath("");
3798
+ } catch (error) {
3799
+ setNewProjectError(error instanceof Error ? error.message : String(error));
3800
+ } finally {
3801
+ setNewProjectPending(false);
3802
+ }
3803
+ };
3486
3804
  const toggleTag = (0, react.useCallback)((name) => {
3487
3805
  setTagFilter((current) => current.includes(name) ? current.filter((entry) => entry !== name) : [...current, name]);
3488
3806
  }, []);
@@ -3521,6 +3839,41 @@ window.__ModuleLoader__.load({
3521
3839
  timeZone: snapshot.host.scheduler.timeZone
3522
3840
  })
3523
3841
  }),
3842
+ (projects.length > 0 || canCreateProject) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3843
+ className: board_module_css_default.projectFilter,
3844
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3845
+ className: board_module_css_default.projectFilterLabel,
3846
+ children: t("board.project")
3847
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
3848
+ className: board_module_css_default.select,
3849
+ "data-dsh-part": "project-filter",
3850
+ value: projectId,
3851
+ "aria-label": t("board.project"),
3852
+ onChange: (event) => {
3853
+ const value = event.target.value;
3854
+ if (value === "__dsh_new_project__") {
3855
+ setNewProjectError(void 0);
3856
+ setShowNewProject(true);
3857
+ return;
3858
+ }
3859
+ setProjectId(value);
3860
+ },
3861
+ children: [
3862
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3863
+ value: "",
3864
+ children: t("board.projectAll")
3865
+ }),
3866
+ projects.map((project) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3867
+ value: project.workspaceId,
3868
+ children: project.title
3869
+ }, project.workspaceId)),
3870
+ canCreateProject && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3871
+ value: "__dsh_new_project__",
3872
+ children: t("board.projectNew")
3873
+ })
3874
+ ]
3875
+ })]
3876
+ }),
3524
3877
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3525
3878
  className: board_module_css_default.search,
3526
3879
  type: "search",
@@ -3549,6 +3902,52 @@ window.__ModuleLoader__.load({
3549
3902
  })
3550
3903
  ]
3551
3904
  }),
3905
+ showNewProject && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3906
+ className: board_module_css_default.projectDialog,
3907
+ "data-dsh-part": "project-dialog",
3908
+ children: [
3909
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3910
+ className: board_module_css_default.field,
3911
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3912
+ className: board_module_css_default.fieldLabel,
3913
+ children: t("board.projectNewPath")
3914
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3915
+ className: board_module_css_default.input,
3916
+ value: newProjectPath,
3917
+ placeholder: t("board.projectNewPathPlaceholder"),
3918
+ spellCheck: false,
3919
+ onChange: (event) => {
3920
+ setNewProjectPath(event.target.value);
3921
+ setNewProjectError(void 0);
3922
+ }
3923
+ })]
3924
+ }),
3925
+ newProjectError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3926
+ className: board_module_css_default.formError,
3927
+ children: t("board.projectCreateFailed", { error: newProjectError })
3928
+ }),
3929
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3930
+ className: board_module_css_default.projectDialogActions,
3931
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3932
+ type: "button",
3933
+ className: board_module_css_default.ghostButton,
3934
+ onClick: () => {
3935
+ setShowNewProject(false);
3936
+ setNewProjectError(void 0);
3937
+ },
3938
+ children: t("new.cancel")
3939
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3940
+ type: "button",
3941
+ className: board_module_css_default.primaryButton,
3942
+ disabled: newProjectPending || newProjectPath.trim() === "",
3943
+ onClick: () => {
3944
+ submitNewProject();
3945
+ },
3946
+ children: t("board.projectCreate")
3947
+ })]
3948
+ })
3949
+ ]
3950
+ }),
3552
3951
  !archiveView && knownTags.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3553
3952
  className: board_module_css_default.tagFilter,
3554
3953
  "data-dsh-part": "tag-filter",
@@ -3681,6 +4080,7 @@ window.__ModuleLoader__.load({
3681
4080
  }),
3682
4081
  showNew && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NewTaskModal, {
3683
4082
  controller,
4083
+ ...projectId === "" ? {} : { defaultWorkspaceId: projectId },
3684
4084
  onClose: () => {
3685
4085
  setShowNew(false);
3686
4086
  }
@@ -3933,10 +4333,12 @@ window.__ModuleLoader__.load({
3933
4333
  if (nested !== null) return nested;
3934
4334
  for (const child of root.children) if (child.tagName === "BUTTON") return child;
3935
4335
  }
3936
- /** Build the entry row (a detached button; insert once the shell is up). */
4336
+ /** Build the entry row (detached; inserted once the shell is up). */
3937
4337
  function createEntry(options) {
3938
- const entry = document.createElement("button");
3939
- entry.type = "button";
4338
+ const actions = options.actions ?? [];
4339
+ const composite = actions.length > 0;
4340
+ const entry = document.createElement(composite ? "div" : "button");
4341
+ if (!composite) entry.type = "button";
3940
4342
  entry.setAttribute(options.rowAttribute, "");
3941
4343
  if (options.plugin !== void 0) {
3942
4344
  entry.setAttribute("data-dsh-plugin", options.plugin);
@@ -3948,17 +4350,57 @@ window.__ModuleLoader__.load({
3948
4350
  const iconSpan = document.createElement("span");
3949
4351
  iconSpan.className = options.css["entryIcon"] ?? "";
3950
4352
  iconSpan.innerHTML = options.icon;
3951
- entry.append(iconSpan, labelSpan);
4353
+ const main = composite ? document.createElement("button") : entry;
4354
+ if (composite) {
4355
+ main.type = "button";
4356
+ main.className = options.css["entryMain"] ?? "";
4357
+ main.append(iconSpan, labelSpan);
4358
+ entry.append(main);
4359
+ } else entry.append(iconSpan, labelSpan);
4360
+ const actionButtons = [];
4361
+ for (const action of actions) {
4362
+ const button = document.createElement("button");
4363
+ button.type = "button";
4364
+ button.className = options.css["entryAction"] ?? "";
4365
+ button.setAttribute("data-dsh-entry-action", action.id);
4366
+ button.innerHTML = action.icon;
4367
+ button.addEventListener("click", () => {
4368
+ action.onClick(button);
4369
+ });
4370
+ entry.append(button);
4371
+ actionButtons.push({
4372
+ action,
4373
+ button
4374
+ });
4375
+ }
4376
+ let open = false;
4377
+ const applyActions = () => {
4378
+ for (const { action, button } of actionButtons) {
4379
+ button.innerHTML = open || action.inactiveIcon === void 0 ? action.icon : action.inactiveIcon;
4380
+ const text = action.label(open);
4381
+ button.setAttribute("aria-label", text);
4382
+ button.setAttribute("title", text);
4383
+ if (action.inactiveIcon !== void 0) button.setAttribute("aria-expanded", String(open));
4384
+ }
4385
+ };
3952
4386
  const applyLabel = () => {
3953
- entry.setAttribute("aria-label", options.label());
3954
- if (options.tooltip !== void 0) entry.setAttribute("title", options.tooltip());
4387
+ main.setAttribute("aria-label", options.label());
4388
+ if (options.tooltip !== void 0) main.setAttribute("title", options.tooltip());
3955
4389
  labelSpan.textContent = options.label();
4390
+ applyActions();
3956
4391
  };
3957
4392
  applyLabel();
3958
- entry.addEventListener("click", options.onToggle);
4393
+ main.addEventListener("click", options.onToggle);
4394
+ if (composite) entry.addEventListener("click", (event) => {
4395
+ if (event.target === entry) options.onToggle();
4396
+ });
3959
4397
  return {
3960
4398
  entry,
3961
- applyLabel
4399
+ applyLabel,
4400
+ setOpen: (next) => {
4401
+ open = next;
4402
+ applyActions();
4403
+ }
3962
4404
  };
3963
4405
  }
3964
4406
  /** Re-insert the entry after the New Session row (before the browser region). */
@@ -3982,7 +4424,7 @@ window.__ModuleLoader__.load({
3982
4424
  */
3983
4425
  function mountSidebarEntry$1(options) {
3984
4426
  if (typeof document !== "undefined" && document.querySelector(options.rowSelector) !== null) return () => {};
3985
- const { entry, applyLabel } = createEntry(options);
4427
+ const { entry, applyLabel, setOpen } = createEntry(options);
3986
4428
  let root;
3987
4429
  let placed = false;
3988
4430
  let unsubscribeRefresh;
@@ -4022,8 +4464,10 @@ window.__ModuleLoader__.load({
4022
4464
  });
4023
4465
  const unsubscribeActive = options.active === void 0 ? void 0 : (() => {
4024
4466
  const syncActive = () => {
4025
- if (options.active.isOpen()) entry.dataset.active = "true";
4467
+ const open = options.active.isOpen();
4468
+ if (open) entry.dataset.active = "true";
4026
4469
  else delete entry.dataset.active;
4470
+ setOpen(open);
4027
4471
  };
4028
4472
  const unsubscribe = options.active.subscribe(syncActive);
4029
4473
  syncActive();
@@ -4902,19 +5346,64 @@ window.__ModuleLoader__.load({
4902
5346
  //#endregion
4903
5347
  //#region src/protocol.ts
4904
5348
  const TASK_BOARD_API_PREFIX = "/api/task-board";
5349
+ /** Whether a decoded reply carries the three draft strings the form accepts. */
5350
+ function isTaskParseDraft(value) {
5351
+ if (typeof value !== "object" || value === null) return false;
5352
+ const record = value;
5353
+ return typeof record.title === "string" && typeof record.description === "string" && typeof record.prompt === "string";
5354
+ }
4905
5355
  //#endregion
4906
5356
  //#region src/client/host-api.ts
4907
5357
  const IMPORT_MARKER = "dsh.taskBoard.v2.hostImported";
4908
5358
  const SOURCE_KEY = "dsh.taskBoard.v2.sourceId";
4909
5359
  const IMPORT_REQUEST_KEY = "dsh.taskBoard.v2.importRequestId";
4910
5360
  const REQUEST_TIMEOUT_MS = 15e3;
5361
+ /** Re-notify the panel at most this often while the event stream stays broken. */
5362
+ const STREAM_ERROR_NOTIFY_MS = 15e3;
5363
+ /** Mirrors the Host's own parse budget; only used to phrase the timeout. */
5364
+ const TASK_PARSE_TIMEOUT_SECONDS = 45;
5365
+ /** Transport failure carrying a stable class next to its user-facing message. */
5366
+ var HostApiError = class extends Error {
5367
+ failure;
5368
+ status;
5369
+ constructor(failure, message, status) {
5370
+ super(message);
5371
+ this.failure = failure;
5372
+ this.status = status;
5373
+ this.name = "HostApiError";
5374
+ }
5375
+ };
4911
5376
  function uuid() {
4912
5377
  return globalThis.crypto?.randomUUID?.() ?? `browser-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
4913
5378
  }
5379
+ /**
5380
+ * Read one Host response without ever handing a non-JSON body to JSON.parse:
5381
+ * the core webserver answers an unmounted `/api/*` path with the plain text
5382
+ * "not found", which used to surface as a JavaScript parse error in the panel.
5383
+ */
4914
5384
  async function readJson(response) {
4915
- const body = await response.json();
4916
- if (!response.ok) throw new Error(body.error ?? `task-board request failed: ${response.status}`);
4917
- return body;
5385
+ const text = await response.text();
5386
+ let parsed;
5387
+ let readable = false;
5388
+ if (text.trim() !== "") try {
5389
+ parsed = JSON.parse(text);
5390
+ readable = true;
5391
+ } catch {
5392
+ readable = false;
5393
+ }
5394
+ const hostError = readable && typeof parsed === "object" && parsed !== null && typeof parsed.error === "string" ? parsed.error : void 0;
5395
+ if (response.ok) {
5396
+ if (!readable) throw new HostApiError("unexpected", t("board.hostError.unexpected", { status: String(response.status) }), response.status);
5397
+ return parsed;
5398
+ }
5399
+ if (hostError !== void 0) {
5400
+ if (hostError === "forbidden") throw new HostApiError("unauthorized", t("board.hostError.unauthorized"), response.status);
5401
+ if (response.status === 503 || /lock/i.test(hostError)) throw new HostApiError("locked", t("board.hostError.locked", { detail: hostError }), response.status);
5402
+ throw new HostApiError("rejected", hostError, response.status);
5403
+ }
5404
+ if (response.status === 404) throw new HostApiError("not-mounted", t("board.hostError.notMounted"), 404);
5405
+ if (response.status === 401 || response.status === 403) throw new HostApiError("unauthorized", t("board.hostError.unauthorized"), response.status);
5406
+ throw new HostApiError("unexpected", t("board.hostError.unexpected", { status: String(response.status) }), response.status);
4918
5407
  }
4919
5408
  var HttpTaskBoardHostTransport = class {
4920
5409
  storage;
@@ -4974,14 +5463,54 @@ window.__ModuleLoader__.load({
4974
5463
  signal: controller.signal
4975
5464
  }));
4976
5465
  } catch (error) {
4977
- if (controller.signal.aborted) throw new Error(`task-board Host request timed out after ${REQUEST_TIMEOUT_MS / 1e3}s`);
4978
- throw error;
5466
+ if (error instanceof HostApiError) throw error;
5467
+ if (controller.signal.aborted) throw new HostApiError("timeout", t("board.hostError.timeout", { seconds: String(REQUEST_TIMEOUT_MS / 1e3) }));
5468
+ throw new HostApiError("unreachable", t("board.hostError.unreachable"));
4979
5469
  } finally {
4980
5470
  globalThis.clearTimeout(timeout);
4981
5471
  }
4982
5472
  }
5473
+ /**
5474
+ * Ask the Host to turn pasted text into task fields. The route answers a
5475
+ * typed failure (no model, timeout, unparseable reply) already phrased for
5476
+ * the form, so the UI never renders a raw status code (issue #1540).
5477
+ */
5478
+ async parseDraft(request, signal) {
5479
+ let response;
5480
+ try {
5481
+ response = await fetch(`${TASK_BOARD_API_PREFIX}/parse`, {
5482
+ method: "POST",
5483
+ headers: { "content-type": "application/json" },
5484
+ body: JSON.stringify(request),
5485
+ ...signal === void 0 ? {} : { signal }
5486
+ });
5487
+ } catch {
5488
+ throw new HostApiError("unreachable", t("board.hostError.unreachable"));
5489
+ }
5490
+ const text = await response.text();
5491
+ let parsed;
5492
+ let readable = false;
5493
+ if (text.trim() !== "") try {
5494
+ parsed = JSON.parse(text);
5495
+ readable = true;
5496
+ } catch {
5497
+ readable = false;
5498
+ }
5499
+ const record = readable && typeof parsed === "object" && parsed !== null ? parsed : void 0;
5500
+ if (response.ok) {
5501
+ if (record !== void 0 && isTaskParseDraft(record.draft)) return record.draft;
5502
+ throw new HostApiError("unexpected", t("new.aiParseFailed", { error: t("board.hostError.unexpected", { status: String(response.status) }) }), response.status);
5503
+ }
5504
+ if (response.status === 404) throw new HostApiError("not-mounted", t("new.aiParseUnavailable"), 404);
5505
+ if (response.status === 401 || response.status === 403) throw new HostApiError("unauthorized", t("board.hostError.unauthorized"), response.status);
5506
+ const code = typeof record?.code === "string" ? record.code : void 0;
5507
+ if (code === "no-model") throw new HostApiError("rejected", t("new.aiParseNoModel"), response.status);
5508
+ if (code === "timeout") throw new HostApiError("timeout", t("new.aiParseTimeout", { seconds: String(TASK_PARSE_TIMEOUT_SECONDS) }), response.status);
5509
+ throw new HostApiError("rejected", t("new.aiParseFailed", { error: typeof record?.error === "string" && record.error !== "" ? record.error : String(response.status) }), response.status);
5510
+ }
4983
5511
  subscribe(listener) {
4984
5512
  const events = new EventSource(`${TASK_BOARD_API_PREFIX}/events`);
5513
+ let lastStreamErrorNotify = 0;
4985
5514
  events.onmessage = (message) => {
4986
5515
  try {
4987
5516
  const parsed = JSON.parse(message.data);
@@ -4991,6 +5520,12 @@ window.__ModuleLoader__.load({
4991
5520
  listener();
4992
5521
  }
4993
5522
  };
5523
+ events.onerror = () => {
5524
+ const now = Date.now();
5525
+ if (now - lastStreamErrorNotify < STREAM_ERROR_NOTIFY_MS) return;
5526
+ lastStreamErrorNotify = now;
5527
+ listener();
5528
+ };
4994
5529
  const onVisible = () => {
4995
5530
  if (document.visibilityState === "visible") listener();
4996
5531
  };
@@ -5009,7 +5544,7 @@ window.__ModuleLoader__.load({
5009
5544
  /** The building package's version, when the bundle carries it. */
5010
5545
  function bakedVersion() {
5011
5546
  try {
5012
- return "0.3.22";
5547
+ return "0.3.24";
5013
5548
  } catch {
5014
5549
  return;
5015
5550
  }
@@ -5072,6 +5607,129 @@ window.__ModuleLoader__.load({
5072
5607
  } catch {}
5073
5608
  }
5074
5609
  //#endregion
5610
+ //#region src/client/plugin-card-seat.ts
5611
+ /**
5612
+ * Family plugin-card seat.
5613
+ *
5614
+ * A family plugin contributes its settings card to whichever plugin-card seat
5615
+ * the running host actually renders:
5616
+ *
5617
+ * - `web-ui.plugin.item` — the list seat declared by the dsh-web-settings
5618
+ * group section (this family's own first-level "Web UI plugins" section);
5619
+ * - `settings.plugin.item` — the official keyed seat of the harness's
5620
+ * `ui-settings-plugins` tab, keyed by the settings namespace the card edits.
5621
+ *
5622
+ * SEAT SELECTION IS NOT A DECLARATION PROBE. The official `ui-settings-plugins`
5623
+ * row belongs to the harness bundle and its `configurable` tab always declares
5624
+ * `settings.plugin.item` before any external plugin's `apply()` runs, so
5625
+ * "is the official seat declared?" answers yes even in the deployment whose
5626
+ * whole point is the family group. Choosing on that probe sends every family
5627
+ * card to the official Plugins tab and leaves the group's own section
5628
+ * permanently empty — the family of reports where the section renders its
5629
+ * heading and zero cards.
5630
+ *
5631
+ * The signal that actually distinguishes the two deployments is whether
5632
+ * dsh-web-settings is loaded: it is the package that owns the group section and
5633
+ * it publishes the `webUiSettings` service during `apply()`, which every
5634
+ * family plugin already reads for its settings scope. Group loaded -> the family
5635
+ * seat; group absent -> the official seat.
5636
+ *
5637
+ * The decision is re-evaluated on every `slots/changed` because the group may
5638
+ * apply after this plugin (the family aggregate orders it first, a profile that
5639
+ * installs the group separately need not): the initial contribution goes to the
5640
+ * official seat, then moves to the family seat the moment the group's section
5641
+ * registers. The entry is disposed before the replacement is registered, so a
5642
+ * card is never in two seats at once.
5643
+ *
5644
+ * The shared tree has no client-SDK dependency, so this module reads its
5645
+ * context through the structural shape below; callers pass the plugin's own
5646
+ * `ctx`.
5647
+ */
5648
+ /** The family list seat key. */
5649
+ const FAMILY_PLUGIN_CARD_SEAT = "web-ui.plugin.item";
5650
+ /** The official keyed plugin-card seat key. */
5651
+ const OFFICIAL_PLUGIN_CARD_SEAT = "settings.plugin.item";
5652
+ /** The service dsh-web-settings publishes while it is loaded. */
5653
+ const FAMILY_GROUP_SERVICE = "webUiSettings";
5654
+ /**
5655
+ * Whether the family group (dsh-web-settings) is loaded in this page. The
5656
+ * service is the group package's own contract, so the probe cannot be fooled
5657
+ * by a harness release that starts declaring the official seat differently.
5658
+ */
5659
+ function familyGroupLoaded(ctx) {
5660
+ const get = ctx.get;
5661
+ if (typeof get !== "function") return false;
5662
+ try {
5663
+ return get.call(ctx, FAMILY_GROUP_SERVICE) !== void 0;
5664
+ } catch {
5665
+ return false;
5666
+ }
5667
+ }
5668
+ /** Report a refused registration instead of leaving the user with no card. */
5669
+ function warnRefusedSeat(seat, error) {
5670
+ try {
5671
+ console.warn(`[dsh-web] plugin card registration into "${seat}" was refused; the card will not render`, error);
5672
+ } catch {}
5673
+ }
5674
+ /**
5675
+ * Contribute one family plugin card to the seat this host renders, following
5676
+ * the group if it loads later. The entry is disposed and re-registered on a
5677
+ * seat change, never duplicated.
5678
+ * @param ctx - client context (its slot registry decides the seat).
5679
+ * @param seat - the card contribution.
5680
+ */
5681
+ function installPluginCard(ctx, seat) {
5682
+ const slots = ctx.slots;
5683
+ const component = seat.component;
5684
+ const inject = seat.inject;
5685
+ let dispose;
5686
+ let current;
5687
+ /**
5688
+ * Re-entrancy latch. The registry emits a change event synchronously from
5689
+ * inside both `register` and the previous entry's disposer, so an unguarded
5690
+ * reconcile would re-enter itself mid-move and register the card twice into
5691
+ * the seat it is leaving ("already has an entry for key ...").
5692
+ */
5693
+ let reconciling = false;
5694
+ /** Reconcile the contribution with the currently live seat (no-op when unchanged). */
5695
+ const reconcile = () => {
5696
+ if (reconciling) return;
5697
+ const target = familyGroupLoaded(ctx) ? FAMILY_PLUGIN_CARD_SEAT : OFFICIAL_PLUGIN_CARD_SEAT;
5698
+ if (current === target) return;
5699
+ reconciling = true;
5700
+ const previous = dispose;
5701
+ dispose = void 0;
5702
+ current = void 0;
5703
+ previous?.();
5704
+ try {
5705
+ dispose = slots.register(target === "web-ui.plugin.item" ? {
5706
+ name: FAMILY_PLUGIN_CARD_SEAT,
5707
+ id: seat.id,
5708
+ ...seat.order === void 0 ? {} : { order: seat.order },
5709
+ ...seat.label === void 0 ? {} : { label: seat.label },
5710
+ locale: seat.locale,
5711
+ ...seat.inject === void 0 ? {} : { inject }
5712
+ } : {
5713
+ name: OFFICIAL_PLUGIN_CARD_SEAT,
5714
+ key: seat.namespace,
5715
+ locale: seat.locale,
5716
+ ...seat.inject === void 0 ? {} : { inject }
5717
+ }, component);
5718
+ current = target;
5719
+ } catch (error) {
5720
+ warnRefusedSeat(target, error);
5721
+ } finally {
5722
+ reconciling = false;
5723
+ }
5724
+ };
5725
+ if (typeof ctx.on === "function") try {
5726
+ ctx.on("slots/changed", () => {
5727
+ reconcile();
5728
+ });
5729
+ } catch {}
5730
+ reconcile();
5731
+ }
5732
+ //#endregion
5075
5733
  //#region src/client/index.ts
5076
5734
  /** Locale namespace this plugin owns. */
5077
5735
  const NS = "task-board";
@@ -5118,23 +5776,17 @@ window.__ModuleLoader__.load({
5118
5776
  } catch {}
5119
5777
  const settingsScope = (ctx.get("webUiSettings") ?? ctx.settingsScope).bind({ namespace: TASK_BOARD_NS });
5120
5778
  const settingsCard = new TaskBoardSettingsCardController(settingsScope);
5121
- ctx.slots.inject("web-ui.plugin.item", () => {
5122
- try {
5123
- const unregister = ctx.slots.register({
5124
- name: "web-ui.plugin.item",
5125
- id: "task-board",
5126
- order: 110,
5127
- locale: NS,
5128
- inject: () => settingsCard.inject()
5129
- }, TaskBoardSettingsCard);
5130
- return () => {
5131
- settingsCard.dispose();
5132
- unregister();
5133
- };
5134
- } catch {
5135
- return () => {};
5136
- }
5779
+ installPluginCard(ctx, {
5780
+ namespace: TASK_BOARD_NS,
5781
+ id: "task-board",
5782
+ order: 110,
5783
+ locale: NS,
5784
+ inject: () => settingsCard.inject(),
5785
+ component: TaskBoardSettingsCard
5137
5786
  });
5787
+ ctx.effect(() => () => {
5788
+ settingsCard.dispose();
5789
+ }, "task-board: settings card");
5138
5790
  let uiDisposer;
5139
5791
  const mountUi = () => {
5140
5792
  if (uiDisposer !== void 0) return;
@@ -5160,6 +5812,9 @@ window.__ModuleLoader__.load({
5160
5812
  };
5161
5813
  pushWorkspaceOptions();
5162
5814
  disposers.push(workspaces.list.subscribe(pushWorkspaceOptions));
5815
+ controller.setWorkspaceCreator(async (path) => {
5816
+ return { workspaceId: (await workspaces.create({ path })).workspaceId };
5817
+ });
5163
5818
  const pushModelOptions = async () => {
5164
5819
  try {
5165
5820
  let models = [];