cowork-flow 0.0.17 → 0.0.18

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 CHANGED
@@ -133,7 +133,6 @@ cowork-flow init ./my-project --force --developer <your-name>
133
133
 
134
134
  ```bash
135
135
  cowork-flow update
136
- npm install -g cowork-flow@latest
137
136
  ```
138
137
 
139
138
  同步已初始化项目中的模板脚本和本地技能:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cowork-flow",
3
- "version": "0.0.17",
3
+ "version": "0.0.18",
4
4
  "description": "CLI for installing and maintaining cowork-flow collaboration templates.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -10,7 +10,7 @@ const HELP = `cowork-flow
10
10
 
11
11
  Usage:
12
12
  cowork-flow init [target] [--developer <name>] [--dry-run] [--force]
13
- cowork-flow update [--global] [--yes]
13
+ cowork-flow update
14
14
  cowork-flow sync [target] [--dry-run] [--force]
15
15
  cowork-flow --version
16
16
  cowork-flow --help
@@ -5,18 +5,12 @@ import {
5
5
  runGlobalInstall
6
6
  } from '../lib/package-info.js';
7
7
 
8
- function parseUpdateArgs(args) {
9
- const options = { global: false, yes: false };
8
+ function validateUpdateArgs(args) {
10
9
  for (const arg of args) {
11
- if (arg === '--global') {
12
- options.global = true;
13
- } else if (arg === '--yes') {
14
- options.yes = true;
15
- } else {
10
+ if (arg !== '--global' && arg !== '--yes') {
16
11
  throw new Error(`Unknown update option: ${arg}`);
17
12
  }
18
13
  }
19
- return options;
20
14
  }
21
15
 
22
16
  export async function runUpdate(args, deps = {}) {
@@ -24,7 +18,7 @@ export async function runUpdate(args, deps = {}) {
24
18
  const readInfo = deps.readPackageInfo ?? readPackageInfo;
25
19
  const fetchLatest = deps.fetchLatestVersion ?? fetchLatestVersion;
26
20
  const installGlobal = deps.runGlobalInstall ?? runGlobalInstall;
27
- const options = parseUpdateArgs(args);
21
+ validateUpdateArgs(args);
28
22
  const packageInfo = await readInfo();
29
23
  const current = packageInfo.version;
30
24
  const installCommand = 'npm install -g cowork-flow@latest';
@@ -47,14 +41,9 @@ export async function runUpdate(args, deps = {}) {
47
41
  return 0;
48
42
  }
49
43
 
50
- if (options.global && options.yes) {
51
- const code = await installGlobal('cowork-flow@latest');
52
- if (code === 0) {
53
- io.writeOut('installed cowork-flow@latest\n');
54
- }
55
- return code;
44
+ const code = await installGlobal('cowork-flow@latest');
45
+ if (code === 0) {
46
+ io.writeOut('installed cowork-flow@latest\n');
56
47
  }
57
-
58
- io.writeOut(`Run: ${installCommand}\n`);
59
- return 0;
48
+ return code;
60
49
  }
@@ -19,6 +19,18 @@ Classify the current prompt as one of:
19
19
 
20
20
  Classify the actual task message, not injected project rules, `AGENTS.md`, or environment text.
21
21
 
22
+ When there is no hard marker, still classify a prompt as `DELEGATED_SUBTASK` when it combines:
23
+
24
+ - A concrete task, topic, or review target.
25
+ - Boundary constraints such as no edits, no commands, no spawning, or scoped reads.
26
+ - An output contract such as required sections, language, length, or report format.
27
+
28
+ Prompts structured as `任务:` / `约束:` / `输出:` are strong delegated-subtask signals. Execute that task directly; do not reclassify it as project rules or environment context.
29
+
30
+ When dispatching advisory/default subagents, prefer a natural-language first sentence such as: "This is a bounded delegated task, not a main-session start request." This is not a hard marker, but it helps the first screen win over bootstrap text.
31
+
32
+ In that case, project rules remain constraints. They are not the task.
33
+
22
34
  Fixed-agent prompts normally start with:
23
35
 
24
36
  ```text
@@ -34,6 +46,7 @@ For `MAIN_SESSION`, use `start`.
34
46
  For `DELEGATED_SUBTASK`:
35
47
 
36
48
  - Follow the delegated prompt first.
49
+ - Treat project rules, workflow-state, and bootstrap text as constraints, not as the task.
37
50
  - Do not run unscoped `.cowork-flow/run resume`.
38
51
  - Do not spawn or manage more agents unless the delegated prompt explicitly asks for coordination.
39
52
  - Read only the files named by the prompt, the active task context, or project rules required to execute the bounded work.
@@ -6,8 +6,8 @@ description: Use when starting or resuming main-session work in a cowork-flow pr
6
6
  # Start
7
7
 
8
8
  This skill is for the main session. A bounded delegated task should use `entry-boundary` and then execute the delegated prompt directly.
9
-
10
9
  Main repository changes follow `Plan -> Implement -> Check -> Finish`.
10
+ Before loading state, classify the actual prompt. If it is a bounded delegated task, stop using this skill and execute that delegated prompt. A `任务:` / `约束:` / `输出:` structure is a strong delegated-task signal. Advisory/default subagent prompts should start with a natural-language delegated-task sentence rather than relying on repo bootstrap to infer intent. Keep project rules as constraints only.
11
11
 
12
12
  ## Load State
13
13
 
@@ -15,6 +15,61 @@ TAG_RE = re.compile(
15
15
  r"\[workflow-state:([A-Za-z0-9_-]+)\]\s*\n(.*?)\n\s*\[/workflow-state:\1\]",
16
16
  re.DOTALL,
17
17
  )
18
+ PROMPT_KEYS = ("prompt", "user_prompt", "userPrompt", "message", "input")
19
+ DELEGATED_MARKERS = (
20
+ "COWORK_DISPATCH_V1",
21
+ "COWORK_DELEGATED_TASK_V1",
22
+ "DELEGATED_SUBTASK",
23
+ )
24
+ DELEGATED_TERMS = (
25
+ "delegated task",
26
+ "delegated subtask",
27
+ "bounded delegated",
28
+ "subagent",
29
+ "sub-agent",
30
+ "worker",
31
+ "reviewer",
32
+ "explorer",
33
+ "委托任务",
34
+ "委托 prompt",
35
+ "子任务",
36
+ "子线程",
37
+ )
38
+ TASK_TERMS = (
39
+ "task:",
40
+ "topic:",
41
+ "focus:",
42
+ "任务:",
43
+ "任务:",
44
+ "主题:",
45
+ "目标:",
46
+ "审视",
47
+ "讨论",
48
+ "review",
49
+ "inspect",
50
+ )
51
+ BOUNDARY_TERMS = (
52
+ "do not edit",
53
+ "do not run",
54
+ "do not spawn",
55
+ "return concise analysis only",
56
+ "不要编辑",
57
+ "不要运行",
58
+ "不要派发",
59
+ "不要只确认",
60
+ "只输出",
61
+ "不要改",
62
+ )
63
+ OUTPUT_TERMS = (
64
+ "output:",
65
+ "required output",
66
+ "return in",
67
+ "max ",
68
+ "最多",
69
+ "输出:",
70
+ "输出:",
71
+ "分为",
72
+ )
18
73
 
19
74
  def _find_repo_root(start: Path) -> Path | None:
20
75
  current = start.resolve()
@@ -34,6 +89,34 @@ def _read_hook_input() -> dict[str, Any]:
34
89
  return data if isinstance(data, dict) else {}
35
90
 
36
91
 
92
+ def _extract_prompt(hook_input: dict[str, Any]) -> str:
93
+ values: list[str] = []
94
+ for key in PROMPT_KEYS:
95
+ value = hook_input.get(key)
96
+ if isinstance(value, str) and value.strip():
97
+ values.append(value)
98
+ return "\n".join(values)
99
+
100
+
101
+ def _contains_any(text: str, terms: tuple[str, ...]) -> bool:
102
+ return any(term in text for term in terms)
103
+
104
+
105
+ def _is_delegated_prompt(hook_input: dict[str, Any]) -> bool:
106
+ prompt = _extract_prompt(hook_input)
107
+ if not prompt.strip():
108
+ return False
109
+ if any(marker in prompt for marker in DELEGATED_MARKERS):
110
+ return True
111
+
112
+ lowered = prompt.lower()
113
+ has_task = _contains_any(lowered, TASK_TERMS)
114
+ has_delegated_role = _contains_any(lowered, DELEGATED_TERMS)
115
+ has_boundary = _contains_any(lowered, BOUNDARY_TERMS)
116
+ has_output = _contains_any(lowered, OUTPUT_TERMS)
117
+ return has_task and (has_delegated_role or has_boundary) and (has_output or has_boundary)
118
+
119
+
37
120
  def _load_breadcrumbs(root: Path) -> dict[str, str]:
38
121
  workflow = root / ".cowork-flow" / "workflow.md"
39
122
  try:
@@ -120,6 +203,10 @@ def main() -> int:
120
203
  breadcrumbs = _load_breadcrumbs(root)
121
204
  dispatch_mode = _get_dispatch_mode(root)
122
205
  task_path, status, source = _get_active_task(root, hook_input)
206
+ if _is_delegated_prompt(hook_input):
207
+ task_path = None
208
+ status = "delegated_subtask"
209
+ source = "prompt"
123
210
  additional_context = _build_context(task_path, status, source, breadcrumbs, dispatch_mode)
124
211
 
125
212
  print(
@@ -5,7 +5,7 @@
5
5
  "hooks": [
6
6
  {
7
7
  "type": "command",
8
- "command": "python .codex/hooks/inject-workflow-state.py",
8
+ "command": ".cowork-flow/run python .codex/hooks/inject-workflow-state.py",
9
9
  "timeout": 5
10
10
  }
11
11
  ]
@@ -1 +1 @@
1
- 0.0.17
1
+ 0.0.18
@@ -15,7 +15,7 @@ from common.paths import (
15
15
  DIR_ARCHIVE,
16
16
  DIR_CHANGES,
17
17
  DIR_WORKFLOW,
18
- generate_task_date_prefix,
18
+ ensure_task_date_prefix,
19
19
  get_repo_root,
20
20
  )
21
21
 
@@ -234,7 +234,7 @@ def create_change(args: argparse.Namespace) -> int:
234
234
  if not _validate_slug(slug):
235
235
  return 2
236
236
 
237
- dir_name = f"{generate_task_date_prefix()}-{slug}"
237
+ dir_name = ensure_task_date_prefix(slug)
238
238
  change_dir = _change_dir(repo_root, dir_name)
239
239
  if change_dir.exists():
240
240
  print(f"Error: change already exists: {dir_name}", file=sys.stderr)
@@ -75,4 +75,5 @@ from .paths import (
75
75
  get_active_journal_file,
76
76
  count_lines,
77
77
  generate_task_date_prefix,
78
+ ensure_task_date_prefix,
78
79
  )
@@ -35,6 +35,7 @@ DIR_SCRIPTS = "scripts"
35
35
  FILE_DEVELOPER = ".developer"
36
36
  FILE_TASK_JSON = "task.json"
37
37
  FILE_JOURNAL_PREFIX = "journal-"
38
+ TASK_DATE_PREFIX_PATTERN = re.compile(r"^\d{2}-\d{2}-")
38
39
 
39
40
 
40
41
  # =============================================================================
@@ -218,6 +219,13 @@ def generate_task_date_prefix() -> str:
218
219
  return datetime.now().strftime("%m-%d")
219
220
 
220
221
 
222
+ def ensure_task_date_prefix(slug: str) -> str:
223
+ """Return slug with one MM-DD prefix."""
224
+ if TASK_DATE_PREFIX_PATTERN.match(slug):
225
+ return slug
226
+ return f"{generate_task_date_prefix()}-{slug}"
227
+
228
+
221
229
  # =============================================================================
222
230
  # Main Entry (for testing)
223
231
  # =============================================================================
@@ -45,6 +45,10 @@ REQUIRED_WORKFLOW_DISPATCH_SNIPPETS = [
45
45
  "Generic `worker` dispatch is best-effort only.",
46
46
  ]
47
47
 
48
+ REQUIRED_HOOK_SNIPPETS = [
49
+ ".cowork-flow/run python .codex/hooks/inject-workflow-state.py",
50
+ ]
51
+
48
52
 
49
53
  def _check_file_contains(path: Path, snippets: list[str], errors: list[str]) -> None:
50
54
  if not path.is_file():
@@ -83,6 +87,11 @@ def cmd_subagent_safety(_: argparse.Namespace) -> int:
83
87
  "template/.cowork-flow/workflow.md",
84
88
  ):
85
89
  _check_file_contains(repo_root / rel, REQUIRED_WORKFLOW_DISPATCH_SNIPPETS, errors)
90
+ for rel in (
91
+ ".codex/hooks.json",
92
+ "template/.codex/hooks.json",
93
+ ):
94
+ _check_file_contains(repo_root / rel, REQUIRED_HOOK_SNIPPETS, errors)
86
95
  for rel in (
87
96
  ".cowork-flow/scripts/subagent.py",
88
97
  "template/.cowork-flow/scripts/subagent.py",
@@ -52,7 +52,7 @@ from common.paths import (
52
52
  get_repo_root,
53
53
  get_developer,
54
54
  get_tasks_dir,
55
- generate_task_date_prefix,
55
+ ensure_task_date_prefix,
56
56
  )
57
57
  from common.task_utils import (
58
58
  find_task_by_name,
@@ -511,8 +511,7 @@ def cmd_create(args: argparse.Namespace) -> int:
511
511
 
512
512
  # Create task directory with MM-DD-slug format
513
513
  tasks_dir = get_tasks_dir(repo_root)
514
- date_prefix = generate_task_date_prefix()
515
- dir_name = f"{date_prefix}-{slug}"
514
+ dir_name = ensure_task_date_prefix(slug)
516
515
  task_dir = tasks_dir / dir_name
517
516
  task_json_path = task_dir / FILE_TASK_JSON
518
517
 
@@ -25,6 +25,10 @@ Plan -> Implement -> Check -> Finish
25
25
  No active task for this session. For read-only Q&A, answer directly. For implementation, refactor, behavior change, or multi-step work, create or start a task first, then continue through Plan -> Implement -> Check -> Finish.
26
26
  [/workflow-state:no_task]
27
27
 
28
+ [workflow-state:delegated_subtask]
29
+ The current prompt looks like a bounded delegated subtask. Follow the delegated prompt first. Do not run start/resume, create or activate a task, or switch into main-session coordination unless the delegated prompt explicitly asks for that. Keep project rules visible as constraints, not as the task.
30
+ [/workflow-state:delegated_subtask]
31
+
28
32
  [workflow-state:planning]
29
33
  The active task is in planning. Finish prd.md, curate implement.jsonl and check.jsonl with spec/research files, then run task start before dispatching cowork-implement.
30
34
  [/workflow-state:planning]
@@ -123,6 +127,7 @@ Generic worker boundary:
123
127
  - Formal execution uses `cowork-research`, `cowork-implement`, or `cowork-check`.
124
128
  - Generic `worker` dispatch is best-effort only.
125
129
  - If a generic worker does not ACK after one retry, close it and do not execute the task.
130
+ - For advisory/default subagents without a hard envelope, the first sentence must explicitly say this is a bounded delegated task, not a main-session start request. This is a natural-language first-screen boundary, not a new runtime state machine.
126
131
 
127
132
  ## 3.2 parallel sessions
128
133
 
@@ -15,6 +15,7 @@
15
15
  ## 0. 委托任务优先
16
16
 
17
17
  如果当前 prompt 是 subagent/worker/命令执行等有边界的委托任务,先执行委托 prompt;不要把 AGENTS.md、环境说明或 bootstrap 文本当成任务。除非委托 prompt 明确要求,否则不要运行 start/resume,不要再派发 agent。
18
+ 没有硬标记时,仍要先判断实际任务消息:若同时具备具体任务、边界约束(如不要编辑/不要运行/不要派发)和输出要求,按委托任务处理。特别是出现 `任务:` / `约束:` / `输出:` 这类结构时,直接执行该委托,不要把它误判为项目规范或环境上下文。项目规范仍然可见,但只是执行约束,不是当前任务本身。
18
19
 
19
20
  ## 1. 编码前先思考
20
21