cowork-flow 1.9.2 → 2.1.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/CHANGELOG.md +45 -1
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/docs/architecture.md +1 -34
- package/docs/direction.md +4 -3
- package/docs/hosts.md +14 -10
- package/docs/index.md +1 -1
- package/docs/release.md +1 -1
- package/package.json +6 -6
- package/presets/claude-code/.claude-plugin/plugin.json +1 -1
- package/presets/codex/.codex-plugin/plugin.json +1 -1
- package/presets/dsh/plugins/workflow-state.js +72 -24
- package/presets/kimi-code/.kimi-plugin/plugin.json +1 -1
- package/presets/kimi-code/hooks/cowork-flow-inject.mjs +28 -2
- package/presets/opencode/cowork-flow/plugin-core.js +70 -88
- package/presets/opencode/plugins/cowork-flow.js +7 -9
- package/presets/qoder/.qoder-plugin/plugin.json +1 -1
- package/presets/qoder/hooks/hooks.json +1 -1
- package/presets/qoder/hooks/inject-context.mjs +28 -2
- package/presets/zcode/.zcode-plugin/plugin.json +1 -1
- package/presets/zcode/hooks/inject-context.mjs +28 -3
- package/src/commands/host.js +23 -2
- package/src/commands/install-dsh-hook.js +1 -6
- package/src/commands/install-dsh-preset.js +1 -6
- package/src/commands/install-zcode-plugin.js +2 -3
- package/src/commands/registry.js +4 -2
- package/src/lib/asset-plan.js +1 -1
- package/src/lib/dsh-home.js +6 -0
- package/src/lib/plan-applier.js +1 -6
- package/template/.claude/agents/cowork-check.md +1 -1
- package/template/.claude/agents/cowork-implement.md +1 -1
- package/template/.claude/agents/cowork-research.md +1 -1
- package/template/.codex/agents/cowork-check.toml +1 -1
- package/template/.codex/agents/cowork-implement.toml +1 -1
- package/template/.codex/agents/cowork-research.toml +1 -1
- package/template/.codex/agents/default.toml +1 -1
- package/template/.codex/agents/explorer.toml +1 -1
- package/template/.codex/agents/worker.toml +1 -1
- package/template/.cowork-flow/.version +1 -1
- package/template/.cowork-flow/adapters/claude-code/adapter.yaml +1 -1
- package/template/.cowork-flow/adapters/codex/adapter.yaml +1 -1
- package/template/.cowork-flow/adapters/dsh/adapter.yaml +1 -1
- package/template/.cowork-flow/adapters/opencode/adapter.yaml +1 -1
- package/template/.cowork-flow/adapters/qoder/adapter.yaml +1 -1
- package/template/.cowork-flow/run +2 -2
- package/template/.cowork-flow/run.cmd +3 -3
- package/template/.cowork-flow/scripts/adapters/cli/task_lifecycle_commands.py +0 -1
- package/template/.cowork-flow/scripts/adapters/cli/task_parser.py +0 -7
- package/template/.cowork-flow/scripts/adapters/host/claude_code_policy.py +6 -4
- package/template/.cowork-flow/scripts/adapters/host/codex_policy.py +8 -6
- package/template/.cowork-flow/scripts/adapters/host/edit_scope.py +113 -0
- package/template/.cowork-flow/scripts/adapters/host/opencode_policy.py +12 -0
- package/template/.cowork-flow/scripts/adapters/host/qoder_policy.py +7 -5
- package/template/.cowork-flow/scripts/adapters/host/workflow_state_hook.py +0 -91
- package/template/.cowork-flow/scripts/adapters/host/zcode_policy.py +15 -77
- package/template/.cowork-flow/scripts/run.py +0 -2
- package/template/.cowork-flow/scripts/services/context_paths.py +2 -2
- package/template/.cowork-flow/scripts/services/fact_view.py +49 -21
- package/template/.cowork-flow/scripts/services/lifecycle_checks.py +1 -216
- package/template/.cowork-flow/scripts/services/lifecycle_policy.py +0 -5
- package/template/.cowork-flow/scripts/services/task_gate_profiles.py +1 -1
- package/template/.cowork-flow/scripts/services/task_lifecycle.py +14 -31
- package/template/.cowork-flow/spec/backend/error-handling.md +0 -14
- package/template/.cowork-flow/spec/contracts/context-injection.md +53 -26
- package/template/.cowork-flow/spec/contracts/decision-anchor.md +3 -3
- package/template/.cowork-flow/spec/contracts/error-codes.md +1 -3
- package/template/.cowork-flow/spec/contracts/evidence-completion.md +6 -8
- package/template/.cowork-flow/spec/contracts/index.md +0 -1
- package/template/.cowork-flow/spec/contracts/plan-binding.md +3 -2
- package/template/.cowork-flow/spec/contracts/subagent-dispatch.md +6 -21
- package/template/.cowork-flow/spec/contracts/task-adopt.md +1 -1
- package/template/.cowork-flow/spec/runtime/contract-registry.json +0 -13
- package/template/.cowork-flow/spec/runtime/host-assets.json +10 -7
- package/template/.cowork-flow/spec/runtime/scope-rules.json +2 -2
- package/template/.kimi-code/agents/cowork-check.md +1 -1
- package/template/.kimi-code/agents/cowork-implement.md +1 -1
- package/template/.kimi-code/agents/cowork-research.md +1 -1
- package/template/.opencode/agents/cowork-check.md +1 -1
- package/template/.opencode/agents/cowork-implement.md +1 -1
- package/template/.opencode/agents/cowork-research.md +1 -1
- package/template/.opencode/cowork-flow/plugin-core.js +70 -88
- package/template/.opencode/plugins/cowork-flow.js +7 -9
- package/template/AGENTS.md +0 -1
- package/template/skills/agent-dispatch/SKILL.md +0 -15
- package/template/skills/runtime-health/scripts/doctor.py +83 -58
- package/template/skills/spec-sync/SKILL.md +0 -1
- package/template/skills/task-planning/SKILL.md +1 -1
- package/template/skills/task-review/SKILL.md +3 -3
- package/template/.cowork-flow/scripts/adapters/cli/spec_check.py +0 -178
- package/template/.cowork-flow/scripts/services/spec_check.py +0 -573
- package/template/.cowork-flow/spec/contracts/spec-checks.md +0 -281
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// Transport shim behind the opencode plugin. Every workflow fact
|
|
2
2
|
// (contract digest, workflow-state, decision anchor, stage contract,
|
|
3
|
-
// runtime-context bind) is rendered by the Python
|
|
4
|
-
// <scripts>/adapters/host/inject.py --host opencode; this module
|
|
5
|
-
// bytes only. What stays here is what the Python side cannot
|
|
6
|
-
// the config-hook skills registration, the shell.env context-key
|
|
7
|
-
// export, and the
|
|
8
|
-
//
|
|
3
|
+
// runtime-context bind, edit-scope warning) is rendered by the Python
|
|
4
|
+
// source <scripts>/adapters/host/inject.py --host opencode; this module
|
|
5
|
+
// moves bytes only. What stays here is what the Python side cannot
|
|
6
|
+
// reach: the config-hook skills registration, the shell.env context-key
|
|
7
|
+
// export, and the tool-shape translation for the tool.execute.after
|
|
8
|
+
// edit-scope delegation.
|
|
9
9
|
//
|
|
10
10
|
// This module must NOT live under plugins/: opencode
|
|
11
11
|
// treats every file directly inside plugins/ as a plugin
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// install, so the adapter's relative import resolves in
|
|
17
17
|
// both.
|
|
18
18
|
|
|
19
|
-
import { spawn } from "node:child_process"
|
|
19
|
+
import { spawn, spawnSync } from "node:child_process"
|
|
20
20
|
import { existsSync } from "node:fs"
|
|
21
21
|
import { dirname, resolve } from "node:path"
|
|
22
22
|
import { fileURLToPath } from "node:url"
|
|
@@ -24,6 +24,27 @@ import { fileURLToPath } from "node:url"
|
|
|
24
24
|
const DIR_WORKFLOW = ".cowork-flow"
|
|
25
25
|
// One prompt assembly must not wait longer than a host hook event.
|
|
26
26
|
const INJECT_TIMEOUT_MS = 4800
|
|
27
|
+
// The .cowork-flow/run floor, reused here: a candidate that cannot run
|
|
28
|
+
// Python 3.10+ is skipped like a missing binary, so a stale `python3`
|
|
29
|
+
// never hides a working `python` further down the list.
|
|
30
|
+
const MIN_PYTHON_CHECK =
|
|
31
|
+
"import sys; raise SystemExit(0 if sys.version_info >= (3, 10) else 1)"
|
|
32
|
+
|
|
33
|
+
function interpreterIsUsable(candidate) {
|
|
34
|
+
try {
|
|
35
|
+
const probe = spawnSync(candidate[0], [...candidate.slice(1), "-c", MIN_PYTHON_CHECK], {
|
|
36
|
+
timeout: INJECT_TIMEOUT_MS,
|
|
37
|
+
windowsHide: true,
|
|
38
|
+
})
|
|
39
|
+
// A probe killed by its own timeout reached no verdict — a loaded machine,
|
|
40
|
+
// not a bad interpreter — so let the real spawn decide; it carries the
|
|
41
|
+
// same timeout and its own diagnostics.
|
|
42
|
+
if (probe.error && probe.error.code === "ETIMEDOUT") return true
|
|
43
|
+
return !probe.error && probe.status === 0
|
|
44
|
+
} catch {
|
|
45
|
+
return false
|
|
46
|
+
}
|
|
47
|
+
}
|
|
27
48
|
|
|
28
49
|
// Last-resort repository root: this module lives at
|
|
29
50
|
// <payload>/cowork-flow/plugin-core.js, next to plugins/,
|
|
@@ -175,8 +196,9 @@ function runInject(candidate, script, payload) {
|
|
|
175
196
|
// inject.py and hand back whatever it printed. The payload spreads the
|
|
176
197
|
// host input so every session/runtime-context key the Python identity
|
|
177
198
|
// table knows arrives unchanged; hook_event_name and cwd are the two
|
|
178
|
-
// fields the hook contract requires.
|
|
179
|
-
|
|
199
|
+
// fields the hook contract requires. `overrides` swaps the event name
|
|
200
|
+
// and adds the tool fields the PostToolUse contract needs.
|
|
201
|
+
async function renderViaInject(input, overrides = null) {
|
|
180
202
|
if (
|
|
181
203
|
process.env.COWORK_FLOW_HOOKS === "0" ||
|
|
182
204
|
process.env.COWORK_FLOW_DISABLE_HOOKS === "1"
|
|
@@ -188,21 +210,57 @@ async function renderViaInject(input) {
|
|
|
188
210
|
if (!script) {
|
|
189
211
|
return ""
|
|
190
212
|
}
|
|
213
|
+
const extra = overrides && typeof overrides === "object" ? overrides : {}
|
|
191
214
|
const payload = {
|
|
192
215
|
...(input && typeof input === "object" ? input : {}),
|
|
193
|
-
|
|
216
|
+
...extra,
|
|
217
|
+
hook_event_name: extra.hook_event_name || "UserPromptSubmit",
|
|
194
218
|
cwd: inputCwd(input) || (root === pluginRoot ? null : root) || process.cwd(),
|
|
195
219
|
}
|
|
196
220
|
for (const candidate of pythonCandidates()) {
|
|
221
|
+
if (!interpreterIsUsable(candidate)) {
|
|
222
|
+
continue
|
|
223
|
+
}
|
|
197
224
|
const result = await runInject(candidate, script, payload)
|
|
198
225
|
if (!result.ran) {
|
|
199
226
|
continue
|
|
200
227
|
}
|
|
201
228
|
return result.stdout.trim()
|
|
202
229
|
}
|
|
230
|
+
console.error(
|
|
231
|
+
"cowork-flow opencode plugin: no Python 3.10+ interpreter found; skipping injection"
|
|
232
|
+
)
|
|
203
233
|
return ""
|
|
204
234
|
}
|
|
205
235
|
|
|
236
|
+
// Editor-phase scope warning: opencode sessions get the same
|
|
237
|
+
// single-line out-of-scope advisory the other hosts receive on
|
|
238
|
+
// PostToolUse. Best-effort — a missing runtime, a timeout, or any
|
|
239
|
+
// error stays silent and never blocks the edit that already happened.
|
|
240
|
+
const EDIT_TOOL_NAMES = new Set(["edit", "write"])
|
|
241
|
+
|
|
242
|
+
function editedFilePath(input) {
|
|
243
|
+
const tool = typeof input?.tool === "string" ? input.tool : ""
|
|
244
|
+
if (!EDIT_TOOL_NAMES.has(tool)) {
|
|
245
|
+
return ""
|
|
246
|
+
}
|
|
247
|
+
const args = input?.args
|
|
248
|
+
const candidate = args && typeof args === "object" ? args.filePath : null
|
|
249
|
+
return typeof candidate === "string" && candidate.trim() ? candidate.trim() : ""
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function renderEditScopeWarning(input) {
|
|
253
|
+
const filePath = editedFilePath(input)
|
|
254
|
+
if (!filePath) {
|
|
255
|
+
return ""
|
|
256
|
+
}
|
|
257
|
+
return renderViaInject(input, {
|
|
258
|
+
hook_event_name: "PostToolUse",
|
|
259
|
+
tool_name: typeof input?.tool === "string" ? input.tool : "edit",
|
|
260
|
+
tool_input: { file_path: filePath },
|
|
261
|
+
})
|
|
262
|
+
}
|
|
263
|
+
|
|
206
264
|
function sanitize(value) {
|
|
207
265
|
return String(value ?? "")
|
|
208
266
|
.trim()
|
|
@@ -262,82 +320,6 @@ function injectShellEnv(input, output) {
|
|
|
262
320
|
}
|
|
263
321
|
}
|
|
264
322
|
|
|
265
|
-
// Editor-phase spec-check: opencode sessions get the same
|
|
266
|
-
// single-line violation hint zcode and claude-code
|
|
267
|
-
// receive. Best-effort — a missing runtime, a timeout, or
|
|
268
|
-
// any error stays silent and never blocks the edit that
|
|
269
|
-
// already happened.
|
|
270
|
-
const EDIT_TOOL_NAMES = new Set(["edit", "write"])
|
|
271
|
-
const EDIT_CHECK_TIMEOUT_MS = 2500
|
|
272
|
-
|
|
273
|
-
function editedFilePath(input) {
|
|
274
|
-
const tool = typeof input?.tool === "string" ? input.tool : ""
|
|
275
|
-
if (!EDIT_TOOL_NAMES.has(tool)) {
|
|
276
|
-
return ""
|
|
277
|
-
}
|
|
278
|
-
const args = input?.args
|
|
279
|
-
const candidate = args && typeof args === "object" ? args.filePath : null
|
|
280
|
-
return typeof candidate === "string" && candidate.trim() ? candidate.trim() : ""
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function runEditSpecCheck(root, filePath) {
|
|
284
|
-
return new Promise((resolvePromise) => {
|
|
285
|
-
let settled = false
|
|
286
|
-
const finish = (value) => {
|
|
287
|
-
if (!settled) {
|
|
288
|
-
settled = true
|
|
289
|
-
resolvePromise(value)
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
const entry = resolve(
|
|
293
|
-
root,
|
|
294
|
-
DIR_WORKFLOW,
|
|
295
|
-
process.platform === "win32" ? "run.cmd" : "run"
|
|
296
|
-
)
|
|
297
|
-
if (!existsSync(entry)) {
|
|
298
|
-
finish("")
|
|
299
|
-
return
|
|
300
|
-
}
|
|
301
|
-
let child
|
|
302
|
-
try {
|
|
303
|
-
child = spawn(
|
|
304
|
-
entry,
|
|
305
|
-
["spec-check", "--phase", "edit", "--file", filePath, "--throttled"],
|
|
306
|
-
{
|
|
307
|
-
cwd: root,
|
|
308
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
309
|
-
shell: process.platform === "win32",
|
|
310
|
-
}
|
|
311
|
-
)
|
|
312
|
-
} catch {
|
|
313
|
-
finish("")
|
|
314
|
-
return
|
|
315
|
-
}
|
|
316
|
-
const timer = setTimeout(() => {
|
|
317
|
-
try {
|
|
318
|
-
child.kill()
|
|
319
|
-
} catch {
|
|
320
|
-
// the edit must not be held up by a stuck checker
|
|
321
|
-
}
|
|
322
|
-
finish("")
|
|
323
|
-
}, EDIT_CHECK_TIMEOUT_MS)
|
|
324
|
-
let stdout = ""
|
|
325
|
-
if (child.stdout) {
|
|
326
|
-
child.stdout.on("data", (chunk) => {
|
|
327
|
-
stdout += String(chunk)
|
|
328
|
-
})
|
|
329
|
-
}
|
|
330
|
-
child.on("error", () => {
|
|
331
|
-
clearTimeout(timer)
|
|
332
|
-
finish("")
|
|
333
|
-
})
|
|
334
|
-
child.on("close", () => {
|
|
335
|
-
clearTimeout(timer)
|
|
336
|
-
finish(stdout.trim())
|
|
337
|
-
})
|
|
338
|
-
})
|
|
339
|
-
}
|
|
340
|
-
|
|
341
323
|
// The payload's own skills directory, located from this
|
|
342
324
|
// module's file path and not from the repository root: the
|
|
343
325
|
// machine install puts this file in
|
|
@@ -382,11 +364,11 @@ export function registerPayloadSkills(config) {
|
|
|
382
364
|
|
|
383
365
|
// The plugin adapter imports the hook helpers; these are
|
|
384
366
|
// the unit-test surface (transport, fail-open, shell env,
|
|
385
|
-
// edit-
|
|
367
|
+
// edit-scope delegation). opencode never reads this module.
|
|
386
368
|
export {
|
|
387
369
|
renderViaInject,
|
|
388
370
|
editedFilePath,
|
|
371
|
+
renderEditScopeWarning,
|
|
389
372
|
findRepoRoot,
|
|
390
373
|
injectShellEnv,
|
|
391
|
-
runEditSpecCheck,
|
|
392
374
|
}
|
|
@@ -7,12 +7,10 @@
|
|
|
7
7
|
// re-exported helper would be called as a plugin and break
|
|
8
8
|
// host bootstrap.
|
|
9
9
|
import {
|
|
10
|
-
editedFilePath,
|
|
11
|
-
findRepoRoot,
|
|
12
10
|
injectShellEnv,
|
|
13
11
|
registerPayloadSkills,
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
renderEditScopeWarning,
|
|
13
|
+
renderViaInject
|
|
16
14
|
} from "../cowork-flow/plugin-core.js"
|
|
17
15
|
|
|
18
16
|
export const CoworkFlowPlugin = async () => {
|
|
@@ -37,12 +35,12 @@ export const CoworkFlowPlugin = async () => {
|
|
|
37
35
|
output.system.push(text)
|
|
38
36
|
}
|
|
39
37
|
},
|
|
38
|
+
// Editor-phase scope advisory: the tool already ran, so
|
|
39
|
+
// the single out-of-scope line is appended to its output
|
|
40
|
+
// and the call is never blocked. Non-edit tools and a
|
|
41
|
+
// missing/failing runtime stay silent.
|
|
40
42
|
"tool.execute.after": async (input, output) => {
|
|
41
|
-
const
|
|
42
|
-
if (!filePath) {
|
|
43
|
-
return
|
|
44
|
-
}
|
|
45
|
-
const warning = await runEditSpecCheck(findRepoRoot(input), filePath)
|
|
43
|
+
const warning = await renderEditScopeWarning(input)
|
|
46
44
|
if (warning) {
|
|
47
45
|
output.output = [output.output, warning].filter(Boolean).join("\n")
|
|
48
46
|
}
|
|
@@ -33,6 +33,27 @@ const HOST = "qoder";
|
|
|
33
33
|
const DIR_WORKFLOW = ".cowork-flow";
|
|
34
34
|
// hooks.json grants 30s per event.
|
|
35
35
|
const SPAWN_TIMEOUT_MS = 25000;
|
|
36
|
+
// The .cowork-flow/run floor, reused here: a candidate that cannot run
|
|
37
|
+
// Python 3.10+ is skipped like a missing binary, so a stale `python3`
|
|
38
|
+
// never hides a working `python` further down the list.
|
|
39
|
+
const MIN_PYTHON_CHECK = "import sys; raise SystemExit(0 if sys.version_info >= (3, 10) else 1)";
|
|
40
|
+
|
|
41
|
+
function interpreterIsUsable(candidate) {
|
|
42
|
+
try {
|
|
43
|
+
const probe = spawnSync(
|
|
44
|
+
candidate[0],
|
|
45
|
+
[...candidate.slice(1), "-c", MIN_PYTHON_CHECK],
|
|
46
|
+
{ timeout: SPAWN_TIMEOUT_MS, windowsHide: true }
|
|
47
|
+
);
|
|
48
|
+
// A probe killed by its own timeout reached no verdict — a loaded machine,
|
|
49
|
+
// not a bad interpreter — so let the real spawn decide; it carries the
|
|
50
|
+
// same timeout and its own diagnostics.
|
|
51
|
+
if (probe.error && probe.error.code === "ETIMEDOUT") return true;
|
|
52
|
+
return !probe.error && probe.status === 0;
|
|
53
|
+
} catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
36
57
|
|
|
37
58
|
function readHookInput() {
|
|
38
59
|
if (process.stdin.isTTY) return { raw: "", parsed: {} };
|
|
@@ -113,6 +134,7 @@ function main() {
|
|
|
113
134
|
if (!existsSync(injectScript)) process.exit(0);
|
|
114
135
|
|
|
115
136
|
for (const candidate of pythonCandidates()) {
|
|
137
|
+
if (!interpreterIsUsable(candidate)) continue;
|
|
116
138
|
let result;
|
|
117
139
|
try {
|
|
118
140
|
result = spawnSync(
|
|
@@ -142,8 +164,12 @@ function main() {
|
|
|
142
164
|
}
|
|
143
165
|
process.exit(0);
|
|
144
166
|
}
|
|
145
|
-
// No
|
|
146
|
-
//
|
|
167
|
+
// No candidate could run the entry: stay fail-open, but say why once
|
|
168
|
+
// instead of hiding a misconfiguration (a Python below the floor, or
|
|
169
|
+
// none at all).
|
|
170
|
+
process.stderr.write(
|
|
171
|
+
"cowork-flow qoder hook: no Python 3.10+ interpreter found; skipping injection\n"
|
|
172
|
+
);
|
|
147
173
|
process.exit(0);
|
|
148
174
|
}
|
|
149
175
|
|
|
@@ -35,6 +35,27 @@ const DIR_WORKFLOW = ".cowork-flow";
|
|
|
35
35
|
const LIFECYCLE_BASH_RE = /\brun(?:\.cmd)?\s+(?:task|subagent|resume)\b/;
|
|
36
36
|
// hooks.json grants 5s per event.
|
|
37
37
|
const SPAWN_TIMEOUT_MS = 4800;
|
|
38
|
+
// The .cowork-flow/run floor, reused here: a candidate that cannot run
|
|
39
|
+
// Python 3.10+ is skipped like a missing binary, so a stale `python3`
|
|
40
|
+
// never hides a working `python` further down the list.
|
|
41
|
+
const MIN_PYTHON_CHECK = "import sys; raise SystemExit(0 if sys.version_info >= (3, 10) else 1)";
|
|
42
|
+
|
|
43
|
+
function interpreterIsUsable(candidate) {
|
|
44
|
+
try {
|
|
45
|
+
const probe = spawnSync(
|
|
46
|
+
candidate[0],
|
|
47
|
+
[...candidate.slice(1), "-c", MIN_PYTHON_CHECK],
|
|
48
|
+
{ timeout: SPAWN_TIMEOUT_MS, windowsHide: true }
|
|
49
|
+
);
|
|
50
|
+
// A probe killed by its own timeout reached no verdict — a loaded machine,
|
|
51
|
+
// not a bad interpreter — so let the real spawn decide; it carries the
|
|
52
|
+
// same timeout and its own diagnostics.
|
|
53
|
+
if (probe.error && probe.error.code === "ETIMEDOUT") return true;
|
|
54
|
+
return !probe.error && probe.status === 0;
|
|
55
|
+
} catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
38
59
|
|
|
39
60
|
function readHookInput() {
|
|
40
61
|
if (process.stdin.isTTY) return { raw: "", parsed: {} };
|
|
@@ -152,6 +173,7 @@ function main() {
|
|
|
152
173
|
if (!injectScript) process.exit(0);
|
|
153
174
|
|
|
154
175
|
for (const candidate of pythonCandidates()) {
|
|
176
|
+
if (!interpreterIsUsable(candidate)) continue;
|
|
155
177
|
let result;
|
|
156
178
|
try {
|
|
157
179
|
result = spawnSync(
|
|
@@ -180,9 +202,12 @@ function main() {
|
|
|
180
202
|
}
|
|
181
203
|
process.exit(0);
|
|
182
204
|
}
|
|
183
|
-
// No
|
|
184
|
-
//
|
|
185
|
-
//
|
|
205
|
+
// No candidate could run the entry: stay fail-open, but say why once
|
|
206
|
+
// instead of hiding a misconfiguration (a Python below the floor, or
|
|
207
|
+
// none at all).
|
|
208
|
+
process.stderr.write(
|
|
209
|
+
"cowork-flow zcode hook: no Python 3.10+ interpreter found; skipping injection\n"
|
|
210
|
+
);
|
|
186
211
|
process.exit(0);
|
|
187
212
|
}
|
|
188
213
|
|
package/src/commands/host.js
CHANGED
|
@@ -13,6 +13,8 @@ import { runInstallOpenCodePlugin, FLAGS as OPENCODE_PLUGIN_FLAGS } from './inst
|
|
|
13
13
|
import { runInstallQoderPlugin, FLAGS as QODER_PLUGIN_FLAGS } from './install-qoder-plugin.js';
|
|
14
14
|
import { runInstallZCodePlugin, FLAGS as ZCODE_PLUGIN_FLAGS } from './install-zcode-plugin.js';
|
|
15
15
|
|
|
16
|
+
const KIMI_ALL_FLAGS = [...new Set([...KIMI_HOOK_FLAGS, ...KIMI_PLUGIN_FLAGS])].sort();
|
|
17
|
+
|
|
16
18
|
|
|
17
19
|
// Which machine-level integrations a host has.
|
|
18
20
|
// Deliberately not part of host-assets.json: that manifest
|
|
@@ -58,10 +60,11 @@ export const HOST_COMPONENTS = {
|
|
|
58
60
|
}
|
|
59
61
|
},
|
|
60
62
|
'kimi-code': {
|
|
61
|
-
default: '
|
|
63
|
+
default: 'all',
|
|
62
64
|
components: {
|
|
63
65
|
plugin: { summary: 'Kimi Code plugin: the bootstrap Skill it loads at session start', flags: KIMI_PLUGIN_FLAGS, run: runInstallKimiPlugin },
|
|
64
|
-
hook: { summary: 'Kimi Code context-injection hook as a user-level config.toml [[hooks]] row', flags: KIMI_HOOK_FLAGS, run: runInstallKimiHook }
|
|
66
|
+
hook: { summary: 'Kimi Code context-injection hook as a user-level config.toml [[hooks]] row', flags: KIMI_HOOK_FLAGS, run: runInstallKimiHook },
|
|
67
|
+
all: { summary: 'Kimi Code hook plus plugin, installed in one command (the default)', flags: KIMI_ALL_FLAGS, run: runInstallKimiAll }
|
|
65
68
|
}
|
|
66
69
|
}
|
|
67
70
|
};
|
|
@@ -78,6 +81,24 @@ export const HOST_FLAGS = [...new Set(
|
|
|
78
81
|
)].sort();
|
|
79
82
|
|
|
80
83
|
|
|
84
|
+
function withoutForceFlag(args) {
|
|
85
|
+
return args.filter((arg) => arg !== '--force' && !arg.startsWith('--force='));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
// kimi-code's default component: the hook (injection, the
|
|
90
|
+
// required half) first, then the plugin, in one command.
|
|
91
|
+
// Both installers stay the only validators — unknown flags
|
|
92
|
+
// pass through untouched and are rejected by whichever
|
|
93
|
+
// installer reaches them — but `--force` cannot: the hook
|
|
94
|
+
// has no such flag, so the combined path strips it for the
|
|
95
|
+
// hook segment only, and the plugin still receives it.
|
|
96
|
+
async function runInstallKimiAll(args) {
|
|
97
|
+
await runInstallKimiHook(withoutForceFlag(args));
|
|
98
|
+
await runInstallKimiPlugin(args);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
81
102
|
function resolveHost(token) {
|
|
82
103
|
const id = hostRegistry.platformIdFor(token);
|
|
83
104
|
if (id === null) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { copyFile, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
|
-
import { homedir } from 'node:os';
|
|
4
3
|
|
|
4
|
+
import { getDshHome } from '../lib/dsh-home.js';
|
|
5
5
|
import { parseInstallArgs, pathExists } from '../lib/install-support.js';
|
|
6
6
|
import { packageRoot } from '../lib/paths.js';
|
|
7
7
|
import { removeManagedBlock, upsertManagedBlock } from '../lib/payload-install.js';
|
|
@@ -19,11 +19,6 @@ const MANAGED_MARK = '# cowork-flow: managed workflow-state-hook row. Run "cowor
|
|
|
19
19
|
export const FLAGS = ['--dry-run', '--force', '--uninstall'];
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
function getDshHome() {
|
|
23
|
-
return process.env.DSH_HOME || join(homedir(), '.dsh');
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
22
|
async function readIfExists(file) {
|
|
28
23
|
try {
|
|
29
24
|
return await readFile(file, 'utf8');
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import { homedir } from 'node:os';
|
|
4
3
|
import { pathToFileURL } from 'node:url';
|
|
5
4
|
|
|
5
|
+
import { getDshHome } from '../lib/dsh-home.js';
|
|
6
6
|
import { parseInstallArgs, pathExists, readJsonFile, writeJsonAtomic } from '../lib/install-support.js';
|
|
7
7
|
import { packageRoot } from '../lib/paths.js';
|
|
8
8
|
import { readPackageInfo } from '../lib/package-info.js';
|
|
@@ -34,11 +34,6 @@ const DESKTOP_PERSONA_KEY = /(- id: persona\n(?:.*\n)*? {4})text: /;
|
|
|
34
34
|
export const FLAGS = ['--dry-run', '--force', '--uninstall'];
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
function getDshHome() {
|
|
38
|
-
return process.env.DSH_HOME || join(homedir(), '.dsh');
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
37
|
function getDshPresetRoot() {
|
|
43
38
|
return join(getDshHome(), '.agent-presets');
|
|
44
39
|
}
|
|
@@ -250,9 +250,8 @@ async function materializePlugin({ pluginSrc, destDir, cacheRoot, manifest, vers
|
|
|
250
250
|
// folder; keep workflow runtime files out of scaffold.
|
|
251
251
|
await rm(join(destDir, "scaffold", ".cowork-flow"), { recursive: true, force: true });
|
|
252
252
|
|
|
253
|
-
// The
|
|
254
|
-
//
|
|
255
|
-
// template.
|
|
253
|
+
// The preset ships no runtime payload, so materialize
|
|
254
|
+
// the authoritative workflow scripts from the template.
|
|
256
255
|
const mainScriptsSrc = join(templateRoot, ".cowork-flow", "scripts");
|
|
257
256
|
const pluginScriptsDest = join(destDir, "hooks", "runtime", "scripts");
|
|
258
257
|
if (await pathExists(mainScriptsSrc)) {
|
package/src/commands/registry.js
CHANGED
|
@@ -41,9 +41,11 @@ const HOST_NOTE = `Hosts: ${Object.entries(HOST_COMPONENTS)
|
|
|
41
41
|
.join('; ')}.`;
|
|
42
42
|
// The union across hosts contains one gap: kimi-code's
|
|
43
43
|
// hook has no --force, so the help names it instead of
|
|
44
|
-
// letting the user hit a usage error.
|
|
44
|
+
// letting the user hit a usage error. kimi-code's default
|
|
45
|
+
// component routes --force to the plugin segment only.
|
|
45
46
|
const HOST_FORCE_NOTE = '`--force` applies to every component except kimi-code\'s hook, '
|
|
46
|
-
+ 'which overwrites unconditionally and therefore does not accept the flag
|
|
47
|
+
+ 'which overwrites unconditionally and therefore does not accept the flag; '
|
|
48
|
+
+ 'kimi-code\'s default component consumes it for the plugin only.';
|
|
47
49
|
|
|
48
50
|
export const COMMANDS = [
|
|
49
51
|
{
|
package/src/lib/asset-plan.js
CHANGED
|
@@ -5,7 +5,7 @@ import { resolve } from 'node:path';
|
|
|
5
5
|
const VERSION_PATH = '.cowork-flow/.version';
|
|
6
6
|
const MATERIALIZED_ACTIONS = new Set(['create', 'update']);
|
|
7
7
|
|
|
8
|
-
function sha256(content) {
|
|
8
|
+
export function sha256(content) {
|
|
9
9
|
return createHash('sha256').update(content).digest('hex');
|
|
10
10
|
}
|
|
11
11
|
|
package/src/lib/plan-applier.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
1
|
import * as defaultFileSystem from 'node:fs/promises';
|
|
3
2
|
import { basename, dirname, join, resolve, sep } from 'node:path';
|
|
4
3
|
|
|
5
|
-
import { planActions } from './asset-plan.js';
|
|
4
|
+
import { planActions, sha256 } from './asset-plan.js';
|
|
6
5
|
|
|
7
6
|
const MATERIALIZED_ACTIONS = new Set(['create', 'update']);
|
|
8
7
|
const VERSION_PATH = '.cowork-flow/.version';
|
|
@@ -15,10 +14,6 @@ const TRANSACTION_STATUSES = new Set([
|
|
|
15
14
|
'committed'
|
|
16
15
|
]);
|
|
17
16
|
|
|
18
|
-
function sha256(content) {
|
|
19
|
-
return createHash('sha256').update(content).digest('hex');
|
|
20
|
-
}
|
|
21
|
-
|
|
22
17
|
async function pathExists(fileSystem, path) {
|
|
23
18
|
try {
|
|
24
19
|
await fileSystem.access(path);
|
|
@@ -23,6 +23,6 @@ Execution:
|
|
|
23
23
|
Rules:
|
|
24
24
|
|
|
25
25
|
- Do not use the Task tool or invoke subagents.
|
|
26
|
-
- Do not run task
|
|
26
|
+
- Do not run task lifecycle transitions, unscoped resume, commit, or push.
|
|
27
27
|
- Treat backend/frontend natural-language specs as checklist context, not dynamic hard validators.
|
|
28
28
|
- Do not accept shallow tests that would still pass when target behavior breaks.
|
|
@@ -24,6 +24,6 @@ Execution:
|
|
|
24
24
|
Rules:
|
|
25
25
|
|
|
26
26
|
- Do not use the Task tool or invoke subagents.
|
|
27
|
-
- Do not run task
|
|
27
|
+
- Do not run task lifecycle transitions, unscoped resume, commit, or push.
|
|
28
28
|
- Do not create task-local review artifacts, `tdd.jsonl`, or TDD evidence records in `check.jsonl`.
|
|
29
29
|
- Do not claim completion without current verification.
|
|
@@ -19,4 +19,4 @@ Rules:
|
|
|
19
19
|
|
|
20
20
|
- Do not use the Task tool or invoke subagents.
|
|
21
21
|
- Do not edit code, specs, task state, or git state.
|
|
22
|
-
- Do not run task
|
|
22
|
+
- Do not run task lifecycle transitions, unscoped resume, commit, or push.
|
|
@@ -19,7 +19,7 @@ Execution:
|
|
|
19
19
|
|
|
20
20
|
Rules:
|
|
21
21
|
- MUST NOT spawn, wait for, list, or close other agents.
|
|
22
|
-
- MUST NOT run task
|
|
22
|
+
- MUST NOT run task lifecycle transitions, unscoped resume, commit, or push.
|
|
23
23
|
- Treat backend/frontend natural-language specs as checklist context, not dynamic hard validators.
|
|
24
24
|
- Do not accept shallow tests that would still pass when target behavior breaks.
|
|
25
25
|
"""
|
|
@@ -20,7 +20,7 @@ Execution:
|
|
|
20
20
|
|
|
21
21
|
Rules:
|
|
22
22
|
- MUST NOT spawn, wait for, list, or close other agents.
|
|
23
|
-
- MUST NOT run task
|
|
23
|
+
- MUST NOT run task lifecycle transitions, unscoped resume, commit, or push.
|
|
24
24
|
- Do not create task-local review artifacts, `tdd.jsonl`, or TDD evidence records in `check.jsonl`.
|
|
25
25
|
- Do not claim completion without current verification.
|
|
26
26
|
"""
|
|
@@ -16,7 +16,7 @@ Execution:
|
|
|
16
16
|
Rules:
|
|
17
17
|
- MUST NOT spawn, wait for, list, or close other agents.
|
|
18
18
|
- MUST NOT edit code, specs, task state, or git state.
|
|
19
|
-
- MUST NOT run task
|
|
19
|
+
- MUST NOT run task lifecycle transitions, unscoped resume, commit, or push.
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
22
|
[features]
|
|
@@ -15,7 +15,7 @@ constraints only; they are not the task.
|
|
|
15
15
|
|
|
16
16
|
Rules:
|
|
17
17
|
- MUST NOT spawn, wait for, list, or close other agents.
|
|
18
|
-
- MUST NOT run task
|
|
18
|
+
- MUST NOT run task lifecycle transitions or unscoped resume.
|
|
19
19
|
- MUST NOT commit or push.
|
|
20
20
|
- Keep edits inside requested scope.
|
|
21
21
|
- Report changed files and exact verification commands.
|
|
@@ -16,7 +16,7 @@ constraints only; they are not the task.
|
|
|
16
16
|
Rules:
|
|
17
17
|
- MUST NOT edit files unless the prompt explicitly allows it.
|
|
18
18
|
- MUST NOT spawn, wait for, list, or close other agents.
|
|
19
|
-
- MUST NOT run task
|
|
19
|
+
- MUST NOT run task lifecycle transitions or unscoped resume.
|
|
20
20
|
- MUST NOT commit or push.
|
|
21
21
|
- Prefer rg for file and text search.
|
|
22
22
|
- Return concise findings with file and line evidence.
|
|
@@ -16,7 +16,7 @@ constraints only; they are not the task.
|
|
|
16
16
|
|
|
17
17
|
Rules:
|
|
18
18
|
- MUST NOT spawn, wait for, list, or close other agents.
|
|
19
|
-
- MUST NOT run task
|
|
19
|
+
- MUST NOT run task lifecycle transitions or unscoped resume.
|
|
20
20
|
- MUST NOT commit or push.
|
|
21
21
|
- Keep edits inside requested scope.
|
|
22
22
|
- Report changed files and exact verification commands.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
2.1.0
|