@phnx-labs/agents-cli 1.22.49 → 1.22.51
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 +229 -0
- package/README.md +10 -0
- package/dist/bootstrap.js +1 -1
- package/dist/commands/accounts.js +1 -13
- package/dist/commands/browser.d.ts +10 -0
- package/dist/commands/browser.js +77 -2
- package/dist/commands/config.js +20 -0
- package/dist/commands/daemon-test-harness.d.ts +42 -0
- package/dist/commands/daemon-test-harness.js +90 -0
- package/dist/commands/device-config-test-harness.d.ts +11 -0
- package/dist/commands/device-config-test-harness.js +59 -0
- package/dist/commands/exec.js +24 -1
- package/dist/commands/feedback.js +1 -1
- package/dist/commands/menubar.js +6 -4
- package/dist/commands/share.js +9 -4
- package/dist/commands/teams.d.ts +12 -0
- package/dist/commands/teams.js +44 -67
- package/dist/commands/traces.js +36 -4
- package/dist/lib/account-provider-registry.d.ts +9 -0
- package/dist/lib/account-provider-registry.js +20 -0
- package/dist/lib/accounting/account-pool-collect.d.ts +34 -0
- package/dist/lib/accounting/account-pool-collect.js +71 -0
- package/dist/lib/accounting/account-pool.d.ts +46 -0
- package/dist/lib/accounting/account-pool.js +33 -0
- package/dist/lib/accounting/capacity.d.ts +24 -0
- package/dist/lib/accounting/capacity.js +30 -0
- package/dist/lib/accounting/rotate.d.ts +11 -18
- package/dist/lib/accounting/rotate.js +6 -24
- package/dist/lib/claude-statusline.d.ts +9 -0
- package/dist/lib/claude-statusline.js +45 -4
- package/dist/lib/computer/ssh-tunnel.d.ts +7 -6
- package/dist/lib/computer/ssh-tunnel.js +13 -8
- package/dist/lib/config-keys.d.ts +4 -3
- package/dist/lib/config-keys.js +9 -2
- package/dist/lib/device-config.js +23 -0
- package/dist/lib/factory/snapshot.d.ts +1 -1
- package/dist/lib/factory/snapshot.js +1 -1
- package/dist/lib/helper-download.d.ts +12 -2
- package/dist/lib/helper-download.js +12 -2
- package/dist/lib/installations/migrate.js +4 -4
- package/dist/lib/installations/store.js +6 -3
- package/dist/lib/installations/versions.js +48 -46
- package/dist/lib/menubar/install-menubar.d.ts +71 -8
- package/dist/lib/menubar/install-menubar.js +183 -24
- package/dist/lib/share/publish.d.ts +8 -3
- package/dist/lib/share/publish.js +6 -3
- package/dist/lib/share/worker-template.js +239 -22
- package/dist/lib/star-nudge.d.ts +2 -2
- package/dist/lib/star-nudge.js +2 -2
- package/dist/lib/state.d.ts +1 -1
- package/dist/lib/state.js +2 -2
- package/dist/lib/teams/agents.d.ts +14 -12
- package/dist/lib/teams/agents.js +188 -73
- package/dist/lib/teams/api.d.ts +6 -1
- package/dist/lib/teams/api.js +2 -0
- package/dist/lib/teams/scheduler.d.ts +7 -0
- package/dist/lib/teams/scheduler.js +26 -8
- package/dist/lib/traces/sync.d.ts +30 -0
- package/dist/lib/traces/sync.js +103 -10
- package/package.json +3 -3
package/dist/lib/traces/sync.js
CHANGED
|
@@ -9,6 +9,15 @@
|
|
|
9
9
|
* Sync gate: `sessions.db` `file_mtime_ms` is the source of truth. A ledger
|
|
10
10
|
* at `getRuntimeStateDir()/traces-sync.json` records the last sync timestamp
|
|
11
11
|
* per device so re-runs skip unchanged sessions.
|
|
12
|
+
*
|
|
13
|
+
* Retry ledger: the mtime watermark alone cannot retry failures — a later-mtime
|
|
14
|
+
* success advances it past an earlier failed row, which the next run's
|
|
15
|
+
* `file_mtime_ms > watermark` filter then skips forever. So the same ledger also
|
|
16
|
+
* records the identity + typed evidence of each failed session (`failures`), and
|
|
17
|
+
* the row query unions those retry-worthy ids back in regardless of the watermark
|
|
18
|
+
* (PHNX-3267). Failures are typed so a gone transcript (`transcript-unavailable`,
|
|
19
|
+
* expected history, aged out after a TTL and never re-read) is distinguished from a
|
|
20
|
+
* real parse/upload failure (retried until it resolves).
|
|
12
21
|
*/
|
|
13
22
|
import fs from 'node:fs';
|
|
14
23
|
import os from 'node:os';
|
|
@@ -39,17 +48,60 @@ export async function syncTraces(opts = {}) {
|
|
|
39
48
|
// NULL machine rows are legacy local sessions (pre-machine-field). A dry-run
|
|
40
49
|
// ignores the incremental watermark so the export covers every session.
|
|
41
50
|
const sinceMtime = dryRun ? 0 : (ledger.lastSyncMtime ?? 0);
|
|
42
|
-
const
|
|
51
|
+
const watermarkRows = db
|
|
43
52
|
.prepare('SELECT * FROM sessions WHERE (machine = ? OR machine IS NULL) AND file_mtime_ms > ? ORDER BY file_mtime_ms ASC')
|
|
44
53
|
.all(device, sinceMtime);
|
|
54
|
+
// The watermark alone loses failures: a later-mtime success advances it past an
|
|
55
|
+
// earlier failed row, which the `file_mtime_ms > sinceMtime` filter then skips
|
|
56
|
+
// forever. So union in the sessions we explicitly recorded as retry-worthy
|
|
57
|
+
// failures (parse/upload — a `transcript-unavailable` file is gone, re-reading it
|
|
58
|
+
// wastes work) regardless of where the watermark sits. A dry-run ignores the
|
|
59
|
+
// ledger entirely and already selects every row.
|
|
60
|
+
const retryIds = dryRun
|
|
61
|
+
? []
|
|
62
|
+
: (ledger.failures ?? [])
|
|
63
|
+
.filter((f) => f.kind !== 'transcript-unavailable')
|
|
64
|
+
.map((f) => f.id);
|
|
65
|
+
let rows = watermarkRows;
|
|
66
|
+
if (retryIds.length) {
|
|
67
|
+
const seen = new Set(watermarkRows.map((r) => r.id));
|
|
68
|
+
const retryRows = [];
|
|
69
|
+
for (let i = 0; i < retryIds.length; i += 400) {
|
|
70
|
+
const chunk = retryIds.slice(i, i + 400);
|
|
71
|
+
retryRows.push(...db
|
|
72
|
+
.prepare(`SELECT * FROM sessions WHERE (machine = ? OR machine IS NULL) AND id IN (${chunk.map(() => '?').join(',')})`)
|
|
73
|
+
.all(device, ...chunk));
|
|
74
|
+
}
|
|
75
|
+
const stranded = retryRows.filter((r) => !seen.has(r.id));
|
|
76
|
+
rows = [...watermarkRows, ...stranded].sort((a, b) => (a.file_mtime_ms ?? 0) - (b.file_mtime_ms ?? 0));
|
|
77
|
+
}
|
|
45
78
|
const limited = opts.limit !== undefined ? rows.slice(0, opts.limit) : rows;
|
|
46
79
|
const knownSecrets = knownSecretValuesFromEnv();
|
|
47
80
|
let uploaded = 0;
|
|
48
81
|
let skipped = 0;
|
|
49
|
-
let
|
|
50
|
-
|
|
51
|
-
|
|
82
|
+
let transcriptUnavailable = 0;
|
|
83
|
+
let parseFailed = 0;
|
|
84
|
+
let uploadFailed = 0;
|
|
85
|
+
// Advance the watermark only to the max mtime of successfully uploaded sessions.
|
|
86
|
+
// On its own this does NOT retry failures (a later success strands earlier ones);
|
|
87
|
+
// the failure ledger below is what actually re-selects them next run.
|
|
52
88
|
let maxSuccessMtime = ledger.lastSyncMtime ?? 0;
|
|
89
|
+
// Carry prior failures forward by id so a stranded row's identity + evidence
|
|
90
|
+
// survives across syncs. A success removes the id; a repeat failure updates it.
|
|
91
|
+
const now = Date.now();
|
|
92
|
+
const failures = new Map((ledger.failures ?? []).map((f) => [f.id, f]));
|
|
93
|
+
const recordFailure = (row, kind, err) => {
|
|
94
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
95
|
+
const prev = failures.get(row.id);
|
|
96
|
+
failures.set(row.id, {
|
|
97
|
+
id: row.id,
|
|
98
|
+
mtimeMs: row.file_mtime_ms ?? 0,
|
|
99
|
+
kind,
|
|
100
|
+
detail: redactSecrets(raw.split('\n')[0] ?? '', knownSecrets).slice(0, 200),
|
|
101
|
+
firstSeen: prev?.firstSeen ?? now,
|
|
102
|
+
attempts: (prev?.attempts ?? 0) + 1,
|
|
103
|
+
});
|
|
104
|
+
};
|
|
53
105
|
if (dryRun && outDir) {
|
|
54
106
|
fs.mkdirSync(path.join(outDir, 'sessions'), { recursive: true });
|
|
55
107
|
}
|
|
@@ -65,8 +117,17 @@ export async function syncTraces(opts = {}) {
|
|
|
65
117
|
const events = parseSession(row.file_path, row.agent);
|
|
66
118
|
traj = buildTrajectory(events, session, { redact: true, knownSecrets });
|
|
67
119
|
}
|
|
68
|
-
catch {
|
|
69
|
-
|
|
120
|
+
catch (err) {
|
|
121
|
+
// A gone/unreadable transcript is expected history, not a retry-worthy error;
|
|
122
|
+
// a file that IS present but failed to parse is a real problem to retry.
|
|
123
|
+
if (!fs.existsSync(row.file_path)) {
|
|
124
|
+
transcriptUnavailable++;
|
|
125
|
+
recordFailure(row, 'transcript-unavailable', err);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
parseFailed++;
|
|
129
|
+
recordFailure(row, 'parse-failed', err);
|
|
130
|
+
}
|
|
70
131
|
continue;
|
|
71
132
|
}
|
|
72
133
|
try {
|
|
@@ -78,9 +139,11 @@ export async function syncTraces(opts = {}) {
|
|
|
78
139
|
}
|
|
79
140
|
uploaded++;
|
|
80
141
|
maxSuccessMtime = Math.max(maxSuccessMtime, row.file_mtime_ms ?? 0);
|
|
142
|
+
failures.delete(row.id); // recovered — drop it from the retry set
|
|
81
143
|
}
|
|
82
|
-
catch {
|
|
83
|
-
|
|
144
|
+
catch (err) {
|
|
145
|
+
uploadFailed++;
|
|
146
|
+
recordFailure(row, 'upload-failed', err);
|
|
84
147
|
}
|
|
85
148
|
}
|
|
86
149
|
if (!opts.skipIndex) {
|
|
@@ -114,9 +177,32 @@ export async function syncTraces(opts = {}) {
|
|
|
114
177
|
}
|
|
115
178
|
// A dry-run never advances the incremental watermark: it is a read-only export.
|
|
116
179
|
if (!dryRun) {
|
|
117
|
-
|
|
180
|
+
// Age out long-unavailable transcripts so a corpus of deleted files cannot grow
|
|
181
|
+
// the retry set without bound; parse/upload failures persist until they resolve.
|
|
182
|
+
const persistedFailures = [...failures.values()].filter((f) => !(f.kind === 'transcript-unavailable' &&
|
|
183
|
+
now - f.firstSeen > TRANSCRIPT_UNAVAILABLE_TTL_MS));
|
|
184
|
+
writeSyncLedger({ lastSyncMtime: maxSuccessMtime, failures: persistedFailures });
|
|
185
|
+
// Register the Phoenix session with Prix so the console can fetch live data
|
|
186
|
+
// (PHNX-3257). Fire-and-forget — a link failure must not block sync.
|
|
187
|
+
// Managed backend only: the BYO path's token is a static write token that
|
|
188
|
+
// bypasses Phoenix auth (resolveTracesBackend's userId: 'byo' sentinel) —
|
|
189
|
+
// it is not a Phoenix identity credential and must never be sent to Prix.
|
|
190
|
+
if (backend && backend.userId !== 'byo') {
|
|
191
|
+
fetch('https://api.prix.dev/api/v1/traces/link', {
|
|
192
|
+
method: 'POST',
|
|
193
|
+
headers: { Authorization: `Bearer ${backend.token}` },
|
|
194
|
+
signal: AbortSignal.timeout(8_000),
|
|
195
|
+
}).catch(() => { });
|
|
196
|
+
}
|
|
118
197
|
}
|
|
119
|
-
return {
|
|
198
|
+
return {
|
|
199
|
+
uploaded,
|
|
200
|
+
skipped,
|
|
201
|
+
errors: transcriptUnavailable + parseFailed + uploadFailed,
|
|
202
|
+
transcriptUnavailable,
|
|
203
|
+
parseFailed,
|
|
204
|
+
uploadFailed,
|
|
205
|
+
};
|
|
120
206
|
}
|
|
121
207
|
function percentile(values, ratio) {
|
|
122
208
|
if (values.length === 0)
|
|
@@ -385,6 +471,13 @@ async function putIndexShard(backend, device, owner, shard) {
|
|
|
385
471
|
throw new Error(`PUT ${url} → ${res.status}`);
|
|
386
472
|
}
|
|
387
473
|
}
|
|
474
|
+
/**
|
|
475
|
+
* A `transcript-unavailable` failure is kept (so its count stays reportable) but
|
|
476
|
+
* not retried, because re-reading a file that is gone wastes work. Age it out of
|
|
477
|
+
* the ledger after this window so a corpus of long-deleted transcripts cannot grow
|
|
478
|
+
* the failure set without bound.
|
|
479
|
+
*/
|
|
480
|
+
const TRANSCRIPT_UNAVAILABLE_TTL_MS = 14 * 24 * 60 * 60 * 1000;
|
|
388
481
|
function ledgerPath() {
|
|
389
482
|
return path.join(getRuntimeStateDir(), 'traces-sync.json');
|
|
390
483
|
}
|
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.51",
|
|
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",
|
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
},
|
|
37
37
|
"repository": {
|
|
38
38
|
"type": "git",
|
|
39
|
-
"url": "git+https://github.com/phnx-labs/
|
|
39
|
+
"url": "git+https://github.com/phnx-labs/agi-cli.git"
|
|
40
40
|
},
|
|
41
41
|
"homepage": "https://agents-cli.sh",
|
|
42
42
|
"bugs": {
|
|
43
|
-
"url": "https://github.com/phnx-labs/
|
|
43
|
+
"url": "https://github.com/phnx-labs/agi-cli/issues"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "tsc && rm -rf 'dist/lib/secrets/AgentsKeychain.app' 'dist/lib/secrets/Agents CLI.app' 'dist/lib/menubar/MenubarHelper.app' && ([ -f 'bin/agents-macos' ] && mkdir -p dist/bin && cp -f 'bin/agents-macos' 'dist/bin/agents' && chmod 755 'dist/bin/agents' || true)",
|