@covibes/zeroshot 5.3.0 → 5.4.1

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 (119) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +8 -385
  3. package/cli/index.js +108 -4615
  4. package/package.json +16 -125
  5. package/CHANGELOG.md +0 -537
  6. package/cli/commands/providers.js +0 -150
  7. package/cli/lib/first-run.js +0 -211
  8. package/cli/lib/update-checker.js +0 -281
  9. package/cli/message-formatter-utils.js +0 -75
  10. package/cli/message-formatters-normal.js +0 -253
  11. package/cli/message-formatters-watch.js +0 -181
  12. package/cluster-templates/base-templates/debug-workflow.json +0 -375
  13. package/cluster-templates/base-templates/full-workflow.json +0 -697
  14. package/cluster-templates/base-templates/single-worker.json +0 -63
  15. package/cluster-templates/base-templates/worker-validator.json +0 -152
  16. package/cluster-templates/conductor-bootstrap.json +0 -124
  17. package/docker/zeroshot-cluster/Dockerfile +0 -152
  18. package/docker/zeroshot-cluster/pre-baked-deps.json +0 -28
  19. package/lib/completion.js +0 -174
  20. package/lib/docker-config.js +0 -212
  21. package/lib/id-detector.js +0 -53
  22. package/lib/provider-detection.js +0 -59
  23. package/lib/provider-names.js +0 -56
  24. package/lib/settings.js +0 -405
  25. package/lib/stream-json-parser.js +0 -10
  26. package/scripts/fix-node-pty-permissions.js +0 -75
  27. package/scripts/record-demo.sh +0 -279
  28. package/scripts/test-install.sh +0 -40
  29. package/scripts/validate-templates.js +0 -100
  30. package/src/agent/agent-config.js +0 -193
  31. package/src/agent/agent-context-builder.js +0 -335
  32. package/src/agent/agent-hook-executor.js +0 -402
  33. package/src/agent/agent-lifecycle.js +0 -683
  34. package/src/agent/agent-stuck-detector.js +0 -256
  35. package/src/agent/agent-task-executor.js +0 -1431
  36. package/src/agent/agent-trigger-evaluator.js +0 -67
  37. package/src/agent/output-extraction.js +0 -200
  38. package/src/agent/output-reformatter.js +0 -175
  39. package/src/agent/schema-utils.js +0 -111
  40. package/src/agent-wrapper.js +0 -570
  41. package/src/agents/git-pusher-agent.json +0 -20
  42. package/src/attach/attach-client.js +0 -438
  43. package/src/attach/attach-server.js +0 -543
  44. package/src/attach/index.js +0 -35
  45. package/src/attach/protocol.js +0 -220
  46. package/src/attach/ring-buffer.js +0 -121
  47. package/src/attach/socket-discovery.js +0 -267
  48. package/src/claude-task-runner.js +0 -523
  49. package/src/config-router.js +0 -80
  50. package/src/config-validator.js +0 -1660
  51. package/src/copy-worker.js +0 -43
  52. package/src/github.js +0 -139
  53. package/src/isolation-manager.js +0 -1381
  54. package/src/ledger.js +0 -614
  55. package/src/logic-engine.js +0 -223
  56. package/src/message-bus-bridge.js +0 -139
  57. package/src/message-bus.js +0 -249
  58. package/src/name-generator.js +0 -232
  59. package/src/orchestrator.js +0 -2519
  60. package/src/preflight.js +0 -485
  61. package/src/process-metrics.js +0 -566
  62. package/src/providers/anthropic/cli-builder.js +0 -45
  63. package/src/providers/anthropic/index.js +0 -134
  64. package/src/providers/anthropic/models.js +0 -23
  65. package/src/providers/anthropic/output-parser.js +0 -159
  66. package/src/providers/base-provider.js +0 -181
  67. package/src/providers/capabilities.js +0 -51
  68. package/src/providers/google/cli-builder.js +0 -55
  69. package/src/providers/google/index.js +0 -116
  70. package/src/providers/google/models.js +0 -24
  71. package/src/providers/google/output-parser.js +0 -92
  72. package/src/providers/index.js +0 -75
  73. package/src/providers/openai/cli-builder.js +0 -122
  74. package/src/providers/openai/index.js +0 -135
  75. package/src/providers/openai/models.js +0 -21
  76. package/src/providers/openai/output-parser.js +0 -129
  77. package/src/schemas/sub-cluster.js +0 -156
  78. package/src/status-footer.js +0 -980
  79. package/src/sub-cluster-wrapper.js +0 -560
  80. package/src/task-runner.js +0 -30
  81. package/src/template-resolver.js +0 -369
  82. package/src/tui/CHANGES.txt +0 -133
  83. package/src/tui/LAYOUT.md +0 -261
  84. package/src/tui/README.txt +0 -192
  85. package/src/tui/TWO-LEVEL-NAVIGATION.md +0 -186
  86. package/src/tui/data-poller.js +0 -325
  87. package/src/tui/demo.js +0 -208
  88. package/src/tui/formatters.js +0 -128
  89. package/src/tui/index.js +0 -193
  90. package/src/tui/keybindings.js +0 -383
  91. package/src/tui/layout.js +0 -334
  92. package/src/tui/renderer.js +0 -194
  93. package/task-lib/attachable-watcher.js +0 -272
  94. package/task-lib/claude-recovery.js +0 -119
  95. package/task-lib/commands/clean.js +0 -50
  96. package/task-lib/commands/get-log-path.js +0 -23
  97. package/task-lib/commands/kill.js +0 -32
  98. package/task-lib/commands/list.js +0 -105
  99. package/task-lib/commands/logs.js +0 -411
  100. package/task-lib/commands/resume.js +0 -42
  101. package/task-lib/commands/run.js +0 -57
  102. package/task-lib/commands/schedule.js +0 -105
  103. package/task-lib/commands/scheduler-cmd.js +0 -96
  104. package/task-lib/commands/schedules.js +0 -98
  105. package/task-lib/commands/status.js +0 -44
  106. package/task-lib/commands/unschedule.js +0 -16
  107. package/task-lib/completion.js +0 -9
  108. package/task-lib/config.js +0 -10
  109. package/task-lib/name-generator.js +0 -230
  110. package/task-lib/package.json +0 -3
  111. package/task-lib/runner.js +0 -144
  112. package/task-lib/scheduler.js +0 -252
  113. package/task-lib/store.js +0 -230
  114. package/task-lib/tui/formatters.js +0 -166
  115. package/task-lib/tui/index.js +0 -197
  116. package/task-lib/tui/layout.js +0 -111
  117. package/task-lib/tui/renderer.js +0 -119
  118. package/task-lib/tui.js +0 -384
  119. package/task-lib/watcher.js +0 -188
@@ -1,683 +0,0 @@
1
- // @ts-nocheck
2
- /**
3
- * AgentLifecycle - Agent state machine and lifecycle management
4
- *
5
- * Provides:
6
- * - Agent startup and shutdown
7
- * - Message handling and routing
8
- * - Trigger action execution (execute_task, stop_cluster)
9
- * - Task execution with retry logic
10
- * - Liveness monitoring with multi-indicator stuck detection
11
- *
12
- * State machine: idle → evaluating → building_context → executing → idle
13
- */
14
-
15
- const { buildContext } = require('./agent-context-builder');
16
- const { findMatchingTrigger, evaluateTrigger } = require('./agent-trigger-evaluator');
17
- const { executeHook } = require('./agent-hook-executor');
18
- const {
19
- analyzeProcessHealth,
20
- isPlatformSupported,
21
- STUCK_THRESHOLD,
22
- } = require('./agent-stuck-detector');
23
-
24
- /**
25
- * Start the agent (begin listening for triggers)
26
- * @param {AgentWrapper} agent - Agent instance
27
- */
28
- function start(agent) {
29
- if (agent.running) {
30
- throw new Error(`Agent ${agent.id} is already running`);
31
- }
32
-
33
- agent.running = true;
34
- agent.state = 'idle';
35
-
36
- // Subscribe to all messages for this cluster
37
- agent.unsubscribe = agent.messageBus.subscribe((message) => {
38
- if (message.cluster_id === agent.cluster.id) {
39
- handleMessage(agent, message).catch((error) => {
40
- // FATAL: Message handling failed - crash loud
41
- console.error(`\n${'='.repeat(80)}`);
42
- console.error(`🔴 FATAL: Agent ${agent.id} message handler crashed`);
43
- console.error(`${'='.repeat(80)}`);
44
- console.error(`Topic: ${message.topic}`);
45
- console.error(`Error: ${error.message}`);
46
- console.error(`Stack: ${error.stack}`);
47
- console.error(`${'='.repeat(80)}\n`);
48
- // Re-throw to crash the process - DO NOT SILENTLY CONTINUE
49
- throw error;
50
- });
51
- }
52
- });
53
-
54
- agent._log(`Agent ${agent.id} started (role: ${agent.role})`);
55
- agent._publishLifecycle('STARTED', {
56
- triggers: agent.config.triggers?.map((t) => t.topic) || [],
57
- });
58
- }
59
-
60
- /**
61
- * Stop the agent
62
- * Waits for any in-flight execution to complete before returning.
63
- * @param {AgentWrapper} agent - Agent instance
64
- * @returns {Promise<void>}
65
- */
66
- async function stop(agent) {
67
- if (!agent.running) {
68
- return;
69
- }
70
-
71
- agent.running = false;
72
- agent.state = 'stopped';
73
-
74
- if (agent.unsubscribe) {
75
- agent.unsubscribe();
76
- agent.unsubscribe = null;
77
- }
78
-
79
- // Kill current task if any
80
- if (agent.currentTask) {
81
- agent._killTask();
82
- }
83
-
84
- // Wait for in-flight execution to complete (up to 5 seconds)
85
- // This prevents write-after-close race conditions
86
- if (agent._currentExecution) {
87
- try {
88
- await Promise.race([
89
- agent._currentExecution,
90
- new Promise((resolve) => setTimeout(resolve, 5000)),
91
- ]);
92
- } catch {
93
- // Ignore errors from cancelled execution
94
- }
95
- agent._currentExecution = null;
96
- }
97
-
98
- agent._log(`Agent ${agent.id} stopped`);
99
- }
100
-
101
- /**
102
- * Handle incoming message
103
- * @param {AgentWrapper} agent - Agent instance
104
- * @param {Object} message - Incoming message
105
- */
106
- async function handleMessage(agent, message) {
107
- // Check if any trigger matches FIRST (before state check)
108
- const matchingTrigger = findMatchingTrigger({
109
- triggers: agent.config.triggers,
110
- message,
111
- });
112
-
113
- if (!matchingTrigger) {
114
- return; // No trigger for this message type
115
- }
116
-
117
- // Now check state - LOG if we're dropping a message we SHOULD handle
118
- if (!agent.running) {
119
- console.warn(`[${agent.id}] ⚠️ DROPPING message (not running): ${message.topic}`);
120
- return;
121
- }
122
- if (agent.state !== 'idle') {
123
- console.warn(
124
- `[${agent.id}] ⚠️ DROPPING message (busy, state=${agent.state}): ${message.topic}`
125
- );
126
- return;
127
- }
128
-
129
- // Evaluate trigger logic
130
- agent.state = 'evaluating_logic';
131
-
132
- const agentContext = {
133
- id: agent.id,
134
- role: agent.role,
135
- iteration: agent.iteration,
136
- cluster_id: agent.cluster.id,
137
- };
138
-
139
- const shouldExecute = evaluateTrigger({
140
- trigger: matchingTrigger,
141
- message,
142
- agent: agentContext,
143
- logicEngine: agent.logicEngine,
144
- });
145
-
146
- if (!shouldExecute) {
147
- agent.state = 'idle';
148
- return;
149
- }
150
-
151
- // Execute trigger action (lifecycle event published inside for execute_task)
152
- // Track execution so stop() can wait for it
153
- const executionPromise = executeTriggerAction(agent, matchingTrigger, message);
154
- agent._currentExecution = executionPromise;
155
- try {
156
- await executionPromise;
157
- } finally {
158
- // Clear only if this is still our execution (not replaced by another)
159
- if (agent._currentExecution === executionPromise) {
160
- agent._currentExecution = null;
161
- }
162
- }
163
- }
164
-
165
- /**
166
- * Execute trigger action
167
- * @param {AgentWrapper} agent - Agent instance
168
- * @param {Object} trigger - Matched trigger config
169
- * @param {Object} message - Triggering message
170
- */
171
- async function executeTriggerAction(agent, trigger, message) {
172
- const action = trigger.action || 'execute_task';
173
-
174
- if (action === 'execute_task') {
175
- await executeTask(agent, message);
176
- } else if (action === 'stop_cluster') {
177
- // Publish CLUSTER_COMPLETE message to signal successful completion
178
- agent._publish({
179
- topic: 'CLUSTER_COMPLETE',
180
- receiver: 'system',
181
- content: {
182
- text: 'All validation passed. Cluster completing successfully.',
183
- data: {
184
- reason: 'all_validators_approved',
185
- timestamp: Date.now(),
186
- },
187
- },
188
- });
189
- agent.state = 'completed';
190
- agent._log(`Agent ${agent.id}: Cluster completion triggered`);
191
- } else {
192
- console.warn(`Unknown action: ${action}`);
193
- agent.state = 'idle';
194
- }
195
- }
196
-
197
- /**
198
- * Execute claude-zeroshots with built context
199
- * Retries disabled by default. Set agent config `maxRetries` to enable (e.g., 3).
200
- * @param {AgentWrapper} agent - Agent instance
201
- * @param {Object} triggeringMessage - Message that triggered execution
202
- */
203
- async function executeTask(agent, triggeringMessage) {
204
- // Early exit if agent was stopped
205
- if (!agent.running) {
206
- return;
207
- }
208
-
209
- // Default: no retries (maxRetries=1 means 1 attempt only)
210
- // Set agent config `maxRetries: 3` to enable exponential backoff retries
211
- const maxRetries = agent.config.maxRetries ?? 1;
212
- const baseDelay = 2000; // 2 seconds
213
-
214
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
215
- // Check if agent was stopped between retries
216
- if (!agent.running) {
217
- return;
218
- }
219
-
220
- try {
221
- // Execute onStart hook
222
- await executeHook({
223
- hook: agent.config.hooks?.onStart,
224
- agent: agent,
225
- message: triggeringMessage,
226
- result: undefined,
227
- messageBus: agent.messageBus,
228
- cluster: agent.cluster,
229
- orchestrator: agent.orchestrator,
230
- });
231
-
232
- // Check max iterations limit BEFORE incrementing (prevents infinite rejection loops)
233
- if (agent.iteration >= agent.maxIterations) {
234
- agent._log(
235
- `[Agent ${agent.id}] Hit max iterations (${agent.maxIterations}), stopping cluster`
236
- );
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
- }
257
-
258
- // Increment iteration BEFORE building context so worker knows current iteration
259
- agent.iteration++;
260
-
261
- // Build context
262
- agent.state = 'building_context';
263
- const context = buildContext({
264
- id: agent.id,
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
- });
274
-
275
- // Log input context (helps debug what each agent sees)
276
- if (!agent.quiet) {
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
- }
283
-
284
- // Spawn provider task
285
- agent.state = 'executing_task';
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
- }
301
-
302
- const modelSpec = agent._resolveModelSpec ? agent._resolveModelSpec() : null;
303
- agent._publishLifecycle('TASK_STARTED', {
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
- });
311
-
312
- const result = await agent._spawnClaudeTask(context);
313
-
314
- // Add task ID to result for debugging and hooks
315
- result.taskId = agent.currentTaskId;
316
- result.agentId = agent.id;
317
- result.iteration = agent.iteration;
318
-
319
- // Check if task execution failed
320
- if (!result.success) {
321
- throw new Error(result.error || 'Task execution failed');
322
- }
323
-
324
- // Set state to idle BEFORE publishing lifecycle event
325
- // (so lifecycle message includes correct state)
326
- agent.state = 'idle';
327
-
328
- // Track completion time for context filtering (used by "since: last_task_end")
329
- agent.lastTaskEndTime = Date.now();
330
-
331
- agent._publishLifecycle('TASK_COMPLETED', {
332
- iteration: agent.iteration,
333
- success: true,
334
- taskId: agent.currentTaskId,
335
- tokenUsage: result.tokenUsage || null,
336
- });
337
-
338
- // Publish TOKEN_USAGE event for aggregation and tracking
339
- // CRITICAL: Include taskId for causal linking - allows consumers to group
340
- // messages by task regardless of interleaved timing from async hooks
341
- if (result.tokenUsage) {
342
- agent.messageBus.publish({
343
- cluster_id: agent.cluster.id,
344
- topic: 'TOKEN_USAGE',
345
- sender: agent.id,
346
- content: {
347
- text: `${agent.id} used ${result.tokenUsage.inputTokens} input + ${result.tokenUsage.outputTokens} output tokens`,
348
- data: {
349
- agentId: agent.id,
350
- role: agent.role,
351
- model: agent._selectModel(),
352
- iteration: agent.iteration,
353
- taskId: agent.currentTaskId, // Causal linking for message ordering
354
- ...result.tokenUsage,
355
- },
356
- },
357
- });
358
- }
359
-
360
- // Execute onComplete hook WITH RETRY
361
- // Hook failure shouldn't retry the entire task - just the hook
362
- const hookMaxRetries = 3;
363
- const hookBaseDelay = 1000;
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
- }
402
-
403
- // ✅ SUCCESS - exit retry loop
404
- return;
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');
409
-
410
- // Log attempt failure
411
- console.error(`\n${'='.repeat(80)}`);
412
- console.error(
413
- `🔴 TASK EXECUTION FAILED - AGENT: ${agent.id} (Attempt ${attempt}/${maxRetries})`
414
- );
415
- console.error(`${'='.repeat(80)}`);
416
- console.error(`Error: ${error.message}`);
417
-
418
- if (isLockError) {
419
- // Lock contention - add significant jittered delay
420
- const lockDelay = 10000 + Math.floor(Math.random() * 20000); // 10-30 seconds
421
- console.error(
422
- `⚠️ Lock contention detected - waiting ${Math.round(lockDelay / 1000)}s before retry`
423
- );
424
- await new Promise((resolve) => setTimeout(resolve, lockDelay));
425
- } else if (attempt < maxRetries) {
426
- console.error(`Will retry in ${baseDelay * Math.pow(2, attempt - 1)}ms...`);
427
- }
428
- console.error(`${'='.repeat(80)}\n`);
429
-
430
- // Last attempt - give up
431
- if (attempt >= maxRetries) {
432
- console.error(`\n${'='.repeat(80)}`);
433
- console.error(`🔴🔴🔴 MAX RETRIES EXHAUSTED - AGENT: ${agent.id} 🔴🔴🔴`);
434
- console.error(`${'='.repeat(80)}`);
435
- console.error(`All ${maxRetries} attempts failed`);
436
- console.error(`Final error: ${error.message}`);
437
- console.error(`Stack: ${error.stack}`);
438
- console.error(`${'='.repeat(80)}\n`);
439
-
440
- // CRITICAL FIX: Validator crash = REJECTION (not auto-approval)
441
- // Auto-approval on crash allowed broken code to be merged - unacceptable!
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
- });
522
-
523
- // Execute onError hook
524
- await executeHook({
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
- });
533
-
534
- agent.state = 'idle';
535
- return; // Give up
536
- }
537
-
538
- // Not the last attempt - prepare for retry
539
- const delay = baseDelay * Math.pow(2, attempt - 1); // 2s, 4s, 8s
540
-
541
- agent._publishLifecycle('RETRY_SCHEDULED', {
542
- attempt,
543
- maxRetries,
544
- delayMs: delay,
545
- error: error.message,
546
- });
547
-
548
- agent._log(`[${agent.id}] ⚠️ Retrying in ${delay}ms... (${attempt + 1}/${maxRetries})`);
549
-
550
- // Exponential backoff
551
- await new Promise((resolve) => setTimeout(resolve, delay));
552
-
553
- agent._log(`[${agent.id}] 🔄 Starting retry attempt ${attempt + 1}/${maxRetries}`);
554
- // Continue to next iteration of for loop
555
- }
556
- }
557
- }
558
-
559
- /**
560
- * Start monitoring agent output liveness using multi-indicator stuck detection
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
- *
572
- * @param {AgentWrapper} agent - Agent instance
573
- */
574
- function startLivenessCheck(agent) {
575
- if (agent.livenessCheckInterval) {
576
- clearInterval(agent.livenessCheckInterval);
577
- }
578
-
579
- // Check if platform supports /proc filesystem (Linux only)
580
- if (!isPlatformSupported()) {
581
- agent._log(
582
- `[${agent.id}] Liveness check disabled: /proc filesystem not available (non-Linux platform)`
583
- );
584
- return;
585
- }
586
-
587
- // Check every 60 seconds (gives time for multi-indicator analysis)
588
- const CHECK_INTERVAL_MS = 60 * 1000;
589
- const ANALYSIS_SAMPLE_MS = 5000; // Sample CPU/context switches over 5 seconds
590
-
591
- agent.livenessCheckInterval = setInterval(async () => {
592
- // Skip if no task running or no PID tracked
593
- if (!agent.currentTask || !agent.processPid) {
594
- return;
595
- }
596
-
597
- // Skip if output is recent (process is clearly active)
598
- if (agent.lastOutputTime) {
599
- const timeSinceLastOutput = Date.now() - agent.lastOutputTime;
600
- if (timeSinceLastOutput < agent.staleDuration) {
601
- return; // Output is recent, definitely not stuck
602
- }
603
- }
604
-
605
- // Output is stale - run multi-indicator analysis to confirm
606
- agent._log(
607
- `[${agent.id}] Output stale for ${Math.round((Date.now() - (agent.lastOutputTime || 0)) / 1000)}s, running multi-indicator analysis...`
608
- );
609
-
610
- try {
611
- const analysis = await analyzeProcessHealth(agent.processPid, ANALYSIS_SAMPLE_MS);
612
-
613
- // Process died during analysis
614
- if (analysis.isLikelyStuck === null) {
615
- agent._log(`[${agent.id}] Process analysis inconclusive: ${analysis.reason}`);
616
- return;
617
- }
618
-
619
- // Log analysis details for debugging
620
- agent._log(
621
- `[${agent.id}] Analysis: score=${analysis.stuckScore}/${STUCK_THRESHOLD}, ` +
622
- `state=${analysis.state}, wchan=${analysis.wchan}, ` +
623
- `CPU=${analysis.cpuPercent}%, ctxSwitches=${analysis.ctxSwitchesDelta}`
624
- );
625
-
626
- if (analysis.isLikelyStuck) {
627
- agent._log(`⚠️ Agent ${agent.id}: CONFIRMED STUCK (confidence: ${analysis.confidence})`);
628
- agent._log(` ${analysis.analysis}`);
629
-
630
- // CHANGED: Stale detection is informational only - never kills tasks
631
- // Publish stale detection event with full analysis (for logging/monitoring)
632
- agent._publishLifecycle('AGENT_STALE_WARNING', {
633
- timeSinceLastOutput: Date.now() - (agent.lastOutputTime || 0),
634
- staleDuration: agent.staleDuration,
635
- lastOutputTime: agent.lastOutputTime,
636
- // Multi-indicator analysis results
637
- stuckScore: analysis.stuckScore,
638
- confidence: analysis.confidence,
639
- processState: analysis.state,
640
- wchan: analysis.wchan,
641
- cpuPercent: analysis.cpuPercent,
642
- ctxSwitchesDelta: analysis.ctxSwitchesDelta,
643
- indicators: analysis.indicators,
644
- analysis: analysis.analysis,
645
- });
646
-
647
- // Keep monitoring - do NOT stop the agent
648
- // User can manually intervene with 'zeroshot resume' if needed
649
- // stopLivenessCheck(agent); // REMOVED - keep monitoring
650
- } else {
651
- agent._log(
652
- `[${agent.id}] Process appears WORKING despite stale output (score: ${analysis.stuckScore})`
653
- );
654
- agent._log(` ${analysis.analysis}`);
655
- // Don't flag as stuck - process is legitimately working
656
- }
657
- } catch (err) {
658
- agent._log(`[${agent.id}] Error during stuck analysis: ${err.message}`);
659
- // Don't flag as stuck on analysis error
660
- }
661
- }, CHECK_INTERVAL_MS);
662
- }
663
-
664
- /**
665
- * Stop liveness monitoring
666
- * @param {AgentWrapper} agent - Agent instance
667
- */
668
- function stopLivenessCheck(agent) {
669
- if (agent.livenessCheckInterval) {
670
- clearInterval(agent.livenessCheckInterval);
671
- agent.livenessCheckInterval = null;
672
- }
673
- }
674
-
675
- module.exports = {
676
- start,
677
- stop,
678
- handleMessage,
679
- executeTriggerAction,
680
- executeTask,
681
- startLivenessCheck,
682
- stopLivenessCheck,
683
- };