@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.
- package/CHANGELOG.md +56 -0
- package/dist/bootstrap.js +8 -1
- package/dist/commands/accounts.js +7 -3
- package/dist/commands/apply.js +10 -2
- package/dist/commands/fork.d.ts +23 -10
- package/dist/commands/fork.js +115 -58
- package/dist/commands/monitors.js +11 -0
- package/dist/commands/prune.js +5 -3
- package/dist/commands/routines.d.ts +8 -0
- package/dist/commands/routines.js +57 -3
- package/dist/commands/sessions-picker.d.ts +11 -0
- package/dist/commands/sessions-picker.js +16 -0
- package/dist/commands/sessions.js +1 -0
- package/dist/commands/share.d.ts +14 -0
- package/dist/commands/share.js +43 -2
- package/dist/commands/status.js +1 -1
- package/dist/commands/sync.js +83 -7
- package/dist/commands/traces.js +7 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -1
- package/dist/lib/account-registry.d.ts +5 -1
- package/dist/lib/account-registry.js +47 -14
- package/dist/lib/accounting/capacity.d.ts +18 -7
- package/dist/lib/accounting/capacity.js +19 -8
- package/dist/lib/accounting/usage-sync.d.ts +29 -1
- package/dist/lib/accounting/usage-sync.js +76 -2
- package/dist/lib/accounting/usage.js +7 -1
- package/dist/lib/auth-mint.d.ts +11 -1
- package/dist/lib/auth-mint.js +21 -6
- package/dist/lib/browser/ipc.d.ts +8 -0
- package/dist/lib/browser/ipc.js +87 -0
- package/dist/lib/browser/service.d.ts +19 -0
- package/dist/lib/browser/service.js +96 -11
- package/dist/lib/browser/sessions-list.js +10 -1
- package/dist/lib/daemon/runner.d.ts +3 -0
- package/dist/lib/daemon/runner.js +86 -45
- package/dist/lib/daemon/usage-sync-service.d.ts +3 -3
- package/dist/lib/daemon/usage-sync-service.js +14 -8
- package/dist/lib/daemon-services.js +1 -1
- package/dist/lib/devices/connect.d.ts +17 -8
- package/dist/lib/devices/connect.js +31 -14
- package/dist/lib/doctor-diff.js +77 -7
- package/dist/lib/fleet/manifest.d.ts +17 -0
- package/dist/lib/fleet/manifest.js +26 -0
- package/dist/lib/hooks/install.d.ts +27 -11
- package/dist/lib/hooks/install.js +42 -17
- package/dist/lib/hosts/reconnect.d.ts +52 -203
- package/dist/lib/hosts/reconnect.js +64 -284
- package/dist/lib/installations/migrate.d.ts +6 -120
- package/dist/lib/installations/migrate.js +27 -259
- package/dist/lib/installations/shims.d.ts +13 -95
- package/dist/lib/installations/shims.js +22 -139
- package/dist/lib/installations/store.js +1 -1
- package/dist/lib/installations/versions.d.ts +26 -133
- package/dist/lib/installations/versions.js +41 -204
- package/dist/lib/plugins/skills.d.ts +8 -1
- package/dist/lib/plugins/skills.js +18 -2
- package/dist/lib/refresh.d.ts +9 -0
- package/dist/lib/refresh.js +3 -1
- package/dist/lib/routine-readiness.d.ts +15 -1
- package/dist/lib/routine-readiness.js +41 -0
- package/dist/lib/sandbox.d.ts +4 -1
- package/dist/lib/sandbox.js +30 -1
- package/dist/lib/secrets/agent.d.ts +80 -225
- package/dist/lib/secrets/agent.js +139 -401
- package/dist/lib/secrets/bundles.d.ts +73 -222
- package/dist/lib/secrets/bundles.js +168 -467
- package/dist/lib/secrets/reaper.d.ts +28 -70
- package/dist/lib/secrets/reaper.js +30 -85
- package/dist/lib/secrets/remote.d.ts +42 -129
- package/dist/lib/secrets/remote.js +55 -173
- package/dist/lib/self-heal/checks/install-staging.d.ts +4 -0
- package/dist/lib/self-heal/checks/install-staging.js +96 -0
- package/dist/lib/self-heal/registry.js +2 -0
- package/dist/lib/self-heal/types.d.ts +1 -1
- package/dist/lib/self-update.d.ts +23 -0
- package/dist/lib/self-update.js +50 -0
- package/dist/lib/session/active.d.ts +13 -1
- package/dist/lib/session/active.js +2 -0
- package/dist/lib/session/db.d.ts +20 -1
- package/dist/lib/session/db.js +139 -9
- package/dist/lib/session/fork.d.ts +45 -26
- package/dist/lib/session/fork.js +32 -95
- package/dist/lib/session/tool-calls.d.ts +43 -1
- package/dist/lib/session/tool-calls.js +74 -44
- package/dist/lib/session/tool-store.d.ts +33 -2
- package/dist/lib/session/tool-store.js +56 -3
- package/dist/lib/staleness/writers/sources.d.ts +5 -0
- package/dist/lib/staleness/writers/sources.js +2 -1
- package/dist/lib/sync-status.d.ts +22 -0
- package/dist/lib/sync-status.js +27 -0
- package/dist/lib/sync-umbrella.d.ts +9 -0
- package/dist/lib/sync-umbrella.js +21 -2
- package/dist/lib/traces/insights.d.ts +47 -14
- package/dist/lib/traces/insights.js +92 -21
- package/dist/lib/traces/phenotype.d.ts +23 -3
- package/dist/lib/traces/phenotype.js +72 -24
- package/dist/lib/traces/sync.d.ts +15 -0
- package/dist/lib/traces/sync.js +104 -19
- package/dist/lib/traces/worker-template.js +154 -1
- 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>/...
|
|
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.
|
|
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",
|