diff --git a/docs/external-plugin-guide.md b/docs/external-plugin-guide.md index 0bcfabb..57605b6 100644 --- a/docs/external-plugin-guide.md +++ b/docs/external-plugin-guide.md @@ -409,7 +409,7 @@ ctx.effect(() => { |---|---|---|---|---| | `editor` | 10 | 否(按 path 去重) | 否 | 唯一「文件窗口」(编辑/预览 + 资源管理)。chrome 恒合并形态:路径输入框 + 编辑器控件 + 可开关内嵌文件树(全局搜索 `fs.search`;状态存 `tab.meta.treeOpen/treeWidth`)。`editorExplorer`:关(默认)= 按 path 新开,无路径窗口 = 纯资源管理器;开 = 树点击/Enter 经 `updateTab` 原地切换(id/meta 不变),无路径窗口 = 带 chrome 空窗口。树右键「在新 Tab 中打开」「在侧边打开」(pane 右侧 split)。新会话 seed 空文件窗口(`title:'Files'`);旧 `explorer` tab 经 `sanitizeState` 迁移 | | `git` | 20 | 是 | 是(本轮文件操作数) | 「文件变动」统一 tab(id 保留 `git` 以兼容持久化布局):**Git 视角**(原 Git 面板:staged/unstaged / 提交 / 历史 / worktree·子仓库选择)+ **本轮文件视角**(原 file-trace:模型读/写/编辑实时折叠,按文件分组、类型筛选);会话事件经插件自有宿主路由 `changes.ops` 供给(live 日志优先、冷会话回放持久化记录,`afterSeq` 增量),badge 读 tab 轮询写入的同步缓存。两视角共用底部可拖拽预览面板(`tab.meta.lens/previewH` 持久化),diff 渲染统一走 `src/client/diff/`(`DiffRows`/`DiffFiles`:mod 配对 + 行内高亮 + 语法着色 + 上下文折叠);Git 目标可展开为独立 diff tab(落进工作台的 diff 分栏) | -| `subagent` | 30 | 是 | 否 | 子代理拓扑 | +| `subagent` | 30 | 是 | 否 | 任务管理(工作流图/树、团队任务板、后台任务抽屉) | | `sidechat` | 35 | 否(`sidechat:`,按 `meta.threadId` 去重) | 否 | 侧边对话(每对话一 Tab):打开即建空线程(首条消息赢得标签并同步标题);线程 = 插件自建子会话(种子继承父会话上下文,进行中回合以 `interrupted` 闭合;种子带合法 `subagent/descriptor`,SubagentView 按 `Side: ` 前缀过滤),`origin:'subagent'` 隐藏于主列表;走 `/sidebar/api/sidechat.*` 路由;头部菜单切换/重开(`parkSidechatReopen` + 确定性 id),关 Tab 释放 live agent;重开经 `collectOwnEvents` 回源到种子边界;「保存为新会话」= `session.fork`(`this` 敏感)。[设计文档](plans/2026-08-20-sidechat-tab-design.md) | | `diff` | -1 | 否(按 id 去重) | 是 | 差异查看(changes tab 的预览面板「展开为独立页签」触发,同一渲染栈) | diff --git a/docs/plans/2026-09-14-tasks-graph-workflow-teams-design.md b/docs/plans/2026-09-14-tasks-graph-workflow-teams-design.md new file mode 100644 index 0000000..5c806c3 --- /dev/null +++ b/docs/plans/2026-09-14-tasks-graph-workflow-teams-design.md @@ -0,0 +1,91 @@ +# 任务管理页重构:工作流图 + Agent Teams + 后台任务抽屉(v0.20 设计) + +日期:2026-09-14 分支:`feat/tasks-graph-workflow-teams` + +## 背景与目标 + +DSH 0.1.5-rc.2 引入两个新的可观测面:**workflow**(`dsh-tool-workflow` 的 run/agent 生命周期事件)与 **Agent Teams**(实验层,`ctx.agentTeams` 的共享任务板)。任务管理页(原「子代理拓扑」)系统性地接纳两者,并按用户确认的「简洁后现代主义」方向重构为 **Variant D 工作流图**:分层节点 + 贝塞尔连线、拖拽平移、滚轮缩放、右下角控制条、已完成节点折叠聚合、点击浮窗取代页内 dock。 + +用户确认的关键交互决策(问答摘要): + +1. 数据范围 = 当前会话树(主代理 + 全部后代)。 +2. 节点点击 = 跳转转录;ⓘ = 详情浮窗。 +3. 后台任务输出 = 锚定浮窗(替换页内底部 dock)。 +4. 自动折叠**只**作用于后台任务抽屉,阈值 8 个代理;图节点的折叠是「已完成」语义的折叠聚合,点击展开、控制条可再折叠。 +5. 图/树切换按钮在**两种模式下都可用**(mockup 的 toggle 藏在画布容器内、树模式下无法切回——真实实现把控制条移出滚动面)。 +6. 默认视图由设置 select 决定(默认工作流图),页内切换为临时态。 +7. 任务板 v1 只做基础操作(完成/重开/删除/改派/新建/编辑,CAS)。 + +## 数据源事实(侦察结论) + +### Workflow + +- **没有**服务注册表 / 持久化 / HTTP 接口;只有调用方会话日志里的 4 个会话事件:`tool-workflow/run-start | agent-start | agent-end | run-end`。 +- 只有**顶层 run** 落事件(`exec.parent === undefined`),嵌套 run 不可见——与官方 `dsh-client-ui-workflow-run` 面板折叠的恰好是同 4 类事件,因此插件折叠结果与官方面板一致。 +- run 的 member agent 是普通子会话(catalog 里有行),`childId` 即子会话 id——图模式把它们**重挂**到 run 节点下;catalog 里没有的 member 用 run 自带数据合成占位节点。 + +### Agent Teams(实验层) + +- 只在 `~/.dsh-web` 类 profile 的实验 bundle 里存在;`ctx.get('agentTeams')` 缺席时返回 `undefined`(不抛)——**结构性降级**的支点。 +- TeamId ≡ lead SessionId;`tryMembership(agent)` 命中后 `remoteView(agent) → {members, tasks}`;`remoteCreateTask` / `remoteUpdateTask`(CAS,`expectedRevision`,冲突走 `team-task-conflict` union 返回而非抛错)。 +- 成员上限 8、任务上限 256;teammate = lead 的可续接子会话(`member.id` = 子会话 id,与拓扑树天然关联)。 +- `~/.dsh/task-board/ledger-v2.json` 与 Agent Teams **无关**(名字误导,已核证)。 +- 官方团队面板是 read-mostly + 手动刷新——客户端 5s 轮询(仅页面可见时)不是过度设计。 + +### Jobs + +沿用既有:`jobsBySession` push 镜像 + `jobs.output` 事件回放(不碰模型游标)+ 两击终止。 + +## 架构 + +``` +host 半(src/) + workflow-runs.ts 纯折叠 foldWorkflowRuns(events, originSessionId) → WorkflowRunView[] + workflow-routes.ts workflows.list:树枚举 + 存储日志与 live 镜像按 seq 去重合并 + team-routes.ts teams.view / taskCreate / taskUpdate:结构镜像 Remote 词汇,三分支降级 +client 半(src/client/) + tasks-model.ts 统一视图模型(图/树共用):catalog 走树、run 重挂、team 富化、fold 聚合 + tasks-graph-layout.ts 免依赖 tidy-tree 分层布局(<100 节点无需虚拟化;bundle 纯度禁图库) + TasksGraph.tsx 画布:pan / wheel-zoom-to-cursor / 相位虚线框 / 控制条(图树切换+折叠+缩放) + TasksTree.tsx 树模式:缩进行 + 键盘导航(官方 catalog 配方) + TasksPopovers.tsx 节点详情 / run 详情 / 团队任务板(CAS 操作) + JobsDrawer.tsx 底部抽屉(≥8 代理自动折叠,手动优先)+ 输出浮窗(回放,尾钉) + AnchoredPopover.tsx 锚定浮窗:geometry + 关闭契约(沿用 selection-popup 的 #425 修复) + tool-icons.tsx 工具字形映射(宿主 primitives 图标,currentColor) +``` + +### 降级矩阵(可证伪) + +| 环境 | workflows.list | teams.view | 页面表现 | +|---|---|---|---| +| 无 workflow/无 team(3384 桌面 profile r) | `{runs: []}`(200,空而非错) | `{available:false}` | 图/树照常,无 run 节点、无团队 chip | +| 有 team 无 workflow(3080) | `{runs: []}` | `{available:true, team:{...}}` | roster 富化节点 + chip + 任务板 | +| 有 workflow | 折叠结果 | — | run 节点 + 相位框 + member 重挂 | +| subagents 服务缺席 | 仅折叠 root | 不影响 | 旧快照退化行为 | + +## 关键取舍 + +- **零实验包 import**:`context-types.ts` 结构镜像 `TeamService` 的 Remote 词汇;`ctx.get('agentTeams')` 探活在路由内。实验层缺席时 mutation 抛 503 `team-error`(与 `subagents-unavailable` 同形)。 +- **workflow 无事件 = 空列表不是错误**:旧会话/无 workflow 会话静默为空(jobs 镜像同款语义)。 +- **fold 规则**:per-parent 的「已完成/出错的**叶子** agent」折叠为一个聚合节点;当前会话、teammate、有子节点的、run 节点**永不折叠**(折叠父节点会藏住活跃分支)。 +- **树/图同一模型**:fold 状态、团队富化、run 重挂两种模式共享——不会视觉漂移。 +- **控制条移出滚动面**:图模式的缩放按钮与图/树切换都在视图容器级(绝对定位右下),树模式下缩放按钮不渲染但切换在(mockup bug 的教训,有测试守护)。 +- **页内视图切换是临时态**,默认视图走 `tasksViewMode` pref(schemastery `z.union([z.const('graph'), z.const('tree')])`——schemastery 无 `z.enum`)。 +- **AnchoredPopover 不复用 primitives 的 HoverCard**:浮窗需要持久交互(任务板表单、输出滚动),HoverCard 是悬停语义;关闭契约(外部 mousedown / Escape / anchor 离屏的 IntersectionObserver)沿用 selection-popup 已验证的模式。 +- **主题硬阴影用 `color-mix(in srgb, var(--dsw-alias-border-l1) 55%, transparent)`**:后现代硬投影但颜色全部出自令牌(theme.spec 只守 `color:`,皮肤契约的精神照旧满足)。 + +## 实施偏差记录 + +1. **诊断行(diagnostic catalog entries)从模型中省略**:旧树逐 parent 渲染 corrupt/unavailable 行;新模型跳过它们,目录加载失败改由页头横幅「N 个分支加载失败 + 重试(逐个 refresh)」统一承载。理由:诊断行罕见(通常是 side-chat 遗产),逐 parent 内联会破坏图的 tidy 布局;横幅保留可达性与重试。 +2. **catalog `state:'error'` 不再逐层内联**,同上进横幅。 +3. **loadin rows**:图模式无「加载中占位卡」;树模式保留 `summaryBackedLoading` 的 loading 行。图模式靠 5s 轮询自然收敛。 +4. **子代理 keyboard 导航保留在树模式**;图模式节点 `tabIndex=-1`(平移/缩放下 tab 序无意义,树模式是可达性面)。 +5. **本地 `pnpm test:mount` 需要绕过桌面 shim**:桌面版 `~/.local/bin/dsh` shim 回退到桌面捆绑 CLI 且**不尊重 `DSH_HOME`**(`dsh plugin add` 会写到真实 `~/.dsh` profile——本次亲历,把真实 profile web 的 better-sidebar 指到了本分支 tarball)。正确姿势与 CI 一致:`DSH_CMD="npx -y --package @deepseek-ai/dsh@0.1.5-rc.2 dsh" pnpm test:mount`。 +6. **测试断言从 `container.textContent` 迁到 `document.body`**:输出浮窗 portal 到 body(#425 契约),jobs-view 套件相应更新。 +7. **fold 聚合节点的 aria-label 带计数文本**:与控制条的 fold 切换按钮消歧(两者同文案会导致 a11y 选择器歧义)。 + +## 验证 + +- `pnpm typecheck` / `pnpm vitest run`(1401:新增 fold/路由/模型/布局/页面交互 58 个用例)/ `pnpm build` 全绿。 +- `pnpm test:mount`(钉版 npx CLI):见实施偏差 5。 +- 真机:3384(profile r,无实验层)验证降级矩阵左列;3080(~/.dsh-web)验证 teams/workflow 实景。 diff --git a/src/client/locales-ja.ts b/src/client/locales-ja.ts index e1bf0be..b995edc 100644 --- a/src/client/locales-ja.ts +++ b/src/client/locales-ja.ts @@ -19,7 +19,7 @@ export const ja: Record = { files: 'ファイル', guideDescFiles: 'ワークスペースのファイルツリーを閲覧し、クリックでプレビュー/編集', guideDescGit: 'Git の変更と今回のセッションのファイル変更を 1 つのタブに', - guideDescSubagent: 'サブエージェントの構成とバックグラウンドタスクをリアルタイム表示', + guideDescSubagent: 'ワークフロー図・チームタスクボード・バックグラウンドジョブをリアルタイム表示', guideDescSidechat: '現在のセッションの文脈を引き継ぐ独立した追加スレッド', guideDescTerminal: '本物のシェル(xterm + pty)、再接続対応', guideDescBrowser: '埋め込みブラウザ。チャット内のリンクも引き受け可能', diff --git a/src/client/locales.ts b/src/client/locales.ts index 76cfb2a..4c2df74 100644 --- a/src/client/locales.ts +++ b/src/client/locales.ts @@ -26,7 +26,7 @@ export const zh = { files: '文件', guideDescFiles: '浏览工作区文件树,点开即预览或编辑', guideDescGit: 'Git 变更与本轮文件改动,双视角合一', - guideDescSubagent: '子代理拓扑与后台任务,实时状态', + guideDescSubagent: '工作流图 · 团队任务板 · 后台任务,实时状态', guideDescSidechat: '继承当前会话上下文的独立追问线程', guideDescTerminal: '真实 shell 终端(xterm + pty),断线重连', guideDescBrowser: '内嵌网页浏览器,可接管聊天里的外链', @@ -519,7 +519,7 @@ export const en: Record = { files: 'Files', guideDescFiles: 'Browse the workspace tree; click a file to preview or edit', guideDescGit: 'Git changes and this session\'s file edits, in one tab', - guideDescSubagent: 'Subagent topology and background jobs, live', + guideDescSubagent: 'Workflow graph, team task board, and background jobs, live', guideDescSidechat: 'An independent follow-up thread inheriting this session\'s context', guideDescTerminal: 'A real shell (xterm + pty) with reconnect', guideDescBrowser: 'An embedded browser that can take over chat links',