@enderfga/claw-orchestrator 7.12.1 → 7.14.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.
Files changed (88) hide show
  1. package/README.md +14 -11
  2. package/configs/engines/README.md +20 -0
  3. package/configs/engines/dsh.json +17 -0
  4. package/configs/engines/kimi-code.json +17 -0
  5. package/dist/bin/cli.js +123 -34
  6. package/dist/bin/cli.js.map +1 -1
  7. package/dist/src/acp-server.js +4 -2
  8. package/dist/src/acp-server.js.map +1 -1
  9. package/dist/src/autoloop/dispatcher.d.ts +19 -0
  10. package/dist/src/autoloop/dispatcher.js +57 -0
  11. package/dist/src/autoloop/dispatcher.js.map +1 -1
  12. package/dist/src/autoloop/runner.d.ts +102 -2
  13. package/dist/src/autoloop/runner.js +367 -44
  14. package/dist/src/autoloop/runner.js.map +1 -1
  15. package/dist/src/autoloop/types.d.ts +17 -0
  16. package/dist/src/autoloop/types.js.map +1 -1
  17. package/dist/src/dashboard/index.html +79 -73
  18. package/dist/src/embedded-server.js +35 -7
  19. package/dist/src/embedded-server.js.map +1 -1
  20. package/dist/src/engine-presets.d.ts +7 -3
  21. package/dist/src/engine-presets.js +39 -6
  22. package/dist/src/engine-presets.js.map +1 -1
  23. package/dist/src/engine-spawn.d.ts +8 -1
  24. package/dist/src/engine-spawn.js +13 -3
  25. package/dist/src/engine-spawn.js.map +1 -1
  26. package/dist/src/index.js +78 -71
  27. package/dist/src/index.js.map +1 -1
  28. package/dist/src/kernel/agent-step.d.ts +3 -0
  29. package/dist/src/kernel/agent-step.js.map +1 -1
  30. package/dist/src/kernel/engine.d.ts +5 -2
  31. package/dist/src/kernel/engine.js +11 -12
  32. package/dist/src/kernel/engine.js.map +1 -1
  33. package/dist/src/kernel/nodes/autoloop.d.ts +32 -2
  34. package/dist/src/kernel/nodes/autoloop.js +122 -15
  35. package/dist/src/kernel/nodes/autoloop.js.map +1 -1
  36. package/dist/src/kernel/nodes/goal.d.ts +94 -0
  37. package/dist/src/kernel/nodes/goal.js +386 -0
  38. package/dist/src/kernel/nodes/goal.js.map +1 -0
  39. package/dist/src/kernel/nodes/index.d.ts +2 -1
  40. package/dist/src/kernel/nodes/index.js +3 -1
  41. package/dist/src/kernel/nodes/index.js.map +1 -1
  42. package/dist/src/kernel/templates/index.d.ts +27 -3
  43. package/dist/src/kernel/templates/index.js +78 -1
  44. package/dist/src/kernel/templates/index.js.map +1 -1
  45. package/dist/src/kernel/types.d.ts +41 -2
  46. package/dist/src/kernel/types.js.map +1 -1
  47. package/dist/src/persistent-acp-session.d.ts +76 -0
  48. package/dist/src/persistent-acp-session.js +409 -0
  49. package/dist/src/persistent-acp-session.js.map +1 -0
  50. package/dist/src/persistent-agy-session.js +1 -0
  51. package/dist/src/persistent-agy-session.js.map +1 -1
  52. package/dist/src/persistent-codex-app-session.d.ts +55 -26
  53. package/dist/src/persistent-codex-app-session.js +109 -24
  54. package/dist/src/persistent-codex-app-session.js.map +1 -1
  55. package/dist/src/persistent-codex-session.js +1 -0
  56. package/dist/src/persistent-codex-session.js.map +1 -1
  57. package/dist/src/persistent-cursor-session.js +1 -0
  58. package/dist/src/persistent-cursor-session.js.map +1 -1
  59. package/dist/src/persistent-custom-session.js +2 -0
  60. package/dist/src/persistent-custom-session.js.map +1 -1
  61. package/dist/src/persistent-gemini-session.js +1 -0
  62. package/dist/src/persistent-gemini-session.js.map +1 -1
  63. package/dist/src/persistent-grok-session.js +1 -0
  64. package/dist/src/persistent-grok-session.js.map +1 -1
  65. package/dist/src/persistent-opencode-session.js +1 -0
  66. package/dist/src/persistent-opencode-session.js.map +1 -1
  67. package/dist/src/persistent-session.js +1 -0
  68. package/dist/src/persistent-session.js.map +1 -1
  69. package/dist/src/sanitize.d.ts +10 -0
  70. package/dist/src/sanitize.js +26 -0
  71. package/dist/src/sanitize.js.map +1 -1
  72. package/dist/src/session-manager.d.ts +25 -20
  73. package/dist/src/session-manager.js +92 -43
  74. package/dist/src/session-manager.js.map +1 -1
  75. package/dist/src/types.d.ts +35 -1
  76. package/dist/src/types.js +2 -0
  77. package/dist/src/types.js.map +1 -1
  78. package/openclaw.plugin.json +1 -1
  79. package/package.json +2 -2
  80. package/skills/SKILL.md +8 -5
  81. package/skills/references/acp.md +4 -0
  82. package/skills/references/autoloop.md +27 -2
  83. package/skills/references/claude-cli-tracking.md +3 -1
  84. package/skills/references/cli.md +79 -39
  85. package/skills/references/dashboard.md +5 -1
  86. package/skills/references/multi-engine.md +39 -10
  87. package/skills/references/tools.md +77 -65
  88. package/skills/references/workflow.md +87 -13
package/README.md CHANGED
@@ -49,6 +49,7 @@ clawo runs --since 1h # every turn on every engine: model, tokens, cost, verdi
49
49
  | Switch engines mid-task | `session_handoff` replays a live conversation onto another engine in the same workspace. → [sessions](./skills/references/sessions.md) |
50
50
  | Know what it cost | A durable per-turn ledger across engines (`clawo runs`) and a `maxBudgetUsd` cap the runtime enforces on every engine. → [observability](./skills/references/observability.md) |
51
51
  | Runs that survive a crash | Durable workflows: every transition is checkpointed; resume, retry, timeout, cancel and steer come from the kernel. → [workflow](./skills/references/workflow.md) |
52
+ | Leave Codex on a goal for hours | `clawo goal` runs a Codex `/goal` on one thread until it finishes, reattaches it after a crash, resumes it after usage limits within a bound, optionally inside your own sandbox, then judges it with your checks. → [workflow](./skills/references/workflow.md#goal) |
52
53
  | Call it from your own tools | An MCP server (`clawo-mcp`), an ACP agent (`clawo acp`) for Zed, JetBrains and other ACP editors, an OpenAI-compatible endpoint, a TypeScript SDK, and the CLI. → [integrations](#integrations) |
53
54
 
54
55
  **How it relates to other tools.** claude-squad, Conductor and Vibe Kanban are interfaces for running agents side by side in worktrees; Claude Code subagents and Codex's parallel attempts stay within one vendor. Claw Orchestrator is a headless runtime: several vendors behind one API, durable workflows, and a verification step the agents cannot edit. They can be used together.
@@ -71,6 +72,7 @@ The server writes an access token to `~/.openclaw/server-token`; the CLI reads i
71
72
  ```bash
72
73
  clawo solve "Fix the failing tests" --engine claude --check "npm test" --wait
73
74
  clawo fanout "How would you speed up this test suite?" --engines claude,codex,grok --synthesize --wait
75
+ clawo goal --objective-file GOAL.md --check "npm run bench:check" --check-timeout 120 --wait
74
76
  clawo workflow list
75
77
  ```
76
78
 
@@ -99,7 +101,7 @@ const result = await manager.sendMessage('fix-tests', 'Fix the failing tests');
99
101
  | Capability | What it does | Reference |
100
102
  | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
101
103
  | **Persistent Sessions** | Long-lived coding agents kept alive across requests, with full context, tool, model, and worktree control. | [`sessions.md`](./skills/references/sessions.md) |
102
- | **Multi-Engine Runtime** | One interface over Claude Code, Codex, Antigravity (agy), Grok Build, OpenCode, and arbitrary custom CLIs. | [`multi-engine.md`](./skills/references/multi-engine.md) |
104
+ | **Multi-Engine Runtime** | One interface over Claude Code, Codex, Antigravity (agy), Grok Build, OpenCode, any Agent Client Protocol agent (Kimi Code, dsh), and arbitrary custom CLIs. | [`multi-engine.md`](./skills/references/multi-engine.md) |
103
105
  | **Session Handoff** | Move a live conversation to another engine or model — a stuck Claude session into Codex, an expensive model into a cheaper one. The new session picks up where the old one stopped, in the same workspace; the old one keeps running. | [`sessions.md`](./skills/references/sessions.md) |
104
106
  | **Multi-Agent Council** | Parallel agents in isolated git worktrees, voting on consensus until they agree. | [`council.md`](./skills/references/council.md) |
105
107
  | **Fan-out** | Run one task across N engine/model agents in parallel and collect their answers, with an optional synthesis pass — the cross-engine best-of-N / diverse-perspective primitive (no rounds or worktrees). | [`tools.md`](./skills/references/tools.md) |
@@ -165,16 +167,17 @@ block, and the cancellation and permission limitations are in
165
167
 
166
168
  ## Engine Compatibility
167
169
 
168
- | Engine | CLI | Tested Version |
169
- | ----------- | ---------- | -------------- |
170
- | Claude Code | `claude` | 2.1.307 |
171
- | Codex | `codex` | 0.163.0 |
172
- | Antigravity | `agy` | 1.4.4 |
173
- | Grok Build | `grok` | 1.0.67 |
174
- | OpenCode | `opencode` | 1.19.3 |
175
- | Custom CLI | any | — |
176
-
177
- Any coding CLI that runs as a subprocess can be wired up as a custom engine — see [`multi-engine.md`](./skills/references/multi-engine.md#custom-engine-engine-custom).
170
+ | Engine | CLI | Tested Version |
171
+ | ----------- | ---------- | ------------------------------- |
172
+ | Claude Code | `claude` | 2.1.310 |
173
+ | Codex | `codex` | 0.164.0 |
174
+ | Antigravity | `agy` | 1.4.6 |
175
+ | Grok Build | `grok` | 1.0.70 |
176
+ | OpenCode | `opencode` | 1.19.4 |
177
+ | ACP agent | any | Kimi Code 2.3.1, dsh 0.2.1-rc.2 |
178
+ | Custom CLI | any | — |
179
+
180
+ An agent that serves the Agent Client Protocol on stdio runs as `engine: 'acp'` — see [`multi-engine.md`](./skills/references/multi-engine.md#acp-agents-engine-acp). Any other coding CLI that runs as a subprocess can be wired up as a custom engine — see [`multi-engine.md`](./skills/references/multi-engine.md#custom-engine-engine-custom).
178
181
 
179
182
  ---
180
183
 
@@ -47,3 +47,23 @@ Contribution steps, the smoke script, and the full field reference are in
47
47
 
48
48
  `engine.bin` must be a command name, not an absolute path — one machine's layout
49
49
  is not a preset. Use `binEnv` so people who installed it elsewhere can point at it.
50
+
51
+ ## ACP agents
52
+
53
+ A preset can name an agent that serves the Agent Client Protocol on stdio instead
54
+ of a `CustomEngineConfig`: it carries `acp` in place of `engine`, and is started
55
+ with `engine: "acp"` (`clawo session-start <name> -e acp --acp-agent <id>`). No
56
+ protocol mapping is needed, since every ACP agent speaks the same protocol.
57
+
58
+ ```json
59
+ {
60
+ "id": "kimi-code",
61
+ "tier": "community",
62
+ "description": "…",
63
+ "provenance": { "maintainer": "…", "verifiedAgainst": "2.3.1", "verifiedOn": "2026-10-24" },
64
+ "acp": { "name": "kimi-code", "command": "kimi", "commandEnv": "KIMI_BIN", "args": ["acp"] }
65
+ }
66
+ ```
67
+
68
+ `acp.command` follows the same rule as `engine.bin`: a command name, with
69
+ `commandEnv` for an install elsewhere.
@@ -0,0 +1,17 @@
1
+ {
2
+ "id": "dsh",
3
+ "tier": "community",
4
+ "description": "DeepSeek Harness (`dsh`), served over the Agent Client Protocol (`dsh --profile acp`). Start with engine 'acp'.",
5
+ "provenance": {
6
+ "maintainer": "Enderfga",
7
+ "verifiedAgainst": "0.2.1-rc.2",
8
+ "verifiedOn": "2026-10-24",
9
+ "sourceUrl": "https://www.npmjs.com/package/@deepseek-ai/dsh"
10
+ },
11
+ "acp": {
12
+ "name": "dsh",
13
+ "command": "dsh",
14
+ "commandEnv": "DSH_BIN",
15
+ "args": ["--profile", "acp"]
16
+ }
17
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "id": "kimi-code",
3
+ "tier": "community",
4
+ "description": "Moonshot AI's Kimi Code CLI, served over the Agent Client Protocol (`kimi acp`). Start with engine 'acp'.",
5
+ "provenance": {
6
+ "maintainer": "Enderfga",
7
+ "verifiedAgainst": "2.3.1",
8
+ "verifiedOn": "2026-10-24",
9
+ "sourceUrl": "https://www.kimi.com/code/docs/en/kimi-code-cli/reference/kimi-acp"
10
+ },
11
+ "acp": {
12
+ "name": "kimi-code",
13
+ "command": "kimi",
14
+ "commandEnv": "KIMI_BIN",
15
+ "args": ["acp"]
16
+ }
17
+ }
package/dist/bin/cli.js CHANGED
@@ -67,6 +67,15 @@ async function api(path, method = 'GET', body) {
67
67
  }
68
68
  }
69
69
  // ─── CLI ─────────────────────────────────────────────────────────────────────
70
+ /**
71
+ * Print an API failure the same way every command does, and exit non-zero so a
72
+ * script can tell: a command that could not reach the server used to exit 0.
73
+ */
74
+ function fail(r) {
75
+ const error = r && typeof r === 'object' && 'error' in r ? r.error : r;
76
+ console.error(`Failed: ${String(error)}`);
77
+ process.exitCode = 1;
78
+ }
70
79
  const program = new Command();
71
80
  program.name('clawo').description('Claw Orchestrator CLI').version(getCliVersion());
72
81
  // Serve (standalone mode — no OpenClaw needed)
@@ -146,9 +155,9 @@ program
146
155
  // Session commands
147
156
  program
148
157
  .command('session-start [name]')
149
- .description('Start a persistent coding session (Claude Code, Codex, Antigravity, Grok Build, OpenCode, or a custom engine preset)')
158
+ .description('Start a persistent coding session (Claude Code, Codex, Antigravity, Grok Build, OpenCode, an ACP agent, or a custom engine preset)')
150
159
  .option('-d, --cwd <dir>', 'Working directory')
151
- .option('-e, --engine <engine>', 'Engine: claude (default), codex, codex-app, gemini, agy, cursor, grok, opencode, or custom')
160
+ .option('-e, --engine <engine>', 'Engine: claude (default), codex, codex-app, gemini, agy, cursor, grok, opencode, acp, or custom')
152
161
  .option('-m, --model <model>', 'Model to use')
153
162
  .option('--permission-mode <mode>', 'Permission mode', 'acceptEdits')
154
163
  .option('--effort <level>', 'Effort level')
@@ -174,6 +183,7 @@ program
174
183
  .option('--base-url <url>', 'Custom API endpoint (for proxy)')
175
184
  .option('--add-dir <dirs>', 'Comma-separated additional working directories')
176
185
  .option('--custom-engine <preset>', 'With -e custom: the id of a bundled engine preset (see `clawo engines`). Inline configs are not accepted here — that is what presets are for')
186
+ .option('--acp-agent <preset>', 'With -e acp: the id of a bundled ACP agent preset (see `clawo engines`)')
177
187
  .action(async (name, opts) => {
178
188
  const body = { name: name || `session-${Date.now()}` };
179
189
  if (opts.cwd)
@@ -185,6 +195,8 @@ program
185
195
  // refuses those — see `clawo engines` for what is available.
186
196
  if (opts.customEngine)
187
197
  body.customEngine = opts.customEngine;
198
+ if (opts.acpAgent)
199
+ body.acpAgent = opts.acpAgent;
188
200
  if (opts.model)
189
201
  body.model = opts.model;
190
202
  if (opts.permissionMode)
@@ -271,19 +283,26 @@ program
271
283
  if (result.ok) {
272
284
  console.log(`Session '${body.name}' started!`);
273
285
  if (result.claudeSessionId)
274
- console.log(`Claude Session ID: ${result.claudeSessionId}`);
286
+ console.log(`Engine session ID: ${result.claudeSessionId}`);
275
287
  }
276
288
  else
277
- console.error(`Failed: ${result.error}`);
289
+ fail(result.error);
278
290
  });
279
291
  program
280
- .command('session-send <name> <message>')
292
+ .command('session-send <name> [message]')
281
293
  .description('Send a message to a session')
294
+ .option('--message-file <file>', 'Read the message from a file (for long or dash-leading text)')
282
295
  .option('--effort <level>', 'Effort level')
283
296
  .option('--plan', 'Plan mode')
284
297
  .option('-t, --timeout <ms>', 'Timeout', '300000')
285
298
  .option('-s, --stream', 'Collect streaming chunks and include in output')
286
- .action(async (name, message, opts) => {
299
+ .option('--json', 'Print the whole result as JSON')
300
+ .action(async (name, messageArg, opts) => {
301
+ const message = opts.messageFile ? fs.readFileSync(opts.messageFile, 'utf-8') : messageArg;
302
+ if (!message) {
303
+ fail('give a message, or --message-file');
304
+ return;
305
+ }
287
306
  const result = await api('/session/send', 'POST', {
288
307
  name,
289
308
  message,
@@ -292,14 +311,19 @@ program
292
311
  timeout: parseInt(opts.timeout),
293
312
  stream: opts.stream || undefined,
294
313
  });
295
- if (result.ok) {
314
+ if (opts.json) {
315
+ console.log(JSON.stringify(result));
316
+ if (!result.ok)
317
+ process.exitCode = 1;
318
+ }
319
+ else if (result.ok) {
296
320
  console.log(result.output);
297
321
  if (opts.stream && Array.isArray(result.chunks) && result.chunks.length > 0) {
298
322
  console.log(`\n[${result.chunks.length} streaming chunks collected]`);
299
323
  }
300
324
  }
301
325
  else
302
- console.error(`Failed: ${result.error}`);
326
+ fail(result.error);
303
327
  });
304
328
  program
305
329
  .command('session-stop <name>')
@@ -309,7 +333,7 @@ program
309
333
  if (r.ok)
310
334
  console.log(`Session '${name}' stopped.`);
311
335
  else
312
- console.error(`Failed: ${r.error}`);
336
+ fail(r.error);
313
337
  });
314
338
  program
315
339
  .command('session-list')
@@ -317,7 +341,7 @@ program
317
341
  .action(async () => {
318
342
  const r = await api('/session/list');
319
343
  if (!r.ok) {
320
- console.error(`Failed: ${r.error}`);
344
+ fail(r.error);
321
345
  return;
322
346
  }
323
347
  const sessions = r.sessions;
@@ -331,10 +355,17 @@ program
331
355
  program
332
356
  .command('session-status <name>')
333
357
  .description('Get session status')
334
- .action(async (name) => {
358
+ .option('--json', 'Print the whole status as JSON')
359
+ .action(async (name, opts) => {
335
360
  const r = await api('/session/status', 'POST', { name });
361
+ if (opts.json) {
362
+ console.log(JSON.stringify(r));
363
+ if (!r.ok)
364
+ process.exitCode = 1;
365
+ return;
366
+ }
336
367
  if (!r.ok) {
337
- console.error(`Failed: ${r.error}`);
368
+ fail(r.error);
338
369
  return;
339
370
  }
340
371
  const s = r.stats;
@@ -343,10 +374,6 @@ program
343
374
  console.log(` Tokens: ${s.tokensIn} in / ${s.tokensOut} out`);
344
375
  console.log(` Uptime: ${s.uptime}s`);
345
376
  });
346
- /** Print an API failure the same way every command does. */
347
- function fail(r) {
348
- console.error(`Failed: ${r.error}`);
349
- }
350
377
  program
351
378
  .command('engines')
352
379
  .description('List the community engine presets bundled with this package')
@@ -372,7 +399,9 @@ program
372
399
  console.log(` verified by ${p.provenance.maintainer} against ${p.provenance.verifiedAgainst} on ${p.provenance.verifiedOn}`);
373
400
  if (p.provenance.smokeUrl)
374
401
  console.log(` smoke: ${p.provenance.smokeUrl}`);
375
- console.log(` use: clawo session-start my-session -e custom --custom-engine ${p.id}`);
402
+ console.log(p.acp
403
+ ? ` use: clawo session-start my-session -e acp --acp-agent ${p.id}`
404
+ : ` use: clawo session-start my-session -e custom --custom-engine ${p.id}`);
376
405
  console.log('');
377
406
  }
378
407
  });
@@ -401,7 +430,7 @@ program
401
430
  params.set('verified', 'false');
402
431
  const r = await api(`/runs?${params.toString()}`);
403
432
  if (!r.ok) {
404
- console.error(`Failed: ${r.error}`);
433
+ fail(r.error);
405
434
  return;
406
435
  }
407
436
  if (opts.json) {
@@ -418,7 +447,7 @@ program
418
447
  .action(async (name, pattern, opts) => {
419
448
  const r = await api('/session/grep', 'POST', { name, pattern, limit: parseInt(opts.limit) });
420
449
  if (!r.ok) {
421
- console.error(`Failed: ${r.error}`);
450
+ fail(r.error);
422
451
  return;
423
452
  }
424
453
  console.log(`Found ${r.count} match(es)`);
@@ -434,7 +463,7 @@ program
434
463
  if (r.ok)
435
464
  console.log('Compacted.');
436
465
  else
437
- console.error(`Failed: ${r.error}`);
466
+ fail(r.error);
438
467
  });
439
468
  // Agent management
440
469
  program
@@ -445,7 +474,7 @@ program
445
474
  const q = opts.cwd ? `?cwd=${encodeURIComponent(opts.cwd)}` : '';
446
475
  const r = await api(`/agents${q}`);
447
476
  if (!r.ok) {
448
- console.error(`Failed: ${r.error}`);
477
+ fail(r.error);
449
478
  return;
450
479
  }
451
480
  const agents = r.agents;
@@ -472,7 +501,7 @@ program
472
501
  if (r.ok)
473
502
  console.log(`Agent '${name}' created at: ${r.path}`);
474
503
  else
475
- console.error(`Failed: ${r.error}`);
504
+ fail(r.error);
476
505
  });
477
506
  // Skills
478
507
  program
@@ -483,7 +512,7 @@ program
483
512
  const q = opts.cwd ? `?cwd=${encodeURIComponent(opts.cwd)}` : '';
484
513
  const r = await api(`/skills${q}`);
485
514
  if (!r.ok) {
486
- console.error(`Failed: ${r.error}`);
515
+ fail(r.error);
487
516
  return;
488
517
  }
489
518
  const skills = r.skills;
@@ -512,7 +541,7 @@ program
512
541
  if (r.ok)
513
542
  console.log(`Skill '${name}' created at: ${r.path}`);
514
543
  else
515
- console.error(`Failed: ${r.error}`);
544
+ fail(r.error);
516
545
  });
517
546
  // Rules
518
547
  program
@@ -523,7 +552,7 @@ program
523
552
  const q = opts.cwd ? `?cwd=${encodeURIComponent(opts.cwd)}` : '';
524
553
  const r = await api(`/rules${q}`);
525
554
  if (!r.ok) {
526
- console.error(`Failed: ${r.error}`);
555
+ fail(r.error);
527
556
  return;
528
557
  }
529
558
  const rules = r.rules;
@@ -562,7 +591,7 @@ program
562
591
  if (r.ok)
563
592
  console.log(`Rule '${name}' created at: ${r.path}`);
564
593
  else
565
- console.error(`Failed: ${r.error}`);
594
+ fail(r.error);
566
595
  });
567
596
  // Agent teams
568
597
  program
@@ -573,7 +602,7 @@ program
573
602
  if (r.ok)
574
603
  console.log(r.response || 'No team info');
575
604
  else
576
- console.error(`Failed: ${r.error}`);
605
+ fail(r.error);
577
606
  });
578
607
  program
579
608
  .command('session-team-send <name> <teammate> <message>')
@@ -583,7 +612,7 @@ program
583
612
  if (r.ok)
584
613
  console.log(r.output || 'Sent');
585
614
  else
586
- console.error(`Failed: ${r.error}`);
615
+ fail(r.error);
587
616
  });
588
617
  // ─── workflow / verify ──────────────────────────────────────────────────────
589
618
  program
@@ -635,7 +664,15 @@ program
635
664
  if (run.error)
636
665
  console.log(`error: ${String(run.error)}`);
637
666
  for (const node of Object.values(run.nodes)) {
638
- console.log(` ${String(node.id).padEnd(18)} ${String(node.kind).padEnd(11)} ${node.state}`);
667
+ console.log(` ${String(node.id).padEnd(18)} ${String(node.kind).padEnd(11)} ${String(node.state)}`);
668
+ // A goal runs for hours; its checkpoint is the only progress there is.
669
+ const g = node.kind === 'goal' ? node.data : undefined;
670
+ if (g) {
671
+ const err = g.lastError;
672
+ console.log(` goal ${String(g.status)} ${String(g.tokensUsed)} tokens ${String(g.timeUsedSeconds)}s ` +
673
+ `resumes ${String(g.resumes)} thread ${String(g.threadId)}` +
674
+ (err ? `\n last error: ${err.code ?? err.message}` : ''));
675
+ }
639
676
  }
640
677
  return;
641
678
  }
@@ -716,19 +753,19 @@ function collect(value, previous) {
716
753
  * `--check "node --test"` → a command check. Split on whitespace and run as
717
754
  * argv, never through a shell; anything that needs quoting goes in `--contract`.
718
755
  */
719
- function buildContract(checks, contractFile) {
756
+ function buildContract(checks, contractFile, timeoutMs) {
720
757
  if (contractFile) {
721
758
  const raw = JSON.parse(fs.readFileSync(contractFile, 'utf-8'));
722
759
  if (!checks.length)
723
760
  return raw;
724
- return { ...raw, checks: [...(raw.checks ?? []), ...checksFromFlags(checks)] };
761
+ return { ...raw, checks: [...(raw.checks ?? []), ...checksFromFlags(checks, timeoutMs)] };
725
762
  }
726
- return checks.length ? { checks: checksFromFlags(checks) } : undefined;
763
+ return checks.length ? { checks: checksFromFlags(checks, timeoutMs) } : undefined;
727
764
  }
728
- function checksFromFlags(checks) {
765
+ function checksFromFlags(checks, timeoutMs) {
729
766
  return checks.map((c, i) => {
730
767
  const [cmd, ...args] = c.trim().split(/\s+/);
731
- return { id: `check-${i + 1}`, type: 'command', cmd, args };
768
+ return { id: `check-${i + 1}`, type: 'command', cmd, args, ...(timeoutMs ? { timeoutMs } : {}) };
732
769
  });
733
770
  }
734
771
  const RUN_DONE = new Set(['completed', 'failed', 'cancelled']);
@@ -946,5 +983,57 @@ program
946
983
  if (node?.output)
947
984
  console.log(`\n${node.output}`);
948
985
  });
986
+ program
987
+ .command('goal')
988
+ .description('Run a Codex /goal on one codex-app thread, then have the runtime check it: goal → verify → repair')
989
+ .argument('[objective]', 'What Codex should achieve (or use --objective-file)')
990
+ .option('--objective-file <file>', 'Read the objective from a file')
991
+ .option('-m, --model <model>', "Codex model (default: codex's own)")
992
+ .option('--effort <level>', 'Reasoning effort')
993
+ .option('--sandbox <mode>', "Codex's own sandbox: read-only | workspace-write | danger-full-access")
994
+ .option('--launcher <arg>', 'Run codex under this command, one argv element per flag, e.g. --launcher ./sandbox.sh --launcher task --launcher "0 1"', collect, [])
995
+ .option('--thread <id>', 'Start on this existing codex thread: its goal is replaced, its history kept')
996
+ .option('--token-budget <n>', 'Tokens the goal may spend before it stops as budgetLimited')
997
+ .option('--goal-timeout <minutes>', 'Wall-clock budget for one goal pass (default: none)')
998
+ .option('-c, --check <command>', 'Acceptance check the runtime runs after the goal (repeatable)', collect, [])
999
+ .option('--check-timeout <minutes>', 'Timeout for each --check (default 10)')
1000
+ .option('--contract <file>', 'Acceptance contract JSON file (merged with --check)')
1001
+ .option('--max-repairs <n>', 'Times a red verdict sends the goal back to the same thread', '1')
1002
+ .option('--cwd <dir>', 'Project directory', process.cwd())
1003
+ .option('-w, --wait', 'Follow the run until it ends; exit 1 unless it completes')
1004
+ .option('--json', 'Machine-readable output')
1005
+ .action(async (objectiveArg, opts) => {
1006
+ const { goalWorkflow } = await import('../src/kernel/templates/index.js');
1007
+ const { GOAL_OBJECTIVE_MAX_CHARS } = await import('../src/kernel/nodes/goal.js');
1008
+ const objective = opts.objectiveFile ? fs.readFileSync(opts.objectiveFile, 'utf-8').trim() : objectiveArg?.trim();
1009
+ if (!objective) {
1010
+ console.error('Error: give an objective, or --objective-file');
1011
+ process.exitCode = 1;
1012
+ return;
1013
+ }
1014
+ if ([...objective].length > GOAL_OBJECTIVE_MAX_CHARS) {
1015
+ console.error(`Error: the objective is ${[...objective].length} characters; Codex accepts at most ${GOAL_OBJECTIVE_MAX_CHARS}. ` +
1016
+ 'Put the full brief in a file in the working directory and name that file in the objective.');
1017
+ process.exitCode = 1;
1018
+ return;
1019
+ }
1020
+ const minutes = (v) => (v ? Math.max(1, parseFloat(v)) * 60_000 : undefined);
1021
+ const cwd = path.resolve(opts.cwd);
1022
+ const contract = buildContract(opts.check, opts.contract, minutes(opts.checkTimeout));
1023
+ const spec = goalWorkflow({
1024
+ task: objective,
1025
+ cwd,
1026
+ contract: contract,
1027
+ model: opts.model,
1028
+ effort: opts.effort,
1029
+ sandboxMode: opts.sandbox,
1030
+ launcher: opts.launcher.length ? opts.launcher : undefined,
1031
+ threadId: opts.thread,
1032
+ tokenBudget: opts.tokenBudget ? parseInt(opts.tokenBudget, 10) : undefined,
1033
+ goalTimeoutMs: minutes(opts.goalTimeout),
1034
+ maxRepairs: Math.max(0, parseInt(opts.maxRepairs, 10) || 0),
1035
+ });
1036
+ await startWorkflow(spec, cwd, contract, opts);
1037
+ });
949
1038
  program.parse();
950
1039
  //# sourceMappingURL=cli.js.map