atris 3.37.0 → 3.38.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.
Files changed (139) hide show
  1. package/README.md +2 -0
  2. package/atris/PERSONA.md +3 -7
  3. package/atris.md +10 -2
  4. package/ax +158 -33
  5. package/bin/atris.js +49 -2
  6. package/commands/activate.js +16 -3
  7. package/commands/agent-spawn.js +0 -7
  8. package/commands/autoland.js +92 -14
  9. package/commands/autopilot-front.js +0 -4
  10. package/commands/autopilot.js +0 -12
  11. package/commands/brain.js +0 -7
  12. package/commands/brief.js +0 -2
  13. package/commands/business-sync.js +0 -1
  14. package/commands/business.js +107 -12
  15. package/commands/clean.js +31 -2
  16. package/commands/close.js +46 -11
  17. package/commands/cloud.js +0 -1
  18. package/commands/codex-goal.js +0 -2
  19. package/commands/computer.js +6 -5
  20. package/commands/console.js +46 -9
  21. package/commands/deck.js +29 -8
  22. package/commands/dream.js +0 -7
  23. package/commands/drill.js +0 -1
  24. package/commands/engine.js +10 -18
  25. package/commands/experiments.js +0 -4
  26. package/commands/fleet-report.js +12 -4
  27. package/commands/game.js +261 -0
  28. package/commands/gm.js +10 -2
  29. package/commands/goal.js +0 -6
  30. package/commands/harvest.js +0 -1
  31. package/commands/improve.js +29 -8
  32. package/commands/init.js +18 -34
  33. package/commands/integrations.js +0 -7
  34. package/commands/land.js +71 -8
  35. package/commands/launchpad.js +0 -2
  36. package/commands/lesson.js +17 -7
  37. package/commands/loop-front.js +0 -2
  38. package/commands/loops.js +6 -5
  39. package/commands/meet.js +0 -2
  40. package/commands/member.js +68 -3
  41. package/commands/mission.js +359 -102
  42. package/commands/next.js +0 -1
  43. package/commands/now.js +0 -12
  44. package/commands/one-lap.js +8 -8
  45. package/commands/orb.js +0 -4
  46. package/commands/pack-craft.js +0 -1
  47. package/commands/pack.js +670 -77
  48. package/commands/play.js +0 -2
  49. package/commands/probe.js +10 -4
  50. package/commands/pulse.js +73 -48
  51. package/commands/push.js +0 -4
  52. package/commands/radar.js +14 -11
  53. package/commands/recover.js +340 -0
  54. package/commands/report.js +0 -1
  55. package/commands/router.js +319 -0
  56. package/commands/run-front.js +0 -2
  57. package/commands/search.js +0 -3
  58. package/commands/skill.js +7 -2
  59. package/commands/slop.js +225 -7
  60. package/commands/status.js +6 -0
  61. package/commands/stream.js +0 -2
  62. package/commands/study.js +0 -3
  63. package/commands/sync-checkout.js +51 -0
  64. package/commands/sync.js +11 -40
  65. package/commands/task.js +125 -39
  66. package/commands/teach.js +86 -0
  67. package/commands/unknowns.js +0 -10
  68. package/commands/update.js +0 -1
  69. package/commands/visualize.js +0 -5
  70. package/commands/watch.js +0 -6
  71. package/commands/wiki.js +41 -11
  72. package/commands/wish.js +0 -1
  73. package/commands/worktree.js +87 -14
  74. package/commands/xp.js +1 -6
  75. package/commands/youtube.js +0 -2
  76. package/lib/accept-verify-gate.js +122 -0
  77. package/lib/activity-stream.js +0 -2
  78. package/lib/auto-accept-certified.js +145 -2
  79. package/lib/autoland.js +0 -3
  80. package/lib/ax-auto-lane.js +94 -10
  81. package/lib/bench/context.js +0 -2
  82. package/lib/bench/report.js +0 -1
  83. package/lib/bench/runner.js +0 -11
  84. package/lib/brief-ledger.js +0 -6
  85. package/lib/business-simulate.js +382 -0
  86. package/lib/chat-log-scan.js +0 -5
  87. package/lib/checkout-sync.js +52 -0
  88. package/lib/claude-boot-block.js +48 -0
  89. package/lib/cloud-mission.js +0 -6
  90. package/lib/codex-flight.js +0 -1
  91. package/lib/conductor-artifacts.js +25 -0
  92. package/lib/context-gatherer.js +0 -1
  93. package/lib/deck-compose.js +0 -1
  94. package/lib/deck-history.js +0 -1
  95. package/lib/deck-layout.js +0 -5
  96. package/lib/deck-review.js +0 -7
  97. package/lib/deck-schema.js +0 -1
  98. package/lib/default-verifier.js +0 -2
  99. package/lib/engine-registry.js +16 -8
  100. package/lib/experiments/daily.js +0 -6
  101. package/lib/fleet.js +210 -27
  102. package/lib/functional-owner.js +0 -3
  103. package/lib/inspect-fields.js +0 -1
  104. package/lib/known-commands.js +2 -2
  105. package/lib/learnings.js +0 -2
  106. package/lib/loop-doctor.js +11 -17
  107. package/lib/manifest.js +0 -2
  108. package/lib/member-alive.js +0 -1
  109. package/lib/member-switches.js +0 -5
  110. package/lib/mission-artifact.js +0 -1
  111. package/lib/mission-protected-lane.js +293 -0
  112. package/lib/mission-room.js +0 -2
  113. package/lib/mission-runtime-loop.js +0 -1
  114. package/lib/next-moves.js +0 -18
  115. package/lib/official-cli-integration.js +0 -4
  116. package/lib/orb-context.js +6 -3
  117. package/lib/orb-scorecard.js +0 -1
  118. package/lib/outbound-send-gate.js +0 -6
  119. package/lib/permission-grants.js +0 -6
  120. package/lib/policy-lessons.js +0 -2
  121. package/lib/pulse.js +68 -15
  122. package/lib/review-integrity.js +0 -2
  123. package/lib/router-brain.js +0 -2
  124. package/lib/runner-command.js +0 -4
  125. package/lib/runtime-bootstrap.js +0 -1
  126. package/lib/scorecard.js +0 -1
  127. package/lib/self-drive.js +1 -1
  128. package/lib/state-detection.js +0 -6
  129. package/lib/task-db.js +0 -4
  130. package/lib/team-presence.js +0 -1
  131. package/lib/voice-gate.js +63 -2
  132. package/lib/wiki.js +152 -9
  133. package/lib/wish-audit.js +20 -6
  134. package/lib/wish-delegate.js +26 -20
  135. package/lib/wish-stats.js +0 -5
  136. package/lib/wish-store.js +0 -1
  137. package/lib/workspace-safety.js +0 -2
  138. package/lib/zip.js +0 -1
  139. package/package.json +1 -1
package/commands/play.js CHANGED
@@ -485,6 +485,4 @@ async function playCommand(...args) {
485
485
 
486
486
  module.exports = {
487
487
  playCommand,
488
- modeState,
489
- selectMission,
490
488
  };
package/commands/probe.js CHANGED
@@ -26,6 +26,7 @@
26
26
 
27
27
  const https = require('https');
28
28
  const http = require('http');
29
+ const path = require('path');
29
30
  const { getApiBaseUrl } = require('../utils/api');
30
31
  const { loadCredentials } = require('../utils/auth');
31
32
  const { encodeToolResult } = require('../lib/tool-result-encode');
@@ -188,10 +189,13 @@ function runTerminal(base, token, command, businessId) {
188
189
  // command runs in the mission's OWN workspace directory instead of the hosted
189
190
  // ai-computer. Without this, a mission tick in a local workspace read the
190
191
  // remote computer's files and reported local members (e.g. maze) as missing.
191
- function runLocalTerminal(command, cwd) {
192
+ function runLocalTerminal(command, cwd, environment = process.env, pathPrefix = '') {
192
193
  const { exec } = require('child_process');
193
194
  const { envWithNodeDir } = require('../lib/spawn-env');
194
- const env = envWithNodeDir(process.env);
195
+ const baseEnv = envWithNodeDir(environment);
196
+ const env = pathPrefix
197
+ ? { ...baseEnv, PATH: `${pathPrefix}${path.delimiter}${baseEnv.PATH || ''}` }
198
+ : baseEnv;
195
199
  return new Promise((resolve) => {
196
200
  exec(command, { cwd, env, timeout: 60000, maxBuffer: 4 * 1024 * 1024, shell: '/bin/bash' }, (error, stdout, stderr) => {
197
201
  resolve({
@@ -237,6 +241,8 @@ async function runAtris2Turn(opts = {}) {
237
241
  connectTimeoutMs = 60000,
238
242
  signal = null,
239
243
  localCwd = null,
244
+ localEnv = null,
245
+ localPathPrefix = '',
240
246
  } = opts;
241
247
  const t0 = Date.now();
242
248
  const out = { ok: false, text: '', engine: null, tools_run: 0, cli_ops: [], unsupported: [], error: null, duration_ms: 0 };
@@ -322,7 +328,7 @@ async function runAtris2Turn(opts = {}) {
322
328
  unsupported.push(`file_op:${op}`);
323
329
  } else {
324
330
  const term = localCwd
325
- ? await runLocalTerminal(cmd, localCwd)
331
+ ? await runLocalTerminal(cmd, localCwd, localEnv || process.env, localPathPrefix)
326
332
  : await runTerminal(base, token, cmd, business);
327
333
  const ok = term.exit_code === 0;
328
334
  out = ok
@@ -341,7 +347,7 @@ async function runAtris2Turn(opts = {}) {
341
347
  unsupported.push(`cli_op:${op || '?'}`);
342
348
  } else {
343
349
  const term = localCwd
344
- ? await runLocalTerminal(cmd, localCwd)
350
+ ? await runLocalTerminal(cmd, localCwd, localEnv || process.env, localPathPrefix)
345
351
  : await runTerminal(base, token, cmd, business);
346
352
  out = atrisCliResult(cmd, term);
347
353
  cliOps.push(op || '?');
package/commands/pulse.js CHANGED
@@ -2,16 +2,16 @@
2
2
 
3
3
  // `atris pulse` - the durable overnight self-improvement heartbeat for atris-cli.
4
4
  //
5
- // atris pulse tick run ONE self-improvement tick (what the cron calls)
5
+ // atris pulse tick run ONE self-improvement tick (what launchd calls)
6
6
  // atris pulse status liveness, reward sum, ghost-tick detection
7
- // atris pulse install write the OS-cron tick script + install the crontab line
8
- // atris pulse uninstall remove the crontab line
7
+ // atris pulse install write the tick script + install a launchd agent
8
+ // atris pulse uninstall remove the launchd agent
9
9
  // atris pulse run run N ticks in the foreground (manual / testing)
10
10
  //
11
11
  // One tick: lock -> 'started' receipt -> run mission engine -> verify -> write
12
12
  // 'finished' receipt (pulse_agi_loop_receipts.jsonl) + reward scorecard
13
13
  // (scorecards.jsonl, gated) -> release lock. The whole point is that this fires
14
- // from an OS cron, so it self-improves overnight without Claude Code open.
14
+ // from launchd, so it self-improves overnight without Claude Code open.
15
15
 
16
16
  const fs = require('fs');
17
17
  const os = require('os');
@@ -41,8 +41,8 @@ Durable overnight self-improvement heartbeat.
41
41
  Commands:
42
42
  tick Run one heartbeat tick
43
43
  status Show liveness, reward, and ghost-tick detection
44
- install Install the OS cron heartbeat
45
- uninstall Remove the OS cron heartbeat
44
+ install Install the launchd heartbeat
45
+ uninstall Remove the launchd heartbeat
46
46
  run Run bounded ticks in the foreground
47
47
 
48
48
  Options:
@@ -298,7 +298,12 @@ function tickCommand(args, root = process.cwd()) {
298
298
  }
299
299
 
300
300
  const elapsedMs = Date.now() - startedAt;
301
- const reward = pulse.scoreTick({ verifyPassed: verify.passed, producedWork });
301
+ const reward = pulse.scoreTick({
302
+ verifyPassed: verify.passed,
303
+ producedWork,
304
+ actorOk: engine.ok,
305
+ actorReason: engine.reason,
306
+ });
302
307
  const changedTail = committed
303
308
  ? ' - committed'
304
309
  : (changedFiles.length ? ` - ${changedFiles.length} file(s) changed` : '');
@@ -492,13 +497,21 @@ function statusCommand(args, root = process.cwd(), options = {}) {
492
497
  const slot = pulse.resolvePulseSlot(root, options.homeDir ? { homeDir: options.homeDir } : {});
493
498
  const receipts = pulse.readPulseReceipts(root);
494
499
  const summary = pulse.summarizePulse(receipts);
495
- const installed = cronInstalled(slot.markers, options.spawnSync || spawnSync);
500
+ const label = `com.atris.pulse.${slot.marker.toLowerCase().replace(/^atris_pulse_/, '').replace(/[^a-z0-9]+/g, '-')}`;
501
+ const homeDir = options.homeDir || os.homedir();
502
+ const plistPath = path.join(homeDir, 'Library', 'LaunchAgents', `${label}.plist`);
503
+ const legacyCronInstalled = cronInstalled(slot.markers, options.spawnSync || spawnSync);
504
+ const installed = fs.existsSync(plistPath) || legacyCronInstalled;
496
505
  const expiryBreadcrumb = installed ? null : readPulseExpiryBreadcrumb(root);
497
506
  const writeOutput = options.writeOutput || ((text) => process.stdout.write(text));
498
507
  const out = {
499
508
  ok: true,
500
509
  action: 'pulse_status',
501
510
  cron_installed: installed,
511
+ launchd_installed: fs.existsSync(plistPath),
512
+ legacy_cron_installed: legacyCronInstalled,
513
+ label,
514
+ plist_path: plistPath,
502
515
  expiry_breadcrumb: expiryBreadcrumb,
503
516
  state_home: slot.activeStateHome,
504
517
  marker: slot.activeMarker,
@@ -509,7 +522,7 @@ function statusCommand(args, root = process.cwd(), options = {}) {
509
522
  };
510
523
  if (!asJson) {
511
524
  writeOutput([
512
- `pulse: ${installed ? 'cron installed' : 'cron not installed (run: atris pulse install)'}`,
525
+ `pulse: ${installed ? 'scheduler installed' : 'scheduler not installed (run: atris pulse install)'}`,
513
526
  expiryBreadcrumb ? `heartbeat expired on ${expiryBreadcrumb.expired_at}; renew with: ${expiryBreadcrumb.renew_command}` : '',
514
527
  `state home: ${slot.activeStateHome} marker: ${slot.activeMarker}`,
515
528
  `ticks: ${summary.total_ticks} | reward: ${summary.reward_sum} | verify pass/fail: ${summary.verify_pass}/${summary.verify_fail}`,
@@ -606,8 +619,12 @@ function installCommand(args, root = process.cwd(), options = {}) {
606
619
 
607
620
  fs.mkdirSync(slot.stateHome, { recursive: true });
608
621
  const scriptPath = path.join(slot.stateHome, 'tick.sh');
609
- // Resolve the real bin dirs the engine spawns by bare name, so cron's minimal
610
- // PATH doesn't silently break the worker spawn (claude lives in ~/.local/bin).
622
+ const label = `com.atris.pulse.${slot.marker.toLowerCase().replace(/^atris_pulse_/, '').replace(/[^a-z0-9]+/g, '-')}`;
623
+ const homeDir = options.homeDir || os.homedir();
624
+ const launchAgentsDir = path.join(homeDir, 'Library', 'LaunchAgents');
625
+ const plistPath = path.join(launchAgentsDir, `${label}.plist`);
626
+ // Resolve the real bin dirs the engine spawns by bare name, so launchd's
627
+ // minimal PATH doesn't break the worker spawn (claude lives in ~/.local/bin).
611
628
  const pathDirs = resolveEngineBinDirs([runnerBin], commandSpawn);
612
629
  const script = pulse.buildTickScript({
613
630
  root,
@@ -621,18 +638,26 @@ function installCommand(args, root = process.cwd(), options = {}) {
621
638
  runnerCommandTemplate,
622
639
  verifyCmd,
623
640
  pathDirs,
641
+ launchLabel: label,
642
+ launchPlistPath: plistPath,
624
643
  });
625
644
  fs.writeFileSync(scriptPath, script, { mode: 0o755 });
626
645
 
627
- const line = pulse.buildCrontabLine({ cron, scriptPath, marker: slot.marker });
628
- // Append our line to the existing crontab. Strip this repo's new marker and,
629
- // when the legacy tick.sh belongs to this root, strip the old single-slot marker too.
646
+ fs.mkdirSync(path.join(slot.stateHome, 'logs'), { recursive: true });
647
+ fs.mkdirSync(launchAgentsDir, { recursive: true });
648
+ fs.writeFileSync(plistPath, pulse.buildLaunchAgentPlist({ label, scriptPath, cron, stateHome: slot.stateHome }), 'utf8');
649
+
650
+ // Strip old cron entries while migrating existing installations.
630
651
  const existing = commandSpawn('crontab', ['-l'], { encoding: 'utf8', timeout: 10000 });
631
652
  const prior = existing.status === 0 ? String(existing.stdout || '') : '';
632
653
  const cleaned = stripMarkedCrontabLines(prior, slot.markers);
633
- const next = `${cleaned ? cleaned + '\n' : ''}${line}\n`;
634
- const apply = commandSpawn('crontab', ['-'], { input: next, encoding: 'utf8', timeout: 10000 });
654
+ const legacyHad = slot.markers.some((marker) => prior.includes(marker));
655
+ if (existing.status === 0 && legacyHad) commandSpawn('crontab', ['-'], { input: cleaned ? `${cleaned}\n` : '', encoding: 'utf8', timeout: 10000 });
656
+ const uid = options.uid != null ? options.uid : process.getuid();
657
+ commandSpawn('launchctl', ['bootout', `gui/${uid}/${label}`], { encoding: 'utf8', timeout: 10000 });
658
+ const apply = commandSpawn('launchctl', ['bootstrap', `gui/${uid}`, plistPath], { encoding: 'utf8', timeout: 10000 });
635
659
  const expiryBreadcrumbCleared = apply.status === 0 && clearPulseExpiryBreadcrumb(root);
660
+ const cadenceMapping = pulse.launchIntervalFromCadence({ cron });
636
661
 
637
662
  const out = {
638
663
  ok: apply.status === 0,
@@ -641,7 +666,11 @@ function installCommand(args, root = process.cwd(), options = {}) {
641
666
  state_home: slot.stateHome,
642
667
  marker: slot.marker,
643
668
  legacy_migrated: slot.legacyMatches,
644
- crontab_line: line,
669
+ label,
670
+ plist_path: plistPath,
671
+ crontab_line: null,
672
+ removed_legacy_crontab: legacyHad,
673
+ cadence_fallback: cadenceMapping.fallback,
645
674
  cadence: cron,
646
675
  cadence_input: cadenceInput,
647
676
  expires_in_days: expiry.days,
@@ -656,46 +685,49 @@ function installCommand(args, root = process.cwd(), options = {}) {
656
685
  if (!asJson) {
657
686
  if (apply.status === 0) {
658
687
  writeOutput([
659
- `pulse installed. heartbeat fires '${cron}' against ${root}.`,
688
+ `pulse installed. launchd heartbeat fires '${cron}' against ${root}.`,
689
+ `label: ${label}`,
690
+ `plist: ${plistPath}`,
660
691
  `script: ${scriptPath}`,
692
+ cadenceMapping.fallback ? `cadence '${cron}' is not directly mappable; using 3600 seconds.` : '',
661
693
  `auto-expires in ${expiry.hours ? `${expiry.hours} hours` : `${expiry.days} days`}. stop early: atris pulse uninstall`,
662
- ].join('\n') + '\n');
694
+ ].filter(Boolean).join('\n') + '\n');
663
695
  } else {
664
- writeOutput(`pulse install failed to write crontab: ${apply.stderr || apply.status}\nscript written to ${scriptPath}; add this line to your crontab manually:\n${line}\n`);
696
+ writeOutput(`pulse install failed to bootstrap ${label}: ${apply.stderr || apply.status}\nplist: ${plistPath}\n`);
665
697
  }
666
698
  }
667
699
  return emit(out, asJson);
668
700
  }
669
701
 
670
- function uninstallCommand(args, root = process.cwd()) {
702
+ function uninstallCommand(args, root = process.cwd(), options = {}) {
671
703
  const asJson = wantsJson(args);
672
- const slot = pulse.resolvePulseSlot(root);
673
- const existing = spawnSync('crontab', ['-l'], { encoding: 'utf8', timeout: 10000 });
674
- if (existing.status !== 0) {
675
- const out = {
676
- ok: true,
677
- action: 'pulse_uninstall',
678
- removed: false,
679
- reason: 'no_crontab',
680
- state_home: slot.activeStateHome,
681
- marker: slot.activeMarker,
682
- };
683
- if (!asJson) process.stdout.write('pulse: no crontab to clean.\n');
684
- return emit(out, asJson);
685
- }
704
+ const slot = pulse.resolvePulseSlot(root, options.homeDir ? { homeDir: options.homeDir } : {});
705
+ const commandSpawn = options.spawnSync || spawnSync;
706
+ const homeDir = options.homeDir || os.homedir();
707
+ const label = `com.atris.pulse.${slot.marker.toLowerCase().replace(/^atris_pulse_/, '').replace(/[^a-z0-9]+/g, '-')}`;
708
+ const plistPath = path.join(homeDir, 'Library', 'LaunchAgents', `${label}.plist`);
709
+ const uid = options.uid != null ? options.uid : process.getuid();
710
+ commandSpawn('launchctl', ['bootout', `gui/${uid}/${label}`], { encoding: 'utf8', timeout: 10000 });
711
+ const plistHad = fs.existsSync(plistPath);
712
+ fs.rmSync(plistPath, { force: true });
713
+ const existing = commandSpawn('crontab', ['-l'], { encoding: 'utf8', timeout: 10000 });
686
714
  const prior = String(existing.stdout || '');
687
715
  const had = slot.markers.some((marker) => prior.includes(marker));
688
716
  const cleaned = stripMarkedCrontabLines(prior, slot.markers);
689
- const apply = spawnSync('crontab', ['-'], { input: cleaned ? cleaned + '\n' : '', encoding: 'utf8', timeout: 10000 });
717
+ let apply = { status: 0 };
718
+ if (existing.status === 0 && had) apply = commandSpawn('crontab', ['-'], { input: cleaned ? cleaned + '\n' : '', encoding: 'utf8', timeout: 10000 });
690
719
  const out = {
691
720
  ok: apply.status === 0,
692
721
  action: 'pulse_uninstall',
693
- removed: had,
722
+ removed: plistHad || had,
723
+ label,
724
+ plist_path: plistPath,
725
+ removed_legacy_crontab: had,
694
726
  state_home: slot.activeStateHome,
695
727
  marker: slot.activeMarker,
696
728
  legacy_slot: slot.legacyMatches,
697
729
  };
698
- if (!asJson) process.stdout.write(had ? 'pulse uninstalled (crontab line removed).\n' : 'pulse: no heartbeat line found.\n');
730
+ if (!asJson) process.stdout.write(plistHad || had ? `pulse uninstalled (${label}).\n` : 'pulse: no heartbeat agent found.\n');
699
731
  return emit(out, asJson);
700
732
  }
701
733
 
@@ -717,6 +749,9 @@ function pulseCommand(argv = []) {
717
749
  const sub = argv[0];
718
750
  const rest = argv.slice(1);
719
751
  if (sub === '--help' || sub === '-h' || sub === 'help') return showHelp();
752
+ // `pulse <sub> --help` is a help request, not a run — never execute the
753
+ // subcommand (tick/status/etc. have real side effects) just to show usage.
754
+ if (rest.includes('--help') || rest.includes('-h')) return showHelp();
720
755
  switch (sub) {
721
756
  case 'tick':
722
757
  return tickCommand(rest);
@@ -740,21 +775,11 @@ function pulseCommand(argv = []) {
740
775
 
741
776
  module.exports = {
742
777
  pulseCommand,
743
- tickCommand,
744
778
  statusCommand,
745
779
  cronInstalled,
746
780
  crontabHasActiveMarker,
747
781
  pulseExpiryBreadcrumbPath,
748
- readPulseExpiryBreadcrumb,
749
- clearPulseExpiryBreadcrumb,
750
782
  installCommand,
751
- uninstallCommand,
752
783
  runCommand,
753
- runMissionEngine,
754
- runAutopilotTick,
755
- runEngine,
756
- gitChangedFiles,
757
- runVerify,
758
784
  refreshOrbPolicyLessons,
759
- stripMarkedCrontabLines,
760
785
  };
package/commands/push.js CHANGED
@@ -1055,7 +1055,6 @@ module.exports = {
1055
1055
  buildPushUploadBatches,
1056
1056
  shouldRetrySyncIndividually,
1057
1057
  resolvePushSourceDir,
1058
- canonicalWorkspaceRoot,
1059
1058
  basenameOfManifestPath,
1060
1059
  buildCloudHashMap,
1061
1060
  isBusinessWorkspaceRoot,
@@ -1066,9 +1065,6 @@ module.exports = {
1066
1065
  resolveChangedScopePaths,
1067
1066
  mergePushScopes,
1068
1067
  analyzePushSafety,
1069
- isNestedWorkspacePollutionPath,
1070
- isSyncReviewArtifactPath,
1071
- renderPushSafetyBlock,
1072
1068
  renderDriftBlock,
1073
1069
  needsForceBroadWorkspaceConfirm,
1074
1070
  promptConfirm,
package/commands/radar.js CHANGED
@@ -396,16 +396,21 @@ function loadLoop(missions, root, deps) {
396
396
  const events = readJsonLines(path.join(root, '.atris', 'state', 'mission_events.jsonl'), deps.readFile, deps.exists);
397
397
  const codexGoal = readJsonFile(path.join(root, '.atris', 'state', 'codex_goal.json'), deps, {}) || {};
398
398
  const tickEvents = events.filter(event => event.type === 'mission_tick');
399
+ // Live missions sit in planning/ready/running; counting only 'running'
400
+ // reported 0 while a worker was mid-flight (CLI-1183, seen live 2026-07-25).
401
+ const ACTIVE_MISSION_STATUSES = new Set(['planning', 'running', 'ready']);
402
+ const isActive = (mission) => ACTIVE_MISSION_STATUSES.has(String(mission.status || ''));
399
403
  return {
400
- running: missions.filter(mission => mission.status === 'running').length,
404
+ running: missions.filter(isActive).length,
405
+ paused: missions.filter(mission => mission.status === 'paused').length,
401
406
  always_on: missions.filter(mission => mission.always_on).length,
402
407
  stale: missions.filter(mission => mission.stale).length,
403
- no_verifier: missions.filter(mission => mission.status === 'running' && !mission.verifier).length,
408
+ no_verifier: missions.filter(mission => isActive(mission) && !mission.verifier).length,
404
409
  ticks: tickEvents.length,
405
410
  last_event_at: events[events.length - 1]?.at || null,
406
411
  codex_goal: codexGoal.goal?.objective || null,
407
412
  codex_goal_mission: codexGoal.goal?.mission_id || null,
408
- infinite_loop_risk: missions.some(mission => mission.stale || (mission.status === 'running' && !mission.verifier)),
413
+ infinite_loop_risk: missions.some(mission => mission.stale || (isActive(mission) && !mission.verifier)),
409
414
  };
410
415
  }
411
416
 
@@ -735,7 +740,11 @@ function summarize(tasks, missions, worktrees, agents) {
735
740
  review: count(tasks, t => t.status === 'review'),
736
741
  certifiedReview: count(tasks, t => t.status === 'review' && t.metadata && t.metadata.agent_certified),
737
742
  },
738
- missions: { running: count(missions, m => m.status === 'running'), stale: count(missions, m => m.stale) },
743
+ missions: {
744
+ running: count(missions, m => ['planning', 'running', 'ready'].includes(String(m.status || ''))),
745
+ paused: count(missions, m => m.status === 'paused'),
746
+ stale: count(missions, m => m.stale),
747
+ },
739
748
  worktrees: { total: worktrees.length, dirty: count(worktrees, w => Number(w.dirty) > 0) },
740
749
  };
741
750
  }
@@ -832,7 +841,7 @@ function renderRadar(data) {
832
841
  lines.push('');
833
842
  lines.push(`Agents: ${s.agents.active}/${s.agents.total} active`);
834
843
  lines.push(`Tasks: ${s.tasks.open} open, ${s.tasks.claimed} claimed, ${s.tasks.review} review (${s.tasks.certifiedReview} certified)`);
835
- lines.push(`Missions: ${s.missions.running} running, ${s.missions.stale} stale/no-verifier`);
844
+ lines.push(`Missions: ${s.missions.running} active, ${s.missions.paused || 0} paused, ${s.missions.stale} stale/no-verifier`);
836
845
  lines.push(`Worktrees: ${s.worktrees.total} registered, ${s.worktrees.dirty} dirty`);
837
846
  lines.push(`Next: ${data.next_action}`);
838
847
  if (data.os) {
@@ -1188,16 +1197,10 @@ function radarCommand(args = [], options = {}) {
1188
1197
  module.exports = {
1189
1198
  agentTypeForCommand,
1190
1199
  agentTopPayload,
1191
- branchIdentityAtCwd,
1192
1200
  collectRadar,
1193
- isInteractiveClaudeCommand,
1194
- loadAgentWorktreeSidecar,
1195
1201
  parsePsOutput,
1196
- parseSessionIdFromCommand,
1197
1202
  parseWorktrees,
1198
1203
  radarCommand,
1199
1204
  renderAgentTop,
1200
1205
  renderRadar,
1201
- resolveAgentTaskBinding,
1202
- taskForOwner,
1203
1206
  };