@enderfga/claw-orchestrator 7.13.0 → 7.15.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 (143) hide show
  1. package/README.md +163 -137
  2. package/configs/autoloop-reviewer-prompt.md +1 -1
  3. package/configs/engines/README.md +45 -5
  4. package/configs/engines/dsh.json +17 -0
  5. package/configs/engines/kimi-code.json +17 -0
  6. package/dist/bin/acp-server.js +7 -12
  7. package/dist/bin/acp-server.js.map +1 -1
  8. package/dist/bin/cli.js +127 -67
  9. package/dist/bin/cli.js.map +1 -1
  10. package/dist/bin/mcp-server.js +2 -54
  11. package/dist/bin/mcp-server.js.map +1 -1
  12. package/dist/src/acp-server.d.ts +17 -9
  13. package/dist/src/acp-server.js +151 -30
  14. package/dist/src/acp-server.js.map +1 -1
  15. package/dist/src/autoloop/agent-tools.d.ts +2 -0
  16. package/dist/src/autoloop/agent-tools.js +7 -0
  17. package/dist/src/autoloop/agent-tools.js.map +1 -1
  18. package/dist/src/autoloop/dispatcher.d.ts +6 -0
  19. package/dist/src/autoloop/dispatcher.js +23 -1
  20. package/dist/src/autoloop/dispatcher.js.map +1 -1
  21. package/dist/src/autoloop/planner-tools.js +2 -0
  22. package/dist/src/autoloop/planner-tools.js.map +1 -1
  23. package/dist/src/council.d.ts +8 -0
  24. package/dist/src/council.js +45 -12
  25. package/dist/src/council.js.map +1 -1
  26. package/dist/src/dashboard/index.html +28 -14
  27. package/dist/src/embedded-server.js +34 -10
  28. package/dist/src/embedded-server.js.map +1 -1
  29. package/dist/src/engine-presets.d.ts +7 -3
  30. package/dist/src/engine-presets.js +39 -6
  31. package/dist/src/engine-presets.js.map +1 -1
  32. package/dist/src/engine-spawn.js +3 -1
  33. package/dist/src/engine-spawn.js.map +1 -1
  34. package/dist/src/fanout.d.ts +13 -3
  35. package/dist/src/fanout.js +10 -0
  36. package/dist/src/fanout.js.map +1 -1
  37. package/dist/src/inbox-manager.d.ts +9 -0
  38. package/dist/src/inbox-manager.js +8 -2
  39. package/dist/src/inbox-manager.js.map +1 -1
  40. package/dist/src/index.d.ts +2 -0
  41. package/dist/src/index.js +136 -52
  42. package/dist/src/index.js.map +1 -1
  43. package/dist/src/kernel/conditions.d.ts +1 -1
  44. package/dist/src/kernel/conditions.js +1 -1
  45. package/dist/src/kernel/engine.d.ts +28 -8
  46. package/dist/src/kernel/engine.js +93 -19
  47. package/dist/src/kernel/engine.js.map +1 -1
  48. package/dist/src/kernel/nodes/agent.js +2 -0
  49. package/dist/src/kernel/nodes/agent.js.map +1 -1
  50. package/dist/src/kernel/nodes/council.js +9 -4
  51. package/dist/src/kernel/nodes/council.js.map +1 -1
  52. package/dist/src/kernel/nodes/fanout.js +5 -0
  53. package/dist/src/kernel/nodes/fanout.js.map +1 -1
  54. package/dist/src/kernel/nodes/goal.d.ts +8 -1
  55. package/dist/src/kernel/nodes/goal.js +40 -19
  56. package/dist/src/kernel/nodes/goal.js.map +1 -1
  57. package/dist/src/kernel/nodes/subflow.js +9 -1
  58. package/dist/src/kernel/nodes/subflow.js.map +1 -1
  59. package/dist/src/kernel/nodes/ultraapp.d.ts +1 -0
  60. package/dist/src/kernel/nodes/ultraapp.js +1 -0
  61. package/dist/src/kernel/nodes/ultraapp.js.map +1 -1
  62. package/dist/src/kernel/nodes/verifier.js +5 -1
  63. package/dist/src/kernel/nodes/verifier.js.map +1 -1
  64. package/dist/src/kernel/projections.d.ts +1 -0
  65. package/dist/src/kernel/projections.js +7 -1
  66. package/dist/src/kernel/projections.js.map +1 -1
  67. package/dist/src/kernel/templates/index.d.ts +6 -3
  68. package/dist/src/kernel/templates/index.js +8 -3
  69. package/dist/src/kernel/templates/index.js.map +1 -1
  70. package/dist/src/kernel/types.d.ts +30 -10
  71. package/dist/src/kernel/types.js.map +1 -1
  72. package/dist/src/logger.d.ts +6 -0
  73. package/dist/src/logger.js +14 -0
  74. package/dist/src/logger.js.map +1 -1
  75. package/dist/src/mcp-annotations.d.ts +13 -0
  76. package/dist/src/mcp-annotations.js +100 -0
  77. package/dist/src/mcp-annotations.js.map +1 -0
  78. package/dist/src/models.d.ts +7 -0
  79. package/dist/src/models.js +9 -0
  80. package/dist/src/models.js.map +1 -1
  81. package/dist/src/openai-compat.d.ts +11 -5
  82. package/dist/src/openai-compat.js +55 -54
  83. package/dist/src/openai-compat.js.map +1 -1
  84. package/dist/src/persistent-acp-session.d.ts +83 -0
  85. package/dist/src/persistent-acp-session.js +433 -0
  86. package/dist/src/persistent-acp-session.js.map +1 -0
  87. package/dist/src/persistent-codex-app-session.d.ts +2 -0
  88. package/dist/src/persistent-codex-app-session.js +18 -9
  89. package/dist/src/persistent-codex-app-session.js.map +1 -1
  90. package/dist/src/persistent-codex-session.d.ts +14 -3
  91. package/dist/src/persistent-codex-session.js +22 -19
  92. package/dist/src/persistent-codex-session.js.map +1 -1
  93. package/dist/src/persistent-custom-session.d.ts +9 -0
  94. package/dist/src/persistent-custom-session.js +41 -21
  95. package/dist/src/persistent-custom-session.js.map +1 -1
  96. package/dist/src/persistent-grok-session.js +1 -1
  97. package/dist/src/persistent-grok-session.js.map +1 -1
  98. package/dist/src/run-ledger.d.ts +3 -3
  99. package/dist/src/session-manager.d.ts +34 -1
  100. package/dist/src/session-manager.js +169 -30
  101. package/dist/src/session-manager.js.map +1 -1
  102. package/dist/src/types.d.ts +63 -12
  103. package/dist/src/types.js +2 -0
  104. package/dist/src/types.js.map +1 -1
  105. package/dist/src/ultraapp/build.d.ts +5 -11
  106. package/dist/src/ultraapp/build.js +5 -11
  107. package/dist/src/ultraapp/build.js.map +1 -1
  108. package/dist/src/ultraapp/council-adapter.d.ts +5 -2
  109. package/dist/src/ultraapp/council-adapter.js +6 -6
  110. package/dist/src/ultraapp/council-adapter.js.map +1 -1
  111. package/dist/src/ultraapp/files.d.ts +7 -0
  112. package/dist/src/ultraapp/files.js +7 -1
  113. package/dist/src/ultraapp/files.js.map +1 -1
  114. package/dist/src/ultraapp/manager.d.ts +23 -3
  115. package/dist/src/ultraapp/manager.js +170 -72
  116. package/dist/src/ultraapp/manager.js.map +1 -1
  117. package/dist/src/ultraapp/narrator.js +1 -1
  118. package/dist/src/ultraapp/narrator.js.map +1 -1
  119. package/dist/src/ultraapp/versions.d.ts +27 -5
  120. package/dist/src/ultraapp/versions.js +58 -13
  121. package/dist/src/ultraapp/versions.js.map +1 -1
  122. package/openclaw.plugin.json +4 -13
  123. package/package.json +13 -6
  124. package/skills/SKILL.md +52 -24
  125. package/skills/references/acp.md +140 -57
  126. package/skills/references/autoloop.md +141 -35
  127. package/skills/references/claude-cli-tracking.md +9 -6
  128. package/skills/references/cli.md +129 -91
  129. package/skills/references/council.md +44 -22
  130. package/skills/references/dashboard.md +37 -21
  131. package/skills/references/getting-started.md +49 -15
  132. package/skills/references/inbox.md +13 -8
  133. package/skills/references/mcp.md +25 -17
  134. package/skills/references/multi-engine.md +180 -144
  135. package/skills/references/observability.md +90 -58
  136. package/skills/references/openai-compat.md +172 -82
  137. package/skills/references/sessions.md +101 -65
  138. package/skills/references/tools.md +209 -157
  139. package/skills/references/ultra.md +7 -7
  140. package/skills/references/ultraapp.md +101 -56
  141. package/skills/references/verification.md +112 -56
  142. package/skills/references/workflow.md +227 -93
  143. package/assets/banner.jpg +0 -0
package/dist/src/index.js CHANGED
@@ -22,12 +22,14 @@ export { SessionManager } from './session-manager.js';
22
22
  export { PersistentClaudeSession } from './persistent-session.js';
23
23
  export { BaseOneShotSession } from './base-oneshot-session.js';
24
24
  export { PersistentCodexSession } from './persistent-codex-session.js';
25
+ export { PersistentCodexAppServerSession } from './persistent-codex-app-session.js';
25
26
  export { PersistentGeminiSession } from './persistent-gemini-session.js';
26
27
  export { PersistentAgySession } from './persistent-agy-session.js';
27
28
  export { PersistentCursorSession } from './persistent-cursor-session.js';
28
29
  export { PersistentGrokSession } from './persistent-grok-session.js';
29
30
  export { PersistentOpencodeSession } from './persistent-opencode-session.js';
30
31
  export { PersistentCustomSession } from './persistent-custom-session.js';
32
+ export { PersistentAcpSession } from './persistent-acp-session.js';
31
33
  export { Council, getDefaultCouncilConfig } from './council.js';
32
34
  export { AutoloopRunner } from './autoloop/runner.js';
33
35
  export { ClaudeAgentDispatcher } from './autoloop/dispatcher.js';
@@ -54,6 +56,33 @@ function normalizeToolResult(result) {
54
56
  details: result,
55
57
  };
56
58
  }
59
+ const ACP_AGENT_SCHEMA = {
60
+ description: 'The ACP agent (required when engine is "acp"): the id of a bundled preset (`clawo engines`), or, from a local caller only, an inline config.',
61
+ oneOf: [
62
+ { type: 'string', description: 'Preset id, e.g. "kimi-code"' },
63
+ {
64
+ type: 'object',
65
+ properties: {
66
+ name: { type: 'string', description: 'Display name' },
67
+ command: { type: 'string', description: 'Command that serves ACP on stdio' },
68
+ commandEnv: { type: 'string', description: 'Env var that overrides command' },
69
+ args: { type: 'array', items: { type: 'string' }, description: 'Arguments, e.g. ["acp"]' },
70
+ env: { type: 'object', additionalProperties: { type: 'string' } },
71
+ },
72
+ required: ['name', 'command'],
73
+ },
74
+ ],
75
+ };
76
+ /**
77
+ * `acpAgent` on the surfaces that write their agents into a stored run spec
78
+ * (fan-out, council, workflow templates): a preset id only. An inline config can
79
+ * carry an environment, and the spec is written to disk.
80
+ */
81
+ const AUTOLOOP_ENGINES = ENGINE_TYPES.filter((e) => e !== 'acp');
82
+ const ACP_AGENT_ID_SCHEMA = {
83
+ type: 'string',
84
+ description: 'With engine "acp": the id of a bundled ACP agent preset (`clawo engines`), such as kimi-code or dsh.',
85
+ };
57
86
  const CUSTOM_ENGINE_SCHEMA = {
58
87
  type: 'object',
59
88
  description: 'Custom engine config (required when the corresponding engine is "custom").',
@@ -101,13 +130,23 @@ const CUSTOM_ENGINE_SCHEMA = {
101
130
  },
102
131
  required: ['name', 'bin', 'args'],
103
132
  };
133
+ /**
134
+ * `customEngine` on the surfaces that also take a preset id: session_start,
135
+ * session_handoff and council agents all reach `_createSession`, which expands a
136
+ * string through resolveCustomEngine. Autoloop roles validate an inline config
137
+ * and take CUSTOM_ENGINE_SCHEMA itself.
138
+ */
139
+ const CUSTOM_ENGINE_OR_PRESET_SCHEMA = {
140
+ description: 'Custom engine (required when engine is "custom"): the id of a bundled preset (`clawo engines`), or, from a local caller only, an inline config.',
141
+ oneOf: [{ type: 'string', description: 'Preset id, e.g. "zcode"' }, CUSTOM_ENGINE_SCHEMA],
142
+ };
104
143
  /**
105
144
  * OpenClaw plugin object — standard format
106
145
  */
107
146
  const plugin = {
108
147
  id: 'claw-orchestrator',
109
148
  name: 'Claw Orchestrator',
110
- description: 'Run Claude Code, Codex, Antigravity, Grok Build, OpenCode and custom coding CLIs as one unified runtime — persistent sessions, multi-agent council, worktree isolation, multi-model proxy',
149
+ description: 'Run Claude Code, Codex, Antigravity, Grok Build, OpenCode, any ACP agent and custom coding CLIs as one unified runtime — persistent sessions, multi-agent council, worktree isolation, multi-model proxy',
111
150
  register(api) {
112
151
  const rawConfig = (api.pluginConfig || {});
113
152
  const registerTool = (definition) => {
@@ -190,7 +229,7 @@ const plugin = {
190
229
  // ─── Tool: session_start ──────────────────────────────────────────────
191
230
  registerTool({
192
231
  name: 'session_start',
193
- description: 'Start a persistent coding session. Engines: claude (default) for the Claude Code CLI, codex for the OpenAI Codex CLI, agy for the Google Antigravity CLI, grok for the xAI Grok Build CLI, opencode for the sst/opencode CLI, or custom for any user-configured coding agent CLI.',
232
+ description: 'Start a persistent coding session. Engines: claude (default) for the Claude Code CLI, codex for the OpenAI Codex CLI, agy for the Google Antigravity CLI, grok for the xAI Grok Build CLI, opencode for the sst/opencode CLI, acp for any Agent Client Protocol agent (Kimi Code, dsh, OpenCode, Claude Code via an ACP adapter), or custom for any user-configured coding agent CLI.',
194
233
  parameters: {
195
234
  type: 'object',
196
235
  properties: {
@@ -199,7 +238,7 @@ const plugin = {
199
238
  engine: {
200
239
  type: 'string',
201
240
  enum: ENGINE_TYPES,
202
- description: 'Engine to use (default: claude). codex = `codex exec` per send (no /goal). codex-app = long-running `codex app-server` with /goal support. agy = Google Antigravity CLI (Gemini CLI successor; plain-text output, tokens estimated, conversation resume handled automatically). opencode = sst/opencode CLI (provider-agnostic; pass model as `provider/model`). Use "custom" with customEngine config for any CLI.',
241
+ description: 'Engine to use (default: claude). codex = `codex exec` per send (no /goal). codex-app = long-running `codex app-server` with /goal support. agy = Google Antigravity CLI (stream-json output with reported token usage; the conversation is resumed by its id on each send). grok = xAI Grok Build CLI (reports its own per-turn cost). opencode = sst/opencode CLI (provider-agnostic; pass model as `provider/model`). acp = any Agent Client Protocol agent, chosen with acpAgent. Use "custom" with customEngine config for any CLI. gemini and cursor are legacy, kept for existing callers: use agy and grok instead.',
203
242
  },
204
243
  model: { type: 'string', description: 'Model to use (opus, sonnet, haiku, gpt-5.5, agy-pro, etc.)' },
205
244
  permissionMode: {
@@ -214,6 +253,16 @@ const plugin = {
214
253
  description: 'Tools to auto-approve',
215
254
  },
216
255
  disallowedTools: { type: 'array', maxItems: 200, items: { type: 'string' }, description: 'Tools to deny' },
256
+ tools: {
257
+ type: ['string', 'array'],
258
+ items: { type: 'string' },
259
+ description: 'Claude engine only. The built-in tool set (`--tools`): names such as ["Bash", "Edit", "Read"], "" for none, or "default".',
260
+ },
261
+ addDir: {
262
+ type: 'array',
263
+ items: { type: 'string' },
264
+ description: 'Directories the session may use besides cwd (`--add-dir`). Claude and Codex; a resumed Codex thread keeps the directories its first turn opened.',
265
+ },
217
266
  maxTurns: { type: 'number', description: 'Max agent loop turns' },
218
267
  maxBudgetUsd: { type: 'number', description: 'Max API spend (USD)' },
219
268
  autoCompactPercent: {
@@ -243,14 +292,24 @@ const plugin = {
243
292
  description: 'MCP server config file(s)',
244
293
  },
245
294
  settings: { type: 'string', description: 'Settings.json path or inline JSON' },
295
+ pluginUrl: {
296
+ type: ['string', 'array'],
297
+ items: { type: 'string' },
298
+ description: 'Claude engine only. Plugin .zip URL(s) fetched for this session only (`--plugin-url`).',
299
+ },
246
300
  ultracode: {
247
301
  type: 'boolean',
248
- description: 'Claude engine only. Enable "ultracode" / dynamic workflows: Claude plans a JS orchestration script per substantive task and fans out to subagents. Injected as the ultracode:true settings key (not a --effort value).',
302
+ description: 'Claude engine only. Enable "ultracode" / dynamic workflows: Claude plans a JS orchestration script per substantive task and fans out to subagents. Injected as the ultracode:true settings key (not a --effort value). A workflow runs in the background: session_send returns the launch reply, and the completion arrives later in a turn that no send returns.',
249
303
  },
250
304
  sandboxMode: {
251
305
  type: 'string',
252
306
  enum: ['read-only', 'workspace-write', 'danger-full-access'],
253
- description: 'Sandbox policy. Codex supports all values; other built-in engines map read-only to their native plan/read-only mode.',
307
+ description: 'Sandbox policy. Codex supports all values (default workspace-write). read-only is enforced on claude, agy, grok and opencode through their own plan/read-only modes; a custom engine must map it via permissionModes or the session refuses to start. The acp engine refuses read-only, because the agent runs its own tools; its permission answers follow permissionMode.',
308
+ },
309
+ launcher: {
310
+ type: 'array',
311
+ items: { type: 'string' },
312
+ description: 'Argv prefix every engine process of this session runs under, e.g. a sandbox wrapper: ["/opt/sandbox.sh", "task"]. Any engine. It runs with your authority, like a contract command check.',
254
313
  },
255
314
  codexProfile: {
256
315
  type: 'string',
@@ -339,10 +398,11 @@ const plugin = {
339
398
  enum: ['default', 'flex', 'priority'],
340
399
  description: 'AWS Bedrock service tier (sets ANTHROPIC_BEDROCK_SERVICE_TIER). Only effective when routing through Bedrock.',
341
400
  },
342
- customEngine: CUSTOM_ENGINE_SCHEMA,
401
+ customEngine: CUSTOM_ENGINE_OR_PRESET_SCHEMA,
402
+ acpAgent: ACP_AGENT_SCHEMA,
343
403
  resumeSessionId: {
344
404
  type: 'string',
345
- description: 'Resume an existing Claude Code session by its ID (e.g. from ~/.claude/sessions/). Replays conversation history via session/load instead of starting fresh.',
405
+ description: "Resume an earlier conversation by the engine's own id instead of starting fresh: a Claude Code session id (`--resume`), a Codex or codex-app thread id, an Antigravity conversation id, a Grok or OpenCode session id, an ACP agent's session id, or a custom engine's id when its config maps a resume flag.",
346
406
  },
347
407
  },
348
408
  },
@@ -366,9 +426,12 @@ const plugin = {
366
426
  effort: {
367
427
  type: 'string',
368
428
  enum: ['low', 'medium', 'high', 'xhigh', 'max', 'ultra'],
369
- description: 'Effort for this message',
429
+ description: 'Effort for this message. agy, grok and opencode pass it to the engine for this turn; on claude, high and above prefix the message with `ultrathink` (a persistent custom engine does the same for high and max). Other engines ignore it.',
430
+ },
431
+ plan: {
432
+ type: 'boolean',
433
+ description: 'Ask for a plan only: claude and persistent custom engines prefix the message with a plan-only instruction. The permission mode is unchanged (start with sandboxMode read-only to enforce it); other engines ignore this flag.',
370
434
  },
371
- plan: { type: 'boolean', description: 'Enable plan mode' },
372
435
  timeout: { type: 'number', description: 'Timeout in ms (default 300000)' },
373
436
  stream: {
374
437
  type: 'boolean',
@@ -422,7 +485,8 @@ const plugin = {
422
485
  type: 'number',
423
486
  description: 'Upper bound on the carried history, in characters (default 240000, minimum 4000). When the conversation is longer, the opening request and the newest turns are kept and the turns between them are left out.',
424
487
  },
425
- customEngine: CUSTOM_ENGINE_SCHEMA,
488
+ customEngine: CUSTOM_ENGINE_OR_PRESET_SCHEMA,
489
+ acpAgent: ACP_AGENT_SCHEMA,
426
490
  },
427
491
  required: ['name', 'engine'],
428
492
  },
@@ -434,6 +498,7 @@ const plugin = {
434
498
  message: args.message,
435
499
  maxChars: args.maxChars,
436
500
  customEngine: args.customEngine,
501
+ acpAgent: args.acpAgent,
437
502
  });
438
503
  return { ok: true, ...result };
439
504
  },
@@ -441,7 +506,7 @@ const plugin = {
441
506
  // ─── Tool: session_stop ───────────────────────────────────────────────
442
507
  registerTool({
443
508
  name: 'session_stop',
444
- description: 'Stop a persistent coding session',
509
+ description: 'Stop a persistent coding session and remove it from the resume registry, so a later session_start under the same name begins a new conversation',
445
510
  parameters: {
446
511
  type: 'object',
447
512
  properties: { name: { type: 'string', description: 'Session name' } },
@@ -455,12 +520,11 @@ const plugin = {
455
520
  // ─── Tool: session_list ───────────────────────────────────────────────
456
521
  registerTool({
457
522
  name: 'session_list',
458
- description: 'List all active coding sessions',
523
+ description: 'List the active coding sessions, and the persisted ones that a session_start under the same name resumes',
459
524
  parameters: { type: 'object', properties: {} },
460
525
  execute: async (_id) => {
461
- if (!manager)
462
- return { ok: true, sessions: [], persisted: [] };
463
- return { ok: true, sessions: manager.listSessions(), persisted: manager.listPersistedSessions() };
526
+ const m = getManager();
527
+ return { ok: true, sessions: m.listSessions(), persisted: m.listPersistedSessions() };
464
528
  },
465
529
  });
466
530
  // ─── Tool: sessions_overview ──────────────────────────────────────────
@@ -484,7 +548,7 @@ const plugin = {
484
548
  // ─── Tool: coding_session_status ──────────────────────────────────────
485
549
  registerTool({
486
550
  name: 'coding_session_status',
487
- description: 'Get detailed status of a coding session (context %, tokens, cost, uptime)',
551
+ description: 'Get detailed status of a coding session: context %, tokens, cost and spend cap, turns, tool calls, retries, start time and last activity',
488
552
  parameters: {
489
553
  type: 'object',
490
554
  properties: { name: { type: 'string', description: 'Session name' } },
@@ -498,7 +562,7 @@ const plugin = {
498
562
  // ─── Tool: session_grep ───────────────────────────────────────────────
499
563
  registerTool({
500
564
  name: 'session_grep',
501
- description: 'Search session history for events matching a regex pattern',
565
+ description: "Search the session's in-memory event history (its 100 most recent events) for a regex pattern, case-insensitive",
502
566
  parameters: {
503
567
  type: 'object',
504
568
  properties: {
@@ -547,7 +611,7 @@ const plugin = {
547
611
  // ─── Tool: team_list ──────────────────────────────────────────────────
548
612
  registerTool({
549
613
  name: 'team_list',
550
- description: 'List teammates in an agent team session (requires enableAgentTeams)',
614
+ description: "List the other active sessions as this session's teammates, with engine, idle/busy/paused state and turn count. Works on every engine; enableAgentTeams is not required.",
551
615
  parameters: {
552
616
  type: 'object',
553
617
  properties: { name: { type: 'string', description: 'Session name' } },
@@ -561,7 +625,7 @@ const plugin = {
561
625
  // ─── Tool: team_send ──────────────────────────────────────────────────
562
626
  registerTool({
563
627
  name: 'team_send',
564
- description: 'Send a message to a specific teammate in an agent team session',
628
+ description: 'Send a message to another active session through the cross-session inbox: delivered now if it is idle, queued if it is busy.',
565
629
  parameters: {
566
630
  type: 'object',
567
631
  properties: {
@@ -579,7 +643,7 @@ const plugin = {
579
643
  // ─── Tool: session_update_tools ───────────────────────────────────────
580
644
  registerTool({
581
645
  name: 'session_update_tools',
582
- description: 'Update allowedTools or disallowedTools for a running session. Restarts the session process with --resume to apply the new tool constraints while preserving conversation history. Rejects if the session is currently busy.',
646
+ description: 'Update allowedTools or disallowedTools for a running session. Restarts the session process with --resume to apply the new tool constraints while preserving conversation history. Rejects if the session is currently busy. Without merge:true both lists are replaced, so a list you omit is cleared.',
583
647
  parameters: {
584
648
  type: 'object',
585
649
  properties: {
@@ -639,7 +703,7 @@ const plugin = {
639
703
  properties: {
640
704
  path: {
641
705
  type: 'string',
642
- description: 'Project path to purge (resolved to absolute). Ignored when all=true. Defaults to current cwd.',
706
+ description: "Project path to purge (resolved to absolute). Ignored when all=true. Defaults to the orchestrator process's working directory.",
643
707
  },
644
708
  all: {
645
709
  type: 'boolean',
@@ -766,7 +830,7 @@ const plugin = {
766
830
  // ─── Tool: codex_review ─────────────────────────────────────────────
767
831
  registerTool({
768
832
  name: 'codex_review',
769
- description: 'Run a non-interactive Codex code review (`codex review`). Pick exactly one diff scope: uncommitted (working-tree changes), base (vs branch), or commit (vs SHA). Output is plain text — Codex `review` does not emit JSON.',
833
+ description: 'Run a non-interactive Codex code review (`codex review`). Pick at most one diff scope — uncommitted (working-tree changes), base (vs branch), or commit (vs SHA); passing two is an error. Output is plain text — Codex `review` does not emit JSON.',
770
834
  parameters: {
771
835
  type: 'object',
772
836
  properties: {
@@ -961,7 +1025,7 @@ const plugin = {
961
1025
  agents: {
962
1026
  type: 'array',
963
1027
  maxItems: 16,
964
- description: 'Agent specs: { name, engine?, model?, effort?, prompt?, persona?, baseUrl?, permissionMode?, customEngine? }.',
1028
+ description: 'Agent specs: { name, engine?, model?, effort?, prompt?, persona?, baseUrl?, permissionMode?, customEngine?, acpAgent? }.',
965
1029
  items: {
966
1030
  type: 'object',
967
1031
  properties: {
@@ -981,6 +1045,7 @@ const plugin = {
981
1045
  description: 'Per-agent role instructions prepended to the shared task when no non-empty prompt is supplied.',
982
1046
  },
983
1047
  baseUrl: { type: 'string' },
1048
+ acpAgent: ACP_AGENT_ID_SCHEMA,
984
1049
  },
985
1050
  required: ['name'],
986
1051
  },
@@ -995,6 +1060,10 @@ const plugin = {
995
1060
  enum: ENGINE_TYPES,
996
1061
  description: 'Engine for the synthesis pass (default claude).',
997
1062
  },
1063
+ synthesisAcpAgent: {
1064
+ ...ACP_AGENT_ID_SCHEMA,
1065
+ description: 'With synthesisEngine "acp": the ACP agent preset the synthesis pass starts.',
1066
+ },
998
1067
  agentTimeoutMs: { type: 'number', description: 'Per-agent timeout in ms (default 600000).' },
999
1068
  maxTurnsPerAgent: { type: 'number', description: 'Max agent loop turns (default 30).' },
1000
1069
  maxBudgetUsd: { type: 'number', description: 'Per-agent spend cap (USD).' },
@@ -1009,6 +1078,7 @@ const plugin = {
1009
1078
  synthesize: args.synthesize,
1010
1079
  synthesisModel: args.synthesisModel,
1011
1080
  synthesisEngine: args.synthesisEngine,
1081
+ synthesisAcpAgent: args.synthesisAcpAgent,
1012
1082
  agentTimeoutMs: args.agentTimeoutMs,
1013
1083
  maxTurnsPerAgent: args.maxTurnsPerAgent,
1014
1084
  maxBudgetUsd: args.maxBudgetUsd,
@@ -1053,13 +1123,13 @@ const plugin = {
1053
1123
  template: {
1054
1124
  type: 'string',
1055
1125
  enum: ['solve', 'council', 'fanout', 'goal'],
1056
- description: 'Build a built-in workflow instead of supplying `spec`. `solve` = triage → implement → verify → repair-until-green → review. `goal` = a Codex /goal on one codex-app thread (`task` is the objective) → verify → repair on the same thread.',
1126
+ description: 'Build a built-in workflow instead of supplying `spec`. `solve` = triage → implement → review → verify → repair-until-green. `goal` = a Codex /goal on one codex-app thread (`task` is the objective) → verify → repair on the same thread.',
1057
1127
  },
1058
1128
  task: { type: 'string', description: 'Task text, when using `template`.' },
1059
1129
  agents: {
1060
1130
  type: 'array',
1061
1131
  maxItems: 16,
1062
- description: 'Agents for the template: { name, engine?, model?, effort?, persona? }.',
1132
+ description: 'Agents for the template: { name, engine?, model?, effort?, persona?, acpAgent? }.',
1063
1133
  items: {
1064
1134
  type: 'object',
1065
1135
  properties: {
@@ -1068,6 +1138,7 @@ const plugin = {
1068
1138
  model: { type: 'string' },
1069
1139
  effort: { type: 'string', enum: EFFORT_LEVELS },
1070
1140
  persona: { type: 'string' },
1141
+ acpAgent: ACP_AGENT_ID_SCHEMA,
1071
1142
  },
1072
1143
  required: ['name'],
1073
1144
  },
@@ -1084,6 +1155,7 @@ const plugin = {
1084
1155
  model: { type: 'string' },
1085
1156
  effort: { type: 'string', enum: EFFORT_LEVELS },
1086
1157
  persona: { type: 'string' },
1158
+ acpAgent: ACP_AGENT_ID_SCHEMA,
1087
1159
  },
1088
1160
  required: ['name'],
1089
1161
  },
@@ -1094,9 +1166,11 @@ const plugin = {
1094
1166
  properties: {
1095
1167
  engine: { type: 'string', enum: ENGINE_TYPES },
1096
1168
  model: { type: 'string' },
1169
+ acpAgent: ACP_AGENT_ID_SCHEMA,
1097
1170
  },
1098
1171
  },
1099
1172
  humanGate: { type: 'boolean', description: '`solve` only: park for approval before writing anything.' },
1173
+ maxRounds: { type: 'number', description: '`council` only: max collaboration rounds (default 15).' },
1100
1174
  maxRepairs: {
1101
1175
  type: 'number',
1102
1176
  description: '`solve` / `goal`: repair attempts allowed (default 3 for solve, 1 for goal).',
@@ -1189,7 +1263,7 @@ const plugin = {
1189
1263
  throw new Error('workflow_start needs `spec`, or `template` with `task`');
1190
1264
  const agents = args.agents ?? [{ name: 'agent' }];
1191
1265
  if (template === 'council') {
1192
- spec = councilWorkflow({ task, cwd, agents });
1266
+ spec = councilWorkflow({ task, cwd, agents, maxRounds: args.maxRounds });
1193
1267
  }
1194
1268
  else if (template === 'fanout') {
1195
1269
  spec = fanoutWorkflow({ task, cwd, agents, synthesize: agents.length >= 2 });
@@ -1234,7 +1308,7 @@ const plugin = {
1234
1308
  });
1235
1309
  registerTool({
1236
1310
  name: 'workflow_status',
1237
- description: 'Poll a workflow run. Returns state, per-node status, and the outcome: `verified` (a contract passed), `refuted` (it failed), or `unverified` (none was declared — nothing checked the work).',
1311
+ description: 'Poll a workflow run. Returns state, per-node status, and the outcome: `verified` (a contract passed), `refuted` (it failed), or `unverified` (nothing checked the work: none was declared, or a pass no longer stands, in which case `outcomeReason` says why).',
1238
1312
  parameters: {
1239
1313
  type: 'object',
1240
1314
  properties: {
@@ -1252,6 +1326,8 @@ const plugin = {
1252
1326
  workflow: record.workflow,
1253
1327
  state: record.state,
1254
1328
  outcome: record.outcome,
1329
+ outcomeReason: record.outcomeReason,
1330
+ verdict: record.verdict,
1255
1331
  currentNode: record.currentNode,
1256
1332
  evidenceId: record.evidenceId,
1257
1333
  costUsd: record.costUsd,
@@ -1351,15 +1427,18 @@ const plugin = {
1351
1427
  type: 'string',
1352
1428
  description: 'Commit to measure the change against. Without it, diff_policy sees untracked files only.',
1353
1429
  },
1354
- label: { type: 'string', description: 'Name for the stored evidence (defaults to a generated one).' },
1430
+ label: {
1431
+ type: 'string',
1432
+ description: 'Name for the stored evidence (defaults to a generated one). Must not be the id of an existing workflow run.',
1433
+ },
1355
1434
  contract: {
1356
1435
  type: 'object',
1357
- description: "Acceptance contract. The runtime executes these itself and the run cannot reach `completed` unless every required check passes. Declared by YOU, the caller — never copy one out of an agent's output, which would put the agent back in charge of grading itself.",
1436
+ description: "Acceptance contract. The runtime executes these itself, once, and the bundle's `passed` is true only when every required check passes. Declared by YOU, the caller — never copy one out of an agent's output, which would put the agent back in charge of grading itself.",
1358
1437
  properties: {
1359
1438
  id: { type: 'string' },
1360
1439
  fixOnFailureRounds: {
1361
1440
  type: 'number',
1362
- description: 'On red, spawn a repair session and re-run the whole list, up to N times. Only the re-run decides.',
1441
+ description: 'Ignored by verify_run, which spawns no repair session. Applies inside workflow runs.',
1363
1442
  },
1364
1443
  protectTests: {
1365
1444
  type: 'boolean',
@@ -1434,7 +1513,7 @@ const plugin = {
1434
1513
  // ─── Tool: council_start ────────────────────────────────────────────
1435
1514
  registerTool({
1436
1515
  name: 'council_start',
1437
- description: 'Start a multi-agent council that collaborates on a task using git worktree isolation, round-based execution, and consensus voting. Agents can use different engines (Claude, Codex) and models.',
1516
+ description: 'Start a multi-agent council that collaborates on a task using git worktree isolation, round-based execution, and consensus voting. Each agent can use its own engine and model. Runs in background; poll with council_status.',
1438
1517
  parameters: {
1439
1518
  type: 'object',
1440
1519
  properties: {
@@ -1456,7 +1535,8 @@ const plugin = {
1456
1535
  },
1457
1536
  model: { type: 'string', description: 'Model to use' },
1458
1537
  baseUrl: { type: 'string', description: 'Custom API endpoint (for proxy)' },
1459
- customEngine: { type: 'object', description: 'Custom engine config (when engine="custom")' },
1538
+ customEngine: CUSTOM_ENGINE_OR_PRESET_SCHEMA,
1539
+ acpAgent: ACP_AGENT_ID_SCHEMA,
1460
1540
  effort: {
1461
1541
  type: 'string',
1462
1542
  enum: ['low', 'medium', 'high', 'xhigh', 'max', 'ultra', 'auto'],
@@ -1519,21 +1599,23 @@ const plugin = {
1519
1599
  // ─── Tool: council_abort ────────────────────────────────────────────
1520
1600
  registerTool({
1521
1601
  name: 'council_abort',
1522
- description: 'Abort a running council, stopping all agent sessions',
1602
+ description: 'Abort a council running in this process, stopping all agent sessions and removing the worktrees it created. A council that is not running here is left alone and the call returns ok: false.',
1523
1603
  parameters: {
1524
1604
  type: 'object',
1525
1605
  properties: { id: { type: 'string', description: 'Council session ID' } },
1526
1606
  required: ['id'],
1527
1607
  },
1528
1608
  execute: async (_id, args) => {
1529
- getManager().councilAbort(args.id);
1530
- return { ok: true };
1609
+ const result = getManager().councilAbort(args.id);
1610
+ if (!result.aborted)
1611
+ return { ok: false, aborted: false, state: result.state, error: result.reason };
1612
+ return { ok: true, aborted: true };
1531
1613
  },
1532
1614
  });
1533
1615
  // ─── Tool: council_inject ───────────────────────────────────────────
1534
1616
  registerTool({
1535
1617
  name: 'council_inject',
1536
- description: 'Inject a user message into the next round of a running council. The message will be appended to all agent prompts in the next round.',
1618
+ description: 'Inject a user message into the next round of a running council. The message will be appended to all agent prompts in the next round; a second inject before that round replaces the first. The council must be running in this process.',
1537
1619
  parameters: {
1538
1620
  type: 'object',
1539
1621
  properties: {
@@ -1699,9 +1781,10 @@ const plugin = {
1699
1781
  engines: {
1700
1782
  type: 'array',
1701
1783
  // Excluded: 'custom', because ultrareview spawns reviewers without a
1702
- // customEngine config.
1703
- items: { type: 'string', enum: ENGINE_TYPES.filter((e) => e !== 'custom') },
1704
- description: 'Engines to round-robin reviewers across (default ["claude"]). Every reviewer runs read-only (sandboxMode read-only), so custom is not accepted. Reviewers fan out in parallel; per-agent failures are isolated.',
1784
+ // customEngine config, and 'acp', because an ACP agent cannot be held
1785
+ // to the read-only sandbox every reviewer runs in.
1786
+ items: { type: 'string', enum: ENGINE_TYPES.filter((e) => e !== 'custom' && e !== 'acp') },
1787
+ description: 'Engines to round-robin reviewers across (default ["claude"]). Every reviewer runs read-only (sandboxMode read-only), so custom and acp are not accepted. Reviewers fan out in parallel; per-agent failures are isolated.',
1705
1788
  },
1706
1789
  },
1707
1790
  required: ['cwd'],
@@ -1749,14 +1832,19 @@ const plugin = {
1749
1832
  description: 'Stable run identifier (used to address subsequent chat / status calls)',
1750
1833
  },
1751
1834
  workspace: { type: 'string', description: 'Path to the git workspace where the run lives' },
1752
- planner_engine: { type: 'string', enum: ENGINE_TYPES, description: 'Planner engine (default: claude)' },
1835
+ // No 'acp': a role is configured by engine name alone and cannot name the ACP agent.
1836
+ planner_engine: { type: 'string', enum: AUTOLOOP_ENGINES, description: 'Planner engine (default: claude)' },
1753
1837
  planner_model: {
1754
1838
  type: 'string',
1755
1839
  description: 'Planner model (default: opus for Claude; engine default otherwise)',
1756
1840
  },
1757
1841
  planner_effort: { type: 'string', enum: EFFORT_LEVELS, description: 'Fixed Planner reasoning effort' },
1758
1842
  planner_custom_engine: CUSTOM_ENGINE_SCHEMA,
1759
- coder_engine: { type: 'string', enum: ENGINE_TYPES, description: 'Default Coder engine (default: claude)' },
1843
+ coder_engine: {
1844
+ type: 'string',
1845
+ enum: AUTOLOOP_ENGINES,
1846
+ description: 'Default Coder engine (default: claude)',
1847
+ },
1760
1848
  coder_model: {
1761
1849
  type: 'string',
1762
1850
  description: 'Default Coder model (default: sonnet for Claude; engine default otherwise)',
@@ -1765,7 +1853,7 @@ const plugin = {
1765
1853
  coder_custom_engine: CUSTOM_ENGINE_SCHEMA,
1766
1854
  reviewer_engine: {
1767
1855
  type: 'string',
1768
- enum: ENGINE_TYPES,
1856
+ enum: AUTOLOOP_ENGINES,
1769
1857
  description: 'Default Reviewer engine (default: claude)',
1770
1858
  },
1771
1859
  reviewer_model: {
@@ -1856,13 +1944,9 @@ const plugin = {
1856
1944
  // ─── Tool: autoloop_list ─────────────────────────────────────
1857
1945
  registerTool({
1858
1946
  name: 'autoloop_list',
1859
- description: 'List all v2 autoloop runs in this manager process.',
1947
+ description: 'List the v2 autoloop runs recorded on this machine, running and finished, newest first.',
1860
1948
  parameters: { type: 'object', properties: {} },
1861
- execute: async () => {
1862
- if (!manager)
1863
- return { ok: true, runs: [] };
1864
- return { ok: true, runs: getManager().autoloopList() };
1865
- },
1949
+ execute: async () => ({ ok: true, runs: getManager().autoloopList() }),
1866
1950
  });
1867
1951
  // ─── Tool: autoloop_reset_agent ──────────────────────────────
1868
1952
  registerTool({
@@ -1913,7 +1997,7 @@ const plugin = {
1913
1997
  // ─── ultraapp (read-only) ─────────────────────────────────────────────
1914
1998
  registerTool({
1915
1999
  name: 'ultraapp_list',
1916
- description: 'List all ultraapp runs (interview/build/done) with title, mode, and timestamps. Read-only — to start a new run, open the Forge dashboard tab.',
2000
+ description: 'List all ultraapp runs with title, mode (interview, queued, building, build-complete, deploying, done, failed, cancelled), and timestamps. Read-only — to start a new run, call ultraapp_new or open the Forge dashboard tab.',
1917
2001
  parameters: { type: 'object', properties: {} },
1918
2002
  execute: async () => {
1919
2003
  const ua = getManager().getUltraappManager();
@@ -2043,7 +2127,7 @@ const plugin = {
2043
2127
  });
2044
2128
  registerTool({
2045
2129
  name: 'ultraapp_build_cancel',
2046
- description: 'Cancel a queued or in-flight build. Best-effort for in-flight; the worker is expected to honour its own cancellation signal (v0.2 only emits the event).',
2130
+ description: "Cancel a queued or in-flight build. A queued build leaves the queue; an in-flight build's workflow run is cancelled, and the build stops at its next cancellation check.",
2047
2131
  parameters: {
2048
2132
  type: 'object',
2049
2133
  properties: { runId: { type: 'string' } },
@@ -2057,7 +2141,7 @@ const plugin = {
2057
2141
  });
2058
2142
  registerTool({
2059
2143
  name: 'ultraapp_feedback',
2060
- description: 'Submit a done-mode feedback message (after the run reaches `done`). The text is classified by Haiku into cosmetic / spec-delta / structural and routed: cosmetic runs the patcher (Opus diff + validate + auto-revert + version snapshot); spec-delta flips the run back to `interview` with a focused bootstrap; structural posts a "start fresh" suggestion.',
2144
+ description: 'Submit a done-mode feedback message (after the run reaches `done`). The text is classified by Haiku into cosmetic / spec-delta / structural and routed: cosmetic runs the patcher (Opus diff + validate + auto-revert + version snapshot); spec-delta flips the run back to `interview` with a focused bootstrap; structural posts a "start fresh" suggestion. Before the run reaches `done`, the text is sent to the interview as a free-form answer instead.',
2061
2145
  parameters: {
2062
2146
  type: 'object',
2063
2147
  properties: {
@@ -2074,7 +2158,7 @@ const plugin = {
2074
2158
  });
2075
2159
  registerTool({
2076
2160
  name: 'ultraapp_promote_version',
2077
- description: "Atomically swap which previously-built version is currently deployed. Stops the old container, starts the target version's container, updates the router map. Requires a router to be wired (see clawo serve).",
2161
+ description: 'Make a version the one serving the app. A version deployed before is swapped in (the live one is stopped, the target restarted, the router map updated); one never deployed, such as a patched version, is built and deployed first. Requires a router to be wired (see clawo serve).',
2078
2162
  parameters: {
2079
2163
  type: 'object',
2080
2164
  properties: {