cowork-flow 0.0.46 → 0.0.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +86 -22
  3. package/package.json +3 -1
  4. package/src/commands/init.js +0 -83
  5. package/src/commands/sync.js +6 -0
  6. package/src/commands/update.js +59 -6
  7. package/src/lib/copy-template.js +70 -49
  8. package/src/lib/host-assets.js +185 -27
  9. package/src/lib/plan-applier.js +47 -0
  10. package/template/.claude/hooks/inject-workflow-state.py +7 -0
  11. package/template/.codex/hooks/inject-workflow-state.py +7 -0
  12. package/template/.cowork-flow/.version +1 -1
  13. package/template/.cowork-flow/config.yaml +0 -11
  14. package/template/.cowork-flow/scripts/adapters/cli/init_developer.py +0 -1
  15. package/template/.cowork-flow/scripts/adapters/cli/subagent.py +7 -4
  16. package/template/.cowork-flow/scripts/adapters/cli/task.py +34 -669
  17. package/template/.cowork-flow/scripts/adapters/cli/task_archive_commands.py +3 -134
  18. package/template/.cowork-flow/scripts/adapters/cli/task_context_commands.py +7 -1
  19. package/template/.cowork-flow/scripts/adapters/cli/task_lifecycle_commands.py +503 -0
  20. package/template/.cowork-flow/scripts/adapters/cli/task_navigation.py +151 -2
  21. package/template/.cowork-flow/scripts/adapters/cli/task_next_runner.py +229 -0
  22. package/template/.cowork-flow/scripts/adapters/cli/task_parser.py +1 -1
  23. package/template/.cowork-flow/scripts/adapters/cli/task_tree_commands.py +68 -0
  24. package/template/.cowork-flow/scripts/adapters/git/git_context.py +16 -193
  25. package/template/.cowork-flow/scripts/adapters/host/host_manifest.py +102 -0
  26. package/template/.cowork-flow/scripts/infra/config.py +0 -17
  27. package/template/.cowork-flow/scripts/infra/developer_profile.py +0 -95
  28. package/template/.cowork-flow/scripts/infra/paths.py +0 -79
  29. package/template/.cowork-flow/scripts/infra/skill_manifest.py +25 -2
  30. package/template/.cowork-flow/scripts/kernel/workflow_route.py +15 -15
  31. package/template/.cowork-flow/scripts/run.py +0 -5
  32. package/template/.cowork-flow/scripts/services/lifecycle_checks.py +163 -65
  33. package/template/.cowork-flow/scripts/services/readiness.py +3 -170
  34. package/template/.cowork-flow/scripts/services/task_archive.py +16 -11
  35. package/template/.cowork-flow/scripts/services/task_context.py +208 -40
  36. package/template/.cowork-flow/scripts/services/task_creation.py +32 -1
  37. package/template/.cowork-flow/scripts/services/task_routing.py +80 -56
  38. package/template/.cowork-flow/scripts/services/task_utils.py +0 -146
  39. package/template/.cowork-flow/scripts/services/workflow_runtime.py +1 -1
  40. package/template/.cowork-flow/spec/contracts/capabilities.md +23 -0
  41. package/template/.cowork-flow/spec/contracts/decision-anchor.md +2 -2
  42. package/template/.cowork-flow/spec/contracts/index.md +1 -0
  43. package/template/.cowork-flow/spec/contracts/party-mode-v2-board.md +20 -0
  44. package/template/.cowork-flow/spec/contracts/plan-binding.md +50 -0
  45. package/template/.cowork-flow/spec/contracts/skill-owned-actions.md +40 -7
  46. package/template/.cowork-flow/spec/guides/index.md +3 -1
  47. package/template/.cowork-flow/spec/index.md +5 -4
  48. package/template/.cowork-flow/spec/references/definition-of-done.md +2 -3
  49. package/template/.cowork-flow/spec/references/orchestration-patterns.md +1 -1
  50. package/template/.cowork-flow/spec/runtime/contract-registry.json +14 -0
  51. package/template/.cowork-flow/spec/runtime/host-assets.json +73 -3
  52. package/template/.cowork-flow/spec/schemas/host-assets.schema.json +119 -3
  53. package/template/.zcode/.zcode-plugin/plugin.json +1 -1
  54. package/template/skills/adversarial-review/SKILL.md +17 -0
  55. package/template/skills/batch-execution/SKILL.md +37 -3
  56. package/template/skills/batch-execution/scripts/batch_action.py +4 -0
  57. package/template/skills/batch-execution/scripts/batch_execution.py +154 -15
  58. package/template/skills/brainstorming/SKILL.md +8 -7
  59. package/template/skills/party-mode/scripts/party_action_contract.py +113 -0
  60. package/template/skills/party-mode/scripts/party_board_store.py +148 -0
  61. package/template/skills/party-mode/scripts/party_mode_v2.py +246 -142
  62. package/template/skills/runtime-health/SKILL.md +18 -1
  63. package/template/skills/runtime-health/scripts/doctor.py +225 -11
  64. package/template/skills/task-planning/SKILL.md +105 -61
  65. package/template/skills/task-review/SKILL.md +9 -4
  66. package/template/skills/task-review/manifest.json +4 -2
  67. package/template/skills/task-review/scripts/review_check.py +142 -18
  68. package/template/.cowork-flow/changes/archive/.gitkeep +0 -1
  69. package/template/.cowork-flow/scripts/adapters/cli/add_session.py +0 -465
  70. package/template/.cowork-flow/scripts/adapters/cli/change.py +0 -412
  71. package/template/.cowork-flow/workspace/index.md +0 -102
  72. package/template/.zcode/scaffold/AGENTS.md +0 -145
  73. package/template/.zcode/scaffold/CLAUDE.md +0 -5
@@ -12,6 +12,19 @@ export const hostAssetManifestPath = join(
12
12
  'host-assets.json'
13
13
  );
14
14
 
15
+ const REQUIRED_HOST_NEUTRAL_CAPABILITIES = [
16
+ 'task_action',
17
+ 'subagent_dispatch',
18
+ 'file_write',
19
+ 'party_board_action'
20
+ ];
21
+ const REQUIRED_CAPABILITY_MATRIX_HOSTS = [
22
+ 'codex',
23
+ 'claude-code',
24
+ 'opencode',
25
+ 'zcode'
26
+ ];
27
+
15
28
 
16
29
  export function loadHostAssetManifest(path = hostAssetManifestPath) {
17
30
  const manifest = JSON.parse(readFileSync(path, 'utf8'));
@@ -41,6 +54,7 @@ export function createHostRegistry(manifest) {
41
54
  }
42
55
  }
43
56
  const platformIds = platforms.map((platform) => platform.id);
57
+ const capabilityMatrix = normalizeCapabilityMatrix(manifest.capabilityMatrix);
44
58
  const syncPolicy = {
45
59
  protectedFiles: manifest.syncPolicy.protectedFiles.map(normalizePath),
46
60
  protectedPrefixes: manifest.syncPolicy.protectedPrefixes.map(normalizePath),
@@ -49,6 +63,20 @@ export function createHostRegistry(manifest) {
49
63
  managedBlockFiles: manifest.syncPolicy.managedBlockFiles.map(normalizePath),
50
64
  obsoleteFiles: manifest.syncPolicy.obsoleteFiles.map(normalizePath)
51
65
  };
66
+ const assetPrefixes = unique(
67
+ platforms.flatMap((platform) => platform.assetPrefixes)
68
+ );
69
+ const skillTargets = unique(
70
+ platforms
71
+ .map((platform) => platform.skillTarget)
72
+ .filter(
73
+ (value) => typeof value === 'string' && value.length > 0
74
+ )
75
+ .map(normalizePath)
76
+ );
77
+ const protectedFiles = new Set(syncPolicy.protectedFiles);
78
+ const safeFiles = new Set(syncPolicy.safeFiles);
79
+ const managedBlockFiles = new Set(syncPolicy.managedBlockFiles);
52
80
 
53
81
  function parsePlatformSelection(values) {
54
82
  const rawValues = Array.isArray(values) ? values : [values];
@@ -92,20 +120,57 @@ export function createHostRegistry(manifest) {
92
120
  ) {
93
121
  return false;
94
122
  }
95
- const owners = platforms.filter((platform) => (
96
- platform.assetFiles.includes(normalized)
97
- || platform.assetPrefixes.some(
98
- (prefix) => normalized.startsWith(prefix)
99
- )
100
- ));
123
+ const owners = assetOwners(normalized);
101
124
  if (owners.length === 0) {
102
125
  return true;
103
126
  }
104
127
  return owners.some(
105
- (platform) => selectedPlatforms.includes(platform.id)
128
+ (platformId) => selectedPlatforms.includes(platformId)
106
129
  );
107
130
  }
108
131
 
132
+ function assetOwners(relativePath) {
133
+ const normalized = normalizePath(relativePath);
134
+ return platforms
135
+ .filter((platform) => ownsAsset(platform, normalized))
136
+ .map((platform) => platform.id);
137
+ }
138
+
139
+ function isKnownPlatformAsset(relativePath) {
140
+ return assetOwners(relativePath).length > 0;
141
+ }
142
+
143
+ function isSafeSyncFile(relativePath) {
144
+ const normalized = normalizePath(relativePath);
145
+ return safeFiles.has(normalized)
146
+ || syncPolicy.safePrefixes.some((prefix) => normalized.startsWith(prefix))
147
+ || assetPrefixes.some((prefix) => normalized.startsWith(prefix))
148
+ || skillTargets.some((target) => normalized.startsWith(`${target}/`))
149
+ || normalized.endsWith('/.gitkeep');
150
+ }
151
+
152
+ function isProtectedSyncFile(relativePath) {
153
+ const normalized = normalizePath(relativePath);
154
+ if (safeFiles.has(normalized)) {
155
+ return false;
156
+ }
157
+ return protectedFiles.has(normalized)
158
+ || syncPolicy.protectedPrefixes.some((prefix) => normalized.startsWith(prefix));
159
+ }
160
+
161
+ function isManagedBlockFile(relativePath) {
162
+ return managedBlockFiles.has(normalizePath(relativePath));
163
+ }
164
+
165
+ function obsoleteSyncFiles() {
166
+ return [...syncPolicy.obsoleteFiles].sort((left, right) => {
167
+ if (left.length !== right.length) {
168
+ return left.length - right.length;
169
+ }
170
+ return left.localeCompare(right);
171
+ });
172
+ }
173
+
109
174
  async function detectInstalledPlatforms(targetDir, pathExists) {
110
175
  const installed = [];
111
176
  for (const platform of platforms) {
@@ -123,20 +188,13 @@ export function createHostRegistry(manifest) {
123
188
  manifest,
124
189
  platforms,
125
190
  platformIds,
191
+ capabilityMatrix,
126
192
  syncPolicy,
127
- assetPrefixes: unique(
128
- platforms.flatMap((platform) => platform.assetPrefixes)
129
- ),
130
- skillTargets: unique(
131
- platforms
132
- .map((platform) => platform.skillTarget)
133
- .filter(
134
- (value) => typeof value === 'string' && value.length > 0
135
- )
136
- .map(normalizePath)
137
- ),
193
+ assetPrefixes,
194
+ skillTargets,
138
195
  parsePlatformSelection,
139
196
  shouldInclude,
197
+ assetOwners,
140
198
  detectInstalledPlatforms,
141
199
  platform(platformId) {
142
200
  return byId.get(platformId) ?? null;
@@ -144,22 +202,29 @@ export function createHostRegistry(manifest) {
144
202
  platformLabel(platformId) {
145
203
  return byId.get(platformId)?.displayName ?? platformId;
146
204
  },
205
+ hostCapability(hostId, capability) {
206
+ return capabilityMatrix.hosts[hostId]?.[capability] ?? null;
207
+ },
147
208
  skillDestination(platformId) {
148
209
  return byId.get(platformId)?.skillTarget ?? null;
149
210
  },
150
- isKnownPlatformAsset(relativePath) {
151
- const normalized = normalizePath(relativePath);
152
- return platforms.some((platform) => (
153
- platform.assetFiles.includes(normalized)
154
- || platform.assetPrefixes.some(
155
- (prefix) => normalized.startsWith(prefix)
156
- )
157
- ));
158
- }
211
+ isKnownPlatformAsset,
212
+ isSafeSyncFile,
213
+ isProtectedSyncFile,
214
+ isManagedBlockFile,
215
+ obsoleteSyncFiles
159
216
  };
160
217
  }
161
218
 
162
219
 
220
+ function ownsAsset(platform, normalizedPath) {
221
+ return platform.assetFiles.includes(normalizedPath)
222
+ || platform.assetPrefixes.some(
223
+ (prefix) => normalizedPath.startsWith(prefix)
224
+ );
225
+ }
226
+
227
+
163
228
  function validateManifest(manifest) {
164
229
  if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
165
230
  throw new Error('Host Asset Manifest must be an object');
@@ -179,6 +244,7 @@ function validateManifest(manifest) {
179
244
  'Host Asset Manifest excludedPrefixes must be an array'
180
245
  );
181
246
  }
247
+ validateCapabilityMatrix(manifest);
182
248
  if (!manifest.syncPolicy || typeof manifest.syncPolicy !== 'object') {
183
249
  throw new Error('Host Asset Manifest syncPolicy must be an object');
184
250
  }
@@ -217,6 +283,91 @@ function validateManifest(manifest) {
217
283
  }
218
284
 
219
285
 
286
+ function validateCapabilityMatrix(manifest) {
287
+ if (!Array.isArray(manifest.capabilityValues) || manifest.capabilityValues.length === 0) {
288
+ throw new Error('Host Asset Manifest capabilityValues must be a non-empty array');
289
+ }
290
+ const allowed = new Set(manifest.capabilityValues);
291
+ for (const value of allowed) {
292
+ if (typeof value !== 'string' || value.length === 0) {
293
+ throw new Error('Host Asset Manifest capabilityValues entries must be strings');
294
+ }
295
+ }
296
+ const matrix = manifest.capabilityMatrix;
297
+ if (!matrix || typeof matrix !== 'object' || Array.isArray(matrix)) {
298
+ throw new Error('Host Asset Manifest capabilityMatrix must be an object');
299
+ }
300
+ if (!arraysEqual(matrix.required, REQUIRED_HOST_NEUTRAL_CAPABILITIES)) {
301
+ throw new Error(
302
+ `Host Asset Manifest capabilityMatrix.required must be ${REQUIRED_HOST_NEUTRAL_CAPABILITIES.join(', ')}`
303
+ );
304
+ }
305
+ if (!matrix.hosts || typeof matrix.hosts !== 'object' || Array.isArray(matrix.hosts)) {
306
+ throw new Error('Host Asset Manifest capabilityMatrix.hosts must be an object');
307
+ }
308
+ const requiredHosts = new Set([
309
+ ...REQUIRED_CAPABILITY_MATRIX_HOSTS,
310
+ ...manifest.platforms.map((platform) => platform.id)
311
+ ]);
312
+ for (const hostId of [...requiredHosts].sort()) {
313
+ if (!matrix.hosts[hostId]) {
314
+ throw new Error(`Host Asset Manifest capability matrix missing host: ${hostId}`);
315
+ }
316
+ }
317
+ for (const [hostId, capabilities] of Object.entries(matrix.hosts)) {
318
+ if (!capabilities || typeof capabilities !== 'object' || Array.isArray(capabilities)) {
319
+ throw new Error(`Host Asset Manifest capabilityMatrix host ${hostId} must be an object`);
320
+ }
321
+ for (const key of Object.keys(capabilities)) {
322
+ if (!REQUIRED_HOST_NEUTRAL_CAPABILITIES.includes(key)) {
323
+ throw new Error(`Host Asset Manifest unknown host-neutral capability ${hostId}:${key}`);
324
+ }
325
+ }
326
+ for (const capability of REQUIRED_HOST_NEUTRAL_CAPABILITIES) {
327
+ const declaration = capabilities[capability];
328
+ if (!declaration || typeof declaration !== 'object' || Array.isArray(declaration)) {
329
+ throw new Error(`Host Asset Manifest missing host-neutral capability ${hostId}:${capability}`);
330
+ }
331
+ if (!allowed.has(declaration.status)) {
332
+ throw new Error(
333
+ `Host Asset Manifest illegal host-neutral capability ${hostId}:${capability}=${declaration.status}`
334
+ );
335
+ }
336
+ if (declaration.fallback !== undefined && (
337
+ typeof declaration.fallback !== 'string' || declaration.fallback.length === 0
338
+ )) {
339
+ throw new Error(
340
+ `Host Asset Manifest capability fallback must be a non-empty string: ${hostId}:${capability}`
341
+ );
342
+ }
343
+ if (declaration.status === 'unsupported' && !declaration.fallback) {
344
+ throw new Error(
345
+ `Host Asset Manifest unsupported capability requires fallback: ${hostId}:${capability}`
346
+ );
347
+ }
348
+ }
349
+ }
350
+ }
351
+
352
+
353
+ function normalizeCapabilityMatrix(matrix) {
354
+ return {
355
+ required: [...matrix.required],
356
+ hosts: Object.fromEntries(
357
+ Object.entries(matrix.hosts).map(([hostId, capabilities]) => [
358
+ hostId,
359
+ Object.fromEntries(
360
+ Object.entries(capabilities).map(([capability, declaration]) => [
361
+ capability,
362
+ { ...declaration }
363
+ ])
364
+ )
365
+ ])
366
+ )
367
+ };
368
+ }
369
+
370
+
220
371
  function normalizePath(value) {
221
372
  return String(value).replaceAll('\\', '/');
222
373
  }
@@ -227,5 +378,12 @@ function unique(values) {
227
378
  }
228
379
 
229
380
 
381
+ function arraysEqual(left, right) {
382
+ return Array.isArray(left)
383
+ && left.length === right.length
384
+ && left.every((value, index) => value === right[index]);
385
+ }
386
+
387
+
230
388
  export const hostAssetManifest = loadHostAssetManifest();
231
389
  export const hostRegistry = createHostRegistry(hostAssetManifest);
@@ -226,6 +226,53 @@ function validateMetadata(metadata, transactionRoot, resolvedTarget) {
226
226
  }
227
227
  }
228
228
 
229
+ export async function inspectAssetTransactions(
230
+ targetDir,
231
+ { fileSystem = defaultFileSystem } = {}
232
+ ) {
233
+ const fs = fileSystem ?? defaultFileSystem;
234
+ const resolvedTarget = resolve(targetDir);
235
+ const parentDir = dirname(resolvedTarget);
236
+ let entries;
237
+ try {
238
+ entries = await fs.readdir(parentDir, { withFileTypes: true });
239
+ } catch (error) {
240
+ if (error && error.code === 'ENOENT') {
241
+ return [];
242
+ }
243
+ throw error;
244
+ }
245
+
246
+ const prefix = transactionPrefix(resolvedTarget);
247
+ const transactions = [];
248
+ for (const entry of entries) {
249
+ if (!entry.isDirectory() || !entry.name.startsWith(prefix)) {
250
+ continue;
251
+ }
252
+ const transactionRoot = join(parentDir, entry.name);
253
+ try {
254
+ const metadata = await readMetadata(fs, transactionRoot);
255
+ validateMetadata(metadata, transactionRoot, resolvedTarget);
256
+ transactions.push({
257
+ path: transactionRoot,
258
+ status: metadata.status,
259
+ actionCount: metadata.actions.length,
260
+ pending: metadata.status !== 'committed'
261
+ });
262
+ } catch (error) {
263
+ transactions.push({
264
+ path: transactionRoot,
265
+ status: 'unknown',
266
+ actionCount: null,
267
+ pending: true,
268
+ error: error instanceof Error ? error.message : String(error)
269
+ });
270
+ }
271
+ }
272
+
273
+ return transactions.sort((left, right) => left.path.localeCompare(right.path));
274
+ }
275
+
229
276
  export async function recoverAssetTransactions(
230
277
  targetDir,
231
278
  { fileSystem = defaultFileSystem } = {}
@@ -10,6 +10,12 @@ from pathlib import Path
10
10
  from typing import Any
11
11
 
12
12
 
13
+ def _configure_stdio() -> None:
14
+ for stream in (sys.stdout, sys.stderr):
15
+ if hasattr(stream, "reconfigure"):
16
+ stream.reconfigure(encoding="utf-8", errors="replace")
17
+
18
+
13
19
  def _read_input() -> dict[str, Any]:
14
20
  raw = sys.stdin.buffer.read()
15
21
  if not raw.strip():
@@ -90,4 +96,5 @@ def main() -> int:
90
96
 
91
97
 
92
98
  if __name__ == "__main__":
99
+ _configure_stdio()
93
100
  raise SystemExit(main())
@@ -10,6 +10,12 @@ from pathlib import Path
10
10
  from typing import Any
11
11
 
12
12
 
13
+ def _configure_stdio() -> None:
14
+ for stream in (sys.stdout, sys.stderr):
15
+ if hasattr(stream, "reconfigure"):
16
+ stream.reconfigure(encoding="utf-8", errors="replace")
17
+
18
+
13
19
  def _read_input() -> dict[str, Any]:
14
20
  raw = sys.stdin.buffer.read()
15
21
  if not raw.strip():
@@ -87,4 +93,5 @@ def main() -> int:
87
93
 
88
94
 
89
95
  if __name__ == "__main__":
96
+ _configure_stdio()
90
97
  raise SystemExit(main())
@@ -1 +1 @@
1
- 0.0.46
1
+ 0.0.48
@@ -3,17 +3,6 @@
3
3
  #
4
4
  # All values have sensible defaults. Only override what you need.
5
5
 
6
- #-------------------------------------------------------------------------------
7
- # Session Recording
8
- #-------------------------------------------------------------------------------
9
-
10
- # Commit message used when auto-committing journal/index changes
11
- # after running add_session.py
12
- session_commit_message: "chore: record journal"
13
-
14
- # Maximum lines per journal file before rotating to a new one
15
- max_journal_lines: 2000
16
-
17
6
  #-------------------------------------------------------------------------------
18
7
  # Codex Runtime
19
8
  #-------------------------------------------------------------------------------
@@ -7,7 +7,6 @@ Usage:
7
7
 
8
8
  This creates:
9
9
  - .cowork-flow/.developer file with developer info
10
- - .cowork-flow/workspace/<name>/ directory structure
11
10
  """
12
11
 
13
12
  from __future__ import annotations
@@ -98,6 +98,9 @@ def _host_context_prefix(host: str) -> str:
98
98
  def _suggest_host_context_key(host: str, runtime_context_id: str) -> str:
99
99
  return f"{_host_context_prefix(host)}_{runtime_context_id}"
100
100
 
101
+ def _print_runtime_error(error: RuntimeContextError) -> None:
102
+ print(f"Error: {error.code}: {error.detail}", file=sys.stderr)
103
+
101
104
  def _resolve_agent_type(role: str, agent_type: str | None) -> tuple[str, str]:
102
105
  normalized_role = role.strip()
103
106
  requested = agent_type.strip() if isinstance(agent_type, str) else ""
@@ -178,7 +181,7 @@ def cmd_init(args: argparse.Namespace) -> int:
178
181
  context,
179
182
  )
180
183
  except RuntimeContextError as error:
181
- print(f"Error: {error.detail}", file=sys.stderr)
184
+ _print_runtime_error(error)
182
185
  return 1
183
186
  logical_context_key = initialized.logical_context_key
184
187
  host_context_key = _suggest_host_context_key(host, runtime_context_id)
@@ -244,7 +247,7 @@ def cmd_update(args: argparse.Namespace) -> int:
244
247
  note=args.note,
245
248
  )
246
249
  except RuntimeContextError as error:
247
- print(f"Error: {error.detail}", file=sys.stderr)
250
+ _print_runtime_error(error)
248
251
  return 1
249
252
  if context is None:
250
253
  print(f"Error: subagent not found: {args.subagent_id}", file=sys.stderr)
@@ -261,7 +264,7 @@ def cmd_bind(args: argparse.Namespace) -> int:
261
264
  args.context_key,
262
265
  )
263
266
  except RuntimeContextError as error:
264
- print(f"Error: {error.detail}", file=sys.stderr)
267
+ _print_runtime_error(error)
265
268
  return 1
266
269
  if context is None:
267
270
  print(f"Error: cannot bind runtime context: {args.subagent_id}", file=sys.stderr)
@@ -275,7 +278,7 @@ def cmd_close(args: argparse.Namespace) -> int:
275
278
  try:
276
279
  closed = RuntimeContextService(repo_root).close(args.subagent_id)
277
280
  except RuntimeContextError as error:
278
- print(f"Error: {error.detail}", file=sys.stderr)
281
+ _print_runtime_error(error)
279
282
  return 1
280
283
  if not closed:
281
284
  print(f"Error: subagent not found: {args.subagent_id}", file=sys.stderr)