@enderfga/claw-orchestrator 6.5.0 → 7.0.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.
package/dist/src/index.js CHANGED
@@ -16,6 +16,7 @@ import { ENGINE_TYPES, } from './types.js';
16
16
  import { councilWorkflow, fanoutWorkflow, solveWorkflow } from './kernel/templates/index.js';
17
17
  import { readEvents as readKernelEvents } from './kernel/store.js';
18
18
  import { resolveSecretRefs } from './kernel/secrets.js';
19
+ import { AUTOLOOP_TIMEOUT_SCHEMA, validateAutoloopTimeoutConfig } from './autoloop/types.js';
19
20
  // ─── Standalone Export ───────────────────────────────────────────────────────
20
21
  export { SessionManager } from './session-manager.js';
21
22
  export { PersistentClaudeSession } from './persistent-session.js';
@@ -37,6 +38,22 @@ export { createConsoleLogger, nullLogger } from './logger.js';
37
38
  export { CircuitBreaker } from './circuit-breaker.js';
38
39
  export { InboxManager } from './inbox-manager.js';
39
40
  export * from './types.js';
41
+ function isAgentToolResult(result) {
42
+ return (typeof result === 'object' &&
43
+ result !== null &&
44
+ 'content' in result &&
45
+ Array.isArray(result.content) &&
46
+ 'details' in result);
47
+ }
48
+ function normalizeToolResult(result) {
49
+ if (isAgentToolResult(result))
50
+ return result;
51
+ const text = JSON.stringify(result, (_key, value) => (typeof value === 'bigint' ? value.toString() : value), 2);
52
+ return {
53
+ content: [{ type: 'text', text: text ?? 'null' }],
54
+ details: result,
55
+ };
56
+ }
40
57
  const CUSTOM_ENGINE_SCHEMA = {
41
58
  type: 'object',
42
59
  description: 'Custom engine config (required when the corresponding engine is "custom").',
@@ -93,6 +110,12 @@ const plugin = {
93
110
  description: 'Run Claude Code, Codex, Gemini, Cursor Agent and custom coding CLIs as one unified runtime — persistent sessions, multi-agent council, worktree isolation, multi-model proxy',
94
111
  register(api) {
95
112
  const rawConfig = (api.pluginConfig || {});
113
+ const registerTool = (definition) => {
114
+ api.registerTool({
115
+ ...definition,
116
+ execute: async (toolCallId, params) => normalizeToolResult(await definition.execute(toolCallId, params)),
117
+ });
118
+ };
96
119
  // ─── Lazy Init ────────────────────────────────────────────────────────
97
120
  //
98
121
  // Neither SessionManager nor EmbeddedServer is created at plugin load
@@ -165,7 +188,7 @@ const plugin = {
165
188
  }
166
189
  }
167
190
  // ─── Tool: session_start ──────────────────────────────────────────────
168
- api.registerTool({
191
+ registerTool({
169
192
  name: 'session_start',
170
193
  description: 'Start a persistent coding session. Supports multiple engines: claude (default) for Claude Code CLI, codex for OpenAI Codex CLI, gemini for Google Gemini CLI, agy for Google Antigravity CLI, cursor for Cursor Agent CLI, opencode for sst/opencode CLI, or custom for any user-configured coding agent CLI.',
171
194
  parameters: {
@@ -311,7 +334,7 @@ const plugin = {
311
334
  },
312
335
  });
313
336
  // ─── Tool: session_send ───────────────────────────────────────────────
314
- api.registerTool({
337
+ registerTool({
315
338
  name: 'session_send',
316
339
  description: 'Send a message to a persistent coding session and get the response',
317
340
  parameters: {
@@ -356,7 +379,7 @@ const plugin = {
356
379
  },
357
380
  });
358
381
  // ─── Tool: session_stop ───────────────────────────────────────────────
359
- api.registerTool({
382
+ registerTool({
360
383
  name: 'session_stop',
361
384
  description: 'Stop a persistent coding session',
362
385
  parameters: {
@@ -370,7 +393,7 @@ const plugin = {
370
393
  },
371
394
  });
372
395
  // ─── Tool: session_list ───────────────────────────────────────────────
373
- api.registerTool({
396
+ registerTool({
374
397
  name: 'session_list',
375
398
  description: 'List all active coding sessions',
376
399
  parameters: { type: 'object', properties: {} },
@@ -381,7 +404,7 @@ const plugin = {
381
404
  },
382
405
  });
383
406
  // ─── Tool: sessions_overview ──────────────────────────────────────────
384
- api.registerTool({
407
+ registerTool({
385
408
  name: 'sessions_overview',
386
409
  description: 'Get an aggregate overview of all active coding sessions — readiness, busy/paused state, cost, context usage, and last activity for each. Use this for a dashboard view across all sessions. For single-session detail, use coding_session_status instead.',
387
410
  parameters: { type: 'object', properties: {} },
@@ -399,7 +422,7 @@ const plugin = {
399
422
  },
400
423
  });
401
424
  // ─── Tool: coding_session_status ──────────────────────────────────────
402
- api.registerTool({
425
+ registerTool({
403
426
  name: 'coding_session_status',
404
427
  description: 'Get detailed status of a coding session (context %, tokens, cost, uptime)',
405
428
  parameters: {
@@ -413,7 +436,7 @@ const plugin = {
413
436
  },
414
437
  });
415
438
  // ─── Tool: session_grep ───────────────────────────────────────────────
416
- api.registerTool({
439
+ registerTool({
417
440
  name: 'session_grep',
418
441
  description: 'Search session history for events matching a regex pattern',
419
442
  parameters: {
@@ -432,7 +455,7 @@ const plugin = {
432
455
  },
433
456
  });
434
457
  // ─── Tool: session_compact ────────────────────────────────────────────
435
- api.registerTool({
458
+ registerTool({
436
459
  name: 'session_compact',
437
460
  description: 'Compact a session to reclaim context window space',
438
461
  parameters: {
@@ -449,7 +472,7 @@ const plugin = {
449
472
  },
450
473
  });
451
474
  // ─── Tool: coding_agents_list ─────────────────────────────────────────
452
- api.registerTool({
475
+ registerTool({
453
476
  name: 'coding_agents_list',
454
477
  description: 'List agent definitions from .claude/agents/',
455
478
  parameters: {
@@ -462,7 +485,7 @@ const plugin = {
462
485
  },
463
486
  });
464
487
  // ─── Tool: team_list ──────────────────────────────────────────────────
465
- api.registerTool({
488
+ registerTool({
466
489
  name: 'team_list',
467
490
  description: 'List teammates in an agent team session (requires enableAgentTeams)',
468
491
  parameters: {
@@ -476,7 +499,7 @@ const plugin = {
476
499
  },
477
500
  });
478
501
  // ─── Tool: team_send ──────────────────────────────────────────────────
479
- api.registerTool({
502
+ registerTool({
480
503
  name: 'team_send',
481
504
  description: 'Send a message to a specific teammate in an agent team session',
482
505
  parameters: {
@@ -494,7 +517,7 @@ const plugin = {
494
517
  },
495
518
  });
496
519
  // ─── Tool: session_update_tools ───────────────────────────────────────
497
- api.registerTool({
520
+ registerTool({
498
521
  name: 'session_update_tools',
499
522
  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.',
500
523
  parameters: {
@@ -531,7 +554,7 @@ const plugin = {
531
554
  },
532
555
  });
533
556
  // ─── Tool: session_switch_model ───────────────────────────────────────
534
- api.registerTool({
557
+ registerTool({
535
558
  name: 'session_switch_model',
536
559
  description: 'Switch the model for a running session immediately. Restarts the session process with --resume so the new model takes effect on the next message while preserving conversation history.',
537
560
  parameters: {
@@ -548,7 +571,7 @@ const plugin = {
548
571
  },
549
572
  });
550
573
  // ─── Tool: project_purge (CLI 2.1.126) ────────────────────────────────
551
- api.registerTool({
574
+ registerTool({
552
575
  name: 'project_purge',
553
576
  description: 'Delete Claude Code project state (transcripts, tasks, file history, config entry) via `claude project purge`. Defaults to dry-run for safety — pass dry_run=false to actually delete. Use all=true to purge every project.',
554
577
  parameters: {
@@ -578,7 +601,7 @@ const plugin = {
578
601
  },
579
602
  });
580
603
  // ─── Tool: plugin_details (CLI 2.1.139) ─────────────────────────────
581
- api.registerTool({
604
+ registerTool({
582
605
  name: 'plugin_details',
583
606
  description: "Show a plugin's component inventory (commands, hooks, MCP servers, agents, skills) plus the per-session token cost of loading it. Wraps `claude plugin details <name>` (CLI 2.1.139+).",
584
607
  parameters: {
@@ -604,7 +627,7 @@ const plugin = {
604
627
  // These tools just pre-format the slash text and send it via the
605
628
  // normal session channel — there is no separate goal-state event to
606
629
  // intercept (unlike Codex). Engine must be "claude".
607
- api.registerTool({
630
+ registerTool({
608
631
  name: 'claude_goal_set',
609
632
  description: 'Set a goal condition on a claude session. Claude Code keeps working across turns until the condition is met, evaluating after each turn via Haiku. Sends `/goal <objective>`. Requires engine: "claude".',
610
633
  parameters: {
@@ -623,7 +646,7 @@ const plugin = {
623
646
  return await getManager().claudeGoalSet(args.name, args.objective, args.timeout);
624
647
  },
625
648
  });
626
- api.registerTool({
649
+ registerTool({
627
650
  name: 'claude_goal_clear',
628
651
  description: 'Clear the active goal on a claude session. Sends `/goal clear`. Requires engine: "claude".',
629
652
  parameters: {
@@ -638,7 +661,7 @@ const plugin = {
638
661
  return await getManager().claudeGoalClear(args.name, args.timeout);
639
662
  },
640
663
  });
641
- api.registerTool({
664
+ registerTool({
642
665
  name: 'claude_goal_status',
643
666
  description: 'Query the active goal on a claude session (objective, elapsed, turns, tokens). Sends bare `/goal` and returns the assistant reply. Requires engine: "claude".',
644
667
  parameters: {
@@ -654,7 +677,7 @@ const plugin = {
654
677
  },
655
678
  });
656
679
  // ─── Tool: codex_resume (Codex 0.119+) ──────────────────────────────
657
- api.registerTool({
680
+ registerTool({
658
681
  name: 'codex_resume',
659
682
  description: 'Resume a previously recorded Codex thread by UUID/name, or pick the most recent with last=true. Spawns `codex exec resume` with --json so the output is parsed into structured fields. Independent of session manager state — useful for cross-process continuity.',
660
683
  parameters: {
@@ -681,7 +704,7 @@ const plugin = {
681
704
  },
682
705
  });
683
706
  // ─── Tool: codex_review ─────────────────────────────────────────────
684
- api.registerTool({
707
+ registerTool({
685
708
  name: 'codex_review',
686
709
  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.',
687
710
  parameters: {
@@ -726,7 +749,7 @@ const plugin = {
726
749
  // tools are convenience wrappers around that pattern, scoped to sessions
727
750
  // started with engine: "codex-app". Calls against any other engine
728
751
  // surface a clear error rather than silently no-op'ing.
729
- api.registerTool({
752
+ registerTool({
730
753
  name: 'codex_goal_set',
731
754
  description: 'Set a long-horizon objective on a codex-app session. Sends `/goal <objective>` via the app-server slash command. Requires engine: "codex-app".',
732
755
  parameters: {
@@ -745,7 +768,7 @@ const plugin = {
745
768
  return await getManager().codexGoalCommand(args.name, args.objective, args.timeout);
746
769
  },
747
770
  });
748
- api.registerTool({
771
+ registerTool({
749
772
  name: 'codex_goal_get',
750
773
  description: 'Read the cached goal state on a codex-app session (objective, status, tokensUsed, timeUsedSeconds, tokenBudget). Returns null if no goal is active. Pure read — does not send any turn.',
751
774
  parameters: {
@@ -757,7 +780,7 @@ const plugin = {
757
780
  return getManager().codexGoalGet(args.name);
758
781
  },
759
782
  });
760
- api.registerTool({
783
+ registerTool({
761
784
  name: 'codex_goal_pause',
762
785
  description: 'Pause goal pursuit on a codex-app session. Sends `/goal pause`.',
763
786
  parameters: {
@@ -772,7 +795,7 @@ const plugin = {
772
795
  return await getManager().codexGoalCommand(args.name, 'pause', args.timeout);
773
796
  },
774
797
  });
775
- api.registerTool({
798
+ registerTool({
776
799
  name: 'codex_goal_resume',
777
800
  description: 'Resume a paused goal on a codex-app session. Sends `/goal resume`.',
778
801
  parameters: {
@@ -787,7 +810,7 @@ const plugin = {
787
810
  return await getManager().codexGoalCommand(args.name, 'resume', args.timeout);
788
811
  },
789
812
  });
790
- api.registerTool({
813
+ registerTool({
791
814
  name: 'codex_goal_clear',
792
815
  description: 'Clear the active goal on a codex-app session. Sends `/goal clear`.',
793
816
  parameters: {
@@ -803,7 +826,7 @@ const plugin = {
803
826
  },
804
827
  });
805
828
  // ─── Codex app-server v2 RPC tools (codex-app engine, Codex 0.137) ────
806
- api.registerTool({
829
+ registerTool({
807
830
  name: 'codex_interrupt',
808
831
  description: 'Cancel the in-flight turn on a codex-app session (`turn/interrupt`).',
809
832
  parameters: {
@@ -813,7 +836,7 @@ const plugin = {
813
836
  },
814
837
  execute: async (_id, args) => getManager().codexInterrupt(args.name),
815
838
  });
816
- api.registerTool({
839
+ registerTool({
817
840
  name: 'codex_steer',
818
841
  description: 'Add input to the in-flight turn on a codex-app session without restarting it (`turn/steer`). Falls back to a normal turn when idle.',
819
842
  parameters: {
@@ -826,7 +849,7 @@ const plugin = {
826
849
  },
827
850
  execute: async (_id, args) => getManager().codexSteer(args.name, args.message),
828
851
  });
829
- api.registerTool({
852
+ registerTool({
830
853
  name: 'codex_fork',
831
854
  description: 'Branch a codex-app thread into a new one (`thread/fork`); returns the forked thread id.',
832
855
  parameters: {
@@ -836,7 +859,7 @@ const plugin = {
836
859
  },
837
860
  execute: async (_id, args) => getManager().codexForkThread(args.name),
838
861
  });
839
- api.registerTool({
862
+ registerTool({
840
863
  name: 'codex_rollback',
841
864
  description: 'Drop the last N turns from a codex-app thread (`thread/rollback`).',
842
865
  parameters: {
@@ -849,7 +872,7 @@ const plugin = {
849
872
  },
850
873
  execute: async (_id, args) => getManager().codexRollback(args.name, args.numTurns),
851
874
  });
852
- api.registerTool({
875
+ registerTool({
853
876
  name: 'codex_models',
854
877
  description: 'List models available to a codex-app session (`model/list`), incl. supported reasoning efforts.',
855
878
  parameters: {
@@ -859,8 +882,8 @@ const plugin = {
859
882
  },
860
883
  execute: async (_id, args) => getManager().codexModels(args.name),
861
884
  });
862
- api.registerTool({
863
- name: 'codex_threads',
885
+ registerTool({
886
+ name: 'codex_thread_list',
864
887
  description: 'List Codex threads visible to a codex-app session (`thread/list`), with optional filters.',
865
888
  parameters: {
866
889
  type: 'object',
@@ -883,7 +906,7 @@ const plugin = {
883
906
  }),
884
907
  });
885
908
  // ─── Tool: claude_agents_list (Claude CLI 2.1.x, claude engine) ───────
886
- api.registerTool({
909
+ registerTool({
887
910
  name: 'claude_agents_list',
888
911
  description: 'List Claude Code background agent sessions via `claude agents --json` (state/model/title/progress). One-shot, not tied to a managed session.',
889
912
  parameters: {
@@ -896,7 +919,7 @@ const plugin = {
896
919
  execute: async (_id, args) => getManager().claudeAgentsList({ all: args.all, cwd: args.cwd }),
897
920
  });
898
921
  // ─── Fan-out tools (parallel multi-engine task, no consensus) ─────────
899
- api.registerTool({
922
+ registerTool({
900
923
  name: 'fanout_start',
901
924
  description: 'Run one task across N engine/model agents IN PARALLEL and collect their answers (optional synthesis). Cross-engine best-of-N / diverse-perspective primitive. No rounds, votes, or worktrees — use council for isolated parallel edits. Runs in background; poll with fanout_status.',
902
925
  parameters: {
@@ -957,7 +980,7 @@ const plugin = {
957
980
  return { ok: true, ...session, note: 'Fan-out running in background. Poll with fanout_status.' };
958
981
  },
959
982
  });
960
- api.registerTool({
983
+ registerTool({
961
984
  name: 'fanout_status',
962
985
  description: 'Poll a running or finished fan-out by id; returns per-agent results and any synthesis.',
963
986
  parameters: {
@@ -967,7 +990,7 @@ const plugin = {
967
990
  },
968
991
  execute: async (_id, args) => ({ ok: true, ...getManager().fanoutStatus(args.id) }),
969
992
  });
970
- api.registerTool({
993
+ registerTool({
971
994
  name: 'fanout_abort',
972
995
  description: 'Abort a running fan-out by id (already-started agents finish; synthesis is skipped).',
973
996
  parameters: {
@@ -981,7 +1004,7 @@ const plugin = {
981
1004
  },
982
1005
  });
983
1006
  // ─── Tools: workflow_* (durable run kernel) ─────────────────────────
984
- api.registerTool({
1007
+ registerTool({
985
1008
  name: 'workflow_start',
986
1009
  description: "Start a durable workflow run. Nodes: agent | fanout | council | verifier | human_gate | router | subflow. Every state transition is checkpointed to disk, so a run survives a process restart and can be resumed. When a contract is supplied the run cannot reach `completed` unless the runtime's own checks pass — without one it completes as `unverified`, which means nothing checked it. Runs in background; poll with workflow_status.",
987
1010
  parameters: {
@@ -1137,7 +1160,7 @@ const plugin = {
1137
1160
  };
1138
1161
  },
1139
1162
  });
1140
- api.registerTool({
1163
+ registerTool({
1141
1164
  name: 'workflow_status',
1142
1165
  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).',
1143
1166
  parameters: {
@@ -1167,7 +1190,7 @@ const plugin = {
1167
1190
  };
1168
1191
  },
1169
1192
  });
1170
- api.registerTool({
1193
+ registerTool({
1171
1194
  name: 'workflow_list',
1172
1195
  description: 'List workflow runs on this machine, newest first. Survives restarts.',
1173
1196
  parameters: {
@@ -1190,7 +1213,7 @@ const plugin = {
1190
1213
  }),
1191
1214
  }),
1192
1215
  });
1193
- api.registerTool({
1216
+ registerTool({
1194
1217
  name: 'workflow_resume',
1195
1218
  description: 'Re-attach to a workflow whose process died. Nodes already marked succeeded are not re-run; the node that was in flight is retried, because a half-finished node left no result to trust.',
1196
1219
  parameters: {
@@ -1213,13 +1236,13 @@ const plugin = {
1213
1236
  return { ok: true, runId: record.runId, state: record.state, currentNode: record.currentNode };
1214
1237
  },
1215
1238
  });
1216
- api.registerTool({
1239
+ registerTool({
1217
1240
  name: 'workflow_cancel',
1218
1241
  description: 'Cancel a running workflow. The current node is asked to stop; the run ends in `cancelled`.',
1219
1242
  parameters: { type: 'object', properties: { runId: { type: 'string' } }, required: ['runId'] },
1220
1243
  execute: async (_id, args) => ({ ok: true, ...getManager().workflowCancel(args.runId) }),
1221
1244
  });
1222
- api.registerTool({
1245
+ registerTool({
1223
1246
  name: 'workflow_steer',
1224
1247
  description: "Queue a correction for a running workflow. The text is prepended to the next agent node's prompt — corrections belong before the task, not after it.",
1225
1248
  parameters: {
@@ -1232,7 +1255,7 @@ const plugin = {
1232
1255
  ...getManager().workflowSteer(args.runId, args.text),
1233
1256
  }),
1234
1257
  });
1235
- api.registerTool({
1258
+ registerTool({
1236
1259
  name: 'workflow_approve',
1237
1260
  description: 'Answer a workflow parked at a human_gate node.',
1238
1261
  parameters: {
@@ -1245,7 +1268,7 @@ const plugin = {
1245
1268
  ...getManager().workflowApprove(args.runId, args.approved),
1246
1269
  }),
1247
1270
  });
1248
- api.registerTool({
1271
+ registerTool({
1249
1272
  name: 'verify_run',
1250
1273
  description: 'Run an acceptance contract against a directory and return the evidence bundle. Use it to check work that did not come through a workflow — a plain session that edited a repo, say. The contract is yours; nothing is taken from agent output.',
1251
1274
  parameters: {
@@ -1333,7 +1356,7 @@ const plugin = {
1333
1356
  },
1334
1357
  });
1335
1358
  // ─── Tool: council_start ────────────────────────────────────────────
1336
- api.registerTool({
1359
+ registerTool({
1337
1360
  name: 'council_start',
1338
1361
  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.',
1339
1362
  parameters: {
@@ -1402,7 +1425,7 @@ const plugin = {
1402
1425
  },
1403
1426
  });
1404
1427
  // ─── Tool: council_status ───────────────────────────────────────────
1405
- api.registerTool({
1428
+ registerTool({
1406
1429
  name: 'council_status',
1407
1430
  description: 'Get the status of a running council session',
1408
1431
  parameters: {
@@ -1418,7 +1441,7 @@ const plugin = {
1418
1441
  },
1419
1442
  });
1420
1443
  // ─── Tool: council_abort ────────────────────────────────────────────
1421
- api.registerTool({
1444
+ registerTool({
1422
1445
  name: 'council_abort',
1423
1446
  description: 'Abort a running council, stopping all agent sessions',
1424
1447
  parameters: {
@@ -1432,7 +1455,7 @@ const plugin = {
1432
1455
  },
1433
1456
  });
1434
1457
  // ─── Tool: council_inject ───────────────────────────────────────────
1435
- api.registerTool({
1458
+ registerTool({
1436
1459
  name: 'council_inject',
1437
1460
  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.',
1438
1461
  parameters: {
@@ -1449,7 +1472,7 @@ const plugin = {
1449
1472
  },
1450
1473
  });
1451
1474
  // ─── Tool: council_review ──────────────────────────────────────────
1452
- api.registerTool({
1475
+ registerTool({
1453
1476
  name: 'council_review',
1454
1477
  description: 'Review a completed council session. Returns a structured report of all changed files, branches, worktrees, plan.md status, review files, and agent summaries. Does not modify any state — purely informational. Use this before deciding to accept or reject.',
1455
1478
  parameters: {
@@ -1463,7 +1486,7 @@ const plugin = {
1463
1486
  },
1464
1487
  });
1465
1488
  // ─── Tool: council_accept ──────────────────────────────────────────
1466
- api.registerTool({
1489
+ registerTool({
1467
1490
  name: 'council_accept',
1468
1491
  description: 'Accept and finalize council work. Cleans up all council scaffolding: removes worktrees, deletes council/* branches, removes plan.md and reviews/ directory. Only call after reviewing with council_review.',
1469
1492
  parameters: {
@@ -1477,7 +1500,7 @@ const plugin = {
1477
1500
  },
1478
1501
  });
1479
1502
  // ─── Tool: council_reject ──────────────────────────────────────────
1480
- api.registerTool({
1503
+ registerTool({
1481
1504
  name: 'council_reject',
1482
1505
  description: 'Reject council work and provide feedback. Rewrites plan.md with rejection feedback and commits it. Does NOT delete any worktrees or branches — the council can be restarted to retry. Use this when the council output is incomplete or broken.',
1483
1506
  parameters: {
@@ -1497,7 +1520,7 @@ const plugin = {
1497
1520
  },
1498
1521
  });
1499
1522
  // ─── Tool: session_send_to ────────────────────────────────────────────
1500
- api.registerTool({
1523
+ registerTool({
1501
1524
  name: 'session_send_to',
1502
1525
  description: 'Send a cross-session message from one session to another. If the target is idle, the message is delivered immediately. If busy, it is queued in the inbox for later delivery. Use "*" as target to broadcast to all other sessions.',
1503
1526
  parameters: {
@@ -1516,7 +1539,7 @@ const plugin = {
1516
1539
  },
1517
1540
  });
1518
1541
  // ─── Tool: session_inbox ──────────────────────────────────────────────
1519
- api.registerTool({
1542
+ registerTool({
1520
1543
  name: 'session_inbox',
1521
1544
  description: 'Read inbox messages for a session. Returns unread messages by default.',
1522
1545
  parameters: {
@@ -1533,7 +1556,7 @@ const plugin = {
1533
1556
  },
1534
1557
  });
1535
1558
  // ─── Tool: session_deliver_inbox ──────────────────────────────────────
1536
- api.registerTool({
1559
+ registerTool({
1537
1560
  name: 'session_deliver_inbox',
1538
1561
  description: 'Deliver all queued inbox messages to an idle session. Call this when a session finishes a task to process waiting messages.',
1539
1562
  parameters: {
@@ -1547,7 +1570,7 @@ const plugin = {
1547
1570
  },
1548
1571
  });
1549
1572
  // ─── Tool: ultraplan_start ──────────────────────────────────────
1550
- api.registerTool({
1573
+ registerTool({
1551
1574
  name: 'ultraplan_start',
1552
1575
  description: 'Start an Ultraplan session: a dedicated Opus planning session that explores your project for up to 30 minutes and produces a detailed implementation plan. Runs in background.',
1553
1576
  parameters: {
@@ -1570,7 +1593,7 @@ const plugin = {
1570
1593
  },
1571
1594
  });
1572
1595
  // ─── Tool: ultraplan_status ─────────────────────────────────────
1573
- api.registerTool({
1596
+ registerTool({
1574
1597
  name: 'ultraplan_status',
1575
1598
  description: 'Get the status of an Ultraplan session. Returns the plan text when completed.',
1576
1599
  parameters: {
@@ -1586,7 +1609,7 @@ const plugin = {
1586
1609
  },
1587
1610
  });
1588
1611
  // ─── Tool: ultrareview_start ────────────────────────────────────
1589
- api.registerTool({
1612
+ registerTool({
1590
1613
  name: 'ultrareview_start',
1591
1614
  description: 'Start an Ultrareview: a fleet of bug-hunting agents (5-20) that review your codebase from different angles in parallel. Each agent specializes in a different area (security, performance, logic, types, etc.). Runs in background.',
1592
1615
  parameters: {
@@ -1620,7 +1643,7 @@ const plugin = {
1620
1643
  },
1621
1644
  });
1622
1645
  // ─── Tool: ultrareview_status ───────────────────────────────────
1623
- api.registerTool({
1646
+ registerTool({
1624
1647
  name: 'ultrareview_status',
1625
1648
  description: 'Get the status of an Ultrareview. Returns all findings when completed.',
1626
1649
  parameters: {
@@ -1640,7 +1663,7 @@ const plugin = {
1640
1663
  // v2 architecture: three persistent agents (Planner / Coder / Reviewer).
1641
1664
  // Starting a run only launches the Planner — Coder and Reviewer are
1642
1665
  // spawned later by the Planner once the user approves the plan (S3).
1643
- api.registerTool({
1666
+ registerTool({
1644
1667
  name: 'autoloop_start',
1645
1668
  description: 'Start a v2 autoloop run in chat mode. Planner, Coder, and Reviewer default to Claude (opus/sonnet/sonnet) but each role can use a different engine/model. Coder and Reviewer are spawned later by the Planner after plan approval. Returns a run_id and the Planner session name.',
1646
1669
  parameters: {
@@ -1673,11 +1696,28 @@ const plugin = {
1673
1696
  description: 'Default Reviewer model (default: sonnet for Claude; engine default otherwise)',
1674
1697
  },
1675
1698
  reviewer_custom_engine: CUSTOM_ENGINE_SCHEMA,
1676
- send_timeout_ms: { type: 'number', description: 'Per-message wall-clock cap (default 600000 = 10 min)' },
1699
+ send_timeout_ms: {
1700
+ ...AUTOLOOP_TIMEOUT_SCHEMA.sendTimeoutMs,
1701
+ description: 'Per-agent send wall-clock cap in milliseconds (default 600000; inclusive range 5000–7200000)',
1702
+ },
1703
+ activity_lease_ms: {
1704
+ ...AUTOLOOP_TIMEOUT_SCHEMA.activityLeaseMs,
1705
+ description: 'Inactivity lease in milliseconds, renewed by qualified progress (default 1800000; inclusive range 60000–7200000)',
1706
+ },
1707
+ autoloop_hard_timeout_ms: {
1708
+ ...AUTOLOOP_TIMEOUT_SCHEMA.autoloopHardTimeoutMs,
1709
+ description: 'Absolute non-renewable run deadline in milliseconds (default 86400000; inclusive range 600000–259200000)',
1710
+ },
1677
1711
  },
1678
1712
  required: ['run_id', 'workspace'],
1679
1713
  },
1680
1714
  execute: async (_id, args) => {
1715
+ const timeoutConfig = {
1716
+ sendTimeoutMs: args.send_timeout_ms,
1717
+ activityLeaseMs: args.activity_lease_ms,
1718
+ autoloopHardTimeoutMs: args.autoloop_hard_timeout_ms,
1719
+ };
1720
+ validateAutoloopTimeoutConfig(timeoutConfig);
1681
1721
  const result = await getManager().autoloopStart({
1682
1722
  runId: args.run_id,
1683
1723
  workspace: sanitizeCwd(args.workspace),
@@ -1690,7 +1730,7 @@ const plugin = {
1690
1730
  reviewerEngine: args.reviewer_engine,
1691
1731
  reviewerModel: args.reviewer_model,
1692
1732
  reviewerCustomEngine: args.reviewer_custom_engine,
1693
- sendTimeoutMs: args.send_timeout_ms,
1733
+ ...timeoutConfig,
1694
1734
  });
1695
1735
  return {
1696
1736
  ok: true,
@@ -1700,7 +1740,7 @@ const plugin = {
1700
1740
  },
1701
1741
  });
1702
1742
  // ─── Tool: autoloop_chat ─────────────────────────────────────
1703
- api.registerTool({
1743
+ registerTool({
1704
1744
  name: 'autoloop_chat',
1705
1745
  description: "Send a chat message to the Planner of a v2 autoloop run. Returns the Planner's natural-language reply. Blocking: resolves after the Planner finishes its turn.",
1706
1746
  parameters: {
@@ -1717,7 +1757,7 @@ const plugin = {
1717
1757
  },
1718
1758
  });
1719
1759
  // ─── Tool: autoloop_status ───────────────────────────────────
1720
- api.registerTool({
1760
+ registerTool({
1721
1761
  name: 'autoloop_status',
1722
1762
  description: 'Get current state of a v2 autoloop run (status, iter, push count, subagents_spawned).',
1723
1763
  parameters: {
@@ -1733,7 +1773,7 @@ const plugin = {
1733
1773
  },
1734
1774
  });
1735
1775
  // ─── Tool: autoloop_list ─────────────────────────────────────
1736
- api.registerTool({
1776
+ registerTool({
1737
1777
  name: 'autoloop_list',
1738
1778
  description: 'List all v2 autoloop runs in this manager process.',
1739
1779
  parameters: { type: 'object', properties: {} },
@@ -1744,7 +1784,7 @@ const plugin = {
1744
1784
  },
1745
1785
  });
1746
1786
  // ─── Tool: autoloop_reset_agent ──────────────────────────────
1747
- api.registerTool({
1787
+ registerTool({
1748
1788
  name: 'autoloop_reset_agent',
1749
1789
  description: 'Reset a single subagent (Coder or Reviewer) on a v2 run. Stops its persistent session; the next directive/review_request will re-prime from the system prompt + ledger artifacts. Use when an agent has drifted (repeated rejects, hallucinated context, token bloat). Planner reset requires force=true because it discards chat history with the user.',
1750
1790
  parameters: {
@@ -1771,7 +1811,7 @@ const plugin = {
1771
1811
  },
1772
1812
  });
1773
1813
  // ─── Tool: autoloop_stop ─────────────────────────────────────
1774
- api.registerTool({
1814
+ registerTool({
1775
1815
  name: 'autoloop_stop',
1776
1816
  description: 'Terminate a v2 autoloop run. Stops Planner (and Coder/Reviewer once spawned).',
1777
1817
  parameters: {
@@ -1790,7 +1830,7 @@ const plugin = {
1790
1830
  },
1791
1831
  });
1792
1832
  // ─── ultraapp (read-only) ─────────────────────────────────────────────
1793
- api.registerTool({
1833
+ registerTool({
1794
1834
  name: 'ultraapp_list',
1795
1835
  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.',
1796
1836
  parameters: { type: 'object', properties: {} },
@@ -1800,7 +1840,7 @@ const plugin = {
1800
1840
  return { ok: true, runs };
1801
1841
  },
1802
1842
  });
1803
- api.registerTool({
1843
+ registerTool({
1804
1844
  name: 'ultraapp_get',
1805
1845
  description: 'Get the AppSpec, chat history, and state for one ultraapp run. Pass `runId` from ultraapp_list.',
1806
1846
  parameters: {
@@ -1819,7 +1859,7 @@ const plugin = {
1819
1859
  return { ok: true, spec, chat, state };
1820
1860
  },
1821
1861
  });
1822
- api.registerTool({
1862
+ registerTool({
1823
1863
  name: 'ultraapp_status',
1824
1864
  description: 'Get the run state (mode, failure reason if any) for one ultraapp run. Cheap; no chat or spec data.',
1825
1865
  parameters: {
@@ -1834,7 +1874,7 @@ const plugin = {
1834
1874
  },
1835
1875
  });
1836
1876
  // ─── ultraapp (write) ─────────────────────────────────────────────────
1837
- api.registerTool({
1877
+ registerTool({
1838
1878
  name: 'ultraapp_new',
1839
1879
  description: 'Start a new ultraapp run. Spawns the interview Claude Opus session; the first question lands on the SSE stream / next ultraapp_get call. Returns the runId.',
1840
1880
  parameters: { type: 'object', properties: {} },
@@ -1844,7 +1884,7 @@ const plugin = {
1844
1884
  return { ok: true, runId };
1845
1885
  },
1846
1886
  });
1847
- api.registerTool({
1887
+ registerTool({
1848
1888
  name: 'ultraapp_answer',
1849
1889
  description: 'Submit an answer to the current interview question. Either pick `value` (one of the option values from the latest question) or pass `freeform` text — both can be used together for "selected X with caveat".',
1850
1890
  parameters: {
@@ -1865,7 +1905,7 @@ const plugin = {
1865
1905
  return { ok: true };
1866
1906
  },
1867
1907
  });
1868
- api.registerTool({
1908
+ registerTool({
1869
1909
  name: 'ultraapp_add_file',
1870
1910
  description: 'Reference a local example file by absolute path (under $HOME or /tmp; symlinks and dotfiles rejected). Use this to attach reference inputs the interview can extract metadata from. For binary uploads from a browser, use the dashboard.',
1871
1911
  parameters: {
@@ -1885,7 +1925,7 @@ const plugin = {
1885
1925
  return { ok: true, ...r };
1886
1926
  },
1887
1927
  });
1888
- api.registerTool({
1928
+ registerTool({
1889
1929
  name: 'ultraapp_spec_edit',
1890
1930
  description: 'Manually edit the AppSpec via a JSON Patch (RFC 6902). Use sparingly — the interview engine produces and updates the spec automatically. Useful to correct a slot before clicking build.',
1891
1931
  parameters: {
@@ -1906,7 +1946,7 @@ const plugin = {
1906
1946
  return { ok: true };
1907
1947
  },
1908
1948
  });
1909
- api.registerTool({
1949
+ registerTool({
1910
1950
  name: 'ultraapp_build_start',
1911
1951
  description: 'Enqueue a build for the run. Builds run serially. Watch ultraapp_status for transitions: queued → building → build-complete (or → deploying → done if a router is wired).',
1912
1952
  parameters: {
@@ -1920,7 +1960,7 @@ const plugin = {
1920
1960
  return { ok: true };
1921
1961
  },
1922
1962
  });
1923
- api.registerTool({
1963
+ registerTool({
1924
1964
  name: 'ultraapp_build_cancel',
1925
1965
  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).',
1926
1966
  parameters: {
@@ -1934,7 +1974,7 @@ const plugin = {
1934
1974
  return { ok: true };
1935
1975
  },
1936
1976
  });
1937
- api.registerTool({
1977
+ registerTool({
1938
1978
  name: 'ultraapp_feedback',
1939
1979
  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.',
1940
1980
  parameters: {
@@ -1951,7 +1991,7 @@ const plugin = {
1951
1991
  return { ok: true };
1952
1992
  },
1953
1993
  });
1954
- api.registerTool({
1994
+ registerTool({
1955
1995
  name: 'ultraapp_promote_version',
1956
1996
  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).",
1957
1997
  parameters: {
@@ -1968,7 +2008,7 @@ const plugin = {
1968
2008
  return r;
1969
2009
  },
1970
2010
  });
1971
- api.registerTool({
2011
+ registerTool({
1972
2012
  name: 'ultraapp_start_container',
1973
2013
  description: 'Start the deployed container for a run (e.g., after `ultraapp_stop_container` or after orchestrator restart with a stopped container). Re-registers the slug on the router.',
1974
2014
  parameters: {
@@ -1982,7 +2022,7 @@ const plugin = {
1982
2022
  return r;
1983
2023
  },
1984
2024
  });
1985
- api.registerTool({
2025
+ registerTool({
1986
2026
  name: 'ultraapp_stop_container',
1987
2027
  description: 'Stop the deployed container and deregister the slug from the router. URL returns 404 until ultraapp_start_container.',
1988
2028
  parameters: {
@@ -1996,7 +2036,7 @@ const plugin = {
1996
2036
  return r;
1997
2037
  },
1998
2038
  });
1999
- api.registerTool({
2039
+ registerTool({
2000
2040
  name: 'ultraapp_delete',
2001
2041
  description: 'Delete a run completely: stops the container, removes the image, deregisters the slug, and removes the on-disk run dir at ~/.claw-orchestrator/ultraapps/<runId>/. Irreversible.',
2002
2042
  parameters: {