@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.
- package/LICENSE +1 -1
- package/README.md +8 -385
- package/cli/index.js +108 -4615
- package/package.json +16 -125
- package/CHANGELOG.md +0 -537
- package/cli/commands/providers.js +0 -150
- package/cli/lib/first-run.js +0 -211
- package/cli/lib/update-checker.js +0 -281
- package/cli/message-formatter-utils.js +0 -75
- package/cli/message-formatters-normal.js +0 -253
- package/cli/message-formatters-watch.js +0 -181
- package/cluster-templates/base-templates/debug-workflow.json +0 -375
- package/cluster-templates/base-templates/full-workflow.json +0 -697
- package/cluster-templates/base-templates/single-worker.json +0 -63
- package/cluster-templates/base-templates/worker-validator.json +0 -152
- package/cluster-templates/conductor-bootstrap.json +0 -124
- package/docker/zeroshot-cluster/Dockerfile +0 -152
- package/docker/zeroshot-cluster/pre-baked-deps.json +0 -28
- package/lib/completion.js +0 -174
- package/lib/docker-config.js +0 -212
- package/lib/id-detector.js +0 -53
- package/lib/provider-detection.js +0 -59
- package/lib/provider-names.js +0 -56
- package/lib/settings.js +0 -405
- package/lib/stream-json-parser.js +0 -10
- package/scripts/fix-node-pty-permissions.js +0 -75
- package/scripts/record-demo.sh +0 -279
- package/scripts/test-install.sh +0 -40
- package/scripts/validate-templates.js +0 -100
- package/src/agent/agent-config.js +0 -193
- package/src/agent/agent-context-builder.js +0 -335
- package/src/agent/agent-hook-executor.js +0 -402
- package/src/agent/agent-lifecycle.js +0 -683
- package/src/agent/agent-stuck-detector.js +0 -256
- package/src/agent/agent-task-executor.js +0 -1431
- package/src/agent/agent-trigger-evaluator.js +0 -67
- package/src/agent/output-extraction.js +0 -200
- package/src/agent/output-reformatter.js +0 -175
- package/src/agent/schema-utils.js +0 -111
- package/src/agent-wrapper.js +0 -570
- package/src/agents/git-pusher-agent.json +0 -20
- package/src/attach/attach-client.js +0 -438
- package/src/attach/attach-server.js +0 -543
- package/src/attach/index.js +0 -35
- package/src/attach/protocol.js +0 -220
- package/src/attach/ring-buffer.js +0 -121
- package/src/attach/socket-discovery.js +0 -267
- package/src/claude-task-runner.js +0 -523
- package/src/config-router.js +0 -80
- package/src/config-validator.js +0 -1660
- package/src/copy-worker.js +0 -43
- package/src/github.js +0 -139
- package/src/isolation-manager.js +0 -1381
- package/src/ledger.js +0 -614
- package/src/logic-engine.js +0 -223
- package/src/message-bus-bridge.js +0 -139
- package/src/message-bus.js +0 -249
- package/src/name-generator.js +0 -232
- package/src/orchestrator.js +0 -2519
- package/src/preflight.js +0 -485
- package/src/process-metrics.js +0 -566
- package/src/providers/anthropic/cli-builder.js +0 -45
- package/src/providers/anthropic/index.js +0 -134
- package/src/providers/anthropic/models.js +0 -23
- package/src/providers/anthropic/output-parser.js +0 -159
- package/src/providers/base-provider.js +0 -181
- package/src/providers/capabilities.js +0 -51
- package/src/providers/google/cli-builder.js +0 -55
- package/src/providers/google/index.js +0 -116
- package/src/providers/google/models.js +0 -24
- package/src/providers/google/output-parser.js +0 -92
- package/src/providers/index.js +0 -75
- package/src/providers/openai/cli-builder.js +0 -122
- package/src/providers/openai/index.js +0 -135
- package/src/providers/openai/models.js +0 -21
- package/src/providers/openai/output-parser.js +0 -129
- package/src/schemas/sub-cluster.js +0 -156
- package/src/status-footer.js +0 -980
- package/src/sub-cluster-wrapper.js +0 -560
- package/src/task-runner.js +0 -30
- package/src/template-resolver.js +0 -369
- package/src/tui/CHANGES.txt +0 -133
- package/src/tui/LAYOUT.md +0 -261
- package/src/tui/README.txt +0 -192
- package/src/tui/TWO-LEVEL-NAVIGATION.md +0 -186
- package/src/tui/data-poller.js +0 -325
- package/src/tui/demo.js +0 -208
- package/src/tui/formatters.js +0 -128
- package/src/tui/index.js +0 -193
- package/src/tui/keybindings.js +0 -383
- package/src/tui/layout.js +0 -334
- package/src/tui/renderer.js +0 -194
- package/task-lib/attachable-watcher.js +0 -272
- package/task-lib/claude-recovery.js +0 -119
- package/task-lib/commands/clean.js +0 -50
- package/task-lib/commands/get-log-path.js +0 -23
- package/task-lib/commands/kill.js +0 -32
- package/task-lib/commands/list.js +0 -105
- package/task-lib/commands/logs.js +0 -411
- package/task-lib/commands/resume.js +0 -42
- package/task-lib/commands/run.js +0 -57
- package/task-lib/commands/schedule.js +0 -105
- package/task-lib/commands/scheduler-cmd.js +0 -96
- package/task-lib/commands/schedules.js +0 -98
- package/task-lib/commands/status.js +0 -44
- package/task-lib/commands/unschedule.js +0 -16
- package/task-lib/completion.js +0 -9
- package/task-lib/config.js +0 -10
- package/task-lib/name-generator.js +0 -230
- package/task-lib/package.json +0 -3
- package/task-lib/runner.js +0 -144
- package/task-lib/scheduler.js +0 -252
- package/task-lib/store.js +0 -230
- package/task-lib/tui/formatters.js +0 -166
- package/task-lib/tui/index.js +0 -197
- package/task-lib/tui/layout.js +0 -111
- package/task-lib/tui/renderer.js +0 -119
- package/task-lib/tui.js +0 -384
- 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
|
-
};
|