@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
package/cli/index.js
CHANGED
|
@@ -47,6 +47,8 @@ const {
|
|
|
47
47
|
DEFAULT_SETTINGS,
|
|
48
48
|
} = require('../lib/settings');
|
|
49
49
|
const { requirePreflight } = require('../src/preflight');
|
|
50
|
+
const { checkFirstRun } = require('./lib/first-run');
|
|
51
|
+
const { checkForUpdates } = require('./lib/update-checker');
|
|
50
52
|
const { StatusFooter } = require('../src/status-footer');
|
|
51
53
|
|
|
52
54
|
const program = new Command();
|
|
@@ -60,6 +62,39 @@ let activeClusterId = null;
|
|
|
60
62
|
/** @type {import('../src/orchestrator') | null} */
|
|
61
63
|
let orchestratorInstance = null;
|
|
62
64
|
|
|
65
|
+
// Track active status footer for safe output routing
|
|
66
|
+
// When set, all output routes through statusFooter.print() to prevent garbling
|
|
67
|
+
/** @type {import('../src/status-footer').StatusFooter | null} */
|
|
68
|
+
let activeStatusFooter = null;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Safe print - routes through statusFooter when active to prevent garbling
|
|
72
|
+
* @param {...any} args - Arguments to print (like console.log)
|
|
73
|
+
*/
|
|
74
|
+
function safePrint(...args) {
|
|
75
|
+
const text = args.map(arg =>
|
|
76
|
+
typeof arg === 'string' ? arg : String(arg)
|
|
77
|
+
).join(' ');
|
|
78
|
+
|
|
79
|
+
if (activeStatusFooter) {
|
|
80
|
+
activeStatusFooter.print(text + '\n');
|
|
81
|
+
} else {
|
|
82
|
+
console.log(...args);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Safe write - routes through statusFooter when active
|
|
88
|
+
* @param {string} text - Text to write
|
|
89
|
+
*/
|
|
90
|
+
function safeWrite(text) {
|
|
91
|
+
if (activeStatusFooter) {
|
|
92
|
+
activeStatusFooter.print(text);
|
|
93
|
+
} else {
|
|
94
|
+
process.stdout.write(text);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
63
98
|
/**
|
|
64
99
|
* Handle fatal errors: log, cleanup cluster state, exit
|
|
65
100
|
* @param {string} type - 'uncaughtException' or 'unhandledRejection'
|
|
@@ -342,11 +377,12 @@ program
|
|
|
342
377
|
.name('zeroshot')
|
|
343
378
|
.description('Multi-agent orchestration and task management for Claude')
|
|
344
379
|
.version(require('../package.json').version)
|
|
380
|
+
.option('-q, --quiet', 'Suppress prompts (first-run wizard, update checks)')
|
|
345
381
|
.addHelpText(
|
|
346
382
|
'after',
|
|
347
383
|
`
|
|
348
384
|
Examples:
|
|
349
|
-
${chalk.cyan('zeroshot
|
|
385
|
+
${chalk.cyan('zeroshot run 123 --ship')} Full automation: isolated + auto-merge PR
|
|
350
386
|
${chalk.cyan('zeroshot run 123')} Run cluster and attach to first agent
|
|
351
387
|
${chalk.cyan('zeroshot run 123 -d')} Run cluster in background (detached)
|
|
352
388
|
${chalk.cyan('zeroshot run "Implement feature X"')} Run cluster on plain text task
|
|
@@ -362,19 +398,20 @@ Examples:
|
|
|
362
398
|
${chalk.cyan('zeroshot status <id>')} Detailed status of task or cluster
|
|
363
399
|
${chalk.cyan('zeroshot finish <id>')} Convert cluster to completion task (creates and merges PR)
|
|
364
400
|
${chalk.cyan('zeroshot kill <id>')} Kill a running task or cluster
|
|
365
|
-
${chalk.cyan('zeroshot
|
|
366
|
-
${chalk.cyan('zeroshot
|
|
401
|
+
${chalk.cyan('zeroshot purge')} Kill all processes and delete all data (with confirmation)
|
|
402
|
+
${chalk.cyan('zeroshot purge -y')} Purge everything without confirmation
|
|
367
403
|
${chalk.cyan('zeroshot settings')} Show/manage zeroshot settings (default model, config, etc.)
|
|
368
404
|
${chalk.cyan('zeroshot settings set <key> <val>')} Set a setting (e.g., defaultModel haiku)
|
|
369
405
|
${chalk.cyan('zeroshot config list')} List available cluster configs
|
|
370
406
|
${chalk.cyan('zeroshot config show <name>')} Visualize a cluster config (agents, triggers, flow)
|
|
371
407
|
${chalk.cyan('zeroshot export <id>')} Export cluster conversation to file
|
|
372
408
|
|
|
373
|
-
|
|
374
|
-
${chalk.yellow('zeroshot
|
|
375
|
-
${chalk.yellow('zeroshot run')}
|
|
376
|
-
${chalk.yellow('zeroshot run
|
|
377
|
-
${chalk.yellow('zeroshot
|
|
409
|
+
Automation levels (cascading: --ship → --pr → --isolation):
|
|
410
|
+
${chalk.yellow('zeroshot run 123')} → Local run, no isolation
|
|
411
|
+
${chalk.yellow('zeroshot run 123 --isolation')} → Docker isolation, no PR
|
|
412
|
+
${chalk.yellow('zeroshot run 123 --pr')} → Isolation + PR (human reviews)
|
|
413
|
+
${chalk.yellow('zeroshot run 123 --ship')} → Isolation + PR + auto-merge (full automation)
|
|
414
|
+
${chalk.yellow('zeroshot task run')} → Single-agent background task (simpler, faster)
|
|
378
415
|
|
|
379
416
|
Shell completion:
|
|
380
417
|
${chalk.dim('zeroshot --completion >> ~/.bashrc && source ~/.bashrc')}
|
|
@@ -396,8 +433,8 @@ program
|
|
|
396
433
|
'--strict-schema',
|
|
397
434
|
'Enforce JSON schema via CLI (no live streaming). Default: live streaming with local validation'
|
|
398
435
|
)
|
|
399
|
-
.option('--pr', 'Create PR
|
|
400
|
-
.option('--
|
|
436
|
+
.option('--pr', 'Create PR for human review (auto-enables --isolation)')
|
|
437
|
+
.option('--ship', 'Full automation: isolation + PR + auto-merge')
|
|
401
438
|
.option('--workers <n>', 'Max sub-agents for worker to spawn in parallel', parseInt)
|
|
402
439
|
.option('-d, --detach', 'Run in background (default: attach to first agent)')
|
|
403
440
|
.addHelpText(
|
|
@@ -412,10 +449,15 @@ Input formats:
|
|
|
412
449
|
)
|
|
413
450
|
.action(async (inputArg, options) => {
|
|
414
451
|
try {
|
|
415
|
-
//
|
|
416
|
-
|
|
417
|
-
|
|
452
|
+
// Cascading flag implications: --ship → --pr → --isolation
|
|
453
|
+
// --ship = full automation (isolation + PR + auto-merge)
|
|
454
|
+
if (options.ship) {
|
|
418
455
|
options.pr = true;
|
|
456
|
+
options.isolation = true;
|
|
457
|
+
}
|
|
458
|
+
// --pr = PR for human review (auto-enables isolation)
|
|
459
|
+
if (options.pr) {
|
|
460
|
+
options.isolation = true;
|
|
419
461
|
}
|
|
420
462
|
|
|
421
463
|
// Auto-detect input type
|
|
@@ -443,18 +485,12 @@ Input formats:
|
|
|
443
485
|
// This gives users clear, actionable error messages upfront
|
|
444
486
|
const preflightOptions = {
|
|
445
487
|
requireGh: !!input.issue, // gh CLI required when fetching GitHub issues
|
|
446
|
-
requireDocker: options.isolation
|
|
488
|
+
requireDocker: options.isolation, // Docker required for isolation mode
|
|
447
489
|
quiet: process.env.CREW_DAEMON === '1', // Suppress success in daemon mode
|
|
448
490
|
};
|
|
449
491
|
requirePreflight(preflightOptions);
|
|
450
492
|
|
|
451
493
|
// === CLUSTER MODE ===
|
|
452
|
-
// Validate --pr requires --isolation
|
|
453
|
-
if (options.pr && !options.isolation) {
|
|
454
|
-
console.error(chalk.red('Error: --pr requires --isolation flag for safety'));
|
|
455
|
-
console.error(chalk.dim(' Usage: zeroshot run 123 --isolation --pr'));
|
|
456
|
-
process.exit(1);
|
|
457
|
-
}
|
|
458
494
|
|
|
459
495
|
const { generateName } = require('../src/name-generator');
|
|
460
496
|
|
|
@@ -614,6 +650,8 @@ Input formats:
|
|
|
614
650
|
statusFooter.setCluster(clusterId);
|
|
615
651
|
statusFooter.setClusterState('running');
|
|
616
652
|
statusFooter.setMessageBus(cluster.messageBus);
|
|
653
|
+
// Set module-level reference so safePrint/safeWrite route through footer
|
|
654
|
+
activeStatusFooter = statusFooter;
|
|
617
655
|
|
|
618
656
|
// Subscribe to AGENT_LIFECYCLE to track agent states and PIDs
|
|
619
657
|
const lifecycleUnsubscribe = cluster.messageBus.subscribeTopic('AGENT_LIFECYCLE', (msg) => {
|
|
@@ -698,14 +736,16 @@ Input formats:
|
|
|
698
736
|
if (status.state !== 'running') {
|
|
699
737
|
clearInterval(checkInterval);
|
|
700
738
|
clearInterval(flushInterval);
|
|
701
|
-
// Stop status footer
|
|
702
|
-
statusFooter.stop();
|
|
703
739
|
lifecycleUnsubscribe();
|
|
704
|
-
// Final flush
|
|
740
|
+
// Final flush BEFORE stopping status footer
|
|
741
|
+
// (statusFooter.stop() sends ANSI codes that can clear terminal area)
|
|
705
742
|
for (const sender of sendersWithOutput) {
|
|
706
743
|
const prefix = getColorForSender(sender)(`${sender.padEnd(15)} |`);
|
|
707
744
|
flushLineBuffer(prefix, sender);
|
|
708
745
|
}
|
|
746
|
+
// Stop status footer AFTER output is done
|
|
747
|
+
statusFooter.stop();
|
|
748
|
+
activeStatusFooter = null;
|
|
709
749
|
unsubscribe();
|
|
710
750
|
resolve();
|
|
711
751
|
}
|
|
@@ -714,6 +754,7 @@ Input formats:
|
|
|
714
754
|
clearInterval(checkInterval);
|
|
715
755
|
clearInterval(flushInterval);
|
|
716
756
|
statusFooter.stop();
|
|
757
|
+
activeStatusFooter = null;
|
|
717
758
|
lifecycleUnsubscribe();
|
|
718
759
|
unsubscribe();
|
|
719
760
|
resolve();
|
|
@@ -726,6 +767,7 @@ Input formats:
|
|
|
726
767
|
process.on('SIGINT', async () => {
|
|
727
768
|
// Stop status footer first to restore terminal
|
|
728
769
|
statusFooter.stop();
|
|
770
|
+
activeStatusFooter = null;
|
|
729
771
|
lifecycleUnsubscribe();
|
|
730
772
|
|
|
731
773
|
console.log(chalk.dim('\n\n--- Interrupted ---'));
|
|
@@ -750,74 +792,28 @@ Input formats:
|
|
|
750
792
|
}
|
|
751
793
|
|
|
752
794
|
// Daemon mode: cluster runs in background, stay alive via orchestrator's setInterval
|
|
795
|
+
// Add cleanup handlers for daemon mode to ensure container cleanup on process exit
|
|
796
|
+
// CRITICAL: Without this, containers become orphaned when daemon process dies
|
|
797
|
+
if (process.env.CREW_DAEMON) {
|
|
798
|
+
const cleanup = async (signal) => {
|
|
799
|
+
console.log(`\n[DAEMON] Received ${signal}, cleaning up cluster ${clusterId}...`);
|
|
800
|
+
try {
|
|
801
|
+
await orchestrator.stop(clusterId);
|
|
802
|
+
console.log(`[DAEMON] Cluster ${clusterId} stopped.`);
|
|
803
|
+
} catch (e) {
|
|
804
|
+
console.error(`[DAEMON] Cleanup error: ${e.message}`);
|
|
805
|
+
}
|
|
806
|
+
process.exit(0);
|
|
807
|
+
};
|
|
808
|
+
process.on('SIGTERM', () => cleanup('SIGTERM'));
|
|
809
|
+
process.on('SIGINT', () => cleanup('SIGINT'));
|
|
810
|
+
}
|
|
753
811
|
} catch (error) {
|
|
754
812
|
console.error('Error:', error.message);
|
|
755
813
|
process.exit(1);
|
|
756
814
|
}
|
|
757
815
|
});
|
|
758
816
|
|
|
759
|
-
// Auto command - full automation (isolation + PR)
|
|
760
|
-
program
|
|
761
|
-
.command('auto <input>')
|
|
762
|
-
.description('Full automation: isolated + auto-merge PR (shorthand for run --isolation --pr)')
|
|
763
|
-
.option('--config <file>', 'Path to cluster config JSON (default: conductor-bootstrap)')
|
|
764
|
-
.option('-m, --model <model>', 'Model for all agents: opus, sonnet, haiku (default: from config)')
|
|
765
|
-
.option(
|
|
766
|
-
'--isolation-image <image>',
|
|
767
|
-
'Docker image for isolation (default: zeroshot-cluster-base)'
|
|
768
|
-
)
|
|
769
|
-
.option(
|
|
770
|
-
'--strict-schema',
|
|
771
|
-
'Enforce JSON schema via CLI (no live streaming). Default: live streaming with local validation'
|
|
772
|
-
)
|
|
773
|
-
.option('--workers <n>', 'Max sub-agents for worker to spawn in parallel', parseInt)
|
|
774
|
-
.option('-d, --detach', 'Run in background (default: attach to first agent)')
|
|
775
|
-
.addHelpText(
|
|
776
|
-
'after',
|
|
777
|
-
`
|
|
778
|
-
Input formats:
|
|
779
|
-
123 GitHub issue number (uses current repo)
|
|
780
|
-
org/repo#123 GitHub issue with explicit repo
|
|
781
|
-
https://github.com/.../issues/1 Full GitHub issue URL
|
|
782
|
-
"Implement feature X" Plain text task description
|
|
783
|
-
|
|
784
|
-
Examples:
|
|
785
|
-
${chalk.cyan('zeroshot auto 123')} Auto-resolve issue (isolated + PR)
|
|
786
|
-
${chalk.cyan('zeroshot auto 123 -d')} Same, but detached/background
|
|
787
|
-
`
|
|
788
|
-
)
|
|
789
|
-
.action((inputArg, options) => {
|
|
790
|
-
// Auto command is shorthand for: zeroshot run <input> --isolation --pr [options]
|
|
791
|
-
// Re-invoke CLI with the correct flags to avoid Commander.js internal API issues
|
|
792
|
-
const { spawn } = require('child_process');
|
|
793
|
-
|
|
794
|
-
const args = ['run', inputArg, '--isolation', '--pr'];
|
|
795
|
-
|
|
796
|
-
// Forward other options
|
|
797
|
-
if (options.config) args.push('--config', options.config);
|
|
798
|
-
if (options.model) args.push('--model', options.model);
|
|
799
|
-
if (options.isolationImage) args.push('--isolation-image', options.isolationImage);
|
|
800
|
-
if (options.strictSchema) args.push('--strict-schema');
|
|
801
|
-
if (options.workers) args.push('--workers', String(options.workers));
|
|
802
|
-
if (options.detach) args.push('--detach');
|
|
803
|
-
|
|
804
|
-
// Spawn zeroshot run with inherited stdio
|
|
805
|
-
const proc = spawn(process.execPath, [process.argv[1], ...args], {
|
|
806
|
-
stdio: 'inherit',
|
|
807
|
-
cwd: process.cwd(),
|
|
808
|
-
env: process.env,
|
|
809
|
-
});
|
|
810
|
-
|
|
811
|
-
proc.on('close', (code) => {
|
|
812
|
-
process.exit(code || 0);
|
|
813
|
-
});
|
|
814
|
-
|
|
815
|
-
proc.on('error', (err) => {
|
|
816
|
-
console.error(chalk.red(`Error: ${err.message}`));
|
|
817
|
-
process.exit(1);
|
|
818
|
-
});
|
|
819
|
-
});
|
|
820
|
-
|
|
821
817
|
// === TASK COMMANDS ===
|
|
822
818
|
// Task run - single-agent background task
|
|
823
819
|
const taskCmd = program.command('task').description('Single-agent task management');
|
|
@@ -901,48 +897,78 @@ program
|
|
|
901
897
|
.description('List all tasks and clusters')
|
|
902
898
|
.option('-s, --status <status>', 'Filter tasks by status (running, completed, failed)')
|
|
903
899
|
.option('-n, --limit <n>', 'Limit number of results', parseInt)
|
|
900
|
+
.option('--json', 'Output as JSON')
|
|
904
901
|
.action(async (options) => {
|
|
905
902
|
try {
|
|
906
903
|
// Get clusters
|
|
907
904
|
const clusters = getOrchestrator().listClusters();
|
|
905
|
+
const orchestrator = getOrchestrator();
|
|
906
|
+
|
|
907
|
+
// Enrich clusters with token data
|
|
908
|
+
const enrichedClusters = clusters.map((cluster) => {
|
|
909
|
+
let totalTokens = 0;
|
|
910
|
+
let totalCostUsd = 0;
|
|
911
|
+
try {
|
|
912
|
+
const clusterObj = orchestrator.getCluster(cluster.id);
|
|
913
|
+
if (clusterObj?.messageBus) {
|
|
914
|
+
const tokensByRole = clusterObj.messageBus.getTokensByRole(cluster.id);
|
|
915
|
+
if (tokensByRole?._total?.count > 0) {
|
|
916
|
+
const total = tokensByRole._total;
|
|
917
|
+
totalTokens = (total.inputTokens || 0) + (total.outputTokens || 0);
|
|
918
|
+
totalCostUsd = total.totalCostUsd || 0;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
} catch {
|
|
922
|
+
/* Token tracking not available */
|
|
923
|
+
}
|
|
924
|
+
return {
|
|
925
|
+
...cluster,
|
|
926
|
+
totalTokens,
|
|
927
|
+
totalCostUsd,
|
|
928
|
+
};
|
|
929
|
+
});
|
|
908
930
|
|
|
909
931
|
// Get tasks (dynamic import)
|
|
910
|
-
const { listTasks } = await import('../task-lib/commands/list.js');
|
|
932
|
+
const { listTasks, getTasksData } = await import('../task-lib/commands/list.js');
|
|
911
933
|
|
|
912
|
-
//
|
|
913
|
-
|
|
934
|
+
// JSON output mode
|
|
935
|
+
if (options.json) {
|
|
936
|
+
// Get tasks data if available
|
|
937
|
+
let tasks = [];
|
|
938
|
+
try {
|
|
939
|
+
if (typeof getTasksData === 'function') {
|
|
940
|
+
tasks = await getTasksData(options);
|
|
941
|
+
}
|
|
942
|
+
} catch {
|
|
943
|
+
/* Tasks not available */
|
|
944
|
+
}
|
|
914
945
|
|
|
946
|
+
console.log(
|
|
947
|
+
JSON.stringify(
|
|
948
|
+
{
|
|
949
|
+
clusters: enrichedClusters,
|
|
950
|
+
tasks,
|
|
951
|
+
},
|
|
952
|
+
null,
|
|
953
|
+
2
|
|
954
|
+
)
|
|
955
|
+
);
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// Human-readable output (default)
|
|
915
960
|
// Print clusters
|
|
916
|
-
if (
|
|
961
|
+
if (enrichedClusters.length > 0) {
|
|
917
962
|
console.log(chalk.bold('\n=== Clusters ==='));
|
|
918
963
|
console.log(
|
|
919
964
|
`${'ID'.padEnd(25)} ${'State'.padEnd(12)} ${'Agents'.padEnd(8)} ${'Tokens'.padEnd(12)} ${'Cost'.padEnd(8)} Created`
|
|
920
965
|
);
|
|
921
966
|
console.log('-'.repeat(100));
|
|
922
967
|
|
|
923
|
-
const
|
|
924
|
-
for (const cluster of clusters) {
|
|
968
|
+
for (const cluster of enrichedClusters) {
|
|
925
969
|
const created = new Date(cluster.createdAt).toLocaleString();
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
let tokenDisplay = '-';
|
|
929
|
-
let costDisplay = '-';
|
|
930
|
-
try {
|
|
931
|
-
const clusterObj = orchestrator.getCluster(cluster.id);
|
|
932
|
-
if (clusterObj?.messageBus) {
|
|
933
|
-
const tokensByRole = clusterObj.messageBus.getTokensByRole(cluster.id);
|
|
934
|
-
if (tokensByRole?._total?.count > 0) {
|
|
935
|
-
const total = tokensByRole._total;
|
|
936
|
-
const totalTokens = (total.inputTokens || 0) + (total.outputTokens || 0);
|
|
937
|
-
tokenDisplay = totalTokens.toLocaleString();
|
|
938
|
-
if (total.totalCostUsd > 0) {
|
|
939
|
-
costDisplay = '$' + total.totalCostUsd.toFixed(3);
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
} catch {
|
|
944
|
-
/* Token tracking not available */
|
|
945
|
-
}
|
|
970
|
+
const tokenDisplay = cluster.totalTokens > 0 ? cluster.totalTokens.toLocaleString() : '-';
|
|
971
|
+
const costDisplay = cluster.totalCostUsd > 0 ? '$' + cluster.totalCostUsd.toFixed(3) : '-';
|
|
946
972
|
|
|
947
973
|
// Highlight zombie clusters in red
|
|
948
974
|
const stateDisplay =
|
|
@@ -974,14 +1000,19 @@ program
|
|
|
974
1000
|
program
|
|
975
1001
|
.command('status <id>')
|
|
976
1002
|
.description('Get detailed status of a task or cluster')
|
|
977
|
-
.
|
|
1003
|
+
.option('--json', 'Output as JSON')
|
|
1004
|
+
.action(async (id, options) => {
|
|
978
1005
|
try {
|
|
979
1006
|
const { detectIdType } = require('../lib/id-detector');
|
|
980
1007
|
const type = detectIdType(id);
|
|
981
1008
|
|
|
982
1009
|
if (!type) {
|
|
983
|
-
|
|
984
|
-
|
|
1010
|
+
if (options.json) {
|
|
1011
|
+
console.log(JSON.stringify({ error: 'ID not found', id }, null, 2));
|
|
1012
|
+
} else {
|
|
1013
|
+
console.error(`ID not found: ${id}`);
|
|
1014
|
+
console.error('Not found in tasks or clusters');
|
|
1015
|
+
}
|
|
985
1016
|
process.exit(1);
|
|
986
1017
|
}
|
|
987
1018
|
|
|
@@ -989,6 +1020,35 @@ program
|
|
|
989
1020
|
// Show cluster status
|
|
990
1021
|
const status = getOrchestrator().getStatus(id);
|
|
991
1022
|
|
|
1023
|
+
// Get token usage
|
|
1024
|
+
let tokensByRole = null;
|
|
1025
|
+
try {
|
|
1026
|
+
const cluster = getOrchestrator().getCluster(id);
|
|
1027
|
+
if (cluster?.messageBus) {
|
|
1028
|
+
tokensByRole = cluster.messageBus.getTokensByRole(id);
|
|
1029
|
+
}
|
|
1030
|
+
} catch {
|
|
1031
|
+
/* Token tracking not available */
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// JSON output mode
|
|
1035
|
+
if (options.json) {
|
|
1036
|
+
console.log(
|
|
1037
|
+
JSON.stringify(
|
|
1038
|
+
{
|
|
1039
|
+
type: 'cluster',
|
|
1040
|
+
...status,
|
|
1041
|
+
createdAtISO: new Date(status.createdAt).toISOString(),
|
|
1042
|
+
tokensByRole,
|
|
1043
|
+
},
|
|
1044
|
+
null,
|
|
1045
|
+
2
|
|
1046
|
+
)
|
|
1047
|
+
);
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
// Human-readable output
|
|
992
1052
|
console.log(`\nCluster: ${status.id}`);
|
|
993
1053
|
if (status.isZombie) {
|
|
994
1054
|
console.log(
|
|
@@ -1011,20 +1071,14 @@ program
|
|
|
1011
1071
|
console.log(`Messages: ${status.messageCount}`);
|
|
1012
1072
|
|
|
1013
1073
|
// Show token usage if available
|
|
1014
|
-
|
|
1015
|
-
const
|
|
1016
|
-
if (
|
|
1017
|
-
|
|
1018
|
-
const
|
|
1019
|
-
|
|
1020
|
-
console.log('');
|
|
1021
|
-
for (const line of tokenLines) {
|
|
1022
|
-
console.log(line);
|
|
1023
|
-
}
|
|
1074
|
+
if (tokensByRole) {
|
|
1075
|
+
const tokenLines = formatTokenUsage(tokensByRole);
|
|
1076
|
+
if (tokenLines) {
|
|
1077
|
+
console.log('');
|
|
1078
|
+
for (const line of tokenLines) {
|
|
1079
|
+
console.log(line);
|
|
1024
1080
|
}
|
|
1025
1081
|
}
|
|
1026
|
-
} catch {
|
|
1027
|
-
/* Token tracking not available */
|
|
1028
1082
|
}
|
|
1029
1083
|
|
|
1030
1084
|
console.log(`\nAgents:`);
|
|
@@ -1049,11 +1103,30 @@ program
|
|
|
1049
1103
|
console.log('');
|
|
1050
1104
|
} else {
|
|
1051
1105
|
// Show task status
|
|
1052
|
-
const { showStatus } = await import('../task-lib/commands/status.js');
|
|
1106
|
+
const { showStatus, getStatusData } = await import('../task-lib/commands/status.js');
|
|
1107
|
+
|
|
1108
|
+
if (options.json) {
|
|
1109
|
+
// Try to get JSON data if available
|
|
1110
|
+
let taskData = null;
|
|
1111
|
+
try {
|
|
1112
|
+
if (typeof getStatusData === 'function') {
|
|
1113
|
+
taskData = await getStatusData(id);
|
|
1114
|
+
}
|
|
1115
|
+
} catch {
|
|
1116
|
+
/* Not available */
|
|
1117
|
+
}
|
|
1118
|
+
console.log(JSON.stringify({ type: 'task', id, ...taskData }, null, 2));
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1053
1122
|
await showStatus(id);
|
|
1054
1123
|
}
|
|
1055
1124
|
} catch (error) {
|
|
1056
|
-
|
|
1125
|
+
if (options.json) {
|
|
1126
|
+
console.log(JSON.stringify({ error: error.message }, null, 2));
|
|
1127
|
+
} else {
|
|
1128
|
+
console.error('Error getting status:', error.message);
|
|
1129
|
+
}
|
|
1057
1130
|
process.exit(1);
|
|
1058
1131
|
}
|
|
1059
1132
|
});
|
|
@@ -2405,10 +2478,10 @@ program
|
|
|
2405
2478
|
}
|
|
2406
2479
|
});
|
|
2407
2480
|
|
|
2408
|
-
//
|
|
2481
|
+
// Purge all runs (clusters + tasks) - NUCLEAR option
|
|
2409
2482
|
program
|
|
2410
|
-
.command('
|
|
2411
|
-
.description('Kill all running processes and delete all data')
|
|
2483
|
+
.command('purge')
|
|
2484
|
+
.description('NUCLEAR: Kill all running processes and delete all data')
|
|
2412
2485
|
.option('-y, --yes', 'Skip confirmation')
|
|
2413
2486
|
.action(async (options) => {
|
|
2414
2487
|
try {
|
|
@@ -2549,7 +2622,7 @@ program
|
|
|
2549
2622
|
await cleanTasks({ all: true });
|
|
2550
2623
|
}
|
|
2551
2624
|
|
|
2552
|
-
console.log(chalk.bold.green('\nAll runs
|
|
2625
|
+
console.log(chalk.bold.green('\nAll runs purged.'));
|
|
2553
2626
|
} catch (error) {
|
|
2554
2627
|
console.error('Error clearing runs:', error.message);
|
|
2555
2628
|
process.exit(1);
|
|
@@ -2950,6 +3023,210 @@ configCmd
|
|
|
2950
3023
|
}
|
|
2951
3024
|
});
|
|
2952
3025
|
|
|
3026
|
+
// Agent library commands
|
|
3027
|
+
const agentsCmd = program.command('agents').description('View available agent definitions');
|
|
3028
|
+
|
|
3029
|
+
agentsCmd
|
|
3030
|
+
.command('list')
|
|
3031
|
+
.alias('ls')
|
|
3032
|
+
.description('List available agent definitions')
|
|
3033
|
+
.option('--verbose', 'Show full agent details')
|
|
3034
|
+
.option('--json', 'Output as JSON')
|
|
3035
|
+
.action((options) => {
|
|
3036
|
+
try {
|
|
3037
|
+
const agentsDir = path.join(PACKAGE_ROOT, 'src', 'agents');
|
|
3038
|
+
|
|
3039
|
+
// Check if agents directory exists
|
|
3040
|
+
if (!fs.existsSync(agentsDir)) {
|
|
3041
|
+
if (options.json) {
|
|
3042
|
+
console.log(JSON.stringify({ agents: [], error: null }, null, 2));
|
|
3043
|
+
} else {
|
|
3044
|
+
console.log(chalk.dim('No agents directory found.'));
|
|
3045
|
+
}
|
|
3046
|
+
return;
|
|
3047
|
+
}
|
|
3048
|
+
|
|
3049
|
+
const files = fs.readdirSync(agentsDir).filter((f) => f.endsWith('.json'));
|
|
3050
|
+
|
|
3051
|
+
if (files.length === 0) {
|
|
3052
|
+
if (options.json) {
|
|
3053
|
+
console.log(JSON.stringify({ agents: [], error: null }, null, 2));
|
|
3054
|
+
} else {
|
|
3055
|
+
console.log(chalk.dim('No agent definitions found in src/agents/'));
|
|
3056
|
+
}
|
|
3057
|
+
return;
|
|
3058
|
+
}
|
|
3059
|
+
|
|
3060
|
+
// Parse all agent files
|
|
3061
|
+
const agents = [];
|
|
3062
|
+
for (const file of files) {
|
|
3063
|
+
try {
|
|
3064
|
+
const agentPath = path.join(agentsDir, file);
|
|
3065
|
+
const agent = JSON.parse(fs.readFileSync(agentPath, 'utf8'));
|
|
3066
|
+
agents.push({
|
|
3067
|
+
file: file.replace('.json', ''),
|
|
3068
|
+
id: agent.id || file.replace('.json', ''),
|
|
3069
|
+
role: agent.role || 'unspecified',
|
|
3070
|
+
model: agent.model || 'default',
|
|
3071
|
+
triggers: agent.triggers?.length || 0,
|
|
3072
|
+
prompt: agent.prompt || null,
|
|
3073
|
+
output: agent.output || null,
|
|
3074
|
+
});
|
|
3075
|
+
} catch (err) {
|
|
3076
|
+
// Skip invalid JSON files
|
|
3077
|
+
console.error(chalk.yellow(`Warning: Could not parse ${file}: ${err.message}`));
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3081
|
+
// JSON output
|
|
3082
|
+
if (options.json) {
|
|
3083
|
+
console.log(JSON.stringify({ agents, error: null }, null, 2));
|
|
3084
|
+
return;
|
|
3085
|
+
}
|
|
3086
|
+
|
|
3087
|
+
// Human-readable output
|
|
3088
|
+
console.log(chalk.bold('\nAvailable agent definitions:\n'));
|
|
3089
|
+
|
|
3090
|
+
for (const agent of agents) {
|
|
3091
|
+
console.log(
|
|
3092
|
+
` ${chalk.cyan(agent.id.padEnd(25))} ${chalk.dim('role:')} ${agent.role.padEnd(20)} ${chalk.dim('model:')} ${agent.model}`
|
|
3093
|
+
);
|
|
3094
|
+
|
|
3095
|
+
if (options.verbose) {
|
|
3096
|
+
console.log(chalk.dim(` Triggers: ${agent.triggers}`));
|
|
3097
|
+
if (agent.output) {
|
|
3098
|
+
console.log(chalk.dim(` Output topic: ${agent.output.topic || 'none'}`));
|
|
3099
|
+
}
|
|
3100
|
+
if (agent.prompt) {
|
|
3101
|
+
const promptPreview = agent.prompt.substring(0, 100).replace(/\n/g, ' ');
|
|
3102
|
+
console.log(chalk.dim(` Prompt: ${promptPreview}...`));
|
|
3103
|
+
}
|
|
3104
|
+
console.log('');
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
|
|
3108
|
+
if (!options.verbose) {
|
|
3109
|
+
console.log('');
|
|
3110
|
+
console.log(chalk.dim(' Use --verbose for full details'));
|
|
3111
|
+
}
|
|
3112
|
+
console.log('');
|
|
3113
|
+
} catch (error) {
|
|
3114
|
+
if (options.json) {
|
|
3115
|
+
console.log(JSON.stringify({ agents: [], error: error.message }, null, 2));
|
|
3116
|
+
} else {
|
|
3117
|
+
console.error(chalk.red(`Error listing agents: ${error.message}`));
|
|
3118
|
+
}
|
|
3119
|
+
process.exit(1);
|
|
3120
|
+
}
|
|
3121
|
+
});
|
|
3122
|
+
|
|
3123
|
+
agentsCmd
|
|
3124
|
+
.command('show <name>')
|
|
3125
|
+
.description('Show detailed agent definition')
|
|
3126
|
+
.option('--json', 'Output as JSON')
|
|
3127
|
+
.action((name, options) => {
|
|
3128
|
+
try {
|
|
3129
|
+
const agentsDir = path.join(PACKAGE_ROOT, 'src', 'agents');
|
|
3130
|
+
|
|
3131
|
+
// Support both with and without .json extension
|
|
3132
|
+
const agentName = name.endsWith('.json') ? name : `${name}.json`;
|
|
3133
|
+
const agentPath = path.join(agentsDir, agentName);
|
|
3134
|
+
|
|
3135
|
+
if (!fs.existsSync(agentPath)) {
|
|
3136
|
+
// Try with -agent.json suffix
|
|
3137
|
+
const altPath = path.join(agentsDir, `${name}-agent.json`);
|
|
3138
|
+
if (fs.existsSync(altPath)) {
|
|
3139
|
+
const agent = JSON.parse(fs.readFileSync(altPath, 'utf8'));
|
|
3140
|
+
outputAgent(agent, options);
|
|
3141
|
+
return;
|
|
3142
|
+
}
|
|
3143
|
+
|
|
3144
|
+
if (options.json) {
|
|
3145
|
+
console.log(JSON.stringify({ error: `Agent not found: ${name}` }, null, 2));
|
|
3146
|
+
} else {
|
|
3147
|
+
console.error(chalk.red(`Agent not found: ${name}`));
|
|
3148
|
+
console.log(chalk.dim('\nAvailable agents:'));
|
|
3149
|
+
const files = fs.readdirSync(agentsDir).filter((f) => f.endsWith('.json'));
|
|
3150
|
+
files.forEach((f) => console.log(chalk.dim(` - ${f.replace('.json', '')}`)));
|
|
3151
|
+
}
|
|
3152
|
+
process.exit(1);
|
|
3153
|
+
}
|
|
3154
|
+
|
|
3155
|
+
const agent = JSON.parse(fs.readFileSync(agentPath, 'utf8'));
|
|
3156
|
+
outputAgent(agent, options);
|
|
3157
|
+
} catch (error) {
|
|
3158
|
+
if (options.json) {
|
|
3159
|
+
console.log(JSON.stringify({ error: error.message }, null, 2));
|
|
3160
|
+
} else {
|
|
3161
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
|
3162
|
+
}
|
|
3163
|
+
process.exit(1);
|
|
3164
|
+
}
|
|
3165
|
+
});
|
|
3166
|
+
|
|
3167
|
+
function outputAgent(agent, options) {
|
|
3168
|
+
if (options.json) {
|
|
3169
|
+
console.log(JSON.stringify(agent, null, 2));
|
|
3170
|
+
return;
|
|
3171
|
+
}
|
|
3172
|
+
|
|
3173
|
+
// Human-readable output
|
|
3174
|
+
console.log('');
|
|
3175
|
+
console.log(chalk.bold.cyan('═'.repeat(80)));
|
|
3176
|
+
console.log(chalk.bold.cyan(` Agent: ${agent.id}`));
|
|
3177
|
+
console.log(chalk.bold.cyan('═'.repeat(80)));
|
|
3178
|
+
console.log('');
|
|
3179
|
+
|
|
3180
|
+
// Basic info
|
|
3181
|
+
console.log(chalk.bold('Configuration:'));
|
|
3182
|
+
console.log(` ${chalk.dim('ID:')} ${agent.id}`);
|
|
3183
|
+
console.log(` ${chalk.dim('Role:')} ${agent.role || 'unspecified'}`);
|
|
3184
|
+
console.log(` ${chalk.dim('Model:')} ${agent.model || 'default'}`);
|
|
3185
|
+
console.log('');
|
|
3186
|
+
|
|
3187
|
+
// Triggers
|
|
3188
|
+
if (agent.triggers && agent.triggers.length > 0) {
|
|
3189
|
+
console.log(chalk.bold('Triggers:'));
|
|
3190
|
+
for (const trigger of agent.triggers) {
|
|
3191
|
+
console.log(` ${chalk.yellow('•')} Topic: ${chalk.cyan(trigger.topic)}`);
|
|
3192
|
+
if (trigger.action) {
|
|
3193
|
+
console.log(` Action: ${trigger.action}`);
|
|
3194
|
+
}
|
|
3195
|
+
if (trigger.logic?.script) {
|
|
3196
|
+
const scriptPreview = trigger.logic.script.substring(0, 80).replace(/\n/g, ' ');
|
|
3197
|
+
console.log(chalk.dim(` Logic: ${scriptPreview}...`));
|
|
3198
|
+
}
|
|
3199
|
+
}
|
|
3200
|
+
console.log('');
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3203
|
+
// Output
|
|
3204
|
+
if (agent.output) {
|
|
3205
|
+
console.log(chalk.bold('Output:'));
|
|
3206
|
+
console.log(` ${chalk.dim('Topic:')} ${agent.output.topic || 'none'}`);
|
|
3207
|
+
if (agent.output.publishAfter) {
|
|
3208
|
+
console.log(` ${chalk.dim('Publish after:')} ${agent.output.publishAfter}`);
|
|
3209
|
+
}
|
|
3210
|
+
console.log('');
|
|
3211
|
+
}
|
|
3212
|
+
|
|
3213
|
+
// Prompt
|
|
3214
|
+
if (agent.prompt) {
|
|
3215
|
+
console.log(chalk.bold('Prompt:'));
|
|
3216
|
+
console.log(chalk.dim('─'.repeat(76)));
|
|
3217
|
+
// Show first 500 chars of prompt
|
|
3218
|
+
const promptLines = agent.prompt.substring(0, 500).split('\n');
|
|
3219
|
+
for (const line of promptLines) {
|
|
3220
|
+
console.log(` ${line}`);
|
|
3221
|
+
}
|
|
3222
|
+
if (agent.prompt.length > 500) {
|
|
3223
|
+
console.log(chalk.dim(` ... (${agent.prompt.length - 500} more characters)`));
|
|
3224
|
+
}
|
|
3225
|
+
console.log(chalk.dim('─'.repeat(76)));
|
|
3226
|
+
console.log('');
|
|
3227
|
+
}
|
|
3228
|
+
}
|
|
3229
|
+
|
|
2953
3230
|
// Helper function to keep the process alive for follow mode
|
|
2954
3231
|
function keepProcessAlive(cleanupFn) {
|
|
2955
3232
|
// Prevent Node.js from exiting by keeping the event loop active
|
|
@@ -3570,32 +3847,40 @@ function accumulateText(prefix, sender, text) {
|
|
|
3570
3847
|
buf.textBuffer = buf.textBuffer.slice(newlineIdx + 1);
|
|
3571
3848
|
|
|
3572
3849
|
// Word wrap and print the complete line
|
|
3850
|
+
// CRITICAL: Batch all output into single safeWrite() to prevent interleaving with render()
|
|
3573
3851
|
const wrappedLines = wordWrap(completeLine, contentWidth);
|
|
3852
|
+
let outputBuffer = '';
|
|
3853
|
+
|
|
3574
3854
|
for (let i = 0; i < wrappedLines.length; i++) {
|
|
3575
3855
|
const wrappedLine = wrappedLines[i];
|
|
3576
3856
|
|
|
3577
3857
|
// Print prefix (real or continuation)
|
|
3578
3858
|
if (buf.needsPrefix) {
|
|
3579
|
-
|
|
3859
|
+
outputBuffer += `${prefix} `;
|
|
3580
3860
|
buf.needsPrefix = false;
|
|
3581
3861
|
} else if (i > 0) {
|
|
3582
|
-
|
|
3862
|
+
outputBuffer += `${continuationPrefix}`;
|
|
3583
3863
|
}
|
|
3584
3864
|
|
|
3585
3865
|
if (wrappedLine.trim()) {
|
|
3586
|
-
|
|
3866
|
+
outputBuffer += formatInlineMarkdown(wrappedLine);
|
|
3587
3867
|
}
|
|
3588
3868
|
|
|
3589
3869
|
// Newline after each wrapped segment
|
|
3590
3870
|
if (i < wrappedLines.length - 1) {
|
|
3591
|
-
|
|
3871
|
+
outputBuffer += '\n';
|
|
3592
3872
|
}
|
|
3593
3873
|
}
|
|
3594
3874
|
|
|
3595
3875
|
// Complete the line
|
|
3596
|
-
|
|
3876
|
+
outputBuffer += '\n';
|
|
3597
3877
|
buf.needsPrefix = true;
|
|
3598
3878
|
buf.pendingNewline = false;
|
|
3879
|
+
|
|
3880
|
+
// Single atomic write prevents interleaving
|
|
3881
|
+
if (outputBuffer) {
|
|
3882
|
+
safeWrite(outputBuffer);
|
|
3883
|
+
}
|
|
3599
3884
|
}
|
|
3600
3885
|
|
|
3601
3886
|
// Mark that we have pending text (no newline yet)
|
|
@@ -3620,35 +3905,45 @@ function accumulateThinking(prefix, sender, text) {
|
|
|
3620
3905
|
const newlineIdx = remaining.indexOf('\n');
|
|
3621
3906
|
const rawLine = newlineIdx === -1 ? remaining : remaining.slice(0, newlineIdx);
|
|
3622
3907
|
|
|
3908
|
+
// CRITICAL: Batch all output into single safeWrite() to prevent interleaving with render()
|
|
3623
3909
|
const wrappedLines = wordWrap(rawLine, contentWidth);
|
|
3910
|
+
let outputBuffer = '';
|
|
3624
3911
|
|
|
3625
3912
|
for (let i = 0; i < wrappedLines.length; i++) {
|
|
3626
3913
|
const wrappedLine = wrappedLines[i];
|
|
3627
3914
|
|
|
3628
3915
|
if (buf.thinkingNeedsPrefix) {
|
|
3629
|
-
|
|
3916
|
+
outputBuffer += `${prefix} ${chalk.dim.italic('💭 ')}`;
|
|
3630
3917
|
buf.thinkingNeedsPrefix = false;
|
|
3631
3918
|
} else if (i > 0) {
|
|
3632
|
-
|
|
3919
|
+
outputBuffer += `${continuationPrefix}`;
|
|
3633
3920
|
}
|
|
3634
3921
|
|
|
3635
3922
|
if (wrappedLine.trim()) {
|
|
3636
|
-
|
|
3923
|
+
outputBuffer += chalk.dim.italic(wrappedLine);
|
|
3637
3924
|
}
|
|
3638
3925
|
|
|
3639
3926
|
if (i < wrappedLines.length - 1) {
|
|
3640
|
-
|
|
3927
|
+
outputBuffer += '\n';
|
|
3641
3928
|
}
|
|
3642
3929
|
}
|
|
3643
3930
|
|
|
3644
3931
|
if (newlineIdx === -1) {
|
|
3645
3932
|
buf.thinkingPendingNewline = true;
|
|
3933
|
+
// Single atomic write
|
|
3934
|
+
if (outputBuffer) {
|
|
3935
|
+
safeWrite(outputBuffer);
|
|
3936
|
+
}
|
|
3646
3937
|
break;
|
|
3647
3938
|
} else {
|
|
3648
|
-
|
|
3939
|
+
outputBuffer += '\n';
|
|
3649
3940
|
buf.thinkingNeedsPrefix = true;
|
|
3650
3941
|
buf.thinkingPendingNewline = false;
|
|
3651
3942
|
remaining = remaining.slice(newlineIdx + 1);
|
|
3943
|
+
// Single atomic write
|
|
3944
|
+
if (outputBuffer) {
|
|
3945
|
+
safeWrite(outputBuffer);
|
|
3946
|
+
}
|
|
3652
3947
|
}
|
|
3653
3948
|
}
|
|
3654
3949
|
}
|
|
@@ -3658,7 +3953,10 @@ function flushLineBuffer(prefix, sender) {
|
|
|
3658
3953
|
const buf = lineBuffers.get(sender);
|
|
3659
3954
|
if (!buf) return;
|
|
3660
3955
|
|
|
3661
|
-
// CRITICAL:
|
|
3956
|
+
// CRITICAL: Batch all output into single safeWrite() to prevent interleaving with render()
|
|
3957
|
+
let outputBuffer = '';
|
|
3958
|
+
|
|
3959
|
+
// Flush any remaining text in textBuffer (text without trailing newline)
|
|
3662
3960
|
if (buf.textBuffer && buf.textBuffer.length > 0) {
|
|
3663
3961
|
// Calculate widths for word wrapping (same as accumulateText)
|
|
3664
3962
|
const prefixLen = chalk.reset(prefix).replace(/\\x1b\[[0-9;]*m/g, '').length + 1;
|
|
@@ -3671,18 +3969,18 @@ function flushLineBuffer(prefix, sender) {
|
|
|
3671
3969
|
const wrappedLine = wrappedLines[i];
|
|
3672
3970
|
|
|
3673
3971
|
if (buf.needsPrefix) {
|
|
3674
|
-
|
|
3972
|
+
outputBuffer += `${prefix} `;
|
|
3675
3973
|
buf.needsPrefix = false;
|
|
3676
3974
|
} else if (i > 0) {
|
|
3677
|
-
|
|
3975
|
+
outputBuffer += `${continuationPrefix}`;
|
|
3678
3976
|
}
|
|
3679
3977
|
|
|
3680
3978
|
if (wrappedLine.trim()) {
|
|
3681
|
-
|
|
3979
|
+
outputBuffer += formatInlineMarkdown(wrappedLine);
|
|
3682
3980
|
}
|
|
3683
3981
|
|
|
3684
3982
|
if (i < wrappedLines.length - 1) {
|
|
3685
|
-
|
|
3983
|
+
outputBuffer += '\n';
|
|
3686
3984
|
}
|
|
3687
3985
|
}
|
|
3688
3986
|
|
|
@@ -3692,15 +3990,20 @@ function flushLineBuffer(prefix, sender) {
|
|
|
3692
3990
|
}
|
|
3693
3991
|
|
|
3694
3992
|
if (buf.pendingNewline) {
|
|
3695
|
-
|
|
3993
|
+
outputBuffer += '\n';
|
|
3696
3994
|
buf.needsPrefix = true;
|
|
3697
3995
|
buf.pendingNewline = false;
|
|
3698
3996
|
}
|
|
3699
3997
|
if (buf.thinkingPendingNewline) {
|
|
3700
|
-
|
|
3998
|
+
outputBuffer += '\n';
|
|
3701
3999
|
buf.thinkingNeedsPrefix = true;
|
|
3702
4000
|
buf.thinkingPendingNewline = false;
|
|
3703
4001
|
}
|
|
4002
|
+
|
|
4003
|
+
// Single atomic write prevents interleaving
|
|
4004
|
+
if (outputBuffer) {
|
|
4005
|
+
safeWrite(outputBuffer);
|
|
4006
|
+
}
|
|
3704
4007
|
}
|
|
3705
4008
|
|
|
3706
4009
|
// Lines to filter out (noise, metadata, errors)
|
|
@@ -3783,17 +4086,17 @@ function printMessage(msg, showClusterId = false, watchMode = false, isActive =
|
|
|
3783
4086
|
}
|
|
3784
4087
|
|
|
3785
4088
|
if (msg.topic === 'PR_CREATED') {
|
|
3786
|
-
formatPrCreated(msg, prefix, timestamp);
|
|
4089
|
+
formatPrCreated(msg, prefix, timestamp, safePrint);
|
|
3787
4090
|
return;
|
|
3788
4091
|
}
|
|
3789
4092
|
|
|
3790
4093
|
if (msg.topic === 'CLUSTER_COMPLETE') {
|
|
3791
|
-
formatClusterComplete(msg, prefix, timestamp);
|
|
4094
|
+
formatClusterComplete(msg, prefix, timestamp, safePrint);
|
|
3792
4095
|
return;
|
|
3793
4096
|
}
|
|
3794
4097
|
|
|
3795
4098
|
if (msg.topic === 'CLUSTER_FAILED') {
|
|
3796
|
-
formatClusterFailed(msg, prefix, timestamp);
|
|
4099
|
+
formatClusterFailed(msg, prefix, timestamp, safePrint);
|
|
3797
4100
|
return;
|
|
3798
4101
|
}
|
|
3799
4102
|
|
|
@@ -3819,7 +4122,7 @@ function printMessage(msg, showClusterId = false, watchMode = false, isActive =
|
|
|
3819
4122
|
if (event.text) {
|
|
3820
4123
|
accumulateThinking(prefix, msg.sender, event.text);
|
|
3821
4124
|
} else if (event.type === 'thinking_start') {
|
|
3822
|
-
|
|
4125
|
+
safePrint(`${prefix} ${chalk.dim.italic('💭 thinking...')}`);
|
|
3823
4126
|
}
|
|
3824
4127
|
break;
|
|
3825
4128
|
|
|
@@ -3833,7 +4136,7 @@ function printMessage(msg, showClusterId = false, watchMode = false, isActive =
|
|
|
3833
4136
|
flushLineBuffer(prefix, msg.sender);
|
|
3834
4137
|
const icon = getToolIcon(event.toolName);
|
|
3835
4138
|
const toolDesc = formatToolCall(event.toolName, event.input);
|
|
3836
|
-
|
|
4139
|
+
safePrint(`${prefix} ${icon} ${chalk.cyan(event.toolName)} ${chalk.dim(toolDesc)}`);
|
|
3837
4140
|
// Store tool call info for matching with result
|
|
3838
4141
|
currentToolCall.set(msg.sender, {
|
|
3839
4142
|
toolName: event.toolName,
|
|
@@ -3855,7 +4158,7 @@ function printMessage(msg, showClusterId = false, watchMode = false, isActive =
|
|
|
3855
4158
|
toolCall?.toolName,
|
|
3856
4159
|
toolCall?.input
|
|
3857
4160
|
);
|
|
3858
|
-
|
|
4161
|
+
safePrint(`${prefix} ${status} ${resultDesc}`);
|
|
3859
4162
|
// Clear stored tool call after result
|
|
3860
4163
|
currentToolCall.delete(msg.sender);
|
|
3861
4164
|
break;
|
|
@@ -3865,7 +4168,7 @@ function printMessage(msg, showClusterId = false, watchMode = false, isActive =
|
|
|
3865
4168
|
flushLineBuffer(prefix, msg.sender);
|
|
3866
4169
|
// Final result - only show errors (success text already streamed)
|
|
3867
4170
|
if (!event.success) {
|
|
3868
|
-
|
|
4171
|
+
safePrint(`${prefix} ${chalk.bold.red('✗ Error:')} ${event.error || 'Task failed'}`);
|
|
3869
4172
|
}
|
|
3870
4173
|
break;
|
|
3871
4174
|
|
|
@@ -3906,7 +4209,7 @@ function printMessage(msg, showClusterId = false, watchMode = false, isActive =
|
|
|
3906
4209
|
// Skip duplicate content
|
|
3907
4210
|
if (isDuplicate(trimmed)) continue;
|
|
3908
4211
|
|
|
3909
|
-
|
|
4212
|
+
safePrint(`${prefix} ${line}`);
|
|
3910
4213
|
}
|
|
3911
4214
|
}
|
|
3912
4215
|
return;
|
|
@@ -3914,22 +4217,22 @@ function printMessage(msg, showClusterId = false, watchMode = false, isActive =
|
|
|
3914
4217
|
|
|
3915
4218
|
// AGENT_ERROR: Show errors with visual prominence
|
|
3916
4219
|
if (msg.topic === 'AGENT_ERROR') {
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
4220
|
+
safePrint(''); // Blank line before error
|
|
4221
|
+
safePrint(chalk.bold.red(`${'─'.repeat(60)}`));
|
|
4222
|
+
safePrint(`${prefix} ${chalk.gray(timestamp)} ${chalk.bold.red('🔴 AGENT ERROR')}`);
|
|
3920
4223
|
if (msg.content?.text) {
|
|
3921
|
-
|
|
4224
|
+
safePrint(`${prefix} ${chalk.red(msg.content.text)}`);
|
|
3922
4225
|
}
|
|
3923
4226
|
if (msg.content?.data?.stack) {
|
|
3924
4227
|
// Show first 5 lines of stack trace
|
|
3925
4228
|
const stackLines = msg.content.data.stack.split('\n').slice(0, 5);
|
|
3926
4229
|
for (const line of stackLines) {
|
|
3927
4230
|
if (line.trim()) {
|
|
3928
|
-
|
|
4231
|
+
safePrint(`${prefix} ${chalk.dim(line)}`);
|
|
3929
4232
|
}
|
|
3930
4233
|
}
|
|
3931
4234
|
}
|
|
3932
|
-
|
|
4235
|
+
safePrint(chalk.bold.red(`${'─'.repeat(60)}`));
|
|
3933
4236
|
return;
|
|
3934
4237
|
}
|
|
3935
4238
|
|
|
@@ -3941,29 +4244,29 @@ function printMessage(msg, showClusterId = false, watchMode = false, isActive =
|
|
|
3941
4244
|
}
|
|
3942
4245
|
shownNewTaskForCluster.add(msg.cluster_id);
|
|
3943
4246
|
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
4247
|
+
safePrint(''); // Blank line before new task
|
|
4248
|
+
safePrint(chalk.bold.blue(`${'─'.repeat(60)}`));
|
|
4249
|
+
safePrint(`${prefix} ${chalk.gray(timestamp)} ${chalk.bold.blue('📋 NEW TASK')}`);
|
|
3947
4250
|
if (msg.content?.text) {
|
|
3948
4251
|
// Show task description (first 3 lines max)
|
|
3949
4252
|
const lines = msg.content.text.split('\n').slice(0, 3);
|
|
3950
4253
|
for (const line of lines) {
|
|
3951
4254
|
if (line.trim() && line.trim() !== '# Manual Input') {
|
|
3952
|
-
|
|
4255
|
+
safePrint(`${prefix} ${chalk.white(line)}`);
|
|
3953
4256
|
}
|
|
3954
4257
|
}
|
|
3955
4258
|
}
|
|
3956
|
-
|
|
4259
|
+
safePrint(chalk.bold.blue(`${'─'.repeat(60)}`));
|
|
3957
4260
|
return;
|
|
3958
4261
|
}
|
|
3959
4262
|
|
|
3960
4263
|
// IMPLEMENTATION_READY: milestone marker
|
|
3961
4264
|
if (msg.topic === 'IMPLEMENTATION_READY') {
|
|
3962
|
-
|
|
4265
|
+
safePrint(
|
|
3963
4266
|
`${prefix} ${chalk.gray(timestamp)} ${chalk.bold.yellow('✅ IMPLEMENTATION READY')}`
|
|
3964
4267
|
);
|
|
3965
4268
|
if (msg.content?.data?.commit) {
|
|
3966
|
-
|
|
4269
|
+
safePrint(
|
|
3967
4270
|
`${prefix} ${chalk.gray('Commit:')} ${chalk.cyan(msg.content.data.commit.substring(0, 8))}`
|
|
3968
4271
|
);
|
|
3969
4272
|
}
|
|
@@ -3976,33 +4279,33 @@ function printMessage(msg, showClusterId = false, watchMode = false, isActive =
|
|
|
3976
4279
|
const approved = data.approved === true || data.approved === 'true';
|
|
3977
4280
|
const status = approved ? chalk.bold.green('✓ APPROVED') : chalk.bold.red('✗ REJECTED');
|
|
3978
4281
|
|
|
3979
|
-
|
|
4282
|
+
safePrint(`${prefix} ${chalk.gray(timestamp)} ${status}`);
|
|
3980
4283
|
|
|
3981
4284
|
// Show summary if present and not a template variable
|
|
3982
4285
|
if (msg.content?.text && !msg.content.text.includes('{{')) {
|
|
3983
|
-
|
|
4286
|
+
safePrint(`${prefix} ${msg.content.text.substring(0, 100)}`);
|
|
3984
4287
|
}
|
|
3985
4288
|
|
|
3986
4289
|
// Show full JSON data structure
|
|
3987
|
-
|
|
4290
|
+
safePrint(
|
|
3988
4291
|
`${prefix} ${chalk.dim(JSON.stringify(data, null, 2).split('\n').join(`\n${prefix} `))}`
|
|
3989
4292
|
);
|
|
3990
4293
|
|
|
3991
4294
|
// Show errors/issues if any
|
|
3992
4295
|
if (data.errors && Array.isArray(data.errors) && data.errors.length > 0) {
|
|
3993
|
-
|
|
4296
|
+
safePrint(`${prefix} ${chalk.red('Errors:')}`);
|
|
3994
4297
|
data.errors.forEach((err) => {
|
|
3995
4298
|
if (err && typeof err === 'string') {
|
|
3996
|
-
|
|
4299
|
+
safePrint(`${prefix} - ${err}`);
|
|
3997
4300
|
}
|
|
3998
4301
|
});
|
|
3999
4302
|
}
|
|
4000
4303
|
|
|
4001
4304
|
if (data.issues && Array.isArray(data.issues) && data.issues.length > 0) {
|
|
4002
|
-
|
|
4305
|
+
safePrint(`${prefix} ${chalk.yellow('Issues:')}`);
|
|
4003
4306
|
data.issues.forEach((issue) => {
|
|
4004
4307
|
if (issue && typeof issue === 'string') {
|
|
4005
|
-
|
|
4308
|
+
safePrint(`${prefix} - ${issue}`);
|
|
4006
4309
|
}
|
|
4007
4310
|
});
|
|
4008
4311
|
}
|
|
@@ -4010,26 +4313,42 @@ function printMessage(msg, showClusterId = false, watchMode = false, isActive =
|
|
|
4010
4313
|
}
|
|
4011
4314
|
|
|
4012
4315
|
// Fallback: generic message display for unknown topics
|
|
4013
|
-
formatGenericMessage(msg, prefix, timestamp);
|
|
4316
|
+
formatGenericMessage(msg, prefix, timestamp, safePrint);
|
|
4014
4317
|
}
|
|
4015
4318
|
|
|
4016
|
-
//
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
//
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4319
|
+
// Main async entry point
|
|
4320
|
+
async function main() {
|
|
4321
|
+
// First-run setup wizard (blocks on first use only)
|
|
4322
|
+
const isQuiet = process.argv.includes('-q') || process.argv.includes('--quiet');
|
|
4323
|
+
await checkFirstRun({ quiet: isQuiet });
|
|
4324
|
+
|
|
4325
|
+
// Check for updates (non-blocking if offline)
|
|
4326
|
+
await checkForUpdates({ quiet: isQuiet });
|
|
4327
|
+
|
|
4328
|
+
// Default command handling: if first arg doesn't match a known command, treat it as 'run'
|
|
4329
|
+
// This allows `zeroshot "task"` to work the same as `zeroshot run "task"`
|
|
4330
|
+
const args = process.argv.slice(2);
|
|
4331
|
+
if (args.length > 0) {
|
|
4332
|
+
const firstArg = args[0];
|
|
4333
|
+
|
|
4334
|
+
// Skip if it's a flag/option (starts with -)
|
|
4335
|
+
// Skip if it's --help or --version (these are handled by commander)
|
|
4336
|
+
if (!firstArg.startsWith('-')) {
|
|
4337
|
+
// Get all registered command names
|
|
4338
|
+
const commandNames = program.commands.map((cmd) => cmd.name());
|
|
4339
|
+
|
|
4340
|
+
// If first arg is not a known command, prepend 'run'
|
|
4341
|
+
if (!commandNames.includes(firstArg)) {
|
|
4342
|
+
process.argv.splice(2, 0, 'run');
|
|
4343
|
+
}
|
|
4031
4344
|
}
|
|
4032
4345
|
}
|
|
4346
|
+
|
|
4347
|
+
program.parse();
|
|
4033
4348
|
}
|
|
4034
4349
|
|
|
4035
|
-
|
|
4350
|
+
// Run main
|
|
4351
|
+
main().catch((err) => {
|
|
4352
|
+
console.error('Fatal error:', err.message);
|
|
4353
|
+
process.exit(1);
|
|
4354
|
+
});
|