@phnx-labs/agents-cli 1.22.47 → 1.22.50

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 (272) hide show
  1. package/CHANGELOG.md +503 -17
  2. package/README.md +76 -32
  3. package/dist/bootstrap.js +4 -2
  4. package/dist/cli/command-registry.d.ts +1 -0
  5. package/dist/cli/command-registry.js +2 -0
  6. package/dist/commands/accounts.d.ts +58 -0
  7. package/dist/commands/accounts.js +201 -39
  8. package/dist/commands/artifacts.js +7 -6
  9. package/dist/commands/attach.js +1 -1
  10. package/dist/commands/browser.js +501 -103
  11. package/dist/commands/config.d.ts +0 -1
  12. package/dist/commands/config.js +0 -20
  13. package/dist/commands/daemon-test-harness.d.ts +42 -0
  14. package/dist/commands/daemon-test-harness.js +90 -0
  15. package/dist/commands/daemon.d.ts +21 -0
  16. package/dist/commands/daemon.js +147 -12
  17. package/dist/commands/device-config-test-harness.d.ts +11 -0
  18. package/dist/commands/device-config-test-harness.js +59 -0
  19. package/dist/commands/exec.js +76 -16
  20. package/dist/commands/focus.d.ts +14 -0
  21. package/dist/commands/focus.js +48 -5
  22. package/dist/commands/go.d.ts +3 -0
  23. package/dist/commands/go.js +16 -3
  24. package/dist/commands/inspect.js +2 -2
  25. package/dist/commands/lease.d.ts +1 -1
  26. package/dist/commands/lease.js +4 -3
  27. package/dist/commands/mailboxes.js +4 -3
  28. package/dist/commands/menubar.js +11 -4
  29. package/dist/commands/projects.d.ts +16 -1
  30. package/dist/commands/projects.js +58 -64
  31. package/dist/commands/resume.js +1 -1
  32. package/dist/commands/route.js +4 -4
  33. package/dist/commands/routines.js +3 -2
  34. package/dist/commands/run-broadcast.d.ts +1 -1
  35. package/dist/commands/run-broadcast.js +1 -1
  36. package/dist/commands/sessions-picker.d.ts +22 -0
  37. package/dist/commands/sessions-picker.js +48 -9
  38. package/dist/commands/sessions-trace.js +1 -1
  39. package/dist/commands/sessions.js +1 -0
  40. package/dist/commands/setup-secrets.js +23 -0
  41. package/dist/commands/share.d.ts +20 -5
  42. package/dist/commands/share.js +158 -53
  43. package/dist/commands/ssh.js +54 -7
  44. package/dist/commands/teams.d.ts +12 -0
  45. package/dist/commands/teams.js +44 -67
  46. package/dist/commands/traces.d.ts +2 -0
  47. package/dist/commands/traces.js +188 -0
  48. package/dist/commands/view.d.ts +1 -1
  49. package/dist/commands/view.js +17 -34
  50. package/dist/index.d.ts +1 -0
  51. package/dist/index.js +5 -0
  52. package/dist/lib/account-provider-registry.d.ts +9 -0
  53. package/dist/lib/account-provider-registry.js +20 -0
  54. package/dist/lib/accounting/account-pool-collect.d.ts +34 -0
  55. package/dist/lib/accounting/account-pool-collect.js +71 -0
  56. package/dist/lib/accounting/account-pool.d.ts +46 -0
  57. package/dist/lib/accounting/account-pool.js +33 -0
  58. package/dist/lib/accounting/capacity.d.ts +24 -0
  59. package/dist/lib/accounting/capacity.js +30 -0
  60. package/dist/lib/accounting/rotate.d.ts +11 -18
  61. package/dist/lib/accounting/rotate.js +6 -24
  62. package/dist/lib/accounting/usage.d.ts +14 -3
  63. package/dist/lib/accounting/usage.js +64 -18
  64. package/dist/lib/agent-spec/agents.js +11 -9
  65. package/dist/lib/browser/drivers/ssh.d.ts +16 -2
  66. package/dist/lib/browser/drivers/ssh.js +36 -9
  67. package/dist/lib/browser/ipc.js +8 -3
  68. package/dist/lib/browser/profiles.d.ts +35 -101
  69. package/dist/lib/browser/profiles.js +130 -301
  70. package/dist/lib/browser/registry.d.ts +39 -0
  71. package/dist/lib/browser/registry.js +149 -0
  72. package/dist/lib/browser/remote-control.d.ts +8 -6
  73. package/dist/lib/browser/remote-control.js +8 -6
  74. package/dist/lib/browser/resolve-target.d.ts +68 -0
  75. package/dist/lib/browser/resolve-target.js +250 -0
  76. package/dist/lib/browser/runtime-state.d.ts +40 -16
  77. package/dist/lib/browser/runtime-state.js +92 -25
  78. package/dist/lib/browser/service.d.ts +28 -9
  79. package/dist/lib/browser/service.js +139 -62
  80. package/dist/lib/browser/task-index.d.ts +70 -0
  81. package/dist/lib/browser/task-index.js +180 -0
  82. package/dist/lib/byok-usage.js +2 -2
  83. package/dist/lib/claude-statusline.d.ts +29 -0
  84. package/dist/lib/claude-statusline.js +139 -0
  85. package/dist/lib/cloud/host.d.ts +1 -1
  86. package/dist/lib/cloud/host.js +1 -1
  87. package/dist/lib/cloud/rush.js +1 -1
  88. package/dist/lib/computer/computer-rpc.js +2 -2
  89. package/dist/lib/computer/download.d.ts +15 -15
  90. package/dist/lib/computer/download.js +35 -98
  91. package/dist/lib/computer/ssh-tunnel.d.ts +0 -3
  92. package/dist/lib/computer/ssh-tunnel.js +12 -22
  93. package/dist/lib/config-keys.d.ts +2 -8
  94. package/dist/lib/config-keys.js +2 -13
  95. package/dist/lib/crabbox/cli.d.ts +1 -1
  96. package/dist/lib/crabbox/cli.js +2 -2
  97. package/dist/lib/crabbox/lease.d.ts +9 -0
  98. package/dist/lib/crabbox/lease.js +70 -15
  99. package/dist/lib/crabbox/runtimes.js +1 -1
  100. package/dist/lib/daemon/account-state-daemon-service.d.ts +16 -0
  101. package/dist/lib/daemon/account-state-daemon-service.js +26 -0
  102. package/dist/lib/daemon/browser-ipc-service.d.ts +20 -0
  103. package/dist/lib/daemon/browser-ipc-service.js +46 -0
  104. package/dist/lib/daemon/daemon.d.ts +3 -0
  105. package/dist/lib/daemon/daemon.js +212 -444
  106. package/dist/lib/daemon/device-probe-service.d.ts +26 -0
  107. package/dist/lib/daemon/device-probe-service.js +47 -0
  108. package/dist/lib/daemon/keychain-reap-service.d.ts +17 -0
  109. package/dist/lib/daemon/keychain-reap-service.js +32 -0
  110. package/dist/lib/daemon/monitor-engine-service.d.ts +22 -0
  111. package/dist/lib/daemon/monitor-engine-service.js +29 -0
  112. package/dist/lib/daemon/secrets-broker-service.d.ts +20 -0
  113. package/dist/lib/daemon/secrets-broker-service.js +66 -0
  114. package/dist/lib/daemon/self-heal-service.d.ts +27 -0
  115. package/dist/lib/daemon/self-heal-service.js +48 -0
  116. package/dist/lib/daemon/service.d.ts +102 -0
  117. package/dist/lib/daemon/service.js +98 -0
  118. package/dist/lib/daemon/session-index-service.d.ts +22 -0
  119. package/dist/lib/daemon/session-index-service.js +40 -0
  120. package/dist/lib/daemon/state-dir-check-service.d.ts +43 -0
  121. package/dist/lib/daemon/state-dir-check-service.js +63 -0
  122. package/dist/lib/daemon/supervisor.d.ts +87 -0
  123. package/dist/lib/daemon/supervisor.js +298 -0
  124. package/dist/lib/daemon/watchdog-service.d.ts +19 -0
  125. package/dist/lib/daemon/watchdog-service.js +40 -0
  126. package/dist/lib/daemon-health.d.ts +15 -0
  127. package/dist/lib/daemon-health.js +29 -5
  128. package/dist/lib/daemon-services.d.ts +8 -1
  129. package/dist/lib/daemon-services.js +55 -0
  130. package/dist/lib/daemon-ticks.d.ts +13 -2
  131. package/dist/lib/daemon-ticks.js +20 -18
  132. package/dist/lib/device-config.d.ts +0 -2
  133. package/dist/lib/device-config.js +1 -23
  134. package/dist/lib/devices/registry.d.ts +1 -1
  135. package/dist/lib/devices/registry.js +1 -1
  136. package/dist/lib/devices/stats-cache.d.ts +20 -0
  137. package/dist/lib/devices/stats-cache.js +49 -5
  138. package/dist/lib/devices/worker-pick.d.ts +48 -0
  139. package/dist/lib/devices/worker-pick.js +113 -0
  140. package/dist/lib/exec.d.ts +67 -13
  141. package/dist/lib/exec.js +85 -29
  142. package/dist/lib/harness/adapters/cursor.js +8 -11
  143. package/dist/lib/helper-download.d.ts +108 -0
  144. package/dist/lib/helper-download.js +194 -0
  145. package/dist/lib/helper-versions.d.ts +52 -0
  146. package/dist/lib/helper-versions.js +58 -0
  147. package/dist/lib/hooks/cache.js +11 -0
  148. package/dist/lib/hooks/install.js +2 -2
  149. package/dist/lib/hosts/credentials.d.ts +1 -1
  150. package/dist/lib/hosts/credentials.js +1 -1
  151. package/dist/lib/hosts/dispatch.d.ts +7 -1
  152. package/dist/lib/hosts/dispatch.js +24 -5
  153. package/dist/lib/hosts/passthrough.d.ts +8 -15
  154. package/dist/lib/hosts/passthrough.js +19 -16
  155. package/dist/lib/hosts/providers/devices.d.ts +1 -1
  156. package/dist/lib/hosts/providers/devices.js +1 -1
  157. package/dist/lib/hosts/reconnect.d.ts +179 -21
  158. package/dist/lib/hosts/reconnect.js +266 -45
  159. package/dist/lib/installations/migrate.d.ts +4 -5
  160. package/dist/lib/installations/migrate.js +6 -7
  161. package/dist/lib/installations/shims.d.ts +1 -1
  162. package/dist/lib/installations/shims.js +23 -13
  163. package/dist/lib/installations/store.js +6 -3
  164. package/dist/lib/installations/versions.js +73 -48
  165. package/dist/lib/linear-autoclose.d.ts +1 -1
  166. package/dist/lib/linear-autoclose.js +1 -1
  167. package/dist/lib/loop.d.ts +2 -2
  168. package/dist/lib/loop.js +1 -1
  169. package/dist/lib/menubar/download-menubar.d.ts +47 -0
  170. package/dist/lib/menubar/download-menubar.js +60 -0
  171. package/dist/lib/menubar/install-menubar.d.ts +31 -7
  172. package/dist/lib/menubar/install-menubar.js +79 -21
  173. package/dist/lib/menubar/notify-desktop.d.ts +3 -3
  174. package/dist/lib/menubar/notify-desktop.js +6 -6
  175. package/dist/lib/model-tiers.d.ts +1 -1
  176. package/dist/lib/project-probe.d.ts +1 -1
  177. package/dist/lib/project-probe.js +1 -1
  178. package/dist/lib/project-pull.d.ts +1 -1
  179. package/dist/lib/project-pull.js +2 -2
  180. package/dist/lib/projects.d.ts +1 -1
  181. package/dist/lib/projects.js +1 -1
  182. package/dist/lib/sandbox.js +6 -7
  183. package/dist/lib/secrets/download-keychain.d.ts +47 -0
  184. package/dist/lib/secrets/download-keychain.js +70 -0
  185. package/dist/lib/secrets/install-helper.d.ts +20 -1
  186. package/dist/lib/secrets/install-helper.js +44 -3
  187. package/dist/lib/session/active.js +17 -1
  188. package/dist/lib/session/db.d.ts +11 -1
  189. package/dist/lib/session/db.js +138 -6
  190. package/dist/lib/session/discover.d.ts +21 -1
  191. package/dist/lib/session/discover.js +33 -5
  192. package/dist/lib/session/highlights.d.ts +49 -1
  193. package/dist/lib/session/highlights.js +95 -0
  194. package/dist/lib/session/host-link.d.ts +16 -2
  195. package/dist/lib/session/host-link.js +7 -1
  196. package/dist/lib/session/parse.js +33 -1
  197. package/dist/lib/session/remote/remote-list.d.ts +9 -0
  198. package/dist/lib/session/remote/remote-list.js +19 -2
  199. package/dist/lib/session/remote/watch.js +11 -2
  200. package/dist/lib/session/session-cache.d.ts +53 -0
  201. package/dist/lib/session/session-cache.js +88 -0
  202. package/dist/lib/session/state.js +1 -1
  203. package/dist/lib/session/tool-calls.d.ts +2 -0
  204. package/dist/lib/session/tool-calls.js +1 -1
  205. package/dist/lib/session/trajectory-html.d.ts +5 -1
  206. package/dist/lib/session/trajectory-html.js +42 -22
  207. package/dist/lib/session/trajectory-text.js +9 -2
  208. package/dist/lib/session/trajectory.js +53 -4
  209. package/dist/lib/session/types.d.ts +15 -0
  210. package/dist/lib/sha256-asset.d.ts +26 -0
  211. package/dist/lib/sha256-asset.js +41 -0
  212. package/dist/lib/share/backend.d.ts +95 -0
  213. package/dist/lib/share/backend.js +139 -0
  214. package/dist/lib/share/delete.d.ts +6 -2
  215. package/dist/lib/share/delete.js +8 -9
  216. package/dist/lib/share/html.d.ts +22 -0
  217. package/dist/lib/share/html.js +88 -0
  218. package/dist/lib/share/provision.d.ts +18 -4
  219. package/dist/lib/share/provision.js +38 -4
  220. package/dist/lib/share/publish.d.ts +44 -15
  221. package/dist/lib/share/publish.js +58 -26
  222. package/dist/lib/share/worker-template.js +402 -25
  223. package/dist/lib/sqlite.js +16 -3
  224. package/dist/lib/ssh-exec.d.ts +65 -0
  225. package/dist/lib/ssh-exec.js +120 -4
  226. package/dist/lib/startup/command-registry.js +1 -1
  227. package/dist/lib/startup/dev-build.js +3 -3
  228. package/dist/lib/state.d.ts +1 -1
  229. package/dist/lib/state.js +7 -3
  230. package/dist/lib/subagents-registry.js +1 -1
  231. package/dist/lib/teams/agents.d.ts +14 -12
  232. package/dist/lib/teams/agents.js +188 -73
  233. package/dist/lib/teams/api.d.ts +6 -1
  234. package/dist/lib/teams/api.js +2 -0
  235. package/dist/lib/teams/scheduler.d.ts +7 -0
  236. package/dist/lib/teams/scheduler.js +26 -8
  237. package/dist/lib/terminal/index.d.ts +1 -1
  238. package/dist/lib/terminal/types.d.ts +1 -1
  239. package/dist/lib/terminal/types.js +1 -1
  240. package/dist/lib/tmux/orphan-reap.js +1 -1
  241. package/dist/lib/tmux/session.js +31 -0
  242. package/dist/lib/traces/backend.d.ts +29 -0
  243. package/dist/lib/traces/backend.js +46 -0
  244. package/dist/lib/traces/classify.d.ts +52 -0
  245. package/dist/lib/traces/classify.js +72 -0
  246. package/dist/lib/traces/config.d.ts +10 -0
  247. package/dist/lib/traces/config.js +2 -0
  248. package/dist/lib/traces/provision.d.ts +19 -0
  249. package/dist/lib/traces/provision.js +30 -0
  250. package/dist/lib/traces/sync.d.ts +150 -0
  251. package/dist/lib/traces/sync.js +459 -0
  252. package/dist/lib/traces/worker-template.d.ts +4 -0
  253. package/dist/lib/traces/worker-template.js +159 -0
  254. package/dist/lib/types.d.ts +18 -19
  255. package/dist/lib/types.js +14 -0
  256. package/dist/lib/usage-refresh.d.ts +1 -1
  257. package/dist/lib/usage-refresh.js +11 -8
  258. package/dist/lib/workflows.d.ts +1 -1
  259. package/package.json +4 -6
  260. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  261. package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +0 -24
  262. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  263. package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
  264. package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +0 -128
  265. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  266. package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -26
  267. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  268. package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
  269. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +0 -135
  270. package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
  271. package/dist/lib/usage-fleet.d.ts +0 -32
  272. package/dist/lib/usage-fleet.js +0 -125
@@ -364,17 +364,21 @@ export function wireCloudDispatcher(mgr) {
364
364
  throw new Error(`Teammate ${a.agentId} has no cloud provider set`);
365
365
  }
366
366
  const prov = resolveProvider(a.cloudProvider);
367
- const dispatchOpts = {
368
- prompt: a.prompt,
369
- agent: a.agentType,
370
- repo: a.cloudRepo ?? undefined,
371
- branch: a.cloudBranch ?? undefined,
372
- model: a.model ?? undefined,
373
- };
367
+ const dispatchOpts = cloudDispatchOptions(a);
374
368
  const cloudTask = await prov.dispatch(dispatchOpts);
375
369
  return { cloudSessionId: cloudTask.id };
376
370
  });
377
371
  }
372
+ export function cloudDispatchOptions(agent) {
373
+ return {
374
+ prompt: agent.prompt,
375
+ agent: agent.agentType,
376
+ repo: agent.cloudRepo ?? undefined,
377
+ branch: agent.cloudBranch ?? undefined,
378
+ model: agent.model ?? undefined,
379
+ env: shareRuntimeEnv(),
380
+ };
381
+ }
378
382
  /**
379
383
  * Advisory: warn once per agent type of any still-pending (staged `--after`)
380
384
  * teammate whose CLI may not be signed in. Warn-only — never blocks `start`.
@@ -446,20 +450,32 @@ async function warnUnsignedTeammates(mgr, team) {
446
450
  }
447
451
  }
448
452
  }
449
- /** Single-wave start used by `teams start` without --watch. */
450
- async function runOneWave(mgr, team, json) {
453
+ /**
454
+ * Single-wave start used by `teams start` without --watch. startReady()
455
+ * terminalizes a placement/spawn/cloud/dependency failure to FAILED with
456
+ * evidence rather than leaving it PENDING, so a teammate that failed this wave
457
+ * appears in neither `launched` nor `still_pending` — it must be diffed against
458
+ * the pre-wave roster and reported explicitly, or the wave reads as a success.
459
+ * A wave that only produced failures sets a non-zero exit code.
460
+ */
461
+ export async function runOneWave(mgr, team, json) {
462
+ const preWavePending = new Set((await mgr.listByTask(team)).filter((a) => a.status === 'pending').map((a) => a.agentId));
451
463
  const launched = await mgr.startReady(team);
452
464
  const all = await mgr.listByTask(team);
453
465
  const stillPending = all.filter((a) => a.status === 'pending');
466
+ const failed = all.filter((a) => a.status === 'failed' && preWavePending.has(a.agentId));
467
+ if (failed.length > 0 && launched.length === 0)
468
+ process.exitCode = 1;
454
469
  if (json) {
455
470
  console.log(JSON.stringify({
456
471
  team,
457
472
  launched: launched.map((a) => ({ agent_id: a.agentId, name: a.name, after: a.after })),
458
473
  still_pending: stillPending.map((a) => ({ agent_id: a.agentId, name: a.name, after: a.after })),
474
+ failed: failed.map((a) => ({ agent_id: a.agentId, name: a.name, after: a.after, failure: a.failure })),
459
475
  }, null, 2));
460
476
  return;
461
477
  }
462
- if (launched.length === 0 && stillPending.length === 0) {
478
+ if (launched.length === 0 && stillPending.length === 0 && failed.length === 0) {
463
479
  console.log(chalk.gray(`No pending teammates in team ${team}.`));
464
480
  return;
465
481
  }
@@ -471,6 +487,16 @@ async function runOneWave(mgr, team, json) {
471
487
  console.log(` ${chalk.cyan(h)} ${who}`);
472
488
  }
473
489
  }
490
+ if (failed.length > 0) {
491
+ if (launched.length > 0)
492
+ console.log();
493
+ console.log(chalk.red(`Failed this wave (${failed.length}):`));
494
+ for (const a of failed) {
495
+ const h = a.name || shortId(a.agentId);
496
+ const why = a.failure ? `${a.failure.code}: ${a.failure.message}` : 'no failure evidence recorded';
497
+ console.log(` ${chalk.red(h)} ${why}`);
498
+ }
499
+ }
474
500
  if (stillPending.length > 0) {
475
501
  console.log();
476
502
  console.log(chalk.gray(`Still pending (${stillPending.length}):`));
@@ -710,6 +736,9 @@ function printAgentDetail(a, session) {
710
736
  if (a.host) {
711
737
  console.log(` ${chalk.gray('host ')} ${chalk.cyan(a.host)}`);
712
738
  }
739
+ if (a.failure) {
740
+ console.log(` ${chalk.red('failure ')} ${a.failure.code}: ${a.failure.message}`);
741
+ }
713
742
  // If the agent's internal session id differs from ours (non-Claude), show
714
743
  // it as a hint for `agents sessions <id>`.
715
744
  if (a.remote_session_id && a.remote_session_id !== a.agent_id) {
@@ -798,6 +827,9 @@ function printAgentSummary(s) {
798
827
  const tools = chalk.gray(` · ${s.tool_count} tools`);
799
828
  const hostBadge = s.host ? chalk.gray(' · on ') + chalk.cyan(s.host) : '';
800
829
  console.log(` ${chalk.cyan(handle.padEnd(14))} ${ident.padEnd(11)} ${label}${duration}${tools}${hostBadge}${errBadge}`);
830
+ if (s.failure) {
831
+ console.log(` ${chalk.red('failure ')} ${s.failure.code}: ${s.failure.message}`);
832
+ }
801
833
  // Files: counts + basenames. Read is count only.
802
834
  const fileLines = [];
803
835
  const renderCat = (label, cat) => {
@@ -1856,36 +1888,8 @@ export function registerTeamsCommands(program) {
1856
1888
  return { cloudSessionId: cloudTask.id };
1857
1889
  });
1858
1890
  }
1859
- let cloudSessionId = null;
1891
+ const cloudSessionId = null;
1860
1892
  const isStaged = after.length > 0;
1861
- if (cloudProviderId && !isStaged) {
1862
- // Ready to run now: dispatch to the cloud provider before registering
1863
- // the teammate so we have the remote session id up front.
1864
- //
1865
- // resolveProvider() and shareRuntimeEnv() are INSIDE the try on purpose:
1866
- // both read config (the cloud registry; `agents.yaml`'s share block) and
1867
- // both throw on a malformed one, and by this point the worktree already
1868
- // exists — leaving them outside stranded the branch with no catch at all.
1869
- try {
1870
- const prov = resolveProvider(cloudProviderId);
1871
- const dispatchOpts = {
1872
- prompt: effectiveTask,
1873
- agent,
1874
- repo: opts.repo,
1875
- branch: opts.branch,
1876
- model: opts.model,
1877
- env: shareRuntimeEnv(),
1878
- };
1879
- const cloudTask = await prov.dispatch(dispatchOpts);
1880
- cloudSessionId = cloudTask.id;
1881
- }
1882
- catch (err) {
1883
- // Same orphan class as a failed spawn: this add already created its
1884
- // worktree, so a dispatch failure must not strand the branch either.
1885
- await tearDownCreatedWorktree();
1886
- dieFriction('teams', 'cloud-dispatch-failed', `Cloud dispatch failed: ${err.message}`);
1887
- }
1888
- }
1889
1893
  try {
1890
1894
  const result = await handleSpawn(mgr, team, agent, effectiveTask, cwd, opts.mode, opts.effort, null, cwd, version, opts.name ?? null, after, opts.model ?? null, envOverrides ?? null, taskType, cloudProviderId, cloudSessionId, opts.repo ?? null, opts.branch ?? null, worktreeName, worktreePath, profileName, hostName, hostTarget, hostRepoPath, teamMeta?.project ?? null);
1891
1895
  emit('teams.add', { module: 'teams', team, agent, name: result.name, agent_id: result.agent_id, status: result.status });
@@ -2057,7 +2061,7 @@ export function registerTeamsCommands(program) {
2057
2061
  });
2058
2062
  // start — fire any staged teammates whose --after deps have all completed
2059
2063
  addHostOption(teams.command('start [team]'))
2060
- .description('Launch any pending teammates whose --after dependencies are satisfied. Use --watch to keep draining the DAG as teammates finish and as new tasks are added mid-flight.')
2064
+ .description('Launch ready teammates independently. Failed launch/placement and blocked --after dependencies persist in status; --watch keeps draining viable DAG branches.')
2061
2065
  .option('--json', 'Output machine-readable JSON')
2062
2066
  .option('--watch', 'Keep running: poll every --interval seconds, fire new waves, exit when the DAG drains.')
2063
2067
  .option('--interval <seconds>', 'Seconds between waves in --watch mode (default 8)', '8')
@@ -2094,33 +2098,6 @@ export function registerTeamsCommands(program) {
2094
2098
  await warnUnsignedTeammates(mgr, team);
2095
2099
  await warnThrottledTeammates(mgr, team);
2096
2100
  }
2097
- // Health-/harness-aware pre-flight (RUSH-2002): if the team declares a
2098
- // device pool and no device in it can run a pending teammate's agent, fail
2099
- // loud here rather than launching a wave that strands teammates pending.
2100
- // --force downgrades it to a warning so the user can still try.
2101
- const placementError = await mgr.preflightPlacement(team);
2102
- if (placementError) {
2103
- if (isJsonMode(opts)) {
2104
- console.log(JSON.stringify({
2105
- team,
2106
- error: 'no-viable-device',
2107
- message: placementError.message,
2108
- excluded: placementError.excluded,
2109
- }));
2110
- if (!opts.force) {
2111
- process.exitCode = 1;
2112
- return;
2113
- }
2114
- }
2115
- else if (opts.force) {
2116
- console.error(chalk.yellow(`⚠ ${placementError.message}\n Continuing anyway (--force).`));
2117
- }
2118
- else {
2119
- console.error(chalk.red(placementError.message));
2120
- process.exitCode = 1;
2121
- return;
2122
- }
2123
- }
2124
2101
  emit('teams.start', { module: 'teams', team, watch: Boolean(opts.watch) });
2125
2102
  if (!opts.watch) {
2126
2103
  await runOneWave(mgr, team, Boolean(opts.json));
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerTracesCommands(program: Command): void;
@@ -0,0 +1,188 @@
1
+ import chalk from 'chalk';
2
+ import { setHelpSections } from '../lib/help.js';
3
+ import { syncTraces } from '../lib/traces/sync.js';
4
+ import { readSyncLedger } from '../lib/traces/sync.js';
5
+ import { managedTracesBaseUrl, resolveTracesBackend } from '../lib/traces/backend.js';
6
+ import { showUrl } from '../lib/open-url.js';
7
+ import { DEFAULT_CF_BUNDLE, readCloudflareCreds } from '../lib/share/config.js';
8
+ import { PHOENIX_ID_BASE } from '../lib/identity/client.js';
9
+ import { DEFAULT_BUCKET_NAME, DEFAULT_WORKER_NAME } from '../lib/traces/config.js';
10
+ import { DEFAULT_TRACES_DOMAIN } from '../lib/traces/backend.js';
11
+ import { provisionTraces } from '../lib/traces/provision.js';
12
+ // ---------------------------------------------------------------------------
13
+ // Help text
14
+ // ---------------------------------------------------------------------------
15
+ const SYNC_EXAMPLES = `
16
+ $ agents traces sync
17
+ Push this device's derived, redacted trajectories to your Phoenix account.
18
+
19
+ $ agents traces sync --limit 20
20
+ Sync at most 20 sessions (useful for a first run on a large fleet device).
21
+
22
+ $ agents traces sync --dry-run --out ./trace-preview
23
+ Compute the shards from your real sessions.db and WRITE them locally — no
24
+ Phoenix sign-in, no worker, no upload. Point a console at ./trace-preview to
25
+ see your real trajectories before wiring the hosted path.
26
+ `.trimStart();
27
+ const SYNC_NOTES = `
28
+ Reads from the local sessions.db, computes a derived SessionTrajectory (steps +
29
+ gaps + stats, no raw transcript text), redacts secrets, and PUTs each to
30
+ the agents-traces store.
31
+
32
+ An incremental gate (traces-sync.json ledger) means re-runs only upload
33
+ sessions whose file_mtime_ms changed since the last sync.
34
+
35
+ On a real (non-dry-run) sync with the managed Phoenix backend, also
36
+ fire-and-forget registers this session with Prix (api.prix.dev) so the
37
+ console can serve live trajectories — a link failure never blocks sync.
38
+
39
+ Sign in first with: agents auth login
40
+ `.trimStart();
41
+ const STATUS_EXAMPLES = `
42
+ $ agents traces status
43
+ Show what's been synced — session count and last sync time for this device.
44
+ `.trimStart();
45
+ const OPEN_EXAMPLES = `
46
+ $ agents traces open
47
+ Open the Phoenix Evals console for your account.
48
+ `.trimStart();
49
+ const SETUP_EXAMPLES = `
50
+ $ agents traces setup
51
+ Provision the private agents-traces Worker, R2 bucket, and managed domain.
52
+
53
+ $ agents traces setup --account <id> --token <token>
54
+ Provision non-interactively with explicit Cloudflare credentials.
55
+ `.trimStart();
56
+ // ---------------------------------------------------------------------------
57
+ // Command handlers
58
+ // ---------------------------------------------------------------------------
59
+ async function handleSync(opts) {
60
+ const limit = opts.limit !== undefined ? parseInt(opts.limit, 10) : undefined;
61
+ const dryRun = opts.dryRun === true;
62
+ if (dryRun && !opts.out) {
63
+ console.error(chalk.red('traces sync --dry-run requires --out <dir>'));
64
+ process.exitCode = 1;
65
+ return;
66
+ }
67
+ // A dry-run computes locally and needs no Phoenix backend; a real sync does.
68
+ if (!dryRun) {
69
+ try {
70
+ resolveTracesBackend();
71
+ }
72
+ catch (err) {
73
+ console.error(chalk.red(err.message));
74
+ process.exitCode = 1;
75
+ return;
76
+ }
77
+ }
78
+ console.log(chalk.dim(dryRun ? `Computing traces locally → ${opts.out}` : 'Syncing traces…'));
79
+ let result;
80
+ try {
81
+ result = await syncTraces({ limit, dryRun, outDir: opts.out });
82
+ }
83
+ catch (err) {
84
+ console.error(chalk.red(`Sync failed: ${err.message}`));
85
+ process.exitCode = 1;
86
+ return;
87
+ }
88
+ const parts = [];
89
+ if (result.uploaded > 0) {
90
+ parts.push(chalk.green(`${result.uploaded} ${dryRun ? 'written' : 'uploaded'}`));
91
+ }
92
+ if (result.skipped > 0) {
93
+ parts.push(chalk.dim(`${result.skipped} skipped`));
94
+ }
95
+ if (result.errors > 0) {
96
+ parts.push(chalk.yellow(`${result.errors} errors`));
97
+ }
98
+ if (parts.length === 0) {
99
+ parts.push(chalk.dim('nothing new'));
100
+ }
101
+ console.log(parts.join(chalk.dim(' · ')));
102
+ }
103
+ async function handleStatus() {
104
+ const ledger = readSyncLedger();
105
+ if (!ledger.lastSyncMtime) {
106
+ console.log(chalk.dim('No sync recorded on this device. Run: agents traces sync'));
107
+ return;
108
+ }
109
+ const when = new Date(ledger.lastSyncMtime).toLocaleString();
110
+ console.log(`Last sync: ${chalk.bold(when)}`);
111
+ console.log(chalk.dim('Run `agents traces sync` to push new sessions.'));
112
+ }
113
+ async function handleOpen() {
114
+ // M2 console — for now open the base URL which will route to the console once deployed.
115
+ const url = managedTracesBaseUrl();
116
+ console.log(chalk.dim(`Opening ${url}`));
117
+ const outcome = await showUrl(url);
118
+ if (outcome.via === 'none') {
119
+ console.log(url);
120
+ }
121
+ }
122
+ async function handleSetup(opts) {
123
+ const { input } = await import('@inquirer/prompts');
124
+ const { apiToken, accountId: bundledAccountId } = readCloudflareCreds(opts.bundle, {
125
+ apiToken: opts.token,
126
+ accountId: opts.account,
127
+ });
128
+ const accountId = opts.account ?? bundledAccountId ?? await input({ message: 'Cloudflare account id' });
129
+ if (!accountId)
130
+ throw new Error('A Cloudflare account id is required.');
131
+ const result = await provisionTraces({
132
+ apiToken,
133
+ accountId,
134
+ workerName: opts.worker,
135
+ bucketName: opts.bucket,
136
+ domain: opts.domain,
137
+ phoenixIdBase: PHOENIX_ID_BASE,
138
+ });
139
+ console.log(chalk.green(`Traces endpoint ready → ${chalk.bold(result.baseUrl)}`));
140
+ }
141
+ // ---------------------------------------------------------------------------
142
+ // Registration
143
+ // ---------------------------------------------------------------------------
144
+ export function registerTracesCommands(program) {
145
+ const tracesCmd = program
146
+ .command('traces')
147
+ .description("Sync this device's derived, redacted trajectories to your Phoenix account");
148
+ const setupCmd = tracesCmd
149
+ .command('setup')
150
+ .description('Provision the private Cloudflare Worker and R2 bucket for traces')
151
+ .option('--bundle <name>', 'secrets bundle holding the Cloudflare API token', DEFAULT_CF_BUNDLE)
152
+ .option('--worker <name>', 'Worker name', DEFAULT_WORKER_NAME)
153
+ .option('--bucket <name>', 'R2 bucket name', DEFAULT_BUCKET_NAME)
154
+ .option('--account <id>', 'Cloudflare account id (else read from the bundle / prompt)')
155
+ .option('--token <token>', 'Cloudflare API token (else read from the --bundle)')
156
+ .option('--domain <host>', 'custom domain to map', DEFAULT_TRACES_DOMAIN)
157
+ .action(async (opts) => {
158
+ try {
159
+ await handleSetup(opts);
160
+ }
161
+ catch (err) {
162
+ console.error(chalk.red(err.message));
163
+ process.exitCode = 1;
164
+ }
165
+ });
166
+ setHelpSections(setupCmd, { examples: SETUP_EXAMPLES });
167
+ // sync
168
+ const syncCmd = tracesCmd
169
+ .command('sync')
170
+ .description('Push derived, redacted trajectories (incremental)')
171
+ .option('--limit <n>', 'Maximum number of sessions to sync in this run')
172
+ .option('--dry-run', 'Compute the shards from real sessions.db and WRITE them locally (no auth, no upload)')
173
+ .option('--out <dir>', 'Directory to write index.json + sessions/<id>.json (required with --dry-run)')
174
+ .action(handleSync);
175
+ setHelpSections(syncCmd, { examples: SYNC_EXAMPLES, notes: SYNC_NOTES });
176
+ // status
177
+ const statusCmd = tracesCmd
178
+ .command('status')
179
+ .description('Show last sync time for this device')
180
+ .action(handleStatus);
181
+ setHelpSections(statusCmd, { examples: STATUS_EXAMPLES });
182
+ // open
183
+ const openCmd = tracesCmd
184
+ .command('open')
185
+ .description('Open the Phoenix Evals console')
186
+ .action(handleOpen);
187
+ setHelpSections(openCmd, { examples: OPEN_EXAMPLES });
188
+ }
@@ -15,7 +15,7 @@ import type { ResourceSection, ViewJsonAgent } from '../lib/view-types.js';
15
15
  export type { ResourceItemJson, ResourceSection, SyncState, VersionResourcesJson, ViewJsonAgent, ViewJsonVersion } from '../lib/view-types.js';
16
16
  import { type ProfileSummary } from '../lib/profiles.js';
17
17
  import { type ByokUsageResult } from '../lib/byok-usage.js';
18
- export declare function viewUsageSummaryOptions(agentId: AgentId, signedIn: boolean, usageInfo: UsageInfo | undefined, maxWindows: number | undefined): FormatUsageSummaryOpts;
18
+ export declare function viewUsageSummaryOptions(agentId: AgentId, signedIn: boolean, usageInfo: UsageInfo | undefined, maxWindows: number | undefined, version?: string): FormatUsageSummaryOpts;
19
19
  /** Shared account identity formatter, re-exported for the view-specific tests. */
20
20
  export declare const accountColumnLabel: typeof accountDisplayLabel;
21
21
  /**
@@ -7,7 +7,7 @@ import * as path from 'path';
7
7
  import { AGENTS, ALL_AGENT_IDS, accountDisplayLabel, getAllCliStates, getUnmanagedCliState, getAccountInfo, resolveAgentName, formatAgentError, agentLabel, colorAgent, } from '../lib/agents.js';
8
8
  import { ambientClaudeToken, loginHint } from '../lib/signin-badge.js';
9
9
  import { machineId } from '../lib/machine-id.js';
10
- import { authCacheKey, formatCheckedAge, readAuthHealthCache } from '../lib/auth-health.js';
10
+ import { authCacheKey, readAuthHealthCache } from '../lib/auth-health.js';
11
11
  import { agentReportsUsage, classifyUsageErrorKind, deriveUsageStatusFromSnapshot, formatUsageSection, formatUsageSummary, formatUsageStatusBadge, getUsageBenignState, getUsageInfoForIdentity, getUsageInfoByIdentity, getUsageLookupKey, isUsageHeadlessScopeError, } from '../lib/accounting/usage.js';
12
12
  import { readManifest } from '../lib/manifest.js';
13
13
  import { listInstalledVersions, listInstalledVersionDirs, getGlobalDefault, getVersionHomePath, getVersionDir, removeVersion, printTrashFooter, reconcileStaleLatestForAgent, isGlobalBinaryAgent, getLiveVersion, isVersionIsolated, getIsolatedDefault, } from '../lib/installations/versions.js';
@@ -32,7 +32,7 @@ import { renderHarnessDetail } from './harness.js';
32
32
  import { confirm } from '@inquirer/prompts';
33
33
  import { formatPath, isInteractiveTerminal, isPromptCancelled } from './utils.js';
34
34
  import { terminalWidth, truncateToWidth, stringWidth, padToWidth } from '../lib/session/width.js';
35
- export function viewUsageSummaryOptions(agentId, signedIn, usageInfo, maxWindows) {
35
+ export function viewUsageSummaryOptions(agentId, signedIn, usageInfo, maxWindows, version) {
36
36
  const headless = isUsageHeadlessScopeError(usageInfo?.error);
37
37
  const benignState = usageInfo ? getUsageBenignState(usageInfo) : null;
38
38
  return {
@@ -40,9 +40,15 @@ export function viewUsageSummaryOptions(agentId, signedIn, usageInfo, maxWindows
40
40
  unverified: !headless && !!usageInfo?.snapshot && !!usageInfo.error,
41
41
  headless,
42
42
  maxWindows,
43
+ expectedWindows: agentId === 'claude'
44
+ ? [{ key: 'session', shortLabel: 'S' }, { key: 'week', shortLabel: 'W' }]
45
+ : undefined,
43
46
  errorKind: classifyUsageErrorKind(usageInfo?.error),
44
47
  errorDetail: usageInfo?.error ?? null,
45
48
  benignState,
49
+ noRecentUsageLabel: agentId === 'grok'
50
+ ? `run grok${version ? `@${version}` : ''} once to refresh usage`
51
+ : null,
46
52
  };
47
53
  }
48
54
  /** Shared account identity formatter, re-exported for the view-specific tests. */
@@ -92,8 +98,6 @@ export function compareAccountOrderedVersions(a, b, globalDefault) {
92
98
  * views leave the cap unset and show every blocking window.
93
99
  */
94
100
  const OVERVIEW_MAX_USAGE_WINDOWS = 2;
95
- /** Fixed width for the last-active column ("just now", "8h ago", "2d ago"). */
96
- const LAST_ACTIVE_COL_WIDTH = 10;
97
101
  /**
98
102
  * Join fixed view columns with a consistent two-space gutter. Empty trailing
99
103
  * columns are dropped so a row without an auth chip does not grow a dangling
@@ -312,22 +316,6 @@ function renderHostClisSection(cwd) {
312
316
  console.log(` ${chalk.red('error')} ${chalk.gray(err.file)}: ${chalk.gray(err.reason)}`);
313
317
  }
314
318
  }
315
- /**
316
- * A compact live-auth chip for a version row, read from the fleet auth-health
317
- * cache (written by `agents fleet ping` / the daemon). Empty when the install
318
- * hasn't been probed — this is the local "signed in" flag's ground-truth
319
- * companion: ● live, ○ revoked (re-login), ◐ unverified/limited, plus its age.
320
- */
321
- function liveAuthChip(cache, host, agentId, version) {
322
- const h = cache[authCacheKey(host, agentId, version)];
323
- if (!h)
324
- return '';
325
- const glyph = h.verdict === 'live' ? chalk.green('●')
326
- : h.verdict === 'revoked' ? chalk.red('○')
327
- : h.verdict === 'expired' ? chalk.yellow('○')
328
- : chalk.yellow('◐');
329
- return `${glyph} ${chalk.gray(formatCheckedAge(h.checkedAt))}`;
330
- }
331
319
  async function showInstalledVersions(filterAgentId, viewOpts) {
332
320
  const spinnerText = filterAgentId
333
321
  ? `Checking ${agentLabel(filterAgentId)} agents...`
@@ -378,9 +366,6 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
378
366
  }
379
367
  }
380
368
  // Shim healing is silent — users don't need to know about internal repairs
381
- const selfHost = machineId();
382
- // Read the auth-health cache once (not per version row — see the batching note above).
383
- const authCache = readAuthHealthCache();
384
369
  // Pre-fetch account info for all versions in parallel. Spinner stays up through
385
370
  // account + usage so a multi-account cold path doesn't leave a blank terminal
386
371
  // after "Checking…" vanishes (the hang the screenshots caught).
@@ -548,7 +533,7 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
548
533
  const info = rawInfo ? mergeCanonical(rawInfo) : undefined;
549
534
  const usageKey = getUsageLookupKey(info);
550
535
  const usageInfo = usageKey ? usageByKey.get(usageKey) : undefined;
551
- const usageStr = formatUsageSummary(info?.plan || null, usageInfo?.snapshot || null, maxPlanWidth, viewUsageSummaryOptions(agentId, !!info?.signedIn, usageInfo, usageWindowCap));
536
+ const usageStr = formatUsageSummary(info?.plan || null, usageInfo?.snapshot || null, maxPlanWidth, viewUsageSummaryOptions(agentId, !!info?.signedIn, usageInfo, usageWindowCap, v));
552
537
  maxUsageWidth = Math.max(maxUsageWidth, stringWidth(usageStr));
553
538
  const statusStr = formatUsageStatusBadge(info?.usageStatus);
554
539
  maxStatusWidth = Math.max(maxStatusWidth, stringWidth(statusStr));
@@ -608,11 +593,12 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
608
593
  }
609
594
  const hasEmail = !!vInfo?.email;
610
595
  const signedIn = !!vInfo?.signedIn;
611
- const usageStr = formatUsageSummary(vInfo?.plan || null, usageInfo?.snapshot || null, maxPlanWidth, viewUsageSummaryOptions(agentId, signedIn, usageInfo, usageWindowCap));
596
+ const usageStr = formatUsageSummary(vInfo?.plan || null, usageInfo?.snapshot || null, maxPlanWidth, viewUsageSummaryOptions(agentId, signedIn, usageInfo, usageWindowCap, version));
612
597
  const hasUsage = usageStr.length > 0;
613
598
  // Only show lastActive for versions with an actual logged-in account.
614
599
  // Otherwise it reflects install time (misleading "just now" for fresh installs).
615
- const activeStr = vInfo && hasEmail ? formatLastActive(vInfo.lastActive) : '';
600
+ const lastActive = vInfo && hasEmail ? formatLastActive(vInfo.lastActive) : '';
601
+ const activeStr = lastActive;
616
602
  const hasActive = activeStr.length > 0;
617
603
  // The model now has its own column above; keep only the run mode here.
618
604
  const runDefaults = resolveRunDefaults(agentId, version);
@@ -649,16 +635,12 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
649
635
  const statusStr = formatUsageStatusBadge(vInfo?.usageStatus);
650
636
  parts.push(padToWidth(statusStr, maxStatusWidth));
651
637
  }
652
- // Fixed-width lastActive so the auth chip (●/○/◐) lines up even when
653
- // some rows have no email-derived lastActive.
654
- parts.push(hasActive ? padToWidth(activeStr, LAST_ACTIVE_COL_WIDTH) : ' '.repeat(LAST_ACTIVE_COL_WIDTH));
638
+ if (hasActive)
639
+ parts.push(activeStr);
655
640
  }
656
641
  if (runDefaultBits.length > 0) {
657
642
  parts.push(chalk.gray(`run ${runDefaultBits.join(' ')}`));
658
643
  }
659
- const authChip = liveAuthChip(authCache, selfHost, agentId, version);
660
- if (authChip)
661
- parts.push(authChip);
662
644
  console.log(joinViewColumns(parts));
663
645
  if (showPaths) {
664
646
  const versionDir = getVersionDir(agentId, version);
@@ -747,7 +729,8 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
747
729
  const gUsageKey = getUsageLookupKey(gInfo);
748
730
  const gUsage = gUsageKey ? usageByKey.get(gUsageKey) : undefined;
749
731
  const gUsageStr = formatUsageSummary(gInfo?.plan || null, gUsage?.snapshot || null, 3, viewUsageSummaryOptions(agentId, !!gInfo?.signedIn, gUsage, usageWindowCap));
750
- const gActiveStr = gInfo ? formatLastActive(gInfo.lastActive) : '';
732
+ const gLastActive = gInfo ? formatLastActive(gInfo.lastActive) : '';
733
+ const gActiveStr = gLastActive;
751
734
  if (gInfo?.email || gUsageStr || gActiveStr || gInfo?.signedIn) {
752
735
  const gDisplay = accountColumnLabel(gInfo);
753
736
  parts.push(gDisplay ? chalk.cyan(padToWidth(gDisplay, gMaxEmail)) : ' '.repeat(gMaxEmail));
@@ -758,7 +741,7 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
758
741
  parts.push(padToWidth(formatUsageStatusBadge(gInfo?.usageStatus), gMaxStatusWidth));
759
742
  }
760
743
  if (gActiveStr)
761
- parts.push(padToWidth(gActiveStr, LAST_ACTIVE_COL_WIDTH));
744
+ parts.push(gActiveStr);
762
745
  console.log(joinViewColumns(parts));
763
746
  if (showPaths && cliState?.path) {
764
747
  console.log(chalk.gray(` ${cliState.path}`));
package/dist/index.d.ts CHANGED
@@ -14,6 +14,7 @@
14
14
  * - `__vault-age-helper`
15
15
  * - `__secrets-get` / `__secrets-ping` / `__secrets-lock` (SYNC_* tokens)
16
16
  * - `__shim`
17
+ * - `__claude-statusline`
17
18
  * - `__daemon-run`
18
19
  *
19
20
  * The tokens are imported from the leaf module sync-commands.ts — the SAME
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@
14
14
  * - `__vault-age-helper`
15
15
  * - `__secrets-get` / `__secrets-ping` / `__secrets-lock` (SYNC_* tokens)
16
16
  * - `__shim`
17
+ * - `__claude-statusline`
17
18
  * - `__daemon-run`
18
19
  *
19
20
  * The tokens are imported from the leaf module sync-commands.ts — the SAME
@@ -70,6 +71,10 @@ if (process.argv[2] === '__shim') {
70
71
  const code = await execShimPassthrough(agent, rawArgs, process.cwd(), pinned || undefined);
71
72
  process.exit(code);
72
73
  }
74
+ if (process.argv[2] === '__claude-statusline') {
75
+ const { runClaudeStatusLine } = await import('./lib/claude-statusline.js');
76
+ process.exit(await runClaudeStatusLine());
77
+ }
73
78
  if (process.argv[2] === '__daemon-run') {
74
79
  const { runDaemon, log: daemonLog } = await import('./lib/daemon/daemon.js');
75
80
  // RUSH-2418: the daemon is the one always-on process here, and it ran with no
@@ -15,4 +15,13 @@ export interface AccountProviderAdapter {
15
15
  validate(kind: AccountAuthKind, value: string): void;
16
16
  }
17
17
  export declare function getAccountProvider(provider: string): AccountProviderAdapter;
18
+ /**
19
+ * Whether a provider account of kind `auth` can authenticate `agent` — i.e. the
20
+ * adapter has an `envFor(agent, auth)` mapping. Used both by the account UI and by
21
+ * the run-candidate pool so a Cursor key never enters Claude's pool and a harness
22
+ * with no provider adapter at all (e.g. kimi, native-login only) yields nothing.
23
+ * A "cannot authenticate" is a clean false; any other error (unknown provider,
24
+ * unsupported kind) is a real bug and re-throws rather than being swallowed.
25
+ */
26
+ export declare function providerAuthenticatesHarness(provider: string, auth: AccountAuthKind, agent: AgentId): boolean;
18
27
  export declare function listAccountProviders(): string[];
@@ -54,6 +54,26 @@ export function getAccountProvider(provider) {
54
54
  throw new Error(`Unknown account provider '${provider}'. Supported: ${listAccountProviders().join(', ')}.`);
55
55
  return adapter;
56
56
  }
57
+ /**
58
+ * Whether a provider account of kind `auth` can authenticate `agent` — i.e. the
59
+ * adapter has an `envFor(agent, auth)` mapping. Used both by the account UI and by
60
+ * the run-candidate pool so a Cursor key never enters Claude's pool and a harness
61
+ * with no provider adapter at all (e.g. kimi, native-login only) yields nothing.
62
+ * A "cannot authenticate" is a clean false; any other error (unknown provider,
63
+ * unsupported kind) is a real bug and re-throws rather than being swallowed.
64
+ */
65
+ export function providerAuthenticatesHarness(provider, auth, agent) {
66
+ try {
67
+ getAccountProvider(provider).envFor(agent, auth);
68
+ return true;
69
+ }
70
+ catch (err) {
71
+ const message = err instanceof Error ? err.message : String(err);
72
+ if (message.includes('cannot authenticate'))
73
+ return false;
74
+ throw err;
75
+ }
76
+ }
57
77
  export function listAccountProviders() {
58
78
  return [...ADAPTERS.keys()].sort();
59
79
  }
@@ -0,0 +1,34 @@
1
+ import type { AgentId } from '../types.js';
2
+ import { type RotateCandidate } from './rotate.js';
3
+ import { type RegistryAccountRecord } from './account-pool.js';
4
+ /** Inputs to {@link foldRegistryCandidates} — injectable so the fold is unit-tested. */
5
+ export interface RunCandidateInputs {
6
+ native: RotateCandidate[];
7
+ records: RegistryAccountRecord[];
8
+ runVersion: string | undefined;
9
+ }
10
+ /**
11
+ * PURE: fold provider-account candidates into the native version-home list.
12
+ *
13
+ * A registry account runs in `runVersion` (the agent's default installed home);
14
+ * the token/key overrides that home's own login at exec, so any home works. It
15
+ * carries `providerAccount` so the run path routes it through the existing
16
+ * `--account` injection (`resolveSpawnAccount` → `accountEnv`). An account already
17
+ * covered by a native login is skipped by accountKey. With no installed version
18
+ * there is nowhere to execute a registry account, so the native list passes
19
+ * through unchanged.
20
+ */
21
+ export declare function foldRegistryCandidates(agent: AgentId, inputs: RunCandidateInputs): RotateCandidate[];
22
+ /**
23
+ * Candidate list for the `agents run` strategy pick, account-centric: the usual
24
+ * native version-home candidates PLUS one candidate per provider account whose
25
+ * provider can authenticate `agent` and whose credential is present locally
26
+ * (RUSH-3182). This is what makes a setup-token / API-key account balance-eligible.
27
+ *
28
+ * Passed as the `collect` argument to {@link resolveRunVersion} on the RUN path
29
+ * ONLY — the other consumers of {@link collectRunCandidates} (watchdog, session
30
+ * recovery, teams placement) keep calling it directly and are unaffected. The
31
+ * existing {@link pickBalancedCandidate} selects over the combined list, so the
32
+ * revoked / rate-limit / exhausted logic is unchanged.
33
+ */
34
+ export declare function collectRunCandidatesForRun(agent: AgentId): Promise<RotateCandidate[]>;