@covibes/zeroshot 5.2.1 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +174 -189
- package/README.md +226 -195
- package/cli/commands/providers.js +149 -0
- package/cli/index.js +3145 -2366
- package/cli/lib/first-run.js +40 -3
- package/cli/message-formatters-normal.js +28 -6
- package/cluster-templates/base-templates/debug-workflow.json +24 -78
- package/cluster-templates/base-templates/full-workflow.json +99 -316
- package/cluster-templates/base-templates/single-worker.json +23 -15
- package/cluster-templates/base-templates/worker-validator.json +105 -36
- package/cluster-templates/conductor-bootstrap.json +9 -7
- package/lib/docker-config.js +14 -1
- package/lib/git-remote-utils.js +165 -0
- package/lib/id-detector.js +10 -7
- package/lib/provider-defaults.js +62 -0
- package/lib/provider-detection.js +59 -0
- package/lib/provider-names.js +57 -0
- package/lib/settings/claude-auth.js +78 -0
- package/lib/settings.js +298 -15
- package/lib/stream-json-parser.js +4 -238
- package/package.json +27 -6
- package/scripts/setup-merge-queue.sh +170 -0
- package/scripts/validate-templates.js +100 -0
- package/src/agent/agent-config.js +140 -63
- package/src/agent/agent-context-builder.js +336 -165
- package/src/agent/agent-hook-executor.js +337 -67
- package/src/agent/agent-lifecycle.js +386 -287
- package/src/agent/agent-stuck-detector.js +7 -7
- package/src/agent/agent-task-executor.js +944 -683
- package/src/agent/output-extraction.js +217 -0
- package/src/agent/output-reformatter.js +175 -0
- package/src/agent/schema-utils.js +146 -0
- package/src/agent-wrapper.js +112 -31
- package/src/agents/git-pusher-template.js +285 -0
- package/src/claude-task-runner.js +145 -44
- package/src/config-router.js +13 -13
- package/src/config-validator.js +1049 -563
- package/src/input-helpers.js +65 -0
- package/src/isolation-manager.js +499 -320
- package/src/issue-providers/README.md +305 -0
- package/src/issue-providers/azure-devops-provider.js +273 -0
- package/src/issue-providers/base-provider.js +232 -0
- package/src/issue-providers/github-provider.js +179 -0
- package/src/issue-providers/gitlab-provider.js +241 -0
- package/src/issue-providers/index.js +196 -0
- package/src/issue-providers/jira-provider.js +239 -0
- package/src/ledger.js +50 -11
- package/src/lib/safe-exec.js +88 -0
- package/src/orchestrator.js +1348 -757
- package/src/preflight.js +306 -149
- package/src/process-metrics.js +98 -56
- package/src/providers/anthropic/cli-builder.js +73 -0
- package/src/providers/anthropic/index.js +204 -0
- package/src/providers/anthropic/models.js +23 -0
- package/src/providers/anthropic/output-parser.js +177 -0
- package/src/providers/base-provider.js +251 -0
- package/src/providers/capabilities.js +60 -0
- package/src/providers/google/cli-builder.js +55 -0
- package/src/providers/google/index.js +116 -0
- package/src/providers/google/models.js +24 -0
- package/src/providers/google/output-parser.js +101 -0
- package/src/providers/index.js +91 -0
- package/src/providers/openai/cli-builder.js +133 -0
- package/src/providers/openai/index.js +136 -0
- package/src/providers/openai/models.js +21 -0
- package/src/providers/openai/output-parser.js +143 -0
- package/src/providers/opencode/cli-builder.js +42 -0
- package/src/providers/opencode/index.js +103 -0
- package/src/providers/opencode/models.js +55 -0
- package/src/providers/opencode/output-parser.js +122 -0
- package/src/schemas/sub-cluster.js +68 -39
- package/src/status-footer.js +94 -48
- package/src/sub-cluster-wrapper.js +92 -36
- package/src/task-runner.js +8 -6
- package/src/template-resolver.js +12 -9
- package/src/tui/data-poller.js +123 -99
- package/src/tui/formatters.js +4 -3
- package/src/tui/keybindings.js +259 -318
- package/src/tui/layout.js +20 -3
- package/src/tui/renderer.js +11 -21
- package/task-lib/attachable-watcher.js +150 -111
- package/task-lib/claude-recovery.js +156 -0
- package/task-lib/commands/episodes.js +105 -0
- package/task-lib/commands/list.js +3 -3
- package/task-lib/commands/logs.js +231 -189
- package/task-lib/commands/resume.js +3 -2
- package/task-lib/commands/run.js +12 -3
- package/task-lib/commands/schedules.js +111 -61
- package/task-lib/config.js +0 -2
- package/task-lib/runner.js +128 -50
- package/task-lib/scheduler.js +3 -3
- package/task-lib/store.js +464 -152
- package/task-lib/tui/formatters.js +94 -45
- package/task-lib/tui/renderer.js +13 -3
- package/task-lib/tui.js +73 -32
- package/task-lib/watcher.js +157 -100
- package/src/agents/git-pusher-agent.json +0 -20
- package/src/github.js +0 -103
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
function parseToolPart(part) {
|
|
2
|
+
const state = part.state || {};
|
|
3
|
+
|
|
4
|
+
if (state.status === 'pending' || state.status === 'running') {
|
|
5
|
+
return {
|
|
6
|
+
type: 'tool_call',
|
|
7
|
+
toolName: part.tool,
|
|
8
|
+
toolId: part.callID,
|
|
9
|
+
input: state.input || {},
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (state.status === 'completed') {
|
|
14
|
+
return {
|
|
15
|
+
type: 'tool_result',
|
|
16
|
+
toolId: part.callID,
|
|
17
|
+
content: state.output || '',
|
|
18
|
+
isError: false,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (state.status === 'error') {
|
|
23
|
+
return {
|
|
24
|
+
type: 'tool_result',
|
|
25
|
+
toolId: part.callID,
|
|
26
|
+
content: state.error || '',
|
|
27
|
+
isError: true,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function parseStepFinish(part) {
|
|
35
|
+
const tokens = part.tokens || {};
|
|
36
|
+
return {
|
|
37
|
+
type: 'result',
|
|
38
|
+
success: true,
|
|
39
|
+
inputTokens: tokens.input || 0,
|
|
40
|
+
outputTokens: tokens.output || 0,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parsePart(part) {
|
|
45
|
+
if (!part || typeof part !== 'object') return null;
|
|
46
|
+
|
|
47
|
+
if (part.type === 'text' && part.text) {
|
|
48
|
+
return { type: 'text', text: part.text };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (part.type === 'reasoning' && part.text) {
|
|
52
|
+
return { type: 'thinking', text: part.text };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (part.type === 'tool') {
|
|
56
|
+
return parseToolPart(part);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (part.type === 'step-finish') {
|
|
60
|
+
return parseStepFinish(part);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function parseErrorEvent(event) {
|
|
67
|
+
const error = event.error || {};
|
|
68
|
+
return {
|
|
69
|
+
type: 'result',
|
|
70
|
+
success: false,
|
|
71
|
+
error: error.data?.message || error.message || error.name || 'Unknown error',
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function parseEvent(line) {
|
|
76
|
+
let event;
|
|
77
|
+
try {
|
|
78
|
+
event = JSON.parse(line);
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!event || typeof event !== 'object') return null;
|
|
84
|
+
|
|
85
|
+
if (event.type === 'error') {
|
|
86
|
+
return parseErrorEvent(event);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (event.type === 'text' || event.type === 'step_start' || event.type === 'step_finish') {
|
|
90
|
+
return parsePart(event.part || event);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (event.type === 'message.part.updated') {
|
|
94
|
+
return parsePart(event.properties?.part);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function parseChunk(chunk) {
|
|
101
|
+
const events = [];
|
|
102
|
+
const lines = chunk.split('\n');
|
|
103
|
+
|
|
104
|
+
for (const line of lines) {
|
|
105
|
+
if (!line.trim()) continue;
|
|
106
|
+
const event = parseEvent(line);
|
|
107
|
+
if (!event) continue;
|
|
108
|
+
if (Array.isArray(event)) {
|
|
109
|
+
events.push(...event);
|
|
110
|
+
} else {
|
|
111
|
+
events.push(event);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return events;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = {
|
|
119
|
+
parseEvent,
|
|
120
|
+
parseChunk,
|
|
121
|
+
parsePart,
|
|
122
|
+
};
|
|
@@ -30,16 +30,18 @@
|
|
|
30
30
|
* @param {Number} depth - Current nesting depth (for recursion limit)
|
|
31
31
|
* @returns {{ valid: boolean, errors: string[], warnings: string[] }}
|
|
32
32
|
*/
|
|
33
|
+
const MAX_SUBCLUSTER_DEPTH = 5;
|
|
34
|
+
|
|
33
35
|
function validateSubCluster(agentConfig, depth = 0) {
|
|
34
36
|
const errors = [];
|
|
35
37
|
const warnings = [];
|
|
36
38
|
|
|
37
39
|
// Max nesting depth to prevent infinite recursion
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return
|
|
40
|
+
if (depth > MAX_SUBCLUSTER_DEPTH) {
|
|
41
|
+
errors.push(
|
|
42
|
+
`Sub-cluster '${agentConfig.id}' exceeds max nesting depth (${MAX_SUBCLUSTER_DEPTH})`
|
|
43
|
+
);
|
|
44
|
+
return buildValidationResult(errors, warnings);
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
// Validate required fields
|
|
@@ -47,19 +49,44 @@ function validateSubCluster(agentConfig, depth = 0) {
|
|
|
47
49
|
errors.push(`Agent '${agentConfig.id}' must have type: 'subcluster'`);
|
|
48
50
|
}
|
|
49
51
|
|
|
52
|
+
if (!validateSubClusterConfig(agentConfig, depth, errors, warnings)) {
|
|
53
|
+
return buildValidationResult(errors, warnings);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Validate triggers (sub-cluster must have triggers to activate)
|
|
57
|
+
validateSubClusterTriggers(agentConfig, errors);
|
|
58
|
+
|
|
59
|
+
// Validate hooks structure
|
|
60
|
+
validateSubClusterHooks(agentConfig, errors);
|
|
61
|
+
|
|
62
|
+
// Check for context bridging configuration
|
|
63
|
+
validateContextStrategy(agentConfig, errors);
|
|
64
|
+
|
|
65
|
+
return buildValidationResult(errors, warnings);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function buildValidationResult(errors, warnings) {
|
|
69
|
+
return {
|
|
70
|
+
valid: errors.length === 0,
|
|
71
|
+
errors,
|
|
72
|
+
warnings,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function validateSubClusterConfig(agentConfig, depth, errors, warnings) {
|
|
50
77
|
if (!agentConfig.config) {
|
|
51
78
|
errors.push(`Sub-cluster '${agentConfig.id}' missing config field`);
|
|
52
|
-
return
|
|
79
|
+
return false;
|
|
53
80
|
}
|
|
54
81
|
|
|
55
82
|
if (!agentConfig.config.agents || !Array.isArray(agentConfig.config.agents)) {
|
|
56
83
|
errors.push(`Sub-cluster '${agentConfig.id}' config.agents must be an array`);
|
|
57
|
-
return
|
|
84
|
+
return false;
|
|
58
85
|
}
|
|
59
86
|
|
|
60
87
|
if (agentConfig.config.agents.length === 0) {
|
|
61
88
|
errors.push(`Sub-cluster '${agentConfig.id}' config.agents cannot be empty`);
|
|
62
|
-
return
|
|
89
|
+
return false;
|
|
63
90
|
}
|
|
64
91
|
|
|
65
92
|
// Recursively validate nested cluster config
|
|
@@ -71,46 +98,48 @@ function validateSubCluster(agentConfig, depth = 0) {
|
|
|
71
98
|
}
|
|
72
99
|
|
|
73
100
|
warnings.push(...childValidation.warnings.map((w) => `Sub-cluster '${agentConfig.id}': ${w}`));
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
74
103
|
|
|
75
|
-
|
|
104
|
+
function validateSubClusterTriggers(agentConfig, errors) {
|
|
76
105
|
if (!agentConfig.triggers || agentConfig.triggers.length === 0) {
|
|
77
106
|
errors.push(`Sub-cluster '${agentConfig.id}' must have triggers to activate`);
|
|
78
107
|
}
|
|
108
|
+
}
|
|
79
109
|
|
|
80
|
-
|
|
81
|
-
if (agentConfig.hooks) {
|
|
82
|
-
|
|
83
|
-
const hook = agentConfig.hooks.onComplete;
|
|
84
|
-
if (!hook.action) {
|
|
85
|
-
errors.push(`Sub-cluster '${agentConfig.id}' onComplete hook missing action`);
|
|
86
|
-
}
|
|
87
|
-
if (hook.action === 'publish_message' && !hook.config?.topic) {
|
|
88
|
-
errors.push(`Sub-cluster '${agentConfig.id}' onComplete hook missing config.topic`);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
110
|
+
function validateSubClusterHooks(agentConfig, errors) {
|
|
111
|
+
if (!agentConfig.hooks?.onComplete) {
|
|
112
|
+
return;
|
|
91
113
|
}
|
|
92
114
|
|
|
93
|
-
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
for (const topic of agentConfig.contextStrategy.parentTopics) {
|
|
100
|
-
if (typeof topic !== 'string') {
|
|
101
|
-
errors.push(
|
|
102
|
-
`Sub-cluster '${agentConfig.id}' parentTopics must contain strings, got ${typeof topic}`
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
115
|
+
const hook = agentConfig.hooks.onComplete;
|
|
116
|
+
if (!hook.action) {
|
|
117
|
+
errors.push(`Sub-cluster '${agentConfig.id}' onComplete hook missing action`);
|
|
118
|
+
}
|
|
119
|
+
if (hook.action === 'publish_message' && !hook.config?.topic) {
|
|
120
|
+
errors.push(`Sub-cluster '${agentConfig.id}' onComplete hook missing config.topic`);
|
|
107
121
|
}
|
|
122
|
+
}
|
|
108
123
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
124
|
+
function validateContextStrategy(agentConfig, errors) {
|
|
125
|
+
const parentTopics = agentConfig.contextStrategy?.parentTopics;
|
|
126
|
+
if (!parentTopics) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!Array.isArray(parentTopics)) {
|
|
131
|
+
errors.push(`Sub-cluster '${agentConfig.id}' contextStrategy.parentTopics must be an array`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Validate each parent topic is a string
|
|
136
|
+
for (const topic of parentTopics) {
|
|
137
|
+
if (typeof topic !== 'string') {
|
|
138
|
+
errors.push(
|
|
139
|
+
`Sub-cluster '${agentConfig.id}' parentTopics must contain strings, got ${typeof topic}`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
114
143
|
}
|
|
115
144
|
|
|
116
145
|
/**
|
package/src/status-footer.js
CHANGED
|
@@ -58,7 +58,7 @@ const AGENT_STATE = Object.freeze({
|
|
|
58
58
|
// Agent states that indicate "active" work (from agent-lifecycle.js)
|
|
59
59
|
// These should show in the footer with metrics
|
|
60
60
|
const ACTIVE_STATES = new Set([
|
|
61
|
-
AGENT_STATE.EXECUTING_TASK,
|
|
61
|
+
AGENT_STATE.EXECUTING_TASK, // Actually running claude CLI
|
|
62
62
|
AGENT_STATE.EVALUATING_LOGIC, // Evaluating trigger conditions
|
|
63
63
|
AGENT_STATE.BUILDING_CONTEXT, // Building context for task
|
|
64
64
|
]);
|
|
@@ -81,7 +81,8 @@ function debounce(fn, ms) {
|
|
|
81
81
|
* @typedef {Object} AgentState
|
|
82
82
|
* @property {string} id - Agent ID
|
|
83
83
|
* @property {string} state - Agent state (idle, executing, etc.)
|
|
84
|
-
* @property {number|null}
|
|
84
|
+
* @property {number|null} processPid - Process ID if running (preferred)
|
|
85
|
+
* @property {number|null} pid - Legacy alias for processPid
|
|
85
86
|
* @property {number} iteration - Current iteration
|
|
86
87
|
*/
|
|
87
88
|
|
|
@@ -175,7 +176,7 @@ class StatusFooter {
|
|
|
175
176
|
if (this.printQueue.length === 0) return;
|
|
176
177
|
|
|
177
178
|
// Write all queued output
|
|
178
|
-
const output = this.printQueue.map(text => text + '\n').join('');
|
|
179
|
+
const output = this.printQueue.map((text) => text + '\n').join('');
|
|
179
180
|
this.printQueue = [];
|
|
180
181
|
process.stdout.write(output);
|
|
181
182
|
}
|
|
@@ -396,8 +397,11 @@ class StatusFooter {
|
|
|
396
397
|
* @param {AgentState} agentState
|
|
397
398
|
*/
|
|
398
399
|
updateAgent(agentState) {
|
|
400
|
+
const processPid = agentState.processPid ?? agentState.pid ?? null;
|
|
399
401
|
this.agents.set(agentState.id, {
|
|
400
402
|
...agentState,
|
|
403
|
+
processPid,
|
|
404
|
+
pid: processPid,
|
|
401
405
|
lastUpdate: Date.now(),
|
|
402
406
|
});
|
|
403
407
|
}
|
|
@@ -418,11 +422,12 @@ class StatusFooter {
|
|
|
418
422
|
*/
|
|
419
423
|
async _sampleMetrics() {
|
|
420
424
|
for (const [agentId, agent] of this.agents) {
|
|
421
|
-
|
|
425
|
+
const processPid = agent.processPid ?? agent.pid;
|
|
426
|
+
if (!processPid) continue;
|
|
422
427
|
|
|
423
428
|
try {
|
|
424
429
|
// Get actual metrics with 200ms sample window (for CPU calculation)
|
|
425
|
-
const raw = await getProcessMetrics(
|
|
430
|
+
const raw = await getProcessMetrics(processPid, { samplePeriodMs: 200 });
|
|
426
431
|
const existing = this.interpolatedMetrics.get(agentId);
|
|
427
432
|
|
|
428
433
|
this.interpolatedMetrics.set(agentId, {
|
|
@@ -458,14 +463,8 @@ class StatusFooter {
|
|
|
458
463
|
if (!data.target || !data.current) continue;
|
|
459
464
|
|
|
460
465
|
// Lerp CPU and RAM toward target (they fluctuate)
|
|
461
|
-
data.current.cpuPercent = this._lerp(
|
|
462
|
-
|
|
463
|
-
data.target.cpuPercent
|
|
464
|
-
);
|
|
465
|
-
data.current.memoryMB = this._lerp(
|
|
466
|
-
data.current.memoryMB,
|
|
467
|
-
data.target.memoryMB
|
|
468
|
-
);
|
|
466
|
+
data.current.cpuPercent = this._lerp(data.current.cpuPercent, data.target.cpuPercent);
|
|
467
|
+
data.current.memoryMB = this._lerp(data.current.memoryMB, data.target.memoryMB);
|
|
469
468
|
// Network is cumulative counter - no interpolation, just use latest
|
|
470
469
|
}
|
|
471
470
|
}
|
|
@@ -767,60 +766,107 @@ class StatusFooter {
|
|
|
767
766
|
// Border with corner
|
|
768
767
|
parts.push(`${COLORS.gray}└─${COLORS.reset}`);
|
|
769
768
|
|
|
770
|
-
|
|
769
|
+
this.appendClusterStateSummary(parts);
|
|
770
|
+
this.appendDurationSummary(parts);
|
|
771
|
+
this.appendAgentCountSummary(parts);
|
|
772
|
+
this.appendTokenCostSummary(parts);
|
|
773
|
+
this.appendAggregateMetricsSummary(parts);
|
|
774
|
+
|
|
775
|
+
// Pad and close with bottom corner
|
|
776
|
+
return this.padSummaryLine(parts, width);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
appendClusterStateSummary(parts) {
|
|
771
780
|
const stateColor = this.clusterState === 'running' ? COLORS.green : COLORS.yellow;
|
|
772
781
|
parts.push(` ${stateColor}${this.clusterState}${COLORS.reset}`);
|
|
782
|
+
}
|
|
773
783
|
|
|
774
|
-
|
|
784
|
+
appendDurationSummary(parts) {
|
|
775
785
|
const duration = this.formatDuration(Date.now() - this.startTime);
|
|
776
786
|
parts.push(` ${COLORS.gray}│${COLORS.reset} ${COLORS.dim}${duration}${COLORS.reset}`);
|
|
787
|
+
}
|
|
777
788
|
|
|
778
|
-
|
|
779
|
-
const executing =
|
|
780
|
-
|
|
781
|
-
|
|
789
|
+
appendAgentCountSummary(parts) {
|
|
790
|
+
const { executing, total } = this.getActiveAgentCounts();
|
|
791
|
+
parts.push(
|
|
792
|
+
` ${COLORS.gray}│${COLORS.reset} ${COLORS.green}${executing}/${total}${COLORS.reset} active`
|
|
793
|
+
);
|
|
794
|
+
}
|
|
782
795
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
796
|
+
getActiveAgentCounts() {
|
|
797
|
+
const executing = Array.from(this.agents.values()).filter((a) =>
|
|
798
|
+
ACTIVE_STATES.has(a.state)
|
|
799
|
+
).length;
|
|
800
|
+
return { executing, total: this.agents.size };
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
appendTokenCostSummary(parts) {
|
|
804
|
+
const costStr = this.getTokenCostSummary();
|
|
805
|
+
if (!costStr) {
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
parts.push(` ${COLORS.gray}│${COLORS.reset} ${COLORS.yellow}${costStr}${COLORS.reset}`);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
getTokenCostSummary() {
|
|
812
|
+
if (!this.messageBus || !this.clusterId) {
|
|
813
|
+
return null;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
try {
|
|
817
|
+
const tokensByRole = this.messageBus.getTokensByRole(this.clusterId);
|
|
818
|
+
const totalCost = tokensByRole?._total?.totalCostUsd || 0;
|
|
819
|
+
if (totalCost <= 0) {
|
|
820
|
+
return null;
|
|
795
821
|
}
|
|
822
|
+
|
|
823
|
+
// Format: $0.05 or $1.23 or $12.34
|
|
824
|
+
return totalCost < 0.01 ? '<$0.01' : `$${totalCost.toFixed(2)}`;
|
|
825
|
+
} catch {
|
|
826
|
+
// Ignore errors - token tracking is optional
|
|
827
|
+
return null;
|
|
796
828
|
}
|
|
829
|
+
}
|
|
797
830
|
|
|
798
|
-
|
|
831
|
+
appendAggregateMetricsSummary(parts) {
|
|
832
|
+
const { totalCpu, totalMem, totalBytesSent, totalBytesReceived } = this.getAggregateMetrics();
|
|
833
|
+
|
|
834
|
+
if (totalCpu <= 0 && totalMem <= 0) {
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
parts.push(` ${COLORS.gray}│${COLORS.reset}`);
|
|
839
|
+
let aggregateStr = ` ${COLORS.cyan}Σ${COLORS.reset} `;
|
|
840
|
+
aggregateStr += `${COLORS.dim}CPU:${COLORS.reset}${totalCpu.toFixed(1)}%`;
|
|
841
|
+
aggregateStr += ` ${COLORS.dim}RAM:${COLORS.reset}${totalMem.toFixed(1)}MB`;
|
|
842
|
+
if (totalBytesSent > 0 || totalBytesReceived > 0) {
|
|
843
|
+
aggregateStr += ` ${COLORS.dim}NET:${COLORS.reset}${COLORS.cyan}↑${this.formatBytes(totalBytesSent)} ↓${this.formatBytes(totalBytesReceived)}${COLORS.reset}`;
|
|
844
|
+
}
|
|
845
|
+
parts.push(aggregateStr);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
getAggregateMetrics() {
|
|
799
849
|
let totalCpu = 0;
|
|
800
850
|
let totalMem = 0;
|
|
801
851
|
let totalBytesSent = 0;
|
|
802
852
|
let totalBytesReceived = 0;
|
|
853
|
+
|
|
854
|
+
// Aggregate metrics (using interpolated values for smooth display)
|
|
803
855
|
for (const data of this.interpolatedMetrics.values()) {
|
|
804
|
-
if (data.exists
|
|
805
|
-
|
|
806
|
-
totalMem += data.current.memoryMB;
|
|
807
|
-
totalBytesSent += data.network?.bytesSent || 0;
|
|
808
|
-
totalBytesReceived += data.network?.bytesReceived || 0;
|
|
856
|
+
if (!data.exists || !data.current) {
|
|
857
|
+
continue;
|
|
809
858
|
}
|
|
810
|
-
}
|
|
811
859
|
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
aggregateStr += ` ${COLORS.dim}RAM:${COLORS.reset}${totalMem.toFixed(1)}MB`;
|
|
817
|
-
if (totalBytesSent > 0 || totalBytesReceived > 0) {
|
|
818
|
-
aggregateStr += ` ${COLORS.dim}NET:${COLORS.reset}${COLORS.cyan}↑${this.formatBytes(totalBytesSent)} ↓${this.formatBytes(totalBytesReceived)}${COLORS.reset}`;
|
|
819
|
-
}
|
|
820
|
-
parts.push(aggregateStr);
|
|
860
|
+
totalCpu += data.current.cpuPercent;
|
|
861
|
+
totalMem += data.current.memoryMB;
|
|
862
|
+
totalBytesSent += data.network?.bytesSent || 0;
|
|
863
|
+
totalBytesReceived += data.network?.bytesReceived || 0;
|
|
821
864
|
}
|
|
822
865
|
|
|
823
|
-
|
|
866
|
+
return { totalCpu, totalMem, totalBytesSent, totalBytesReceived };
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
padSummaryLine(parts, width) {
|
|
824
870
|
const content = parts.join('');
|
|
825
871
|
const contentLen = this.stripAnsi(content).length;
|
|
826
872
|
const padding = Math.max(0, width - contentLen - 1);
|
|
@@ -36,6 +36,7 @@ class SubClusterWrapper {
|
|
|
36
36
|
this.childClusterId = null;
|
|
37
37
|
|
|
38
38
|
this.quiet = options.quiet || false;
|
|
39
|
+
this.modelOverride = options.modelOverride || null;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
/**
|
|
@@ -285,47 +286,88 @@ class SubClusterWrapper {
|
|
|
285
286
|
_buildChildContext(triggeringMessage) {
|
|
286
287
|
const parentTopics = this.config.contextStrategy?.parentTopics || [];
|
|
287
288
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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
|
+
];
|
|
292
297
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
context += `## Parent Cluster Messages\n\n`;
|
|
298
|
+
this._appendParentTopicContext(lines, parentTopics);
|
|
299
|
+
this._appendTriggeringMessageContext(lines, triggeringMessage);
|
|
296
300
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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', '');
|
|
303
310
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
if (msg.content?.text) {
|
|
309
|
-
context += `${msg.content.text}\n`;
|
|
310
|
-
}
|
|
311
|
-
if (msg.content?.data) {
|
|
312
|
-
context += `Data: ${JSON.stringify(msg.content.data, null, 2)}\n`;
|
|
313
|
-
}
|
|
314
|
-
context += '\n';
|
|
315
|
-
}
|
|
316
|
-
}
|
|
311
|
+
for (const topic of parentTopics) {
|
|
312
|
+
const topicLines = this._buildTopicContextLines(topic);
|
|
313
|
+
if (topicLines.length === 0) {
|
|
314
|
+
continue;
|
|
317
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);
|
|
318
349
|
}
|
|
319
350
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
context += `Topic: ${triggeringMessage.topic}\n`;
|
|
323
|
-
context += `Sender: ${triggeringMessage.sender}\n`;
|
|
324
|
-
if (triggeringMessage.content?.text) {
|
|
325
|
-
context += `\n${triggeringMessage.content.text}\n`;
|
|
351
|
+
if (data) {
|
|
352
|
+
lines.push(`Data: ${JSON.stringify(data, null, 2)}`);
|
|
326
353
|
}
|
|
327
354
|
|
|
328
|
-
return
|
|
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
|
+
}
|
|
329
371
|
}
|
|
330
372
|
|
|
331
373
|
/**
|
|
@@ -341,17 +383,31 @@ class SubClusterWrapper {
|
|
|
341
383
|
this.childClusterId = childId;
|
|
342
384
|
|
|
343
385
|
// Create child orchestrator with separate database
|
|
344
|
-
const childOrchestrator =
|
|
386
|
+
const childOrchestrator = await Orchestrator.create({
|
|
345
387
|
quiet: this.quiet,
|
|
346
388
|
skipLoad: true,
|
|
347
389
|
storageDir: path.join(this.parentCluster.ledger.dbPath, '..', 'subclusters', childId),
|
|
348
390
|
});
|
|
349
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
|
+
|
|
350
406
|
// Start child cluster with text input (context from parent)
|
|
351
407
|
const childCluster = await childOrchestrator.start(
|
|
352
|
-
|
|
408
|
+
childConfig, // Child cluster config
|
|
353
409
|
{ text: context },
|
|
354
|
-
{ testMode: false }
|
|
410
|
+
{ testMode: false, modelOverride: this.modelOverride || undefined }
|
|
355
411
|
);
|
|
356
412
|
|
|
357
413
|
// Create message bridge
|
package/src/task-runner.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* TaskRunner - Strategy Pattern interface for executing
|
|
2
|
+
* TaskRunner - Strategy Pattern interface for executing provider tasks
|
|
3
3
|
*
|
|
4
|
-
* Implementations must provide a `run()` method that executes a
|
|
4
|
+
* Implementations must provide a `run()` method that executes a task
|
|
5
5
|
* with the given context and options. Different runners can implement various
|
|
6
|
-
* execution strategies (
|
|
6
|
+
* execution strategies (CLI, mock responses, etc).
|
|
7
7
|
*/
|
|
8
8
|
class TaskRunner {
|
|
9
9
|
/**
|
|
10
|
-
* Execute a
|
|
10
|
+
* Execute a provider task with the given context and options
|
|
11
11
|
*
|
|
12
|
-
* @param {string} _context - Full prompt/context for
|
|
12
|
+
* @param {string} _context - Full prompt/context for the provider to process
|
|
13
13
|
* @param {Object} _options - Execution options
|
|
14
14
|
* @param {string} _options.agentId - Identifier for this agent/task
|
|
15
|
-
* @param {string} _options.model - Model to use (
|
|
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)
|
|
16
18
|
* @param {string} [_options.outputFormat] - Output format ('text', 'json', 'stream-json')
|
|
17
19
|
* @param {Object} [_options.jsonSchema] - JSON schema for structured output validation
|
|
18
20
|
* @param {string} [_options.cwd] - Working directory for task execution
|