@covibes/zeroshot 1.4.0 → 2.0.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 +63 -0
- package/README.md +20 -6
- package/cli/index.js +513 -194
- package/cli/lib/first-run.js +174 -0
- package/cli/lib/update-checker.js +234 -0
- package/cli/message-formatters-normal.js +77 -38
- package/cluster-templates/base-templates/debug-workflow.json +11 -2
- package/cluster-templates/base-templates/full-workflow.json +20 -7
- package/cluster-templates/base-templates/single-worker.json +8 -1
- package/cluster-templates/base-templates/worker-validator.json +10 -2
- package/docker/zeroshot-cluster/Dockerfile +7 -0
- package/lib/settings.js +25 -7
- package/package.json +3 -1
- package/src/agent/agent-config.js +19 -6
- package/src/agent/agent-context-builder.js +9 -0
- package/src/agent/agent-task-executor.js +149 -65
- package/src/config-validator.js +13 -0
- package/src/isolation-manager.js +11 -7
- package/src/orchestrator.js +78 -1
- package/src/status-footer.js +59 -6
- package/src/template-resolver.js +23 -1
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* First-Run Setup Wizard
|
|
3
|
+
*
|
|
4
|
+
* Interactive setup on first use:
|
|
5
|
+
* - Welcome banner
|
|
6
|
+
* - Default model selection (sonnet/opus/haiku)
|
|
7
|
+
* - Auto-update preference
|
|
8
|
+
* - Marks setup as complete
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const readline = require('readline');
|
|
12
|
+
const { loadSettings, saveSettings } = require('../../lib/settings');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Print welcome banner
|
|
16
|
+
*/
|
|
17
|
+
function printWelcome() {
|
|
18
|
+
console.log(`
|
|
19
|
+
╔═══════════════════════════════════════════════════════════════╗
|
|
20
|
+
║ ║
|
|
21
|
+
║ Welcome to Zeroshot! ║
|
|
22
|
+
║ Multi-agent orchestration for Claude ║
|
|
23
|
+
║ ║
|
|
24
|
+
║ Let's configure a few settings to get started. ║
|
|
25
|
+
║ ║
|
|
26
|
+
╚═══════════════════════════════════════════════════════════════╝
|
|
27
|
+
`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create readline interface
|
|
32
|
+
* @returns {readline.Interface}
|
|
33
|
+
*/
|
|
34
|
+
function createReadline() {
|
|
35
|
+
return readline.createInterface({
|
|
36
|
+
input: process.stdin,
|
|
37
|
+
output: process.stdout,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Prompt for model selection
|
|
43
|
+
* @param {readline.Interface} rl
|
|
44
|
+
* @returns {Promise<string>}
|
|
45
|
+
*/
|
|
46
|
+
function promptModel(rl) {
|
|
47
|
+
return new Promise((resolve) => {
|
|
48
|
+
console.log('Which Claude model should agents use by default?\n');
|
|
49
|
+
console.log(' 1) sonnet - Fast & capable (recommended)');
|
|
50
|
+
console.log(' 2) opus - Most capable, slower');
|
|
51
|
+
console.log(' 3) haiku - Fastest, for simple tasks\n');
|
|
52
|
+
|
|
53
|
+
rl.question('Enter 1, 2, or 3 [1]: ', (answer) => {
|
|
54
|
+
const choice = answer.trim() || '1';
|
|
55
|
+
switch (choice) {
|
|
56
|
+
case '2':
|
|
57
|
+
resolve('opus');
|
|
58
|
+
break;
|
|
59
|
+
case '3':
|
|
60
|
+
resolve('haiku');
|
|
61
|
+
break;
|
|
62
|
+
default:
|
|
63
|
+
resolve('sonnet');
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Prompt for auto-update preference
|
|
71
|
+
* @param {readline.Interface} rl
|
|
72
|
+
* @returns {Promise<boolean>}
|
|
73
|
+
*/
|
|
74
|
+
function promptAutoUpdate(rl) {
|
|
75
|
+
return new Promise((resolve) => {
|
|
76
|
+
console.log('\nWould you like zeroshot to check for updates automatically?');
|
|
77
|
+
console.log('(Checks npm registry every 24 hours)\n');
|
|
78
|
+
|
|
79
|
+
rl.question('Enable auto-update checks? [Y/n]: ', (answer) => {
|
|
80
|
+
const normalized = answer.trim().toLowerCase();
|
|
81
|
+
// Default to yes if empty or starts with 'y'
|
|
82
|
+
resolve(normalized === '' || normalized === 'y' || normalized === 'yes');
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Print completion message
|
|
89
|
+
* @param {object} settings - Saved settings
|
|
90
|
+
*/
|
|
91
|
+
function printComplete(settings) {
|
|
92
|
+
console.log(`
|
|
93
|
+
╔═══════════════════════════════════════════════════════════════╗
|
|
94
|
+
║ Setup complete! ║
|
|
95
|
+
╚═══════════════════════════════════════════════════════════════╝
|
|
96
|
+
|
|
97
|
+
Your settings:
|
|
98
|
+
• Default model: ${settings.defaultModel}
|
|
99
|
+
• Auto-updates: ${settings.autoCheckUpdates ? 'enabled' : 'disabled'}
|
|
100
|
+
|
|
101
|
+
Change anytime with: zeroshot settings set <key> <value>
|
|
102
|
+
|
|
103
|
+
Get started:
|
|
104
|
+
zeroshot run "Fix the bug in auth.js"
|
|
105
|
+
zeroshot run 123 (GitHub issue number)
|
|
106
|
+
zeroshot --help
|
|
107
|
+
|
|
108
|
+
`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Check if first-run setup is needed
|
|
113
|
+
* @param {object} settings - Current settings
|
|
114
|
+
* @returns {boolean}
|
|
115
|
+
*/
|
|
116
|
+
function detectFirstRun(settings) {
|
|
117
|
+
return !settings.firstRunComplete;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Main entry point - run first-time setup if needed
|
|
122
|
+
* @param {object} options
|
|
123
|
+
* @param {boolean} options.quiet - Skip interactive prompts
|
|
124
|
+
* @returns {Promise<boolean>} True if setup was run
|
|
125
|
+
*/
|
|
126
|
+
async function checkFirstRun(options = {}) {
|
|
127
|
+
const settings = loadSettings();
|
|
128
|
+
|
|
129
|
+
// Already completed setup
|
|
130
|
+
if (!detectFirstRun(settings)) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Quiet mode - use defaults, mark complete
|
|
135
|
+
if (options.quiet) {
|
|
136
|
+
settings.firstRunComplete = true;
|
|
137
|
+
saveSettings(settings);
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Interactive setup
|
|
142
|
+
printWelcome();
|
|
143
|
+
|
|
144
|
+
const rl = createReadline();
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
// Model selection
|
|
148
|
+
const model = await promptModel(rl);
|
|
149
|
+
settings.defaultModel = model;
|
|
150
|
+
|
|
151
|
+
// Auto-update preference
|
|
152
|
+
const autoUpdate = await promptAutoUpdate(rl);
|
|
153
|
+
settings.autoCheckUpdates = autoUpdate;
|
|
154
|
+
|
|
155
|
+
// Mark complete
|
|
156
|
+
settings.firstRunComplete = true;
|
|
157
|
+
saveSettings(settings);
|
|
158
|
+
|
|
159
|
+
// Print completion
|
|
160
|
+
printComplete(settings);
|
|
161
|
+
|
|
162
|
+
return true;
|
|
163
|
+
} finally {
|
|
164
|
+
rl.close();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
module.exports = {
|
|
169
|
+
checkFirstRun,
|
|
170
|
+
// Exported for testing
|
|
171
|
+
detectFirstRun,
|
|
172
|
+
printWelcome,
|
|
173
|
+
printComplete,
|
|
174
|
+
};
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update Checker - Checks npm registry for newer versions
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - 24-hour check interval (avoids registry spam)
|
|
6
|
+
* - 5-second timeout (non-blocking if offline)
|
|
7
|
+
* - Interactive prompt for manual update
|
|
8
|
+
* - Respects quiet mode (no prompts in CI/scripts)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const https = require('https');
|
|
12
|
+
const { spawn } = require('child_process');
|
|
13
|
+
const readline = require('readline');
|
|
14
|
+
const { loadSettings, saveSettings } = require('../../lib/settings');
|
|
15
|
+
|
|
16
|
+
// 24 hours in milliseconds
|
|
17
|
+
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
18
|
+
|
|
19
|
+
// Timeout for npm registry fetch (5 seconds)
|
|
20
|
+
const FETCH_TIMEOUT_MS = 5000;
|
|
21
|
+
|
|
22
|
+
// npm registry URL
|
|
23
|
+
const REGISTRY_URL = 'https://registry.npmjs.org/@covibes/zeroshot/latest';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get current package version
|
|
27
|
+
* @returns {string}
|
|
28
|
+
*/
|
|
29
|
+
function getCurrentVersion() {
|
|
30
|
+
const pkg = require('../../package.json');
|
|
31
|
+
return pkg.version;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Compare semver versions
|
|
36
|
+
* @param {string} current - Current version (e.g., "1.5.0")
|
|
37
|
+
* @param {string} latest - Latest version (e.g., "1.6.0")
|
|
38
|
+
* @returns {boolean} True if latest > current
|
|
39
|
+
*/
|
|
40
|
+
function isNewerVersion(current, latest) {
|
|
41
|
+
const currentParts = current.split('.').map(Number);
|
|
42
|
+
const latestParts = latest.split('.').map(Number);
|
|
43
|
+
|
|
44
|
+
for (let i = 0; i < 3; i++) {
|
|
45
|
+
const c = currentParts[i] || 0;
|
|
46
|
+
const l = latestParts[i] || 0;
|
|
47
|
+
if (l > c) return true;
|
|
48
|
+
if (l < c) return false;
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Fetch latest version from npm registry
|
|
55
|
+
* @returns {Promise<string|null>} Latest version or null on failure
|
|
56
|
+
*/
|
|
57
|
+
function fetchLatestVersion() {
|
|
58
|
+
return new Promise((resolve) => {
|
|
59
|
+
const req = https.get(REGISTRY_URL, { timeout: FETCH_TIMEOUT_MS }, (res) => {
|
|
60
|
+
if (res.statusCode !== 200) {
|
|
61
|
+
resolve(null);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let data = '';
|
|
66
|
+
res.on('data', (chunk) => {
|
|
67
|
+
data += chunk;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
res.on('end', () => {
|
|
71
|
+
try {
|
|
72
|
+
const json = JSON.parse(data);
|
|
73
|
+
resolve(json.version || null);
|
|
74
|
+
} catch {
|
|
75
|
+
resolve(null);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
req.on('error', () => {
|
|
81
|
+
resolve(null);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
req.on('timeout', () => {
|
|
85
|
+
req.destroy();
|
|
86
|
+
resolve(null);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Additional safety timeout
|
|
90
|
+
setTimeout(() => {
|
|
91
|
+
req.destroy();
|
|
92
|
+
resolve(null);
|
|
93
|
+
}, FETCH_TIMEOUT_MS + 1000);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Prompt user for update confirmation
|
|
99
|
+
* @param {string} currentVersion
|
|
100
|
+
* @param {string} latestVersion
|
|
101
|
+
* @returns {Promise<boolean>} True if user wants to update
|
|
102
|
+
*/
|
|
103
|
+
function promptForUpdate(currentVersion, latestVersion) {
|
|
104
|
+
return new Promise((resolve) => {
|
|
105
|
+
const rl = readline.createInterface({
|
|
106
|
+
input: process.stdin,
|
|
107
|
+
output: process.stdout,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
console.log(`\n📦 Update available: ${currentVersion} → ${latestVersion}`);
|
|
111
|
+
rl.question(' Install now? [y/N] ', (answer) => {
|
|
112
|
+
rl.close();
|
|
113
|
+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Run npm install to update the package
|
|
120
|
+
* @returns {Promise<boolean>} True if update succeeded
|
|
121
|
+
*/
|
|
122
|
+
function runUpdate() {
|
|
123
|
+
return new Promise((resolve) => {
|
|
124
|
+
console.log('\n📥 Installing update...');
|
|
125
|
+
|
|
126
|
+
const proc = spawn('npm', ['install', '-g', '@covibes/zeroshot@latest'], {
|
|
127
|
+
stdio: 'inherit',
|
|
128
|
+
shell: true,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
proc.on('close', (code) => {
|
|
132
|
+
if (code === 0) {
|
|
133
|
+
console.log('✅ Update installed successfully!');
|
|
134
|
+
console.log(' Restart zeroshot to use the new version.\n');
|
|
135
|
+
resolve(true);
|
|
136
|
+
} else {
|
|
137
|
+
console.log('❌ Update failed. Try manually:');
|
|
138
|
+
console.log(' npm install -g @covibes/zeroshot@latest\n');
|
|
139
|
+
resolve(false);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
proc.on('error', () => {
|
|
144
|
+
console.log('❌ Update failed. Try manually:');
|
|
145
|
+
console.log(' npm install -g @covibes/zeroshot@latest\n');
|
|
146
|
+
resolve(false);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Check if update check should run
|
|
153
|
+
* @param {object} settings - Current settings
|
|
154
|
+
* @returns {boolean}
|
|
155
|
+
*/
|
|
156
|
+
function shouldCheckForUpdates(settings) {
|
|
157
|
+
// Disabled by user
|
|
158
|
+
if (!settings.autoCheckUpdates) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Never checked before
|
|
163
|
+
if (!settings.lastUpdateCheckAt) {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Check if 24 hours have passed
|
|
168
|
+
const elapsed = Date.now() - settings.lastUpdateCheckAt;
|
|
169
|
+
return elapsed >= CHECK_INTERVAL_MS;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Main entry point - check for updates
|
|
174
|
+
* @param {object} options
|
|
175
|
+
* @param {boolean} options.quiet - Skip interactive prompts
|
|
176
|
+
* @returns {Promise<void>}
|
|
177
|
+
*/
|
|
178
|
+
async function checkForUpdates(options = {}) {
|
|
179
|
+
const settings = loadSettings();
|
|
180
|
+
|
|
181
|
+
// Skip check if not due
|
|
182
|
+
if (!shouldCheckForUpdates(settings)) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const currentVersion = getCurrentVersion();
|
|
187
|
+
const latestVersion = await fetchLatestVersion();
|
|
188
|
+
|
|
189
|
+
// Update last check timestamp regardless of result
|
|
190
|
+
settings.lastUpdateCheckAt = Date.now();
|
|
191
|
+
saveSettings(settings);
|
|
192
|
+
|
|
193
|
+
// Network failure - silently skip
|
|
194
|
+
if (!latestVersion) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// No update available
|
|
199
|
+
if (!isNewerVersion(currentVersion, latestVersion)) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Already notified about this version
|
|
204
|
+
if (settings.lastSeenVersion === latestVersion) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Update lastSeenVersion so we don't nag about the same version
|
|
209
|
+
settings.lastSeenVersion = latestVersion;
|
|
210
|
+
saveSettings(settings);
|
|
211
|
+
|
|
212
|
+
// Quiet mode - just inform, no prompt
|
|
213
|
+
if (options.quiet) {
|
|
214
|
+
console.log(`📦 Update available: ${currentVersion} → ${latestVersion}`);
|
|
215
|
+
console.log(' Run: npm install -g @covibes/zeroshot@latest\n');
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Interactive mode - prompt for update
|
|
220
|
+
const wantsUpdate = await promptForUpdate(currentVersion, latestVersion);
|
|
221
|
+
if (wantsUpdate) {
|
|
222
|
+
await runUpdate();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
module.exports = {
|
|
227
|
+
checkForUpdates,
|
|
228
|
+
// Exported for testing
|
|
229
|
+
getCurrentVersion,
|
|
230
|
+
isNewerVersion,
|
|
231
|
+
fetchLatestVersion,
|
|
232
|
+
shouldCheckForUpdates,
|
|
233
|
+
CHECK_INTERVAL_MS,
|
|
234
|
+
};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Normal mode message formatters
|
|
3
3
|
* Full-detail message display for non-watch mode
|
|
4
|
+
*
|
|
5
|
+
* All functions accept an optional `print` parameter for output routing.
|
|
6
|
+
* When StatusFooter is active, pass safePrint to avoid terminal garbling.
|
|
4
7
|
*/
|
|
5
8
|
|
|
6
9
|
const chalk = require('chalk');
|
|
@@ -9,9 +12,10 @@ const chalk = require('chalk');
|
|
|
9
12
|
* Format AGENT_LIFECYCLE events
|
|
10
13
|
* @param {Object} msg - Message object
|
|
11
14
|
* @param {string} prefix - Formatted message prefix
|
|
15
|
+
* @param {Function} [print=console.log] - Print function for output
|
|
12
16
|
* @returns {boolean} True if message was handled
|
|
13
17
|
*/
|
|
14
|
-
function formatAgentLifecycle(msg, prefix) {
|
|
18
|
+
function formatAgentLifecycle(msg, prefix, print = console.log) {
|
|
15
19
|
const data = msg.content?.data;
|
|
16
20
|
const event = data?.event;
|
|
17
21
|
|
|
@@ -35,7 +39,7 @@ function formatAgentLifecycle(msg, prefix) {
|
|
|
35
39
|
eventText = event || 'unknown event';
|
|
36
40
|
}
|
|
37
41
|
|
|
38
|
-
|
|
42
|
+
print(`${prefix} ${icon} ${eventText}`);
|
|
39
43
|
return true;
|
|
40
44
|
}
|
|
41
45
|
|
|
@@ -44,27 +48,28 @@ function formatAgentLifecycle(msg, prefix) {
|
|
|
44
48
|
* @param {Object} msg - Message object
|
|
45
49
|
* @param {string} prefix - Formatted message prefix
|
|
46
50
|
* @param {string} timestamp - Formatted timestamp
|
|
51
|
+
* @param {Function} [print=console.log] - Print function for output
|
|
47
52
|
* @returns {boolean} True if message was handled
|
|
48
53
|
*/
|
|
49
|
-
function formatAgentError(msg, prefix, timestamp) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
function formatAgentError(msg, prefix, timestamp, print = console.log) {
|
|
55
|
+
print(''); // Blank line before error
|
|
56
|
+
print(chalk.bold.red(`${'─'.repeat(60)}`));
|
|
57
|
+
print(`${prefix} ${chalk.gray(timestamp)} ${chalk.bold.red('🔴 AGENT ERROR')}`);
|
|
53
58
|
|
|
54
59
|
if (msg.content?.text) {
|
|
55
|
-
|
|
60
|
+
print(`${prefix} ${chalk.red(msg.content.text)}`);
|
|
56
61
|
}
|
|
57
62
|
|
|
58
63
|
if (msg.content?.data?.stack) {
|
|
59
64
|
const stackLines = msg.content.data.stack.split('\n').slice(0, 5);
|
|
60
65
|
for (const line of stackLines) {
|
|
61
66
|
if (line.trim()) {
|
|
62
|
-
|
|
67
|
+
print(`${prefix} ${chalk.dim(line)}`);
|
|
63
68
|
}
|
|
64
69
|
}
|
|
65
70
|
}
|
|
66
71
|
|
|
67
|
-
|
|
72
|
+
print(chalk.bold.red(`${'─'.repeat(60)}`));
|
|
68
73
|
return true;
|
|
69
74
|
}
|
|
70
75
|
|
|
@@ -74,29 +79,30 @@ function formatAgentError(msg, prefix, timestamp) {
|
|
|
74
79
|
* @param {string} prefix - Formatted message prefix
|
|
75
80
|
* @param {string} timestamp - Formatted timestamp
|
|
76
81
|
* @param {Set} shownNewTaskForCluster - Set tracking shown tasks
|
|
82
|
+
* @param {Function} [print=console.log] - Print function for output
|
|
77
83
|
* @returns {boolean} True if message was handled
|
|
78
84
|
*/
|
|
79
|
-
function formatIssueOpened(msg, prefix, timestamp, shownNewTaskForCluster) {
|
|
85
|
+
function formatIssueOpened(msg, prefix, timestamp, shownNewTaskForCluster, print = console.log) {
|
|
80
86
|
// Skip duplicate - conductor re-publishes after spawning agents
|
|
81
87
|
if (shownNewTaskForCluster.has(msg.cluster_id)) {
|
|
82
88
|
return true;
|
|
83
89
|
}
|
|
84
90
|
shownNewTaskForCluster.add(msg.cluster_id);
|
|
85
91
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
92
|
+
print(''); // Blank line before new task
|
|
93
|
+
print(chalk.bold.blue(`${'─'.repeat(60)}`));
|
|
94
|
+
print(`${prefix} ${chalk.gray(timestamp)} ${chalk.bold.blue('📋 NEW TASK')}`);
|
|
89
95
|
|
|
90
96
|
if (msg.content?.text) {
|
|
91
97
|
const lines = msg.content.text.split('\n').slice(0, 3);
|
|
92
98
|
for (const line of lines) {
|
|
93
99
|
if (line.trim() && line.trim() !== '# Manual Input') {
|
|
94
|
-
|
|
100
|
+
print(`${prefix} ${chalk.white(line)}`);
|
|
95
101
|
}
|
|
96
102
|
}
|
|
97
103
|
}
|
|
98
104
|
|
|
99
|
-
|
|
105
|
+
print(chalk.bold.blue(`${'─'.repeat(60)}`));
|
|
100
106
|
return true;
|
|
101
107
|
}
|
|
102
108
|
|
|
@@ -105,15 +111,16 @@ function formatIssueOpened(msg, prefix, timestamp, shownNewTaskForCluster) {
|
|
|
105
111
|
* @param {Object} msg - Message object
|
|
106
112
|
* @param {string} prefix - Formatted message prefix
|
|
107
113
|
* @param {string} timestamp - Formatted timestamp
|
|
114
|
+
* @param {Function} [print=console.log] - Print function for output
|
|
108
115
|
* @returns {boolean} True if message was handled
|
|
109
116
|
*/
|
|
110
|
-
function formatImplementationReady(msg, prefix, timestamp) {
|
|
111
|
-
|
|
117
|
+
function formatImplementationReady(msg, prefix, timestamp, print = console.log) {
|
|
118
|
+
print(
|
|
112
119
|
`${prefix} ${chalk.gray(timestamp)} ${chalk.bold.yellow('✅ IMPLEMENTATION READY')}`
|
|
113
120
|
);
|
|
114
121
|
|
|
115
122
|
if (msg.content?.data?.commit) {
|
|
116
|
-
|
|
123
|
+
print(
|
|
117
124
|
`${prefix} ${chalk.gray('Commit:')} ${chalk.cyan(msg.content.data.commit.substring(0, 8))}`
|
|
118
125
|
);
|
|
119
126
|
}
|
|
@@ -126,22 +133,23 @@ function formatImplementationReady(msg, prefix, timestamp) {
|
|
|
126
133
|
* @param {Object} msg - Message object
|
|
127
134
|
* @param {string} prefix - Formatted message prefix
|
|
128
135
|
* @param {string} timestamp - Formatted timestamp
|
|
136
|
+
* @param {Function} [print=console.log] - Print function for output
|
|
129
137
|
* @returns {boolean} True if message was handled
|
|
130
138
|
*/
|
|
131
|
-
function formatValidationResult(msg, prefix, timestamp) {
|
|
139
|
+
function formatValidationResult(msg, prefix, timestamp, print = console.log) {
|
|
132
140
|
const data = msg.content?.data || {};
|
|
133
141
|
const approved = data.approved === true || data.approved === 'true';
|
|
134
142
|
const status = approved ? chalk.bold.green('✓ APPROVED') : chalk.bold.red('✗ REJECTED');
|
|
135
143
|
|
|
136
|
-
|
|
144
|
+
print(`${prefix} ${chalk.gray(timestamp)} ${status}`);
|
|
137
145
|
|
|
138
146
|
// Show summary if present and not a template variable
|
|
139
147
|
if (msg.content?.text && !msg.content.text.includes('{{')) {
|
|
140
|
-
|
|
148
|
+
print(`${prefix} ${msg.content.text.substring(0, 100)}`);
|
|
141
149
|
}
|
|
142
150
|
|
|
143
151
|
// Show full JSON data structure
|
|
144
|
-
|
|
152
|
+
print(
|
|
145
153
|
`${prefix} ${chalk.dim(JSON.stringify(data, null, 2).split('\n').join(`\n${prefix} `))}`
|
|
146
154
|
);
|
|
147
155
|
|
|
@@ -153,16 +161,17 @@ function formatValidationResult(msg, prefix, timestamp) {
|
|
|
153
161
|
* @param {Object} msg - Message object
|
|
154
162
|
* @param {string} prefix - Formatted message prefix
|
|
155
163
|
* @param {string} timestamp - Formatted timestamp
|
|
164
|
+
* @param {Function} [print=console.log] - Print function for output
|
|
156
165
|
* @returns {boolean} True if message was handled
|
|
157
166
|
*/
|
|
158
|
-
function formatClusterComplete(msg, prefix, timestamp) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
167
|
+
function formatClusterComplete(msg, prefix, timestamp, print = console.log) {
|
|
168
|
+
print(''); // Blank line
|
|
169
|
+
print(chalk.bold.green(`${'═'.repeat(60)}`));
|
|
170
|
+
print(`${prefix} ${chalk.gray(timestamp)} ${chalk.bold.green('🎉 CLUSTER COMPLETE')}`);
|
|
162
171
|
if (msg.content?.data?.reason) {
|
|
163
|
-
|
|
172
|
+
print(`${prefix} ${chalk.green(msg.content.data.reason)}`);
|
|
164
173
|
}
|
|
165
|
-
|
|
174
|
+
print(chalk.bold.green(`${'═'.repeat(60)}`));
|
|
166
175
|
return true;
|
|
167
176
|
}
|
|
168
177
|
|
|
@@ -171,19 +180,47 @@ function formatClusterComplete(msg, prefix, timestamp) {
|
|
|
171
180
|
* @param {Object} msg - Message object
|
|
172
181
|
* @param {string} prefix - Formatted message prefix
|
|
173
182
|
* @param {string} timestamp - Formatted timestamp
|
|
183
|
+
* @param {Function} [print=console.log] - Print function for output
|
|
174
184
|
* @returns {boolean} True if message was handled
|
|
175
185
|
*/
|
|
176
|
-
function formatClusterFailed(msg, prefix, timestamp) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
186
|
+
function formatClusterFailed(msg, prefix, timestamp, print = console.log) {
|
|
187
|
+
print(''); // Blank line
|
|
188
|
+
print(chalk.bold.red(`${'═'.repeat(60)}`));
|
|
189
|
+
print(`${prefix} ${chalk.gray(timestamp)} ${chalk.bold.red('❌ CLUSTER FAILED')}`);
|
|
180
190
|
if (msg.content?.text) {
|
|
181
|
-
|
|
191
|
+
print(`${prefix} ${chalk.red(msg.content.text)}`);
|
|
182
192
|
}
|
|
183
193
|
if (msg.content?.data?.reason) {
|
|
184
|
-
|
|
194
|
+
print(`${prefix} ${chalk.red(msg.content.data.reason)}`);
|
|
185
195
|
}
|
|
186
|
-
|
|
196
|
+
print(chalk.bold.red(`${'═'.repeat(60)}`));
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Format PR_CREATED events
|
|
202
|
+
* @param {Object} msg - Message object
|
|
203
|
+
* @param {string} prefix - Formatted message prefix
|
|
204
|
+
* @param {string} timestamp - Formatted timestamp
|
|
205
|
+
* @param {Function} [print=console.log] - Print function for output
|
|
206
|
+
* @returns {boolean} True if message was handled
|
|
207
|
+
*/
|
|
208
|
+
function formatPrCreated(msg, prefix, timestamp, print = console.log) {
|
|
209
|
+
const prNumber = msg.content?.data?.pr_number || '';
|
|
210
|
+
const prUrl = msg.content?.data?.pr_url || '';
|
|
211
|
+
|
|
212
|
+
print(''); // Blank line before PR notification
|
|
213
|
+
print(chalk.bold.green(`${'─'.repeat(60)}`));
|
|
214
|
+
print(`${prefix} ${chalk.gray(timestamp)} ${chalk.bold.green('🎉 PULL REQUEST CREATED')}`);
|
|
215
|
+
|
|
216
|
+
if (prNumber) {
|
|
217
|
+
print(`${prefix} ${chalk.gray('PR:')} ${chalk.cyan(`#${prNumber}`)}`);
|
|
218
|
+
}
|
|
219
|
+
if (prUrl) {
|
|
220
|
+
print(`${prefix} ${chalk.gray('URL:')} ${chalk.blue(prUrl)}`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
print(chalk.bold.green(`${'─'.repeat(60)}`));
|
|
187
224
|
return true;
|
|
188
225
|
}
|
|
189
226
|
|
|
@@ -192,12 +229,13 @@ function formatClusterFailed(msg, prefix, timestamp) {
|
|
|
192
229
|
* @param {Object} msg - Message object
|
|
193
230
|
* @param {string} prefix - Formatted message prefix
|
|
194
231
|
* @param {string} timestamp - Formatted timestamp
|
|
232
|
+
* @param {Function} [print=console.log] - Print function for output
|
|
195
233
|
* @returns {boolean} True if message was handled
|
|
196
234
|
*/
|
|
197
|
-
function formatGenericMessage(msg, prefix, timestamp) {
|
|
198
|
-
|
|
235
|
+
function formatGenericMessage(msg, prefix, timestamp, print = console.log) {
|
|
236
|
+
print(`${prefix} ${chalk.gray(timestamp)} ${chalk.bold(msg.topic)}`);
|
|
199
237
|
if (msg.content?.text) {
|
|
200
|
-
|
|
238
|
+
print(`${prefix} ${msg.content.text}`);
|
|
201
239
|
}
|
|
202
240
|
return true;
|
|
203
241
|
}
|
|
@@ -208,6 +246,7 @@ module.exports = {
|
|
|
208
246
|
formatIssueOpened,
|
|
209
247
|
formatImplementationReady,
|
|
210
248
|
formatValidationResult,
|
|
249
|
+
formatPrCreated,
|
|
211
250
|
formatClusterComplete,
|
|
212
251
|
formatClusterFailed,
|
|
213
252
|
formatGenericMessage,
|