@phnx-labs/agents-cli 1.22.117 → 1.22.118

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 (248) hide show
  1. package/CHANGELOG.md +235 -0
  2. package/dist/bootstrap.js +4 -11
  3. package/dist/commands/browser.d.ts +52 -70
  4. package/dist/commands/browser.js +192 -3562
  5. package/dist/commands/daemon.d.ts +39 -2
  6. package/dist/commands/daemon.js +72 -73
  7. package/dist/commands/events.js +1 -1
  8. package/dist/commands/exec.js +53 -1
  9. package/dist/commands/repo.js +13 -49
  10. package/dist/commands/route.js +5 -5
  11. package/dist/commands/routines.js +39 -12
  12. package/dist/commands/sessions-render.d.ts +1 -1
  13. package/dist/commands/sessions-render.js +0 -1
  14. package/dist/commands/sessions.test-fixture.d.ts +0 -1
  15. package/dist/commands/sessions.test-fixture.js +0 -37
  16. package/dist/commands/setup-browser.d.ts +17 -12
  17. package/dist/commands/setup-browser.js +52 -87
  18. package/dist/commands/setup-preferences.d.ts +0 -17
  19. package/dist/commands/setup-preferences.js +18 -49
  20. package/dist/commands/setup.js +9 -9
  21. package/dist/commands/ssh.js +10 -1
  22. package/dist/commands/sync.js +6 -44
  23. package/dist/commands/teams.js +0 -1
  24. package/dist/commands/update.d.ts +19 -0
  25. package/dist/commands/update.js +34 -6
  26. package/dist/commands/view.d.ts +12 -1
  27. package/dist/commands/view.js +47 -2
  28. package/dist/index.js +107 -26
  29. package/dist/lib/account-capabilities.js +0 -2
  30. package/dist/lib/account-catalog.d.ts +38 -0
  31. package/dist/lib/account-catalog.js +110 -26
  32. package/dist/lib/account-provider-registry.js +1 -1
  33. package/dist/lib/account-registry.d.ts +1 -1
  34. package/dist/lib/account-registry.js +36 -2
  35. package/dist/lib/accounting/rotate.js +22 -8
  36. package/dist/lib/accounting/usage-ingest.d.ts +6 -1
  37. package/dist/lib/accounting/usage-ingest.js +115 -45
  38. package/dist/lib/accounting/usage-sync.d.ts +134 -31
  39. package/dist/lib/accounting/usage-sync.js +227 -66
  40. package/dist/lib/accounting/usage.d.ts +16 -3
  41. package/dist/lib/accounting/usage.js +26 -8
  42. package/dist/lib/accounts/slots.d.ts +8 -0
  43. package/dist/lib/accounts/slots.js +17 -0
  44. package/dist/lib/add-dir.js +0 -1
  45. package/dist/lib/agent-cli-commands.js +0 -1
  46. package/dist/lib/agent-spec/agents.d.ts +6 -7
  47. package/dist/lib/agent-spec/agents.js +6 -60
  48. package/dist/lib/auth-health.d.ts +69 -1
  49. package/dist/lib/auth-health.js +133 -4
  50. package/dist/lib/browser/context.d.ts +82 -0
  51. package/dist/lib/browser/context.js +88 -0
  52. package/dist/lib/browser/paths.d.ts +25 -0
  53. package/dist/lib/browser/paths.js +51 -0
  54. package/dist/lib/browser/record.d.ts +38 -0
  55. package/dist/lib/browser/record.js +57 -0
  56. package/dist/lib/browser/sessions-list.js +1 -2
  57. package/dist/lib/browser-client.d.ts +158 -0
  58. package/dist/lib/browser-client.js +234 -0
  59. package/dist/lib/channels/owner-sink.d.ts +1 -18
  60. package/dist/lib/channels/owner-sink.js +29 -82
  61. package/dist/lib/channels/providers/rush.d.ts +7 -7
  62. package/dist/lib/channels/providers/rush.js +127 -57
  63. package/dist/lib/claude-statusline.d.ts +1 -1
  64. package/dist/lib/claude-statusline.js +15 -3
  65. package/dist/lib/commands.js +1 -1
  66. package/dist/lib/crabbox/lease.js +1 -1
  67. package/dist/lib/crabbox/runtimes.js +1 -3
  68. package/dist/lib/daemon/account-state-daemon-service.js +1 -0
  69. package/dist/lib/daemon/auth-sync-service.d.ts +21 -18
  70. package/dist/lib/daemon/auth-sync-service.js +37 -43
  71. package/dist/lib/daemon/daemon.d.ts +25 -14
  72. package/dist/lib/daemon/daemon.js +92 -154
  73. package/dist/lib/daemon/runner.d.ts +2 -2
  74. package/dist/lib/daemon/runner.js +32 -9
  75. package/dist/lib/daemon/self-update-service.d.ts +35 -34
  76. package/dist/lib/daemon/self-update-service.js +40 -36
  77. package/dist/lib/daemon/service.d.ts +12 -8
  78. package/dist/lib/daemon/supervisor.d.ts +65 -54
  79. package/dist/lib/daemon/supervisor.js +136 -171
  80. package/dist/lib/daemon/testdata/process-view-start.js +5 -2
  81. package/dist/lib/daemon/usage-sync-service.d.ts +21 -30
  82. package/dist/lib/daemon/usage-sync-service.js +46 -78
  83. package/dist/lib/daemon-health.d.ts +22 -5
  84. package/dist/lib/daemon-health.js +47 -3
  85. package/dist/lib/daemon-services.d.ts +1 -1
  86. package/dist/lib/daemon-services.js +0 -10
  87. package/dist/lib/daemon-ticks.js +4 -2
  88. package/dist/lib/devices/doctor-findings.js +8 -11
  89. package/dist/lib/devices/harness-inventory.js +14 -5
  90. package/dist/lib/exec-account-home.js +5 -0
  91. package/dist/lib/exec.js +0 -11
  92. package/dist/lib/feed/events.js +0 -1
  93. package/dist/lib/feed/tool-activity.js +5 -2
  94. package/dist/lib/fleet-shared-state.d.ts +45 -0
  95. package/dist/lib/fleet-shared-state.js +78 -9
  96. package/dist/lib/fs-atomic.js +25 -0
  97. package/dist/lib/harness-auth-capabilities.js +0 -1
  98. package/dist/lib/helper-versions.js +1 -1
  99. package/dist/lib/hooks/cache.d.ts +7 -0
  100. package/dist/lib/hooks/cache.js +27 -2
  101. package/dist/lib/hooks/install.js +7 -1
  102. package/dist/lib/hosts/ready.d.ts +38 -8
  103. package/dist/lib/hosts/ready.js +62 -14
  104. package/dist/lib/installations/active-check.d.ts +27 -0
  105. package/dist/lib/installations/active-check.js +61 -0
  106. package/dist/lib/installations/migrate.js +6 -3
  107. package/dist/lib/installations/shims.js +9 -7
  108. package/dist/lib/installations/update.js +21 -15
  109. package/dist/lib/installations/versions.js +12 -1
  110. package/dist/lib/manifest.js +1 -1
  111. package/dist/lib/models.js +0 -2
  112. package/dist/lib/open-url.js +28 -54
  113. package/dist/lib/profiles.js +0 -2
  114. package/dist/lib/refresh.js +1 -1
  115. package/dist/lib/routine-readiness.d.ts +35 -0
  116. package/dist/lib/routine-readiness.js +74 -16
  117. package/dist/lib/rules/compile.js +6 -0
  118. package/dist/lib/sandbox.js +0 -2
  119. package/dist/lib/secrets-policy.d.ts +89 -11
  120. package/dist/lib/secrets-policy.js +175 -28
  121. package/dist/lib/session/db.d.ts +1 -1
  122. package/dist/lib/session/db.js +65 -18
  123. package/dist/lib/session/discover.js +1 -283
  124. package/dist/lib/session/mirror.d.ts +4 -3
  125. package/dist/lib/session/mirror.js +4 -3
  126. package/dist/lib/session/parse.d.ts +0 -2
  127. package/dist/lib/session/parse.js +1 -138
  128. package/dist/lib/session/throughput.d.ts +1 -3
  129. package/dist/lib/session/throughput.js +0 -20
  130. package/dist/lib/session/types.d.ts +2 -2
  131. package/dist/lib/session/types.js +2 -2
  132. package/dist/lib/sessions-client.d.ts +60 -1
  133. package/dist/lib/sessions-client.js +227 -4
  134. package/dist/lib/setup-tool-status.js +5 -10
  135. package/dist/lib/signin-badge.d.ts +3 -2
  136. package/dist/lib/signin-badge.js +6 -3
  137. package/dist/lib/smart-launch.js +4 -1
  138. package/dist/lib/staleness/detectors/commands.d.ts +1 -1
  139. package/dist/lib/staleness/detectors/hooks.d.ts +1 -1
  140. package/dist/lib/staleness/detectors/mcp.d.ts +1 -1
  141. package/dist/lib/staleness/detectors/permissions.d.ts +1 -1
  142. package/dist/lib/staleness/detectors/plugins.d.ts +1 -1
  143. package/dist/lib/staleness/detectors/rules.d.ts +1 -1
  144. package/dist/lib/staleness/detectors/skills.d.ts +1 -1
  145. package/dist/lib/staleness/detectors/subagents.d.ts +1 -1
  146. package/dist/lib/staleness/detectors/workflows.d.ts +1 -1
  147. package/dist/lib/staleness/writers/commands.d.ts +1 -1
  148. package/dist/lib/staleness/writers/hooks.d.ts +1 -1
  149. package/dist/lib/staleness/writers/mcp.d.ts +1 -1
  150. package/dist/lib/staleness/writers/permissions.d.ts +1 -1
  151. package/dist/lib/staleness/writers/plugins.d.ts +1 -1
  152. package/dist/lib/staleness/writers/rules.d.ts +1 -1
  153. package/dist/lib/staleness/writers/skills.d.ts +1 -1
  154. package/dist/lib/staleness/writers/sources.js +5 -0
  155. package/dist/lib/staleness/writers/subagents.d.ts +1 -1
  156. package/dist/lib/staleness/writers/workflows.d.ts +1 -1
  157. package/dist/lib/state.d.ts +6 -4
  158. package/dist/lib/state.js +7 -17
  159. package/dist/lib/teams/parsers.d.ts +1 -1
  160. package/dist/lib/teams/parsers.js +1 -136
  161. package/dist/lib/teams/placement-probe.js +50 -15
  162. package/dist/lib/teams/scheduler.d.ts +7 -0
  163. package/dist/lib/types.d.ts +10 -1
  164. package/dist/lib/types.js +1 -1
  165. package/dist/lib/view-types.d.ts +32 -0
  166. package/dist/lib/watchdog/read.js +0 -1
  167. package/dist/lib/watchdog/rotate.js +1 -2
  168. package/dist/lib/watchdog/runner.js +2 -2
  169. package/dist/lib/watchdog/watchdog.d.ts +1 -1
  170. package/dist/lib/watchdog/watchdogTail.js +0 -31
  171. package/package.json +1 -1
  172. package/scripts/postinstall.js +8 -8
  173. package/dist/browser.d.ts +0 -2
  174. package/dist/browser.js +0 -17
  175. package/dist/commands/browser-picker.d.ts +0 -18
  176. package/dist/commands/browser-picker.js +0 -97
  177. package/dist/lib/browser/arc-discovery.d.ts +0 -52
  178. package/dist/lib/browser/arc-discovery.js +0 -197
  179. package/dist/lib/browser/arc-dom.d.ts +0 -14
  180. package/dist/lib/browser/arc-dom.js +0 -121
  181. package/dist/lib/browser/caller-identity.d.ts +0 -40
  182. package/dist/lib/browser/caller-identity.js +0 -175
  183. package/dist/lib/browser/cdp.d.ts +0 -51
  184. package/dist/lib/browser/cdp.js +0 -254
  185. package/dist/lib/browser/chrome.d.ts +0 -141
  186. package/dist/lib/browser/chrome.js +0 -704
  187. package/dist/lib/browser/chromium-discovery.d.ts +0 -28
  188. package/dist/lib/browser/chromium-discovery.js +0 -96
  189. package/dist/lib/browser/devices.d.ts +0 -34
  190. package/dist/lib/browser/devices.js +0 -61
  191. package/dist/lib/browser/domain-skills.d.ts +0 -71
  192. package/dist/lib/browser/domain-skills.js +0 -195
  193. package/dist/lib/browser/drivers/arc.d.ts +0 -40
  194. package/dist/lib/browser/drivers/arc.js +0 -276
  195. package/dist/lib/browser/drivers/firefox.d.ts +0 -99
  196. package/dist/lib/browser/drivers/firefox.js +0 -377
  197. package/dist/lib/browser/drivers/local.d.ts +0 -55
  198. package/dist/lib/browser/drivers/local.js +0 -266
  199. package/dist/lib/browser/drivers/ssh.d.ts +0 -120
  200. package/dist/lib/browser/drivers/ssh.js +0 -467
  201. package/dist/lib/browser/editor.d.ts +0 -3
  202. package/dist/lib/browser/editor.js +0 -50
  203. package/dist/lib/browser/ffmpeg.d.ts +0 -12
  204. package/dist/lib/browser/ffmpeg.js +0 -184
  205. package/dist/lib/browser/firefox-discovery.d.ts +0 -69
  206. package/dist/lib/browser/firefox-discovery.js +0 -162
  207. package/dist/lib/browser/har.d.ts +0 -85
  208. package/dist/lib/browser/har.js +0 -77
  209. package/dist/lib/browser/hygiene.d.ts +0 -97
  210. package/dist/lib/browser/hygiene.js +0 -153
  211. package/dist/lib/browser/index.d.ts +0 -5
  212. package/dist/lib/browser/index.js +0 -5
  213. package/dist/lib/browser/input.d.ts +0 -7
  214. package/dist/lib/browser/input.js +0 -92
  215. package/dist/lib/browser/ipc.d.ts +0 -204
  216. package/dist/lib/browser/ipc.js +0 -1453
  217. package/dist/lib/browser/login-detection.d.ts +0 -87
  218. package/dist/lib/browser/login-detection.js +0 -274
  219. package/dist/lib/browser/profiles.d.ts +0 -422
  220. package/dist/lib/browser/profiles.js +0 -1157
  221. package/dist/lib/browser/refs.d.ts +0 -89
  222. package/dist/lib/browser/refs.js +0 -191
  223. package/dist/lib/browser/registry.d.ts +0 -70
  224. package/dist/lib/browser/registry.js +0 -252
  225. package/dist/lib/browser/remote-control.d.ts +0 -59
  226. package/dist/lib/browser/remote-control.js +0 -85
  227. package/dist/lib/browser/resolve-target.d.ts +0 -66
  228. package/dist/lib/browser/resolve-target.js +0 -264
  229. package/dist/lib/browser/runtime-state.d.ts +0 -291
  230. package/dist/lib/browser/runtime-state.js +0 -584
  231. package/dist/lib/browser/secret-ref.d.ts +0 -10
  232. package/dist/lib/browser/secret-ref.js +0 -14
  233. package/dist/lib/browser/service.d.ts +0 -812
  234. package/dist/lib/browser/service.js +0 -4693
  235. package/dist/lib/browser/stream.d.ts +0 -17
  236. package/dist/lib/browser/stream.js +0 -72
  237. package/dist/lib/browser/task-index.d.ts +0 -78
  238. package/dist/lib/browser/task-index.js +0 -199
  239. package/dist/lib/browser/types.d.ts +0 -647
  240. package/dist/lib/browser/types.js +0 -61
  241. package/dist/lib/browser/upload.d.ts +0 -29
  242. package/dist/lib/browser/upload.js +0 -298
  243. package/dist/lib/daemon/browser-ipc-service.d.ts +0 -20
  244. package/dist/lib/daemon/browser-ipc-service.js +0 -46
  245. package/dist/lib/daemon/browser-task-reap-service.d.ts +0 -14
  246. package/dist/lib/daemon/browser-task-reap-service.js +0 -26
  247. package/dist/lib/fleet-shared-repo-sync.d.ts +0 -55
  248. package/dist/lib/fleet-shared-repo-sync.js +0 -490
@@ -1,5 +1,5 @@
1
1
  /** Supported agent CLI types for team spawning. */
2
- export type AgentType = 'codex' | 'gemini' | 'cursor' | 'claude' | 'opencode' | 'grok' | 'antigravity' | 'kimi' | 'droid' | 'warp';
2
+ export type AgentType = 'codex' | 'cursor' | 'claude' | 'opencode' | 'grok' | 'antigravity' | 'kimi' | 'droid' | 'warp';
3
3
  /** Normalize a raw JSON event from any agent type into an array of unified event objects. */
4
4
  export declare function normalizeEvents(agentType: AgentType, raw: any): any[];
5
5
  /** Normalize a raw JSON event, returning only the first unified event (convenience wrapper). */
@@ -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,15 @@ 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;
193
202
  /**
194
203
  * This agent's *own* cloud backend. `agents cloud run --agent <id>` routes
195
204
  * 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.118",
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
- }