@covibes/zeroshot 2.0.0 ā 3.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 +82 -0
- package/README.md +19 -8
- package/cli/index.js +146 -111
- package/cli/lib/first-run.js +11 -11
- package/cli/lib/update-checker.js +2 -1
- package/cluster-templates/base-templates/debug-workflow.json +75 -6
- package/cluster-templates/conductor-bootstrap.json +3 -3
- package/docker/zeroshot-cluster/Dockerfile +8 -1
- package/docker/zeroshot-cluster/pre-baked-deps.json +28 -0
- package/lib/settings.js +46 -4
- package/package.json +1 -1
- package/src/agent/agent-config.js +38 -3
- package/src/agent/agent-task-executor.js +229 -85
- package/src/agent-wrapper.js +49 -13
- package/src/config-validator.js +198 -0
- package/src/copy-worker.js +43 -0
- package/src/isolation-manager.js +328 -108
- package/src/orchestrator.js +93 -11
- package/src/preflight.js +28 -2
- package/src/process-metrics.js +16 -4
- package/src/status-footer.js +151 -42
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,85 @@
|
|
|
1
|
+
# [3.0.0](https://github.com/covibes/zeroshot/compare/v2.1.0...v3.0.0) (2025-12-29)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **isolation:** replace busy-wait with async/await for parallel copy ([c8afbf0](https://github.com/covibes/zeroshot/commit/c8afbf00927ce939af633406c47a928507c339c4)), closes [#21](https://github.com/covibes/zeroshot/issues/21)
|
|
7
|
+
* **security:** escape shell arguments in Docker commands ([43476ad](https://github.com/covibes/zeroshot/commit/43476adfb3c67634d478b4dd53d52a6afb42b297))
|
|
8
|
+
* shell injection prevention and test reliability improvements ([45254f7](https://github.com/covibes/zeroshot/commit/45254f7f75b027ba43f6e16fa3668960d4b77f97))
|
|
9
|
+
* **status-footer:** use decimal display for interpolated metrics ([#26](https://github.com/covibes/zeroshot/issues/26)) ([73ce673](https://github.com/covibes/zeroshot/commit/73ce67376078f97faefe6724e32ff34619f33374))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
* **cli:** change default model ceiling to opus ([#28](https://github.com/covibes/zeroshot/issues/28)) ([1810be3](https://github.com/covibes/zeroshot/commit/1810be3a6a2cbfbb4d3aefa711c32f9ff9718f5a))
|
|
15
|
+
* **cli:** change default model ceiling to opus + fix worktree flag cascade ([#29](https://github.com/covibes/zeroshot/issues/29)) ([eaa30b0](https://github.com/covibes/zeroshot/commit/eaa30b06baf381c4fb7306d08fcd2d4e980de002))
|
|
16
|
+
* **cli:** consolidate StatusFooter for logs -f mode + add blinking agent indicator ([fe2722d](https://github.com/covibes/zeroshot/commit/fe2722d157e04048b56368e2c0ffcd7052604f36))
|
|
17
|
+
* real-time metrics via interpolation + maxModel cost ceiling ([#24](https://github.com/covibes/zeroshot/issues/24)) ([f1db466](https://github.com/covibes/zeroshot/commit/f1db46691eca592de67e399aca18f6db3e94d628)), closes [#21](https://github.com/covibes/zeroshot/issues/21)
|
|
18
|
+
* **settings:** replace defaultModel with maxModel cost ceiling ([#25](https://github.com/covibes/zeroshot/issues/25)) ([9877dad](https://github.com/covibes/zeroshot/commit/9877dadad890f78b3af1404b0341da392f6f4bb7)), closes [#23](https://github.com/covibes/zeroshot/issues/23)
|
|
19
|
+
* **validation:** add Phase 5 template variable validation ([#27](https://github.com/covibes/zeroshot/issues/27)) ([5e5e7c6](https://github.com/covibes/zeroshot/commit/5e5e7c6ab2a11ba23a3600d101a9c9c7de02569e))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Performance Improvements
|
|
23
|
+
|
|
24
|
+
* **isolation:** optimize startup with 4 key improvements ([f28f89c](https://github.com/covibes/zeroshot/commit/f28f89c36ac98c341484124bbaffee745818dffa)), closes [#20](https://github.com/covibes/zeroshot/issues/20) [#21](https://github.com/covibes/zeroshot/issues/21) [#22](https://github.com/covibes/zeroshot/issues/22) [#23](https://github.com/covibes/zeroshot/issues/23) [#20](https://github.com/covibes/zeroshot/issues/20) [#21](https://github.com/covibes/zeroshot/issues/21) [#22](https://github.com/covibes/zeroshot/issues/22) [#23](https://github.com/covibes/zeroshot/issues/23)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### BREAKING CHANGES
|
|
28
|
+
|
|
29
|
+
* None
|
|
30
|
+
* **settings:** defaultModel setting renamed to maxModel
|
|
31
|
+
* defaultModel setting renamed to maxModel
|
|
32
|
+
|
|
33
|
+
* feat(status-footer): implement real-time metrics via interpolation
|
|
34
|
+
|
|
35
|
+
Replace blocking 1s metrics polling with background sampling + interpolation:
|
|
36
|
+
- Sample actual metrics every 500ms (non-blocking background)
|
|
37
|
+
- Display updates every 100ms (10 fps - appears continuous)
|
|
38
|
+
- Values smoothly drift toward targets via lerp (15% per tick)
|
|
39
|
+
- CPU and RAM interpolate; Network is cumulative (no interpolation)
|
|
40
|
+
|
|
41
|
+
Result: Real-time seeming monitoring while reducing actual polling.
|
|
42
|
+
|
|
43
|
+
š¤ Generated with [Claude Code](https://claude.com/claude-code)
|
|
44
|
+
|
|
45
|
+
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
|
46
|
+
|
|
47
|
+
* feat(debug-workflow): harden investigator/fixer/tester for senior-dev quality
|
|
48
|
+
|
|
49
|
+
Implements 7 hardening changes to ensure debug-workflow produces
|
|
50
|
+
trustworthy output without manual code review:
|
|
51
|
+
|
|
52
|
+
**Investigator:**
|
|
53
|
+
- Structured rootCauses schema requiring proof each is fundamental
|
|
54
|
+
- Mandatory similarPatternLocations field from codebase-wide scan
|
|
55
|
+
- Prompt requires documenting WHY each cause is root (not symptom)
|
|
56
|
+
|
|
57
|
+
**Fixer:**
|
|
58
|
+
- Mandatory root cause mapping (each cause ā specific fix)
|
|
59
|
+
- Mandatory test addition with escape hatch for valid justifications
|
|
60
|
+
- Must fix ALL similar pattern locations, not just original failure
|
|
61
|
+
|
|
62
|
+
**Tester:**
|
|
63
|
+
- Structured verification schema with commandResult, rootCauseVerification,
|
|
64
|
+
similarLocationVerification, testVerification, regressionCheck
|
|
65
|
+
- Comprehensive checklist: A (command), B (root causes), C (similar locs),
|
|
66
|
+
D (test quality), E (regression via smart tiering)
|
|
67
|
+
- Explicit forbidden rationalizations and approval criteria
|
|
68
|
+
|
|
69
|
+
Result: Workflow now blocks incomplete work, band-aid fixes, missing tests,
|
|
70
|
+
and ignored similar bugs. Output can be trusted.
|
|
71
|
+
|
|
72
|
+
š¤ Generated with [Claude Code](https://claude.com/claude-code)
|
|
73
|
+
|
|
74
|
+
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
|
75
|
+
|
|
76
|
+
# [2.1.0](https://github.com/covibes/zeroshot/compare/v2.0.0...v2.1.0) (2025-12-29)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
### Features
|
|
80
|
+
|
|
81
|
+
* add first-run wizard and update checker ([c93cdfe](https://github.com/covibes/zeroshot/commit/c93cdfec65f05ce5b1ed4583b5ec0a23fcf56f31)), closes [#17](https://github.com/covibes/zeroshot/issues/17)
|
|
82
|
+
|
|
1
83
|
# [2.0.0](https://github.com/covibes/zeroshot/compare/v1.5.0...v2.0.0) (2025-12-29)
|
|
2
84
|
|
|
3
85
|
|
package/README.md
CHANGED
|
@@ -70,10 +70,11 @@ gh auth login
|
|
|
70
70
|
zeroshot run 123 # Run on GitHub issue
|
|
71
71
|
zeroshot run "Add dark mode" # Run from description
|
|
72
72
|
|
|
73
|
-
# Automation levels (cascading: --ship ā --pr ā --
|
|
74
|
-
zeroshot run 123 --
|
|
75
|
-
zeroshot run 123 --
|
|
76
|
-
zeroshot run 123 --
|
|
73
|
+
# Automation levels (cascading: --ship ā --pr ā --worktree)
|
|
74
|
+
zeroshot run 123 --docker # Docker isolation (full container)
|
|
75
|
+
zeroshot run 123 --worktree # Git worktree isolation (lightweight)
|
|
76
|
+
zeroshot run 123 --pr # Worktree + PR (human reviews)
|
|
77
|
+
zeroshot run 123 --ship # Worktree + PR + auto-merge (full automation)
|
|
77
78
|
|
|
78
79
|
# Background mode
|
|
79
80
|
zeroshot run 123 -d # Detached/daemon
|
|
@@ -331,13 +332,23 @@ zeroshot resume cluster-bold-panther
|
|
|
331
332
|
|
|
332
333
|
---
|
|
333
334
|
|
|
334
|
-
##
|
|
335
|
+
## Isolation Modes
|
|
336
|
+
|
|
337
|
+
### Git Worktree (Default for --pr/--ship)
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
zeroshot 123 --worktree
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Lightweight isolation using git worktree. Creates a separate working directory with its own branch. Fast (<1s setup), no Docker required. Auto-enabled with `--pr` and `--ship`.
|
|
344
|
+
|
|
345
|
+
### Docker Container
|
|
335
346
|
|
|
336
347
|
```bash
|
|
337
|
-
zeroshot 123 --
|
|
348
|
+
zeroshot 123 --docker
|
|
338
349
|
```
|
|
339
350
|
|
|
340
|
-
|
|
351
|
+
Full isolation in a fresh container. Your workspace stays untouched. Good for risky experiments or parallel agents.
|
|
341
352
|
|
|
342
353
|
---
|
|
343
354
|
|
|
@@ -356,7 +367,7 @@ Runs in a fresh container. Your workspace stays untouched. Good for risky experi
|
|
|
356
367
|
| `claude: command not found` | `npm i -g @anthropic-ai/claude-code && claude auth login` |
|
|
357
368
|
| `gh: command not found` | [Install GitHub CLI](https://cli.github.com/) |
|
|
358
369
|
| CLI frozen for minutes | Normal - agents use JSON schema output, can't stream partial results |
|
|
359
|
-
| `--
|
|
370
|
+
| `--docker` fails | Docker must be running: `docker ps` to verify |
|
|
360
371
|
| Cluster stuck | `zeroshot resume <id>` to continue with guidance |
|
|
361
372
|
| Agent keeps failing | Check `zeroshot logs <id>` for actual error |
|
|
362
373
|
| `zeroshot: command not found` | `npm install -g @covibes/zeroshot` |
|
package/cli/index.js
CHANGED
|
@@ -386,7 +386,7 @@ Examples:
|
|
|
386
386
|
${chalk.cyan('zeroshot run 123')} Run cluster and attach to first agent
|
|
387
387
|
${chalk.cyan('zeroshot run 123 -d')} Run cluster in background (detached)
|
|
388
388
|
${chalk.cyan('zeroshot run "Implement feature X"')} Run cluster on plain text task
|
|
389
|
-
${chalk.cyan('zeroshot run 123 --
|
|
389
|
+
${chalk.cyan('zeroshot run 123 --docker')} Run in Docker container (safe for e2e tests)
|
|
390
390
|
${chalk.cyan('zeroshot task run "Fix the bug"')} Run single-agent background task
|
|
391
391
|
${chalk.cyan('zeroshot list')} List all tasks and clusters
|
|
392
392
|
${chalk.cyan('zeroshot task list')} List tasks only
|
|
@@ -400,17 +400,18 @@ Examples:
|
|
|
400
400
|
${chalk.cyan('zeroshot kill <id>')} Kill a running task or cluster
|
|
401
401
|
${chalk.cyan('zeroshot purge')} Kill all processes and delete all data (with confirmation)
|
|
402
402
|
${chalk.cyan('zeroshot purge -y')} Purge everything without confirmation
|
|
403
|
-
${chalk.cyan('zeroshot settings')} Show/manage zeroshot settings (
|
|
404
|
-
${chalk.cyan('zeroshot settings set <key> <val>')} Set a setting (e.g.,
|
|
403
|
+
${chalk.cyan('zeroshot settings')} Show/manage zeroshot settings (maxModel, config, etc.)
|
|
404
|
+
${chalk.cyan('zeroshot settings set <key> <val>')} Set a setting (e.g., maxModel haiku)
|
|
405
405
|
${chalk.cyan('zeroshot config list')} List available cluster configs
|
|
406
406
|
${chalk.cyan('zeroshot config show <name>')} Visualize a cluster config (agents, triggers, flow)
|
|
407
407
|
${chalk.cyan('zeroshot export <id>')} Export cluster conversation to file
|
|
408
408
|
|
|
409
|
-
Automation levels (cascading: --ship ā --pr ā --
|
|
409
|
+
Automation levels (cascading: --ship ā --pr ā --worktree):
|
|
410
410
|
${chalk.yellow('zeroshot run 123')} ā Local run, no isolation
|
|
411
|
-
${chalk.yellow('zeroshot run 123 --
|
|
412
|
-
${chalk.yellow('zeroshot run 123 --
|
|
413
|
-
${chalk.yellow('zeroshot run 123 --
|
|
411
|
+
${chalk.yellow('zeroshot run 123 --docker')} ā Docker isolation, no PR
|
|
412
|
+
${chalk.yellow('zeroshot run 123 --worktree')} ā Git worktree isolation, no PR
|
|
413
|
+
${chalk.yellow('zeroshot run 123 --pr')} ā Worktree + PR (human reviews)
|
|
414
|
+
${chalk.yellow('zeroshot run 123 --ship')} ā Worktree + PR + auto-merge (full automation)
|
|
414
415
|
${chalk.yellow('zeroshot task run')} ā Single-agent background task (simpler, faster)
|
|
415
416
|
|
|
416
417
|
Shell completion:
|
|
@@ -423,18 +424,18 @@ program
|
|
|
423
424
|
.command('run <input>')
|
|
424
425
|
.description('Start a multi-agent cluster (auto-detects GitHub issue or plain text)')
|
|
425
426
|
.option('--config <file>', 'Path to cluster config JSON (default: conductor-bootstrap)')
|
|
426
|
-
.option('
|
|
427
|
-
.option('--
|
|
427
|
+
.option('--docker', 'Run cluster inside Docker container (full isolation)')
|
|
428
|
+
.option('--worktree', 'Use git worktree for isolation (lightweight, no Docker required)')
|
|
428
429
|
.option(
|
|
429
|
-
'--
|
|
430
|
-
'Docker image for
|
|
430
|
+
'--docker-image <image>',
|
|
431
|
+
'Docker image for --docker mode (default: zeroshot-cluster-base)'
|
|
431
432
|
)
|
|
432
433
|
.option(
|
|
433
434
|
'--strict-schema',
|
|
434
435
|
'Enforce JSON schema via CLI (no live streaming). Default: live streaming with local validation'
|
|
435
436
|
)
|
|
436
|
-
.option('--pr', 'Create PR for human review (
|
|
437
|
-
.option('--ship', 'Full automation: isolation + PR + auto-merge')
|
|
437
|
+
.option('--pr', 'Create PR for human review (uses worktree isolation by default, use --docker for Docker)')
|
|
438
|
+
.option('--ship', 'Full automation: worktree isolation + PR + auto-merge (use --docker for Docker)')
|
|
438
439
|
.option('--workers <n>', 'Max sub-agents for worker to spawn in parallel', parseInt)
|
|
439
440
|
.option('-d, --detach', 'Run in background (default: attach to first agent)')
|
|
440
441
|
.addHelpText(
|
|
@@ -449,15 +450,23 @@ Input formats:
|
|
|
449
450
|
)
|
|
450
451
|
.action(async (inputArg, options) => {
|
|
451
452
|
try {
|
|
452
|
-
// Cascading flag implications: --ship ā --pr ā --
|
|
453
|
-
// --ship = full automation (isolation + PR + auto-merge)
|
|
453
|
+
// Cascading flag implications: --ship ā --pr ā worktree (unless --docker)
|
|
454
|
+
// --ship = full automation (worktree isolation + PR + auto-merge)
|
|
454
455
|
if (options.ship) {
|
|
455
456
|
options.pr = true;
|
|
456
|
-
|
|
457
|
+
// Use worktree by default, Docker only if explicitly requested
|
|
458
|
+
if (!options.docker) {
|
|
459
|
+
options.worktree = true;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
// --pr = PR for human review (worktree by default, Docker if requested)
|
|
463
|
+
if (options.pr && !options.docker && !options.worktree) {
|
|
464
|
+
options.worktree = true;
|
|
457
465
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
466
|
+
|
|
467
|
+
// Mutual exclusivity: --docker explicitly disables worktree
|
|
468
|
+
if (options.docker) {
|
|
469
|
+
options.worktree = false;
|
|
461
470
|
}
|
|
462
471
|
|
|
463
472
|
// Auto-detect input type
|
|
@@ -485,7 +494,8 @@ Input formats:
|
|
|
485
494
|
// This gives users clear, actionable error messages upfront
|
|
486
495
|
const preflightOptions = {
|
|
487
496
|
requireGh: !!input.issue, // gh CLI required when fetching GitHub issues
|
|
488
|
-
requireDocker: options.
|
|
497
|
+
requireDocker: options.docker, // Docker required for --docker mode
|
|
498
|
+
requireGit: options.worktree, // Git required for worktree isolation
|
|
489
499
|
quiet: process.env.CREW_DAEMON === '1', // Suppress success in daemon mode
|
|
490
500
|
};
|
|
491
501
|
requirePreflight(preflightOptions);
|
|
@@ -503,8 +513,8 @@ Input formats:
|
|
|
503
513
|
const clusterId = generateName('cluster');
|
|
504
514
|
|
|
505
515
|
// Output cluster ID and help
|
|
506
|
-
if (options.
|
|
507
|
-
console.log(`Started ${clusterId} (
|
|
516
|
+
if (options.docker) {
|
|
517
|
+
console.log(`Started ${clusterId} (docker)`);
|
|
508
518
|
} else {
|
|
509
519
|
console.log(`Started ${clusterId}`);
|
|
510
520
|
}
|
|
@@ -533,10 +543,10 @@ Input formats:
|
|
|
533
543
|
...process.env,
|
|
534
544
|
CREW_DAEMON: '1',
|
|
535
545
|
CREW_CLUSTER_ID: clusterId,
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
CREW_ISOLATION_IMAGE: options.isolationImage || '',
|
|
546
|
+
CREW_DOCKER: options.docker ? '1' : '',
|
|
547
|
+
CREW_DOCKER_IMAGE: options.dockerImage || '',
|
|
539
548
|
CREW_PR: options.pr ? '1' : '',
|
|
549
|
+
CREW_WORKTREE: options.worktree ? '1' : '',
|
|
540
550
|
CREW_WORKERS: options.workers?.toString() || '',
|
|
541
551
|
CREW_CWD: targetCwd, // Explicit CWD for orchestrator
|
|
542
552
|
},
|
|
@@ -587,8 +597,10 @@ Input formats:
|
|
|
587
597
|
|
|
588
598
|
// In foreground mode, show startup info
|
|
589
599
|
if (!process.env.CREW_DAEMON) {
|
|
590
|
-
if (options.
|
|
591
|
-
console.log(`Starting ${clusterId} (
|
|
600
|
+
if (options.docker) {
|
|
601
|
+
console.log(`Starting ${clusterId} (docker)`);
|
|
602
|
+
} else if (options.worktree) {
|
|
603
|
+
console.log(`Starting ${clusterId} (worktree)`);
|
|
592
604
|
} else {
|
|
593
605
|
console.log(`Starting ${clusterId}`);
|
|
594
606
|
}
|
|
@@ -596,17 +608,6 @@ Input formats:
|
|
|
596
608
|
console.log(chalk.dim('Ctrl+C to stop following (cluster keeps running)\n'));
|
|
597
609
|
}
|
|
598
610
|
|
|
599
|
-
// Override model (CLI > settings > config)
|
|
600
|
-
const modelOverride = process.env.CREW_MODEL || options.model || settings.defaultModel;
|
|
601
|
-
if (modelOverride) {
|
|
602
|
-
for (const agent of config.agents) {
|
|
603
|
-
// Only override if agent doesn't already specify a model
|
|
604
|
-
if (!agent.model || modelOverride) {
|
|
605
|
-
agent.model = modelOverride;
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
|
|
610
611
|
// Apply strictSchema setting to all agents (CLI > env > settings)
|
|
611
612
|
const strictSchema =
|
|
612
613
|
options.strictSchema || process.env.CREW_STRICT_SCHEMA === '1' || settings.strictSchema;
|
|
@@ -623,8 +624,9 @@ Input formats:
|
|
|
623
624
|
const startOptions = {
|
|
624
625
|
cwd: targetCwd, // Target working directory for agents
|
|
625
626
|
isolation:
|
|
626
|
-
options.
|
|
627
|
-
isolationImage: options.
|
|
627
|
+
options.docker || process.env.CREW_DOCKER === '1' || settings.defaultDocker,
|
|
628
|
+
isolationImage: options.dockerImage || process.env.CREW_DOCKER_IMAGE || undefined,
|
|
629
|
+
worktree: options.worktree || process.env.CREW_WORKTREE === '1',
|
|
628
630
|
autoPr: options.pr || process.env.CREW_PR === '1',
|
|
629
631
|
autoMerge: process.env.CREW_MERGE === '1',
|
|
630
632
|
autoPush: process.env.CREW_PUSH === '1',
|
|
@@ -822,10 +824,6 @@ taskCmd
|
|
|
822
824
|
.command('run <prompt>')
|
|
823
825
|
.description('Run a single-agent background task')
|
|
824
826
|
.option('-C, --cwd <path>', 'Working directory for task')
|
|
825
|
-
.option(
|
|
826
|
-
'-m, --model <model>',
|
|
827
|
-
'Model to use: opus, sonnet, haiku (default: sonnet or ANTHROPIC_MODEL env)'
|
|
828
|
-
)
|
|
829
827
|
.option('-r, --resume <sessionId>', 'Resume a specific Claude session')
|
|
830
828
|
.option('-c, --continue', 'Continue the most recent session')
|
|
831
829
|
.option(
|
|
@@ -1269,11 +1267,23 @@ program
|
|
|
1269
1267
|
clusterStates.set(c.id, c.state);
|
|
1270
1268
|
}
|
|
1271
1269
|
|
|
1272
|
-
//
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1270
|
+
// === STATUS FOOTER: Live agent monitoring (same as foreground mode) ===
|
|
1271
|
+
// Shows CPU, memory, network metrics for all agents at bottom of terminal
|
|
1272
|
+
let statusFooter = null;
|
|
1273
|
+
if ((options.follow || options.watch) && process.stdout.isTTY) {
|
|
1274
|
+
statusFooter = new StatusFooter({
|
|
1275
|
+
refreshInterval: 1000,
|
|
1276
|
+
enabled: true,
|
|
1277
|
+
});
|
|
1278
|
+
// Set first cluster as the active one (for display purposes)
|
|
1279
|
+
if (allClusters.length > 0) {
|
|
1280
|
+
statusFooter.setCluster(allClusters[0].id);
|
|
1281
|
+
statusFooter.setClusterState(clusterStates.get(allClusters[0].id) || 'running');
|
|
1282
|
+
}
|
|
1283
|
+
// Set module-level reference so safePrint/safeWrite route through footer
|
|
1284
|
+
activeStatusFooter = statusFooter;
|
|
1285
|
+
statusFooter.start();
|
|
1286
|
+
}
|
|
1277
1287
|
|
|
1278
1288
|
// Buffered message handler - collects messages and sorts by timestamp
|
|
1279
1289
|
const flushMessages = () => {
|
|
@@ -1287,19 +1297,46 @@ program
|
|
|
1287
1297
|
if (msg.topic === 'AGENT_OUTPUT' && msg.sender) {
|
|
1288
1298
|
sendersWithOutput.add(msg.sender);
|
|
1289
1299
|
}
|
|
1290
|
-
// Track agent state from AGENT_LIFECYCLE messages
|
|
1291
|
-
if (msg.topic === 'AGENT_LIFECYCLE' && msg.sender && msg.content?.data?.state) {
|
|
1292
|
-
agentStates.set(msg.sender, {
|
|
1293
|
-
state: msg.content.data.state,
|
|
1294
|
-
model: msg.sender_model, // sender_model is always set by agent-wrapper._publish
|
|
1295
|
-
timestamp: msg.timestamp || Date.now(),
|
|
1296
|
-
});
|
|
1297
|
-
}
|
|
1298
1300
|
|
|
1299
|
-
//
|
|
1300
|
-
if (
|
|
1301
|
-
|
|
1302
|
-
|
|
1301
|
+
// Update StatusFooter from polled AGENT_LIFECYCLE messages (cross-process)
|
|
1302
|
+
if (msg.topic === 'AGENT_LIFECYCLE' && statusFooter) {
|
|
1303
|
+
const data = msg.content?.data || {};
|
|
1304
|
+
const event = data.event;
|
|
1305
|
+
const agentId = data.agent || msg.sender;
|
|
1306
|
+
|
|
1307
|
+
if (event === 'STARTED') {
|
|
1308
|
+
statusFooter.updateAgent({
|
|
1309
|
+
id: agentId,
|
|
1310
|
+
state: 'idle',
|
|
1311
|
+
pid: null,
|
|
1312
|
+
iteration: data.iteration || 0,
|
|
1313
|
+
});
|
|
1314
|
+
} else if (event === 'TASK_STARTED') {
|
|
1315
|
+
statusFooter.updateAgent({
|
|
1316
|
+
id: agentId,
|
|
1317
|
+
state: 'executing',
|
|
1318
|
+
pid: statusFooter.agents.get(agentId)?.pid || null,
|
|
1319
|
+
iteration: data.iteration || 0,
|
|
1320
|
+
});
|
|
1321
|
+
} else if (event === 'PROCESS_SPAWNED') {
|
|
1322
|
+
// Got the PID - update the agent with it for CPU/memory metrics
|
|
1323
|
+
const current = statusFooter.agents.get(agentId) || { state: 'executing', iteration: 0 };
|
|
1324
|
+
statusFooter.updateAgent({
|
|
1325
|
+
id: agentId,
|
|
1326
|
+
state: current.state,
|
|
1327
|
+
pid: data.pid,
|
|
1328
|
+
iteration: current.iteration,
|
|
1329
|
+
});
|
|
1330
|
+
} else if (event === 'TASK_COMPLETED' || event === 'TASK_FAILED') {
|
|
1331
|
+
statusFooter.updateAgent({
|
|
1332
|
+
id: agentId,
|
|
1333
|
+
state: 'idle',
|
|
1334
|
+
pid: null,
|
|
1335
|
+
iteration: data.iteration || 0,
|
|
1336
|
+
});
|
|
1337
|
+
} else if (event === 'STOPPED') {
|
|
1338
|
+
statusFooter.removeAgent(agentId);
|
|
1339
|
+
}
|
|
1303
1340
|
}
|
|
1304
1341
|
|
|
1305
1342
|
const isActive = clusterStates.get(msg.cluster_id) === 'running';
|
|
@@ -1322,51 +1359,6 @@ program
|
|
|
1322
1359
|
// Flush buffer every 250ms
|
|
1323
1360
|
const flushInterval = setInterval(flushMessages, 250);
|
|
1324
1361
|
|
|
1325
|
-
// Blinking status indicator (follow/watch mode) - uses AGENT_LIFECYCLE state
|
|
1326
|
-
let blinkState = false;
|
|
1327
|
-
let statusInterval = null;
|
|
1328
|
-
if (options.follow || options.watch) {
|
|
1329
|
-
statusInterval = setInterval(() => {
|
|
1330
|
-
blinkState = !blinkState;
|
|
1331
|
-
|
|
1332
|
-
// Get active agents from tracked states
|
|
1333
|
-
const activeList = [];
|
|
1334
|
-
for (const [agentId, info] of agentStates.entries()) {
|
|
1335
|
-
// Agent is active if not idle and not stopped
|
|
1336
|
-
if (info.state !== 'idle' && info.state !== 'stopped') {
|
|
1337
|
-
activeList.push({
|
|
1338
|
-
id: agentId,
|
|
1339
|
-
state: info.state,
|
|
1340
|
-
model: info.model,
|
|
1341
|
-
});
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1344
|
-
|
|
1345
|
-
// Build status line - only show when agents are actively working
|
|
1346
|
-
if (activeList.length > 0) {
|
|
1347
|
-
const indicator = blinkState ? chalk.yellow('ā') : chalk.dim('ā');
|
|
1348
|
-
const agents = activeList
|
|
1349
|
-
.map((a) => {
|
|
1350
|
-
// Show state only for non-standard states (error, etc.)
|
|
1351
|
-
const showState = a.state === 'error';
|
|
1352
|
-
const stateLabel = showState ? chalk.red(` (${a.state})`) : '';
|
|
1353
|
-
// Always show model
|
|
1354
|
-
const modelLabel = a.model ? chalk.dim(` [${a.model}]`) : '';
|
|
1355
|
-
return getColorForSender(a.id)(a.id) + modelLabel + stateLabel;
|
|
1356
|
-
})
|
|
1357
|
-
.join(', ');
|
|
1358
|
-
process.stdout.write(`\r${indicator} Active: ${agents}` + ' '.repeat(20));
|
|
1359
|
-
statusLineShown = true;
|
|
1360
|
-
} else {
|
|
1361
|
-
// Clear status line when no agents actively working
|
|
1362
|
-
if (statusLineShown) {
|
|
1363
|
-
process.stdout.write('\r' + ' '.repeat(60) + '\r');
|
|
1364
|
-
statusLineShown = false;
|
|
1365
|
-
}
|
|
1366
|
-
}
|
|
1367
|
-
}, 500);
|
|
1368
|
-
}
|
|
1369
|
-
|
|
1370
1362
|
for (const clusterInfo of allClusters) {
|
|
1371
1363
|
const cluster = quietOrchestrator.getCluster(clusterInfo.id);
|
|
1372
1364
|
if (cluster) {
|
|
@@ -1399,13 +1391,13 @@ program
|
|
|
1399
1391
|
|
|
1400
1392
|
keepProcessAlive(() => {
|
|
1401
1393
|
clearInterval(flushInterval);
|
|
1402
|
-
if (statusInterval) clearInterval(statusInterval);
|
|
1403
1394
|
flushMessages();
|
|
1404
1395
|
stopPollers.forEach((stop) => stop());
|
|
1405
1396
|
stopWatching();
|
|
1406
|
-
//
|
|
1407
|
-
if (
|
|
1408
|
-
|
|
1397
|
+
// Stop status footer and restore terminal
|
|
1398
|
+
if (statusFooter) {
|
|
1399
|
+
statusFooter.stop();
|
|
1400
|
+
activeStatusFooter = null;
|
|
1409
1401
|
}
|
|
1410
1402
|
// Restore terminal title
|
|
1411
1403
|
restoreTerminalTitle();
|
|
@@ -2833,6 +2825,48 @@ settingsCmd.action(() => {
|
|
|
2833
2825
|
console.log('');
|
|
2834
2826
|
});
|
|
2835
2827
|
|
|
2828
|
+
// Update command
|
|
2829
|
+
program
|
|
2830
|
+
.command('update')
|
|
2831
|
+
.description('Update zeroshot to the latest version')
|
|
2832
|
+
.option('--check', 'Check for updates without installing')
|
|
2833
|
+
.action(async (options) => {
|
|
2834
|
+
const {
|
|
2835
|
+
getCurrentVersion,
|
|
2836
|
+
fetchLatestVersion,
|
|
2837
|
+
isNewerVersion,
|
|
2838
|
+
runUpdate,
|
|
2839
|
+
} = require('./lib/update-checker');
|
|
2840
|
+
|
|
2841
|
+
const currentVersion = getCurrentVersion();
|
|
2842
|
+
console.log(chalk.dim(`Current version: ${currentVersion}`));
|
|
2843
|
+
console.log(chalk.dim('Checking for updates...'));
|
|
2844
|
+
|
|
2845
|
+
const latestVersion = await fetchLatestVersion();
|
|
2846
|
+
|
|
2847
|
+
if (!latestVersion) {
|
|
2848
|
+
console.error(chalk.red('Failed to check for updates. Check your internet connection.'));
|
|
2849
|
+
process.exit(1);
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
console.log(chalk.dim(`Latest version: ${latestVersion}`));
|
|
2853
|
+
|
|
2854
|
+
if (!isNewerVersion(currentVersion, latestVersion)) {
|
|
2855
|
+
console.log(chalk.green('\nā You are already on the latest version!'));
|
|
2856
|
+
return;
|
|
2857
|
+
}
|
|
2858
|
+
|
|
2859
|
+
console.log(chalk.yellow(`\nš¦ Update available: ${currentVersion} ā ${latestVersion}`));
|
|
2860
|
+
|
|
2861
|
+
if (options.check) {
|
|
2862
|
+
console.log(chalk.dim('\nRun `zeroshot update` to install the update.'));
|
|
2863
|
+
return;
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2866
|
+
const success = await runUpdate();
|
|
2867
|
+
process.exit(success ? 0 : 1);
|
|
2868
|
+
});
|
|
2869
|
+
|
|
2836
2870
|
// Config visualization commands
|
|
2837
2871
|
const configCmd = program.command('config').description('Manage and visualize cluster configs');
|
|
2838
2872
|
|
|
@@ -4319,7 +4353,8 @@ function printMessage(msg, showClusterId = false, watchMode = false, isActive =
|
|
|
4319
4353
|
// Main async entry point
|
|
4320
4354
|
async function main() {
|
|
4321
4355
|
// First-run setup wizard (blocks on first use only)
|
|
4322
|
-
|
|
4356
|
+
// CRITICAL: Auto-enable quiet mode in test environment to prevent stdin hangs
|
|
4357
|
+
const isQuiet = process.argv.includes('-q') || process.argv.includes('--quiet') || process.env.NODE_ENV === 'test';
|
|
4323
4358
|
await checkFirstRun({ quiet: isQuiet });
|
|
4324
4359
|
|
|
4325
4360
|
// Check for updates (non-blocking if offline)
|
package/cli/lib/first-run.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Interactive setup on first use:
|
|
5
5
|
* - Welcome banner
|
|
6
|
-
* -
|
|
6
|
+
* - Max model ceiling selection (sonnet/opus/haiku)
|
|
7
7
|
* - Auto-update preference
|
|
8
8
|
* - Marks setup as complete
|
|
9
9
|
*/
|
|
@@ -45,13 +45,13 @@ function createReadline() {
|
|
|
45
45
|
*/
|
|
46
46
|
function promptModel(rl) {
|
|
47
47
|
return new Promise((resolve) => {
|
|
48
|
-
console.log('
|
|
49
|
-
console.log(' 1) sonnet -
|
|
50
|
-
console.log(' 2) opus -
|
|
51
|
-
console.log(' 3) haiku -
|
|
48
|
+
console.log('What is the maximum model agents can use? (cost ceiling)\n');
|
|
49
|
+
console.log(' 1) sonnet - Agents can use sonnet or haiku (recommended)');
|
|
50
|
+
console.log(' 2) opus - Agents can use opus, sonnet, or haiku');
|
|
51
|
+
console.log(' 3) haiku - Agents can only use haiku (lowest cost)\n');
|
|
52
52
|
|
|
53
|
-
rl.question('Enter 1, 2, or 3 [
|
|
54
|
-
const choice = answer.trim() || '
|
|
53
|
+
rl.question('Enter 1, 2, or 3 [2]: ', (answer) => {
|
|
54
|
+
const choice = answer.trim() || '2';
|
|
55
55
|
switch (choice) {
|
|
56
56
|
case '2':
|
|
57
57
|
resolve('opus');
|
|
@@ -95,8 +95,8 @@ function printComplete(settings) {
|
|
|
95
95
|
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
96
96
|
|
|
97
97
|
Your settings:
|
|
98
|
-
ā¢
|
|
99
|
-
⢠Auto-updates:
|
|
98
|
+
⢠Max model: ${settings.maxModel} (agents can use this model or lower)
|
|
99
|
+
⢠Auto-updates: ${settings.autoCheckUpdates ? 'enabled' : 'disabled'}
|
|
100
100
|
|
|
101
101
|
Change anytime with: zeroshot settings set <key> <value>
|
|
102
102
|
|
|
@@ -144,9 +144,9 @@ async function checkFirstRun(options = {}) {
|
|
|
144
144
|
const rl = createReadline();
|
|
145
145
|
|
|
146
146
|
try {
|
|
147
|
-
// Model selection
|
|
147
|
+
// Model ceiling selection
|
|
148
148
|
const model = await promptModel(rl);
|
|
149
|
-
settings.
|
|
149
|
+
settings.maxModel = model;
|
|
150
150
|
|
|
151
151
|
// Auto-update preference
|
|
152
152
|
const autoUpdate = await promptAutoUpdate(rl);
|
|
@@ -225,10 +225,11 @@ async function checkForUpdates(options = {}) {
|
|
|
225
225
|
|
|
226
226
|
module.exports = {
|
|
227
227
|
checkForUpdates,
|
|
228
|
-
// Exported for testing
|
|
228
|
+
// Exported for testing and CLI update command
|
|
229
229
|
getCurrentVersion,
|
|
230
230
|
isNewerVersion,
|
|
231
231
|
fetchLatestVersion,
|
|
232
|
+
runUpdate,
|
|
232
233
|
shouldCheckForUpdates,
|
|
233
234
|
CHECK_INTERVAL_MS,
|
|
234
235
|
};
|