# dsh-task-board-team · DSH 任务看板的团队协作提供方 [English](README.md) | 中文 面向 DeepSeek Harness (DSH) 任务看板(`@linxin666/dsh-client-ui-task-board`)的外部提供方扩展。它把本机接入云端 `team-hub`:hub 上指派给本机绑定的 dsh-agents 成员的 issue 会在租约下被领取、作为镜像卡在本机执行,执行结束后写回 hub;决策问题、评论与返工请求则在团队与该执行会话之间往返。看板的列、账本与执行规则零改动,插件经 `cordis.patch.yml` 与 profile 挂载,不修改任何 DSH 源码。 ## 功能 - **按成员绑定,不是共享队列**:设置项填写 dsh-agents 成员 id,成员定义提供 hub 解析 @ 时使用的名字、Agent 预设与团队工作区。指派给其他 Agent 的 issue 即使被 hub 列出,本机也不会领取。 - **注册与心跳**:宿主启动时用主机名、客户端版本与 Agent 定义注册,随后每 15 秒心跳一次,这是协议固定的节奏。已被其他宿主占用的成员名由 hub 跳过,并作为可见问题列出,不会静默消失。 - **带租约的领取**:领取会拿到租约令牌与执行 attempt id,镜像卡把两者存在本扩展自己的 `integrations` 载荷里,卡片执行期间每 20 秒续租。 - **幂等的重连**:重连会重新注册、按最后收到的序号续传事件通道,并用卡片仍在的 attempt 凭证重新领取——这只发生在执行还在本机进行且仍持有租约时;结算之后的卡片两者都已释放,因此再次领取该 issue 是全新领取,新的 attempt 由 hub 生成。 - **结算写回**:已结算执行按该次执行自身的 Host 回合判定写回——执行成功即记为 `succeeded`(issue 进入 `in_review`),失败或取消记为 `failed` 并附原因——摘要截断到协议规定的 4000 字符。每次写回都带上产生结果的那次 attempt。永远无法改变结果的拒绝——412 `LEASE_EXPIRED` 或任何其他确定性的 4xx(但有四种例外)——属于终局:宿主丢弃待写回结算而不重试,仅当被拒的正是卡片当前那次 attempt 时才清掉 attempt 与租约凭据,并在卡片上记录一条可见错误;要报告新结果需重新启动该卡片。可能过去的失败——传输失败、5xx,以及四种 4xx 例外(401:Host Token 未被识别或已吊销,操作者换上正确的令牌后仍能送达;408 和 429;没有 hub 错误信封的 4xx:这类错误来自 hub 前面的 CDN 或防火墙,不是 hub 自身的门禁)——则留在卡片上,下一次轮询在领取该卡片之前先重试它,因为此时领卡只会拿到一个本机不执行的 attempt,随后的重试会被它判死。 - **问题路由**:镜像卡会话里通过 `ask_user_question` 提出的决策问题会上报 hub,由该 issue 的审阅人回答;回答、过期或本地取消都会结束提问,本机其他会话仍由各自的应答方处理。 - **双向评论**:`team_comment` 工具让 Agent 在卡片对应的 issue 上发表评论,而 @ 到本机 Agent 的评论会作为后续消息投递进该卡片的会话。 - **返工与重试**:审阅被拒与轮询共用同一个逐 issue 领取,只有领取成功才决定跑什么——待处理的返工用审阅人的说明在既有会话里重跑该卡片,而人工打回、本机没有在跑执行的卡片会经首次领取所用的同一入口重新启动。领取被拒则什么都不执行,只在本地记录这次拒绝;全新领取会丢弃上一轮 attempt 留下的待写回结算,因为那次 attempt 已被取代,它的重试永远落不了地。 - **运行通过 taskBoard 宿主服务面的 apply 启动**:提供方契约没有「运行」成员,因此领取到的卡片经 `taskBoard` 宿主服务的 `apply` 启动——与远程桥驱动看板的是同一个进程内动作面,运行门禁照旧全部生效。 - **可见的降级**:挂载时探测后续消息投递、返工与执行入口三项能力,缺少其中任何一项就失去对应的投递,并在卡片与设置区说明,其余功能照常工作。 - **三个浏览器席位**:任务详情区(issue、含 `in_review` 的 hub 状态、负责人、审阅人、评论线程、活动流与待答问题)、卡片上的 hub 状态徽标,以及看板自身设置卡内的配置区块——扩展关闭时该区块仍然可达。 - **一个开关同时收放两侧**:关闭扩展即停止领取轮询、租约定时器、事件通道与工具注册,清空已发布的摘要并撤下渲染席位,但不删除任何卡片。 ## 安装 把本包装进 profile 后重启 `dsh web`: ```sh dsh plugin --profile web add @linxin666/dsh-client-ui-task-board-team@latest ``` ### 本地开发 ```sh pnpm install pnpm --filter @linxin666/dsh-client-ui-task-board-team build dsh plugin --profile web add link:$(pwd)/packages/dsh-task-board-team ``` ## 配置 打开任务看板设置,找到「团队协作」区块:hub 地址与团队 id 存在本插件自己的 volatile 设置里,粘贴的 Host Token 只向宿主发送一次。 - Host Token 保存在 harness 凭据库的 `hostTokenRef` 名下(默认 `DSH_TEAM_HUB_TOKEN`),没有凭据库时读同名环境变量;快照、摘要与工具结果都不携带它的值。 - 成员绑定是 dsh-agents 成员 id(区块内用逗号分隔)。绑定的成员已不在 dsh-agents 账本里时,区块会报出来并跳过它;成员定义没有钉住 Agent 预设时同样跳过——hub 要求 `preset_id` 非空,而跑这样的成员会静默退回部署默认预设。 ```yaml # 对应的 profile patch 写法 hubUrl: https://hub.example.com teamId: team_01j7b5w8m4f9q2e3r6t1y8u4i5 hostTokenRef: DSH_TEAM_HUB_TOKEN agents: - memberId: mem_01j7b5w8m4f9q2e3r6t1y8u4i6 ``` ## 已知限制 - **无法钉住建卡目标的看板会被拒绝,而不是被猜**:成员的预设、模型与思考档位随 `tasks.create` 草案一起下达,建卡后再读回卡片确认它们被保留。丢弃这些字段的看板版本会得到一张**不启动**的镜像卡,并在设置区给出可见提示——用部署默认预设跑它,就不是该成员的那次运行。 - **启动执行依赖看板的动作面**:契约没有「运行」能力,因此挂载时会探测看板的进程内动作应用器;部署不提供时,领取到的卡片被创建出来交由操作者自行启动。 - **活动流是本机观察到的事件**:`GET /issues/:id/activity` 存在,但只允许成员会话读取,而本宿主用 Host Token 认证,因此详情区渲染的是本宿主自启动以来从通道收到的事件;完整活动流在 Whale App 里看。 - **问题逐个上报**:一次提问携带多个问题时只上报第一个,其余留在本机,因为 hub 的问题表存储单个问题。 - **被认领的问题最多持有两分钟**:超过该窗口,提问交回本地应答方并从 hub 撤回,这样即使团队始终不回答,会话也不会被拖到 hub 的 24 小时 TTL。 ## 安全模型 - **Host Token 不会第二次到达浏览器**:它从设置区单向写入凭据库,只有宿主半区读取,之后只报告「已配置」与来源。 - **工具权限审批绝不进入 hub**:本提供方只订阅 `user-questions/request`,`approval/request` 在这里没有监听器,因此工具沙箱的决策留在本机,永远不会变成远端请求。 - **远端文本只是内容,不是权力**:issue 标题、正文或评论只会成为卡片文本、摘要字段或会话消息,绝不会变成权限、工作区身份、Agent 预设或 `promptPrefix`。 - **领取受租约约束**:本机无法镜像或无法启动的领取会被记录并任其过期,租约只在宿主仍持有它时续期。