@enderfga/claw-orchestrator 6.4.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.
Files changed (40) hide show
  1. package/README.md +19 -4
  2. package/dist/bin/mcp-server.js +5 -3
  3. package/dist/bin/mcp-server.js.map +1 -1
  4. package/dist/src/autoloop/dispatcher.d.ts +37 -3
  5. package/dist/src/autoloop/dispatcher.js +171 -30
  6. package/dist/src/autoloop/dispatcher.js.map +1 -1
  7. package/dist/src/autoloop/messages.d.ts +19 -1
  8. package/dist/src/autoloop/messages.js +4 -0
  9. package/dist/src/autoloop/messages.js.map +1 -1
  10. package/dist/src/autoloop/runner.d.ts +57 -2
  11. package/dist/src/autoloop/runner.js +260 -26
  12. package/dist/src/autoloop/runner.js.map +1 -1
  13. package/dist/src/autoloop/types.d.ts +62 -2
  14. package/dist/src/autoloop/types.js +63 -0
  15. package/dist/src/autoloop/types.js.map +1 -1
  16. package/dist/src/embedded-server.d.ts +8 -0
  17. package/dist/src/embedded-server.js +65 -4
  18. package/dist/src/embedded-server.js.map +1 -1
  19. package/dist/src/index.d.ts +9 -7
  20. package/dist/src/index.js +120 -80
  21. package/dist/src/index.js.map +1 -1
  22. package/dist/src/kernel/engine.js +19 -14
  23. package/dist/src/kernel/engine.js.map +1 -1
  24. package/dist/src/models.js +16 -4
  25. package/dist/src/models.js.map +1 -1
  26. package/dist/src/persistent-agy-session.d.ts +4 -2
  27. package/dist/src/persistent-agy-session.js +12 -3
  28. package/dist/src/persistent-agy-session.js.map +1 -1
  29. package/dist/src/persistent-session.js +11 -1
  30. package/dist/src/persistent-session.js.map +1 -1
  31. package/dist/src/session-manager.d.ts +6 -0
  32. package/dist/src/session-manager.js +246 -13
  33. package/dist/src/session-manager.js.map +1 -1
  34. package/openclaw.plugin.json +1 -1
  35. package/package.json +1 -1
  36. package/skills/SKILL.md +1 -1
  37. package/skills/references/autoloop.md +34 -3
  38. package/skills/references/claude-cli-tracking.md +2 -1
  39. package/skills/references/multi-engine.md +14 -2
  40. package/skills/references/tools.md +24 -3
package/README.md CHANGED
@@ -110,17 +110,32 @@ block, and the cancellation and permission limitations are in
110
110
 
111
111
  | Engine | CLI | Tested Version |
112
112
  | ----------- | ---------- | -------------- |
113
- | Claude Code | `claude` | 2.1.258 |
114
- | Codex | `codex` | 0.152.1 |
115
- | Antigravity | `agy` | 1.1.22 |
113
+ | Claude Code | `claude` | 2.1.259 |
114
+ | Codex | `codex` | 0.153.0 |
115
+ | Antigravity | `agy` | 1.1.25 |
116
116
  | Grok Build | `grok` | 1.0.13 |
117
- | OpenCode | `opencode` | 1.18.26 |
117
+ | OpenCode | `opencode` | 1.18.27 |
118
118
  | Custom CLI | any | — |
119
119
 
120
120
  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-enginecustom).
121
121
 
122
122
  ---
123
123
 
124
+ ## How the engine table stays honest
125
+
126
+ The versions above are not typed in — they are what the weekly sweep last ran. `scripts/sweep.ts`
127
+ measures each core engine's installed, pinned and upstream version, diffs the flags the wrapper
128
+ passes against the binary's `--help`, runs one live turn **through the real wrapper class**, and
129
+ smokes the ACP and MCP entry points. It has no LLM in it, so the thing that reports a wrapper as
130
+ broken cannot share the wrapper's failure modes.
131
+
132
+ `scripts/sweep-workflow.json` wraps it as a durable run on this project's own kernel: verifier →
133
+ router → an agent that drafts the alignment on a `sweep/<date>` branch → a human gate. That is the
134
+ bounded form of recursive self-improvement this project practises — the loop measures, proposes and
135
+ verifies; a person merges. The orchestrator never edits its own code unattended, on purpose: the
136
+ recovery path has to stay simpler than what it recovers. Its first scripted run found a default
137
+ model an engine had silently dropped, which three weeks of by-hand sweeps had walked past.
138
+
124
139
  ## Contributing
125
140
 
126
141
  See [`CONTRIBUTING.md`](./CONTRIBUTING.md). Run `npm run build && npm run lint && npm run format:check && npm run test` before submitting.
@@ -148,9 +148,11 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
148
148
  }
149
149
  try {
150
150
  const result = await tool.execute(`mcp-${Date.now()}`, (req.params.arguments ?? {}));
151
- return {
152
- content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
153
- };
151
+ // The plugin entry already formatted the payload as MCP-shaped text blocks,
152
+ // so hand them straight over. Stringifying `result` here instead would ship
153
+ // the data twice — once escaped inside `content[0].text`, once again under
154
+ // `details` — which is what this line did before the results were normalised.
155
+ return { content: result.content };
154
156
  }
155
157
  catch (err) {
156
158
  const msg = err instanceof Error ? err.message : String(err);
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-server.js","sourceRoot":"","sources":["../../bin/mcp-server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mcp-server.js","sourceRoot":"","sources":["../../bin/mcp-server.ts"],"names":[],"mappings":"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"}
@@ -47,6 +47,8 @@ export interface ClaudeAgentDispatcherConfig {
47
47
  reviewerCustomEngine?: CustomEngineConfig;
48
48
  /** Per-message wall-clock cap. Default 10 min. */
49
49
  sendTimeoutMs?: number;
50
+ /** Internal failure-atomic resume marker; never accepted from an agent. */
51
+ suppressFailedStartAudit?: boolean;
50
52
  /**
51
53
  * Optional acceptance contract. When present the Reviewer's `advance` is no
52
54
  * longer sufficient on its own: the contract runs against the workspace and a
@@ -110,6 +112,23 @@ export declare class ClaudeAgentDispatcher extends EventEmitter implements Agent
110
112
  /** Where Reviewer reads from. Created lazily by stageReviewSandbox(). */
111
113
  private reviewerSandboxDir;
112
114
  private ledgerDir;
115
+ /**
116
+ * One promise per immutable logical dispatch, so a re-delivered message is
117
+ * coalesced onto the first send instead of spending a second agent turn.
118
+ *
119
+ * Bounded, because what these promises resolve to is not small: a Coder
120
+ * dispatch returns `iter_artifacts` carrying the iteration's entire diff, and
121
+ * a run may legitimately last up to `MAX_AUTOLOOP_HARD_TIMEOUT_MS` (72 h).
122
+ * Retaining every one of those for the run's lifetime grows without limit.
123
+ *
124
+ * Evicting the oldest is safe: the queue consumes each message once, and the
125
+ * only replay path (`restorePausedMessages`) re-queues messages that were
126
+ * parked *before* delivery. Re-delivery therefore happens within a few
127
+ * messages of the original, far inside this window — while an entry still
128
+ * in flight is never evicted, so concurrent duplicates always coalesce.
129
+ */
130
+ private logicalDispatches;
131
+ private readonly settledDispatches;
113
132
  constructor(config: ClaudeAgentDispatcherConfig);
114
133
  get sessionNames(): {
115
134
  planner: string;
@@ -121,6 +140,17 @@ export declare class ClaudeAgentDispatcher extends EventEmitter implements Agent
121
140
  purge?: boolean;
122
141
  }): Promise<void>;
123
142
  deliver(env: AnyAutoloopMessage): Promise<AnyAutoloopMessage[]>;
143
+ /** Drop the oldest settled dispatches once the retained set exceeds its cap. */
144
+ private trimLogicalDispatches;
145
+ /** Current per-agent deadline, including the backward-compatible default. */
146
+ get effectiveSendTimeoutMs(): number;
147
+ /**
148
+ * Apply an already-authorized timeout migration. This seam intentionally has
149
+ * no decrease mode: even an internal caller must supply a valid strict
150
+ * increase, while SessionManager owns persistence and dispatch matching.
151
+ */
152
+ increaseSendTimeoutMs(next: number): void;
153
+ private deliverOnce;
124
154
  private roleModel;
125
155
  private validateSelection;
126
156
  /**
@@ -171,10 +201,14 @@ export declare class ClaudeAgentDispatcher extends EventEmitter implements Agent
171
201
  force?: boolean;
172
202
  eagerRestart?: boolean;
173
203
  }): Promise<void>;
204
+ private pendingSendTimeout;
205
+ private recoverableSendTimeout;
206
+ /** One physical send attempt with strict timeout classification. */
207
+ private sendAttempt;
174
208
  /**
175
- * Wrap a subagent send. If the underlying session throws or returns an
176
- * error string, auto-reset the subagent once and retry. Used by
177
- * deliverToCoder / deliverToReviewer to recover from subprocess deaths.
209
+ * Wrap a subagent send. Genuine timeouts are never retried because the first
210
+ * turn may still finish with side effects. Other throws/error results retain
211
+ * the established reset-once/retry-once subprocess recovery path.
178
212
  */
179
213
  private sendWithRecovery;
180
214
  /**
@@ -17,6 +17,7 @@
17
17
  * into the runner queue.
18
18
  */
19
19
  import { EventEmitter } from 'node:events';
20
+ import { createHash } from 'node:crypto';
20
21
  import * as fs from 'node:fs';
21
22
  import * as path from 'node:path';
22
23
  import { fileURLToPath } from 'node:url';
@@ -27,7 +28,7 @@ import { capturePatch, changedFilesSince } from '../verify/baseline.js';
27
28
  import { runContract } from '../verify/runner.js';
28
29
  import { writeEvidence } from '../verify/evidence.js';
29
30
  import { Msg } from './messages.js';
30
- import { LEDGER_SCHEMA_VERSION, } from './types.js';
31
+ import { DEFAULT_SEND_TIMEOUT_MS, LEDGER_SCHEMA_VERSION, validateAutoloopTimeoutConfig, } from './types.js';
31
32
  import { applyPlannerToolCalls, parsePlannerReply, } from './planner-tools.js';
32
33
  import { extractIterComplete, extractReviewComplete, parseAgentReply } from './agent-tools.js';
33
34
  /**
@@ -66,6 +67,35 @@ function resolveConfigByName(filename) {
66
67
  const resolveDefaultPlannerPrompt = () => resolveConfigByName('autoloop-planner-prompt.md');
67
68
  const resolveDefaultCoderPrompt = () => resolveConfigByName('autoloop-coder-prompt.md');
68
69
  const resolveDefaultReviewerPrompt = () => resolveConfigByName('autoloop-reviewer-prompt.md');
70
+ /**
71
+ * Adapter send deadlines use one of these explicit signals. Deliberately do
72
+ * not classify arbitrary messages containing "timeout": configuration errors
73
+ * and other subprocess failures must retain reset-once/retry-once recovery.
74
+ */
75
+ function genuineSendTimeoutMessage(error) {
76
+ const record = error;
77
+ const message = typeof error === 'string' ? error : typeof record?.message === 'string' ? record.message : '';
78
+ const namedTimeout = record?.name === 'TimeoutError';
79
+ const codedTimeout = record?.code === 'ETIMEDOUT';
80
+ const adapterTimeout = /^Timeout waiting for (?:(?:.+ )?response|.+ turn to complete)$/i.test(message);
81
+ return namedTimeout || codedTimeout || adapterTimeout ? message || 'Agent send timed out' : null;
82
+ }
83
+ /**
84
+ * How many settled logical dispatches stay cached for dedup. One iteration
85
+ * spends a handful, so this holds many iterations' worth of replay window while
86
+ * keeping the retained diffs bounded.
87
+ */
88
+ const MAX_RETAINED_DISPATCHES = 64;
89
+ /**
90
+ * Hash only immutable logical routing identity. `msg_id` distinguishes two
91
+ * intentional sends with otherwise identical content; envelope timestamps,
92
+ * wall-clock time, random values, retry attempts, and mutable counters are not
93
+ * inputs, so re-delivery in this or another dispatcher derives the same ID.
94
+ */
95
+ function deriveDispatchId(runId, env) {
96
+ const identity = JSON.stringify([runId, env.msg_id, env.iter, env.from, env.to, env.type]);
97
+ return `dispatch_${createHash('sha256').update(identity).digest('hex')}`;
98
+ }
69
99
  export class ClaudeAgentDispatcher extends EventEmitter {
70
100
  config;
71
101
  logger;
@@ -85,6 +115,23 @@ export class ClaudeAgentDispatcher extends EventEmitter {
85
115
  /** Where Reviewer reads from. Created lazily by stageReviewSandbox(). */
86
116
  reviewerSandboxDir;
87
117
  ledgerDir;
118
+ /**
119
+ * One promise per immutable logical dispatch, so a re-delivered message is
120
+ * coalesced onto the first send instead of spending a second agent turn.
121
+ *
122
+ * Bounded, because what these promises resolve to is not small: a Coder
123
+ * dispatch returns `iter_artifacts` carrying the iteration's entire diff, and
124
+ * a run may legitimately last up to `MAX_AUTOLOOP_HARD_TIMEOUT_MS` (72 h).
125
+ * Retaining every one of those for the run's lifetime grows without limit.
126
+ *
127
+ * Evicting the oldest is safe: the queue consumes each message once, and the
128
+ * only replay path (`restorePausedMessages`) re-queues messages that were
129
+ * parked *before* delivery. Re-delivery therefore happens within a few
130
+ * messages of the original, far inside this window — while an entry still
131
+ * in flight is never evicted, so concurrent duplicates always coalesce.
132
+ */
133
+ logicalDispatches = new Map();
134
+ settledDispatches = new Set();
88
135
  constructor(config) {
89
136
  super();
90
137
  this.config = config;
@@ -122,11 +169,13 @@ export class ClaudeAgentDispatcher extends EventEmitter {
122
169
  await this.ensurePlanner();
123
170
  }
124
171
  async shutdown(reason, opts = {}) {
125
- this.appendDecisionLog({
126
- kind: 'terminate',
127
- actor: reason === 'phase_error_circuit' ? 'runner' : 'planner',
128
- payload: { reason },
129
- });
172
+ if (!(reason === 'start-failed' && this.config.suppressFailedStartAudit)) {
173
+ this.appendDecisionLog({
174
+ kind: 'terminate',
175
+ actor: reason === 'phase_error_circuit' ? 'runner' : 'planner',
176
+ payload: { reason },
177
+ });
178
+ }
130
179
  // Best-effort cleanup. Stopping a non-existent session is a no-op.
131
180
  // keepPersisted: true keeps the persistedSessions entry on disk so a
132
181
  // later /autoloop/<id>/resume can re-attach the Planner's Claude
@@ -141,13 +190,61 @@ export class ClaudeAgentDispatcher extends EventEmitter {
141
190
  }
142
191
  }
143
192
  async deliver(env) {
193
+ const dispatchId = deriveDispatchId(this.config.runId, env);
194
+ const existing = this.logicalDispatches.get(dispatchId);
195
+ if (existing)
196
+ return await existing;
197
+ const pending = this.deliverOnce(env, dispatchId);
198
+ this.logicalDispatches.set(dispatchId, pending);
199
+ // Mark settled before trimming so eviction can tell an in-flight dispatch
200
+ // from a finished one. A rejection settles too; `deliver` still rethrows it
201
+ // to this caller, and the entry is only a dedup record afterwards.
202
+ void pending.then(() => this.settledDispatches.add(dispatchId), () => this.settledDispatches.add(dispatchId));
203
+ try {
204
+ return await pending;
205
+ }
206
+ finally {
207
+ this.trimLogicalDispatches();
208
+ }
209
+ }
210
+ /** Drop the oldest settled dispatches once the retained set exceeds its cap. */
211
+ trimLogicalDispatches() {
212
+ if (this.logicalDispatches.size <= MAX_RETAINED_DISPATCHES)
213
+ return;
214
+ for (const id of this.logicalDispatches.keys()) {
215
+ if (this.logicalDispatches.size <= MAX_RETAINED_DISPATCHES)
216
+ break;
217
+ if (!this.settledDispatches.has(id))
218
+ continue; // still in flight — must stay
219
+ this.logicalDispatches.delete(id);
220
+ this.settledDispatches.delete(id);
221
+ }
222
+ }
223
+ /** Current per-agent deadline, including the backward-compatible default. */
224
+ get effectiveSendTimeoutMs() {
225
+ return this.config.sendTimeoutMs ?? DEFAULT_SEND_TIMEOUT_MS;
226
+ }
227
+ /**
228
+ * Apply an already-authorized timeout migration. This seam intentionally has
229
+ * no decrease mode: even an internal caller must supply a valid strict
230
+ * increase, while SessionManager owns persistence and dispatch matching.
231
+ */
232
+ increaseSendTimeoutMs(next) {
233
+ validateAutoloopTimeoutConfig({ sendTimeoutMs: next });
234
+ const current = this.effectiveSendTimeoutMs;
235
+ if (next <= current) {
236
+ throw new Error(`sendTimeoutMs must be strictly greater than the current effective value ${current}`);
237
+ }
238
+ this.config.sendTimeoutMs = next;
239
+ }
240
+ async deliverOnce(env, dispatchId) {
144
241
  switch (env.to) {
145
242
  case 'planner':
146
- return await this.deliverToPlanner(env);
243
+ return await this.deliverToPlanner(env, dispatchId);
147
244
  case 'coder':
148
- return await this.deliverToCoder(env);
245
+ return await this.deliverToCoder(env, dispatchId);
149
246
  case 'reviewer':
150
- return await this.deliverToReviewer(env);
247
+ return await this.deliverToReviewer(env, dispatchId);
151
248
  default:
152
249
  throw new Error(`[autoloop] unexpected dispatcher target: ${env.to}`);
153
250
  }
@@ -375,17 +472,54 @@ export class ClaudeAgentDispatcher extends EventEmitter {
375
472
  await this.ensureReviewer();
376
473
  }
377
474
  }
378
- /**
379
- * Wrap a subagent send. If the underlying session throws or returns an
380
- * error string, auto-reset the subagent once and retry. Used by
381
- * deliverToCoder / deliverToReviewer to recover from subprocess deaths.
382
- */
383
- async sendWithRecovery(agent, name, promptText) {
475
+ pendingSendTimeout(env, agent, dispatchId) {
476
+ return {
477
+ status: 'awaiting_resume',
478
+ dispatch_id: dispatchId,
479
+ agent,
480
+ message_id: env.msg_id,
481
+ message_type: env.type,
482
+ iter: env.iter,
483
+ timeout_ms: this.config.sendTimeoutMs ?? DEFAULT_SEND_TIMEOUT_MS,
484
+ };
485
+ }
486
+ recoverableSendTimeout(pending, error) {
487
+ const timeout = { ...pending, error };
488
+ this.appendDecisionLog({
489
+ kind: 'send_timeout',
490
+ actor: 'dispatcher',
491
+ payload: { ...timeout },
492
+ });
493
+ return { output: '', error, recoverable_timeout: timeout };
494
+ }
495
+ /** One physical send attempt with strict timeout classification. */
496
+ async sendAttempt(name, promptText, pending) {
384
497
  try {
385
- return (await this.config.manager.sendMessage(name, promptText, {
386
- timeout: this.config.sendTimeoutMs ?? 10 * 60_000,
498
+ const result = (await this.config.manager.sendMessage(name, promptText, {
499
+ timeout: this.config.sendTimeoutMs ?? DEFAULT_SEND_TIMEOUT_MS,
387
500
  parentRunId: this.config.runId,
388
501
  }));
502
+ const timeoutMessage = result.error ? genuineSendTimeoutMessage(result.error) : null;
503
+ return timeoutMessage ? this.recoverableSendTimeout(pending, timeoutMessage) : result;
504
+ }
505
+ catch (err) {
506
+ const timeoutMessage = genuineSendTimeoutMessage(err);
507
+ if (timeoutMessage)
508
+ return this.recoverableSendTimeout(pending, timeoutMessage);
509
+ throw err;
510
+ }
511
+ }
512
+ /**
513
+ * Wrap a subagent send. Genuine timeouts are never retried because the first
514
+ * turn may still finish with side effects. Other throws/error results retain
515
+ * the established reset-once/retry-once subprocess recovery path.
516
+ */
517
+ async sendWithRecovery(agent, name, promptText, pending) {
518
+ try {
519
+ const result = await this.sendAttempt(name, promptText, pending);
520
+ if (result.recoverable_timeout || !result.error)
521
+ return result;
522
+ throw new Error(result.error);
389
523
  }
390
524
  catch (err) {
391
525
  this.logger.warn?.(`[autoloop] ${agent} send threw, attempting reset+retry: ${err.message}`);
@@ -396,10 +530,10 @@ export class ClaudeAgentDispatcher extends EventEmitter {
396
530
  // lockstep retries across concurrent runs.
397
531
  await new Promise((r) => setTimeout(r, 500 + Math.floor(Math.random() * 250)));
398
532
  try {
399
- return (await this.config.manager.sendMessage(name, promptText, {
400
- timeout: this.config.sendTimeoutMs ?? 10 * 60_000,
401
- parentRunId: this.config.runId,
402
- }));
533
+ const result = await this.sendAttempt(name, promptText, pending);
534
+ if (result.recoverable_timeout || !result.error)
535
+ return result;
536
+ throw new Error(result.error);
403
537
  }
404
538
  catch (err2) {
405
539
  this.logger.error?.(`[autoloop] ${agent} second attempt failed after reset: ${err2.message}`);
@@ -530,7 +664,7 @@ export class ClaudeAgentDispatcher extends EventEmitter {
530
664
  });
531
665
  this.plannerStarted = true;
532
666
  }
533
- async deliverToPlanner(env) {
667
+ async deliverToPlanner(env, dispatchId) {
534
668
  if (env.type !== 'chat' && env.type !== 'directive_ack' && env.type !== 'iter_done') {
535
669
  // Other types (push_user / pause / resume / terminate) are runner-only
536
670
  // or planner-emitted; they should never arrive *to* planner.
@@ -553,10 +687,11 @@ export class ClaudeAgentDispatcher extends EventEmitter {
553
687
  // iter_done
554
688
  promptText = `[system] iter ${env.iter} done. verdict=${env.payload.verdict} metric=${env.payload.metric}`;
555
689
  }
556
- const result = (await this.config.manager.sendMessage(this.plannerName, this.withRoleInstructions('planner', this.plannerSelection, this.plannerSystemPrompt, promptText), {
557
- timeout: this.config.sendTimeoutMs ?? 10 * 60_000,
558
- parentRunId: this.config.runId,
559
- }));
690
+ const pendingTimeout = this.pendingSendTimeout(env, 'planner', dispatchId);
691
+ const result = await this.sendAttempt(this.plannerName, this.withRoleInstructions('planner', this.plannerSelection, this.plannerSystemPrompt, promptText), pendingTimeout);
692
+ if (result.recoverable_timeout) {
693
+ return [Msg.sendTimeout(env.iter, result.recoverable_timeout)];
694
+ }
560
695
  if (result.error) {
561
696
  this.logger.error?.(`[autoloop] planner send error: ${result.error}`);
562
697
  this.emit('planner_error', new Error(result.error));
@@ -681,7 +816,7 @@ export class ClaudeAgentDispatcher extends EventEmitter {
681
816
  });
682
817
  this.coderStarted = true;
683
818
  }
684
- async deliverToCoder(env) {
819
+ async deliverToCoder(env, dispatchId) {
685
820
  if (env.type !== 'directive') {
686
821
  throw new Error(`[autoloop] coder does not accept message type=${env.type}`);
687
822
  }
@@ -727,7 +862,10 @@ export class ClaudeAgentDispatcher extends EventEmitter {
727
862
  text: `🔨 Coder iter ${env.iter} working…`,
728
863
  ts: new Date().toISOString(),
729
864
  });
730
- const result = await this.sendWithRecovery('coder', this.coderName, this.withRoleInstructions('coder', this.coderSelection, this.coderSystemPrompt, promptText));
865
+ const result = await this.sendWithRecovery('coder', this.coderName, this.withRoleInstructions('coder', this.coderSelection, this.coderSystemPrompt, promptText), this.pendingSendTimeout(env, 'coder', dispatchId));
866
+ if (result.recoverable_timeout) {
867
+ return [Msg.sendTimeout(env.iter, result.recoverable_timeout)];
868
+ }
731
869
  this.recordTurn('coder', 'user', promptText);
732
870
  this.recordTurn('coder', 'agent', (result.output ?? '').trim());
733
871
  // A3: subprocess died (recovery retry exhausted). Surface as phase_error
@@ -921,7 +1059,7 @@ export class ClaudeAgentDispatcher extends EventEmitter {
921
1059
  }
922
1060
  }
923
1061
  }
924
- async deliverToReviewer(env) {
1062
+ async deliverToReviewer(env, dispatchId) {
925
1063
  if (env.type !== 'review_request') {
926
1064
  throw new Error(`[autoloop] reviewer does not accept message type=${env.type}`);
927
1065
  }
@@ -942,7 +1080,10 @@ export class ClaudeAgentDispatcher extends EventEmitter {
942
1080
  text: `🔍 Reviewer iter ${env.payload.iter} auditing…`,
943
1081
  ts: new Date().toISOString(),
944
1082
  });
945
- const result = await this.sendWithRecovery('reviewer', this.reviewerName, this.withRoleInstructions('reviewer', this.reviewerSelection, this.reviewerSessionPrompt ?? this.reviewerSystemPrompt, promptText));
1083
+ const result = await this.sendWithRecovery('reviewer', this.reviewerName, this.withRoleInstructions('reviewer', this.reviewerSelection, this.reviewerSessionPrompt ?? this.reviewerSystemPrompt, promptText), this.pendingSendTimeout(env, 'reviewer', dispatchId));
1084
+ if (result.recoverable_timeout) {
1085
+ return [Msg.sendTimeout(env.iter, result.recoverable_timeout)];
1086
+ }
946
1087
  this.recordTurn('reviewer', 'user', promptText);
947
1088
  this.recordTurn('reviewer', 'agent', (result.output ?? '').trim());
948
1089
  if (result.fatal) {