@phnx-labs/agents-cli 1.22.57 → 1.22.58

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 (101) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/dist/bootstrap.js +8 -1
  3. package/dist/commands/accounts.js +7 -3
  4. package/dist/commands/apply.js +10 -2
  5. package/dist/commands/fork.d.ts +23 -10
  6. package/dist/commands/fork.js +115 -58
  7. package/dist/commands/monitors.js +11 -0
  8. package/dist/commands/prune.js +5 -3
  9. package/dist/commands/routines.d.ts +8 -0
  10. package/dist/commands/routines.js +57 -3
  11. package/dist/commands/sessions-picker.d.ts +11 -0
  12. package/dist/commands/sessions-picker.js +16 -0
  13. package/dist/commands/sessions.js +1 -0
  14. package/dist/commands/share.d.ts +14 -0
  15. package/dist/commands/share.js +43 -2
  16. package/dist/commands/status.js +1 -1
  17. package/dist/commands/sync.js +83 -7
  18. package/dist/commands/traces.js +7 -0
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.js +6 -1
  21. package/dist/lib/account-registry.d.ts +5 -1
  22. package/dist/lib/account-registry.js +47 -14
  23. package/dist/lib/accounting/capacity.d.ts +18 -7
  24. package/dist/lib/accounting/capacity.js +19 -8
  25. package/dist/lib/accounting/usage-sync.d.ts +29 -1
  26. package/dist/lib/accounting/usage-sync.js +76 -2
  27. package/dist/lib/accounting/usage.js +7 -1
  28. package/dist/lib/auth-mint.d.ts +11 -1
  29. package/dist/lib/auth-mint.js +21 -6
  30. package/dist/lib/browser/ipc.d.ts +8 -0
  31. package/dist/lib/browser/ipc.js +87 -0
  32. package/dist/lib/browser/service.d.ts +19 -0
  33. package/dist/lib/browser/service.js +96 -11
  34. package/dist/lib/browser/sessions-list.js +10 -1
  35. package/dist/lib/daemon/runner.d.ts +3 -0
  36. package/dist/lib/daemon/runner.js +86 -45
  37. package/dist/lib/daemon/usage-sync-service.d.ts +3 -3
  38. package/dist/lib/daemon/usage-sync-service.js +14 -8
  39. package/dist/lib/daemon-services.js +1 -1
  40. package/dist/lib/devices/connect.d.ts +17 -8
  41. package/dist/lib/devices/connect.js +31 -14
  42. package/dist/lib/doctor-diff.js +77 -7
  43. package/dist/lib/fleet/manifest.d.ts +17 -0
  44. package/dist/lib/fleet/manifest.js +26 -0
  45. package/dist/lib/hooks/install.d.ts +27 -11
  46. package/dist/lib/hooks/install.js +42 -17
  47. package/dist/lib/hosts/reconnect.d.ts +52 -203
  48. package/dist/lib/hosts/reconnect.js +64 -284
  49. package/dist/lib/installations/migrate.d.ts +6 -120
  50. package/dist/lib/installations/migrate.js +27 -259
  51. package/dist/lib/installations/shims.d.ts +13 -95
  52. package/dist/lib/installations/shims.js +22 -139
  53. package/dist/lib/installations/store.js +1 -1
  54. package/dist/lib/installations/versions.d.ts +26 -133
  55. package/dist/lib/installations/versions.js +41 -204
  56. package/dist/lib/plugins/skills.d.ts +8 -1
  57. package/dist/lib/plugins/skills.js +18 -2
  58. package/dist/lib/refresh.d.ts +9 -0
  59. package/dist/lib/refresh.js +3 -1
  60. package/dist/lib/routine-readiness.d.ts +15 -1
  61. package/dist/lib/routine-readiness.js +41 -0
  62. package/dist/lib/sandbox.d.ts +4 -1
  63. package/dist/lib/sandbox.js +30 -1
  64. package/dist/lib/secrets/agent.d.ts +80 -225
  65. package/dist/lib/secrets/agent.js +139 -401
  66. package/dist/lib/secrets/bundles.d.ts +73 -222
  67. package/dist/lib/secrets/bundles.js +168 -467
  68. package/dist/lib/secrets/reaper.d.ts +28 -70
  69. package/dist/lib/secrets/reaper.js +30 -85
  70. package/dist/lib/secrets/remote.d.ts +42 -129
  71. package/dist/lib/secrets/remote.js +55 -173
  72. package/dist/lib/self-heal/checks/install-staging.d.ts +4 -0
  73. package/dist/lib/self-heal/checks/install-staging.js +96 -0
  74. package/dist/lib/self-heal/registry.js +2 -0
  75. package/dist/lib/self-heal/types.d.ts +1 -1
  76. package/dist/lib/self-update.d.ts +23 -0
  77. package/dist/lib/self-update.js +50 -0
  78. package/dist/lib/session/active.d.ts +13 -1
  79. package/dist/lib/session/active.js +2 -0
  80. package/dist/lib/session/db.d.ts +20 -1
  81. package/dist/lib/session/db.js +139 -9
  82. package/dist/lib/session/fork.d.ts +45 -26
  83. package/dist/lib/session/fork.js +32 -95
  84. package/dist/lib/session/tool-calls.d.ts +43 -1
  85. package/dist/lib/session/tool-calls.js +74 -44
  86. package/dist/lib/session/tool-store.d.ts +33 -2
  87. package/dist/lib/session/tool-store.js +56 -3
  88. package/dist/lib/staleness/writers/sources.d.ts +5 -0
  89. package/dist/lib/staleness/writers/sources.js +2 -1
  90. package/dist/lib/sync-status.d.ts +22 -0
  91. package/dist/lib/sync-status.js +27 -0
  92. package/dist/lib/sync-umbrella.d.ts +9 -0
  93. package/dist/lib/sync-umbrella.js +21 -2
  94. package/dist/lib/traces/insights.d.ts +47 -14
  95. package/dist/lib/traces/insights.js +92 -21
  96. package/dist/lib/traces/phenotype.d.ts +23 -3
  97. package/dist/lib/traces/phenotype.js +72 -24
  98. package/dist/lib/traces/sync.d.ts +15 -0
  99. package/dist/lib/traces/sync.js +104 -19
  100. package/dist/lib/traces/worker-template.js +154 -1
  101. package/package.json +1 -1
@@ -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);
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.58",
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",