@covibes/zeroshot 5.3.0 → 5.4.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/README.md +94 -14
- package/cli/commands/providers.js +8 -9
- package/cli/index.js +3032 -2409
- package/cli/message-formatters-normal.js +28 -6
- package/cluster-templates/base-templates/debug-workflow.json +1 -1
- package/cluster-templates/base-templates/full-workflow.json +72 -188
- package/cluster-templates/base-templates/worker-validator.json +59 -3
- package/cluster-templates/conductor-bootstrap.json +4 -4
- package/lib/docker-config.js +8 -0
- package/lib/git-remote-utils.js +165 -0
- package/lib/id-detector.js +10 -7
- package/lib/provider-defaults.js +62 -0
- package/lib/provider-names.js +2 -1
- package/lib/settings/claude-auth.js +78 -0
- package/lib/settings.js +161 -63
- package/package.json +7 -2
- package/scripts/setup-merge-queue.sh +170 -0
- package/src/agent/agent-config.js +135 -82
- package/src/agent/agent-context-builder.js +297 -188
- package/src/agent/agent-hook-executor.js +310 -113
- package/src/agent/agent-lifecycle.js +385 -325
- package/src/agent/agent-stuck-detector.js +7 -7
- package/src/agent/agent-task-executor.js +824 -565
- package/src/agent/output-extraction.js +41 -24
- package/src/agent/output-reformatter.js +1 -1
- package/src/agent/schema-utils.js +108 -73
- package/src/agent-wrapper.js +10 -1
- package/src/agents/git-pusher-template.js +285 -0
- package/src/claude-task-runner.js +85 -34
- package/src/config-validator.js +922 -657
- package/src/input-helpers.js +65 -0
- package/src/isolation-manager.js +289 -199
- package/src/issue-providers/README.md +305 -0
- package/src/issue-providers/azure-devops-provider.js +273 -0
- package/src/issue-providers/base-provider.js +232 -0
- package/src/issue-providers/github-provider.js +179 -0
- package/src/issue-providers/gitlab-provider.js +241 -0
- package/src/issue-providers/index.js +196 -0
- package/src/issue-providers/jira-provider.js +239 -0
- package/src/ledger.js +22 -5
- package/src/lib/safe-exec.js +88 -0
- package/src/orchestrator.js +1107 -811
- package/src/preflight.js +313 -159
- package/src/process-metrics.js +98 -56
- package/src/providers/anthropic/cli-builder.js +53 -25
- package/src/providers/anthropic/index.js +72 -2
- package/src/providers/anthropic/output-parser.js +32 -14
- package/src/providers/base-provider.js +70 -0
- package/src/providers/capabilities.js +9 -0
- package/src/providers/google/output-parser.js +47 -38
- package/src/providers/index.js +19 -3
- package/src/providers/openai/cli-builder.js +14 -3
- package/src/providers/openai/index.js +1 -0
- package/src/providers/openai/output-parser.js +44 -30
- package/src/providers/opencode/cli-builder.js +42 -0
- package/src/providers/opencode/index.js +103 -0
- package/src/providers/opencode/models.js +55 -0
- package/src/providers/opencode/output-parser.js +122 -0
- package/src/schemas/sub-cluster.js +68 -39
- package/src/status-footer.js +94 -48
- package/src/sub-cluster-wrapper.js +76 -35
- package/src/template-resolver.js +12 -9
- package/src/tui/data-poller.js +123 -99
- package/src/tui/formatters.js +4 -3
- package/src/tui/keybindings.js +259 -318
- package/src/tui/renderer.js +11 -21
- package/task-lib/attachable-watcher.js +118 -81
- package/task-lib/claude-recovery.js +61 -24
- package/task-lib/commands/episodes.js +105 -0
- package/task-lib/commands/list.js +2 -2
- package/task-lib/commands/logs.js +231 -189
- package/task-lib/commands/schedules.js +111 -61
- package/task-lib/config.js +0 -2
- package/task-lib/runner.js +84 -27
- package/task-lib/scheduler.js +1 -1
- package/task-lib/store.js +467 -168
- package/task-lib/tui/formatters.js +94 -45
- package/task-lib/tui/renderer.js +13 -3
- package/task-lib/tui.js +73 -32
- package/task-lib/watcher.js +128 -90
- package/src/agents/git-pusher-agent.json +0 -20
- package/src/github.js +0 -139
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
* State machine: idle → evaluating → building_context → executing → idle
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
const { buildContext } = require('./agent-context-builder');
|
|
16
15
|
const { findMatchingTrigger, evaluateTrigger } = require('./agent-trigger-evaluator');
|
|
17
16
|
const { executeHook } = require('./agent-hook-executor');
|
|
18
17
|
const {
|
|
@@ -200,377 +199,438 @@ async function executeTriggerAction(agent, trigger, message) {
|
|
|
200
199
|
* @param {AgentWrapper} agent - Agent instance
|
|
201
200
|
* @param {Object} triggeringMessage - Message that triggered execution
|
|
202
201
|
*/
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
202
|
+
function handleMaxIterations(agent) {
|
|
203
|
+
if (agent.iteration < agent.maxIterations) {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
agent._log(`[Agent ${agent.id}] Hit max iterations (${agent.maxIterations}), stopping cluster`);
|
|
208
|
+
agent._publishLifecycle('MAX_ITERATIONS_REACHED', {
|
|
209
|
+
iteration: agent.iteration,
|
|
210
|
+
maxIterations: agent.maxIterations,
|
|
211
|
+
});
|
|
212
|
+
// Publish failure message - orchestrator watches for this and auto-stops
|
|
213
|
+
agent._publish({
|
|
214
|
+
topic: 'CLUSTER_FAILED',
|
|
215
|
+
receiver: 'system',
|
|
216
|
+
content: {
|
|
217
|
+
text: `Agent ${agent.id} hit max iterations limit (${agent.maxIterations}). Stopping cluster.`,
|
|
218
|
+
data: {
|
|
219
|
+
reason: 'max_iterations',
|
|
220
|
+
iteration: agent.iteration,
|
|
221
|
+
maxIterations: agent.maxIterations,
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
agent.state = 'failed';
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function logInputContext(agent, context) {
|
|
230
|
+
if (agent.quiet) {
|
|
206
231
|
return;
|
|
207
232
|
}
|
|
208
233
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
234
|
+
console.log(`
|
|
235
|
+
${'='.repeat(80)}`);
|
|
236
|
+
console.log(`📥 INPUT CONTEXT - Agent: ${agent.id} (Iteration: ${agent.iteration})`);
|
|
237
|
+
console.log(`${'='.repeat(80)}`);
|
|
238
|
+
console.log(context);
|
|
239
|
+
console.log(`${'='.repeat(80)}
|
|
240
|
+
`);
|
|
241
|
+
}
|
|
213
242
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
243
|
+
async function applyValidatorJitter(agent) {
|
|
244
|
+
// LOCK CONTENTION FIX: Add random jitter for validators to prevent thundering herd
|
|
245
|
+
// When multiple validators wake on the same trigger (e.g., IMPLEMENTATION_READY),
|
|
246
|
+
// they all try to spawn Claude CLI at the same time. Claude CLI uses a lock file
|
|
247
|
+
// per workspace, so only one can run. Adding jitter staggers their starts.
|
|
248
|
+
// SKIP in testMode - tests use mocks and don't need jitter
|
|
249
|
+
if (agent.role !== 'validator' || agent.testMode) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const jitterMs = Math.floor(Math.random() * 15000); // 0-15 seconds
|
|
254
|
+
if (!agent.quiet) {
|
|
255
|
+
agent._log(
|
|
256
|
+
`[Agent ${agent.id}] Adding ${Math.round(jitterMs / 1000)}s jitter to prevent lock contention`
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
await new Promise((resolve) => setTimeout(resolve, jitterMs));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function publishTaskStarted(agent, triggeringMessage) {
|
|
263
|
+
const modelSpec = agent._resolveModelSpec ? agent._resolveModelSpec() : null;
|
|
264
|
+
agent._publishLifecycle('TASK_STARTED', {
|
|
265
|
+
iteration: agent.iteration,
|
|
266
|
+
model: agent._selectModel(),
|
|
267
|
+
provider: agent._resolveProvider ? agent._resolveProvider() : 'claude',
|
|
268
|
+
modelSpec,
|
|
269
|
+
triggeredBy: triggeringMessage.topic,
|
|
270
|
+
triggerFrom: triggeringMessage.sender,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function attachResultMetadata(agent, result) {
|
|
275
|
+
// Add task ID to result for debugging and hooks
|
|
276
|
+
result.taskId = agent.currentTaskId;
|
|
277
|
+
result.agentId = agent.id;
|
|
278
|
+
result.iteration = agent.iteration;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function publishTaskCompleted(agent, result) {
|
|
282
|
+
agent._publishLifecycle('TASK_COMPLETED', {
|
|
283
|
+
iteration: agent.iteration,
|
|
284
|
+
success: true,
|
|
285
|
+
taskId: agent.currentTaskId,
|
|
286
|
+
tokenUsage: result.tokenUsage || null,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function publishTokenUsage(agent, result) {
|
|
291
|
+
// Publish TOKEN_USAGE event for aggregation and tracking
|
|
292
|
+
// CRITICAL: Include taskId for causal linking - allows consumers to group
|
|
293
|
+
// messages by task regardless of interleaved timing from async hooks
|
|
294
|
+
if (!result.tokenUsage) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
219
297
|
|
|
298
|
+
agent.messageBus.publish({
|
|
299
|
+
cluster_id: agent.cluster.id,
|
|
300
|
+
topic: 'TOKEN_USAGE',
|
|
301
|
+
sender: agent.id,
|
|
302
|
+
content: {
|
|
303
|
+
text: `${agent.id} used ${result.tokenUsage.inputTokens} input + ${result.tokenUsage.outputTokens} output tokens`,
|
|
304
|
+
data: {
|
|
305
|
+
agentId: agent.id,
|
|
306
|
+
role: agent.role,
|
|
307
|
+
model: agent._selectModel(),
|
|
308
|
+
iteration: agent.iteration,
|
|
309
|
+
taskId: agent.currentTaskId, // Causal linking for message ordering
|
|
310
|
+
...result.tokenUsage,
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function executeOnCompleteHookWithRetry(agent, triggeringMessage, result) {
|
|
317
|
+
// Execute onComplete hook WITH RETRY
|
|
318
|
+
// Hook failure shouldn't retry the entire task - just the hook
|
|
319
|
+
const hookMaxRetries = 3;
|
|
320
|
+
const hookBaseDelay = 1000;
|
|
321
|
+
let hookSuccess = false;
|
|
322
|
+
|
|
323
|
+
for (let hookAttempt = 1; hookAttempt <= hookMaxRetries && !hookSuccess; hookAttempt++) {
|
|
220
324
|
try {
|
|
221
|
-
// Execute onStart hook
|
|
222
325
|
await executeHook({
|
|
223
|
-
hook: agent.config.hooks?.
|
|
326
|
+
hook: agent.config.hooks?.onComplete,
|
|
224
327
|
agent: agent,
|
|
225
328
|
message: triggeringMessage,
|
|
226
|
-
result:
|
|
329
|
+
result: result,
|
|
227
330
|
messageBus: agent.messageBus,
|
|
228
331
|
cluster: agent.cluster,
|
|
229
332
|
orchestrator: agent.orchestrator,
|
|
230
333
|
});
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
334
|
+
hookSuccess = true;
|
|
335
|
+
} catch (hookError) {
|
|
336
|
+
console.error(`
|
|
337
|
+
${'='.repeat(80)}`);
|
|
338
|
+
console.error(
|
|
339
|
+
`🔴 HOOK EXECUTION FAILED - AGENT: ${agent.id} (Attempt ${hookAttempt}/${hookMaxRetries})`
|
|
340
|
+
);
|
|
341
|
+
console.error(`${'='.repeat(80)}`);
|
|
342
|
+
console.error(`Error: ${hookError.message}`);
|
|
343
|
+
|
|
344
|
+
if (hookAttempt < hookMaxRetries) {
|
|
345
|
+
const delay = hookBaseDelay * Math.pow(2, hookAttempt - 1);
|
|
346
|
+
console.error(`Will retry hook in ${delay}ms...`);
|
|
347
|
+
console.error(`${'='.repeat(80)}
|
|
348
|
+
`);
|
|
349
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
350
|
+
} else {
|
|
351
|
+
console.error(`${'='.repeat(80)}
|
|
352
|
+
`);
|
|
353
|
+
// All hook retries exhausted - throw to trigger task-level handling
|
|
354
|
+
throw new Error(
|
|
355
|
+
`Hook execution failed after ${hookMaxRetries} attempts. ` +
|
|
356
|
+
`Task completed successfully but hook could not publish result. ` +
|
|
357
|
+
`Original error: ${hookError.message}`
|
|
236
358
|
);
|
|
237
|
-
agent._publishLifecycle('MAX_ITERATIONS_REACHED', {
|
|
238
|
-
iteration: agent.iteration,
|
|
239
|
-
maxIterations: agent.maxIterations,
|
|
240
|
-
});
|
|
241
|
-
// Publish failure message - orchestrator watches for this and auto-stops
|
|
242
|
-
agent._publish({
|
|
243
|
-
topic: 'CLUSTER_FAILED',
|
|
244
|
-
receiver: 'system',
|
|
245
|
-
content: {
|
|
246
|
-
text: `Agent ${agent.id} hit max iterations limit (${agent.maxIterations}). Stopping cluster.`,
|
|
247
|
-
data: {
|
|
248
|
-
reason: 'max_iterations',
|
|
249
|
-
iteration: agent.iteration,
|
|
250
|
-
maxIterations: agent.maxIterations,
|
|
251
|
-
},
|
|
252
|
-
},
|
|
253
|
-
});
|
|
254
|
-
agent.state = 'failed';
|
|
255
|
-
return;
|
|
256
359
|
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
257
363
|
|
|
258
|
-
|
|
259
|
-
|
|
364
|
+
async function runTaskAttempt(agent, triggeringMessage) {
|
|
365
|
+
// Execute onStart hook
|
|
366
|
+
await executeHook({
|
|
367
|
+
hook: agent.config.hooks?.onStart,
|
|
368
|
+
agent: agent,
|
|
369
|
+
message: triggeringMessage,
|
|
370
|
+
result: undefined,
|
|
371
|
+
messageBus: agent.messageBus,
|
|
372
|
+
cluster: agent.cluster,
|
|
373
|
+
orchestrator: agent.orchestrator,
|
|
374
|
+
});
|
|
260
375
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
role: agent.role,
|
|
266
|
-
iteration: agent.iteration,
|
|
267
|
-
config: agent.config,
|
|
268
|
-
messageBus: agent.messageBus,
|
|
269
|
-
cluster: agent.cluster,
|
|
270
|
-
lastTaskEndTime: agent.lastTaskEndTime,
|
|
271
|
-
triggeringMessage,
|
|
272
|
-
selectedPrompt: agent._selectPrompt(),
|
|
273
|
-
});
|
|
376
|
+
// Check max iterations limit BEFORE incrementing (prevents infinite rejection loops)
|
|
377
|
+
if (handleMaxIterations(agent)) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
274
380
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
console.log(`\n${'='.repeat(80)}`);
|
|
278
|
-
console.log(`📥 INPUT CONTEXT - Agent: ${agent.id} (Iteration: ${agent.iteration})`);
|
|
279
|
-
console.log(`${'='.repeat(80)}`);
|
|
280
|
-
console.log(context);
|
|
281
|
-
console.log(`${'='.repeat(80)}\n`);
|
|
282
|
-
}
|
|
381
|
+
// Increment iteration BEFORE building context so worker knows current iteration
|
|
382
|
+
agent.iteration++;
|
|
283
383
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
// LOCK CONTENTION FIX: Add random jitter for validators to prevent thundering herd
|
|
288
|
-
// When multiple validators wake on the same trigger (e.g., IMPLEMENTATION_READY),
|
|
289
|
-
// they all try to spawn Claude CLI at the same time. Claude CLI uses a lock file
|
|
290
|
-
// per workspace, so only one can run. Adding jitter staggers their starts.
|
|
291
|
-
// SKIP in testMode - tests use mocks and don't need jitter
|
|
292
|
-
if (agent.role === 'validator' && !agent.testMode) {
|
|
293
|
-
const jitterMs = Math.floor(Math.random() * 15000); // 0-15 seconds
|
|
294
|
-
if (!agent.quiet) {
|
|
295
|
-
agent._log(
|
|
296
|
-
`[Agent ${agent.id}] Adding ${Math.round(jitterMs / 1000)}s jitter to prevent lock contention`
|
|
297
|
-
);
|
|
298
|
-
}
|
|
299
|
-
await new Promise((resolve) => setTimeout(resolve, jitterMs));
|
|
300
|
-
}
|
|
384
|
+
// Build context
|
|
385
|
+
agent.state = 'building_context';
|
|
386
|
+
const context = agent._buildContext(triggeringMessage);
|
|
301
387
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
iteration: agent.iteration,
|
|
305
|
-
model: agent._selectModel(),
|
|
306
|
-
provider: agent._resolveProvider ? agent._resolveProvider() : 'claude',
|
|
307
|
-
modelSpec,
|
|
308
|
-
triggeredBy: triggeringMessage.topic,
|
|
309
|
-
triggerFrom: triggeringMessage.sender,
|
|
310
|
-
});
|
|
388
|
+
// Log input context (helps debug what each agent sees)
|
|
389
|
+
logInputContext(agent, context);
|
|
311
390
|
|
|
312
|
-
|
|
391
|
+
// Spawn provider task
|
|
392
|
+
agent.state = 'executing_task';
|
|
393
|
+
await applyValidatorJitter(agent);
|
|
394
|
+
publishTaskStarted(agent, triggeringMessage);
|
|
313
395
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
result.agentId = agent.id;
|
|
317
|
-
result.iteration = agent.iteration;
|
|
396
|
+
const result = await agent._spawnClaudeTask(context);
|
|
397
|
+
attachResultMetadata(agent, result);
|
|
318
398
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
399
|
+
// Check if task execution failed
|
|
400
|
+
if (!result.success) {
|
|
401
|
+
throw new Error(result.error || 'Task execution failed');
|
|
402
|
+
}
|
|
323
403
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
404
|
+
// Set state to idle BEFORE publishing lifecycle event
|
|
405
|
+
// (so lifecycle message includes correct state)
|
|
406
|
+
agent.state = 'idle';
|
|
327
407
|
|
|
328
|
-
|
|
329
|
-
|
|
408
|
+
// Track completion time for context filtering (used by "since: last_task_end")
|
|
409
|
+
agent.lastTaskEndTime = Date.now();
|
|
330
410
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
411
|
+
publishTaskCompleted(agent, result);
|
|
412
|
+
publishTokenUsage(agent, result);
|
|
413
|
+
await executeOnCompleteHookWithRetry(agent, triggeringMessage, result);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function logTaskAttemptFailure(agent, attempt, maxRetries, error) {
|
|
417
|
+
// Log attempt failure
|
|
418
|
+
console.error(`
|
|
419
|
+
${'='.repeat(80)}`);
|
|
420
|
+
console.error(`🔴 TASK EXECUTION FAILED - AGENT: ${agent.id} (Attempt ${attempt}/${maxRetries})`);
|
|
421
|
+
console.error(`${'='.repeat(80)}`);
|
|
422
|
+
console.error(`Error: ${error.message}`);
|
|
423
|
+
}
|
|
337
424
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
425
|
+
async function handleLockContention() {
|
|
426
|
+
// Lock contention - add significant jittered delay
|
|
427
|
+
const lockDelay = 10000 + Math.floor(Math.random() * 20000); // 10-30 seconds
|
|
428
|
+
console.error(
|
|
429
|
+
`⚠️ Lock contention detected - waiting ${Math.round(lockDelay / 1000)}s before retry`
|
|
430
|
+
);
|
|
431
|
+
await new Promise((resolve) => setTimeout(resolve, lockDelay));
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
async function handleFinalFailure(agent, triggeringMessage, error, maxRetries) {
|
|
435
|
+
console.error(`
|
|
436
|
+
${'='.repeat(80)}`);
|
|
437
|
+
console.error(`🔴🔴🔴 MAX RETRIES EXHAUSTED - AGENT: ${agent.id} 🔴🔴🔴`);
|
|
438
|
+
console.error(`${'='.repeat(80)}`);
|
|
439
|
+
console.error(`All ${maxRetries} attempts failed`);
|
|
440
|
+
console.error(`Final error: ${error.message}`);
|
|
441
|
+
console.error(`Stack: ${error.stack}`);
|
|
442
|
+
console.error(`${'='.repeat(80)}
|
|
443
|
+
`);
|
|
444
|
+
|
|
445
|
+
// CRITICAL FIX: Validator crash = REJECTION (not auto-approval)
|
|
446
|
+
// Auto-approval on crash allowed broken code to be merged - unacceptable!
|
|
447
|
+
// If validator crashed 3x, something is fundamentally wrong - REJECT and investigate
|
|
448
|
+
if (agent.role === 'validator') {
|
|
449
|
+
console.error(`
|
|
450
|
+
${'='.repeat(80)}`);
|
|
451
|
+
console.error(`❌ VALIDATOR CRASHED - REJECTING (NOT AUTO-APPROVING)`);
|
|
452
|
+
console.error(`${'='.repeat(80)}`);
|
|
453
|
+
console.error(`Validator ${agent.id} crashed ${maxRetries} times`);
|
|
454
|
+
console.error(`Error: ${error.message}`);
|
|
455
|
+
console.error(`REJECTING validation - broken code will NOT be merged`);
|
|
456
|
+
console.error(`Investigation required before retry`);
|
|
457
|
+
console.error(`${'='.repeat(80)}
|
|
458
|
+
`);
|
|
459
|
+
|
|
460
|
+
// Publish REJECTION message (NOT approval!)
|
|
461
|
+
const hook = agent.config.hooks?.onComplete;
|
|
462
|
+
if (hook && hook.action === 'publish_message') {
|
|
463
|
+
agent._publish({
|
|
464
|
+
topic: hook.config.topic,
|
|
465
|
+
receiver: hook.config.receiver || 'broadcast',
|
|
466
|
+
content: {
|
|
467
|
+
text: `REJECTED: Validator crashed ${maxRetries} times - ${error.message}`,
|
|
468
|
+
data: {
|
|
469
|
+
approved: false, // REJECT!
|
|
470
|
+
crashedAfterRetries: true,
|
|
471
|
+
errors: JSON.stringify([
|
|
472
|
+
`VALIDATOR CRASHED ${maxRetries}x: ${error.message}`,
|
|
473
|
+
`Validation could not be performed - REJECTING to prevent broken code merge`,
|
|
474
|
+
`Investigation required before retry`,
|
|
475
|
+
]),
|
|
476
|
+
attempts: maxRetries,
|
|
477
|
+
requiresInvestigation: true,
|
|
356
478
|
},
|
|
357
|
-
}
|
|
358
|
-
}
|
|
479
|
+
},
|
|
480
|
+
});
|
|
481
|
+
}
|
|
359
482
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
let hookSuccess = false;
|
|
365
|
-
|
|
366
|
-
for (let hookAttempt = 1; hookAttempt <= hookMaxRetries && !hookSuccess; hookAttempt++) {
|
|
367
|
-
try {
|
|
368
|
-
await executeHook({
|
|
369
|
-
hook: agent.config.hooks?.onComplete,
|
|
370
|
-
agent: agent,
|
|
371
|
-
message: triggeringMessage,
|
|
372
|
-
result: result,
|
|
373
|
-
messageBus: agent.messageBus,
|
|
374
|
-
cluster: agent.cluster,
|
|
375
|
-
orchestrator: agent.orchestrator,
|
|
376
|
-
});
|
|
377
|
-
hookSuccess = true;
|
|
378
|
-
} catch (hookError) {
|
|
379
|
-
console.error(`\n${'='.repeat(80)}`);
|
|
380
|
-
console.error(
|
|
381
|
-
`🔴 HOOK EXECUTION FAILED - AGENT: ${agent.id} (Attempt ${hookAttempt}/${hookMaxRetries})`
|
|
382
|
-
);
|
|
383
|
-
console.error(`${'='.repeat(80)}`);
|
|
384
|
-
console.error(`Error: ${hookError.message}`);
|
|
385
|
-
|
|
386
|
-
if (hookAttempt < hookMaxRetries) {
|
|
387
|
-
const delay = hookBaseDelay * Math.pow(2, hookAttempt - 1);
|
|
388
|
-
console.error(`Will retry hook in ${delay}ms...`);
|
|
389
|
-
console.error(`${'='.repeat(80)}\n`);
|
|
390
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
391
|
-
} else {
|
|
392
|
-
console.error(`${'='.repeat(80)}\n`);
|
|
393
|
-
// All hook retries exhausted - throw to trigger task-level handling
|
|
394
|
-
throw new Error(
|
|
395
|
-
`Hook execution failed after ${hookMaxRetries} attempts. ` +
|
|
396
|
-
`Task completed successfully but hook could not publish result. ` +
|
|
397
|
-
`Original error: ${hookError.message}`
|
|
398
|
-
);
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
}
|
|
483
|
+
agent.state = 'error';
|
|
484
|
+
// Don't return - fall through to publish AGENT_ERROR and save failure info
|
|
485
|
+
// This allows the cluster to stop and be resumed after investigation
|
|
486
|
+
}
|
|
402
487
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
} catch (error) {
|
|
406
|
-
// LOCK CONTENTION: Add extra jittered delay for lock file errors
|
|
407
|
-
// This happens when multiple validators try to run Claude CLI in the same workspace
|
|
408
|
-
const isLockError = error.message && error.message.includes('Lock file');
|
|
488
|
+
// Non-validator agents: publish error and stop
|
|
489
|
+
agent.state = 'error';
|
|
409
490
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
491
|
+
// Save failure info to cluster for resume capability
|
|
492
|
+
agent.cluster.failureInfo = {
|
|
493
|
+
agentId: agent.id,
|
|
494
|
+
taskId: agent.currentTaskId,
|
|
495
|
+
iteration: agent.iteration,
|
|
496
|
+
error: error.message,
|
|
497
|
+
attempts: maxRetries,
|
|
498
|
+
timestamp: Date.now(),
|
|
499
|
+
};
|
|
417
500
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
501
|
+
// Publish error to message bus for visibility in logs
|
|
502
|
+
agent._publish({
|
|
503
|
+
topic: 'AGENT_ERROR',
|
|
504
|
+
receiver: 'broadcast',
|
|
505
|
+
content: {
|
|
506
|
+
text: `Task execution failed after ${maxRetries} attempts: ${error.message}`,
|
|
507
|
+
data: {
|
|
508
|
+
error: error.message,
|
|
509
|
+
stack: error.stack,
|
|
510
|
+
agent: agent.id,
|
|
511
|
+
role: agent.role,
|
|
512
|
+
iteration: agent.iteration,
|
|
513
|
+
taskId: agent.currentTaskId,
|
|
514
|
+
attempts: maxRetries,
|
|
515
|
+
hookFailureContext: error.message.includes('Hook uses result')
|
|
516
|
+
? {
|
|
517
|
+
taskId: agent.currentTaskId || 'UNKNOWN',
|
|
518
|
+
retrieveLogs: agent.currentTaskId
|
|
519
|
+
? `zeroshot task logs ${agent.currentTaskId}`
|
|
520
|
+
: 'N/A',
|
|
521
|
+
}
|
|
522
|
+
: undefined,
|
|
523
|
+
},
|
|
524
|
+
},
|
|
525
|
+
metadata: {
|
|
526
|
+
triggeringTopic: triggeringMessage.topic,
|
|
527
|
+
},
|
|
528
|
+
});
|
|
429
529
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
530
|
+
// Execute onError hook
|
|
531
|
+
await executeHook({
|
|
532
|
+
hook: agent.config.hooks?.onError,
|
|
533
|
+
agent: agent,
|
|
534
|
+
message: triggeringMessage,
|
|
535
|
+
result: { error },
|
|
536
|
+
messageBus: agent.messageBus,
|
|
537
|
+
cluster: agent.cluster,
|
|
538
|
+
orchestrator: agent.orchestrator,
|
|
539
|
+
});
|
|
439
540
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
// If validator crashed 3x, something is fundamentally wrong - REJECT and investigate
|
|
443
|
-
if (agent.role === 'validator') {
|
|
444
|
-
console.error(`\n${'='.repeat(80)}`);
|
|
445
|
-
console.error(`❌ VALIDATOR CRASHED - REJECTING (NOT AUTO-APPROVING)`);
|
|
446
|
-
console.error(`${'='.repeat(80)}`);
|
|
447
|
-
console.error(`Validator ${agent.id} crashed ${maxRetries} times`);
|
|
448
|
-
console.error(`Error: ${error.message}`);
|
|
449
|
-
console.error(`REJECTING validation - broken code will NOT be merged`);
|
|
450
|
-
console.error(`Investigation required before retry`);
|
|
451
|
-
console.error(`${'='.repeat(80)}\n`);
|
|
452
|
-
|
|
453
|
-
// Publish REJECTION message (NOT approval!)
|
|
454
|
-
const hook = agent.config.hooks?.onComplete;
|
|
455
|
-
if (hook && hook.action === 'publish_message') {
|
|
456
|
-
agent._publish({
|
|
457
|
-
topic: hook.config.topic,
|
|
458
|
-
receiver: hook.config.receiver || 'broadcast',
|
|
459
|
-
content: {
|
|
460
|
-
text: `REJECTED: Validator crashed ${maxRetries} times - ${error.message}`,
|
|
461
|
-
data: {
|
|
462
|
-
approved: false, // REJECT!
|
|
463
|
-
crashedAfterRetries: true,
|
|
464
|
-
errors: JSON.stringify([
|
|
465
|
-
`VALIDATOR CRASHED ${maxRetries}x: ${error.message}`,
|
|
466
|
-
`Validation could not be performed - REJECTING to prevent broken code merge`,
|
|
467
|
-
`Investigation required before retry`,
|
|
468
|
-
]),
|
|
469
|
-
attempts: maxRetries,
|
|
470
|
-
requiresInvestigation: true,
|
|
471
|
-
},
|
|
472
|
-
},
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
agent.state = 'error';
|
|
477
|
-
// Don't return - fall through to publish AGENT_ERROR and save failure info
|
|
478
|
-
// This allows the cluster to stop and be resumed after investigation
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
// Non-validator agents: publish error and stop
|
|
482
|
-
agent.state = 'error';
|
|
483
|
-
|
|
484
|
-
// Save failure info to cluster for resume capability
|
|
485
|
-
agent.cluster.failureInfo = {
|
|
486
|
-
agentId: agent.id,
|
|
487
|
-
taskId: agent.currentTaskId,
|
|
488
|
-
iteration: agent.iteration,
|
|
489
|
-
error: error.message,
|
|
490
|
-
attempts: maxRetries,
|
|
491
|
-
timestamp: Date.now(),
|
|
492
|
-
};
|
|
493
|
-
|
|
494
|
-
// Publish error to message bus for visibility in logs
|
|
495
|
-
agent._publish({
|
|
496
|
-
topic: 'AGENT_ERROR',
|
|
497
|
-
receiver: 'broadcast',
|
|
498
|
-
content: {
|
|
499
|
-
text: `Task execution failed after ${maxRetries} attempts: ${error.message}`,
|
|
500
|
-
data: {
|
|
501
|
-
error: error.message,
|
|
502
|
-
stack: error.stack,
|
|
503
|
-
agent: agent.id,
|
|
504
|
-
role: agent.role,
|
|
505
|
-
iteration: agent.iteration,
|
|
506
|
-
taskId: agent.currentTaskId,
|
|
507
|
-
attempts: maxRetries,
|
|
508
|
-
hookFailureContext: error.message.includes('Hook uses result')
|
|
509
|
-
? {
|
|
510
|
-
taskId: agent.currentTaskId || 'UNKNOWN',
|
|
511
|
-
retrieveLogs: agent.currentTaskId
|
|
512
|
-
? `zeroshot task logs ${agent.currentTaskId}`
|
|
513
|
-
: 'N/A',
|
|
514
|
-
}
|
|
515
|
-
: undefined,
|
|
516
|
-
},
|
|
517
|
-
},
|
|
518
|
-
metadata: {
|
|
519
|
-
triggeringTopic: triggeringMessage.topic,
|
|
520
|
-
},
|
|
521
|
-
});
|
|
541
|
+
agent.state = 'idle';
|
|
542
|
+
}
|
|
522
543
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
hook: agent.config.hooks?.onError,
|
|
526
|
-
agent: agent,
|
|
527
|
-
message: triggeringMessage,
|
|
528
|
-
result: { error },
|
|
529
|
-
messageBus: agent.messageBus,
|
|
530
|
-
cluster: agent.cluster,
|
|
531
|
-
orchestrator: agent.orchestrator,
|
|
532
|
-
});
|
|
544
|
+
async function scheduleRetry(agent, error, attempt, maxRetries, baseDelay) {
|
|
545
|
+
const delay = baseDelay * Math.pow(2, attempt - 1); // 2s, 4s, 8s
|
|
533
546
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
547
|
+
agent._publishLifecycle('RETRY_SCHEDULED', {
|
|
548
|
+
attempt,
|
|
549
|
+
maxRetries,
|
|
550
|
+
delayMs: delay,
|
|
551
|
+
error: error.message,
|
|
552
|
+
});
|
|
537
553
|
|
|
538
|
-
|
|
539
|
-
const delay = baseDelay * Math.pow(2, attempt - 1); // 2s, 4s, 8s
|
|
554
|
+
agent._log(`[${agent.id}] ⚠️ Retrying in ${delay}ms... (${attempt + 1}/${maxRetries})`);
|
|
540
555
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
maxRetries,
|
|
544
|
-
delayMs: delay,
|
|
545
|
-
error: error.message,
|
|
546
|
-
});
|
|
556
|
+
// Exponential backoff
|
|
557
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
547
558
|
|
|
548
|
-
|
|
559
|
+
agent._log(`[${agent.id}] 🔄 Starting retry attempt ${attempt + 1}/${maxRetries}`);
|
|
560
|
+
}
|
|
549
561
|
|
|
550
|
-
|
|
551
|
-
|
|
562
|
+
async function handleTaskAttemptFailure({
|
|
563
|
+
agent,
|
|
564
|
+
triggeringMessage,
|
|
565
|
+
error,
|
|
566
|
+
attempt,
|
|
567
|
+
maxRetries,
|
|
568
|
+
baseDelay,
|
|
569
|
+
}) {
|
|
570
|
+
// LOCK CONTENTION: Add extra jittered delay for lock file errors
|
|
571
|
+
// This happens when multiple validators try to run Claude CLI in the same workspace
|
|
572
|
+
const isLockError = error.message && error.message.includes('Lock file');
|
|
573
|
+
|
|
574
|
+
logTaskAttemptFailure(agent, attempt, maxRetries, error);
|
|
575
|
+
|
|
576
|
+
if (isLockError) {
|
|
577
|
+
await handleLockContention();
|
|
578
|
+
} else if (attempt < maxRetries) {
|
|
579
|
+
console.error(`Will retry in ${baseDelay * Math.pow(2, attempt - 1)}ms...`);
|
|
580
|
+
}
|
|
581
|
+
console.error(`${'='.repeat(80)}
|
|
582
|
+
`);
|
|
552
583
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
584
|
+
if (attempt >= maxRetries) {
|
|
585
|
+
await handleFinalFailure(agent, triggeringMessage, error, maxRetries);
|
|
586
|
+
return true;
|
|
556
587
|
}
|
|
588
|
+
|
|
589
|
+
await scheduleRetry(agent, error, attempt, maxRetries, baseDelay);
|
|
590
|
+
return false;
|
|
557
591
|
}
|
|
558
592
|
|
|
559
593
|
/**
|
|
560
|
-
*
|
|
561
|
-
*
|
|
562
|
-
* SAFE DETECTION: Only flags as stuck when MULTIPLE indicators agree:
|
|
563
|
-
* - Process sleeping (state=S)
|
|
564
|
-
* - Blocked on epoll/poll wait
|
|
565
|
-
* - Low CPU usage (<1%)
|
|
566
|
-
* - Low context switches (<10)
|
|
567
|
-
* - No network data in flight
|
|
568
|
-
*
|
|
569
|
-
* Single-indicator detection (just output freshness) has HIGH false positive risk.
|
|
570
|
-
* This multi-indicator approach eliminates false positives.
|
|
571
|
-
*
|
|
594
|
+
* Execute claude-zeroshots with built context
|
|
595
|
+
* Retries disabled by default. Set agent config `maxRetries` to enable (e.g., 3).
|
|
572
596
|
* @param {AgentWrapper} agent - Agent instance
|
|
597
|
+
* @param {Object} triggeringMessage - Message that triggered execution
|
|
573
598
|
*/
|
|
599
|
+
async function executeTask(agent, triggeringMessage) {
|
|
600
|
+
// Early exit if agent was stopped
|
|
601
|
+
if (!agent.running) {
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// Default: no retries (maxRetries=1 means 1 attempt only)
|
|
606
|
+
// Set agent config `maxRetries: 3` to enable exponential backoff retries
|
|
607
|
+
const maxRetries = agent.config.maxRetries ?? 1;
|
|
608
|
+
const baseDelay = 2000; // 2 seconds
|
|
609
|
+
|
|
610
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
611
|
+
// Check if agent was stopped between retries
|
|
612
|
+
if (!agent.running) {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
try {
|
|
617
|
+
await runTaskAttempt(agent, triggeringMessage);
|
|
618
|
+
return;
|
|
619
|
+
} catch (error) {
|
|
620
|
+
const shouldStop = await handleTaskAttemptFailure({
|
|
621
|
+
agent,
|
|
622
|
+
triggeringMessage,
|
|
623
|
+
error,
|
|
624
|
+
attempt,
|
|
625
|
+
maxRetries,
|
|
626
|
+
baseDelay,
|
|
627
|
+
});
|
|
628
|
+
if (shouldStop) {
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
574
634
|
function startLivenessCheck(agent) {
|
|
575
635
|
if (agent.livenessCheckInterval) {
|
|
576
636
|
clearInterval(agent.livenessCheckInterval);
|