@phnx-labs/agents-cli 1.22.117 → 1.22.119

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 (271) hide show
  1. package/CHANGELOG.md +301 -0
  2. package/dist/bootstrap.js +4 -11
  3. package/dist/commands/accounts.js +7 -4
  4. package/dist/commands/browser.d.ts +52 -70
  5. package/dist/commands/browser.js +192 -3562
  6. package/dist/commands/daemon.d.ts +39 -2
  7. package/dist/commands/daemon.js +72 -73
  8. package/dist/commands/events.js +1 -1
  9. package/dist/commands/exec.js +53 -1
  10. package/dist/commands/projects.js +92 -7
  11. package/dist/commands/repo.js +13 -49
  12. package/dist/commands/route.js +5 -5
  13. package/dist/commands/routines.js +39 -12
  14. package/dist/commands/sessions-render.d.ts +1 -1
  15. package/dist/commands/sessions-render.js +0 -1
  16. package/dist/commands/sessions.test-fixture.d.ts +0 -1
  17. package/dist/commands/sessions.test-fixture.js +0 -37
  18. package/dist/commands/setup-browser.d.ts +17 -12
  19. package/dist/commands/setup-browser.js +52 -87
  20. package/dist/commands/setup-preferences.d.ts +0 -17
  21. package/dist/commands/setup-preferences.js +18 -49
  22. package/dist/commands/setup.js +9 -9
  23. package/dist/commands/ssh.js +10 -1
  24. package/dist/commands/sync.js +6 -44
  25. package/dist/commands/teams.js +0 -1
  26. package/dist/commands/update.d.ts +19 -0
  27. package/dist/commands/update.js +34 -6
  28. package/dist/commands/view.d.ts +12 -1
  29. package/dist/commands/view.js +47 -2
  30. package/dist/index.js +107 -26
  31. package/dist/lib/account-capabilities.js +0 -2
  32. package/dist/lib/account-catalog.d.ts +38 -0
  33. package/dist/lib/account-catalog.js +110 -26
  34. package/dist/lib/account-provider-registry.js +1 -1
  35. package/dist/lib/account-registry.d.ts +1 -1
  36. package/dist/lib/account-registry.js +36 -2
  37. package/dist/lib/accounting/rotate.js +22 -8
  38. package/dist/lib/accounting/usage-ingest.d.ts +6 -1
  39. package/dist/lib/accounting/usage-ingest.js +115 -45
  40. package/dist/lib/accounting/usage-sync.d.ts +134 -31
  41. package/dist/lib/accounting/usage-sync.js +227 -66
  42. package/dist/lib/accounting/usage.d.ts +16 -3
  43. package/dist/lib/accounting/usage.js +26 -8
  44. package/dist/lib/accounts/add.d.ts +3 -1
  45. package/dist/lib/accounts/add.js +24 -13
  46. package/dist/lib/accounts/slots.d.ts +8 -0
  47. package/dist/lib/accounts/slots.js +17 -0
  48. package/dist/lib/add-dir.js +0 -1
  49. package/dist/lib/agent-cli-commands.js +0 -1
  50. package/dist/lib/agent-spec/agents.d.ts +6 -7
  51. package/dist/lib/agent-spec/agents.js +7 -60
  52. package/dist/lib/auth-health.d.ts +69 -1
  53. package/dist/lib/auth-health.js +133 -4
  54. package/dist/lib/browser/context.d.ts +82 -0
  55. package/dist/lib/browser/context.js +88 -0
  56. package/dist/lib/browser/paths.d.ts +25 -0
  57. package/dist/lib/browser/paths.js +51 -0
  58. package/dist/lib/browser/record.d.ts +38 -0
  59. package/dist/lib/browser/record.js +57 -0
  60. package/dist/lib/browser/sessions-list.js +1 -2
  61. package/dist/lib/browser-client.d.ts +158 -0
  62. package/dist/lib/browser-client.js +234 -0
  63. package/dist/lib/channels/owner-sink.d.ts +1 -18
  64. package/dist/lib/channels/owner-sink.js +29 -82
  65. package/dist/lib/channels/providers/rush.d.ts +7 -7
  66. package/dist/lib/channels/providers/rush.js +127 -57
  67. package/dist/lib/claude-account-token.d.ts +7 -1
  68. package/dist/lib/claude-account-token.js +26 -4
  69. package/dist/lib/claude-statusline.d.ts +1 -1
  70. package/dist/lib/claude-statusline.js +15 -3
  71. package/dist/lib/commands.js +1 -1
  72. package/dist/lib/crabbox/lease.js +1 -1
  73. package/dist/lib/crabbox/runtimes.js +1 -3
  74. package/dist/lib/daemon/account-state-daemon-service.js +1 -0
  75. package/dist/lib/daemon/auth-sync-service.d.ts +21 -18
  76. package/dist/lib/daemon/auth-sync-service.js +37 -43
  77. package/dist/lib/daemon/daemon.d.ts +25 -14
  78. package/dist/lib/daemon/daemon.js +92 -154
  79. package/dist/lib/daemon/runner.d.ts +2 -2
  80. package/dist/lib/daemon/runner.js +32 -9
  81. package/dist/lib/daemon/self-update-service.d.ts +35 -34
  82. package/dist/lib/daemon/self-update-service.js +40 -36
  83. package/dist/lib/daemon/service.d.ts +12 -8
  84. package/dist/lib/daemon/supervisor.d.ts +65 -54
  85. package/dist/lib/daemon/supervisor.js +136 -171
  86. package/dist/lib/daemon/testdata/process-view-start.js +5 -2
  87. package/dist/lib/daemon/usage-sync-service.d.ts +21 -30
  88. package/dist/lib/daemon/usage-sync-service.js +46 -78
  89. package/dist/lib/daemon-health.d.ts +22 -5
  90. package/dist/lib/daemon-health.js +47 -3
  91. package/dist/lib/daemon-services.d.ts +1 -1
  92. package/dist/lib/daemon-services.js +0 -10
  93. package/dist/lib/daemon-ticks.js +4 -2
  94. package/dist/lib/devices/doctor-findings.js +8 -11
  95. package/dist/lib/devices/harness-inventory.js +14 -5
  96. package/dist/lib/exec-account-home.js +5 -0
  97. package/dist/lib/exec.js +10 -13
  98. package/dist/lib/feed/events.js +0 -1
  99. package/dist/lib/feed/tool-activity.js +5 -2
  100. package/dist/lib/fleet-shared-state.d.ts +45 -0
  101. package/dist/lib/fleet-shared-state.js +78 -9
  102. package/dist/lib/fs-atomic.js +25 -0
  103. package/dist/lib/github/pr-mergeable.d.ts +4 -2
  104. package/dist/lib/github/pr-mergeable.js +5 -3
  105. package/dist/lib/github/project-prs.d.ts +58 -3
  106. package/dist/lib/github/project-prs.js +205 -7
  107. package/dist/lib/harness/adapters/cursor.js +14 -4
  108. package/dist/lib/harness-auth-capabilities.d.ts +4 -3
  109. package/dist/lib/harness-auth-capabilities.js +1 -2
  110. package/dist/lib/helper-versions.js +1 -1
  111. package/dist/lib/hooks/cache.d.ts +7 -0
  112. package/dist/lib/hooks/cache.js +27 -2
  113. package/dist/lib/hooks/install.js +7 -1
  114. package/dist/lib/hosts/ready.d.ts +38 -8
  115. package/dist/lib/hosts/ready.js +62 -14
  116. package/dist/lib/installations/active-check.d.ts +27 -0
  117. package/dist/lib/installations/active-check.js +61 -0
  118. package/dist/lib/installations/migrate.js +6 -3
  119. package/dist/lib/installations/shims.d.ts +1 -1
  120. package/dist/lib/installations/shims.js +32 -12
  121. package/dist/lib/installations/store.d.ts +9 -0
  122. package/dist/lib/installations/store.js +24 -0
  123. package/dist/lib/installations/update.js +21 -15
  124. package/dist/lib/installations/versions.js +15 -3
  125. package/dist/lib/manifest.js +1 -1
  126. package/dist/lib/models.js +0 -2
  127. package/dist/lib/open-url.js +28 -54
  128. package/dist/lib/profiles.js +0 -2
  129. package/dist/lib/projects.d.ts +16 -0
  130. package/dist/lib/projects.js +31 -0
  131. package/dist/lib/refresh.js +1 -1
  132. package/dist/lib/routine-readiness.d.ts +35 -0
  133. package/dist/lib/routine-readiness.js +74 -16
  134. package/dist/lib/rules/compile.js +6 -0
  135. package/dist/lib/sandbox.js +0 -2
  136. package/dist/lib/secrets-policy.d.ts +89 -11
  137. package/dist/lib/secrets-policy.js +175 -28
  138. package/dist/lib/session/active.d.ts +17 -6
  139. package/dist/lib/session/active.js +105 -49
  140. package/dist/lib/session/db.d.ts +1 -1
  141. package/dist/lib/session/db.js +65 -18
  142. package/dist/lib/session/discover.js +1 -283
  143. package/dist/lib/session/mirror.d.ts +4 -3
  144. package/dist/lib/session/mirror.js +4 -3
  145. package/dist/lib/session/parse.d.ts +0 -2
  146. package/dist/lib/session/parse.js +1 -138
  147. package/dist/lib/session/pid-registry.d.ts +6 -3
  148. package/dist/lib/session/pid-registry.js +18 -8
  149. package/dist/lib/session/remote/watch.d.ts +1 -1
  150. package/dist/lib/session/remote/watch.js +7 -3
  151. package/dist/lib/session/throughput.d.ts +1 -3
  152. package/dist/lib/session/throughput.js +0 -20
  153. package/dist/lib/session/types.d.ts +2 -2
  154. package/dist/lib/session/types.js +2 -2
  155. package/dist/lib/sessions-client.d.ts +60 -1
  156. package/dist/lib/sessions-client.js +227 -4
  157. package/dist/lib/setup-tool-status.js +5 -10
  158. package/dist/lib/signin-badge.d.ts +3 -2
  159. package/dist/lib/signin-badge.js +6 -3
  160. package/dist/lib/smart-launch.js +4 -1
  161. package/dist/lib/staleness/detectors/commands.d.ts +1 -1
  162. package/dist/lib/staleness/detectors/hooks.d.ts +1 -1
  163. package/dist/lib/staleness/detectors/mcp.d.ts +1 -1
  164. package/dist/lib/staleness/detectors/permissions.d.ts +1 -1
  165. package/dist/lib/staleness/detectors/plugins.d.ts +1 -1
  166. package/dist/lib/staleness/detectors/rules.d.ts +1 -1
  167. package/dist/lib/staleness/detectors/skills.d.ts +1 -1
  168. package/dist/lib/staleness/detectors/subagents.d.ts +1 -1
  169. package/dist/lib/staleness/detectors/workflows.d.ts +1 -1
  170. package/dist/lib/staleness/writers/commands.d.ts +1 -1
  171. package/dist/lib/staleness/writers/hooks.d.ts +1 -1
  172. package/dist/lib/staleness/writers/mcp.d.ts +1 -1
  173. package/dist/lib/staleness/writers/permissions.d.ts +1 -1
  174. package/dist/lib/staleness/writers/plugins.d.ts +1 -1
  175. package/dist/lib/staleness/writers/rules.d.ts +1 -1
  176. package/dist/lib/staleness/writers/skills.d.ts +1 -1
  177. package/dist/lib/staleness/writers/sources.js +5 -0
  178. package/dist/lib/staleness/writers/subagents.d.ts +1 -1
  179. package/dist/lib/staleness/writers/workflows.d.ts +1 -1
  180. package/dist/lib/state.d.ts +6 -4
  181. package/dist/lib/state.js +7 -17
  182. package/dist/lib/teams/parsers.d.ts +1 -1
  183. package/dist/lib/teams/parsers.js +1 -136
  184. package/dist/lib/teams/placement-probe.js +50 -15
  185. package/dist/lib/teams/scheduler.d.ts +7 -0
  186. package/dist/lib/types.d.ts +16 -1
  187. package/dist/lib/types.js +1 -1
  188. package/dist/lib/view-types.d.ts +32 -0
  189. package/dist/lib/watchdog/read.js +0 -1
  190. package/dist/lib/watchdog/rotate.js +1 -2
  191. package/dist/lib/watchdog/runner.js +2 -2
  192. package/dist/lib/watchdog/watchdog.d.ts +1 -1
  193. package/dist/lib/watchdog/watchdogTail.js +0 -31
  194. package/package.json +1 -1
  195. package/scripts/postinstall.js +8 -8
  196. package/dist/browser.d.ts +0 -2
  197. package/dist/browser.js +0 -17
  198. package/dist/commands/browser-picker.d.ts +0 -18
  199. package/dist/commands/browser-picker.js +0 -97
  200. package/dist/lib/browser/arc-discovery.d.ts +0 -52
  201. package/dist/lib/browser/arc-discovery.js +0 -197
  202. package/dist/lib/browser/arc-dom.d.ts +0 -14
  203. package/dist/lib/browser/arc-dom.js +0 -121
  204. package/dist/lib/browser/caller-identity.d.ts +0 -40
  205. package/dist/lib/browser/caller-identity.js +0 -175
  206. package/dist/lib/browser/cdp.d.ts +0 -51
  207. package/dist/lib/browser/cdp.js +0 -254
  208. package/dist/lib/browser/chrome.d.ts +0 -141
  209. package/dist/lib/browser/chrome.js +0 -704
  210. package/dist/lib/browser/chromium-discovery.d.ts +0 -28
  211. package/dist/lib/browser/chromium-discovery.js +0 -96
  212. package/dist/lib/browser/devices.d.ts +0 -34
  213. package/dist/lib/browser/devices.js +0 -61
  214. package/dist/lib/browser/domain-skills.d.ts +0 -71
  215. package/dist/lib/browser/domain-skills.js +0 -195
  216. package/dist/lib/browser/drivers/arc.d.ts +0 -40
  217. package/dist/lib/browser/drivers/arc.js +0 -276
  218. package/dist/lib/browser/drivers/firefox.d.ts +0 -99
  219. package/dist/lib/browser/drivers/firefox.js +0 -377
  220. package/dist/lib/browser/drivers/local.d.ts +0 -55
  221. package/dist/lib/browser/drivers/local.js +0 -266
  222. package/dist/lib/browser/drivers/ssh.d.ts +0 -120
  223. package/dist/lib/browser/drivers/ssh.js +0 -467
  224. package/dist/lib/browser/editor.d.ts +0 -3
  225. package/dist/lib/browser/editor.js +0 -50
  226. package/dist/lib/browser/ffmpeg.d.ts +0 -12
  227. package/dist/lib/browser/ffmpeg.js +0 -184
  228. package/dist/lib/browser/firefox-discovery.d.ts +0 -69
  229. package/dist/lib/browser/firefox-discovery.js +0 -162
  230. package/dist/lib/browser/har.d.ts +0 -85
  231. package/dist/lib/browser/har.js +0 -77
  232. package/dist/lib/browser/hygiene.d.ts +0 -97
  233. package/dist/lib/browser/hygiene.js +0 -153
  234. package/dist/lib/browser/index.d.ts +0 -5
  235. package/dist/lib/browser/index.js +0 -5
  236. package/dist/lib/browser/input.d.ts +0 -7
  237. package/dist/lib/browser/input.js +0 -92
  238. package/dist/lib/browser/ipc.d.ts +0 -204
  239. package/dist/lib/browser/ipc.js +0 -1453
  240. package/dist/lib/browser/login-detection.d.ts +0 -87
  241. package/dist/lib/browser/login-detection.js +0 -274
  242. package/dist/lib/browser/profiles.d.ts +0 -422
  243. package/dist/lib/browser/profiles.js +0 -1157
  244. package/dist/lib/browser/refs.d.ts +0 -89
  245. package/dist/lib/browser/refs.js +0 -191
  246. package/dist/lib/browser/registry.d.ts +0 -70
  247. package/dist/lib/browser/registry.js +0 -252
  248. package/dist/lib/browser/remote-control.d.ts +0 -59
  249. package/dist/lib/browser/remote-control.js +0 -85
  250. package/dist/lib/browser/resolve-target.d.ts +0 -66
  251. package/dist/lib/browser/resolve-target.js +0 -264
  252. package/dist/lib/browser/runtime-state.d.ts +0 -291
  253. package/dist/lib/browser/runtime-state.js +0 -584
  254. package/dist/lib/browser/secret-ref.d.ts +0 -10
  255. package/dist/lib/browser/secret-ref.js +0 -14
  256. package/dist/lib/browser/service.d.ts +0 -812
  257. package/dist/lib/browser/service.js +0 -4693
  258. package/dist/lib/browser/stream.d.ts +0 -17
  259. package/dist/lib/browser/stream.js +0 -72
  260. package/dist/lib/browser/task-index.d.ts +0 -78
  261. package/dist/lib/browser/task-index.js +0 -199
  262. package/dist/lib/browser/types.d.ts +0 -647
  263. package/dist/lib/browser/types.js +0 -61
  264. package/dist/lib/browser/upload.d.ts +0 -29
  265. package/dist/lib/browser/upload.js +0 -298
  266. package/dist/lib/daemon/browser-ipc-service.d.ts +0 -20
  267. package/dist/lib/daemon/browser-ipc-service.js +0 -46
  268. package/dist/lib/daemon/browser-task-reap-service.d.ts +0 -14
  269. package/dist/lib/daemon/browser-task-reap-service.js +0 -26
  270. package/dist/lib/fleet-shared-repo-sync.d.ts +0 -55
  271. package/dist/lib/fleet-shared-repo-sync.js +0 -490
@@ -2,7 +2,7 @@
2
2
  * Agent event stream parsers.
3
3
  *
4
4
  * Normalizes the heterogeneous JSON event formats emitted by each agent CLI
5
- * (Claude, Codex, Gemini, Cursor, OpenCode, Grok, Antigravity, Kimi) into a unified
5
+ * (Claude, Codex, Cursor, OpenCode, Grok, Antigravity, Kimi) into a unified
6
6
  * event schema with consistent types: init, message, tool_use, bash,
7
7
  * file_read, file_write, file_create, file_delete, result, error, and others.
8
8
  */
@@ -18,7 +18,6 @@ const droidToolUseMap = new Map();
18
18
  const TEAM_EVENT_NORMALIZERS = {
19
19
  codex: normalizeCodex,
20
20
  cursor: normalizeCursor,
21
- gemini: normalizeGemini,
22
21
  claude: normalizeClaude,
23
22
  opencode: normalizeOpencode,
24
23
  grok: normalizeGrok,
@@ -415,140 +414,6 @@ function normalizeCursor(raw) {
415
414
  timestamp: timestamp,
416
415
  }];
417
416
  }
418
- function normalizeGemini(raw) {
419
- if (!raw || typeof raw !== 'object') {
420
- return [{
421
- type: 'unknown',
422
- agent: 'gemini',
423
- raw: raw,
424
- timestamp: new Date().toISOString(),
425
- }];
426
- }
427
- const eventType = raw?.type || 'unknown';
428
- const timestamp = raw?.timestamp || new Date().toISOString();
429
- if (eventType === 'init') {
430
- return [{
431
- type: 'init',
432
- agent: 'gemini',
433
- model: raw?.model,
434
- session_id: raw?.session_id,
435
- timestamp: timestamp,
436
- }];
437
- }
438
- else if (eventType === 'message') {
439
- const role = raw?.role || 'assistant';
440
- if (role === 'assistant') {
441
- return [{
442
- type: 'message',
443
- agent: 'gemini',
444
- content: raw?.content || '',
445
- complete: !raw?.delta,
446
- timestamp: timestamp,
447
- }];
448
- }
449
- else {
450
- return [{
451
- type: 'user_message',
452
- agent: 'gemini',
453
- content: raw?.content || '',
454
- timestamp: timestamp,
455
- }];
456
- }
457
- }
458
- else if (eventType === 'tool_call' || eventType === 'tool_use') {
459
- const toolNameRaw = raw?.tool_name || raw?.name || 'unknown';
460
- const toolName = String(toolNameRaw);
461
- let toolArgsRaw = raw?.parameters;
462
- if (toolArgsRaw === null || toolArgsRaw === undefined) {
463
- toolArgsRaw = raw?.args;
464
- }
465
- const toolArgs = (typeof toolArgsRaw === 'object' && toolArgsRaw !== null) ? toolArgsRaw : {};
466
- const toolNameLower = toolName.toLowerCase();
467
- const filePath = toolArgs?.file_path || toolArgs?.path || '';
468
- const command = toolArgs?.command || '';
469
- // File write/edit tools - Gemini uses 'replace', 'edit', 'patch', 'write_file', etc.
470
- const writeTools = ['replace', 'edit', 'patch', 'write_file', 'edit_file', 'update_file', 'modify_file'];
471
- if (writeTools.includes(toolNameLower) || (toolNameLower.includes('write') && toolNameLower.includes('file'))) {
472
- if (!filePath.trim()) {
473
- return [];
474
- }
475
- return [{
476
- type: 'file_write',
477
- agent: 'gemini',
478
- tool: toolName,
479
- path: filePath,
480
- timestamp: timestamp,
481
- }];
482
- }
483
- // File read tools
484
- const readTools = ['read_file', 'view_file', 'cat_file', 'get_file'];
485
- if (readTools.includes(toolNameLower) || (toolNameLower.includes('read') && toolNameLower.includes('file'))) {
486
- if (!filePath.trim()) {
487
- return [];
488
- }
489
- return [{
490
- type: 'file_read',
491
- agent: 'gemini',
492
- tool: toolName,
493
- path: filePath,
494
- timestamp: timestamp,
495
- }];
496
- }
497
- // File delete tools
498
- const deleteTools = ['delete_file', 'remove_file', 'rm_file'];
499
- if (deleteTools.includes(toolNameLower) || (toolNameLower.includes('delete') && toolNameLower.includes('file'))) {
500
- if (!filePath.trim()) {
501
- return [];
502
- }
503
- return [{
504
- type: 'file_delete',
505
- agent: 'gemini',
506
- tool: toolName,
507
- path: filePath,
508
- timestamp: timestamp,
509
- }];
510
- }
511
- // Shell/bash tools
512
- if (['shell', 'bash', 'execute', 'run_command', 'run_shell_command'].includes(toolNameLower)) {
513
- if (!command.trim()) {
514
- return [];
515
- }
516
- return [{
517
- type: 'bash',
518
- agent: 'gemini',
519
- tool: toolName,
520
- command: command,
521
- timestamp: timestamp,
522
- }];
523
- }
524
- return [{
525
- type: 'tool_use',
526
- agent: 'gemini',
527
- tool: toolName,
528
- args: toolArgs,
529
- timestamp: timestamp,
530
- }];
531
- }
532
- else if (eventType === 'result') {
533
- const stats = raw?.stats || {};
534
- return [{
535
- type: 'result',
536
- agent: 'gemini',
537
- status: raw?.status || 'success',
538
- duration_ms: stats?.duration_ms,
539
- usage: {
540
- total_tokens: stats?.total_tokens || 0,
541
- },
542
- timestamp: timestamp,
543
- }];
544
- }
545
- return [{
546
- type: eventType,
547
- agent: 'gemini',
548
- raw: raw,
549
- timestamp: timestamp,
550
- }];
551
- }
552
417
  function normalizeClaude(raw) {
553
418
  const eventType = raw.type || 'unknown';
554
419
  const subtype = raw.subtype;
@@ -16,12 +16,16 @@
16
16
  * of the rank stays live (the pure pick recounts the roster each call), only the
17
17
  * SSH-measured load/harness snapshot is reused within the TTL.
18
18
  *
19
- * All SSH here is via `execFile` (async, bounded) so the whole pool is probed in
20
- * parallel; a slow or wedged box degrades to "no signal" instead of blocking the
21
- * launch. The readiness payload is `agents view --json`, so remote and local
22
- * candidates carry the same installed/sign-in verdict.
19
+ * All SSH here is via `spawn` (async, kill-bounded) so the whole pool is probed
20
+ * in parallel; a slow or wedged box degrades to "no signal" instead of blocking
21
+ * the launch. The readiness payload is `agents view --json`, so remote and local
22
+ * candidates carry the same installed/sign-in verdict. The dispatcher's usage
23
+ * envelope rides the same round-trip on stdin (`agents __usage-ingest` runs
24
+ * first in the probe, PHNX-4116), so the worker the pick lands on holds current
25
+ * usage numbers at dispatch rather than at the next usage-sync tick.
23
26
  */
24
- import { execFile } from 'child_process';
27
+ import { spawn } from 'child_process';
28
+ import { buildFleetStatePayload } from '../accounting/usage-sync.js';
25
29
  import { probeFleetStats, headroom } from '../devices/health.js';
26
30
  import { loadDevicesSync } from '../devices/registry.js';
27
31
  import { buildSshInvocation, writeAskpassShim } from '../devices/connect.js';
@@ -51,34 +55,62 @@ export function clearPlacementSignalCache() {
51
55
  * failure) — reachability is then left to {@link probeFleetStats}; `false` when
52
56
  * the box answered but agents-cli or the agent is absent (a genuine can't-run).
53
57
  */
54
- function probeRemoteReadiness(device, agent) {
58
+ function probeRemoteReadiness(device, agent, usagePayload) {
59
+ const unknown = { installed: undefined, signedIn: undefined, pickerEligible: undefined };
55
60
  let args;
56
61
  let env;
57
62
  try {
58
63
  const shim = writeAskpassShim();
59
- const cmd = buildReadyProbeCommand(device.shell === 'powershell' ? 'windows' : undefined);
64
+ const cmd = buildReadyProbeCommand(device.shell === 'powershell' ? 'windows' : undefined, { ingestUsage: true });
60
65
  // agentOnly: a read-only probe must never force a foreground Touch ID sheet
61
66
  // on a password-auth device (mirrors probeDeviceStats in devices/health).
62
67
  ({ args, env } = buildSshInvocation(device, [cmd], shim, {}, { agentOnly: true }));
63
68
  }
64
69
  catch {
65
- return Promise.resolve({ installed: undefined, signedIn: undefined, pickerEligible: undefined });
70
+ return Promise.resolve(unknown);
66
71
  }
67
72
  return new Promise((resolve) => {
68
- execFile('ssh', args, { encoding: 'utf-8', env: { ...process.env, ...env }, timeout: READY_PROBE_TIMEOUT_MS }, (err, stdout) => {
69
- if (err || !stdout)
70
- return resolve({ installed: undefined, signedIn: undefined, pickerEligible: undefined });
73
+ const child = spawn('ssh', args, {
74
+ stdio: ['pipe', 'pipe', 'pipe'],
75
+ env: { ...process.env, ...env },
76
+ windowsHide: true,
77
+ });
78
+ let stdout = '';
79
+ let settled = false;
80
+ const finish = (value) => {
81
+ if (settled)
82
+ return;
83
+ settled = true;
84
+ clearTimeout(timer);
85
+ clearTimeout(killTimer);
86
+ resolve(value);
87
+ };
88
+ // A wedged box degrades to "no signal": SIGTERM at the budget, SIGKILL shortly
89
+ // after, so one dead peer can never hold the whole placement wave open.
90
+ const timer = setTimeout(() => child.kill('SIGTERM'), READY_PROBE_TIMEOUT_MS);
91
+ const killTimer = setTimeout(() => { if (!settled)
92
+ child.kill('SIGKILL'); }, READY_PROBE_TIMEOUT_MS + 250);
93
+ killTimer.unref?.();
94
+ child.stdout.setEncoding('utf-8');
95
+ child.stdout.on('data', (chunk) => { stdout += chunk; });
96
+ child.stderr.resume();
97
+ child.on('error', () => finish(unknown));
98
+ child.on('close', (code) => {
99
+ if (code !== 0 || !stdout)
100
+ return finish(unknown);
71
101
  const probe = parseReadyProbe(stdout);
72
102
  if (!probe.reachable)
73
- return resolve({ installed: undefined, signedIn: undefined, pickerEligible: undefined });
103
+ return finish(unknown);
74
104
  if (!probe.version)
75
- return resolve({ installed: false, signedIn: false, pickerEligible: false });
105
+ return finish({ installed: false, signedIn: false, pickerEligible: false });
76
106
  const installed = viewHasAgent(probe.view, agent);
77
107
  const eligibility = installed
78
108
  ? viewAgentAccountEligibility(probe.view, agent)
79
109
  : { signedIn: false, pickerEligible: false };
80
- resolve({ installed, ...eligibility });
110
+ finish({ installed, ...eligibility });
81
111
  });
112
+ child.stdin.on('error', () => { });
113
+ child.stdin.end(usagePayload);
82
114
  });
83
115
  }
84
116
  /**
@@ -107,6 +139,9 @@ export async function probePoolSignals(pool, agent, opts = {}) {
107
139
  }
108
140
  const selfProfile = profiles.find((d) => normalizeHost(d.name) === self);
109
141
  const stats = await probeFleetStats(profiles, { selfName: selfProfile?.name });
142
+ // Built once per wave: this box's current usage rows (headed only — a worker
143
+ // dispatcher has none to give), ingested by every remote candidate it probes.
144
+ const usagePayload = JSON.stringify(buildFleetStatePayload({ usageOnly: true }));
110
145
  const installed = new Map(agent
111
146
  ? await Promise.all(profiles.map(async (d) => {
112
147
  const isSelf = normalizeHost(d.name) === self;
@@ -125,7 +160,7 @@ export async function probePoolSignals(pool, agent, opts = {}) {
125
160
  pickerEligible: readiness.some((candidate) => candidate.ready || isSignInRecoverable(candidate)),
126
161
  }];
127
162
  }
128
- return [d.name, await probeRemoteReadiness(d, agent)];
163
+ return [d.name, await probeRemoteReadiness(d, agent, usagePayload)];
129
164
  }))
130
165
  : []);
131
166
  const signals = new Map();
@@ -29,6 +29,13 @@ export interface DevicePlacementSignal {
29
29
  signedIn?: boolean;
30
30
  /** Requested agent has at least one row the interactive account picker can launch. */
31
31
  pickerEligible?: boolean;
32
+ /**
33
+ * The box's OWN aggregate readiness reason when it has no ready account
34
+ * (`runReady.reason`, e.g. `all signed_out`), so the placement error names why
35
+ * the remote turned itself away rather than a bare "no ready harness account"
36
+ * (PHNX-4116). Undefined when ready or when the remote gave no reason.
37
+ */
38
+ reason?: string;
32
39
  }
33
40
  /** Team fields the placement cascade reads (a subset of TeamMeta). */
34
41
  interface PlacementTeam {
@@ -8,7 +8,7 @@
8
8
  import type { CloudProviderId } from './cloud/types.js';
9
9
  import type { FeedBroadcastConfig } from './feed-broadcast.js';
10
10
  /** Unique identifier for a current or legacy AI coding agent. */
11
- export declare const AGENT_IDS: readonly ["claude", "codex", "gemini", "cursor", "opencode", "openclaw", "copilot", "amp", "goose", "antigravity", "grok", "kimi", "droid", "hermes", "muse", "warp"];
11
+ export declare const AGENT_IDS: readonly ["claude", "codex", "cursor", "opencode", "openclaw", "copilot", "amp", "goose", "antigravity", "grok", "kimi", "droid", "hermes", "muse", "warp"];
12
12
  export type AgentId = typeof AGENT_IDS[number];
13
13
  export declare function isAgentId(value: string): value is AgentId;
14
14
  /**
@@ -190,6 +190,21 @@ export interface AgentConfig {
190
190
  variableSyntax: string;
191
191
  supportsHooks: boolean;
192
192
  nativeAgentsSkillsDir?: boolean;
193
+ /**
194
+ * The harness loads a plugin's bundled skills itself, namespaced
195
+ * `<plugin>:<skill>`, from the plugin registration sync writes. Sync must
196
+ * then NOT also flatten them into top-level `skills/<name>/`: the flat copy
197
+ * is a second, un-namespaced registration of the same skill, so every
198
+ * plugin skill showed twice (`/continue` and `/sessions:continue`). Harnesses
199
+ * without this flag only see plugin skills through the flattened copies.
200
+ */
201
+ nativePluginSkills?: boolean;
202
+ /**
203
+ * Directories under the version home's `skills/` that the harness itself
204
+ * writes (Claude Code's `synced` bucket). The sync never placed them, so the
205
+ * full-sync orphan sweep and skill cleanup must never remove them.
206
+ */
207
+ ownedSkillDirs?: readonly string[];
193
208
  /**
194
209
  * This agent's *own* cloud backend. `agents cloud run --agent <id>` routes
195
210
  * here when no `--provider` is given (precedence: --provider > this >
package/dist/lib/types.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * formats for each supported agent.
7
7
  */
8
8
  /** Unique identifier for a current or legacy AI coding agent. */
9
- export const AGENT_IDS = ['claude', 'codex', 'gemini', 'cursor', 'opencode', 'openclaw', 'copilot', 'amp', 'goose', 'antigravity', 'grok', 'kimi', 'droid', 'hermes', 'muse', 'warp'];
9
+ export const AGENT_IDS = ['claude', 'codex', 'cursor', 'opencode', 'openclaw', 'copilot', 'amp', 'goose', 'antigravity', 'grok', 'kimi', 'droid', 'hermes', 'muse', 'warp'];
10
10
  export function isAgentId(value) {
11
11
  return AGENT_IDS.includes(value);
12
12
  }
@@ -66,6 +66,33 @@ export interface ViewJsonVersion {
66
66
  } | null;
67
67
  resources?: VersionResourcesJson;
68
68
  }
69
+ /**
70
+ * Whether a `run` on THIS box would find a launch-ready account for the agent,
71
+ * computed from the SAME enumeration the local router uses
72
+ * (`collectRunCandidates` → `readinessFromCandidate`), including native slots
73
+ * (`fromSlot`) — NOT the per-version `versions[]` list, which enumerates version
74
+ * homes and misses the account slots a run actually picks from (PHNX-4116). This
75
+ * is the one readiness gate: `agents run --device auto` reads THIS answer off
76
+ * `agents view --json` rather than re-deriving freshness on the dispatching box,
77
+ * so the box that runs and the box that dispatches can never disagree.
78
+ */
79
+ export interface ViewJsonRunReady {
80
+ /** At least one account is launch-ready right now. */
81
+ ready: boolean;
82
+ /**
83
+ * When ready, names a ready account (`ready (work)`); when not, the aggregate
84
+ * exclusion reason the dispatcher surfaces (`all signed_out`, `no accounts`, or
85
+ * a comma list of distinct reasons).
86
+ */
87
+ reason: string;
88
+ /** Per-account verdict; `reason` is `'ready'` for a ready account, else the
89
+ * `readinessFromCandidate` reason (`signed_out`/`revoked`/`rate_limited`/…). */
90
+ accounts: Array<{
91
+ name: string;
92
+ ready: boolean;
93
+ reason: string;
94
+ }>;
95
+ }
69
96
  export interface ViewJsonAgent {
70
97
  agent: AgentId;
71
98
  versions: ViewJsonVersion[];
@@ -74,6 +101,11 @@ export interface ViewJsonAgent {
74
101
  * never the internal catalog row, so consumers (AGI EXT) read one shape.
75
102
  */
76
103
  accounts?: AccountListEntryJson[];
104
+ /**
105
+ * The one run-readiness gate for this agent on this box (PHNX-4116). Absent on
106
+ * an older remote CLI, whose readers fall back to per-version `launchable`.
107
+ */
108
+ runReady?: ViewJsonRunReady;
77
109
  harnesses: ProfileSummary[];
78
110
  }
79
111
  export type ResourceSection = 'commands' | 'skills' | 'mcp' | 'memory' | 'hooks' | 'workflows' | 'plugins';
@@ -35,7 +35,6 @@ const WATCHDOG_SESSION_LAYOUT = {
35
35
  claude: { subdir: 'projects', ext: '.jsonl' },
36
36
  codex: { subdir: 'sessions', ext: '.jsonl' },
37
37
  droid: { subdir: 'sessions', ext: '.jsonl' },
38
- gemini: { subdir: 'tmp', ext: '.json' },
39
38
  };
40
39
  const WATCHDOG_SESSION_LAYOUT_DEFAULT = { subdir: 'projects', ext: '.jsonl' };
41
40
  /**
@@ -145,7 +145,6 @@ function nextOccurrenceMs(hour, minute, timeZone, nowMs) {
145
145
  export const ROTATE_EXIT_SEQUENCES = {
146
146
  claude: ['\x1b', '\x03', '\x03'], // Esc, Ctrl+C, Ctrl+C (Esc first for Claude)
147
147
  codex: ['\x03', '\x03'], // Ctrl+C twice
148
- gemini: ['\x03', '\x03'],
149
148
  cursor: ['\x03', '\x03'],
150
149
  opencode: ['\x03', '\x03'],
151
150
  };
@@ -314,7 +313,7 @@ export async function defaultRotateGate() {
314
313
  }
315
314
  // --- readiness --------------------------------------------------------------------
316
315
  /** Transcript layouts to probe for the new session (mirrors read.ts's table). */
317
- const ROTATE_TRANSCRIPT_AGENTS = ['claude', 'codex', 'gemini', 'droid'];
316
+ const ROTATE_TRANSCRIPT_AGENTS = ['claude', 'codex', 'droid'];
318
317
  /**
319
318
  * Default TUI-liveness probe for the relaunched session: the new session's
320
319
  * transcript resolves under any known harness layout. `--session-id` is honored
@@ -379,7 +379,7 @@ export async function runWatchdogTick(opts = {}) {
379
379
  session,
380
380
  candidate: {
381
381
  terminalId: sid,
382
- agentType: session.kind === 'codex' || session.kind === 'gemini' ? session.kind : 'claude',
382
+ agentType: session.kind === 'codex' ? 'codex' : 'claude',
383
383
  tailLines: tail,
384
384
  stalledForMs: st.stalledForMs,
385
385
  task: session.topic ?? session.label ?? session.name,
@@ -502,7 +502,7 @@ export async function runWatchdogTick(opts = {}) {
502
502
  const tailLines = tailCache.get(session.sessionId) ?? tailFor(session);
503
503
  const candidate = {
504
504
  terminalId: session.sessionId,
505
- agentType: (session.kind === 'codex' || session.kind === 'gemini' ? session.kind : 'claude'),
505
+ agentType: (session.kind === 'codex' ? 'codex' : 'claude'),
506
506
  tailLines,
507
507
  stalledForMs: status.stalledForMs,
508
508
  };
@@ -1,6 +1,6 @@
1
1
  export interface WatchdogCandidate {
2
2
  terminalId: string;
3
- agentType: 'claude' | 'codex' | 'gemini';
3
+ agentType: 'claude' | 'codex';
4
4
  tailLines: string[];
5
5
  stalledForMs: number;
6
6
  /** The originating task / first prompt / topic — so the agent can judge "was given a task but hasn't finished it". */
@@ -32,13 +32,6 @@ export function summarizeWatchdogTail(tailLines, agentType) {
32
32
  else if (claim?.role === 'assistant' && !lastAssistant)
33
33
  lastAssistant = claim.text;
34
34
  }
35
- else if (agentType === 'gemini') {
36
- const claim = readGemini(raw);
37
- if (claim?.role === 'user' && !lastUser)
38
- lastUser = claim.text;
39
- else if (claim?.role === 'assistant' && !lastAssistant)
40
- lastAssistant = claim.text;
41
- }
42
35
  }
43
36
  return {
44
37
  lastUserMessage: clip(lastUser),
@@ -71,30 +64,6 @@ function readCodex(raw) {
71
64
  return null;
72
65
  return { role, text };
73
66
  }
74
- // Gemini stores chats as a single pretty-printed JSON document — readTailLines
75
- // returns partial fragments that JSON.parse rejects. So in practice this
76
- // branch only fires for the JSONL-style events that some Gemini wrappers emit
77
- // during a live session. Acceptable: the result is an empty summary, which
78
- // the UI handles gracefully.
79
- function readGemini(raw) {
80
- const t = raw.type;
81
- if (t === 'user_message') {
82
- const text = typeof raw.text === 'string' ? raw.text : null;
83
- return text ? { role: 'user', text } : null;
84
- }
85
- if (t === 'agent_message' || t === 'model_message') {
86
- const text = typeof raw.text === 'string' ? raw.text : null;
87
- return text ? { role: 'assistant', text } : null;
88
- }
89
- if (t === 'message') {
90
- const role = raw.role === 'user' ? 'user' : raw.role === 'assistant' ? 'assistant' : null;
91
- if (!role)
92
- return null;
93
- const text = typeof raw.text === 'string' ? raw.text : pickContentText(raw.content);
94
- return text ? { role, text } : null;
95
- }
96
- return null;
97
- }
98
67
  function pickContentText(content) {
99
68
  if (typeof content === 'string')
100
69
  return isSyntheticTagged(content) ? null : content;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phnx-labs/agents-cli",
3
- "version": "1.22.117",
3
+ "version": "1.22.119",
4
4
  "description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -50,16 +50,16 @@ function shellQuote(value) {
50
50
  }
51
51
 
52
52
  // Shorthands that delegate to the installed agents-cli entrypoint.
53
- const ALIASES = ['browser', 'teams'];
53
+ const ALIASES = ['teams'];
54
54
 
55
55
  // Aliases this script USED to write and must now remove on every install.
56
- // `secrets` (PHNX-3989), `sessions` (PHNX-4012), and `pty` (PHNX-4091): those
57
- // names belong to standalone CLIs now (`pty` -> `@phnx-labs/term-cli`'s
58
- // `term`). An alias shim that `exec`s `agents <name>` re-enters the
59
- // passthrough and shadows the real binary because the shims dir sits first on
60
- // PATH — the 1.22.85 fork bomb. Postinstall is the upgrade path that actually
61
- // runs, so it prunes here.
62
- const RETIRED_ALIASES = ['secrets', 'sessions', 'pty'];
56
+ // `secrets` (PHNX-3989), `sessions` (PHNX-4012), `pty` (PHNX-4091), and
57
+ // `browser` (PHNX-4101): those names belong to standalone CLIs now (`pty` ->
58
+ // `@phnx-labs/term-cli`'s `term`; `browser` -> `@phnx-labs/browser-cli`). An
59
+ // alias shim that `exec`s `agents <name>` re-enters the passthrough and shadows
60
+ // the real binary because the shims dir sits first on PATH — the 1.22.85 fork
61
+ // bomb. Postinstall is the upgrade path that actually runs, so it prunes here.
62
+ const RETIRED_ALIASES = ['secrets', 'sessions', 'pty', 'browser'];
63
63
 
64
64
  function removeRetiredAliasShims() {
65
65
  for (const name of RETIRED_ALIASES) {
package/dist/browser.d.ts DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
package/dist/browser.js DELETED
@@ -1,17 +0,0 @@
1
- #!/usr/bin/env node
2
- import { Command } from 'commander';
3
- import { registerBrowserSubcommands } from './commands/browser.js';
4
- import { maybeRunStandaloneOnHost } from './lib/hosts/passthrough.js';
5
- async function main() {
6
- // `browser … --device <box>` routes to a remote over SSH, exactly like
7
- // `agents browser … --device <box>` does through index.ts. Standalone-only:
8
- // this binary never enters index.ts, so without this the flag was dropped.
9
- if (await maybeRunStandaloneOnHost('browser')) {
10
- process.exit(process.exitCode ?? 0);
11
- }
12
- const program = new Command();
13
- program.name('browser').description('Browser automation via CDP');
14
- registerBrowserSubcommands(program);
15
- program.parse();
16
- }
17
- void main();
@@ -1,18 +0,0 @@
1
- import type { ProfileStatus, TaskStatus } from '../lib/browser/types.js';
2
- export interface BrowserTask {
3
- task: TaskStatus;
4
- profile: ProfileStatus;
5
- }
6
- interface PickedBrowserTask {
7
- task: BrowserTask;
8
- action: 'view' | 'stop';
9
- }
10
- interface BrowserPickerConfig {
11
- message: string;
12
- tasks: BrowserTask[];
13
- pageSize?: number;
14
- initialSearch?: string;
15
- }
16
- /** Show an interactive browser task picker. */
17
- export declare function browserTaskPicker(config: BrowserPickerConfig): Promise<PickedBrowserTask | null>;
18
- export {};
@@ -1,97 +0,0 @@
1
- /**
2
- * Interactive browser task picker with preview.
3
- *
4
- * Powers the fuzzy-searchable task list shown by `agents browser status` in a TTY.
5
- * Shows task name, profile, domains, and tab count; preview pane shows full tab list.
6
- */
7
- import chalk from 'chalk';
8
- import { truncate, humanDuration } from '../lib/format.js';
9
- import { itemPicker } from '../lib/picker.js';
10
- const DOT = chalk.gray(' · ');
11
- function formatAge(ms) {
12
- return humanDuration(Date.now() - ms) + ' ago';
13
- }
14
- /** Build the preview pane for a browser task. */
15
- function buildBrowserPreview(item) {
16
- const { task, profile } = item;
17
- const lines = [];
18
- // Line 1: Task name (ID: xxx)
19
- lines.push(chalk.bold.white(`Task: ${task.name}`) + chalk.gray(` (${task.id})`));
20
- // Line 2: Profile info
21
- const profileParts = [chalk.cyan(profile.name)];
22
- if (profile.port)
23
- profileParts.push(`port ${profile.port}`);
24
- if (profile.pid)
25
- profileParts.push(`pid ${profile.pid}`);
26
- lines.push(chalk.gray('Profile: ') + profileParts.join(DOT));
27
- // Line 3: Timing
28
- const started = formatAge(task.createdAt);
29
- const duration = humanDuration(Date.now() - task.createdAt);
30
- lines.push(chalk.gray('Started: ') + chalk.white(started) + DOT + chalk.gray('Duration: ') + chalk.white(duration));
31
- // Blank line
32
- lines.push('');
33
- // Tabs section
34
- if (task.tabs && task.tabs.length > 0) {
35
- lines.push(chalk.gray('Tabs:'));
36
- const termWidth = process.stdout.columns || 80;
37
- const urlMax = Math.max(30, termWidth - 16);
38
- for (const tab of task.tabs) {
39
- const marker = tab.current ? chalk.yellow('*') : ' ';
40
- const id = chalk.gray(tab.id);
41
- const url = truncate(tab.url, urlMax);
42
- lines.push(` ${id} ${marker} ${chalk.white(url)}`);
43
- }
44
- }
45
- else {
46
- lines.push(chalk.gray('No tabs open'));
47
- }
48
- return lines.join('\n');
49
- }
50
- /** Build the list label for a browser task. */
51
- function buildBrowserLabel(item, query) {
52
- const { task, profile } = item;
53
- const termWidth = process.stdout.columns || 80;
54
- // Format: name profile tabs domains age
55
- const name = task.name.padEnd(20);
56
- const profileName = profile.name.padEnd(12);
57
- const tabs = `${task.tabCount} tab${task.tabCount === 1 ? '' : 's'}`.padEnd(8);
58
- const age = formatAge(task.createdAt);
59
- // Domains - fill remaining space
60
- const fixedWidth = 20 + 12 + 8 + 10; // name + profile + tabs + age
61
- const domainsWidth = Math.max(10, termWidth - fixedWidth - 4);
62
- const domains = task.domains?.length
63
- ? truncate(task.domains.join(', '), domainsWidth)
64
- : chalk.gray('no sites');
65
- // Highlight query matches
66
- let label = `${name}${chalk.cyan(profileName)}${tabs}${domains.padEnd(domainsWidth)}${chalk.gray(age)}`;
67
- if (query) {
68
- const re = new RegExp(`(${query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
69
- label = label.replace(re, chalk.yellow.bold('$1'));
70
- }
71
- return label;
72
- }
73
- /** Show an interactive browser task picker. */
74
- export async function browserTaskPicker(config) {
75
- const picked = await itemPicker({
76
- message: config.message,
77
- items: config.tasks,
78
- filter: (query) => {
79
- if (!query)
80
- return config.tasks;
81
- const q = query.toLowerCase();
82
- return config.tasks.filter((t) => t.task.name.toLowerCase().includes(q) ||
83
- t.profile.name.toLowerCase().includes(q) ||
84
- t.task.domains?.some((d) => d.toLowerCase().includes(q)));
85
- },
86
- labelFor: buildBrowserLabel,
87
- buildPreview: buildBrowserPreview,
88
- shortIdFor: (t) => t.task.name,
89
- pageSize: config.pageSize ?? 10,
90
- initialSearch: config.initialSearch,
91
- emptyMessage: 'No browser tasks running.',
92
- enterHint: 'view tabs',
93
- });
94
- if (!picked)
95
- return null;
96
- return { task: picked.item, action: 'view' };
97
- }