@phnx-labs/agents-cli 1.22.116 → 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 (254) hide show
  1. package/CHANGELOG.md +247 -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.d.ts +18 -0
  9. package/dist/commands/exec.js +104 -12
  10. package/dist/commands/repo.js +13 -49
  11. package/dist/commands/route.js +5 -5
  12. package/dist/commands/routines.js +39 -12
  13. package/dist/commands/sessions-render.d.ts +1 -1
  14. package/dist/commands/sessions-render.js +0 -1
  15. package/dist/commands/sessions.test-fixture.d.ts +0 -1
  16. package/dist/commands/sessions.test-fixture.js +0 -37
  17. package/dist/commands/setup-browser.d.ts +17 -12
  18. package/dist/commands/setup-browser.js +52 -87
  19. package/dist/commands/setup-preferences.d.ts +0 -17
  20. package/dist/commands/setup-preferences.js +18 -49
  21. package/dist/commands/setup-term.d.ts +4 -3
  22. package/dist/commands/setup-term.js +5 -4
  23. package/dist/commands/setup.js +11 -11
  24. package/dist/commands/ssh.js +10 -1
  25. package/dist/commands/sync.js +6 -44
  26. package/dist/commands/teams.js +0 -1
  27. package/dist/commands/update.d.ts +19 -0
  28. package/dist/commands/update.js +34 -6
  29. package/dist/commands/view.d.ts +12 -1
  30. package/dist/commands/view.js +47 -2
  31. package/dist/index.js +107 -26
  32. package/dist/lib/account-capabilities.js +0 -2
  33. package/dist/lib/account-catalog.d.ts +38 -0
  34. package/dist/lib/account-catalog.js +110 -26
  35. package/dist/lib/account-provider-registry.js +1 -1
  36. package/dist/lib/account-registry.d.ts +1 -1
  37. package/dist/lib/account-registry.js +36 -2
  38. package/dist/lib/accounting/rotate.js +22 -8
  39. package/dist/lib/accounting/usage-ingest.d.ts +6 -1
  40. package/dist/lib/accounting/usage-ingest.js +115 -45
  41. package/dist/lib/accounting/usage-sync.d.ts +134 -31
  42. package/dist/lib/accounting/usage-sync.js +227 -66
  43. package/dist/lib/accounting/usage.d.ts +16 -3
  44. package/dist/lib/accounting/usage.js +26 -8
  45. package/dist/lib/accounts/slots.d.ts +8 -0
  46. package/dist/lib/accounts/slots.js +17 -0
  47. package/dist/lib/add-dir.js +0 -1
  48. package/dist/lib/agent-cli-commands.js +0 -1
  49. package/dist/lib/agent-spec/agents.d.ts +6 -7
  50. package/dist/lib/agent-spec/agents.js +6 -60
  51. package/dist/lib/auth-health.d.ts +69 -1
  52. package/dist/lib/auth-health.js +133 -4
  53. package/dist/lib/browser/context.d.ts +82 -0
  54. package/dist/lib/browser/context.js +88 -0
  55. package/dist/lib/browser/paths.d.ts +25 -0
  56. package/dist/lib/browser/paths.js +51 -0
  57. package/dist/lib/browser/record.d.ts +38 -0
  58. package/dist/lib/browser/record.js +57 -0
  59. package/dist/lib/browser/sessions-list.js +1 -2
  60. package/dist/lib/browser-client.d.ts +158 -0
  61. package/dist/lib/browser-client.js +234 -0
  62. package/dist/lib/channels/owner-sink.d.ts +1 -18
  63. package/dist/lib/channels/owner-sink.js +29 -82
  64. package/dist/lib/channels/providers/rush.d.ts +7 -7
  65. package/dist/lib/channels/providers/rush.js +127 -57
  66. package/dist/lib/claude-statusline.d.ts +1 -1
  67. package/dist/lib/claude-statusline.js +15 -3
  68. package/dist/lib/commands.js +1 -1
  69. package/dist/lib/crabbox/lease.js +1 -1
  70. package/dist/lib/crabbox/runtimes.js +1 -3
  71. package/dist/lib/daemon/account-state-daemon-service.js +1 -0
  72. package/dist/lib/daemon/auth-sync-service.d.ts +21 -18
  73. package/dist/lib/daemon/auth-sync-service.js +37 -43
  74. package/dist/lib/daemon/daemon.d.ts +25 -14
  75. package/dist/lib/daemon/daemon.js +92 -154
  76. package/dist/lib/daemon/runner.d.ts +2 -2
  77. package/dist/lib/daemon/runner.js +32 -9
  78. package/dist/lib/daemon/self-update-service.d.ts +35 -34
  79. package/dist/lib/daemon/self-update-service.js +40 -36
  80. package/dist/lib/daemon/service.d.ts +12 -8
  81. package/dist/lib/daemon/supervisor.d.ts +65 -54
  82. package/dist/lib/daemon/supervisor.js +136 -171
  83. package/dist/lib/daemon/testdata/process-view-start.js +5 -2
  84. package/dist/lib/daemon/usage-sync-service.d.ts +21 -30
  85. package/dist/lib/daemon/usage-sync-service.js +46 -78
  86. package/dist/lib/daemon-health.d.ts +22 -5
  87. package/dist/lib/daemon-health.js +47 -3
  88. package/dist/lib/daemon-services.d.ts +1 -1
  89. package/dist/lib/daemon-services.js +0 -10
  90. package/dist/lib/daemon-ticks.js +4 -2
  91. package/dist/lib/devices/doctor-findings.js +8 -11
  92. package/dist/lib/devices/harness-inventory.js +14 -5
  93. package/dist/lib/exec-account-home.js +5 -0
  94. package/dist/lib/exec.js +0 -11
  95. package/dist/lib/feed/events.js +0 -1
  96. package/dist/lib/feed/tool-activity.js +5 -2
  97. package/dist/lib/fleet-shared-state.d.ts +45 -0
  98. package/dist/lib/fleet-shared-state.js +78 -9
  99. package/dist/lib/fs-atomic.js +25 -0
  100. package/dist/lib/harness-auth-capabilities.js +0 -1
  101. package/dist/lib/helper-versions.js +1 -1
  102. package/dist/lib/hooks/cache.d.ts +7 -0
  103. package/dist/lib/hooks/cache.js +27 -2
  104. package/dist/lib/hooks/install.js +7 -1
  105. package/dist/lib/hosts/ready.d.ts +38 -8
  106. package/dist/lib/hosts/ready.js +62 -14
  107. package/dist/lib/hosts/remote-cmd.js +1 -0
  108. package/dist/lib/installations/active-check.d.ts +27 -0
  109. package/dist/lib/installations/active-check.js +61 -0
  110. package/dist/lib/installations/migrate.js +6 -3
  111. package/dist/lib/installations/shims.js +9 -7
  112. package/dist/lib/installations/update.js +21 -15
  113. package/dist/lib/installations/versions.js +12 -1
  114. package/dist/lib/manifest.js +1 -1
  115. package/dist/lib/models.js +0 -2
  116. package/dist/lib/open-url.js +28 -54
  117. package/dist/lib/placement.d.ts +2 -0
  118. package/dist/lib/placement.js +5 -0
  119. package/dist/lib/profiles.js +0 -2
  120. package/dist/lib/refresh.js +1 -1
  121. package/dist/lib/routine-readiness.d.ts +35 -0
  122. package/dist/lib/routine-readiness.js +74 -16
  123. package/dist/lib/rules/compile.js +6 -0
  124. package/dist/lib/sandbox.js +0 -2
  125. package/dist/lib/secrets-policy.d.ts +89 -11
  126. package/dist/lib/secrets-policy.js +175 -28
  127. package/dist/lib/session/db.d.ts +1 -1
  128. package/dist/lib/session/db.js +65 -18
  129. package/dist/lib/session/discover.js +1 -283
  130. package/dist/lib/session/mirror.d.ts +4 -3
  131. package/dist/lib/session/mirror.js +4 -3
  132. package/dist/lib/session/parse.d.ts +0 -2
  133. package/dist/lib/session/parse.js +1 -138
  134. package/dist/lib/session/throughput.d.ts +1 -3
  135. package/dist/lib/session/throughput.js +0 -20
  136. package/dist/lib/session/types.d.ts +2 -2
  137. package/dist/lib/session/types.js +2 -2
  138. package/dist/lib/sessions-client.d.ts +60 -1
  139. package/dist/lib/sessions-client.js +227 -4
  140. package/dist/lib/setup-tool-status.js +8 -12
  141. package/dist/lib/signin-badge.d.ts +3 -2
  142. package/dist/lib/signin-badge.js +6 -3
  143. package/dist/lib/smart-launch.js +4 -1
  144. package/dist/lib/staleness/detectors/commands.d.ts +1 -1
  145. package/dist/lib/staleness/detectors/hooks.d.ts +1 -1
  146. package/dist/lib/staleness/detectors/mcp.d.ts +1 -1
  147. package/dist/lib/staleness/detectors/permissions.d.ts +1 -1
  148. package/dist/lib/staleness/detectors/plugins.d.ts +1 -1
  149. package/dist/lib/staleness/detectors/rules.d.ts +1 -1
  150. package/dist/lib/staleness/detectors/skills.d.ts +1 -1
  151. package/dist/lib/staleness/detectors/subagents.d.ts +1 -1
  152. package/dist/lib/staleness/detectors/workflows.d.ts +1 -1
  153. package/dist/lib/staleness/writers/commands.d.ts +1 -1
  154. package/dist/lib/staleness/writers/hooks.d.ts +1 -1
  155. package/dist/lib/staleness/writers/mcp.d.ts +1 -1
  156. package/dist/lib/staleness/writers/permissions.d.ts +1 -1
  157. package/dist/lib/staleness/writers/plugins.d.ts +1 -1
  158. package/dist/lib/staleness/writers/rules.d.ts +1 -1
  159. package/dist/lib/staleness/writers/skills.d.ts +1 -1
  160. package/dist/lib/staleness/writers/sources.js +5 -0
  161. package/dist/lib/staleness/writers/subagents.d.ts +1 -1
  162. package/dist/lib/staleness/writers/workflows.d.ts +1 -1
  163. package/dist/lib/state.d.ts +6 -4
  164. package/dist/lib/state.js +7 -17
  165. package/dist/lib/teams/parsers.d.ts +1 -1
  166. package/dist/lib/teams/parsers.js +1 -136
  167. package/dist/lib/teams/placement-probe.js +50 -15
  168. package/dist/lib/teams/scheduler.d.ts +7 -0
  169. package/dist/lib/types.d.ts +10 -1
  170. package/dist/lib/types.js +1 -1
  171. package/dist/lib/view-types.d.ts +32 -0
  172. package/dist/lib/watchdog/read.js +0 -1
  173. package/dist/lib/watchdog/rotate.js +1 -2
  174. package/dist/lib/watchdog/runner.js +2 -2
  175. package/dist/lib/watchdog/watchdog.d.ts +1 -1
  176. package/dist/lib/watchdog/watchdogTail.js +0 -31
  177. package/package.json +1 -1
  178. package/scripts/postinstall.js +8 -8
  179. package/dist/browser.d.ts +0 -2
  180. package/dist/browser.js +0 -17
  181. package/dist/commands/browser-picker.d.ts +0 -18
  182. package/dist/commands/browser-picker.js +0 -97
  183. package/dist/lib/browser/arc-discovery.d.ts +0 -52
  184. package/dist/lib/browser/arc-discovery.js +0 -197
  185. package/dist/lib/browser/arc-dom.d.ts +0 -14
  186. package/dist/lib/browser/arc-dom.js +0 -121
  187. package/dist/lib/browser/caller-identity.d.ts +0 -40
  188. package/dist/lib/browser/caller-identity.js +0 -175
  189. package/dist/lib/browser/cdp.d.ts +0 -51
  190. package/dist/lib/browser/cdp.js +0 -254
  191. package/dist/lib/browser/chrome.d.ts +0 -141
  192. package/dist/lib/browser/chrome.js +0 -704
  193. package/dist/lib/browser/chromium-discovery.d.ts +0 -28
  194. package/dist/lib/browser/chromium-discovery.js +0 -96
  195. package/dist/lib/browser/devices.d.ts +0 -34
  196. package/dist/lib/browser/devices.js +0 -61
  197. package/dist/lib/browser/domain-skills.d.ts +0 -71
  198. package/dist/lib/browser/domain-skills.js +0 -195
  199. package/dist/lib/browser/drivers/arc.d.ts +0 -40
  200. package/dist/lib/browser/drivers/arc.js +0 -276
  201. package/dist/lib/browser/drivers/firefox.d.ts +0 -99
  202. package/dist/lib/browser/drivers/firefox.js +0 -377
  203. package/dist/lib/browser/drivers/local.d.ts +0 -55
  204. package/dist/lib/browser/drivers/local.js +0 -266
  205. package/dist/lib/browser/drivers/ssh.d.ts +0 -120
  206. package/dist/lib/browser/drivers/ssh.js +0 -467
  207. package/dist/lib/browser/editor.d.ts +0 -3
  208. package/dist/lib/browser/editor.js +0 -50
  209. package/dist/lib/browser/ffmpeg.d.ts +0 -12
  210. package/dist/lib/browser/ffmpeg.js +0 -184
  211. package/dist/lib/browser/firefox-discovery.d.ts +0 -69
  212. package/dist/lib/browser/firefox-discovery.js +0 -162
  213. package/dist/lib/browser/har.d.ts +0 -85
  214. package/dist/lib/browser/har.js +0 -77
  215. package/dist/lib/browser/hygiene.d.ts +0 -97
  216. package/dist/lib/browser/hygiene.js +0 -153
  217. package/dist/lib/browser/index.d.ts +0 -5
  218. package/dist/lib/browser/index.js +0 -5
  219. package/dist/lib/browser/input.d.ts +0 -7
  220. package/dist/lib/browser/input.js +0 -92
  221. package/dist/lib/browser/ipc.d.ts +0 -204
  222. package/dist/lib/browser/ipc.js +0 -1453
  223. package/dist/lib/browser/login-detection.d.ts +0 -87
  224. package/dist/lib/browser/login-detection.js +0 -274
  225. package/dist/lib/browser/profiles.d.ts +0 -422
  226. package/dist/lib/browser/profiles.js +0 -1157
  227. package/dist/lib/browser/refs.d.ts +0 -89
  228. package/dist/lib/browser/refs.js +0 -191
  229. package/dist/lib/browser/registry.d.ts +0 -70
  230. package/dist/lib/browser/registry.js +0 -252
  231. package/dist/lib/browser/remote-control.d.ts +0 -59
  232. package/dist/lib/browser/remote-control.js +0 -85
  233. package/dist/lib/browser/resolve-target.d.ts +0 -66
  234. package/dist/lib/browser/resolve-target.js +0 -264
  235. package/dist/lib/browser/runtime-state.d.ts +0 -291
  236. package/dist/lib/browser/runtime-state.js +0 -584
  237. package/dist/lib/browser/secret-ref.d.ts +0 -10
  238. package/dist/lib/browser/secret-ref.js +0 -14
  239. package/dist/lib/browser/service.d.ts +0 -812
  240. package/dist/lib/browser/service.js +0 -4693
  241. package/dist/lib/browser/stream.d.ts +0 -17
  242. package/dist/lib/browser/stream.js +0 -72
  243. package/dist/lib/browser/task-index.d.ts +0 -78
  244. package/dist/lib/browser/task-index.js +0 -199
  245. package/dist/lib/browser/types.d.ts +0 -647
  246. package/dist/lib/browser/types.js +0 -61
  247. package/dist/lib/browser/upload.d.ts +0 -29
  248. package/dist/lib/browser/upload.js +0 -298
  249. package/dist/lib/daemon/browser-ipc-service.d.ts +0 -20
  250. package/dist/lib/daemon/browser-ipc-service.js +0 -46
  251. package/dist/lib/daemon/browser-task-reap-service.d.ts +0 -14
  252. package/dist/lib/daemon/browser-task-reap-service.js +0 -26
  253. package/dist/lib/fleet-shared-repo-sync.d.ts +0 -55
  254. package/dist/lib/fleet-shared-repo-sync.js +0 -490
package/dist/lib/state.js CHANGED
@@ -864,17 +864,6 @@ function writeIfChanged(filePath, content) {
864
864
  atomicWriteFileSync(filePath, content);
865
865
  return true;
866
866
  }
867
- /**
868
- * True in the always-on daemon process (launched as `agents __daemon-run`,
869
- * see cli/src/index.ts). The daemon owns central git commits through
870
- * fleet-shared-repo-sync's publish tick, so a central write from inside the
871
- * daemon must NOT also commit here — that would race the publisher's own
872
- * add/commit/rebase/push on the same index. Every ordinary CLI invocation
873
- * returns false and commits its own central edit synchronously.
874
- */
875
- function isDaemonProcess() {
876
- return process.argv[2] === '__daemon-run';
877
- }
878
867
  /**
879
868
  * Commit the central `agents.yaml` in the user repo, synchronously, so a CLI
880
869
  * config mutation never leaves the working tree dirty on that one shared-line
@@ -892,8 +881,11 @@ function isDaemonProcess() {
892
881
  * never run inside the short, non-heartbeated lockfile window; the user repo
893
882
  * already pushes.
894
883
  *
895
- * Scoped tightly: only called when the central bytes actually changed, and never
896
- * from the daemon (see {@link isDaemonProcess}). A commit failure fails open —
884
+ * Scoped tightly: only called when the central bytes actually changed — from a
885
+ * CLI command and from the daemon alike, since no daemon tick commits the user
886
+ * repo any more (the shared-state exchange moved to SSH, PHNX-4116), so a
887
+ * daemon-side central write would otherwise sit dirty until the next CLI
888
+ * central edit. A commit failure fails open —
897
889
  * a concurrent daemon holding `index.lock`, a mid-rebase repo — leaving
898
890
  * agents.yaml dirty; the next successful central write commits it, and until
899
891
  * then a pull that would collide refuses rather than losing data. A config
@@ -1685,13 +1677,11 @@ export function updateMeta(updates) {
1685
1677
  * A CLI command that actually moved the fleet-shared central agents.yaml commits
1686
1678
  * it so the tree is never left dirty on that file at rest — the window that
1687
1679
  * trips `dirtyTreeRefusal` and wedges pulls fleet-wide (PHNX-3968). Gated on a
1688
- * real byte change and never run in the daemon, whose publish tick owns central
1689
- * commits. See {@link commitCentralConfig}.
1680
+ * real byte change. See {@link commitCentralConfig}.
1690
1681
  */
1691
1682
  export function commitCentralConfigAfterWrite(centralChanged) {
1692
- if (centralChanged && !isDaemonProcess()) {
1683
+ if (centralChanged)
1693
1684
  commitCentralConfig(USER_AGENTS_DIR);
1694
- }
1695
1685
  }
1696
1686
  /** Derive a filesystem-safe local clone path for a package source URL. */
1697
1687
  export function getPackageLocalPath(source) {
@@ -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.116",
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 {};