deepseek-foreman 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 yanauto (upstream opus-manager)
4
+ Copyright (c) 2026 biantao1108 (deepseek-foreman port)
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.en.md ADDED
@@ -0,0 +1,143 @@
1
+ English | [中文](README.md)
2
+
3
+ > A community plugin for DeepSeek Harness, not affiliated with DeepSeek.
4
+
5
+ # deepseek-foreman
6
+
7
+ Write work as tickets and dispatch them to **cheaper models**; the Lead only breaks down the work, dispatches it, re-runs the acceptance commands personally, sends it to a **different vendor** for read-only review, and verifies every finding one by one. When nobody is around, it keeps working through the queue.
8
+
9
+ Built for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (`dsh`).
10
+
11
+ ## Origin
12
+
13
+ The design and the ticket/receipt contract are ported from [yanauto/opus-manager](https://github.com/yanauto/opus-manager) (MIT, Copyright (c) 2026 yanauto). Upstream is a **Claude Code skill**; this project is its **dsh port**. Upstream validated the flow over 8 weeks, 13 repositories and 360 tickets; this project keeps its directory contract, its acceptance criteria, and the principle that "a receipt is a claim, not evidence".
14
+
15
+ ## Acknowledgements
16
+
17
+ Our thanks to [yanauto/opus-manager](https://github.com/yanauto/opus-manager): the ticket/receipt contract and the whole discipline of "re-run acceptance yourself, hand the work to a different vendor for read-only review, verify every finding one by one" were proven upstream over 8 weeks, 13 repositories and 360 tickets. This project is only its **dsh port** — the directory contract is kept, the principles are kept, and so is the line "a receipt is a claim, not evidence". Both are released under **MIT**; the two copyright notices live in [LICENSE](LICENSE).
18
+
19
+ ## Why port it to dsh
20
+
21
+ Upstream has to use external CLI tools (`pi`, `cursor-agent`, `codex`, `agy`) as workers, because **Claude Code has no entry point for swapping models per sub-task** — the model is hard-coded in the subagent definition, plus a global switch meaning "all subagents use the same model".
22
+
23
+ dsh's `subagent` tool accepts `provider` / `model` / `reasoning_effort` **at call time**, constrained by the session-level `allowedModels` allowlist. So "one vendor as manager, one as builder, one as reviewer" in dsh is **three tool calls inside the same process**, with no external CLI required.
24
+
25
+ ## Differences from upstream
26
+
27
+ | | opus-manager | this project |
28
+ |---|---|---|
29
+ | Host | Claude Code skill | dsh skill |
30
+ | Workers | external CLI processes (pi / cursor-agent / codex / agy) | `subagent` tool + per-call `provider`/`model` |
31
+ | First-time setup | scan which CLIs are installed, read each `--help`, write `dispatch.sh` / `review.sh` | read the `allowedModels` allowlist + cross-check with `list_subagent_models`; **no dispatch scripts written** |
32
+ | Worker detached from session | `nohup` / `Start-Process` | a continuable child session with `run_in_background: true` |
33
+ | Read-only review | the review command opens only `read,grep,find,ls` | `subagent_readonly` first: physical read-only (only `read`/`grep`/`glob` at runtime); when that tool is absent from the session, fall back to a review prompt stating "do not modify files; run read-only commands only" + the Lead checks `git status` before and after |
34
+ | Context isolation | separate process | separate Session (child-session work does not enter the parent conversation) |
35
+ | Cost rule | one new session per ticket | same, **plus**: keep the same model by continuing with `subagent_fork` (preserving the prefix KV cache); use `subagent` only when the model must change; children inherit the deployment persona, so worker discipline is not restated per dispatch |
36
+ | Unattended management | `queue.md` + background wait | same, stackable with dsh's `goal` |
37
+
38
+ The ticket/receipt templates, the `_tickets/` directory contract, and the acceptance and verification flow **stay the same** — that part is host-independent, and it is upstream's most valuable piece.
39
+
40
+ ## Install
41
+
42
+ From package to first dispatched ticket in ≤ 10 minutes — just follow steps 0–6. Step-by-step detail and a troubleshooting table live in the full guide: [docs/install-flow.md](docs/install-flow.md).
43
+
44
+ 0. **dsh desktop**, with LLM routes for **≥2 different vendors** configured (an API key for each).
45
+ 1. **The `allowedModels` allowlist** (`@deepseek-ai/dsh-tool-subagent/model-selection-settings`): list every `provider/model` subtasks may use, **at least two from different vendors** (a hard requirement for cross-vendor review). After editing the allowlist, open a new session — it is a session snapshot; see [Prerequisites](#prerequisites).
46
+ 2. **Install the package**: add `deepseek-foreman` from the dsh plugin page; four things take effect immediately:
47
+ - it mounts `pick_route` (route adjudication: peak-hour lock, vision, output ceiling, cross-vendor review — see "Plugin" below);
48
+ - it mounts `subagent_readonly` (the read-only review instance: a child session dispatched through it has only the `read` / `grep` / `glob` read tools at runtime);
49
+ - it auto-installs the ticket skill into `~/.dsh/skills/deepseek-foreman`: a symlink first (so repo edits stay live), falling back to a recursive copy where symlinks are refused (e.g. Windows privileges); an existing install (symlink or directory) is left untouched — nothing is ever overwritten; if both steps fail it throws nothing and never blocks dsh startup — the reason is recorded in `setup` (step 5); set the plugin's `installSkill: false` to turn auto-install off;
50
+ - if no role table is found, it **lays down a template with Chinese comments** at `~/.dsh/foreman.roles.yml` (the packaged [roles.example.yml](roles.example.yml)) and the plugin enters an "unconfigured" guidance state — no errors, dsh startup is never blocked.
51
+
52
+ dsh's `skill-filesystem` scans `~/.dsh/skills` (the `user-dsh` root) and `~/.agents/skills` (the `user-agents` root) by default. Installing here serves dsh only and does not pollute the four-tool shared `~/.agents/skills`.
53
+ 3. **Open a new session**, so the allowlist snapshot covers the newly installed instance.
54
+ 4. **Edit `~/.dsh/foreman.roles.yml`**: uncomment one of the "combination A" (single-vendor, full stack) / "combination B" (multi-vendor mix) groups in the template (**only one**), then replace `provider` / `model` with routes already present in your step-1 allowlist. **Saving takes effect immediately** — every `pick_route` call checks the file's mtime and re-reads on change, no restart; every field has its own comment; see the "Configure the role table" section below.
55
+ 5. **Self-check**: tell dsh "**call pick_route and show me setup**". A `pick_route` call without a role is the self-check mode; its `setup` block shows at a glance what is still missing:
56
+
57
+ | Field | Contents |
58
+ |---|---|
59
+ | `skill` | skill install outcome: `linked` / `copied` / `exists` / `disabled` / `failed: ...` |
60
+ | `rolesFile` | role-table path, status (`ok` / `unconfigured` / `error`), role count and an error summary |
61
+ | `allowlist` | the `allowedModels` pairs scanned from each profile's `cordis.patch.yml`, reconciled against the role table; `unmatchedRoles` lists roles not on the allowlist |
62
+ | `hints` | a plain-language hint for each problem found (e.g. "route deepseek/xxx of role daily-code is not in allowedModels; add it to the allowlist, then open a new session") |
63
+
64
+ 6. **Work by ticket**: tell dsh "**work this ticket: do yyy in the xxx project**". The SOP then runs itself: write the ticket → `pick_route` picks the route → `subagent` dispatches → the Lead re-runs acceptance → `subagent_readonly` dispatches a cross-vendor read-only review → verify item by item → wrap up. Say "I'm leaving, keep going" to enter unattended mode — the nine-step section and the queue rules live in [SKILL.md](skill/deepseek-foreman/SKILL.md) §9 (无人托管 / unattended management); if something goes wrong after dispatch, use the 故障速查 (troubleshooting) table in [docs/install-flow.md](docs/install-flow.md).
65
+
66
+ Note: hints 目前为中文输出 / hints are currently emitted in Chinese.
67
+
68
+ ## Configure the role table (`~/.dsh/foreman.roles.yml`)
69
+
70
+ The role table does not live in cordis config; it lives in an external YAML file, default `~/.dsh/foreman.roles.yml` (change the location with the plugin's `rolesFile` field).
71
+
72
+ On package install, if that file does not exist, the plugin **lays down a template with Chinese comments** (the packaged [roles.example.yml](roles.example.yml)) and enters an "unconfigured" guidance state — every `pick_route` returns `ok:false`, with a reason naming the file location and the next step ("fill in provider/model per the comments; saving takes effect immediately"). The plugin itself neither errors nor blocks dsh startup.
73
+
74
+ The only thing to edit is step 4 of the install flow: uncomment one of the "combination A" (single-vendor, full stack) / "combination B" (multi-vendor mix) groups in the template (**only one**; uncommenting both produces two top-level `roles:` keys), then replace `provider` / `model` with routes already present in your own allowlist. Changing the allowlist requires a new session (see [Prerequisites](#prerequisites)); the role-table file is not subject to this rule — saving takes effect immediately.
75
+
76
+ ```bash
77
+ $EDITOR ~/.dsh/foreman.roles.yml # fill in provider/model; saving takes effect immediately
78
+ ```
79
+
80
+ **No restart needed to change the role table**: every `pick_route` call checks the file's mtime and re-reads and re-validates on change. A broken file will not crash the plugin either: the last good role table is kept, the call returns `ok:false` explaining what is wrong, and the next call after you fix it recovers automatically.
81
+
82
+ ## Prerequisites
83
+
84
+ dsh desktop or CLI, with `cordis.patch.yml` configured:
85
+
86
+ - `@deepseek-ai/dsh-tool-subagent/model-selection-settings` → `enabled: true` + `allowedModels` with at least two routes from **different vendors**
87
+ - the `standard` preset (whose `subagent` tool instance carries `modelSelectionSettings: true`)
88
+
89
+ Without both, `subagent` will not expose `provider`/`model` parameters, and this skill's dispatch step cannot run.
90
+
91
+ **The allowlist is a session snapshot**: `allowedModels` is read once **when a new top-level session is created**; changing it afterwards does not affect sessions already running — after editing the allowlist you must **open a new session**. The role-table file (`~/.dsh/foreman.roles.yml`) is not subject to this rule; it is re-read on every call, so saving takes effect immediately.
92
+
93
+ ## Plugin (v1: route adjudication)
94
+
95
+ `src/index.ts` is a Cordis plugin registering one model-visible tool, `pick_route`. It handles only the hard constraints the skill cannot — these could only rely on model self-discipline in markdown, but can be actually refused in code:
96
+
97
+ | Constraint | Config field | When refused |
98
+ |---|---|---|
99
+ | **Peak-hour lock** | `peakWindows` / `peakDays` | Weekday 09:00–18:00 dispatched to deepseek → refused, with an automatic `fallback` role |
100
+ | **Vision capability** | `vision` | a job with screenshots dispatched to a text-only role → refused |
101
+ | **Output ceiling** | `maxOutputTokens` | a whole long deliverable dispatched to a role capped below 100K → refused |
102
+ | **Cross-vendor review** | `vendor` + a call-time `review_for` | reviewer and code author from the same vendor → refused, with other-vendor candidates listed |
103
+
104
+ Dispatch itself still goes through dsh's native `subagent` (which already supports per-call `provider`/`model`/`reasoning_effort`); the ticket contract is still file-based. **What the plugin does not do**: rebuild a task board, take over dispatch, or touch persistence.
105
+
106
+ The role table is configured in the "Configure the role table" section above. The old wiring still works too: write `roles: [...]` directly in cordis config; when non-empty it wins and `rolesFile` is ignored (see [example.cordis.yml](example.cordis.yml); the shipped bundle's [cordis.patch.yml](cordis.patch.yml) no longer carries a real role table).
107
+
108
+ The bundle patch of this package also mounts **`subagent_readonly`** (the read-only review instance): a child session dispatched through it has, **at runtime**, only the three read tools `read` / `grep` / `glob` — write tools disappear from the prompt and their execution is refused, so read-only review goes from "prompt-level self-discipline" to runtime enforcement (the native `subagent` itself still has no read-only filter parameter; see the table above). the child session of a read-only review runs **the session's default route (usually the lead's model)** — cross-vendor review covers work written by non-lead models (per-call model switching is impossible at the bundle layer: a standing mount needs a preset scope, see the `dsh-tool-subagent` source; work written by k3 itself remains a known gap), and the delegation tools (`subagent` / `subagent_fork` / `subagent_readonly` / `workflow`) are blocked by `toolFilter.deny` so a read-only child session cannot dispatch an unrestricted subagent.
109
+
110
+ Note: whether this tool **appears in a session depends on dsh's preset layer** — this package only guarantees that its own bundle-patch layer is written correctly. If `subagent_readonly` is not present in the session, fall back to the scheme in [SKILL.md](skill/deepseek-foreman/SKILL.md): the review prompt states "do not modify any files; run read-only commands only", and the Lead checks `git status` once before and once after the review.
111
+
112
+ ```bash
113
+ npm install && npm run build && node test/smoke.mjs # 110 self-checks
114
+ ```
115
+
116
+ The `Lead` role should match `agent-default-model` (the model the session actually runs as); otherwise the "brain" is misnamed.
117
+
118
+ ## Pitfall: bundle patches must use `insert:`
119
+
120
+ In a bundle's `cordis.patch.yml`, **a new plugin row must be wrapped under `insert:`**:
121
+
122
+ ```yaml
123
+ - insert:
124
+ - id: foreman
125
+ name: deepseek-foreman
126
+ config: { ... }
127
+ ```
128
+
129
+ A bare entry `- id: ... / name: ...` is interpreted as **overriding an existing row by id**; when the composition has no such row it **silently does nothing** — no error, no warning, the plugin page says "this plugin package contains no components", and the model side returns `NO_TOOL`. The official spec is [Package and install a plugin](https://github.com/deepseek-ai/deepseek-harness/blob/main/docs/user/develop/basic/publish.md).
130
+
131
+ Also: **hand-editing a bundle's patch file does not notify the running Host** (changes made outside the manager "announce nothing"). After editing, toggle the switch off and on again in the plugin page, or restart the app, for that layer to be reapplied.
132
+
133
+ ## Status
134
+
135
+ Installed into dsh desktop 0.2.0-rc.2 (Intel iMac) and verified live: cold start is normal, `pick_route` is callable by the model, the vision constraint really blocks and returns a fallback; the final `subagent_readonly` state is live-verified too (read-only tool set enforced, delegation tools blocked).
136
+
137
+ The ticket SOP (`skill/`) has run a full sprint on real tasks: on 2026-10-01 it took T001–T207 (fix tickets included) through the whole night, the `_tickets/` / `_receipts/` contract is established here, the whole chain (dispatch → build → cross-vendor read-only review → item-by-item verification → fix receipt) is closed, and the self-checks grew from 20 to 110.
138
+
139
+ Field report: [docs/dogfooding.md](docs/dogfooding.md).
140
+
141
+ ## License
142
+
143
+ MIT. See [LICENSE](LICENSE) (containing both the upstream and this project's copyright notices).
package/README.md ADDED
@@ -0,0 +1,141 @@
1
+ [English](README.en.md) | 中文
2
+
3
+ > 一个 DeepSeek Harness 社区插件,与 DeepSeek 官方无隶属关系。
4
+
5
+ # deepseek-foreman
6
+
7
+ 把活写成工单派给**更便宜的模型**去做,Lead 只负责拆活、派活、亲自重跑验收命令、派**另一家厂商**只读审查、逐条核实。人不在的时候按队列接着干。
8
+
9
+ 面向 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(`dsh`)。
10
+
11
+ ## 来源
12
+
13
+ 设计与工单/回执契约移植自 [yanauto/opus-manager](https://github.com/yanauto/opus-manager)(MIT,Copyright (c) 2026 yanauto)。上游是一个 **Claude Code skill**;本项目是它的 **dsh 移植版**。上游用 8 周、13 个仓库、360 张工单验证了这套流程,本项目沿用它的目录契约、验收标准和"回执是说法不是证据"原则。
14
+
15
+ ## 致谢
16
+
17
+ 感谢 [yanauto/opus-manager](https://github.com/yanauto/opus-manager):工单/回执契约,以及"验收亲自重跑、换一家厂商只读审查、发现逐条核实"这套打法,都是上游用 8 周、13 个仓库、360 张工单实打实跑出来的。本项目只是它的 **dsh 移植版**——目录契约照搬、原则照搬,连"回执是说法不是证据"这句话也照搬。上游与本项目同以 **MIT** 发布,双份版权声明见 [LICENSE](LICENSE)。
18
+
19
+ ## 为什么要移植到 dsh
20
+
21
+ 上游必须靠外部命令行工具(`pi`、`cursor-agent`、`codex`、`agy`)当工人,因为 **Claude Code 没有"按次给子任务换模型"的入口**——它的模型静态写在 subagent 定义里,外加一个"所有子代理用同一个模型"的全局开关。
22
+
23
+ `dsh` 的 `subagent` 工具在**调用那一刻**接受 `provider` / `model` / `reasoning_effort`,并受会话级 `allowedModels` 白名单约束。所以"经理一家、施工一家、审查一家"在 dsh 里是**同一进程内的三次工具调用**,不需要任何外部 CLI。
24
+
25
+ ## 与上游的差异
26
+
27
+ | | opus-manager | 本项目 |
28
+ |---|---|---|
29
+ | 宿主 | Claude Code skill | dsh skill |
30
+ | 工人 | 外部 CLI 进程(pi / cursor-agent / codex / agy) | `subagent` 工具 + 按次传 `provider`/`model` |
31
+ | 首次配置 | 扫描本机装了哪些 CLI、读各自 `--help`、写 `dispatch.sh` / `review.sh` | 读 `allowedModels` 白名单 + `list_subagent_models` 核对,**不写派单脚本** |
32
+ | 工人脱离会话 | `nohup` / `Start-Process` | `run_in_background: true` 的 continuable 子会话 |
33
+ | 只读审查 | 审查命令只开 `read,grep,find,ls` | 首选 `subagent_readonly` 物理只读(运行时只有 `read`/`grep`/`glob`);会话里没有该工具时降级为审查提示词写明「不改文件,只跑只读命令」+ Lead 审查前后各看一次 `git status` |
34
+ | 上下文隔离 | 靠独立进程 | 靠独立 Session(子会话工作不进父对话) |
35
+ | 省钱规则 | 一张单一个新会话 | 同上,**外加**:同模型继续干用 `subagent_fork`(保住前缀 KV cache),只有必须换模型才用 `subagent`;子会话继承部署 persona,工人纪律不用按次重述 |
36
+ | 无人托管 | `queue.md` + 后台等待 | 同上,可叠 dsh 的 `goal` |
37
+
38
+ 工单/回执模板、`_tickets/` 目录契约、验收与核实流程**保持一致**——这部分和宿主无关,是上游最有价值的东西。
39
+
40
+ ## 安装
41
+
42
+ 从装包到派出第一张工单 ≤ 10 分钟,0–6 步照做即可。逐步细节与故障速查见详版 [docs/install-flow.md](docs/install-flow.md)。
43
+
44
+ 0. **dsh 桌面版**,已配好 ≥2 家厂商的 LLM 路由(API key 各家的)。
45
+ 1. **`allowedModels` 白名单**(`@deepseek-ai/dsh-tool-subagent/model-selection-settings`):把允许子任务使用的 `provider/model` 列进去,**至少两家不同厂商**(异族审查的硬要求)。改完白名单要开新会话——它是会话快照,详见[前置条件](#前置条件)。
46
+ 2. **装包**:dsh 插件页安装 `deepseek-foreman`,安装即生效的**四件套**——
47
+ - 挂载 `pick_route`(路由裁决:高峰时段锁、视觉、输出上限、异族审查四条硬约束,见下文「插件」一节);
48
+ - 挂载 `subagent_readonly`(只读审查实例:经它派出的子会话在运行时只有 `read` / `grep` / `glob` 三个读工具);
49
+ - 自动把工单 skill 装进 `~/.dsh/skills/deepseek-foreman`:先建软链(改仓库即生效),软链被系统拒绝(如 Windows 权限)自动退化为递归拷贝;已装过(软链或目录)一律不动,不会覆盖;两步都失败也不抛错、不影响 dsh 启动,失败原因记进 `setup`(见第 5 步);插件配置 `installSkill: false` 可关闭自动安装;
50
+ - 发现没有角色表 → 自动在 `~/.dsh/foreman.roles.yml` 铺一份带中文注释的模板(内容就是包里的 [roles.example.yml](roles.example.yml)),插件进入「未配置」引导态——不报错、不影响 dsh 启动。
51
+
52
+ dsh 的 `skill-filesystem` 默认扫 `~/.dsh/skills`(`user-dsh` 根)和 `~/.agents/skills`(`user-agents` 根)。装在这里只给 dsh 用,不污染四工具共享的 `~/.agents/skills`。
53
+ 3. **开新会话**:让白名单快照覆盖到新装的实例。
54
+ 4. **编辑 `~/.dsh/foreman.roles.yml`**:把模板里「组合 A」(单一厂商全家桶)或「组合 B」(多厂商混合)其中一组的注释解开(**只解一组**),`provider` / `model` 换成第 1 步白名单里已有的路由。**保存即生效**——`pick_route` 每次调用查 mtime 热更新,不用重启;字段逐条有注释,详见下文「配置角色表」一节。
55
+ 5. **自检**:对 dsh 说「**调 pick_route 看看 setup**」。`pick_route` 不带 role 即自检模式,返回的 `setup` 一眼看到还缺什么:
56
+
57
+ | 字段 | 内容 |
58
+ |---|---|
59
+ | `skill` | skill 安装结果:`linked` / `copied` / `exists` / `disabled` / `failed: ...` |
60
+ | `rolesFile` | 角色表路径、状态(`ok` / `unconfigured` / `error`)、角色数与错误摘要 |
61
+ | `allowlist` | 扫各 profile 的 `cordis.patch.yml` 拿到的 `allowedModels` 白名单,与角色表对账;`unmatchedRoles` 列出不在白名单的角色 |
62
+ | `hints` | 有问题时的一句人话指引(如「角色 daily-code 的路由不在 allowedModels,把它加进白名单后开新会话」) |
63
+
64
+ 6. **走工单**:对 dsh 说「**走工单:把 xxx 项目里的 yyy 做了**」。之后 SOP 自动运转:写工单 → `pick_route` 选路由 → `subagent` 派单 → Lead 重跑验收 → `subagent_readonly` 派异族只读审查 → 逐条核实 → 收尾。用户不在时说「我走了你接着干」进入无人托管——九个环节与队列规矩见 [SKILL.md](skill/deepseek-foreman/SKILL.md) 的「九、无人托管」;派单后出岔子按 [docs/install-flow.md](docs/install-flow.md) 的「故障速查」查。
65
+
66
+ ## 配置角色表(`~/.dsh/foreman.roles.yml`)
67
+
68
+ 角色表不在 cordis config 里,而在一个外部 YAML 文件,默认 `~/.dsh/foreman.roles.yml`(可用插件的 `rolesFile` 字段换位置)。
69
+
70
+ 装包时若发现该文件不存在,插件会**自动铺一份带中文注释的模板**(内容就是包里的 [roles.example.yml](roles.example.yml))并进入「未配置」引导态——`pick_route` 全部返回 `ok:false`,reason 写明文件位置和下一步(「按注释填好 provider/model,保存即生效」),插件本身不报错、不影响 dsh 启动。
71
+
72
+ 要改的就是安装第 4 步这一件事:把模板里「组合 A」(单一厂商全家桶)或「组合 B」(多厂商混合)其中一组的注释解开(**只解一组**,同时解开会出现两个顶层 `roles:` 键),再把 `provider` / `model` 换成你自己白名单里已有的路由。改白名单要开新会话(见[前置条件](#前置条件));角色表文件不受这条限制,保存即生效。
73
+
74
+ ```bash
75
+ $EDITOR ~/.dsh/foreman.roles.yml # 填好 provider/model,保存即生效
76
+ ```
77
+
78
+ **改角色表不用重启**:`pick_route` 每次调用都查一次该文件的 mtime,变了就重读 + 重校验。文件写坏也不会把插件带崩:保留上一份可用角色表、本次调用返回 `ok:false` 并说明错在哪,改好保存后下一次调用自动恢复。
79
+
80
+ ## 前置条件
81
+
82
+ `dsh` 桌面版或 CLI,且 `cordis.patch.yml` 里配好:
83
+
84
+ - `@deepseek-ai/dsh-tool-subagent/model-selection-settings` → `enabled: true` + `allowedModels` 至少两条**不同厂商**的路由
85
+ - `standard` preset(其 `subagent` 工具实例带 `modelSelectionSettings: true`)
86
+
87
+ 两者缺一,`subagent` 就不会暴露 `provider`/`model` 入参,本 skill 的派单步骤无法执行。
88
+
89
+ **白名单是会话快照**:`allowedModels` 在**新顶层会话创建时取一次**,之后改它不影响已经在跑的会话——改完白名单必须**开新会话**才生效。角色表文件(`~/.dsh/foreman.roles.yml`)不受这条限制,它每次调用都重读,改完保存即时生效。
90
+
91
+ ## 插件(v1:路由裁决)
92
+
93
+ `src/index.ts` 是 Cordis 插件,注册一个模型可见工具 `pick_route`。它只管 skill 管不了的三件硬约束——这三条写在 markdown 里只能靠模型自觉,写在代码里才能拒:
94
+
95
+ | 约束 | 配置字段 | 拒的时候 |
96
+ |---|---|---|
97
+ | **高峰时段锁** | `peakWindows` / `peakDays` | 工作日 09:00–18:00 派给 deepseek → 拒,并自动给 `fallback` 角色 |
98
+ | **视觉能力** | `vision` | 带截图的活派给纯文本角色 → 拒 |
99
+ | **输出上限** | `maxOutputTokens` | 整篇长产出派给上限 <100K 的角色 → 拒 |
100
+ | **异族审查** | `vendor` + 调用时传 `review_for` | 审查者和写代码者同厂商 → 拒,并列出别家候选 |
101
+
102
+ 派单本身仍走 dsh 原生 `subagent`(它已支持按次 `provider`/`model`/`reasoning_effort`),工单契约仍是文件。**插件不做的事**:不重造任务板、不接管派单、不碰持久化。
103
+
104
+ 角色表的装法见上面「配置角色表」一节。也仍然兼容老接法:在 cordis config 里直接写 `roles: [...]`,非空时优先,`rolesFile` 被忽略(写法见 [example.cordis.yml](example.cordis.yml);出厂 bundle 的 [cordis.patch.yml](cordis.patch.yml) 里不再带真实角色表)。
105
+
106
+ 本包在 bundle patch 里还挂载了 **`subagent_readonly`**(只读审查实例):经这个实例派出的子会话在**运行时**只有 `read` / `grep` / `glob` 三个读工具——写工具从提示里消失,执行也会被拒,只读审查从「提示词自觉」升级为运行时强制(原生 `subagent` 本身仍没有只读过滤参数,见上表)。只读审查的子会话跑**会话默认路由(通常=经理位模型)**——异族审查覆盖非经理模型写的活(bundle 层不能开按次换模型:standing 挂载需要 preset scope,见 dsh-tool-subagent 源码;k3 自己写的活仍是已知空洞);委派工具(`subagent` / `subagent_fork` / `subagent_readonly` / `workflow`)已被 `toolFilter.deny` 堵住,只读子会话派不出不受限子代理。
107
+
108
+ 注意:这个工具**能否出现在会话里取决于 dsh 的 preset 层**,本包只保证 bundle patch 这一层写对。若会话里没有 `subagent_readonly`,就按 [SKILL.md](skill/deepseek-foreman/SKILL.md) 的降级方案执行——审查提示词写明「不要改任何文件,只跑只读命令」,Lead 在审查前后各看一次 `git status` 核实。
109
+
110
+ ```bash
111
+ npm install && npm run build && node test/smoke.mjs # 110 项自检
112
+ ```
113
+
114
+ `Lead` 角色应与 `agent-default-model`(会话实际跑的模型)一致,否则"大脑"名不副实。
115
+
116
+ ## 踩过的坑:bundle patch 必须用 `insert:`
117
+
118
+ bundle 的 `cordis.patch.yml` 里**新增插件行要包在 `insert:` 下**:
119
+
120
+ ```yaml
121
+ - insert:
122
+ - id: foreman
123
+ name: deepseek-foreman
124
+ config: { ... }
125
+ ```
126
+
127
+ 写成裸条目 `- id: ... / name: ...` 会被解释为**按 id 覆盖已存在的行**;组合里没有这一行时**静默不生效**——不报错、不告警,插件页显示「这个插件包不包含任何组件」,模型侧 `NO_TOOL`。官方规范见 [Package and install a plugin](https://github.com/deepseek-ai/deepseek-harness/blob/main/docs/user/develop/basic/publish.md)。
128
+
129
+ 另外:**手改 bundle 的 patch 文件不会通知运行中的 Host**(管理器之外的改动"announces nothing")。改完要在插件页把开关关再开,或重启 app,才会重新应用这一层。
130
+
131
+ ## 状态
132
+
133
+ 已装进 dsh 桌面版 0.2.0-rc.2(Intel iMac)并 live 验证:冷启动正常,`pick_route` 可被模型调用,视觉约束会真的拦截并给 fallback;`subagent_readonly` 最终态同样 live 实测通过(只读工具集生效、委派工具被拦死)。
134
+
135
+ 工单 SOP(`skill/`)已在真实任务上跑完整轮冲刺:2026-10-01 一晚把 T001–T207(含修复单)全部走完,`_tickets/` / `_receipts/` 契约在本项目立了起来,「派单 → 施工 → 异族只读审查 → 逐条核实 → 修复回单」整条链路闭环,自检从 20 项一路长到 110 项。
136
+
137
+ 实证记录见 [docs/dogfooding.md](docs/dogfooding.md)。
138
+
139
+ ## 许可
140
+
141
+ MIT。见 [LICENSE](LICENSE)(含上游与本项目两份版权声明)。
@@ -0,0 +1,39 @@
1
+ # deepseek-foreman bundle patch — 安装本包即挂载 pick_route 工具。
2
+ # 关键:bundle 层新增插件行必须包在 insert: 下;裸条目是按 id 覆盖已有行,匹配不到会静默失效。
3
+ # 用户 profile 的 cordis.patch.yml 里同 id 条目可整体覆盖本行的 config(roles 或 rolesFile 都行)。
4
+ - insert:
5
+ - id: foreman
6
+ name: deepseek-foreman
7
+ config:
8
+ # 角色表已外置成文件:插件每次执行 pick_route 前查该文件的 mtime,改完保存即生效,不用重启。
9
+ # 这里留空(config 里不写 rolesFile)就用默认位置 ~/.dsh/foreman.roles.yml。
10
+ # 该文件不存在时,插件会自动铺一份带中文注释的模板,并进入「未配置」引导态:
11
+ # pick_route 全部返回 ok:false,reason 写明文件位置和下一步(按注释填好 provider/model,保存即生效)。
12
+ # 要把角色表放到别处,就把下一行的注释解开、换成你的路径(开头写 ~/ 会展开成家目录):
13
+ # rolesFile: ~/.dsh/foreman.roles.yml
14
+ #
15
+ # 仍然兼容老接法:也可以直接在这里写 roles: [...],非空时优先,rolesFile 被忽略。
16
+ # 审查派单用 subagent_readonly:经这个实例派出的子会话只有读工具(read/grep/glob),
17
+ # 写工具从提示里消失、执行也会被拒——只读审查从「提示词自觉」变成运行时强制。
18
+ # toolFilter.deny 是为了堵委派绕过:实测委派工具(subagent 等)不会被 allow 名单滤掉,
19
+ # 不写 deny 的话只读子会话仍能派出不受限子代理、绕开只读;deny 把委派与 workflow 工具全部挡掉。
20
+ # 能否出现在会话里取决于 dsh preset 层;不存在时按 SKILL.md 的降级方案(提示词 + git 核实)执行。
21
+ - id: tool-subagent-readonly
22
+ name: '@deepseek-ai/dsh-tool-subagent'
23
+ config:
24
+ provider: spawn
25
+ toolName: subagent_readonly
26
+ backgroundMode: continuable
27
+ # bundle 层不能开按次换模型(standing 挂载需要 preset scope,见 dsh-tool-subagent 源码),
28
+ # 子会话跑会话默认路由(=经理位模型),正好覆盖异族审查主场景;k3 自己写的活仍是已知空洞。
29
+ toolFilter:
30
+ allow:
31
+ - read
32
+ - grep
33
+ - glob
34
+ # subagent 不在子组合的全局工具表里,deny 它会触发 restrict() 的 unknown global tool 校验失败;
35
+ # 委派绕过由 maxDepth 兜底(T204 已实测),这里只挡实际存在的 subagent_fork/subagent_readonly/workflow。
36
+ deny:
37
+ - subagent_fork
38
+ - subagent_readonly
39
+ - workflow
package/lib/index.d.ts ADDED
@@ -0,0 +1,55 @@
1
+ /** Route adjudication for ticket dispatch: role -> provider/model, with hard constraints. */
2
+ import type { Context } from '@deepseek-ai/cordis';
3
+ import z from '@deepseek-ai/schemastery';
4
+ /** Cordis plugin name. */
5
+ export declare const name = "foreman";
6
+ /** Services required by this plugin. */
7
+ export declare const inject: string[];
8
+ /** One declared role and the route it maps to. */
9
+ export interface RoleRoute {
10
+ /** Role key the model asks for, e.g. `daily-code`. */
11
+ role: string;
12
+ /** Vendor key for cross-vendor review; two routes of one vendor never review each other. */
13
+ vendor: string;
14
+ /** LLM provider route to hand to the `subagent` tool. */
15
+ provider: string;
16
+ /** Model id interpreted by that provider. */
17
+ model: string;
18
+ /** Adapter-owned reasoning effort; empty follows the model's own default. */
19
+ reasoningEffort: string;
20
+ /** Route may only take image-bearing work when true. */
21
+ vision: boolean;
22
+ /** Declared output-token ceiling; 0 means undeclared and skips the check. */
23
+ maxOutputTokens: number;
24
+ /** Expensive-hours windows as `HH:MM-HH:MM`; empty disables the peak check. */
25
+ peakWindows: string[];
26
+ /** Weekdays the windows apply to, ISO numbering (1 = Monday). Empty means every day. */
27
+ peakDays: number[];
28
+ /** `YYYY-MM-DD` dates treated as off-peak all day even on a weekday. */
29
+ holidays: string[];
30
+ /** Role to suggest when this one is refused. */
31
+ fallback: string;
32
+ /** Model-facing note: what this role is for and what to watch for. */
33
+ note: string;
34
+ }
35
+ /** Plugin configuration: the inline role table plus the external role file. */
36
+ export interface Config {
37
+ /** Inline role table. Non-empty wins over `rolesFile`, for callers that configure in cordis. */
38
+ roles: RoleRoute[];
39
+ /** External role file; a leading `~/` expands to the home directory. Empty means `<home>/.dsh/foreman.roles.yml`. */
40
+ rolesFile: string;
41
+ /** Install the packaged skill into `<home>/.dsh/skills` when the plugin loads. Default true. */
42
+ installSkill: boolean;
43
+ }
44
+ /** Loader schema for the role table. */
45
+ export declare const Config: z<Config>;
46
+ /** One resolved role route as the model sees it. */
47
+ export interface Route {
48
+ role: string;
49
+ provider: string;
50
+ model: string;
51
+ reasoning_effort?: string;
52
+ note?: string;
53
+ }
54
+ /** Register the role-to-route adjudication tool. */
55
+ export declare function apply(ctx: Context, config: Config): void;