@covibes/zeroshot 5.4.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 -465
- package/cli/index.js +101 -5231
- package/package.json +16 -130
- package/CHANGELOG.md +0 -537
- package/cli/commands/providers.js +0 -149
- 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 -275
- 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 -581
- package/cluster-templates/base-templates/single-worker.json +0 -63
- package/cluster-templates/base-templates/worker-validator.json +0 -208
- 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 -220
- package/lib/git-remote-utils.js +0 -165
- package/lib/id-detector.js +0 -56
- package/lib/provider-defaults.js +0 -62
- package/lib/provider-detection.js +0 -59
- package/lib/provider-names.js +0 -57
- package/lib/settings/claude-auth.js +0 -78
- package/lib/settings.js +0 -503
- 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/setup-merge-queue.sh +0 -170
- package/scripts/test-install.sh +0 -40
- package/scripts/validate-templates.js +0 -100
- package/src/agent/agent-config.js +0 -246
- package/src/agent/agent-context-builder.js +0 -444
- package/src/agent/agent-hook-executor.js +0 -599
- package/src/agent/agent-lifecycle.js +0 -743
- package/src/agent/agent-stuck-detector.js +0 -256
- package/src/agent/agent-task-executor.js +0 -1690
- package/src/agent/agent-trigger-evaluator.js +0 -67
- package/src/agent/output-extraction.js +0 -217
- package/src/agent/output-reformatter.js +0 -175
- package/src/agent/schema-utils.js +0 -146
- package/src/agent-wrapper.js +0 -579
- package/src/agents/git-pusher-template.js +0 -285
- 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 -574
- package/src/config-router.js +0 -80
- package/src/config-validator.js +0 -1925
- package/src/copy-worker.js +0 -43
- package/src/input-helpers.js +0 -65
- package/src/isolation-manager.js +0 -1471
- package/src/issue-providers/README.md +0 -305
- package/src/issue-providers/azure-devops-provider.js +0 -273
- package/src/issue-providers/base-provider.js +0 -232
- package/src/issue-providers/github-provider.js +0 -179
- package/src/issue-providers/gitlab-provider.js +0 -241
- package/src/issue-providers/index.js +0 -196
- package/src/issue-providers/jira-provider.js +0 -239
- package/src/ledger.js +0 -631
- package/src/lib/safe-exec.js +0 -88
- 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 -2815
- package/src/preflight.js +0 -639
- package/src/process-metrics.js +0 -608
- package/src/providers/anthropic/cli-builder.js +0 -73
- package/src/providers/anthropic/index.js +0 -204
- package/src/providers/anthropic/models.js +0 -23
- package/src/providers/anthropic/output-parser.js +0 -177
- package/src/providers/base-provider.js +0 -251
- package/src/providers/capabilities.js +0 -60
- 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 -101
- package/src/providers/index.js +0 -91
- package/src/providers/openai/cli-builder.js +0 -133
- package/src/providers/openai/index.js +0 -136
- package/src/providers/openai/models.js +0 -21
- package/src/providers/openai/output-parser.js +0 -143
- package/src/providers/opencode/cli-builder.js +0 -42
- package/src/providers/opencode/index.js +0 -103
- package/src/providers/opencode/models.js +0 -55
- package/src/providers/opencode/output-parser.js +0 -122
- package/src/schemas/sub-cluster.js +0 -185
- package/src/status-footer.js +0 -1026
- package/src/sub-cluster-wrapper.js +0 -601
- package/src/task-runner.js +0 -30
- package/src/template-resolver.js +0 -372
- 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 -349
- package/src/tui/demo.js +0 -208
- package/src/tui/formatters.js +0 -129
- package/src/tui/index.js +0 -193
- package/src/tui/keybindings.js +0 -324
- package/src/tui/layout.js +0 -334
- package/src/tui/renderer.js +0 -184
- package/task-lib/attachable-watcher.js +0 -309
- package/task-lib/claude-recovery.js +0 -156
- package/task-lib/commands/clean.js +0 -50
- package/task-lib/commands/episodes.js +0 -105
- 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 -453
- 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 -148
- 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 -8
- package/task-lib/name-generator.js +0 -230
- package/task-lib/package.json +0 -3
- package/task-lib/runner.js +0 -201
- package/task-lib/scheduler.js +0 -252
- package/task-lib/store.js +0 -529
- package/task-lib/tui/formatters.js +0 -215
- package/task-lib/tui/index.js +0 -197
- package/task-lib/tui/layout.js +0 -111
- package/task-lib/tui/renderer.js +0 -129
- package/task-lib/tui.js +0 -425
- package/task-lib/watcher.js +0 -226
|
@@ -1,601 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SubClusterWrapper - Manages nested cluster lifecycle
|
|
3
|
-
*
|
|
4
|
-
* Implements same interface as AgentWrapper but spawns a child Orchestrator
|
|
5
|
-
* instead of a Claude task. Enables recursive cluster composition.
|
|
6
|
-
*
|
|
7
|
-
* Lifecycle:
|
|
8
|
-
* - On trigger match: spawns nested Orchestrator with child cluster config
|
|
9
|
-
* - Passes parent context to child via ISSUE_OPENED equivalent
|
|
10
|
-
* - Listens for child CLUSTER_COMPLETE → executes onComplete hook
|
|
11
|
-
* - Listens for child CLUSTER_FAILED → executes onError hook
|
|
12
|
-
* - Supports maxIterations at subcluster level (restart child on failure)
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
const LogicEngine = require('./logic-engine');
|
|
16
|
-
const MessageBusBridge = require('./message-bus-bridge');
|
|
17
|
-
const { DEFAULT_MAX_ITERATIONS } = require('./agent/agent-config');
|
|
18
|
-
|
|
19
|
-
class SubClusterWrapper {
|
|
20
|
-
constructor(config, messageBus, parentCluster, options = {}) {
|
|
21
|
-
this.id = config.id;
|
|
22
|
-
this.role = config.role || 'orchestrator';
|
|
23
|
-
this.config = config;
|
|
24
|
-
this.messageBus = messageBus; // Parent message bus
|
|
25
|
-
this.parentCluster = parentCluster;
|
|
26
|
-
this.logicEngine = new LogicEngine(messageBus, parentCluster);
|
|
27
|
-
|
|
28
|
-
this.state = 'idle';
|
|
29
|
-
this.iteration = 0;
|
|
30
|
-
this.maxIterations = config.maxIterations || DEFAULT_MAX_ITERATIONS;
|
|
31
|
-
this.running = false;
|
|
32
|
-
this.unsubscribe = null;
|
|
33
|
-
|
|
34
|
-
// Child cluster state
|
|
35
|
-
this.childCluster = null; // { id, orchestrator, messageBus, bridge }
|
|
36
|
-
this.childClusterId = null;
|
|
37
|
-
|
|
38
|
-
this.quiet = options.quiet || false;
|
|
39
|
-
this.modelOverride = options.modelOverride || null;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Log message (respects quiet mode)
|
|
44
|
-
* @private
|
|
45
|
-
*/
|
|
46
|
-
_log(...args) {
|
|
47
|
-
if (!this.quiet) {
|
|
48
|
-
console.log(...args);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Publish lifecycle event to parent message bus
|
|
54
|
-
* @private
|
|
55
|
-
*/
|
|
56
|
-
_publishLifecycle(event, details = {}) {
|
|
57
|
-
this.messageBus.publish({
|
|
58
|
-
cluster_id: this.parentCluster.id,
|
|
59
|
-
topic: 'AGENT_LIFECYCLE',
|
|
60
|
-
sender: this.id,
|
|
61
|
-
receiver: 'system',
|
|
62
|
-
content: {
|
|
63
|
-
text: `${this.id}: ${event}`,
|
|
64
|
-
data: {
|
|
65
|
-
event,
|
|
66
|
-
agent: this.id,
|
|
67
|
-
role: this.role,
|
|
68
|
-
state: this.state,
|
|
69
|
-
type: 'subcluster',
|
|
70
|
-
...details,
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Start the sub-cluster wrapper (begin listening for triggers)
|
|
78
|
-
*/
|
|
79
|
-
start() {
|
|
80
|
-
if (this.running) {
|
|
81
|
-
throw new Error(`SubCluster ${this.id} is already running`);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
this.running = true;
|
|
85
|
-
this.state = 'idle';
|
|
86
|
-
|
|
87
|
-
// Subscribe to parent cluster messages
|
|
88
|
-
this.unsubscribe = this.messageBus.subscribe((message) => {
|
|
89
|
-
if (message.cluster_id === this.parentCluster.id) {
|
|
90
|
-
this._handleMessage(message).catch((error) => {
|
|
91
|
-
console.error(`\n${'='.repeat(80)}`);
|
|
92
|
-
console.error(`🔴 FATAL: SubCluster ${this.id} message handler crashed`);
|
|
93
|
-
console.error(`${'='.repeat(80)}`);
|
|
94
|
-
console.error(`Topic: ${message.topic}`);
|
|
95
|
-
console.error(`Error: ${error.message}`);
|
|
96
|
-
console.error(`Stack: ${error.stack}`);
|
|
97
|
-
console.error(`${'='.repeat(80)}\n`);
|
|
98
|
-
throw error;
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
this._log(`SubCluster ${this.id} started (role: ${this.role})`);
|
|
104
|
-
this._publishLifecycle('STARTED', {
|
|
105
|
-
triggers: this.config.triggers?.map((t) => t.topic) || [],
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Stop the sub-cluster wrapper
|
|
111
|
-
*/
|
|
112
|
-
async stop() {
|
|
113
|
-
if (!this.running) {
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
this.running = false;
|
|
118
|
-
this.state = 'stopped';
|
|
119
|
-
|
|
120
|
-
if (this.unsubscribe) {
|
|
121
|
-
this.unsubscribe();
|
|
122
|
-
this.unsubscribe = null;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Stop child cluster if running
|
|
126
|
-
if (this.childCluster) {
|
|
127
|
-
await this._stopChildCluster();
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
this._log(`SubCluster ${this.id} stopped`);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Handle incoming message from parent cluster
|
|
135
|
-
* @private
|
|
136
|
-
*/
|
|
137
|
-
async _handleMessage(message) {
|
|
138
|
-
// Check if any trigger matches
|
|
139
|
-
const matchingTrigger = this._findMatchingTrigger(message);
|
|
140
|
-
if (!matchingTrigger) {
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Check state
|
|
145
|
-
if (!this.running) {
|
|
146
|
-
console.warn(`[${this.id}] ⚠️ DROPPING message (not running): ${message.topic}`);
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
if (this.state !== 'idle') {
|
|
150
|
-
console.warn(
|
|
151
|
-
`[${this.id}] ⚠️ DROPPING message (busy, state=${this.state}): ${message.topic}`
|
|
152
|
-
);
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Evaluate trigger logic
|
|
157
|
-
this.state = 'evaluating_logic';
|
|
158
|
-
const shouldExecute = this._evaluateTrigger(matchingTrigger, message);
|
|
159
|
-
|
|
160
|
-
if (!shouldExecute) {
|
|
161
|
-
this.state = 'idle';
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Execute trigger action (spawn child cluster)
|
|
166
|
-
await this._handleTrigger(message);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Find trigger matching the message topic
|
|
171
|
-
* @private
|
|
172
|
-
*/
|
|
173
|
-
_findMatchingTrigger(message) {
|
|
174
|
-
if (!this.config.triggers) {
|
|
175
|
-
return null;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return this.config.triggers.find((trigger) => {
|
|
179
|
-
if (trigger.topic === '*' || trigger.topic === message.topic) {
|
|
180
|
-
return true;
|
|
181
|
-
}
|
|
182
|
-
if (trigger.topic.endsWith('*')) {
|
|
183
|
-
const prefix = trigger.topic.slice(0, -1);
|
|
184
|
-
return message.topic.startsWith(prefix);
|
|
185
|
-
}
|
|
186
|
-
return false;
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Evaluate trigger logic
|
|
192
|
-
* @private
|
|
193
|
-
*/
|
|
194
|
-
_evaluateTrigger(trigger, message) {
|
|
195
|
-
if (!trigger.logic || !trigger.logic.script) {
|
|
196
|
-
return true;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const agent = {
|
|
200
|
-
id: this.id,
|
|
201
|
-
role: this.role,
|
|
202
|
-
iteration: this.iteration,
|
|
203
|
-
cluster_id: this.parentCluster.id,
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
return this.logicEngine.evaluate(trigger.logic.script, agent, message);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Handle trigger: spawn child cluster
|
|
211
|
-
* @private
|
|
212
|
-
*/
|
|
213
|
-
async _handleTrigger(triggeringMessage) {
|
|
214
|
-
// Check max iterations
|
|
215
|
-
if (this.iteration >= this.maxIterations) {
|
|
216
|
-
this._log(`[SubCluster ${this.id}] Hit max iterations (${this.maxIterations}), failing`);
|
|
217
|
-
this._publishLifecycle('MAX_ITERATIONS_REACHED', {
|
|
218
|
-
iteration: this.iteration,
|
|
219
|
-
maxIterations: this.maxIterations,
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
this.messageBus.publish({
|
|
223
|
-
cluster_id: this.parentCluster.id,
|
|
224
|
-
topic: 'CLUSTER_FAILED',
|
|
225
|
-
sender: this.id,
|
|
226
|
-
receiver: 'system',
|
|
227
|
-
content: {
|
|
228
|
-
text: `SubCluster ${this.id} hit max iterations limit (${this.maxIterations})`,
|
|
229
|
-
data: {
|
|
230
|
-
reason: 'max_iterations',
|
|
231
|
-
iteration: this.iteration,
|
|
232
|
-
maxIterations: this.maxIterations,
|
|
233
|
-
},
|
|
234
|
-
},
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
this.state = 'failed';
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
this.iteration++;
|
|
242
|
-
this.state = 'spawning_child';
|
|
243
|
-
|
|
244
|
-
this._publishLifecycle('SPAWNING_CHILD', {
|
|
245
|
-
iteration: this.iteration,
|
|
246
|
-
triggeredBy: triggeringMessage.topic,
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
try {
|
|
250
|
-
// Build child cluster context from parent messages
|
|
251
|
-
const context = this._buildChildContext(triggeringMessage);
|
|
252
|
-
|
|
253
|
-
// Spawn child cluster
|
|
254
|
-
await this._spawnChildCluster(context);
|
|
255
|
-
|
|
256
|
-
this._publishLifecycle('CHILD_SPAWNED', {
|
|
257
|
-
childClusterId: this.childClusterId,
|
|
258
|
-
iteration: this.iteration,
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
this.state = 'monitoring_child';
|
|
262
|
-
} catch (error) {
|
|
263
|
-
console.error(`\n${'='.repeat(80)}`);
|
|
264
|
-
console.error(`🔴 CHILD CLUSTER SPAWN FAILED - ${this.id}`);
|
|
265
|
-
console.error(`${'='.repeat(80)}`);
|
|
266
|
-
console.error(`Error: ${error.message}`);
|
|
267
|
-
console.error(`Stack: ${error.stack}`);
|
|
268
|
-
console.error(`${'='.repeat(80)}\n`);
|
|
269
|
-
|
|
270
|
-
this.state = 'error';
|
|
271
|
-
|
|
272
|
-
// Execute onError hook
|
|
273
|
-
await this._executeHook('onError', { error, triggeringMessage });
|
|
274
|
-
|
|
275
|
-
// Return to idle if we haven't hit max iterations
|
|
276
|
-
if (this.iteration < this.maxIterations) {
|
|
277
|
-
this.state = 'idle';
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Build context for child cluster from parent messages
|
|
284
|
-
* @private
|
|
285
|
-
*/
|
|
286
|
-
_buildChildContext(triggeringMessage) {
|
|
287
|
-
const parentTopics = this.config.contextStrategy?.parentTopics || [];
|
|
288
|
-
|
|
289
|
-
const lines = [
|
|
290
|
-
'# Child Cluster Context',
|
|
291
|
-
'',
|
|
292
|
-
`Parent Cluster: ${this.parentCluster.id}`,
|
|
293
|
-
`SubCluster ID: ${this.id}`,
|
|
294
|
-
`Iteration: ${this.iteration}`,
|
|
295
|
-
'',
|
|
296
|
-
];
|
|
297
|
-
|
|
298
|
-
this._appendParentTopicContext(lines, parentTopics);
|
|
299
|
-
this._appendTriggeringMessageContext(lines, triggeringMessage);
|
|
300
|
-
|
|
301
|
-
return lines.join('\n');
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
_appendParentTopicContext(lines, parentTopics) {
|
|
305
|
-
if (parentTopics.length === 0) {
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
lines.push('## Parent Cluster Messages', '');
|
|
310
|
-
|
|
311
|
-
for (const topic of parentTopics) {
|
|
312
|
-
const topicLines = this._buildTopicContextLines(topic);
|
|
313
|
-
if (topicLines.length === 0) {
|
|
314
|
-
continue;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
lines.push(...topicLines);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
_buildTopicContextLines(topic) {
|
|
322
|
-
const messages = this.messageBus.query({
|
|
323
|
-
cluster_id: this.parentCluster.id,
|
|
324
|
-
topic,
|
|
325
|
-
limit: 10,
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
if (messages.length === 0) {
|
|
329
|
-
return [];
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
const lines = [`### Topic: ${topic}`, ''];
|
|
333
|
-
|
|
334
|
-
for (const message of messages) {
|
|
335
|
-
lines.push(...this._buildMessageContextLines(message));
|
|
336
|
-
lines.push('');
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
return lines;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
_buildMessageContextLines(message) {
|
|
343
|
-
const lines = [`[${new Date(message.timestamp).toISOString()}] ${message.sender}:`];
|
|
344
|
-
const text = message.content?.text;
|
|
345
|
-
const data = message.content?.data;
|
|
346
|
-
|
|
347
|
-
if (text) {
|
|
348
|
-
lines.push(text);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
if (data) {
|
|
352
|
-
lines.push(`Data: ${JSON.stringify(data, null, 2)}`);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
return lines;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
_appendTriggeringMessageContext(lines, triggeringMessage) {
|
|
359
|
-
lines.push(
|
|
360
|
-
'',
|
|
361
|
-
'## Triggering Message',
|
|
362
|
-
'',
|
|
363
|
-
`Topic: ${triggeringMessage.topic}`,
|
|
364
|
-
`Sender: ${triggeringMessage.sender}`
|
|
365
|
-
);
|
|
366
|
-
|
|
367
|
-
const text = triggeringMessage.content?.text;
|
|
368
|
-
if (text) {
|
|
369
|
-
lines.push('', text);
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
/**
|
|
374
|
-
* Spawn child cluster with nested Orchestrator
|
|
375
|
-
* @private
|
|
376
|
-
*/
|
|
377
|
-
async _spawnChildCluster(context) {
|
|
378
|
-
const Orchestrator = require('./orchestrator');
|
|
379
|
-
const path = require('path');
|
|
380
|
-
|
|
381
|
-
// Generate child cluster ID (namespaced under parent)
|
|
382
|
-
const childId = `${this.parentCluster.id}.${this.id}`;
|
|
383
|
-
this.childClusterId = childId;
|
|
384
|
-
|
|
385
|
-
// Create child orchestrator with separate database
|
|
386
|
-
const childOrchestrator = await Orchestrator.create({
|
|
387
|
-
quiet: this.quiet,
|
|
388
|
-
skipLoad: true,
|
|
389
|
-
storageDir: path.join(this.parentCluster.ledger.dbPath, '..', 'subclusters', childId),
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
const childConfig = JSON.parse(JSON.stringify(this.config.config));
|
|
393
|
-
const parentConfig = this.parentCluster?.config || {};
|
|
394
|
-
|
|
395
|
-
if (parentConfig.forceProvider) {
|
|
396
|
-
childConfig.forceProvider = parentConfig.forceProvider;
|
|
397
|
-
childConfig.defaultProvider = parentConfig.forceProvider;
|
|
398
|
-
if (parentConfig.forceLevel) {
|
|
399
|
-
childConfig.forceLevel = parentConfig.forceLevel;
|
|
400
|
-
childConfig.defaultLevel = parentConfig.forceLevel;
|
|
401
|
-
}
|
|
402
|
-
} else if (parentConfig.defaultProvider && !childConfig.defaultProvider) {
|
|
403
|
-
childConfig.defaultProvider = parentConfig.defaultProvider;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
// Start child cluster with text input (context from parent)
|
|
407
|
-
const childCluster = await childOrchestrator.start(
|
|
408
|
-
childConfig, // Child cluster config
|
|
409
|
-
{ text: context },
|
|
410
|
-
{ testMode: false, modelOverride: this.modelOverride || undefined }
|
|
411
|
-
);
|
|
412
|
-
|
|
413
|
-
// Create message bridge
|
|
414
|
-
const bridge = new MessageBusBridge(this.messageBus, childCluster.messageBus, {
|
|
415
|
-
parentClusterId: this.parentCluster.id,
|
|
416
|
-
childClusterId: childId,
|
|
417
|
-
parentTopics: this.config.contextStrategy?.parentTopics || [],
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
// Store child cluster state
|
|
421
|
-
this.childCluster = {
|
|
422
|
-
id: childId,
|
|
423
|
-
orchestrator: childOrchestrator,
|
|
424
|
-
messageBus: childCluster.messageBus,
|
|
425
|
-
bridge,
|
|
426
|
-
};
|
|
427
|
-
|
|
428
|
-
// Listen for child cluster completion
|
|
429
|
-
childCluster.messageBus.subscribe((message) => {
|
|
430
|
-
if (message.topic === 'CLUSTER_COMPLETE' && message.cluster_id === childId) {
|
|
431
|
-
this._onChildComplete(message).catch((err) => {
|
|
432
|
-
console.error(`Failed to handle child completion: ${err.message}`);
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
// Listen for child cluster failure
|
|
438
|
-
childCluster.messageBus.subscribe((message) => {
|
|
439
|
-
if (message.topic === 'CLUSTER_FAILED' && message.cluster_id === childId) {
|
|
440
|
-
this._onChildFailed(message).catch((err) => {
|
|
441
|
-
console.error(`Failed to handle child failure: ${err.message}`);
|
|
442
|
-
});
|
|
443
|
-
}
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
/**
|
|
448
|
-
* Handle child cluster completion
|
|
449
|
-
* @private
|
|
450
|
-
*/
|
|
451
|
-
async _onChildComplete(message) {
|
|
452
|
-
this._log(`[SubCluster ${this.id}] Child cluster completed`);
|
|
453
|
-
|
|
454
|
-
this._publishLifecycle('CHILD_COMPLETE', {
|
|
455
|
-
childClusterId: this.childClusterId,
|
|
456
|
-
iteration: this.iteration,
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
// Execute onComplete hook
|
|
460
|
-
await this._executeHook('onComplete', {
|
|
461
|
-
result: message,
|
|
462
|
-
triggeringMessage: null,
|
|
463
|
-
});
|
|
464
|
-
|
|
465
|
-
// Clean up child cluster
|
|
466
|
-
await this._stopChildCluster();
|
|
467
|
-
|
|
468
|
-
this.state = 'idle';
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
/**
|
|
472
|
-
* Handle child cluster failure
|
|
473
|
-
* @private
|
|
474
|
-
*/
|
|
475
|
-
async _onChildFailed(message) {
|
|
476
|
-
this._log(`[SubCluster ${this.id}] Child cluster failed`);
|
|
477
|
-
|
|
478
|
-
this._publishLifecycle('CHILD_FAILED', {
|
|
479
|
-
childClusterId: this.childClusterId,
|
|
480
|
-
iteration: this.iteration,
|
|
481
|
-
error: message.content?.data?.reason,
|
|
482
|
-
});
|
|
483
|
-
|
|
484
|
-
// Execute onError hook
|
|
485
|
-
const error = new Error(message.content?.data?.reason || 'Child cluster failed');
|
|
486
|
-
await this._executeHook('onError', { error, triggeringMessage: null });
|
|
487
|
-
|
|
488
|
-
// Clean up child cluster
|
|
489
|
-
await this._stopChildCluster();
|
|
490
|
-
|
|
491
|
-
// Retry if within max iterations
|
|
492
|
-
if (this.iteration < this.maxIterations) {
|
|
493
|
-
this.state = 'idle';
|
|
494
|
-
} else {
|
|
495
|
-
this.state = 'failed';
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
/**
|
|
500
|
-
* Stop child cluster
|
|
501
|
-
* @private
|
|
502
|
-
*/
|
|
503
|
-
async _stopChildCluster() {
|
|
504
|
-
if (!this.childCluster) {
|
|
505
|
-
return;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
// Close message bridge
|
|
509
|
-
if (this.childCluster.bridge) {
|
|
510
|
-
this.childCluster.bridge.close();
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
// Stop child orchestrator
|
|
514
|
-
try {
|
|
515
|
-
await this.childCluster.orchestrator.stop(this.childCluster.id);
|
|
516
|
-
} catch (err) {
|
|
517
|
-
console.warn(`Warning: Failed to stop child cluster ${this.childCluster.id}: ${err.message}`);
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
this.childCluster = null;
|
|
521
|
-
this.childClusterId = null;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
/**
|
|
525
|
-
* Execute a hook
|
|
526
|
-
* @private
|
|
527
|
-
*/
|
|
528
|
-
_executeHook(hookName, context) {
|
|
529
|
-
const hook = this.config.hooks?.[hookName];
|
|
530
|
-
if (!hook) {
|
|
531
|
-
return;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
if (hook.action === 'publish_message') {
|
|
535
|
-
const message = this._substituteTemplate(hook.config, context);
|
|
536
|
-
this.messageBus.publish({
|
|
537
|
-
cluster_id: this.parentCluster.id,
|
|
538
|
-
sender: this.id,
|
|
539
|
-
...message,
|
|
540
|
-
});
|
|
541
|
-
} else {
|
|
542
|
-
throw new Error(`Unknown hook action: ${hook.action}`);
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
/**
|
|
547
|
-
* Substitute template variables in hook config
|
|
548
|
-
* @private
|
|
549
|
-
*/
|
|
550
|
-
_substituteTemplate(config, context) {
|
|
551
|
-
if (!config) {
|
|
552
|
-
throw new Error('_substituteTemplate: config is required');
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
const json = JSON.stringify(config);
|
|
556
|
-
|
|
557
|
-
let substituted = json
|
|
558
|
-
.replace(/\{\{cluster\.id\}\}/g, this.parentCluster.id)
|
|
559
|
-
.replace(/\{\{subcluster\.id\}\}/g, this.id)
|
|
560
|
-
.replace(/\{\{child\.id\}\}/g, this.childClusterId || '')
|
|
561
|
-
.replace(/\{\{iteration\}\}/g, String(this.iteration))
|
|
562
|
-
.replace(/\{\{error\.message\}\}/g, context.error?.message || '');
|
|
563
|
-
|
|
564
|
-
// Parse and validate
|
|
565
|
-
let result;
|
|
566
|
-
try {
|
|
567
|
-
result = JSON.parse(substituted);
|
|
568
|
-
} catch (e) {
|
|
569
|
-
console.error('JSON parse failed. Substituted string:');
|
|
570
|
-
console.error(substituted);
|
|
571
|
-
throw new Error(`Template substitution produced invalid JSON: ${e.message}`);
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
return result;
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
/**
|
|
578
|
-
* Resume sub-cluster task (not implemented for subclusters)
|
|
579
|
-
*/
|
|
580
|
-
resume(_resumeContext) {
|
|
581
|
-
throw new Error('Resume not implemented for subclusters');
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
/**
|
|
585
|
-
* Get current sub-cluster state
|
|
586
|
-
*/
|
|
587
|
-
getState() {
|
|
588
|
-
return {
|
|
589
|
-
id: this.id,
|
|
590
|
-
role: this.role,
|
|
591
|
-
state: this.state,
|
|
592
|
-
iteration: this.iteration,
|
|
593
|
-
maxIterations: this.maxIterations,
|
|
594
|
-
type: 'subcluster',
|
|
595
|
-
childClusterId: this.childClusterId,
|
|
596
|
-
childRunning: this.childCluster !== null,
|
|
597
|
-
};
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
module.exports = SubClusterWrapper;
|
package/src/task-runner.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TaskRunner - Strategy Pattern interface for executing provider tasks
|
|
3
|
-
*
|
|
4
|
-
* Implementations must provide a `run()` method that executes a task
|
|
5
|
-
* with the given context and options. Different runners can implement various
|
|
6
|
-
* execution strategies (CLI, mock responses, etc).
|
|
7
|
-
*/
|
|
8
|
-
class TaskRunner {
|
|
9
|
-
/**
|
|
10
|
-
* Execute a provider task with the given context and options
|
|
11
|
-
*
|
|
12
|
-
* @param {string} _context - Full prompt/context for the provider to process
|
|
13
|
-
* @param {Object} _options - Execution options
|
|
14
|
-
* @param {string} _options.agentId - Identifier for this agent/task
|
|
15
|
-
* @param {string} _options.model - Model to use (provider-specific model id)
|
|
16
|
-
* @param {string} [_options.provider] - Provider to use (claude|codex|gemini)
|
|
17
|
-
* @param {object} [_options.modelSpec] - Resolved model spec (level/model/reasoningEffort)
|
|
18
|
-
* @param {string} [_options.outputFormat] - Output format ('text', 'json', 'stream-json')
|
|
19
|
-
* @param {Object} [_options.jsonSchema] - JSON schema for structured output validation
|
|
20
|
-
* @param {string} [_options.cwd] - Working directory for task execution
|
|
21
|
-
* @param {boolean} [_options.isolation] - Whether to run in isolated container
|
|
22
|
-
*
|
|
23
|
-
* @returns {Promise<{success: boolean, output: string, error: string|null, taskId?: string}>} Result object with success status, output, error message, and optional taskId
|
|
24
|
-
*/
|
|
25
|
-
run(_context, _options) {
|
|
26
|
-
throw new Error('TaskRunner.run() not implemented');
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
module.exports = TaskRunner;
|