@covibes/zeroshot 5.2.0 → 5.3.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 +178 -186
- package/README.md +199 -248
- package/cli/commands/providers.js +150 -0
- package/cli/index.js +214 -58
- package/cli/lib/first-run.js +40 -3
- package/cluster-templates/base-templates/debug-workflow.json +24 -78
- package/cluster-templates/base-templates/full-workflow.json +44 -145
- package/cluster-templates/base-templates/single-worker.json +23 -15
- package/cluster-templates/base-templates/worker-validator.json +47 -34
- package/cluster-templates/conductor-bootstrap.json +7 -5
- package/lib/docker-config.js +6 -1
- package/lib/provider-detection.js +59 -0
- package/lib/provider-names.js +56 -0
- package/lib/settings.js +191 -6
- package/lib/stream-json-parser.js +4 -238
- package/package.json +21 -5
- package/scripts/validate-templates.js +100 -0
- package/src/agent/agent-config.js +37 -13
- package/src/agent/agent-context-builder.js +64 -2
- package/src/agent/agent-hook-executor.js +82 -9
- package/src/agent/agent-lifecycle.js +53 -14
- package/src/agent/agent-task-executor.js +196 -194
- package/src/agent/output-extraction.js +200 -0
- package/src/agent/output-reformatter.js +175 -0
- package/src/agent/schema-utils.js +111 -0
- package/src/agent-wrapper.js +102 -30
- package/src/agents/git-pusher-agent.json +2 -2
- package/src/claude-task-runner.js +80 -30
- package/src/config-router.js +13 -13
- package/src/config-validator.js +231 -10
- package/src/github.js +36 -0
- package/src/isolation-manager.js +243 -154
- package/src/ledger.js +28 -6
- package/src/orchestrator.js +391 -96
- package/src/preflight.js +85 -82
- package/src/providers/anthropic/cli-builder.js +45 -0
- package/src/providers/anthropic/index.js +134 -0
- package/src/providers/anthropic/models.js +23 -0
- package/src/providers/anthropic/output-parser.js +159 -0
- package/src/providers/base-provider.js +181 -0
- package/src/providers/capabilities.js +51 -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 +92 -0
- package/src/providers/index.js +75 -0
- package/src/providers/openai/cli-builder.js +122 -0
- package/src/providers/openai/index.js +135 -0
- package/src/providers/openai/models.js +21 -0
- package/src/providers/openai/output-parser.js +129 -0
- package/src/sub-cluster-wrapper.js +18 -3
- package/src/task-runner.js +8 -6
- package/src/tui/layout.js +20 -3
- package/task-lib/attachable-watcher.js +80 -78
- package/task-lib/claude-recovery.js +119 -0
- package/task-lib/commands/list.js +1 -1
- package/task-lib/commands/resume.js +3 -2
- package/task-lib/commands/run.js +12 -3
- package/task-lib/runner.js +59 -38
- package/task-lib/scheduler.js +2 -2
- package/task-lib/store.js +43 -30
- package/task-lib/watcher.js +81 -62
package/cli/index.js
CHANGED
|
@@ -21,7 +21,6 @@ const os = require('os');
|
|
|
21
21
|
const chalk = require('chalk');
|
|
22
22
|
const Orchestrator = require('../src/orchestrator');
|
|
23
23
|
const { setupCompletion } = require('../lib/completion');
|
|
24
|
-
const { parseChunk } = require('../lib/stream-json-parser');
|
|
25
24
|
const { formatWatchMode } = require('./message-formatters-watch');
|
|
26
25
|
const {
|
|
27
26
|
formatAgentLifecycle,
|
|
@@ -46,8 +45,11 @@ const {
|
|
|
46
45
|
coerceValue,
|
|
47
46
|
DEFAULT_SETTINGS,
|
|
48
47
|
} = require('../lib/settings');
|
|
48
|
+
const { normalizeProviderName } = require('../lib/provider-names');
|
|
49
|
+
const { getProvider, parseProviderChunk } = require('../src/providers');
|
|
49
50
|
const { MOUNT_PRESETS, resolveEnvs } = require('../lib/docker-config');
|
|
50
51
|
const { requirePreflight } = require('../src/preflight');
|
|
52
|
+
const { providersCommand, setDefaultCommand, setupCommand } = require('./commands/providers');
|
|
51
53
|
// Setup wizard removed - use: zeroshot settings set <key> <value>
|
|
52
54
|
const { checkForUpdates } = require('./lib/update-checker');
|
|
53
55
|
const { StatusFooter, AGENT_STATE, ACTIVE_STATES } = require('../src/status-footer');
|
|
@@ -73,9 +75,7 @@ let activeStatusFooter = null;
|
|
|
73
75
|
* @param {...any} args - Arguments to print (like console.log)
|
|
74
76
|
*/
|
|
75
77
|
function safePrint(...args) {
|
|
76
|
-
const text = args.map(arg =>
|
|
77
|
-
typeof arg === 'string' ? arg : String(arg)
|
|
78
|
-
).join(' ');
|
|
78
|
+
const text = args.map((arg) => (typeof arg === 'string' ? arg : String(arg))).join(' ');
|
|
79
79
|
|
|
80
80
|
if (activeStatusFooter) {
|
|
81
81
|
activeStatusFooter.print(text + '\n');
|
|
@@ -191,14 +191,23 @@ function parseMountSpecs(specs) {
|
|
|
191
191
|
// Lazy-loaded orchestrator (quiet by default) - created on first use
|
|
192
192
|
/** @type {import('../src/orchestrator') | null} */
|
|
193
193
|
let _orchestrator = null;
|
|
194
|
+
/** @type {Promise<import('../src/orchestrator')> | null} */
|
|
195
|
+
let _orchestratorPromise = null;
|
|
194
196
|
/**
|
|
195
|
-
* @returns {import('../src/orchestrator')}
|
|
197
|
+
* @returns {Promise<import('../src/orchestrator')>}
|
|
196
198
|
*/
|
|
197
199
|
function getOrchestrator() {
|
|
198
|
-
if (
|
|
199
|
-
|
|
200
|
+
if (_orchestrator) {
|
|
201
|
+
return Promise.resolve(_orchestrator);
|
|
200
202
|
}
|
|
201
|
-
|
|
203
|
+
// Use a promise to prevent multiple concurrent initializations
|
|
204
|
+
if (!_orchestratorPromise) {
|
|
205
|
+
_orchestratorPromise = Orchestrator.create({ quiet: true }).then((orch) => {
|
|
206
|
+
_orchestrator = orch;
|
|
207
|
+
return orch;
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
return _orchestratorPromise;
|
|
202
211
|
}
|
|
203
212
|
|
|
204
213
|
/**
|
|
@@ -394,7 +403,7 @@ if (shouldShowBanner) {
|
|
|
394
403
|
|
|
395
404
|
program
|
|
396
405
|
.name('zeroshot')
|
|
397
|
-
.description('Multi-agent orchestration and task management for Claude')
|
|
406
|
+
.description('Multi-agent orchestration and task management for Claude, Codex, and Gemini')
|
|
398
407
|
.version(require('../package.json').version)
|
|
399
408
|
.option('-q, --quiet', 'Suppress prompts (first-run wizard, update checks)')
|
|
400
409
|
.addHelpText(
|
|
@@ -402,9 +411,10 @@ program
|
|
|
402
411
|
`
|
|
403
412
|
Examples:
|
|
404
413
|
${chalk.cyan('zeroshot run 123 --ship')} Full automation: isolated + auto-merge PR
|
|
405
|
-
${chalk.cyan('zeroshot run 123')} Run cluster
|
|
406
|
-
${chalk.cyan('zeroshot run
|
|
407
|
-
${chalk.cyan('zeroshot run "Implement feature X"')} Run cluster
|
|
414
|
+
${chalk.cyan('zeroshot run 123')} Run cluster from GitHub issue
|
|
415
|
+
${chalk.cyan('zeroshot run feature.md')} Run cluster from markdown file
|
|
416
|
+
${chalk.cyan('zeroshot run "Implement feature X"')} Run cluster from plain text
|
|
417
|
+
${chalk.cyan('zeroshot run 123 -d')} Run in background (detached)
|
|
408
418
|
${chalk.cyan('zeroshot run 123 --docker')} Run in Docker container (safe for e2e tests)
|
|
409
419
|
${chalk.cyan('zeroshot task run "Fix the bug"')} Run single-agent background task
|
|
410
420
|
${chalk.cyan('zeroshot list')} List all tasks and clusters
|
|
@@ -421,6 +431,7 @@ Examples:
|
|
|
421
431
|
${chalk.cyan('zeroshot purge -y')} Purge everything without confirmation
|
|
422
432
|
${chalk.cyan('zeroshot settings')} Show/manage zeroshot settings (maxModel, config, etc.)
|
|
423
433
|
${chalk.cyan('zeroshot settings set <key> <val>')} Set a setting (e.g., maxModel haiku)
|
|
434
|
+
${chalk.cyan('zeroshot providers')} Show provider status and defaults
|
|
424
435
|
${chalk.cyan('zeroshot config list')} List available cluster configs
|
|
425
436
|
${chalk.cyan('zeroshot config show <name>')} Visualize a cluster config (agents, triggers, flow)
|
|
426
437
|
${chalk.cyan('zeroshot export <id>')} Export cluster conversation to file
|
|
@@ -441,7 +452,7 @@ Shell completion:
|
|
|
441
452
|
// Run command - CLUSTER with auto-detection
|
|
442
453
|
program
|
|
443
454
|
.command('run <input>')
|
|
444
|
-
.description('Start a multi-agent cluster (
|
|
455
|
+
.description('Start a multi-agent cluster (GitHub issue, markdown file, or plain text)')
|
|
445
456
|
.option('--config <file>', 'Path to cluster config JSON (default: conductor-bootstrap)')
|
|
446
457
|
.option('--docker', 'Run cluster inside Docker container (full isolation)')
|
|
447
458
|
.option('--worktree', 'Use git worktree for isolation (lightweight, no Docker required)')
|
|
@@ -453,13 +464,24 @@ program
|
|
|
453
464
|
'--strict-schema',
|
|
454
465
|
'Enforce JSON schema via CLI (no live streaming). Default: live streaming with local validation'
|
|
455
466
|
)
|
|
456
|
-
.option(
|
|
457
|
-
|
|
467
|
+
.option(
|
|
468
|
+
'--pr',
|
|
469
|
+
'Create PR for human review (uses worktree isolation by default, use --docker for Docker)'
|
|
470
|
+
)
|
|
471
|
+
.option(
|
|
472
|
+
'--ship',
|
|
473
|
+
'Full automation: worktree isolation + PR + auto-merge (use --docker for Docker)'
|
|
474
|
+
)
|
|
458
475
|
.option('--workers <n>', 'Max sub-agents for worker to spawn in parallel', parseInt)
|
|
476
|
+
.option('--provider <provider>', 'Override all agents to use a provider (claude, codex, gemini)')
|
|
477
|
+
.option('--model <model>', 'Override all agent models (provider-specific model id)')
|
|
459
478
|
.option('-d, --detach', 'Run in background (default: attach to first agent)')
|
|
460
479
|
.option('--mount <spec...>', 'Add Docker mount (host:container[:ro]). Repeatable.')
|
|
461
480
|
.option('--no-mounts', 'Disable all Docker credential mounts')
|
|
462
|
-
.option(
|
|
481
|
+
.option(
|
|
482
|
+
'--container-home <path>',
|
|
483
|
+
'Container home directory for $HOME expansion (default: /root)'
|
|
484
|
+
)
|
|
463
485
|
.addHelpText(
|
|
464
486
|
'after',
|
|
465
487
|
`
|
|
@@ -506,6 +528,10 @@ Input formats:
|
|
|
506
528
|
else if (inputArg.match(/^[\w-]+\/[\w-]+#\d+$/)) {
|
|
507
529
|
input.issue = inputArg;
|
|
508
530
|
}
|
|
531
|
+
// Check if it's a markdown file (.md or .markdown)
|
|
532
|
+
else if (/\.(md|markdown)$/i.test(inputArg)) {
|
|
533
|
+
input.file = inputArg;
|
|
534
|
+
}
|
|
509
535
|
// Otherwise, treat as plain text
|
|
510
536
|
else {
|
|
511
537
|
input.text = inputArg;
|
|
@@ -514,11 +540,16 @@ Input formats:
|
|
|
514
540
|
// === PREFLIGHT CHECKS ===
|
|
515
541
|
// Validate all dependencies BEFORE starting anything
|
|
516
542
|
// This gives users clear, actionable error messages upfront
|
|
543
|
+
const settings = loadSettings();
|
|
544
|
+
const providerOverride = normalizeProviderName(
|
|
545
|
+
options.provider || process.env.ZEROSHOT_PROVIDER || settings.defaultProvider
|
|
546
|
+
);
|
|
517
547
|
const preflightOptions = {
|
|
518
548
|
requireGh: !!input.issue, // gh CLI required when fetching GitHub issues
|
|
519
549
|
requireDocker: options.docker, // Docker required for --docker mode
|
|
520
550
|
requireGit: options.worktree, // Git required for worktree isolation
|
|
521
551
|
quiet: process.env.ZEROSHOT_DAEMON === '1', // Suppress success in daemon mode
|
|
552
|
+
provider: providerOverride,
|
|
522
553
|
};
|
|
523
554
|
requirePreflight(preflightOptions);
|
|
524
555
|
|
|
@@ -570,6 +601,8 @@ Input formats:
|
|
|
570
601
|
ZEROSHOT_PR: options.pr ? '1' : '',
|
|
571
602
|
ZEROSHOT_WORKTREE: options.worktree ? '1' : '',
|
|
572
603
|
ZEROSHOT_WORKERS: options.workers?.toString() || '',
|
|
604
|
+
ZEROSHOT_MODEL: options.model || '',
|
|
605
|
+
ZEROSHOT_PROVIDER: options.provider || '',
|
|
573
606
|
ZEROSHOT_CWD: targetCwd, // Explicit CWD for orchestrator
|
|
574
607
|
},
|
|
575
608
|
});
|
|
@@ -580,8 +613,6 @@ Input formats:
|
|
|
580
613
|
}
|
|
581
614
|
|
|
582
615
|
// === FOREGROUND MODE (default) or DAEMON CHILD ===
|
|
583
|
-
// Load user settings
|
|
584
|
-
const settings = loadSettings();
|
|
585
616
|
|
|
586
617
|
// Use cluster ID from env (daemon mode) or generate new one (foreground mode)
|
|
587
618
|
// IMPORTANT: Set env var so orchestrator picks it up
|
|
@@ -610,9 +641,24 @@ Input formats:
|
|
|
610
641
|
}
|
|
611
642
|
|
|
612
643
|
// Create orchestrator with clusterId override for foreground mode
|
|
613
|
-
const orchestrator = getOrchestrator();
|
|
644
|
+
const orchestrator = await getOrchestrator();
|
|
614
645
|
config = orchestrator.loadConfig(configPath);
|
|
615
646
|
|
|
647
|
+
if (!config.defaultProvider) {
|
|
648
|
+
config.defaultProvider = settings.defaultProvider || 'claude';
|
|
649
|
+
}
|
|
650
|
+
config.defaultProvider = normalizeProviderName(config.defaultProvider) || 'claude';
|
|
651
|
+
|
|
652
|
+
if (providerOverride) {
|
|
653
|
+
const provider = getProvider(providerOverride);
|
|
654
|
+
const providerSettings = settings.providerSettings?.[providerOverride] || {};
|
|
655
|
+
config.forceProvider = providerOverride;
|
|
656
|
+
config.defaultProvider = providerOverride;
|
|
657
|
+
config.forceLevel = providerSettings.defaultLevel || provider.getDefaultLevel();
|
|
658
|
+
config.defaultLevel = config.forceLevel;
|
|
659
|
+
console.log(chalk.dim(`Provider override: ${providerOverride} (all agents)`));
|
|
660
|
+
}
|
|
661
|
+
|
|
616
662
|
// Track for global error handler cleanup
|
|
617
663
|
activeClusterId = clusterId;
|
|
618
664
|
orchestratorInstance = orchestrator;
|
|
@@ -639,19 +685,59 @@ Input formats:
|
|
|
639
685
|
}
|
|
640
686
|
}
|
|
641
687
|
|
|
688
|
+
// Apply model override to all agents (CLI > env)
|
|
689
|
+
const modelOverride = options.model || process.env.ZEROSHOT_MODEL;
|
|
690
|
+
if (modelOverride) {
|
|
691
|
+
const providerName = normalizeProviderName(
|
|
692
|
+
providerOverride || config.defaultProvider || settings.defaultProvider || 'claude'
|
|
693
|
+
);
|
|
694
|
+
const provider = getProvider(providerName);
|
|
695
|
+
const catalog = provider.getModelCatalog();
|
|
696
|
+
|
|
697
|
+
if (catalog && !catalog[modelOverride]) {
|
|
698
|
+
console.warn(
|
|
699
|
+
chalk.yellow(
|
|
700
|
+
`Warning: model override "${modelOverride}" is not in the ${providerName} catalog`
|
|
701
|
+
)
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
if (providerName === 'claude' && ['opus', 'sonnet', 'haiku'].includes(modelOverride)) {
|
|
706
|
+
const { validateModelAgainstMax } = require('../lib/settings');
|
|
707
|
+
try {
|
|
708
|
+
validateModelAgainstMax(modelOverride, settings.maxModel);
|
|
709
|
+
} catch (err) {
|
|
710
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
711
|
+
process.exit(1);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// Override all agent models
|
|
716
|
+
for (const agent of config.agents) {
|
|
717
|
+
agent.model = modelOverride;
|
|
718
|
+
if (agent.modelRules) {
|
|
719
|
+
delete agent.modelRules;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
console.log(chalk.dim(`Model override: ${modelOverride} (all agents)`));
|
|
723
|
+
}
|
|
724
|
+
|
|
642
725
|
// Build start options (CLI flags > env vars > settings)
|
|
643
726
|
// In foreground mode, use CLI options directly; in daemon mode, use env vars
|
|
644
727
|
// CRITICAL: cwd must be passed to orchestrator for agent CWD propagation
|
|
645
728
|
const targetCwd = process.env.ZEROSHOT_CWD || detectGitRepoRoot();
|
|
646
729
|
const startOptions = {
|
|
730
|
+
clusterId,
|
|
647
731
|
cwd: targetCwd, // Target working directory for agents
|
|
648
|
-
isolation:
|
|
649
|
-
options.docker || process.env.ZEROSHOT_DOCKER === '1' || settings.defaultDocker,
|
|
732
|
+
isolation: options.docker || process.env.ZEROSHOT_DOCKER === '1' || settings.defaultDocker,
|
|
650
733
|
isolationImage: options.dockerImage || process.env.ZEROSHOT_DOCKER_IMAGE || undefined,
|
|
651
734
|
worktree: options.worktree || process.env.ZEROSHOT_WORKTREE === '1',
|
|
652
735
|
autoPr: options.pr || process.env.ZEROSHOT_PR === '1',
|
|
653
736
|
autoMerge: process.env.ZEROSHOT_MERGE === '1',
|
|
654
737
|
autoPush: process.env.ZEROSHOT_PUSH === '1',
|
|
738
|
+
// Model override (for dynamically added agents)
|
|
739
|
+
modelOverride: modelOverride || undefined,
|
|
740
|
+
providerOverride: providerOverride || undefined,
|
|
655
741
|
// Docker mount options
|
|
656
742
|
noMounts: options.noMounts || false,
|
|
657
743
|
mounts: options.mount ? parseMountSpecs(options.mount) : undefined,
|
|
@@ -850,8 +936,12 @@ taskCmd
|
|
|
850
936
|
.command('run <prompt>')
|
|
851
937
|
.description('Run a single-agent background task')
|
|
852
938
|
.option('-C, --cwd <path>', 'Working directory for task')
|
|
853
|
-
.option('
|
|
854
|
-
.option('
|
|
939
|
+
.option('--provider <provider>', 'Provider to use (claude, codex, gemini)')
|
|
940
|
+
.option('--model <model>', 'Model id override for the provider')
|
|
941
|
+
.option('--model-level <level>', 'Model level override (level1, level2, level3)')
|
|
942
|
+
.option('--reasoning-effort <effort>', 'Reasoning effort (low, medium, high, xhigh)')
|
|
943
|
+
.option('-r, --resume <sessionId>', 'Resume a specific Claude session (claude only)')
|
|
944
|
+
.option('-c, --continue', 'Continue the most recent Claude session (claude only)')
|
|
855
945
|
.option(
|
|
856
946
|
'-o, --output-format <format>',
|
|
857
947
|
'Output format: stream-json (default), text, json',
|
|
@@ -862,11 +952,16 @@ taskCmd
|
|
|
862
952
|
.action(async (prompt, options) => {
|
|
863
953
|
try {
|
|
864
954
|
// === PREFLIGHT CHECKS ===
|
|
865
|
-
//
|
|
955
|
+
// Provider CLI must be installed for task execution
|
|
956
|
+
const settings = loadSettings();
|
|
957
|
+
const providerOverride = normalizeProviderName(
|
|
958
|
+
options.provider || process.env.ZEROSHOT_PROVIDER || settings.defaultProvider
|
|
959
|
+
);
|
|
866
960
|
requirePreflight({
|
|
867
961
|
requireGh: false, // gh not needed for plain tasks
|
|
868
962
|
requireDocker: false, // Docker not needed for plain tasks
|
|
869
963
|
quiet: false,
|
|
964
|
+
provider: providerOverride,
|
|
870
965
|
});
|
|
871
966
|
|
|
872
967
|
// Dynamically import task command (ESM module)
|
|
@@ -925,8 +1020,8 @@ program
|
|
|
925
1020
|
.action(async (options) => {
|
|
926
1021
|
try {
|
|
927
1022
|
// Get clusters
|
|
928
|
-
const clusters = getOrchestrator().listClusters();
|
|
929
|
-
const orchestrator = getOrchestrator();
|
|
1023
|
+
const clusters = (await getOrchestrator()).listClusters();
|
|
1024
|
+
const orchestrator = await getOrchestrator();
|
|
930
1025
|
|
|
931
1026
|
// Enrich clusters with token data
|
|
932
1027
|
const enrichedClusters = clusters.map((cluster) => {
|
|
@@ -992,7 +1087,8 @@ program
|
|
|
992
1087
|
for (const cluster of enrichedClusters) {
|
|
993
1088
|
const created = new Date(cluster.createdAt).toLocaleString();
|
|
994
1089
|
const tokenDisplay = cluster.totalTokens > 0 ? cluster.totalTokens.toLocaleString() : '-';
|
|
995
|
-
const costDisplay =
|
|
1090
|
+
const costDisplay =
|
|
1091
|
+
cluster.totalCostUsd > 0 ? '$' + cluster.totalCostUsd.toFixed(3) : '-';
|
|
996
1092
|
|
|
997
1093
|
// Highlight zombie clusters in red
|
|
998
1094
|
const stateDisplay =
|
|
@@ -1042,12 +1138,12 @@ program
|
|
|
1042
1138
|
|
|
1043
1139
|
if (type === 'cluster') {
|
|
1044
1140
|
// Show cluster status
|
|
1045
|
-
const status = getOrchestrator().getStatus(id);
|
|
1141
|
+
const status = (await getOrchestrator()).getStatus(id);
|
|
1046
1142
|
|
|
1047
1143
|
// Get token usage
|
|
1048
1144
|
let tokensByRole = null;
|
|
1049
1145
|
try {
|
|
1050
|
-
const cluster = getOrchestrator().getCluster(id);
|
|
1146
|
+
const cluster = (await getOrchestrator()).getCluster(id);
|
|
1051
1147
|
if (cluster?.messageBus) {
|
|
1052
1148
|
tokensByRole = cluster.messageBus.getTokensByRole(id);
|
|
1053
1149
|
}
|
|
@@ -1186,7 +1282,7 @@ program
|
|
|
1186
1282
|
|
|
1187
1283
|
// === CLUSTER LOGS ===
|
|
1188
1284
|
const limit = parseInt(options.limit);
|
|
1189
|
-
const quietOrchestrator =
|
|
1285
|
+
const quietOrchestrator = await Orchestrator.create({ quiet: true });
|
|
1190
1286
|
|
|
1191
1287
|
// No ID: show/follow ALL clusters
|
|
1192
1288
|
if (!id) {
|
|
@@ -1597,7 +1693,7 @@ program
|
|
|
1597
1693
|
.action(async (clusterId) => {
|
|
1598
1694
|
try {
|
|
1599
1695
|
console.log(`Stopping cluster ${clusterId}...`);
|
|
1600
|
-
await getOrchestrator().stop(clusterId);
|
|
1696
|
+
await (await getOrchestrator()).stop(clusterId);
|
|
1601
1697
|
console.log('Cluster stopped successfully');
|
|
1602
1698
|
} catch (error) {
|
|
1603
1699
|
console.error('Error stopping cluster:', error.message);
|
|
@@ -1621,7 +1717,7 @@ program
|
|
|
1621
1717
|
|
|
1622
1718
|
if (type === 'cluster') {
|
|
1623
1719
|
console.log(`Killing cluster ${id}...`);
|
|
1624
|
-
await getOrchestrator().kill(id);
|
|
1720
|
+
await (await getOrchestrator()).kill(id);
|
|
1625
1721
|
console.log('Cluster killed successfully');
|
|
1626
1722
|
} else {
|
|
1627
1723
|
// Kill task
|
|
@@ -1749,15 +1845,13 @@ Key bindings:
|
|
|
1749
1845
|
|
|
1750
1846
|
// Create orchestrator instance to query agent states
|
|
1751
1847
|
// This loads the cluster from disk including its ledger and agents
|
|
1752
|
-
const orchestrator =
|
|
1848
|
+
const orchestrator = await Orchestrator.create({ quiet: true });
|
|
1753
1849
|
|
|
1754
1850
|
try {
|
|
1755
1851
|
const status = orchestrator.getStatus(id);
|
|
1756
1852
|
// Agent is "active" if in any working state
|
|
1757
1853
|
// Note: currentTaskId may be null briefly between TASK_STARTED and TASK_ID_ASSIGNED
|
|
1758
|
-
const activeAgents = status.agents.filter(
|
|
1759
|
-
(a) => ACTIVE_STATES.has(a.state)
|
|
1760
|
-
);
|
|
1854
|
+
const activeAgents = status.agents.filter((a) => ACTIVE_STATES.has(a.state));
|
|
1761
1855
|
|
|
1762
1856
|
if (activeAgents.length === 0) {
|
|
1763
1857
|
console.error(chalk.yellow(`No agents currently executing tasks in cluster ${id}`));
|
|
@@ -1808,10 +1902,16 @@ Key bindings:
|
|
|
1808
1902
|
if (!agent.currentTaskId) {
|
|
1809
1903
|
if (ACTIVE_STATES.has(agent.state)) {
|
|
1810
1904
|
// Agent is working but task ID not yet assigned
|
|
1811
|
-
console.error(
|
|
1905
|
+
console.error(
|
|
1906
|
+
chalk.yellow(
|
|
1907
|
+
`Agent '${options.agent}' is working (state: ${agent.state}, task ID not yet assigned)`
|
|
1908
|
+
)
|
|
1909
|
+
);
|
|
1812
1910
|
console.log(chalk.dim('Try again in a moment...'));
|
|
1813
1911
|
} else {
|
|
1814
|
-
console.error(
|
|
1912
|
+
console.error(
|
|
1913
|
+
chalk.yellow(`Agent '${options.agent}' is not currently running a task`)
|
|
1914
|
+
);
|
|
1815
1915
|
console.log(chalk.dim(`State: ${agent.state}`));
|
|
1816
1916
|
}
|
|
1817
1917
|
return;
|
|
@@ -1917,7 +2017,7 @@ program
|
|
|
1917
2017
|
.action(async (options) => {
|
|
1918
2018
|
try {
|
|
1919
2019
|
// Get counts first
|
|
1920
|
-
const orchestrator = getOrchestrator();
|
|
2020
|
+
const orchestrator = await getOrchestrator();
|
|
1921
2021
|
const clusters = orchestrator.listClusters();
|
|
1922
2022
|
const runningClusters = clusters.filter(
|
|
1923
2023
|
(c) => c.state === 'running' || c.state === 'initializing'
|
|
@@ -2148,17 +2248,24 @@ program
|
|
|
2148
2248
|
// Check if cluster exists
|
|
2149
2249
|
const cluster = orchestrator.getCluster(id);
|
|
2150
2250
|
|
|
2151
|
-
|
|
2152
|
-
// Claude CLI must be installed and authenticated
|
|
2153
|
-
// Check if cluster uses isolation (needs Docker)
|
|
2154
|
-
const requiresDocker = cluster?.isolation?.enabled || false;
|
|
2155
|
-
requirePreflight({
|
|
2156
|
-
requireGh: false, // Resume doesn't fetch new issues
|
|
2157
|
-
requireDocker: requiresDocker,
|
|
2158
|
-
quiet: false,
|
|
2159
|
-
});
|
|
2251
|
+
const settings = loadSettings();
|
|
2160
2252
|
|
|
2161
2253
|
if (cluster) {
|
|
2254
|
+
// === PREFLIGHT CHECKS ===
|
|
2255
|
+
// Provider CLI must be installed; Docker needed if isolation was used
|
|
2256
|
+
const requiresDocker = cluster?.isolation?.enabled || false;
|
|
2257
|
+
const providerName =
|
|
2258
|
+
cluster.config?.forceProvider ||
|
|
2259
|
+
cluster.config?.defaultProvider ||
|
|
2260
|
+
settings.defaultProvider;
|
|
2261
|
+
|
|
2262
|
+
requirePreflight({
|
|
2263
|
+
requireGh: false, // Resume doesn't fetch new issues
|
|
2264
|
+
requireDocker: requiresDocker,
|
|
2265
|
+
quiet: false,
|
|
2266
|
+
provider: providerName,
|
|
2267
|
+
});
|
|
2268
|
+
|
|
2162
2269
|
// Resume cluster
|
|
2163
2270
|
console.log(chalk.cyan(`Resuming cluster ${id}...`));
|
|
2164
2271
|
const result = await orchestrator.resume(id, prompt);
|
|
@@ -2273,6 +2380,24 @@ program
|
|
|
2273
2380
|
|
|
2274
2381
|
console.log(chalk.dim(`\nCluster ${id} completed.`));
|
|
2275
2382
|
} else {
|
|
2383
|
+
let providerName = settings.defaultProvider;
|
|
2384
|
+
try {
|
|
2385
|
+
const { getTask } = await import('../task-lib/store.js');
|
|
2386
|
+
const task = getTask(id);
|
|
2387
|
+
if (task?.provider) {
|
|
2388
|
+
providerName = task.provider;
|
|
2389
|
+
}
|
|
2390
|
+
} catch {
|
|
2391
|
+
// If task store is unavailable, fall back to default provider
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
requirePreflight({
|
|
2395
|
+
requireGh: false,
|
|
2396
|
+
requireDocker: false,
|
|
2397
|
+
quiet: false,
|
|
2398
|
+
provider: providerName,
|
|
2399
|
+
});
|
|
2400
|
+
|
|
2276
2401
|
// Try resuming as task
|
|
2277
2402
|
const { resumeTask } = await import('../task-lib/commands/resume.js');
|
|
2278
2403
|
await resumeTask(id, prompt);
|
|
@@ -2525,7 +2650,7 @@ program
|
|
|
2525
2650
|
.option('-y, --yes', 'Skip confirmation')
|
|
2526
2651
|
.action(async (options) => {
|
|
2527
2652
|
try {
|
|
2528
|
-
const orchestrator = getOrchestrator();
|
|
2653
|
+
const orchestrator = await getOrchestrator();
|
|
2529
2654
|
|
|
2530
2655
|
// Get counts first
|
|
2531
2656
|
const clusters = orchestrator.listClusters();
|
|
@@ -2751,7 +2876,7 @@ program
|
|
|
2751
2876
|
try {
|
|
2752
2877
|
const TUI = require('../src/tui');
|
|
2753
2878
|
const tui = new TUI({
|
|
2754
|
-
orchestrator: getOrchestrator(),
|
|
2879
|
+
orchestrator: await getOrchestrator(),
|
|
2755
2880
|
refreshRate: parseInt(options.refreshRate, 10),
|
|
2756
2881
|
});
|
|
2757
2882
|
await tui.start();
|
|
@@ -2866,7 +2991,11 @@ function formatSettingsList(settings, showUsage = false) {
|
|
|
2866
2991
|
console.log(chalk.dim(' zeroshot settings set dockerMounts \'["gh","git","ssh","aws"]\''));
|
|
2867
2992
|
console.log(chalk.dim(' zeroshot settings set dockerEnvPassthrough \'["AWS_*","TF_VAR_*"]\''));
|
|
2868
2993
|
console.log('');
|
|
2869
|
-
console.log(
|
|
2994
|
+
console.log(
|
|
2995
|
+
chalk.dim(
|
|
2996
|
+
'Available mount presets: gh, git, ssh, aws, azure, kube, terraform, gcloud, claude, codex, gemini'
|
|
2997
|
+
)
|
|
2998
|
+
);
|
|
2870
2999
|
console.log('');
|
|
2871
3000
|
}
|
|
2872
3001
|
}
|
|
@@ -2960,6 +3089,26 @@ settingsCmd.action(() => {
|
|
|
2960
3089
|
formatSettingsList(settings, true);
|
|
2961
3090
|
});
|
|
2962
3091
|
|
|
3092
|
+
// Providers management
|
|
3093
|
+
const providersCmd = program.command('providers').description('Manage AI providers');
|
|
3094
|
+
providersCmd.action(async () => {
|
|
3095
|
+
await providersCommand();
|
|
3096
|
+
});
|
|
3097
|
+
|
|
3098
|
+
providersCmd
|
|
3099
|
+
.command('set-default <provider>')
|
|
3100
|
+
.description('Set default provider (claude, codex, gemini)')
|
|
3101
|
+
.action(async (provider) => {
|
|
3102
|
+
await setDefaultCommand([provider]);
|
|
3103
|
+
});
|
|
3104
|
+
|
|
3105
|
+
providersCmd
|
|
3106
|
+
.command('setup <provider>')
|
|
3107
|
+
.description('Configure provider model levels and overrides')
|
|
3108
|
+
.action(async (provider) => {
|
|
3109
|
+
await setupCommand([provider]);
|
|
3110
|
+
});
|
|
3111
|
+
|
|
2963
3112
|
// Update command
|
|
2964
3113
|
program
|
|
2965
3114
|
.command('update')
|
|
@@ -3882,7 +4031,10 @@ function renderMessagesToTerminal(clusterId, messages) {
|
|
|
3882
4031
|
const content = msg.content?.data?.line || msg.content?.data?.chunk || msg.content?.text;
|
|
3883
4032
|
if (!content || !content.trim()) continue;
|
|
3884
4033
|
|
|
3885
|
-
const
|
|
4034
|
+
const provider = normalizeProviderName(
|
|
4035
|
+
msg.content?.data?.provider || msg.sender_provider || 'claude'
|
|
4036
|
+
);
|
|
4037
|
+
const events = parseProviderChunk(provider, content);
|
|
3886
4038
|
for (const event of events) {
|
|
3887
4039
|
switch (event.type) {
|
|
3888
4040
|
case 'text':
|
|
@@ -4181,7 +4333,7 @@ const FILTERED_PATTERNS = [
|
|
|
4181
4333
|
/^--- Following log/,
|
|
4182
4334
|
/--- Following logs/,
|
|
4183
4335
|
/Ctrl\+C to stop/,
|
|
4184
|
-
/^=== Claude Task:/,
|
|
4336
|
+
/^=== (Claude|Codex|Gemini) Task:/,
|
|
4185
4337
|
/^Started:/,
|
|
4186
4338
|
/^Finished:/,
|
|
4187
4339
|
/^Exit code:/,
|
|
@@ -4276,7 +4428,10 @@ function printMessage(msg, showClusterId = false, watchMode = false, isActive =
|
|
|
4276
4428
|
if (!content || !content.trim()) return;
|
|
4277
4429
|
|
|
4278
4430
|
// Parse streaming JSON events using the parser
|
|
4279
|
-
const
|
|
4431
|
+
const provider = normalizeProviderName(
|
|
4432
|
+
msg.content?.data?.provider || msg.sender_provider || 'claude'
|
|
4433
|
+
);
|
|
4434
|
+
const events = parseProviderChunk(provider, content);
|
|
4280
4435
|
|
|
4281
4436
|
for (const event of events) {
|
|
4282
4437
|
switch (event.type) {
|
|
@@ -4431,9 +4586,7 @@ function printMessage(msg, showClusterId = false, watchMode = false, isActive =
|
|
|
4431
4586
|
|
|
4432
4587
|
// IMPLEMENTATION_READY: milestone marker
|
|
4433
4588
|
if (msg.topic === 'IMPLEMENTATION_READY') {
|
|
4434
|
-
safePrint(
|
|
4435
|
-
`${prefix} ${chalk.gray(timestamp)} ${chalk.bold.yellow('✅ IMPLEMENTATION READY')}`
|
|
4436
|
-
);
|
|
4589
|
+
safePrint(`${prefix} ${chalk.gray(timestamp)} ${chalk.bold.yellow('✅ IMPLEMENTATION READY')}`);
|
|
4437
4590
|
if (msg.content?.data?.commit) {
|
|
4438
4591
|
safePrint(
|
|
4439
4592
|
`${prefix} ${chalk.gray('Commit:')} ${chalk.cyan(msg.content.data.commit.substring(0, 8))}`
|
|
@@ -4487,7 +4640,10 @@ function printMessage(msg, showClusterId = false, watchMode = false, isActive =
|
|
|
4487
4640
|
|
|
4488
4641
|
// Main async entry point
|
|
4489
4642
|
async function main() {
|
|
4490
|
-
const isQuiet =
|
|
4643
|
+
const isQuiet =
|
|
4644
|
+
process.argv.includes('-q') ||
|
|
4645
|
+
process.argv.includes('--quiet') ||
|
|
4646
|
+
process.env.NODE_ENV === 'test';
|
|
4491
4647
|
|
|
4492
4648
|
// Check for updates (non-blocking if offline)
|
|
4493
4649
|
await checkForUpdates({ quiet: isQuiet });
|
package/cli/lib/first-run.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
const readline = require('readline');
|
|
12
12
|
const { loadSettings, saveSettings } = require('../../lib/settings');
|
|
13
|
+
const { detectProviders } = require('../../src/providers');
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Print welcome banner
|
|
@@ -19,7 +20,7 @@ function printWelcome() {
|
|
|
19
20
|
╔═══════════════════════════════════════════════════════════════╗
|
|
20
21
|
║ ║
|
|
21
22
|
║ Welcome to Zeroshot! ║
|
|
22
|
-
║ Multi-agent orchestration
|
|
23
|
+
║ Multi-agent orchestration engine ║
|
|
23
24
|
║ ║
|
|
24
25
|
║ Let's configure a few settings to get started. ║
|
|
25
26
|
║ ║
|
|
@@ -38,6 +39,37 @@ function createReadline() {
|
|
|
38
39
|
});
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Prompt for provider selection
|
|
44
|
+
* @param {readline.Interface} rl
|
|
45
|
+
* @param {object} detected
|
|
46
|
+
* @returns {Promise<string>}
|
|
47
|
+
*/
|
|
48
|
+
function promptProvider(rl, detected) {
|
|
49
|
+
console.log('\nWhich AI provider would you like to use by default?\n');
|
|
50
|
+
|
|
51
|
+
const available = Object.entries(detected).filter(([_, status]) => status.available);
|
|
52
|
+
|
|
53
|
+
if (available.length === 0) {
|
|
54
|
+
console.log('No AI CLI tools detected. Please install one of:');
|
|
55
|
+
console.log(' - Claude Code: npm install -g @anthropic-ai/claude-code');
|
|
56
|
+
console.log(' - Codex CLI: npm install -g @openai/codex');
|
|
57
|
+
console.log(' - Gemini CLI: npm install -g @google/gemini-cli');
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
available.forEach(([name], i) => {
|
|
62
|
+
console.log(` ${i + 1}) ${name} (installed)`);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return new Promise((resolve) => {
|
|
66
|
+
rl.question('\nChoice [1]: ', (answer) => {
|
|
67
|
+
const idx = parseInt(answer) - 1 || 0;
|
|
68
|
+
resolve(available[idx]?.[0] || available[0][0]);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
41
73
|
/**
|
|
42
74
|
* Prompt for model selection
|
|
43
75
|
* @param {readline.Interface} rl
|
|
@@ -45,7 +77,7 @@ function createReadline() {
|
|
|
45
77
|
*/
|
|
46
78
|
function promptModel(rl) {
|
|
47
79
|
return new Promise((resolve) => {
|
|
48
|
-
console.log('What is the maximum model agents can use? (cost ceiling)\n');
|
|
80
|
+
console.log('What is the maximum Claude model agents can use? (cost ceiling)\n');
|
|
49
81
|
console.log(' 1) sonnet - Agents can use sonnet or haiku (recommended)');
|
|
50
82
|
console.log(' 2) opus - Agents can use opus, sonnet, or haiku');
|
|
51
83
|
console.log(' 3) haiku - Agents can only use haiku (lowest cost)\n');
|
|
@@ -95,7 +127,8 @@ function printComplete(settings) {
|
|
|
95
127
|
╚═══════════════════════════════════════════════════════════════╝
|
|
96
128
|
|
|
97
129
|
Your settings:
|
|
98
|
-
•
|
|
130
|
+
• Provider: ${settings.defaultProvider}
|
|
131
|
+
• Max model: ${settings.maxModel} (Claude ceiling)
|
|
99
132
|
• Auto-updates: ${settings.autoCheckUpdates ? 'enabled' : 'disabled'}
|
|
100
133
|
|
|
101
134
|
Change anytime with: zeroshot settings set <key> <value>
|
|
@@ -144,6 +177,10 @@ async function checkFirstRun(options = {}) {
|
|
|
144
177
|
const rl = createReadline();
|
|
145
178
|
|
|
146
179
|
try {
|
|
180
|
+
const detected = await detectProviders();
|
|
181
|
+
const provider = await promptProvider(rl, detected);
|
|
182
|
+
settings.defaultProvider = provider;
|
|
183
|
+
|
|
147
184
|
// Model ceiling selection
|
|
148
185
|
const model = await promptModel(rl);
|
|
149
186
|
settings.maxModel = model;
|