cowork-flow 0.0.19 → 0.0.21
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 +65 -20
- package/package.json +1 -1
- package/src/cli.js +109 -2
- package/src/commands/init.js +61 -4
- package/src/commands/sync.js +10 -1
- package/src/lib/copy-template.js +38 -7
- package/src/lib/platforms.js +73 -0
- package/template/.agent/skills/brainstorming/SKILL.md +13 -9
- package/template/.agent/skills/finish-work/SKILL.md +3 -3
- package/template/.agent/skills/meta/SKILL.md +1 -1
- package/template/.agent/skills/start/SKILL.md +38 -14
- package/template/.agent/skills/writing-plans/SKILL.md +1 -1
- package/template/.claude/agents/cowork-check.md +41 -0
- package/template/.claude/agents/cowork-implement.md +40 -0
- package/template/.claude/agents/cowork-research.md +40 -0
- package/template/.claude/commands/cowork-check.md +27 -0
- package/template/.claude/commands/cowork-implement.md +27 -0
- package/template/.claude/commands/cowork-research.md +27 -0
- package/template/.claude/hooks/inject-workflow-state.py +298 -0
- package/template/.claude/settings.json +24 -0
- package/template/.claude/skills/before-dev/SKILL.md +31 -0
- package/template/.claude/skills/brainstorming/SKILL.md +32 -0
- package/template/.claude/skills/break-loop/SKILL.md +26 -0
- package/template/.claude/skills/check/SKILL.md +29 -0
- package/template/.claude/skills/continue/SKILL.md +29 -0
- package/template/.claude/skills/finish-work/SKILL.md +32 -0
- package/template/.claude/skills/meta/SKILL.md +30 -0
- package/template/.claude/skills/python-design/SKILL.md +27 -0
- package/template/.claude/skills/start/SKILL.md +78 -0
- package/template/.claude/skills/update-spec/SKILL.md +39 -0
- package/template/.claude/skills/writing-plans/SKILL.md +70 -0
- package/template/.codex/agents/cowork-check.toml +17 -22
- package/template/.codex/agents/cowork-implement.toml +18 -23
- package/template/.codex/agents/cowork-research.toml +14 -19
- package/template/.codex/agents/default.toml +27 -0
- package/template/.codex/agents/explorer.toml +28 -0
- package/template/.codex/agents/worker.toml +29 -0
- package/template/.codex/hooks/inject-workflow-state.py +172 -90
- package/template/.cowork-flow/.version +1 -1
- package/template/.cowork-flow/adapters/claude-code/adapter.yaml +35 -0
- package/template/.cowork-flow/adapters/codex/adapter.yaml +35 -0
- package/template/.cowork-flow/adapters/opencode/adapter.yaml +33 -0
- package/template/.cowork-flow/config.yaml +4 -1
- package/template/.cowork-flow/scripts/add_session.py +20 -7
- package/template/.cowork-flow/scripts/common/active_task.py +212 -1
- package/template/.cowork-flow/scripts/common/entry_classifier.py +122 -0
- package/template/.cowork-flow/scripts/common/execution_context.py +10 -17
- package/template/.cowork-flow/scripts/common/readiness.py +249 -0
- package/template/.cowork-flow/scripts/doctor.py +417 -29
- package/template/.cowork-flow/scripts/project_context.py +230 -0
- package/template/.cowork-flow/scripts/run.py +3 -0
- package/template/.cowork-flow/scripts/subagent.py +152 -238
- package/template/.cowork-flow/scripts/task.py +330 -17
- package/template/.cowork-flow/spec/adapter.schema.json +132 -0
- package/template/.cowork-flow/spec/capabilities.md +34 -0
- package/template/.cowork-flow/spec/entry-contract.md +45 -0
- package/template/.cowork-flow/spec/registry.json +54 -0
- package/template/.cowork-flow/spec/subagent-dispatch.md +77 -0
- package/template/.cowork-flow/spec/workflow-state-templates.md +66 -0
- package/template/.cowork-flow/workflow.md +154 -190
- package/template/.opencode/agents/cowork-check.md +46 -0
- package/template/.opencode/agents/cowork-implement.md +45 -0
- package/template/.opencode/agents/cowork-research.md +46 -0
- package/template/.opencode/commands/cowork-check.md +26 -0
- package/template/.opencode/commands/cowork-implement.md +26 -0
- package/template/.opencode/commands/cowork-research.md +26 -0
- package/template/.opencode/plugins/cowork-flow.js +317 -0
- package/template/CLAUDE.md +5 -0
- package/template/.agent/skills/entry-boundary/SKILL.md +0 -81
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
|
-
"""
|
|
3
|
+
"""Runtime-context scoped subagent state."""
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
7
|
import argparse
|
|
8
8
|
import json
|
|
9
9
|
import re
|
|
10
|
-
import secrets
|
|
11
10
|
import sys
|
|
12
11
|
from datetime import datetime, timezone
|
|
13
12
|
from pathlib import Path
|
|
14
13
|
|
|
14
|
+
from common.active_task import (
|
|
15
|
+
bind_runtime_context,
|
|
16
|
+
close_runtime_context,
|
|
17
|
+
read_runtime_context,
|
|
18
|
+
resolve_context_key,
|
|
19
|
+
runtime_context_path,
|
|
20
|
+
sessions_dir,
|
|
21
|
+
subagent_contexts_dir,
|
|
22
|
+
write_runtime_context,
|
|
23
|
+
write_subagent_logical_session,
|
|
24
|
+
)
|
|
15
25
|
from common.execution_context import build_internal_execution_context_parser
|
|
16
|
-
from common.paths import
|
|
26
|
+
from common.paths import get_repo_root
|
|
17
27
|
|
|
18
|
-
VALID_STATUSES = {"active", "success", "needs_context", "blocked"}
|
|
28
|
+
VALID_STATUSES = {"pending", "bound", "active", "success", "needs_context", "blocked", "closed"}
|
|
19
29
|
FIXED_AGENT_TYPES = {"cowork-research", "cowork-implement", "cowork-check"}
|
|
20
30
|
GENERIC_AGENT_TYPE = "worker"
|
|
21
31
|
ROLE_AGENT_TYPE_ALIASES = {
|
|
@@ -34,10 +44,6 @@ def _slug(value: str) -> str:
|
|
|
34
44
|
return slug or "subagent"
|
|
35
45
|
|
|
36
46
|
|
|
37
|
-
def _subagents_dir(repo_root: Path) -> Path:
|
|
38
|
-
return repo_root / DIR_WORKFLOW / "subagents"
|
|
39
|
-
|
|
40
|
-
|
|
41
47
|
def _relative(repo_root: Path, path: Path) -> str:
|
|
42
48
|
try:
|
|
43
49
|
return path.resolve().relative_to(repo_root.resolve()).as_posix()
|
|
@@ -45,28 +51,26 @@ def _relative(repo_root: Path, path: Path) -> str:
|
|
|
45
51
|
return str(path)
|
|
46
52
|
|
|
47
53
|
|
|
48
|
-
def _write_json(path: Path, data: object) -> None:
|
|
49
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
50
|
-
path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def _append_event(path: Path, event: dict[str, object]) -> None:
|
|
54
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
55
|
-
with path.open("a", encoding="utf-8") as handle:
|
|
56
|
-
handle.write(json.dumps(event, ensure_ascii=False) + "\n")
|
|
57
|
-
|
|
58
|
-
|
|
59
54
|
def _next_id(base_dir: Path, title: str) -> str:
|
|
60
|
-
prefix = datetime.now().strftime("%Y%m%
|
|
61
|
-
base = f"{prefix}
|
|
55
|
+
prefix = datetime.now().strftime("rtx_%Y%m%d_%H%M%S")
|
|
56
|
+
base = f"{prefix}_{_slug(title)}"
|
|
62
57
|
candidate = base
|
|
63
58
|
counter = 2
|
|
64
|
-
while (base_dir / candidate).exists():
|
|
65
|
-
candidate = f"{base}
|
|
59
|
+
while (base_dir / f"{candidate}.json").exists():
|
|
60
|
+
candidate = f"{base}_{counter}"
|
|
66
61
|
counter += 1
|
|
67
62
|
return candidate
|
|
68
63
|
|
|
69
64
|
|
|
65
|
+
def _host_context_prefix(host: str) -> str:
|
|
66
|
+
normalized = host.strip().lower()
|
|
67
|
+
if normalized == "claude-code":
|
|
68
|
+
return "claude"
|
|
69
|
+
return re.sub(r"[^a-z0-9]+", "_", normalized).strip("_") or "host"
|
|
70
|
+
|
|
71
|
+
def _suggest_host_context_key(host: str, runtime_context_id: str) -> str:
|
|
72
|
+
return f"{_host_context_prefix(host)}_{runtime_context_id}"
|
|
73
|
+
|
|
70
74
|
def _resolve_agent_type(role: str, agent_type: str | None) -> tuple[str, str]:
|
|
71
75
|
normalized_role = role.strip()
|
|
72
76
|
requested = agent_type.strip() if isinstance(agent_type, str) else ""
|
|
@@ -76,245 +80,119 @@ def _resolve_agent_type(role: str, agent_type: str | None) -> tuple[str, str]:
|
|
|
76
80
|
raise ValueError(f"agent-type {requested} cannot use role {normalized_role}")
|
|
77
81
|
if normalized_role in ROLE_AGENT_TYPE_ALIASES and ROLE_AGENT_TYPE_ALIASES[normalized_role] != requested:
|
|
78
82
|
raise ValueError(f"agent-type {requested} cannot use role {normalized_role}")
|
|
79
|
-
return requested, "
|
|
83
|
+
return requested, "formal"
|
|
80
84
|
if requested == GENERIC_AGENT_TYPE:
|
|
81
85
|
if normalized_role in FIXED_AGENT_TYPES or normalized_role in ROLE_AGENT_TYPE_ALIASES:
|
|
82
86
|
raise ValueError("agent-type worker requires non-fixed role")
|
|
83
|
-
return requested, "
|
|
87
|
+
return requested, "advisory"
|
|
84
88
|
raise ValueError("agent-type must be cowork-research, cowork-implement, cowork-check, or worker")
|
|
85
89
|
if normalized_role in FIXED_AGENT_TYPES:
|
|
86
|
-
return normalized_role, "
|
|
90
|
+
return normalized_role, "formal"
|
|
87
91
|
if normalized_role in ROLE_AGENT_TYPE_ALIASES:
|
|
88
|
-
return ROLE_AGENT_TYPE_ALIASES[normalized_role], "
|
|
89
|
-
return GENERIC_AGENT_TYPE, "
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def _build_dispatch_message(
|
|
93
|
-
*,
|
|
94
|
-
dispatch_id: str,
|
|
95
|
-
agent_type: str,
|
|
96
|
-
role: str,
|
|
97
|
-
task_dir: str | None,
|
|
98
|
-
context_file: Path,
|
|
99
|
-
ack_token: str,
|
|
100
|
-
dispatch_reliability: str,
|
|
101
|
-
) -> str:
|
|
102
|
-
lines = [
|
|
103
|
-
"COWORK_DISPATCH_V1",
|
|
104
|
-
f"dispatch_id: {dispatch_id}",
|
|
105
|
-
f"agent_type: {agent_type}",
|
|
106
|
-
]
|
|
107
|
-
if task_dir:
|
|
108
|
-
lines.append(f"task_dir: {task_dir}")
|
|
109
|
-
lines.extend(
|
|
110
|
-
[
|
|
111
|
-
f"role: {role}",
|
|
112
|
-
f"context_file: {_relative(get_repo_root(), context_file)}",
|
|
113
|
-
f"ack_token: {ack_token}",
|
|
114
|
-
f"dispatch_reliability: {dispatch_reliability}",
|
|
115
|
-
"COWORK_DISPATCH_END",
|
|
116
|
-
"",
|
|
117
|
-
f"Return only: COWORK_ACK {dispatch_id} {ack_token}",
|
|
118
|
-
]
|
|
119
|
-
)
|
|
120
|
-
if dispatch_reliability == "best-effort":
|
|
121
|
-
lines.insert(7, "This generic worker dispatch is best-effort only.")
|
|
122
|
-
return "\n".join(lines)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
def _build_execute_message(dispatch_id: str, goal: str, dispatch_reliability: str) -> str:
|
|
126
|
-
lines = [
|
|
127
|
-
f"EXECUTE {dispatch_id}",
|
|
128
|
-
"",
|
|
129
|
-
goal,
|
|
130
|
-
]
|
|
131
|
-
if dispatch_reliability == "best-effort":
|
|
132
|
-
lines.extend(
|
|
133
|
-
[
|
|
134
|
-
"",
|
|
135
|
-
"Treat this as best-effort.",
|
|
136
|
-
]
|
|
137
|
-
)
|
|
138
|
-
return "\n".join(lines)
|
|
92
|
+
return ROLE_AGENT_TYPE_ALIASES[normalized_role], "formal"
|
|
93
|
+
return GENERIC_AGENT_TYPE, "advisory"
|
|
139
94
|
|
|
140
95
|
|
|
141
96
|
def cmd_init(args: argparse.Namespace) -> int:
|
|
142
97
|
repo_root = get_repo_root()
|
|
143
|
-
base_dir =
|
|
144
|
-
|
|
145
|
-
subagent_dir = base_dir / subagent_id
|
|
98
|
+
base_dir = subagent_contexts_dir(repo_root)
|
|
99
|
+
runtime_context_id = _next_id(base_dir, args.title)
|
|
146
100
|
task_dir = getattr(args, "execution_task_dir", None)
|
|
147
101
|
try:
|
|
148
|
-
agent_type,
|
|
102
|
+
agent_type, dispatch_kind = _resolve_agent_type(args.role, getattr(args, "agent_type", None))
|
|
149
103
|
except ValueError as error:
|
|
150
104
|
print(f"Error: {error}", file=sys.stderr)
|
|
151
105
|
return 1
|
|
152
|
-
if
|
|
106
|
+
if dispatch_kind == "formal" and not task_dir:
|
|
153
107
|
print("Error: fixed agent dispatch requires --execution-task-dir", file=sys.stderr)
|
|
154
108
|
return 1
|
|
155
|
-
|
|
156
|
-
dispatch_id = subagent_id
|
|
157
|
-
ack_token = f"ACK:{dispatch_id}:{secrets.token_hex(8)}"
|
|
109
|
+
|
|
158
110
|
allowed_context = [{"file": item, "reason": "prompt-named context"} for item in args.allowed_context]
|
|
159
|
-
|
|
160
|
-
events_file = subagent_dir / "events.jsonl"
|
|
161
|
-
brief_file = subagent_dir / "brief.md"
|
|
162
|
-
context_file = subagent_dir / "context.json"
|
|
163
|
-
expected_ack = f"COWORK_ACK {dispatch_id} {ack_token}"
|
|
164
|
-
dispatch_message = _build_dispatch_message(
|
|
165
|
-
dispatch_id=dispatch_id,
|
|
166
|
-
agent_type=agent_type,
|
|
167
|
-
role=args.role,
|
|
168
|
-
task_dir=task_dir,
|
|
169
|
-
context_file=context_file,
|
|
170
|
-
ack_token=ack_token,
|
|
171
|
-
dispatch_reliability=dispatch_reliability,
|
|
172
|
-
)
|
|
173
|
-
execute_message = _build_execute_message(dispatch_id, args.goal or args.title, dispatch_reliability)
|
|
174
|
-
|
|
175
|
-
brief_file.write_text(
|
|
176
|
-
"\n".join(
|
|
177
|
-
[
|
|
178
|
-
dispatch_message,
|
|
179
|
-
"",
|
|
180
|
-
f"Do not execute the task until the coordinator sends EXECUTE {dispatch_id}.",
|
|
181
|
-
"",
|
|
182
|
-
f"# {args.title}",
|
|
183
|
-
"",
|
|
184
|
-
f"Role: {args.role}",
|
|
185
|
-
f"Agent type: {agent_type}",
|
|
186
|
-
f"Task directory: {task_dir or 'unknown'}",
|
|
187
|
-
f"Dispatch reliability: {dispatch_reliability}",
|
|
188
|
-
f"Source: {args.source}",
|
|
189
|
-
"",
|
|
190
|
-
"## Goal",
|
|
191
|
-
"",
|
|
192
|
-
args.goal or args.title,
|
|
193
|
-
"",
|
|
194
|
-
"## Scope Rules",
|
|
195
|
-
"",
|
|
196
|
-
"- Read only prompt-named files and allowed context unless more context is requested.",
|
|
197
|
-
"- Do not run main-session start/resume or coordinator mutation commands.",
|
|
198
|
-
"- Stop with success, needs_context, or blocked evidence.",
|
|
199
|
-
"",
|
|
200
|
-
]
|
|
201
|
-
),
|
|
202
|
-
encoding="utf-8",
|
|
203
|
-
)
|
|
204
|
-
status = {
|
|
205
|
-
"id": subagent_id,
|
|
206
|
-
"status": "active",
|
|
207
|
-
"title": args.title,
|
|
208
|
-
"role": args.role,
|
|
209
|
-
"agentType": agent_type,
|
|
210
|
-
"taskDir": task_dir,
|
|
211
|
-
"dispatchReliability": dispatch_reliability,
|
|
212
|
-
"source": args.source,
|
|
213
|
-
"goal": args.goal or args.title,
|
|
214
|
-
"updatedAt": _now(),
|
|
215
|
-
"note": "initialized",
|
|
216
|
-
"dispatchId": dispatch_id,
|
|
217
|
-
"dispatchStatus": "planned",
|
|
218
|
-
"ackToken": ack_token,
|
|
219
|
-
"expectedAck": expected_ack,
|
|
220
|
-
"dispatchMessage": dispatch_message,
|
|
221
|
-
"executeMessage": execute_message,
|
|
222
|
-
}
|
|
223
|
-
_write_json(status_file, status)
|
|
111
|
+
parent_context_key = resolve_context_key()
|
|
224
112
|
context = {
|
|
225
|
-
"
|
|
226
|
-
"
|
|
227
|
-
"
|
|
228
|
-
"
|
|
229
|
-
"
|
|
230
|
-
"
|
|
231
|
-
"dispatchId": dispatch_id,
|
|
232
|
-
"ackToken": ack_token,
|
|
233
|
-
"expectedAck": expected_ack,
|
|
234
|
-
"dispatchMessage": dispatch_message,
|
|
235
|
-
"executeMessage": execute_message,
|
|
236
|
-
"dispatchStatus": "planned",
|
|
237
|
-
"title": args.title,
|
|
113
|
+
"schema_version": 2,
|
|
114
|
+
"runtime_context_id": runtime_context_id,
|
|
115
|
+
"scope": "subagent",
|
|
116
|
+
"host": args.host,
|
|
117
|
+
"adapter": args.adapter,
|
|
118
|
+
"agent_type": agent_type,
|
|
238
119
|
"role": args.role,
|
|
239
|
-
"
|
|
240
|
-
"
|
|
241
|
-
"
|
|
242
|
-
|
|
243
|
-
"
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
"
|
|
247
|
-
"
|
|
248
|
-
"
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
"
|
|
120
|
+
"task_dir": task_dir,
|
|
121
|
+
"parent_context_key": parent_context_key,
|
|
122
|
+
"transport": {
|
|
123
|
+
"kind": "prompt",
|
|
124
|
+
"key": "cowork_runtime_context_id",
|
|
125
|
+
},
|
|
126
|
+
"assignment": {
|
|
127
|
+
"title": args.title,
|
|
128
|
+
"goal": args.goal or args.title,
|
|
129
|
+
"allowed_context": allowed_context,
|
|
130
|
+
"expected_output": args.expected_output,
|
|
131
|
+
"source": args.source,
|
|
132
|
+
},
|
|
133
|
+
"authority": {
|
|
134
|
+
"may_start_task": False,
|
|
135
|
+
"may_resume_main": False,
|
|
136
|
+
"may_archive": False,
|
|
137
|
+
"may_commit": False,
|
|
138
|
+
"may_spawn": False,
|
|
139
|
+
},
|
|
140
|
+
"status": "pending",
|
|
141
|
+
"dispatch_kind": dispatch_kind,
|
|
142
|
+
"created_at": _now(),
|
|
143
|
+
"bound_context_key": None,
|
|
144
|
+
"closed_at": None,
|
|
253
145
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
index = {"subagents": []}
|
|
263
|
-
else:
|
|
264
|
-
index = {"subagents": []}
|
|
265
|
-
subagents = index.setdefault("subagents", [])
|
|
266
|
-
if isinstance(subagents, list):
|
|
267
|
-
subagents.append(
|
|
268
|
-
{
|
|
269
|
-
"id": subagent_id,
|
|
270
|
-
"dispatchId": dispatch_id,
|
|
271
|
-
"agentType": agent_type,
|
|
272
|
-
"taskDir": task_dir,
|
|
273
|
-
"title": args.title,
|
|
274
|
-
"contextFile": _relative(repo_root, context_file),
|
|
275
|
-
"status": "active",
|
|
276
|
-
"dispatchStatus": "planned",
|
|
277
|
-
"dispatchReliability": dispatch_reliability,
|
|
278
|
-
}
|
|
279
|
-
)
|
|
280
|
-
_write_json(index_path, index)
|
|
146
|
+
write_runtime_context(repo_root, runtime_context_id, context)
|
|
147
|
+
logical_context_key = write_subagent_logical_session(
|
|
148
|
+
repo_root,
|
|
149
|
+
runtime_context_id,
|
|
150
|
+
task_dir,
|
|
151
|
+
args.host,
|
|
152
|
+
)
|
|
153
|
+
host_context_key = _suggest_host_context_key(args.host, runtime_context_id)
|
|
281
154
|
|
|
282
155
|
print(
|
|
283
156
|
json.dumps(
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
},
|
|
296
|
-
|
|
297
|
-
|
|
157
|
+
{
|
|
158
|
+
"id": runtime_context_id,
|
|
159
|
+
"runtimeContextId": runtime_context_id,
|
|
160
|
+
"cowork_runtime_context_id": runtime_context_id,
|
|
161
|
+
"hostContextKey": host_context_key,
|
|
162
|
+
"cowork_host_context_key": host_context_key,
|
|
163
|
+
"agentType": agent_type,
|
|
164
|
+
"role": args.role,
|
|
165
|
+
"taskDir": task_dir,
|
|
166
|
+
"dispatchKind": dispatch_kind,
|
|
167
|
+
"runtimeContextFile": _relative(repo_root, runtime_context_path(repo_root, runtime_context_id)),
|
|
168
|
+
"logicalSessionFile": _relative(repo_root, sessions_dir(repo_root) / f"{logical_context_key}.json"),
|
|
169
|
+
"promptTransport": (
|
|
170
|
+
f"cowork_runtime_context_id: {runtime_context_id}\n"
|
|
171
|
+
f"cowork_host_context_key: {host_context_key}"
|
|
172
|
+
),
|
|
173
|
+
"bindCommand": f".cowork-flow/run subagent bind {runtime_context_id} {host_context_key}",
|
|
174
|
+
},
|
|
175
|
+
ensure_ascii=False,
|
|
298
176
|
)
|
|
177
|
+
)
|
|
299
178
|
return 0
|
|
300
179
|
|
|
301
180
|
|
|
302
|
-
def _find_subagent(repo_root: Path,
|
|
303
|
-
|
|
304
|
-
if not
|
|
305
|
-
raise FileNotFoundError(
|
|
306
|
-
return
|
|
181
|
+
def _find_subagent(repo_root: Path, runtime_context_id: str) -> dict:
|
|
182
|
+
context = read_runtime_context(repo_root, runtime_context_id)
|
|
183
|
+
if not context:
|
|
184
|
+
raise FileNotFoundError(runtime_context_id)
|
|
185
|
+
return context
|
|
307
186
|
|
|
308
187
|
|
|
309
188
|
def cmd_status(args: argparse.Namespace) -> int:
|
|
310
189
|
repo_root = get_repo_root()
|
|
311
190
|
try:
|
|
312
|
-
|
|
191
|
+
context = _find_subagent(repo_root, args.subagent_id)
|
|
313
192
|
except FileNotFoundError:
|
|
314
193
|
print(f"Error: subagent not found: {args.subagent_id}", file=sys.stderr)
|
|
315
194
|
return 1
|
|
316
|
-
|
|
317
|
-
print(status_file.read_text(encoding="utf-8"), end="")
|
|
195
|
+
print(json.dumps(context, ensure_ascii=False, indent=2) + "\n", end="")
|
|
318
196
|
return 0
|
|
319
197
|
|
|
320
198
|
|
|
@@ -324,48 +202,84 @@ def cmd_update(args: argparse.Namespace) -> int:
|
|
|
324
202
|
return 1
|
|
325
203
|
repo_root = get_repo_root()
|
|
326
204
|
try:
|
|
327
|
-
|
|
205
|
+
context = _find_subagent(repo_root, args.subagent_id)
|
|
328
206
|
except FileNotFoundError:
|
|
329
207
|
print(f"Error: subagent not found: {args.subagent_id}", file=sys.stderr)
|
|
330
208
|
return 1
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
status["status"] = args.status
|
|
334
|
-
status["updatedAt"] = _now()
|
|
209
|
+
context["status"] = args.status
|
|
210
|
+
context["updated_at"] = _now()
|
|
335
211
|
if args.note:
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
_append_event(subagent_dir / "events.jsonl", {"time": _now(), "event": "update", "status": args.status, "note": args.note})
|
|
212
|
+
context["note"] = args.note
|
|
213
|
+
write_runtime_context(repo_root, args.subagent_id, context)
|
|
339
214
|
print(f"subagent {args.subagent_id} status={args.status}")
|
|
340
215
|
return 0
|
|
341
216
|
|
|
342
217
|
|
|
218
|
+
def cmd_bind(args: argparse.Namespace) -> int:
|
|
219
|
+
repo_root = get_repo_root()
|
|
220
|
+
existing = read_runtime_context(repo_root, args.subagent_id)
|
|
221
|
+
existing_key = existing.get("bound_context_key") if existing else None
|
|
222
|
+
if isinstance(existing_key, str) and existing_key.strip() and existing_key != args.context_key:
|
|
223
|
+
print(
|
|
224
|
+
f"Error: runtime context {args.subagent_id} already bound to {existing_key}",
|
|
225
|
+
file=sys.stderr,
|
|
226
|
+
)
|
|
227
|
+
return 1
|
|
228
|
+
context = bind_runtime_context(repo_root, args.subagent_id, args.context_key)
|
|
229
|
+
if context is None:
|
|
230
|
+
print(f"Error: cannot bind runtime context: {args.subagent_id}", file=sys.stderr)
|
|
231
|
+
return 1
|
|
232
|
+
print(json.dumps(context, ensure_ascii=False, indent=2) + "\n", end="")
|
|
233
|
+
return 0
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def cmd_close(args: argparse.Namespace) -> int:
|
|
237
|
+
repo_root = get_repo_root()
|
|
238
|
+
if not close_runtime_context(repo_root, args.subagent_id):
|
|
239
|
+
print(f"Error: subagent not found: {args.subagent_id}", file=sys.stderr)
|
|
240
|
+
return 1
|
|
241
|
+
print(f"subagent {args.subagent_id} closed")
|
|
242
|
+
return 0
|
|
243
|
+
|
|
244
|
+
|
|
343
245
|
def build_parser() -> argparse.ArgumentParser:
|
|
344
246
|
parser = argparse.ArgumentParser(
|
|
345
|
-
description="
|
|
247
|
+
description="Runtime-context scoped subagent state",
|
|
346
248
|
parents=[build_internal_execution_context_parser()],
|
|
347
249
|
)
|
|
348
250
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
349
251
|
|
|
350
|
-
init = subparsers.add_parser("init", help="Create
|
|
252
|
+
init = subparsers.add_parser("init", help="Create runtime subagent context")
|
|
351
253
|
init.add_argument("--title", required=True)
|
|
352
254
|
init.add_argument("--role", default="subagent")
|
|
353
255
|
init.add_argument("--agent-type")
|
|
354
256
|
init.add_argument("--source", default="auto")
|
|
355
257
|
init.add_argument("--goal")
|
|
258
|
+
init.add_argument("--expected-output", default="Files changed, validation commands, and blockers.")
|
|
356
259
|
init.add_argument("--allowed-context", action="append", default=[])
|
|
260
|
+
init.add_argument("--host", default="codex")
|
|
261
|
+
init.add_argument("--adapter", default="codex.spawn_agent")
|
|
357
262
|
init.set_defaults(func=cmd_init)
|
|
358
263
|
|
|
359
|
-
status = subparsers.add_parser("status", help="Print subagent
|
|
264
|
+
status = subparsers.add_parser("status", help="Print subagent runtime context")
|
|
360
265
|
status.add_argument("subagent_id")
|
|
361
266
|
status.set_defaults(func=cmd_status)
|
|
362
267
|
|
|
363
|
-
update = subparsers.add_parser("update", help="Update subagent status")
|
|
268
|
+
update = subparsers.add_parser("update", help="Update subagent runtime status")
|
|
364
269
|
update.add_argument("subagent_id")
|
|
365
270
|
update.add_argument("--status", required=True)
|
|
366
271
|
update.add_argument("--note")
|
|
367
272
|
update.set_defaults(func=cmd_update)
|
|
368
273
|
|
|
274
|
+
bind = subparsers.add_parser("bind", help="Bind host session to runtime context")
|
|
275
|
+
bind.add_argument("subagent_id")
|
|
276
|
+
bind.add_argument("context_key")
|
|
277
|
+
bind.set_defaults(func=cmd_bind)
|
|
278
|
+
|
|
279
|
+
close = subparsers.add_parser("close", help="Close subagent runtime context")
|
|
280
|
+
close.add_argument("subagent_id")
|
|
281
|
+
close.set_defaults(func=cmd_close)
|
|
282
|
+
|
|
369
283
|
return parser
|
|
370
284
|
|
|
371
285
|
|