cowork-flow 0.0.49 → 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.
- package/README.md +23 -3
- package/package.json +2 -1
- package/presets/dsh/agent.cordis.yml +206 -0
- package/presets/dsh/plugins/workflow-state.js +248 -0
- package/presets/dsh/preset.yml +2 -0
- package/src/cli.js +6 -0
- package/src/commands/install-dsh-preset.js +72 -0
- package/template/.cowork-flow/.version +1 -1
- package/template/.cowork-flow/adapters/dsh/adapter.yaml +30 -0
- package/template/.cowork-flow/scripts/adapters/cli/batch_mode.py +7 -0
- package/template/.cowork-flow/scripts/adapters/cli/task_navigation.py +20 -2
- package/template/.cowork-flow/scripts/adapters/cli/task_next_runner.py +35 -3
- package/template/.cowork-flow/scripts/adapters/cli/task_parser.py +6 -3
- package/template/.cowork-flow/scripts/infra/paths.py +9 -1
- package/template/.cowork-flow/scripts/infra/process.py +32 -0
- package/template/.cowork-flow/scripts/run.py +2 -8
- package/template/.cowork-flow/scripts/runtime/session_state.py +31 -3
- package/template/.cowork-flow/scripts/services/plan_binding.py +21 -0
- package/template/.cowork-flow/scripts/services/task_archive.py +45 -0
- package/template/.cowork-flow/scripts/services/task_creation.py +2 -5
- package/template/.cowork-flow/scripts/services/workflow_runtime.py +2 -11
- package/template/.cowork-flow/spec/contracts/plan-binding.md +17 -0
- package/template/.cowork-flow/spec/runtime/host-assets.json +45 -0
- package/template/.cowork-flow/spec/schemas/host-assets.schema.json +2 -1
- package/template/.dsh/README.md +20 -0
- package/template/.zcode/.zcode-plugin/plugin.json +1 -1
- package/template/skills/brainstorming/manifest.json +1 -1
- package/template/skills/task-review/SKILL.md +4 -3
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.
|
|
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
|
+
}
|
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.
|
|
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
|