cowork-flow 0.0.50 → 0.0.51

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 (27) hide show
  1. package/README.md +23 -3
  2. package/package.json +2 -1
  3. package/presets/dsh/agent.cordis.yml +206 -0
  4. package/presets/dsh/plugins/workflow-state.js +248 -0
  5. package/presets/dsh/preset.yml +2 -0
  6. package/src/cli.js +6 -0
  7. package/src/commands/install-dsh-preset.js +72 -0
  8. package/template/.cowork-flow/.version +1 -1
  9. package/template/.cowork-flow/adapters/dsh/adapter.yaml +30 -0
  10. package/template/.cowork-flow/scripts/adapters/cli/batch_mode.py +7 -0
  11. package/template/.cowork-flow/scripts/adapters/cli/task_navigation.py +20 -2
  12. package/template/.cowork-flow/scripts/adapters/cli/task_next_runner.py +35 -3
  13. package/template/.cowork-flow/scripts/adapters/cli/task_parser.py +6 -3
  14. package/template/.cowork-flow/scripts/infra/paths.py +9 -1
  15. package/template/.cowork-flow/scripts/infra/process.py +32 -0
  16. package/template/.cowork-flow/scripts/run.py +2 -8
  17. package/template/.cowork-flow/scripts/runtime/session_state.py +31 -3
  18. package/template/.cowork-flow/scripts/services/plan_binding.py +21 -0
  19. package/template/.cowork-flow/scripts/services/task_archive.py +45 -0
  20. package/template/.cowork-flow/scripts/services/task_creation.py +2 -5
  21. package/template/.cowork-flow/scripts/services/workflow_runtime.py +2 -11
  22. package/template/.cowork-flow/spec/contracts/plan-binding.md +17 -0
  23. package/template/.cowork-flow/spec/runtime/host-assets.json +45 -0
  24. package/template/.cowork-flow/spec/schemas/host-assets.schema.json +2 -1
  25. package/template/.dsh/README.md +20 -0
  26. package/template/.zcode/.zcode-plugin/plugin.json +1 -1
  27. package/template/skills/brainstorming/manifest.json +1 -1
package/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
  |---|---|
13
13
  | 任务流程 | `task next --json` 给出下一步 action,`task next --run` 只执行当前 action。 |
14
14
  | 运行健康 | `doctor` 诊断 runtime、host assets、Skill replica 和任务 hygiene,不推进生命周期。 |
15
- | Host 分发 | Host Asset Manifest 驱动 Codex / OpenCode / Claude Code / ZCode 资产和 obsolete 清理。 |
15
+ | Host 分发 | Host Asset Manifest 驱动 Codex / OpenCode / Claude Code / ZCode / DeepSeek Harness 资产和 obsolete 清理。 |
16
16
  | 批处理与讨论 | Batch 发布 Host action;Party Mode 只输出 advisory final facts。 |
17
17
  | 发布准备 | `release:check`、`CHANGELOG.md`、`pack:check` 固定发布前证据。 |
18
18
 
@@ -58,11 +58,14 @@ cowork-flow update --dry-run
58
58
  # 安装 ZCode 插件(可选)
59
59
  cowork-flow install-zcode-plugin
60
60
 
61
+ # 安装 DSH 预设(可选)
62
+ cowork-flow install-dsh-preset
63
+
61
64
  # 维护者发布前检查
62
65
  npm run release:check
63
66
  ```
64
67
 
65
- 平台选项:`codex` / `opencode` / `claude-code` / `all`(逗号分隔)
68
+ 平台选项:`codex` / `opencode` / `claude-code` / `dsh` / `all`(逗号分隔)
66
69
 
67
70
  ## 仓库结构
68
71
 
@@ -74,6 +77,7 @@ template/
74
77
  ├── .codex/ # Codex agents / hooks / config
75
78
  ├── .claude/ # Claude Code settings / agents / hooks
76
79
  ├── .opencode/ # OpenCode agents / commands / plugins
80
+ ├── .dsh/ # DeepSeek Harness 标记(sync 检测 + 说明)
77
81
  ├── .zcode/ # ⭐ ZCode 插件(hooks + skills + agents + scaffold instructions)
78
82
  └── .cowork-flow/
79
83
  ├── config.yaml # 项目配置
@@ -100,6 +104,7 @@ Skills 维护在 `template/skills/` 唯一源码,`init` / `sync` 时按目录
100
104
  | 平台 | 目标目录 |
101
105
  |---|---|
102
106
  | `codex` / `opencode` | `.agents/skills/` |
107
+ | `dsh` | `.agents/skills/` |
103
108
  | `claude-code` | `.claude/skills/` |
104
109
 
105
110
  分发动作:`adversarial-review`、`agent-dispatch`、`batch-execution`、`brainstorming`、`cowork-flow`、`cowork-flow-maintenance`、`decision-audit`、`failure-analysis`、`game-design`、`party-mode`、`python-runtime-design`、`runtime-health`、`spec-sync`、`task-planning`、`task-review`、`test-first`
@@ -112,13 +117,14 @@ Skills 维护在 `template/skills/` 唯一源码,`init` / `sync` 时按目录
112
117
  | `sync <path> [--dry-run]` | 同步已初始化项目的模板和技能 |
113
118
  | `source-refresh [path] [--dry-run]` | 维护者刷新 source checkout 的 ignored live runtime 与 Host Skill replica |
114
119
  | `install-zcode-plugin` | 安装 ZCode 插件到全局缓存 |
120
+ | `install-dsh-preset` | 安装 DSH agent 预设到 `~/.dsh/.agent-presets/cowork-flow/` |
115
121
  | `update [--dry-run]` | 升级 CLI 本身 |
116
122
 
117
123
  ### init 选项
118
124
 
119
125
  | 选项 | 说明 |
120
126
  |---|---|
121
- | `--platform <p>` | 平台:`codex` / `opencode` / `claude-code` / `all` |
127
+ | `--platform <p>` | 平台:`codex` / `opencode` / `claude-code` / `dsh` / `all` |
122
128
  | `--developer <n>` | 开发者名称 |
123
129
  | `--force` | 覆盖已有文件 |
124
130
  | `--dry-run` | 预览不写入 |
@@ -169,6 +175,20 @@ ZCode 插件只安装 hook、skills、agents 和轻量说明文件;`.cowork-fl
169
175
  - `cowork-check` — 绑定 runtime context 后做独立检查
170
176
  - `cowork-research` — 绑定 runtime context 后做只读调研
171
177
 
178
+ ## DSH 预设
179
+
180
+ ```bash
181
+ cowork-flow install-dsh-preset # 安装
182
+ cowork-flow install-dsh-preset --force # 覆盖已安装
183
+ cowork-flow install-dsh-preset --dry-run # 预览不写入
184
+ ```
185
+
186
+ 安装到 `~/.dsh/.agent-presets/cowork-flow/`(`DSH_HOME` 存在时以其为准):`agent.cordis.yml` + `preset.yml` + 全部流程技能。安装后在 DeepSeek Harness 中新建会话并选择 **Cowork Flow** 预设即可使用:persona 携带流程门禁规则,技能目录随预设挂载,不依赖项目本地副本。
187
+
188
+ 预设组合是部署 `standard` 预设的拷贝 + 最小改动(persona 流程规则、`skill-filesystem` 指向预设自带 `skills/`);`cowork-flow init --platform dsh` 仍负责项目级资产(`AGENTS.md`、`.agents/skills/`、`.dsh/` 标记)。
189
+
190
+ 预设内置 **workflow-state hook**(`plugins/workflow-state.js`):DSH 原生等效于 Codex/Claude hook,向系统提示末尾注入与其它宿主同构的 `<workflow-state>` 块,每条用户消息刷新一次,并在生命周期命令(`task`/`subagent`/`resume`)执行完成后立即轮内刷新(替换语义,不累积)。项目无 `.cowork-flow` 根、缺少 Python 或设 `COWORK_FLOW_HOOKS=0` / `COWORK_FLOW_DISABLE_HOOKS=1` 时静默降级,由 AGENTS.md 门禁的运行导航器兜底。
191
+
172
192
  ## 任务流程
173
193
 
174
194
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cowork-flow",
3
- "version": "0.0.50",
3
+ "version": "0.0.51",
4
4
  "description": "CLI for installing and maintaining cowork-flow collaboration templates.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -11,6 +11,7 @@
11
11
  "src",
12
12
  "template",
13
13
  "template/.zcode",
14
+ "presets",
14
15
  "README.md",
15
16
  "CHANGELOG.md",
16
17
  "!template/**/__pycache__/**",
@@ -0,0 +1,206 @@
1
+ # The `cowork-flow` agent preset: the standard coding agent plus the
2
+ # cowork-flow workflow framework (task gate, planning, fixed subagents, skills).
3
+ #
4
+ # This file is a copy of the deployment's `standard` preset with two changes:
5
+ # the persona carries cowork-flow operating rules, and `skill-filesystem`
6
+ # points at this preset's own `skills/` directory (installed alongside by
7
+ # `cowork-flow install-dsh-preset`).
8
+ #
9
+ # The `dsh` host adapter lives in the cowork-flow repository
10
+ # (template/.cowork-flow/adapters/dsh/adapter.yaml) and declares this preset's
11
+ # capability model; this file is the DSH-side composition it describes.
12
+
13
+ # ── identity ────────────────────────────────────────────────────────────────
14
+
15
+ - id: persona
16
+ name: '@deepseek-ai/dsh-persona'
17
+ config:
18
+ text: >-
19
+ You are a coding agent powered by the {{model}} model. Your working directory is {{cwd}}.
20
+
21
+ 你运行在 cowork-flow 流程框架上。项目已接入时,工作区 AGENTS.md 的"0.1 编码前强制门禁"生效:编辑文件、变更代码、派发子代理之前,先确定当前工作流状态(系统提示含 <workflow-state> 块则按其路由,否则运行 ./.cowork-flow/run task next),再决定放行、阻断还是路由;no_task / planning / completed 状态阻断写代码。只读问答、纯查询命令、用户明确说"跳过流程"时豁免。
22
+
23
+ 子代理身份由 runtime context 绑定决定,不由 prompt 推断:固定子代理(cowork-implement / cowork-check / cowork-research)按 agent-dispatch 技能执行 subagent bind / close,未绑定时禁止 start / resume / archive / commit / 派发 agent。
24
+
25
+ 项目流程以 task next、runtime gate、Skill command manifests 和 .cowork-flow/spec/ 硬契约为准;不要维护第二套流程文档或 Skill 注册控制面。文档语言中文;提交策略允许 AI 提交。
26
+
27
+ - id: agent-instructions
28
+ name: '@deepseek-ai/dsh-agent-instructions'
29
+ config:
30
+ maxBytes: 65536
31
+
32
+ # ── shell ───────────────────────────────────────────────────────────────────
33
+
34
+ - id: tool-bash
35
+ name: '@deepseek-ai/dsh-tool-bash'
36
+ disabled: !!js process.platform === 'win32'
37
+
38
+ - id: tool-pwsh
39
+ name: '@deepseek-ai/dsh-tool-pwsh'
40
+ disabled: !!js process.platform !== 'win32'
41
+
42
+ # ── filesystem ──────────────────────────────────────────────────────────────
43
+
44
+ - id: tool-fs
45
+ name: '@deepseek-ai/dsh-tool-fs'
46
+
47
+ - id: tool-fs-search
48
+ name: '@deepseek-ai/dsh-tool-fs-search'
49
+ config:
50
+ sampleOverCapGlobResults: false
51
+
52
+ # ── background jobs ────────────────────────────────────────────────────────
53
+
54
+ - id: tool-jobs
55
+ name: '@deepseek-ai/dsh-tool-jobs'
56
+
57
+ # ── skills ──────────────────────────────────────────────────────────────────
58
+
59
+ # The skill REGISTRY lives in the host composition and is layered per scope:
60
+ # these rows register into THIS preset's layer of it. `skill-filesystem`
61
+ # contributes the cowork-flow skills shipped with this preset (`customSkillDirs`,
62
+ # resolved against the preset's own directory via `baseUrl`) on top of the
63
+ # local-root discovery for the current workspace.
64
+ - id: skill-filesystem
65
+ name: '@deepseek-ai/dsh-skill-filesystem'
66
+ config:
67
+ customSkillDirs:
68
+ - !!js "process.getBuiltinModule('node:url').fileURLToPath(new URL('skills/', baseUrl))"
69
+
70
+ - id: tool-skill
71
+ name: '@deepseek-ai/dsh-tool-skill'
72
+
73
+ # ── workflow state hook ────────────────────────────────────────────────────
74
+ # DSH-native equivalent of the Codex/Claude hooks: registers a system-prompt
75
+ # section (rendered last) that carries the `<workflow-state>` block, refreshed
76
+ # per user message. Consumes the host `systemPrompt` registry and publishes
77
+ # nothing, so it sits loose like the tool rows above.
78
+ - id: workflow-state-hook
79
+ name: './plugins/workflow-state.js'
80
+
81
+ # ── goals ───────────────────────────────────────────────────────────────────
82
+
83
+ - id: tool-goal
84
+ name: '@deepseek-ai/dsh-tool-goal'
85
+
86
+ # ── plan mode ───────────────────────────────────────────────────────────────
87
+
88
+ - id: planning
89
+ name: cordis:group
90
+ group: true
91
+ isolate:
92
+ planMode: true
93
+ config:
94
+ - id: plan-mode
95
+ name: '@deepseek-ai/dsh-plan-mode'
96
+ config:
97
+ section: |
98
+ You are in plan mode. Stay in plan mode until exit_plan_mode succeeds or the user switches the session mode. Imperative language to implement changes means plan the implementation, not execute it. A user's conversational agreement — including an answer confirming something you asked — approves nothing and does not end plan mode; fold the confirmed decision into the plan and submit it through exit_plan_mode.
99
+
100
+ Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery.
101
+
102
+ The tool catalog stays the same across modes for request-cache stability. These plan-mode rules override any later tool description or guidance that suggests using mutation tools; those tools remain listed to keep the tool catalog unchanged. Do not use todo_write to track this planning phase: it tracks implementation after an approved plan, while the plan itself belongs in exit_plan_mode.
103
+
104
+ Resolve discoverable facts by inspection. Use ask_user_question only for user-owned choices or material ambiguity that inspection cannot answer. Do not ask the user where code lives or how current behavior works when you can find out.
105
+
106
+ Make the plan decision-complete: state the goal and success criteria; group implementation changes by subsystem; identify public API, schema, and data-flow changes; cover edge cases, failure modes, tests, acceptance criteria, and explicit assumptions. Keep it concise enough to review but detailed enough that another engineer can implement it without making design decisions.
107
+
108
+ When ready, call exit_plan_mode with the complete plan markdown, starting with a # title. Make exit_plan_mode the only and final tool call in that assistant response: it presents the plan for approval, and implementation begins only in a later step after approval. Do not paste the final plan as a plain reply or ask "should I proceed?" through prose or ask_user_question. If review rejects it, incorporate the feedback and present again. If the review channel is unavailable or aborted, stay in plan mode and ask the user to switch modes manually; do not proceed with implementation.
109
+
110
+ # ── compaction ──────────────────────────────────────────────────────────────
111
+
112
+ - id: compaction
113
+ name: cordis:group
114
+ group: true
115
+ isolate:
116
+ compaction: true
117
+ toolResultPruner: true
118
+ config:
119
+ - id: compaction-basic
120
+ name: '@deepseek-ai/dsh-compaction-basic'
121
+
122
+ - id: command-compact
123
+ name: '@deepseek-ai/dsh-command-compact'
124
+
125
+ - id: tool-result-pruner
126
+ name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
127
+ config:
128
+ thresholdChars: 8192
129
+ headChars: 4096
130
+ tailChars: 1024
131
+
132
+ # ── delegation and workflows ────────────────────────────────────────────────
133
+
134
+ - id: delegation
135
+ name: cordis:group
136
+ group: true
137
+ isolate:
138
+ workflowEngine: true
139
+ config:
140
+ - id: tool-subagent-control
141
+ name: '@deepseek-ai/dsh-tool-subagent-control'
142
+
143
+ - id: tool-subagent-list-agents
144
+ name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
145
+
146
+ - id: tool-subagent
147
+ name: '@deepseek-ai/dsh-tool-subagent'
148
+ config:
149
+ provider: spawn
150
+ toolName: subagent
151
+ backgroundMode: continuable
152
+
153
+ - id: tool-subagent-fork
154
+ name: '@deepseek-ai/dsh-tool-subagent'
155
+ config:
156
+ provider: fork
157
+ toolName: subagent_fork
158
+ backgroundMode: continuable
159
+
160
+ - id: tool-subagent-codex
161
+ name: '@deepseek-ai/dsh-tool-subagent'
162
+ disabled: true
163
+ config:
164
+ provider: codex
165
+ toolName: subagent_codex
166
+ enableRunInBackground: false
167
+ maxDepth: provider-managed
168
+
169
+ - id: tool-subagent-claude-code
170
+ name: '@deepseek-ai/dsh-tool-subagent'
171
+ disabled: true
172
+ config:
173
+ provider: claude-code
174
+ toolName: subagent_claude_code
175
+ enableRunInBackground: false
176
+ maxDepth: provider-managed
177
+
178
+ - id: workflow-worker-thread
179
+ name: '@deepseek-ai/dsh-workflow-worker-thread'
180
+ config:
181
+ provider: spawn
182
+
183
+ - id: tool-workflow
184
+ name: '@deepseek-ai/dsh-tool-workflow'
185
+
186
+ - id: tool-ralph
187
+ name: '@deepseek-ai/dsh-tool-ralph'
188
+ config:
189
+ subagentProvider: spawn
190
+ maxRounds: 64
191
+
192
+ # ── remaining model-facing rows ─────────────────────────────────────────────
193
+
194
+ - id: tool-ask-user
195
+ name: '@deepseek-ai/dsh-tool-ask-user'
196
+
197
+ - id: tool-todo
198
+ name: '@deepseek-ai/dsh-tool-todo'
199
+ config:
200
+ allowParallelInProgress: true
201
+
202
+ - id: tool-web
203
+ name: '@deepseek-ai/dsh-tool-web'
204
+ config:
205
+ fetch: false
206
+ searchTimeoutMs: 60000
@@ -0,0 +1,248 @@
1
+ // cowork-flow DSH workflow-state hook.
2
+ //
3
+ // DSH-native equivalent of the Codex/Claude hooks
4
+ // (template/.codex/hooks/inject-workflow-state.py and its Claude sibling):
5
+ // this preset plugin registers ONE system-prompt section, rendered last
6
+ // (order 1000, after identity/persona/tool guidance), whose text is the
7
+ // `<workflow-state>` block produced by the SAME Python protocol the other
8
+ // hosts use (adapters/host/workflow_state_hook.py), so the injected content
9
+ // is structurally identical across hosts.
10
+ //
11
+ // The block is refreshed per user message (`agent/session-start` warms it,
12
+ // `agent/inbox/claimed` refreshes it) and after lifecycle commands settle
13
+ // (`tools/result`), then cached per agent; each prompt assembly re-renders
14
+ // the current cached value in place — replace semantics, no accumulation
15
+ // across turns or steps. Between refreshes the text is byte-stable, so the
16
+ // static prompt prefix stays cacheable and the dynamic cost is confined to
17
+ // the trailing block.
18
+ //
19
+ // Degradation is silent: no `.cowork-flow` root, a missing Python, a broken
20
+ // runtime, or the `COWORK_FLOW_HOOKS=0` / `COWORK_FLOW_DISABLE_HOOKS=1`
21
+ // switches all yield an empty section, and the workspace AGENTS.md gate
22
+ // falls back to running the navigator manually.
23
+
24
+ import { execFile } from 'node:child_process';
25
+
26
+ export const name = 'workflow-state-hook';
27
+ export const inject = ['systemPrompt'];
28
+
29
+ const SECTION_NAME = 'cowork-flow-workflow-state';
30
+ const SECTION_ORDER = 1000;
31
+ const REFRESH_TIMEOUT_MS = 15000;
32
+ const EXEC_OPTIONS = {
33
+ timeout: REFRESH_TIMEOUT_MS,
34
+ windowsHide: true,
35
+ maxBuffer: 1024 * 1024,
36
+ };
37
+ const NO_ROOT_MARKER = '__COWORK_FLOW_NO_ROOT__';
38
+
39
+ // Python 3.9+ protocol reused from the host hook adapters. It resolves the
40
+ // `.cowork-flow` root from the given cwd, loads the shared
41
+ // workflow_state_hook module, and prints the full hook context block.
42
+ const PYTHON_PROTOCOL = `\
43
+ import sys
44
+ from pathlib import Path
45
+
46
+ cwd = sys.argv[1]
47
+ current = Path(cwd).resolve()
48
+ root = None
49
+ while True:
50
+ if (current / ".cowork-flow").is_dir():
51
+ root = current
52
+ break
53
+ if current == current.parent:
54
+ break
55
+ current = current.parent
56
+ if root is None:
57
+ print(${JSON.stringify(NO_ROOT_MARKER)})
58
+ raise SystemExit(0)
59
+ scripts_dir = root / ".cowork-flow" / "scripts"
60
+ sys.path.insert(0, str(scripts_dir))
61
+ from adapters.host.workflow_state_hook import build_hook_context
62
+ print(build_hook_context(
63
+ root,
64
+ {"cwd": cwd},
65
+ host="dsh",
66
+ adapter="dsh.preset.systemPrompt",
67
+ preamble=(
68
+ "<dsh-runtime>\\n"
69
+ "injection: agent-preset workflow-state section, refreshed per user message\\n"
70
+ "runtime_context_identity: formal subagent sessions bind before workflow-state injection\\n"
71
+ "</dsh-runtime>",
72
+ ),
73
+ ))
74
+ `;
75
+
76
+ // The first interpreter that ran the protocol successfully; a missing one is
77
+ // dropped so a later refresh can rediscover (e.g. after a PATH change).
78
+ let workingPython = null;
79
+
80
+ // Cowork-flow lifecycle commands executed through any tool. Matching the
81
+ // command text (not the tool name) keeps the trigger stable across tool
82
+ // surface changes.
83
+ const LIFECYCLE_COMMAND = /\.cowork-flow[\\/]run(?:\.cmd)?(?:\s+[^\s"']*)?\s+(task|subagent|resume)\b/;
84
+
85
+
86
+ /**
87
+ * True when a tool argument value embeds a cowork-flow lifecycle command.
88
+ * Accepts strings and JSON-serializable structures; never throws.
89
+ */
90
+ export function isLifecycleCommand(value) {
91
+ try {
92
+ const text = typeof value === 'string' ? value : JSON.stringify(value);
93
+ return typeof text === 'string' && LIFECYCLE_COMMAND.test(text);
94
+ } catch {
95
+ return false;
96
+ }
97
+ }
98
+
99
+
100
+ function hooksDisabled() {
101
+ return (
102
+ process.env.COWORK_FLOW_HOOKS === '0'
103
+ || process.env.COWORK_FLOW_DISABLE_HOOKS === '1'
104
+ );
105
+ }
106
+
107
+
108
+ function discoverCandidates() {
109
+ const candidates = [];
110
+ for (const variable of ['COWORK_FLOW_PYTHON', 'PYTHON']) {
111
+ const value = process.env[variable];
112
+ if (value && value.trim()) {
113
+ candidates.push({ command: value.trim(), args: [] });
114
+ }
115
+ }
116
+ candidates.push({ command: 'python3', args: [] });
117
+ candidates.push({ command: 'python', args: [] });
118
+ candidates.push({ command: 'py', args: ['-3'] });
119
+ return candidates;
120
+ }
121
+
122
+
123
+ function tryPython(candidate, cwd) {
124
+ return new Promise((resolve) => {
125
+ execFile(
126
+ candidate.command,
127
+ [...candidate.args, '-c', PYTHON_PROTOCOL, cwd],
128
+ EXEC_OPTIONS,
129
+ (error, stdout) => {
130
+ if (error) {
131
+ // ENOENT = the interpreter does not exist (try the next one).
132
+ // Any other failure means the interpreter ran but the protocol
133
+ // failed — the candidate works, the project runtime does not.
134
+ resolve({ missing: error.code === 'ENOENT', output: '' });
135
+ return;
136
+ }
137
+ resolve({ missing: false, output: stdout.trim() });
138
+ },
139
+ );
140
+ });
141
+ }
142
+
143
+
144
+ function normalize(output) {
145
+ if (!output || output === NO_ROOT_MARKER) {
146
+ return '';
147
+ }
148
+ return output;
149
+ }
150
+
151
+
152
+ /**
153
+ * Test seam: forget the memoized interpreter so the next call rediscovers.
154
+ */
155
+ export function resetWorkingPython() {
156
+ workingPython = null;
157
+ }
158
+
159
+
160
+ /**
161
+ * Produce the `<workflow-state>` hook context block for a workspace cwd.
162
+ * Empty string means "contribute nothing" (no root, hooks disabled, no
163
+ * usable interpreter, or a broken runtime).
164
+ */
165
+ export async function runWorkflowState(cwd) {
166
+ if (hooksDisabled()) {
167
+ return '';
168
+ }
169
+ if (workingPython !== null) {
170
+ const result = await tryPython(workingPython, cwd);
171
+ if (!result.missing) {
172
+ return normalize(result.output);
173
+ }
174
+ workingPython = null;
175
+ }
176
+ for (const candidate of discoverCandidates()) {
177
+ const result = await tryPython(candidate, cwd);
178
+ if (result.missing) {
179
+ continue;
180
+ }
181
+ workingPython = candidate;
182
+ return normalize(result.output);
183
+ }
184
+ return '';
185
+ }
186
+
187
+
188
+ export function apply(ctx) {
189
+ // One cache entry per agent: { text, inflight, queued }. The WeakMap key
190
+ // is the live Agent object the events and the assembly context both carry.
191
+ const states = new WeakMap();
192
+
193
+ const refresh = (agent) => {
194
+ const cwd = agent && agent.cwd;
195
+ if (!cwd) {
196
+ return;
197
+ }
198
+ let entry = states.get(agent);
199
+ if (!entry) {
200
+ entry = { text: '', inflight: false, queued: false };
201
+ states.set(agent, entry);
202
+ }
203
+ if (entry.inflight) {
204
+ // A refresh arrived while one was running (e.g. several lifecycle
205
+ // commands back to back). Re-run once after it settles so the latest
206
+ // on-disk state wins instead of being dropped.
207
+ entry.queued = true;
208
+ return;
209
+ }
210
+ entry.inflight = true;
211
+ runWorkflowState(cwd)
212
+ .then((text) => {
213
+ entry.text = text;
214
+ })
215
+ .catch(() => {
216
+ entry.text = '';
217
+ })
218
+ .finally(() => {
219
+ entry.inflight = false;
220
+ if (entry.queued) {
221
+ entry.queued = false;
222
+ refresh(agent);
223
+ }
224
+ });
225
+ };
226
+
227
+ ctx.on('agent/session-start', (payload) => refresh(payload && payload.agent));
228
+ ctx.on('agent/inbox/claimed', (payload) => refresh(payload && payload.agent));
229
+ // Intra-turn refresh: a lifecycle command just settled, so the next prompt
230
+ // assembly should already see the new state instead of the stale block.
231
+ ctx.on('tools/result', (exec) => {
232
+ const agent = exec && exec.agent;
233
+ if (!agent || !isLifecycleCommand(exec && exec.arguments)) {
234
+ return;
235
+ }
236
+ refresh(agent);
237
+ });
238
+
239
+ ctx.effect(() => ctx.systemPrompt.section({
240
+ name: SECTION_NAME,
241
+ order: SECTION_ORDER,
242
+ text: (assembleContext) => {
243
+ const agent = assembleContext && assembleContext.agent;
244
+ const entry = agent ? states.get(agent) : undefined;
245
+ return entry ? entry.text : '';
246
+ },
247
+ }), 'workflow-state.section()');
248
+ }
@@ -0,0 +1,2 @@
1
+ name: Cowork Flow
2
+ description: 接入 cowork-flow 流程框架的编码 Agent:任务门禁(task next)、计划绑定、runtime-context 固定子代理与 16 个流程技能(brainstorming / task-planning / task-review / agent-dispatch 等)。
package/src/cli.js CHANGED
@@ -3,6 +3,7 @@ import { emitKeypressEvents } from 'node:readline';
3
3
  import { createInterface } from 'node:readline/promises';
4
4
 
5
5
  import { runInit } from './commands/init.js';
6
+ import { runInstallDshPreset } from './commands/install-dsh-preset.js';
6
7
  import { runInstallZCodePlugin } from './commands/install-zcode-plugin.js';
7
8
  import { runSourceRefresh } from './commands/source-refresh.js';
8
9
  import { runSync } from './commands/sync.js';
@@ -14,6 +15,7 @@ const HELP = `cowork-flow
14
15
  Usage:
15
16
  cowork-flow init [target] --platform <codex|opencode|claude-code|all> [--developer <name>] [--dry-run] [--force]
16
17
  cowork-flow install-zcode-plugin [--dry-run] [--force] [--prune-old]
18
+ cowork-flow install-dsh-preset [--dry-run] [--force]
17
19
  cowork-flow update
18
20
  cowork-flow sync [target] [--dry-run] [--force]
19
21
  cowork-flow source-refresh [target] [--dry-run]
@@ -187,6 +189,10 @@ export async function main(argv = process.argv.slice(2), options = {}) {
187
189
  return await runInstallZCodePlugin(args);
188
190
  }
189
191
 
192
+ if (command === 'install-dsh-preset') {
193
+ return await runInstallDshPreset(args);
194
+ }
195
+
190
196
  io.writeErr(`Unknown command: ${command}\n`);
191
197
  return 1;
192
198
  } catch (error) {
@@ -0,0 +1,72 @@
1
+ import { cp, mkdir, rm, access } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+
5
+ import { packageRoot, templateRoot } from '../lib/paths.js';
6
+
7
+ const PRESET_ID = 'cowork-flow';
8
+ const PRESET_SRC = join(packageRoot, 'presets', 'dsh');
9
+ const SKILLS_SRC = join(templateRoot, 'skills');
10
+
11
+
12
+ function parseArgs(args) {
13
+ return {
14
+ dryRun: args.includes('--dry-run'),
15
+ force: args.includes('--force')
16
+ };
17
+ }
18
+
19
+
20
+ async function pathExists(target) {
21
+ try {
22
+ await access(target);
23
+ return true;
24
+ } catch {
25
+ return false;
26
+ }
27
+ }
28
+
29
+
30
+ function getDshPresetRoot() {
31
+ const base = process.env.DSH_HOME || join(homedir(), '.dsh');
32
+ return join(base, '.agent-presets');
33
+ }
34
+
35
+
36
+ export async function runInstallDshPreset(args = []) {
37
+ const { dryRun, force } = parseArgs(args);
38
+
39
+ if (!(await pathExists(PRESET_SRC))) {
40
+ throw new Error(`DSH preset source missing at ${PRESET_SRC}. Reinstall cowork-flow.`);
41
+ }
42
+ if (!(await pathExists(SKILLS_SRC))) {
43
+ throw new Error(`Skills source missing at ${SKILLS_SRC}. Reinstall cowork-flow.`);
44
+ }
45
+
46
+ const destDir = join(getDshPresetRoot(), PRESET_ID);
47
+ const skillsDest = join(destDir, 'skills');
48
+
49
+ if (dryRun) {
50
+ console.log('[dry-run] Would install DSH preset:');
51
+ console.log(` Preset: ${PRESET_SRC} -> ${destDir}`);
52
+ console.log(` Skills: ${SKILLS_SRC} -> ${skillsDest}`);
53
+ return;
54
+ }
55
+
56
+ if (!force && (await pathExists(destDir))) {
57
+ console.log(`cowork-flow DSH preset already installed at ${destDir}`);
58
+ console.log('Use --force to overwrite.');
59
+ return;
60
+ }
61
+
62
+ await mkdir(getDshPresetRoot(), { recursive: true });
63
+ if (await pathExists(destDir)) {
64
+ await rm(destDir, { recursive: true, force: true });
65
+ }
66
+
67
+ await cp(PRESET_SRC, destDir, { recursive: true });
68
+ await cp(SKILLS_SRC, skillsDest, { recursive: true, force: true });
69
+
70
+ console.log(`✓ cowork-flow DSH preset installed to ${destDir}`);
71
+ console.log(' Start a new DSH session and pick the "Cowork Flow" preset.');
72
+ }
@@ -1 +1 @@
1
- 0.0.50
1
+ 0.0.51
@@ -0,0 +1,30 @@
1
+ schemaVersion: 1
2
+ host: dsh
3
+ displayName: DeepSeek Harness
4
+ capabilities:
5
+ dispatchSubagent: native
6
+ freshChildContext: native
7
+ sendFollowup: native
8
+ waitChild: native
9
+ listChildren: native
10
+ cancelChild: native
11
+ stateInjection: external
12
+ backgroundChild: native
13
+ runtimeContextDispatch: native
14
+ runtimeContextBinding: shim
15
+ runtimeContextCleanup: shim
16
+ dispatch:
17
+ primitive: subagent
18
+ skillsPath: .agents/skills
19
+ memoryPath: AGENTS.md
20
+ runtimeContext:
21
+ transport: prompt
22
+ promptKey: cowork_runtime_context_id
23
+ envKey: COWORK_FLOW_RUNTIME_CONTEXT_ID
24
+ metadataKey: cowork_runtime_context_id
25
+ contracts:
26
+ dispatch: RUNTIME_CONTEXT_DISPATCH_V2
27
+ leafExecutor: true
28
+ fallback:
29
+ whenRequiredCapabilityMissing: inline_or_manual
30
+ whenRuntimeContextMissing: fail_closed