@phnx-labs/agents-cli 1.22.57 → 1.22.59

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 (152) hide show
  1. package/CHANGELOG.md +294 -0
  2. package/README.md +29 -0
  3. package/dist/bootstrap.js +39 -1
  4. package/dist/commands/accounts.js +7 -3
  5. package/dist/commands/apply.js +10 -2
  6. package/dist/commands/fork.d.ts +23 -10
  7. package/dist/commands/fork.js +115 -58
  8. package/dist/commands/monitors.js +198 -23
  9. package/dist/commands/prune.js +5 -3
  10. package/dist/commands/routines.d.ts +8 -0
  11. package/dist/commands/routines.js +57 -3
  12. package/dist/commands/routines.test-fixture.js +5 -0
  13. package/dist/commands/send.d.ts +2 -1
  14. package/dist/commands/send.js +7 -5
  15. package/dist/commands/sessions-picker.d.ts +11 -0
  16. package/dist/commands/sessions-picker.js +16 -0
  17. package/dist/commands/sessions-stats.js +37 -5
  18. package/dist/commands/sessions.js +40 -5
  19. package/dist/commands/share.d.ts +14 -0
  20. package/dist/commands/share.js +43 -2
  21. package/dist/commands/ssh.js +12 -1
  22. package/dist/commands/status.js +1 -1
  23. package/dist/commands/sync.js +83 -7
  24. package/dist/commands/traces.js +7 -0
  25. package/dist/commands/versions.js +12 -4
  26. package/dist/commands/view.js +7 -2
  27. package/dist/index.d.ts +1 -1
  28. package/dist/index.js +6 -1
  29. package/dist/lib/account-registry.d.ts +5 -1
  30. package/dist/lib/account-registry.js +47 -14
  31. package/dist/lib/accounting/capacity.d.ts +18 -7
  32. package/dist/lib/accounting/capacity.js +19 -8
  33. package/dist/lib/accounting/usage-sync.d.ts +29 -1
  34. package/dist/lib/accounting/usage-sync.js +76 -2
  35. package/dist/lib/accounting/usage.js +7 -1
  36. package/dist/lib/auth-mint.d.ts +11 -1
  37. package/dist/lib/auth-mint.js +21 -6
  38. package/dist/lib/auto-pull-worker.js +7 -2
  39. package/dist/lib/browser/ipc.d.ts +8 -0
  40. package/dist/lib/browser/ipc.js +87 -0
  41. package/dist/lib/browser/service.d.ts +19 -0
  42. package/dist/lib/browser/service.js +96 -11
  43. package/dist/lib/browser/sessions-list.js +10 -1
  44. package/dist/lib/cloud/rush.d.ts +7 -0
  45. package/dist/lib/cloud/rush.js +29 -1
  46. package/dist/lib/daemon/daemon.d.ts +22 -0
  47. package/dist/lib/daemon/daemon.js +39 -0
  48. package/dist/lib/daemon/runner.d.ts +3 -0
  49. package/dist/lib/daemon/runner.js +86 -45
  50. package/dist/lib/daemon/session-index-service.js +9 -1
  51. package/dist/lib/daemon/usage-sync-service.d.ts +3 -3
  52. package/dist/lib/daemon/usage-sync-service.js +14 -8
  53. package/dist/lib/daemon-services.js +1 -1
  54. package/dist/lib/daemon-ticks.d.ts +15 -0
  55. package/dist/lib/daemon-ticks.js +26 -0
  56. package/dist/lib/device-config.d.ts +5 -1
  57. package/dist/lib/device-config.js +2 -2
  58. package/dist/lib/devices/connect.d.ts +17 -8
  59. package/dist/lib/devices/connect.js +31 -14
  60. package/dist/lib/devices/health.js +5 -1
  61. package/dist/lib/devices/pool.d.ts +25 -2
  62. package/dist/lib/devices/pool.js +32 -2
  63. package/dist/lib/devices/stats-cache.d.ts +0 -6
  64. package/dist/lib/devices/stats-cache.js +2 -9
  65. package/dist/lib/doctor-diff.d.ts +14 -0
  66. package/dist/lib/doctor-diff.js +120 -9
  67. package/dist/lib/fleet/manifest.d.ts +17 -0
  68. package/dist/lib/fleet/manifest.js +26 -0
  69. package/dist/lib/git.d.ts +38 -0
  70. package/dist/lib/git.js +58 -0
  71. package/dist/lib/hooks/install.d.ts +27 -11
  72. package/dist/lib/hooks/install.js +42 -17
  73. package/dist/lib/hosts/ready.d.ts +8 -0
  74. package/dist/lib/hosts/ready.js +13 -2
  75. package/dist/lib/hosts/reconnect.d.ts +52 -203
  76. package/dist/lib/hosts/reconnect.js +64 -284
  77. package/dist/lib/installations/migrate.d.ts +6 -120
  78. package/dist/lib/installations/migrate.js +27 -259
  79. package/dist/lib/installations/shims.d.ts +13 -95
  80. package/dist/lib/installations/shims.js +22 -139
  81. package/dist/lib/installations/store.js +1 -1
  82. package/dist/lib/installations/versions.d.ts +43 -133
  83. package/dist/lib/installations/versions.js +94 -206
  84. package/dist/lib/monitors/config.d.ts +71 -3
  85. package/dist/lib/monitors/config.js +100 -12
  86. package/dist/lib/monitors/pid-watch.d.ts +35 -0
  87. package/dist/lib/monitors/pid-watch.js +45 -0
  88. package/dist/lib/monitors/remote.d.ts +18 -0
  89. package/dist/lib/monitors/remote.js +11 -0
  90. package/dist/lib/permissions.js +7 -2
  91. package/dist/lib/plugins/plugins.d.ts +17 -3
  92. package/dist/lib/plugins/plugins.js +84 -9
  93. package/dist/lib/plugins/skills.d.ts +8 -1
  94. package/dist/lib/plugins/skills.js +18 -2
  95. package/dist/lib/pty-server.d.ts +14 -0
  96. package/dist/lib/pty-server.js +49 -5
  97. package/dist/lib/refresh.d.ts +9 -0
  98. package/dist/lib/refresh.js +3 -1
  99. package/dist/lib/routine-readiness.d.ts +15 -1
  100. package/dist/lib/routine-readiness.js +41 -0
  101. package/dist/lib/sandbox.d.ts +4 -1
  102. package/dist/lib/sandbox.js +30 -1
  103. package/dist/lib/secrets/agent.d.ts +80 -225
  104. package/dist/lib/secrets/agent.js +139 -401
  105. package/dist/lib/secrets/bundles.d.ts +73 -222
  106. package/dist/lib/secrets/bundles.js +168 -467
  107. package/dist/lib/secrets/drivers/rush.js +5 -0
  108. package/dist/lib/secrets/reaper.d.ts +28 -70
  109. package/dist/lib/secrets/reaper.js +30 -85
  110. package/dist/lib/secrets/remote.d.ts +42 -129
  111. package/dist/lib/secrets/remote.js +55 -173
  112. package/dist/lib/self-heal/checks/install-staging.d.ts +4 -0
  113. package/dist/lib/self-heal/checks/install-staging.js +96 -0
  114. package/dist/lib/self-heal/registry.js +2 -0
  115. package/dist/lib/self-heal/types.d.ts +1 -1
  116. package/dist/lib/self-update.d.ts +65 -0
  117. package/dist/lib/self-update.js +138 -0
  118. package/dist/lib/session/active.d.ts +13 -1
  119. package/dist/lib/session/active.js +2 -0
  120. package/dist/lib/session/cloud.js +5 -0
  121. package/dist/lib/session/db.d.ts +51 -6
  122. package/dist/lib/session/db.js +266 -20
  123. package/dist/lib/session/fork.d.ts +45 -26
  124. package/dist/lib/session/fork.js +32 -95
  125. package/dist/lib/session/tool-calls.d.ts +43 -1
  126. package/dist/lib/session/tool-calls.js +74 -44
  127. package/dist/lib/session/tool-store.d.ts +33 -2
  128. package/dist/lib/session/tool-store.js +56 -3
  129. package/dist/lib/smart-launch.d.ts +6 -0
  130. package/dist/lib/smart-launch.js +5 -2
  131. package/dist/lib/staleness/writers/plugins.js +5 -2
  132. package/dist/lib/staleness/writers/sources.d.ts +5 -0
  133. package/dist/lib/staleness/writers/sources.js +2 -1
  134. package/dist/lib/staleness/writers/subagents.js +13 -3
  135. package/dist/lib/state.d.ts +7 -4
  136. package/dist/lib/state.js +7 -4
  137. package/dist/lib/subagents.js +8 -2
  138. package/dist/lib/sync-status.d.ts +22 -0
  139. package/dist/lib/sync-status.js +27 -0
  140. package/dist/lib/sync-umbrella.d.ts +9 -0
  141. package/dist/lib/sync-umbrella.js +21 -2
  142. package/dist/lib/teams/scheduler.d.ts +10 -0
  143. package/dist/lib/teams/scheduler.js +8 -0
  144. package/dist/lib/traces/insights.d.ts +47 -14
  145. package/dist/lib/traces/insights.js +92 -21
  146. package/dist/lib/traces/phenotype.d.ts +23 -3
  147. package/dist/lib/traces/phenotype.js +72 -24
  148. package/dist/lib/traces/sync.d.ts +128 -6
  149. package/dist/lib/traces/sync.js +294 -35
  150. package/dist/lib/traces/worker-template.js +154 -1
  151. package/dist/lib/view-types.d.ts +12 -0
  152. package/package.json +2 -2
@@ -8,9 +8,19 @@
8
8
  // Routes (all require Phoenix bearer + userId owner match):
9
9
  // PUT /<userId>/<device>/index.json — per-device stats shard
10
10
  // PUT /<userId>/<device>/sessions/<id>.json — per-session SessionDetail
11
- // GET /<userId>/... — returns the stored object
11
+ // GET /<userId>/<device>/... — returns the stored object
12
+ // GET /<userId>/all/index.json — cross-device merge (read-side)
13
+ // GET /<userId>/all/sessions/<id>.json — first device that holds <id>
12
14
  // GET / — 200 description (no data)
13
15
  //
16
+ // The CLI writes per-device (`localDevice()` == hostname); the console asks for
17
+ // the "all agents" view (`/all/`). `all` is not a real device — the worker
18
+ // synthesizes it on read by listing this owner's device prefixes and merging.
19
+ // A single device is an exact passthrough; cross-device stats that need the raw
20
+ // sample (median, latency percentiles) are session-weighted approximations,
21
+ // documented at the merge site. This keeps the CLI unchanged and uploads nothing
22
+ // extra — the per-device shards already in R2 are the source (PHNX-3397).
23
+ //
14
24
  // Emitted as a string so it compiles into dist/** and ships with no
15
25
  // package.json#files change. `provisionTraces.ts` uploads this verbatim as an
16
26
  // ES-module Worker with a BUCKET (R2) binding + a PHOENIX_ID_BASE secret.
@@ -79,6 +89,13 @@ async function handleGet(request, env, path) {
79
89
  const auth = await authorizeRead(request, env, path);
80
90
  if (auth.error) return auth.error;
81
91
 
92
+ const segments = path.split('/').filter(Boolean);
93
+ // The "all agents" view is synthesized, not stored: merge this owner's device
94
+ // shards on read. segments[0] === owner is already enforced by authorizeRead.
95
+ if (segments[1] === 'all') {
96
+ return handleAggregate(request, env, segments[0], segments.slice(2).join('/'));
97
+ }
98
+
82
99
  const object = await env.BUCKET.get(path);
83
100
  if (!object) return json({ error: 'not found' }, 404);
84
101
 
@@ -87,6 +104,142 @@ async function handleGet(request, env, path) {
87
104
  return new Response(object.body, { headers });
88
105
  }
89
106
 
107
+ // Enumerate this owner's real device names (never 'all') using a delimited list
108
+ // so we read one page of prefixes, not every session object.
109
+ async function listDevices(env, owner) {
110
+ const listed = await env.BUCKET.list({ prefix: owner + '/', delimiter: '/' });
111
+ const prefixes = (listed && listed.delimitedPrefixes) || [];
112
+ const base = owner + '/';
113
+ return prefixes
114
+ .map((p) => (p.startsWith(base) ? p.slice(base.length) : p).replace(/\\/+$/, ''))
115
+ .filter((d) => d && d !== 'all');
116
+ }
117
+
118
+ async function handleAggregate(request, env, owner, rest) {
119
+ const devices = await listDevices(env, owner);
120
+ if (devices.length === 0) return json({ error: 'not found' }, 404);
121
+
122
+ // A single session drill-down: the shard lives under whichever device owns it.
123
+ if (rest.startsWith('sessions/')) {
124
+ for (const device of devices) {
125
+ const object = await env.BUCKET.get(owner + '/' + device + '/' + rest);
126
+ if (object) {
127
+ const headers = new Headers(object.httpMetadata ?? {});
128
+ headers.set('cache-control', 'private, no-store');
129
+ return new Response(object.body, { headers });
130
+ }
131
+ }
132
+ return json({ error: 'not found' }, 404);
133
+ }
134
+
135
+ if (rest === 'index.json') {
136
+ const shards = [];
137
+ for (const device of devices) {
138
+ const object = await env.BUCKET.get(owner + '/' + device + '/index.json');
139
+ if (!object) continue;
140
+ try {
141
+ shards.push(JSON.parse(await object.text()));
142
+ } catch {
143
+ // A corrupt per-device shard must not sink the whole aggregate.
144
+ }
145
+ }
146
+ if (shards.length === 0) return json({ error: 'not found' }, 404);
147
+ return json(mergeIndexShards(shards, owner), 200);
148
+ }
149
+
150
+ return json({ error: 'not found' }, 404);
151
+ }
152
+
153
+ // Merge per-device index shards into one "all agents" shard. One device is an
154
+ // exact passthrough (relabelled). For many, counts sum and lists concat+resort;
155
+ // median/latency percentiles cannot be recomputed without the raw per-session
156
+ // sample, so they are session-count-weighted approximations — good enough for an
157
+ // at-a-glance fleet view, and exact the moment you filter to a single device.
158
+ function mergeIndexShards(shards, owner) {
159
+ if (shards.length === 1) {
160
+ return Object.assign({}, shards[0], { device: 'all', owner });
161
+ }
162
+ const sorted = shards.slice().sort((a, b) => (b.syncedAt || 0) - (a.syncedAt || 0));
163
+ const totalSessions = sorted.reduce((n, s) => n + ((s.stats && s.stats.sessionsImported) || 0), 0) || 1;
164
+ const wsum = (pick) => sorted.reduce((n, s) => n + (pick(s) || 0) * ((s.stats && s.stats.sessionsImported) || 0), 0) / totalSessions;
165
+
166
+ const topicCounts = new Map();
167
+ for (const s of sorted) for (const t of s.topics || []) {
168
+ const cur = topicCounts.get(t.key) || Object.assign({}, t, { count: 0 });
169
+ cur.count += t.count || 0;
170
+ topicCounts.set(t.key, cur);
171
+ }
172
+ const byToolError = new Map();
173
+ let real = 0, guard = 0, hook = 0;
174
+ for (const s of sorted) {
175
+ const f = s.failures || {};
176
+ for (const e of f.byToolError || []) {
177
+ const key = e.tool + '\\u0000' + e.desc + '\\u0000' + e.cause;
178
+ const cur = byToolError.get(key) || Object.assign({}, e, { count: 0 });
179
+ cur.count += e.count || 0;
180
+ byToolError.set(key, cur);
181
+ }
182
+ real += (f.byCause && f.byCause.real) || 0;
183
+ guard += (f.byCause && f.byCause.guard) || 0;
184
+ hook += (f.byCause && f.byCause.hook) || 0;
185
+ }
186
+ // Fold failure patterns by their stable id, the same way topics/byToolError fold
187
+ // by key above — a signature that fired on two devices (a rate limit on the laptop
188
+ // AND on CI) is ONE ranked issue with combined counts, not two half-counted rows.
189
+ const patternById = new Map();
190
+ for (const s of sorted) for (const p of s.failurePatterns || []) {
191
+ const cur = patternById.get(p.id);
192
+ if (!cur) {
193
+ patternById.set(p.id, Object.assign({}, p, { exampleSessionIds: (p.exampleSessionIds || []).slice(0, 5) }));
194
+ } else {
195
+ cur.sessions += p.sessions || 0;
196
+ cur.occurrences += p.occurrences || 0;
197
+ cur.wastedMs += p.wastedMs || 0;
198
+ for (const id of p.exampleSessionIds || []) {
199
+ if (cur.exampleSessionIds.length < 5 && !cur.exampleSessionIds.includes(id)) cur.exampleSessionIds.push(id);
200
+ }
201
+ }
202
+ }
203
+ const latencies = sorted.map((s) => s.latency).filter(Boolean);
204
+
205
+ return {
206
+ schema: 1,
207
+ device: 'all',
208
+ owner,
209
+ syncedAt: sorted[0].syncedAt || 0,
210
+ stats: {
211
+ sessionsImported: sorted.reduce((n, s) => n + ((s.stats && s.stats.sessionsImported) || 0), 0),
212
+ medianMs: Math.round(wsum((s) => s.stats && s.stats.medianMs)),
213
+ p90Ms: Math.max.apply(null, sorted.map((s) => (s.stats && s.stats.p90Ms) || 0)),
214
+ needAttention: sorted.reduce((n, s) => n + ((s.stats && s.stats.needAttention) || 0), 0),
215
+ toolErrorRate: wsum((s) => s.stats && s.stats.toolErrorRate),
216
+ },
217
+ needsAttention: sorted.flatMap((s) => s.needsAttention || [])
218
+ .sort((a, b) => (b.severity || 0) - (a.severity || 0) || String(a.id).localeCompare(String(b.id)))
219
+ .slice(0, 100),
220
+ topics: Array.from(topicCounts.values()).sort((a, b) => b.count - a.count),
221
+ failures: {
222
+ byToolError: Array.from(byToolError.values()).sort((a, b) => b.count - a.count).slice(0, 50),
223
+ byCause: { real, guard, hook },
224
+ },
225
+ failurePatterns: Array.from(patternById.values())
226
+ .sort((a, b) => (b.wastedMs || 0) - (a.wastedMs || 0)).slice(0, 25),
227
+ wastedMsTotal: sorted.reduce((n, s) => n + (s.wastedMsTotal || 0), 0),
228
+ latency: latencies.length ? {
229
+ firstToolMs: {
230
+ p50: Math.round(wsum((s) => s.latency && s.latency.firstToolMs && s.latency.firstToolMs.p50)),
231
+ p90: Math.max.apply(null, latencies.map((l) => (l.firstToolMs && l.firstToolMs.p90) || 0)),
232
+ p99: Math.max.apply(null, latencies.map((l) => (l.firstToolMs && l.firstToolMs.p99) || 0)),
233
+ max: Math.max.apply(null, latencies.map((l) => (l.firstToolMs && l.firstToolMs.max) || 0)),
234
+ },
235
+ } : undefined,
236
+ // Rolling history / drift are per-device time series; the freshest device's
237
+ // are the most representative for the merged view without double-counting.
238
+ bucketHistory: sorted[0].bucketHistory || [],
239
+ driftSignals: sorted[0].driftSignals || [],
240
+ };
241
+ }
242
+
90
243
  // Require a Phoenix bearer that owns the path prefix.
91
244
  async function authorizeRead(request, env, path) {
92
245
  const claims = await hooks.verifyPhoenixToken(request, env);
@@ -9,6 +9,18 @@ export interface ViewJsonVersion {
9
9
  isolated: boolean;
10
10
  isIsolatedDefault: boolean;
11
11
  signedIn: boolean;
12
+ /**
13
+ * Whether THIS version home can actually spawn a signed-in agent — the strict
14
+ * per-version launch truth (`isLaunchableSignedIn`), not the display `signedIn`
15
+ * above. `signedIn` is true when the version *inherits* the active/global HOME
16
+ * login even with no per-version credential of its own; such a home shows "who
17
+ * is logged in" but dies at spawn once launch isolates HOME to it. Automatic
18
+ * `--device auto` placement gates on THIS field so a remote box is judged by
19
+ * the same launchability the local candidate uses (`collectRunCandidates` →
20
+ * `isLaunchableSignedIn`), closing the local/remote asymmetry (PHNX-3466).
21
+ * Absent on an older remote CLI, whose consumers fall back to `signedIn`.
22
+ */
23
+ launchable: boolean;
12
24
  /** Live cached authentication verdict for this installed version. */
13
25
  authVerdict: AuthVerdict | null;
14
26
  email: string | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phnx-labs/agents-cli",
3
- "version": "1.22.57",
3
+ "version": "1.22.59",
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",
@@ -88,7 +88,7 @@
88
88
  "dependencies": {
89
89
  "@fontsource/inter": "^5.3.0",
90
90
  "@fontsource/jetbrains-mono": "^5.3.0",
91
- "@homebridge/node-pty-prebuilt-multiarch": "0.13.1",
91
+ "@homebridge/node-pty-prebuilt-multiarch": "0.14.1",
92
92
  "@inquirer/prompts": "8.5.2",
93
93
  "@resvg/resvg-wasm": "^2.6.2",
94
94
  "@types/proper-lockfile": "4.1.4",