# dsh-task-board · DeepSeek Harness (DSH) 自动化任务看板与 Cron 定时调度插件

[English](README.md) | 中文

<p align="center">
  <img src="https://img.shields.io/npm/v/@linxin666/dsh-task-board?style=flat-square" alt="Version">
  &nbsp;
  <img src="https://img.shields.io/badge/DSH-%3E%3D0.2.1--rc.3-4c6ef5?style=flat-square&amp;labelColor=454a54" alt="DSH">
  &nbsp;
  <img src="https://img.shields.io/badge/license-Apache--2.0-blue?style=flat-square" alt="License">
</p>

<p align="center">
  <strong>DeepSeek Harness（DSH）官方 Web GUI 与桌面客户端自动化任务管理与长程后台执行引擎</strong><br>
  <em>Host 权威事务账本 · 父卡自治派发子任务 · 对抗式双盲裁判验收 · 离线 Cron 与防休眠电源保护 · GitHub Issue-to-PR 闭环</em>
</p>

一个高可用、可热插拔的 DeepSeek Harness (DSH) Web GUI 与官方桌面客户端任务自动化插件，提供 Host 权威任务账本、真实 DSH 会话自动化执行、Cron 表达式后台定时调度、跨平台空闲睡眠保护、父卡自治派发子任务与自动化质量验收门禁。用户无需常驻浏览器，即可在后台无人值守运行代码巡检、健康检查与定时自动化流水线。插件只通过 `cordis.patch.yml` 与 profile 机制挂载，零修改侵入 DSH 官方源码。

- 宿主权威架构与离线后台执行：浏览器仅为异步视图，关闭页面或断网不影响 Host 调度、会话执行与原子账本结算；内置跨平台防休眠电源管理（macOS/Windows/Linux），允许显示器息屏同时阻止整机闲置休眠，确保无人值守长程自动化。
- 树状子任务的父卡自治派发：任务可以带子任务，深度按部署设置 1 到 3 层。执行一张卡只开它自己的一个会话：Host 不为子任务开任何会话，这个会话自己用 `task_board_get` 读子卡、用 `subagent` 按依赖顺序派发、完工前用 `task_board_manage` 回填结论。还有未完成子任务时，这次执行改用梁神（liangshen）派发预设。
- 对抗式双盲质量验收门禁（No False Completion）：拦截 Agent 虚假完工自述，必须经由独立裁判模型沿用严苛 Coding 判据与 0.65 阈值进行 A/B 位置轮换打分，结合真实工作区磁盘 Diff 行级变更进行质检验收，带结构化问题反馈闭环自愈。
- 全自动 GitHub 闭环、冻结交接与智能工具面：无缝双向同步 GitHub Issues，自动驱动 Agent 修复并创建关联 PR；支持 `<<<FREEZE` 会话上下文安全快照与交接包；提供 8 项面向模型的原生工具，支持 Agent 自主规划与外部执行结果登记。

## 功能

卡片保留多行标题、描述预览及可见的元数据。预览去除 Markdown 标记，详情中的描述与 Prompt 按安全 Markdown 渲染，不执行原始 HTML 或自动加载远程图片；超过约八行的内容默认按固定高度钳制并带渐隐提示，点「展开全文」展开、可再收起，未超出的内容原样渲染且无切换按钮。任务原文保持不变。标题开头的方括号前缀（如 `[Bug]:`）渲染为类型徽章，描述预览跳过 Issue 表单的模板内容（勾选列表、小节标题、空字段占位），存在摘要类小节时优先取它。卡片左侧色条显示最近一次执行（运行中、成功、失败、取消）；所在列与该次执行不一致的卡片标注为手动结算。每列显示能区分卡片的时间——待规划/待办为创建时间、运行中为开始时间、已完成/已失败为结算时间——按 Host 时区读取。最近一次执行的会话可直接从卡片打开。每个列头都可切换紧凑单行卡片（已完成列默认紧凑，选择保存在浏览器中）。五个主列与归档先按提供方来源分组——GitHub 显示完整 `owner/repository`——未被提供方认领的卡片进入「本地任务」组；来源组按名称排序，折叠选择按浏览器保存。已完成的来源组默认折叠，其他主列与归档默认展开，搜索总会露出命中的分组。已完成、已失败与归档在每个来源组内部再按「今天 / 近 7 天 / 更早」分组，该来源超过 30 张卡时折叠最早一组，搜索时不折叠。

- **任务看板 UI**：看板是 shell 自己的中栏面板——它像桌面版【插件】页那样，向 shell 的官方侧栏面板列表贡献一行、向布局的中栏座位贡献一页，因此行的几何、选中高亮、折叠 rail、标签与语言切换都由 shell 统一负责；看板提供五列布局、搜索、任务详情、归档/恢复、执行历史和执行会话跳转。五列都可以手工切换：拖拽卡片或从详情页的「移动到…」菜单切换状态，只写列、不产生执行记录——「已完成」「已失败」声明工作在受追踪的执行之外结束（或失败），「进行中」则表示工作正在进行但没有对应会话；真正在执行中的卡片在结算前拒绝任何移动。归档任务除恢复、删除和查看 transcript 外保持只读，恢复前不能手动或定时执行。
- **侧栏只有一个调度入口**：看板启用期间，shell 官方侧栏里的「自动化任务」入口（宿主 `ui-schedule` 页面，挂在 Host Schedule 服务上）会被隐藏，侧栏因此只提供一个调度面而不是两个。该状态由看板自己的总开关掌握：关闭看板，官方入口随之回来；官方页面在其他仍可触达的地方照常工作——隐藏的是入口，不是卸载。
- **任务详情的布局与折叠**：任务详情在宽视口上是双栏——左栏是任务本身（描述、执行 Prompt、标签、子任务、冻结快照、交接包），右栏是这次执行会怎么跑（执行设置、定时运行、执行记录、状态）；窄视口按同一顺序退回单栏。执行设置里的工作区、Agent 预设、权限、模型与思考档位常驻可见，三个卡片级开关（同一对话继续、以 /goal 开始执行、跳过本任务的验收）收进一行「高级设置」，收起时在行上回显已开启项，因此不展开也知道这张卡会怎么跑。定时运行与执行记录各占一行折叠：定时行说明是否启用与下次触发时刻，执行记录行说明次数与最近一次结果，记录本身默认只列最近五次，展开才看全部；每条记录里的验收报告在行上只给一个结论徽标，展开后才是判定、裁判路由、逐项分数与问题反馈。权限确认门与当前状态不折叠。视图内字号只有三档（标题、正文、元信息），状态与结果徽标共用一套填充色槽位，折叠行与按钮的命中区不小于 44 像素。
- **续接卡片（数据面）**：新建任务时可粘贴会话输出的 `<<<FREEZE … >>>FREEZE` 冻结块，解析为「目标/进度/下一步」快照随任务持久化（v3 账本）；卡片带冻结徽标，详情页可读完整快照与冻结时间，搜索覆盖快照文本，归档/恢复与普通任务一致。快照在协议层复用冻结安全门：敏感模式自动替换为 `[REDACTED]` 并标记、以 `/` 开头的命令行整体拒绝、每字段 8 KiB 上限。
- **交接包与权限确认门**：续接卡片可附交接包——钉住三元组（工作区/agent 预设/权限）加文档与脚本引用。执行时交接包三元组覆盖普通钉住字段，引用以交接前言随 Prompt 下发。有效权限高于部署权限基线——未钉住 `sessionDefaultPermission` 时即宿主自己的默认权限预设——的绑定处于待确认状态：手动执行被拒绝、cron 跳过该卡并滚动到下一触发点，任务详情中的确认按钮完成人工确认；此后任何权限或交接包变更都会重新武装确认门。
- **领卡来源声明包裹与来源审计**：执行续接卡片（带冻结快照的卡片）时，任务指令被来源声明模板强制包裹——冻结时间、来源会话与未经人工审查提示，组合在交接前言之后，使接手 Agent 对卡片文本中的存储型提示注入保持警惕。create/update 动作的发起方会话织入快照（frozenBy，快照被替换时重新盖章），run/rerun 的发起方会话连同冻结来源的捕获副本一起落在执行记录（initiatedBy）上，两者均可在任务详情查看。发起方为客户端断言的审计元数据，不构成信任边界。
- **任务标签（issue #1521）**：每个任务可带最多八个标签。标签在卡片上渲染为带色调的徽章——色调由标签名哈希得到，因此同一标签永远同色，也不需要存储颜色；看板顶栏据此提供多选标签筛选，候选项来自全账本在用的标签（含已归档任务），搜索也会匹配标签名。填了「执行提示」的标签会在每次执行前以 `标签提示` 段注入到执行 Prompt 之前；没填的标签只作展示与筛选，因此无标签任务的 Prompt 与加入该功能前逐字节一致。标签在首次执行后仍可编辑：它们给任务分类、塑造下一次运行，而不是「已经跑过什么」的凭证。筛选行的**管理标签**入口会打开标签管理器：列出每个在用标签及其任务数，重命名会写到所有卡片上（与已有同名标签合并，并保留该标签的执行提示），删除会从全部任务（含已归档）上移除。两者都是 Host 的整账本事务，因此多个窗口看同一块看板时状态一致。
- **标签清理**：标签管理器里的**清理**一行给出一个阈值输入框（默认 5，可填 1 到 99），用来清掉看板已经不再使用的标签。携带某个标签的任务数小于该阈值、且这些任务全部是已完成或已失败时，这个标签成为候选；已归档卡片按同一口径参与，所以归档后已结算的卡片会让它的标签放行，归档后仍未完成的卡片会把标签留住。清理先算出候选再动账本：没有候选时只说明这一点、不碰账本；有候选则打开确认框，逐个列出标签与携带它的任务数，确认后每个标签都走管理器的删除按钮所用的同一笔整账本删除。标签会从所有卡片（含已归档）上移除，执行提示随之一并消失，任务本身不受其他影响；删除不可撤销。
- **子任务与父卡自治派发**：任务可以有子任务——在详情页新建子任务（表单以父任务的工作区、模式、权限和模型为起点，每一项都可覆盖），或把一个还没有父任务的现有任务关联为子任务；子任务不在运行中时也可以解除关联。父子关系的准入由 Host 判定：父任务必须存在且在看板上、任务不能挂到自己的后代之下、树的深度不得超过配置的 `maxSubtaskDepth`（默认 1 层，最大 3 层）。执行一张卡只为这张卡开一个会话，不为它的子任务开任何会话；直接点某张子卡执行也是同一件事：它自己就是这次运行的 root，即使它有父卡、自己也有子任务，也不会展开。已在「完成」列的子任务不在这次的派发集合里，因此把子任务逐个做完再执行父卡只花一个会话，而不是整棵树重来；需要重新派发就把它移回「待办」列。看板会标注父任务与子任务卡片，详情页列出直接子任务并说明一次执行会怎么做，顶栏提供一键隐藏子任务。
- **父卡自治派发与子卡记账**：点击一张带未完成子任务的卡，只开这张卡自己的会话，别的都不开。这次执行强制使用 `packages/dsh-liangshen` 提供的 `liangshen` 派发预设，卡片自己存的 Agent 预设绝不被改写，session 复用被跳过、新开一个会话；部署没有注册 `liangshen` 预设时失败关闭，原因写在卡片上。启动 Prompt 会列出每张未完成子卡的标题、任务 id 与状态，说明本次不为它们开会话，并要求这个会话用 `task_board_get` 读每张子卡、用 `subagent` 按依赖顺序派发（互不依赖的可以并发）、完工前用 `task_board_manage({action:'record-external-outcome'})` 把结论一次性写回。会话没有回填的结论由 Host 在父卡自身运行结束时补上：仍未回填的未完成子卡继承这次运行的判定，记为一条 external 记录，发起方写作 `parent-run:<父卡 id>`，原因说明本卡未单独开会话执行、结论继承自父卡运行。这次回填幂等，父卡判定为 cancelled 时不写。没有未完成子任务的卡不受影响。
- **项目分区（issue #1536）**：看板头部新增项目下拉，选项来自当前部署的 DSH 工作区——「全部项目」加每个已注册项目。选定项目后各列只显示钉在该项目上的任务，未钉工作区的任务只在「全部项目」下可见；在某个项目下打开「新建任务」会把该项目预选为任务工作区，「新建项目…」则用与 GUI「添加项目」相同的运行时接口把宿主目录注册为项目。未选项目而创建的根任务——无论来自表单还是看板的 agent 工具——继承创建它的会话所在的工作区；仍未钉住工作区的卡片在部署最近使用的工作区里执行，而不是落到宿主自身的工作目录。
- **粘贴文本 AI 解析（issue #1540）**：新建任务表单可以把从别处复制的一段话交给模型，整理成标题、描述和执行 Prompt。模型取当前部署已配置的模型（与任务「模型」下拉同一份列表，默认选中第一项），调用在宿主侧完成，走看板既有的回环与同源校验，预算 45 秒并支持取消。解析结果只填进表单：不提交就不会创建任务，解析失败也不会改动你已经输入的内容。
- **可折叠的任务表单**：新建/复制/子任务对话框把配置分成可折叠区域——任务内容默认展开，其后依次是标签、执行设置、运行方式、交接与续跑、定时运行——每个折叠区域都保留一行当前值摘要，因此对话框不需要滚动条（正文仍保留极矮窗口下的溢出兜底）。Agent 预设字段更名为「Agent 预设」，把部署内置预设以本地化名称列出，并附上任何用户预设；默认的「继承」选项会写明未钉住时真正使用的预设。
- **卡片附件（issue #1979）**：一张卡片最多带八个文件或图片。新建与编辑表单支持三种加入方式——拖放、粘贴、文件选择器；选中的文件在保存卡片之前只是一份本地预览：上传发生在保存时，因此被放弃的卡片不会在存储里留下对象。拖放区可聚焦、可用键盘操作，保存进行期间移除按钮禁用。图片按位图图片存储，随执行以 Prompt 的图片部件下发，位置在文字部件之后；其余文件按原样存储，以只读路径进入执行会话，并写在独立于交接引用的附件小节里。卡片只保存**元数据**——类型、存储标识、名称、大小、媒体类型、精确字节的摘要，以及图片的像素尺寸——绝不保存字节、base64 或 data URL。展示名称始终是一行规范形、不含任何路径部分，因此既不会破坏 Prompt，也不会指向某个目录。账本提交附件之前，Host 会拿官方附件存储证明这条声明：图片读回比对，文件流式读取并重算摘要，存储无法证明的引用会让整个动作被拒绝，因此伪造的客户端声明不会成为已存事实。每次执行都会重新解析附件；声明不支持图片的路由会在 Prompt 入队之前拒绝这次运行。看板的限额是每卡 8 个附件、单附件 10 MiB、每卡合计 32 MiB；部署自身的附件存储更严格时（含每条消息的图片预算）会下调公布的上限，Host 在写入卡片与执行解析两处都强制该预算。同一个存储对象只被认领一次：同一份内容不会在一张卡上出现两次。
- **Host 权威账本**：任务、计划和执行记录存于 `$DSH_HOME/task-board/ledger-v2.json`；浏览器动作只有经 Host 确认后才成为 UI 状态。
- **有界执行历史**：每个任务只保留最近 20 条执行记录；新运行开始时截掉最旧的记录，使账本大小与每次写入成本不随任务历史无限增长。
- **真实执行**：手动运行和定时运行共用 Host runner，默认新建独立会话、重命名、应用 agent 预设和 `/permission <id>`，再以 queue 模式发送任务 Prompt。
- **目标驱动执行（卡片逐张选择加入，且需总开关开启）**：全局原生 `/goal` 开关（见下，默认关闭）是总开关；开启后，只有卡片在详情页勾选加入（新建任务对话框默认不勾选）时，runner 才会先入队任务 Prompt，再以同一份组合 Prompt 作为目标武装 dsh 内置的 `/goal`。dsh 的目标轮次驱动器会在同一会话中不断开启续跑轮次，直到 agent 标记目标完成；看板则让该执行一直保持 `running`，直到目标离开 active 阶段：目标正常完成判为成功，被阻塞则以目标自身的原因判为失败。该选项按任务设置（`goalRun`，缺席即关闭，因此从未动过该选项的卡片只执行一轮普通对话），`task_board_create`/`task_board_update` 同样可用。勾选框始终可操作：总开关只决定该偏好是否生效，从不决定它能否被表达，也绝不改写已保存的值。
- **任务验收（默认开启）**：以 goal 形式执行的任务必须先通过一次验收，`update_goal(action: complete)` 才会生效——看板在官方「工具执行前」生命周期上拦截这次调用，而不是相信 agent 的自述。验收沿用已安装 dsh-llm-verifier（MIT）的默认最终验收：三项 coding 判据（Specification Adherence、Output Match、Error Signal Detection）、总分与每项判据都必须达到 0.65 阈值、每项两轮并交换 A/B 位置后取平均、以任务真实轨迹（工具调用及其输出、assistant 文本）对比空工作基线，实际工作必须胜过该基线；部署记录工作区变更时，还会附上宿主自身对「本次运行改了磁盘上什么」的记录（文件清单与增删行数、有界的逐文件对比）作为提示词的参考上下文，使「只在自述里写过的补丁」无法通过。否决只有在**可定位**时才记为质量判定：任何导致不通过的判据都必须带有结构化问题反馈，写明它施加的任务要求、未达标的实际观测、差异，以及对任务、本次执行自己的轨迹或宿主工作区记录的逐字引用。没有反馈的否决、空泛或空的反馈、引用并不出现在所审证据中、引用空工作基线、以及「判据不通过却没有它自己的证据」，都记为**无效验收**：不消耗质量额度、不判卡片失败，并单独有界（两次）；额度用尽后看板停止调用裁判、把卡片留给人工处理。若某次执行的记录只有无效验收，它同样因没有匹配的通过记录而结束，但使用独立终态原因，说明验收本身不可用，并明确否认这是有证据的质量判负。轨迹被截断导致裁判看不到输出时同样按无效验收处理，因为证据不足不等于工作没做。同一 execution 最多验收两次：第一次不通过把总分、逐项分数与可定位问题交回修复的 agent，第二次仍不通过即判该 execution 失败。执行**通过**并结算后，验收专用详细材料（问题反馈与结构化引用）会自动移除，只保留轻量审计凭据——判定、分数、时间、裁判路由、证据哈希与一行问题摘要。该 execution 记录之外的任何东西都不动；失败或无效的执行保留修复与复核所需的全部材料；移除是幂等的；清理失败会被记录并在宿主下次启动时重试，且绝不撤销通过记录。额度记录在 execution 记录上，因此重复完成调用、跨入下一 goal 轮次、插件重载与宿主重启都复用同一周期；重跑或一次定时触发则是新的 execution、重新计数。验收异常（超时、鉴权失败、裁判回答无法解析、裁判路由不可解析）与质量判定分开记录，不消耗质量额度。异常额度用尽并不把卡片判死：裁判路由答不上来是环境故障而不是工作不合格，此时执行周期保持打开、不再发起裁判调用，直到用户通过 `task_board_manage(action: reset-verification)` 清除已记录的异常。若一次验收在产生任何判定前就耗尽时间预算，会单独记为一类结果，不消耗任何额度，同样不判死卡片。两个上限都是配置项（`goalVerificationCallTimeoutSeconds`、`goalVerificationBudgetSeconds`），按需实时读取；剩余时间装不下一次调用时不会发起这次注定被中断的调用。运行列显示「执行中 / 验收中 / 验收未通过修复中」，每条 execution 记录展示判定、裁判模型与强度、次数、各项分数与阈值、问题反馈、证据范围与 token 用量。强制验收还堵住了旧回退路径：暂停的 goal、读取失败的 projection、已完成的回合与人工强制结算，都不能在没有匹配通过记录的情况下把 goal 执行结算为成功。带着验收记录失败意味着裁判给出了判定而工作没通过；一条验收记录都没有的失败则单独说明：验收门从未打开，因为执行会话没有调用 `update_goal(action= complete)`（常见于只在回复里宣告完成），交付从未被判定，更谈不上被判负。**单张卡片可以退出这道门**：新建任务与任务详情都有「跳过本任务的验收」勾选（默认不勾，语义与「同一对话继续」「以 /goal 开始执行任务」同级的卡片级选项），勾选后该卡以 goal 形式执行时不再拦截完成声明，执行记录标注 `applicability: skipped` 并在验收面板写明「该任务卡勾选了跳过验收」——它与总开关关闭是两回事，看板会分别说明。只对之后新开的执行生效，运行中改勾选不会改写已在飞的执行的判定规则。
- **任务思考强度**：所选模型在 Host 模型目录中声明支持的思考档位时，任务模型选择器提供思考强度下拉框。留空沿用模型/宿主默认值，只提供该模型声明的档位。任务保存该选择，并在选择执行模型时应用，包括复用已有会话的执行。此设置独立于验收裁判的思考强度。
- **可选会话复用**：任务可选择在上一执行会话中继续（issue #1419）。仅当该会话空闲且仍在运行时会话名册中才复用——Host 会重新应用钉住的权限与模型再以 queue 模式发送 Prompt，会话标题与历史保持不变；否则照旧新建会话，因此名册未知或会话正忙都不会阻塞定时运行。名册读取就发生在这次启动本身，所以长期空闲的看板会复用一个一直空闲的会话，而不是因为缓存过期而拒绝它。
- **空闲时停止会话轮询（issue #1821）**：只有当看板确实有需要名册裁决的事情时，Host 才读取 DSH 会话名册——存在待核对的执行，或运行列里有一张卡片（它的结果可能来自本进程没观测到的结算）。空看板、没有运行中卡片、没有未结算执行、也没有已启用计划时不轮询任何东西，因此空闲的 Host 不再按固定心跳重建每一条持久化会话行（字符串转换、对象分配、每条记录一次 stat）。有工作时按配置项 `sessionPollSeconds` 的节奏轮询；读取失败或名册仍未知时按翻倍延迟退避，上限一分钟，因此会话树故障时不会被按轮询间隔反复冲击。
- **钉子失败即关闭**：工作区缺失、预设缺失或损坏、权限命令被拒绝时，任务 Prompt 不会发送。
- **Host 调度器**：5 段 cron 支持 `*`、`*/n`、范围、逗号列表和周日 `0/7`。每条规则携带自己的 IANA 时区（默认跟随 Host 时区），日期/星期遵循 Vixie 语义：两个字段都受限时为 OR，其余组合为 AND。夏令时切换处的墙上时间空洞会被跳过，重复出现的歧义时刻只触发一次并取更早者。规则有两种计划：循环 cron（可设次数上限，支持不限、1、2 或任意正整数）或在未来某一时刻只执行一次的单次计划；Host 只统计它真正开启的执行，达到上限或单次已执行的规则会在账本里自行停止，而不是等被执行的工作去关掉它。
- **确定性恢复**：已有 session id 的 running execution 在重启后继续观察；没有 session id 的启动中断会取消且不会重发。
- **实时同步**：变更返回完整 revision snapshot；SSE 只提示 revision、scheduler 与 power 变化。镜像的重读入口共有五个：变更帧（revision 前进）、连接建立或重建（`EventSource.onopen`）、页面恢复可见、流报错（`EventSource.onerror`，按 `STREAM_ERROR_NOTIFY_MS` 节流）、以及镜像里仍有未结算执行时的一次有界校验读取（`src/client/pending-verify.ts`，`PENDING_VERIFICATION_MS` 15 秒，最后一条执行结算即停止）。连接可以保持打开却不再投递，此时既没有 `onerror` 也没有帧，所以帧无法从浏览器侧被验证。无未结算执行的空闲看板零读取。
- **可选空闲睡眠保护**：默认关闭；开启后覆盖全部运行中的 DSH 会话、已启用且未归档的任务计划和未知会话状态。
- **系统提示词注入**：Host 通过 `SystemPrompt.section` 注册 order 200 的 `plugin:task-board` 段；任务看板设置可单独关闭声明而不关闭看板。该提示也会提醒 agent 在最终回复前收尾可见的 `todo_write` 计划列表。
- **Agent 工具**：每个会话都可使用八个面向模型的工具（`task_board_list`、`task_board_get`、`task_board_create`、`task_board_update`、`task_board_set_parent`、`task_board_run`、`task_board_manage`、`task_board_schedule`），它们驱动与浏览器完全相同的 Host 账本，因此 agent 可以在对话里列出看板、创建子任务、关联或解除关联、执行一张卡片、把卡片移到任意列（含不经执行直接声明完成、失败或进行中）、归档/恢复/删除卡片、对看板已无法观察的卡片强制结算，以及为卡片配置 cron 计划。
- **Host 之外完成的执行结果（issue #1826）**：不由本 Host 执行的工作——例如在 DSH 之外用各自模型工作的 Codex、Claude Code——可以登记成一次真实执行，而不是只改一列。`task_board_manage(action: 'record-external-outcome', result: 'succeeded'|'failed', initiatedBy: <完成者>)` 会追加一条 execution 记录，带上判定、完成者与可选摘要；终态列由这条记录推导，与 Host 结算走同一条规则（`core/tasks.ts` 的 `settledStatus`）。该记录带 `external` 标记、永远不带 session，并在卡片仍有未结算 execution 时被拒绝（本 Host 正在执行时它才是唯一权威），归档卡片同样拒绝。`running` 与 `cancelled` 不能这样登记：它们只属于 Host。手工 `move-done`/`move-failed` 仍然可用，也仍然只写列——区别在于这一条会留下记录，因此列与执行历史永远不会脱节。

- **外部提供方扩展**：GitHub Issues 扩展（`@linxin666/dsh-client-ui-task-board-github`，默认开启）把 GitHub Issues 同步成看板卡片。它是本看板的提供方，因此其配置渲染在本插件自己的设置卡里——仅当扩展已安装时该区块才出现，且只有扩展开启时才显示仓库与凭据表单；见[扩展包 README](../dsh-task-board-github/README.zh.md)。

## 架构与协议

看板视图在首次打开时渲染，关闭后重新打开会保留本地视图状态。Host 同步、调度和执行独立于视图持续运行。

- `src/index.ts` 通过官方 `@deepseek-ai/dsh-api-gateway`、`@deepseek-ai/dsh-workspace` 与 `@deepseek-ai/dsh-host-webserver` SDK 挂载 Host 服务。
- `src/host-ledger.ts` 串行动作，并用临时文件加原子 rename 持久化 `{ schemaVersion: 6, revision, tasks, scheduler, recentRequests }`。
- `src/host-service.ts` 负责调度触发武装（按账本最近的到期时刻武装单个定时器，宿主未提供 `timer` 服务时回退到进程定时器）、错过触发跳过、runner 启动、重启对账和电源保护理由；次数预算由账本自己执行，因此单次计划或已用尽上限的循环规则绝不会被第二次开启。
- `src/client/native-panel.tsx` 注册看板的侧栏行与中栏页面（官方 `sidebar.panellist` 列表座位与 keyed `main` 座位），并维护与 ssh 面板的中栏互斥协议。
- `src/client/host-api.ts` 单次导入旧浏览器数据、提交幂等动作，并把 Host snapshot 当作唯一已确认 UI 状态。
- 同源接口为 `GET /api/task-board/state`、`GET /api/task-board/events` 和 `POST /api/task-board/action`。
- 所有接口都要求浏览器认证。在看板自己的围栏下，这体现为浏览器同源标记：`sec-fetch-site: same-origin`、`Origin` 头，或 Host 的 `dsh-auth-*` 浏览器认证 cookie。最后一种是 DSH 桌面版到达看板的方式：它从 `dsh-app://app/` 提供 Web GUI，并自行转发该页面的请求，转发时删掉 `Origin` 与 `sec-fetch-site`，改为附上启动时用 Host 启动链接换来的、与 authority 绑定的 cookie。直接访问只允许 DSH loopback origin。由 DSH 自己经可信反向代理或 Ingress 提供服务的部署，改由 Host 的 `connection` 服务放行：它执行自己的 Host/Origin 围栏并校验签名浏览器会话，再把请求交给看板；DSH 不负责前置的同机认证反向代理，仍必须使用显式 Host 白名单和服务端注入 token；部署自己的隧道拨的是本机回环上游时，也可以改为打开 `trustForwardedProxy`（见「配置」），该形态仅凭同源标记即可通过。POST 还必须为 JSON。普通动作上限 64 KiB，导入上限 2 MiB。action 联合中没有命令、可执行路径、shell 文本或任意参数字段。

## Agent 工具

看板不止能从界面操作，也能从对话里操作。每个工具都驱动同一个 Host 账本，因此在界面创建的任务对 agent 立即可见，反之亦然；界面遵守的每一道门禁在这里同样生效。工具面跟随 `enabled` 开关：看板关闭时不响应任何工具调用；运行时不提供工具注册表的部署仍会正常挂载看板，界面不受影响。

- `task_board_list`：按列、父任务、仅根任务、标签、自由文本与归档状态筛选卡片，并返回看板摘要——revision、时区、子任务深度上限、会话默认权限与各列计数。
- `task_board_get`：读取单张卡片的完整信息——Prompt、标签、父任务链接、直接子任务、执行目标、计划、权限门状态，以及最近十次执行（含会话 id、发起方与结果）。
- `task_board_create`：创建任务；带 `parentId` 时创建子任务。未单独设置的执行目标继承父任务，并受部署的子任务深度上限约束。
- `task_board_update`：修改内容、标签与执行目标；空字符串清除某个目标，空标签数组清除全部标签。
- `task_board_set_parent`：把现有卡片挂到某个父任务下，或用空 parentId 解除关联。
- `task_board_run`：立即执行一张卡片（可选择重跑已结算的卡片），只开这张卡自己的会话：Host 不为它的子任务开会话，带未完成子任务的卡会被告知要派发哪些子卡；会消耗真实 API 额度，且未确认的高于默认权限的绑定会以 `confirmation-required` 被拒绝。
- `task_board_manage`：把卡片移到任意列——只写列、绝不伪造执行记录，因此待规划/待办用于规划、已完成/已失败用于声明工作在受追踪的执行之外结束、进行中用于表示工作正在进行但没有会话；真正有未结算执行的卡片会拒绝移动直到结算——归档或恢复、删除，或对看板已无法观察的运行中卡片强制结算（记为 cancelled 并记录调用者原因，卡片回到待办），同样受 Host 的运行中子任务与父子关系护栏约束。
- `task_board_schedule`：启用、修改或关闭卡片的计划——循环 cron（可选 `maxRuns` 上限，传 `0` 清空回不限次）或在 `at`（epoch 毫秒）指定时刻只执行一次的单次计划——包括其 IANA 时区（`timeZone` 传空字符串即清除，回退到 Host 时区）。

这里刻意没有「确认权限」工具：该门禁存在的意义就是由人工放行高于默认值的权限，若 agent 能自行盖章，这道门就形同虚设。遇到 `confirmation-required` 的 agent 应当请用户在界面确认该卡片。工具调用带归属：执行会把发起会话记为 initiator，创建/更新则会把发起会话写进续接卡片快照。

## 安装

安装聚合包或单独安装本包，然后重启 `dsh web`：

```sh
dsh plugin --profile web add @linxin666/dsh-client-ui-task-board@latest
```

本地开发安装：

```sh
git clone https://github.com/zhu1090093659/dsh-web.git
cd dsh-web
pnpm install
pnpm build
dsh plugin --profile web add link:$(pwd)/packages/dsh-task-board
```

## 配置

本插件的设置卡把选项组织成可折叠的分区——看板与运行行为、任务验收、新建卡片默认值，以及提供方扩展（GitHub Issues 集成）贡献的分区。每个分区默认折叠。改动即时生效：点选的选项立刻写入，输入的值在停止输入、按 Enter 或离开输入框时写入；按 Escape 恢复已保存的值。

| 键 | 默认值 | 行为 |
| --- | --- | --- |
| `enabled` | `true` | 启用 Host 服务与浏览器看板。 |
| `announceToAgent` | `false` | 按需开启：开启后向 agent 系统提示加入任务看板说明。 |
| `preventIdleSleep` | `false` | 存在运行中的 DSH 会话、已启用计划或未知会话状态时，持有一个系统空闲睡眠断言。 |
| `trustedProxyHosts` | `[]` | 仅通过已认证 loopback 反向代理路径接受的规范 `host[:port]` authority 白名单。 |
| `proxyTokenEnv` | `DSH_TASK_BOARD_PROXY_TOKEN` | 保存反向代理 token 的环境变量名；token 本身不会写入插件配置。 |
| `trustForwardedProxy` | `false` | 放行落在本机回环上游的被转发请求——也就是你自己的反向代理或隧道拨 `127.0.0.1:<端口>` 时产生的形态——且仍以浏览器同源标记为前提。关闭时一切被转发请求都判为远程（issue #1906）；开启后显式 `sec-fetch-site: cross-site` 标记照旧拒绝，上面那条 token 白名单反代路径不变。volatile：每次请求实时读取，改动无需重启即对下一次请求生效。 |
| `sessionDefaultPermission` | 跟随宿主 | 权限确认门的显式基线。未设置时跟随宿主自己的默认权限预设（DSH 新会话的起始权限），部署不提供权限目录时回退 `read-only`。卡片有效权限（交接包或钉住字段）高于该基线时，运行前必须经人工确认；cron 拒绝调度待确认卡片。 |
| `maxSubtaskDepth` | `1` | 子任务深度上限，1 到 3。取 1 时一个任务只能有一层子任务，子任务不能再创建或关联子任务。它只决定一张卡下面能挂多深：执行一张卡只开它自己的一个会话、子任务由它自己派发，所以层级深浅不改变一次执行开几个会话。 |
| `sessionPollSeconds` | `5` | 看板有需要核对的事情（运行中的卡片或未结算执行）时，Host 重新读取 DSH 会话名册的间隔秒数。两者都没有的空闲看板完全不轮询。取值 1 到 300；间隔越短结算越快、Host 负载越高。 |
| `runLaunchWidth` | `3` | 同一刻最多几个执行被准入启动，1 到 16。它只在多张卡片同时要执行时起作用——手动执行、重新执行与到点定时一样：这么多个执行会创建会话并把 Prompt 入队，其余排队等待（队列在多次运行之间轮转、同一次运行内 FIFO）。单卡执行只开一个会话，所以这个值不改变单次运行的形态。默认值取自本仓「重型实施子代理并发 ≤ 3」的实测纪律。每次准入时读取，修改只影响尚未入列的启动。 |
| `spendBudgetTokens` | `0` | 看板派活的每日 token 上限。`0` 表示不设闸，此时看板不读任何账本、也不做任何计数。计量单位是 token，因为使用账本只对 DeepSeek 官方路由计价，其他路由恒为 0。口径是 usage 账本按本地日期分桶的用量：当天已达上限时，手动执行与重新执行被拒绝，调用方拿到的拒绝原因就是这条文本；到点的定时执行把拒绝写进调度错误并跳过本次触发，不消耗计划次数，因此调高上限或跨过本地零点后下一次触发照常执行。每次闸门判定都重新读取，改完即时生效、无需重启。 |
| `goalRunEnabled` | `false` | 全局原生 `/goal` 执行开关。关闭时所有卡片都按普通单回合执行，看板不再武装 dsh 内置的 `/goal`；只有它开启、且卡片自身的「以 /goal 开始执行任务」被勾选时（该选项默认关闭），运行才使用原生 /goal。该值在执行启动时冻结，因此实时修改只影响之后新开的执行，卡片存储的 `goalRun` 偏好绝不被改写。这类运行记录为 `goal-disabled`：既不会被报告为已验收通过，也不会因缺少完成调用被判失败。与 `goalVerification` 相互独立。 |
| `goalVerification` | `true` | 本插件所开执行的 goal 验收。开启后，任务执行内的 `update_goal(action: complete)` 在该执行取得验收通过记录前一律被拒绝。只影响以 goal 形式执行的运行：普通聊天与未勾选 `goalRun` 的卡片不受影响。单张任务卡可以用「跳过本任务的验收」勾选退出本开关的约束（见下）。 |
| `goalVerificationModel` | `''`（继承） | 裁判模型路由，形如 `provider/model`。留空继承宿主模型目录的默认路由，绝不是任务卡钉住的执行模型。 |
| `goalVerificationReasoningEffort` | `''`（继承） | 裁判推理强度。留空继承宿主默认档位；目标模型未声明的档位绝不传参，回退到该模型自身默认档位并记录、展示。 |
| `goalVerificationCallTimeoutSeconds` | `150` | 单次裁判调用的上限（秒，范围 30–600）。一次验收要发六次裁判调用，上限过小会把「慢但健康」的路由变成一整排超时。 |
| `goalVerificationBudgetSeconds` | `1200` | 单次验收的总时间上限（秒，范围 120–1800），覆盖证据渲染与全部裁判调用（含重试）。应不小于单次调用上限的六倍，否则一次验收永远跑不完；耗尽时记为一类「预算耗尽」结果，不产生任何判定，也不消耗任何额度。 |
| `cardDefaultMode` | `''`（无默认值） | 新建卡片起始的 agent 预设。留空不钉住任何值，卡片照旧跟随部署默认预设。 |
| `cardDefaultPermission` | `''`（无默认值） | 新建卡片起始的权限。它可以高于会话权限基线：它只是预填，高于基线的绑定仍需人工确认后才能运行。 |
| `cardDefaultModel` | `''`（无默认值） | 新建卡片起始的执行模型（`provider/model`）。留空不钉住任何值，并使 `cardDefaultReasoningEffort` 失效。 |
| `cardDefaultReasoningEffort` | `''`（无默认值） | 新建卡片起始的思考档位，仅在卡片最终模型就是配置的 `cardDefaultModel` 时生效；自己选了模型的调用方不借用任何默认档位。 |

四个新建卡片默认值都默认空，且都是 volatile：改动无需重启即生效，既作用于之后新建的卡片，也作用于已经打开的页面（无需刷新，新建对话框就会用上刚保存的值）；留空不钉住任何值，因此完全不配置它们的部署创建卡片的行为与今天一致。新卡片的每个执行目标按同一顺序取值——新建对话框里的显式选择优先于从源卡带过来的值（复制卡片沿用源卡已钉住的值，新建子任务沿用父卡的），后者优先于看板默认值——而父卡的权限永不参与。这四个默认值都不会放松权限确认门：高于会话基线的默认权限只是预填，卡片仍要等人工确认后才能运行。

浏览器直接访问仍限制为 DSH loopback origin。由 DSH 自己经可信反向代理或 Ingress 提供服务的部署（例如 TLS 在边缘终结、`dsh web --trusted-host dsh.example.internal`）无需看板侧配置：路由桥接 Host 的 `connection` 服务，该服务接受的 Host/Origin 围栏与签名 `dsh-auth-*` 浏览器会话即放行请求，被它拒绝的请求仍要过看板自己的围栏。这条路径不会给未持有 Host 签发会话的浏览器任何额外能力，也与下面两项配置无关。

若使用同机认证反向代理，应让 DSH Web 绑定 loopback，配置 `trustedProxyHosts`，在 `proxyTokenEnv` 指定的环境变量中放置高熵 token，并让代理在完成认证后替换（不能透传客户端提供的）`X-Dsh-Task-Board-Proxy-Token`。代理 Host 必须在白名单内，浏览器 `Origin` 必须与其 authority 相同。修改这些 composition 级代理设置后需重启 Host。

另一种形态是运维自己的隧道或反向代理转发到本机的请求。打开 `trustForwardedProxy` 后，携带转发头且落在回环上游（回环套接字与回环 Host）的请求仅凭浏览器同源标记即可通过；显式 `sec-fetch-site: cross-site` 标记照旧拒绝，经并非拨向本机的代理而来的请求仍判为远程。该开关是 volatile、按请求读取，改动对下一次请求生效；只有你自己运行那一跳时才打开它，因为不归你控制的代理可以替你伪造这些标记。

macOS 后端启动 `/usr/bin/caffeinate -i -w <host-pid>`，绝不请求 `-d`。Windows 后端从 `SystemRoot` 启动绝对路径的 Windows PowerShell，固定 helper 只请求 `ES_CONTINUOUS | ES_SYSTEM_REQUIRED`；不请求 `ES_DISPLAY_REQUIRED`，不修改电源计划，也不需要管理员权限。Linux 后端只从 `/usr/bin/systemd-inhibit` 或 `/bin/systemd-inhibit` 启动 systemd-logind `idle` block inhibitor，不请求 `sleep`、`handle-lid-switch` 或显示器/屏保 inhibitor；没有 systemd-logind 时显示 `unsupported` 或可见错误，不启动桌面环境专用替代命令。其他平台报告 `unsupported`。

## 数据存储与迁移

- 权威账本文件固定为 `$DSH_HOME/task-board/ledger-v2.json`（文件名为历史沿用）；当前文档 schema 为 v7，更旧的文档（v2/v3/v4/v5/v6）会在下一次 Host 启动时逐字段无损迁移并原地写回。POSIX 新文件权限为 `0600`；Windows 继承用户目录 ACL。
- v5 增加每 execution 的验收块（`verification`）。迁移刻意不补写该块：v5 之前开启的 execution 仍按历史判定结算，不会因为一个从未为它武装的完成门而被追溯验收。无法解析的验收块会被丢弃（该 execution 视为未验收，fail closed）；import 带入的 execution 记录一律剥离该块，否则伪造的通过记录会让导入的工作看起来已验收。
- v6 增加调度计划类型与次数预算，v7 增加卡片附件元数据（`attachments`）。两者都是增量式的：该字段出现之前写入的卡片行为完全不变；非法的附件列表逐条修复（丢弃不可用条目，并应用数量与字节上限），而不是丢掉整行任务。旧版浏览器 import **不接受**附件：v1 导出从不携带附件，因此声称携带附件的文档会被拒绝，而不是绕过存储校验进入账本。
- v2 到 v3 迁移失败（任务行结构非法）时失败关闭并报出明确错误，原文件保持不动；绝不静默以空账本重启。损坏或未知 schema 的文件会移动到防碰撞的 `ledger-v2.json.corrupt-*` 名称，Host 以空账本和可见 scheduler 错误启动，不覆盖损坏字节。
- 每个 origin 首次加载新版页面时，按稳定 source id 和 request id 导入 `dsh.taskBoard.v1`。任务按 id 合并，浏览器端严格较新的顶层字段优先，时间戳相同时保留 Host 字段，执行记录按 execution id 合并。
- 最近 256 个 request id 与动作的 SHA-256 指纹会随账本持久化，因此 Host 重启后的变更重试仍保持幂等，且不会复制完整动作载荷。
- 外部结果 execution 记录只新增一个可选布尔字段（`external`）。该字段出现之前写入的账本没有它，读取时既不会凭空补写、也不会把「没有该字段」当成外部执行，因此旧文档原样读回；非布尔值会被丢弃，不会被当成可信来源标记。账本仍保持 v5：该字段在既有行里一律缺席，正是 v5 迁移已描述的那种增量变更。
- 任务标签是任务行上的可选 `tags` 字段（`{ name, promptPrefix? }[]`），无需 bump schema：不含该字段的 v3 文档原样加载，非法的标签列表逐条修复（丢弃空名、重复项与超长名，并封顶数量），而不是丢掉整行任务。
- /goal 选项是任务行上的可选 `goalRun` 字段，同样无需 bump schema：缺席即关闭，显式 `true` 是加入选择，多写的 `false` 在加载时被修复回缺席。
- 只有导入成功并经 Host 确认后，`dsh.taskBoard.v2.hostImported` 才保存当前 Host 账本 generation；新建或损坏恢复出的新 generation 会再次接收保留的 v1 数据。v1 localStorage 原值保持不变，作为只读回退备份。
- 同一时间只有一个 Host 进程能通过 `$DSH_HOME/task-board/ledger-v2.lock` 持有任务看板账本目录；第二个使用同一 DSH home 的 Host 会失败关闭，不并发写账本。

## 安全模型

- 插件仍处在 DSH Web 既有部署与网络边界内，不返回宽松 CORS 头。state、action 与 SSE 共用同一访问栅栏；裸本地命令行请求不会被当作浏览器请求接受。部署提供 Host 的 `connection` 服务时，围栏只放行该服务已认证的请求——它的 Host/Origin 围栏加签名 `dsh-auth-*` 浏览器会话——其余请求继续走看板自己的 loopback 与 token 判定。除此之外，围栏拒绝一切被转发请求；除非打开 `trustForwardedProxy`，即使打开，也只有落在本机回环上游、且不带跨站标记的那种被转发形态被放行。
- 所有变更载荷使用严格、版本化的判别联合；浏览器不能写入 scheduler 独占时间戳或 execution 结果。
- 工作区、预设、权限、cron、任务状态和导入记录都会在 Host 再校验。
- 卡片有效权限高于配置的会话默认值时进入待确认状态：人工确认该确切绑定之前，Host 拒绝手动执行与 cron 触发；变更钉住权限或交接包会清除确认（封死先确认后替换的提权路径）。
- 任务 Prompt 是发给 DSH agent 会话的数据。协议不接受 shell 命令、PowerShell 正文、可执行路径或可配置 helper 参数。
- 任务标签与 Prompt 同属客户端断言，并走同一条受门禁的动作通道：协议层拒绝空标签名、未知键、超过八个标签以及超长的标签名或执行提示。标签的执行提示会做分隔符转义（无法伪造续接卡片的来源声明标记），且注入位置在该来源声明包裹之外。
- 附件元数据同属客户端断言，而 Host 不相信它：动作携带的每个附件都会在账本提交前从官方附件存储读回核对（图片逐字段比对、并核对摘要与存储自己的展示名称，文件流式读取并重算摘要），存储无法证明的引用会让动作被拒绝。上传路由按请求流的**实际**字节计量，超限的请求体被拒绝——被拒的上传不会进入账本，而存储 SDK 不保证已写入后端的部分对象被清理，因此看板不承诺这样的清理。图片读取路由只服务于本看板某张卡片引用过的对象，未被引用的标识读不出来。
- 子任务父子关系由 Host 而非浏览器判定：父任务必须存在且在看板上、任务不能挂到自己的后代之下、结果深度不得超过 `maxSubtaskDepth`。权限门只看被运行的那张卡自身解析出的绑定：子卡自己钉住的未确认高权限不再拦住父卡，因为父卡根本不为它开会话；子卡自行执行时按祖先链解析继承的绑定，并带上该祖先的人工确认戳。绑定绝不复制到卡片上，因此解除关联不会残留一张已确认的高权限任务。
- Agent 工具面经进程内服务直达同一个 Host 账本，而不经过 HTTP 栅栏，并且不携带任何确认能力：拒绝语义、失败即关闭的钉子、深度门禁与运行中任务锁全部照旧生效，任何工具调用都无法解除高于默认权限的绑定。
- 电源 helper 使用固定可执行路径、固定参数、`shell: false`，失败后按 1、2、5、10、30 秒有界退避。Linux helper 通过 Host stdin 生命周期退出，使 systemd inhibitor 随 Host 异常退出自动释放。

## 构建与测试

需要 Node 20 或更高版本及官方 NPM SDK 包；不使用 DSH 源码 checkout。

```sh
pnpm --filter @linxin666/dsh-client-ui-task-board typecheck
pnpm --filter @linxin666/dsh-client-ui-task-board test
pnpm --filter @linxin666/dsh-client-ui-task-board build
```

设置 `DSH_POWER_SMOKE=1` 可在 Windows、macOS 或 Linux 上显式启用原生 helper smoke：真实启动固定 helper、等待 ready、在清理路径释放并确认进程退出，不修改系统电源计划。Linux 会先以有界超时探测 systemd-logind；没有可用 system bus 时原生部分跳过，纯逻辑测试仍可运行。

## 手工验证

1. 挂载插件并重启 `dsh web`，打开任务看板，确认 Host 时区和电源状态可见。
2. 新建并编辑任务；刷新或打开第二个同源标签页，确认两者显示同一 Host revision。
3. 执行一个钉住工作区、预设和权限的任务；确认出现新会话，并由该会话的 `turn/end` 历史结算任务。
4. 在勾选 /goal 选项的情况下执行一个任务：确认会话启动了目标，卡片在多轮续跑期间保持 `running`，直到目标完成后结算为成功。取消勾选后再执行一次，确认变回单回合、由一条 `turn/end` 结算。
5. 启用一个即将到期的 cron，关闭全部浏览器页面，确认 Host 仍只创建并结算一次 execution。再把单次计划设在即将到来的时刻，确认恰好创建一次 execution 且规则显示为已结束；把循环规则的次数上限设为 2，确认第三次触发不再创建任何执行。
6. 让 Host 停止并错过一个 cron 触发点，重启后确认该次被跳过，`nextRunAt` 从当前 Host 时间向后滚动。
7. 开启 `preventIdleSleep` 并运行长任务，让显示器自动熄灭；恢复显示后确认会话继续且 execution 已结算。
8. 关闭设置并禁用所有计划，再停止 DSH，确认 helper 退出；macOS 可用 `pmset -g assertions` 辅助确认插件没有 display-sleep assertion。
9. Linux 可用 `systemd-inhibit --list` 确认只存在 `idle`/`block` 条目；显示器仍按桌面设置关闭，手动睡眠和合盖仍由系统策略处理。

## 已知限制

- Host 停止、系统睡眠或长暂停期间错过的触发点会跳过，绝不排队补跑。
- 同一任务已在运行时会跳过到期出现并滚动到下一 cron 匹配点；任务运行不并发、不排队。单次计划没有下一匹配点：因卡片正在运行或 Host 停机而错过的时刻会以「已跳过」结束该规则，绝不补跑。
- 有限次规则按调度器真正开启的执行计数，手动执行绝不消耗定时预算。任何会话存在之前就启动失败的那次会退回次数，规则因此保留下一次出现；而确实已到达会话的运行即使随后失败也计数。
- DST 采用 Host 本地墙上时钟语义：春季跳时中不存在的分钟会跳过，秋季回拨中重复的分钟不会执行第二次。
- 电源保护只阻止空闲系统睡眠，明确允许显示器睡眠与锁屏。
- 合盖、手动睡眠、休眠、关机、低电量强制睡眠和企业电源策略不在保证范围内。
- 插件不创建唤醒定时器，也不能唤醒已经睡眠的机器。
- Linux 需要 systemd-logind 及允许当前用户取得 idle block lock 的策略；容器、WSL、无 system bus 或非 systemd 系统可能显示 `unsupported` 或 `error`。桌面环境是否把 logind idle lock 与显示器空闲联动属于其自身策略，插件不请求屏保或显示器 inhibitor。
- 已启用计划会从未来触发点之前持续持锁，因此可能增加电池消耗。
- Host 执行消耗与普通 DSH agent 会话相同的 API 额度。
- 执行一张卡只开它自己一个会话，这个会话在内部用 subagent 派发未完成的子任务：子任务多的卡会消耗它派发的每个子 agent 的额度，而看板本身不为它们多开会话。没有回填结论的子卡继承父卡这次运行的判定——那是对父卡判断的记录，不是这张卡自己跑过一遍。
- Agent 工具调用由模型驱动：从对话发起的执行或定时运行同样消耗 API 额度，且 agent 可以启用一个持续触发的计划，直到被关闭或看板被关掉。
- 目标执行会按目标需要不断续跑：每一轮都消耗 API 额度，会话会一直忙碌，直到目标完成或被 dsh 阻塞（轮次上限、回合被拒或入队失败），后者在看板上记为执行失败。
- 目标模式依赖运行时的 `/goal` 命令与命令派发器。两者都不提供的部署仍会把每张卡片当单回合执行，并在宿主日志中记录目标模式不可用，而不是让执行失败。

- 每次验收都会消耗模型请求：一次验收是 3 项判据 × 2 轮（6 次裁判请求），而同一 execution 最多验收两次，因此一个需要修复一次的 goal 周期最多在任务本身之外多花 12 次请求。
- 验收不估费用：本部署无法为目标裁判路由提供可靠的分 token 价格来源，报告只列出 token 用量。
- 宿主观察的工作区变更块只在部署确实记录变更且读取成功时出现；服务不可用或对比读取失败时，证据降级为只判轨迹，而不是让验收失败。
- 验收只在能生效的地方强制执行：没有成为 goal 执行的运行（`/goal` 被拒绝或不可用，或全局总开关关闭）不受门禁，记录会明确说明，而不是暗示它已被验收；历史上团队执行遗留的 teammate 记录同样不带验收。总开关关闭的运行记为 `goal-disabled`（独立原因，与「命令被拒」区分），因此也不会被读成已验收通过。宿主不提供模型目录时无法解析裁判路由，完成声明按 fail closed 拒绝并记为验收异常；异常额度用尽后卡片保持打开而不是判死，恢复需要用户清除这些异常。
- 第一版一律使用 coding 判据：没有自动 rubric 选择，非工程任务也按工程判据评判（设置界面已说明）。
- 若第三方验收插件（例如已安装的 `dsh-llm-verifier`）也开启了自己的自动验收，两个裁判会各自对同一会话评分：本看板的验收决定能否完成，另一个只做提示，公开 SDK 接口无法让二者共享同一判定。要避免为两次判定付费，只能关闭对方的自动模式。
- 官方附件存储没有删除接口。从卡片移除附件、或删除卡片本身，移除的只是**元数据**：存储对象仍留在部署的附件存储里。看板既不承诺物理删除，也不为此绕路。
- 非图片附件以可读宿主路径进入执行会话，因此只有当部署的附件存储以宿主文件形式保存时，会话才能真正读到该文件。存储不给可读路径的部署会让这次运行以该原因失败，而不是悄悄丢掉附件照常执行。
- 图片附件需要接受图片输入的模型路由。看板会向官方模型服务询问这次运行真正使用的路由（卡片钉住的模型，或复用会话当前所在的路由）；声明了模态且不含图片的路由会在 Prompt 入队之前拒绝这次运行。模态读不到的路由视为未知，放行；完全解析不出路由时预检被跳过——这两种情况下官方 Host 的检查仍是兜底。

## 数据遥测

浏览器半区每个 UTC 日向 dsh-market.com 发送一次匿名安装心跳：仅含一个 localStorage 随机 ID 与本包名，无其他数据。服务端只存储该 ID 的加盐哈希，不存 IP，且只暴露聚合计数。完整契约见 [docs/telemetry.md](../../docs/telemetry.md)。
