@covibes/zeroshot 1.0.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/CHANGELOG.md +167 -0
- package/LICENSE +21 -0
- package/README.md +364 -0
- package/cli/index.js +3990 -0
- package/cluster-templates/base-templates/debug-workflow.json +181 -0
- package/cluster-templates/base-templates/full-workflow.json +455 -0
- package/cluster-templates/base-templates/single-worker.json +48 -0
- package/cluster-templates/base-templates/worker-validator.json +131 -0
- package/cluster-templates/conductor-bootstrap.json +122 -0
- package/cluster-templates/conductor-junior-bootstrap.json +69 -0
- package/docker/zeroshot-cluster/Dockerfile +132 -0
- package/lib/completion.js +174 -0
- package/lib/id-detector.js +53 -0
- package/lib/settings.js +97 -0
- package/lib/stream-json-parser.js +236 -0
- package/package.json +121 -0
- package/src/agent/agent-config.js +121 -0
- package/src/agent/agent-context-builder.js +241 -0
- package/src/agent/agent-hook-executor.js +329 -0
- package/src/agent/agent-lifecycle.js +555 -0
- package/src/agent/agent-stuck-detector.js +256 -0
- package/src/agent/agent-task-executor.js +1034 -0
- package/src/agent/agent-trigger-evaluator.js +67 -0
- package/src/agent-wrapper.js +459 -0
- package/src/agents/git-pusher-agent.json +20 -0
- package/src/attach/attach-client.js +438 -0
- package/src/attach/attach-server.js +543 -0
- package/src/attach/index.js +35 -0
- package/src/attach/protocol.js +220 -0
- package/src/attach/ring-buffer.js +121 -0
- package/src/attach/socket-discovery.js +242 -0
- package/src/claude-task-runner.js +468 -0
- package/src/config-router.js +80 -0
- package/src/config-validator.js +598 -0
- package/src/github.js +103 -0
- package/src/isolation-manager.js +1042 -0
- package/src/ledger.js +429 -0
- package/src/logic-engine.js +223 -0
- package/src/message-bus-bridge.js +139 -0
- package/src/message-bus.js +202 -0
- package/src/name-generator.js +232 -0
- package/src/orchestrator.js +1938 -0
- package/src/schemas/sub-cluster.js +156 -0
- package/src/sub-cluster-wrapper.js +545 -0
- package/src/task-runner.js +28 -0
- package/src/template-resolver.js +347 -0
- package/src/tui/CHANGES.txt +133 -0
- package/src/tui/LAYOUT.md +261 -0
- package/src/tui/README.txt +192 -0
- package/src/tui/TWO-LEVEL-NAVIGATION.md +186 -0
- package/src/tui/data-poller.js +325 -0
- package/src/tui/demo.js +208 -0
- package/src/tui/formatters.js +123 -0
- package/src/tui/index.js +193 -0
- package/src/tui/keybindings.js +383 -0
- package/src/tui/layout.js +317 -0
- package/src/tui/renderer.js +194 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentTriggerEvaluator - Trigger matching and logic evaluation
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* - Trigger matching based on message topics
|
|
6
|
+
* - Logic evaluation via LogicEngine
|
|
7
|
+
* - Trigger action determination
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Find trigger matching the message topic
|
|
12
|
+
* @param {Object} params - Evaluation parameters
|
|
13
|
+
* @param {Array} params.triggers - Agent triggers configuration
|
|
14
|
+
* @param {Object} params.message - Message to match against
|
|
15
|
+
* @returns {Object|null} Matching trigger or null
|
|
16
|
+
*/
|
|
17
|
+
function findMatchingTrigger({ triggers, message }) {
|
|
18
|
+
if (!triggers) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return triggers.find((trigger) => {
|
|
23
|
+
// Match exact topic or wildcard
|
|
24
|
+
if (trigger.topic === '*' || trigger.topic === message.topic) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
// Match topic prefix (e.g., "VALIDATION_*")
|
|
28
|
+
if (trigger.topic.endsWith('*')) {
|
|
29
|
+
const prefix = trigger.topic.slice(0, -1);
|
|
30
|
+
return message.topic.startsWith(prefix);
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Evaluate trigger logic
|
|
38
|
+
* @param {Object} params - Evaluation parameters
|
|
39
|
+
* @param {Object} params.trigger - Trigger to evaluate
|
|
40
|
+
* @param {Object} params.message - Triggering message
|
|
41
|
+
* @param {Object} params.agent - Agent context (id, role, iteration, cluster_id)
|
|
42
|
+
* @param {Object} params.logicEngine - LogicEngine instance
|
|
43
|
+
* @returns {boolean} Whether trigger logic passed
|
|
44
|
+
*/
|
|
45
|
+
function evaluateTrigger({ trigger, message, agent, logicEngine }) {
|
|
46
|
+
if (!trigger.logic || !trigger.logic.script) {
|
|
47
|
+
return true; // No logic = always true
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// NO TRY/CATCH - let errors propagate and crash
|
|
51
|
+
return logicEngine.evaluate(trigger.logic.script, agent, message);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get trigger action type
|
|
56
|
+
* @param {Object} trigger - Trigger object
|
|
57
|
+
* @returns {string} Action type ('execute_task' or 'stop_cluster')
|
|
58
|
+
*/
|
|
59
|
+
function getTriggerAction(trigger) {
|
|
60
|
+
return trigger.action || 'execute_task';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = {
|
|
64
|
+
findMatchingTrigger,
|
|
65
|
+
evaluateTrigger,
|
|
66
|
+
getTriggerAction,
|
|
67
|
+
};
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* AgentWrapper - Manages agent lifecycle and claude-zeroshots execution
|
|
4
|
+
*
|
|
5
|
+
* Provides:
|
|
6
|
+
* - Agent state machine (idle -> evaluating -> building context -> executing -> idle)
|
|
7
|
+
* - Trigger evaluation via LogicEngine
|
|
8
|
+
* - Context building from ledger
|
|
9
|
+
* - claude-zeroshots spawning and monitoring
|
|
10
|
+
* - Hook execution (onStart, onComplete, onError)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const LogicEngine = require('./logic-engine');
|
|
14
|
+
const { validateAgentConfig } = require('./agent/agent-config');
|
|
15
|
+
const { buildContext } = require('./agent/agent-context-builder');
|
|
16
|
+
const { findMatchingTrigger, evaluateTrigger } = require('./agent/agent-trigger-evaluator');
|
|
17
|
+
const { executeHook } = require('./agent/agent-hook-executor');
|
|
18
|
+
const {
|
|
19
|
+
spawnClaudeTask,
|
|
20
|
+
followClaudeTaskLogs,
|
|
21
|
+
waitForTaskReady,
|
|
22
|
+
spawnClaudeTaskIsolated,
|
|
23
|
+
getClaudeTasksPath,
|
|
24
|
+
parseResultOutput,
|
|
25
|
+
killTask,
|
|
26
|
+
} = require('./agent/agent-task-executor');
|
|
27
|
+
const {
|
|
28
|
+
start: lifecycleStart,
|
|
29
|
+
stop: lifecycleStop,
|
|
30
|
+
handleMessage: lifecycleHandleMessage,
|
|
31
|
+
executeTriggerAction: lifecycleExecuteTriggerAction,
|
|
32
|
+
executeTask: lifecycleExecuteTask,
|
|
33
|
+
startLivenessCheck: lifecycleStartLivenessCheck,
|
|
34
|
+
stopLivenessCheck: lifecycleStopLivenessCheck,
|
|
35
|
+
} = require('./agent/agent-lifecycle');
|
|
36
|
+
|
|
37
|
+
class AgentWrapper {
|
|
38
|
+
/**
|
|
39
|
+
* @param {any} config - Agent configuration
|
|
40
|
+
* @param {any} messageBus - Message bus instance
|
|
41
|
+
* @param {any} cluster - Cluster instance
|
|
42
|
+
* @param {any} options - Options
|
|
43
|
+
*/
|
|
44
|
+
constructor(config, messageBus, cluster, options = {}) {
|
|
45
|
+
// Validate and normalize configuration
|
|
46
|
+
const normalizedConfig = validateAgentConfig(config, options);
|
|
47
|
+
|
|
48
|
+
this.id = normalizedConfig.id;
|
|
49
|
+
this.role = normalizedConfig.role;
|
|
50
|
+
this.modelConfig = normalizedConfig.modelConfig;
|
|
51
|
+
this.config = normalizedConfig;
|
|
52
|
+
this.messageBus = messageBus;
|
|
53
|
+
this.cluster = cluster;
|
|
54
|
+
this.logicEngine = new LogicEngine(messageBus, cluster);
|
|
55
|
+
|
|
56
|
+
this.state = 'idle';
|
|
57
|
+
this.iteration = 0;
|
|
58
|
+
this.maxIterations = normalizedConfig.maxIterations;
|
|
59
|
+
this.timeout = normalizedConfig.timeout;
|
|
60
|
+
/** @type {any} */
|
|
61
|
+
this.currentTask = null;
|
|
62
|
+
/** @type {string | null} */
|
|
63
|
+
this.currentTaskId = null; // Track spawned task ID for resume capability
|
|
64
|
+
/** @type {number | null} */
|
|
65
|
+
this.processPid = null; // Track process PID for resource monitoring
|
|
66
|
+
this.running = false;
|
|
67
|
+
/** @type {Function | null} */
|
|
68
|
+
this.unsubscribe = null;
|
|
69
|
+
/** @type {number | null} */
|
|
70
|
+
this.lastTaskEndTime = null; // Track when last task completed (for context filtering)
|
|
71
|
+
|
|
72
|
+
// LIVENESS DETECTION - Track output freshness to detect stuck agents
|
|
73
|
+
/** @type {number | null} */
|
|
74
|
+
this.lastOutputTime = null; // Timestamp of last output received
|
|
75
|
+
/** @type {NodeJS.Timeout | null} */
|
|
76
|
+
this.livenessCheckInterval = null; // Interval for health checks
|
|
77
|
+
this.staleDuration = normalizedConfig.staleDuration;
|
|
78
|
+
this.enableLivenessCheck = normalizedConfig.enableLivenessCheck;
|
|
79
|
+
|
|
80
|
+
// MOCK SUPPORT - Inject mock spawn function for testing
|
|
81
|
+
// When set, _spawnClaudeTask uses this instead of real ct CLI
|
|
82
|
+
this.mockSpawnFn = options.mockSpawnFn || null;
|
|
83
|
+
this.testMode = options.testMode || false;
|
|
84
|
+
this.quiet = options.quiet || false;
|
|
85
|
+
|
|
86
|
+
// ISOLATION SUPPORT - Run tasks inside Docker container
|
|
87
|
+
this.isolation = options.isolation || null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Log message (respects quiet mode)
|
|
92
|
+
* @private
|
|
93
|
+
*/
|
|
94
|
+
_log(...args) {
|
|
95
|
+
if (!this.quiet) {
|
|
96
|
+
console.log(...args);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Publish a message to the message bus, always including sender_model
|
|
102
|
+
* @private
|
|
103
|
+
*/
|
|
104
|
+
_publish(message) {
|
|
105
|
+
this.messageBus.publish({
|
|
106
|
+
...message,
|
|
107
|
+
cluster_id: this.cluster.id,
|
|
108
|
+
sender: this.id,
|
|
109
|
+
sender_model: this._selectModel(),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Publish agent lifecycle event to message bus (visible in zeroshot logs)
|
|
115
|
+
* @private
|
|
116
|
+
*/
|
|
117
|
+
_publishLifecycle(event, details = {}) {
|
|
118
|
+
this._publish({
|
|
119
|
+
topic: 'AGENT_LIFECYCLE',
|
|
120
|
+
receiver: 'system',
|
|
121
|
+
content: {
|
|
122
|
+
text: `${this.id}: ${event}`,
|
|
123
|
+
data: {
|
|
124
|
+
event,
|
|
125
|
+
agent: this.id,
|
|
126
|
+
role: this.role,
|
|
127
|
+
state: this.state,
|
|
128
|
+
model: this._selectModel(),
|
|
129
|
+
...details,
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Select model based on current iteration and agent config
|
|
137
|
+
* @returns {string} Model name ('sonnet', 'opus', 'haiku')
|
|
138
|
+
* @private
|
|
139
|
+
*/
|
|
140
|
+
_selectModel() {
|
|
141
|
+
// Backward compatibility: static model
|
|
142
|
+
if (this.modelConfig.type === 'static') {
|
|
143
|
+
return this.modelConfig.model;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Dynamic rules: evaluate based on iteration
|
|
147
|
+
for (const rule of this.modelConfig.rules) {
|
|
148
|
+
if (this._matchesIterationRange(rule.iterations)) {
|
|
149
|
+
return rule.model;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// No match: fail fast
|
|
154
|
+
throw new Error(
|
|
155
|
+
`Agent ${this.id}: No model rule matched iteration ${this.iteration}. ` +
|
|
156
|
+
`Add a catch-all rule like { "iterations": "all", "model": "sonnet" }`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Check if current iteration matches the range pattern
|
|
162
|
+
* @param {string} pattern - e.g., "1", "1-3", "5+", "all"
|
|
163
|
+
* @returns {boolean}
|
|
164
|
+
* @private
|
|
165
|
+
*/
|
|
166
|
+
_matchesIterationRange(pattern) {
|
|
167
|
+
if (pattern === 'all') return true;
|
|
168
|
+
|
|
169
|
+
const current = this.iteration;
|
|
170
|
+
|
|
171
|
+
// Exact match: "3"
|
|
172
|
+
if (/^\d+$/.test(pattern)) {
|
|
173
|
+
return current === parseInt(pattern);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Range: "1-3"
|
|
177
|
+
const rangeMatch = pattern.match(/^(\d+)-(\d+)$/);
|
|
178
|
+
if (rangeMatch) {
|
|
179
|
+
const [, start, end] = rangeMatch;
|
|
180
|
+
return current >= parseInt(start) && current <= parseInt(end);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Open-ended: "5+"
|
|
184
|
+
const openMatch = pattern.match(/^(\d+)\+$/);
|
|
185
|
+
if (openMatch) {
|
|
186
|
+
const [, start] = openMatch;
|
|
187
|
+
return current >= parseInt(start);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
throw new Error(
|
|
191
|
+
`Agent ${this.id}: Invalid iteration pattern '${pattern}'. ` +
|
|
192
|
+
`Valid formats: "1", "1-3", "5+", "all"`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Select prompt based on current iteration and agent config
|
|
198
|
+
* @returns {string|null} System prompt string, or null if no prompt configured
|
|
199
|
+
* @private
|
|
200
|
+
*/
|
|
201
|
+
_selectPrompt() {
|
|
202
|
+
const promptConfig = this.config.promptConfig;
|
|
203
|
+
|
|
204
|
+
// No prompt configured
|
|
205
|
+
if (!promptConfig) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Backward compatibility: static prompt
|
|
210
|
+
if (promptConfig.type === 'static') {
|
|
211
|
+
return promptConfig.system;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Dynamic rules: evaluate based on iteration
|
|
215
|
+
for (const rule of promptConfig.rules) {
|
|
216
|
+
if (this._matchesIterationRange(rule.match)) {
|
|
217
|
+
return rule.system;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// No match: fail fast
|
|
222
|
+
throw new Error(
|
|
223
|
+
`Agent ${this.id}: No prompt rule matched iteration ${this.iteration}. ` +
|
|
224
|
+
`Add a catch-all rule like { "match": "all", "system": "..." }`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Start the agent (begin listening for triggers)
|
|
230
|
+
*/
|
|
231
|
+
start() {
|
|
232
|
+
return lifecycleStart(this);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Stop the agent
|
|
237
|
+
*/
|
|
238
|
+
stop() {
|
|
239
|
+
return lifecycleStop(this);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Handle incoming message
|
|
244
|
+
* @private
|
|
245
|
+
*/
|
|
246
|
+
_handleMessage(message) {
|
|
247
|
+
return lifecycleHandleMessage(this, message);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Find trigger matching the message topic
|
|
252
|
+
* @private
|
|
253
|
+
*/
|
|
254
|
+
_findMatchingTrigger(message) {
|
|
255
|
+
return findMatchingTrigger({
|
|
256
|
+
triggers: this.config.triggers,
|
|
257
|
+
message,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Evaluate trigger logic
|
|
263
|
+
* @private
|
|
264
|
+
*/
|
|
265
|
+
_evaluateTrigger(trigger, message) {
|
|
266
|
+
const agent = {
|
|
267
|
+
id: this.id,
|
|
268
|
+
role: this.role,
|
|
269
|
+
iteration: this.iteration,
|
|
270
|
+
cluster_id: this.cluster.id,
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
return evaluateTrigger({
|
|
274
|
+
trigger,
|
|
275
|
+
message,
|
|
276
|
+
agent,
|
|
277
|
+
logicEngine: this.logicEngine,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Execute trigger action
|
|
283
|
+
* @private
|
|
284
|
+
*/
|
|
285
|
+
_executeTriggerAction(trigger, message) {
|
|
286
|
+
return lifecycleExecuteTriggerAction(this, trigger, message);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Execute claude-zeroshots with built context
|
|
291
|
+
* Retries disabled by default. Set agent config `maxRetries` to enable (e.g., 3).
|
|
292
|
+
* @private
|
|
293
|
+
*/
|
|
294
|
+
_executeTask(triggeringMessage) {
|
|
295
|
+
return lifecycleExecuteTask(this, triggeringMessage);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Build context from ledger based on contextStrategy
|
|
300
|
+
* @private
|
|
301
|
+
*/
|
|
302
|
+
_buildContext(triggeringMessage) {
|
|
303
|
+
return buildContext({
|
|
304
|
+
id: this.id,
|
|
305
|
+
role: this.role,
|
|
306
|
+
iteration: this.iteration,
|
|
307
|
+
config: this.config,
|
|
308
|
+
messageBus: this.messageBus,
|
|
309
|
+
cluster: this.cluster,
|
|
310
|
+
lastTaskEndTime: this.lastTaskEndTime,
|
|
311
|
+
triggeringMessage,
|
|
312
|
+
selectedPrompt: this._selectPrompt(),
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Spawn claude-zeroshots process and stream output via message bus
|
|
318
|
+
* @private
|
|
319
|
+
*/
|
|
320
|
+
_spawnClaudeTask(context) {
|
|
321
|
+
return spawnClaudeTask(this, context);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Wait for task to be registered in ct storage
|
|
326
|
+
* @private
|
|
327
|
+
*/
|
|
328
|
+
_waitForTaskReady(taskId, maxRetries = 10, delayMs = 200) {
|
|
329
|
+
return waitForTaskReady(this, taskId, maxRetries, delayMs);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Follow claude-zeroshots logs until completion, streaming to message bus
|
|
334
|
+
* Reads log file directly for reliable streaming
|
|
335
|
+
* @private
|
|
336
|
+
*/
|
|
337
|
+
_followClaudeTaskLogs(taskId) {
|
|
338
|
+
return followClaudeTaskLogs(this, taskId);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Get path to claude-zeroshots executable
|
|
343
|
+
* @private
|
|
344
|
+
*/
|
|
345
|
+
_getClaudeTasksPath() {
|
|
346
|
+
return getClaudeTasksPath();
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Spawn claude-zeroshots inside Docker container (isolation mode)
|
|
351
|
+
* Runs Claude CLI inside the container for full isolation
|
|
352
|
+
* @private
|
|
353
|
+
*/
|
|
354
|
+
_spawnClaudeTaskIsolated(context) {
|
|
355
|
+
return spawnClaudeTaskIsolated(this, context);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Kill current task
|
|
360
|
+
* @private
|
|
361
|
+
*/
|
|
362
|
+
_killTask() {
|
|
363
|
+
return killTask(this);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Execute a hook
|
|
368
|
+
* THROWS on failure - no silent errors
|
|
369
|
+
* @private
|
|
370
|
+
*/
|
|
371
|
+
_executeHook(hookName, context) {
|
|
372
|
+
const hook = this.config.hooks?.[hookName];
|
|
373
|
+
return executeHook({
|
|
374
|
+
hook,
|
|
375
|
+
agent: this,
|
|
376
|
+
message: context.triggeringMessage,
|
|
377
|
+
result: context.result,
|
|
378
|
+
messageBus: this.messageBus,
|
|
379
|
+
cluster: this.cluster,
|
|
380
|
+
orchestrator: this.orchestrator,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Parse agent output to extract structured result data
|
|
386
|
+
* GENERIC - returns whatever structured output the agent provides
|
|
387
|
+
* Works with any agent schema (planner, validator, worker, etc.)
|
|
388
|
+
* @private
|
|
389
|
+
*/
|
|
390
|
+
_parseResultOutput(output) {
|
|
391
|
+
return parseResultOutput(this, output);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Resume agent task with context from previous failure
|
|
396
|
+
* Called by Orchestrator.resume() to continue where we left off
|
|
397
|
+
* @param {String} resumeContext - Context describing what to resume
|
|
398
|
+
*/
|
|
399
|
+
async resume(resumeContext) {
|
|
400
|
+
if (!this.running) {
|
|
401
|
+
throw new Error(`Agent ${this.id} is not running. Start it first.`);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (this.state !== 'idle') {
|
|
405
|
+
throw new Error(`Agent ${this.id} is busy (state: ${this.state}). Wait for current task.`);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
this._log(`[${this.id}] Resuming task...`);
|
|
409
|
+
|
|
410
|
+
// Create a synthetic triggering message for resume
|
|
411
|
+
const triggeringMessage = {
|
|
412
|
+
cluster_id: this.cluster.id,
|
|
413
|
+
topic: 'AGENT_RESUME',
|
|
414
|
+
sender: 'system',
|
|
415
|
+
content: {
|
|
416
|
+
text: resumeContext,
|
|
417
|
+
},
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
// Execute the task with resume context
|
|
421
|
+
await this._executeTask(triggeringMessage);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Get current agent state
|
|
426
|
+
*/
|
|
427
|
+
getState() {
|
|
428
|
+
return {
|
|
429
|
+
id: this.id,
|
|
430
|
+
role: this.role,
|
|
431
|
+
model: this._selectModel(),
|
|
432
|
+
state: this.state,
|
|
433
|
+
iteration: this.iteration,
|
|
434
|
+
maxIterations: this.maxIterations,
|
|
435
|
+
currentTask: this.currentTask ? true : false,
|
|
436
|
+
currentTaskId: this.currentTaskId,
|
|
437
|
+
pid: this.processPid,
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Start monitoring agent output liveness
|
|
443
|
+
* Detects when agent produces no output for configured staleDuration
|
|
444
|
+
* @private
|
|
445
|
+
*/
|
|
446
|
+
_startLivenessCheck() {
|
|
447
|
+
return lifecycleStartLivenessCheck(this);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Stop liveness monitoring
|
|
452
|
+
* @private
|
|
453
|
+
*/
|
|
454
|
+
_stopLivenessCheck() {
|
|
455
|
+
return lifecycleStopLivenessCheck(this);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
module.exports = AgentWrapper;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "git-pusher",
|
|
3
|
+
"role": "completion-detector",
|
|
4
|
+
"model": "sonnet",
|
|
5
|
+
"triggers": [
|
|
6
|
+
{
|
|
7
|
+
"topic": "VALIDATION_RESULT",
|
|
8
|
+
"logic": {
|
|
9
|
+
"engine": "javascript",
|
|
10
|
+
"script": "const validators = cluster.getAgentsByRole('validator'); const lastPush = ledger.findLast({ topic: 'IMPLEMENTATION_READY' }); if (!lastPush) return false; const results = ledger.query({ topic: 'VALIDATION_RESULT', since: lastPush.timestamp }); if (results.length < validators.length) return false; return results.every(r => r.content?.data?.approved === 'true' || r.content?.data?.approved === true);"
|
|
11
|
+
},
|
|
12
|
+
"action": "execute_task"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"prompt": "🚨 CRITICAL: ALL VALIDATORS APPROVED. YOU MUST CREATE A PR AND GET IT MERGED. DO NOT STOP UNTIL THE PR IS MERGED. 🚨\n\n## MANDATORY STEPS - EXECUTE EACH ONE IN ORDER - DO NOT SKIP ANY STEP\n\n### STEP 1: Stage ALL changes (MANDATORY)\n```bash\ngit add -A\n```\nRun this command. Do not skip it.\n\n### STEP 2: Check what's staged\n```bash\ngit status\n```\nRun this. If nothing to commit, output JSON with pr_url: null and stop.\n\n### STEP 3: Commit the changes (MANDATORY if there are changes)\n```bash\ngit commit -m \"feat: implement #{{issue_number}} - {{issue_title}}\"\n```\nRun this command. Do not skip it.\n\n### STEP 4: Push to origin (MANDATORY)\n```bash\ngit push -u origin HEAD\n```\nRun this. If it fails, check the error and fix it.\n\n⚠️ AFTER PUSH YOU ARE NOT DONE! CONTINUE TO STEP 5! ⚠️\n\n### STEP 5: CREATE THE PR (MANDATORY - YOU MUST RUN THIS COMMAND)\n```bash\ngh pr create --title \"feat: {{issue_title}}\" --body \"Closes #{{issue_number}}\"\n```\n🚨 YOU MUST RUN `gh pr create`! Outputting a link is NOT creating a PR! 🚨\nThe push output shows a \"Create a pull request\" link - IGNORE IT.\nYou MUST run the `gh pr create` command above. Save the actual PR URL from the output.\n\n⚠️ AFTER PR CREATION YOU ARE NOT DONE! CONTINUE TO STEP 6! ⚠️\n\n### STEP 6: MERGE THE PR (MANDATORY - THIS IS NOT OPTIONAL)\n```bash\ngh pr merge --merge --auto\n```\nThis sets auto-merge. If it fails (e.g., no auto-merge enabled), try:\n```bash\ngh pr merge --merge\n```\n\n🚨 IF MERGE FAILS DUE TO CONFLICTS - YOU MUST RESOLVE THEM:\na) Pull latest main and rebase:\n ```bash\n git fetch origin main\n git rebase origin/main\n ```\nb) If conflicts appear - RESOLVE THEM IMMEDIATELY:\n - Read the conflicting files\n - Make intelligent decisions about what code to keep\n - Edit the files to resolve conflicts\n - `git add <resolved-files>`\n - `git rebase --continue`\nc) Force push the resolved branch:\n ```bash\n git push --force-with-lease\n ```\nd) Retry merge:\n ```bash\n gh pr merge --merge\n ```\n\nREPEAT UNTIL MERGED. DO NOT GIVE UP. DO NOT SKIP. THE PR MUST BE MERGED.\nIf merge is blocked by CI, wait and retry. If blocked by reviews, set auto-merge.\n\n## CRITICAL RULES\n- Execute EVERY step in order (1, 2, 3, 4, 5, 6)\n- Do NOT skip git add -A\n- Do NOT skip git commit\n- Do NOT skip gh pr create - THE TASK IS NOT DONE UNTIL PR EXISTS\n- Do NOT skip gh pr merge - THE TASK IS NOT DONE UNTIL PR IS MERGED\n- If push fails, debug and fix it\n- If PR creation fails, debug and fix it\n- If merge fails, debug and fix it\n- DO NOT OUTPUT JSON UNTIL PR IS ACTUALLY MERGED\n- A link from git push is NOT a PR - you must run gh pr create\n\n## Final Output\nONLY after the PR is MERGED, output:\n```json\n{\"pr_url\": \"https://github.com/owner/repo/pull/123\", \"pr_number\": 123, \"merged\": true}\n```\n\nIf truly no changes exist, output:\n```json\n{\"pr_url\": null, \"pr_number\": null, \"merged\": false}\n```",
|
|
16
|
+
"output": {
|
|
17
|
+
"topic": "PR_CREATED",
|
|
18
|
+
"publishAfter": "CLUSTER_COMPLETE"
|
|
19
|
+
}
|
|
20
|
+
}
|